Isolate Godot validation profile
This commit is contained in:
@@ -26,7 +26,7 @@ whenever a tool's inputs/outputs change.**
|
|||||||
|
|
||||||
| Tool | Purpose | Run | Reads → Writes |
|
| Tool | Purpose | Run | Reads → Writes |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `validate.py` | Layered project validation front door. `core` regenerates/runtime-checks opcode metadata, lints canonical registries, runs pure Python tooling tests and all .NET engine tests, checks generated opcode references, and runs `git diff --check`. `workspace` adds corpus-derived global generation, the real-data Python suites, full SYS4 decode, and Python RECOVER. `runtime` adds the Godot C# build and forced-portable threaded self-test. `full` combines all phases and adds the booted faithful-wait C# scene sweep. Selected prerequisites are strict: an unavailable game/corpus/Godot requirement fails before execution instead of becoming a green skip. Each gate has a timeout and UTF-8 log under `build/validation/validate-<timestamp>/`; the final table reports results/durations and a before/after Godot-process leak audit. | `validate.py` (defaults to `--level full`) · `--level core|workspace|runtime|full` · `--godot <console>` · `--game-root <install>` · `--verbose` · `--fail-fast` | sources + selected toolchain/game/corpus prerequisites → console summary + ⚙ `build/validation/validate-*/<gate>.log` |
|
| `validate.py` | Layered project validation front door. `core` regenerates/runtime-checks opcode metadata, lints canonical registries, runs pure Python tooling tests and all .NET engine tests, checks generated opcode references, and runs `git diff --check`. `workspace` adds corpus-derived global generation, the real-data Python suites, full SYS4 decode, and Python RECOVER. `runtime` adds the Godot C# build and forced-portable threaded self-test. `full` combines all phases and adds the booted faithful-wait C# scene sweep. Selected prerequisites are strict: an unavailable game/corpus/Godot requirement fails before execution instead of becoming a green skip. Each gate has a timeout and UTF-8 log under `build/validation/validate-<timestamp>/`; Godot receives an isolated validation-owned user-data/log root there so it cannot read or modify the developer's saves/settings. The final table reports results/durations and a before/after Godot-process leak audit. | `validate.py` (defaults to `--level full`) · `--level core|workspace|runtime|full` · `--godot <console>` · `--game-root <install>` · `--verbose` · `--fail-fast` | sources + selected toolchain/game/corpus prerequisites → console summary + ⚙ `build/validation/validate-*/<gate>.log` |
|
||||||
| `test_validate.py` | Pure tests for launcher-equivalent explicit/environment/PATH/conventional resolution precedence, invalid-explicit hard failure, level composition, and final gate ordering. | `test_validate.py` | temporary files only |
|
| `test_validate.py` | Pure tests for launcher-equivalent explicit/environment/PATH/conventional resolution precedence, invalid-explicit hard failure, level composition, and final gate ordering. | `test_validate.py` | temporary files only |
|
||||||
|
|
||||||
Levels are cumulative around `core`: `workspace` means core+workspace-corpus, `runtime` means core+Godot,
|
Levels are cumulative around `core`: `workspace` means core+workspace-corpus, `runtime` means core+Godot,
|
||||||
|
|||||||
@@ -90,11 +90,16 @@ class GatePlanTests(unittest.TestCase):
|
|||||||
def test_full_contains_every_phase(self) -> None:
|
def test_full_contains_every_phase(self) -> None:
|
||||||
fake_godot = Path("godot")
|
fake_godot = Path("godot")
|
||||||
fake_root = Path("game")
|
fake_root = Path("game")
|
||||||
keys = {gate.key for gate in validate.build_gate_plan("full", fake_godot, fake_root)}
|
state_root = Path("state")
|
||||||
|
plan = validate.build_gate_plan("full", fake_godot, fake_root, state_root)
|
||||||
|
keys = {gate.key for gate in plan}
|
||||||
self.assertIn("sys4-corpus-validate", keys)
|
self.assertIn("sys4-corpus-validate", keys)
|
||||||
self.assertIn("godot-selftest", keys)
|
self.assertIn("godot-selftest", keys)
|
||||||
self.assertIn("age-cli-sweep", keys)
|
self.assertIn("age-cli-sweep", keys)
|
||||||
self.assertEqual("diff-check", validate.build_gate_plan("full", fake_godot, fake_root)[-1].key)
|
self.assertEqual("diff-check", plan[-1].key)
|
||||||
|
selftest = next(gate for gate in plan if gate.key == "godot-selftest")
|
||||||
|
self.assertIn(str(state_root / "godot.log"), selftest.command)
|
||||||
|
self.assertIn(("APPDATA", str(state_root / "appdata")), selftest.environment)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ class Gate:
|
|||||||
label: str
|
label: str
|
||||||
command: tuple[str, ...]
|
command: tuple[str, ...]
|
||||||
timeout_seconds: int = 180
|
timeout_seconds: int = 180
|
||||||
|
environment: tuple[tuple[str, str], ...] = ()
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -124,6 +125,7 @@ def build_gate_plan(
|
|||||||
level: str,
|
level: str,
|
||||||
godot: Path | None = None,
|
godot: Path | None = None,
|
||||||
game_root: Path | None = None,
|
game_root: Path | None = None,
|
||||||
|
runtime_state_root: Path | None = None,
|
||||||
) -> list[Gate]:
|
) -> list[Gate]:
|
||||||
phases = selected_phases(level)
|
phases = selected_phases(level)
|
||||||
gates = [
|
gates = [
|
||||||
@@ -185,6 +187,13 @@ def build_gate_plan(
|
|||||||
if "runtime" in phases:
|
if "runtime" in phases:
|
||||||
if godot is None or game_root is None:
|
if godot is None or game_root is None:
|
||||||
raise ValueError("runtime validation requires resolved Godot and game-root paths")
|
raise ValueError("runtime validation requires resolved Godot and game-root paths")
|
||||||
|
runtime_root = runtime_state_root or paths.BUILD / "validation" / "runtime-user"
|
||||||
|
runtime_environment = (
|
||||||
|
("APPDATA", str(runtime_root / "appdata")),
|
||||||
|
("LOCALAPPDATA", str(runtime_root / "local-appdata")),
|
||||||
|
("XDG_DATA_HOME", str(runtime_root / "xdg-data")),
|
||||||
|
("XDG_CONFIG_HOME", str(runtime_root / "xdg-config")),
|
||||||
|
)
|
||||||
gates.extend((
|
gates.extend((
|
||||||
Gate(
|
Gate(
|
||||||
"godot-build",
|
"godot-build",
|
||||||
@@ -196,10 +205,12 @@ def build_gate_plan(
|
|||||||
"godot-selftest",
|
"godot-selftest",
|
||||||
"Godot threaded self-test",
|
"Godot threaded self-test",
|
||||||
(
|
(
|
||||||
str(godot), "--headless", "--path", str(REPO / "godot"), "--",
|
str(godot), "--headless", "--log-file", str(runtime_root / "godot.log"),
|
||||||
|
"--path", str(REPO / "godot"), "--",
|
||||||
"--selftest", "--game-root", str(game_root), "--text-backend", "portable",
|
"--selftest", "--game-root", str(game_root), "--text-backend", "portable",
|
||||||
),
|
),
|
||||||
180,
|
300,
|
||||||
|
runtime_environment,
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
|
|
||||||
@@ -274,6 +285,7 @@ def run_gate(gate: Gate, log_dir: Path, verbose: bool) -> GateResult:
|
|||||||
"DOTNET_CLI_TELEMETRY_OPTOUT": "1",
|
"DOTNET_CLI_TELEMETRY_OPTOUT": "1",
|
||||||
"DOTNET_NOLOGO": "1",
|
"DOTNET_NOLOGO": "1",
|
||||||
})
|
})
|
||||||
|
environment.update(dict(gate.environment))
|
||||||
popen_arguments = {
|
popen_arguments = {
|
||||||
"args": gate.command,
|
"args": gate.command,
|
||||||
"cwd": REPO,
|
"cwd": REPO,
|
||||||
@@ -401,7 +413,9 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
log_dir.mkdir(parents=True, exist_ok=True)
|
log_dir.mkdir(parents=True, exist_ok=True)
|
||||||
initial_godot = snapshot_godot_processes()
|
initial_godot = snapshot_godot_processes()
|
||||||
results: list[GateResult] = []
|
results: list[GateResult] = []
|
||||||
for gate in build_gate_plan(arguments.level, godot, game_root):
|
runtime_state_root = log_dir / "godot-user"
|
||||||
|
runtime_state_root.mkdir(parents=True, exist_ok=True)
|
||||||
|
for gate in build_gate_plan(arguments.level, godot, game_root, runtime_state_root):
|
||||||
result = run_gate(gate, log_dir, arguments.verbose)
|
result = run_gate(gate, log_dir, arguments.verbose)
|
||||||
results.append(result)
|
results.append(result)
|
||||||
if result.status == "FAIL" and arguments.fail_fast:
|
if result.status == "FAIL" and arguments.fail_fast:
|
||||||
|
|||||||
Reference in New Issue
Block a user