fix(mocks): reference TUnit.Mocks once in snapshot test compilations - #6506
Conversation
A mock snapshot mismatch reported only the two file paths. That is enough locally, but a mismatch that only reproduces on CI — currently GenerateMock_Attribute_With_Concrete_Class and KiotaIRequestAdapter_ConstrainedGenericMethod_EmitsDefaultConstraint on the windows and macos jobs, both green on ubuntu and on a local windows run — leaves nothing in the log to diagnose from, because the .received.txt never leaves the runner. Print the first divergence with three lines of context and a 40-line cap, plus both line counts.
Greptile SummaryThis PR revises mock-generator test references and adds inline snapshot-difference diagnostics.
Confidence Score: 4/5The PR appears safe to merge, although the previously reported diagnostic output-cap issue remains. Sparse snapshot differences can still emit every intervening unchanged line because those lines do not advance the report counter, leaving the intended diagnostic bound ineffective. Files Needing Attention: tests/TUnit.Mocks.SourceGenerator.Tests/SnapshotTestBase.cs
|
| Filename | Overview |
|---|---|
| tests/TUnit.Mocks.SourceGenerator.Tests/SnapshotTestBase.cs | Uses one loaded mocks assembly for test compilations and adds snapshot mismatch diagnostics. |
| tests/TUnit.Mocks.SourceGenerator.Tests/TUnit.Mocks.SourceGenerator.Tests.csproj | Removes the additional netstandard2.0 project build and copied reference assembly. |
Reviews (2): Last reviewed commit: "test(mocks): reference TUnit.Mocks exact..." | Re-trigger Greptile
The test compilations were handed TUnit.Mocks twice: once from the AppDomain assembly list and once from a ref\TUnit.Mocks.dll copy of the netstandard2.0 build. Unless the two are byte-identical, Mock and GenerateMockAttribute bind to an error type, the generator finds no mock targets, and it emits only its post-init namespace stub — with no diagnostic from Roslyn or the generator. The affected tests fail as a bare snapshot mismatch whose received file is five lines long. That is what broke GenerateMock_Attribute_With_Concrete_Class and KiotaIRequestAdapter_ConstrainedGenericMethod_EmitsDefaultConstraint on the windows and macos jobs while ubuntu stayed green: the copy came from a <None Include> glob expanded at evaluation time, so whether it shipped at all — and which build it came from — depended on build ordering. Reference the already-loaded assembly and exclude it from the AppDomain sweep, and drop the ref copy and its netstandard2.0 ProjectReference. Verified by planting a version-skewed TUnit.Mocks.dll in the old ref location: before, 8+ tests fail with five-line output; after, 84/84 pass with it still present.
There was a problem hiding this comment.
Review
The core fix is solid: excluding the loaded TUnit.Mocks assembly from the AppDomain sweep and adding it back exactly once (SnapshotTestBase.cs:41-47) directly closes the double-reference hole, and dropping the ref\TUnit.Mocks.dll copy + its SetTargetFramework=netstandard2.0 ProjectReference from the csproj removes the build-order dependency entirely rather than papering over it. Good root-cause fix, and the verification method described (planting a version-skewed dll) is a convincing way to prove it.
Bug: DescribeDifference's 40-line cap doesn't bound total output
In the reporting loop (SnapshotTestBase.cs:347-370), reported is only incremented when a line actually differs:
if (string.Equals(verifiedLine, receivedLine, StringComparison.Ordinal))
{
report.Append(" ").Append(verifiedLine).Append('\n');
continue; // <-- reported not incremented
}
...
reported++;For the most common real-world case — a single line changed with the surrounding file otherwise identical and the same total line count — every line after the one difference re-matches by position, so the loop keeps appending matched lines all the way to EOF without ever hitting the MaxReportedLines (40) cutoff. On a several-hundred/thousand-line generated snapshot, one differing line near the top would dump nearly the entire file into the failure message — exactly the CI log noise this change is meant to avoid (per the PR description: "capped at 40").
The cap needs to bound total lines emitted in this loop, not just the count of differing positions, e.g.:
for (var i = firstDifference; i < Math.Max(verifiedLines.Length, receivedLines.Length) && reported < MaxReportedLines; i++)
{
...
reported++; // increment unconditionally, whether the line matched or differed
}(or track a separate linesEmitted counter for the loop bound while keeping reported for the "... (truncated)" check if you want it to reflect only genuine diffs).
Everything else — the LoadReferences change, the csproj cleanup, the inline diff context — looks correct and well-reasoned.
Updated [TUnit.Core](https://github.com/thomhurst/TUnit) from 1.61.38 to 1.63.0. <details> <summary>Release notes</summary> _Sourced from [TUnit.Core's releases](https://github.com/thomhurst/TUnit/releases)._ ## 1.63.0 <!-- Release notes generated using configuration in .github/release.yml at v1.63.0 --> ## What's Changed ### Other Changes * fix(mocks): dispatch abstract indexers through the engine instead of calling base by @thomhurst in thomhurst/TUnit#6517 * feat(mocks): experimental compile-time internals access (#6514 Tier 2) by @thomhurst in thomhurst/TUnit#6520 * feat(mocks): make the async-factory Returns alias available below net9.0 by @thomhurst in thomhurst/TUnit#6518 * +semver:minor - feat(mocks): runtime auto-stubs for interfaces the source generator cannot see by @thomhurst in thomhurst/TUnit#6519 ### Dependencies * chore(deps): update tunit to 1.62.0 by @thomhurst in thomhurst/TUnit#6508 * chore(deps): update dependency mockolate to 3.4.0 by @thomhurst in thomhurst/TUnit#6512 * chore(deps): update dependency cliwrap to 3.10.4 by @thomhurst in thomhurst/TUnit#6513 * chore(deps): update dependency stackexchange.redis to 3.0.25 by @thomhurst in thomhurst/TUnit#6522 * chore(deps): update dependency brace-expansion to v5.0.9 by @thomhurst in thomhurst/TUnit#6523 * chore(deps): update dependency microsoft.build.utilities.core to v18 by @thomhurst in thomhurst/TUnit#6525 **Full Changelog**: thomhurst/TUnit@v1.62.0...v1.63.0 ## 1.62.0 <!-- Release notes generated using configuration in .github/release.yml at v1.62.0 --> ## What's Changed ### Other Changes * fix(docs): make Mermaid lifecycle diagrams readable by @thomhurst in thomhurst/TUnit#6485 * fix(mocks): strip nullable annotation from constructor dispatch patterns by @thomhurst in thomhurst/TUnit#6498 * fix(mocks): skip interfaces with inaccessible abstract members by @thomhurst in thomhurst/TUnit#6502 * feat(mocks): accept an async factory in Returns() on async members by @thomhurst in thomhurst/TUnit#6503 * fix(mocks): report TM006 instead of CS1729 for unsubclassable classes by @thomhurst in thomhurst/TUnit#6501 * fix(mocks): emit the setup surface into the globally-imported namespace by @thomhurst in thomhurst/TUnit#6504 * fix(mocks): reference TUnit.Mocks once in snapshot test compilations by @thomhurst in thomhurst/TUnit#6506 * fix(mocks): make generated identifier sanitization injective by @thomhurst in thomhurst/TUnit#6507 ### Dependencies * chore(deps): update tunit to 1.61.38 by @thomhurst in thomhurst/TUnit#6479 * chore(deps): update dependency brace-expansion to v5.0.8 by @thomhurst in thomhurst/TUnit#6478 * chore(deps): update dependency cliwrap to 3.10.3 by @thomhurst in thomhurst/TUnit#6482 * chore(deps): bump postcss from 8.5.10 to 8.5.22 in /docs by @dependabot[bot] in thomhurst/TUnit#6480 * chore(deps): update dependency fscheck to 3.3.4 by @thomhurst in thomhurst/TUnit#6486 * chore(deps): update dependency minimatch to v10.2.6 by @thomhurst in thomhurst/TUnit#6490 * chore(deps): update actions/stale action to v11 by @thomhurst in thomhurst/TUnit#6496 * chore(deps): update microsoft.testing to 2.3.3 by @thomhurst in thomhurst/TUnit#6499 * chore(deps): update mstest to 4.3.3 by @thomhurst in thomhurst/TUnit#6500 **Full Changelog**: thomhurst/TUnit@v1.61.38...v1.62.0 Commits viewable in [compare view](thomhurst/TUnit@v1.61.38...v1.63.0). </details> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Root cause
TUnit.Mocks.SourceGenerator.Testshanded each test compilation two TUnit.Mocks references: one from the AppDomain assembly sweep inSnapshotTestBase.LoadReferences, and one from aref\TUnit.Mocks.dllcopy of the netstandard2.0 build staged by the csproj.Unless those two files are byte-identical,
MockandGenerateMockAttributebind to an error type. Nothing reports it — not Roslyn (the harness never inspected the input compilation's diagnostics) and not the generator (no mock targets found is not an error). The generator emits only its post-initTUnit.Mocks.Generatednamespace stub, so the test fails as a bare snapshot mismatch with a five-line received file.The copy came from a
<None Include>glob expanded at evaluation time, so whether it shipped at all — and which build it came from — depended on build ordering. That is whyGenerateMock_Attribute_With_Concrete_ClassandKiotaIRequestAdapter_ConstrainedGenericMethod_EmitsDefaultConstraintfailed on the windows and macos jobs on every branch rebased onto currentmain(including #6501, which touches no snapshot file at all), while ubuntu and local Windows runs stayed green.maindoesn't run the pipeline on push, so it was invisible there.Fix
LoadReferencesnow references the TUnit.Mocks assembly the test project already loads, and excludes that same assembly from the AppDomain sweep — exactly one reference, no ordering dependence.ref\TUnit.Mocks.dllcopy and the netstandard2.0ProjectReferencethat existed only to feed it.-/+capped at 40). Without this the CI-only failure was undiagnosable from the log, since.received.txtnever leaves the runner.Verification
Planted a version-skewed
TUnit.Mocks.dllin the oldreflocation to force the duplicate-reference state:Full suite green locally on net10.0.