@@ -132,7 +132,11 @@ def from_str(cls, s: str):
132
132
133
133
@classmethod
134
134
def from_test_case (cls , test_file : Path , test : unittest .TestCase ):
135
- return cls (test_file , test .id ())
135
+ test_id = test .id ()
136
+ if type (test ).id is not unittest .TestCase .id :
137
+ # Qualify doctests so that we know what they are
138
+ test_id = f'{ type (test ).__qualname__ } .{ test_id } '
139
+ return cls (test_file , test_id )
136
140
137
141
138
142
@dataclass
@@ -288,10 +292,10 @@ def test_path_to_module(path: Path):
288
292
289
293
290
294
class TestRunner :
291
- def __init__ (self , * , failfast , report_durations ):
295
+ def __init__ (self , * , failfast : bool , report_durations : int | None ):
292
296
self .failfast = failfast
293
297
self .report_durations = report_durations
294
- self .results = []
298
+ self .results : list [ TestResult ] = []
295
299
self .total_duration = 0.0
296
300
297
301
@staticmethod
@@ -399,6 +403,21 @@ def generate_mx_report(self, path: str):
399
403
# noinspection PyTypeChecker
400
404
json .dump (report_data , f )
401
405
406
+ def generate_tags (self ):
407
+ by_file = defaultdict (list )
408
+ for result in self .results :
409
+ by_file [result .test_id .test_file ].append (result )
410
+ for test_file , results in by_file .items ():
411
+ config = config_for_file (test_file )
412
+ tag_file = config .get_tag_file (test_file )
413
+ if not tag_file :
414
+ log (f"WARNNING: no tag directory for test file { result .test_id .test_file } " )
415
+ continue
416
+ with open (tag_file , 'w' ) as f :
417
+ for result in results :
418
+ if result .status == TestStatus .SUCCESS :
419
+ f .write (f'*graalpython.lib-python.3.{ result .test_id .test_name } \n ' )
420
+
402
421
403
422
def interrupt_process (process : subprocess .Popen ):
404
423
sig = signal .SIGINT if sys .platform != 'win32' else signal .CTRL_C_EVENT
@@ -640,6 +659,10 @@ def is_partial_splits_individual_tests(self, test_file: Path):
640
659
name = str (resolved ).removesuffix ('.py' )
641
660
return name in self .partial_splits_individual_tests
642
661
662
+ def get_tag_file (self , test_file : Path ):
663
+ if self .tags_dir :
664
+ return self .tags_dir / (test_file .name .removesuffix ('.py' ) + '.txt' )
665
+
643
666
644
667
@lru_cache
645
668
def config_for_dir (path : Path ) -> Config :
@@ -803,7 +826,7 @@ def collect(all_specifiers: list[TestSpecifier], *, use_tags=False, ignore=None,
803
826
804
827
805
828
def read_tags (test_file : Path , config : Config ) -> list [TestId ]:
806
- tag_file = config .tags_dir / (test_file . name . removesuffix ( '.py' ) + '.txt' )
829
+ tag_file = config .get_tag_file (test_file )
807
830
tags = []
808
831
if tag_file .exists ():
809
832
with open (tag_file ) as f :
@@ -864,6 +887,8 @@ def main():
864
887
help = "Exit immediately after the first failure" )
865
888
parser .add_argument ('--all' , action = 'store_true' ,
866
889
help = "Run tests that are normally not enabled due to tags" )
890
+ parser .add_argument ('--retag' , action = 'store_true' ,
891
+ help = "Run tests and regenerate tags based on the results. Implies --all, --tagged and -n" )
867
892
parser .add_argument ('--collect-only' , action = 'store_true' ,
868
893
help = "Print found tests IDs without running tests" )
869
894
parser .add_argument ('--durations' , type = int , default = 0 ,
@@ -894,6 +919,11 @@ def main():
894
919
895
920
args = parser .parse_args ()
896
921
922
+ if args .retag :
923
+ args .all = True
924
+ args .tagged = True
925
+ args .num_processes = args .num_processes or 1
926
+
897
927
if get_bool_env ('GRAALPYTEST_FAIL_FAST' ):
898
928
args .failfast = True
899
929
@@ -948,6 +978,9 @@ def main():
948
978
runner .run_tests (tests )
949
979
if args .mx_report :
950
980
runner .generate_mx_report (args .mx_report )
981
+ if args .retag :
982
+ runner .generate_tags ()
983
+ return
951
984
if runner .tests_failed ():
952
985
sys .exit (1 )
953
986
0 commit comments