diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 6f6d677b..437399a9 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -17,7 +17,7 @@ "rollForward": false }, "dotnet-ef": { - "version": "10.0.12", + "version": "10.0.13", "commands": [ "dotnet-ef" ], diff --git a/Directory.Packages.props b/Directory.Packages.props index db0dfaea..91681fd1 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -32,7 +32,7 @@ - + @@ -42,8 +42,8 @@ - - + + diff --git a/tests/RustPlusBot.Features.Map.Tests/Hosting/MapRefreshTests.cs b/tests/RustPlusBot.Features.Map.Tests/Hosting/MapRefreshTests.cs index a4f4dfda..93d0a132 100644 --- a/tests/RustPlusBot.Features.Map.Tests/Hosting/MapRefreshTests.cs +++ b/tests/RustPlusBot.Features.Map.Tests/Hosting/MapRefreshTests.cs @@ -169,33 +169,41 @@ public async Task A_server_that_is_no_longer_connected_drops_out_of_the_periodic // #map keeps being re-uploaded for a world the bot is no longer watching. using var h = Harness.Create(tick: TimeSpan.FromMilliseconds(20)); h.ConnectionStore.GetStateAsync(Guild, Server, Arg.Any()).Returns(Connected(Server)); - h.ConnectionStore.GetStateAsync(Guild, SecondServer, Arg.Any()) - .Returns(Connected(SecondServer)); + var secondServerRead = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + h.ConnectionStore.GetStateAsync(Guild, SecondServer, Arg.Any()).Returns(_ => + { + secondServerRead.TrySetResult(); + return Task.FromResult(Connected(SecondServer)); + }); int afterDisconnect; await h.Service.StartAsync(CancellationToken.None); try { + // Republish only until the status loop is listening. Every status event repaints, so copies + // queued behind a slow render pile up on the sequential loop, and each later step would wait + // behind them while republishing more of its own. await h.PublishUntilAsync(() => new ConnectionStatusChangedEvent(Guild, Server, true, false), - h.PostsToReachesAsync(MapChannel, 1)); - - // Nothing is published here, so reaching three posts can only be the tick repainting it: the - // server really is on the tick before the disconnect under test. - await h.PostsToReachesAsync(MapChannel, 3).WaitAsync(Patience); + h.WhenStatusHandled); // A second server stays connected throughout. Its posts are the heartbeat that proves the tick - // kept firing afterwards, which is what makes "stopped repainting" mean something. - await h.PublishUntilAsync(() => new ConnectionStatusChangedEvent(Guild, SecondServer, true, false), - h.PostsToReachesAsync(SecondMapChannel, 1)); + // kept firing afterwards, which is what makes "stopped repainting" mean something. The loop is + // listening now, so one publish is enough, and its read is a fence: the loop is first-in + // first-out and awaits each repaint, so every copy queued ahead of it has been fully handled. + await h.Bus.PublishAsync(new ConnectionStatusChangedEvent(Guild, SecondServer, true, false)); + await secondServerRead.Task.WaitAsync(Patience); + + // Nothing republishes the first server's connect any more, so its next post can only be the + // tick: the server really is on the tick before the disconnect under test. + await h.PostsToReachesAsync(MapChannel, h.PostsTo(MapChannel) + 1).WaitAsync(Patience); + await h.PostsToReachesAsync(SecondMapChannel, 1).WaitAsync(Patience); var offlineReads = 0; var handled = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); h.ConnectionStore.GetStateAsync(Guild, Server, Arg.Any()).Returns(_ => { - // Only reads that see the offline state count, so status events queued before the store was - // switched over cannot satisfy the fence. The status loop consumes sequentially, so a second - // offline read proves the first one's handler - the one that drops the server from the tick - - // already ran to completion. + // The status loop consumes sequentially, so the second offline read proves the first one's + // handler - the one that drops the server from the tick - already ran to completion. if (Interlocked.Increment(ref offlineReads) == 2) { handled.TrySetResult(); @@ -203,8 +211,9 @@ public async Task A_server_that_is_no_longer_connected_drops_out_of_the_periodic return Task.FromResult(null); }); - await h.PublishUntilAsync(() => new ConnectionStatusChangedEvent(Guild, Server, false, true), - handled.Task); + await h.Bus.PublishAsync(new ConnectionStatusChangedEvent(Guild, Server, false, true)); + await h.Bus.PublishAsync(new ConnectionStatusChangedEvent(Guild, Server, false, true)); + await handled.Task.WaitAsync(Patience); // Nothing is published from here on, so every further post comes from the tick. The tick reads // a snapshot of the connected set, so the iteration that was already in flight when the server