Skip to content

Commit d625655

Browse files
committed
New report hierarchy abilities:
- new hierarchy levels for module directories and parametrized tests; - ability to configure report hierarchy via pytest.ini; - generating hierarchy item name depends on configuration; - hierarchy item result now depends on nested tests results.
1 parent cd9dacf commit d625655

File tree

3 files changed

+169
-4
lines changed

3 files changed

+169
-4
lines changed

README.rst

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

6671

6772
Examples

pytest_reportportal/plugin.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,3 +246,32 @@ def pytest_addoption(parser):
246246
'rp_ignore_tags',
247247
type='args',
248248
help='Ignore specified pytest markers, i.e parametrize')
249+
250+
parser.addini(
251+
'rp_hierarchy_dirs_level',
252+
default=0,
253+
help='Directory starting hierarchy level')
254+
255+
parser.addini(
256+
'rp_hierarchy_dirs',
257+
default=False,
258+
type='bool',
259+
help='Enables hierarchy for directories')
260+
261+
parser.addini(
262+
'rp_hierarchy_module',
263+
default=True,
264+
type='bool',
265+
help='Enables hierarchy for module')
266+
267+
parser.addini(
268+
'rp_hierarchy_class',
269+
default=True,
270+
type='bool',
271+
help='Enables hierarchy for class')
272+
273+
parser.addini(
274+
'rp_hierarchy_parametrize',
275+
default=False,
276+
type='bool',
277+
help='Enables hierarchy for parametrized tests')

pytest_reportportal/service.py

Lines changed: 135 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from _pytest.main import Session
1212
from _pytest.python import Class, Function, Instance, Module
1313
from _pytest.doctest import DoctestItem
14+
from _pytest.nodes import File, Item
1415
from reportportal_client import ReportPortalServiceAsync
1516

1617
log = logging.getLogger(__name__)
@@ -105,11 +106,43 @@ def collect_tests(self, session):
105106
if self.RP is None:
106107
return
107108

109+
hier_dirs = session.config.getini('rp_hierarchy_dirs')
110+
hier_module = session.config.getini('rp_hierarchy_module')
111+
hier_class = session.config.getini('rp_hierarchy_class')
112+
hier_param = session.config.getini('rp_hierarchy_parametrize')
113+
try:
114+
hier_dirs_level = int(session.config.getini('rp_hierarchy_dirs_level'))
115+
except ValueError:
116+
hier_dirs_level = 0
117+
118+
dirs_parts = {}
119+
tests_parts = {}
120+
108121
for item in session.items:
109122
# Start collecting test item parts
110123
parts_in = []
111124
parts_out = []
112-
parts = self._get_item_parts(item)
125+
parts = []
126+
127+
# Hierarchy for directories
128+
rp_name = self._add_item_hier_parts_dirs(item, hier_dirs, hier_dirs_level, parts, dirs_parts)
129+
130+
# Hierarchy for Module and Class
131+
item_parts = self._get_item_parts(item)
132+
rp_name = self._add_item_hier_parts_other(item_parts, item, Module, hier_module, parts, rp_name)
133+
rp_name = self._add_item_hier_parts_other(item_parts, item, Class, hier_class, parts, rp_name)
134+
135+
# Hierarchy for parametrized tests
136+
if hier_param:
137+
rp_name = self._add_item_hier_parts_parametrize(item, parts, tests_parts, rp_name)
138+
139+
# Hierarchy for test itself
140+
self._add_item_hier_parts_other(item_parts, item, Function, True, parts, rp_name)
141+
142+
# Result initialization
143+
for part in parts:
144+
part._rp_result = "PASSED"
145+
113146
# Add all parts in revers order to parts_out
114147
parts_out.extend(reversed(parts))
115148
parts_out.append(None) # marker
@@ -179,12 +212,14 @@ def finish_pytest_item(self, status, issue=None):
179212
part = self._finish_stack.pop(0)
180213
if part is None:
181214
break
215+
if status == "FAILED":
216+
part._rp_result = status
182217
if self._finish_stack.count(part):
183218
continue
184219
payload = {
185220
'end_time': timestamp(),
186221
'issue': issue,
187-
'status': 'PASSED'
222+
'status': part._rp_result
188223
}
189224
log.debug('ReportPortal - End TestSuite: request_body=%s', payload)
190225
self.RP.finish_test_item(**payload)
@@ -231,6 +266,87 @@ def _stop_if_necessary(self):
231266
except queue.Empty:
232267
pass
233268

269+
270+
@staticmethod
271+
def _add_item_hier_parts_dirs(item, hier_flag, dirs_level, report_parts, dirs_parts, rp_name=""):
272+
273+
parts_dirs = PyTestServiceClass._get_item_dirs(item)
274+
dir_path = item.fspath.new(dirname="", basename="", drive="")
275+
rp_name_path = ""
276+
277+
for dir_name in parts_dirs[dirs_level:]:
278+
dir_path = dir_path.join(dir_name)
279+
path = str(dir_path)
280+
281+
if hier_flag:
282+
if path in dirs_parts:
283+
item_dir = dirs_parts[path]
284+
rp_name = ""
285+
else:
286+
item_dir = File(dir_name, nodeid=dir_name, session=item.session, config=item.session.config)
287+
rp_name += dir_name
288+
item_dir._rp_name = rp_name
289+
dirs_parts[path] = item_dir
290+
rp_name = ""
291+
292+
report_parts.append(item_dir)
293+
else:
294+
rp_name_path = path[1:]
295+
296+
if not hier_flag:
297+
rp_name += rp_name_path
298+
299+
return rp_name
300+
301+
@staticmethod
302+
def _add_item_hier_parts_parametrize(item, report_parts, tests_parts, rp_name=""):
303+
304+
for mark in item.own_markers:
305+
if mark.name == 'parametrize':
306+
ch_index = item.nodeid.find("[")
307+
test_fullname = item.nodeid[:ch_index if ch_index > 0 else len(item.nodeid)]
308+
test_name = item.originalname
309+
310+
rp_name += ("::" if rp_name else "") + test_name
311+
312+
if test_fullname in tests_parts:
313+
item_test = tests_parts[test_fullname]
314+
else:
315+
item_test = Item(test_fullname, nodeid=test_fullname, session=item.session, config=item.session.config)
316+
item_test._rp_name = rp_name
317+
item_test.obj = item.obj
318+
item_test.keywords = item.keywords
319+
item_test.own_markers = item.own_markers
320+
item_test.parent = item.parent
321+
322+
tests_parts[test_fullname] = item_test
323+
324+
rp_name = ""
325+
report_parts.append(item_test)
326+
break
327+
328+
return rp_name
329+
330+
@staticmethod
331+
def _add_item_hier_parts_other(item_parts, item, type, hier_flag, report_parts, rp_name=""):
332+
333+
for part in item_parts:
334+
335+
if isinstance(part, type):
336+
337+
if type is Module:
338+
module_path = str(item.fspath.new(dirname=rp_name, basename=part.fspath.basename, drive=""))
339+
rp_name = module_path if rp_name else module_path[1:]
340+
elif type in (Class, Function):
341+
rp_name += ("::" if rp_name else "") + part.name
342+
343+
if hier_flag:
344+
part._rp_name = rp_name
345+
rp_name = ""
346+
report_parts.append(part)
347+
348+
return rp_name
349+
234350
@staticmethod
235351
def _get_item_parts(item):
236352
parts = []
@@ -251,6 +367,21 @@ def _get_item_parts(item):
251367
parts.append(item)
252368
return parts
253369

370+
@staticmethod
371+
def _get_item_dirs(item):
372+
373+
root_path = item.session.config.rootdir.strpath
374+
dir_path = item.fspath.new(basename="")
375+
rel_dir = dir_path.new(dirname=dir_path.relto(root_path), basename="", drive="")
376+
377+
dir_list = []
378+
for directory in rel_dir.parts(reverse=False):
379+
dir_name = directory.basename
380+
if dir_name:
381+
dir_list.append(dir_name)
382+
383+
return dir_list
384+
254385
def _get_item_tags(self, item):
255386
# Try to extract names of @pytest.mark.* decorators used for test item
256387
# and exclude those which present in rp_ignore_tags parameter
@@ -262,7 +393,7 @@ def _get_parameters(self, item):
262393

263394
@staticmethod
264395
def _get_item_name(test_item):
265-
name = test_item.name
396+
name = test_item._rp_name
266397
if len(name) > 256:
267398
name = name[:256]
268399
test_item.warn(
@@ -274,7 +405,7 @@ def _get_item_name(test_item):
274405

275406
@staticmethod
276407
def _get_item_description(test_item):
277-
if isinstance(test_item, (Class, Function, Module)):
408+
if isinstance(test_item, (Class, Function, Module, Item)):
278409
doc = test_item.obj.__doc__
279410
if doc is not None:
280411
return doc.strip()

0 commit comments

Comments
 (0)