76
76
# noinspection PyUnresolvedReferences
77
77
PLATFORM_KEYS .add ('native_image' if __graalpython__ .is_native else 'jvm' )
78
78
79
- CURRENT_PLATFORM_KEYS = frozenset ({f'{ sys .platform } -{ platform .machine ()} ' })
79
+ CURRENT_PLATFORM = f'{ sys .platform } -{ platform .machine ()} '
80
+ CURRENT_PLATFORM_KEYS = frozenset ({CURRENT_PLATFORM })
80
81
81
82
82
83
class Logger :
@@ -125,6 +126,9 @@ class TestId:
125
126
def __repr__ (self ):
126
127
return f'{ self .test_file } ::{ self .test_name } '
127
128
129
+ def normalized (self ):
130
+ return TestId (self .test_file .resolve (), self .test_name )
131
+
128
132
@classmethod
129
133
def from_str (cls , s : str ):
130
134
test_file , _ , test_id = s .partition ('::' )
@@ -419,6 +423,9 @@ def run_tests(self, tests: list['TestSuite']):
419
423
def generate_mx_report (self , path : str ):
420
424
report_data = []
421
425
for result in self .results :
426
+ # Skip synthetic results for failed class setups and such
427
+ if '<' in result .test_id .test_name :
428
+ continue
422
429
match result .status :
423
430
case TestStatus .SUCCESS | TestStatus .EXPECTED_FAILURE :
424
431
status = 'PASSED'
@@ -441,27 +448,42 @@ def generate_tags(self, append=False):
441
448
by_file [result .test_id .test_file ].append (result )
442
449
for test_file , results in by_file .items ():
443
450
test_file = configure_test_file (test_file )
444
- new_tags = [
445
- Tag (result .test_id , keys = CURRENT_PLATFORM_KEYS )
446
- for result in results if result .status == TestStatus .SUCCESS
447
- ]
448
- tags = merge_tags (read_tags (test_file ), new_tags , append = append )
451
+ tags = update_tags (
452
+ current = read_tags (test_file ),
453
+ results = results ,
454
+ tag_platform = CURRENT_PLATFORM ,
455
+ untag_failed = (not append ),
456
+ untag_skipped = (not append ),
457
+ untag_missing = (not append ),
458
+ )
449
459
write_tags (test_file , tags )
450
460
451
461
452
- def merge_tags (original : typing .Iterable ['Tag' ], new : typing .Iterable ['Tag' ], append : bool ) -> set ['Tag' ]:
453
- original_by_name = {t .test_id .test_name : t .keys for t in original }
454
- merged = {}
455
- for tag in new :
456
- if existing_keys := original_by_name .get (tag .test_id .test_name ):
457
- merged [tag .test_id .test_name ] = tag .with_merged_keys (existing_keys )
458
- else :
459
- merged [tag .test_id .test_name ] = tag
460
- if append :
461
- for test_name , tag in original_by_name .items ():
462
- if test_name not in merged :
463
- merged [test_name ] = tag
464
- return set (merged .values ())
462
+ def update_tags (current : typing .Iterable ['Tag' ], results : typing .Iterable [TestResult ], tag_platform : str ,
463
+ untag_failed = False , untag_skipped = False , untag_missing = False ) -> set ['Tag' ]:
464
+ status_by_id = {r .test_id .normalized (): r .status for r in results }
465
+ tag_by_id = {}
466
+ for tag in current :
467
+ if untag_missing and tag .test_id not in status_by_id :
468
+ tag = tag .without_key (tag_platform )
469
+ if tag :
470
+ tag_by_id [tag .test_id ] = tag
471
+
472
+ for test_id , status in status_by_id .items ():
473
+ if tag := tag_by_id .get (test_id ):
474
+ if status == TestStatus .SUCCESS :
475
+ tag_by_id [test_id ] = tag .with_key (tag_platform )
476
+ elif (untag_skipped and status == TestStatus .SKIPPED
477
+ or untag_failed and status == TestStatus .FAILURE ):
478
+ tag = tag .without_key (tag_platform )
479
+ if tag :
480
+ tag_by_id [test_id ] = tag
481
+ else :
482
+ del tag_by_id [test_id ]
483
+ elif status == TestStatus .SUCCESS :
484
+ tag_by_id [test_id ] = Tag .for_key (test_id , tag_platform )
485
+
486
+ return set (tag_by_id .values ())
465
487
466
488
467
489
def write_tags (test_file : 'TestFile' , tags : typing .Iterable ['Tag' ]):
@@ -976,7 +998,7 @@ def filter_tree(test_file: TestFile, test_suite: unittest.TestSuite, specifiers:
976
998
else :
977
999
test_id = TestId .from_test_case (test_file .path , test )
978
1000
if any (s .match (test_id ) for s in specifiers ):
979
- if tagged_ids is None or test_id in tagged_ids :
1001
+ if tagged_ids is None or test_id . normalized () in tagged_ids :
980
1002
keep_tests .append (test )
981
1003
collected_tests .append (Test (test_id , test_file ))
982
1004
test_suite ._tests = keep_tests
@@ -1107,14 +1129,27 @@ class Tag:
1107
1129
test_id : TestId
1108
1130
keys : frozenset [str ]
1109
1131
1110
- def with_merged_keys (self , keys : typing .AbstractSet [str ]) -> 'Tag' :
1111
- return Tag (self .test_id , self .keys | keys )
1132
+ @classmethod
1133
+ def for_key (cls , test_id , key ):
1134
+ return Tag (test_id , frozenset ({key }))
1135
+
1136
+ def with_key (self , key : str ):
1137
+ return Tag (self .test_id , self .keys | {key })
1138
+
1139
+ def without_key (self , key : str ):
1140
+ if key not in self .keys :
1141
+ return self
1142
+ keys = self .keys - {key }
1143
+ if keys :
1144
+ return Tag (self .test_id , keys )
1112
1145
1113
1146
def __str__ (self ):
1114
1147
return f'{ self .test_id .test_name } @ { "," .join (sorted (self .keys ))} '
1115
1148
1116
1149
1117
1150
def read_tags (test_file : TestFile ) -> list [Tag ]:
1151
+ # To make them easily comparable
1152
+ test_path = test_file .path .resolve ()
1118
1153
tag_file = test_file .get_tag_file ()
1119
1154
tags = []
1120
1155
if tag_file .exists ():
@@ -1126,7 +1161,7 @@ def read_tags(test_file: TestFile) -> list[Tag]:
1126
1161
if not keys :
1127
1162
log (f'WARNING: invalid tag { test } : missing platform keys' )
1128
1163
tags .append (Tag (
1129
- TestId (test_file . path , test ),
1164
+ TestId (test_path , test ),
1130
1165
frozenset (keys .split (',' )),
1131
1166
))
1132
1167
return tags
@@ -1169,6 +1204,38 @@ def result_factory(suite):
1169
1204
pickle .dump (data , f )
1170
1205
1171
1206
1207
+ def main_merge_tags (args ):
1208
+ with open (args .report_path ) as f :
1209
+ report = json .load (f )
1210
+ status_map = {
1211
+ 'PASSED' : TestStatus .SUCCESS ,
1212
+ 'FAILED' : TestStatus .FAILURE ,
1213
+ 'IGNORED' : TestStatus .SKIPPED ,
1214
+ }
1215
+ all_results = [
1216
+ TestResult (
1217
+ test_id = TestId .from_str (result ['name' ]),
1218
+ status = status_map [result ['status' ]],
1219
+ )
1220
+ for result in report
1221
+ if '<' not in result ['name' ]
1222
+ ]
1223
+ by_file = defaultdict (list )
1224
+ for result in all_results :
1225
+ by_file [result .test_id .test_file ].append (result )
1226
+ for test_file , results in by_file .items ():
1227
+ test_file = configure_test_file (test_file )
1228
+ tags = update_tags (
1229
+ current = read_tags (test_file ),
1230
+ results = results ,
1231
+ tag_platform = args .platform ,
1232
+ untag_failed = False ,
1233
+ untag_skipped = True ,
1234
+ untag_missing = True ,
1235
+ )
1236
+ write_tags (test_file , tags )
1237
+
1238
+
1172
1239
def get_bool_env (name : str ):
1173
1240
return os .environ .get (name , '' ).lower () in ('true' , '1' )
1174
1241
@@ -1179,7 +1246,11 @@ def main():
1179
1246
subparsers = parent_parser .add_subparsers ()
1180
1247
1181
1248
# run command declaration
1182
- run_parser = subparsers .add_parser ('run' , prog = ('mx graalpytest' if is_mx_graalpytest else None ))
1249
+ run_parser = subparsers .add_parser (
1250
+ 'run' ,
1251
+ prog = ('mx graalpytest' if is_mx_graalpytest else None ),
1252
+ help = "Run GraalPy unittests or CPython unittest with tagging" ,
1253
+ )
1183
1254
run_parser .set_defaults (main = main_run )
1184
1255
if is_mx_graalpytest :
1185
1256
# mx graalpytest takes this option, but it forwards --help here, so pretend we take it
@@ -1282,14 +1353,20 @@ def main():
1282
1353
)
1283
1354
1284
1355
# worker command declaration
1285
- worker_parser = subparsers .add_parser ('worker' )
1356
+ worker_parser = subparsers .add_parser ('worker' , help = "Internal command for subprocess workers" )
1286
1357
worker_parser .set_defaults (main = main_worker )
1287
1358
group = worker_parser .add_mutually_exclusive_group ()
1288
1359
group .add_argument ('--pipe-fd' , type = int )
1289
1360
group .add_argument ('--result-file' , type = Path )
1290
1361
worker_parser .add_argument ('--tests-file' , type = Path , required = True )
1291
1362
worker_parser .add_argument ('--failfast' , action = 'store_true' )
1292
1363
1364
+ # merge-tags-from-report command declaration
1365
+ merge_tags_parser = subparsers .add_parser ('merge-tags-from-report' , help = "Merge tags from automated retagger" )
1366
+ merge_tags_parser .set_defaults (main = main_merge_tags )
1367
+ merge_tags_parser .add_argument ('platform' )
1368
+ merge_tags_parser .add_argument ('report_path' )
1369
+
1293
1370
# run the appropriate command
1294
1371
args = parent_parser .parse_args ()
1295
1372
args .main (args )
0 commit comments