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
85 changes: 83 additions & 2 deletions src/Verify.Tests/InlineRetireTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,83 @@ public async Task NotInlineRetiresTheCallSite()
// No framework: the statement is "there is no inline snapshot here", not "this framework
// now passes", so the owner takes the whole entry rather than one variant of it.
Assert.Null(settle.Origin);

// And no value, since no call site here holds an inline snapshot to settle by
Assert.Null(settle.Value);
}

/// <summary>
/// A passing inline verification settles with the value its expected argument holds. Where the
/// line names no entry the owner falls back to the member, and a member is not a call site: the
/// value is what keeps a passing call from settling the entry of a failing sibling beside it.
/// </summary>
[Fact]
public async Task APassingSnapshotSettlesWithItsValue()
{
// Nothing is at this path. A settle names the call site and reads nothing from it
var source = Path.Combine(listener.Directory, "Snapshot.cs");

var settings = new VerifySettings();
settings.UseDirectory(listener.Directory);
settings.Snapshot("value", source, 7, "\"value\"");

await Verify("value", settings);

var settle = listener.AwaitSettle(nameof(APassingSnapshotSettlesWithItsValue));
Assert.NotNull(settle);
Assert.Equal(InlineKey.For(InnerVerifier.MapSourceFile(source), 7), settle.Key);
Assert.Equal("value", settle.Value);
}

/// <summary>
/// The same on disk. A trio staged for another call site in this member - a failing sibling -
/// is found by the member fallback, and only the value says it is not this call's: the passing
/// call holds neither its anchor nor its content. Without the value it was deleted.
/// </summary>
[Fact]
public async Task APassingSnapshotLeavesASiblingsStagedSnapshot()
{
var intermediate = VerifierSettings.IntermediateDir;
Assert.NotNull(intermediate);

// ReSharper disable once RedundantSuppressNullableWarningExpression
var staging = Path.Combine(intermediate!, InlineStaging.DirectoryName);
Directory.CreateDirectory(staging);

var source = Path.Combine(listener.Directory, "Sibling.cs");
var stem = nameof(APassingSnapshotLeavesASiblingsStagedSnapshot);
var patchFile = Path.Combine(staging, $"{stem}.inlinepatch");
var receivedFile = Path.Combine(staging, $"{stem}.received.txt");
var expectedFile = Path.Combine(staging, $"{stem}.expected.txt");
InlinePatchFile.Write(
patchFile,
new(InnerVerifier.MapSourceFile(source), 20, "\"old\"", "staged content")
{
TestName = $"InlineRetireTests.{stem}",
MemberName = stem,
OriginalValue = "old"
});
await File.WriteAllTextAsync(receivedFile, "staged content");
await File.WriteAllTextAsync(expectedFile, "old");

try
{
var settings = new VerifySettings();
settings.UseDirectory(listener.Directory);
settings.Snapshot("value", source, 7, "\"value\"");

await Verify("value", settings);

Assert.True(File.Exists(patchFile));
Assert.True(File.Exists(receivedFile));
Assert.True(File.Exists(expectedFile));
}
finally
{
File.Delete(patchFile);
File.Delete(receivedFile);
File.Delete(expectedFile);
}
}

/// <summary>
Expand Down Expand Up @@ -153,7 +230,7 @@ public async Task NotInlineClearsStagedFilesForTheCallSite()
static string SourceFile([CallerFilePath] string file = "") =>
InnerVerifier.MapSourceFile(file);

sealed record Settle(string? Key, string? Origin, string? Member);
sealed record Settle(string? Key, string? Origin, string? Member, string? Value);

/// <summary>
/// Stands in for whoever owns the inline queue: accepts connections, records what arrives, and
Expand Down Expand Up @@ -246,6 +323,7 @@ static bool TryReadSettle(string payload, out Settle? settle)
string? key = null;
string? origin = null;
string? member = null;
string? settledBy = null;
foreach (var raw in payload.Replace("\r\n", "\n").Split('\n'))
{
var separator = raw.IndexOf(':');
Expand All @@ -270,6 +348,9 @@ static bool TryReadSettle(string payload, out Settle? settle)
case "member":
member = Decode(value);
break;
case "value":
settledBy = Decode(value);
break;
}
}

Expand All @@ -278,7 +359,7 @@ static bool TryReadSettle(string payload, out Settle? settle)
return false;
}

settle = new(key, origin, member);
settle = new(key, origin, member, settledBy);
return true;
}

Expand Down
23 changes: 17 additions & 6 deletions src/Verify/Verifier/InlineEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,24 +109,35 @@ internal static string NormalizeExpected(string expected, string sourceFile) =>
/// <summary>
/// Drops anything a prior failing run left queued in the viewer.
/// </summary>
/// <remarks>
/// The member goes with the line, because the line alone stops finding the entry as soon as
/// an accept earlier in the file inserts a literal above this call site. The value goes with
/// the member, because a member is not a call site: where the line names nothing, the owner
/// falls back to the member, and a member holding one pending entry held it for whichever of
/// its calls passed. A passing sibling of a failing call settled the failing one's entry, and
/// its snapshot was pending nowhere. With the value, the owner only takes an entry this value
/// settles - one anchored to it, or waiting to become it. It is what the source holds, which
/// is what an entry's anchor and content are both written against, not the comparison's
/// trimmed form of it.
/// </remarks>
public void Settle()
{
if (diffEnabled)
{
// The member goes with the line, because the line alone stops finding the entry as
// soon as an accept earlier in the file inserts a literal above this call site.
DiffRunner.SettleInline(MappedSourceFile, inline.Line, inline.MemberName);
ClearStaged(MappedSourceFile, inline.Line, inline.MemberName);
DiffRunner.SettleInline(MappedSourceFile, inline.Line, inline.MemberName, SnapshotInSource);
ClearStaged(MappedSourceFile, inline.Line, inline.MemberName, SnapshotInSource);
}
}

/// <summary>
/// A settle only reaches a queue owner, and a snapshot can be on disk instead: staged by a run
/// that found no owner, or written out by one on its way out. Those files are what accept
/// tooling reads, so without this the snapshot stays pending for a test that now passes.
/// <paramref name="value" /> narrows the member fallback as it does for the queue: see
/// <see cref="Settle" />.
/// </summary>
static void ClearStaged(string mappedSourceFile, int line, string? memberName) =>
InlineStaging.Clear(mappedSourceFile, line, memberName, VerifierSettings.IntermediateDir);
static void ClearStaged(string mappedSourceFile, int line, string? memberName, string? value = null) =>
InlineStaging.Clear(mappedSourceFile, line, memberName, VerifierSettings.IntermediateDir, value: value);

/// <summary>
/// The members this process has actually inlined a verification for, so a retire in the same
Expand Down
Loading