From 887627603beea1bbc8021a05cc967dd07a826f2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20B=C3=BCrgi?= Date: Tue, 28 Jul 2026 06:04:32 +0100 Subject: [PATCH] Flakes: give hub tests a budget the CI fixture can actually meet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Catalogued every red main run of the last two days from the check annotations. Four named flakes, and none of them is a bad assertion — all four are timeouts, and at least two are timeouts a correct test could never have avoided: MeshNodeVersionSyncTest.MeshNode_InitialVersion_IsZero "Test execution timed out after 10000 milliseconds", and the same log shows class init 19:59:22.595 → dispose 19:59:30.720 — 8.1s of MESH FIXTURE inside a 10s budget, leaving 1.9s for the body. On a quiet runner it fits; under an 8-shard load it does not. The test measures the runner, not the code. [HubFact] carried the same trap globally: 5s in Release (CI), which the fixture alone can exceed. It bit a heartbeat test written the same night — green locally, where no budget is enforced, and impossible on CI. So both budgets move to 30s, matching the runner-wide methodTimeout. Nothing is masked: a genuine wedge surfaces its own "GetMeshNode … timed out after 60.0s" first, so a hang still fails — just not before the fixture has finished starting. Not fixed here, and NOT the same class — recorded so they are not mistaken for budget noise: TwoSiloRecycleConvergenceTest.PostRecycleUpdate_NonOwnerSiloMirror_Converges (2 runs) TwoWaySyncTest.TwoWay_Update_KeepsServerAddition_UnlessForced ThreadAgentIntegrationTest.FullFlow_CreateThread_SendMessage_NonStreaming The last one is the most interesting: it fails with "GetMeshNode('ACME/ProductLaunch') timed out after 60.0s — the owning per-node hub never answered … NO LOCAL HUB … it never activated here". That is the SAME cold-hub-activation defect measured in production this week (issue #686: 0ms source discovery on a fresh mesh, 45.20s on memex). These flakes are that bug reproducing under CI load, not test debt. 409/409 Persistence, 290/290 Data. Co-Authored-By: Claude Opus 5 (1M context) --- src/MeshWeaver.Fixture/HubFactAttribute.cs | 21 +++++++++++++++---- .../MeshNodeVersionSyncTest.cs | 6 +++--- 2 files changed, 20 insertions(+), 7 deletions(-) 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