fix(parity): keep saved probe selections compatible with the library split

Co-Authored-By: Codex <[email protected]>
This commit is contained in:
[email protected]andCodex committed 2026-11-01 12:17:53 +01:00
1 parent 42a5981e94
commit 452910ffce
6 files changed
+64

No files matched your search

+22
View File
@@ -0,0 +1,22 @@
from pathlib import Path
import unittest
from unittest import mock
from tools import interaction_lab
class CargoExampleTests(unittest.TestCase):
def test_client_probe_uses_the_reusable_library_package(self):
with mock.patch.object(interaction_lab.subprocess, 'run') as run:
interaction_lab.cargo_example('horizon-client', 'menu_fixture', Path('cases.txt'), profile='fast')
run.assert_called_once_with(
['cargo', 'run', '-q', '-j3', '--profile', 'fast', '-p', 'horizon-client-lib',
'--example', 'client_probe', '--', 'menu_fixture', 'cases.txt'],
check=True, cwd=interaction_lab.lab.ROOT)
def test_other_probe_packages_are_unchanged(self):
with mock.patch.object(interaction_lab.subprocess, 'run') as run:
interaction_lab.cargo_example('horizon-world', 'loot_probe', 42)
run.assert_called_once_with(
['cargo', 'run', '-q', '-j3', '-p', 'horizon-world', '--example', 'world_probe', '--', 'loot_probe', '42'],
check=True, cwd=interaction_lab.lab.ROOT)
+3
View File
@@ -138,6 +138,7 @@ class ReachTests(unittest.TestCase):
'[[example]]\nname = "client_probe"\npath = "../a/examples/probe/main.rs"\n',
"crates/a/src/shared.rs": "pub mod used;\n",
"crates/a/src/lib.rs": "pub fn game_only() {}\n",
"crates/a/Cargo.toml": '[package]\nname = "a"\nautobins = false\nautoexamples = false\n',
"crates/a/examples/probe/main.rs": "extern crate b as legacy;\nfn main() { legacy::used::helper(); }\n",
"crates/b/src/main.rs": "compile_error!(\"disabled auto-bin\");\n",
"crates/b/examples/ignored.rs": "compile_error!(\"disabled auto-example\");\n",
@@ -148,6 +149,8 @@ class ReachTests(unittest.TestCase):
self.assertIn((self.root / "crates/a/examples/probe/main.rs").resolve(), roots)
self.assertNotIn((self.root / "crates/b/src/main.rs").resolve(), roots)
self.assertTrue(graph.is_library("crates/a/src/shared.rs"))
self.assertEqual(graph.entries([["example", "client_probe", "dev", ["-p", "a"], True]],
{"crates/a/examples/probe/main.rs"}), {"crates/a/examples/probe/main.rs"})
reached = graph.reachable({"crates/a/examples/probe/main.rs"},
{"crates/a/examples/probe/main.rs", "crates/a/src/shared.rs", "crates/a/src/used.rs"})
self.assertIn("crates/a/src/used.rs", reached)
+24
View File
@@ -30,6 +30,19 @@ class ParityRunnerTests(unittest.TestCase):
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_follows_a_moved_target_without_losing_features(self):
selection = ["-p", "horizon-client", "--no-default-features", "--features", "horizon-client/rng-trace"]
artifacts = [["example", "client_probe", "fast", selection, True],
["bin", "horizon-ctl", "fast", selection, False]]
packages = {("example", "client_probe"): "horizon-client-lib", ("bin", "horizon-ctl"): "horizon-client-lib"}
commands = parity.prebuild_commands(artifacts, packages)
self.assertEqual(len(commands), 2)
for command in commands:
self.assertEqual(command[:5], ["cargo", "build", "--locked", "--profile", "fast"])
self.assertEqual(command[5:5 + len(selection)], selection)
self.assertEqual(command[5 + len(selection):7 + len(selection)], ["-p", "horizon-client-lib"])
self.assertEqual(artifacts[0][3], selection)
def test_prebuild_repeats_each_harness_selection(self):
artifacts = [
*parity.artifacts_of(["cargo", "build", "--release", "-p", "horizon-world", "--example", "a"]),
@@ -578,5 +591,16 @@ class WorkspaceManifestTests(unittest.TestCase):
optional = {"crates/horizon-client/Cargo.toml": self.CLIENT + '\n[features]\nfast = ["wgpu/x"]\n'}
self.assertEqual(self.verdict({"crates/horizon-client/Cargo.toml": self.CLIENT}, optional), set())
def test_external_feature_sources_are_not_discarded(self):
with tempfile.TemporaryDirectory() as temp, mock.patch.object(parity, "ROOT", Path(temp)):
root = Path(temp)
(root / "crates/core").mkdir(parents=True)
(root / "shared").mkdir()
(root / "crates/core/Cargo.toml").write_text(
'[package]\nname = "core"\n[lib]\npath = "../../shared/lib.rs"\n'
'[features]\nextra = []\n')
(root / "shared/lib.rs").write_text('#[cfg(feature = "extra")]\nfn extra() {}\n')
self.assertFalse(parity.feature_sources_changed("core", "extra", {"crates/core/Cargo.toml"}))
def test_without_a_base_everything_counts(self):
self.assertEqual(parity.workspace_only_manifests({"Cargo.lock"}, None), set())
+2
View File
@@ -51,6 +51,8 @@ def cargo_example(package, example, *args, profile=None):
if profile:
command += ['--profile', profile]
probe = package.removeprefix('horizon-') + '_probe' # each crate's probe example: client_probe, world_probe
if package == 'horizon-client':
package = 'horizon-client-lib'
command += ['-p', package, '--example', probe, '--', example, *map(str, args)]
subprocess.run(command, check=True, cwd=lab.ROOT)
+8
View File
@@ -644,6 +644,11 @@ def prebuild_commands(artifacts, packages=None):
if len(artifact) < 5:
continue
kind, name, profile, selection, examples = artifact
owner = (packages or {}).get((kind, name))
selected = [value for flag, value in zip(selection, selection[1:]) if flag == "-p"]
if owner and selected and owner not in selected:
# A recorded target may have moved packages; retain its feature flags and old selections.
selection = [*selection, "-p", owner]
targets = groups.setdefault((profile, tuple(selection), examples), [])
target = [f"--{kind}", name] if name else [f"--{kind}"]
if target not in targets:
@@ -1351,6 +1356,9 @@ def feature_sources_changed(crate, feature, changed):
break
else:
return False
if any(not root.is_relative_to(manifest.parent.resolve()) for root in parity_reach.crate_roots(manifest.parent)):
# A directory-only scan cannot prove that shared external sources changed too.
return False
table = re.search(r"^\[features\]\n(.*?)(?=^\[|\Z)", text, re.M | re.S)
implies = {name: re.findall(r'"([^"]+)"', value) for name, value in
re.findall(r'^([\w-]+)\s*=\s*\[([^\]]*)\]', table.group(1) if table else "", re.M)}
+5
View File
@@ -260,6 +260,11 @@ class SourceGraph:
packages = [value for flag, value in zip(selection, selection[1:]) if flag == "-p"] or \
[manifest.parent.name for manifest in sorted((self.root / "crates").glob("*/Cargo.toml"))]
roots = set()
if name and not any(name in self.targets(self.root / "crates" / package) for package in packages):
owners = [manifest.parent.name for manifest in (self.root / "crates").glob("*/Cargo.toml")
if self.targets(manifest.parent).get(name, (None,))[0] == kind.rstrip("s")]
if len(owners) == 1:
packages = [*packages, owners[0]]
for package in packages:
crate = self.root / "crates" / package
targets = self.targets(crate)