test(runtime): reach the runtime and the store through one harness #39

Merged
james.maes merged 13 commits from test/2.0-runtime-harness-20261110 into release/2.0 2026-11-11 01:06:00 +00:00
Owner

Test code only: nothing under src changes and no existing test changes what it proves. It puts one layer between the runtime tests and the two product types the multi-server build is about to split, so later steps change one helper instead of some fifty test files.

What is new (all in tests/Qqq.PrintAgent.Cli.Tests)

  • SingleServerHarness: the only test type that names AgentRuntime. Tests reach the snapshot, sign-in, sign-out, enrollment, the service loop, the pipe and download pacing through it. It generates its own pipe name and refuses the installed service's.
  • TestState: the only place tests construct the store (through the alias JournalStore), and the only source of a raw journal connection, which is unpooled so a test can never disturb the store's connection pool.
  • ScriptedPrintServer: the scripted server taken out of the realtime fixture, with no shared state, so a later fixture can run several servers against one host. It refuses and counts a request addressed to another server.
  • The synthetic printer can hold one printer's queue while another prints.

Evidence that no existing test proves less: test cases go from 984 to 992 on this base (the eight new ones are the helpers' own tests); attribute and assertion counts are identical per pre-existing file; with the renames normalized, every pre-existing test file is byte-identical except eleven whose remaining lines are not assertions (pipe names the harness now generates, hand-written pipe exchanges replaced by the same exchange in the harness, raw connections now from TestState).

Fixture teardown order, stated because it moved: the fixture's stop token is cancelled, which ends its waits and cancels the runtime's loop; the printer hold is released; the scripted server cancels its own token, which is what now stops the connection pumps, then releases a held claim and closes its connections; the fixture waits for the runtime to end; and the server stops its listener. Before, one token stopped the runtime and the pumps in the same statement and the claim hold was released before the printer hold. No test observes the difference. A wait cancelled during the server's WELCOME send now surfaces as the fixture's own diagnostic instead of a bare cancellation; only the failure text of an already failing test changes.

Merge as a squash: eleven of the thirteen commits do not compile on their own on this base, because a download-pacing test arrived with the runtime seam and the harness gains the member it calls only in the last commit.

Verification, local, on the reviewed head 78a0de0 (on c3efed4): build/Verify.ps1 passed twice: 766 Core, 1,117 Windows/command-line and 352 presentation tests, zero warnings; Core, command line and presentation 100%; Windows 74.7 to 74.8% (minimum 73, unchanged: no product line was touched). One further run failed only in CliProcessTests.PrinterWorkerArgument_IsAnUnknownCommand, a known flake under machine load that this change does not touch and that has its own fix.

Review: independent, two passes by the same reviewer. First pass: nothing that weakens an existing test; two should-fix items in the new code (a vacuous assertion, the store's type name spelled in 25 files), both fixed. Second pass, on head 78a0de0 including the fix round and the last pass-through commit: "Nothing at should-fix or above remains or was introduced in the tree at 78a0de0." Four nits are left for the multi-server fixture step: a shared-state case one test does not catch, a printer test that would hang instead of failing, a test name that claims more than it shows, and a counter the fixture does not surface.

🤖 Generated with Claude Code

Test code only: nothing under `src` changes and no existing test changes what it proves. It puts one layer between the runtime tests and the two product types the multi-server build is about to split, so later steps change one helper instead of some fifty test files. **What is new** (all in `tests/Qqq.PrintAgent.Cli.Tests`) - `SingleServerHarness`: the only test type that names `AgentRuntime`. Tests reach the snapshot, sign-in, sign-out, enrollment, the service loop, the pipe and download pacing through it. It generates its own pipe name and refuses the installed service's. - `TestState`: the only place tests construct the store (through the alias `JournalStore`), and the only source of a raw journal connection, which is unpooled so a test can never disturb the store's connection pool. - `ScriptedPrintServer`: the scripted server taken out of the realtime fixture, with no shared state, so a later fixture can run several servers against one host. It refuses and counts a request addressed to another server. - The synthetic printer can hold one printer's queue while another prints. **Evidence that no existing test proves less**: test cases go from 984 to 992 on this base (the eight new ones are the helpers' own tests); attribute and assertion counts are identical per pre-existing file; with the renames normalized, every pre-existing test file is byte-identical except eleven whose remaining lines are not assertions (pipe names the harness now generates, hand-written pipe exchanges replaced by the same exchange in the harness, raw connections now from `TestState`). **Fixture teardown order**, stated because it moved: the fixture's stop token is cancelled, which ends its waits and cancels the runtime's loop; the printer hold is released; the scripted server cancels its own token, which is what now stops the connection pumps, then releases a held claim and closes its connections; the fixture waits for the runtime to end; and the server stops its listener. Before, one token stopped the runtime and the pumps in the same statement and the claim hold was released before the printer hold. No test observes the difference. A wait cancelled during the server's WELCOME send now surfaces as the fixture's own diagnostic instead of a bare cancellation; only the failure text of an already failing test changes. **Merge as a squash**: eleven of the thirteen commits do not compile on their own on this base, because a download-pacing test arrived with the runtime seam and the harness gains the member it calls only in the last commit. **Verification**, local, on the reviewed head `78a0de0` (on `c3efed4`): `build/Verify.ps1` passed twice: 766 Core, 1,117 Windows/command-line and 352 presentation tests, zero warnings; Core, command line and presentation 100%; Windows 74.7 to 74.8% (minimum 73, unchanged: no product line was touched). One further run failed only in `CliProcessTests.PrinterWorkerArgument_IsAnUnknownCommand`, a known flake under machine load that this change does not touch and that has its own fix. **Review**: independent, two passes by the same reviewer. First pass: nothing that weakens an existing test; two should-fix items in the new code (a vacuous assertion, the store's type name spelled in 25 files), both fixed. Second pass, on head `78a0de0` including the fix round and the last pass-through commit: "Nothing at should-fix or above remains or was introduced in the tree at `78a0de0`." Four nits are left for the multi-server fixture step: a shared-state case one test does not catch, a printer test that would hang instead of failing, a test name that claims more than it shows, and a counter the fixture does not surface. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
james.maes added 13 commits 2026-11-11 01:05:57 +00:00
The realtime peer, the HTTP handler, the job queue and the accepted
connections become ScriptedPrintServer, which knows nothing about the
runtime it answers. RealtimeRuntimeFixture keeps one and passes through
the members tests use, so a later fixture can put several servers
behind one service. Tests name the connection type by its new home;
nothing else in them changes. One new test shows two servers share no
state.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
SingleServerHarness wraps the runtime for the one-server case and is
now the only test type that names AgentRuntime: the fixtures build it,
tests hold it, and the constants and pure functions tests read from
the runtime class are read from it. It makes the management pipe
itself, so the tests that built a pipe by hand, or wrote a command to
it by hand, ask the harness instead. When the runtime becomes a host
with one connection per server, this file changes and the tests do not.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
TestState.OpenStore is now the only place the test project constructs
AgentStore: 101 constructions in 24 files become calls to it, and the
store's static lock and connection-string lookups are read through it
too. Tests still declare the store by its type, because the build
refuses var where the type is not apparent. When the store is opened
for one server out of a registry, the constructor call changes in one
file.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
Six places in five test files opened their own SQLite connection to a
journal, to seed rows or to damage the schema. Those connections used
the same pool as the stores on that journal without taking the
journal's schema lock, so the pool race the store's lock closes was
still open to them. TestState.OpenJournal opens such a connection with
pooling off, and each of those places now gets its connection there.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
BlockPrinter names a printer whose submissions stay inside the fake
until they are released, while submissions to the other queue pass
through. BlockPrint, which holds everything, is unchanged. No test
uses the new switch yet; the tests of claiming for an idle printer
while another is busy need it.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
The section on testing the service runtime now names the three test
helpers a new test goes through: SingleServerHarness for the runtime,
ScriptedPrintServer for the server, and TestState for a store or a
connection of the test's own to a journal, which must be unpooled.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
The summary placed both at the end of the file; the constants are at
the top.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
A direct test of the fake: with BlockPrinter set, a submission to the
named queue stays inside the printer, a later submission to the other
queue finishes, and the held one finishes only when released.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
The harness said its pipe is never the installed service's, but a
caller could pass that name. The constructor now refuses it, in any
letter case, before the runtime is built, and a test shows it. The
harness's private field gets its summary.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
The first test of two servers checked the second's queue after the
first's only job had been claimed, so it passed with a shared queue.
The queues are now compared while the job is still queued, and the
tests go on to what a fixture with several servers leans on: each
server's claimed job, its acknowledged connection, a hold and a
refusal set on one, and closing one while the other answers.

The HTTP handler now refuses and counts a request addressed to another
server's origin, so a session built on the wrong server's handler
shows at once. The private fields of the server and its connection
get their summaries.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
With no queue named, a job that names no printer compared equal to
the unset switch and was held. The switch is now compared only when
set, with a test. The test of the held queue reads the submission and
print counts instead of the entry signal, which either queue raises,
and says that it drives the printer directly.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
Tests declared the store by the product type's name in 104 lines of
25 files, so the step that renames that type would have edited all of
them. TestState.cs now declares the alias JournalStore, and every test
and fixture uses it. The product type's name appears in the test
project only in TestState.cs.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
The download-pacing tests that arrived with the runtime seam call the
runtime through the fixture; the harness now forwards that one member.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
james.maes merged commit 6f0237dde8 into release/2.0 2026-11-11 01:06:00 +00:00
Sign in to join this conversation.