From 494d3d9fc14748026461a563519d21afbb0db12d Mon Sep 17 00:00:00 2001 From: Zaafar Date: Thu, 18 Mar 2021 04:04:46 -0400 Subject: [PATCH 1/4] added unit tests for time based Wait. updated some unit tests minor changes --- CoroutineTests/CoroutineTests.csproj | 20 ++ CoroutineTests/TimeBasedCoroutineTests.cs | 369 ++++++++++++++++++++++ 2 files changed, 389 insertions(+) create mode 100644 CoroutineTests/CoroutineTests.csproj create mode 100644 CoroutineTests/TimeBasedCoroutineTests.cs diff --git a/CoroutineTests/CoroutineTests.csproj b/CoroutineTests/CoroutineTests.csproj new file mode 100644 index 0000000..0214345 --- /dev/null +++ b/CoroutineTests/CoroutineTests.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + + + diff --git a/CoroutineTests/TimeBasedCoroutineTests.cs b/CoroutineTests/TimeBasedCoroutineTests.cs new file mode 100644 index 0000000..4b8bd74 --- /dev/null +++ b/CoroutineTests/TimeBasedCoroutineTests.cs @@ -0,0 +1,369 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace CoroutineTests +{ + using Coroutine; + using System.Collections.Generic; + using System.Threading; + + [TestClass] + public class TimeBasedCoroutineTests + { + [TestMethod] + public void TestTimerBasedCorotuine() + { + int counter = 0; + IEnumerator OnTimeTickCodeExecuted() + { + counter++; + yield return new Wait(0.1d); + counter++; + } + + var cr = CoroutineHandler.Start(OnTimeTickCodeExecuted()); + Assert.AreEqual(1, counter, "instruction before yield is not executed."); + Assert.AreEqual(string.Empty, cr.Name, "Incorrect default name found"); + Assert.AreEqual(0, cr.Priority, "Default priority is not minimum"); + for (int i = 0; i < 5; i++) + this.SimulateTime(1); + Assert.AreEqual(2, counter, "instruction after yield is not executed."); + Assert.AreEqual(true, cr.IsFinished, "Incorrect IsFinished value."); + Assert.AreEqual(false, cr.WasCanceled, "Incorrect IsCanceled value."); + Assert.AreEqual(cr.MoveNextCount, 2, "Incorrect MoveNextCount value."); + } + + [TestMethod] + public void TestCorotuineReturningWeirdYields() + { + int counter = 0; + IEnumerator OnTimeTickNeverReturnYield() + { + counter++; // 1 + if (counter == 100) // condition that's expected to be false. + { + yield return new Wait(0.1d); + } + + counter++; // 2 + } + + IEnumerator OnTimeTickYieldBreak() + { + counter++; // 3 + yield break; + counter++; // still 3 + } + + var cr = new ActiveCoroutine[2]; + cr[0] = CoroutineHandler.Start(OnTimeTickNeverReturnYield()); + cr[1] = CoroutineHandler.Start(OnTimeTickYieldBreak()); + for (int i = 0; i < 5; i++) + this.SimulateTime(1); + + Assert.AreEqual(3, counter, $"Incorrect counter value."); + for (int i = 0; i < cr.Length; i++) + { + Assert.AreEqual(true, cr[i].IsFinished, $"Incorrect IsFinished value on index {i}."); + Assert.AreEqual(false, cr[i].WasCanceled, $"Incorrect IsCanceled value on index {i}"); + Assert.AreEqual(1, cr[i].MoveNextCount, $"Incorrect MoveNextCount value on index {i}"); + } + } + + [TestMethod] + public void TestCorotuineReturningDefaultYield() + { + int counter = 0; + IEnumerator OnTimeTickYieldDefault() + { + counter++; // 1 + yield return default; + counter++; // 2 + } + + var cr = CoroutineHandler.Start(OnTimeTickYieldDefault()); + for (int i = 0; i < 5; i++) + this.SimulateTime(1); + + Assert.AreEqual(2, counter, $"Incorrect counter value."); + Assert.AreEqual(true, cr.IsFinished, $"Incorrect IsFinished value."); + Assert.AreEqual(false, cr.WasCanceled, $"Incorrect IsCanceled value."); + Assert.AreEqual(2, cr.MoveNextCount, $"Incorrect MoveNextCount value."); + } + + [TestMethod] + public void TestInfiniteCorotuineNeverFinishesUnlessCanceled() + { + int counter = 0; + IEnumerator OnTimerTickInfinite() + { + while (true) + { + counter++; + yield return new Wait(1); + } + } + + void setCounterToUnreachableValue(ActiveCoroutine coroutine) + { + counter = -100; + } + + var cr = CoroutineHandler.Start(OnTimerTickInfinite()); + cr.OnFinished += setCounterToUnreachableValue; + for (int i = 0; i < 50; i++) + this.SimulateTime(1); + + Assert.AreEqual(51, counter, $"Incorrect counter value."); + Assert.AreEqual(false, cr.IsFinished, $"Incorrect IsFinished value."); + Assert.AreEqual(false, cr.WasCanceled, $"Incorrect IsCanceled value."); + Assert.AreEqual(51, cr.MoveNextCount, $"Incorrect MoveNextCount value."); + + cr.Cancel(); + Assert.AreEqual(true, cr.WasCanceled, $"Incorrect IsCanceled value after canceling."); + Assert.AreEqual(-100, counter, $"OnFinished event not triggered when canceled."); + Assert.AreEqual(51, cr.MoveNextCount, $"Incorrect MoveNextCount value."); + Assert.AreEqual(true, cr.IsFinished, $"Incorrect IsFinished value."); + } + + [TestMethod] + public void TestOnFinishedEventExecuted() + { + int counter = 0; + IEnumerator OnTimeTick() + { + counter++; + yield return new Wait(0.1d); + } + + void setCounterToUnreachableValue(ActiveCoroutine coroutine) + { + counter = -100; + } + + var cr = CoroutineHandler.Start(OnTimeTick()); + cr.OnFinished += setCounterToUnreachableValue; + this.SimulateTime(50); + Assert.AreEqual(-100, counter, $"Incorrect counter value."); + } + + [TestMethod] + public void TestNestedCorotuine() + { + int counterAlwaysRunning = 0; + IEnumerator AlwaysRunning() + { + while (true) + { + yield return new Wait(1); + counterAlwaysRunning++; + } + } + + int counterChild = 0; + IEnumerator Child() + { + yield return new Wait(1); + counterChild++; + } + + int counterParent = 0; + IEnumerator Parent() + { + yield return new Wait(1); + counterParent++; + // OnFinish I will start child. + } + + int counterGrandParent = 0; + IEnumerator GrandParent() + { + yield return new Wait(1); + counterGrandParent++; + + // Nested corotuine starting. + var p = CoroutineHandler.Start(Parent()); + + // Nested corotuine starting in OnFinished. + p.OnFinished += (ActiveCoroutine ac) => { CoroutineHandler.Start(Child()); }; + } + + CoroutineHandler.Start(AlwaysRunning()); + CoroutineHandler.Start(GrandParent()); + Assert.AreEqual(0, counterAlwaysRunning, "Always running counter is invalid at time 0."); + Assert.AreEqual(0, counterGrandParent, "Grand Parent counter is invalid at time 0."); + Assert.AreEqual(0, counterParent, "Parent counter is invalid at time 0."); + Assert.AreEqual(0, counterChild, "Child counter is invalid at time 0."); + this.SimulateTime(1); + Assert.AreEqual(1, counterAlwaysRunning, "Always running counter is invalid at time 1."); + Assert.AreEqual(1, counterGrandParent, "Grand Parent counter is invalid at time 1."); + Assert.AreEqual(0, counterParent, "Parent counter is invalid at time 1."); + Assert.AreEqual(0, counterChild, "Child counter is invalid at time 1."); + this.SimulateTime(1); + Assert.AreEqual(2, counterAlwaysRunning, "Always running counter is invalid at time 2."); + Assert.AreEqual(1, counterGrandParent, "Grand Parent counter is invalid at time 2."); + Assert.AreEqual(1, counterParent, "Parent counter is invalid at time 2."); + Assert.AreEqual(0, counterChild, "Child counter is invalid at time 2."); + this.SimulateTime(1); + Assert.AreEqual(3, counterAlwaysRunning, "Always running counter is invalid at time 3."); + Assert.AreEqual(1, counterGrandParent, "Grand Parent counter is invalid at time 3."); + Assert.AreEqual(1, counterParent, "Parent counter is invalid at time 3."); + Assert.AreEqual(1, counterChild, "Child counter is invalid at time 3."); + this.SimulateTime(1); + Assert.AreEqual(4, counterAlwaysRunning, "Always running counter is invalid at time 4."); + Assert.AreEqual(1, counterGrandParent, "Grand Parent counter is invalid at time 4."); + Assert.AreEqual(1, counterParent, "Parent counter is invalid at time 4."); + Assert.AreEqual(1, counterChild, "Child counter is invalid at time 4."); + } + + [TestMethod] + public void TestPriority() + { + int counterShouldExecuteBefore0 = 0; + IEnumerator ShouldExecuteBefore0() + { + while (true) + { + yield return new Wait(1); + counterShouldExecuteBefore0++; + } + } + + int counterShouldExecuteBefore1 = 0; + IEnumerator ShouldExecuteBefore1() + { + while (true) + { + yield return new Wait(1); + counterShouldExecuteBefore1++; + } + } + + int counterShouldExecuteAfter = 0; + IEnumerator ShouldExecuteAfter() + { + while (true) + { + yield return new Wait(1); + if (counterShouldExecuteBefore0 == 1 && + counterShouldExecuteBefore1 == 1) + { + counterShouldExecuteAfter++; + } + } + } + + int counterShouldExecuteFinally = 0; + IEnumerator ShouldExecuteFinally() + { + while (true) + { + yield return new Wait(1); + if (counterShouldExecuteAfter > 0) + { + counterShouldExecuteFinally++; + } + } + } + + int highPriority = int.MaxValue; + CoroutineHandler.Start(ShouldExecuteBefore1(), priority: highPriority); + CoroutineHandler.Start(ShouldExecuteAfter()); + CoroutineHandler.Start(ShouldExecuteBefore0(), priority: highPriority); + CoroutineHandler.Start(ShouldExecuteFinally(), priority: -1); + this.SimulateTime(10); + Assert.AreEqual(1, counterShouldExecuteAfter, $"ShouldExecuteAfter counter {counterShouldExecuteAfter} is invalid."); + Assert.AreEqual(1, counterShouldExecuteFinally, $"ShouldExecuteFinally counter {counterShouldExecuteFinally} is invalid."); + } + + [TestMethod] + public void TestTimeBasedCorotuineIsAccurate() + { + int counter0 = 0; + IEnumerator IncrementCounter0Ever10Seconds() + { + while (true) + { + yield return new Wait(10); + counter0++; + } + } + + int counter1 = 0; + IEnumerator IncrementCounter1Every5Seconds() + { + while (true) + { + yield return new Wait(5); + counter1++; + } + } + + CoroutineHandler.Start(IncrementCounter0Ever10Seconds()); + CoroutineHandler.Start(IncrementCounter1Every5Seconds()); + this.SimulateTime(3); + Assert.AreEqual(0, counter0, $"Incorrect counter0 value after 3 seconds."); + Assert.AreEqual(0, counter1, $"Incorrect counter1 value after 3 seconds."); + this.SimulateTime(3); + Assert.AreEqual(0, counter0, $"Incorrect counter0 value after 6 seconds."); + Assert.AreEqual(1, counter1, $"Incorrect counter1 value after 6 seconds."); + + // it's 5 over here because IncrementCounter1Every5Seconds + // increments 5 seconds after last yield. not 5 seconds since start. + // So the when we send 3 seconds in the last SimulateTime, + // the 3rd second was technically ignored. + this.SimulateTime(5); + Assert.AreEqual(1, counter0, $"Incorrect counter0 value after 10 seconds."); + Assert.AreEqual(2, counter1, $"Incorrect counter1 value after next 5 seconds."); + } + + [TestMethod] + public void InvokeLaterAndNameTest() + { + int counter = 0; + var cr = CoroutineHandler.InvokeLater(new Wait(10), () => { + counter++; + }, "Bird"); + + this.SimulateTime(5); + Assert.AreEqual(0, counter, $"Incorrect counter value after 5 seconds."); + this.SimulateTime(5); + Assert.AreEqual(1, counter, $"Incorrect counter value after 10 seconds."); + Assert.AreEqual(true, cr.IsFinished, "Incorrect IsFinished value."); + Assert.AreEqual(false, cr.WasCanceled, "Incorrect IsCanceled value."); + Assert.AreEqual(cr.MoveNextCount, 2, "Incorrect MoveNextCount value."); + Assert.AreEqual(cr.Name, "Bird", "Incorrect name of the coroutine."); + } + + [TestMethod] + public void CorotuineStatsAre95PercentAccurate() + { + IEnumerator CorotuineTakesMax500MS() + { + Thread.Sleep(200); + yield return new Wait(10); + Thread.Sleep(500); + } + + var cr = CoroutineHandler.Start(CorotuineTakesMax500MS()); + for (int i = 0; i < 5; i++) + this.SimulateTime(50); + + int expected1 = 350; + float errorbar1 = (5 / 100f * expected1); + bool gTA = cr.AverageMoveNextTime.Milliseconds > (expected1 - errorbar1); // 95% accuracy. + bool lTB = cr.AverageMoveNextTime.Milliseconds < (expected1 + errorbar1); // 95% accuracy. + Assert.IsTrue(gTA && lTB, $"Average Move Next Time {cr.AverageMoveNextTime.Milliseconds} is invalid."); + + int expected2 = 500; + float errorbar2 = (5 / 100f * expected2); + bool gTC = cr.MaxMoveNextTime.Milliseconds > (expected2 - errorbar2); // 95% accuracy. + bool lTD = cr.MaxMoveNextTime.Milliseconds < (expected2 + errorbar2); // 95% accuracy. + Assert.IsTrue(gTC && lTD, $"Maximum Move Next Time {cr.MaxMoveNextTime.Milliseconds} is invalid."); + } + + private void SimulateTime(double totalSeconds) + { + CoroutineHandler.Tick(totalSeconds); + } + } +} From 71f9573c9c8094aa071963c5d0b2831aa0691227 Mon Sep 17 00:00:00 2001 From: Zaafar Date: Thu, 18 Mar 2021 04:34:03 -0400 Subject: [PATCH 2/4] manually cleaned up the Coroutine.sln file --- Coroutine.sln | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Coroutine.sln b/Coroutine.sln index 1b25596..b8cfe5f 100644 --- a/Coroutine.sln +++ b/Coroutine.sln @@ -4,6 +4,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coroutine", "Coroutine\Coro EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test\Test.csproj", "{8BE6B559-927D-47A6-8253-D7D809D337AF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoroutineTests", "CoroutineTests\CoroutineTests.csproj", "{8E110BC2-38FD-404A-B5BD-02C771B0D1D5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -18,5 +20,9 @@ Global {8BE6B559-927D-47A6-8253-D7D809D337AF}.Debug|Any CPU.Build.0 = Debug|Any CPU {8BE6B559-927D-47A6-8253-D7D809D337AF}.Release|Any CPU.ActiveCfg = Release|Any CPU {8BE6B559-927D-47A6-8253-D7D809D337AF}.Release|Any CPU.Build.0 = Release|Any CPU + {8E110BC2-38FD-404A-B5BD-02C771B0D1D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8E110BC2-38FD-404A-B5BD-02C771B0D1D5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8E110BC2-38FD-404A-B5BD-02C771B0D1D5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8E110BC2-38FD-404A-B5BD-02C771B0D1D5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal From bda1e21ea39948e72bd71f16ce80227ffc3e7111 Mon Sep 17 00:00:00 2001 From: Zaafar Date: Thu, 18 Mar 2021 09:20:36 -0400 Subject: [PATCH 3/4] Minor fixup --- CoroutineTests/TimeBasedCoroutineTests.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/CoroutineTests/TimeBasedCoroutineTests.cs b/CoroutineTests/TimeBasedCoroutineTests.cs index 4b8bd74..7390e0e 100644 --- a/CoroutineTests/TimeBasedCoroutineTests.cs +++ b/CoroutineTests/TimeBasedCoroutineTests.cs @@ -10,7 +10,7 @@ namespace CoroutineTests public class TimeBasedCoroutineTests { [TestMethod] - public void TestTimerBasedCorotuine() + public void TestTimerBasedCoroutine() { int counter = 0; IEnumerator OnTimeTickCodeExecuted() @@ -33,7 +33,7 @@ namespace CoroutineTests } [TestMethod] - public void TestCorotuineReturningWeirdYields() + public void TestCoroutineReturningWeirdYields() { int counter = 0; IEnumerator OnTimeTickNeverReturnYield() @@ -70,7 +70,7 @@ namespace CoroutineTests } [TestMethod] - public void TestCorotuineReturningDefaultYield() + public void TestCoroutineReturningDefaultYield() { int counter = 0; IEnumerator OnTimeTickYieldDefault() @@ -91,7 +91,7 @@ namespace CoroutineTests } [TestMethod] - public void TestInfiniteCorotuineNeverFinishesUnlessCanceled() + public void TestInfiniteCoroutineNeverFinishesUnlessCanceled() { int counter = 0; IEnumerator OnTimerTickInfinite() @@ -147,7 +147,7 @@ namespace CoroutineTests } [TestMethod] - public void TestNestedCorotuine() + public void TestNestedCoroutine() { int counterAlwaysRunning = 0; IEnumerator AlwaysRunning() @@ -276,7 +276,7 @@ namespace CoroutineTests } [TestMethod] - public void TestTimeBasedCorotuineIsAccurate() + public void TestTimeBasedCoroutineIsAccurate() { int counter0 = 0; IEnumerator IncrementCounter0Ever10Seconds() @@ -335,16 +335,16 @@ namespace CoroutineTests } [TestMethod] - public void CorotuineStatsAre95PercentAccurate() + public void CoroutineStatsAre95PercentAccurate() { - IEnumerator CorotuineTakesMax500MS() + IEnumerator CoroutineTakesMax500MS() { Thread.Sleep(200); yield return new Wait(10); Thread.Sleep(500); } - var cr = CoroutineHandler.Start(CorotuineTakesMax500MS()); + var cr = CoroutineHandler.Start(CoroutineTakesMax500MS()); for (int i = 0; i < 5; i++) this.SimulateTime(50); From 1c97d17ef70c8f2a0539f51c4f3822a253b0d5ed Mon Sep 17 00:00:00 2001 From: Zaafar Date: Thu, 18 Mar 2021 09:37:15 -0400 Subject: [PATCH 4/4] Added event based test. --- CoroutineTests/EventBasedCoroutineTests.cs | 34 ++++++++++++++++++++++ CoroutineTests/TimeBasedCoroutineTests.cs | 8 ++--- 2 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 CoroutineTests/EventBasedCoroutineTests.cs diff --git a/CoroutineTests/EventBasedCoroutineTests.cs b/CoroutineTests/EventBasedCoroutineTests.cs new file mode 100644 index 0000000..853b5f8 --- /dev/null +++ b/CoroutineTests/EventBasedCoroutineTests.cs @@ -0,0 +1,34 @@ +using Coroutine; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; + +namespace CoroutineTests +{ + [TestClass] + public class EventBasedCoroutineTests + { + [TestMethod] + public void TestEventBasedCoroutine() + { + int counter = 0; + var myEvent = new Event(); + IEnumerator OnEventTriggered() + { + counter++; + yield return new Wait(myEvent); + counter++; + } + + var cr = CoroutineHandler.Start(OnEventTriggered()); + Assert.AreEqual(1, counter, "instruction before yield is not executed."); + CoroutineHandler.RaiseEvent(myEvent); + Assert.AreEqual(2, counter, "instruction after yield is not executed."); + CoroutineHandler.RaiseEvent(myEvent); + Assert.AreEqual(2, counter, "instruction after yield is not executed."); + + Assert.AreEqual(true, cr.IsFinished, "Incorrect IsFinished value."); + Assert.AreEqual(false, cr.WasCanceled, "Incorrect IsCanceled value."); + Assert.AreEqual(cr.MoveNextCount, 2, "Incorrect MoveNextCount value."); + } + } +} diff --git a/CoroutineTests/TimeBasedCoroutineTests.cs b/CoroutineTests/TimeBasedCoroutineTests.cs index 7390e0e..615c322 100644 --- a/CoroutineTests/TimeBasedCoroutineTests.cs +++ b/CoroutineTests/TimeBasedCoroutineTests.cs @@ -1,11 +1,11 @@ +using System.Collections.Generic; +using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Coroutine; + namespace CoroutineTests { - using Coroutine; - using System.Collections.Generic; - using System.Threading; - [TestClass] public class TimeBasedCoroutineTests {