mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2026-01-01 00:46:39 +01:00
added mainloop tests and updated others
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)"/>.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
Reference in New Issue
Block a user