Merge branch 'worktree-agent-ae3652f34db9ac0b4' into fix/flow-debugger-arch-review
This commit is contained in:
@@ -89,6 +89,17 @@ public class InstanceActorTraceArmTests : TestKit, IDisposable
|
||||
Assert.True(ExpectMsg<SetStaticAttributeResponse>(TimeSpan.FromSeconds(5)).Success);
|
||||
}
|
||||
|
||||
/// <summary>Same as <see cref="Write"/> but the DCL rejects the write, so the arm must be dropped.</summary>
|
||||
private void FailingWrite(IActorRef actor, TestProbe dcl, string instance, Guid? source, bool trace)
|
||||
{
|
||||
actor.Tell(new SetStaticAttributeCommand("c", instance, "Processed", "true", DateTimeOffset.UtcNow,
|
||||
SourceExecutionId: source, Trace: trace));
|
||||
var write = ExpectWrite(dcl);
|
||||
Assert.Equal(Tag, write.TagPath);
|
||||
dcl.Reply(new WriteTagResponse(write.CorrelationId, false, "rejected", DateTimeOffset.UtcNow));
|
||||
Assert.False(ExpectMsg<SetStaticAttributeResponse>(TimeSpan.FromSeconds(5)).Success);
|
||||
}
|
||||
|
||||
private static AttributeValueChanged Echo(IActorRef actor, TestProbe subscriber, object? value)
|
||||
{
|
||||
actor.Tell(new TagValueUpdate("PLC", Tag, value, QualityCode.Good, DateTimeOffset.UtcNow));
|
||||
@@ -123,6 +134,54 @@ public class InstanceActorTraceArmTests : TestKit, IDisposable
|
||||
Assert.False(published.Trace);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Arch-review F6: the arm is recorded when the write is ISSUED, not when the DCL acknowledges
|
||||
/// it. A write the device rejects must drop the arm, otherwise the next unrelated subscription
|
||||
/// update on that attribute is stamped with the debugger's execution id for the rest of the
|
||||
/// 30 s window — the Flow Debugger would draw a "device echo" of a write that never landed.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TracedDataWrite_ThatFails_DisarmsTheAttribute_SoTheNextEchoIsUnstamped()
|
||||
{
|
||||
var (actor, dcl, sub) = Create("P6");
|
||||
|
||||
FailingWrite(actor, dcl, "P6", Guid.NewGuid(), trace: true);
|
||||
var published = Echo(actor, sub, "unrelated");
|
||||
|
||||
Assert.Null(published.SourceExecutionId);
|
||||
Assert.False(published.Trace);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A failed write must only drop ITS OWN arm — a newer traced write that succeeded in the
|
||||
/// meantime owns the arm and keeps it (the disarm is keyed on the writer's execution id).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void FailedWrite_DoesNotDisarm_AnArmTakenByALaterWriter()
|
||||
{
|
||||
var (actor, dcl, sub) = Create("P7");
|
||||
var loser = Guid.NewGuid();
|
||||
var winner = Guid.NewGuid();
|
||||
|
||||
// Issue the traced write that will fail, but hold its DCL reply back. The reply goes to the
|
||||
// Ask's temporary ref, so capture it now — a later Reply() would answer the second write.
|
||||
actor.Tell(new SetStaticAttributeCommand("c1", "P7", "Processed", "true", DateTimeOffset.UtcNow,
|
||||
SourceExecutionId: loser, Trace: true));
|
||||
var first = ExpectWrite(dcl);
|
||||
var firstAsker = dcl.LastSender;
|
||||
|
||||
// A second traced write lands and succeeds — it now owns the arm.
|
||||
Write(actor, dcl, "P7", winner, trace: true);
|
||||
|
||||
// Only now does the first write fail; its disarm must not steal the winner's arm.
|
||||
firstAsker.Tell(new WriteTagResponse(first.CorrelationId, false, "rejected", DateTimeOffset.UtcNow), dcl.Ref);
|
||||
Assert.False(ExpectMsg<SetStaticAttributeResponse>(TimeSpan.FromSeconds(5)).Success);
|
||||
|
||||
var published = Echo(actor, sub, "echo");
|
||||
Assert.Equal(winner, published.SourceExecutionId);
|
||||
Assert.True(published.Trace);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Arm_IsConsumedOnce()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user