874b6f20c2
Основа — Horizon (MIT). История апстрима не переносится: этот репозиторий ведёт свою.
142 lines
7.3 KiB
Python
142 lines
7.3 KiB
Python
"""tools/parity_reach.py: which workspace sources a harness's probe can reach."""
|
|
from pathlib import Path
|
|
import tempfile
|
|
import textwrap
|
|
import unittest
|
|
|
|
from tools import parity, parity_reach
|
|
|
|
PROBE = [["example", "world_probe", "release", ["-p", "a"], True]]
|
|
|
|
|
|
def workspace(root, files):
|
|
for path, text in files.items():
|
|
(root / path).parent.mkdir(parents=True, exist_ok=True)
|
|
(root / path).write_text(textwrap.dedent(text), encoding="utf-8")
|
|
|
|
|
|
class ReachTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.directory = tempfile.TemporaryDirectory()
|
|
self.root = Path(self.directory.name)
|
|
workspace(self.root, {
|
|
"crates/a/Cargo.toml": '[package]\nname = "a"\n\n[[example]]\nname = "world_probe"\npath = "examples/probe/main.rs"\n',
|
|
"crates/a/src/lib.rs": """
|
|
pub mod used;
|
|
pub mod unused;
|
|
mod methods;
|
|
mod display;
|
|
mod table;
|
|
#[cfg(test)]
|
|
mod tests;
|
|
""",
|
|
"crates/a/src/used.rs": """
|
|
pub struct Thing;
|
|
pub fn helper() -> u32 { 1 }
|
|
""",
|
|
# Same names as used.rs, but nothing names this module.
|
|
"crates/a/src/unused.rs": "pub struct Thing;\npub fn helper() -> u32 { 2 }\n",
|
|
"crates/a/src/methods.rs": """
|
|
use crate::used::Thing;
|
|
impl Thing {
|
|
pub fn method(&self) -> u32 { 3 }
|
|
pub fn never_called(&self) -> u32 { 4 }
|
|
}
|
|
""",
|
|
"crates/a/src/display.rs": """
|
|
impl std::fmt::Display for crate::used::Thing {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "thing") }
|
|
}
|
|
""",
|
|
"crates/a/src/table.rs": """
|
|
macro_rules! table { () => { pub struct Table; }; }
|
|
table!();
|
|
""",
|
|
"crates/a/src/tests.rs": "use crate::used::Thing;\nimpl Thing { pub fn method_in_tests(&self) {} }\n",
|
|
"crates/a/examples/probe/main.rs": "mod dispatch;\nmod p;\n",
|
|
"crates/a/examples/probe/dispatch.rs": "pub fn args() {}\n",
|
|
"crates/a/examples/probe/p.rs": """
|
|
use a::used::{helper, Thing};
|
|
pub fn main() { println!("{} {} {}", helper(), Thing.method(), Thing); }
|
|
""",
|
|
})
|
|
self.dependencies = sorted(str(path.relative_to(self.root)) for path in self.root.rglob("*") if path.is_file())
|
|
self.dependencies.remove("crates/a/examples/probe/main.rs") # parity.only_probes_run leaves it out
|
|
|
|
def tearDown(self):
|
|
self.directory.cleanup()
|
|
|
|
def narrowed(self, changed=(), artifacts=PROBE):
|
|
graph = parity_reach.SourceGraph(self.root, parity.rust_code)
|
|
return parity_reach.narrow(self.dependencies, artifacts, graph, set(changed))
|
|
|
|
def test_keeps_what_the_probe_reaches(self):
|
|
kept = {path for path, (whole, _) in self.narrowed().items() if whole}
|
|
self.assertIn("crates/a/src/used.rs", kept)
|
|
self.assertIn("crates/a/src/methods.rs", kept) # Thing.method()
|
|
self.assertIn("crates/a/src/display.rs", kept) # std's Display for a reached type
|
|
self.assertIn("crates/a/Cargo.toml", kept)
|
|
self.assertNotIn("crates/a/src/unused.rs", kept) # its names match, but nothing names its module
|
|
self.assertNotIn("crates/a/src/table.rs", kept)
|
|
self.assertNotIn("crates/a/src/tests.rs", kept) # compiled into tests only
|
|
|
|
def test_module_parents_count_through_their_declarations(self):
|
|
self.assertEqual(self.narrowed()["crates/a/src/lib.rs"], (False, {"used", "methods", "display"}))
|
|
text = (self.root / "crates/a/src/lib.rs").read_text()
|
|
self.assertEqual(parity_reach.declarations(text, {"used"}, False), "pub mod used;")
|
|
self.assertEqual(parity_reach.declarations(text, {"tests"}, False), "#[cfg(test)]\nmod tests;")
|
|
# Adding or dropping a module beside the reached ones changes neither view.
|
|
other = text.replace("mod table;", "mod table2;\nmod more;")
|
|
self.assertEqual(parity_reach.declarations(other, {"used"}, False), "pub mod used;")
|
|
self.assertEqual(parity_reach.declarations(other, {"used"}, True), parity_reach.declarations(text, {"used"}, True))
|
|
self.assertNotEqual(parity_reach.declarations(text + "pub fn f() {}\n", {"used"}, True),
|
|
parity_reach.declarations(text, {"used"}, True))
|
|
|
|
def test_a_new_file_counts_once_it_is_reached(self):
|
|
(self.root / "crates/a/src/lib.rs").write_text("pub mod used;\nmod extra;\n")
|
|
workspace(self.root, {"crates/a/src/extra.rs": "impl crate::used::Thing { pub fn method(&self) -> u32 { 5 } }\n"})
|
|
self.assertIn("crates/a/src/extra.rs", self.narrowed(changed={"crates/a/src/extra.rs"}))
|
|
|
|
def test_a_deleted_dependency_counts(self):
|
|
(self.root / "crates/a/src/unused.rs").unlink()
|
|
self.assertIn("crates/a/src/unused.rs", self.narrowed())
|
|
|
|
def test_a_search_told_to_stop_still_keeps_the_file_it_stopped_at(self):
|
|
graph = parity_reach.SourceGraph(self.root, parity.rust_code)
|
|
kept = parity_reach.narrow(self.dependencies, PROBE, graph, {"crates/a/src/used.rs"}, {"crates/a/src/used.rs"})
|
|
self.assertEqual(kept["crates/a/src/used.rs"], (True, set()))
|
|
self.assertFalse(graph.results, "a partial search is not remembered")
|
|
# A stop file outside the harness's sources doesn't end its search.
|
|
full = parity_reach.narrow(self.dependencies, PROBE, graph, (), {"crates/b/src/lib.rs"})
|
|
self.assertIn("crates/a/src/display.rs", full)
|
|
self.assertTrue(graph.results)
|
|
|
|
def test_a_module_moved_to_a_new_crate_counts_through_its_reexport(self):
|
|
# used.rs moves to crate b after the harness was recorded; a re-exports it under its old path. Crate c is
|
|
# a dependency nothing reached names: its items stay out even where their names match.
|
|
(self.root / "crates/a/src/used.rs").unlink()
|
|
workspace(self.root, {
|
|
"crates/a/Cargo.toml": (self.root / "crates/a/Cargo.toml").read_text()
|
|
+ '\n[dependencies]\nb = { path = "../b" }\nc = { path = "../c" }\n',
|
|
"crates/a/src/lib.rs": "pub use b::used;\npub mod unused;\nmod methods;\nmod display;\n",
|
|
"crates/b/Cargo.toml": '[package]\nname = "b"\n',
|
|
"crates/b/src/lib.rs": "pub mod used;\n",
|
|
"crates/b/src/used.rs": "pub struct Thing;\npub fn helper() -> u32 { 1 }\n",
|
|
"crates/c/Cargo.toml": '[package]\nname = "c"\n',
|
|
"crates/c/src/lib.rs": "pub fn helper() -> u32 { 3 }\n",
|
|
})
|
|
changed = {"crates/b/src/used.rs", "crates/c/src/lib.rs"}
|
|
self.assertEqual(parity_reach.unrecorded(self.root, self.dependencies, changed), changed)
|
|
kept = self.narrowed(changed=changed)
|
|
self.assertIn("crates/b/src/used.rs", kept)
|
|
self.assertNotIn("crates/c/src/lib.rs", kept)
|
|
|
|
def test_unknown_probes_keep_everything(self):
|
|
self.assertEqual(set(self.narrowed(artifacts=[])), set(self.dependencies))
|
|
self.assertEqual(set(self.narrowed(artifacts=[["example", "missing", "release", ["-p", "a"], True]])),
|
|
set(self.dependencies))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|