From 3a076e27814b719683a581d96496e1ed4dc515f7 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Tue, 18 Aug 2026 09:00:42 -0400 Subject: [PATCH] Constrain core validation memory usage --- docs/tools-reference.md | 5 +++-- tools/test_validate.py | 25 +++++++++++++++++++++ tools/validate.py | 48 +++++++++++++++++++++++++++++++++++++---- 3 files changed, 72 insertions(+), 6 deletions(-) diff --git a/docs/tools-reference.md b/docs/tools-reference.md index 58ce70f..7a45945 100644 --- a/docs/tools-reference.md +++ b/docs/tools-reference.md @@ -26,7 +26,7 @@ whenever a tool's inputs/outputs change.** | 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 the 502 .NET cases without the `Workspace` trait, checks generated opcode references, and runs `git diff --check`. `workspace` adds the 88 explicitly traited installed-data/native-oracle cases, corpus-derived global generation, 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-/`; 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 ` · `--game-root ` · `--verbose` · `--fail-fast` | sources + selected toolchain/game/corpus prerequisites → console summary + ⚙ `build/validation/validate-*/.log` | +| `validate.py` | Layered project validation front door. `core` regenerates/runtime-checks opcode metadata, lints canonical registries, runs pure Python tooling tests and the source-only .NET cases without the `Workspace` trait, checks generated opcode references, and runs `git diff --check`. Its .NET solution restore, CLI build, and test-project execution are separate low-memory gates; restore/build parallelism, compiler/MSBuild servers, server GC, and debug-symbol emission are disabled so constrained hosted runners do not kill Roslyn with exit 137. `workspace` adds the explicitly traited installed-data/native-oracle cases, corpus-derived global generation, 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-/`; 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 ` · `--game-root ` · `--verbose` · `--fail-fast` | sources + selected toolchain/game/corpus prerequisites → console summary + ⚙ `build/validation/validate-*/.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 | Levels are cumulative around `core`: `workspace` means core+workspace-corpus, `runtime` means core+Godot, @@ -45,7 +45,8 @@ access; failure logs are retained for seven days. The private remote's default a `develop`; pushes and pull requests targeting it select the gate. The rewritten repository is published there, and the first actual Linux/Gitea core run succeeded on 2026-08-03 before the later source-history sanitation; its tree-equivalent rewritten commit is `524ea74`. The hosted gate also succeeded on the sanitized lineage at -`d673652` on 2026-08-03. +`d673652` on 2026-08-03. The same driver now owns the constrained-memory .NET environment rather than relying on +workflow-only settings, so local and hosted core/workspace runs share the Roslyn memory contract. The artifact wrapper is `.gitea/workflows/linux-release-build.yml`. It runs independently from the fast core gate on `develop` pushes, manual dispatch, and `v*` tag pushes. Independent Linux and Windows jobs use the same diff --git a/tools/test_validate.py b/tools/test_validate.py index 23c0f1a..b194dcb 100644 --- a/tools/test_validate.py +++ b/tools/test_validate.py @@ -70,6 +70,8 @@ class EnvironmentResolutionTests(unittest.TestCase): class GatePlanTests(unittest.TestCase): def test_core_contains_no_workspace_or_runtime_gates(self) -> None: keys = {gate.key for gate in validate.build_gate_plan("core")} + self.assertIn("engine-restore", keys) + self.assertIn("engine-cli-build", keys) self.assertIn("engine-tests", keys) self.assertIn("python-test_validate", keys) self.assertNotIn("sys4-corpus-validate", keys) @@ -77,6 +79,26 @@ class GatePlanTests(unittest.TestCase): self.assertNotIn("age-cli-sweep", keys) engine = next(gate for gate in validate.build_gate_plan("core") if gate.key == "engine-tests") self.assertIn("Category!=Workspace", engine.command) + self.assertIn("--no-restore", engine.command) + self.assertIn("-m:1", engine.command) + self.assertIn("-p:BuildInParallel=false", engine.command) + self.assertEqual(validate.LOW_MEMORY_DOTNET_ENVIRONMENT, engine.environment) + self.assertIn( + "engine/Age.Engine.Tests/Age.Engine.Tests.csproj", + engine.command, + ) + cli = next( + gate for gate in validate.build_gate_plan("core") if gate.key == "engine-cli-build" + ) + self.assertIn("engine/Age.Cli/Age.Cli.csproj", cli.command) + self.assertIn("--no-restore", cli.command) + self.assertIn("-m:1", cli.command) + self.assertEqual(validate.LOW_MEMORY_DOTNET_ENVIRONMENT, cli.environment) + restore = next( + gate for gate in validate.build_gate_plan("core") if gate.key == "engine-restore" + ) + self.assertIn("--disable-parallel", restore.command) + self.assertEqual(validate.LOW_MEMORY_DOTNET_ENVIRONMENT, restore.environment) def test_workspace_extends_core(self) -> None: core = {gate.key for gate in validate.build_gate_plan("core")} @@ -90,6 +112,9 @@ class GatePlanTests(unittest.TestCase): if gate.key == "engine-workspace-tests" ) self.assertIn("Category=Workspace", installed.command) + self.assertIn("--no-restore", installed.command) + self.assertIn("-m:1", installed.command) + self.assertEqual(validate.LOW_MEMORY_DOTNET_ENVIRONMENT, installed.environment) def test_runtime_requires_resolved_paths(self) -> None: with self.assertRaisesRegex(ValueError, "resolved Godot"): diff --git a/tools/validate.py b/tools/validate.py index 0ba9984..9ad7c5a 100644 --- a/tools/validate.py +++ b/tools/validate.py @@ -55,6 +55,12 @@ WORKSPACE_GENERATED_INPUTS = ( paths.BUILD / "callscript-names.json", paths.BUILD / "scjump-decisions.json", ) +LOW_MEMORY_DOTNET_ENVIRONMENT = ( + ("DOTNET_CLI_USE_MSBUILD_SERVER", "0"), + ("MSBUILDDISABLENODEREUSE", "1"), + ("DOTNET_gcServer", "0"), + ("DOTNET_GCConserveMemory", "9"), +) @dataclass(frozen=True) @@ -156,15 +162,45 @@ def build_gate_plan( ) for test in CORE_TESTS ) + gates.append( + Gate( + "engine-restore", + ".NET engine restore", + ( + "dotnet", "restore", "engine/AgeEngine.sln", "--nologo", + "--verbosity", "minimal", "--disable-parallel", + "-p:RestoreDisableParallel=true", + ), + 300, + LOW_MEMORY_DOTNET_ENVIRONMENT, + ) + ) + gates.append( + Gate( + "engine-cli-build", + ".NET engine CLI build", + ( + "dotnet", "build", "engine/Age.Cli/Age.Cli.csproj", "--no-restore", "--nologo", + "--verbosity", "minimal", "-m:1", "-p:UseSharedCompilation=false", + "-p:BuildInParallel=false", "-p:DebugType=None", "-p:DebugSymbols=false", + ), + 300, + LOW_MEMORY_DOTNET_ENVIRONMENT, + ) + ) gates.append( Gate( "engine-tests", ".NET engine core tests", ( - "dotnet", "test", "engine/AgeEngine.sln", "--nologo", "--verbosity", "minimal", - "--filter", "Category!=Workspace", "-p:UseSharedCompilation=false", + "dotnet", "test", "engine/Age.Engine.Tests/Age.Engine.Tests.csproj", + "--no-restore", "--nologo", + "--verbosity", "minimal", "--filter", "Category!=Workspace", "-m:1", + "-p:UseSharedCompilation=false", "-p:BuildInParallel=false", + "-p:DebugType=None", "-p:DebugSymbols=false", ), 300, + LOW_MEMORY_DOTNET_ENVIRONMENT, ) ) @@ -174,10 +210,14 @@ def build_gate_plan( "engine-workspace-tests", ".NET installed-data and native-oracle tests", ( - "dotnet", "test", "engine/AgeEngine.sln", "--nologo", "--verbosity", "minimal", - "--filter", "Category=Workspace", "-p:UseSharedCompilation=false", + "dotnet", "test", "engine/Age.Engine.Tests/Age.Engine.Tests.csproj", + "--no-restore", "--nologo", + "--verbosity", "minimal", "--filter", "Category=Workspace", "-m:1", + "-p:UseSharedCompilation=false", "-p:BuildInParallel=false", + "-p:DebugType=None", "-p:DebugSymbols=false", ), 300, + LOW_MEMORY_DOTNET_ENVIRONMENT, ) ) gates.append(