Skip to content

Commit a7f1b5b

Browse files
committed
Add merge-tags-from-report subcommand for retagger
1 parent 9173ecf commit a7f1b5b

File tree

1 file changed

+102
-25
lines changed
  • graalpython/com.oracle.graal.python.test/src

1 file changed

+102
-25
lines changed

graalpython/com.oracle.graal.python.test/src/runner.py

Lines changed: 102 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@
7676
# noinspection PyUnresolvedReferences
7777
PLATFORM_KEYS.add('native_image' if __graalpython__.is_native else 'jvm')
7878

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})
8081

8182

8283
class Logger:
@@ -125,6 +126,9 @@ class TestId:
125126
def __repr__(self):
126127
return f'{self.test_file}::{self.test_name}'
127128

129+
def normalized(self):
130+
return TestId(self.test_file.resolve(), self.test_name)
131+
128132
@classmethod
129133
def from_str(cls, s: str):
130134
test_file, _, test_id = s.partition('::')
@@ -419,6 +423,9 @@ def run_tests(self, tests: list['TestSuite']):
419423
def generate_mx_report(self, path: str):
420424
report_data = []
421425
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
422429
match result.status:
423430
case TestStatus.SUCCESS | TestStatus.EXPECTED_FAILURE:
424431
status = 'PASSED'
@@ -441,27 +448,42 @@ def generate_tags(self, append=False):
441448
by_file[result.test_id.test_file].append(result)
442449
for test_file, results in by_file.items():
443450
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+
)
449459
write_tags(test_file, tags)
450460

451461

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())
465487

466488

467489
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:
976998
else:
977999
test_id = TestId.from_test_case(test_file.path, test)
9781000
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:
9801002
keep_tests.append(test)
9811003
collected_tests.append(Test(test_id, test_file))
9821004
test_suite._tests = keep_tests
@@ -1107,14 +1129,27 @@ class Tag:
11071129
test_id: TestId
11081130
keys: frozenset[str]
11091131

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)
11121145

11131146
def __str__(self):
11141147
return f'{self.test_id.test_name} @ {",".join(sorted(self.keys))}'
11151148

11161149

11171150
def read_tags(test_file: TestFile) -> list[Tag]:
1151+
# To make them easily comparable
1152+
test_path = test_file.path.resolve()
11181153
tag_file = test_file.get_tag_file()
11191154
tags = []
11201155
if tag_file.exists():
@@ -1126,7 +1161,7 @@ def read_tags(test_file: TestFile) -> list[Tag]:
11261161
if not keys:
11271162
log(f'WARNING: invalid tag {test}: missing platform keys')
11281163
tags.append(Tag(
1129-
TestId(test_file.path, test),
1164+
TestId(test_path, test),
11301165
frozenset(keys.split(',')),
11311166
))
11321167
return tags
@@ -1169,6 +1204,38 @@ def result_factory(suite):
11691204
pickle.dump(data, f)
11701205

11711206

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+
11721239
def get_bool_env(name: str):
11731240
return os.environ.get(name, '').lower() in ('true', '1')
11741241

@@ -1179,7 +1246,11 @@ def main():
11791246
subparsers = parent_parser.add_subparsers()
11801247

11811248
# 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+
)
11831254
run_parser.set_defaults(main=main_run)
11841255
if is_mx_graalpytest:
11851256
# mx graalpytest takes this option, but it forwards --help here, so pretend we take it
@@ -1282,14 +1353,20 @@ def main():
12821353
)
12831354

12841355
# worker command declaration
1285-
worker_parser = subparsers.add_parser('worker')
1356+
worker_parser = subparsers.add_parser('worker', help="Internal command for subprocess workers")
12861357
worker_parser.set_defaults(main=main_worker)
12871358
group = worker_parser.add_mutually_exclusive_group()
12881359
group.add_argument('--pipe-fd', type=int)
12891360
group.add_argument('--result-file', type=Path)
12901361
worker_parser.add_argument('--tests-file', type=Path, required=True)
12911362
worker_parser.add_argument('--failfast', action='store_true')
12921363

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+
12931370
# run the appropriate command
12941371
args = parent_parser.parse_args()
12951372
args.main(args)

0 commit comments

Comments
 (0)