From ef89e2f816f8e1f8b9323fee28bd98cffbb6047c Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 14 Aug 2026 22:34:48 +1000 Subject: [PATCH] Isolate DiffRunnerTests from the machine's queue owner Launching sends a real pending move to whatever owns the queue. On a developer box that is the tray, started at login, so accepting the move overwrote the checked in fixtures and discarding it deleted them - DiffRunner.file1.txt had been destroyed that way. A discard also kills the diff tool process the tests assert on. Cut both routes in the module initializer: no tray, and a viewer port nothing is listening on. A failed move send is the end of the road in PendingFiles.AddMove, and only a delete launches a viewer, which nothing here adds. The piper send stays covered by DiffRunnerCanKillTest, which drives a tray under test control. Also run against copies in a temp directory, so the fixtures cannot be touched even if that isolation is later reset. One directory per class, since IsRunning matches the exact command string and WaitForRunning wants the same paths across tests; copied per test, since a discard deletes the temp file and its directory. --- docs/diff-tool.custom.md | 2 +- readme.md | 4 +-- src/DiffEngine.Tests/DiffRunnerTests.cs | 36 +++++++++++++++++++++-- src/DiffEngine.Tests/ModuleInitializer.cs | 36 ++++++++++++++++++++++- 4 files changed, 72 insertions(+), 6 deletions(-) diff --git a/docs/diff-tool.custom.md b/docs/diff-tool.custom.md index 073068bc..c49438b5 100644 --- a/docs/diff-tool.custom.md +++ b/docs/diff-tool.custom.md @@ -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); ``` -snippet source | anchor +snippet source | anchor Alternatively the instance returned from `AddTool*` can be used to explicitly launch that tool. diff --git a/readme.md b/readme.md index 91212d86..017fb99a 100644 --- a/readme.md +++ b/readme.md @@ -108,7 +108,7 @@ A tool can be launched using the following: ```cs await DiffRunner.LaunchAsync(tempFile, targetFile); ``` -snippet source | anchor +snippet source | anchor 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. @@ -123,7 +123,7 @@ A tool can be closed using the following: ```cs DiffRunner.Kill(file1, file2); ``` -snippet source | anchor +snippet source | anchor Note that this method will respect the above [difference behavior](/docs/diff-tool.md#detected-difference-behavior) in terms of MDI behavior. diff --git a/src/DiffEngine.Tests/DiffRunnerTests.cs b/src/DiffEngine.Tests/DiffRunnerTests.cs index 5a0af685..0c179866 100644 --- a/src/DiffEngine.Tests/DiffRunnerTests.cs +++ b/src/DiffEngine.Tests/DiffRunnerTests.cs @@ -7,6 +7,28 @@ public class DiffRunnerTests static string SourceDirectory { get; } = Path.GetDirectoryName(GetSourceFile())!; static string GetSourceFile([CallerFilePath] string path = "") => path; + // 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; @@ -216,11 +238,21 @@ async Task WaitForRunning(bool expected) public DiffRunnerTests() { - file1 = Path.Combine(SourceDirectory, "DiffRunner.file1.txt"); - file2 = Path.Combine(SourceDirectory, "DiffRunner.file2.txt"); + 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( name: "FakeDiffTool", diff --git a/src/DiffEngine.Tests/ModuleInitializer.cs b/src/DiffEngine.Tests/ModuleInitializer.cs index b9205d43..6d65819b 100644 --- a/src/DiffEngine.Tests/ModuleInitializer.cs +++ b/src/DiffEngine.Tests/ModuleInitializer.cs @@ -1,4 +1,10 @@ -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() @@ -6,5 +12,33 @@ public static void Initialize() FileExtensions.AddTextFileConvention(_ => _.EndsWith(".txtConvention".AsSpan())); Logging.Enable(); DiffRunner.Disabled = false; + DetachFromPendingFileSurfaces(); + } + + /// + /// 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. + /// + /// A failed move send is the end of the road in PendingFiles.AddMove, 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. + /// + /// + /// 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. + /// + /// + 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()); } }