Files
cursor-lang/CursorLang.Tests/Services/SingleInstanceGateTests.cs
T
2026-08-09 18:31:43 +05:00

205 lines
5.6 KiB
C#

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;
}
}