ApplicationStressTests.cs 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. using UnitTests;
  2. using Xunit.Abstractions;
  3. namespace StressTests;
  4. public class ApplicationStressTests : TestsAllViews
  5. {
  6. public ApplicationStressTests (ITestOutputHelper output)
  7. {
  8. ConsoleDriver.RunningUnitTests = true;
  9. }
  10. private static volatile int _tbCounter;
  11. #pragma warning disable IDE1006 // Naming Styles
  12. private static readonly ManualResetEventSlim _wakeUp = new (false);
  13. #pragma warning restore IDE1006 // Naming Styles
  14. private const int NUM_PASSES = 50;
  15. private const int NUM_INCREMENTS = 500;
  16. private const int POLL_MS = 100;
  17. /// <summary>
  18. /// Stress test for Application.Invoke to verify that invocations from background threads
  19. /// are not lost or delayed indefinitely. Tests 25,000 concurrent invocations (50 passes × 500 increments).
  20. /// </summary>
  21. /// <remarks>
  22. /// <para>
  23. /// This test automatically adapts its timeout when running under a debugger (500ms vs 100ms)
  24. /// to account for slower iteration times caused by debugger overhead.
  25. /// </para>
  26. /// <para>
  27. /// See InvokeLeakTest_Analysis.md for technical details about the timing improvements made
  28. /// to TimedEvents (Stopwatch-based timing) and Application.Invoke (MainLoop wakeup).
  29. /// </para>
  30. /// </remarks>
  31. [Theory]
  32. [InlineData (typeof (FakeDriver))]
  33. //[InlineData (typeof (DotNetDriver), Skip = "System.IO.IOException: The handle is invalid")]
  34. //[InlineData (typeof (ANSIDriver))]
  35. //[InlineData (typeof (WindowsDriver))]
  36. //[InlineData (typeof (UnixDriver), Skip = "Unable to load DLL 'libc' or one of its dependencies: The specified module could not be found. (0x8007007E)")]
  37. public async Task InvokeLeakTest (Type driverType)
  38. {
  39. Application.Init (driverName: driverType.Name);
  40. Random r = new ();
  41. TextField tf = new ();
  42. var top = new Toplevel ();
  43. top.Add (tf);
  44. _tbCounter = 0;
  45. Task task = Task.Run (() => RunTest (r, tf, NUM_PASSES, NUM_INCREMENTS, POLL_MS));
  46. // blocks here until the RequestStop is processed at the end of the test
  47. Application.Run (top);
  48. await task; // Propagate exception if any occurred
  49. Assert.Equal (NUM_INCREMENTS * NUM_PASSES, _tbCounter);
  50. top.Dispose ();
  51. Application.Shutdown ();
  52. return;
  53. static void RunTest (Random r, TextField tf, int numPasses, int numIncrements, int pollMs)
  54. {
  55. for (var j = 0; j < numPasses; j++)
  56. {
  57. _wakeUp.Reset ();
  58. for (var i = 0; i < numIncrements; i++)
  59. {
  60. Launch (r, tf, (j + 1) * numIncrements);
  61. }
  62. while (_tbCounter != (j + 1) * numIncrements) // Wait for tbCounter to reach expected value
  63. {
  64. int tbNow = _tbCounter;
  65. // Wait for Application.Top to be running to ensure timed events can be processed
  66. while (Application.Top is null || Application.Top is { Running: false })
  67. {
  68. Thread.Sleep (1);
  69. }
  70. _wakeUp.Wait (pollMs);
  71. if (_tbCounter != tbNow)
  72. {
  73. continue;
  74. }
  75. // No change after wait: Idle handlers added via Application.Invoke have gone missing
  76. Application.Invoke (() => Application.RequestStop ());
  77. throw new TimeoutException (
  78. $"Timeout: Increment lost. _tbCounter ({_tbCounter}) didn't "
  79. + $"change after waiting {pollMs} ms. Failed to reach {(j + 1) * numIncrements} on pass {j + 1}"
  80. );
  81. }
  82. ;
  83. }
  84. Application.Invoke (() => Application.RequestStop ());
  85. }
  86. static void Launch (Random r, TextField tf, int target)
  87. {
  88. Task.Run (
  89. () =>
  90. {
  91. Thread.Sleep (r.Next (2, 4));
  92. Application.Invoke (
  93. () =>
  94. {
  95. tf.Text = $"index{r.Next ()}";
  96. Interlocked.Increment (ref _tbCounter);
  97. if (target == _tbCounter)
  98. {
  99. // On last increment wake up the check
  100. _wakeUp.Set ();
  101. }
  102. }
  103. );
  104. }
  105. );
  106. }
  107. }
  108. }