fix: ship FSharp.Core with the F# fixture assemblies - #587
Merged
Conversation
FSharpTypes.dll built to an output directory containing only itself and its deps.json - no FSharp.Core, and no FSharp.Core entry in deps.json. The same gap propagated to CoreTests, which project-references it. Anything that reflected over the assembly threw: System.IO.FileNotFoundException: Could not load file or assembly 'FSharp.Core, Version=10.0.0.0, ...' because the F# compiler stamps FSharpInterfaceDataVersionAttribute on every assembly it emits and that attribute type lives in FSharp.Core. Cause: Microsoft.FSharp.NetSdk.props sets DisableImplicitFSharpCoreReference to true whenever ManagePackageVersionsCentrally is on, so the F# SDK's implicit FSharp.Core reference stopped flowing. Compilation still worked - fsc silently falls back to the copy bundled with whichever SDK is running - which is why this failed quietly rather than loudly, and why the fixture was also being compiled against a different FSharp.Core than the repo pins. Fix is an explicit PackageReference in both F# projects. Under central package management it resolves to the existing FSharp.Core 9.0.303 rather than introducing a second, conflicting pin. Adds CoreTests/Reflection/FSharpTypesFixtureTests to keep the fixture reflectable. Three of its four tests fail with the exact FileNotFoundException above when the reference is removed; the fourth (GetTypes) passes either way, which is precisely why the gap stayed invisible - type loading never forces the attributes to resolve. CoreTests 479 (475 + 4 new) on net9.0 and net10.0, CodegenTests 419, CodegenTests.FSharp compile gate 1. No new compiler warnings: the four pre-existing FS3261 nullness warnings in the fixture are unchanged. Closes #580 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jeremydmiller
added a commit
that referenced
this pull request
Jul 29, 2026
CommandFactory prints "Searching '<assembly>' for commands" until its
static _hasAppliedExtensions latch flips. Being static, it flips once per
process, so exactly one test in the assembly picks up that banner as a
prefix on its captured console output -- whichever test happens to run
first.
execute_single_command_synchronously and its async sibling both asserted
their trimmed output with an exact ShouldBe, so whichever one drew the
short straw failed:
theOutput.ToString().Trim()
should be
"Big is True, Number is 7"
but was
"Searching 'JasperFx, Version=2.36.2.0, ...' for commands
Big is True, Number is 7"
This turned CI red on #587 (which passed on re-run, same commit) and
again on #589. It is not an MTP problem -- #587 was still on the VSTest
bridge -- but MTP orders tests differently, which is why it resurfaced
immediately after the runner change.
The banner is always a prefix, since discovery runs before the command
does, so anchoring to the end of the output keeps the assertion exactly
as strong while making it order-independent.
This is the same class of bug as the AnsiConsole binding already
documented in this constructor, which #577 fixed for the same reason.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jeremydmiller
added a commit
that referenced
this pull request
Jul 29, 2026
…latform (#589) * build: move test suites from the VSTest bridge to Microsoft Testing Platform Follow-up to #577, which moved everything to xunit v3 but deliberately stayed on the VSTest bridge to keep that migration reviewable. All nine test projects drop Microsoft.NET.Test.Sdk and xunit.runner.visualstudio and run as their own MTP executables: no testhost indirection, faster startup, and xunit v3's own reporting. Four things the issue didn't anticipate: 1. OutputType=Exe now has to be explicit. Microsoft.NET.Test.Sdk was setting it; without it, xunit.v3's targets fail the build outright ("xUnit.net v3 test projects must be executable"). 2. Microsoft.Testing.Extensions.CodeCoverage is pinned to 18.0.6, not the latest 18.9.0. xunit.v3 3.2.2 is built against Microsoft.Testing. Platform 1.x -- its packages are literally named xunit.v3.core.mtp-v1. CodeCoverage 18.1.0+ moved to MTP 2.x, and the mixed graph dies at startup on every single test run with TypeLoadException: Could not load type 'Microsoft.Testing.Platform.Extensions.TestHost.IDataConsumer' from assembly 'Microsoft.Testing.Platform, Version=2.3.0.0' 18.0.6 is the last release on MTP 1.x. The constraint is recorded in Directory.Packages.props with the symptom, so the next person to bump it knows what they're looking at. 3. TestingPlatformShowTestsFailure is required, and its absence is a trap. By default MTP prints only error run failed: Tests failed: '.../TestResults/EventStoreTests_net9.0_x64.log' and sends the test name, assertion and stack trace to that file. CI never publishes it, so a red build carries no indication of what broke -- strictly worse than the VSTest bridge, which printed failures inline. Caught by deliberately failing a test and reading the CI log. With the property set, failures print as EventExtensionsTests.cs(32): error test failed: EventStoreTests.EventExtensionsTests.deliberately_failing_probe: Shouldly.ShouldAssertException : "actual" should be "expected" 4. The MTP properties belong in Directory.Build.props rather than per project: TestingPlatformDotnetTestSupport changes how `dotnet test` invokes a project, so a solution where only some projects answered to MTP would leave `dotnet test` on the .slnx in a mixed mode. build/Build.cs needed no changes at all. All seven DotNetTest calls work as-is under MTP, including TestCodegenFSharp's .SetFramework("net9.0"). Verified `./build.sh test` end to end (exit 0), every count matching the current baseline on every target framework: CoreTests 480 (479 + 1 skipped) CodegenTests 419 CommandLineTests 295 EventTests 648 EventStoreTests 72 Aspire.Tests 51 (net10.0) CodegenTests.FSharp 1 SourceGenerator.Tests 19 Events.SourceGenerator.Tests 26 Also verified failures still fail the build, which a silent runner swap could quietly break: a deliberately failing test produced "Failed: 1, Passed: 72" and Nuke surfaced it as ProcessException, 'dotnet' exited with code 1. CodegenTests and CommandLineTests, the two suites carrying [assembly: CollectionBehavior(CollectionPerAssembly)], pass unchanged. Closes #581 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: stop two CommandExecutor tests depending on running first CommandFactory prints "Searching '<assembly>' for commands" until its static _hasAppliedExtensions latch flips. Being static, it flips once per process, so exactly one test in the assembly picks up that banner as a prefix on its captured console output -- whichever test happens to run first. execute_single_command_synchronously and its async sibling both asserted their trimmed output with an exact ShouldBe, so whichever one drew the short straw failed: theOutput.ToString().Trim() should be "Big is True, Number is 7" but was "Searching 'JasperFx, Version=2.36.2.0, ...' for commands Big is True, Number is 7" This turned CI red on #587 (which passed on re-run, same commit) and again on #589. It is not an MTP problem -- #587 was still on the VSTest bridge -- but MTP orders tests differently, which is why it resurfaced immediately after the runner change. The banner is always a prefix, since discovery runs before the command does, so anchoring to the end of the output keeps the assertion exactly as strong while making it order-independent. This is the same class of bug as the AnsiConsole binding already documented in this constructor, which #577 fixed for the same reason. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #580
Root cause
The issue reported the symptom (no
FSharp.CoreinFSharpTypes' output, no entry in itsdeps.json) but not the mechanism. It's in the F# SDK:Central package management turns off the F# SDK's implicit
FSharp.Corereference. Confirmed directly:The projects compiled anyway because
fscfalls back to theFSharp.Corebundled with whichever SDK is running when nothing else supplies one. That's what made this fail quietly instead of loudly — and it also means the fixtures were being compiled against a differentFSharp.Corethan the repo pins, varying with the installed SDK.Reflecting over the output then threw
FileNotFoundExceptionforFSharp.Core, Version=10.0.0.0— the SDK-bundled version it had been compiled against, which was never deployed.Fix
An explicit
<PackageReference Include="FSharp.Core" />in both F# projects —src/FSharpTypesandsrc/CodegenTests.FSharpFixture. Under CPM this resolves to the existing central 9.0.303, so it reconciles withCodegenTests' pin rather than adding a second conflicting one, as the issue asked.src/FSharpCodegenTargetwas also flagged as worth checking — it's a C# project, so it never had the gap; it now picks upFSharp.Coretransitively.Verification
Before/after on
CoreTests' output directory:Answering "what is the fixture actually proving today?"
The issue asked this, and the answer is worth recording: the existing
ValueTypeInfoTestsF# tests were passing on a broken fixture.typeof()and member lookup never force assembly-level attributes to resolve, so they never touched the missing dependency.New
src/CoreTests/Reflection/FSharpTypesFixtureTests.csguards the fixture itself. Removing thePackageReferenceagain:The exact exception from the issue, and the one that passes either way is
GetTypes— which is exactly why this hid for so long. That's noted in the test so nobody mistakes it for a guard.Test results
No new compiler warnings — the fixture's four pre-existing
FS3261nullness warnings are unchanged in count.🤖 Generated with Claude Code