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
70 changes: 46 additions & 24 deletions src/DiffEngine.Tests/InlineApplierTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,26 @@ static byte[] Utf8(string text, bool bom)

const string source = "class C\n{\n void M() => Verify(value).Snapshot(\"old\");\n}";

// Nothing here queues a patch, so none of them has a reviewable identity. Stated once rather
// than at every call site below.
static InlinePatch Patch(
string sourceFile,
int lineHint,
string? originalExpression,
string newContent,
InlinePatchMode mode = InlinePatchMode.Set) =>
new(sourceFile, lineHint, originalExpression, newContent, mode)
{
TestName = null
};

[Test]
public async Task Utf8BomPreserved()
{
var path = WriteTemp(Utf8(source, bom: true));
try
{
var result = InlineApplier.Apply(new(path, 3, "\"old\"", "new"));
var result = InlineApplier.Apply(Patch(path, 3, "\"old\"", "new"));
await Assert.That(result.Status).IsEqualTo(InlineApplyStatus.Applied);
var bytes = await File.ReadAllBytesAsync(path);
await Assert.That(bytes[0]).IsEqualTo((byte)0xEF);
Expand All @@ -51,7 +64,7 @@ public async Task NoBomStaysNoBom()
var path = WriteTemp(Utf8(source, bom: false));
try
{
var result = InlineApplier.Apply(new(path, 3, "\"old\"", "new"));
var result = InlineApplier.Apply(Patch(path, 3, "\"old\"", "new"));
await Assert.That(result.Status).IsEqualTo(InlineApplyStatus.Applied);
var bytes = await File.ReadAllBytesAsync(path);
await Assert.That(bytes[0]).IsEqualTo((byte)'c');
Expand All @@ -69,7 +82,7 @@ public async Task Utf16Preserved()
var path = WriteTemp([.. encoding.GetPreamble(), .. encoding.GetBytes(source)]);
try
{
var result = InlineApplier.Apply(new(path, 3, "\"old\"", "new"));
var result = InlineApplier.Apply(Patch(path, 3, "\"old\"", "new"));
await Assert.That(result.Status).IsEqualTo(InlineApplyStatus.Applied);
var bytes = await File.ReadAllBytesAsync(path);
await Assert.That(bytes[0]).IsEqualTo((byte)0xFF);
Expand All @@ -96,7 +109,7 @@ .. Utf8("class C\n{\n // caf", bom: false),
var path = WriteTemp(bytes);
try
{
var result = InlineApplier.Apply(new(path, 4, "\"old\"", "new"));
var result = InlineApplier.Apply(Patch(path, 4, "\"old\"", "new"));

await Assert.That(result.Status).IsEqualTo(InlineApplyStatus.Failed);
await Assert.That(result.Message!).Contains("Convert it to UTF-8");
Expand All @@ -115,7 +128,7 @@ public async Task NonAsciiUtf8IsPreserved()
var path = WriteTemp(Utf8(text, bom: false));
try
{
var result = InlineApplier.Apply(new(path, 4, "\"old\"", "naïve ☕"));
var result = InlineApplier.Apply(Patch(path, 4, "\"old\"", "naïve ☕"));

await Assert.That(result.Status).IsEqualTo(InlineApplyStatus.Applied);
var after = await File.ReadAllTextAsync(path);
Expand All @@ -134,7 +147,7 @@ public async Task CrlfPreserved()
var path = WriteTemp(Utf8(source.Replace("\n", "\r\n"), bom: false));
try
{
var result = InlineApplier.Apply(new(path, 3, "\"old\"", "a\nb"));
var result = InlineApplier.Apply(Patch(path, 3, "\"old\"", "a\nb"));
await Assert.That(result.Status).IsEqualTo(InlineApplyStatus.Applied);
var text = await File.ReadAllTextAsync(path);
await Assert.That(text).DoesNotContain("a\nb");
Expand Down Expand Up @@ -163,7 +176,7 @@ public async Task EolAndBomCombinations(string fileEol, string contentEol, bool
try
{
var content = "a" + contentEol + "b";
var result = InlineApplier.Apply(new(path, 3, "\"old\"", content));
var result = InlineApplier.Apply(Patch(path, 3, "\"old\"", content));
await Assert.That(result.Status).IsEqualTo(InlineApplyStatus.Applied);

var bytes = await File.ReadAllBytesAsync(path);
Expand Down Expand Up @@ -202,7 +215,7 @@ public async Task LfFileIsNotConvertedToCrlf()
var path = WriteTemp(Utf8(source, bom: false));
try
{
var result = InlineApplier.Apply(new(path, 3, "\"old\"", "a\nb"));
var result = InlineApplier.Apply(Patch(path, 3, "\"old\"", "a\nb"));
await Assert.That(result.Status).IsEqualTo(InlineApplyStatus.Applied);
await Assert.That(await File.ReadAllTextAsync(path)).DoesNotContain("\r");
}
Expand All @@ -217,7 +230,7 @@ public async Task LfFileIsNotConvertedToCrlf()
[Test]
public async Task TryParseToleratesLeadingBom()
{
var patch = new InlinePatch(@"C:\proj\Tests.cs", 7, "\"old\"", "new content");
var patch = Patch(@"C:\proj\Tests.cs", 7, "\"old\"", "new content");
var payload = "" + InlinePatchFile.Build(patch);

var read = InlinePatchFile.TryParse(payload, out var result);
Expand All @@ -231,7 +244,7 @@ public async Task TryParseToleratesLeadingBom()
[Test]
public async Task MissingFileFails()
{
var result = InlineApplier.Apply(new(Path.Combine(Path.GetTempPath(), "does-not-exist-inline.cs"), 1, null, "x"));
var result = InlineApplier.Apply(Patch(Path.Combine(Path.GetTempPath(), "does-not-exist-inline.cs"), 1, null, "x"));
await Assert.That(result.Status).IsEqualTo(InlineApplyStatus.Failed);
}

Expand All @@ -242,7 +255,7 @@ public async Task AlreadyAppliedDoesNotWrite()
try
{
var before = File.GetLastWriteTimeUtc(path);
var result = InlineApplier.Apply(new(path, 3, "\"old\"", "old"));
var result = InlineApplier.Apply(Patch(path, 3, "\"old\"", "old"));
await Assert.That(result.Status).IsEqualTo(InlineApplyStatus.AlreadyApplied);
await Assert.That(File.GetLastWriteTimeUtc(path)).IsEqualTo(before);
}
Expand All @@ -259,8 +272,8 @@ public async Task ParallelAppliesToSameFile()
var path = WriteTemp(Utf8(multi, bom: false));
try
{
var taskA = Task.Run(() => InlineApplier.Apply(new(path, 3, "\"oldA\"", "newA")));
var taskB = Task.Run(() => InlineApplier.Apply(new(path, 4, "\"oldB\"", "newB")));
var taskA = Task.Run(() => InlineApplier.Apply(Patch(path, 3, "\"oldA\"", "newA")));
var taskB = Task.Run(() => InlineApplier.Apply(Patch(path, 4, "\"oldB\"", "newB")));
var results = await Task.WhenAll(taskA, taskB);
await Assert.That(results[0].Status).IsEqualTo(InlineApplyStatus.Applied);
await Assert.That(results[1].Status).IsEqualTo(InlineApplyStatus.Applied);
Expand Down Expand Up @@ -300,8 +313,8 @@ public async Task ParallelAppliesWithIdenticalLiterals()
var path = WriteTemp(Utf8(multi, bom: false));
try
{
var taskA = Task.Run(() => InlineApplier.Apply(new(path, 3, "\"old\"", "same")));
var taskB = Task.Run(() => InlineApplier.Apply(new(path, 4, "\"old\"", "same")));
var taskA = Task.Run(() => InlineApplier.Apply(Patch(path, 3, "\"old\"", "same")));
var taskB = Task.Run(() => InlineApplier.Apply(Patch(path, 4, "\"old\"", "same")));
var results = await Task.WhenAll(taskA, taskB);

await Assert.That(results[0].Status).IsEqualTo(InlineApplyStatus.Applied);
Expand All @@ -324,8 +337,8 @@ public async Task SequentialAppliesWithIdenticalLiterals()
var path = WriteTemp(Utf8(multi, bom: false));
try
{
var first = InlineApplier.Apply(new(path, 3, "\"old\"", "newA"));
var second = InlineApplier.Apply(new(path, 4, "\"old\"", "newB"));
var first = InlineApplier.Apply(Patch(path, 3, "\"old\"", "newA"));
var second = InlineApplier.Apply(Patch(path, 4, "\"old\"", "newB"));

await Assert.That(first.Status).IsEqualTo(InlineApplyStatus.Applied);
await Assert.That(second.Status).IsEqualTo(InlineApplyStatus.Applied);
Expand All @@ -351,7 +364,7 @@ public async Task NotFoundWhenSourceChanged()
var path = WriteTemp(Utf8(source, bom: false));
try
{
var result = InlineApplier.Apply(new(path, 3, "\"gone-expression\"", "new"));
var result = InlineApplier.Apply(Patch(path, 3, "\"gone-expression\"", "new"));
await Assert.That(result.Status).IsEqualTo(InlineApplyStatus.NotFound);
await Assert.That(result.Message!).Contains("Re-run the test");
}
Expand All @@ -367,7 +380,10 @@ public class InlinePatchFileTests
[Test]
public async Task RoundTrip()
{
var patch = new InlinePatch(@"C:\proj\Tests.cs", 42, "\"\"\"\nold\n\"\"\"", "line1\nline2");
var patch = new InlinePatch(@"C:\proj\Tests.cs", 42, "\"\"\"\nold\n\"\"\"", "line1\nline2")
{
TestName = null
};
var path = Path.Combine(Path.GetTempPath(), $"InlinePatchFileTests_{Guid.NewGuid():N}.inlinepatch");
try
{
Expand All @@ -388,7 +404,10 @@ public async Task RoundTrip()
[Test]
public async Task RoundTripNullExpression()
{
var patch = new InlinePatch("Tests.cs", 1, null, "content");
var patch = new InlinePatch("Tests.cs", 1, null, "content")
{
TestName = null
};
var path = Path.Combine(Path.GetTempPath(), $"InlinePatchFileTests_{Guid.NewGuid():N}.inlinepatch");
try
{
Expand All @@ -409,7 +428,10 @@ public async Task RoundTripNullExpression()
[Arguments(InlinePatchMode.Remove)]
public async Task RoundTripMode(InlinePatchMode mode)
{
var patch = new InlinePatch("Tests.cs", 1, null, "content", mode);
var patch = new InlinePatch("Tests.cs", 1, null, "content", mode)
{
TestName = null
};

var read = InlinePatchFile.TryParse(InlinePatchFile.Build(patch), out var result);

Expand All @@ -420,7 +442,7 @@ public async Task RoundTripMode(InlinePatchMode mode)
[Test]
public async Task DefaultModeIsSet()
{
var read = InlinePatchFile.TryParse(InlinePatchFile.Build(new("Tests.cs", 1, null, "content")), out var result);
var read = InlinePatchFile.TryParse(InlinePatchFile.Build(new("Tests.cs", 1, null, "content") { TestName = null }), out var result);

await Assert.That(read).IsTrue();
await Assert.That(result!.Mode).IsEqualTo(InlinePatchMode.Set);
Expand Down Expand Up @@ -481,7 +503,7 @@ public async Task MetadataRoundTrips()
[Test]
public async Task NullMetadataRoundTripsAsNull()
{
var read = InlinePatchFile.TryParse(InlinePatchFile.Build(new("Tests.cs", 1, null, "content")), out var result);
var read = InlinePatchFile.TryParse(InlinePatchFile.Build(new("Tests.cs", 1, null, "content") { TestName = null }), out var result);

await Assert.That(read).IsTrue();
await Assert.That(result!.TestName).IsNull();
Expand Down Expand Up @@ -533,7 +555,7 @@ public async Task MetadataOrderIsFlexible()
[Test]
public async Task UnknownTrailingLinesAreIgnored()
{
var payload = InlinePatchFile.Build(new("Tests.cs", 1, null, "content")) + "future: value\n";
var payload = InlinePatchFile.Build(new("Tests.cs", 1, null, "content") { TestName = null }) + "future: value\n";

var read = InlinePatchFile.TryParse(payload, out var result);

Expand Down
10 changes: 8 additions & 2 deletions src/DiffEngine.Tests/InlineQueueTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,16 @@
/// </summary>
public class InlineQueueTests
{
static InlinePatch Patch(string source = "Sample.cs", int line = 42, string content = "new", string? framework = null) =>
static InlinePatch Patch(
string source = "Sample.cs",
int line = 42,
string content = "new",
string? framework = null,
string? testName = null) =>
new(source, line, "\"old\"", content)
{
Framework = framework
Framework = framework,
TestName = testName
};

static InlineApplyResult Fails(InlinePatch patch) =>
Expand Down
35 changes: 25 additions & 10 deletions src/DiffEngine.Tests/ViewerProtocolTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,25 @@
/// </summary>
public class ViewerProtocolTests
{
// These pin the wire shape rather than what a reviewer reads, so nothing here is named. The
// one test that is about the name says so itself.
static InlinePatch Patch(
string source,
int line,
string? expression,
string content,
InlinePatchMode mode = InlinePatchMode.Set,
string? framework = null) =>
new(source, line, expression, content, mode)
{
TestName = null,
Framework = framework
};

[Test]
public async Task InlineMessageRoundTrips()
{
var patch = new InlinePatch("Tests.cs", 42, "\"old\"", "new content");
var patch = Patch("Tests.cs", 42, "\"old\"", "new content");

var payload = new ViewerMessage(ViewerVerb.Inline, Body: InlinePatchFile.Build(patch)).Build();

Expand All @@ -33,7 +48,7 @@ public async Task InlineMessageRoundTrips()
public async Task AwkwardSnapshotTextSurvivesTheRoundTrip()
{
var content = "line \"one\"\n\tbraces {} and | pipes\r\nversion: 1\nverb: quit\n";
var patch = new InlinePatch("Tests.cs", 1, null, content);
var patch = Patch("Tests.cs", 1, null, content);

var payload = new ViewerMessage(ViewerVerb.Inline, Body: InlinePatchFile.Build(patch)).Build();

Expand All @@ -53,7 +68,7 @@ public async Task EveryModeRoundTrips()
foreach (var name in Enum.GetNames(typeof(InlinePatchMode)))
{
var mode = (InlinePatchMode) Enum.Parse(typeof(InlinePatchMode), name);
var patch = new InlinePatch("Tests.cs", 1, null, "content", mode);
var patch = Patch("Tests.cs", 1, null, "content", mode);
var payload = new ViewerMessage(ViewerVerb.Inline, Body: InlinePatchFile.Build(patch)).Build();

await Assert.That(ViewerMessage.TryParse(payload, out var message)).IsTrue();
Expand Down Expand Up @@ -151,7 +166,7 @@ public async Task AListingItemCarriesKeyNameAndStatus()
[Test]
public async Task AFullListingRoundTripsThePatch()
{
var patch = new InlinePatch("Tests.cs", 42, "\"old\"", "new content");
var patch = Patch("Tests.cs", 42, "\"old\"", "new content");
var listing = ViewerResponse.Listing(
[
new("tests.cs|42", "Tests.cs:42", "locked", InlinePatchFile.Build(patch))
Expand All @@ -174,7 +189,7 @@ public async Task AFullListingRoundTripsThePatch()
[Test]
public async Task AFullListingHasNoItemLines()
{
var patch = InlinePatchFile.Build(new("Tests.cs", 1, null, "content"));
var patch = InlinePatchFile.Build(Patch("Tests.cs", 1, null, "content"));
var text = ViewerResponse.Listing([new("key", "Tests.cs:1", null, patch)]).Build();

await Assert.That(Fields(text, "item: ")).IsEmpty();
Expand Down Expand Up @@ -214,7 +229,7 @@ public async Task SettleCarriesTheOriginInTheBody()
[Test]
public async Task AFullListingCarriesThePrimaryOrigins()
{
var patch = InlinePatchFile.Build(new("Tests.cs", 42, "\"old\"", "new content"));
var patch = InlinePatchFile.Build(Patch("Tests.cs", 42, "\"old\"", "new content"));
var listing = ViewerResponse.Listing(
[
new("tests.cs|42", "Tests.cs:42", null, patch)
Expand All @@ -236,8 +251,8 @@ public async Task AFullListingCarriesThePrimaryOrigins()
[Test]
public async Task AVariantLineRoundTrips()
{
var primary = InlinePatchFile.Build(new("Tests.cs", 42, "\"old\"", "eight"));
var other = InlinePatchFile.Build(new("Tests.cs", 42, "\"old\"", "nine"));
var primary = InlinePatchFile.Build(Patch("Tests.cs", 42, "\"old\"", "eight"));
var other = InlinePatchFile.Build(Patch("Tests.cs", 42, "\"old\"", "nine"));
var listing = ViewerResponse.Listing(
[
new("tests.cs|42", "Tests.cs:42", null, primary)
Expand Down Expand Up @@ -338,8 +353,8 @@ public async Task TrackedKeysCannotCollideWithInlineKeys()
[Test]
public async Task AConflictedEntryListsItsStatus()
{
var eight = new InlinePatch("Tests.cs", 42, "\"old\"", "eight") { Framework = "net8.0" };
var nine = new InlinePatch("Tests.cs", 42, "\"old\"", "nine") { Framework = "net9.0" };
var eight = Patch("Tests.cs", 42, "\"old\"", "eight", framework: "net8.0");
var nine = Patch("Tests.cs", 42, "\"old\"", "nine", framework: "net9.0");
var entry = new PendingInline([new(eight, ["net8.0"]), new(nine, ["net9.0"])]);

var listed = ViewerListing.Items([entry], withPatches: false).Single();
Expand Down
14 changes: 11 additions & 3 deletions src/DiffEngine/Inline/InlinePatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,18 @@ public InlinePatch(
public InlinePatchMode Mode { get; set; }

/// <summary>
/// Display name of the test that produced this patch. Optional; supplied by the caller
/// (Verify). Null when the caller did not provide one.
/// Display name of the test that produced this patch, supplied by the caller (Verify). The
/// viewer labels and groups queue entries by it, falling back to the call site without one.
/// <para>
/// Required, though still nullable: a patch that never reaches a queue — an
/// <see cref="InlinePatchMode.Remove"/>, or an apply straight through
/// <see cref="InlineApplier"/> — has no reviewable identity and says so with an explicit null.
/// Omission and decision were previously indistinguishable, and a producer that simply never
/// set it went unnoticed for as long as it did because the viewer's fallback reads as an
/// unnamed test rather than as a missing field.
/// </para>
/// </summary>
public string? TestName { get; set; }
public required string? TestName { get; set; }

/// <summary>
/// Short target framework of the test process that produced this patch ("net9.0", "net48").
Expand Down
5 changes: 4 additions & 1 deletion src/DiffEngineTray.Tests/DebugReportTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,10 @@ public async Task Owned()

var source = Path.Combine(directory, "SampleTests.cs");
await File.WriteAllTextAsync(source, "");
var patch = new InlinePatch(source, 42, "\"old\"", "line one\nline two");
var patch = new InlinePatch(source, 42, "\"old\"", "line one\nline two")
{
TestName = null
};
// Over the socket, as the test process that failed the assertion sends it
if (!ViewerClient.TrySend(new(ViewerVerb.Inline, Body: InlinePatchFile.Build(patch)), out _, host.Port))
{
Expand Down
10 changes: 8 additions & 2 deletions src/DiffEngineTray.Tests/OwnedInlineHostTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,16 @@ public ViewerResponse Send(ViewerMessage message)
return response;
}

public ViewerResponse Queue(string source = @"c:\repo\SampleTests.cs", int line = 42, string content = "new", string? framework = null) =>
public ViewerResponse Queue(
string source = @"c:\repo\SampleTests.cs",
int line = 42,
string content = "new",
string? framework = null,
string? testName = null) =>
Send(new(ViewerVerb.Inline, Body: InlinePatchFile.Build(new(source, line, "\"old\"", content)
{
Framework = framework
Framework = framework,
TestName = testName
})));

public void Dispose() =>
Expand Down
3 changes: 2 additions & 1 deletion src/DiffEngineTray.Tests/TrayViewerSyncTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -648,7 +648,8 @@ static string Payload(string source, int line, string content, string? framework
InlinePatchFile.Build(
new(source, line, "\"old\"", content)
{
Framework = framework
Framework = framework,
TestName = null
});

/// <summary>
Expand Down
Loading
Loading