Skip to content

test: one config test owns the shared server's configuration (#440) - #443

Merged
Blind-Striker merged 1 commit into
masterfrom
fix/440-config-single-writer
Oct 17, 2026
Merged

Blind-Striker merged 1 commit into
masterfrom
fix/440-config-single-writer

Conversation

@Blind-Striker

@Blind-Striker Blind-Striker commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator

What

ConfigClientLiveTests now has one test that writes the shared server's configuration, and it settles the loaded state after each write. That removes the cross-test residue that ejected #439 from the merge queue (#440).

Fixes #440.

Root cause

The comment on #440 has the measurement in full. In short:

  • Upstream behaves as designed. At the pin, packages/core/src/config.ts:316-328, 380-401:
    • experimental.config.update writes the global file and only requests a reload, through a sliding PubSub(1) with a 100 ms debounce, serialized under reloadLock;
    • the reload publishes config.updated only if the loaded entries change.
  • The two tests shared the server and its config. UpdateConfigAsync_Should_Persist_And_Clear_The_Shell wrote the shell and then null, but checked only the file, so it ended with reloads still pending. When the runner was slow, the next test's identical write landed inside that pending reload. The reload saw no change and published nothing, and the read-back test waited 60 s for an event that never came.
  • Why net48 only: it is the only leg where the read-back test runs after the persist test. The net10.0 and net11.0 legs run them in the opposite order.

Red repro, measured before the fix

A scratch test against the pinned simulated server, not committed, ran five rounds:

  1. Write the shell, then wait for config.updated (the loaded state is now the shell).
  2. Write null and then the shell, back to back. The pair took 6.7–9.5 ms, inside the debounce.

Result: no config.updated arrived within 5 s in 5 of 5 rounds, and the loaded config still showed the shell. As a control, a real change (shell → null) published the event in 5 of 5 rounds.

The change

  • Folded into one test. The persist test is deleted. Its two file assertions move into the read-back test, right after each 204:

    • after the set, the file's shell equals the path;
    • after the clear, the file has no shell property.

    The test is renamed UpdateConfigAsync_Should_Persist_The_Shell_GetConfigAsync_Reads_Back_And_Clear_It, because it now proves the file write, the reload and the clear.

  • Its doc comment records that it is the only writer of the shared server's configuration, and why.

  • No other writer. ConfigClientLiveTests holds the only UpdateConfigAsync calls, and ConfigClientContractTests runs on a fake scenario. No test writes into the run root's config/opencode at test time, and none of the other 40 SimulatedDriveServerFixture users touches the shell or the global file.

  • No retry, and no bound was widened.

Rejected: state-first

Reading config.get before waiting for the event would also stop the timeout. It was rejected because, against a stale loaded shell, it can pass without proving that this test's write reached the loaded configuration. The event-then-read order is what makes the read-back a proof.

Proof

  • The class: 2/2 on net10.0 and on net472.
  • The whole OpenCode.Sdk.Tools.Tests project (Category!=Packaging&Category!=NativeAot): 2019/2019.
  • verify-clones: 0 new (tests 5.42%, under the unchanged 6.0% ceiling).
  • Clone baseline: tools/jscpd/tests.baseline.json only removes two entries, 41ff4bb4c970a18a and c96ee9924b3f4ea7, and adds none. Those were the duplicated config arrange that the fold eliminated; left in, they would fail the gate as stale. This is the ratchet moving down.
  • Slopwatch: 0.
  • Format: both passes clean.
  • Local check: GATE PASS on 9253bde3 (master 3b235436): all 8 steps, and 10,482 tests including net472 with 0 failed. The first run failed verify-clones because the two baseline entries the fold removed were still listed; this commit drops them.

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

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.
@Blind-Striker
Blind-Striker added this pull request to the merge queue Oct 17, 2026
Merged via the queue into master with commit cf62b17 Oct 17, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Flaky live test: GetConfigAsync_Should_Read_Back_The_Shell_UpdateConfig_Wrote never sees config.updated (Windows net48)

1 participant