5034e4557d
Disk: clear_large_outputs only looked at .local directories named after the harness, so the 174 harnesses writing to rust-<name>-parity kept every dump (86 GB in one runner checkout, 13 GB for state_provider alone). It now clears those too, keeps vanilla outputs only up to 256 MB (larger ones cost more disk than rerunning their oracle), and handles shard names. collision, world and geometry dropped one mkdtemp run directory per run (11 GB): each run now removes the earlier ones. Time: harnesses inherited the runner's stdin, a pipe on GitHub's runner, and parity_shim never reuses an oracle run whose stdin is a pipe, so oracle reuse never worked on CI. Harnesses now get /dev/null. CI: parity.yml falls back to the head's own selection when the names main's `--list` printed don't match its summary count. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
317 lines
20 KiB
Python
317 lines
20 KiB
Python
from pathlib import Path
|
|
import os
|
|
import tempfile
|
|
import unittest
|
|
from unittest import mock
|
|
|
|
from tools import parity
|
|
|
|
|
|
class ParityRunnerTests(unittest.TestCase):
|
|
def test_dep_info_keeps_escaped_spaces(self):
|
|
with tempfile.TemporaryDirectory() as temp:
|
|
binary = Path(temp) / "probe"
|
|
Path(temp, "probe.d").write_text(f"{binary}: /a/x.rs /b/with\\ space.rs\n\n/a/x.rs:\n")
|
|
self.assertEqual(parity.dep_info(binary), ["/a/x.rs", "/b/with space.rs"])
|
|
|
|
def test_artifacts_follow_profile_and_stop_at_program_arguments(self):
|
|
self.assertEqual(parity.artifacts_of(["cargo", "build", "--release", "--example", "a", "--bin", "b"]),
|
|
[["example", "a", "release", [], True], ["bin", "b", "release", [], True]])
|
|
self.assertEqual(parity.artifacts_of(["cargo", "run", "--profile", "fast", "--example", "c", "--", "--example", "x"]),
|
|
[["example", "c", "fast", [], True]])
|
|
self.assertEqual(parity.artifacts_of(["cargo", "build", "--example", "d"]), [["example", "d", "dev", [], True]])
|
|
|
|
def test_artifacts_keep_the_package_and_feature_selection(self):
|
|
self.assertEqual(parity.artifacts_of(["cargo", "build", "--locked", "--profile=fast", "-j", "4", "--package",
|
|
"horizon-client", "--bins", "-F", "x"]),
|
|
[["bins", "", "fast", ["-p", "horizon-client", "--features", "x"], False]])
|
|
self.assertEqual(parity.artifacts_of(["cargo", "build", "--release", "-p", "horizon-world", "--bin=horizon-world"]),
|
|
[["bin", "horizon-world", "release", ["-p", "horizon-world"], False]])
|
|
|
|
def test_prebuild_repeats_each_harness_selection(self):
|
|
artifacts = [
|
|
*parity.artifacts_of(["cargo", "build", "--release", "-p", "horizon-world", "--example", "a"]),
|
|
*parity.artifacts_of(["cargo", "build", "--release", "-p", "horizon-world", "--example", "b"]),
|
|
*parity.artifacts_of(["cargo", "build", "--release", "-p", "horizon-world", "--bin", "horizon-world"]),
|
|
*parity.artifacts_of(["cargo", "build", "--release", "--bin", "horizon-world"]),
|
|
*parity.artifacts_of(["cargo", "run", "--profile", "fast", "-p", "horizon-client", "--example", "c"]),
|
|
*parity.artifacts_of(["cargo", "build", "--release", "-p", "horizon-world", "--example", "renamed"]),
|
|
["example", "old", "release"], # recorded before selections were kept: its own crate's -p
|
|
["example", "unknown", "release"], # no longer in the workspace: left to its harness
|
|
]
|
|
packages = {("example", "a"): "horizon-world", ("example", "b"): "horizon-world", ("example", "c"): "horizon-client",
|
|
("bin", "horizon-world"): "horizon-world", ("example", "old"): "horizon-world"}
|
|
self.assertEqual(parity.prebuild_commands(artifacts, packages), [
|
|
["cargo", "build", "--locked", "--profile", "fast", "-p", "horizon-client", "--example", "c"],
|
|
["cargo", "build", "--locked", "--profile", "release", "--bin", "horizon-world"],
|
|
["cargo", "build", "--locked", "--profile", "release", "-p", "horizon-world", "--bin", "horizon-world"],
|
|
["cargo", "build", "--locked", "--profile", "release", "-p", "horizon-world", "--example", "a", "--example", "b",
|
|
"--example", "old"],
|
|
])
|
|
|
|
def test_a_probe_binary_depends_only_on_the_probes_it_ran(self):
|
|
commands = [["/t/release/examples/world_probe", "a_fixture", "/dir"],
|
|
["cargo", "run", "-p", "horizon-client", "--example", "client_probe", "--", "b_fixture"]]
|
|
self.assertEqual(parity.probes_run(commands), {"a_fixture", "b_fixture"})
|
|
with tempfile.TemporaryDirectory() as temp, mock.patch.object(parity, "ROOT", Path(temp)):
|
|
probe = Path(temp, "crates/w/examples/probe")
|
|
(probe / "support").mkdir(parents=True)
|
|
(probe / "a_fixture.rs").write_text('mod integration { include!("d_fixture.rs"); }')
|
|
(probe / "d_fixture.rs").write_text("use crate::support::shared as s;")
|
|
(probe / "b_fixture.rs").write_text("use crate::e_fixture;")
|
|
for name in ("c_fixture.rs", "dispatch.rs", "e_fixture.rs", "main.rs", "support/shared.rs", "support/other.rs"):
|
|
(probe / name).write_text("")
|
|
paths = [f"crates/w/examples/probe/{name}" for name in ("a_fixture.rs", "b_fixture.rs", "c_fixture.rs",
|
|
"d_fixture.rs", "dispatch.rs", "e_fixture.rs", "main.rs",
|
|
"support/other.rs", "support/shared.rs")]
|
|
self.assertEqual(parity.only_probes_run(["crates/w/src/lib.rs", *paths], {"a_fixture", "b_fixture"}),
|
|
["crates/w/src/lib.rs", *paths[:2], *paths[3:6], paths[8]])
|
|
self.assertEqual(parity.only_probes_run(paths, set()), paths)
|
|
|
|
def test_probe_names_are_each_crates_own_target(self):
|
|
self.assertEqual(parity.probes_run([
|
|
["C:/t/debug/examples/client_probe.exe", "a_fixture"],
|
|
["cargo", "run", "--release", "--example=world_probe", "--", "b_fixture", "x"],
|
|
["/t/release/examples/probe", "old_fixture"], # the shared name before 2026-10-24
|
|
["cargo", "run", "--example", "probe", "--", "old_fixture"],
|
|
["/t/release/examples/mod_port_probe", "not_a_fixture"],
|
|
["cargo", "run", "--example", "mod_port_probe", "--", "not_a_fixture"],
|
|
["cargo", "build", "--example", "world_probe"],
|
|
]), {"a_fixture", "b_fixture"})
|
|
|
|
def test_records_of_the_shared_probe_name_build_their_crates_probe(self):
|
|
record = {"dependencies": ["crates/horizon-world/examples/probe/a_fixture.rs", "crates/horizon-world/src/lib.rs"],
|
|
"artifacts": [["example", "probe", "release", ["-p", "horizon-client"], True],
|
|
["example", "probe", "release"], # no selection: the crate of its probe sources
|
|
["example", "probe", "release", ["-p", "horizon-core", "-p", "horizon-world"], True],
|
|
["bin", "horizon-world", "release", ["-p", "horizon-world"], False]]}
|
|
artifacts = parity.recorded_artifacts(record)
|
|
self.assertEqual(artifacts, [["example", "client_probe", "release", ["-p", "horizon-client"], True],
|
|
["example", "world_probe", "release"],
|
|
["example", "world_probe", "release", ["-p", "horizon-core", "-p", "horizon-world"], True],
|
|
["bin", "horizon-world", "release", ["-p", "horizon-world"], False]])
|
|
packages = {("example", "client_probe"): "horizon-client", ("example", "world_probe"): "horizon-world",
|
|
("bin", "horizon-world"): "horizon-world"}
|
|
self.assertEqual(parity.prebuild_commands(artifacts, packages), [
|
|
["cargo", "build", "--locked", "--profile", "release", "-p", "horizon-client", "--example", "client_probe"],
|
|
["cargo", "build", "--locked", "--profile", "release", "-p", "horizon-core", "-p", "horizon-world",
|
|
"--example", "world_probe"],
|
|
["cargo", "build", "--locked", "--profile", "release", "-p", "horizon-world", "--bin", "horizon-world"],
|
|
["cargo", "build", "--locked", "--profile", "release", "-p", "horizon-world", "--example", "world_probe"],
|
|
])
|
|
self.assertEqual(parity.recorded_artifacts({"artifacts": [["example", "probe", "release"]]}),
|
|
[["example", "probe", "release"]]) # no crate to tell: left to its harness
|
|
|
|
def test_java_sources_include_classes_pulled_from_the_sourcepath(self):
|
|
with tempfile.TemporaryDirectory() as temp:
|
|
sources, classes = Path(temp, "src"), Path(temp, "classes")
|
|
(sources / "p").mkdir(parents=True)
|
|
(classes / "p").mkdir(parents=True)
|
|
for name in ("Oracle", "Helper"):
|
|
(sources / "p" / f"{name}.java").write_text("")
|
|
for name in ("Oracle", "Helper$Inner"):
|
|
(classes / "p" / f"{name}.class").write_bytes(b"")
|
|
found = parity.java_sources(["javac", "-sourcepath", str(sources), "-d", str(classes), str(sources / "p/Oracle.java")])
|
|
self.assertEqual(sorted(Path(path).name for path in found), ["Helper.java", "Oracle.java", "Oracle.java"])
|
|
|
|
def test_a_pass_goes_stale_when_a_dependency_changes(self):
|
|
with tempfile.TemporaryDirectory() as temp, mock.patch.object(parity, "ROOT", Path(temp)):
|
|
source = Path(temp, "a.rs")
|
|
source.write_text("one")
|
|
state = {"harnesses": {"x": {"status": "pass", "dependencies": ["a.rs"]}}, "hashes": {}}
|
|
hasher = parity.Hasher(state["hashes"])
|
|
state["harnesses"]["x"]["key"] = parity.key(state["harnesses"]["x"], hasher, "env")
|
|
self.assertFalse(parity.stale("x", state, hasher, "env"))
|
|
self.assertTrue(parity.stale("x", state, hasher, "other toolchain"))
|
|
source.write_text("two!")
|
|
self.assertTrue(parity.stale("x", state, hasher, "env"))
|
|
|
|
def test_a_change_affects_the_harnesses_that_depend_on_it(self):
|
|
with tempfile.TemporaryDirectory() as temp, mock.patch.object(parity, "ROOT", Path(temp)), \
|
|
mock.patch.object(parity, "SHARED", Path(temp, "shared")):
|
|
found = {name: Path(temp, "tools", f"test_{name}_parity.py") for name in ("food", "light", "new")}
|
|
records = {"food": {"dependencies": ["crates/a.rs", "tools/lab.py"]},
|
|
"light": {"dependencies": ["crates/lib.rs#keep:light"]}}
|
|
self.assertEqual(parity.affected_harnesses(found, records, {"crates/a.rs"}), {"food", "new"})
|
|
self.assertEqual(parity.affected_harnesses(found, records, {"crates/lib.rs"}), {"light", "new"})
|
|
self.assertEqual(parity.affected_harnesses(found, records, {"tools/test_light_parity.py"}), {"light", "new"})
|
|
self.assertEqual(parity.affected_harnesses(found, records, {"Cargo.lock"}), {"food", "light", "new"})
|
|
self.assertEqual(parity.affected_harnesses(found, records, {"README.md"}), {"new"})
|
|
|
|
def test_platform_specific_harnesses_are_skipped_elsewhere(self):
|
|
config = {"linux_only": {"options": "libm"}}
|
|
with mock.patch.object(parity.sys, "platform", "linux"):
|
|
self.assertEqual(parity.platform_skips(config), {})
|
|
with mock.patch.object(parity.sys, "platform", "darwin"):
|
|
self.assertEqual(parity.platform_skips(config), {"options": "libm"})
|
|
|
|
def test_a_pass_from_another_worktree_counts_only_for_the_same_content(self):
|
|
with tempfile.TemporaryDirectory() as temp:
|
|
elsewhere, here, shared = Path(temp, "a"), Path(temp, "b"), Path(temp, "shared")
|
|
for root in (elsewhere, here):
|
|
root.mkdir()
|
|
(root / "x.rs").write_text("same")
|
|
with mock.patch.object(parity, "SHARED", shared):
|
|
with mock.patch.object(parity, "ROOT", elsewhere):
|
|
record = {"status": "pass", "dependencies": ["x.rs"], "artifacts": []}
|
|
record["key"] = parity.key(record, parity.Hasher({}), "env")
|
|
parity.publish("x", record)
|
|
with mock.patch.object(parity, "ROOT", here):
|
|
state = {"harnesses": {}, "hashes": {}}
|
|
self.assertFalse(parity.stale("x", state, parity.Hasher(state["hashes"]), "env"))
|
|
self.assertTrue(state["harnesses"]["x"]["shared"])
|
|
(here / "x.rs").write_text("changed")
|
|
state = {"harnesses": {}, "hashes": {}}
|
|
self.assertTrue(parity.stale("x", state, parity.Hasher(state["hashes"]), "env"))
|
|
|
|
|
|
class RustScopeTests(unittest.TestCase):
|
|
"""PARITY_RUST_SCOPE drops Rust sources only when nothing in the scope can reach them."""
|
|
|
|
def crate(self, temp, files):
|
|
root = Path(temp)
|
|
(root / "crates/w/src").mkdir(parents=True)
|
|
(root / "crates/w/examples").mkdir(parents=True)
|
|
(root / "crates/w/Cargo.toml").write_text("")
|
|
for name, text in files.items():
|
|
(root / name).parent.mkdir(parents=True, exist_ok=True)
|
|
(root / name).write_text(text)
|
|
return root
|
|
|
|
FILES = {
|
|
"crates/w/src/lib.rs": "pub mod a;\npub mod b;\n/// The other one.\npub mod c;\npub const V: i32 = 1;\n",
|
|
"crates/w/src/a.rs": 'pub struct Tag;\npub fn f() -> i32 { let s = "crate::c::x"; crate::V }\n'
|
|
"mod tests { fn g() { super::super::b::h(); } }\n",
|
|
"crates/w/src/b.rs": "pub fn h() {}\n",
|
|
"crates/w/src/c.rs": "pub struct Other;\nimpl std::fmt::Display for Other { }\nimpl<R: Copy> Foo for R {}\n",
|
|
"crates/w/examples/probe.rs": "use w::a::f;\nfn main() { f(); }\n",
|
|
}
|
|
|
|
def narrow(self, files, scope):
|
|
with tempfile.TemporaryDirectory() as temp, mock.patch.object(parity, "ROOT", self.crate(temp, files)):
|
|
return parity.narrow_rust_sources(sorted(files), scope)
|
|
|
|
def test_a_closed_scope_drops_the_rest(self):
|
|
kept, note = self.narrow(self.FILES, ["crates/w/src/a.rs", "crates/w/src/b.rs"])
|
|
self.assertIsNone(note)
|
|
self.assertEqual(kept, ["crates/w/examples/probe.rs", "crates/w/src/a.rs", "crates/w/src/b.rs",
|
|
"crates/w/src/lib.rs#keep:a,b"])
|
|
|
|
def test_references_out_of_the_scope_keep_everything(self):
|
|
files = dict(self.FILES, **{"crates/w/src/b.rs": "pub fn h() { crate::c::Other; }\n"})
|
|
kept, note = self.narrow(files, ["crates/w/src/a.rs", "crates/w/src/b.rs"])
|
|
self.assertIn("crate::c::Other", note)
|
|
self.assertEqual(kept, sorted(files))
|
|
|
|
def test_impls_elsewhere_for_scope_types_keep_everything(self):
|
|
files = dict(self.FILES, **{"crates/w/src/c.rs": "impl From<i32> for crate::a::Tag { }\n"})
|
|
self.assertIn("Tag", self.narrow(files, ["crates/w/src/a.rs", "crates/w/src/b.rs"])[1])
|
|
|
|
def test_new_modules_in_the_crate_root_do_not_count(self):
|
|
keep = {"a", "b"}
|
|
root = self.FILES["crates/w/src/lib.rs"]
|
|
self.assertEqual(parity.crate_root_without_modules(root, keep),
|
|
parity.crate_root_without_modules(root + "#[cfg(test)]\nmod d;\n", keep))
|
|
self.assertNotEqual(parity.crate_root_without_modules(root, keep),
|
|
parity.crate_root_without_modules(root + "pub const W: i32 = 2;\n", keep))
|
|
|
|
def test_rust_code_blanks_literals_and_comments(self):
|
|
code = parity.rust_code('let a = "{ crate::x }"; // crate::y\nlet c = \'{\'; fn f<\'a>() { r#"}"# }')
|
|
self.assertNotIn("crate", code)
|
|
self.assertEqual(code.count("{"), code.count("}"))
|
|
self.assertIn("<'a>", code)
|
|
|
|
|
|
class ShardTests(unittest.TestCase):
|
|
def test_a_sharded_harness_runs_as_numbered_entries(self):
|
|
with tempfile.TemporaryDirectory() as temp, mock.patch.object(parity, "ROOT", Path(temp)):
|
|
(Path(temp) / "tools").mkdir()
|
|
(Path(temp) / "tools/test_x_parity.py").write_text("PARITY_SHARDS = 3\n")
|
|
(Path(temp) / "tools/test_y_parity.py").write_text("")
|
|
self.assertEqual(sorted(parity.harnesses()), ["x#1", "x#2", "x#3", "y"])
|
|
self.assertEqual(parity.SHARDS["x#2"], "2/3")
|
|
self.assertEqual(parity.base_name("x#2"), "x")
|
|
|
|
|
|
@unittest.skipIf(os.name == "nt", "oracle PATH shimming uses Unix executable symlink semantics")
|
|
class OracleReuseTests(unittest.TestCase):
|
|
"""tools/parity_shim.py replays a run only when its inputs and outputs are as recorded."""
|
|
|
|
def setUp(self):
|
|
import os, subprocess, sys
|
|
self.temp = tempfile.TemporaryDirectory()
|
|
root = Path(self.temp.name)
|
|
self.bin, self.shim = root / "bin", root / "shim"
|
|
self.bin.mkdir(); self.shim.mkdir()
|
|
# A fake oracle: copies its input to its output and counts its runs.
|
|
fake = self.bin / "java"
|
|
fake.write_text(f"#!{sys.executable}\nimport sys, pathlib\n"
|
|
"runs = pathlib.Path(sys.argv[1]).parent / 'runs'\n"
|
|
"runs.write_text(str(int(runs.read_text() or 0) + 1) if runs.exists() else '1')\n"
|
|
"pathlib.Path(sys.argv[2]).write_text(pathlib.Path(sys.argv[1]).read_text())\nprint('done')\n")
|
|
fake.chmod(0o755)
|
|
(self.shim / "java").symlink_to(Path(parity.__file__).with_name("parity_shim.py"))
|
|
self.input, self.output = root / "work" / "in.txt", root / "work" / "out.txt"
|
|
self.input.parent.mkdir()
|
|
self.input.write_text("a")
|
|
python = Path(sys.executable).parent
|
|
self.env = dict(os.environ, PATH=f"{self.shim}:{self.bin}:{python}", PARITY_SHIM_REAL_PATH=str(self.bin),
|
|
PARITY_SHIM_CACHE=str(root / "cache"))
|
|
self.subprocess = subprocess
|
|
|
|
def tearDown(self):
|
|
self.temp.cleanup()
|
|
|
|
def oracle(self):
|
|
result = self.subprocess.run(["java", str(self.input), str(self.output)], env=self.env, capture_output=True, text=True,
|
|
stdin=self.subprocess.DEVNULL, check=True)
|
|
self.assertEqual(result.stdout, "done\n")
|
|
return int((self.input.parent / "runs").read_text())
|
|
|
|
def test_reuses_until_an_input_or_output_changes(self):
|
|
self.assertEqual(self.oracle(), 1)
|
|
self.assertEqual(self.oracle(), 1) # replayed, stdout included
|
|
self.input.write_text("a") # same content, new mtime: still reusable
|
|
self.assertEqual(self.oracle(), 1)
|
|
self.input.write_text("b")
|
|
self.assertEqual(self.oracle(), 2)
|
|
self.assertEqual(self.output.read_text(), "b")
|
|
self.output.write_text("tampered")
|
|
self.assertEqual(self.oracle(), 3)
|
|
self.assertEqual(self.output.read_text(), "b")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|
|
|
|
|
|
class LargeOutputTests(unittest.TestCase):
|
|
def test_a_pass_clears_only_its_own_directory(self):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
local, state = Path(tmp) / ".local", Path(tmp) / "state"
|
|
own, shared = local / "decorated-region-parity", local / "chunk-generation-parity"
|
|
for directory in (own, shared):
|
|
directory.mkdir(parents=True)
|
|
(directory / "dump.txt").write_bytes(b"x" * 16)
|
|
trace = {"commands": [["java", "-d", str(shared / "classes")], ["probe", str(own / "dump.txt")]]}
|
|
with mock.patch.multiple(parity, LOCAL=local, STATE_DIR=state, LARGE_OUTPUT=8):
|
|
parity.outputs_listing("decorated_region", fresh=True)
|
|
parity.clear_large_outputs("decorated_region", trace)
|
|
self.assertFalse((own / "dump.txt").exists())
|
|
# chunk_generation may still be writing there.
|
|
self.assertTrue((shared / "dump.txt").exists())
|
|
|
|
def test_rust_named_directories_and_big_oracle_outputs_go_too(self):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
local, state = Path(tmp) / ".local", Path(tmp) / "state"
|
|
own = local / "rust-state-provider-parity"
|
|
own.mkdir(parents=True)
|
|
for name, size in (("rust.txt", 16), ("server.txt", 16), ("client.txt", 64), ("report.json", 4)):
|
|
(own / name).write_bytes(b"x" * size)
|
|
trace = {"commands": [["java", "Oracle", str(own)], ["probe", "fixture", str(own)]]}
|
|
with mock.patch.multiple(parity, LOCAL=local, STATE_DIR=state, LARGE_OUTPUT=8, KEPT_ORACLE_OUTPUT=32):
|
|
listing = parity.outputs_listing("state_provider#2", fresh=True)
|
|
listing.write_text(f"{own / 'server.txt'}\n{own / 'client.txt'}\n")
|
|
parity.clear_large_outputs("state_provider#2", trace)
|
|
self.assertEqual(sorted(path.name for path in own.iterdir()), ["report.json", "server.txt"])
|