15
15
16
16
```python
17
17
def test_dummy(upload_assets, tmp_path):
18
- path = tmp_path / "dummy .txt"
19
- path.write_text("dummy content ")
18
+ path = tmp_path / "hello .txt"
19
+ path.write_text("Hello world. ")
20
20
upload_assets(path)
21
21
```
22
22
@@ -33,7 +33,7 @@ def test_dummy(upload_assets, tmp_path):
33
33
import uuid
34
34
import warnings
35
35
from pathlib import Path
36
- from typing import Callable , Dict , Union
36
+ from typing import Callable , Dict
37
37
38
38
import boto3
39
39
import pytest
@@ -80,6 +80,7 @@ def pytest_configure(config: pytest.Config):
80
80
81
81
82
82
def pytest_report_header (config ):
83
+ # TODO Move inside S3UploadPlugin
83
84
plugin : S3UploadPlugin | None = config .pluginmanager .get_plugin (
84
85
_UPLOAD_ASSETS_PLUGIN_NAME
85
86
)
@@ -92,56 +93,54 @@ def pytest_unconfigure(config):
92
93
config .pluginmanager .unregister (name = _UPLOAD_ASSETS_PLUGIN_NAME )
93
94
94
95
95
- class _Collector :
96
- """
97
- Collects test outcomes and files to upload for a single test node.
98
- """
99
-
100
- def __init__ (self , nodeid : str ) -> None :
101
- self .nodeid = nodeid
102
- self .outcomes : Dict [str , str ] = {}
103
- self .assets : Dict [str , Path ] = {}
104
-
105
- def set_outcome (self , when : str , outcome : str ):
106
- self .outcomes [when ] = outcome
107
-
108
- def collect (self , path : Path , name : str ):
109
- self .assets [name ] = path
110
-
111
-
112
96
class S3UploadPlugin :
113
97
def __init__ (self , * , run_id : str | None = None , s3_client , bucket : str ) -> None :
114
98
self .run_id = run_id or uuid .uuid4 ().hex
115
- self .collector : Union [ _Collector , None ] = None
99
+ self .collected_assets : Dict [ str , Path ] | None = None
116
100
self .s3_client = s3_client
117
101
self .bucket = bucket
102
+ self .upload_stats = {"uploaded" : 0 }
118
103
119
- def pytest_runtest_logstart (self , nodeid , location ):
120
- self .collector = _Collector (nodeid = nodeid )
121
-
122
- def pytest_runtest_logreport (self , report : pytest .TestReport ):
123
- self .collector .set_outcome (when = report .when , outcome = report .outcome )
124
-
125
- def pytest_runtest_logfinish (self , nodeid , location ):
126
- # TODO: option to also upload on success?
127
- if self .collector .outcomes .get ("call" ) == "failed" :
128
- self ._upload (self .collector )
104
+ def collect (self , path : Path , name : str ):
105
+ """Collect assets to upload"""
106
+ assert self .collected_assets is not None , "No active collection of assets"
107
+ self .collected_assets [name ] = path
129
108
130
- self .collector = None
109
+ def pytest_runtest_logstart (self , nodeid ):
110
+ # Start new collection of assets for current test node
111
+ self .collected_assets = {}
131
112
132
- def _upload (self , collector : _Collector ):
133
- for name , path in collector .assets .items ():
134
- nodeid = re .sub (r"[^a-zA-Z0-9_.-]" , "_" , collector .nodeid )
135
- key = f"{ self .run_id } !{ nodeid } !{ name } "
136
- # TODO: get upload info in report?
137
- _log .info (f"Uploading { path } to { self .bucket } /{ key } " )
113
+ def pytest_runtest_logreport (self , report : pytest .TestReport ):
114
+ # TODO: option to upload on other outcome as well?
115
+ if report .when == "call" and report .outcome == "failed" :
116
+ # TODO: what to do when upload fails?
117
+ uploaded = self ._upload (nodeid = report .nodeid )
118
+ # TODO: report the uploaded assets somewhere (e.g. in user_properties or JSON report?)
119
+
120
+ def pytest_runtest_logfinish (self , nodeid ):
121
+ # Reset collection of assets
122
+ self .collected_assets = None
123
+
124
+ def _upload (self , nodeid : str ) -> Dict [str , str ]:
125
+ assets = {}
126
+ for name , path in self .collected_assets .items ():
127
+ safe_nodeid = re .sub (r"[^a-zA-Z0-9_.-]" , "_" , nodeid )
128
+ key = f"{ self .run_id } !{ safe_nodeid } !{ name } "
129
+ # TODO: is this manual URL building correct and isn't there a boto utility for that?
130
+ url = f"{ self .s3_client .meta .endpoint_url .rstrip ('/' )} /{ self .bucket } /{ key } "
131
+ _log .info (f"Uploading { path } to { url } " )
138
132
self .s3_client .upload_file (
139
133
Filename = str (path ),
140
134
Bucket = self .bucket ,
141
135
Key = key ,
142
136
# TODO: option to override ACL, or ExtraArgs in general?
143
137
ExtraArgs = {"ACL" : "public-read" },
144
138
)
139
+ assets [name ] = url
140
+ self .upload_stats ["uploaded" ] += 1
141
+
142
+ def pytest_terminal_summary (self , terminalreporter ):
143
+ terminalreporter .write_sep ("-" , f"`upload_assets` stats: { self .upload_stats } " )
145
144
146
145
147
146
@pytest .fixture
@@ -163,7 +162,7 @@ def collect(*paths: Path):
163
162
# (e.g. when test uses an `actual` folder for actual results)
164
163
assert path .is_relative_to (tmp_path )
165
164
name = str (path .relative_to (tmp_path ))
166
- uploader .collector . collect (path = path , name = name )
165
+ uploader .collect (path = path , name = name )
167
166
else :
168
167
warnings .warn ("Fixture `upload_assets` is a no-op (incomplete set up)." )
169
168
0 commit comments