@@ -74,6 +74,24 @@ def __init__(self) -> None:
7474 Initialize a pytest plugin for collecting results
7575 """
7676 self .results = {}
77+ self .tags = set ()
78+ self .annotations = []
79+ self .overall_comments = []
80+
81+ def pytest_configure (self , config ):
82+ """Register custom markers for use with MarkUs."""
83+ config .addinivalue_line ("markers" , "markus_tag(name): indicate that the submission should be given a tag" )
84+ config .addinivalue_line (
85+ "markers" , "markus_annotation(**ann_data): indicate that the submission should be given an annotation"
86+ )
87+ config .addinivalue_line (
88+ "markers" ,
89+ "markus_overall_comments(comment): indicate that the submission should be given an overall comment" ,
90+ )
91+ config .addinivalue_line (
92+ "markers" ,
93+ "markus_message(text): indicate text that is displayed as part of the test output (even on success)" ,
94+ )
7795
7896 @pytest .hookimpl (hookwrapper = True , tryfirst = True )
7997 def pytest_runtest_makereport (self , item , call ):
@@ -96,8 +114,37 @@ def pytest_runtest_makereport(self, item, call):
96114 "errors" : str (rep .longrepr ) if rep .failed else "" ,
97115 "description" : item .obj .__doc__ ,
98116 }
117+
118+ # Only check markers at the end of the test case
119+ if not rep .skipped and rep .when == "teardown" :
120+ self ._process_markers (item )
121+
99122 return rep
100123
124+ def _process_markers (self , item ):
125+ """Process all markers for the given item.
126+
127+ This looks for custom markers used to represent test metadata for MarkUs.
128+ """
129+ for marker in item .iter_markers ():
130+ if marker .name == "markus_tag" :
131+ if len (marker .args ) > 0 :
132+ self .tags .add (marker .args [0 ].strip ())
133+ elif "name" in marker .kwargs :
134+ self .tags .add (marker .kwargs ["name" ].strip ())
135+ elif marker .name == "markus_annotation" :
136+ self .annotations .append (marker .kwargs )
137+ elif marker .name == "markus_overall_comments" :
138+ if len (marker .args ) > 0 :
139+ self .overall_comments .append (marker .args [0 ])
140+ elif "comment" in marker .kwargs :
141+ self .overall_comments .append (marker .kwargs ["comment" ])
142+ elif marker .name == "markus_message" and marker .args != [] and item .nodeid in self .results :
143+ if self .results [item .nodeid ].get ("errors" ):
144+ self .results [item .nodeid ]["errors" ] += f"\n \n { marker .args [0 ]} "
145+ else :
146+ self .results [item .nodeid ]["errors" ] = marker .args [0 ]
147+
101148 def pytest_collectreport (self , report ):
102149 """
103150 Implement a pytest hook that is run after the collector has
@@ -170,6 +217,9 @@ def __init__(
170217 This tester will create tests of type test_class.
171218 """
172219 super ().__init__ (specs , test_class , resource_settings = resource_settings )
220+ self .annotations = []
221+ self .overall_comments = []
222+ self .tags = set ()
173223
174224 @staticmethod
175225 def _load_unittest_tests (test_file : str ) -> unittest .TestSuite :
@@ -210,6 +260,9 @@ def _run_pytest_tests(self, test_file: str) -> List[Dict]:
210260 plugin = PytestPlugin ()
211261 pytest .main ([test_file , f"--tb={ verbosity } " ], plugins = [plugin ])
212262 results .extend (plugin .results .values ())
263+ self .annotations = plugin .annotations
264+ self .overall_comments = plugin .overall_comments
265+ self .tags = plugin .tags
213266 finally :
214267 sys .stdout = sys .__stdout__
215268 return results
@@ -237,3 +290,12 @@ def run(self) -> None:
237290 for res in result :
238291 test = self .test_class (self , test_file , res )
239292 print (test .run (), flush = True )
293+
294+ def after_tester_run (self ) -> None :
295+ """Print all MarkUs metadata from the tests."""
296+ if self .annotations :
297+ print (self .test_class .format_annotations (self .annotations ))
298+ if self .tags :
299+ print (self .test_class .format_tags (self .tags ))
300+ if self .overall_comments :
301+ print (self .test_class .format_overall_comment (self .overall_comments , separator = "\n \n " ))
0 commit comments