fix(core): resolve Low code-review findings (Core-004,008,009,010,011,012)
- Core-004: add ConfigureAwait(false) to DriverHost.RegisterAsync / UnregisterAsync / DisposeAsync. - Core-008: rewrite the BuildAddressSpaceAsync XML doc to correctly name the caller (OpcUaApplicationHost.PopulateAddressSpaces) that owns the per-driver isolation. - Core-009: snapshot DriverResilienceOptions once per non-idempotent write in CapabilityInvoker.ExecuteWriteAsync. - Core-010: switch DriverResilienceOptions.Resolve to TryGetValue with a diagnostic error message when a tier table is missing a capability. - Core-011: add an optional diagnostic callback to PermissionTrieBuilder so production callers can surface scope-path mismatches. - Core-012: correct the stale WedgeDetector ctor summary and add the Reconnecting row to DriverHealthReport's state matrix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -148,4 +148,66 @@ public sealed class CapabilityInvokerTests
|
||||
|
||||
builder.CachedPipelineCount.ShouldBe(2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Core-009 regression: ExecuteWriteAsync's non-idempotent branch must snapshot
|
||||
/// <c>_optionsAccessor</c> exactly once per call. Calling it multiple times allocates
|
||||
/// redundant options objects on the per-write hot path and creates a consistency hazard
|
||||
/// where an Admin edit mid-call could observe two different snapshots.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task ExecuteWriteAsync_NonIdempotent_Snapshots_Options_Once_Per_Call()
|
||||
{
|
||||
var options = new DriverResilienceOptions
|
||||
{
|
||||
Tier = DriverTier.A,
|
||||
CapabilityPolicies = new Dictionary<DriverCapability, CapabilityPolicy>
|
||||
{
|
||||
[DriverCapability.Write] = new(TimeoutSeconds: 2, RetryCount: 3, BreakerFailureThreshold: 5),
|
||||
},
|
||||
};
|
||||
var accessorCalls = 0;
|
||||
var invoker = new CapabilityInvoker(
|
||||
new DriverResiliencePipelineBuilder(),
|
||||
"drv-test",
|
||||
() => { Interlocked.Increment(ref accessorCalls); return options; });
|
||||
|
||||
await invoker.ExecuteWriteAsync(
|
||||
"host-1",
|
||||
isIdempotent: false,
|
||||
_ => ValueTask.FromResult(0),
|
||||
CancellationToken.None);
|
||||
|
||||
accessorCalls.ShouldBe(1,
|
||||
"ExecuteWriteAsync's non-idempotent branch must capture the options snapshot exactly once per call");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Core-009 regression — companion consistency assertion: the non-idempotent branch must
|
||||
/// not observe two different option snapshots even if the accessor's returned value changes
|
||||
/// between calls (simulating an Admin edit landing mid-flight). With a single snapshot the
|
||||
/// two derived values (<c>with</c> base + <c>Resolve(Write)</c>) come from the same options
|
||||
/// instance.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task ExecuteWriteAsync_NonIdempotent_Uses_Consistent_Options_Snapshot()
|
||||
{
|
||||
var a = new DriverResilienceOptions { Tier = DriverTier.A };
|
||||
var b = new DriverResilienceOptions { Tier = DriverTier.B };
|
||||
var alternating = new[] { a, b, a, b }.AsEnumerable().GetEnumerator();
|
||||
var invoker = new CapabilityInvoker(
|
||||
new DriverResiliencePipelineBuilder(),
|
||||
"drv-test",
|
||||
() => { alternating.MoveNext(); return alternating.Current; });
|
||||
|
||||
// If options is read twice, the with-expression and Resolve(Write) come from
|
||||
// different tier tables (A then B) — the resulting one-entry dictionary is
|
||||
// inconsistent with the snapshot used for the rest of the options. Single-snapshot
|
||||
// semantics guarantee the call sees a coherent view.
|
||||
await Should.NotThrowAsync(async () => await invoker.ExecuteWriteAsync(
|
||||
"host-1",
|
||||
isIdempotent: false,
|
||||
_ => ValueTask.FromResult(0),
|
||||
CancellationToken.None));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,4 +99,49 @@ public sealed class DriverResilienceOptionsTests
|
||||
options.Resolve(DriverCapability.Write).ShouldBe(
|
||||
DriverResilienceOptions.GetTierDefaults(DriverTier.A)[DriverCapability.Write]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Core-010 regression: every <see cref="DriverCapability"/> value must successfully resolve
|
||||
/// under every tier with a default <see cref="DriverResilienceOptions"/>. A future
|
||||
/// enum-only addition that forgets to update <c>GetTierDefaults</c> would otherwise blow up
|
||||
/// on the hot path with <see cref="KeyNotFoundException"/>.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData(DriverTier.A)]
|
||||
[InlineData(DriverTier.B)]
|
||||
[InlineData(DriverTier.C)]
|
||||
public void Resolve_Returns_NonNull_Policy_For_Every_Capability(DriverTier tier)
|
||||
{
|
||||
var options = new DriverResilienceOptions { Tier = tier };
|
||||
|
||||
foreach (var capability in Enum.GetValues<DriverCapability>())
|
||||
{
|
||||
var policy = options.Resolve(capability);
|
||||
policy.ShouldNotBeNull(
|
||||
$"every DriverCapability must resolve to a non-null policy for tier {tier} — {capability} did not");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Core-010 regression: when a capability is somehow missing from BOTH the override
|
||||
/// map and the tier defaults (defensive — should be impossible thanks to the
|
||||
/// <c>TierDefaults_Cover_EveryCapability</c> invariant, but is the failure mode the
|
||||
/// finding flagged), <c>Resolve</c> must throw a diagnostic <see cref="KeyNotFoundException"/>
|
||||
/// that names the missing capability and tier — not a bare lookup failure.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Resolve_Throws_Diagnostic_When_Capability_Missing_From_Tier_Defaults()
|
||||
{
|
||||
// Use a CapabilityPolicies dict that purposely omits one capability and use reflection
|
||||
// to confirm the message names the capability when the tier defaults also omit it.
|
||||
// We can't easily mutate GetTierDefaults so we exercise the documented behavior on a
|
||||
// synthetic non-tier-known capability (we cast an out-of-range enum value).
|
||||
var options = new DriverResilienceOptions { Tier = DriverTier.A };
|
||||
var bogus = (DriverCapability)int.MaxValue;
|
||||
|
||||
var ex = Should.Throw<KeyNotFoundException>(() => options.Resolve(bogus));
|
||||
ex.Message.ShouldContain(bogus.ToString());
|
||||
ex.Message.ShouldContain(DriverTier.A.ToString());
|
||||
ex.Message.ShouldContain(nameof(DriverResilienceOptions.GetTierDefaults));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user