MainLoopTests.cs 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics.CodeAnalysis;
  4. using System.Globalization;
  5. using System.Linq;
  6. using System.Runtime.InteropServices.ComTypes;
  7. using System.Threading;
  8. using Terminal.Gui;
  9. using Xunit;
  10. using Xunit.Sdk;
  11. // Alais Console to MockConsole so we don't accidentally use Console
  12. using Console = Terminal.Gui.FakeConsole;
  13. namespace Terminal.Gui {
  14. public class MainLoopTests {
  15. [Fact]
  16. public void Constructor_Setups_Driver ()
  17. {
  18. var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
  19. Assert.NotNull (ml.Driver);
  20. }
  21. // Idle Handler tests
  22. [Fact]
  23. public void AddIdle_Adds_And_Removes ()
  24. {
  25. var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
  26. Func<bool> fnTrue = () => { return true; };
  27. Func<bool> fnFalse = () => { return false; };
  28. ml.AddIdle (fnTrue);
  29. ml.AddIdle (fnFalse);
  30. ml.RemoveIdle (fnTrue);
  31. // BUGBUG: This doens't throw or indicate an error. Ideally RemoveIdle would either
  32. // trhow an exception in this case, or return an error.
  33. ml.RemoveIdle (fnTrue);
  34. ml.RemoveIdle (fnFalse);
  35. // BUGBUG: This doesn't throw an exception or indicate an error. Ideally RemoveIdle would either
  36. // trhow an exception in this case, or return an error.
  37. ml.RemoveIdle (fnFalse);
  38. // Add again, but with dupe
  39. ml.AddIdle (fnTrue);
  40. ml.AddIdle (fnTrue);
  41. ml.RemoveIdle (fnTrue);
  42. ml.RemoveIdle (fnTrue);
  43. // BUGBUG: This doesn't throw an exception or indicate an error. Ideally RemoveIdle would either
  44. // trhow an exception in this case, or return an error.
  45. ml.RemoveIdle (fnTrue);
  46. }
  47. [Fact]
  48. public void AddIdle_Function_GetsCalled_OnIteration ()
  49. {
  50. var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
  51. var functionCalled = 0;
  52. Func<bool> fn = () => {
  53. functionCalled++;
  54. return true;
  55. };
  56. ml.AddIdle (fn);
  57. ml.MainIteration ();
  58. Assert.Equal (1, functionCalled);
  59. }
  60. [Fact]
  61. public void RemoveIdle_Function_NotCalled ()
  62. {
  63. var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
  64. var functionCalled = 0;
  65. Func<bool> fn = () => {
  66. functionCalled++;
  67. return true;
  68. };
  69. functionCalled = 0;
  70. ml.RemoveIdle (fn);
  71. ml.MainIteration ();
  72. Assert.Equal (0, functionCalled);
  73. }
  74. [Fact]
  75. public void AddThenRemoveIdle_Function_NotCalled ()
  76. {
  77. var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
  78. var functionCalled = 0;
  79. Func<bool> fn = () => {
  80. functionCalled++;
  81. return true;
  82. };
  83. functionCalled = 0;
  84. ml.AddIdle (fn);
  85. ml.RemoveIdle (fn);
  86. ml.MainIteration ();
  87. Assert.Equal (0, functionCalled);
  88. }
  89. [Fact]
  90. public void AddTwice_Function_CalledTwice ()
  91. {
  92. var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
  93. var functionCalled = 0;
  94. Func<bool> fn = () => {
  95. functionCalled++;
  96. return true;
  97. };
  98. functionCalled = 0;
  99. ml.AddIdle (fn);
  100. ml.AddIdle (fn);
  101. ml.MainIteration ();
  102. Assert.Equal (2, functionCalled);
  103. functionCalled = 0;
  104. ml.RemoveIdle (fn);
  105. ml.MainIteration ();
  106. Assert.Equal (1, functionCalled);
  107. functionCalled = 0;
  108. ml.RemoveIdle (fn);
  109. ml.MainIteration ();
  110. Assert.Equal (0, functionCalled);
  111. }
  112. [Fact]
  113. public void False_Idle_Stops_It_Being_Called_Again ()
  114. {
  115. var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
  116. var functionCalled = 0;
  117. Func<bool> fn1 = () => {
  118. functionCalled++;
  119. if (functionCalled == 10) {
  120. return false;
  121. }
  122. return true;
  123. };
  124. // Force stop if 20 iterations
  125. var stopCount = 0;
  126. Func<bool> fnStop = () => {
  127. stopCount++;
  128. if (stopCount == 20) {
  129. ml.Stop ();
  130. }
  131. return true;
  132. };
  133. ml.AddIdle (fnStop);
  134. ml.AddIdle (fn1);
  135. ml.Run ();
  136. ml.RemoveIdle (fnStop);
  137. ml.RemoveIdle (fn1);
  138. Assert.Equal (10, functionCalled);
  139. }
  140. [Fact]
  141. public void AddIdle_Twice_Returns_False_Called_Twice ()
  142. {
  143. var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
  144. var functionCalled = 0;
  145. Func<bool> fn1 = () => {
  146. functionCalled++;
  147. return false;
  148. };
  149. // Force stop if 10 iterations
  150. var stopCount = 0;
  151. Func<bool> fnStop = () => {
  152. stopCount++;
  153. if (stopCount == 10) {
  154. ml.Stop ();
  155. }
  156. return true;
  157. };
  158. ml.AddIdle (fnStop);
  159. ml.AddIdle (fn1);
  160. ml.AddIdle (fn1);
  161. ml.Run ();
  162. ml.RemoveIdle (fnStop);
  163. ml.RemoveIdle (fn1);
  164. ml.RemoveIdle (fn1);
  165. Assert.Equal (2, functionCalled);
  166. }
  167. [Fact]
  168. public void Run_Runs_Idle_Stop_Stops_Idle ()
  169. {
  170. var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
  171. var functionCalled = 0;
  172. Func<bool> fn = () => {
  173. functionCalled++;
  174. if (functionCalled == 10) {
  175. ml.Stop ();
  176. }
  177. return true;
  178. };
  179. ml.AddIdle (fn);
  180. ml.Run ();
  181. ml.RemoveIdle (fn);
  182. Assert.Equal (10, functionCalled);
  183. }
  184. // Timeout Handler Tests
  185. [Fact]
  186. public void AddTimer_Adds_Removes_NoFaults ()
  187. {
  188. var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
  189. var ms = 100;
  190. var callbackCount = 0;
  191. Func<MainLoop, bool> callback = (MainLoop loop) => {
  192. callbackCount++;
  193. return true;
  194. };
  195. var token = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback);
  196. ml.RemoveTimeout (token);
  197. // BUGBUG: This should probably fault?
  198. ml.RemoveTimeout (token);
  199. }
  200. [Fact]
  201. public void AddTimer_Run_Called ()
  202. {
  203. var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
  204. var ms = 100;
  205. var callbackCount = 0;
  206. Func<MainLoop, bool> callback = (MainLoop loop) => {
  207. callbackCount++;
  208. ml.Stop ();
  209. return true;
  210. };
  211. var token = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback);
  212. ml.Run ();
  213. ml.RemoveTimeout (token);
  214. Assert.Equal (1, callbackCount);
  215. }
  216. class MillisecondTolerance : IEqualityComparer<TimeSpan> {
  217. int _tolerance = 0;
  218. public MillisecondTolerance (int tolerance) { _tolerance = tolerance; }
  219. public bool Equals (TimeSpan x, TimeSpan y) => Math.Abs (x.Milliseconds - y.Milliseconds) <= _tolerance;
  220. public int GetHashCode (TimeSpan obj) => obj.GetHashCode ();
  221. }
  222. [Fact]
  223. public void AddTimer_Run_CalledAtApproximatelyRightTime ()
  224. {
  225. var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
  226. var ms = TimeSpan.FromMilliseconds (50);
  227. var watch = new System.Diagnostics.Stopwatch ();
  228. var callbackCount = 0;
  229. Func<MainLoop, bool> callback = (MainLoop loop) => {
  230. watch.Stop ();
  231. callbackCount++;
  232. ml.Stop ();
  233. return true;
  234. };
  235. var token = ml.AddTimeout (ms, callback);
  236. watch.Start ();
  237. ml.Run ();
  238. // +/- 100ms should be good enuf
  239. // https://github.com/xunit/assert.xunit/pull/25
  240. Assert.Equal<TimeSpan> (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (100));
  241. ml.RemoveTimeout (token);
  242. Assert.Equal (1, callbackCount);
  243. }
  244. [Fact]
  245. public void AddTimer_Run_CalledTwiceApproximatelyRightTime ()
  246. {
  247. var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
  248. var ms = TimeSpan.FromMilliseconds (50);
  249. var watch = new System.Diagnostics.Stopwatch ();
  250. var callbackCount = 0;
  251. Func<MainLoop, bool> callback = (MainLoop loop) => {
  252. callbackCount++;
  253. if (callbackCount == 2) {
  254. watch.Stop ();
  255. ml.Stop ();
  256. }
  257. return true;
  258. };
  259. var token = ml.AddTimeout (ms, callback);
  260. watch.Start ();
  261. ml.Run ();
  262. // +/- 100ms should be good enuf
  263. // https://github.com/xunit/assert.xunit/pull/25
  264. Assert.Equal<TimeSpan> (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (100));
  265. ml.RemoveTimeout (token);
  266. Assert.Equal (2, callbackCount);
  267. }
  268. [Fact]
  269. public void AddTimer_Remove_NotCalled ()
  270. {
  271. var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
  272. var ms = TimeSpan.FromMilliseconds (50);
  273. // Force stop if 10 iterations
  274. var stopCount = 0;
  275. Func<bool> fnStop = () => {
  276. stopCount++;
  277. if (stopCount == 10) {
  278. ml.Stop ();
  279. }
  280. return true;
  281. };
  282. ml.AddIdle (fnStop);
  283. var callbackCount = 0;
  284. Func<MainLoop, bool> callback = (MainLoop loop) => {
  285. callbackCount++;
  286. return true;
  287. };
  288. var token = ml.AddTimeout (ms, callback);
  289. ml.RemoveTimeout (token);
  290. ml.Run ();
  291. Assert.Equal (0, callbackCount);
  292. }
  293. [Fact]
  294. public void AddTimer_ReturnFalse_StopsBeingCalled ()
  295. {
  296. var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
  297. var ms = TimeSpan.FromMilliseconds (50);
  298. // Force stop if 10 iterations
  299. var stopCount = 0;
  300. Func<bool> fnStop = () => {
  301. Thread.Sleep (10); // Sleep to enable timeer to fire
  302. stopCount++;
  303. if (stopCount == 10) {
  304. ml.Stop ();
  305. }
  306. return true;
  307. };
  308. ml.AddIdle (fnStop);
  309. var callbackCount = 0;
  310. Func<MainLoop, bool> callback = (MainLoop loop) => {
  311. callbackCount++;
  312. return false;
  313. };
  314. var token = ml.AddTimeout (ms, callback);
  315. ml.Run ();
  316. Assert.Equal (1, callbackCount);
  317. Assert.Equal (10, stopCount);
  318. ml.RemoveTimeout (token);
  319. }
  320. // Invoke Tests
  321. // TODO: Test with threading scenarios
  322. [Fact]
  323. public void Invoke_Adds_Idle ()
  324. {
  325. var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
  326. var actionCalled = 0;
  327. ml.Invoke (() => { actionCalled++; });
  328. ml.MainIteration ();
  329. Assert.Equal (1, actionCalled);
  330. }
  331. // TODO: EventsPending tests
  332. // - wait = true
  333. // - wait = false
  334. // TODO: Add IMainLoop tests
  335. }
  336. }