Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions claude.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,9 +211,13 @@ Reference existing adapters like `Verify.Xunit/Verifier.cs` or `Verify.NUnit/Ver

### Accepting New Snapshots

When tests fail, `.received.*` files are created showing actual output. To accept a new snapshot, copy the `.received.*` file to the corresponding `.verified.*` filename.
When tests fail, `.received.*` files are created showing actual output. To accept a new snapshot, copy the `.received.*` file to the path on the `Verified:` line of the exception message. Do not derive the verified name from the received name — the two are not always the same.

**Multi-target naming**: When a test project targets multiple frameworks, `.received.` files include a runtime suffix (e.g., `.DotNet11_0.received.txt`) but `.verified.` files do **not** (e.g., `.verified.txt`). When accepting snapshots, strip the runtime suffix from the filename.
**Multi-target naming**: When a test project targets multiple frameworks (e.g., `Verify.Tests`), the `.received.` file always carries a runtime and version suffix (e.g., `.DotNet11_0.received.txt`). The `.verified.` file carries whatever `UniqueFor*` the test asked for, which may be nothing (`.verified.txt`), the runtime (`.DotNet.verified.txt`), or the same runtime and version (`.DotNet11_0.verified.txt`).

**Single-target naming**: When a test project targets a single framework (e.g., `StaticSettingsTests`), the `.received.` file uses the same uniqueness as its `.verified.` file, including any `UniqueForRuntime`/`UniqueForRuntimeAndVersion` suffix.

**Parameters**: Ignored parameters (`IgnoreParameters`, `IgnoreParametersForVerified`, `IgnoreConstructorParameters`) are kept in the `.received.` name and dropped from the `.verified.` name, regardless of how many frameworks are targeted.

### Debugging Tests

Expand Down
8 changes: 5 additions & 3 deletions docs/ai-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,14 @@ Verified: TestClass.Method.verified.txt

### Step 3: Determine the action

- **If the change is expected** (due to an intentional code change): copy the `.received.*` file over the `.verified.*` file to accept the new snapshot.
- **If it is a new test** (no `.verified.*` file): accept the `.received.*` file as the new snapshot by renaming it to `.verified.*`.
- **If the change is expected** (due to an intentional code change): copy the `.received.*` file to the `Verified:` path to accept the new snapshot.
- **If it is a new test** (no `.verified.*` file): accept the `.received.*` file as the new snapshot by copying it to the `Verified:` path.
- **If the change is a bug**: fix the code, not the snapshot. Re-run the test to confirm the fix.

## Rules

- **Never hand-edit `.verified.*` files** to make tests pass. Always let Verify generate the correct output by running the test.
- **Never derive the verified name from the received name.** The two are not always identical. Multi-targeted projects add a runtime and version suffix to the received file, and ignored parameters are kept in the received name but dropped from the verified name. Always take the destination from the `Verified:` line of the exception message.
- Snapshot files live next to the test source file. For a test in `Tests/MyTests.cs`, look for `Tests/MyTests.MethodName.verified.txt`.

## Scrubbed values
Expand Down Expand Up @@ -132,9 +133,10 @@ When a snapshot test fails:
1. Run the failing test and read the `.received.*` file that was generated.
2. Compare it to the corresponding `.verified.*` file.
3. Determine if the difference is expected (due to an intentional code change) or a bug.
- **If expected**: copy the `.received.*` file over the `.verified.*` file to accept the new snapshot, or delete the `.verified.*` file and rename the `.received.*` file.
- **If expected**: copy the `.received.*` file to the `Verified:` path listed in the exception message to accept the new snapshot.
- **If a bug**: fix the code, not the snapshot.
4. Never hand-edit `.verified.*` files to make tests pass. Always let Verify generate the correct output by running the test.
5. Never derive the verified name from the received name. The two are not always identical. Multi-targeted projects add a runtime and version suffix to the received file, and ignored parameters are kept in the received name but dropped from the verified name. Always take the destination from the `Verified:` line of the exception message.

### Scrubbed values

Expand Down
8 changes: 5 additions & 3 deletions docs/mdsource/ai-usage.source.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,14 @@ Verified: TestClass.Method.verified.txt

### Step 3: Determine the action

- **If the change is expected** (due to an intentional code change): copy the `.received.*` file over the `.verified.*` file to accept the new snapshot.
- **If it is a new test** (no `.verified.*` file): accept the `.received.*` file as the new snapshot by renaming it to `.verified.*`.
- **If the change is expected** (due to an intentional code change): copy the `.received.*` file to the `Verified:` path to accept the new snapshot.
- **If it is a new test** (no `.verified.*` file): accept the `.received.*` file as the new snapshot by copying it to the `Verified:` path.
- **If the change is a bug**: fix the code, not the snapshot. Re-run the test to confirm the fix.

## Rules

- **Never hand-edit `.verified.*` files** to make tests pass. Always let Verify generate the correct output by running the test.
- **Never derive the verified name from the received name.** The two are not always identical. Multi-targeted projects add a runtime and version suffix to the received file, and ignored parameters are kept in the received name but dropped from the verified name. Always take the destination from the `Verified:` line of the exception message.
- Snapshot files live next to the test source file. For a test in `Tests/MyTests.cs`, look for `Tests/MyTests.MethodName.verified.txt`.

## Scrubbed values
Expand Down Expand Up @@ -125,9 +126,10 @@ When a snapshot test fails:
1. Run the failing test and read the `.received.*` file that was generated.
2. Compare it to the corresponding `.verified.*` file.
3. Determine if the difference is expected (due to an intentional code change) or a bug.
- **If expected**: copy the `.received.*` file over the `.verified.*` file to accept the new snapshot, or delete the `.verified.*` file and rename the `.received.*` file.
- **If expected**: copy the `.received.*` file to the `Verified:` path listed in the exception message to accept the new snapshot.
- **If a bug**: fix the code, not the snapshot.
4. Never hand-edit `.verified.*` files to make tests pass. Always let Verify generate the correct output by running the test.
5. Never derive the verified name from the received name. The two are not always identical. Multi-targeted projects add a runtime and version suffix to the received file, and ignored parameters are kept in the received name but dropped from the verified name. Always take the destination from the `Verified:` line of the exception message.

### Scrubbed values

Expand Down
14 changes: 13 additions & 1 deletion docs/mdsource/naming.source.md
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,19 @@ eg. add the following to `.gitignore`

## Received and multi-targeting

When a test project uses more than one `TargetFrameworks` (eg `<TargetFrameworks>net48;net7.0</TargetFrameworks>`) the runtime and version will be always be added as a uniqueness to the received file. This prevents file locking contention when the tests from both target framework run in parallel.
When a test project uses more than one `TargetFrameworks` (eg `<TargetFrameworks>net48;net7.0</TargetFrameworks>`) the runtime and version will always be added as a uniqueness to the received file name. This prevents file locking contention when the tests from both target framework run in parallel.

This applies to the default file naming. Under [UseUniqueDirectory](#useuniquedirectory) the received files use the same uniqueness as the verified files.


## Received and single-targeting

When a test project uses a single `TargetFramework`, there is no such contention, so the received file uses the same uniqueness as the verified file. With `UniqueForRuntime` both files include the runtime:

```
TheTest.TheMethod.DotNet.received.txt
TheTest.TheMethod.DotNet.verified.txt
```


## Orphaned verified files
Expand Down
14 changes: 13 additions & 1 deletion docs/naming.md
Original file line number Diff line number Diff line change
Expand Up @@ -897,7 +897,19 @@ eg. add the following to `.gitignore`

## Received and multi-targeting

When a test project uses more than one `TargetFrameworks` (eg `<TargetFrameworks>net48;net7.0</TargetFrameworks>`) the runtime and version will be always be added as a uniqueness to the received file. This prevents file locking contention when the tests from both target framework run in parallel.
When a test project uses more than one `TargetFrameworks` (eg `<TargetFrameworks>net48;net7.0</TargetFrameworks>`) the runtime and version will always be added as a uniqueness to the received file name. This prevents file locking contention when the tests from both target framework run in parallel.

This applies to the default file naming. Under [UseUniqueDirectory](#useuniquedirectory) the received files use the same uniqueness as the verified files.


## Received and single-targeting

When a test project uses a single `TargetFramework`, there is no such contention, so the received file uses the same uniqueness as the verified file. With `UniqueForRuntime` both files include the runtime:

```
TheTest.TheMethod.DotNet.received.txt
TheTest.TheMethod.DotNet.verified.txt
```


## Orphaned verified files
Expand Down
54 changes: 54 additions & 0 deletions src/StaticSettingsTests/SingleTfmTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,58 @@ public async Task Simple()
File.Delete(received);
}
}

// With a single target framework the received file carries the same uniqueness as the verified
// file, so the two names match and tooling can pair a received file with its verified file. When
// multiple frameworks are targeted the received file uses the runtime and version instead, which
// Verify.Tests covers via DanglingFiles.
[Fact]
public Task ReceivedUsesRuntime() =>
AssertReceivedIsSuffixed(
"SingleTfmTests.ReceivedUsesRuntime",
Namer.Runtime,
_ => _.UniqueForRuntime());

[Fact]
public Task ReceivedUsesRuntimeAndVersion() =>
AssertReceivedIsSuffixed(
"SingleTfmTests.ReceivedUsesRuntimeAndVersion",
Namer.RuntimeAndVersion,
_ => _.UniqueForRuntimeAndVersion());

[Fact]
public Task ReceivedUsesRuntimeForFileName() =>
AssertReceivedIsSuffixed(
"CustomFileName",
Namer.Runtime,
_ =>
{
_.UseFileName("CustomFileName");
_.UniqueForRuntime();
});

static async Task AssertReceivedIsSuffixed(string prefix, string suffix, Action<VerifySettings> configure)
{
var verified = CurrentFile.Relative($"{prefix}.{suffix}.verified.txt");
var received = CurrentFile.Relative($"{prefix}.{suffix}.received.txt");
var unsuffixed = CurrentFile.Relative($"{prefix}.received.txt");
File.Delete(verified);
File.Delete(received);
File.Delete(unsuffixed);
var settings = new VerifySettings();
settings.DisableDiff();
configure(settings);
try
{
await Assert.ThrowsAsync<VerifyException>(() => Verify("value", settings));
Assert.True(File.Exists(received));
Assert.False(File.Exists(unsuffixed));
}
finally
{
File.Delete(verified);
File.Delete(received);
File.Delete(unsuffixed);
}
}
}
26 changes: 19 additions & 7 deletions src/Verify/Verifier/InnerVerifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -306,22 +306,18 @@ void InitForFileConvention(Namer namer, string typeAndMethod, Action<StringBuild
{
var sharedUniqueness = PrefixUnique.SharedUniqueness(namer);
var uniquenessVerified = GetUniquenessVerified(sharedUniqueness, namer);

if (VerifierSettings.TargetsMultipleFramework)
{
sharedUniqueness.Add(Namer.RuntimeAndVersion);
}
var uniquenessReceived = GetUniquenessReceived(sharedUniqueness, uniquenessVerified);

if (settings.FileName is not null)
{
return (
$"{settings.FileName}{sharedUniqueness}",
$"{settings.FileName}{uniquenessReceived}",
$"{settings.FileName}{uniquenessVerified}");
}

var receivedBuilder = new StringBuilder(typeAndMethod);
receivedParameters?.Invoke(receivedBuilder);
receivedBuilder.Append(sharedUniqueness);
receivedBuilder.Append(uniquenessReceived);
var receivedPrefix = receivedBuilder.ToString();
if (settings.ignoreParametersForVerified)
{
Expand All @@ -338,6 +334,22 @@ void InitForFileConvention(Namer namer, string typeAndMethod, Action<StringBuild
verifiedBuilder.ToString());
}

// When multiple frameworks are targeted, the received file uses the runtime and version, so that
// the concurrent runs of each framework do not overwrite each others received files. Otherwise
// only one runtime is in play, so the received file can use the same uniqueness as the verified
// file. Keeping the two aligned allows tooling to pair a received file with its verified file.
// Mutates sharedUniqueness, so GetUniquenessVerified, which takes a copy, has to be called first.
static UniquenessList GetUniquenessReceived(UniquenessList sharedUniqueness, UniquenessList uniquenessVerified)
{
if (!VerifierSettings.TargetsMultipleFramework)
{
return uniquenessVerified;
}

sharedUniqueness.Add(Namer.RuntimeAndVersion);
return sharedUniqueness;
}

static UniquenessList GetUniquenessVerified(UniquenessList sharedUniqueness, Namer namer)
{
var uniquenessVerified = new UniquenessList(sharedUniqueness);
Expand Down
Loading