1111from _pytest .main import Session
1212from _pytest .python import Class , Function , Instance , Module
1313from _pytest .doctest import DoctestItem
14+ from _pytest .nodes import File , Item
1415from reportportal_client import ReportPortalServiceAsync
1516
1617log = 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