From 7fad999e474a0575c0463a6b88ed3296b0a20391 Mon Sep 17 00:00:00 2001 From: Emil Koutanov Date: Mon, 29 Jan 2024 09:41:30 +1100 Subject: [PATCH] Troupe tests --- .vscode/settings.json | 5 ++ Actors.Tests/TroupeTest.cs | 87 +++++++++++++++++++++++++++++++++-- Actors/Actor.cs | 2 +- Actors/ISchedulable.cs | 8 ++++ Actors/Troupe.cs | 33 ++++++------- Examples/Throttle/Throttle.cs | 4 +- 6 files changed, 115 insertions(+), 24 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 Actors/ISchedulable.cs diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..ce509bb --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.words": [ + "Schedulable" + ] +} \ No newline at end of file diff --git a/Actors.Tests/TroupeTest.cs b/Actors.Tests/TroupeTest.cs index 75a2ffb..d062a53 100644 --- a/Actors.Tests/TroupeTest.cs +++ b/Actors.Tests/TroupeTest.cs @@ -1,15 +1,92 @@ -using System.Diagnostics; - namespace Actors.Tests; +static class IEnumerableExtensions +{ + public static void AssertNoError(this PausingActor[] actors) + { + foreach (var actor in actors) + { + actor.AssertNoError(); + } + } +} + [TestClass] public class TroupeTest { + [TestMethod] + public void TestMembers() + { + PausingActor[] actors = [new PausingActor(), new PausingActor()]; + var troupe = Troupe.Of(actors); + CollectionAssert.AreEqual(actors, troupe.Members); + } + [TestMethod] public async Task TestDrainAny() { - // PausingActor[] actors = [new PausingActor(), new PausingActor()]; - // IEnumerable x = actors.AsEnumerable(); - // var troupe = Troupe.Of(x); + PausingActor[] actors = [new PausingActor(), new PausingActor()]; + var troupe = Troupe.Of(actors); + + var m0 = new Barrier(); + var m1 = new Barrier(); + + actors[0].Send(m0); + actors[1].Send(m1); + + var drain = troupe.DrainAny(); + + // no drain task is completed if all actors are still in Perform() + await m0.Entered.Task; + await m1.Entered.Task; + Assert.IsTrue(actors[0].Scheduled); + Assert.IsTrue(actors[1].Scheduled); + Assert.IsFalse(drain.IsCompleted); + + // allow one actor to complete and await drainage + m0.Resume.SetResult(); + await drain; + Assert.IsFalse(actors[0].Scheduled); + + // complete the second actor and let it drain + m1.Resume.SetResult(); + await actors[1].Drain(); + Assert.IsFalse(actors[1].Scheduled); + + actors.AssertNoError(); + } + [TestMethod] + public async Task TestDrainAll() + { + PausingActor[] actors = [new PausingActor(), new PausingActor()]; + var troupe = Troupe.Of(actors); + + var m0 = new Barrier(); + var m1 = new Barrier(); + + actors[0].Send(m0); + actors[1].Send(m1); + + var drain = troupe.DrainAll(); + + // no drain task is completed if all actors are still in Perform() + await m0.Entered.Task; + await m1.Entered.Task; + Assert.IsTrue(actors[0].Scheduled); + Assert.IsTrue(actors[1].Scheduled); + Assert.IsFalse(drain.IsCompleted); + + // allow one actor to complete and drain it... troupe drain will still not be completed + m0.Resume.SetResult(); + await actors[0].Drain(); + Assert.IsFalse(actors[0].Scheduled); + Assert.IsFalse(drain.IsCompleted); + + // complete the second actor and await troupe drainage + m1.Resume.SetResult(); + await drain; + Assert.IsFalse(actors[1].Scheduled); + + actors.AssertNoError(); } } \ No newline at end of file diff --git a/Actors/Actor.cs b/Actors/Actor.cs index fb9fa0f..aafc3ce 100644 --- a/Actors/Actor.cs +++ b/Actors/Actor.cs @@ -9,7 +9,7 @@ /// order of their submission. /// /// The message type. -public abstract class Actor +public abstract class Actor : ISchedulable { /// /// A property indicating that an actor is scheduled, meaning that there diff --git a/Actors/ISchedulable.cs b/Actors/ISchedulable.cs new file mode 100644 index 0000000..3c0616a --- /dev/null +++ b/Actors/ISchedulable.cs @@ -0,0 +1,8 @@ +namespace Actors; + +public interface ISchedulable +{ + bool Scheduled { get; } + + Task Drain(); +} \ No newline at end of file diff --git a/Actors/Troupe.cs b/Actors/Troupe.cs index 21e53fc..84beb2b 100644 --- a/Actors/Troupe.cs +++ b/Actors/Troupe.cs @@ -1,31 +1,31 @@ namespace Actors; /// -/// A grouping of actors for composing drain requests. +/// A grouping of schedulable instances for composing drain requests. /// -/// The message type. -public sealed class Troupe +public sealed class Troupe { /// - /// The actors in the troupe. + /// The members of the troupe. /// - public List> Actors + public List Members { get { - return actors; + return members; } } - private readonly List> actors; + private readonly List members; - private Troupe(List> actors) + private Troupe(List members) { - this.actors = actors; + this.members = members; } /// - /// Obtains a task that is completed when all actors in the troupe have been drained. + /// Obtains a task that is completed when all members of the troupe have been drained of + /// their backlogs and unscheduled. /// /// A Task. public Task DrainAll() @@ -34,7 +34,8 @@ public Task DrainAll() } /// - /// Obtains a task that is completed when at least one actor in the troupe has been drained. + /// Obtains a task that is completed when at least one member of the troupe has been drained + /// of its backlog and unscheduled. /// /// A Task. public Task DrainAny() @@ -44,16 +45,16 @@ public Task DrainAny() private Task[] DrainTasks() { - return actors.Select(actor => actor.Drain()).ToArray(); + return members.Select(x => x.Drain()).ToArray(); } - public static Troupe Of(IEnumerable> actors) + public static Troupe Of(IEnumerable members) { - return new Troupe(actors.ToList()); + return new Troupe(members.ToList()); } - public static Troupe OfNullable(IEnumerable?> actors) + public static Troupe OfNullable(IEnumerable members) { - return Troupe.Of(actors.Where(actor => actor is not null).Select(actor => actor!)); + return Of(members.Where(member => member is not null).Select(member => member!)); } } \ No newline at end of file diff --git a/Examples/Throttle/Throttle.cs b/Examples/Throttle/Throttle.cs index f46d0bf..3eb8c06 100644 --- a/Examples/Throttle/Throttle.cs +++ b/Examples/Throttle/Throttle.cs @@ -123,7 +123,7 @@ private async Task DelegateWork(WorkItem workItem) /// private async Task DisposeSomeActorsAsync() { - await Troupe.OfNullable(children).DrainAny(); + await Troupe.OfNullable(children).DrainAny(); foreach (var child in children) { @@ -143,7 +143,7 @@ private async Task DisposeSomeActorsAsync() private async Task DrainAllActorsAsync() { Console.WriteLine("draining all child actors"); - await Troupe.OfNullable(children).DrainAll(); + await Troupe.OfNullable(children).DrainAll(); Console.WriteLine("drained"); } }