ApplicationStressTests.cs 4.8 KB

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