mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 02:56:17 -10:00
Build every profile from the catalog, fragments and handwritten signatures. Keep xtask for filling handwritten placeholders, and stamping for catalog data. Stage launcher-owned declarations as build input, retaining shared injection validation for older source trees with committed generated traits.
166 lines
7.5 KiB
Python
166 lines
7.5 KiB
Python
import importlib.util
|
|
import json
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
SPEC = importlib.util.spec_from_file_location(
|
|
"stamp_native_hooks", Path(__file__).resolve().parents[1] / "tools/stamp_native_hooks.py"
|
|
)
|
|
stamp = importlib.util.module_from_spec(SPEC)
|
|
SPEC.loader.exec_module(stamp)
|
|
|
|
|
|
class StampTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.tmp = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.tmp.cleanup)
|
|
self.root = Path(self.tmp.name)
|
|
self.original = stamp.ROOT
|
|
stamp.ROOT = self.root
|
|
self.addCleanup(setattr, stamp, "ROOT", self.original)
|
|
subprocess.run(["git", "init", "-q", "-b", "master", str(self.root)], check=True)
|
|
for path, content in {
|
|
stamp.SOURCE: "//! API 7 adds old.\n//!\npub(crate) const NATIVE_HOOKS_API: u32 = 7;\n",
|
|
stamp.DOCS: "API 7 adds old.\n\n## What the launcher does\n",
|
|
stamp.CATALOG: '{"hooks": []}\n',
|
|
}.items():
|
|
(self.root / path).parent.mkdir(parents=True, exist_ok=True)
|
|
(self.root / path).write_text(content)
|
|
(self.root / stamp.FRAGMENTS).mkdir(parents=True)
|
|
|
|
def git(self, *args):
|
|
subprocess.run(["git", "-C", str(self.root), "-c", "user.name=Test",
|
|
"-c", "[email protected]", *args],
|
|
check=True, capture_output=True)
|
|
|
|
def fragment(self, name):
|
|
path = self.root / stamp.FRAGMENTS / f"{name}.json"
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(json.dumps({
|
|
"hooks": [{"name": name, "target": f"target::{name}"}],
|
|
"rust_summary": f"{name}.",
|
|
"doc_summary": f'{name}; requires @API:{name}@.',
|
|
}))
|
|
return path
|
|
|
|
def test_merge_order_and_idempotence(self):
|
|
self.git("add", ".")
|
|
self.git("commit", "-qm", "base")
|
|
self.git("checkout", "-qb", "first")
|
|
self.fragment("z")
|
|
self.git("add", ".")
|
|
self.git("commit", "-qm", "first")
|
|
self.git("checkout", "-q", "master")
|
|
self.git("checkout", "-qb", "second")
|
|
self.fragment("a")
|
|
self.git("add", ".")
|
|
self.git("commit", "-qm", "second")
|
|
self.git("checkout", "-q", "master")
|
|
self.git("merge", "--no-ff", "-qm", "merge first", "first")
|
|
self.git("merge", "--no-ff", "-qm", "merge second", "second")
|
|
self.assertTrue(stamp.stamp(True))
|
|
self.assertFalse(stamp.stamp(False))
|
|
source = (self.root / stamp.SOURCE).read_text()
|
|
docs = (self.root / stamp.DOCS).read_text()
|
|
self.assertIn("API 8 adds z.", source)
|
|
self.assertIn("API 9 adds a.", source)
|
|
self.assertIn("API 8 adds z; requires 8.", docs)
|
|
self.assertIn("API 9 adds a; requires 9.", docs)
|
|
self.assertIn("NATIVE_HOOKS_API: u32 = 9;", source)
|
|
hooks = json.loads((self.root / stamp.CATALOG).read_text())["hooks"]
|
|
self.assertEqual([(h["name"], h["version"]) for h in hooks], [("a", 1), ("z", 1)])
|
|
self.assertFalse(stamp.stamp(False))
|
|
|
|
def test_helper_only_gets_a_number(self):
|
|
self.git("add", ".")
|
|
self.git("commit", "-qm", "base")
|
|
path = self.fragment("helper")
|
|
fragment = json.loads(path.read_text())
|
|
fragment["hooks"] = []
|
|
fragment["helpers"] = [{"name": "helper", "target": "target::helper"}]
|
|
path.write_text(json.dumps(fragment))
|
|
self.assertFalse(stamp.stamp(False))
|
|
self.assertIn("NATIVE_HOOKS_API: u32 = 8;", (self.root / stamp.SOURCE).read_text())
|
|
self.assertEqual(json.loads((self.root / stamp.CATALOG).read_text())["hooks"], [])
|
|
self.assertEqual(json.loads((self.root / stamp.CATALOG).read_text())["helpers"], fragment["helpers"])
|
|
|
|
def test_catalog_facts_travel_with_the_hook(self):
|
|
self.git("add", ".")
|
|
self.git("commit", "-qm", "base")
|
|
path = self.fragment("served")
|
|
fragment = json.loads(path.read_text())
|
|
contract = {"kind": "listen", "receiver": "game"}
|
|
fragment["hooks"][0].update(
|
|
{"side": "server", "cancellable": True, "mod_api": contract, "other": 1})
|
|
path.write_text(json.dumps(fragment))
|
|
self.assertFalse(stamp.stamp(False))
|
|
hooks = json.loads((self.root / stamp.CATALOG).read_text())["hooks"]
|
|
self.assertEqual(hooks, [{"name": "served", "version": 1, "target": "target::served",
|
|
"side": "server", "cancellable": True, "mod_api": contract}])
|
|
|
|
def test_markers_are_stamped_in_hand_written_api_types(self):
|
|
self.git("add", ".")
|
|
self.git("commit", "-qm", "base")
|
|
self.fragment("drops")
|
|
path = self.root / stamp.MOD_API
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text("/// Since API @API:drops@.\n")
|
|
self.assertFalse(stamp.stamp(False))
|
|
self.assertEqual(path.read_text(), "/// Since API 8.\n")
|
|
|
|
def test_stamping_leaves_stale_generated_copies_alone(self):
|
|
# Old checkouts may still have these files; only the generator's inputs are stamped now.
|
|
generated = (
|
|
"crates/horizon-client/src/mod_api/generated.rs",
|
|
"crates/horizon-client/src/mod_api/generated_host.rs",
|
|
"crates/horizon-client/src/game/mod_api_host_generated.rs",
|
|
"crates/horizon-client-render/src/gfx/native_render/generated.rs",
|
|
)
|
|
self.git("add", ".")
|
|
self.git("commit", "-qm", "base")
|
|
self.fragment("drops")
|
|
for name in generated:
|
|
path = self.root / name
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text("/// Since API @API:drops@.\n")
|
|
self.assertFalse(stamp.stamp(False))
|
|
self.assertIn("NATIVE_HOOKS_API: u32 = 8;", (self.root / stamp.SOURCE).read_text())
|
|
for name in generated:
|
|
self.assertEqual((self.root / name).read_text(), "/// Since API @API:drops@.\n")
|
|
|
|
def test_duplicate_fails_before_writes(self):
|
|
self.git("add", ".")
|
|
self.git("commit", "-qm", "base")
|
|
self.fragment("same")
|
|
(self.root / stamp.CATALOG).write_text('{"hooks": [{"name": "same"}]}')
|
|
before = (self.root / stamp.SOURCE).read_text()
|
|
with self.assertRaisesRegex(ValueError, "duplicate"):
|
|
stamp.stamp(False)
|
|
self.assertEqual((self.root / stamp.SOURCE).read_text(), before)
|
|
|
|
def test_the_repository_stamps_every_marker(self):
|
|
# A copy of this checkout's pending fragments and the files they stamp: stamping must
|
|
# succeed and leave no marker, or the stamp workflow fails or ships unnumbered docs.
|
|
repo = self.original
|
|
fragments = [p.relative_to(repo) for p in (repo / stamp.FRAGMENTS).glob("*.json")]
|
|
comments = {Path(c) for p in fragments for c in json.loads((repo / p).read_text()).get("comments", [])}
|
|
stamped = [stamp.SOURCE, stamp.DOCS, stamp.MOD_API, *comments]
|
|
for path in [stamp.CATALOG, *stamped, *fragments]:
|
|
if (repo / path).exists():
|
|
(self.root / path).parent.mkdir(parents=True, exist_ok=True)
|
|
shutil.copyfile(repo / path, self.root / path)
|
|
self.git("add", ".")
|
|
self.git("commit", "-qm", "base")
|
|
self.assertFalse(stamp.stamp(False))
|
|
left = {str(p): re.findall(r"@API:\w+@", (self.root / p).read_text())
|
|
for p in stamped if (self.root / p).exists()}
|
|
self.assertEqual({p: m for p, m in left.items() if m}, {})
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|