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
69 changes: 69 additions & 0 deletions src/DiffEngineViewer.Tests/HeldDeleteTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/// <summary>
/// When a sweep holds its deletes. A snapshot moving inline arrives as two unrelated entries - the
/// patch and a delete of the verified file it replaces - so a patch the applier will not take must
/// stop the delete, or the snapshot loses both copies at once.
/// </summary>
public class HeldDeleteTests
{
/// <summary>
/// A refusal recorded on a conflicted entry belongs to the targeted accept that made it, not
/// to the batch reading it. A bulk accept hands conflicted entries back untouched, so that
/// status sat there and held every pending delete on every later accept-all, citing a refusal
/// that had not happened.
/// </summary>
[Test]
public async Task A_refusal_left_on_a_conflicted_entry_does_not_hold_a_later_sweep()
{
var state = Conflicted();

// One variant accepted on its own, and refused - the file was locked, say. The entry
// stays, conflicted still, and carries what the applier said
var refused = ViewerSession.Apply(
state,
CommandKind.Accept,
Sweeping(InlineApplyResult.Failed("Failed to write: BTests.cs")));
await Assert.That(refused.Queue.Single(_ => _.Kind == QueueEntryKind.Inline).Status).IsNotNull();

var swept = ViewerSession.Apply(refused, CommandKind.AcceptAll, Sweeping(InlineApplyResult.Applied));
await Assert.That(swept.Queue.Where(_ => _.Kind == QueueEntryKind.Delete)).IsEmpty();
}

/// <summary>
/// The reason the read exists at all: a patch this batch could not write still holds the
/// deletes.
/// </summary>
[Test]
public async Task A_refusal_in_this_sweep_holds_the_deletes()
{
var state = ViewerSession.EnqueueTracked(
Fixtures.Inline(Fixtures.Patch("A.cs", 1)),
Fixtures.Delete());

var swept = ViewerSession.Apply(
state,
CommandKind.AcceptAll,
Sweeping(InlineApplyResult.NotFound("Could not locate the VerifyInline call")));

var delete = swept.Queue.Single(_ => _.Kind == QueueEntryKind.Delete);
await Assert.That(delete.Status).StartsWith("Held:");
}

/// <summary>
/// A delete that is actually deletable, so that a held one and a failed one cannot be confused
/// for each other: the fixture's default actions throw for anything touching a file.
/// </summary>
static ViewerActions Sweeping(InlineApplyResult result) =>
Fixtures.Applying(result) with
{
DeleteFile = static _ =>
{
}
};

static SessionState Conflicted() =>
ViewerSession.EnqueueTracked(
Fixtures.Inline(
Fixtures.Patch(content: "eight", framework: "net8.0"),
Fixtures.Patch(content: "nine", framework: "net9.0")),
Fixtures.Delete());
}
10 changes: 7 additions & 3 deletions src/DiffEngineViewer/ViewerSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -630,8 +630,12 @@ static SessionState SweepTracked(
/// not the last copy of it leaving.
/// </para>
/// <para>
/// A status on an inline entry is the outcome of an attempt, so this reads failures only:
/// conflicted entries that a bulk accept skips are handed back untouched and carry none.
/// A status on an inline entry is the outcome of an attempt, and this has to read only the
/// attempts the sweep it is answering for made. A bulk accept skips conflicted entries and
/// hands them back exactly as they were, so a status on one of those was left by a targeted
/// accept of a single variant, at some earlier point - and reading it held every pending
/// delete on every accept-all after it, citing a refusal in a batch that never touched the
/// entry. Everything else left in the queue with a status was tried and refused just now.
/// Discarding asks nothing of the patches, so it sweeps as it always did.
/// </para>
/// </summary>
Expand All @@ -644,7 +648,7 @@ static bool InlineRefused(IReadOnlyList<QueueEntry> queue, bool discarding)

foreach (var entry in queue)
{
if (entry is {Kind: QueueEntryKind.Inline, Status: not null})
if (entry is {Kind: QueueEntryKind.Inline, Conflicted: false, Status: not null})
{
return true;
}
Expand Down
Loading