test(runtime): red — apply-boundary compile-cache clear must be gated on a changed expression set (02/P7)
This commit is contained in:
+39
-11
@@ -345,24 +345,52 @@ public sealed class VirtualTagHostActorTests : RuntimeActorTestBase
|
||||
mux.LastSender.ShouldNotBe(firstChild);
|
||||
}
|
||||
|
||||
/// <summary>Wiring guard (arch-review 02/U3, theme #1): every ApplyVirtualTags calls
|
||||
/// <see cref="IScriptCacheOwner.ClearCompiledScripts"/> on the evaluator at the apply boundary.
|
||||
/// This is the load-bearing wire — the production evaluator roots one collectible ALC per compiled
|
||||
/// script, and without this per-generation clear they accrete across deploys. A pure unit test of
|
||||
/// the evaluator can't catch a missing call here; this asserts the host actor actually makes it.</summary>
|
||||
/// <summary>Wiring guard (arch-review 02/U3 + 02/P7, theme #1): the host actor calls
|
||||
/// <see cref="IScriptCacheOwner.ClearCompiledScripts"/> at the apply boundary — but, per P7, ONLY
|
||||
/// when the deployed expression set actually changed. The clear stays the load-bearing wire (the
|
||||
/// production evaluator roots one collectible ALC per compiled script), yet a byte-identical
|
||||
/// redeploy — or a rename / add / remove that leaves the expression set unchanged — no longer forces
|
||||
/// a full recompilation. This pins that the host makes the call, and gates it correctly.
|
||||
///
|
||||
/// <para>The final <c>ClearCount</c> is read at a deterministic sync point: after all applies are
|
||||
/// queued we drive one EvaluationResult through the actor and wait for the bridged publish — the
|
||||
/// FIFO mailbox guarantees every prior apply is processed by then, so the counter is final (a
|
||||
/// polling AwaitAssert on a monotonic counter would pass transiently as it climbs, hiding the
|
||||
/// unconditional-clear bug).</para></summary>
|
||||
[Fact]
|
||||
public void ApplyVirtualTags_clears_the_evaluator_compile_cache_each_generation()
|
||||
public void ApplyVirtualTags_clears_the_compile_cache_only_when_the_expression_set_changes()
|
||||
{
|
||||
var publish = CreateTestProbe();
|
||||
var evaluator = new CacheOwningStubEvaluator();
|
||||
var host = Sys.ActorOf(VirtualTagHostActor.Props(publish.Ref, mux: null, evaluator));
|
||||
|
||||
host.Tell(new VirtualTagHostActor.ApplyVirtualTags(new[] { Plan("vt-1", "eq-1", "speed-rpm") }));
|
||||
AwaitAssert(() => evaluator.ClearCount.ShouldBe(1));
|
||||
// 1) First generation: previous set {} → {E1} differs → clears.
|
||||
host.Tell(new VirtualTagHostActor.ApplyVirtualTags(
|
||||
new[] { PlanWithRefs("vt-1", "eq-1", "speed-rpm", "E1", "a") }));
|
||||
// 2) Byte-identical redeploy: {E1} → {E1} → NO clear (the P7 win).
|
||||
host.Tell(new VirtualTagHostActor.ApplyVirtualTags(
|
||||
new[] { PlanWithRefs("vt-1", "eq-1", "speed-rpm", "E1", "a") }));
|
||||
// 3) Edited expression: {E1} → {E2} differs → clears.
|
||||
host.Tell(new VirtualTagHostActor.ApplyVirtualTags(
|
||||
new[] { PlanWithRefs("vt-1", "eq-1", "speed-rpm", "E2", "a") }));
|
||||
// 4) Add a second vtag SHARING the same expression E2: {E2} → {E2} → NO clear.
|
||||
host.Tell(new VirtualTagHostActor.ApplyVirtualTags(new[]
|
||||
{
|
||||
PlanWithRefs("vt-1", "eq-1", "speed-rpm", "E2", "a"),
|
||||
PlanWithRefs("vt-2", "eq-1", "torque", "E2", "a"),
|
||||
}));
|
||||
// 5) Remove that second vtag — expression E2 still deployed by vt-1: {E2} → {E2} → NO clear.
|
||||
host.Tell(new VirtualTagHostActor.ApplyVirtualTags(
|
||||
new[] { PlanWithRefs("vt-1", "eq-1", "speed-rpm", "E2", "a") }));
|
||||
|
||||
// A second apply generation clears again — proves it's per-generation, not one-shot.
|
||||
host.Tell(new VirtualTagHostActor.ApplyVirtualTags(new[] { Plan("vt-1", "eq-1", "speed-rpm") }));
|
||||
AwaitAssert(() => evaluator.ClearCount.ShouldBe(2));
|
||||
// Sync point: a result for the still-mapped vt-1 bridges to a publish; when it lands, every
|
||||
// apply above has been processed.
|
||||
host.Tell(new VirtualTagActor.EvaluationResult("vt-1", 1.0, DateTime.UtcNow, CorrelationId.NewId()));
|
||||
publish.ExpectMsg<OpcUaPublishActor.AttributeValueUpdate>();
|
||||
|
||||
// Exactly two clears: the first generation and the E1→E2 edit. The identical redeploy and the
|
||||
// shared-expression add/remove did not clear.
|
||||
evaluator.ClearCount.ShouldBe(2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user