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
7 changes: 6 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ permissions:

jobs:
build-test:
runs-on: ubuntu-latest
strategy:
# Let both legs finish so a Windows-only failure doesn't hide the Linux result (or the reverse).
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand Down
4 changes: 2 additions & 2 deletions tests/EDNexus.App.Tests/PluginServiceExitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ public async Task ShutdownForExit_WhenAHandlerNeverReturns_GivesUpAtTheBound_And

Assert.False(finishedInTime);
Assert.False(latches.HandlerReturned.IsSet);
Assert.Null(service.Runtime);
Assert.True(SpinWait.SpinUntil(() => service.Runtime is null, Ceiling));
Assert.Empty(_errors);

latches.HandlerGate.Set();
Expand All @@ -166,7 +166,7 @@ public async Task Detach_WithABound_WhenAHandlerNeverReturns_ReturnsFalse_AndThe

Assert.False(Bounded(() => service.Detach(ShortBound), "Detach(timeout)"));

Assert.Null(service.Runtime);
Assert.True(SpinWait.SpinUntil(() => service.Runtime is null, Ceiling));
latches.HandlerGate.Set();
Assert.True(latches.HandlerReturned.Wait(Ceiling));
}
Expand Down
9 changes: 6 additions & 3 deletions tests/EDNexus.App.Tests/PluginServiceLifecycleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,11 +159,14 @@ public async Task ASecondDetach_Joins_AndReturnsOnceTheFirstHasFinished()
service.Attach(bus, state);
await service.StartAsync();

var first = Task.Run(service.Detach);
// Dedicated threads and a blocking probe: both detaches block, and with the pool starved (seen on
// the Windows runner) a WaitAsync timeout can't fire until the host's 5 s shutdown bound has let
// the first one finish, so the "not done yet" check would pass or fail on scheduling, not on the join.
var first = Task.Factory.StartNew(() => service.Detach(), TaskCreationOptions.LongRunning);
Assert.True(latches.Entered.Wait(Ceiling));
var second = Task.Run(() => service.Detach(Ceiling));
var second = Task.Factory.StartNew(() => service.Detach(Ceiling), TaskCreationOptions.LongRunning);
// Joined to the first, which is parked: it cannot be done yet.
await Assert.ThrowsAsync<TimeoutException>(() => second.WaitAsync(TimeSpan.FromMilliseconds(200)));
Assert.False(SpinWait.SpinUntil(() => second.IsCompleted, TimeSpan.FromMilliseconds(200)));

latches.Gate.Set();

Expand Down
6 changes: 4 additions & 2 deletions tests/EDNexus.App.Tests/PluginServiceReattachTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ public void Shutdown() { {{latches.Set("returned")}} }

Assert.False(Bounded(() => service.Detach(ShortBound), "Detach(timeout)"));

Assert.Null(service.Runtime); // detached, but its teardown is still waiting for the load pass
Assert.True(SpinWait.SpinUntil(() => service.Runtime is null, Ceiling)); // detached, but its teardown is still waiting for the load pass
Assert.False(stuck.Gate.IsSet);
service.Enablement.SetDisabled(stuckId, true);
return (service, stuck);
Expand Down Expand Up @@ -154,7 +154,9 @@ public async Task Attach_WhileAnOlderTeardownIsStillRunning_DoesNotWaitForIt_And
var finishedInTime = Bounded(() => service.ShutdownForExit(ShortBound), "ShutdownForExit");

Assert.False(finishedInTime); // the older teardown is still running
Assert.True(fresh.Returned.IsSet); // but the new plugin's Shutdown ran: it is not left running at exit
// But the new plugin's Shutdown runs: it is not left running at exit. ShutdownForExit bounds the
// whole teardown, so on a slow machine it can return first; wait for it rather than assume it.
Assert.True(fresh.Returned.Wait(Ceiling));
Assert.Null(service.Runtime);
var (bus3, state3) = Engine();
Assert.False(service.Attach(bus3, state3)); // closed for good
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,8 +318,10 @@ private static string Hash(string dir, string? entry = null)
[Fact]
public void FolderHash_IsDeterministic_AndIndependentOfCreationOrder()
{
var a = Tree("order-a", ("b.txt", "2"), ("a.txt", "1"), ("sub/z.txt", "3"), ("sub/y.txt", "4"), ("A.txt", "5"));
var b = Tree("order-b", ("A.txt", "5"), ("sub/y.txt", "4"), ("sub/z.txt", "3"), ("a.txt", "1"), ("b.txt", "2"));
// "C.txt" (not "A.txt"): an upper-case name that ordinal order puts first, without colliding
// with "a.txt" on a case-insensitive file system (Windows, macOS), where the two are one file.
var a = Tree("order-a", ("b.txt", "2"), ("a.txt", "1"), ("sub/z.txt", "3"), ("sub/y.txt", "4"), ("C.txt", "5"));
var b = Tree("order-b", ("C.txt", "5"), ("sub/y.txt", "4"), ("sub/z.txt", "3"), ("a.txt", "1"), ("b.txt", "2"));

Assert.Equal(Hash(a), Hash(a));
Assert.Equal(Hash(a), Hash(b));
Expand Down
4 changes: 4 additions & 0 deletions tests/EDNexus.Tests/Twitch/TwitchStreamCardServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,10 @@ public async Task A_clear_for_a_closed_game_that_fails_is_queued_and_dropped_onc
Assert.Equal(EbsCleanupKind.ClearCard, pending.Kind);
Assert.Equal("ebs-token", pending.Token);

// Stop the service first: its pump keeps retrying the failed clear (up to MaxPublishRetries), and
// each retry re-enqueues the entry while its own clear is in flight, so a count taken during one
// would see it. What is under test here is the queue's retry alone, as after an app restart.
service.Dispose();
client.RespondToClear = () => StreamStatePublishResult.ClearedOk;
Assert.Equal(0, await env.Queue.RetryPendingAsync());
Assert.Empty(env.Store.Load().Twitch.PendingCleanups);
Expand Down
Loading