Skip to content

Comments

refactor: Add snapshot tests using Verify.SourceGenerators#48

Merged
DrBarnabus merged 1 commit intomainfrom
refactor/add-snapshot-tests
Feb 22, 2026
Merged

refactor: Add snapshot tests using Verify.SourceGenerators#48
DrBarnabus merged 1 commit intomainfrom
refactor/add-snapshot-tests

Conversation

@DrBarnabus
Copy link
Owner

@DrBarnabus DrBarnabus commented Feb 22, 2026

Summary

Closes #39

  • Add Verify.SourceGenerators and Verify.Xunit to central package management and SourceGeneration.UnitTests project references
  • Add ModuleInitializer with targeted scrubber that normalises the non-deterministic GeneratedCodeAttribute version string to "VERSION"
  • Add 32 snapshot tests (16 source output + 16 GenerationSpec model) covering all CompilationHelper factory scenarios, organised in a Snapshots/ subfolder
  • Add *.received.* to .gitignore and *.verified.* entries to .gitattributes per Verify conventions
  • Update CLAUDE.md with snapshot testing guidance

These snapshots act as the safety net for all subsequent refactoring PRs — any change to emitted .g.cs code or parser-extracted GenerationSpec models will cause a snapshot diff failure.

Test plan

  • All 32 new snapshot tests pass on net8.0, net9.0, and net10.0
  • All ~2,130 existing tests continue to pass across all four test projects and three target frameworks
  • Scrubber correctly replaces GeneratedCodeAttribute version with "VERSION" — no other content is scrubbed
  • *.received.* files are gitignored; only *.verified.* baselines are committed

Summary by CodeRabbit

  • Tests

    • Added comprehensive snapshot-based tests to verify source-generation across many scenarios; includes deterministic baseline verification and a new test harness.
    • Integrated Verify-based test tooling and test initialisation to stabilise generated outputs.
  • Chores

    • Added gitignore pattern for received snapshot files.
    • Added gitattributes entries to normalise encoding and line endings for snapshot baseline files.

@coderabbitai
Copy link

coderabbitai bot commented Feb 22, 2026

Walkthrough

Adds Verify-based snapshot tests and baselines for the source generator, configures Git and Verify scrubbers, updates test project dependencies, and introduces a ModuleInitializer plus 32+ snapshot files covering 16 scenarios (SourceOutput + GenerationSpec).

Changes

Cohort / File(s) Summary
Repo config
/.gitattributes, /.gitignore, CLAUDE.md
Track .verified.* baselines, ignore *.received.* temporary snapshot files, and document the snapshot testing workflow and scrubber rationale.
Central package versions
src/Directory.Packages.props
Add Verify.SourceGenerators (2.5.0) and Verify.Xunit (31.12.5) PackageVersion entries.
Test project package refs & lock
src/CompositeKey.SourceGeneration.UnitTests/CompositeKey.SourceGeneration.UnitTests.csproj, src/CompositeKey.SourceGeneration.UnitTests/packages.lock.json
Add package references for Verify.SourceGenerators and Verify.Xunit; update packages.lock.json to include new transitive dependencies.
Verify initialisation
src/CompositeKey.SourceGeneration.UnitTests/ModuleInitializer.cs
New ModuleInitializer that calls VerifySourceGenerators.Initialize() and registers a regex scrubber to normalise the GeneratedCodeAttribute informational version to a stable placeholder.
Snapshot tests
src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.cs
Add test class implementing per-scenario snapshot tests (SourceOutput and GenerationSpec) that run the source generator and call Verify to create/compare baselines.
Snapshot baselines — GenerationSpec
src/CompositeKey.SourceGeneration.UnitTests/Snapshots/*_GenerationSpec.verified.txt
Add 16 GenerationSpec baseline files capturing parser-extracted metadata for each scenario.
Snapshot baselines — SourceOutput
src/CompositeKey.SourceGeneration.UnitTests/Snapshots/*_SourceOutput#*.verified.cs
Add 16+ generated-source baseline files (many new .g.verified.cs) covering produced code for each scenario (format/parse/toString implementations, interface members, helpers).

Sequence Diagram(s)

sequenceDiagram
    participant Test as Test Runner
    participant Verifier as Verify.SourceGenerators
    participant SG as Source Generator
    participant Scrubber as Verifier Scrubber
    participant Git as Baseline Compare

    Test->>SG: Create compilation & run generator
    SG-->>Verifier: Emit generated sources + GenerationSpec
    Verifier->>Scrubber: Apply registered scrubbers (normalize GeneratedCodeAttribute)
    Scrubber-->>Verifier: Return scrubbed outputs
    Verifier->>Git: Compare scrubbed outputs against `.verified.*` baselines
    Git-->>Verifier: Report match or diff
    Verifier-->>Test: Test pass/fail
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

enhancement

"A rabbit nibbling keys and strings,
Snaps each change as safety brings,
Baselines set, the garden sings—
Stable builds and bounding springs." 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'refactor: Add snapshot tests using Verify.SourceGenerators' clearly and concisely summarises the main change: introducing snapshot testing infrastructure for the source generator using the Verify.SourceGenerators library.
Linked Issues check ✅ Passed The pull request comprehensively addresses all coding requirements from issue #39: adds Verify packages to central package management and project references [#39], implements ModuleInitializer with scrubber configuration [#39], adds 32 snapshot tests covering all 16 CompilationHelper scenarios [#39], and includes .gitignore/.gitattributes updates [#39].
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #39 requirements: test infrastructure setup (packages, ModuleInitializer), snapshot test implementations, configuration files (.gitignore, .gitattributes), and documentation (CLAUDE.md) updates. No unrelated refactoring or feature work is present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/add-snapshot-tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Feb 22, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.66%. Comparing base (2162306) to head (6b546dc).
⚠️ Report is 2 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main      #48   +/-   ##
=======================================
  Coverage   87.66%   87.66%           
=======================================
  Files          35       35           
  Lines        2099     2099           
  Branches      344      344           
=======================================
  Hits         1840     1840           
  Misses        157      157           
  Partials      102      102           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (6)
src/Directory.Packages.props (1)

29-30: Verify.Xunit is deprecated — plan migration to Verify.XunitV3.

Verify.Xunit 31.12.5 is marked as legacy and no longer maintained on NuGet, with Verify.XunitV3 as the suggested replacement. The package still resolves against the project's current dependency on xunit.extensibility.execution >= 2.9.3, so the tests function correctly today, but you'll be blocked from future Verify updates once Verify.Xunit is fully abandoned.

Migrating to Verify.XunitV3 would also require upgrading the test project from xunit v2 (2.9.3) to xunit v3 — reasonable to track as a follow-up issue rather than blocking this PR.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Directory.Packages.props` around lines 29 - 30, The project is
referencing the deprecated PackageVersion "Verify.Xunit" (31.12.5); update the
package management to use "Verify.XunitV3" instead and plan a follow-up to
upgrade tests to xUnit v3: replace the PackageVersion entry for Verify.Xunit
with Verify.XunitV3 (keeping the same versioning strategy), and add a task/issue
to upgrade test projects that rely on xunit.extensibility.execution >= 2.9.3 to
xUnit v3 so the new Verify package is fully supported; ensure CI/test pipelines
still restore and build after the change.
src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.RepeatingPropertyCompositeKey_SourceOutput#UnitTests.UserTagKey.g.verified.cs (2)

165-201: Redundant partitionKey.Length check on Line 178 — locked into the snapshot baseline.

Line 167 already validates partitionKey.Length != 36 and throws FormatException. The identical guard on Line 178 (partitionKey.Length != 36) is always false at that point. The same redundancy exists in TryParse (Line 218 vs Line 229). Since this snapshot locks in the generator's output, consider whether the generator should be updated to avoid this redundant check (in a follow-up), or accept it as harmless.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.RepeatingPropertyCompositeKey_SourceOutput`#UnitTests.UserTagKey.g.verified.cs
around lines 165 - 201, The Parse method contains a redundant
partitionKey.Length check duplicated before Guid.TryParseExact; remove the
second unconditional "if (partitionKey.Length != 36)" guard in Parse (and the
analogous duplicate in TryParse) so the code only validates length once, then
proceeds to Guid.TryParseExact(partitionKey, "d", out var userId); update the
generator or snapshot output to eliminate these duplicate checks and keep the
subsequent Guid parsing/validation intact (refer to Parse and TryParse functions
/ methods in the generated output).

184-185: Hardcoded 128-element stackalloc caps the number of parseable repeating tags.

Span<Range> tagsRanges = stackalloc Range[128] means any key with more than 128 tags will silently truncate during Split, potentially producing an incorrect parse result rather than a clear error. The same applies to the TryParse variant on Line 235. This is a generator output characteristic being locked in; if unbounded collections are a valid use case, the generator may need adjustment in a future PR.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.RepeatingPropertyCompositeKey_SourceOutput`#UnitTests.UserTagKey.g.verified.cs
around lines 184 - 185, The stackalloc buffer for tagsRanges (Span<Range>
tagsRanges = stackalloc Range[128]) can silently truncate when more than 128
tags exist; update the parsing code that calls Split (the tagsCount assignment)
and the corresponding TryParse variant to detect capacity exhaustion (i.e.,
tagsCount == 128) and handle it deterministically: either allocate a larger
buffer (heap/ArrayPool) before splitting or return a clear failure/error (throw
or return false) so inputs with >128 tags don't produce silent incorrect parses.
Make the change at the locations referencing tagsRanges, the Split call, and the
TryParse path so both normal parse and TryParse behave consistently.
src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.ClashingKeyNames_SourceOutput#AnotherNamespace.BasicPrimaryKey.g.verified.cs (1)

154-163: [NotNullWhen(true)] on non-nullable string parameter is redundant.

partitionKey is declared as string (non-nullable), yet it carries [NotNullWhen(true)]. The runtime null-guard at line 156 is fine as a defensive measure for callers without NRT, but the attribute has no effect on a non-nullable reference type. Compare with the single-key overload at line 88 which correctly uses string?. This is a generator-level inconsistency — you may want to track it for a future generator fix.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.ClashingKeyNames_SourceOutput`#AnotherNamespace.BasicPrimaryKey.g.verified.cs
around lines 154 - 163, The generated TryParse method has a redundant
[NotNullWhen(true)] on the non-nullable parameter partitionKey; update the
signature of public static bool TryParse(string partitionKey, string sortKey,
... out BasicPrimaryKey? result) to accept nullable refs (string? partitionKey,
string? sortKey) or remove the attribute so it matches the nullability intent
used by the single-key overload; specifically, adjust the TryParse method
declaration and its annotations to either use string? for partitionKey and
sortKey (keeping the NotNullWhen attribute) or drop the NotNullWhen attribute if
you want to keep non-nullable strings, ensuring consistency with the other
TryParse overloads and the ReadOnlySpan-based TryParse call.
src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.InvariantCultureDisabled_SourceOutput#UnitTests.BasicPrimaryKey.g.verified.cs (1)

109-113: Redundant length guard in Parse — sortKey.Length < 1 (line 112) makes sortKey.Length == 0 (line 126) dead code.

The check at line 112 already ensures sortKey.Length >= 1, so the subsequent sortKey.Length == 0 check at line 126 can never be true. The same pattern appears in TryParse (lines 152 vs 166). This is a minor generator-level issue now locked into the snapshot.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.InvariantCultureDisabled_SourceOutput`#UnitTests.BasicPrimaryKey.g.verified.cs
around lines 109 - 113, In both Parse and TryParse, remove the redundant early
guard that checks "if (sortKey.Length < 1)" so the later explicit zero-length
check "sortKey.Length == 0" remains meaningful; locate the checks inside the
Parse and TryParse methods (the "sortKey" length validations) and delete the "<
1" guards to avoid dead code while preserving the later specific length==0
handling and associated error paths.
CLAUDE.md (1)

69-71: Lines exceed the 120-character maximum.

Lines 69 and 71 exceed the 120-character limit specified in the coding guidelines. Consider wrapping them.

As per coding guidelines: **/*.{cs,csproj,props,xml,json,md}: Use 4-space indentation, UTF-8 encoding, LF line endings, and enforce 120-character maximum line length.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CLAUDE.md` around lines 69 - 71, The two Markdown bullet lines referencing
Snapshot tests and the ModuleInitializer exceed the 120-character limit; edit
the CLAUDE.md bullets (the lines mentioning "Snapshot tests
(`Snapshots/`...`GenerationSpec`" and "The `ModuleInitializer` scrubs...") to
wrap them into shorter lines under 120 characters while preserving the exact
meaning and bullet structure (split long sentences at logical boundaries, e.g.,
after phrases like "use Verify.SourceGenerators" or "to keep snapshots stable
across commits"), keep UTF-8/LF encoding and 4-space indentation conventions,
and ensure no other content or punctuation is changed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.BasicCompositePrimaryKey_SourceOutput`#UnitTests.BasicPrimaryKey.g.verified.cs:
- Around line 266-274: The GetFormattedLength method in CustomEnumHelper uses
array indexing into Lengths without validating the enum value, which can produce
an IndexOutOfRangeException for invalid enums; update GetFormattedLength to
validate the value (e.g., cast to uint and check against Lengths.Length or
Names.Length) and throw a descriptive ArgumentOutOfRangeException for
out-of-range values so its semantics match the switch-based helper and the
existing TryFormat guard.

In
`@src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.cs`:
- Around line 232-262: The two repeating-property test pairs are inconsistent:
RepeatingPropertyKey_SourceOutput and RepeatingPropertyCompositeKey_SourceOutput
call RunDriver (which validates generator diagnostics) while their
_GenerationSpec counterparts call
CompilationHelper.RunSourceGenerator(disableDiagnosticValidation: true). Make
validation behavior consistent by disabling diagnostic validation in both
variants: modify RunDriver (or add an overload) to accept a
disableDiagnosticValidation flag and call it with disableDiagnosticValidation:
true from RepeatingPropertyKey_SourceOutput and
RepeatingPropertyCompositeKey_SourceOutput, or alternatively remove the
disableDiagnosticValidation: true from the GenerationSpec calls so both use the
same validation policy; update the tests for RepeatingPropertyKey_* and
RepeatingPropertyCompositeKey_* and the
RunDriver/CompilationHelper.RunSourceGenerator usage accordingly.

In
`@src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.RepeatingPropertyCompositeKey_SourceOutput`#UnitTests.UserTagKey.g.verified.cs:
- Around line 54-61: The ToPartitionKeyString method's switch in
ToPartitionKeyString(int throughPartIndex, bool includeTrailingDelimiter = true)
lacks a (0, true) arm so calling ToPartitionKeyString(0) with the default
includeTrailingDelimiter=true throws; add a (0, true) case (in the same switch
alongside the existing (0, false) case) that returns the partition component
with the PrimaryKeySeparator appended (e.g. the UserId formatted and
concatenated with PrimaryKeySeparator) so both includeTrailingDelimiter variants
are handled.

---

Nitpick comments:
In `@CLAUDE.md`:
- Around line 69-71: The two Markdown bullet lines referencing Snapshot tests
and the ModuleInitializer exceed the 120-character limit; edit the CLAUDE.md
bullets (the lines mentioning "Snapshot tests (`Snapshots/`...`GenerationSpec`"
and "The `ModuleInitializer` scrubs...") to wrap them into shorter lines under
120 characters while preserving the exact meaning and bullet structure (split
long sentences at logical boundaries, e.g., after phrases like "use
Verify.SourceGenerators" or "to keep snapshots stable across commits"), keep
UTF-8/LF encoding and 4-space indentation conventions, and ensure no other
content or punctuation is changed.

In
`@src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.ClashingKeyNames_SourceOutput`#AnotherNamespace.BasicPrimaryKey.g.verified.cs:
- Around line 154-163: The generated TryParse method has a redundant
[NotNullWhen(true)] on the non-nullable parameter partitionKey; update the
signature of public static bool TryParse(string partitionKey, string sortKey,
... out BasicPrimaryKey? result) to accept nullable refs (string? partitionKey,
string? sortKey) or remove the attribute so it matches the nullability intent
used by the single-key overload; specifically, adjust the TryParse method
declaration and its annotations to either use string? for partitionKey and
sortKey (keeping the NotNullWhen attribute) or drop the NotNullWhen attribute if
you want to keep non-nullable strings, ensuring consistency with the other
TryParse overloads and the ReadOnlySpan-based TryParse call.

In
`@src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.InvariantCultureDisabled_SourceOutput`#UnitTests.BasicPrimaryKey.g.verified.cs:
- Around line 109-113: In both Parse and TryParse, remove the redundant early
guard that checks "if (sortKey.Length < 1)" so the later explicit zero-length
check "sortKey.Length == 0" remains meaningful; locate the checks inside the
Parse and TryParse methods (the "sortKey" length validations) and delete the "<
1" guards to avoid dead code while preserving the later specific length==0
handling and associated error paths.

In
`@src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.RepeatingPropertyCompositeKey_SourceOutput`#UnitTests.UserTagKey.g.verified.cs:
- Around line 165-201: The Parse method contains a redundant partitionKey.Length
check duplicated before Guid.TryParseExact; remove the second unconditional "if
(partitionKey.Length != 36)" guard in Parse (and the analogous duplicate in
TryParse) so the code only validates length once, then proceeds to
Guid.TryParseExact(partitionKey, "d", out var userId); update the generator or
snapshot output to eliminate these duplicate checks and keep the subsequent Guid
parsing/validation intact (refer to Parse and TryParse functions / methods in
the generated output).
- Around line 184-185: The stackalloc buffer for tagsRanges (Span<Range>
tagsRanges = stackalloc Range[128]) can silently truncate when more than 128
tags exist; update the parsing code that calls Split (the tagsCount assignment)
and the corresponding TryParse variant to detect capacity exhaustion (i.e.,
tagsCount == 128) and handle it deterministically: either allocate a larger
buffer (heap/ArrayPool) before splitting or return a clear failure/error (throw
or return false) so inputs with >128 tags don't produce silent incorrect parses.
Make the change at the locations referencing tagsRanges, the Split call, and the
TryParse path so both normal parse and TryParse behave consistently.

In `@src/Directory.Packages.props`:
- Around line 29-30: The project is referencing the deprecated PackageVersion
"Verify.Xunit" (31.12.5); update the package management to use "Verify.XunitV3"
instead and plan a follow-up to upgrade tests to xUnit v3: replace the
PackageVersion entry for Verify.Xunit with Verify.XunitV3 (keeping the same
versioning strategy), and add a task/issue to upgrade test projects that rely on
xunit.extensibility.execution >= 2.9.3 to xUnit v3 so the new Verify package is
fully supported; ensure CI/test pipelines still restore and build after the
change.

Add Verify.SourceGenerators snapshot tests to capture generated source
output and GenerationSpec models as baselines for all 16 CompilationHelper
scenarios. These snapshots act as the safety net for subsequent refactoring
work, catching any unintended changes to emitted code or parser output.

- Add Verify.SourceGenerators and Verify.Xunit to central package management
- Add ModuleInitializer with scrubber for non-deterministic GeneratedCodeAttribute version
- Add 32 snapshot tests (16 source output + 16 GenerationSpec) in Snapshots/ subfolder
- Add .gitignore entry for *.received.* and .gitattributes for *.verified.* files
- Update CLAUDE.md with snapshot testing conventions
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (7)
src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.NestedTypeDeclarations_SourceOutput#UnitTests.OutermostClass.BasicPrimaryKey.g.verified.cs (2)

55-88: ToPartitionKeyString() (no-arg overload) is a full copy of ToString().

Because this type has no separator, both methods must return the same full-key string. The emitter could instead emit a one-liner delegation:

♻️ Suggested emitter output simplification
-        public string ToPartitionKeyString()
-        {
-            return string.Create(110, this, static (destination, state) =>
-            {
-                // ... identical body to ToString() ...
-            });
-        }
+        public string ToPartitionKeyString() => ToString();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.NestedTypeDeclarations_SourceOutput`#UnitTests.OutermostClass.BasicPrimaryKey.g.verified.cs
around lines 55 - 88, The ToPartitionKeyString() no-arg overload is duplicating
ToString() output by re-emitting the full formatting logic; since this type has
no separator the two should be identical, so replace the duplicated body in
ToPartitionKeyString() with a simple delegation to ToString() (e.g. return
ToString();) to avoid redundant code and keep behavior consistent between
ToPartitionKeyString() and ToString().

27-27: Numerous lines in the emitted output exceed the 120-character limit.

Examples include the ISpanFormattable.TryFormat(...) call sites (lines 27, 37, 47, 62, 72, 82), the switch-expression arms (lines 94–99), the Guid.TryParseExact(...) chains (lines 120, 123, 126, 155, 158, 161), and the explicit interface delegate lines (175, 181). All of these originate in the emitter; the snapshot simply reflects what the generator produces.

As per coding guidelines, **/*.{cs,...} files enforce a 120-character maximum line length.

Also applies to: 37-37, 47-47, 62-62, 72-72, 82-82, 94-99, 120-127, 155-162, 175-175, 181-181

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.NestedTypeDeclarations_SourceOutput`#UnitTests.OutermostClass.BasicPrimaryKey.g.verified.cs
at line 27, The generated source contains many lines exceeding the 120-char
limit (notably the ISpanFormattable.TryFormat(...) call sites, the
switch-expression arms, the Guid.TryParseExact(...) chains, and the
explicit-interface delegate expressions); update the emitter so it breaks long
expressions into shorter pieces: assign complex expressions or slices to clearly
named local variables before calling TryFormat (e.g., capture
destination[position..] into a var), break argument lists and chained calls
across multiple lines, emit multi-line switch arms instead of long single-line
arms, and replace inline long lambda/delegate expressions for explicit interface
implementations with private helper methods or local funcs; apply these changes
around the symbols seen in the snapshot (ISpanFormattable.TryFormat, the
switch-expression arms, Guid.TryParseExact chains, and the explicit interface
delegate emit sites) so emitted lines stay <=120 chars.
src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.ExplicitlyMarkedConstructor_SourceOutput#UnitTests.BasicPrimaryKey.g.verified.cs (1)

117-123: Lines 117 and 123 exceed the 120-character limit prescribed by the coding guidelines.

Both explicit interface implementations carry two fully-qualified [global::System.Diagnostics.CodeAnalysis.*] attributes inline, pushing the lines well past 120 characters. As this snapshot faithfully captures generator output, the fix is in the emitter — the attribute arguments could be placed on separate lines (or the fully-qualified form used only where necessary).

As per coding guidelines, **/*.{cs,csproj,props,xml,json,md} enforces a 120-character maximum line length.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.ExplicitlyMarkedConstructor_SourceOutput`#UnitTests.BasicPrimaryKey.g.verified.cs
around lines 117 - 123, The snapshot generator emits explicit interface
implementations for IParsable<BasicPrimaryKey>.TryParse and
ISpanParsable<BasicPrimaryKey>.TryParse with long fully-qualified attributes
inline, exceeding the 120-char limit; update the emitter (the code that builds
these members) to break attribute arguments or place attributes on their own
line(s) (or use shorter attribute names where safe) for the methods
IParsable<BasicPrimaryKey>.TryParse, ISpanParsable<BasicPrimaryKey>.Parse, and
ISpanParsable<BasicPrimaryKey>.TryParse so the generated lines stay within 120
characters.
src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.BasicNonSequentialEnumPrimaryKey_GenerationSpec.verified.txt (1)

157-194: LengthRequired: 1 for ThirdPart is lower than the actual minimum member name length.

The CustomEnum has three members whose serialised names are "One" (3), "Two" (3), and "Three" (5). The shortest possible sort-key value is therefore ConstantValue#One = 17 characters, yet the generator assigns LengthRequired: 1 to the enum part, producing a sort-key pre-check of sortKey.Length < 15 (instead of < 17) in the generated code.

Inputs of length 15 or 16 pass the pre-check but are correctly rejected by the subsequent split/parse step, so correctness is maintained. This is solely a minor optimisation gap in the generator's length-inference logic for enum key parts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.BasicNonSequentialEnumPrimaryKey_GenerationSpec.verified.txt`
around lines 157 - 194, The generator is underestimating enum part minimum
length (LengthRequired = 1 for ThirdPart/EnumSpec CustomEnum); update the
enum-length inference in the source generator so that for FormatType/ParseType
Enum you compute the minimum serialized member-name length (min length of
EnumSpec.Members' Names) and add any fixed prefix/suffix length used in the key
format (e.g., the "ConstantValue#" or other constant characters) to produce the
correct LengthRequired, then use that value instead of 1 when emitting
LengthRequired for properties like ThirdPart.
src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.BasicNonSequentialEnumPrimaryKey_SourceOutput#UnitTests.BasicNonSequentialEnum.g.verified.cs (1)

279-300: TryFormat default branch is dead code.

GetFormattedLength(value) (line 281) throws ArgumentOutOfRangeException for any enum value outside One/Two/Three, so execution never reaches the switch default case at lines 296–298. The charsWritten = 0; return false; there is unreachable.

A secondary (even lower-severity) observation: the buffer-too-small path leaves charsWritten set to the required length rather than 0. The ISpanFormattable convention is charsWritten = 0 on any false return, though the value is never observed here because all call sites supply a pre-sized buffer from string.Create.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.BasicNonSequentialEnumPrimaryKey_SourceOutput`#UnitTests.BasicNonSequentialEnum.g.verified.cs
around lines 279 - 300, In TryFormat, ensure false returns set charsWritten=0
and make the unreachable default explicit: instead of leaving charsWritten as
the required length when destination is too small, set charsWritten = 0 before
returning false; and replace the switch default branch (currently setting
charsWritten = 0 and returning false) with throwing an
ArgumentOutOfRangeException (to match GetFormattedLength behavior) or remove the
default entirely so callers see the same exception semantics as
GetFormattedLength; reference TryFormat and GetFormattedLength when making these
changes.
src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.RepeatingPropertyCompositeKey_SourceOutput#UnitTests.UserTagKey.g.verified.cs (1)

184-184: Hard-coded stackalloc Range[128] caps repeating tags at 128.

The generated parsing code uses Span<Range> tagsRanges = stackalloc Range[128], which silently truncates if there are more than 128 tags. If the split produces more than 128 segments, ReadOnlySpan<char>.Split will merge the remaining text into the last range, causing incorrect tag values rather than an explicit error. This is a generator-level concern, but worth documenting as a known limitation or adding an overflow check.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.RepeatingPropertyCompositeKey_SourceOutput`#UnitTests.UserTagKey.g.verified.cs
at line 184, The generated parser currently uses a fixed buffer "Span<Range>
tagsRanges = stackalloc Range[128]" which silently truncates when
ReadOnlySpan<char>.Split produces more than 128 segments; change the generator
to detect overflow by checking the split/segment count against the stackalloc
length (the tagsRanges buffer) and either (a) throw a clear exception when the
number of tags exceeds the capacity, or (b) switch to an heap-allocated array
(e.g., new Range[count]) when more than 128 segments are needed; update the code
paths that call ReadOnlySpan<char>.Split and the logic that writes into
tagsRanges to perform this capacity check before writing into tagsRanges to
avoid merging of trailing text into the last range.
src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.cs (1)

268-281: Minor: RunDriver validates only generator diagnostics, not compilation diagnostics.

CompilationHelper.RunSourceGenerator (used in the _GenerationSpec tests) validates both compilation-level and generator-level diagnostics when disableDiagnosticValidation is false, whereas RunDriver only checks driver.GetRunResult().Diagnostics (generator-level). This means a scenario that emits a compilation error (e.g. Warning or Error severity from the output compilation) would be caught in the _GenerationSpec test but silently pass in the _SourceOutput test.

For snapshot tests this is low risk — the verified baselines would still catch unexpected output changes — but worth being aware of if this is ever used as a correctness gate beyond snapshot diffing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.cs`
around lines 268 - 281, RunDriver currently only checks generator diagnostics
via driver.GetRunResult().Diagnostics and ignores compilation-level diagnostics;
update RunDriver (and respect the disableDiagnosticValidation flag) to also
validate compilation.GetDiagnostics() (filtering Severity >
DiagnosticSeverity.Info) and assert it's empty when validation is enabled, in
addition to the existing check of driver.GetRunResult().Diagnostics.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.BasicNonSequentialEnumPrimaryKey_SourceOutput`#UnitTests.BasicNonSequentialEnum.g.verified.cs:
- Around line 202-211: Update the two-string TryParse overload so its parameters
are nullable to match the NotNullWhen attribute and the single-key overload:
change the parameter types for partitionKey and sortKey from string to string?
in the TryParse method signature (the overload that calls
TryParse(ReadOnlySpan<char>, ReadOnlySpan<char>, out BasicNonSequentialEnum?))
so the [NotNullWhen(true)] attribute is meaningful and consistent with the
single-key TryParse(primaryKey) overload; ensure the null-check/guard remains
and that the source generator emits the nullable signatures (adjust generator
logic that builds the partitionKey/sortKey parameter types).

In
`@src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.ClashingKeyNames_GenerationSpec.verified.txt`:
- Around line 149-153: PropertyValidation.GetFormattedLength currently maps the
Guid "x" format incorrectly to (32,false); update the mapping for the "x" (and
"X" if present) specifier to return (68,true) so the method returns the fixed
68-character exact-length result used by the "x" Guid format; modify the entry
in PropertyValidation.GetFormattedLength (the switch/lookup that includes the
line currently returning "x" => (32, false)) to "x" => (68, true) so emitter and
parser can use the fixed-size fast-path.

In
`@src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.NestedPrivateTypeDeclarations_SourceOutput`#UnitTests.OutermostClass.BasicPrimaryKey.g.verified.cs:
- Around line 27-31: The emitter is writing a hardcoded tab into generated throw
statements (e.g., writer.WriteLine("\tthrow new FormatException();\n") in
SourceGenerator.Emitter.cs); remove the literal "\t" and the explicit "\n" and
use the writer's indentation API/context so the line is emitted with the current
indentation (e.g., replace the call with writer.WriteLine("throw new
FormatException();") or the equivalent writer call your emitter uses for
indented lines) so generated code uses the writer's 4-space indentation rather
than a tab.

---

Duplicate comments:
In
`@src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.BasicCompositePrimaryKey_SourceOutput`#UnitTests.BasicPrimaryKey.g.verified.cs:
- Around line 266-274: GetFormattedLength in class CustomEnumHelper currently
indexes Lengths[(uint)value] without validating value; add the same guard used
in TryFormat (check (uint)value >= Names.Length or Lengths.Length) and if out of
range throw a descriptive ArgumentOutOfRangeException (include the parameter
name and the invalid value) rather than letting an IndexOutOfRangeException
propagate, so look for GetFormattedLength, Lengths, Names and TryFormat and
implement the bounds check and explicit exception there.

In
`@src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.RepeatingPropertyCompositeKey_SourceOutput`#UnitTests.UserTagKey.g.verified.cs:
- Around line 54-61: The switch in ToPartitionKeyString is missing the (0, true)
arm so calling ToPartitionKeyString(0) (default includeTrailingDelimiter = true)
falls to the throw; add a new case for (0, true) in the ToPartitionKeyString
method that returns the UserId formatted as in the existing (0, false) arm but
with the PrimaryKeySeparator appended (e.g., $"{UserId:d}{PrimaryKeySeparator}")
so the generator emits a `(0, true)` branch alongside the existing `(0, false)`
branch.

---

Nitpick comments:
In
`@src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.BasicNonSequentialEnumPrimaryKey_GenerationSpec.verified.txt`:
- Around line 157-194: The generator is underestimating enum part minimum length
(LengthRequired = 1 for ThirdPart/EnumSpec CustomEnum); update the enum-length
inference in the source generator so that for FormatType/ParseType Enum you
compute the minimum serialized member-name length (min length of
EnumSpec.Members' Names) and add any fixed prefix/suffix length used in the key
format (e.g., the "ConstantValue#" or other constant characters) to produce the
correct LengthRequired, then use that value instead of 1 when emitting
LengthRequired for properties like ThirdPart.

In
`@src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.BasicNonSequentialEnumPrimaryKey_SourceOutput`#UnitTests.BasicNonSequentialEnum.g.verified.cs:
- Around line 279-300: In TryFormat, ensure false returns set charsWritten=0 and
make the unreachable default explicit: instead of leaving charsWritten as the
required length when destination is too small, set charsWritten = 0 before
returning false; and replace the switch default branch (currently setting
charsWritten = 0 and returning false) with throwing an
ArgumentOutOfRangeException (to match GetFormattedLength behavior) or remove the
default entirely so callers see the same exception semantics as
GetFormattedLength; reference TryFormat and GetFormattedLength when making these
changes.

In
`@src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.cs`:
- Around line 268-281: RunDriver currently only checks generator diagnostics via
driver.GetRunResult().Diagnostics and ignores compilation-level diagnostics;
update RunDriver (and respect the disableDiagnosticValidation flag) to also
validate compilation.GetDiagnostics() (filtering Severity >
DiagnosticSeverity.Info) and assert it's empty when validation is enabled, in
addition to the existing check of driver.GetRunResult().Diagnostics.

In
`@src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.ExplicitlyMarkedConstructor_SourceOutput`#UnitTests.BasicPrimaryKey.g.verified.cs:
- Around line 117-123: The snapshot generator emits explicit interface
implementations for IParsable<BasicPrimaryKey>.TryParse and
ISpanParsable<BasicPrimaryKey>.TryParse with long fully-qualified attributes
inline, exceeding the 120-char limit; update the emitter (the code that builds
these members) to break attribute arguments or place attributes on their own
line(s) (or use shorter attribute names where safe) for the methods
IParsable<BasicPrimaryKey>.TryParse, ISpanParsable<BasicPrimaryKey>.Parse, and
ISpanParsable<BasicPrimaryKey>.TryParse so the generated lines stay within 120
characters.

In
`@src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.NestedTypeDeclarations_SourceOutput`#UnitTests.OutermostClass.BasicPrimaryKey.g.verified.cs:
- Around line 55-88: The ToPartitionKeyString() no-arg overload is duplicating
ToString() output by re-emitting the full formatting logic; since this type has
no separator the two should be identical, so replace the duplicated body in
ToPartitionKeyString() with a simple delegation to ToString() (e.g. return
ToString();) to avoid redundant code and keep behavior consistent between
ToPartitionKeyString() and ToString().
- Line 27: The generated source contains many lines exceeding the 120-char limit
(notably the ISpanFormattable.TryFormat(...) call sites, the switch-expression
arms, the Guid.TryParseExact(...) chains, and the explicit-interface delegate
expressions); update the emitter so it breaks long expressions into shorter
pieces: assign complex expressions or slices to clearly named local variables
before calling TryFormat (e.g., capture destination[position..] into a var),
break argument lists and chained calls across multiple lines, emit multi-line
switch arms instead of long single-line arms, and replace inline long
lambda/delegate expressions for explicit interface implementations with private
helper methods or local funcs; apply these changes around the symbols seen in
the snapshot (ISpanFormattable.TryFormat, the switch-expression arms,
Guid.TryParseExact chains, and the explicit interface delegate emit sites) so
emitted lines stay <=120 chars.

In
`@src/CompositeKey.SourceGeneration.UnitTests/Snapshots/SourceGeneratorSnapshotTests.RepeatingPropertyCompositeKey_SourceOutput`#UnitTests.UserTagKey.g.verified.cs:
- Line 184: The generated parser currently uses a fixed buffer "Span<Range>
tagsRanges = stackalloc Range[128]" which silently truncates when
ReadOnlySpan<char>.Split produces more than 128 segments; change the generator
to detect overflow by checking the split/segment count against the stackalloc
length (the tagsRanges buffer) and either (a) throw a clear exception when the
number of tags exceeds the capacity, or (b) switch to an heap-allocated array
(e.g., new Range[count]) when more than 128 segments are needed; update the code
paths that call ReadOnlySpan<char>.Split and the logic that writes into
tagsRanges to perform this capacity check before writing into tagsRanges to
avoid merging of trailing text into the last range.

@DrBarnabus DrBarnabus merged commit 30b3bb9 into main Feb 22, 2026
4 checks passed
@DrBarnabus DrBarnabus deleted the refactor/add-snapshot-tests branch February 22, 2026 16:12
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.

refactor: Add snapshot tests using Verify.SourceGenerators

1 participant