added tests to project
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
using System.Collections.Concurrent;
|
||||
using CursorLang.Services;
|
||||
using CursorLang.Tests.Infrastructure;
|
||||
|
||||
namespace CursorLang.Tests.Services;
|
||||
|
||||
/// <summary>
|
||||
/// The place of the single instance. The kernel object names in the tests are
|
||||
/// their own: sharing them with a running application is not an option.
|
||||
/// </summary>
|
||||
public sealed class SingleInstanceGateTests
|
||||
{
|
||||
[Fact]
|
||||
public void The_first_run_takes_the_place()
|
||||
{
|
||||
string suffix = UniqueSuffix();
|
||||
SingleInstanceGate gate = Sta.Run(() => new SingleInstanceGate(suffix));
|
||||
|
||||
try
|
||||
{
|
||||
Assert.True(Sta.Run(gate.TryAcquire));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Sta.Run(gate.Dispose);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void The_second_run_does_not_get_the_place()
|
||||
{
|
||||
string suffix = UniqueSuffix();
|
||||
SingleInstanceGate first = Sta.Run(() => new SingleInstanceGate(suffix));
|
||||
|
||||
try
|
||||
{
|
||||
Assert.True(Sta.Run(first.TryAcquire));
|
||||
Assert.False(TryAcquireApart(suffix));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Sta.Run(first.Dispose);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void The_second_run_asks_the_running_one_to_show_its_window()
|
||||
{
|
||||
string suffix = UniqueSuffix();
|
||||
SingleInstanceGate first = Sta.Run(() => new SingleInstanceGate(suffix));
|
||||
|
||||
ConcurrentQueue<EventArgs> requests = new();
|
||||
first.ActivationRequested += (_, e) => requests.Enqueue(e);
|
||||
|
||||
try
|
||||
{
|
||||
Sta.Run(first.TryAcquire);
|
||||
Assert.False(TryAcquireApart(suffix));
|
||||
|
||||
Sta.WaitFor(() => !requests.IsEmpty, "the running instance got the request to show itself");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Sta.Run(first.Dispose);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Without_a_second_run_no_request_arrives()
|
||||
{
|
||||
string suffix = UniqueSuffix();
|
||||
SingleInstanceGate gate = Sta.Run(() => new SingleInstanceGate(suffix));
|
||||
|
||||
ConcurrentQueue<EventArgs> requests = new();
|
||||
gate.ActivationRequested += (_, e) => requests.Enqueue(e);
|
||||
|
||||
try
|
||||
{
|
||||
Sta.Run(gate.TryAcquire);
|
||||
Sta.Pause(TimeSpan.FromMilliseconds(80));
|
||||
|
||||
Assert.Empty(requests);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Sta.Run(gate.Dispose);
|
||||
}
|
||||
}
|
||||
|
||||
// The place is released on exit — otherwise the app would never start again
|
||||
[Fact]
|
||||
public void After_the_exit_the_place_is_free_again()
|
||||
{
|
||||
string suffix = UniqueSuffix();
|
||||
|
||||
SingleInstanceGate first = Sta.Run(() => new SingleInstanceGate(suffix));
|
||||
Assert.True(Sta.Run(first.TryAcquire));
|
||||
Sta.Run(first.Dispose);
|
||||
|
||||
Assert.True(TryAcquireApart(suffix));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void No_requests_arrive_after_the_exit()
|
||||
{
|
||||
string suffix = UniqueSuffix();
|
||||
SingleInstanceGate first = Sta.Run(() => new SingleInstanceGate(suffix));
|
||||
|
||||
ConcurrentQueue<EventArgs> requests = new();
|
||||
first.ActivationRequested += (_, e) => requests.Enqueue(e);
|
||||
|
||||
Sta.Run(first.TryAcquire);
|
||||
Sta.Run(first.Dispose);
|
||||
|
||||
// The place is free, so the new run simply takes it for itself
|
||||
Assert.True(TryAcquireApart(suffix));
|
||||
Sta.Pause(TimeSpan.FromMilliseconds(80));
|
||||
|
||||
Assert.Empty(requests);
|
||||
}
|
||||
|
||||
// The previous instance crashed and did not release the place. It has no
|
||||
// owner any more, which means the place is free
|
||||
[Fact]
|
||||
public void A_place_left_by_a_crash_counts_as_free()
|
||||
{
|
||||
string suffix = UniqueSuffix();
|
||||
|
||||
// A thread that took the mutex and ended without releasing it is exactly
|
||||
// what a crashed application looks like to Windows
|
||||
Sta.RunApart(() =>
|
||||
{
|
||||
var abandoned = new Mutex(initiallyOwned: false, "CursorLang.SingleInstance" + suffix);
|
||||
abandoned.WaitOne(TimeSpan.Zero, exitContext: false);
|
||||
});
|
||||
|
||||
SingleInstanceGate gate = Sta.Run(() => new SingleInstanceGate(suffix));
|
||||
|
||||
try
|
||||
{
|
||||
Assert.True(Sta.Run(gate.TryAcquire));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Sta.Run(gate.Dispose);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Closing_without_taking_the_place_passes_without_consequence()
|
||||
{
|
||||
SingleInstanceGate gate = Sta.Run(() => new SingleInstanceGate(UniqueSuffix()));
|
||||
|
||||
Sta.Run(gate.Dispose);
|
||||
}
|
||||
|
||||
// The application takes the place under its ordinary name
|
||||
[Fact]
|
||||
public void The_ordinary_application_takes_the_place_under_its_own_name()
|
||||
{
|
||||
SingleInstanceGate gate = Sta.Run(() => new SingleInstanceGate());
|
||||
|
||||
// The place may be held by a running application — then it is simply not taken
|
||||
Sta.Run(gate.Dispose);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Closing_twice_passes_without_consequence()
|
||||
{
|
||||
SingleInstanceGate gate = Sta.Run(() => new SingleInstanceGate(UniqueSuffix()));
|
||||
|
||||
Sta.Run(gate.TryAcquire);
|
||||
Sta.Run(gate.Dispose);
|
||||
Sta.Run(gate.Dispose);
|
||||
}
|
||||
|
||||
// Every test gets its own namespace of kernel objects
|
||||
private static string UniqueSuffix() => "." + Guid.NewGuid().ToString("N");
|
||||
|
||||
/// <summary>
|
||||
/// Tries to take the place the way a run started afterwards does it —
|
||||
/// from another thread rather than from the same one.
|
||||
/// </summary>
|
||||
private static bool TryAcquireApart(string suffix)
|
||||
{
|
||||
bool acquired = false;
|
||||
|
||||
Sta.RunApart(() =>
|
||||
{
|
||||
var gate = new SingleInstanceGate(suffix);
|
||||
|
||||
try
|
||||
{
|
||||
acquired = gate.TryAcquire();
|
||||
}
|
||||
finally
|
||||
{
|
||||
gate.Dispose();
|
||||
}
|
||||
});
|
||||
|
||||
return acquired;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user