diff --git a/src/MeshWeaver.Fixture/HubFactAttribute.cs b/src/MeshWeaver.Fixture/HubFactAttribute.cs index ed5f5ef416..21b0131424 100644 --- a/src/MeshWeaver.Fixture/HubFactAttribute.cs +++ b/src/MeshWeaver.Fixture/HubFactAttribute.cs @@ -10,17 +10,30 @@ namespace MeshWeaver.Fixture; public class HubFactAttribute : FactAttribute; #else /// -/// Marks a hub test method. In non-DEBUG builds a 5 second timeout is applied -/// so that a wedged hub fails the test fast instead of hanging the run. +/// Marks a hub test method. In non-DEBUG builds a timeout is applied so that a wedged hub fails +/// the test instead of hanging the run. +/// +/// 🚨 The budget must exceed what the MESH FIXTURE costs on a loaded CI runner, or the test +/// measures the runner instead of the code. It was 5s, and 5s is not achievable there: a CI log for +/// MeshNodeVersionSyncTest (2026-07-27) shows class init at 19:59:22.595 and dispose at +/// 19:59:30.720 — 8.1s of fixture before the body runs at all. That produced a recurring +/// main-red flake with no defect behind it, and the same trap bit a heartbeat test written the same +/// night ("Test execution timed out after 5000 milliseconds", passing locally where no budget is +/// enforced). +/// +/// 30s matches the runner-wide methodTimeout and still fails a genuine wedge well +/// before the run hangs — a wedged read surfaces its own GetMeshNode … timed out after 60.0s +/// first, so nothing is masked by the larger budget. /// public sealed class HubFactAttribute : FactAttribute { /// - /// Initializes a new instance and sets the test timeout to 5000 milliseconds. + /// Initializes a new instance and sets the test timeout to 30000 milliseconds — above the + /// measured CI fixture cost, in line with the runner-wide methodTimeout. /// public HubFactAttribute() { - Timeout = 5000; + Timeout = 30000; } }; #endif diff --git a/test/MeshWeaver.Persistence.Test/MeshNodeVersionSyncTest.cs b/test/MeshWeaver.Persistence.Test/MeshNodeVersionSyncTest.cs index 24771b285f..ac3b9f2d65 100644 --- a/test/MeshWeaver.Persistence.Test/MeshNodeVersionSyncTest.cs +++ b/test/MeshWeaver.Persistence.Test/MeshNodeVersionSyncTest.cs @@ -190,7 +190,7 @@ public override async ValueTask DisposeAsync() } } - [Fact(Timeout = 10000)] + [Fact(Timeout = 30000)] public async Task MeshNode_InitialVersion_IsZero() { // Arrange - check initial version via the authoritative owner-hub read @@ -201,7 +201,7 @@ public async Task MeshNode_InitialVersion_IsZero() initialNode!.Version.Should().Be(0, "initial version should be 0"); } - [Fact(Timeout = 10000)] + [Fact(Timeout = 30000)] public async Task MeshNode_VersionProperty_CanBeSetAndPersisted() { // Arrange - create a node with a specific version. Use a built-in @@ -227,7 +227,7 @@ public async Task MeshNode_VersionProperty_CanBeSetAndPersisted() savedNode!.Version.Should().Be(42, "version should be preserved in persistence"); } - [Fact(Timeout = 10000)] + [Fact(Timeout = 30000)] public void MessageHub_HasSetInitialVersionMethod() { // Verify that IMessageHub interface has the SetInitialVersion method