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: 1 addition & 1 deletion docs/diff-tool.custom.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ New tools are added to the top of the order, the last tool added will resolve be
```cs
await DiffRunner.LaunchAsync(tempFile, targetFile);
```
<sup><a href='/src/DiffEngine.Tests/DiffRunnerTests.cs#L70-L74' title='Snippet source file'>snippet source</a> | <a href='#snippet-DiffRunnerLaunch' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/DiffEngine.Tests/DiffRunnerTests.cs#L92-L96' title='Snippet source file'>snippet source</a> | <a href='#snippet-DiffRunnerLaunch' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Alternatively the instance returned from `AddTool*` can be used to explicitly launch that tool.
Expand Down
4 changes: 2 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ A tool can be launched using the following:
```cs
await DiffRunner.LaunchAsync(tempFile, targetFile);
```
<sup><a href='/src/DiffEngine.Tests/DiffRunnerTests.cs#L70-L74' title='Snippet source file'>snippet source</a> | <a href='#snippet-DiffRunnerLaunch' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/DiffEngine.Tests/DiffRunnerTests.cs#L92-L96' title='Snippet source file'>snippet source</a> | <a href='#snippet-DiffRunnerLaunch' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Note that this method will respect the above [difference behavior](/docs/diff-tool.md#detected-difference-behavior) in terms of Auto refresh and MDI behaviors.
Expand All @@ -117,7 +117,7 @@ A tool can be closed using the following:
```cs
DiffRunner.Kill(file1, file2);
```
<sup><a href='/src/DiffEngine.Tests/DiffRunnerTests.cs#L84-L88' title='Snippet source file'>snippet source</a> | <a href='#snippet-DiffRunnerKill' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/DiffEngine.Tests/DiffRunnerTests.cs#L106-L110' title='Snippet source file'>snippet source</a> | <a href='#snippet-DiffRunnerKill' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Note that this method will respect the above [difference behavior](/docs/diff-tool.md#detected-difference-behavior) in terms of MDI behavior.
Expand Down
47 changes: 41 additions & 6 deletions src/DiffEngine.Tests/DiffRunnerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,29 @@ public class DiffRunnerTests
static string SourceDirectory { get; } = Path.GetDirectoryName(GetSourceFile())!;
static string GetSourceFile([CallerFilePath] string path = "") => path;

static ResolvedTool tool;
// Launching registers a real pending move of file1 over file2 with whatever owns the queue on
// this machine, usually the developer's tray. Accepting it moves file1, and discarding it
// deletes file1 and then its directory. Run against the source directory that quietly destroys
// the checked in fixtures, so the tests get copies instead.
// One directory for the whole class, because IsRunning matches the exact command string and
// WaitForRunning is also used at test start to wait out the previous test's kill, both of which
// want the same paths across tests. Fresh per run, so a FakeDiffTool left behind by a crashed
// earlier run cannot match this run's command.
static string TempDirectory { get; } = Path.Combine(
Path.GetTempPath(),
"DiffEngine.DiffRunnerTests",
Guid.NewGuid().ToString("N"));

[After(Class)]
public static void DeleteTempDirectory()
{
if (Directory.Exists(TempDirectory))
{
Directory.Delete(TempDirectory, true);
}
}

static ResolvedTool? tool;
string file2;
string file1;
string command;
Expand Down Expand Up @@ -216,13 +238,26 @@ async Task WaitForRunning(bool expected)

public DiffRunnerTests()
{
file1 = Path.Combine(SourceDirectory, "DiffRunner.file1.txt");
file2 = Path.Combine(SourceDirectory, "DiffRunner.file2.txt");
command = tool.BuildCommand(file1, file2);
file1 = CopyFixture("DiffRunner.file1.txt");
file2 = CopyFixture("DiffRunner.file2.txt");
command = Tool.BuildCommand(file1, file2);
}

// Per test rather than per class: a discard deletes the temp file and its directory, so the
// copies have to be put back for the test that follows.
static string CopyFixture(string name)
{
Directory.CreateDirectory(TempDirectory);
var target = Path.Combine(TempDirectory, name);
File.Copy(Path.Combine(SourceDirectory, name), target, true);
return target;
}

static DiffRunnerTests() =>
tool = DiffTools.AddTool(
// Resolved on first use rather than in a type initializer. [After(Class)] runs even when every
// test here is skipped for the OS, so initializing the type has to be safe everywhere, and this
// reaches FakeDiffTool, which is only built for Windows and macOS.
static ResolvedTool Tool =>
tool ??= DiffTools.AddTool(
name: "FakeDiffTool",
autoRefresh: true,
isMdi: false,
Expand Down
2 changes: 1 addition & 1 deletion src/DiffEngine.Tests/FakeDiffTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ static FakeDiffTool()
return;
}

throw new();
throw new($"FakeDiffTool is only built for Windows and macOS. OS: {RuntimeInformation.OSDescription}");
}
}
36 changes: 35 additions & 1 deletion src/DiffEngine.Tests/ModuleInitializer.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,44 @@
public static class ModuleInitializer
// DiffEngineTray is the obsolete public shim, but its IsRunning is still where the tray check lives.
#pragma warning disable CS0618

using System.Net;
using System.Net.Sockets;

public static class ModuleInitializer
{
[ModuleInitializer]
public static void Initialize()
{
FileExtensions.AddTextFileConvention(_ => _.EndsWith(".txtConvention".AsSpan()));
Logging.Enable();
DiffRunner.Disabled = false;
DetachFromPendingFileSurfaces();
}

/// <summary>
/// Launching sends a real pending move to whatever owns the queue on this machine. On a
/// developer box that is the tray, started at login, and an accept or discard from it kills the
/// diff tool process DiffRunnerTests is asserting on. Being inconclusive when a tray is running
/// would mean those tests never run locally, so cut both routes instead: no tray, and a viewer
/// port nothing is listening on.
/// <para>
/// A failed move send is the end of the road in <c>PendingFiles.AddMove</c>, and only a delete
/// launches a viewer, which nothing here adds. So the moves these tests produce go nowhere and
/// no process outside the test can see them.
/// </para>
/// <para>
/// The cost is that the real piper send is not covered from here. That belongs with a tray
/// under test control, which is what DiffEngineTray.Tests/DiffRunnerCanKillTest does.
/// </para>
/// </summary>
static void DetachFromPendingFileSurfaces()
{
DiffEngineTray.IsRunning = false;

var listener = new TcpListener(IPAddress.Loopback, 0);
listener.Start();
var port = ((IPEndPoint) listener.LocalEndpoint).Port;
listener.Stop();
Environment.SetEnvironmentVariable(ViewerClient.PortVariable, port.ToString());
}
}
13 changes: 11 additions & 2 deletions src/DiffEngine.Tests/ViewerProtocolTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,15 @@ public async Task AListingWithNoWindowCommandSaysNothing()
await Assert.That(parsed!.Window).IsNull();
}

/// <summary>
/// The client's three second default is what a real caller uses to decide the owner has died.
/// The tests below are about what the owner answers rather than how fast, and CI starts six
/// test assemblies at once on a two core runner, where an answer arriving on a scheduled task
/// has twice missed that deadline. <see cref="ASlowExchangeDoesNotBlockTheNext"/> keeps the
/// default, because being answered inside it while another exchange is held is the point there.
/// </summary>
static readonly TimeSpan underLoad = TimeSpan.FromSeconds(30);

/// <summary>
/// Bind, serve and exchange for real. The one test here that is not pure string work, because
/// the async socket calls take a different path on the frameworks without a token overload.
Expand All @@ -516,7 +525,7 @@ public async Task AnOwnerAnswersAClient()
using var cancel = new CancelSource();
var listening = server.Listen(_ => ViewerResponse.Success($"heard {_.Verb}"), cancel.Token);

var sent = ViewerClient.TrySend(new(ViewerVerb.List), out var response, server.Port);
var sent = ViewerClient.TrySend(new(ViewerVerb.List), out var response, server.Port, underLoad);

await Assert.That(sent).IsTrue();
await Assert.That(response!.Ok).IsTrue();
Expand Down Expand Up @@ -590,7 +599,7 @@ public async Task AThrowingHandlerAnswersAnError()
using var cancel = new CancelSource();
var listening = server.Listen(_ => throw new("the handler is broken"), cancel.Token);

var sent = ViewerClient.TrySend(new(ViewerVerb.List), out var response, server.Port);
var sent = ViewerClient.TrySend(new(ViewerVerb.List), out var response, server.Port, underLoad);

await Assert.That(sent).IsTrue();
await Assert.That(response!.Ok).IsFalse();
Expand Down
Loading