Files
Terminal.Gui/UnitTests/Application/MainLoopTests.cs
Tig 0df485a890 Fixes #666. Refactor ConsoleDrivers to simplify and remove duplicated code (#2612)
* Added ClipRegion; cleaned up driver code

* clip region unit tests

* api docs

* Moved color stuff from ConsoleDriver to Color.cs

* Removes unused ConsoleDriver APIs

* Code cleanup and Removes unused ConsoleDriver APIs

* Code cleanup and Removes unused ConsoleDriver APIs

* Work around https://github.com/gui-cs/Terminal.Gui/issues/2610

* adjusted unit tests

* initial commit

* Made Rows, Cols, Top, Left virtual

* Made Clipboard non-virtual

* Made EnableConsoleScrolling  non-virtual

* Made Contents non-virtual

* Pulled Row/Col up

* Made MoveTo virtual; fixed stupid FakeDriver cursor issue

* Made CurrentAttribute non-virtual

* Made SetAttribute  non-virtual

* Moved clipboard code out

* Code cleanup

* Removes dependecy on NStack from ConsoleDrivers - WIP

* Fixed unit tests

* Fixed unit tests

* Added list of unit tests needed

* Did some perf testing; tweaked code and charmap to address

* Brough in code from PR #2264 (but commented)

* Tons of code cleanup

* Fighting with ScrollView

* Fixing bugs

* Fixed TabView tests

* Fixed View.Visible test that was not really working

* Fixed unit tests

* Cleaned up clipboard APIs in attempt to track down unit test failure

* Add Cut_Preserves_Selection test

* Removed invalid code

* Removed invalid test code; unit tests now pass

* EscSeq* - Adjusted naming, added more sequences, made code more consistent, simplified, etc...

* Added CSI_SetGraphicsRendition

* NetDriver code cleanup

* code cleanup

* Cleaned up color handling in NetDriver

* refixed tabview unit test

* WindowsDriver color code cleanup

* WindowsDriver color code cleanup

* CursesDriver color code cleanup

* CursesDriver - Adding _BOLD has no effect. Further up the stack we cast the return of ColorToCursesColor from int to short and the _BOLD values don't fit in a short.

* CursesDriver color code - make code more accurate

* CursesDriver color code - make code more accurate

* Simplified ConsoleDriver.GetColors API

* Simplified ConsoleDriver.GetColors API further

* Improved encapslation of Attribute; prep for TrueColor & other attributes like blink

* Fixes #2249. CharacterMap isn't refreshing well non-BMP code points on scroll.

* Use GetRange to take some of the runes before convert to string.

* Attempting to fix unit tests not being cleaned up

* Fixes #2658 - ConsoleDriver.IsRuneSupported

* Fixes #2658 - ConsoleDriver.IsRuneSupported (for WindowsDriver)

* Check all the range values and not only the max value.

* Reducing code.

* Fixes #2674 - Unit test process doesn't exit

* Changed Cell to support IsDirty and list of Runes

* add support for rendering TrueColor output on Windows merging veeman & tznind code

* add colorconverter changes

* fixed merged v2_develop

* Fixing merge bugs

* Fixed merge bugs

* Fixed merge bugs - all unit tests pass

* Debugging netdriver

* More netdriver diag

* API docs for escutils

* Update unicode scenario to stress more stuff

* Contents: Now a 2D array of Cells; WIP

* AddRune and ClearContents no longer virtual/abstract

* WindowsDriver renders correctly again

* Progress on Curses

* Progress on Curses

* broke windowsdriver

* Cleaned up FakeMainLoop

* Cleaned up some build warnings

* Removed _init from AutoInitShutdown as it's not needed anymore

* Removed unused var

* Removed unused var

* Fixed nullabiltiy warning in LineCanvas

* Fixed charmap crash

* Fixes #2758 in v2

* Port testonfail fix to v2

* Remove EnableConsoleScrolling

* Backport #2764 from develop (clear last line)

* Remove uneeded usings

* Progress on unicode

* Merged in changes from PR #2786, Fixes #2784

* revamp charmap rendering

* Charmap option to show glyph widths

* Fixed issue with wide glpyhs being overwritten

* Fixed charmap startcodepoint change issue

* Added abiltiy to see ncurses verison/lib

* Fought with CursesDriver; giving up for now. See notes.

* Leverage Wcwidth nuget library instaed of our own tables

* enhanced charmap Details dialog

* Final attempt at fixing curses

---------

Co-authored-by: BDisp <bd.bdisp@gmail.com>
Co-authored-by: adstep <stephensonadamj@gmail.com>
2023-08-09 14:28:36 -06:00

783 lines
19 KiB
C#

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 System.Threading.Tasks;
using Terminal.Gui;
using Xunit;
using Xunit.Sdk;
// Alias Console to MockConsole so we don't accidentally use Console
using Console = Terminal.Gui.FakeConsole;
namespace Terminal.Gui.ApplicationTests {
/// <summary>
/// Tests MainLoop using the FakeMainLoop.
/// </summary>
public class MainLoopTests {
// TODO: Expand to test all the MainLoop implementations.
[Fact]
public void Constructor_Setups_Driver ()
{
var ml = new MainLoop (new FakeMainLoop ());
Assert.NotNull (ml.MainLoopDriver);
}
// Idle Handler tests
[Fact]
public void AddIdle_Adds_And_Removes ()
{
var ml = new MainLoop (new FakeMainLoop ());
Func<bool> fnTrue = () => true;
Func<bool> fnFalse = () => false;
ml.AddIdle (fnTrue);
ml.AddIdle (fnFalse);
Assert.Equal (2, ml.IdleHandlers.Count);
Assert.Equal (fnTrue, ml.IdleHandlers [0]);
Assert.NotEqual (fnFalse, ml.IdleHandlers [0]);
Assert.True (ml.RemoveIdle (fnTrue));
Assert.Single (ml.IdleHandlers);
// BUGBUG: This doesn't throw or indicate an error. Ideally RemoveIdle would either
// throw an exception in this case, or return an error.
// No. Only need to return a boolean.
Assert.False (ml.RemoveIdle (fnTrue));
Assert.True (ml.RemoveIdle (fnFalse));
// BUGBUG: This doesn't throw an exception or indicate an error. Ideally RemoveIdle would either
// throw an exception in this case, or return an error.
// No. Only need to return a boolean.
Assert.False (ml.RemoveIdle (fnFalse));
// Add again, but with dupe
ml.AddIdle (fnTrue);
ml.AddIdle (fnTrue);
Assert.Equal (2, ml.IdleHandlers.Count);
Assert.Equal (fnTrue, ml.IdleHandlers [0]);
Assert.True (ml.IdleHandlers [0] ());
Assert.Equal (fnTrue, ml.IdleHandlers [1]);
Assert.True (ml.IdleHandlers [1] ());
Assert.True (ml.RemoveIdle (fnTrue));
Assert.Single (ml.IdleHandlers);
Assert.Equal (fnTrue, ml.IdleHandlers [0]);
Assert.NotEqual (fnFalse, ml.IdleHandlers [0]);
Assert.True (ml.RemoveIdle (fnTrue));
Assert.Empty (ml.IdleHandlers);
// BUGBUG: This doesn't throw an exception or indicate an error. Ideally RemoveIdle would either
// throw an exception in this case, or return an error.
// No. Only need to return a boolean.
Assert.False (ml.RemoveIdle (fnTrue));
}
[Fact]
public void AddIdle_Function_GetsCalled_OnIteration ()
{
var ml = new MainLoop (new FakeMainLoop ());
var functionCalled = 0;
Func<bool> fn = () => {
functionCalled++;
return true;
};
ml.AddIdle (fn);
ml.RunIteration ();
Assert.Equal (1, functionCalled);
}
[Fact]
public void RemoveIdle_Function_NotCalled ()
{
var ml = new MainLoop (new FakeMainLoop ());
var functionCalled = 0;
Func<bool> fn = () => {
functionCalled++;
return true;
};
Assert.False (ml.RemoveIdle (fn));
ml.RunIteration ();
Assert.Equal (0, functionCalled);
}
[Fact]
public void AddThenRemoveIdle_Function_NotCalled ()
{
var ml = new MainLoop (new FakeMainLoop ());
var functionCalled = 0;
Func<bool> fn = () => {
functionCalled++;
return true;
};
ml.AddIdle (fn);
Assert.True (ml.RemoveIdle (fn));
ml.RunIteration ();
Assert.Equal (0, functionCalled);
}
[Fact]
public void AddTwice_Function_CalledTwice ()
{
var ml = new MainLoop (new FakeMainLoop ());
var functionCalled = 0;
Func<bool> fn = () => {
functionCalled++;
return true;
};
ml.AddIdle (fn);
ml.AddIdle (fn);
ml.RunIteration ();
Assert.Equal (2, functionCalled);
Assert.Equal (2, ml.IdleHandlers.Count);
functionCalled = 0;
Assert.True (ml.RemoveIdle (fn));
Assert.Single (ml.IdleHandlers);
ml.RunIteration ();
Assert.Equal (1, functionCalled);
functionCalled = 0;
Assert.True (ml.RemoveIdle (fn));
Assert.Empty (ml.IdleHandlers);
ml.RunIteration ();
Assert.Equal (0, functionCalled);
Assert.False (ml.RemoveIdle (fn));
}
[Fact]
public void False_Idle_Stops_It_Being_Called_Again ()
{
var ml = new MainLoop (new FakeMainLoop ());
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 ();
Assert.True (ml.RemoveIdle (fnStop));
Assert.False (ml.RemoveIdle (fn1));
Assert.Equal (10, functionCalled);
Assert.Equal (20, stopCount);
}
[Fact]
public void AddIdle_Twice_Returns_False_Called_Twice ()
{
var ml = new MainLoop (new FakeMainLoop ());
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 ();
Assert.True (ml.RemoveIdle (fnStop));
Assert.False (ml.RemoveIdle (fn1));
Assert.False (ml.RemoveIdle (fn1));
Assert.Equal (2, functionCalled);
}
[Fact]
public void Run_Runs_Idle_Stop_Stops_Idle ()
{
var ml = new MainLoop (new FakeMainLoop ());
var functionCalled = 0;
Func<bool> fn = () => {
functionCalled++;
if (functionCalled == 10) ml.Stop ();
return true;
};
ml.AddIdle (fn);
ml.Run ();
Assert.True (ml.RemoveIdle (fn));
Assert.Equal (10, functionCalled);
}
// Timeout Handler Tests
[Fact]
public void AddTimer_Adds_Removes_NoFaults ()
{
var ml = new MainLoop (new FakeMainLoop ());
var ms = 100;
var callbackCount = 0;
Func<MainLoop, bool> callback = (loop) => {
callbackCount++;
return true;
};
var token = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback);
Assert.True (ml.RemoveTimeout (token));
// BUGBUG: This should probably fault?
// Must return a boolean.
Assert.False (ml.RemoveTimeout (token));
}
// Timeout Handler Tests
[Fact]
public void AddTimer_EventFired ()
{
var ml = new MainLoop (new FakeMainLoop ());
var ms = 100;
var originTicks = DateTime.UtcNow.Ticks;
var callbackCount = 0;
Func<MainLoop, bool> callback = (loop) => {
callbackCount++;
return true;
};
object sender = null;
TimeoutEventArgs args = null;
ml.TimeoutAdded += (s, e) => {
sender = s;
args = e;
};
var token = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback);
Assert.Same (ml,sender);
Assert.NotNull (args.Timeout);
Assert.True (args.Ticks - originTicks >= 100 * TimeSpan.TicksPerMillisecond);
}
[Fact]
public void AddTimer_Run_Called ()
{
var ml = new MainLoop (new FakeMainLoop ());
var ms = 100;
var callbackCount = 0;
Func<MainLoop, bool> callback = (loop) => {
callbackCount++;
ml.Stop ();
return true;
};
var token = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback);
ml.Run ();
Assert.True (ml.RemoveTimeout (token));
Assert.Equal (1, callbackCount);
}
[Fact]
public async Task AddTimer_Duplicate_Keys_Not_Allowed ()
{
var ml = new MainLoop (new FakeMainLoop ());
const int ms = 100;
object token1 = null, token2 = null;
var callbackCount = 0;
Func<MainLoop, bool> callback = (loop) => {
callbackCount++;
if (callbackCount == 2) ml.Stop ();
return true;
};
var task1 = new Task (() => token1 = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback));
var task2 = new Task (() => token2 = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback));
Assert.Null (token1);
Assert.Null (token2);
task1.Start ();
task2.Start ();
ml.Run ();
Assert.NotNull (token1);
Assert.NotNull (token2);
await Task.WhenAll (task1, task2);
Assert.True (ml.RemoveTimeout (token1));
Assert.True (ml.RemoveTimeout (token2));
Assert.Equal (2, callbackCount);
}
[Fact]
public void AddTimer_In_Parallel_Wont_Throw ()
{
var ml = new MainLoop (new FakeMainLoop ());
const int ms = 100;
object token1 = null, token2 = null;
var callbackCount = 0;
Func<MainLoop, bool> callback = (loop) => {
callbackCount++;
if (callbackCount == 2) ml.Stop ();
return true;
};
Parallel.Invoke (
() => token1 = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback),
() => token2 = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback)
);
ml.Run ();
Assert.NotNull (token1);
Assert.NotNull (token2);
Assert.True (ml.RemoveTimeout (token1));
Assert.True (ml.RemoveTimeout (token2));
Assert.Equal (2, 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 FakeMainLoop ());
var ms = TimeSpan.FromMilliseconds (50);
var watch = new System.Diagnostics.Stopwatch ();
var callbackCount = 0;
Func<MainLoop, bool> callback = (loop) => {
watch.Stop ();
callbackCount++;
ml.Stop ();
return true;
};
var token = ml.AddTimeout (ms, callback);
watch.Start ();
ml.Run ();
// +/- 100ms should be good enuf
// https://github.com/xunit/assert.xunit/pull/25
Assert.Equal (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (100));
Assert.True (ml.RemoveTimeout (token));
Assert.Equal (1, callbackCount);
}
[Fact]
public void AddTimer_Run_CalledTwiceApproximatelyRightTime ()
{
var ml = new MainLoop (new FakeMainLoop ());
var ms = TimeSpan.FromMilliseconds (50);
var watch = new System.Diagnostics.Stopwatch ();
var callbackCount = 0;
Func<MainLoop, bool> callback = (loop) => {
callbackCount++;
if (callbackCount == 2) {
watch.Stop ();
ml.Stop ();
}
return true;
};
var token = ml.AddTimeout (ms, callback);
watch.Start ();
ml.Run ();
// +/- 100ms should be good enuf
// https://github.com/xunit/assert.xunit/pull/25
Assert.Equal (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (100));
Assert.True (ml.RemoveTimeout (token));
Assert.Equal (2, callbackCount);
}
[Fact]
public void AddTimer_Remove_NotCalled ()
{
var ml = new MainLoop (new FakeMainLoop ());
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 = (loop) => {
callbackCount++;
return true;
};
var token = ml.AddTimeout (ms, callback);
Assert.True (ml.RemoveTimeout (token));
ml.Run ();
Assert.Equal (0, callbackCount);
}
[Fact]
public void AddTimer_ReturnFalse_StopsBeingCalled ()
{
var ml = new MainLoop (new FakeMainLoop ());
var ms = TimeSpan.FromMilliseconds (50);
// Force stop if 10 iterations
var stopCount = 0;
Func<bool> fnStop = () => {
Thread.Sleep (10); // Sleep to enable timer to fire
stopCount++;
if (stopCount == 10) ml.Stop ();
return true;
};
ml.AddIdle (fnStop);
var callbackCount = 0;
Func<MainLoop, bool> callback = (loop) => {
callbackCount++;
return false;
};
var token = ml.AddTimeout (ms, callback);
ml.Run ();
Assert.Equal (1, callbackCount);
Assert.Equal (10, stopCount);
Assert.False (ml.RemoveTimeout (token));
}
// Invoke Tests
// TODO: Test with threading scenarios
[Fact]
public void Invoke_Adds_Idle ()
{
var ml = new MainLoop (new FakeMainLoop ());
var actionCalled = 0;
ml.Invoke (() => { actionCalled++; });
ml.RunIteration ();
Assert.Equal (1, actionCalled);
}
[Fact]
public void Internal_Tests ()
{
var testMainloop = new TestMainloop ();
var mainloop = new MainLoop (testMainloop);
Assert.Empty (mainloop.timeouts);
Assert.Empty (mainloop.idleHandlers);
Assert.NotNull (new Timeout () {
Span = new TimeSpan (),
Callback = (_) => true
});
}
private class TestMainloop : IMainLoopDriver {
private MainLoop mainLoop;
public bool EventsPending (bool wait)
{
throw new NotImplementedException ();
}
public void Iteration ()
{
throw new NotImplementedException ();
}
public void TearDown ()
{
throw new NotImplementedException ();
}
public void Setup (MainLoop mainLoop)
{
this.mainLoop = mainLoop;
}
public void Wakeup ()
{
throw new NotImplementedException ();
}
}
// TODO: EventsPending tests
// - wait = true
// - wait = false
// TODO: Add IMainLoop tests
volatile static int tbCounter = 0;
static ManualResetEventSlim _wakeUp = new ManualResetEventSlim (false);
private static void Launch (Random r, TextField tf, int target)
{
Task.Run (() => {
Thread.Sleep (r.Next (2, 4));
Application.MainLoop.Invoke (() => {
tf.Text = $"index{r.Next ()}";
Interlocked.Increment (ref tbCounter);
if (target == tbCounter) // On last increment wake up the check
_wakeUp.Set ();
});
});
}
private static void RunTest (Random r, TextField tf, int numPasses, int numIncrements, int pollMs)
{
for (int j = 0; j < numPasses; j++) {
_wakeUp.Reset ();
for (var i = 0; i < numIncrements; i++) Launch (r, tf, (j + 1) * numIncrements);
while (tbCounter != (j + 1) * numIncrements) // Wait for tbCounter to reach expected value
{
var tbNow = tbCounter;
_wakeUp.Wait (pollMs);
if (tbCounter == tbNow) {
// No change after wait: Idle handlers added via Application.MainLoop.Invoke have gone missing
Application.MainLoop.Invoke (() => Application.RequestStop ());
throw new TimeoutException (
$"Timeout: Increment lost. tbCounter ({tbCounter}) didn't " +
$"change after waiting {pollMs} ms. Failed to reach {(j + 1) * numIncrements} on pass {j + 1}");
}
};
}
Application.MainLoop.Invoke (() => Application.RequestStop ());
}
[Fact]
[AutoInitShutdown]
public async Task InvokeLeakTest ()
{
Random r = new ();
TextField tf = new ();
Application.Top.Add (tf);
const int numPasses = 5;
const int numIncrements = 5000;
const int pollMs = 10000;
var task = Task.Run (() => RunTest (r, tf, numPasses, numIncrements, pollMs));
// blocks here until the RequestStop is processed at the end of the test
Application.Run ();
await task; // Propagate exception if any occurred
Assert.Equal (numIncrements * numPasses, tbCounter);
}
private static int total;
private static Button btn;
private static string clickMe;
private static string cancel;
private static string pewPew;
private static int zero;
private static int one;
private static int two;
private static int three;
private static int four;
private static bool taskCompleted;
[Theory, AutoInitShutdown]
[MemberData (nameof (TestAddIdle))]
public void Mainloop_Invoke_Or_AddIdle_Can_Be_Used_For_Events_Or_Actions (Action action, string pclickMe, string pcancel, string ppewPew, int pzero, int pone, int ptwo, int pthree, int pfour)
{
total = 0;
btn = null;
clickMe = pclickMe;
cancel = pcancel;
pewPew = ppewPew;
zero = pzero;
one = pone;
two = ptwo;
three = pthree;
four = pfour;
taskCompleted = false;
var btnLaunch = new Button ("Open Window");
btnLaunch.Clicked += (s,e) => action ();
Application.Top.Add (btnLaunch);
var iterations = -1;
Application.Iteration += () => {
iterations++;
if (iterations == 0) {
Assert.Null (btn);
Assert.Equal (zero, total);
Assert.True (btnLaunch.ProcessKey (new KeyEvent (Key.Enter, null)));
if (btn == null) {
Assert.Null (btn);
Assert.Equal (zero, total);
} else {
Assert.Equal (clickMe, btn.Text);
Assert.Equal (four, total);
}
} else if (iterations == 1) {
Assert.Equal (clickMe, btn.Text);
Assert.Equal (zero, total);
Assert.True (btn.ProcessKey (new KeyEvent (Key.Enter, null)));
Assert.Equal (cancel, btn.Text);
Assert.Equal (one, total);
} else if (taskCompleted) {
Application.RequestStop ();
}
};
Application.Run ();
Assert.True (taskCompleted);
Assert.Equal (clickMe, btn.Text);
Assert.Equal (four, total);
}
public static IEnumerable<object []> TestAddIdle {
get {
// Goes fine
Action a1 = StartWindow;
yield return new object [] { a1, "Click Me", "Cancel", "Pew Pew", 0, 1, 2, 3, 4 };
// Also goes fine
Action a2 = () => Application.MainLoop.Invoke (StartWindow);
yield return new object [] { a2, "Click Me", "Cancel", "Pew Pew", 0, 1, 2, 3, 4 };
}
}
private static void StartWindow ()
{
var startWindow = new Window {
Modal = true
};
btn = new Button {
Text = "Click Me"
};
btn.Clicked += RunAsyncTest;
var totalbtn = new Button () {
X = Pos.Right (btn),
Text = "total"
};
totalbtn.Clicked += (s,e) => {
MessageBox.Query ("Count", $"Count is {total}", "Ok");
};
startWindow.Add (btn);
startWindow.Add (totalbtn);
Application.Run (startWindow);
Assert.Equal (clickMe, btn.Text);
Assert.Equal (four, total);
Application.RequestStop ();
}
private static async void RunAsyncTest (object sender, EventArgs e)
{
Assert.Equal (clickMe, btn.Text);
Assert.Equal (zero, total);
btn.Text = "Cancel";
Interlocked.Increment (ref total);
btn.SetNeedsDisplay ();
await Task.Run (() => {
try {
Assert.Equal (cancel, btn.Text);
Assert.Equal (one, total);
RunSql ();
} finally {
SetReadyToRun ();
}
}).ContinueWith (async (s, e) => {
await Task.Delay (1000);
Assert.Equal (clickMe, btn.Text);
Assert.Equal (three, total);
Interlocked.Increment (ref total);
Assert.Equal (clickMe, btn.Text);
Assert.Equal (four, total);
taskCompleted = true;
}, TaskScheduler.FromCurrentSynchronizationContext ());
}
private static void RunSql ()
{
Thread.Sleep (100);
Assert.Equal (cancel, btn.Text);
Assert.Equal (one, total);
Application.MainLoop.Invoke (() => {
btn.Text = "Pew Pew";
Interlocked.Increment (ref total);
btn.SetNeedsDisplay ();
});
}
private static void SetReadyToRun ()
{
Thread.Sleep (100);
Assert.Equal (pewPew, btn.Text);
Assert.Equal (two, total);
Application.MainLoop.Invoke (() => {
btn.Text = "Click Me";
Interlocked.Increment (ref total);
btn.SetNeedsDisplay ();
});
}
}
}