From 225c634bf798e545972c16e3465d7896412eefcd Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Fri, 25 Sep 2026 00:55:07 -0700 Subject: [PATCH] test: stop the 1 ms promotion window leaking out of StepCacheLifecycleTests (#2679) ## Summary `Find_TwoPathfindsOverlappingChunks_PromoteToBuildOnSecondFind` fails intermittently in CI with `second Find through overlapping chunks must promote (got 0 builds)`. **Cause:** `SecondTouchAfterWindow_RestartsTheCount` and `TwoFindsAcrossTheWindow_DoNotPromote` set `StepCache.Instance.MissPromotionWindowMs = 1` and never restore it, and `Clear()` does not reset it. Both classes share the `Sequential Pathfinding Tests` collection, so when a lifecycle test runs first, the promotion test's two Finds have to land within 1 ms of each other. On Linux, `Environment.TickCount` moves in coarse steps of several ms, so even a sub-millisecond test can see the window lapse. The second Find then restarts the miss count instead of promoting. On Windows the ~15.6 ms tick nearly always reads the same value for both Finds, which is why it didn't reproduce locally. **Fix (tests only):** - `StepCacheLifecycleTests` implements `IDisposable` and restores the window after each test. - The promotion test sets the window it depends on (30 s) instead of inheriting it. ## Test plan - [x] Reproduced deterministically: with a temporary 20 ms sleep between the two Finds, the test fails whenever the lifecycle tests run first and passes when run alone - [x] With the sleep still in, the test passes with the Dispose restore alone and with both fixes - [x] All pathfinding tests pass (123/123) with the sleep removed --- .../Tests/Engines/Pathing/BitmapAStarAlgorithmTests.cs | 3 +++ .../Tests/Engines/Pathing/StepCacheLifecycleTests.cs | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Projects/UOContent.Tests/Tests/Engines/Pathing/BitmapAStarAlgorithmTests.cs b/Projects/UOContent.Tests/Tests/Engines/Pathing/BitmapAStarAlgorithmTests.cs index 20d729c0f..667995278 100644 --- a/Projects/UOContent.Tests/Tests/Engines/Pathing/BitmapAStarAlgorithmTests.cs +++ b/Projects/UOContent.Tests/Tests/Engines/Pathing/BitmapAStarAlgorithmTests.cs @@ -371,6 +371,9 @@ public class BitmapAStarAlgorithmTests { StepCache.Instance.Clear(); StepCache.Instance.MissPromotionThreshold = 2; + // Pinned: promotion needs both Finds inside the window, and Linux's coarse TickCount can + // step several ms between two back-to-back Finds. + StepCache.Instance.MissPromotionWindowMs = 30_000; var map = Map.Maps[1]; var stub = new DefaultWalkerStub(); diff --git a/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheLifecycleTests.cs b/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheLifecycleTests.cs index 7b630173c..b4257f112 100644 --- a/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheLifecycleTests.cs +++ b/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheLifecycleTests.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Reflection; using System.Threading; @@ -14,8 +15,14 @@ namespace Server.Tests.Pathfinding; /// layers, and LRU eviction. /// [Collection("Sequential Pathfinding Tests")] -public class StepCacheLifecycleTests +public class StepCacheLifecycleTests : IDisposable { + // The window is a knob on the singleton and Clear() leaves it alone, so a test that narrows + // it would otherwise leak the narrow window into every later test in the collection. + private readonly uint _savedWindowMs = StepCache.Instance.MissPromotionWindowMs; + + public void Dispose() => StepCache.Instance.MissPromotionWindowMs = _savedWindowMs; + /// Resets to a known state and returns the singleton. private static StepCache FreshCache(int promotionThreshold) {