fix(hosts): a driver that never built a tag table reports NO ANSWER, not zero skips
Found on the docker-dev live gate, not in tests. Two drivers that faulted during initialise (rtu-gate, opcua1) each rendered "0 tags" on /hosts against two authored tags apiece, because a ledger that had never committed returned SkippedTagReport.Empty. A zero report is a positive claim -- "this driver skipped nothing" -- and making it on behalf of a driver that never got as far as looking is exactly the quiet inaccuracy #541 exists to remove. GetSkippedTags() and SkippedTagLedger.Snapshot() now return null until the first committed build, so the column renders the same em-dash it already used for a driver that cannot report at all. Both are 'no answer'; neither is 'nothing skipped'. Knock-on: a FOCAS driver whose only init threw now reports null rather than a zero report, which is strictly more honest than the assertion the test originally made.
This commit is contained in:
@@ -43,11 +43,13 @@ Reading the Tags column:
|
||||
|
||||
| Cell | Meaning |
|
||||
|---|---|
|
||||
| `—` | the driver cannot report skips. **Not** the same as reporting none. |
|
||||
| `12 tags` | the driver reports, and skipped nothing. |
|
||||
| `—` | **no answer.** Either the driver cannot report skips, or it never got as far as building a tag table (it faulted during initialise). Deliberately *not* the same as reporting none. |
|
||||
| `12 tags` | the driver built a table, and skipped nothing. |
|
||||
| `20 / 22 skipped` (amber) | partly working — some tags poll, some do not. |
|
||||
| `22 / 22 skipped` (red) | polling **nothing**. The state most likely to look healthy, because it has no failing reads to report. |
|
||||
|
||||
That first row is a distinction the live rig forced. A pre-fix build rendered `0 tags` for two drivers that had faulted during initialise — each with two authored tags — because a never-built ledger returned a zero report. A zero report is a *positive claim* ("this driver skipped nothing"), and making it on behalf of a driver that never looked is the same quiet inaccuracy the column exists to remove. `GetSkippedTags()` now returns null until the first committed build.
|
||||
|
||||
Three properties worth knowing:
|
||||
|
||||
- **A skip never changes driver state.** Latching non-Healthy for a config problem would satisfy both halves of the [#527](https://gitea.dohertylan.com/dohertj2/lmxopcua/issues/527) comms-loss conjunction — *90 s without a successful read AND non-Healthy* — and a driver whose tags were all skipped has no successful reads by construction, so it would reconnect-loop forever against a device that is perfectly fine. The signal is additive to the state, not a change of it.
|
||||
|
||||
@@ -44,8 +44,15 @@ public interface ISkippedTagReporter
|
||||
/// Must be a pure in-memory read with no I/O — it is called on the health-publish path, is
|
||||
/// deliberately NOT routed through the resilience pipeline, and must never block.
|
||||
/// </remarks>
|
||||
/// <returns>The current report; never null. An all-zero report means "nothing skipped".</returns>
|
||||
SkippedTagReport GetSkippedTags();
|
||||
/// <returns>
|
||||
/// The report from the driver's most recent tag-table build, or <see langword="null"/> when it has
|
||||
/// not built one yet — a driver that faulted during initialise has <b>no answer</b>, which is not
|
||||
/// the same as the answer "nothing skipped". Returning a zero report there would state that a
|
||||
/// driver with a dozen authored tags has none, which is the class of quiet inaccuracy this whole
|
||||
/// capability exists to remove. (Found on the docker-dev rig, where two faulted drivers each
|
||||
/// rendered "0 tags" against two authored ones.)
|
||||
/// </returns>
|
||||
SkippedTagReport? GetSkippedTags();
|
||||
}
|
||||
|
||||
/// <summary>One authored tag a driver could not build into its poll table, and why.</summary>
|
||||
@@ -71,9 +78,6 @@ public sealed record SkippedTag(string RawPath, string Reason);
|
||||
/// </param>
|
||||
public sealed record SkippedTagReport(int AuthoredCount, int SkippedCount, IReadOnlyList<SkippedTag> Sample)
|
||||
{
|
||||
/// <summary>A driver that has authored nothing and skipped nothing.</summary>
|
||||
public static readonly SkippedTagReport Empty = new(0, 0, []);
|
||||
|
||||
/// <summary>Whether any authored tag was skipped — i.e. whether this is worth an operator's attention.</summary>
|
||||
public bool HasSkips => SkippedCount > 0;
|
||||
|
||||
@@ -113,7 +117,9 @@ public sealed class SkippedTagLedger
|
||||
public const int MaxSample = 20;
|
||||
|
||||
private readonly object _gate = new();
|
||||
private SkippedTagReport _visible = SkippedTagReport.Empty;
|
||||
|
||||
/// <summary>Null until the first <see cref="Commit"/> — "no answer yet", never a zero report.</summary>
|
||||
private SkippedTagReport? _visible;
|
||||
|
||||
private int _stagedAuthored;
|
||||
private int _stagedSkipped;
|
||||
@@ -168,8 +174,9 @@ public sealed class SkippedTagLedger
|
||||
}
|
||||
|
||||
/// <summary>The current published snapshot.</summary>
|
||||
/// <returns>The last committed report, or <see cref="SkippedTagReport.Empty"/> before the first commit.</returns>
|
||||
public SkippedTagReport Snapshot()
|
||||
/// <returns>The last committed report, or <see langword="null"/> before the first commit — a driver
|
||||
/// that has not built a tag table has no answer, and a zero report would be a false one.</returns>
|
||||
public SkippedTagReport? Snapshot()
|
||||
{
|
||||
lock (_gate) return _visible;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
private readonly SkippedTagLedger _skippedTags = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public SkippedTagReport GetSkippedTags() => _skippedTags.Snapshot();
|
||||
public SkippedTagReport? GetSkippedTags() => _skippedTags.Snapshot();
|
||||
|
||||
private AbCipDriverOptions _options;
|
||||
private readonly string _driverInstanceId;
|
||||
|
||||
@@ -17,7 +17,7 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
|
||||
private readonly SkippedTagLedger _skippedTags = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public SkippedTagReport GetSkippedTags() => _skippedTags.Snapshot();
|
||||
public SkippedTagReport? GetSkippedTags() => _skippedTags.Snapshot();
|
||||
|
||||
/// <summary>Mutable so <see cref="InitializeAsync"/> can adopt a re-parsed config on reinitialize
|
||||
/// (#516). Only ever written on the init path, before any reader/session uses it.</summary>
|
||||
|
||||
@@ -42,7 +42,7 @@ public sealed class CalculationDriver : IDriver, IDependencyConsumer, ISubscriba
|
||||
private readonly SkippedTagLedger _skippedTags = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public SkippedTagReport GetSkippedTags() => _skippedTags.Snapshot();
|
||||
public SkippedTagReport? GetSkippedTags() => _skippedTags.Snapshot();
|
||||
|
||||
// OPC UA status codes surfaced by the calc driver.
|
||||
private const uint StatusGood = 0u;
|
||||
|
||||
@@ -27,7 +27,7 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
private readonly SkippedTagLedger _skippedTags = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public SkippedTagReport GetSkippedTags() => _skippedTags.Snapshot();
|
||||
public SkippedTagReport? GetSkippedTags() => _skippedTags.Snapshot();
|
||||
|
||||
/// <summary>Mutable so <see cref="InitializeAsync"/> can adopt a re-parsed config (#516). Written only
|
||||
/// on the init path, together with <see cref="_clientFactory"/>, before either is used.</summary>
|
||||
|
||||
@@ -45,7 +45,7 @@ public sealed class GalaxyDriver
|
||||
private readonly SkippedTagLedger _skippedTags = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public SkippedTagReport GetSkippedTags() => _skippedTags.Snapshot();
|
||||
public SkippedTagReport? GetSkippedTags() => _skippedTags.Snapshot();
|
||||
|
||||
private readonly string _driverInstanceId;
|
||||
private readonly GalaxyDriverOptions _options;
|
||||
|
||||
@@ -79,7 +79,7 @@ public sealed class MTConnectDriver
|
||||
private readonly SkippedTagLedger _skippedTags = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public SkippedTagReport GetSkippedTags() => _skippedTags.Snapshot();
|
||||
public SkippedTagReport? GetSkippedTags() => _skippedTags.Snapshot();
|
||||
|
||||
/// <summary>
|
||||
/// The Agent could not be reached, answered unusably, or blew the per-call deadline. Distinct
|
||||
|
||||
@@ -25,7 +25,7 @@ public sealed class ModbusDriver
|
||||
private readonly SkippedTagLedger _skippedTags = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public SkippedTagReport GetSkippedTags() => _skippedTags.Snapshot();
|
||||
public SkippedTagReport? GetSkippedTags() => _skippedTags.Snapshot();
|
||||
|
||||
// ---- instance fields (grouped at top for auditability) ----
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ public sealed class MqttDriver
|
||||
private readonly SkippedTagLedger _skippedTags = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public SkippedTagReport GetSkippedTags() => _skippedTags.Snapshot();
|
||||
public SkippedTagReport? GetSkippedTags() => _skippedTags.Snapshot();
|
||||
|
||||
// ---- Write status codes (Gitea #508) ----
|
||||
//
|
||||
|
||||
@@ -37,7 +37,7 @@ public sealed class OpcUaClientDriver : IDriver, ITagDiscovery, IReadable, IWrit
|
||||
private readonly SkippedTagLedger _skippedTags = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public SkippedTagReport GetSkippedTags() => _skippedTags.Snapshot();
|
||||
public SkippedTagReport? GetSkippedTags() => _skippedTags.Snapshot();
|
||||
|
||||
/// <summary>
|
||||
/// Recomputes the skip report from the freshly-built map. Derived by re-asking the map to resolve
|
||||
|
||||
@@ -36,7 +36,7 @@ public sealed class S7Driver
|
||||
private readonly SkippedTagLedger _skippedTags = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public SkippedTagReport GetSkippedTags() => _skippedTags.Snapshot();
|
||||
public SkippedTagReport? GetSkippedTags() => _skippedTags.Snapshot();
|
||||
|
||||
private readonly string _driverInstanceId;
|
||||
private readonly ILogger<S7Driver> _logger;
|
||||
|
||||
@@ -46,7 +46,7 @@ public sealed class SqlDriver
|
||||
private IReadOnlyList<SkippedTag> _blobSkips = [];
|
||||
|
||||
/// <inheritdoc />
|
||||
public SkippedTagReport GetSkippedTags() => _skippedTags.Snapshot();
|
||||
public SkippedTagReport? GetSkippedTags() => _skippedTags.Snapshot();
|
||||
|
||||
/// <summary>
|
||||
/// Republishes the skip ledger as the union of the blob-level skips and the catalog gate's
|
||||
|
||||
@@ -17,7 +17,7 @@ public sealed class TwinCATDriver : IDriver, IReadable, IWritable, ITagDiscovery
|
||||
private readonly SkippedTagLedger _skippedTags = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public SkippedTagReport GetSkippedTags() => _skippedTags.Snapshot();
|
||||
public SkippedTagReport? GetSkippedTags() => _skippedTags.Snapshot();
|
||||
|
||||
// Mutable so ReinitializeAsync can apply a new config generation. The constructor seeds
|
||||
// it; InitializeAsync re-parses driverConfigJson over the top of it.
|
||||
|
||||
@@ -234,8 +234,11 @@ else
|
||||
Healthy, Errors/5 min stays 0, Last error stays empty, and Last
|
||||
read keeps advancing off the tags that DID map. On the Makino in
|
||||
#540 that read as a perfectly healthy driver ignoring 20 of its 22
|
||||
tags. "—" means the driver cannot report skips, which is distinct
|
||||
from reporting none. *@
|
||||
tags. "—" means NO ANSWER — either the driver cannot report skips,
|
||||
or it faulted before building a tag table — which is distinct from
|
||||
the positive claim "this driver skipped nothing". A pre-fix build
|
||||
rendered "0 tags" for two faulted drivers on the docker-dev rig
|
||||
that had two authored tags apiece. *@
|
||||
@if (d.SkippedTags is null)
|
||||
{
|
||||
<span class="text-muted">—</span>
|
||||
|
||||
@@ -44,9 +44,10 @@ public sealed record HostsDriverInstanceInfo(string DriverInstanceId, string Clu
|
||||
/// <param name="HostStatuses">Per-host connectivity for a multi-device driver, ordered by host name; null
|
||||
/// when the driver reports no per-host detail. Lets one unreachable device show even while the driver
|
||||
/// row itself is Healthy.</param>
|
||||
/// <param name="SkippedTags">Authored tags the driver could not build into its poll table; null when the
|
||||
/// driver cannot report them. Lets "ignoring 20 of 22 tags" show even while every other column on the
|
||||
/// row — state, last read, error count, last error — reads perfectly healthy (Gitea #541).</param>
|
||||
/// <param name="SkippedTags">Authored tags the driver could not build into its poll table; null when there
|
||||
/// is no answer — the driver cannot report skips, or it faulted before building a tag table. Lets
|
||||
/// "ignoring 20 of 22 tags" show even while every other column on the row — state, last read, error
|
||||
/// count, last error — reads perfectly healthy (Gitea #541).</param>
|
||||
public sealed record HostsDriverRow(
|
||||
string DriverInstanceId, string? Name, string? DriverType, string State,
|
||||
DateTime? LastSuccessfulReadUtc, string? LastError, int ErrorCount5Min, DateTime PublishedUtc,
|
||||
|
||||
@@ -9,17 +9,45 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions.Tests;
|
||||
/// </summary>
|
||||
public sealed class SkippedTagLedgerTests
|
||||
{
|
||||
/// <summary>
|
||||
/// <b>A ledger that has never built reports NO ANSWER, not a clean bill of health.</b> Caught on
|
||||
/// the docker-dev rig: two drivers that faulted during initialise each rendered "0 tags" on
|
||||
/// <c>/hosts</c> against two authored tags apiece. A zero report is a positive claim — "this driver
|
||||
/// skipped nothing" — and making it on behalf of a driver that never got as far as looking is the
|
||||
/// same species of quiet inaccuracy this capability exists to remove.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void A_fresh_ledger_reports_nothing()
|
||||
{
|
||||
var report = new SkippedTagLedger().Snapshot();
|
||||
public void A_ledger_that_has_never_built_reports_no_answer_rather_than_a_clean_one()
|
||||
=> new SkippedTagLedger().Snapshot().ShouldBeNull();
|
||||
|
||||
report.AuthoredCount.ShouldBe(0);
|
||||
/// <summary>An empty deployment that DID build reports a zero report — distinct from the above.</summary>
|
||||
[Fact]
|
||||
public void A_committed_empty_build_reports_zero_rather_than_no_answer()
|
||||
{
|
||||
var ledger = new SkippedTagLedger();
|
||||
ledger.BeginRebuild(0);
|
||||
ledger.Commit();
|
||||
|
||||
var report = ledger.Snapshot();
|
||||
report.ShouldNotBeNull();
|
||||
report!.AuthoredCount.ShouldBe(0);
|
||||
report.SkippedCount.ShouldBe(0);
|
||||
report.Sample.ShouldBeEmpty();
|
||||
report.HasSkips.ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An in-flight first build is still "no answer" — the staging state must not leak as a report.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void A_first_build_still_in_progress_reports_no_answer()
|
||||
{
|
||||
var ledger = new SkippedTagLedger();
|
||||
ledger.BeginRebuild(10);
|
||||
ledger.Skip("a", "r");
|
||||
|
||||
ledger.Snapshot().ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_committed_build_reports_its_skips_with_the_authored_denominator()
|
||||
{
|
||||
@@ -30,7 +58,7 @@ public sealed class SkippedTagLedgerTests
|
||||
ledger.Skip("Line1/Makino/Identity/Version", "invalid Address");
|
||||
ledger.Commit();
|
||||
|
||||
var report = ledger.Snapshot();
|
||||
var report = ledger.Snapshot()!;
|
||||
report.AuthoredCount.ShouldBe(22);
|
||||
report.SkippedCount.ShouldBe(2);
|
||||
report.Sample.Select(s => s.RawPath)
|
||||
@@ -54,7 +82,7 @@ public sealed class SkippedTagLedgerTests
|
||||
ledger.Skip("c", "r");
|
||||
ledger.Commit();
|
||||
|
||||
ledger.Snapshot().SkippedEverything.ShouldBeTrue();
|
||||
ledger.Snapshot()!.SkippedEverything.ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -70,7 +98,7 @@ public sealed class SkippedTagLedgerTests
|
||||
ledger.BeginRebuild(0);
|
||||
ledger.Commit();
|
||||
|
||||
ledger.Snapshot().SkippedEverything.ShouldBeFalse();
|
||||
ledger.Snapshot()!.SkippedEverything.ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -86,7 +114,7 @@ public sealed class SkippedTagLedgerTests
|
||||
for (var i = 0; i < 500; i++) ledger.Skip($"tag-{i}", "TagConfig did not map to a definition");
|
||||
ledger.Commit();
|
||||
|
||||
var report = ledger.Snapshot();
|
||||
var report = ledger.Snapshot()!;
|
||||
report.SkippedCount.ShouldBe(500);
|
||||
report.Sample.Count.ShouldBe(SkippedTagLedger.MaxSample);
|
||||
report.Sample[0].RawPath.ShouldBe("tag-0", "the sample keeps the FIRST skips, in encounter order");
|
||||
@@ -108,13 +136,13 @@ public sealed class SkippedTagLedgerTests
|
||||
ledger.BeginRebuild(10);
|
||||
ledger.Skip("new-1", "r");
|
||||
|
||||
var midRebuild = ledger.Snapshot();
|
||||
var midRebuild = ledger.Snapshot()!;
|
||||
midRebuild.SkippedCount.ShouldBe(1);
|
||||
midRebuild.Sample.Single().RawPath.ShouldBe("old");
|
||||
|
||||
ledger.Skip("new-2", "r");
|
||||
ledger.Commit();
|
||||
ledger.Snapshot().Sample.Select(s => s.RawPath).ShouldBe(["new-1", "new-2"]);
|
||||
ledger.Snapshot()!.Sample.Select(s => s.RawPath).ShouldBe(["new-1", "new-2"]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -133,7 +161,7 @@ public sealed class SkippedTagLedgerTests
|
||||
ledger.Skip("would-have-been-reported", "invalid Address");
|
||||
// …driver throws here; no Commit.
|
||||
|
||||
ledger.Snapshot().Sample.Single().RawPath.ShouldBe("known-bad");
|
||||
ledger.Snapshot()!.Sample.Single().RawPath.ShouldBe("known-bad");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -151,7 +179,7 @@ public sealed class SkippedTagLedgerTests
|
||||
ledger.BeginRebuild(2);
|
||||
ledger.Commit();
|
||||
|
||||
var report = ledger.Snapshot();
|
||||
var report = ledger.Snapshot()!;
|
||||
report.HasSkips.ShouldBeFalse();
|
||||
report.SkippedCount.ShouldBe(0);
|
||||
report.AuthoredCount.ShouldBe(2);
|
||||
@@ -167,7 +195,7 @@ public sealed class SkippedTagLedgerTests
|
||||
var ledger = new SkippedTagLedger();
|
||||
|
||||
Should.NotThrow(() => ledger.Skip("orphan", "r"));
|
||||
ledger.Snapshot().SkippedCount.ShouldBe(0);
|
||||
ledger.Snapshot().ShouldBeNull("a stray Skip must not conjure a report out of nothing");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -197,7 +225,7 @@ public sealed class SkippedTagLedgerTests
|
||||
{
|
||||
while (!writer.IsCompleted && !cts.IsCancellationRequested)
|
||||
{
|
||||
var r = ledger.Snapshot();
|
||||
var r = ledger.Snapshot()!;
|
||||
// Every committed snapshot is internally consistent: the sample never exceeds either the
|
||||
// cap or the true count it is drawn from.
|
||||
r.Sample.Count.ShouldBeLessThanOrEqualTo(SkippedTagLedger.MaxSample);
|
||||
|
||||
+10
-8
@@ -42,7 +42,7 @@ public sealed class FocasSkippedTagReportingTests
|
||||
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
|
||||
var report = drv.GetSkippedTags();
|
||||
var report = drv.GetSkippedTags()!;
|
||||
report.AuthoredCount.ShouldBe(2);
|
||||
report.SkippedCount.ShouldBe(0);
|
||||
report.HasSkips.ShouldBeFalse();
|
||||
@@ -75,7 +75,7 @@ public sealed class FocasSkippedTagReportingTests
|
||||
drv.GetHealth().LastError.ShouldBeNull();
|
||||
|
||||
// …and the fact that contradicts it.
|
||||
var report = drv.GetSkippedTags();
|
||||
var report = drv.GetSkippedTags()!;
|
||||
report.AuthoredCount.ShouldBe(22);
|
||||
report.SkippedCount.ShouldBe(20);
|
||||
report.SkippedEverything.ShouldBeFalse("two tags do poll — this driver is partly working");
|
||||
@@ -97,7 +97,7 @@ public sealed class FocasSkippedTagReportingTests
|
||||
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
|
||||
var report = drv.GetSkippedTags();
|
||||
var report = drv.GetSkippedTags()!;
|
||||
report.SkippedCount.ShouldBe(25);
|
||||
report.Sample.Count.ShouldBe(SkippedTagLedger.MaxSample);
|
||||
report.SkippedEverything.ShouldBeTrue("this driver polls nothing at all");
|
||||
@@ -116,11 +116,11 @@ public sealed class FocasSkippedTagReportingTests
|
||||
new FocasTagDefinition("raw/Good", Host, "MACRO:500", FocasDataType.Float64));
|
||||
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
drv.GetSkippedTags().SkippedCount.ShouldBe(1);
|
||||
drv.GetSkippedTags()!.SkippedCount.ShouldBe(1);
|
||||
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
|
||||
var report = drv.GetSkippedTags();
|
||||
var report = drv.GetSkippedTags()!;
|
||||
report.SkippedCount.ShouldBe(1, "the same one bad tag — not two");
|
||||
report.AuthoredCount.ShouldBe(2);
|
||||
}
|
||||
@@ -145,7 +145,7 @@ public sealed class FocasSkippedTagReportingTests
|
||||
};
|
||||
var drv = new FocasDriver(good, "drv-1", new FakeFocasClientFactory());
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
drv.GetSkippedTags().Sample.Single().RawPath.ShouldBe("raw/Bad");
|
||||
drv.GetSkippedTags()!.Sample.Single().RawPath.ShouldBe("raw/Bad");
|
||||
|
||||
// A second driver over an artifact whose tag names a device that is not configured: the loop skips
|
||||
// the first entry, then throws on the second.
|
||||
@@ -164,7 +164,9 @@ public sealed class FocasSkippedTagReportingTests
|
||||
await Should.ThrowAsync<InvalidOperationException>(
|
||||
() => drv2.InitializeAsync("{}", CancellationToken.None));
|
||||
|
||||
drv2.GetSkippedTags().SkippedCount.ShouldBe(0,
|
||||
"an abandoned build must not publish; a partial report would read as 'we skip fewer tags now'");
|
||||
// NO ANSWER, not "zero skipped": this driver's only build threw, so it never reached a verdict.
|
||||
// A zero report here would be the same false reassurance the docker-dev rig showed for two
|
||||
// faulted drivers, and a PARTIAL one would read as "we skip fewer tags now".
|
||||
drv2.GetSkippedTags().ShouldBeNull();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -248,7 +248,7 @@ public sealed class DriverInstanceActorSkippedTagTests : RuntimeActorTestBase
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public SkippedTagReport GetSkippedTags()
|
||||
public SkippedTagReport? GetSkippedTags()
|
||||
{
|
||||
if (ThrowOnGetSkippedTags) throw new InvalidOperationException("reporter is broken");
|
||||
return _ledger.Snapshot();
|
||||
|
||||
Reference in New Issue
Block a user