Skip to content

Commit e6d7af8

Browse files
Close parity review edge cases
1 parent 3f2c69e commit e6d7af8

3 files changed

Lines changed: 47 additions & 7 deletions

File tree

compat-test/compat_test.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,12 @@ def collected():
308308
kill_process_tree(proc)
309309
return -1, collected() + "\n<stopped: exception with no classic counterpart>", False
310310
time.sleep(poll_seconds)
311-
return proc.returncode, collected(), False
311+
output = collected()
312+
# A short-lived worker can write its final exception and exit between polls. Its
313+
# output is still a bridge-only failure and must not leave a score in the result.
314+
if abort_when(output):
315+
return -1, output + "\n<stopped: exception with no classic counterpart>", False
316+
return proc.returncode, output, False
312317

313318

314319
def _drain(stream, sink):
@@ -991,6 +996,17 @@ def should_run(entry, opts, registry=None, key=None):
991996
return False
992997

993998

999+
def retest_option_error(opts, todo=None):
1000+
"""Return an actionable invalid-retetest message, or None when selection is valid."""
1001+
if opts.repair and not opts.retest_cause:
1002+
return "--repair requires --retest-cause so it can be linked to a diagnosis."
1003+
if opts.retest_cause and not opts.repair:
1004+
return "--retest-cause requires --repair to preserve repair evidence."
1005+
if todo is not None and opts.retest_cause and not todo:
1006+
return f"No unresolved registry subject is diagnosed with cause: {opts.retest_cause}"
1007+
return None
1008+
1009+
9941010
def check_prerequisites(opts):
9951011
problems = []
9961012
for label, path in [
@@ -1633,8 +1649,9 @@ def division_setup(collection, opts):
16331649
def main():
16341650
opts = parse_args()
16351651

1636-
if opts.repair and not opts.retest_cause:
1637-
print("--repair requires --retest-cause so it can be linked to a diagnosis.", file=sys.stderr)
1652+
option_error = retest_option_error(opts)
1653+
if option_error:
1654+
print(option_error, file=sys.stderr)
16381655
return 2
16391656

16401657
if opts.conformance:
@@ -1677,6 +1694,10 @@ def main():
16771694
registry = load_registry(PARITY_REGISTRY_FILE)
16781695
todo = [(c, j) for c, j in jars
16791696
if should_run(state["robots"].get(f"{c}/{j.name}"), opts, registry, f"{c}/{j.name}")]
1697+
option_error = retest_option_error(opts, todo)
1698+
if option_error:
1699+
print(option_error, file=sys.stderr)
1700+
return 2
16801701
print(f"Found {len(jars)} jars; {len(jars) - len(todo)} already tested, "
16811702
f"{len(todo)} to test.")
16821703

compat-test/parity_registry.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,8 @@ def _migrate_subject(subject: dict) -> None:
137137
add_diagnosis(subject, legacy["cause"], legacy.get("owner"), "migrated")
138138

139139

140-
def _same_observation(observation: dict, entry: dict, manifest: dict) -> bool:
140+
def _same_observation(observation: dict, entry: dict, manifest: dict,
141+
source_identity: dict) -> bool:
141142
"""Recognize schema-1 observations whose ID predates per-observation identity."""
142143
return (
143144
observation.get("completed_at") == entry.get("completed_at")
@@ -149,6 +150,7 @@ def _same_observation(observation: dict, entry: dict, manifest: dict) -> bool:
149150
and observation.get("confirmation") == entry.get("confirmation")
150151
and observation.get("retest") == entry.get("retest")
151152
and observation.get("manifest") == manifest
153+
and observation.get("source_identity") == source_identity
152154
)
153155

154156

@@ -168,7 +170,8 @@ def sync_state(registry: dict, state: dict, collection_dir: Path, manifest: dict
168170
for observation in subject["observations"]:
169171
observation.setdefault("source_identity", subject["identity"])
170172
oid = observation_id(key, entry, manifest, identity)
171-
if any(observation["id"] == oid or _same_observation(observation, entry, manifest)
173+
if any(observation["id"] == oid or _same_observation(
174+
observation, entry, manifest, identity)
172175
for observation in subject["observations"]):
173176
continue
174177
subject["observations"].append({

compat-test/test_parity_registry.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import tempfile
44
import unittest
55
from pathlib import Path
6+
from types import SimpleNamespace
67

78

89
MODULE = Path(__file__).with_name("parity_registry.py")
@@ -97,8 +98,7 @@ def testHARN001_UnitPositive_ObservationKeepsJarIdentityWhenSameKeyChanges(self)
9798
data = {"schema_version": 1, "subjects": {}}
9899
registry.sync_state(data, state, root, {"bridge_commit": "one"})
99100
jar.write_bytes(b"replacement")
100-
state["robots"]["roborumble/a.Bot_1.0.jar"]["completed_at"] = "2026-09-10T00:00:00Z"
101-
registry.sync_state(data, state, root, {"bridge_commit": "two"})
101+
registry.sync_state(data, state, root, {"bridge_commit": "one"})
102102
observations = data["subjects"]["roborumble/a.Bot_1.0.jar"]["observations"]
103103
self.assertEqual(2, len(observations))
104104
self.assertNotEqual(observations[0]["source_identity"]["jar_sha256"],
@@ -133,6 +133,22 @@ def testC004_UnitNegative_ClassicWorkerExceptionDoesNotTriggerWatcher(self):
133133
"java.lang.IllegalStateException: classic equivalent\n"
134134
" at legacy.Bot.run(Bot.java:12)"))
135135

136+
def testC004_UnitPositive_ImmediateWorkerExitStillTriggersWatcher(self):
137+
watcher = harness.BridgeOnlyErrorWatcher([], [])
138+
returncode, output, timed_out = harness.run_java(
139+
[sys.executable, "-c", "print('java.lang.IllegalStateException: failure\\n at legacy.Bot.run(Bot.java:12)')"],
140+
Path.cwd(), timeout=5, abort_when=watcher, poll_seconds=0.1)
141+
self.assertEqual(-1, returncode)
142+
self.assertFalse(timed_out)
143+
self.assertTrue(watcher.triggered)
144+
self.assertIn("IllegalStateException", output)
145+
146+
def testHARN001_UnitNegative_CauseRetestRequiresRepairAndKnownSelection(self):
147+
missing_repair = SimpleNamespace(repair=None, retest_cause="lifecycle")
148+
unknown_cause = SimpleNamespace(repair="abc123", retest_cause="lifecycle")
149+
self.assertIn("requires --repair", harness.retest_option_error(missing_repair))
150+
self.assertIn("No unresolved", harness.retest_option_error(unknown_cause, []))
151+
136152

137153
if __name__ == "__main__":
138154
unittest.main()

0 commit comments

Comments
 (0)