Fixes #4325. ApplicationImpl.Invoke is sometimes running on UI thread when Application.Top is null (#4339)

This commit is contained in:
BDisp
2025-10-26 02:15:13 +00:00
committed by GitHub
parent 9530fb677a
commit 1046b47be7
5 changed files with 113 additions and 6 deletions

View File

@@ -17,9 +17,7 @@ public class ApplicationStressTests : TestsAllViews
private const int NUM_PASSES = 50;
private const int NUM_INCREMENTS = 500;
// Use longer timeout when running under debugger to account for slower iterations
private static readonly int POLL_MS = System.Diagnostics.Debugger.IsAttached ? 500 : 100;
private const int POLL_MS = 100;
/// <summary>
/// Stress test for Application.Invoke to verify that invocations from background threads
@@ -79,6 +77,13 @@ public class ApplicationStressTests : TestsAllViews
while (_tbCounter != (j + 1) * numIncrements) // Wait for tbCounter to reach expected value
{
int tbNow = _tbCounter;
// Wait for Application.Top to be running to ensure timed events can be processed
while (Application.Top is null || Application.Top is { Running: false })
{
Thread.Sleep (1);
}
_wakeUp.Wait (pollMs);
if (_tbCounter != tbNow)

View File

@@ -586,11 +586,10 @@ public class ApplicationTests
{
var top = new Toplevel ();
RunState rs = Application.Begin (top);
var firstIteration = false;
var actionCalled = 0;
Application.Invoke (() => { actionCalled++; });
Application.RunIteration (ref rs, firstIteration);
ApplicationImpl.Instance.TimedEvents!.RunTimers ();
Assert.Equal (1, actionCalled);
top.Dispose ();
Application.Shutdown ();

View File

@@ -1,4 +1,5 @@
using System.Diagnostics;
using Xunit.Abstractions;
// Alias Console to MockConsole so we don't accidentally use Console
@@ -7,6 +8,14 @@ namespace UnitTests.ApplicationTests;
/// <summary>Tests MainLoop using the FakeMainLoop.</summary>
public class MainLoopTests
{
private readonly ITestOutputHelper _output;
public MainLoopTests (ITestOutputHelper output)
{
_output = output;
ConsoleDriver.RunningUnitTests = true;
}
private static Button btn;
private static string cancel;
private static string clickMe;
@@ -708,6 +717,95 @@ public class MainLoopTests
Assert.Equal (10, functionCalled);
}
[Theory]
[InlineData ("fake")]
[InlineData ("windows")]
[InlineData ("dotnet")]
[InlineData ("unix")]
public void Application_Invoke_Run_TimedEvents (string driverName)
{
// Arrange
Application.Init (driverName: driverName);
var functionCalled = 0;
var stopwatch = new Stopwatch ();
// Act
Application.Invoke (() =>
{
// Stop the stopwatch *after* the function is called.
functionCalled++;
stopwatch.Stop ();
Application.RequestStop ();
});
// Start timing just before running the application loop.
stopwatch.Start ();
Application.Run ();
// Assert
Assert.NotNull (Application.Top);
Application.Top.Dispose ();
Application.Shutdown ();
Assert.Equal (1, functionCalled);
// Output the elapsed time for this test case.
// ReSharper disable once Xunit.XunitTestWithConsoleOutput
// ReSharper disable once LocalizableElement
Console.WriteLine ($"[{driverName}] Duration: {stopwatch.Elapsed.TotalMilliseconds:F2} ms");
// Output elapsed duration to xUnit's test output
_output.WriteLine ($"[{driverName}] Duration: {stopwatch.Elapsed.TotalMilliseconds:F2} ms");
}
[Theory]
[InlineData ("fake")]
[InlineData ("windows")]
[InlineData ("dotnet")]
[InlineData ("unix")]
public void Application_AddTimeout_Run_TimedEvents (string driverName)
{
// Arrange
Application.Init (driverName: driverName);
var functionCalled = 0;
var stopwatch = new Stopwatch ();
// Act
bool Function ()
{
functionCalled++;
if (functionCalled == 10 && Application.Top is { Running: true })
{
stopwatch.Stop ();
Application.RequestStop ();
return false;
}
return true;
}
Application.AddTimeout (TimeSpan.FromMilliseconds (1), Function);
// Start timing just before running the application loop.
stopwatch.Start ();
Application.Run ();
// Assert
Assert.NotNull (Application.Top);
Application.Top.Dispose ();
Application.Shutdown ();
Assert.Equal (10, functionCalled);
// Output the elapsed time for this test case.
// ReSharper disable once Xunit.XunitTestWithConsoleOutput
// ReSharper disable once LocalizableElement
Console.WriteLine ($"[{driverName}] Duration: {stopwatch.Elapsed.TotalMilliseconds:F2} ms");
// Output elapsed duration to xUnit's test output
_output.WriteLine ($"[{driverName}] Duration: {stopwatch.Elapsed.TotalMilliseconds:F2} ms");
}
public static IEnumerable<object []> TestAddTimeout
{
get