Skip to content

fix(#877): make QuickFiler.Test self-sufficient for the netstandard 2.1 bind - #880

Merged
drmoisan merged 9 commits into
mainfrom
bug/quickfiler-test-assembly-resolve-self-sufficiency-877
Sep 13, 2026
Merged

drmoisan merged 9 commits into
mainfrom
bug/quickfiler-test-assembly-resolve-self-sufficiency-877

Conversation

@drmoisan

Copy link
Copy Markdown
Owner

Summary

QuickFiler.Test was not self-sufficient for assembly resolution. Three of its tests load Deedle, and that load requires a bind to netstandard, Version=2.1.0.0, PublicKeyToken=cc7b13ffcd2ddd51 that nothing in the repository or on the build machine satisfies. The bind succeeds only while a process-global AppDomain.CurrentDomain.AssemblyResolve fallback matching on simple name plus public key token is already installed. QuickFiler.Test installed no such fallback; it had been borrowing one by accident from SVGControl, whose resolver is installed lazily from the SVGControl.SvgRenderer static constructor and therefore only after some earlier test class touches an SVG-bearing control. When the Deedle-using class runs first, the bind fails.

This installs the fallback in QuickFiler.Test's own [AssemblyInitialize], which MSTest runs before any test in that assembly, so the assembly no longer depends on what ran before it.

Fixes #877

Verified mechanism

The netstandard 2.1.0.0 requirement enters the closure through the FSharp.Core redirect, not through Deedle:

  • Deedle.dll references FSharp.Core 4.5.0.0 and netstandard 2.0.0.0 only.
  • Both test projects' app.config redirect FSharp.Core to 11.0.0.0.
  • The deployed FSharp.Core 11.0.0.0 references netstandard 2.1.0.0.
  • netstandard 2.1.0.0 exists nowhere on the machine; the GAC holds only v4.0_2.0.0.0__cc7b13ffcd2ddd51, and no *.config in the repository contains the string netstandard.
  • Both versions share the public key token, so a handler matching on simple name plus token resolves the request while the default binder cannot.

Two earlier explanations were held with confidence and are recorded as refuted in issue.md, with the evidence that killed each: sibling-assembly initialiser ordering, and a Deedle static-initialiser race. Neither was implemented.

Why only one test run proves this

The suite result is nondeterministic. Two runs of the identical full-assembly command, both totalling 1395 tests, disagreed: 1392 passed / 3 FAILED on one occasion and 1395 passed / 0 failed on another. Whether the bind succeeds depends on which class happens to touch SvgRenderer first, which the run shape does not fix.

The discriminating run is the Deedle-using class executed alone, with no runsettings file, so no other class can install a resolver first:

Run Total Passed Failed Exit
Before the fix 3 0 3 1
After, run 1 3 3 0 0
After, run 2 3 3 0 0
After, run 3 3 3 0 0
After, independent fourth run 3 3 0 0

The full-assembly run (1395 passed, exit 0) and the UtilitiesCS.Test run (4926 passed, exit 0) are regression checks only and are explicitly non-probative. Neither can confirm this fix and neither can refute it; they are reported to show nothing got worse.

Changes

Five files. Zero production source files are touched.

  • TestSupport/TestAssemblyResolver.cs (new) — the shared resolver, linked into both test projects from one file so the two copies cannot drift. Placed at the repository root because putting it inside either test project would make one test assembly reach into the other's folder.
  • QuickFiler.Test/QuickFiler.Test.csproj, UtilitiesCS.Test/UtilitiesCS.Test.csproj — one additive <Compile> item each with a <Link>. Both are legacy non-SDK projects with explicit items, so nothing is globbed and the link must be declared by hand.
  • QuickFiler.Test/SetupAssemblyInitializer.cs — installs the resolver alongside the existing EnableVisualStyles and SetCompatibleTextRenderingDefault calls.
  • UtilitiesCS.Test/TestAssemblyInitializer.cs — rewired to the shared type; behaviour unchanged in effect.

The resolver is spelled with an explicit null test rather than a null-coalescing assignment: QuickFiler.Test declares no <LangVersion> and targets v4.8.1, so it compiles at the C# 7.3 default where ??= is CS8370. The semantics are identical.

SVGControl is not modified. Three near-identical resolvers exist after this change, which is accepted; consolidating them into production code has a different blast radius and is out of scope.

Out of scope

The same unsatisfiable bind exists in production, where only the lazy SVGControl handler is available. The add-in appears safe only because the VSTO surface renders SVG before anything reaches Deedle, which is an ordering accident rather than a guarantee. Tracked separately as #879.

Validation

  • CSharpier format and check: exit 0, zero Was not formatted.
  • .NET analyzers (/t:Rebuild): exit 0, 0 Warning(s), 0 Error(s).
  • Nullable (/t:Rebuild /p:TreatWarningsAsErrors=true): exit 0, 0 Warning(s), 0 Error(s).
  • Discriminating run: 3/3 passed on four separate post-fix runs.

scripts/vscode/TaskMaster.cli.runsettings is unmodified (SHA-256 unchanged). The diff contains no [DoNotParallelize], no Workers change, no retry, no sleep and no timing tolerance. All 8 acceptance criteria are checked off in issue.md.

Coverage gates are not the gate for this change: it touches zero production files, so no coverage figure is attributable to it and none is claimed.

🤖 Generated with Claude Code

drmoisan and others added 9 commits September 13, 2026 08:45
…older

Issue 877: QuickFiler.Test relies on an AppDomain.CurrentDomain.AssemblyResolve handler installed by UtilitiesCS.Test, so it fails under class-level parallelism. Seeds issue.md with the diagnosis and an explicit Acceptance Criteria section.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The deny text names the correct feature folder and blames a missing Work Mode marker, but folder resolution succeeded and a repo-relative Test-Path at hook line 108 resolved against the session root instead. Observed on a standalone child worktree, not only in parallel items.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Replaces the refuted sibling-assembly attribution in Summary, Suspected Cause and AC1-AC5 with the verified mechanism, and records both refuted explanations with the evidence that killed them. AC6-AC8 are unchanged. Adds the M-matrix and the pre-fix evidence projections.

Promotes the production-side risk as issue #879 rather than folding it into 877.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four preflight rounds, 25 defects closed. Two were existential: every pwsh payload would have run in the coordinator session worktree, which has its own TaskMaster.sln, so msbuild would have built the wrong checkout and csharpier would have reformatted another tree; and vstest.console.exe is not on PATH, so no test span would have resolved. The plan now mandates a Set-Location prefix on every payload and vswhere resolution for vstest. M3 run alone with no runsettings is recorded as the sole discriminator; M2 and the full suite are non-probative regression checks.
…solve self-sufficiency

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… a shared TestSupport source file

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… acceptance criteria check-off

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
pwsh payloads start in the coordinator session worktree, which has its own TaskMaster.sln, so an unqualified msbuild builds the wrong checkout and returns a vacuously green result. Delta application is itself a defect source: rounds 2 and 3 mostly found damage from applying the prior round's delta.
@drmoisan
drmoisan merged commit a5622ab into main Sep 13, 2026
5 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.

Test isolation violation: Deedle static initialisation is unsafe under MSTest class-level parallelism in QuickFiler.Test

1 participant