merge(agent-c): row 100 — SiteScriptCompileCache becomes an instance-scoped sealed class owned by the ScriptCompilationService DI singleton (zero production Clear() callers existed, so Clear() is deleted outright); row-91 serialized collection relaxed and delta assertions restored to exact counts; new registration test pins the load-bearing singleton lifetime

This commit is contained in:
Joseph Doherty
2026-08-17 06:20:17 -04:00
10 changed files with 263 additions and 181 deletions
@@ -29,13 +29,17 @@ namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests.Actors;
/// answer its deployer instead of leaving it to Ask-timeout, and commands for DIFFERENT
/// instances must not block each other.</para>
///
/// <para>Shares the <c>SiteScriptCompileCache</c> collection because the batch pre-warm test
/// asserts on that process-wide cache's hit counter.</para>
/// <para>The batch pre-warm test asserts on the compile cache's hit counter. As of register
/// row 100 that cache is owned by the <c>ScriptCompilationService</c> instance this fixture
/// builds — the one it threads into the Deployment Manager and, through it, into every Instance
/// Actor — so the counter is private to the test. The old
/// <c>[Collection("SiteScriptCompileCache")]</c> serialisation against the two
/// <c>Scripts/</c> test classes is therefore gone: nothing here is shared any more.</para>
/// </summary>
[Collection("SiteScriptCompileCache")]
public class DeploymentWarmThenGateTests : TestKit, IDisposable
{
private readonly SiteStorageService _storage;
private readonly SiteScriptCompileCache _compileCache;
private readonly ScriptCompilationService _compilationService;
private readonly SharedScriptLibrary _sharedScriptLibrary;
private readonly TestLocalDb _localDb;
@@ -45,8 +49,9 @@ public class DeploymentWarmThenGateTests : TestKit, IDisposable
_localDb = TestLocalDb.CreateTemp("dm-warm-gate-test");
_storage = new SiteStorageService(_localDb.Db, NullLogger<SiteStorageService>.Instance);
_storage.InitializeAsync().GetAwaiter().GetResult();
_compileCache = new SiteScriptCompileCache();
_compilationService = new ScriptCompilationService(
NullLogger<ScriptCompilationService>.Instance);
NullLogger<ScriptCompilationService>.Instance, _compileCache);
_sharedScriptLibrary = new SharedScriptLibrary(
_compilationService, NullLogger<SharedScriptLibrary>.Instance);
}
@@ -247,21 +252,23 @@ public class DeploymentWarmThenGateTests : TestKit, IDisposable
await _storage.StoreDeployedConfigAsync(
"BatchTwo", ConfigJson("BatchTwo", sharedCode), "d2", "h2", true);
SiteScriptCompileCache.Clear();
// Baseline rather than Assert.Equal(0, Hits): Hits is a process-global counter and test
// classes outside this collection compile scripts in parallel, so a foreign hit can land
// between the Clear() and the read (deferred-work register row 91). Everything below is
// asserted as a delta over this baseline.
var hitsBefore = SiteScriptCompileCache.Hits;
// No Clear() and no baseline subtraction: this cache is the fixture's own and nothing
// has compiled into it yet (register rows 91/100 — the counter used to be a
// process-global that other test classes bumped in parallel, forcing a delta-over-
// baseline reading). Only the Deployment Manager below writes to it.
Assert.Equal(0, _compileCache.Hits);
CreateDeploymentManager();
AwaitAssert(() =>
{
// One compile, then repeated hits: the second pre-warm plus both PreStarts.
Assert.True(SiteScriptCompileCache.Hits - hitsBefore >= 3,
$"expected the pre-warmed body to be served from cache, saw " +
$"{SiteScriptCompileCache.Hits - hitsBefore} hits over baseline {hitsBefore}");
Assert.True(_compileCache.Hits >= 3,
$"expected the pre-warmed body to be served from cache, saw {_compileCache.Hits} hits");
}, TimeSpan.FromSeconds(20));
// …and exactly one entry for the one shared body: the hits above were the SAME compiled
// script being reused, not several bodies each compiled once.
Assert.Equal(1, _compileCache.Count);
}
}