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
10 changes: 5 additions & 5 deletions src/DiffEngineTray.Tests/DiffRunnerViewerFocusRaceTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public async Task A_move_the_tray_has_tracked_is_a_focus_and_nothing_more()
await Assert.That(fixture.Launches).IsEqualTo(1);
// Focus raised the window over the entry that was already there, so nothing re-tracked it:
// a Diff would have replaced the move, losing the exe and arguments the piper send carries.
await Assert.That(fixture.TrackedExe()).IsEqualTo(Exe);
await Assert.That(fixture.TrackedExe()).IsEqualTo(exe);
}

sealed class Fixture :
Expand Down Expand Up @@ -124,7 +124,7 @@ public Fixture()
/// that always worked.
/// </summary>
public void Track() =>
tracker.AddMove(Temp, Target, Exe, "--diff", false, null);
tracker.AddMove(Temp, Target, exe, "--diff", false, null);

public bool Tracks(string key) =>
((ITrackedFiles) tracker).Has(key);
Expand Down Expand Up @@ -169,13 +169,13 @@ public async ValueTask DisposeAsync()
/// Guarded as existing and never started: the pair is the viewer's, so nothing here reaches a
/// process launch.
/// </summary>
static readonly string Exe = Environment.ProcessPath!;
static readonly string exe = Environment.ProcessPath!;

static ResolvedTool Viewer() =>
new(
name: DiffTool.DiffEngineViewer.ToString(),
name: nameof(DiffTool.DiffEngineViewer),
tool: DiffTool.DiffEngineViewer,
exePath: Exe,
exePath: exe,
launchArguments: new(
Left: (temp, target) => $"\"{target}\" \"{temp}\"",
Right: (temp, target) => $"\"{temp}\" \"{target}\""),
Expand Down
2 changes: 1 addition & 1 deletion src/DiffEngineTray.Tests/DiffRunnerViewerMoveTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ static async Task<MovePayload> CaptureMove(Func<Task<LaunchResult>> launch)

static ResolvedTool Viewer() =>
new(
name: DiffTool.DiffEngineViewer.ToString(),
name: nameof(DiffTool.DiffEngineViewer),
tool: DiffTool.DiffEngineViewer,
// Guarded as existing, and never started: the tray takes the move, so nothing here
// reaches a launch.
Expand Down
1 change: 0 additions & 1 deletion src/DiffEngineViewer.Documents/DocumentRenderer.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Text;
using GeoConvert;
using Morph;
using Morph.PDFium;
Expand Down
4 changes: 4 additions & 0 deletions src/DiffEngineViewer.Tests/DiffEngineViewer.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
-->
<ProjectReference Include="..\DiffEngine\DiffEngine.csproj" Aliases="engine" />
<PackageReference Include="TUnit" />
<!-- For FileTypeLaunchTests: maps in every format, and JPEG and WebP images. -->
<PackageReference Include="GeoConvert" />
<PackageReference Include="SkiaSharp" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" />
<PackageReference Include="Microsoft.Testing.Extensions.CodeCoverage" />

<!--
Expand Down
2 changes: 1 addition & 1 deletion src/DiffEngineViewer.Tests/DocumentFileSideTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ await Assert.That(side.Document).IsEqualTo(new DocumentFile(
path,
19,
DocumentFormat.Pdf,
Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes("%PDF-1.4 not really"))),
Convert.ToHexString(SHA256.HashData("%PDF-1.4 not really"u8.ToArray())),
Reading: true));
}

Expand Down
4 changes: 2 additions & 2 deletions src/DiffEngineViewer.Tests/DocumentRendererTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public async Task APdfDrawsAPageAPng()
{
await Assert.That(ImageHeader.TryRead(page, out var header)).IsTrue();
// 300 x 200 points at 150 dpi
await Assert.That(header).IsEqualTo(new ImageHeader(ImageFormat.Png, 625, 417));
await Assert.That(header).IsEqualTo(new(ImageFormat.Png, 625, 417));
}
}

Expand Down Expand Up @@ -115,7 +115,7 @@ public async Task AnSvgDrawsAtAReviewableSize()
var pages = Render(path);
await Assert.That(pages.Count).IsEqualTo(1);
await Assert.That(ImageHeader.TryRead(pages[0], out var header)).IsTrue();
await Assert.That(header).IsEqualTo(new ImageHeader(ImageFormat.Png, 1024, 512));
await Assert.That(header).IsEqualTo(new(ImageFormat.Png, 1024, 512));
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion src/DiffEngineViewer.Tests/DocumentWatchTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ public async Task ACallThatNeverReturnsIsLeftBehind()
Timeout = TimeSpan.FromMilliseconds(200)
};
watch.Pump();
documents.Landing = () => release.Wait();
documents.Landing = release.Wait;

watch.Pump();

Expand Down
2 changes: 1 addition & 1 deletion src/DiffEngineViewer.Tests/EngineDiffTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ await Assert.That(scope.Fixture.Host.State.Queue.Single().Key)

static EngineResolvedTool Viewer() =>
new(
EngineTool.DiffEngineViewer.ToString(),
nameof(EngineTool.DiffEngineViewer),
EngineTool.DiffEngineViewer,
// Guarded as existing, and never started: an owner answers on the port every time.
Environment.ProcessPath!,
Expand Down
244 changes: 244 additions & 0 deletions src/DiffEngineViewer.Tests/FileTypeLaunchTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
extern alias engine;
using EngineLaunch = engine::DiffEngine.LaunchResult;
using EngineRunner = engine::DiffEngine.DiffRunner;
using EngineTool = engine::DiffEngine.DiffTool;
using EngineTools = engine::DiffEngine.DiffTools;

/// <summary>
/// Launches the viewer built from this working tree on a pair of every file type it compares
/// other than text, through the entry point a test run takes, for a person to look at.
/// <para>
/// What each head draws is the point. Pictures are drawn with each toolkit's own decoder, so which
/// image formats show is per platform, and documents and maps need the documents folder, which a
/// head's bin has and DiffEngine's bundled copy does not. None of that is in a screen snapshot,
/// which stops at the rows every renderer draws.
/// </para>
/// <para>
/// Explicit, as <see cref="ViewerLaunchTests"/> are, and for the same reasons. Run one case, or
/// the class, which opens each in turn as the last is closed:
/// <code>
/// Get-Process DiffEngineViewer -ErrorAction SilentlyContinue | Stop-Process -Force
/// dotnet build src
/// dotnet test --project src/DiffEngineViewer.Tests -- --treenode-filter "/*/*/FileTypeLaunchTests/*"
/// </code>
/// </para>
/// <para>
/// On its own port, and with the tray left out, for the reason <see cref="ViewerLaunchTests.GroupedQueue"/>
/// gives: a running DiffEngineTray would otherwise take the pair into its own queue and show it in
/// its installed viewer rather than this one.
/// </para>
/// </summary>
[NotInParallel]
public class FileTypeLaunchTests
{
const string port = "3499";

[Before(Class)]
public static void Enable()
{
ManualViewer.Enable();
Environment.SetEnvironmentVariable(ViewerClient.PortVariable, port);
EngineRunner.TrayDisabled = true;
// Five by default, and the class launches one window per extension. Left raised afterwards:
// the setter pins a value rather than restoring the ambient one, and nothing else in this
// assembly launches more than a handful.
EngineRunner.MaxInstancesToLaunch(100);
}

[After(Class)]
public static void Cleanup()
{
ManualViewer.Close();
Environment.SetEnvironmentVariable(ViewerClient.PortVariable, null);
EngineRunner.TrayDisabled = false;
}

[Test]
[Explicit]
[Arguments(".png")]
[Arguments(".bmp")]
[Arguments(".gif")]
[Arguments(".ico")]
[Arguments(".jpg")]
[Arguments(".jpeg")]
[Arguments(".webp")]
public Task Image(string extension) =>
Launch(
extension,
SampleImages.Build(extension, 220, 40, 40),
SampleImages.Build(extension, 40, 80, 220),
$"Image {extension}",
"Rows for the format, the size and the bytes, coloured where the two differ",
"Each picture drawn under the rows: red on the left, blue on the right",
"A format this platform's decoder cannot read shows its rows and no picture, which is expected",
"The status line says the two are different files");

[Test]
[Explicit]
public Task Pdf() =>
Launch(
".pdf",
SamplePdf.Build("alpha", "BRAVO", "charlie"),
SamplePdf.Build("alpha", "bravo", "charlie"),
"PDF",
"Text and picture: each page's text above, the page drawn under it",
"Opens at page 2, the one that differs, and the headers say (page 2 of 3)",
"The status line says page 2 differs",
"[ and ] or Prev page and Next page turn both sides together",
"r, or the view button, cycles to Picture only, then Text only, then back",
"In Picture only, Prev change and Next change move between the pages that differ");

[Test]
[Explicit]
[Arguments(".docx", "Hello World!")]
[Arguments(".xlsx", "Dulce")]
[Arguments(".pptx", "Hello, PowerPoint!")]
public Task Office(string extension, string text) =>
Launch(
extension,
Edited(extension, text, $"{text} CHANGED"),
File.ReadAllBytes(Sample(extension)),
$"Office {extension}",
"The status line says reading text, then the line range",
$"The document as Markdown above, one line differing: {text} CHANGED on the left",
"The page drawn under the text",
"r cycles the three views, and the page buttons turn pages where there is more than one");

[Test]
[Explicit]
public Task Svg() =>
Launch(
".svg",
Encoding.UTF8.GetBytes(SvgOf("red")),
Encoding.UTF8.GetBytes(SvgOf("blue")),
"SVG",
"The SVG's source above, the fill line differing",
"A red circle drawn on the left and a blue one on the right, larger than the 48 pixels the file says",
"No page buttons, and the status line says the drawings differ",
"r cycles the three views");

[Test]
[Explicit]
[Arguments(".geojson")]
[Arguments(".topojson")]
[Arguments(".kml")]
[Arguments(".gpx")]
[Arguments(".wkt")]
public Task TextMap(string extension) =>
Launch(
extension,
MapOf(extension, moved: true),
MapOf(extension, moved: false),
$"Map {extension}",
"The file itself as the text, the point's coordinates differing",
"A map drawn under the text: a shaded block, a line across it and a point, the point further east on the left",
"No page buttons, and the status line says the drawings differ",
"r cycles the three views");

[Test]
[Explicit]
[Arguments(".kmz")]
[Arguments(".wkb")]
[Arguments(".fgb")]
[Arguments(".geoparquet")]
public Task BinaryMap(string extension) =>
Launch(
extension,
MapOf(extension, moved: true),
MapOf(extension, moved: false),
$"Map {extension}",
"The status line says reading text, then the line range",
"The text is GeoJSON, indented, the point's coordinates differing",
"A map drawn under the text, the point further east on the left",
"No page buttons, and the status line says the drawings differ");

/// <summary>
/// Through <see cref="EngineRunner"/>, which is what a test run calls, after asking DiffEngine
/// whether it would choose the viewer for this extension at all: a type it would not route
/// there is never seen in it by anyone but this test.
/// </summary>
static async Task Launch(string extension, byte[] received, byte[] verified, string scenario, params string[] checks)
{
await Assert.That(EngineTools.IsDetectedForExtension(EngineTool.DiffEngineViewer, extension)).IsTrue();

var directory = ManualViewer.TempDirectory();
var temp = Path.Combine(directory.FullName, $"Sample.received{extension}");
var target = Path.Combine(directory.FullName, $"Sample.verified{extension}");
await File.WriteAllBytesAsync(temp, received);
await File.WriteAllBytesAsync(target, verified);

ManualViewer.Expect(
scenario,
[$"Headers Sample.received{extension} and Sample.verified{extension}", .. checks]);

var result = await EngineRunner.LaunchAsync(EngineTool.DiffEngineViewer, temp, target);

await Assert.That(result).IsEqualTo(EngineLaunch.StartedNewInstance);
await ManualViewer.WaitForClose();
}

static string Sample(string extension) =>
Path.Combine(AppContext.BaseDirectory, "DocumentSamples", $"sample{extension}");

/// <summary>
/// The committed sample with one string in it changed, wherever in the package that string is
/// kept: the document body, the shared strings or a slide.
/// </summary>
internal static byte[] Edited(string extension, string from, string to)
{
using var stream = new MemoryStream();
stream.Write(File.ReadAllBytes(Sample(extension)));
using (var archive = new ZipArchive(stream, ZipArchiveMode.Update, leaveOpen: true))
{
foreach (var entry in archive.Entries.Where(_ => _.FullName.EndsWith(".xml")).ToList())
{
string xml;
using (var reader = new StreamReader(entry.Open()))
{
xml = reader.ReadToEnd();
}

if (!xml.Contains(from))
{
continue;
}

var name = entry.FullName;
entry.Delete();
using var writer = new StreamWriter(archive.CreateEntry(name).Open());
writer.Write(xml.Replace(from, to));
}
}

return stream.ToArray();
}

internal static string SvgOf(string fill) =>
$"""
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48">
<circle cx="24" cy="24" r="20" fill="{fill}" />
</svg>
""";

/// <summary>
/// Sydney Harbour as a block, a bridge and a point, converted by GeoConvert to whichever format
/// is asked for, so every format holds the same features.
/// </summary>
internal static byte[] MapOf(string extension, bool moved)
{
var longitude = moved ? "151.235" : "151.215";
var geoJson =
$$$"""
{"type":"FeatureCollection","features":[
{"type":"Feature","properties":{"name":"Harbour"},"geometry":{"type":"Polygon","coordinates":[[[151.2,-33.86],[151.24,-33.86],[151.24,-33.84],[151.2,-33.84],[151.2,-33.86]]]}},
{"type":"Feature","properties":{"name":"Bridge"},"geometry":{"type":"LineString","coordinates":[[151.21,-33.85],[151.23,-33.85]]}},
{"type":"Feature","properties":{"name":"Opera House"},"geometry":{"type":"Point","coordinates":[{{{longitude}}},-33.857]}}
]}
""";
var features = GeoConvert.GeoJson.ReadString(geoJson);
var format = GeoConvert.GeoConverter.DetectFormat($"map{extension}");
using var stream = new MemoryStream();
GeoConvert.GeoConverter.Write(features, stream, format);
return stream.ToArray();
}
}
Loading
Loading