test(mqtt): tag 16 wall-clock-bound tests Category=SlowUnit; document local opt-out (#606)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -57,7 +57,8 @@ jobs:
|
||||
# Self-maintaining — a new *.Tests project is covered automatically, no matrix to widen —
|
||||
# and matches CLAUDE.md's own `dotnet test ZB.MOM.WW.OtOpcUa.slnx` guidance. Excludes only
|
||||
# the env-gated tiers: E2E (nightly, needs the docker-dev fleet) and LiveIntegration (needs
|
||||
# a real gateway/PLC/VPN). The skip-gated *.IntegrationTests Assert.Skip when their fixtures
|
||||
# a real gateway/PLC/VPN). Category=SlowUnit (Mqtt socket-timeout tests, #606) is deliberately NOT
|
||||
# excluded here — it is a local-dev-loop opt-out only. The skip-gated *.IntegrationTests Assert.Skip when their fixtures
|
||||
# are unreachable, so they run harmlessly here (and are counted by the skip-gate below).
|
||||
- name: dotnet test (whole solution, minus E2E + LiveIntegration)
|
||||
run: >
|
||||
|
||||
@@ -359,6 +359,10 @@ dotnet test tests/Core/ZB.MOM.WW.OtOpcUa.Core.Tests # a single tes
|
||||
dotnet test --filter "FullyQualifiedName~MyTestClass.MyMethod" # a single test
|
||||
```
|
||||
|
||||
`Category=SlowUnit` (#606) tags the 16 wall-clock-bound Driver.Mqtt unit tests (blackhole connect-timeout /
|
||||
reconnect-backoff windows, ~1 min of the project's 73 s). CI runs them (its filter excludes only E2E +
|
||||
LiveIntegration); for a fast local loop add `--filter "Category!=SlowUnit"`.
|
||||
|
||||
CVE-2025-6965 / GHSA-2m69-gcr7-jv3q (transitive native `SQLitePCLRaw.lib.e_sqlite3`)
|
||||
is **resolved** — the temporary `NuGetAuditSuppress` was removed once the patched bundle
|
||||
shipped. `Core.AlarmHistorian` now carries a surgical direct pin of
|
||||
|
||||
@@ -30,6 +30,7 @@ public sealed class MqttConnectionTests
|
||||
/// actually wedges a client — a refused port returns ECONNREFUSED instantly and would pass
|
||||
/// whether or not any deadline logic existed.
|
||||
/// </summary>
|
||||
[Trait("Category", "SlowUnit")]
|
||||
[Fact]
|
||||
public async Task ConnectAsync_BlackholeBroker_FailsWithinDeadline_NoHang()
|
||||
{
|
||||
@@ -95,6 +96,7 @@ public sealed class MqttConnectionTests
|
||||
/// <c>IMqttClient</c> held across failures. A failed attempt must therefore leave the
|
||||
/// connection usable rather than poisoned.
|
||||
/// </summary>
|
||||
[Trait("Category", "SlowUnit")]
|
||||
[Fact]
|
||||
public async Task ConnectAsync_CanBeRetriedOnTheSameInstanceAfterAFailedAttempt()
|
||||
{
|
||||
@@ -592,6 +594,7 @@ public sealed class MqttConnectionTests
|
||||
/// <c>InitializeAsync</c>) — it must not silently leave a background reconnect worker
|
||||
/// hammering an endpoint the caller has already given up on.
|
||||
/// </summary>
|
||||
[Trait("Category", "SlowUnit")]
|
||||
[Fact]
|
||||
public async Task ConnectAsync_InitialAttemptFails_LeavesNoBackgroundReconnectStorm()
|
||||
{
|
||||
@@ -729,6 +732,7 @@ public sealed class MqttConnectionTests
|
||||
/// <c>Faulted</c>) — and the backoff keeps the retry rate low enough that a handful of
|
||||
/// attempts, not hundreds, land in the observation window.
|
||||
/// </summary>
|
||||
[Trait("Category", "SlowUnit")]
|
||||
[Fact]
|
||||
public async Task BrokerStaysDown_RetriesUnderBackoff_WithoutHotLooping()
|
||||
{
|
||||
@@ -759,6 +763,7 @@ public sealed class MqttConnectionTests
|
||||
/// Dispose during a reconnect backoff must stop the supervisor dead — no leaked task, no
|
||||
/// later attempt resurrecting a connection the caller has torn down.
|
||||
/// </summary>
|
||||
[Trait("Category", "SlowUnit")]
|
||||
[Fact]
|
||||
public async Task Dispose_DuringReconnectBackoff_StopsTheSupervisor_NoFurtherAttempts()
|
||||
{
|
||||
@@ -875,6 +880,7 @@ public sealed class MqttConnectionTests
|
||||
/// against a perfectly healthy connection, at Debug level, inflating <c>attempt</c> so the
|
||||
/// NEXT genuine outage waits max backoff instead of min.
|
||||
/// </summary>
|
||||
[Trait("Category", "SlowUnit")]
|
||||
[Fact]
|
||||
public async Task CallerReconnectsWhileTheSupervisorIsInBackoff_SupervisorStandsDownSilently()
|
||||
{
|
||||
@@ -917,6 +923,7 @@ public sealed class MqttConnectionTests
|
||||
/// A stood-down supervisor answers it one min-backoff (2 s) later. The 5 s window separates the
|
||||
/// two by a clear margin; widening the burn only widens the separation.
|
||||
/// </remarks>
|
||||
[Trait("Category", "SlowUnit")]
|
||||
[Fact]
|
||||
public async Task AfterACallerWinsTheRace_TheNextGenuineOutageStillRecoversAtMinBackoff()
|
||||
{
|
||||
@@ -971,6 +978,7 @@ public sealed class MqttConnectionTests
|
||||
/// dispose-elapsed assertion cannot tell those apart — <c>DisposeAsync</c>'s own bounded join
|
||||
/// returns promptly either way, leaving the supervisor alive behind it.
|
||||
/// </remarks>
|
||||
[Trait("Category", "SlowUnit")]
|
||||
[Fact]
|
||||
public async Task ReSubscribeThatHangsIgnoringItsToken_DoesNotParkTheSupervisor()
|
||||
{
|
||||
@@ -1085,6 +1093,7 @@ public sealed class MqttConnectionTests
|
||||
/// An unrecoverable rejection must not leave a supervisor grinding away every backoff period
|
||||
/// against a broker that will never accept these credentials.
|
||||
/// </summary>
|
||||
[Trait("Category", "SlowUnit")]
|
||||
[Fact]
|
||||
public async Task ConnectAsync_RefusedCredentials_StartsNoReconnectSupervisor()
|
||||
{
|
||||
@@ -1156,6 +1165,7 @@ public sealed class MqttConnectionTests
|
||||
/// The supervisor's own attempts go through the same CONNACK check. A broker that starts
|
||||
/// refusing mid-life (credentials revoked, client banned) must stop the loop, not feed it.
|
||||
/// </summary>
|
||||
[Trait("Category", "SlowUnit")]
|
||||
[Fact]
|
||||
public async Task Reconnect_BrokerStartsRefusing_FaultsAndStopsTheSupervisor()
|
||||
{
|
||||
@@ -1179,6 +1189,7 @@ public sealed class MqttConnectionTests
|
||||
}
|
||||
|
||||
/// <summary>The mirror case: a transient refusal keeps the supervisor retrying under backoff.</summary>
|
||||
[Trait("Category", "SlowUnit")]
|
||||
[Fact]
|
||||
public async Task Reconnect_TransientRefusal_KeepsRetryingUnderBackoff()
|
||||
{
|
||||
|
||||
@@ -211,6 +211,7 @@ public sealed class MqttDriverDiscoveryTests
|
||||
/// fails must land <see cref="DriverState.Faulted"/> rather than letting the failure escape
|
||||
/// with the health surface still claiming Healthy.
|
||||
/// </summary>
|
||||
[Trait("Category", "SlowUnit")]
|
||||
[Fact]
|
||||
public async Task ReinitializeAsync_SessionChangingDelta_Rebuilds_AndFaultsOnUnreachableBroker()
|
||||
{
|
||||
|
||||
@@ -83,6 +83,7 @@ public sealed class MqttDriverSessionLifecycleTests
|
||||
/// The control for the test above: the normalization must not quietly rewrite a <i>legal</i>
|
||||
/// value, or a deployment's authored connect budget would silently become the default.
|
||||
/// </summary>
|
||||
[Trait("Category", "SlowUnit")]
|
||||
[Fact]
|
||||
public async Task InitializeAsync_WithAnAuthoredConnectTimeout_KeepsIt()
|
||||
{
|
||||
@@ -126,6 +127,7 @@ public sealed class MqttDriverSessionLifecycleTests
|
||||
/// can drop a session (which is what this test needs from it) but cannot PUBLISH.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[Trait("Category", "SlowUnit")]
|
||||
[Fact]
|
||||
public async Task ABrokerDrop_MarksCachedRefsBadNoCommunication_AndTheIdenticalReplayRecovers()
|
||||
{
|
||||
|
||||
@@ -243,6 +243,7 @@ public sealed class SparkplugPrimaryHostTests
|
||||
/// one-second role sample, forever. It now backs off. The clock is frozen, so the real one-second
|
||||
/// loop ticks several times without the backoff window ever opening; advancing the clock opens it.
|
||||
/// </summary>
|
||||
[Trait("Category", "SlowUnit")]
|
||||
[Fact]
|
||||
public async Task A_failing_claim_backs_off_instead_of_redialling_every_tick()
|
||||
{
|
||||
@@ -313,6 +314,7 @@ public sealed class SparkplugPrimaryHostTests
|
||||
/// Deterministic rather than timing-dependent: the gate is taken before the connect starts, so
|
||||
/// the broker having accepted the socket proves the claim is inside it when dispose runs.
|
||||
/// </remarks>
|
||||
[Trait("Category", "SlowUnit")]
|
||||
[Fact]
|
||||
public async Task Disposing_while_a_reconcile_is_in_flight_does_not_fault_on_a_disposed_gate()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user