pytestomatio 2.8.2.dev44__tar.gz → 2.9.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. {pytestomatio-2.8.2.dev44/pytestomatio.egg-info → pytestomatio-2.9.0}/PKG-INFO +2 -4
  2. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/README.md +1 -3
  3. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/pyproject.toml +1 -1
  4. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/pytestomatio/connect/connector.py +29 -80
  5. pytestomatio-2.9.0/pytestomatio/connect/s3_connector.py +69 -0
  6. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/pytestomatio/main.py +2 -2
  7. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/pytestomatio/testing/testItem.py +0 -1
  8. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/pytestomatio/testomatio/testRunConfig.py +15 -25
  9. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/pytestomatio/testomatio/testomatio.py +5 -15
  10. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/pytestomatio/utils/helper.py +6 -12
  11. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0/pytestomatio.egg-info}/PKG-INFO +2 -4
  12. pytestomatio-2.8.2.dev44/pytestomatio/connect/s3_connector.py +0 -121
  13. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/LICENSE +0 -0
  14. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/pytestomatio/__init__.py +0 -0
  15. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/pytestomatio/connect/__init__.py +0 -0
  16. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/pytestomatio/decor/__init__.py +0 -0
  17. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/pytestomatio/decor/decorator_updater.py +0 -0
  18. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/pytestomatio/decor/default.py +0 -0
  19. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/pytestomatio/decor/pep8.py +0 -0
  20. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/pytestomatio/testing/__init__.py +0 -0
  21. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/pytestomatio/testing/code_collector.py +0 -0
  22. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/pytestomatio/testomatio/__init__.py +0 -0
  23. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/pytestomatio/testomatio/filter_plugin.py +0 -0
  24. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/pytestomatio/testomatio/testomat_item.py +0 -0
  25. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/pytestomatio/utils/__init__.py +0 -0
  26. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/pytestomatio/utils/parser_setup.py +0 -0
  27. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/pytestomatio/utils/validations.py +0 -0
  28. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/pytestomatio.egg-info/SOURCES.txt +0 -0
  29. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/pytestomatio.egg-info/dependency_links.txt +0 -0
  30. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/pytestomatio.egg-info/entry_points.txt +0 -0
  31. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/pytestomatio.egg-info/requires.txt +0 -0
  32. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/pytestomatio.egg-info/top_level.txt +0 -0
  33. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/setup.cfg +0 -0
  34. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/tests/sub/__init__.py +0 -0
  35. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/tests/sub/sub_mob/__init__.py +0 -0
  36. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/tests/sub/sub_mob/sub_sub_class_test.py +0 -0
  37. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/tests/sub/sub_mob/sub_sub_test.py +0 -0
  38. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/tests/sub/test_class_sub.py +0 -0
  39. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/tests/sub/test_sub.py +0 -0
  40. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/tests/test_class_root.py +0 -0
  41. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/tests/test_cli_param_test_id.py +0 -0
  42. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/tests/test_cli_params.py +0 -0
  43. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/tests/test_decorators.py +0 -0
  44. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/tests/test_parameters.py +0 -0
  45. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/tests/test_root.py +0 -0
  46. {pytestomatio-2.8.2.dev44 → pytestomatio-2.9.0}/tests/test_sync.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytestomatio
3
- Version: 2.8.2.dev44
3
+ Version: 2.9.0
4
4
  Summary: Pytest plugin to sync test with testomat.io
5
5
  Author: Oleksii Ostapov, TikoQA
6
6
  Project-URL: Testomat.io, https://testomat.io/
@@ -120,7 +120,7 @@ https://docs.testomat.io/usage/test-artifacts/
120
120
  Analyser needs to be aware of the cloud storage credentials.
121
121
  There are two options:
122
122
  1. Enable **Share credentials with testomat.io Reporter** option in testomat.io Settings -> Artifacts.
123
- 2. Use environment variables `ACCESS_KEY_ID, SECRET_ACCESS_KEY, ENDPOINT, BUCKET, BUCKET_PATH`
123
+ 2. Use environment variables `ACCESS_KEY_ID, SECRET_ACCESS_KEY, ENDPOINT, BUCKET`
124
124
 
125
125
  You would need to decide when you want to upload your test artifacts to cloud storage
126
126
 
@@ -225,8 +225,6 @@ def test_example():
225
225
  - test run labels, tags
226
226
 
227
227
  ## TODO
228
- - retry test run update with less attributes, we get 500 from api
229
- - handler non configured s3 bucket error
230
228
  - Fix test duration
231
229
 
232
230
  ## Contribution
@@ -92,7 +92,7 @@ https://docs.testomat.io/usage/test-artifacts/
92
92
  Analyser needs to be aware of the cloud storage credentials.
93
93
  There are two options:
94
94
  1. Enable **Share credentials with testomat.io Reporter** option in testomat.io Settings -> Artifacts.
95
- 2. Use environment variables `ACCESS_KEY_ID, SECRET_ACCESS_KEY, ENDPOINT, BUCKET, BUCKET_PATH`
95
+ 2. Use environment variables `ACCESS_KEY_ID, SECRET_ACCESS_KEY, ENDPOINT, BUCKET`
96
96
 
97
97
  You would need to decide when you want to upload your test artifacts to cloud storage
98
98
 
@@ -197,8 +197,6 @@ def test_example():
197
197
  - test run labels, tags
198
198
 
199
199
  ## TODO
200
- - retry test run update with less attributes, we get 500 from api
201
- - handler non configured s3 bucket error
202
200
  - Fix test duration
203
201
 
204
202
  ## Contribution
@@ -13,7 +13,7 @@ version_provider = "pep621"
13
13
  update_changelog_on_bump = false
14
14
  [project]
15
15
  name = "pytestomatio"
16
- version = "2.8.2.dev44"
16
+ version = "2.9.0"
17
17
 
18
18
  dependencies = [
19
19
  "requests>=2.29.0",
@@ -5,7 +5,6 @@ from os.path import join, normpath
5
5
  from os import getenv
6
6
  from pytestomatio.utils.helper import safe_string_list
7
7
  from pytestomatio.testing.testItem import TestItem
8
- import time
9
8
 
10
9
  log = logging.getLogger('pytestomatio')
11
10
 
@@ -13,59 +12,11 @@ log = logging.getLogger('pytestomatio')
13
12
  class Connector:
14
13
  def __init__(self, base_url: str = '', api_key: str = None):
15
14
  self.base_url = base_url
16
- self._session = requests.Session()
15
+ self.session = requests.Session()
16
+ self.session.verify = True
17
17
  self.jwt: str = ''
18
18
  self.api_key = api_key
19
19
 
20
- @property
21
- def session(self):
22
- """Get the session, creating it and applying proxy settings if necessary."""
23
- self._apply_proxy_settings()
24
- return self._session
25
-
26
- @session.setter
27
- def session(self, value):
28
- """Allow setting a custom session, while still applying proxy settings."""
29
- self._session = value
30
- self._apply_proxy_settings()
31
-
32
- def _apply_proxy_settings(self):
33
- """Apply proxy settings based on environment variables, fallback to no proxy if unavailable."""
34
- http_proxy = getenv("HTTP_PROXY")
35
- log.debug(f"HTTP_PROXY: {http_proxy}")
36
- if http_proxy:
37
- self._session.proxies = {"http": http_proxy, "https": http_proxy}
38
- self._session.verify = False
39
- log.debug(f"Proxy settings applied: {self._session.proxies}")
40
-
41
- if not self._test_proxy_connection(timeout=1):
42
- log.debug("Proxy is unavailable. Falling back to a direct connection.")
43
- self._session.proxies.clear()
44
- self._session.verify = True
45
- else:
46
- log.debug("No proxy settings found. Using a direct connection.")
47
- self._session.proxies.clear()
48
- self._session.verify = True
49
- self._test_proxy_connection()
50
-
51
- def _test_proxy_connection(self, test_url="https://api.ipify.org?format=json", timeout=30, retry_interval=1):
52
- log.debug("Current session: %s", self._session.proxies)
53
- log.debug("Current verify: %s", self._session.verify)
54
-
55
- start_time = time.time()
56
- while time.time() - start_time < timeout:
57
- try:
58
- response = self._session.get(test_url, timeout=5)
59
- response.raise_for_status()
60
- log.debug("Internet connection is available.")
61
- return True
62
- except requests.exceptions.RequestException as e:
63
- log.error("Internet connection is unavailable. Error: %s", e)
64
- time.sleep(retry_interval)
65
-
66
- log.error("Internet connection check timed out after %d seconds.", timeout)
67
- return False
68
-
69
20
  def load_tests(
70
21
  self,
71
22
  tests: list[TestItem],
@@ -99,14 +50,14 @@ class Connector:
99
50
 
100
51
  try:
101
52
  response = self.session.post(f'{self.base_url}/api/load?api_key={self.api_key}', json=request)
102
- except ConnectionError as ce:
103
- log.error(f'Failed to connect to {self.base_url}: {ce}')
53
+ except ConnectionError:
54
+ log.error(f'Failed to connect to {self.base_url}')
104
55
  return
105
- except HTTPError as he:
106
- log.error(f'HTTP error occurred while connecting to {self.base_url}: {he}')
56
+ except HTTPError:
57
+ log.error(f'Failed to connect to {self.base_url}')
107
58
  return
108
59
  except Exception as e:
109
- log.error(f'An unexpected exception occurred. Please report an issue: {e}')
60
+ log.error(f'Generic exception happened. Please report an issue. {e}')
110
61
  return
111
62
 
112
63
  if response.status_code < 400:
@@ -128,19 +79,18 @@ class Connector:
128
79
  "label": label,
129
80
  "parallel": parallel,
130
81
  "ci_build_url": ci_build_url,
131
- "shared_run": shared_run
132
82
  }
133
83
  filtered_request = {k: v for k, v in request.items() if v is not None}
134
84
  try:
135
85
  response = self.session.post(f'{self.base_url}/api/reporter', json=filtered_request)
136
- except ConnectionError as ce:
137
- log.error(f'Failed to connect to {self.base_url}: {ce}')
86
+ except ConnectionError:
87
+ log.error(f'Failed to connect to {self.base_url}')
138
88
  return
139
- except HTTPError as he:
140
- log.error(f'HTTP error occurred while connecting to {self.base_url}: {he}')
89
+ except HTTPError:
90
+ log.error(f'Failed to connect to {self.base_url}')
141
91
  return
142
92
  except Exception as e:
143
- log.error(f'An unexpected exception occurred. Please report an issue: {e}')
93
+ log.error(f'Generic exception happened. Please report an issue. {e}')
144
94
  return
145
95
 
146
96
  if response.status_code == 200:
@@ -153,24 +103,23 @@ class Connector:
153
103
  "api_key": self.api_key,
154
104
  "title": title,
155
105
  "group_title": group_title,
156
- "env": env,
157
- "label": label,
106
+ # "env": env, TODO: enabled when bug with 500 response fixed
107
+ # "label": label, TODO: enabled when bug with 500 response fixed
158
108
  "parallel": parallel,
159
109
  "ci_build_url": ci_build_url,
160
- "shared_run": shared_run
161
110
  }
162
111
  filtered_request = {k: v for k, v in request.items() if v is not None}
163
112
 
164
113
  try:
165
114
  response = self.session.put(f'{self.base_url}/api/reporter/{id}', json=filtered_request)
166
- except ConnectionError as ce:
167
- log.error(f'Failed to connect to {self.base_url}: {ce}')
115
+ except ConnectionError:
116
+ log.error(f'Failed to connect to {self.base_url}')
168
117
  return
169
- except HTTPError as he:
170
- log.error(f'HTTP error occurred while connecting to {self.base_url}: {he}')
118
+ except HTTPError:
119
+ log.error(f'Failed to connect to {self.base_url}')
171
120
  return
172
121
  except Exception as e:
173
- log.error(f'An unexpected exception occurred. Please report an issue: {e}')
122
+ log.error(f'Generic exception happened. Please report an issue. {e}')
174
123
  return
175
124
 
176
125
  if response.status_code == 200:
@@ -209,14 +158,14 @@ class Connector:
209
158
  try:
210
159
  response = self.session.post(f'{self.base_url}/api/reporter/{run_id}/testrun?api_key={self.api_key}',
211
160
  json=filtered_request)
212
- except ConnectionError as ce:
213
- log.error(f'Failed to connect to {self.base_url}: {ce}')
161
+ except ConnectionError:
162
+ log.error(f'Failed to connect to {self.base_url}')
214
163
  return
215
- except HTTPError as he:
216
- log.error(f'HTTP error occurred while connecting to {self.base_url}: {he}')
164
+ except HTTPError:
165
+ log.error(f'Failed to connect to {self.base_url}')
217
166
  return
218
167
  except Exception as e:
219
- log.error(f'An unexpected exception occurred. Please report an issue: {e}')
168
+ log.error(f'Generic exception happened. Please report an issue. {e}')
220
169
  return
221
170
  if response.status_code == 200:
222
171
  log.info('Test status updated')
@@ -227,14 +176,14 @@ class Connector:
227
176
  try:
228
177
  self.session.put(f'{self.base_url}/api/reporter/{run_id}?api_key={self.api_key}',
229
178
  json={"status_event": status_event})
230
- except ConnectionError as ce:
231
- log.error(f'Failed to connect to {self.base_url}: {ce}')
179
+ except ConnectionError:
180
+ log.error(f'Failed to connect to {self.base_url}')
232
181
  return
233
- except HTTPError as he:
234
- log.error(f'HTTP error occurred while connecting to {self.base_url}: {he}')
182
+ except HTTPError:
183
+ log.error(f'Failed to connect to {self.base_url}')
235
184
  return
236
185
  except Exception as e:
237
- log.error(f'An unexpected exception occurred. Please report an issue: {e}')
186
+ log.error(f'Generic exception happened. Please report an issue. {e}')
238
187
  return
239
188
 
240
189
  def disconnect(self):
@@ -0,0 +1,69 @@
1
+ import boto3
2
+ import logging
3
+ from io import BytesIO
4
+
5
+ log = logging.getLogger(__name__)
6
+ log.setLevel('INFO')
7
+
8
+
9
+ def parse_endpoint(endpoint: str or None) -> str or None:
10
+ if endpoint is None:
11
+ return
12
+ if endpoint.startswith('https://'):
13
+ return endpoint[8:]
14
+ elif endpoint.startswith('http://'):
15
+ return endpoint[7:]
16
+ return endpoint
17
+
18
+
19
+ class S3Connector:
20
+ def __init__(self, aws_access_key_id: str or None = None,
21
+ aws_secret_access_key: str or None = None,
22
+ endpoint: str or None = None,
23
+ bucket_name: str or None = None):
24
+
25
+ self.endpoint = parse_endpoint(endpoint)
26
+ self.bucket_name = bucket_name
27
+ self.client = None
28
+ self._is_logged_in = False
29
+ self.aws_access_key_id = aws_access_key_id
30
+ self.aws_secret_access_key = aws_secret_access_key
31
+
32
+ def login(self):
33
+ log.debug('creating s3 session')
34
+ self.client = boto3.client(
35
+ 's3',
36
+ endpoint_url=f'https://{self.endpoint}',
37
+ aws_access_key_id=self.aws_access_key_id,
38
+ aws_secret_access_key=self.aws_secret_access_key)
39
+ self._is_logged_in = True
40
+ log.info('s3 session created')
41
+
42
+ def upload_file(self, file_path: str, key: str = None, bucket_name: str = None) -> str or None:
43
+ if not self._is_logged_in:
44
+ log.warning('s3 session is not created, creating new one')
45
+ return
46
+ if not key:
47
+ key = file_path
48
+ if not bucket_name:
49
+ bucket_name = self.bucket_name
50
+ if bucket_name is None:
51
+ raise Exception('bucket name is not defined')
52
+ log.info(f'uploading artifact {file_path} to s3://{bucket_name}/{key}')
53
+ self.client.upload_file(file_path, bucket_name, key)
54
+ log.info(f'artifact {file_path} uploaded to s3://{bucket_name}/{key}')
55
+ return f'https://{bucket_name}.{self.endpoint}/{key}'
56
+
57
+ def upload_file_object(self, file_bytes: bytes, key: str, bucket_name: str = None) -> str or None:
58
+ if not self._is_logged_in:
59
+ log.warning('s3 session is not created, creating new one')
60
+ return
61
+ file = BytesIO(file_bytes)
62
+ if not bucket_name:
63
+ bucket_name = self.bucket_name
64
+ if bucket_name is None:
65
+ raise Exception('bucket name is not defined')
66
+ log.info(f'uploading artifact {key} to s3://{bucket_name}/{key}')
67
+ self.client.upload_fileobj(file, bucket_name, key)
68
+ log.info(f'artifact {key} uploaded to s3://{bucket_name}/{key}')
69
+ return f'https://{bucket_name}.{self.endpoint}/{key}'
@@ -9,6 +9,7 @@ from pytestomatio.decor.decorator_updater import update_tests
9
9
  from pytestomatio.utils.helper import add_and_enrich_tests, get_test_mapping, collect_tests, read_env_s3_keys
10
10
  from pytestomatio.utils.parser_setup import parser_options
11
11
  from pytestomatio.utils import validations
12
+ from xdist.plugin import is_xdist_controller, get_xdist_worker_id
12
13
 
13
14
  from pytestomatio.testomatio.testRunConfig import TestRunConfig
14
15
  from pytestomatio.testomatio.testomatio import Testomatio
@@ -119,8 +120,7 @@ def pytest_collection_modifyitems(session: Session, config: Config, items: list[
119
120
  run_details = pytest.testomatio.connector.update_test_run(**run.to_dict())
120
121
 
121
122
  if run_details is None:
122
- log.error('Test run failed to create. Reporting skipped')
123
- return
123
+ raise Exception('Test run failed to create. Reporting skipped')
124
124
 
125
125
  s3_details = read_env_s3_keys(run_details)
126
126
 
@@ -119,7 +119,6 @@ class TestItem:
119
119
  # We only want fixture names, not the values.
120
120
  param_names.update(callspec.params.keys())
121
121
 
122
- print('_get_test_parameter_key ->', param_names)
123
122
  # Return them as a list, or keep it as a set—whatever you prefer.
124
123
  return list(param_names)
125
124
 
@@ -1,25 +1,22 @@
1
1
  import os
2
2
  import datetime as dt
3
- import tempfile
4
3
  from pytestomatio.utils.helper import safe_string_list
5
4
  from typing import Optional
6
5
 
7
- TESTOMATIO_TEST_RUN_LOCK_FILE = ".testomatio_test_run_id_lock"
8
6
 
9
7
  class TestRunConfig:
10
- def __init__(self):
11
- run_id = os.environ.get('TESTOMATIO_RUN_ID') or os.environ.get('TESTOMATIO_RUN')
12
- title = os.environ.get('TESTOMATIO_TITLE') if os.environ.get('TESTOMATIO_TITLE') else 'test run at ' + dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
13
- shared_run = os.environ.get('TESTOMATIO_SHARED_RUN') in ['True', 'true', '1']
14
- self.test_run_id = run_id
15
- self.title = title
8
+ def __init__(self, parallel: bool = True):
9
+ self.test_run_id = os.environ.get('TESTOMATIO_RUN_ID') or None
10
+ run = os.environ.get('TESTOMATIO_RUN') or None
11
+ title = os.environ.get('TESTOMATIO_TITLE') or None
12
+ run_or_title = run if run else title
13
+ self.title = run_or_title if run_or_title else 'test run at ' + dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
16
14
  self.environment = safe_string_list(os.environ.get('TESTOMATIO_ENV'))
17
15
  self.label = safe_string_list(os.environ.get('TESTOMATIO_LABEL'))
18
- self.group_title = os.environ.get('TESTOMATIO_RUNGROUP_TITLE')
19
- # This allows to report tests to the test run by it's id. https://docs.testomat.io/getting-started/running-automated-tests/#reporting-parallel-tests
20
- self.parallel = False if shared_run else True
21
- # This allows using test run title to group tests under a single test run. This is needed when running tests in different processes or servers.
22
- self.shared_run = shared_run
16
+ self.group_title = os.environ.get('TESTOMATIO_RUNGROUP_TITLE') or None
17
+ self.parallel = parallel
18
+ # stands for run with shards
19
+ self.shared_run = run_or_title is not None
23
20
  self.status_request = {}
24
21
  self.build_url = self.resolve_build_url()
25
22
 
@@ -41,28 +38,21 @@ class TestRunConfig:
41
38
 
42
39
  def save_run_id(self, run_id: str) -> None:
43
40
  self.test_run_id = run_id
44
- temp_dir = tempfile.gettempdir()
45
- temp_file_path = os.path.join(temp_dir, TESTOMATIO_TEST_RUN_LOCK_FILE)
46
- with open(temp_file_path, 'w') as f:
41
+ with open('.temp_test_run_id', 'w') as f:
47
42
  f.write(run_id)
48
43
 
49
-
50
44
  def get_run_id(self) -> Optional[str]:
51
45
  if self.test_run_id:
52
46
  return self.test_run_id
53
- temp_dir = tempfile.gettempdir()
54
- temp_file_path = os.path.join(temp_dir, TESTOMATIO_TEST_RUN_LOCK_FILE)
55
- if os.path.exists(temp_file_path):
56
- with open(temp_file_path, 'r') as f:
47
+ if os.path.exists('.temp_test_run_id'):
48
+ with open('.temp_test_run_id', 'r') as f:
57
49
  self.test_run_id = f.read()
58
50
  return self.test_run_id
59
51
  return None
60
52
 
61
53
  def clear_run_id(self) -> None:
62
- temp_dir = tempfile.gettempdir()
63
- temp_file_path = os.path.join(temp_dir, TESTOMATIO_TEST_RUN_LOCK_FILE)
64
- if os.path.exists(temp_file_path):
65
- os.remove(temp_file_path)
54
+ if os.path.exists('.temp_test_run_id'):
55
+ os.remove('.temp_test_run_id')
66
56
 
67
57
  def resolve_build_url(self) -> Optional[str]:
68
58
  # You might not always want the build URL to change in the Testomat.io test run
@@ -2,9 +2,6 @@ from _pytest.python import Function
2
2
  from .testRunConfig import TestRunConfig
3
3
  from pytestomatio.connect.s3_connector import S3Connector
4
4
  from pytestomatio.connect.connector import Connector
5
- import logging
6
-
7
- log = logging.getLogger(__name__)
8
5
 
9
6
 
10
7
  class Testomatio:
@@ -14,26 +11,19 @@ class Testomatio:
14
11
  self.test_run_config: TestRunConfig = test_run_config
15
12
  self.connector: Connector = None
16
13
 
17
- def upload_files(self, files_list, bucket_name: str = None) -> str:
18
- if self.test_run_config.test_run_id is None:
19
- log.debug("Skipping file upload when testomatio test run is not created")
20
- return ""
21
- return self.s3_connector.upload_files(files_list, bucket_name)
22
-
23
14
  def upload_file(self, file_path: str, key: str = None, bucket_name: str = None) -> str:
24
15
  if self.test_run_config.test_run_id is None:
25
- log.debug("Skipping file upload when testomatio test run is not created")
16
+ print("Skipping file upload when testomatio test run is not created")
26
17
  return ""
27
18
  return self.s3_connector.upload_file(file_path, key, bucket_name)
28
19
 
29
20
  def upload_file_object(self, file_bytes: bytes, key: str, bucket_name: str = None) -> str:
30
21
  if self.test_run_config.test_run_id is None:
31
- log.debug("Skipping file upload when testomatio test run is not created")
22
+ print("Skipping file upload when testomatio test run is not created")
32
23
  return ""
33
24
  return self.s3_connector.upload_file_object(file_bytes, key, bucket_name)
34
25
 
35
- def add_artifacts(self, node: Function, url_list) -> None:
26
+ def add_artifacts(self, node: Function, urls: list[str]) -> None:
36
27
  artifact_urls = node.stash.get("artifact_urls", [])
37
- artifact_urls.extend(url_list)
38
- node.stash["artifact_urls"] = [ url for url in artifact_urls if url is not None]
39
-
28
+ artifact_urls.extend(urls)
29
+ node.stash["artifact_urls"] = artifact_urls
@@ -1,4 +1,4 @@
1
- from os import getenv
1
+ import os
2
2
  from os.path import basename
3
3
  from pytest import Item
4
4
  from pytestomatio.testomatio.testomat_item import TestomatItem
@@ -84,18 +84,12 @@ def add_and_enrich_tests(meta: list[TestItem], test_files: set,
84
84
  update_tests(test_file, mapping, test_names, decorator_name)
85
85
 
86
86
 
87
- def read_env_s3_keys(testRunConfig: dict) -> tuple:
88
- artifacts = testRunConfig.get('artifacts', {})
89
- bucket_path = (getenv('BUCKET_PATH') or getenv('S3_BUCKET_PATH'))
90
- acl = 'private' if (getenv('TESTOMATIO_PRIVATE_ARTIFACTS') or artifacts.get('presign')) else "public-read"
87
+ def read_env_s3_keys(artifact: dict) -> tuple:
91
88
  return (
92
- getenv('REGION') or getenv('S3_REGION') or artifacts.get('REGION'),
93
- getenv('ACCESS_KEY_ID') or getenv('S3_ACCESS_KEY_ID') or artifacts.get('ACCESS_KEY_ID'),
94
- getenv('SECRET_ACCESS_KEY') or getenv('S3_SECRET_ACCESS_KEY') or artifacts.get('SECRET_ACCESS_KEY'),
95
- getenv('ENDPOINT') or getenv('S3_ENDPOINT') or artifacts.get('ENDPOINT'),
96
- getenv('BUCKET') or getenv('S3_BUCKET') or artifacts.get('BUCKET'),
97
- bucket_path + "/" + testRunConfig.get("uid") if bucket_path else testRunConfig.get("uid"),
98
- acl
89
+ os.environ.get('ACCESS_KEY_ID') or artifact.get('ACCESS_KEY_ID'),
90
+ os.environ.get('SECRET_ACCESS_KEY') or artifact.get('SECRET_ACCESS_KEY'),
91
+ os.environ.get('ENDPOINT') or artifact.get('ENDPOINT'),
92
+ os.environ.get('BUCKET') or artifact.get('BUCKET')
99
93
  )
100
94
 
101
95
  def safe_string_list(param: str):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytestomatio
3
- Version: 2.8.2.dev44
3
+ Version: 2.9.0
4
4
  Summary: Pytest plugin to sync test with testomat.io
5
5
  Author: Oleksii Ostapov, TikoQA
6
6
  Project-URL: Testomat.io, https://testomat.io/
@@ -120,7 +120,7 @@ https://docs.testomat.io/usage/test-artifacts/
120
120
  Analyser needs to be aware of the cloud storage credentials.
121
121
  There are two options:
122
122
  1. Enable **Share credentials with testomat.io Reporter** option in testomat.io Settings -> Artifacts.
123
- 2. Use environment variables `ACCESS_KEY_ID, SECRET_ACCESS_KEY, ENDPOINT, BUCKET, BUCKET_PATH`
123
+ 2. Use environment variables `ACCESS_KEY_ID, SECRET_ACCESS_KEY, ENDPOINT, BUCKET`
124
124
 
125
125
  You would need to decide when you want to upload your test artifacts to cloud storage
126
126
 
@@ -225,8 +225,6 @@ def test_example():
225
225
  - test run labels, tags
226
226
 
227
227
  ## TODO
228
- - retry test run update with less attributes, we get 500 from api
229
- - handler non configured s3 bucket error
230
228
  - Fix test duration
231
229
 
232
230
  ## Contribution
@@ -1,121 +0,0 @@
1
- from typing import Optional
2
- import boto3
3
- import logging
4
- from io import BytesIO
5
- import mimetypes
6
-
7
- log = logging.getLogger(__name__)
8
- log.setLevel('INFO')
9
-
10
-
11
- def parse_endpoint(endpoint: str = None) -> Optional[str]:
12
- if endpoint.startswith('https://'):
13
- return endpoint[8:]
14
- elif endpoint.startswith('http://'):
15
- return endpoint[7:]
16
- return endpoint
17
-
18
- # TODO: review error handling. It should be save, and only create log entries without effecting test execution.
19
- class S3Connector:
20
- def __init__(self,
21
- aws_region_name: Optional[str],
22
- aws_access_key_id: Optional[str],
23
- aws_secret_access_key: Optional[str],
24
- endpoint: Optional[str],
25
- bucket_name: Optional[str],
26
- bucker_prefix: Optional[str],
27
- acl: Optional[str] = 'public-read'
28
- ):
29
-
30
- self.aws_region_name = aws_region_name
31
- self.endpoint = parse_endpoint(endpoint)
32
- self.bucket_name = bucket_name
33
- self.bucker_prefix = bucker_prefix
34
- self.client = None
35
- self._is_logged_in = False
36
- self.aws_access_key_id = aws_access_key_id
37
- self.aws_secret_access_key = aws_secret_access_key
38
- self.acl = acl
39
-
40
- def login(self):
41
- log.debug('creating s3 session')
42
- self.client = boto3.client(
43
- 's3',
44
- endpoint_url=f'https://{self.endpoint}',
45
- aws_access_key_id=self.aws_access_key_id,
46
- aws_secret_access_key=self.aws_secret_access_key,
47
- region_name=self.aws_region_name
48
- )
49
-
50
- self._is_logged_in = True
51
- log.info('s3 session created')
52
-
53
- # TODO: upload files async
54
- def upload_files(self, file_list, bucket_name: str = None):
55
- links = []
56
- for file_path, key in file_list:
57
- link = self.upload_file(file_path=file_path, key=key, bucket_name=bucket_name)
58
- links.append(link)
59
- return [link for link in links if link is not None]
60
-
61
-
62
- def upload_file(self, file_path: str, key: str = None, bucket_name: str = None) -> Optional[str]:
63
- if not self._is_logged_in:
64
- log.warning('s3 session is not created, creating new one')
65
- return
66
- if not key:
67
- key = file_path
68
- key = f"{self.bucker_prefix}/{key}"
69
- if not bucket_name:
70
- bucket_name = self.bucket_name
71
-
72
- content_type, _ = mimetypes.guess_type(key)
73
- if content_type is None:
74
- content_type = 'application/octet-stream'
75
-
76
- try:
77
- log.info(f'uploading artifact {file_path} to s3://{bucket_name}/{key}')
78
- self.client.upload_file(
79
- file_path,
80
- bucket_name,
81
- key,
82
- ExtraArgs={
83
- 'ACL': self.acl,
84
- 'ContentType': content_type,
85
- 'ContentDisposition': 'inline'
86
- }
87
- )
88
- log.info(f'artifact {file_path} uploaded to s3://{bucket_name}/{key}')
89
- return f'https://{bucket_name}.{self.endpoint}/{key}'
90
- except Exception as e:
91
- log.error(f'failed to upload file {file_path} to s3://{bucket_name}/{key}: {e}')
92
-
93
- def upload_file_object(self, file_bytes: bytes, key: str, bucket_name: str = None) -> Optional[str]:
94
- if not self._is_logged_in:
95
- log.warning('s3 session is not created, creating new one')
96
- return
97
- file = BytesIO(file_bytes)
98
- if not bucket_name:
99
- bucket_name = self.bucket_name
100
- key = f"{self.bucker_prefix}/{key}"
101
-
102
- content_type, _ = mimetypes.guess_type(key)
103
- if content_type is None:
104
- content_type = 'application/octet-stream'
105
-
106
- try:
107
- log.info(f'uploading artifact {key} to s3://{bucket_name}/{key}')
108
- self.client.upload_fileobj(
109
- file,
110
- bucket_name,
111
- key,
112
- ExtraArgs={
113
- 'ACL': self.acl,
114
- 'ContentType': content_type,
115
- 'ContentDisposition': 'inline'
116
- }
117
- )
118
- log.info(f'artifact {key} uploaded to s3://{bucket_name}/{key}')
119
- return f'https://{bucket_name}.{self.endpoint}/{key}'
120
- except Exception as e:
121
- log.error(f'failed to upload file {key} to s3://{bucket_name}/{key}: {e}')
File without changes