Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d30b202

Browse files
authored
flat containers on robot (via allure-framework#453)
1 parent a1b6fcb commit d30b202

File tree

10 files changed

+403
-312
lines changed

10 files changed

+403
-312
lines changed

allure-behave/tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tox]
22
envlist =
3-
py{27,36,37}
3+
py{36,37}
44
static_check
55

66
[testenv]

allure-pytest/tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tox]
22
envlist =
3-
py{27,36,37}
3+
py{36,37}
44
xdist
55
integration
66
static_check

allure-python-commons-test/tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[tox]
2-
envlist = py{27,36,37}
2+
envlist = py{36,37}
33

44

55
[testenv]

allure-python-commons/src/lifecycle.py

Lines changed: 90 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
from collections import OrderedDict
22
from contextlib import contextmanager
3-
from ._core import plugin_manager
4-
from .model2 import TestResult
5-
from .model2 import TestStepResult
6-
from .model2 import ExecutableItem
7-
from .utils import uuid4
8-
from .utils import now
3+
from allure_commons._core import plugin_manager
4+
from allure_commons.model2 import TestResultContainer
5+
from allure_commons.model2 import TestResult
6+
from allure_commons.model2 import Attachment, ATTACHMENT_PATTERN
7+
from allure_commons.model2 import TestStepResult
8+
from allure_commons.model2 import ExecutableItem
9+
from allure_commons.model2 import TestBeforeResult
10+
from allure_commons.model2 import TestAfterResult
11+
from allure_commons.utils import uuid4
12+
from allure_commons.utils import now
13+
from allure_commons.types import AttachmentType
914

1015

1116
class AllureLifecycle(object):
@@ -61,3 +66,82 @@ def stop_step(self, uuid=None):
6166
step = self._pop_item(uuid=uuid, item_type=TestStepResult)
6267
if step and not step.stop:
6368
step.stop = now()
69+
70+
@contextmanager
71+
def start_container(self, uuid=None):
72+
container = TestResultContainer(uuid=uuid or uuid4())
73+
self._items[container.uuid] = container
74+
yield container
75+
76+
def containers(self):
77+
for item in self._items.values():
78+
if type(item) == TestResultContainer:
79+
yield item
80+
81+
@contextmanager
82+
def update_container(self, uuid=None):
83+
yield self._get_item(uuid=uuid, item_type=TestResultContainer)
84+
85+
def write_container(self, uuid=None):
86+
container = self._pop_item(uuid=uuid, item_type=TestResultContainer)
87+
if container and (container.befores or container.afters):
88+
plugin_manager.hook.report_container(container=container)
89+
90+
@contextmanager
91+
def start_before_fixture(self, parent_uuid=None, uuid=None):
92+
fixture = TestBeforeResult()
93+
parent = self._get_item(uuid=parent_uuid, item_type=TestResultContainer)
94+
if parent:
95+
parent.befores.append(fixture)
96+
self._items[uuid or uuid4()] = fixture
97+
yield fixture
98+
99+
@contextmanager
100+
def update_before_fixture(self, uuid=None):
101+
yield self._get_item(uuid=uuid, item_type=TestBeforeResult)
102+
103+
def stop_before_fixture(self, uuid=None):
104+
fixture = self._pop_item(uuid=uuid, item_type=TestBeforeResult)
105+
if fixture and not fixture.stop:
106+
fixture.stop = now()
107+
108+
@contextmanager
109+
def start_after_fixture(self, parent_uuid=None, uuid=None):
110+
fixture = TestAfterResult()
111+
parent = self._get_item(uuid=parent_uuid, item_type=TestResultContainer)
112+
if parent:
113+
parent.afters.append(fixture)
114+
self._items[uuid or uuid4()] = fixture
115+
yield fixture
116+
117+
@contextmanager
118+
def update_after_fixture(self, uuid=None):
119+
yield self._get_item(uuid=uuid, item_type=TestAfterResult)
120+
121+
def stop_after_fixture(self, uuid=None):
122+
fixture = self._pop_item(uuid=uuid, item_type=TestAfterResult)
123+
if fixture and not fixture.stop:
124+
fixture.stop = now()
125+
126+
def _attach(self, uuid, name=None, attachment_type=None, extension=None):
127+
mime_type = attachment_type
128+
extension = extension if extension else 'attach'
129+
130+
if type(attachment_type) is AttachmentType:
131+
extension = attachment_type.extension
132+
mime_type = attachment_type.mime_type
133+
134+
file_name = ATTACHMENT_PATTERN.format(prefix=uuid, ext=extension)
135+
attachment = Attachment(source=file_name, name=name, type=mime_type)
136+
uuid = self._last_item_uuid(item_type=ExecutableItem)
137+
self._items[uuid].attachments.append(attachment)
138+
139+
return file_name
140+
141+
def attach_file(self, uuid, source, name=None, attachment_type=None, extension=None):
142+
file_name = self._attach(uuid, name=name, attachment_type=attachment_type, extension=extension)
143+
plugin_manager.hook.report_attached_file(source=source, file_name=file_name)
144+
145+
def attach_data(self, uuid, body, name=None, attachment_type=None, extension=None):
146+
file_name = self._attach(uuid, name=name, attachment_type=attachment_type, extension=extension)
147+
plugin_manager.hook.report_attached_data(body=body, file_name=file_name)

allure-python-commons/tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tox]
22
envlist=
3-
py{27,36,37}
3+
py{36,37}
44
static_check
55

66

Lines changed: 185 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,24 @@
1+
import os
2+
import datetime
3+
from itertools import zip_longest
4+
from robot.libraries.BuiltIn import BuiltIn
5+
16
import allure_commons
27
from allure_commons.utils import now
38
from allure_commons.utils import uuid4
4-
from allure_commons.model2 import TestStepResult
9+
from allure_commons.utils import md5
10+
from allure_commons.utils import platform_label
11+
from allure_commons.utils import host_tag
12+
from allure_commons.utils import format_exception, format_traceback
13+
from allure_commons.model2 import Label
514
from allure_commons.model2 import Status, StatusDetails
615
from allure_commons.model2 import Parameter
7-
from allure_commons.utils import format_exception, format_traceback
16+
from allure_commons.types import LabelType, AttachmentType, Severity, LinkType
17+
from allure_robotframework.utils import get_allure_status
18+
from allure_robotframework.utils import get_allure_suites
19+
from allure_robotframework.utils import get_allure_parameters
20+
from allure_robotframework.utils import allure_labels, allure_links, allure_tags
21+
from allure_robotframework.types import RobotStatus, RobotLogLevel
822

923

1024
def get_status(exception):
@@ -21,27 +35,185 @@ def get_status_details(exc_type, exception, exc_traceback):
2135
trace=format_traceback(exc_traceback))
2236

2337

38+
def pool_id():
39+
return BuiltIn().get_variable_value('${PABOTEXECUTIONPOOLID}') or "default"
40+
41+
42+
def get_message_time(timestamp):
43+
s_time = datetime.datetime.strptime(timestamp, "%Y%m%d %H:%M:%S.%f")
44+
return int(s_time.timestamp() * 1000)
45+
46+
47+
LOG_MESSAGE_FORMAT = '<p><b>[{level}]</b>&nbsp;{message}</p>'
48+
FAIL_MESSAGE_FORMAT = '<p style="color: red"><b>[{level}]</b>&nbsp;{message}</p>'
49+
MAX_STEP_MESSAGE_COUNT = int(os.getenv('ALLURE_MAX_STEP_MESSAGE_COUNT', 0))
50+
51+
2452
class AllureListener(object):
25-
def __init__(self, logger):
26-
self.logger = logger
53+
def __init__(self, lifecycle):
54+
self.lifecycle = lifecycle
55+
self._platform = platform_label()
56+
self._host = host_tag()
57+
self._current_msg = None
58+
self._current_tb = None
59+
60+
def start_suite_container(self, name, attributes):
61+
with self.lifecycle.start_container():
62+
pass
63+
64+
def stop_suite_container(self, name, attributes):
65+
suite_status = get_allure_status(attributes.get('status'))
66+
suite_message = attributes.get('message')
67+
68+
with self.lifecycle.update_container() as container:
69+
for uuid in container.children:
70+
with self.lifecycle.update_test_case(uuid) as test_result:
71+
if test_result and test_result.status == Status.PASSED and suite_message:
72+
test_result.status = suite_status
73+
test_result.statusDetails = StatusDetails(message=self._current_msg or suite_message,
74+
trace=self._current_tb)
75+
self.lifecycle.write_test_case(uuid)
76+
self._current_tb, self._current_msg = None, None
77+
self.lifecycle.write_container()
78+
79+
def start_test_container(self, name, attributes):
80+
with self.lifecycle.start_container():
81+
pass
82+
83+
def stop_test_container(self, name, attributes):
84+
suite_status = get_allure_status(attributes.get('status'))
85+
suite_message = attributes.get('message')
86+
87+
with self.lifecycle.schedule_test_case() as test_result:
88+
if test_result.status == Status.PASSED and suite_message:
89+
test_result.status = suite_status
90+
test_result.statusDetails = StatusDetails(message=self._current_msg or suite_message,
91+
trace=self._current_tb)
92+
93+
self._current_tb, self._current_msg = None, None
94+
self.lifecycle.write_container()
95+
96+
def start_before_fixture(self, name):
97+
with self.lifecycle.start_before_fixture() as fixture:
98+
fixture.name = name
99+
100+
def stop_before_fixture(self, attributes, messages):
101+
self._report_messages(messages)
102+
with self.lifecycle.update_before_fixture() as fixture:
103+
fixture.status = get_allure_status(attributes.get('status'))
104+
fixture.statusDetails = StatusDetails(message=self._current_msg, trace=self._current_tb)
105+
self.lifecycle.stop_before_fixture()
106+
107+
def start_after_fixture(self, name):
108+
with self.lifecycle.start_after_fixture() as fixture:
109+
fixture.name = name
110+
111+
def stop_after_fixture(self, attributes, messages):
112+
self._report_messages(messages)
113+
with self.lifecycle.update_after_fixture() as fixture:
114+
fixture.status = get_allure_status(attributes.get('status'))
115+
fixture.statusDetails = StatusDetails(message=self._current_msg, trace=self._current_tb)
116+
self.lifecycle.stop_after_fixture()
117+
118+
def start_test(self, name, attributes):
119+
uuid = uuid4()
120+
with self.lifecycle.schedule_test_case(uuid=uuid) as test_result:
121+
long_name = attributes.get('longname')
122+
test_result.name = name
123+
test_result.fullName = long_name
124+
test_result.historyId = md5(long_name)
125+
test_result.start = now()
126+
127+
for container in self.lifecycle.containers():
128+
container.children.append(uuid)
129+
130+
def stop_test(self, _, attributes, messages):
131+
self._report_messages(messages)
132+
133+
if 'skipped' in [tag.lower() for tag in attributes['tags']]:
134+
attributes['status'] = RobotStatus.SKIPPED
135+
136+
with self.lifecycle.update_test_case() as test_result:
137+
test_result.stop = now()
138+
test_result.description = attributes.get('doc')
139+
test_result.status = get_allure_status(attributes.get('status'))
140+
test_result.labels.extend(get_allure_suites(attributes.get('longname')))
141+
test_result.labels.append(Label(name=LabelType.FRAMEWORK, value='robotframework'))
142+
test_result.labels.append(Label(name=LabelType.LANGUAGE, value=self._platform))
143+
test_result.labels.append(Label(name=LabelType.HOST, value=self._host))
144+
test_result.labels.append(Label(name=LabelType.THREAD, value=pool_id()))
145+
test_result.labels.extend(allure_tags(attributes))
146+
test_result.statusDetails = StatusDetails(message=self._current_msg or attributes.get('message'),
147+
trace=self._current_tb)
148+
149+
if attributes.get('critical') == 'yes':
150+
test_result.labels.append(Label(name=LabelType.SEVERITY, value=Severity.CRITICAL))
151+
152+
for label_type in (LabelType.EPIC, LabelType.FEATURE, LabelType.STORY):
153+
test_result.labels.extend(allure_labels(attributes, label_type))
154+
155+
for link_type in (LinkType.ISSUE, LinkType.TEST_CASE, LinkType.LINK):
156+
test_result.links.extend(allure_links(attributes, link_type))
157+
158+
self._current_tb, self._current_msg = None, None
159+
160+
def start_keyword(self, name):
161+
with self.lifecycle.start_step() as step:
162+
step.name = name
163+
164+
def stop_keyword(self, attributes, messages):
165+
self._report_messages(messages)
166+
with self.lifecycle.update_step() as step:
167+
step.status = get_allure_status(attributes.get('status'))
168+
step.parameters = get_allure_parameters(attributes.get('args'))
169+
step.statusDetails = StatusDetails(message=self._current_msg, trace=self._current_tb)
170+
self.lifecycle.stop_step()
171+
172+
def _report_messages(self, messages):
173+
has_trace = BuiltIn().get_variable_value("${LOG LEVEL}") in (RobotLogLevel.DEBUG, RobotLogLevel.TRACE)
174+
attachment = ""
175+
176+
for message, next_message in zip_longest(messages, messages[1:]):
177+
name = message.get('message')
178+
level = message.get('level')
179+
message_format = FAIL_MESSAGE_FORMAT if level in RobotLogLevel.CRITICAL_LEVELS else LOG_MESSAGE_FORMAT
180+
181+
if level == RobotLogLevel.FAIL:
182+
self._current_msg = name or self._current_msg
183+
self._current_tb = next_message.get("message") if has_trace and next_message else self._current_tb
184+
185+
if len(messages) > MAX_STEP_MESSAGE_COUNT:
186+
attachment += message_format.format(level=level, message=name.replace('\n', '<br>'))
187+
else:
188+
with self.lifecycle.start_step() as step:
189+
step.name = name
190+
step.start = step.stop = get_message_time(message.get("timestamp"))
191+
step.status = Status.FAILED if level in RobotLogLevel.CRITICAL_LEVELS else Status.PASSED
192+
self.lifecycle.stop_step()
193+
194+
if attachment:
195+
self.lifecycle.attach_data(uuid=uuid4(), body=attachment, name='Keyword Log',
196+
attachment_type=AttachmentType.HTML)
27197

28198
@allure_commons.hookimpl
29199
def attach_data(self, body, name, attachment_type, extension):
30-
self.logger.attach_data(uuid4(), body, name=name, attachment_type=attachment_type, extension=extension)
200+
self.lifecycle.attach_data(uuid4(), body, name=name, attachment_type=attachment_type, extension=extension)
31201

32202
@allure_commons.hookimpl
33203
def attach_file(self, source, name, attachment_type, extension):
34-
self.logger.attach_file(uuid4(), source, name=name, attachment_type=attachment_type, extension=extension)
204+
self.lifecycle.attach_file(uuid4(), source, name=name, attachment_type=attachment_type, extension=extension)
35205

36206
@allure_commons.hookimpl
37207
def start_step(self, uuid, title, params):
38-
parameters = [Parameter(name=name, value=value) for name, value in params.items()]
39-
step = TestStepResult(name=title, start=now(), parameters=parameters)
40-
self.logger.start_step(None, uuid, step)
208+
with self.lifecycle.start_step() as step:
209+
step.name = title
210+
step.start = now()
211+
step.parameters = [Parameter(name=name, value=value) for name, value in params.items()]
41212

42213
@allure_commons.hookimpl
43214
def stop_step(self, uuid, exc_type, exc_val, exc_tb):
44-
self.logger.stop_step(uuid,
45-
stop=now(),
46-
status=get_status(exc_val),
47-
statusDetails=get_status_details(exc_type, exc_val, exc_tb))
215+
with self.lifecycle.update_step() as step:
216+
step.stop = now()
217+
step.status = get_status(exc_val)
218+
step.statusDetails = get_status_details(exc_type, exc_val, exc_tb)
219+
self.lifecycle.stop_step()

0 commit comments

Comments
 (0)