added mainloop tests and updated others

This commit is contained in:
Charlie Kindel
2020-06-11 14:08:43 -06:00
committed by BDisp
parent d71987b89b
commit 6349685cf6
8 changed files with 483 additions and 113 deletions

View File

@@ -14,7 +14,7 @@ namespace Terminal.Gui {
/// <summary>
/// Implements a mock ConsoleDriver for unit testing
/// </summary>
public class FakeDriver : ConsoleDriver, IMainLoopDriver {
public class FakeDriver : ConsoleDriver {
int cols, rows;
/// <summary>
///
@@ -421,7 +421,7 @@ namespace Terminal.Gui {
public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
{
// Note: Net doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called
(mainLoop.Driver as FakeDriver).WindowsKeyPressed = delegate (ConsoleKeyInfo consoleKey) {
(mainLoop.Driver as NetMainLoop).KeyPressed = delegate (ConsoleKeyInfo consoleKey) {
var map = MapKey (consoleKey);
if (map == (Key)0xffffffff)
return;
@@ -463,65 +463,5 @@ namespace Terminal.Gui {
public override void UncookMouse ()
{
}
AutoResetEvent keyReady = new AutoResetEvent (false);
AutoResetEvent waitForProbe = new AutoResetEvent (false);
ConsoleKeyInfo? windowsKeyResult = null;
/// <summary>
///
/// </summary>
public Action<ConsoleKeyInfo> WindowsKeyPressed;
MainLoop mainLoop;
void WindowsKeyReader ()
{
while (true) {
waitForProbe.WaitOne ();
windowsKeyResult = FakeConsole.ReadKey (true);
keyReady.Set ();
}
}
void IMainLoopDriver.Setup (MainLoop mainLoop)
{
this.mainLoop = mainLoop;
Thread readThread = new Thread (WindowsKeyReader);
readThread.Start ();
}
void IMainLoopDriver.Wakeup ()
{
}
bool IMainLoopDriver.EventsPending (bool wait)
{
long now = DateTime.UtcNow.Ticks;
int waitTimeout;
if (mainLoop.timeouts.Count > 0) {
waitTimeout = (int)((mainLoop.timeouts.Keys [0] - now) / TimeSpan.TicksPerMillisecond);
if (waitTimeout < 0)
return true;
} else
waitTimeout = -1;
if (!wait)
waitTimeout = 0;
windowsKeyResult = null;
waitForProbe.Set ();
keyReady.WaitOne (waitTimeout);
return windowsKeyResult.HasValue;
}
void IMainLoopDriver.MainIteration ()
{
if (windowsKeyResult.HasValue) {
if (WindowsKeyPressed != null)
WindowsKeyPressed (windowsKeyResult.Value);
windowsKeyResult = null;
}
}
}
}

View File

@@ -354,7 +354,7 @@ namespace Terminal.Gui {
public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
{
// Note: Net doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called
(mainLoop.Driver as NetMainLoop).WindowsKeyPressed = delegate (ConsoleKeyInfo consoleKey) {
(mainLoop.Driver as NetMainLoop).KeyPressed = delegate (ConsoleKeyInfo consoleKey) {
var map = MapKey (consoleKey);
if (map == (Key)0xffffffff)
return;
@@ -393,22 +393,41 @@ namespace Terminal.Gui {
/// be used on Windows and Unix, it is cross platform but lacks things like
/// file descriptor monitoring.
/// </summary>
class NetMainLoop : IMainLoopDriver {
/// <remarks>
/// This implementation is used for both NetDriver and FakeDriver.
/// </remarks>
public class NetMainLoop : IMainLoopDriver {
AutoResetEvent keyReady = new AutoResetEvent (false);
AutoResetEvent waitForProbe = new AutoResetEvent (false);
ConsoleKeyInfo? windowsKeyResult = null;
public Action<ConsoleKeyInfo> WindowsKeyPressed;
ConsoleKeyInfo? keyResult = null;
MainLoop mainLoop;
Func<ConsoleKeyInfo> consoleKeyReaderFn = null;
public NetMainLoop ()
/// <summary>
/// Invoked when a Key is pressed.
/// </summary>
public Action<ConsoleKeyInfo> KeyPressed;
/// <summary>
/// Initializes the class.
/// </summary>
/// <remarks>
/// Passing a consoleKeyReaderfn is provided to support unit test sceanrios.
/// </remarks>
/// <param name="consoleKeyReaderFn">The method to be called to get a key from the console.</param>
public NetMainLoop (Func<ConsoleKeyInfo> consoleKeyReaderFn = null)
{
if (consoleKeyReaderFn == null) {
throw new ArgumentNullException ("key reader function must be provided.");
}
this.consoleKeyReaderFn = consoleKeyReaderFn;
}
void WindowsKeyReader ()
{
while (true) {
waitForProbe.WaitOne ();
windowsKeyResult = Console.ReadKey (true);
keyResult = consoleKeyReaderFn();
keyReady.Set ();
}
}
@@ -439,18 +458,17 @@ namespace Terminal.Gui {
if (!wait)
waitTimeout = 0;
windowsKeyResult = null;
keyResult = null;
waitForProbe.Set ();
keyReady.WaitOne (waitTimeout);
return windowsKeyResult.HasValue;
return keyResult.HasValue;
}
void IMainLoopDriver.MainIteration ()
{
if (windowsKeyResult.HasValue) {
if (WindowsKeyPressed != null)
WindowsKeyPressed (windowsKeyResult.Value);
windowsKeyResult = null;
if (keyResult.HasValue) {
KeyPressed?.Invoke (keyResult.Value);
keyResult = null;
}
}
}

View File

@@ -157,30 +157,32 @@ namespace Terminal.Gui {
/// Creates a <see cref="Toplevel"/> and assigns it to <see cref="Top"/> and <see cref="CurrentView"/>
/// </para>
/// </remarks>
public static void Init (ConsoleDriver driver = null) => Init (() => Toplevel.Create (), driver);
public static void Init (ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null) => Init (() => Toplevel.Create (), driver, mainLoopDriver);
internal static bool _initialized = false;
/// <summary>
/// Initializes the Terminal.Gui application
/// </summary>
static void Init (Func<Toplevel> topLevelFactory, ConsoleDriver driver = null)
static void Init (Func<Toplevel> topLevelFactory, ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null)
{
if (_initialized) return;
// This supports Unit Tests and the passing of a mock driver/loopdriver
if (driver != null) {
if (mainLoopDriver == null) {
throw new ArgumentNullException ("mainLoopDriver cannot be null if driver is provided.");
}
Driver = driver;
Driver.Init (TerminalResized);
MainLoop = new MainLoop ((IMainLoopDriver)driver);
MainLoop = new MainLoop (mainLoopDriver);
SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext (MainLoop));
}
if (Driver == null) {
var p = Environment.OSVersion.Platform;
IMainLoopDriver mainLoopDriver;
if (UseSystemConsole) {
mainLoopDriver = new NetMainLoop ();
mainLoopDriver = new NetMainLoop (() => Console.ReadKey (true));
Driver = new NetDriver ();
} else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) {
var windowsDriver = new WindowsDriver ();
@@ -487,7 +489,7 @@ namespace Terminal.Gui {
}
/// <summary>
/// Shutdown an application initialized with <see cref="Init(ConsoleDriver)"/>
/// Shutdown an application initialized with <see cref="Init(ConsoleDriver, IMainLoopDriver)"/>
/// </summary>
/// /// <param name="closeDriver"><c>true</c>Closes the application.<c>false</c>Closes toplevels only.</param>
public static void Shutdown (bool closeDriver = true)

View File

@@ -53,27 +53,26 @@ namespace Terminal.Gui {
internal SortedList<long, Timeout> timeouts = new SortedList<long, Timeout> ();
internal List<Func<bool>> idleHandlers = new List<Func<bool>> ();
IMainLoopDriver driver;
/// <summary>
/// The current IMainLoopDriver in use.
/// </summary>
/// <value>The driver.</value>
public IMainLoopDriver Driver => driver;
public IMainLoopDriver Driver { get; }
/// <summary>
/// Creates a new Mainloop, to run it you must provide a driver, and choose
/// one of the implementations UnixMainLoop, NetMainLoop or WindowsMainLoop.
/// Creates a new Mainloop.
/// </summary>
/// <param name="driver">Should match the <see cref="ConsoleDriver"/> (one of the implementations UnixMainLoop, NetMainLoop or WindowsMainLoop).</param>
public MainLoop (IMainLoopDriver driver)
{
this.driver = driver;
Driver = driver;
driver.Setup (this);
}
/// <summary>
/// Runs @action on the thread that is processing events
/// Runs <c>action</c> on the thread that is processing events
/// </summary>
/// <param name="action">the action to be invoked on the main processing thread.</param>
public void Invoke (Action action)
{
AddIdle (() => {
@@ -83,8 +82,17 @@ namespace Terminal.Gui {
}
/// <summary>
/// Executes the specified @idleHandler on the idle loop. The return value is a token to remove it.
/// Adds specified idle handler function to mainloop processing. The handler function will be called once per iteration of the main loop after other events have been handled.
/// </summary>
/// <remarks>
/// <para>
/// Remove an idle hander by calling <see cref="RemoveIdle(Func{bool})"/> with the token this method returns.
/// </para>
/// <para>
/// If the <c>idleHandler</c> returns <c>false</c> it will be removed and not called subsequently.
/// </para>
/// </remarks>
/// <param name="idleHandler">Token that can be used to remove the idle handler with <see cref="RemoveIdle(Func{bool})"/> .</param>
public Func<bool> AddIdle (Func<bool> idleHandler)
{
lock (idleHandlers)
@@ -94,12 +102,13 @@ namespace Terminal.Gui {
}
/// <summary>
/// Removes the specified idleHandler from processing.
/// Removes an idle handler added with <see cref="AddIdle(Func{bool})"/> from processing.
/// </summary>
public void RemoveIdle (Func<bool> idleHandler)
/// <param name="token">A token returned by <see cref="AddIdle(Func{bool})"/></param>
public void RemoveIdle (Func<bool> token)
{
lock (idleHandler)
idleHandlers.Remove (idleHandler);
lock (token)
idleHandlers.Remove (token);
}
void AddTimeout (TimeSpan time, Timeout timeout)
@@ -113,10 +122,10 @@ namespace Terminal.Gui {
/// <remarks>
/// When time time specified passes, the callback will be invoked.
/// If the callback returns true, the timeout will be reset, repeating
/// the invocation. If it returns false, the timeout will stop.
/// the invocation. If it returns false, the timeout will stop and be removed.
///
/// The returned value is a token that can be used to stop the timeout
/// by calling RemoveTimeout.
/// by calling <see cref="RemoveTimeout(object)"/>.
/// </remarks>
public object AddTimeout (TimeSpan time, Func<MainLoop, bool> callback)
{
@@ -182,7 +191,7 @@ namespace Terminal.Gui {
public void Stop ()
{
running = false;
driver.Wakeup ();
Driver.Wakeup ();
}
/// <summary>
@@ -195,7 +204,7 @@ namespace Terminal.Gui {
/// </remarks>
public bool EventsPending (bool wait = false)
{
return driver.EventsPending (wait);
return Driver.EventsPending (wait);
}
/// <summary>
@@ -212,7 +221,7 @@ namespace Terminal.Gui {
if (timeouts.Count > 0)
RunTimers ();
driver.MainIteration ();
Driver.MainIteration ();
lock (idleHandlers) {
if (idleHandlers.Count > 0)

View File

@@ -20,7 +20,7 @@ namespace Terminal.Gui {
/// been called (which sets the <see cref="Toplevel.Running"/> property to false).
/// </para>
/// <para>
/// A Toplevel is created when an application initialzies Terminal.Gui by callling <see cref="Application.Init(ConsoleDriver)"/>.
/// A Toplevel is created when an application initialzies Terminal.Gui by callling <see cref="Application.Init(ConsoleDriver, IMainLoopDriver)"/>.
/// The application Toplevel can be accessed via <see cref="Application.Top"/>. Additional Toplevels can be created
/// and run (e.g. <see cref="Dialog"/>s. To run a Toplevel, create the <see cref="Toplevel"/> and
/// call <see cref="Application.Run(Toplevel, bool)"/>.

View File

@@ -21,7 +21,7 @@ namespace Terminal.Gui {
Assert.Null (Application.MainLoop);
Assert.Null (Application.Driver);
Application.Init (new FakeDriver ());
Application.Init (new FakeDriver (), new NetMainLoop (() => FakeConsole.ReadKey (true)));
Assert.NotNull (Application.Current);
Assert.NotNull (Application.CurrentView);
Assert.NotNull (Application.Top);
@@ -57,12 +57,23 @@ namespace Terminal.Gui {
Assert.Throws<InvalidOperationException> (() => rs.Dispose ());
}
void Init ()
{
Application.Init (new FakeDriver (), new NetMainLoop (() => FakeConsole.ReadKey(true)));
Assert.NotNull (Application.Driver);
Assert.NotNull (Application.MainLoop);
}
void Shutdown ()
{
Application.Shutdown (true);
}
[Fact]
public void Begin_End_Cleana_Up ()
{
// Setup Mock driver
Application.Init (new FakeDriver ());
Assert.NotNull (Application.Driver);
Init ();
// Test null Toplevel
Assert.Throws<ArgumentNullException> (() => Application.Begin (null));
@@ -79,15 +90,14 @@ namespace Terminal.Gui {
Assert.Null (Application.MainLoop);
Assert.Null (Application.Driver);
Application.Shutdown (true);
Shutdown ();
}
[Fact]
public void RequestStop_Stops ()
{
// Setup Mock driver
Application.Init (new FakeDriver ());
Assert.NotNull (Application.Driver);
Init ();
var top = new Toplevel ();
var rs = Application.Begin (top);
@@ -112,8 +122,7 @@ namespace Terminal.Gui {
public void RunningFalse_Stops ()
{
// Setup Mock driver
Application.Init (new FakeDriver ());
Assert.NotNull (Application.Driver);
Init ();
var top = new Toplevel ();
var rs = Application.Begin (top);
@@ -139,20 +148,17 @@ namespace Terminal.Gui {
public void KeyUp_Event ()
{
// Setup Mock driver
Application.Init (new FakeDriver ());
Assert.NotNull (Application.Driver);
Init ();
// Setup some fake kepresses (This)
var input = "Tests";
// Put a control-q in at the end
Console.MockKeyPresses.Push (new ConsoleKeyInfo ('q', ConsoleKey.Q, shift: false, alt: false, control: true));
foreach (var c in input.Reverse()) {
foreach (var c in input.Reverse ()) {
if (char.IsLetter (c)) {
Console.MockKeyPresses.Push (new ConsoleKeyInfo (char.ToLower (c), (ConsoleKey)char.ToUpper (c), shift: char.IsUpper (c), alt: false, control: false));
}
else
{
} else {
Console.MockKeyPresses.Push (new ConsoleKeyInfo (c, (ConsoleKey)c, shift: false, alt: false, control: false));
}
}
@@ -162,6 +168,10 @@ namespace Terminal.Gui {
int iterations = 0;
Application.Iteration = () => {
iterations++;
// Stop if we run out of control...
if (iterations > 10) {
Application.RequestStop ();
}
};
int keyUps = 0;

View File

@@ -1,17 +1,407 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices.ComTypes;
using System.Threading;
using Terminal.Gui;
using Xunit;
using Xunit.Sdk;
// Alais Console to MockConsole so we don't accidentally use Console
using Console = Terminal.Gui.FakeConsole;
namespace Terminal.Gui {
public class MainLoopTests {
[Fact]
public void Init_Shutdown_Cleans_Up ()
public void Constructor_Setups_Driver ()
{
var ml = new MainLoop (new NetMainLoop(() => FakeConsole.ReadKey (true)));
Assert.NotNull (ml.Driver);
}
// Idle Handler tests
[Fact]
public void AddIdle_Adds_And_Removes ()
{
var ml = new MainLoop (new NetMainLoop (() => FakeConsole.ReadKey (true)));
Func<bool> fnTrue = () => { return true; };
Func<bool> fnFalse = () => { return false; };
ml.AddIdle (fnTrue);
ml.AddIdle (fnFalse);
ml.RemoveIdle (fnTrue);
// BUGBUG: This doens't throw or indicate an error. Ideally RemoveIdle would either
// trhow an exception in this case, or return an error.
ml.RemoveIdle (fnTrue);
ml.RemoveIdle (fnFalse);
// BUGBUG: This doesn't throw an exception or indicate an error. Ideally RemoveIdle would either
// trhow an exception in this case, or return an error.
ml.RemoveIdle (fnFalse);
// Add again, but with dupe
ml.AddIdle (fnTrue);
ml.AddIdle (fnTrue);
ml.RemoveIdle (fnTrue);
ml.RemoveIdle (fnTrue);
// BUGBUG: This doesn't throw an exception or indicate an error. Ideally RemoveIdle would either
// trhow an exception in this case, or return an error.
ml.RemoveIdle (fnTrue);
}
[Fact]
public void AddIdle_Function_GetsCalled_OnIteration ()
{
var ml = new MainLoop (new NetMainLoop (() => FakeConsole.ReadKey (true)));
var functionCalled = 0;
Func<bool> fn = () => {
functionCalled++;
return true;
};
ml.AddIdle (fn);
ml.MainIteration ();
Assert.Equal (1, functionCalled);
}
[Fact]
public void RemoveIdle_Function_NotCalled ()
{
var ml = new MainLoop (new NetMainLoop (() => FakeConsole.ReadKey (true)));
var functionCalled = 0;
Func<bool> fn = () => {
functionCalled++;
return true;
};
functionCalled = 0;
ml.RemoveIdle (fn);
ml.MainIteration ();
Assert.Equal (0, functionCalled);
}
[Fact]
public void AddThenRemoveIdle_Function_NotCalled ()
{
var ml = new MainLoop (new NetMainLoop (() => FakeConsole.ReadKey (true)));
var functionCalled = 0;
Func<bool> fn = () => {
functionCalled++;
return true;
};
functionCalled = 0;
ml.AddIdle (fn);
ml.RemoveIdle (fn);
ml.MainIteration ();
Assert.Equal (0, functionCalled);
}
[Fact]
public void AddTwice_Function_CalledTwice ()
{
var ml = new MainLoop (new NetMainLoop (() => FakeConsole.ReadKey (true)));
var functionCalled = 0;
Func<bool> fn = () => {
functionCalled++;
return true;
};
functionCalled = 0;
ml.AddIdle (fn);
ml.AddIdle (fn);
ml.MainIteration ();
Assert.Equal (2, functionCalled);
functionCalled = 0;
ml.RemoveIdle (fn);
ml.MainIteration ();
Assert.Equal (1, functionCalled);
functionCalled = 0;
ml.RemoveIdle (fn);
ml.MainIteration ();
Assert.Equal (0, functionCalled);
}
[Fact]
public void False_Idle_Stops_It_Being_Called_Again ()
{
var ml = new MainLoop (new NetMainLoop (() => FakeConsole.ReadKey (true)));
var functionCalled = 0;
Func<bool> fn1 = () => {
functionCalled++;
if (functionCalled == 10) {
return false;
}
return true;
};
// Force stop if 20 iterations
var stopCount = 0;
Func<bool> fnStop = () => {
stopCount++;
if (stopCount == 20) {
ml.Stop ();
}
return true;
};
ml.AddIdle (fnStop);
ml.AddIdle (fn1);
ml.Run ();
ml.RemoveIdle (fnStop);
ml.RemoveIdle (fn1);
Assert.Equal (10, functionCalled);
}
[Fact]
public void AddIdle_Twice_Returns_False_Called_Twice ()
{
var ml = new MainLoop (new NetMainLoop (() => FakeConsole.ReadKey (true)));
var functionCalled = 0;
Func<bool> fn1 = () => {
functionCalled++;
return false;
};
// Force stop if 10 iterations
var stopCount = 0;
Func<bool> fnStop = () => {
stopCount++;
if (stopCount == 10) {
ml.Stop ();
}
return true;
};
ml.AddIdle (fnStop);
ml.AddIdle (fn1);
ml.AddIdle (fn1);
ml.Run ();
ml.RemoveIdle (fnStop);
ml.RemoveIdle (fn1);
ml.RemoveIdle (fn1);
Assert.Equal (2, functionCalled);
}
[Fact]
public void Run_Runs_Idle_Stop_Stops_Idle ()
{
var ml = new MainLoop (new NetMainLoop (() => FakeConsole.ReadKey (true)));
var functionCalled = 0;
Func<bool> fn = () => {
functionCalled++;
if (functionCalled == 10) {
ml.Stop ();
}
return true;
};
ml.AddIdle (fn);
ml.Run ();
ml.RemoveIdle (fn);
Assert.Equal (10, functionCalled);
}
// Timeout Handler Tests
[Fact]
public void AddTimer_Adds_Removes_NoFaults ()
{
var ml = new MainLoop (new NetMainLoop (() => FakeConsole.ReadKey (true)));
var ms = 100;
var callbackCount = 0;
Func<MainLoop, bool> callback = (MainLoop loop) => {
callbackCount++;
return true;
};
var token = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback);
ml.RemoveTimeout (token);
// BUGBUG: This should probably fault?
ml.RemoveTimeout (token);
}
[Fact]
public void AddTimer_Run_Called ()
{
var ml = new MainLoop (new NetMainLoop (() => FakeConsole.ReadKey (true)));
var ms = 100;
var callbackCount = 0;
Func<MainLoop, bool> callback = (MainLoop loop) => {
callbackCount++;
ml.Stop ();
return true;
};
var token = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback);
ml.Run ();
ml.RemoveTimeout (token);
Assert.Equal (1, callbackCount);
}
class MillisecondTolerance : IEqualityComparer<TimeSpan> {
int _tolerance = 0;
public MillisecondTolerance (int tolerance) { _tolerance = tolerance; }
public bool Equals (TimeSpan x, TimeSpan y) => Math.Abs (x.Milliseconds - y.Milliseconds) <= _tolerance;
public int GetHashCode (TimeSpan obj) => obj.GetHashCode ();
}
[Fact]
public void AddTimer_Run_CalledAtApproximatelyRightTime ()
{
var ml = new MainLoop (new NetMainLoop (() => FakeConsole.ReadKey (true)));
var ms = TimeSpan.FromMilliseconds (50);
var watch = new System.Diagnostics.Stopwatch ();
var callbackCount = 0;
Func<MainLoop, bool> callback = (MainLoop loop) => {
watch.Stop ();
callbackCount++;
ml.Stop ();
return true;
};
var token = ml.AddTimeout (ms, callback);
watch.Start ();
ml.Run ();
// +/- 10ms should be good enuf
// https://github.com/xunit/assert.xunit/pull/25
Assert.Equal<TimeSpan> (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (10));
ml.RemoveTimeout (token);
Assert.Equal (1, callbackCount);
}
[Fact]
public void AddTimer_Run_CalledTwiceApproximatelyRightTime ()
{
var ml = new MainLoop (new NetMainLoop (() => FakeConsole.ReadKey (true)));
var ms = TimeSpan.FromMilliseconds (50);
var watch = new System.Diagnostics.Stopwatch ();
var callbackCount = 0;
Func<MainLoop, bool> callback = (MainLoop loop) => {
callbackCount++;
if (callbackCount == 2) {
watch.Stop ();
ml.Stop ();
}
return true;
};
var token = ml.AddTimeout (ms, callback);
watch.Start ();
ml.Run ();
// +/- 10ms should be good enuf
// https://github.com/xunit/assert.xunit/pull/25
Assert.Equal<TimeSpan> (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (10));
ml.RemoveTimeout (token);
Assert.Equal (2, callbackCount);
}
[Fact]
public void AddTimer_Remove_NotCalled ()
{
var ml = new MainLoop (new NetMainLoop (() => FakeConsole.ReadKey (true)));
var ms = TimeSpan.FromMilliseconds (50);
// Force stop if 10 iterations
var stopCount = 0;
Func<bool> fnStop = () => {
stopCount++;
if (stopCount == 10) {
ml.Stop ();
}
return true;
};
ml.AddIdle (fnStop);
var callbackCount = 0;
Func<MainLoop, bool> callback = (MainLoop loop) => {
callbackCount++;
return true;
};
var token = ml.AddTimeout (ms, callback);
ml.RemoveTimeout (token);
ml.Run ();
Assert.Equal (0, callbackCount);
}
[Fact]
public void AddTimer_ReturnFalse_StopsBeingCalled ()
{
var ml = new MainLoop (new NetMainLoop (() => FakeConsole.ReadKey (true)));
var ms = TimeSpan.FromMilliseconds (50);
// Force stop if 10 iterations
var stopCount = 0;
Func<bool> fnStop = () => {
Thread.Sleep (10); // Sleep to enable timeer to fire
stopCount++;
if (stopCount == 10) {
ml.Stop ();
}
return true;
};
ml.AddIdle (fnStop);
var callbackCount = 0;
Func<MainLoop, bool> callback = (MainLoop loop) => {
callbackCount++;
return false;
};
var token = ml.AddTimeout (ms, callback);
ml.Run ();
Assert.Equal (1, callbackCount);
Assert.Equal (10, stopCount);
ml.RemoveTimeout (token);
}
// Invoke Tests
// TODO: Test with threading scenarios
[Fact]
public void Invoke_Adds_Idle ()
{
var ml = new MainLoop (new NetMainLoop (() => FakeConsole.ReadKey (true)));
var actionCalled = 0;
ml.Invoke (() => { actionCalled++; });
ml.MainIteration ();
Assert.Equal (1, actionCalled);
}
// TODO: EventsPending tests
// - wait = true
// - wait = false
// TODO: Add IMainLoop tests
}
}

View File

@@ -7,6 +7,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="System.Collections" Version="4.3.0" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
<PackageReference Include="coverlet.collector" Version="1.2.0" />