acdream/docs/reviews/test-quality-audit.md
2026-08-18 09:09:38 +02:00

28 KiB

Test-quality audit

Status: complete at the recorded baseline.

Baseline

  • 1,260 tracked C# files under tests/, totaling 402,131 lines.
  • 12 primary test projects, plus fixture/helper projects.
  • The reproducible per-project Release total is currently 14,747 passes and 77 reported skips. One additional duplicate-ID theory row is silently dropped by xUnit discovery. The official all-solution process does not currently complete reliably because of the launcher deadlock described below.
  • The Release build emits 26 warnings, all in test projects. These include nullable-flow problems, dead fixture fields, xUnit assertion-style findings, and a duplicate InlineData warning.

A clean Rebuild classifies those 26 as: seven CS8602; three each CS0649, CS8600, CS8604, and CS8767; two each xUnit2013 and xUnit2017; and one each CS8625, xUnit1025, and xUnit2000. An incremental build can misleadingly print zero because no test compiler/analyzer target reruns; the warning baseline must therefore come from a clean/rebuild gate.

Executable baseline by test assembly

Assembly Passed Skipped Notes
App 5,510 76 installed-DAT/GPU/manual/regression groups collapse into dynamic skips; many other gated facts silently no-op
Bake 21 0 completed
CLI 4 0 completed
Content 154 0 completed
Core.Net 1,004 0 three disabled live-network facts are reported as passed
Core 4,797 1 plus one duplicate-ID theory row dropped during discovery; many absent-DAT facts silently no-op
Headless 166 0 completed
Launcher.Core 338 0 passes alone in 48 s; deadlocks in full run
Launcher 67 0 completed
Platform 4 0 completed
Runtime 1,756 0 completed
UI.Abstractions 926 0 completed

Review criteria

Every test source file and parameter/data source will be checked for:

  • name/behavior/assertion agreement;
  • a meaningful regression signal rather than successful execution only;
  • independence from reimplemented production logic;
  • redundancy with stronger tests;
  • stable public behavior versus implementation detail;
  • deterministic time, ordering, filesystem, culture, and environment handling;
  • skip/manual/live-DAT intent and discoverability;
  • correct layer/project placement;
  • fixtures that can actually fail for the behavior named;
  • data rows that exercise distinct boundaries rather than inflating counts.

The all-file pass combines syntax-tree method inventory with a per-file signal matrix (attributed methods, assertions/exception oracles, early returns, environment/asset gates, sleeps/delays, source-text reads, diagnostic naming, and helper-local assertions). It is followed by manual body/helper review for every exception class identified by that matrix. The project totals below make the breadth visible; counts are lexical triage signals, not quality scores:

Test area Files Attributed methods Assert.* calls empty-return sites environment reads sleep/delay sites file-reading heuristic
App 515 4,812 20,997 121 73 10 43
Bake 5 19 91 1 1 0 0
CLI 1 4 19 0 0 0 1
Content 29 134 716 25 6 1 0
Core.Net 115 867 3,467 30 15 21 0
Core 398 3,398 8,576 70 1 9 6
Headless 18 140 806 5 0 5 4
Launcher.Core 27 232 989 19 0 13 10
Launcher 7 38 261 0 0 4 1
Platform 2 4 17 0 0 0 0
Runtime 91 1,344 8,601 64 0 1 3
UI.Abstractions 46 462 1,228 1 0 5 2

Fixture/helper projects were reviewed with their consumers and contain no attributed tests. An assertion count includes helper assertions and repeated parameterized-oracle calls; it is not an executable-case count. The final column is deliberately broad (it also sees fixtures and result files); T-012's manual follow-up is the exact 30-file production-source-reading subset.

Skip and conditional-execution inventory

Source Declared reason Initial concern
AcDream.App.Tests/UI/Layout/CharacterManagementLiveDatTests.cs:373-383 installed-DAT/probe gate dynamic skip behavior and release discoverability
AcDream.App.Tests/UI/Layout/ChatLayoutFixtureGenerator.cs:29 manual fixture generator generator represented as a skipped test
AcDream.App.Tests/UI/Layout/RadarLayoutFixtureGenerator.cs:14 manual fixture generator generator represented as a skipped test
AcDream.App.Tests/Rendering/TowerAscentReplayTests.cs:206 unresolved issue #119 residual permanent red test hidden as skip
AcDream.Core.Tests/Conformance/PvsConformanceTests.cs:22 “P0 scaffold” for future capture incomplete scaffold counted in the suite

The custom InstalledDatFactAttribute is used by 73 methods. It reports a real xUnit skip unless ACDREAM_PROBE_LIVE_MOUNT=1, but its single LA8-specific reason is inherited by unrelated character creation, map, tooltip, and other installed-DAT suites. In contrast, 294 attributed methods across 107 files contain method-level empty return statements. The syntax inventory divides the 314 return sites as follows (a method can contain more than one category):

Gate type Return sites Methods Files Result when gate is unavailable
DAT/package/fixture 251 248 85 pass, without exercising the named behavior
opt-in environment variable 23 23 9 pass, without exercising the named behavior
operating system 21 19 11 pass on the unsupported OS
other condition 19 18 12 varies; several suppress missing expected data

The first two groups are not included in xUnit's 77 reported skips. Therefore the executable totals above describe discovered and completed cases, not the number of named contracts that were actually exercised.

Confirmed test-quality findings

T-001 — Full-suite launcher test exposes a production lock inversion

LauncherProcessSupervisorTests.ANullStderrLogPathBehavesExactlyAsBeforeForBothChildProcessKinds is not itself low-value, but its unbounded using-scope disposal exposed a real production deadlock. The managed stack proves a supervisor-lock / Process- lock inversion between LauncherProcessSupervisor.Dispose and the process exit callback. The test needs a deterministic race harness after the production fix; the current timing-dependent version alternates between passing in 48 s and hanging indefinitely.

Post-baseline resolution checkpoint (2026-08-18, 0a934cf5): DisposeAllowsAnAlreadyCapturedExitCallbackToComplete is now the deterministic race harness. Explicit barriers make the fake child capture the exit delegate, release it from inside disposal, and wait for the callback to return. The old lock shape fails boundedly after five seconds; the fixed shape passed 25/25 fresh-process repetitions, all 22 supervisor tests, Launcher.Core 339/339, and two serialized complete-solution runs of 14,748 passes / 77 skips. The original real-process null-stderr test remains as end-to-end coverage rather than the only accidental race trigger.

T-002 — One physics assertion is a literal tautology

Issue265SteepSlopeCaptureBisectTests.cs:920 asserts Assert.Equal(newModelVelocityBeforeToggle.Z > 0.01f, newModelVelocityBeforeToggle.Z > 0.01f). It cannot fail and therefore proves nothing about the comparison described by the surrounding comment. This is a genuinely useless assertion inside an otherwise evidence-oriented test. It is already recorded as open issue #342.

T-003 — Duplicate theory data is silently discarded

MotionInterpreterTests.cs:491-497 supplies both MotionCommand.Crouch and literal 0x41000012u; those values are identical. xUnit emits duplicate case ID 6a4edd... and skips the second row during discovery. The test name is accurate, but the duplicate row is redundant and the reported suite total does not reveal it. The build's xUnit1025 warning and issue #228 already acknowledge this.

T-004 — Test warnings include dead fixture state and contract mismatches

The current build reports 26 warnings. Examples requiring source-level review include never-assigned BeginTurnBlocked/BeginTurnUnblocked fields in RemoteChaseEndToEndHarnessTests.cs:130-131, nullable dereferences in installed- DAT inspection tests, and nullability-mismatched fake implementations in App composition tests. These are not all equivalent, but a release gate that normalizes them as an old fixed warning count makes new warning regressions invisible.

T-005 — At least 52 tests exercise an unreachable UI presentation stack

The IPanelRenderer widget/menu tests and the ChatPanel layout/input/focus tests are internally meaningful, but no production IPanelRenderer or IPanelHost exists and no shipping code constructs ChatPanel, DebugPanel, or VitalsPanel. The former ImGui implementation was deleted. These tests are therefore obsolete release tests rather than tautologies: they can detect changes in compiled dead code, but cannot protect user-visible retained UI. They should be removed with that abandoned stack or moved behind a clearly owned compatibility contract if the panel API is intentionally supported.

T-006 — The installed-DAT attribute gives 73 unrelated tests one misleading skip identity

InstalledDatFactAttribute is declared inside CharacterManagementLiveDatTests.cs:367-383 and always says “installed-DAT LA8 gate.” Repository-wide attribute inspection finds 73 methods using it, including character creation and other UI/DAT areas unrelated to LA8. The attribute does correctly produce a reported xUnit skip, but the reason does not identify the prerequisite or contract of the test that was skipped. Split the fixture capability from test-specific skip messages, and publish installed-DAT gate results by suite rather than as one campaign-era bucket.

T-007 — 271 gated facts report success without exercising their contract

A syntax-tree scan of every attributed method found 248 methods across 85 files that empty-return when DATs, a prepared package, or a replay fixture are absent, plus 23 methods across nine files that empty-return unless an opt-in environment variable is set. Examples include the three LiveHandshakeTests (:38, :115, :265), whose own comment calls the return a skip although xUnit reports all three as passed; 17 UI live-mount/powerbar probes; and broad App/Core rendering and physics suites guarded by datDir is null. The pattern also appears in all three Bake determinism facts. These names look like normal regression contracts and their passes are included in headline totals. Use a real skip/fail mechanism, group them under explicit installed-DAT/package/live traits, and make release reporting state exactly which group ran.

T-008 — Several diagnostic “tests” are deliberately incapable of failing

The whole SmokeTest.TestProject_IsWired body is Assert.True(true). CellarLipWedgeTests.cs:99,305,331, CellarUpTrajectoryReplayTests.cs:315,372, and DoorCollisionApparatusTests.cs:238 end diagnostic output with an always-true assertion; comments explicitly say some always pass. GpuContractTests.cs:257 compares the same enum constant to itself inside an otherwise useful test. These are tools disguised as release tests. Move output-only apparatus to an explicit diagnostic command/trait, delete the wiring smoke test, and replace the useful diagnostics with assertions over the observed invariant.

T-009 — Double-click tests use wall-clock sleeps instead of a controllable clock

InputDispatcherDoubleClickTests.cs:53,73,89,106,108 sleeps for 10 or 600 ms to cross real timing boundaries. This adds at least 640 ms to a small unit-test class and leaves its outcome dependent on scheduler timing because production InputDispatcher owns the clock. Inject TimeProvider (or a narrow monotonic clock contract) and advance fake time deterministically.

T-010 — Two entire tests have no runtime contract to protect

SmokeTest.TestProject_IsWired is the literal tautology described in T-008. ChaseCameraTests.ImplementsICamera merely assigns a ChaseCamera to an ICamera variable and calls ToString; compilation already proves the stated interface relationship, and the call has no assertion or behavioral signal. Both tests are useless as maintained release cases. The compile-only interface check should be deleted or replaced by a named interface behavior test.

T-011 — 51 release-suite methods are output-only diagnostic apparatus

A method-body and same-file helper scan found 51 attributed methods across 27 files that have no assertion, exception expectation, or other failure signal; their observable contract is limited to diagnostic output. This count excludes legitimate explicit no-throw cases such as no-op disposal, empty lifecycle hooks, and secure/loopback transport construction. It also excludes tests whose assertions live in a helper that the scanner can resolve.

Many names honestly say Probe, Dump, Diagnostic, Inspection, or Characterize; these are useful investigation programs in the wrong execution surface, not regression tests. The more serious naming problem is the subset that reads like a behavioral contract—especially the facility-hub scenarios, stair/camera retention sweeps, ambient-slot existence tests, production- emission replication, and drawn-polygon comparisons—while only printing the calculated value. Most DAT-backed examples also combine with T-007: they pass without output when their local fixture is absent.

Exact inventory (method names in one cell belong to the named file):

Test file Output-only attributed methods
App.Tests/Rendering/CornerFloodReplayTests.cs Scratch_ReciprocalPrimitive_SyntheticPair
App.Tests/Rendering/Issue131SetupProbeTests.cs Diagnostic_LookInFlood_AdmitsHallPorchFromCottage; Diagnostic_DumpOutstageCandidateSetups
App.Tests/Rendering/Issue176177FacilityHubFloodReplayTests.cs ScenarioB_StairDescent_RampCellRetention; ScenarioC_CorridorSeamGazeSweep_Bistability; ScenarioE_RootLagWindow_ForwardChainRetention; ScenarioD_CorridorWalk_PerStepChurn
App.Tests/Rendering/Issue177StairDescentCameraFloodTests.cs StairCellComposition_ShellVsStatics; RealStaircase_FineYawZoomSweep_FindKnifeEdge; FloodDepthFrom015E_VsRetail26; StaircaseSweep_EyeClearanceFromCeilingPortal; ParkedYawZoomSweep_StairAdmission; Descent_RealCameraSweep_StairCellRetention
App.Tests/Rendering/Issue181VisFlapReplayTests.cs Diagnostic_FlappingCellViewRegion_SliverOrLarge
App.Tests/Rendering/Issue181WallPressEquilibriumTests.cs Diagnostic_WallPressedCamera_EyeWanderAndViewerCellStability
App.Tests/Rendering/TowerAscentReplayTests.cs Diagnostic_TopOfStairs_GateByGate
App.Tests/UI/Layout/FaPanelSlotProbeTests.cs ProbePanelSlotTable
App.Tests/UI/Layout/MapHousePanelSlotProbeTests.cs ProbeMapHousePanelSlot
App.Tests/UI/Layout/OptionsPanelLiveMountProbeTests.cs ProbeLiveMountShapes; ProbeMenuPopupSizingAndTextStyle; ProbeFilterLabelHome
App.Tests/UI/Layout/PowerbarLayoutProbeTests.cs ProbePowerbarAuthoredStrings; ProbeSecureTradeLayout; ProbeTotalItemsTemplate
App.Tests/UI/Layout/SpewBoxLayoutDumpDiagnosticTests.cs SweepInstalledLayoutDescs_ForSpewBoxElementClass
Core.Tests/Audio/EnvCellSoundEmitterInventoryTests.cs PortalDat_SetupsWithAmbientSlotSoundTables; SoundTables_WithAmbientSlots_ExistForWireBinding
Core.Tests/Conformance/CottageDoorwayCharacterizationTests.cs Characterize_CottageNeighborhood_PrintStructure; Characterize_Doorway_FindInteriorPoints
Core.Tests/Conformance/DungeonLandblockDatProbeTests.cs Probe_Dungeon0125_vs_Holtburg_A9B4
Core.Tests/Conformance/HoltburgTorchFalloffProbeTests.cs Dump_Holtburg_StaticLight_Falloffs
Core.Tests/Conformance/Issue113DoorVanishDiagnosticTests.cs DumpHoltburgBuildings_OrphanGeometry; ReplicateProductionEmission_OnPortalFills; DumpPortalFillSurfaceTypes; DumpControls_HallAndCottage
Core.Tests/Conformance/Issue113PhantomStairsDumpTests.cs Dump_Cell104_ExteriorPortalPlane_Vs_GapPoint; DumpAAB3_Watchtower_TopDownMap; DumpHallModel_PolyFlagHistogram
Core.Tests/Conformance/PvsConformanceTests.cs Pvs_CottageInterior_MatchesRetailCellDrawList (also explicitly skipped and empty)
Core.Tests/Conformance/ThresholdDivergenceDiagnosticTests.cs Diagnose_ThresholdTransitions
Core.Tests/Physics/CameraCornerSealReplayTests.cs Diagnostic_DispatchTrace_LeakPath_vs_Controls
Core.Tests/Physics/DoorSetupGfxObjInspectionTests.cs HoltburgCottage_CellPortals_DatInspection; HoltburgLandblockStatics_DatInspection
Core.Tests/Physics/Issue137CorridorSeamInspectionTests.cs CorridorSeam_FindPolygonMatchingLiveHit
Core.Tests/Physics/Issue186ConnectorCellGeometryInspectionTests.cs Dump_ConnectorCells_ShellAndCollision
Core.Tests/Physics/Issue188FadingDoorMotionTableInspectionTests.cs Reflect_DatReaderWriter_HookTypes; Dump_PedestalWeakSpot_MotionTable_HookContents
Core.Tests/Rendering/Issue176177DungeonSeamInspectionTests.cs CorridorNeighborhood_CoplanarOverlappingDrawnPolyPairs; UnderHall_DrawnPolys_SurfaceColors; CellVertexNormals_SmoothOrFaceted_Dump
Core.Tests/Rendering/Issue93TownNetworkFountainRoomLightInspectionTests.cs StaticObjects_SetupLightsCount_Dump

Move these to a separately invoked diagnostic tool/trait with artifact output, or give each a stable oracle and assertion before retaining it in release test counts. Contract-like names must not remain green when no comparison occurs.

T-012 — Temporary source-text freezes became a second implementation specification

Thirty App test files locate the repository, read production .cs files, and assert their literal contents or ordering. Across those files there are 560 Assert.Contains/DoesNotContain/Matches calls and 86 calls to custom AssertAppearsInOrder helpers (each commonly checks many fragments), spread through files containing 219 facts. Not all 219 facts use source text, and a few whole-tree negative dependency scans are valuable architecture guards; the problem is the large exact-fragment subset.

The clearest example is GameWindowSlice8BoundaryTests: its class summary calls it a “Temporary source-shape freeze” whose assertions will be replaced by functional tests as owners are extracted. It still contains nine facts, 73 literal fragment assertions, and 31 ordering-helper calls after the documented Slice J/K ownership migrations. UpdateFrameOrchestratorTests contains another 85 literal fragment assertions and 13 ordering calls. Composition and Runtime- ownership files repeat exact field names, constructor spellings, local-variable names, and call text.

These cases are not useless: they did protect mechanical campaign cutovers. They are now wrongly maintained as durable behavioral tests. Harmless renames, formatting, extraction, or equivalent refactoring can break them, while a semantic error that retains the expected text can pass. Keep structural rules that encode real layer boundaries, preferably through project references, reflection, Roslyn syntax/semantic analysis, or architecture-test tooling. Retire campaign freezes once the named functional contract exists, and avoid making literal method bodies a parallel architecture source of truth.

T-013 — A retry-stability assertion compares the controller only with itself

RuntimeLocalPlayerFirstEntryStateTests.RetryAtAwaitingActivationNeverRecommitsPublicationOrDuplicatesTheBody correctly captures the first PhysicsBody outside its three-iteration retry loop and compares every later body with it. The adjacent controller assertion at line 438 instead evaluates Assert.Same(fixture.Movement.Controller, fixture.Movement.Controller) inside each iteration. It can at most prove that two immediate property reads return the same reference; it never compares the controller across retries as the surrounding contract implies. Capture the first controller alongside body and compare subsequent iterations to that reference.

T-014 — Seven open load-sensitive tests make a green full run non-repeatable

The repository's own open issue ledger names seven distinct intermittent tests; all passed in this audit's single per-project baseline, so none should be silently ignored as “the known flake”:

Issue Test Recorded mechanism/state
#302 PortalProjectionTests.ClipToRegion_FrameOwnedStore_ReusesExactResultArray exact per-thread allocation count; observed about one failure in six isolated App runs
#308 NakEmissionTests.LossSoak_TwoPercentBidirectional_ZeroMessageLoss_LedgersConverge virtual transport clock but real 60-second DateTime.UtcNow deadline and Thread.Sleep convergence loop
#321 DatSoundCacheTests.GetWave_ConcurrentSameId_PublishesOneCanonicalWaveAndDecodesOnce unresolved test-harness versus production cache race under load
#336 RuntimeCollisionReportingStateTests.WarmedSteadyContactRefreshDoesNotAllocate exact zero-allocation assertion; measured 2,944 bytes once under full load
#340 StreamingWorkBudgetTests.DestinationAndEmptyUnloadPriorityNeverBypassPublicationBudget load-sensitive, deterministic in isolation; root cause not recorded
#346 PortalProjectionTests.ProjectToClipLease_ReusesPooledWorkWithoutResultArrays second exact-allocation assertion in the same file; repeated full-load failures
#402 LandblockBuildFactoryTests.Build_UsesTheSuppliedSharedReaderGate roughly two failures in five repeated full App runs; shared-state/timing cause unresolved

The #308 XML documentation calls the 10,000-message soak a virtual-clock test, but its convergence actually has two real-time loops and a fixed sleep. The names of the other six describe real contracts; they are not useless, but their measurement mechanisms are unreliable or potentially expose real races. They need deterministic clocks/schedulers, allocation warmup/bounds, and captured race details before the suite can be a release gate.

T-015 — Four explicit skips are tools, an empty scaffold, and a hidden known failure

Beyond the 73 custom installed-DAT skips, the remaining four reported skips are not ordinary unavailable-platform tests:

  • ChatLayoutFixtureGenerator.GenerateChatFixture and RadarLayoutFixtureGenerator.GenerateRadarFixture are manual artifact generators permanently represented as skipped facts. They have no regression oracle and belong in a documented tool command.
  • PvsConformanceTests.Pvs_CottageInterior_MatchesRetailCellDrawList is an empty future scaffold. It is genuinely useless until a captured oracle and implementation exist; a TODO/issue is clearer than a permanent green-suite skip.
  • TowerAscentReplayTests.RetailShouldSeeTwoCells_AtFailureWindow is a known #119 residual deliberately disabled “until the fix.” A skipped failing contract cannot guard against worsening or resolution; track the expected current behavior separately, or make it an explicit non-blocking known- failure lane with ownership and expiry.

Together with T-006, these explain all 77 reported skips. The release report should distinguish unavailable external prerequisites from manual generators, unfinished tests, and known product failures.

Tests whose maintained names overclaim their behavior

This is the actionable naming subset, not a demand to rename every campaign-prefixed test. A name is listed when its stated outcome is materially stronger than its oracle:

Test Why the name is wrong or misleading Classification
PvsConformanceTests.Pvs_CottageInterior_MatchesRetailCellDrawList (:23) Empty body and permanently skipped; performs no comparison. useless unfinished scaffold
SmokeTest.TestProject_IsWired (:7) Only Assert.True(true); compilation already establishes project wiring. useless tautology
ChaseCameraTests.ImplementsICamera (:59) Assignment proves only a compile-time relationship and ToString() is not asserted. useless compile-only check
GpuContractTests.TheDepthStencilAttachmentFormatCarriesAStencilAspect (:245-257) Checks clear/load fields, then “verifies” the format with Assert.Equal(GpuTextureFormat.Depth24Stencil8, GpuTextureFormat.Depth24Stencil8); the pass description carries no format at all. wrongly named and missing its central oracle
RuntimeLocalPlayerFirstEntryStateTests.RetryAtAwaitingActivationNeverRecommitsPublicationOrDuplicatesTheBody (:424-438) The body half is checked across retries, but the adjacent controller stability assertion compares the property with itself. partially overclaimed
EnvCellSoundEmitterInventoryTests.PortalDat_SetupsWithAmbientSlotSoundTables (:130) Inventories/prints candidates without asserting that any qualifying setup exists. output-only diagnostic named as existence contract
EnvCellSoundEmitterInventoryTests.SoundTables_WithAmbientSlots_ExistForWireBinding (:187) Prints the inventory without an existence or binding assertion. output-only diagnostic named as existence contract
Issue176177FacilityHubFloodReplayTests.ScenarioB_StairDescent_RampCellRetention (:101) and the ScenarioC/ScenarioD/ScenarioE peers (:143,217,242) Calculate and print retention/churn; never assert the named retention or bistability result. output-only replay named as contract
Issue113DoorVanishDiagnosticTests.ReplicateProductionEmission_OnPortalFills (:194) Replicates and prints emission; no expected result is asserted. diagnostic whose verb implies verified equivalence
Issue176177DungeonSeamInspectionTests.CorridorNeighborhood_CoplanarOverlappingDrawnPolyPairs (:247) and UnderHall_DrawnPolys_SurfaceColors (:473) Dump calculated pairs/colors without asserting the noun phrase in the name. inspections named as facts

The remaining output-only cases in T-011 generally advertise Probe, Dump, Diagnostic, Inspection, or Characterize; those names are honest, but the methods are still misplaced in the default release suite. Contract-like names in T-011 should either gain a stable oracle or be renamed and moved with the diagnostic apparatus.

T-016 — The test taxonomy preserves investigation history as its primary index

Forty-seven test files begin with an Issue### identifier, 53 filenames contain Probe, Dump, Diagnostic, Inspection, or Characterization, and 17 contain Replay. The descriptive suffix often makes an individual file understandable, so the issue-prefixed group is not automatically low-value. The maintainability problem is that issue identity and one-off evidence shape are the primary browsing taxonomy even after a behavior becomes permanent. This couples discovery to the 19,073-line issue archive and mixes durable regression contracts with T-011's output apparatus. When a test graduates into a stable contract, organize it under the owning component/behavior and retain the issue ID in a trait or comment; keep investigation programs in a separate, non-default diagnostic project.

T-017 — Shown Avalonia test windows escaped their owning UI session

Four MainWindowViewTests called window.Show() but never closed the window. One default-parallel solution run then failed during Avalonia headless cleanup: the runner/compositor touched an object from a thread other than the one that owned it, even though the Launcher project passed alone. The tests now close shown windows and pump dispatcher cleanup in finally on the owning Avalonia session. No product code, suite serialization, or retry was added. The focused class passes 13/13, and three fresh default-parallel complete-solution runs pass all 12 assemblies in 56.719, 56.321, and 58.506 seconds.

T-018 — A live stderr assertion could disable the producer it observed

ReadFileEventuallyContainingAsync used File.ReadAllTextAsync while the ProcessStartInfo stderr callbacks were still draining. Its read handle did not share writes. If the final callback overlapped that short read, the capture sink saw a sharing violation, deliberately latched into its no-throw state, and the test waited five seconds for a line it had itself caused to be dropped. The first complete repository-gate run exposed exactly that two-of-three-lines failure. The helper now opens a live reader with FileShare.ReadWrite | FileShare.Delete, matching the production status tailer; the expected lines and timeout are unchanged. The test subsequently passed 25/25 fresh-process runs and Launcher.Core passed 339/339 in the final gate.