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
2 changes: 2 additions & 0 deletions docs/mdsource/tray.source.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ A test can produce multiple resulting snapshots. If the accepted versions has a

Clicking "file1" or "file2" will delete file1 or file2 respectively. The drop down will expose extra actions for that change.

A delete is withdrawn when a later test run verifies against its file again, since the file is then in use rather than stale: `DiffRunner.SettleDelete(file)` drops the pending delete and leaves the file alone. It reaches a tray that owns the inline queue, which is the usual arrangement since the tray starts at login.


### Pending snapshots

Expand Down
2 changes: 2 additions & 0 deletions docs/tray.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ A test can produce multiple resulting snapshots. If the accepted versions has a

Clicking "file1" or "file2" will delete file1 or file2 respectively. The drop down will expose extra actions for that change.

A delete is withdrawn when a later test run verifies against its file again, since the file is then in use rather than stale: `DiffRunner.SettleDelete(file)` drops the pending delete and leaves the file alone. It reaches a tray that owns the inline queue, which is the usual arrangement since the tray starts at login.


### Pending snapshots

Expand Down
55 changes: 55 additions & 0 deletions src/DiffEngine.Tests/PendingFilesDiffTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,60 @@ public async Task SettlingWithNoOwnerIsSilent()
await Assert.That(() => PendingFiles.SettleDiff(Temp)).ThrowsNothing();
}

/// <summary>
/// The other end of a delete: the file it was raised for is in use again, because a later run
/// verified against it. The delete goes, by the tracked key it is listed under, and nothing
/// reaches the file.
/// </summary>
[Test]
public async Task SettlingADeleteSendsTheDeleteKey()
{
using var owner = new Recording();
var previousDisabled = DiffRunner.Disabled;
// DisabledChecker turns this on for build servers and AI CLIs, and this drives the real
// DiffRunner entry point
DiffRunner.Disabled = false;
try
{
DiffRunner.SettleDelete(Stale);
}
finally
{
DiffRunner.Disabled = previousDisabled;
}

await Assert.That(owner.Heard).IsEquivalentTo([$"{ViewerVerb.Settle}:{TrackedKeys.ForDelete(Stale)}:"]);
}

/// <summary>
/// A settle answers to the same switch the delete it settles did.
/// </summary>
[Test]
public async Task SettlingADeleteWhileDisabledSendsNothing()
{
using var owner = new Recording();
var previousDisabled = DiffRunner.Disabled;
DiffRunner.Disabled = true;
try
{
DiffRunner.SettleDelete(Stale);
}
finally
{
DiffRunner.Disabled = previousDisabled;
}

await Assert.That(owner.Heard).IsEmpty();
}

[Test]
public async Task SettlingADeleteWithNoOwnerIsSilent()
{
using var absent = new NoOwner();

await Assert.That(() => PendingFiles.SettleDelete(Stale)).ThrowsNothing();
}

/// <summary>
/// The tray works the arguments out for itself when a move arrives without them, and used to
/// take the viewer's declared ones - two plain paths, which open a window of its own for a
Expand Down Expand Up @@ -176,6 +230,7 @@ static ResolvedTool Other(bool isMdi) =>

const string Temp = @"c:\temp\Sample.Test.received.png";
const string Target = @"c:\code\Sample.Test.verified.png";
const string Stale = @"c:\code\Sample.Stale.verified.txt";

/// <summary>
/// Carries the identity the route branches on. Never started: an owner answers every time.
Expand Down
20 changes: 20 additions & 0 deletions src/DiffEngine/DiffRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,26 @@ public static Task AddDeleteAsync(string file)
return DiffEngineTray.AddDeleteAsync(file);
}

/// <summary>
/// Withdraws a pending delete, for when the file it was raised for is in use again: a later
/// run verified against it, so accepting the delete would remove a file that run depends on.
/// Nothing is deleted.
/// <para>
/// Does nothing when no tray or viewer holds the queue - and cheaply, the way
/// <see cref="SettleInline" /> does, since a port found with nothing listening is not
/// connected to again for a while.
/// </para>
/// </summary>
public static void SettleDelete(string file)
{
if (Disabled)
{
return;
}

PendingFiles.SettleDelete(file);
}

public static Task<LaunchResult> LaunchAsync(ResolvedTool tool, string tempFile, string targetFile, Encoding? encoding = null)
{
GuardFiles(tempFile, targetFile);
Expand Down
16 changes: 16 additions & 0 deletions src/DiffEngine/Tray/PendingFiles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,22 @@ await ViewerLaunchGate.LaunchAsync(
public static void SettleDiff(string tempFile) =>
ViewerClient.TrySend(new(ViewerVerb.Settle, TrackedKeys.ForMove(tempFile)));

/// <summary>
/// The other end of <see cref="AddDelete" />: the file a delete was raised for is in use
/// again, so the delete goes. Nothing is deleted.
/// <para>
/// To the queue owner, as <see cref="SettleDiff" /> is, which reaches the delete wherever it
/// is held: in a viewer, or in a tray that owns the queue, which keeps the deletes that arrived
/// over the piper port in the same tracked files. A tray that does not own the queue keeps its
/// own, and the piper format that would reach it is frozen at moves and deletes.
/// </para>
/// <para>
/// Silent when nobody answers: no owner means no row, which is the state this was asking for.
/// </para>
/// </summary>
public static void SettleDelete(string file) =>
ViewerClient.TrySend(new(ViewerVerb.Settle, TrackedKeys.ForDelete(file)));

/// <summary>
/// Whether a pending file should take the <see cref="AddDiff" /> route rather than the plain
/// tracking one, which is exactly whether the tool that would have opened a window for it is
Expand Down
21 changes: 21 additions & 0 deletions src/DiffEngineTray.Tests/TrayViewerSyncTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,27 @@ public async Task AnOwningTrayTracksWhatArrivesOnTheViewerPort()
await Assert.That(pair.Pump().Keys()).IsEquivalentTo([TrackedKeys.ForDelete(delete)]);
}

/// <summary>
/// A delete raised for a file that a later run verified against again, and settled by that
/// run. The settle names the file through its folded key while the tray tracked it as it
/// arrived, and the two have to meet: the delete goes, and the file it would have removed
/// stays where it is.
/// </summary>
[Test]
public async Task ASettledDeleteLeavesTheTrayAndKeepsTheFile()
{
await using var pair = new TrayOwned();
var delete = pair.AddDelete();
await Assert.That(pair.Pump().Keys()).IsEquivalentTo([delete.Key]);

var response = pair.Send(new(ViewerVerb.Settle, TrackedKeys.ForDelete(delete.File)));

await Assert.That(response.Ok).IsTrue();
await Assert.That(pair.Tracker.Deletes).IsEmpty();
await Assert.That(pair.Pump().Queue).IsEmpty();
await Assert.That(File.Exists(delete.File)).IsTrue();
}

/// <summary>
/// The tray check is cached, so this is the state a test process is in, not a property of the
/// machine. Set explicitly rather than assumed, because another test in this project sets it
Expand Down
17 changes: 17 additions & 0 deletions src/DiffEngineViewer.Tests/TrackedFileTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,23 @@ await Assert.That(settled.Queue.Select(_ => _.Kind))
await Assert.That(done).IsEmpty();
}

/// <summary>
/// A delete leaving because the file it was raised for is in use again: a later run verified
/// against it. Settling drops it, and a settle is a change to the queue alone, so the file
/// accepting would have removed stays.
/// </summary>
[Test]
public async Task SettlingADeleteDropsItAndKeepsTheRest()
{
var state = Owned(Fixtures.Move(), Fixtures.Delete());
state = ViewerSession.EnqueueInline(state, Fixtures.Patch());

var settled = ViewerSession.Settle(state, Fixtures.Delete().Key);

await Assert.That(settled.Queue.Select(_ => _.Kind))
.IsEquivalentTo([QueueEntryKind.Inline, QueueEntryKind.Move]);
}

[Test]
public async Task SettlingAPairThatIsNotQueuedChangesNothing()
{
Expand Down
Loading