From 9253bde34edb74974a4270b123d2be837e25b079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deniz=20=C4=B0rgin?= Date: Sat, 17 Oct 2026 15:16:32 +0300 Subject: [PATCH] test: one config test owns the shared server's configuration (#440) UpdateConfig writes the global file and only requests a debounced reload that publishes config.updated when the loaded entries change. The persist test wrote and cleared the shell without waiting for that reload, so on a slow runner its pending reload absorbed the read-back test's identical write and no event came. The file checks fold into the read-back test, which settles the loaded state after each write. --- .../Config/ConfigClientLiveTests.cs | 75 ++++++------------- tools/jscpd/tests.baseline.json | 2 - 2 files changed, 24 insertions(+), 53 deletions(-) diff --git a/tests/OpenCode.Sdk.Tests/Config/ConfigClientLiveTests.cs b/tests/OpenCode.Sdk.Tests/Config/ConfigClientLiveTests.cs index 6b27d9a3..d87026e1 100644 --- a/tests/OpenCode.Sdk.Tests/Config/ConfigClientLiveTests.cs +++ b/tests/OpenCode.Sdk.Tests/Config/ConfigClientLiveTests.cs @@ -40,61 +40,19 @@ public async Task GetShellsAsync_Should_Report_Acceptable_Host_Shells(Cancellati " acceptable=" + Number(response.Shells.Count(static shell => shell.Acceptable))); } - [Test] - [Timeout(60_000)] - public async Task UpdateConfigAsync_Should_Persist_And_Clear_The_Shell(CancellationToken cancellationToken) - { - using var client = server.CreateClient(); - var shells = await client.Config.GetShellsAsync(cancellationToken: cancellationToken); - var shell = shells.Shells.First(static candidate => candidate.Acceptable).Path; - var cleanup = new OwnedCleanup(TimeSpan.FromSeconds(10)); - var cleared = false; - Exception? failure = null; - cleanup.Own("clear isolated shell config", async token => - { - if (!cleared) - { - _ = await client.Experimental.UpdateConfigAsync( - new ExperimentalConfigUpdateRequest { Shell = null, }, cancellationToken: token); - } - }); - try - { - var set = await client.Experimental.UpdateConfigAsync( - new ExperimentalConfigUpdateRequest { Shell = shell, }, cancellationToken: cancellationToken); - await Assert.That(set.Status).IsEqualTo(204); - var jsonOptions = new JsonDocumentOptions - { - AllowTrailingCommas = true, - CommentHandling = JsonCommentHandling.Skip, - }; - using var afterSet = JsonDocument.Parse(await server.ReadGlobalConfigAsync(cancellationToken), jsonOptions); - await Assert.That(afterSet.RootElement.GetProperty("shell").GetString()).IsEqualTo(shell); - - var clear = await client.Experimental.UpdateConfigAsync( - new ExperimentalConfigUpdateRequest { Shell = null, }, cancellationToken: cancellationToken); - await Assert.That(clear.Status).IsEqualTo(204); - using var afterClear = JsonDocument.Parse(await server.ReadGlobalConfigAsync(cancellationToken), jsonOptions); - await Assert.That(afterClear.RootElement.TryGetProperty("shell", out _)).IsFalse(); - cleared = true; - } - catch (Exception exception) - { - failure = exception; - } - - await cleanup.CompleteAsync(failure); - } - /// - /// config.get answers the location's loaded configuration, and experimental.config.update - /// writes the global file and only requests a reload (debounced, asynchronous) that publishes - /// config.updated once the loaded entries change. So the read-back waits for that event, - /// never for a clock, and reads again after each one until the write shows. + /// experimental.config.update writes the global file and only requests a reload + /// (debounced, asynchronous) that publishes config.updated once the loaded entries + /// change, and config.get answers the loaded configuration. So each write is checked in + /// the file at once, and its read-back waits for that event, never for a clock, reading again + /// after each one until the write shows. This is the only test that writes the shared server's + /// configuration, and it ends only once the loaded state is back at its start: a write that + /// returns the file to the loaded state inside the debounce publishes nothing, so a second + /// writer that left a reload pending would starve the next one's event. /// [Test] [Timeout(180_000)] - public async Task GetConfigAsync_Should_Read_Back_The_Shell_UpdateConfig_Wrote(CancellationToken cancellationToken) + public async Task UpdateConfigAsync_Should_Persist_The_Shell_GetConfigAsync_Reads_Back_And_Clear_It(CancellationToken cancellationToken) { using var client = server.CreateClient(); var shells = await client.Config.GetShellsAsync(cancellationToken: cancellationToken); @@ -120,11 +78,26 @@ public async Task GetConfigAsync_Should_Read_Back_The_Shell_UpdateConfig_Wrote(C var set = await client.Experimental.UpdateConfigAsync( new ExperimentalConfigUpdateRequest { Shell = shell, }, cancellationToken: cancellationToken); await Assert.That(set.Status).IsEqualTo(204); + var jsonOptions = new JsonDocumentOptions + { + AllowTrailingCommas = true, + CommentHandling = JsonCommentHandling.Skip, + }; + using (var afterSet = JsonDocument.Parse(await server.ReadGlobalConfigAsync(cancellationToken), jsonOptions)) + { + await Assert.That(afterSet.RootElement.GetProperty("shell").GetString()).IsEqualTo(shell); + } + var reloaded = await WaitForShellsAsync(client, probe, after: null, shells => shells.Contains(shell, StringComparer.Ordinal), "the written shell", cancellationToken); var clear = await client.Experimental.UpdateConfigAsync( new ExperimentalConfigUpdateRequest { Shell = null, }, cancellationToken: cancellationToken); await Assert.That(clear.Status).IsEqualTo(204); + using (var afterClear = JsonDocument.Parse(await server.ReadGlobalConfigAsync(cancellationToken), jsonOptions)) + { + await Assert.That(afterClear.RootElement.TryGetProperty("shell", out _)).IsFalse(); + } + _ = await WaitForShellsAsync(client, probe, reloaded, shells => !shells.Contains(shell, StringComparer.Ordinal), "the cleared shell", cancellationToken); cleared = true; } diff --git a/tools/jscpd/tests.baseline.json b/tools/jscpd/tests.baseline.json index a8eb5ad0..c3fe57c1 100644 --- a/tools/jscpd/tests.baseline.json +++ b/tools/jscpd/tests.baseline.json @@ -192,7 +192,6 @@ "411bf938cdbb8b64": 1, "411f04f073e9ad3f": 1, "4192fdb04dccceeb": 1, - "41ff4bb4c970a18a": 1, "4202e901bcb4f8ba": 1, "42e82776f30ff817": 1, "4372939ffb05b447": 1, @@ -523,7 +522,6 @@ "c59efd159d199356": 1, "c661cc78e1086df2": 1, "c6cce4bd6bf12d59": 1, - "c96ee9924b3f4ea7": 1, "c99b81e4ed3d76fb": 1, "c99b9aa8b9c89f27": 1, "c9a68b22b15b2c4f": 1,