Skip to content

Commit c5789e6

Browse files
author
Dzmitry Humianiuk
authored
Merge pull request #88 from bigbZik/hierarchy-updates
Improved hierarchy logic. Fix for xdist
2 parents 0415bf4 + 492cdf1 commit c5789e6

File tree

5 files changed

+44
-57
lines changed

5 files changed

+44
-57
lines changed

README.rst

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,11 @@ The following parameters are optional:
6363
- :code:`rp_log_batch_size = 20` - size of batch log request
6464
- :code:`rp_ignore_errors = True` - Ignore Report Portal errors (exit otherwise)
6565
- :code:`rp_ignore_tags = 'xfail' 'usefixture'` - Ignore specified pytest markers
66-
- :code:`rp_hierarchy_dirs = True` - Enables hierarchy for tests directories (default False)
67-
- :code:`rp_hierarchy_module = True` - Enables hierarchy for module (default True)
68-
- :code:`rp_hierarchy_class = True` - Enables hierarchy for class (default True)
69-
- :code:`rp_hierarchy_parametrize = True` - Enables hierarchy parametrized tests (default False)
70-
- :code:`rp_hierarchy_dirs_level = 0` - Directory starting hierarchy level (from pytest.ini level) (default 0)
66+
- :code:`rp_hierarchy_dirs = True` - Enables hierarchy for tests directories, default `False`. Doesn't support 'xdist' plugin.
67+
- :code:`rp_hierarchy_module = True` - Enables hierarchy for module, default `True`. Doesn't support 'xdist' plugin.
68+
- :code:`rp_hierarchy_class = True` - Enables hierarchy for class, default `True`. Doesn't support 'xdist' plugin.
69+
- :code:`rp_hierarchy_parametrize = True` - Enables hierarchy parametrized tests, default `False`. Doesn't support 'xdist' plugin.
70+
- :code:`rp_hierarchy_dirs_level = 0` - Directory starting hierarchy level (from pytest.ini level) (default `0`)
7171

7272

7373
Examples

pytest_reportportal/listener.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def pytest_runtest_protocol(self, item):
3838
yield
3939
else:
4040
yield
41-
self.PyTestService.finish_pytest_item(self.result or 'SKIPPED', self.issue or None)
41+
self.PyTestService.finish_pytest_item(item, self.result or 'SKIPPED', self.issue or None)
4242

4343
@pytest.hookimpl(hookwrapper=True)
4444
def pytest_runtest_makereport(self):

pytest_reportportal/plugin.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def pytest_sessionstart(session):
4343
# Stop now if the plugin is not properly configured
4444
return
4545

46-
if not session.config.option.enabled:
46+
if not session.config.option.rp_enabled:
4747
return
4848

4949
if is_master(session.config):
@@ -91,8 +91,7 @@ def pytest_collection_finish(session):
9191
# Stop now if the plugin is not properly configured
9292
return
9393

94-
if is_master(session.config):
95-
session.config.py_test_service.collect_tests(session)
94+
session.config.py_test_service.collect_tests(session)
9695

9796

9897
def wait_launch(rp_client):
@@ -111,7 +110,7 @@ def pytest_sessionfinish(session):
111110
# Stop now if the plugin is not properly configured
112111
return
113112

114-
if not session.config.option.enabled:
113+
if not session.config.option.rp_enabled:
115114
return
116115

117116
# FixMe: currently method of RP api takes the string parameter
@@ -186,7 +185,7 @@ def pytest_addoption(parser):
186185
group.addoption(
187186
'--reportportal',
188187
action='store_true',
189-
dest='enabled',
188+
dest='rp_enabled',
190189
default=False,
191190
help='Enable ReportPortal plugin'
192191
)

pytest_reportportal/rp_logging.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,9 @@ def makeRecord(self, name, level, fn, lno, msg, args, exc_info,
141141
return record
142142
return makeRecord
143143

144-
if not hasattr(logger_class, "_patched"):
144+
if not logger_class == RPLogger:
145145
logger_class._log = wrap_log(logger_class._log)
146146
logger_class.makeRecord = wrap_makeRecord(logger_class.makeRecord)
147-
logger_class._patched = True
148147

149148
yield
150149

pytest_reportportal/service.py

Lines changed: 33 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ def __init__(self):
7373

7474
self._errors = queue.Queue()
7575
self._loglevels = ('TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR')
76-
self._start_stack = []
77-
self._finish_stack = []
76+
self._hier_parts = {}
77+
self._item_parts = {}
7878

7979
def init_service(self, endpoint, project, uuid, log_batch_size,
8080
ignore_errors, ignored_tags):
@@ -133,10 +133,17 @@ def collect_tests(self, session):
133133
if self.RP is None:
134134
return
135135

136-
hier_dirs = session.config.getini('rp_hierarchy_dirs')
137-
hier_module = session.config.getini('rp_hierarchy_module')
138-
hier_class = session.config.getini('rp_hierarchy_class')
139-
hier_param = session.config.getini('rp_hierarchy_parametrize')
136+
hier_dirs = False
137+
hier_module = False
138+
hier_class = False
139+
hier_param = False
140+
141+
if not hasattr(session.config, 'slaveinput'):
142+
hier_dirs = session.config.getini('rp_hierarchy_dirs')
143+
hier_module = session.config.getini('rp_hierarchy_module')
144+
hier_class = session.config.getini('rp_hierarchy_class')
145+
hier_param = session.config.getini('rp_hierarchy_parametrize')
146+
140147
try:
141148
hier_dirs_level = int(session.config.getini('rp_hierarchy_dirs_level'))
142149
except ValueError:
@@ -147,59 +154,45 @@ def collect_tests(self, session):
147154

148155
for item in session.items:
149156
# Start collecting test item parts
150-
parts_in = []
151-
parts_out = []
152157
parts = []
153158

154159
# Hierarchy for directories
155160
rp_name = self._add_item_hier_parts_dirs(item, hier_dirs, hier_dirs_level, parts, dirs_parts)
156161

157-
# Hierarchy for Module and Class
162+
# Hierarchy for Module and Class/UnitTestCase
158163
item_parts = self._get_item_parts(item)
159164
rp_name = self._add_item_hier_parts_other(item_parts, item, Module, hier_module, parts, rp_name)
160165
rp_name = self._add_item_hier_parts_other(item_parts, item, Class, hier_class, parts, rp_name)
161-
# Hierarchy for unittest TestCase (class)
162166
rp_name = self._add_item_hier_parts_other(item_parts, item, UnitTestCase, hier_class, parts, rp_name)
163167

164-
165168
# Hierarchy for parametrized tests
166169
if hier_param:
167170
rp_name = self._add_item_hier_parts_parametrize(item, parts, tests_parts, rp_name)
168171

169-
# Hierarchy for test itself
170-
self._add_item_hier_parts_other(item_parts, item, Function, True, parts, rp_name)
171-
# Hierarchy for unittest TestCaseFunction (test)
172-
self._add_item_hier_parts_other(item_parts, item, TestCaseFunction, True, parts, rp_name)
172+
# Hierarchy for test itself (Function/TestCaseFunction)
173+
item._rp_name = rp_name + ("::" if rp_name else "") + item.name
173174

174175
# Result initialization
175176
for part in parts:
176177
part._rp_result = "PASSED"
177178

178-
# Add all parts in revers order to parts_out
179-
parts_out.extend(reversed(parts))
180-
parts_out.append(None) # marker
181-
while parts:
182-
part = parts.pop(0)
183-
if part in self._start_stack:
184-
# If we've seen this part, skip it
185-
continue
186-
# We haven't seen this part yet. Could be a Class, Module or Function
187-
# Appent to parts_in
188-
parts_in.append(part)
189-
190-
# Update self._start_stack and self._finish_stack
191-
self._start_stack.extend(parts_in)
192-
self._finish_stack.extend(parts_out)
179+
self._item_parts[item] = parts
180+
for part in parts:
181+
if part not in self._hier_parts:
182+
self._hier_parts[part] = {"finish_counter": 1, "start_flag": False}
183+
else:
184+
self._hier_parts[part]["finish_counter"] += 1
193185

194186
def start_pytest_item(self, test_item=None):
195187
self._stop_if_necessary()
196188
if self.RP is None:
197189
return
198190

199-
while True:
200-
part = self._start_stack.pop(0)
201-
if part is test_item:
202-
break
191+
for part in self._item_parts[test_item]:
192+
if self._hier_parts[part]["start_flag"]:
193+
continue
194+
self._hier_parts[part]["start_flag"] = True
195+
203196
payload = {
204197
'name': self._get_item_name(part),
205198
'description': self._get_item_description(part),
@@ -223,14 +216,11 @@ def start_pytest_item(self, test_item=None):
223216
log.debug('ReportPortal - Start TestItem: request_body=%s', start_rq)
224217
self.RP.start_test_item(**start_rq)
225218

226-
def finish_pytest_item(self, status, issue=None):
219+
def finish_pytest_item(self, test_item, status, issue=None):
227220
self._stop_if_necessary()
228221
if self.RP is None:
229222
return
230223

231-
# Remove the test from the finish stack
232-
self._finish_stack.pop(0)
233-
234224
fta_rq = {
235225
'end_time': timestamp(),
236226
'status': status,
@@ -240,13 +230,13 @@ def finish_pytest_item(self, status, issue=None):
240230
log.debug('ReportPortal - Finish TestItem: request_body=%s', fta_rq)
241231
self.RP.finish_test_item(**fta_rq)
242232

243-
while self._finish_stack:
244-
part = self._finish_stack.pop(0)
245-
if part is None:
246-
break
233+
parts = self._item_parts[test_item]
234+
while len(parts) > 0:
235+
part = parts.pop()
247236
if status == "FAILED":
248237
part._rp_result = status
249-
if self._finish_stack.count(part):
238+
self._hier_parts[part]["finish_counter"] -= 1
239+
if self._hier_parts[part]["finish_counter"] > 0:
250240
continue
251241
payload = {
252242
'end_time': timestamp(),
@@ -396,7 +386,6 @@ def _get_item_parts(item):
396386
parts.append(parent)
397387

398388
parts.reverse()
399-
parts.append(item)
400389
return parts
401390

402391
@staticmethod

0 commit comments

Comments
 (0)