cloudbeat-common 0.0.1__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.
- cloudbeat_common-0.0.1/PKG-INFO +38 -0
- cloudbeat_common-0.0.1/cloudbeat.py +8 -0
- cloudbeat_common-0.0.1/cloudbeat_common.egg-info/PKG-INFO +38 -0
- cloudbeat_common-0.0.1/cloudbeat_common.egg-info/SOURCES.txt +12 -0
- cloudbeat_common-0.0.1/cloudbeat_common.egg-info/dependency_links.txt +1 -0
- cloudbeat_common-0.0.1/cloudbeat_common.egg-info/requires.txt +2 -0
- cloudbeat_common-0.0.1/cloudbeat_common.egg-info/top_level.txt +2 -0
- cloudbeat_common-0.0.1/setup.cfg +4 -0
- cloudbeat_common-0.0.1/setup.py +59 -0
- cloudbeat_common-0.0.1/src/__init__.py +0 -0
- cloudbeat_common-0.0.1/src/json_util.py +82 -0
- cloudbeat_common-0.0.1/src/models.py +212 -0
- cloudbeat_common-0.0.1/src/reporter.py +151 -0
- cloudbeat_common-0.0.1/tests/test_reporter.py +64 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cloudbeat-common
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Contains the common types and API client for CloudBeat
|
|
5
|
+
Home-page: https://cloudbeat.io/
|
|
6
|
+
Author: CBNR Cloud Solutions LTD
|
|
7
|
+
Author-email: info@cloudbeat.io
|
|
8
|
+
License: Apache-2.0
|
|
9
|
+
Project-URL: Source, https://github.com/cloudbeat-io/cb-kit-python
|
|
10
|
+
Keywords: cloudbeat testing reporting python
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
15
|
+
Classifier: Topic :: Software Development :: Testing
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Requires-Python: >=3.6
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
Requires-Dist: attrs>=16.0.0
|
|
27
|
+
Requires-Dist: pluggy>=0.4.0
|
|
28
|
+
Dynamic: author
|
|
29
|
+
Dynamic: author-email
|
|
30
|
+
Dynamic: classifier
|
|
31
|
+
Dynamic: description-content-type
|
|
32
|
+
Dynamic: home-page
|
|
33
|
+
Dynamic: keywords
|
|
34
|
+
Dynamic: license
|
|
35
|
+
Dynamic: project-url
|
|
36
|
+
Dynamic: requires-dist
|
|
37
|
+
Dynamic: requires-python
|
|
38
|
+
Dynamic: summary
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cloudbeat-common
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Contains the common types and API client for CloudBeat
|
|
5
|
+
Home-page: https://cloudbeat.io/
|
|
6
|
+
Author: CBNR Cloud Solutions LTD
|
|
7
|
+
Author-email: info@cloudbeat.io
|
|
8
|
+
License: Apache-2.0
|
|
9
|
+
Project-URL: Source, https://github.com/cloudbeat-io/cb-kit-python
|
|
10
|
+
Keywords: cloudbeat testing reporting python
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
15
|
+
Classifier: Topic :: Software Development :: Testing
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Requires-Python: >=3.6
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
Requires-Dist: attrs>=16.0.0
|
|
27
|
+
Requires-Dist: pluggy>=0.4.0
|
|
28
|
+
Dynamic: author
|
|
29
|
+
Dynamic: author-email
|
|
30
|
+
Dynamic: classifier
|
|
31
|
+
Dynamic: description-content-type
|
|
32
|
+
Dynamic: home-page
|
|
33
|
+
Dynamic: keywords
|
|
34
|
+
Dynamic: license
|
|
35
|
+
Dynamic: project-url
|
|
36
|
+
Dynamic: requires-dist
|
|
37
|
+
Dynamic: requires-python
|
|
38
|
+
Dynamic: summary
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
cloudbeat.py
|
|
2
|
+
setup.py
|
|
3
|
+
cloudbeat_common.egg-info/PKG-INFO
|
|
4
|
+
cloudbeat_common.egg-info/SOURCES.txt
|
|
5
|
+
cloudbeat_common.egg-info/dependency_links.txt
|
|
6
|
+
cloudbeat_common.egg-info/requires.txt
|
|
7
|
+
cloudbeat_common.egg-info/top_level.txt
|
|
8
|
+
src/__init__.py
|
|
9
|
+
src/json_util.py
|
|
10
|
+
src/models.py
|
|
11
|
+
src/reporter.py
|
|
12
|
+
tests/test_reporter.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from setuptools import setup
|
|
3
|
+
|
|
4
|
+
PACKAGE = "cloudbeat-common"
|
|
5
|
+
|
|
6
|
+
classifiers = [
|
|
7
|
+
'Development Status :: 5 - Production/Stable',
|
|
8
|
+
'Intended Audience :: Developers',
|
|
9
|
+
'License :: OSI Approved :: Apache Software License',
|
|
10
|
+
'Topic :: Software Development :: Quality Assurance',
|
|
11
|
+
'Topic :: Software Development :: Testing',
|
|
12
|
+
'Programming Language :: Python :: 3',
|
|
13
|
+
'Programming Language :: Python :: 3 :: Only',
|
|
14
|
+
'Programming Language :: Python :: 3.7',
|
|
15
|
+
'Programming Language :: Python :: 3.8',
|
|
16
|
+
'Programming Language :: Python :: 3.9',
|
|
17
|
+
'Programming Language :: Python :: 3.10',
|
|
18
|
+
'Programming Language :: Python :: 3.11',
|
|
19
|
+
'Programming Language :: Python :: 3.12',
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
install_requires = [
|
|
23
|
+
"attrs>=16.0.0",
|
|
24
|
+
"pluggy>=0.4.0",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_readme(fname):
|
|
29
|
+
return open(os.path.join(os.path.dirname(__file__), fname)).read()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def main():
|
|
33
|
+
setup(
|
|
34
|
+
name=PACKAGE,
|
|
35
|
+
version="0.0.1",
|
|
36
|
+
use_scm_version={"root": "..", "relative_to": __file__},
|
|
37
|
+
setup_requires=['setuptools_scm'],
|
|
38
|
+
description="Contains the common types and API client for CloudBeat",
|
|
39
|
+
url="https://cloudbeat.io/",
|
|
40
|
+
project_urls={
|
|
41
|
+
"Source": "https://github.com/cloudbeat-io/cb-kit-python",
|
|
42
|
+
},
|
|
43
|
+
author="CBNR Cloud Solutions LTD",
|
|
44
|
+
author_email="info@cloudbeat.io",
|
|
45
|
+
license="Apache-2.0",
|
|
46
|
+
classifiers=classifiers,
|
|
47
|
+
keywords="cloudbeat testing reporting python",
|
|
48
|
+
# long_description=get_readme("README.md"),
|
|
49
|
+
long_description_content_type="text/markdown",
|
|
50
|
+
packages=["cloudbeat_common"],
|
|
51
|
+
package_dir={"cloudbeat_common": 'src'},
|
|
52
|
+
install_requires=install_requires,
|
|
53
|
+
py_modules=['cloudbeat', 'cloudbeat_common'],
|
|
54
|
+
python_requires='>=3.6'
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
if __name__ == '__main__':
|
|
59
|
+
main()
|
|
File without changes
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from cloudbeat_common.models import TestResult, SuiteResult, CaseResult, StepResult
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def to_json(result: TestResult):
|
|
7
|
+
return json.dumps(result, cls=CbResultEncoder, indent=4)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _test_result_to_json(tr: TestResult):
|
|
11
|
+
if not isinstance(tr, TestResult):
|
|
12
|
+
return None
|
|
13
|
+
return {
|
|
14
|
+
"runId": tr.run_id,
|
|
15
|
+
"instanceId": tr.instance_id,
|
|
16
|
+
"startTime": tr.start_time,
|
|
17
|
+
"endTime": tr.end_time,
|
|
18
|
+
"duration": tr.duration,
|
|
19
|
+
"capabilities": tr.capabilities,
|
|
20
|
+
"options": tr.options,
|
|
21
|
+
"metaData": tr.meta_data,
|
|
22
|
+
"environmentVariables": tr.environment_variables,
|
|
23
|
+
"testAttributes": tr.test_attributes,
|
|
24
|
+
"suites": list(map(lambda s: _suite_result_to_json(s), tr.suites))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _suite_result_to_json(r: SuiteResult):
|
|
29
|
+
if not isinstance(r, SuiteResult):
|
|
30
|
+
return None
|
|
31
|
+
return {
|
|
32
|
+
"id": r.id,
|
|
33
|
+
"name": r.name,
|
|
34
|
+
"fqn": r.fqn,
|
|
35
|
+
"startTime": r.start_time,
|
|
36
|
+
"endTime": r.end_time,
|
|
37
|
+
"duration": r.duration,
|
|
38
|
+
"status": r.status,
|
|
39
|
+
"cases": list(map(lambda c: _case_result_to_json(c), r.cases))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _case_result_to_json(r: CaseResult):
|
|
44
|
+
if not isinstance(r, CaseResult):
|
|
45
|
+
return None
|
|
46
|
+
return {
|
|
47
|
+
"id": r.id,
|
|
48
|
+
"name": r.name,
|
|
49
|
+
"display_name": r.display_name,
|
|
50
|
+
"description": r.description,
|
|
51
|
+
"fqn": r.fqn,
|
|
52
|
+
"startTime": r.start_time,
|
|
53
|
+
"endTime": r.end_time,
|
|
54
|
+
"duration": r.duration,
|
|
55
|
+
"status": r.status,
|
|
56
|
+
"context": r.context,
|
|
57
|
+
"arguments": r.arguments,
|
|
58
|
+
"steps": list(map(lambda s: _step_result_to_json(s), r.steps)),
|
|
59
|
+
"hooks": list(map(lambda s: _step_result_to_json(s), r.hooks))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _step_result_to_json(r: StepResult):
|
|
64
|
+
if not isinstance(r, StepResult):
|
|
65
|
+
return None
|
|
66
|
+
return {
|
|
67
|
+
"id": r.id,
|
|
68
|
+
"name": r.name,
|
|
69
|
+
"fqn": r.fqn,
|
|
70
|
+
"startTime": r.start_time,
|
|
71
|
+
"endTime": r.end_time,
|
|
72
|
+
"duration": r.duration,
|
|
73
|
+
"status": r.status,
|
|
74
|
+
"steps": list(map(lambda s: _step_result_to_json(s), r.steps))
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class CbResultEncoder(json.JSONEncoder):
|
|
79
|
+
def default(self, obj):
|
|
80
|
+
if isinstance(obj, TestResult):
|
|
81
|
+
return _test_result_to_json(obj)
|
|
82
|
+
return super().default(obj)
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
from attr import attrs, attrib
|
|
5
|
+
from attr import Factory
|
|
6
|
+
from collections import OrderedDict, defaultdict
|
|
7
|
+
import uuid
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestStatus:
|
|
11
|
+
FAILED = 'failed'
|
|
12
|
+
BROKEN = 'broken'
|
|
13
|
+
PASSED = 'passed'
|
|
14
|
+
SKIPPED = 'skipped'
|
|
15
|
+
UNKNOWN = 'unknown'
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class StepType:
|
|
19
|
+
GENERAL = 'general'
|
|
20
|
+
HOOK = 'hook'
|
|
21
|
+
TRANSACTION = 'transaction'
|
|
22
|
+
HTTP = 'http'
|
|
23
|
+
ASSERT = 'assert'
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@attrs
|
|
27
|
+
class CbConfig:
|
|
28
|
+
is_ready = attrib(default=False)
|
|
29
|
+
run_id = attrib(default=None)
|
|
30
|
+
run_group = attrib(default=None)
|
|
31
|
+
instance_id = attrib(default=None)
|
|
32
|
+
project_id = attrib(default=None)
|
|
33
|
+
api_token = attrib(default=None)
|
|
34
|
+
api_endpoint_url = attrib(default=None)
|
|
35
|
+
selenium_url = attrib(default=None)
|
|
36
|
+
appium_url = attrib(default=None)
|
|
37
|
+
capabilities = attrib(default=defaultdict(OrderedDict))
|
|
38
|
+
options = attrib(default=defaultdict(OrderedDict))
|
|
39
|
+
metadata = attrib(default=defaultdict(OrderedDict))
|
|
40
|
+
env_vars = attrib(default=defaultdict(OrderedDict))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@attrs
|
|
44
|
+
class TestResult:
|
|
45
|
+
start_time = attrib(default=None)
|
|
46
|
+
end_time = attrib(default=None)
|
|
47
|
+
duration = attrib(default=None)
|
|
48
|
+
|
|
49
|
+
run_id = attrib(default=None)
|
|
50
|
+
instance_id = attrib(default=None)
|
|
51
|
+
capabilities = attrib(default=defaultdict(OrderedDict))
|
|
52
|
+
options = attrib(default=defaultdict(OrderedDict))
|
|
53
|
+
meta_data = attrib(default=defaultdict(OrderedDict))
|
|
54
|
+
environment_variables = attrib(default=defaultdict(OrderedDict))
|
|
55
|
+
test_attributes = attrib(default=defaultdict(OrderedDict))
|
|
56
|
+
|
|
57
|
+
hooks = attrib(default=Factory(list))
|
|
58
|
+
suites = attrib(default=Factory(list))
|
|
59
|
+
failures = attrib(default=Factory(list))
|
|
60
|
+
status: TestStatus = attrib(default=None)
|
|
61
|
+
|
|
62
|
+
def __iter__(self):
|
|
63
|
+
yield from {
|
|
64
|
+
"startTime": self.start_time,
|
|
65
|
+
"endTime": self.end_time,
|
|
66
|
+
"duration": self.duration,
|
|
67
|
+
"runId": self.run_id,
|
|
68
|
+
"instanceId": self.instance_id
|
|
69
|
+
}.items()
|
|
70
|
+
|
|
71
|
+
def __str__(self):
|
|
72
|
+
return json.dumps(dict(self), cls=MyJSONEncoder, ensure_ascii=False)
|
|
73
|
+
|
|
74
|
+
def start(self, run_id, instance_id, options, caps, metadata, env_vars):
|
|
75
|
+
self.run_id = run_id
|
|
76
|
+
self.instance_id = instance_id
|
|
77
|
+
self.start_time = int(time.time() * 1000)
|
|
78
|
+
self.options = options
|
|
79
|
+
self.capabilities = caps
|
|
80
|
+
self.meta_data = metadata
|
|
81
|
+
self.environment_variables = env_vars
|
|
82
|
+
|
|
83
|
+
def end(self):
|
|
84
|
+
self.end_time = int(time.time() * 1000)
|
|
85
|
+
self.duration = self.end_time - self.start_time
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@attrs
|
|
89
|
+
class TestableResultBase:
|
|
90
|
+
id = attrib(default=str(uuid.uuid4()))
|
|
91
|
+
name = attrib(default=None)
|
|
92
|
+
display_name = attrib(default=None)
|
|
93
|
+
description = attrib(default=None)
|
|
94
|
+
fqn = attrib(default=None)
|
|
95
|
+
status = attrib(default=TestStatus.PASSED)
|
|
96
|
+
attachments = attrib(default=Factory(list))
|
|
97
|
+
arguments = attrib(default=Factory(list))
|
|
98
|
+
hooks = attrib(default=Factory(list))
|
|
99
|
+
start_time = attrib(default=None)
|
|
100
|
+
end_time = attrib(default=None)
|
|
101
|
+
duration = attrib(default=None)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@attrs
|
|
105
|
+
class SuiteResult(TestableResultBase):
|
|
106
|
+
cases = attrib(default=Factory(list))
|
|
107
|
+
|
|
108
|
+
def start(self, name, fqn=None):
|
|
109
|
+
self.name = name
|
|
110
|
+
self.fqn = fqn
|
|
111
|
+
self.start_time = int(time.time() * 1000)
|
|
112
|
+
|
|
113
|
+
def end(self, status=None):
|
|
114
|
+
self.end_time = int(time.time() * 1000)
|
|
115
|
+
self.duration = self.end_time - self.start_time
|
|
116
|
+
# Mark suite as FAILED if at least one of the test case has failed
|
|
117
|
+
has_failed_cases = any(c.status == TestStatus.FAILED for c in self.cases)
|
|
118
|
+
if has_failed_cases and status is None:
|
|
119
|
+
self.status = TestStatus.FAILED
|
|
120
|
+
elif status is not None:
|
|
121
|
+
self.status = status
|
|
122
|
+
|
|
123
|
+
def add_case(self, case_result):
|
|
124
|
+
self.cases.append(case_result)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@attrs
|
|
128
|
+
class StepResult(TestableResultBase):
|
|
129
|
+
type: StepType = attrib(default=None)
|
|
130
|
+
steps = attrib(default=Factory(list))
|
|
131
|
+
logs = attrib(default=Factory(list))
|
|
132
|
+
|
|
133
|
+
def start(self, name, fqn=None):
|
|
134
|
+
self.name = name
|
|
135
|
+
self.fqn = fqn
|
|
136
|
+
self.start_time = int(time.time() * 1000)
|
|
137
|
+
|
|
138
|
+
def end(self, status=TestStatus.PASSED):
|
|
139
|
+
self.end_time = int(time.time() * 1000)
|
|
140
|
+
self.duration = self.end_time - self.start_time
|
|
141
|
+
self.status = status if status is not None else TestStatus.PASSED
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@attrs
|
|
145
|
+
class CaseResult(TestableResultBase):
|
|
146
|
+
context = defaultdict(OrderedDict)
|
|
147
|
+
steps = attrib(default=Factory(list))
|
|
148
|
+
_started_steps_stack: List[StepResult] = []
|
|
149
|
+
|
|
150
|
+
def start(self, name, fqn=None):
|
|
151
|
+
self.name = name
|
|
152
|
+
self.fqn = fqn
|
|
153
|
+
self.start_time = int(time.time() * 1000)
|
|
154
|
+
|
|
155
|
+
def end(self, status=None):
|
|
156
|
+
# End all unfinished steps
|
|
157
|
+
for _ in range(len(self._started_steps_stack)):
|
|
158
|
+
step_result = self._started_steps_stack.pop()
|
|
159
|
+
step_result.end()
|
|
160
|
+
# Do not override the calculated FAILED status by the status function argument
|
|
161
|
+
if status is not None and self.status != TestStatus.FAILED:
|
|
162
|
+
self.status = status
|
|
163
|
+
self.end_time = int(time.time() * 1000)
|
|
164
|
+
self.duration = self.end_time - self.start_time
|
|
165
|
+
|
|
166
|
+
def start_hook(self, name):
|
|
167
|
+
hook_result = StepResult()
|
|
168
|
+
hook_result.start(name)
|
|
169
|
+
hook_result.type = StepType.HOOK
|
|
170
|
+
self.hooks.append(hook_result)
|
|
171
|
+
self._started_steps_stack.append(hook_result)
|
|
172
|
+
return hook_result
|
|
173
|
+
|
|
174
|
+
def end_hook(self, status=TestStatus.PASSED):
|
|
175
|
+
if len(self.hooks) == 0:
|
|
176
|
+
return None
|
|
177
|
+
last_hook = self.hooks[len(self.hooks) - 1]
|
|
178
|
+
# End hook's child steps, if remain open
|
|
179
|
+
if len(self._started_steps_stack) > 0:
|
|
180
|
+
while last_step := self._started_steps_stack.pop():
|
|
181
|
+
if last_step == last_hook:
|
|
182
|
+
break
|
|
183
|
+
last_step.end()
|
|
184
|
+
last_hook.end(status)
|
|
185
|
+
return last_hook
|
|
186
|
+
|
|
187
|
+
def start_step(self, name, fqn=None):
|
|
188
|
+
# Check if there is a started step
|
|
189
|
+
parent_step = self._started_steps_stack[len(self._started_steps_stack) - 1] \
|
|
190
|
+
if len(self._started_steps_stack) > 0 else None
|
|
191
|
+
step_result = StepResult()
|
|
192
|
+
step_result.start(name, fqn)
|
|
193
|
+
if parent_step is None:
|
|
194
|
+
self.steps.append(step_result)
|
|
195
|
+
else:
|
|
196
|
+
parent_step.steps.append(step_result)
|
|
197
|
+
self._started_steps_stack.append(step_result)
|
|
198
|
+
return step_result
|
|
199
|
+
|
|
200
|
+
def end_step(self, status=TestStatus.PASSED):
|
|
201
|
+
if len(self._started_steps_stack) == 0 or len(self.steps) == 0:
|
|
202
|
+
return None
|
|
203
|
+
last_step = self._started_steps_stack.pop() \
|
|
204
|
+
if len(self._started_steps_stack) > 0 else self.steps[len(self.steps) - 1]
|
|
205
|
+
last_step.end(status)
|
|
206
|
+
if status == TestStatus.FAILED:
|
|
207
|
+
self.status = TestStatus.FAILED
|
|
208
|
+
return last_step
|
|
209
|
+
|
|
210
|
+
def add_parameters(self, parameters):
|
|
211
|
+
# TODO: merge with the existing parameters
|
|
212
|
+
self.context["params"] = parameters
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
from collections import OrderedDict, defaultdict
|
|
3
|
+
import platform
|
|
4
|
+
|
|
5
|
+
from cloudbeat_common.models import TestResult, CbConfig, SuiteResult, CaseResult, StepResult
|
|
6
|
+
from cloudbeat_common.json_util import to_json
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ThreadContext:
|
|
10
|
+
_thread_context = defaultdict(OrderedDict)
|
|
11
|
+
_init_thread: threading.Thread
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def thread_context(self):
|
|
15
|
+
context = self._thread_context[threading.current_thread()]
|
|
16
|
+
if not context and threading.current_thread() is not self._init_thread:
|
|
17
|
+
uuid, last_item = next(reversed(self._thread_context[self._init_thread].items()))
|
|
18
|
+
context[uuid] = last_item
|
|
19
|
+
return context
|
|
20
|
+
|
|
21
|
+
def __init__(self, *args, **kwargs):
|
|
22
|
+
self._init_thread = threading.current_thread()
|
|
23
|
+
super().__init__(*args, **kwargs)
|
|
24
|
+
|
|
25
|
+
def __setitem__(self, key, value):
|
|
26
|
+
self.thread_context.__setitem__(key, value)
|
|
27
|
+
|
|
28
|
+
def __getitem__(self, item):
|
|
29
|
+
return self.thread_context.__getitem__(item)
|
|
30
|
+
|
|
31
|
+
def __iter__(self):
|
|
32
|
+
return self.thread_context.__iter__()
|
|
33
|
+
|
|
34
|
+
def __reversed__(self):
|
|
35
|
+
return self.thread_context.__reversed__()
|
|
36
|
+
|
|
37
|
+
def get(self, key):
|
|
38
|
+
return self.thread_context.get(key)
|
|
39
|
+
|
|
40
|
+
def pop(self, key):
|
|
41
|
+
return self.thread_context.pop(key)
|
|
42
|
+
|
|
43
|
+
def cleanup(self):
|
|
44
|
+
stopped_threads = []
|
|
45
|
+
for thread in self._thread_context.keys():
|
|
46
|
+
if not thread.is_alive():
|
|
47
|
+
stopped_threads.append(thread)
|
|
48
|
+
for thread in stopped_threads:
|
|
49
|
+
del self._thread_context[thread]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class CbTestReporter:
|
|
53
|
+
_result: TestResult = None
|
|
54
|
+
_config: CbConfig = None
|
|
55
|
+
|
|
56
|
+
def __init__(self, config: CbConfig):
|
|
57
|
+
self._context = ThreadContext()
|
|
58
|
+
self._config = config
|
|
59
|
+
|
|
60
|
+
def start_instance(self):
|
|
61
|
+
self._result = TestResult()
|
|
62
|
+
self._result.start(
|
|
63
|
+
self._config.run_id,
|
|
64
|
+
self._config.instance_id,
|
|
65
|
+
self._config.options,
|
|
66
|
+
self._config.capabilities,
|
|
67
|
+
self._config.metadata,
|
|
68
|
+
self._config.env_vars)
|
|
69
|
+
self._add_system_attributes()
|
|
70
|
+
|
|
71
|
+
def end_instance(self) -> None:
|
|
72
|
+
if self._result is None:
|
|
73
|
+
return
|
|
74
|
+
self._result.end()
|
|
75
|
+
# Serializing json
|
|
76
|
+
json_str = to_json(self._result)
|
|
77
|
+
|
|
78
|
+
# Writing to sample.json
|
|
79
|
+
with open(".CB_TEST_RESULTS.json", "w") as outfile:
|
|
80
|
+
outfile.write(json_str)
|
|
81
|
+
|
|
82
|
+
def start_suite(self, name, fqn=None):
|
|
83
|
+
if self._result is None:
|
|
84
|
+
return None
|
|
85
|
+
suite_result = SuiteResult()
|
|
86
|
+
suite_result.start(name, fqn)
|
|
87
|
+
self._result.suites.append(suite_result)
|
|
88
|
+
self._context["suite"] = suite_result
|
|
89
|
+
self._context["case"] = None
|
|
90
|
+
return suite_result
|
|
91
|
+
|
|
92
|
+
def end_suite(self):
|
|
93
|
+
if self._context["suite"] is None:
|
|
94
|
+
return None
|
|
95
|
+
suite_result: SuiteResult = self._context["suite"]
|
|
96
|
+
suite_result.end()
|
|
97
|
+
return suite_result
|
|
98
|
+
|
|
99
|
+
def start_case(self, name, fqn=None):
|
|
100
|
+
if self._context["suite"] is None:
|
|
101
|
+
return None
|
|
102
|
+
case_result = CaseResult()
|
|
103
|
+
case_result.start(name, fqn)
|
|
104
|
+
suite_result: SuiteResult = self._context["suite"]
|
|
105
|
+
suite_result.add_case(case_result)
|
|
106
|
+
self._context["case"] = case_result
|
|
107
|
+
return case_result
|
|
108
|
+
|
|
109
|
+
def end_case(self, status=None):
|
|
110
|
+
case_result: CaseResult = self._context["case"] if "case" in self._context else None
|
|
111
|
+
if case_result is None:
|
|
112
|
+
return None
|
|
113
|
+
# TODO: end started steps of the case
|
|
114
|
+
case_result.end(status)
|
|
115
|
+
return case_result
|
|
116
|
+
|
|
117
|
+
def start_case_hook(self, name):
|
|
118
|
+
case_result: CaseResult = self._context["case"] if "case" in self._context else None
|
|
119
|
+
if case_result is None:
|
|
120
|
+
return None
|
|
121
|
+
return case_result.start_hook(name)
|
|
122
|
+
|
|
123
|
+
def end_case_hook(self, status=None):
|
|
124
|
+
case_result: CaseResult = self._context["case"] if "case" in self._context else None
|
|
125
|
+
if case_result is None:
|
|
126
|
+
return None
|
|
127
|
+
return case_result.end_hook(status)
|
|
128
|
+
|
|
129
|
+
def start_step(self, name, fqn=None):
|
|
130
|
+
if self._context["case"] is None:
|
|
131
|
+
return None
|
|
132
|
+
case_result: CaseResult = self._context["case"]
|
|
133
|
+
step_result = case_result.start_step(name, fqn)
|
|
134
|
+
return step_result
|
|
135
|
+
|
|
136
|
+
def end_step(self):
|
|
137
|
+
if self._context["case"] is None:
|
|
138
|
+
return None
|
|
139
|
+
case_result: CaseResult = self._context["case"]
|
|
140
|
+
return case_result.end_step()
|
|
141
|
+
|
|
142
|
+
def _add_system_attributes(self):
|
|
143
|
+
self._result.test_attributes["agent.hostname"] = platform.node()
|
|
144
|
+
self._result.test_attributes["agent.os.name"] = platform.system()
|
|
145
|
+
# Determine OS version
|
|
146
|
+
os_version = platform.version() if platform.system() != "Darwin" else platform.mac_ver()[0]
|
|
147
|
+
self._result.test_attributes["agent.os.version"] = os_version
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def result(self):
|
|
151
|
+
return self._result
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from src.reporter import CbTestReporter
|
|
2
|
+
from src.models import SuiteResult, CaseResult, StepResult
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def test_init(cb_reporter: CbTestReporter):
|
|
6
|
+
assert cb_reporter._config is not None
|
|
7
|
+
assert cb_reporter.result is None
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_start_instance(cb_reporter: CbTestReporter):
|
|
11
|
+
cb_reporter.start_instance()
|
|
12
|
+
assert cb_reporter.result is not None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_start_suite(cb_reporter: CbTestReporter):
|
|
16
|
+
# cb_reporter.start_instance()
|
|
17
|
+
# assert cb_reporter.result is not None
|
|
18
|
+
cb_reporter.start_suite("my suite", "cb.python.suite")
|
|
19
|
+
assert len(cb_reporter.result.suites) > 0
|
|
20
|
+
assert cb_reporter.result.suites[0].name == "my suite"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_start_case(cb_reporter: CbTestReporter):
|
|
24
|
+
case_name = "my case"
|
|
25
|
+
case_fqn = "cb.python.suite.case"
|
|
26
|
+
case_result: CaseResult = cb_reporter.start_case(case_name, case_fqn)
|
|
27
|
+
assert len(cb_reporter.result.suites[0].cases) > 0
|
|
28
|
+
assert case_result is not None
|
|
29
|
+
assert cb_reporter.result.suites[0].cases[0] == case_result
|
|
30
|
+
assert case_result.name == case_name
|
|
31
|
+
assert case_result.fqn == case_fqn
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_start_step(cb_reporter: CbTestReporter):
|
|
35
|
+
step_name = "my step"
|
|
36
|
+
step_result: StepResult = cb_reporter.start_step(step_name)
|
|
37
|
+
assert len(cb_reporter.result.suites[0].cases[0].steps) > 0
|
|
38
|
+
assert step_result is not None
|
|
39
|
+
assert cb_reporter.result.suites[0].cases[0].steps[0] == step_result
|
|
40
|
+
assert step_result.name == step_name
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_start_sub_step(cb_reporter: CbTestReporter):
|
|
44
|
+
step_name = "my sub step"
|
|
45
|
+
step_result: StepResult = cb_reporter.start_step(step_name)
|
|
46
|
+
assert step_result is not None
|
|
47
|
+
assert len(cb_reporter.result.suites[0].cases[0].steps[0].steps) > 0, \
|
|
48
|
+
"Sub step must be created"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_end_sub_step(cb_reporter: CbTestReporter):
|
|
52
|
+
step_result: StepResult = cb_reporter.end_step()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_end_case(cb_reporter: CbTestReporter):
|
|
56
|
+
case_result: CaseResult = cb_reporter.end_case()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_end_suite(cb_reporter: CbTestReporter):
|
|
60
|
+
suite_result: SuiteResult = cb_reporter.end_suite()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_end_instance(cb_reporter: CbTestReporter):
|
|
64
|
+
cb_reporter.end_instance()
|