qase-python-commons 2.0.12__tar.gz → 3.0.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.
- {qase_python_commons-2.0.12/src/qase_python_commons.egg-info → qase_python_commons-3.0.0}/PKG-INFO +14 -3
- {qase_python_commons-2.0.12 → qase_python_commons-3.0.0}/pyproject.toml +15 -3
- qase_python_commons-3.0.0/src/qase/commons/__init__.py +14 -0
- qase_python_commons-3.0.0/src/qase/commons/config.py +61 -0
- qase_python_commons-3.0.0/src/qase/commons/exceptions/reporter.py +2 -0
- {qase_python_commons-2.0.12/src/qaseio → qase_python_commons-3.0.0/src/qase}/commons/loader.py +5 -4
- qase_python_commons-3.0.0/src/qase/commons/logger.py +28 -0
- qase_python_commons-3.0.0/src/qase/commons/models/__init__.py +17 -0
- {qase_python_commons-2.0.12/src/qaseio → qase_python_commons-3.0.0/src/qase}/commons/models/attachment.py +13 -10
- {qase_python_commons-2.0.12/src/qaseio → qase_python_commons-3.0.0/src/qase}/commons/models/relation.py +2 -1
- {qase_python_commons-2.0.12/src/qaseio → qase_python_commons-3.0.0/src/qase}/commons/models/result.py +34 -29
- {qase_python_commons-2.0.12/src/qaseio → qase_python_commons-3.0.0/src/qase}/commons/models/run.py +11 -8
- qase_python_commons-3.0.0/src/qase/commons/models/runtime.py +43 -0
- {qase_python_commons-2.0.12/src/qaseio → qase_python_commons-3.0.0/src/qase}/commons/models/step.py +42 -23
- {qase_python_commons-2.0.12/src/qaseio → qase_python_commons-3.0.0/src/qase}/commons/models/suite.py +3 -2
- qase_python_commons-3.0.0/src/qase/commons/profilers/__init__.py +10 -0
- qase_python_commons-3.0.0/src/qase/commons/profilers/db.py +19 -0
- qase_python_commons-2.0.12/src/qaseio/commons/interceptor.py → qase_python_commons-3.0.0/src/qase/commons/profilers/network.py +37 -40
- qase_python_commons-3.0.0/src/qase/commons/profilers/sleep.py +54 -0
- qase_python_commons-3.0.0/src/qase/commons/reporters/__init__.py +9 -0
- qase_python_commons-3.0.0/src/qase/commons/reporters/core.py +187 -0
- {qase_python_commons-2.0.12/src/qaseio/commons → qase_python_commons-3.0.0/src/qase/commons/reporters}/report.py +48 -40
- qase_python_commons-3.0.0/src/qase/commons/reporters/testops.py +369 -0
- {qase_python_commons-2.0.12/src/qaseio → qase_python_commons-3.0.0/src/qase}/commons/utils.py +48 -6
- qase_python_commons-3.0.0/src/qase/commons/validators/base.py +6 -0
- {qase_python_commons-2.0.12 → qase_python_commons-3.0.0/src/qase_python_commons.egg-info}/PKG-INFO +14 -3
- qase_python_commons-3.0.0/src/qase_python_commons.egg-info/SOURCES.txt +30 -0
- {qase_python_commons-2.0.12 → qase_python_commons-3.0.0}/src/qase_python_commons.egg-info/requires.txt +1 -1
- qase_python_commons-3.0.0/src/qase_python_commons.egg-info/top_level.txt +1 -0
- qase_python_commons-2.0.12/AUTHORS.rst +0 -5
- qase_python_commons-2.0.12/src/qase_python_commons.egg-info/SOURCES.txt +0 -23
- qase_python_commons-2.0.12/src/qase_python_commons.egg-info/top_level.txt +0 -1
- qase_python_commons-2.0.12/src/qaseio/commons/__init__.py +0 -15
- qase_python_commons-2.0.12/src/qaseio/commons/config.py +0 -50
- qase_python_commons-2.0.12/src/qaseio/commons/models/__init__.py +0 -17
- qase_python_commons-2.0.12/src/qaseio/commons/models/runtime.py +0 -29
- qase_python_commons-2.0.12/src/qaseio/commons/testops.py +0 -399
- {qase_python_commons-2.0.12 → qase_python_commons-3.0.0}/README.md +0 -0
- {qase_python_commons-2.0.12 → qase_python_commons-3.0.0}/setup.cfg +0 -0
- {qase_python_commons-2.0.12 → qase_python_commons-3.0.0}/src/qase_python_commons.egg-info/dependency_links.txt +0 -0
{qase_python_commons-2.0.12/src/qase_python_commons.egg-info → qase_python_commons-3.0.0}/PKG-INFO
RENAMED
|
@@ -1,17 +1,28 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: qase-python-commons
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.0
|
|
4
4
|
Summary: A library for Qase TestOps and Qase Report
|
|
5
5
|
Author-email: Qase Team <support@qase.io>
|
|
6
6
|
Project-URL: Homepage, https://github.com/qase-tms/qase-python/tree/master/qase-python-commons
|
|
7
7
|
Classifier: Development Status :: 5 - Production/Stable
|
|
8
8
|
Classifier: Programming Language :: Python
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
12
|
+
Classifier: Topic :: Software Development :: Testing
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
9
21
|
Requires-Python: >=3.7
|
|
10
22
|
Description-Content-Type: text/markdown
|
|
11
|
-
License-File: AUTHORS.rst
|
|
12
23
|
Requires-Dist: certifi>=2024.2.2
|
|
13
|
-
Requires-Dist: qaseio<5.0.0,>=4.0.2
|
|
14
24
|
Requires-Dist: attrs>=23.2.0
|
|
25
|
+
Requires-Dist: qaseio
|
|
15
26
|
Requires-Dist: more_itertools
|
|
16
27
|
Provides-Extra: testing
|
|
17
28
|
Requires-Dist: pytest; extra == "testing"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "qase-python-commons"
|
|
7
|
-
version = "
|
|
7
|
+
version = "3.0.0"
|
|
8
8
|
description = "A library for Qase TestOps and Qase Report"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{name = "Qase Team", email = "support@qase.io"}]
|
|
@@ -12,13 +12,25 @@ license = {file = "LICENSE"}
|
|
|
12
12
|
classifiers = [
|
|
13
13
|
"Development Status :: 5 - Production/Stable",
|
|
14
14
|
"Programming Language :: Python",
|
|
15
|
+
'Intended Audience :: Developers',
|
|
16
|
+
'License :: OSI Approved :: Apache Software License',
|
|
17
|
+
'Topic :: Software Development :: Quality Assurance',
|
|
18
|
+
'Topic :: Software Development :: Testing',
|
|
19
|
+
'Programming Language :: Python :: 3',
|
|
20
|
+
'Programming Language :: Python :: 3 :: Only',
|
|
21
|
+
'Programming Language :: Python :: 3.7',
|
|
22
|
+
'Programming Language :: Python :: 3.8',
|
|
23
|
+
'Programming Language :: Python :: 3.9',
|
|
24
|
+
'Programming Language :: Python :: 3.10',
|
|
25
|
+
'Programming Language :: Python :: 3.11',
|
|
26
|
+
'Programming Language :: Python :: 3.12',
|
|
15
27
|
]
|
|
16
28
|
urls = {"Homepage" = "https://github.com/qase-tms/qase-python/tree/master/qase-python-commons"}
|
|
17
29
|
requires-python = ">=3.7"
|
|
18
30
|
dependencies = [
|
|
19
31
|
"certifi>=2024.2.2",
|
|
20
|
-
"qaseio>=4.0.2,<5.0.0",
|
|
21
32
|
"attrs>=23.2.0",
|
|
33
|
+
"qaseio",
|
|
22
34
|
"more_itertools"
|
|
23
35
|
]
|
|
24
36
|
|
|
@@ -66,7 +78,7 @@ exclude = [".tox", "build", "dist", ".eggs", "docs/conf.py"]
|
|
|
66
78
|
branch = true
|
|
67
79
|
|
|
68
80
|
[tool.coverage.paths]
|
|
69
|
-
source = ["src/
|
|
81
|
+
source = ["src/qase/commons"]
|
|
70
82
|
|
|
71
83
|
[tool.coverage.report]
|
|
72
84
|
# Regexes for lines to exclude from consideration
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from .utils import QaseUtils, StringFormatter
|
|
2
|
+
from .config import ConfigManager
|
|
3
|
+
from .loader import TestOpsPlanLoader
|
|
4
|
+
from .logger import Logger
|
|
5
|
+
from .exceptions.reporter import ReporterException
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
QaseUtils,
|
|
9
|
+
StringFormatter,
|
|
10
|
+
ConfigManager,
|
|
11
|
+
TestOpsPlanLoader,
|
|
12
|
+
Logger,
|
|
13
|
+
ReporterException
|
|
14
|
+
]
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
from .logger import Logger
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ConfigManager:
|
|
7
|
+
|
|
8
|
+
def __init__(self, config_file='./qase.config.json', env_vars_prefix='QASE_'):
|
|
9
|
+
self.logger = Logger()
|
|
10
|
+
self.config = {}
|
|
11
|
+
self.parseBool = lambda d: d in ("y", "yes", "true", "1", 1, True)
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
if os.path.exists(config_file):
|
|
15
|
+
with open(config_file, "r") as file:
|
|
16
|
+
self.config = json.load(file)
|
|
17
|
+
except Exception as e:
|
|
18
|
+
self.logger.log("Failed to load config from file", "error")
|
|
19
|
+
|
|
20
|
+
# Load from env vars
|
|
21
|
+
try:
|
|
22
|
+
for key, value in os.environ.items():
|
|
23
|
+
if key.startswith(env_vars_prefix):
|
|
24
|
+
self._set_config(key[len(env_vars_prefix):].lower().replace('_', '.'), value)
|
|
25
|
+
except Exception as e:
|
|
26
|
+
self.logger.log("Failed to load config from env vars {e}", "error")
|
|
27
|
+
|
|
28
|
+
def get(self, key, default=None, value_type=None):
|
|
29
|
+
# Use _get_config method to get the value. If None, return default.
|
|
30
|
+
value = self._get_config(key)
|
|
31
|
+
if value_type and value_type == bool:
|
|
32
|
+
return self.parseBool(value or default)
|
|
33
|
+
return value or default
|
|
34
|
+
|
|
35
|
+
def validate_config(self):
|
|
36
|
+
if self.validator:
|
|
37
|
+
self.validator.validate(self.config)
|
|
38
|
+
|
|
39
|
+
def set(self, key, value):
|
|
40
|
+
self._set_config(key, value)
|
|
41
|
+
|
|
42
|
+
def _get_keys(self, config, prefix=""):
|
|
43
|
+
for key, value in config.items():
|
|
44
|
+
if isinstance(value, dict):
|
|
45
|
+
yield from self._get_keys(value, f"{prefix}{key}.")
|
|
46
|
+
else:
|
|
47
|
+
yield f"{prefix}{key}"
|
|
48
|
+
|
|
49
|
+
def _set_config(self, key, value, delimiter="."):
|
|
50
|
+
keys = key.split(delimiter)
|
|
51
|
+
config = self.config
|
|
52
|
+
for key in keys[:-1]:
|
|
53
|
+
config = config.setdefault(key, {})
|
|
54
|
+
config[keys[-1]] = value
|
|
55
|
+
|
|
56
|
+
def _get_config(self, key):
|
|
57
|
+
keys = key.split(".")
|
|
58
|
+
config = self.config
|
|
59
|
+
for key in keys[:-1]:
|
|
60
|
+
config = config.get(key, {})
|
|
61
|
+
return config.get(keys[-1], None)
|
{qase_python_commons-2.0.12/src/qaseio → qase_python_commons-3.0.0/src/qase}/commons/loader.py
RENAMED
|
@@ -2,10 +2,12 @@ from qaseio.api_client import ApiClient
|
|
|
2
2
|
from qaseio.configuration import Configuration
|
|
3
3
|
from qaseio.api.plans_api import PlansApi
|
|
4
4
|
from qaseio.rest import ApiException
|
|
5
|
+
|
|
5
6
|
import certifi
|
|
6
7
|
|
|
8
|
+
|
|
7
9
|
class TestOpsPlanLoader:
|
|
8
|
-
def __init__(self, api_token, host
|
|
10
|
+
def __init__(self, api_token, host='qase.io'):
|
|
9
11
|
configuration = Configuration()
|
|
10
12
|
configuration.api_key['TokenAuth'] = api_token
|
|
11
13
|
configuration.host = f'https://api.{host}/v1'
|
|
@@ -16,13 +18,12 @@ class TestOpsPlanLoader:
|
|
|
16
18
|
configuration.ssl_ca_cert = certifi.where()
|
|
17
19
|
|
|
18
20
|
def load(self, code: str, plan_id: int) -> list:
|
|
19
|
-
api_instance = PlansApi(self.client)
|
|
20
21
|
try:
|
|
21
|
-
response =
|
|
22
|
+
response = PlansApi(self.client).get_plan(code=code, id=plan_id)
|
|
22
23
|
if hasattr(response, 'result'):
|
|
23
24
|
self.case_list = [c.case_id for c in response.result.cases]
|
|
24
25
|
return self.case_list
|
|
25
26
|
raise ValueError("Unable to find given plan")
|
|
26
27
|
except ApiException as e:
|
|
27
28
|
print("Unable to load test plan data: %s\n" % e)
|
|
28
|
-
return []
|
|
29
|
+
return []
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Logger:
|
|
7
|
+
def __init__(self, debug: bool = False, prefix: str = '', dir: str = './logs') -> None:
|
|
8
|
+
self.debug = debug
|
|
9
|
+
if self.debug:
|
|
10
|
+
filename = f'{prefix}_{self._get_timestamp()}.log'
|
|
11
|
+
if not os.path.exists(dir):
|
|
12
|
+
os.makedirs(dir)
|
|
13
|
+
self.log_file = os.path.join(dir, f'{filename}')
|
|
14
|
+
with open(self.log_file, 'w'):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
def log(self, message: str, level: str = 'info'):
|
|
18
|
+
time_str = self._get_timestamp("%H:%M:%S")
|
|
19
|
+
log = f"[Qase][{time_str}][{level}] {message}\n"
|
|
20
|
+
print(log)
|
|
21
|
+
if self.debug:
|
|
22
|
+
with open(self.log_file, 'a') as f:
|
|
23
|
+
f.write(log)
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def _get_timestamp(format: str = "%Y%m%d_%H:%M:%S"):
|
|
27
|
+
now = datetime.datetime.now()
|
|
28
|
+
return now.strftime(format)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from .result import Result
|
|
2
|
+
from .run import Run
|
|
3
|
+
from .attachment import Attachment
|
|
4
|
+
from .suite import Suite
|
|
5
|
+
from .relation import Relation
|
|
6
|
+
from .step import Step
|
|
7
|
+
from .runtime import Runtime
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
Result,
|
|
11
|
+
Run,
|
|
12
|
+
Attachment,
|
|
13
|
+
Suite,
|
|
14
|
+
Relation,
|
|
15
|
+
Step,
|
|
16
|
+
Runtime
|
|
17
|
+
]
|
|
@@ -1,28 +1,32 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import uuid
|
|
3
|
-
from typing import Optional
|
|
3
|
+
from typing import Optional, Union
|
|
4
4
|
from io import BytesIO, StringIO
|
|
5
5
|
import json
|
|
6
|
+
import pathlib
|
|
7
|
+
|
|
6
8
|
|
|
7
9
|
class Attachment:
|
|
8
|
-
def __init__(self,
|
|
10
|
+
def __init__(self,
|
|
9
11
|
file_name: str,
|
|
10
12
|
mime_type: str,
|
|
11
13
|
content: Optional[str] = None,
|
|
12
|
-
file_path: Optional[str] = None
|
|
14
|
+
file_path: Optional[str] = None,
|
|
15
|
+
temporary: bool = False):
|
|
13
16
|
self.file_name = file_name
|
|
14
17
|
self.mime_type = mime_type
|
|
15
18
|
if (not content) and (not file_path):
|
|
16
19
|
raise ValueError('Either content or file_path must be provided.')
|
|
17
20
|
self.file_path = file_path
|
|
18
21
|
self.content = content
|
|
22
|
+
self.temporary = temporary
|
|
19
23
|
|
|
20
24
|
if (not isinstance(content, str)) and (not isinstance(content, bytes)):
|
|
21
25
|
self.content = json.dumps(self.content, default=lambda o: o.__dict__, sort_keys=False, indent=4)
|
|
22
26
|
|
|
23
27
|
self.size = self._get_size(content)
|
|
24
28
|
self.id = str(uuid.uuid4())
|
|
25
|
-
|
|
29
|
+
|
|
26
30
|
def _get_size(self, content):
|
|
27
31
|
if self.file_path:
|
|
28
32
|
return os.path.getsize(self.file_path)
|
|
@@ -30,17 +34,16 @@ class Attachment:
|
|
|
30
34
|
return len(content)
|
|
31
35
|
else:
|
|
32
36
|
return 0
|
|
33
|
-
|
|
34
|
-
def get_id(self):
|
|
37
|
+
|
|
38
|
+
def get_id(self) -> str:
|
|
35
39
|
return self.id
|
|
36
|
-
|
|
40
|
+
|
|
37
41
|
def get_for_upload(self) -> BytesIO:
|
|
38
42
|
if self.file_path:
|
|
39
|
-
|
|
40
|
-
content = BytesIO(fc.read())
|
|
43
|
+
return pathlib.Path(os.path.abspath(self.file_path))
|
|
41
44
|
else:
|
|
42
45
|
if isinstance(self.content, str):
|
|
43
|
-
content = BytesIO(
|
|
46
|
+
content = BytesIO(self.content.encode('utf-8'))
|
|
44
47
|
elif isinstance(self.content, bytes):
|
|
45
48
|
content = BytesIO(self.content)
|
|
46
49
|
content.name = self.file_name
|
|
@@ -3,27 +3,29 @@ from pathlib import PosixPath
|
|
|
3
3
|
import time
|
|
4
4
|
import uuid
|
|
5
5
|
import json
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
from
|
|
9
|
-
from
|
|
10
|
-
from
|
|
6
|
+
from .step import Step
|
|
7
|
+
from .suite import Suite
|
|
8
|
+
from .attachment import Attachment
|
|
9
|
+
from .relation import Relation
|
|
10
|
+
from .. import QaseUtils
|
|
11
|
+
|
|
11
12
|
|
|
12
13
|
class Field:
|
|
13
|
-
def __init__(self,
|
|
14
|
+
def __init__(self,
|
|
14
15
|
name: str,
|
|
15
16
|
value: Union[str, list]):
|
|
16
17
|
self.name = name
|
|
17
18
|
self.value = value
|
|
18
19
|
|
|
20
|
+
|
|
19
21
|
class Execution(object):
|
|
20
|
-
def __init__(self,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
def __init__(self,
|
|
23
|
+
status: Optional[str] = None,
|
|
24
|
+
end_time: int = 0,
|
|
25
|
+
duration: int = 0,
|
|
26
|
+
stacktrace: Optional[str] = None,
|
|
27
|
+
thread: Optional[str] = QaseUtils.get_thread_name()
|
|
28
|
+
):
|
|
27
29
|
self.start_time = time.time()
|
|
28
30
|
self.status = status
|
|
29
31
|
self.end_time = end_time
|
|
@@ -32,11 +34,11 @@ class Execution(object):
|
|
|
32
34
|
self.thread = thread
|
|
33
35
|
|
|
34
36
|
def set_status(self, status: Optional[str]):
|
|
35
|
-
if
|
|
37
|
+
if status in ['passed', 'failed', 'skipped', 'untested']:
|
|
36
38
|
self.status = status
|
|
37
39
|
else:
|
|
38
40
|
raise ValueError('Step status must be one of: passed, failed, skipped, untested')
|
|
39
|
-
|
|
41
|
+
|
|
40
42
|
def get_status(self):
|
|
41
43
|
return self.status
|
|
42
44
|
|
|
@@ -44,15 +46,16 @@ class Execution(object):
|
|
|
44
46
|
self.end_time = time.time()
|
|
45
47
|
self.duration = (int)((self.end_time - self.start_time) * 1000)
|
|
46
48
|
|
|
49
|
+
|
|
47
50
|
class Request(object):
|
|
48
51
|
def __init__(self,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
method: str,
|
|
53
|
+
url: str,
|
|
54
|
+
status: int,
|
|
55
|
+
request_headers: Dict[str, str],
|
|
56
|
+
request_body: str,
|
|
57
|
+
response_headers: Dict[str, str],
|
|
58
|
+
response_body: str):
|
|
56
59
|
self.method = method
|
|
57
60
|
self.url = url
|
|
58
61
|
self.status = status
|
|
@@ -61,6 +64,7 @@ class Request(object):
|
|
|
61
64
|
self.response_headers = response_headers
|
|
62
65
|
self.response_body = response_body
|
|
63
66
|
|
|
67
|
+
|
|
64
68
|
class Result(object):
|
|
65
69
|
def __init__(self, title: str, signature: str) -> None:
|
|
66
70
|
self.id: str = str(uuid.uuid4())
|
|
@@ -82,7 +86,7 @@ class Result(object):
|
|
|
82
86
|
|
|
83
87
|
def add_message(self, message: str) -> None:
|
|
84
88
|
self.message = message
|
|
85
|
-
|
|
89
|
+
|
|
86
90
|
def add_field(self, field: Type[Field]) -> None:
|
|
87
91
|
self.fields[field.name] = field.value
|
|
88
92
|
|
|
@@ -106,24 +110,24 @@ class Result(object):
|
|
|
106
110
|
|
|
107
111
|
def get_status(self) -> Optional[str]:
|
|
108
112
|
return self.execution.status
|
|
109
|
-
|
|
113
|
+
|
|
110
114
|
def get_id(self) -> str:
|
|
111
115
|
return self.id
|
|
112
|
-
|
|
116
|
+
|
|
113
117
|
def get_title(self) -> str:
|
|
114
118
|
return self.title
|
|
115
|
-
|
|
119
|
+
|
|
116
120
|
def get_field(self, name: str) -> Optional[Type[Field]]:
|
|
117
121
|
if name in self.fields:
|
|
118
122
|
return self.fields[name]
|
|
119
123
|
return None
|
|
120
|
-
|
|
124
|
+
|
|
121
125
|
def get_testops_id(self) -> Optional[int]:
|
|
122
126
|
if self.testops_id is None:
|
|
123
127
|
# Hack for old API
|
|
124
128
|
return 0
|
|
125
129
|
return self.testops_id
|
|
126
|
-
|
|
130
|
+
|
|
127
131
|
def get_duration(self) -> int:
|
|
128
132
|
return self.execution.duration
|
|
129
133
|
|
|
@@ -135,4 +139,5 @@ class Result(object):
|
|
|
135
139
|
self.run_id = run_id
|
|
136
140
|
|
|
137
141
|
def to_json(self) -> str:
|
|
138
|
-
return json.dumps(self, default=lambda o: o.__str__() if isinstance(o, PosixPath) else o.__dict__,
|
|
142
|
+
return json.dumps(self, default=lambda o: o.__str__() if isinstance(o, PosixPath) else o.__dict__,
|
|
143
|
+
sort_keys=False, indent=4)
|
{qase_python_commons-2.0.12/src/qaseio → qase_python_commons-3.0.0/src/qase}/commons/models/run.py
RENAMED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from typing import Optional, List
|
|
3
3
|
|
|
4
|
+
|
|
4
5
|
class RunExecution(object):
|
|
5
6
|
def __init__(self,
|
|
6
7
|
start_time: float,
|
|
@@ -9,12 +10,13 @@ class RunExecution(object):
|
|
|
9
10
|
) -> None:
|
|
10
11
|
self.start_time = start_time
|
|
11
12
|
self.end_time = end_time
|
|
12
|
-
self.duration = int((end_time - start_time)*1000)
|
|
13
|
+
self.duration = int((end_time - start_time) * 1000)
|
|
13
14
|
self.cumulative_duration = cumulative_duration
|
|
14
15
|
|
|
15
16
|
def track(self, result: dict):
|
|
16
17
|
self.cumulative_duration += result["execution"]["duration"]
|
|
17
18
|
|
|
19
|
+
|
|
18
20
|
class RunStats(object):
|
|
19
21
|
def __init__(self) -> None:
|
|
20
22
|
self.passed = 0
|
|
@@ -23,7 +25,7 @@ class RunStats(object):
|
|
|
23
25
|
self.broken = 0
|
|
24
26
|
self.muted = 0
|
|
25
27
|
self.total = 0
|
|
26
|
-
|
|
28
|
+
|
|
27
29
|
def track(self, result: dict):
|
|
28
30
|
status = result["execution"]["status"]
|
|
29
31
|
if status == "passed":
|
|
@@ -37,10 +39,10 @@ class RunStats(object):
|
|
|
37
39
|
self.total += 1
|
|
38
40
|
if result.get('muted', False):
|
|
39
41
|
self.muted += 1
|
|
40
|
-
|
|
42
|
+
|
|
41
43
|
|
|
42
44
|
class Run(object):
|
|
43
|
-
def __init__(self,
|
|
45
|
+
def __init__(self,
|
|
44
46
|
title: str,
|
|
45
47
|
start_time: float,
|
|
46
48
|
end_time: float,
|
|
@@ -49,6 +51,7 @@ class Run(object):
|
|
|
49
51
|
suites: List[str] = [],
|
|
50
52
|
environment: Optional[str] = None
|
|
51
53
|
):
|
|
54
|
+
self.host_data = None
|
|
52
55
|
self.title = title
|
|
53
56
|
self.execution = RunExecution(start_time=start_time, end_time=end_time)
|
|
54
57
|
self.stats = RunStats()
|
|
@@ -56,10 +59,10 @@ class Run(object):
|
|
|
56
59
|
self.threads = threads
|
|
57
60
|
self.suites = suites
|
|
58
61
|
self.environment = environment
|
|
59
|
-
|
|
62
|
+
|
|
60
63
|
def to_json(self) -> str:
|
|
61
64
|
return json.dumps(self, default=lambda o: o.__dict__, sort_keys=False, indent=4)
|
|
62
|
-
|
|
65
|
+
|
|
63
66
|
def add_result(self, result: dict):
|
|
64
67
|
compact_result = {
|
|
65
68
|
"id": result["id"],
|
|
@@ -71,8 +74,8 @@ class Run(object):
|
|
|
71
74
|
self.results.append(compact_result)
|
|
72
75
|
self.execution.track(result)
|
|
73
76
|
self.stats.track(result)
|
|
74
|
-
if
|
|
77
|
+
if result["execution"]["thread"] not in self.threads:
|
|
75
78
|
self.threads.append(result["execution"]["thread"])
|
|
76
79
|
|
|
77
80
|
def add_host_data(self, host_data: dict):
|
|
78
|
-
self.host_data = host_data
|
|
81
|
+
self.host_data = host_data
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from .step import Step, StepTextData
|
|
2
|
+
from .attachment import Attachment
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class QaseRuntimeException(Exception):
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Runtime:
|
|
10
|
+
def __init__(self):
|
|
11
|
+
self.result = None
|
|
12
|
+
self.steps = {}
|
|
13
|
+
self.step_id = None
|
|
14
|
+
|
|
15
|
+
def add_step(self, step: Step):
|
|
16
|
+
try:
|
|
17
|
+
if self.step_id:
|
|
18
|
+
step.set_parent_id(self.step_id)
|
|
19
|
+
|
|
20
|
+
self.steps[step.id] = step
|
|
21
|
+
self.step_id = step.id
|
|
22
|
+
except Exception as e:
|
|
23
|
+
raise QaseRuntimeException(e)
|
|
24
|
+
|
|
25
|
+
def finish_step(self, id: str, status: str, data=None):
|
|
26
|
+
try:
|
|
27
|
+
self.steps[id].execution.set_status(status)
|
|
28
|
+
if data:
|
|
29
|
+
self.steps[id].set_data(data)
|
|
30
|
+
|
|
31
|
+
self.steps[id].execution.complete()
|
|
32
|
+
self.step_id = self.steps[id].parent_id
|
|
33
|
+
except Exception as e:
|
|
34
|
+
raise QaseRuntimeException(e)
|
|
35
|
+
|
|
36
|
+
def add_attachment(self, attachment: Attachment):
|
|
37
|
+
try:
|
|
38
|
+
if self.step_id:
|
|
39
|
+
self.steps[self.step_id].add_attachment(attachment)
|
|
40
|
+
else:
|
|
41
|
+
self.result.add_attachment(attachment)
|
|
42
|
+
except Exception as e:
|
|
43
|
+
raise QaseRuntimeException(e)
|