mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-02 18:21:36 -05:00
Ten desktop test classes drew the same squares and the same 4 x 2 rectangle in private helpers, in two equivalent styles. They now use TestPrograms.Square, ClockwiseSquare and Rectangle. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
200 lines
6.4 KiB
C#
200 lines
6.4 KiB
C#
using System.Diagnostics;
|
|
using System.Windows.Forms;
|
|
using OpenNest.Controls;
|
|
using OpenNest.Forms;
|
|
using OpenNest.Geometry;
|
|
|
|
namespace OpenNest.WinForms.Tests.Forms;
|
|
|
|
public class OperationProgressFormTests
|
|
{
|
|
private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(15);
|
|
|
|
[Fact]
|
|
public void CancelWaitsForTheWorkerBeforeClosing() => StaTestThread.Run(() =>
|
|
{
|
|
using var worker = new HeldWorker();
|
|
using var form = new OperationProgressForm("Expand Spacing", "Working", worker.Task, worker.Source);
|
|
try
|
|
{
|
|
form.Show();
|
|
worker.WaitForEntry();
|
|
|
|
// Escape clicks the same CancelButton.
|
|
Assert.NotNull(form.CancelButton);
|
|
form.CancelButton.PerformClick();
|
|
|
|
Assert.True(form.CancelRequested);
|
|
Assert.False(form.CancelEnabled);
|
|
Assert.StartsWith("Canceling", form.StatusText);
|
|
PumpFor(TimeSpan.FromMilliseconds(200));
|
|
Assert.False(form.IsDisposed, "Cancel closed the window while the worker was still running.");
|
|
Assert.True(form.Visible);
|
|
|
|
worker.Release();
|
|
PumpUntil(() => form.IsDisposed, "The window did not close after the worker stopped.");
|
|
Assert.True(Completed(worker.Task), "The worker did not observe cancellation.");
|
|
}
|
|
finally
|
|
{
|
|
worker.Release();
|
|
}
|
|
}, TimeSpan.FromMinutes(1), "The STA test did not complete.");
|
|
|
|
[Fact]
|
|
public void ClosingWhileRunningRequestsCancellationAndWaits() => StaTestThread.Run(() =>
|
|
{
|
|
using var worker = new HeldWorker();
|
|
using var form = new OperationProgressForm("Expand Spacing", "Working", worker.Task, worker.Source);
|
|
try
|
|
{
|
|
form.Show();
|
|
worker.WaitForEntry();
|
|
|
|
form.Close();
|
|
|
|
Assert.True(form.CancelRequested);
|
|
Assert.False(form.IsDisposed, "Closing did not wait for the worker.");
|
|
Assert.True(form.Visible);
|
|
|
|
worker.Release();
|
|
PumpUntil(() => form.IsDisposed, "The window did not close after the worker stopped.");
|
|
}
|
|
finally
|
|
{
|
|
worker.Release();
|
|
}
|
|
}, TimeSpan.FromMinutes(1), "The STA test did not complete.");
|
|
|
|
[Fact]
|
|
public void RunReturnsAQuickResultAndRethrowsItsFailure() => StaTestThread.Run(() =>
|
|
{
|
|
// A long show delay keeps both runs below it, so no window can open.
|
|
var delay = TimeSpan.FromSeconds(30);
|
|
|
|
Assert.Equal(42, OperationProgressForm.Run(null, "Title", "Working", _ => 42, delay));
|
|
|
|
var error = Assert.Throws<InvalidOperationException>(() =>
|
|
OperationProgressForm.Run<int>(
|
|
null,
|
|
"Title",
|
|
"Working",
|
|
_ => throw new InvalidOperationException("search failed"),
|
|
delay
|
|
)
|
|
);
|
|
Assert.Equal("search failed", error.Message);
|
|
}, TimeSpan.FromMinutes(1), "The STA test did not complete.");
|
|
|
|
[Fact]
|
|
public void CanceledExpandMovesNoPartAndReportsIt() => StaTestThread.Run(() =>
|
|
{
|
|
using var view = TwoPartView();
|
|
var before = view.Plate.Parts.Select(p => p.Location).ToArray();
|
|
|
|
var handled = view.ExpandSelected(work =>
|
|
{
|
|
using var canceled = new CancellationTokenSource();
|
|
canceled.Cancel();
|
|
return work(canceled.Token);
|
|
});
|
|
|
|
Assert.True(handled);
|
|
Assert.Equal(before, view.Plate.Parts.Select(p => p.Location).ToArray());
|
|
Assert.Contains("canceled", view.Status);
|
|
}, TimeSpan.FromMinutes(1), "The STA test did not complete.");
|
|
|
|
[Fact]
|
|
public void ExpandRunsItsSearchThroughTheSuppliedRunner() => StaTestThread.Run(() =>
|
|
{
|
|
using var view = TwoPartView();
|
|
var before = view.Plate.Parts.Select(p => p.Location).ToArray();
|
|
var runs = 0;
|
|
|
|
var handled = view.ExpandSelected(work =>
|
|
{
|
|
runs++;
|
|
return work(CancellationToken.None);
|
|
});
|
|
|
|
Assert.True(handled);
|
|
Assert.Equal(1, runs);
|
|
Assert.NotEqual(before, view.Plate.Parts.Select(p => p.Location).ToArray());
|
|
Assert.StartsWith("Expanded spacing to", view.Status);
|
|
}, TimeSpan.FromMinutes(1), "The STA test did not complete.");
|
|
|
|
private static PlateView TwoPartView()
|
|
{
|
|
var plate = new Plate(100, 100);
|
|
plate.Parts.Add(new Part(
|
|
new Drawing("left", TestPrograms.ClockwiseSquare(10)), new Vector(1, 1)));
|
|
plate.Parts.Add(new Part(
|
|
new Drawing("right", TestPrograms.ClockwiseSquare(10)), new Vector(12, 1)));
|
|
var view = new PlateView { Plate = plate };
|
|
view.SelectAll();
|
|
return view;
|
|
}
|
|
|
|
private static void PumpFor(TimeSpan duration)
|
|
{
|
|
var watch = Stopwatch.StartNew();
|
|
while (watch.Elapsed < duration)
|
|
{
|
|
Application.DoEvents();
|
|
Thread.Sleep(10);
|
|
}
|
|
}
|
|
|
|
private static void PumpUntil(Func<bool> condition, string message)
|
|
{
|
|
var watch = Stopwatch.StartNew();
|
|
while (!condition())
|
|
{
|
|
Assert.True(watch.Elapsed < Timeout, message);
|
|
Application.DoEvents();
|
|
Thread.Sleep(10);
|
|
}
|
|
}
|
|
|
|
private static bool Completed(Task<bool> task)
|
|
{
|
|
Assert.True(task.Wait(Timeout), "The worker did not finish.");
|
|
return task.Result;
|
|
}
|
|
|
|
/// <summary>A worker held until released; its result says whether it saw cancellation.</summary>
|
|
private sealed class HeldWorker : IDisposable
|
|
{
|
|
private readonly ManualResetEventSlim entered = new();
|
|
private readonly ManualResetEventSlim release = new();
|
|
|
|
public HeldWorker()
|
|
{
|
|
var token = Source.Token;
|
|
Task = System.Threading.Tasks.Task.Run(() =>
|
|
{
|
|
entered.Set();
|
|
release.Wait(Timeout);
|
|
return token.IsCancellationRequested;
|
|
});
|
|
}
|
|
|
|
public CancellationTokenSource Source { get; } = new();
|
|
|
|
public Task<bool> Task { get; }
|
|
|
|
public void WaitForEntry() => Assert.True(entered.Wait(Timeout), "Worker did not start.");
|
|
|
|
public void Release() => release.Set();
|
|
|
|
public void Dispose()
|
|
{
|
|
release.Set();
|
|
Task.Wait(Timeout);
|
|
Source.Dispose();
|
|
entered.Dispose();
|
|
release.Dispose();
|
|
}
|
|
}
|
|
}
|