|
@@ -1,4 +1,3 @@
|
|
|
-#nullable enable
|
|
|
|
|
using Xunit.Abstractions;
|
|
using Xunit.Abstractions;
|
|
|
|
|
|
|
|
namespace ApplicationTests.Timeout;
|
|
namespace ApplicationTests.Timeout;
|
|
@@ -11,43 +10,143 @@ namespace ApplicationTests.Timeout;
|
|
|
public class NestedRunTimeoutTests (ITestOutputHelper output)
|
|
public class NestedRunTimeoutTests (ITestOutputHelper output)
|
|
|
{
|
|
{
|
|
|
[Fact]
|
|
[Fact]
|
|
|
- public void Timeout_Fires_With_Single_Session ()
|
|
|
|
|
|
|
+ public void Multiple_Timeouts_Fire_In_Correct_Order_With_Nested_Run ()
|
|
|
{
|
|
{
|
|
|
// Arrange
|
|
// Arrange
|
|
|
- using IApplication? app = Application.Create (example: false);
|
|
|
|
|
-
|
|
|
|
|
|
|
+ using IApplication? app = Application.Create ();
|
|
|
app.Init ("FakeDriver");
|
|
app.Init ("FakeDriver");
|
|
|
|
|
|
|
|
- // Create a simple window for the main run loop
|
|
|
|
|
|
|
+ List<string> executionOrder = new ();
|
|
|
|
|
+
|
|
|
var mainWindow = new Window { Title = "Main Window" };
|
|
var mainWindow = new Window { Title = "Main Window" };
|
|
|
|
|
+ var dialog = new Dialog { Title = "Nested Dialog", Buttons = [new() { Text = "Ok" }] };
|
|
|
|
|
+ var nestedRunCompleted = false;
|
|
|
|
|
|
|
|
- // Schedule a timeout that will ensure the app quits
|
|
|
|
|
- var requestStopTimeoutFired = false;
|
|
|
|
|
- app.AddTimeout (
|
|
|
|
|
- TimeSpan.FromMilliseconds (100),
|
|
|
|
|
- () =>
|
|
|
|
|
- {
|
|
|
|
|
- output.WriteLine ($"RequestStop Timeout fired!");
|
|
|
|
|
- requestStopTimeoutFired = true;
|
|
|
|
|
- app.RequestStop ();
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ // Use iteration counter for safety instead of time-based timeout
|
|
|
|
|
+ var iterations = 0;
|
|
|
|
|
+ app.Iteration += IterationHandler;
|
|
|
|
|
|
|
|
- // Act - Start the main run loop
|
|
|
|
|
- app.Run (mainWindow);
|
|
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ // Schedule multiple timeouts
|
|
|
|
|
+ app.AddTimeout (
|
|
|
|
|
+ TimeSpan.FromMilliseconds (100),
|
|
|
|
|
+ () =>
|
|
|
|
|
+ {
|
|
|
|
|
+ executionOrder.Add ("Timeout1-100ms");
|
|
|
|
|
+ output.WriteLine ("Timeout1 fired at 100ms");
|
|
|
|
|
|
|
|
- // Assert
|
|
|
|
|
- Assert.True (requestStopTimeoutFired, "RequestStop Timeout should have fired");
|
|
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
- mainWindow.Dispose ();
|
|
|
|
|
|
|
+ app.AddTimeout (
|
|
|
|
|
+ TimeSpan.FromMilliseconds (200),
|
|
|
|
|
+ () =>
|
|
|
|
|
+ {
|
|
|
|
|
+ executionOrder.Add ("Timeout2-200ms-StartNestedRun");
|
|
|
|
|
+ output.WriteLine ("Timeout2 fired at 200ms - Starting nested run");
|
|
|
|
|
+
|
|
|
|
|
+ // Start nested run
|
|
|
|
|
+ app.Run (dialog);
|
|
|
|
|
+
|
|
|
|
|
+ executionOrder.Add ("Timeout2-NestedRunEnded");
|
|
|
|
|
+ nestedRunCompleted = true;
|
|
|
|
|
+ output.WriteLine ("Nested run ended");
|
|
|
|
|
+
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ app.AddTimeout (
|
|
|
|
|
+ TimeSpan.FromMilliseconds (300),
|
|
|
|
|
+ () =>
|
|
|
|
|
+ {
|
|
|
|
|
+ executionOrder.Add ("Timeout3-300ms-InNestedRun");
|
|
|
|
|
+ output.WriteLine ($"Timeout3 fired at 300ms - TopRunnable: {app.TopRunnableView?.Title}");
|
|
|
|
|
+
|
|
|
|
|
+ // This should fire while dialog is running
|
|
|
|
|
+ Assert.Equal (dialog, app.TopRunnableView);
|
|
|
|
|
+
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ app.AddTimeout (
|
|
|
|
|
+ TimeSpan.FromMilliseconds (400),
|
|
|
|
|
+ () =>
|
|
|
|
|
+ {
|
|
|
|
|
+ executionOrder.Add ("Timeout4-400ms-CloseDialog");
|
|
|
|
|
+ output.WriteLine ("Timeout4 fired at 400ms - Closing dialog");
|
|
|
|
|
+
|
|
|
|
|
+ // Close the dialog
|
|
|
|
|
+ app.RequestStop (dialog);
|
|
|
|
|
+
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // Event-driven: Only stop main window AFTER nested run completes
|
|
|
|
|
+ // Use a repeating timeout that checks the condition
|
|
|
|
|
+ app.AddTimeout (
|
|
|
|
|
+ TimeSpan.FromMilliseconds (50),
|
|
|
|
|
+ () =>
|
|
|
|
|
+ {
|
|
|
|
|
+ // Keep checking until nested run completes
|
|
|
|
|
+ if (nestedRunCompleted)
|
|
|
|
|
+ {
|
|
|
|
|
+ executionOrder.Add ("Timeout5-AfterNestedRun-StopMain");
|
|
|
|
|
+ output.WriteLine ("Timeout5 fired after nested run completed - Stopping main window");
|
|
|
|
|
+ app.RequestStop (mainWindow);
|
|
|
|
|
+
|
|
|
|
|
+ return false; // Don't repeat
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return true; // Keep checking
|
|
|
|
|
+ }
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // Act
|
|
|
|
|
+ app.Run (mainWindow);
|
|
|
|
|
+
|
|
|
|
|
+ // Assert - Verify all timeouts fired in the correct order
|
|
|
|
|
+ output.WriteLine ($"Execution order: {string.Join (", ", executionOrder)}");
|
|
|
|
|
+
|
|
|
|
|
+ Assert.Equal (6, executionOrder.Count); // 5 timeout events + 1 nested run end marker
|
|
|
|
|
+ Assert.Equal ("Timeout1-100ms", executionOrder [0]);
|
|
|
|
|
+ Assert.Equal ("Timeout2-200ms-StartNestedRun", executionOrder [1]);
|
|
|
|
|
+ Assert.Equal ("Timeout3-300ms-InNestedRun", executionOrder [2]);
|
|
|
|
|
+ Assert.Equal ("Timeout4-400ms-CloseDialog", executionOrder [3]);
|
|
|
|
|
+ Assert.Equal ("Timeout2-NestedRunEnded", executionOrder [4]);
|
|
|
|
|
+ Assert.Equal ("Timeout5-AfterNestedRun-StopMain", executionOrder [5]);
|
|
|
|
|
+ }
|
|
|
|
|
+ finally
|
|
|
|
|
+ {
|
|
|
|
|
+ app.Iteration -= IterationHandler;
|
|
|
|
|
+ dialog.Dispose ();
|
|
|
|
|
+ mainWindow.Dispose ();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return;
|
|
|
|
|
+
|
|
|
|
|
+ void IterationHandler (object? s, EventArgs<IApplication?> e)
|
|
|
|
|
+ {
|
|
|
|
|
+ iterations++;
|
|
|
|
|
+
|
|
|
|
|
+ // Safety limit - should never be hit with event-driven logic
|
|
|
|
|
+ if (iterations > 2000)
|
|
|
|
|
+ {
|
|
|
|
|
+ output.WriteLine ($"SAFETY: Hit iteration limit. Execution order: {string.Join (", ", executionOrder)}");
|
|
|
|
|
+ app.RequestStop ();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
[Fact]
|
|
[Fact]
|
|
|
public void Timeout_Fires_In_Nested_Run ()
|
|
public void Timeout_Fires_In_Nested_Run ()
|
|
|
{
|
|
{
|
|
|
// Arrange
|
|
// Arrange
|
|
|
- using IApplication? app = Application.Create (example: false);
|
|
|
|
|
|
|
+ using IApplication? app = Application.Create ();
|
|
|
|
|
|
|
|
app.Init ("FakeDriver");
|
|
app.Init ("FakeDriver");
|
|
|
|
|
|
|
@@ -59,60 +158,61 @@ public class NestedRunTimeoutTests (ITestOutputHelper output)
|
|
|
var mainWindow = new Window { Title = "Main Window" };
|
|
var mainWindow = new Window { Title = "Main Window" };
|
|
|
|
|
|
|
|
// Create a dialog for the nested run loop
|
|
// Create a dialog for the nested run loop
|
|
|
- var dialog = new Dialog { Title = "Nested Dialog", Buttons = [new Button { Text = "Ok" }] };
|
|
|
|
|
|
|
+ var dialog = new Dialog { Title = "Nested Dialog", Buttons = [new() { Text = "Ok" }] };
|
|
|
|
|
|
|
|
// Schedule a safety timeout that will ensure the app quits if test hangs
|
|
// Schedule a safety timeout that will ensure the app quits if test hangs
|
|
|
var requestStopTimeoutFired = false;
|
|
var requestStopTimeoutFired = false;
|
|
|
|
|
+
|
|
|
app.AddTimeout (
|
|
app.AddTimeout (
|
|
|
TimeSpan.FromMilliseconds (5000),
|
|
TimeSpan.FromMilliseconds (5000),
|
|
|
() =>
|
|
() =>
|
|
|
{
|
|
{
|
|
|
- output.WriteLine ($"SAFETY: RequestStop Timeout fired - test took too long!");
|
|
|
|
|
|
|
+ output.WriteLine ("SAFETY: RequestStop Timeout fired - test took too long!");
|
|
|
requestStopTimeoutFired = true;
|
|
requestStopTimeoutFired = true;
|
|
|
app.RequestStop ();
|
|
app.RequestStop ();
|
|
|
|
|
+
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
-
|
|
|
|
|
// Schedule a timeout that will fire AFTER the nested run starts and stop the dialog
|
|
// Schedule a timeout that will fire AFTER the nested run starts and stop the dialog
|
|
|
app.AddTimeout (
|
|
app.AddTimeout (
|
|
|
- TimeSpan.FromMilliseconds (200),
|
|
|
|
|
- () =>
|
|
|
|
|
- {
|
|
|
|
|
- output.WriteLine ($"DialogRequestStop Timeout fired! TopRunnable: {app.TopRunnableView?.Title ?? "null"}");
|
|
|
|
|
- timeoutFired = true;
|
|
|
|
|
-
|
|
|
|
|
- // Close the dialog when timeout fires
|
|
|
|
|
- if (app.TopRunnableView == dialog)
|
|
|
|
|
- {
|
|
|
|
|
- app.RequestStop (dialog);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ TimeSpan.FromMilliseconds (200),
|
|
|
|
|
+ () =>
|
|
|
|
|
+ {
|
|
|
|
|
+ output.WriteLine ($"DialogRequestStop Timeout fired! TopRunnable: {app.TopRunnableView?.Title ?? "null"}");
|
|
|
|
|
+ timeoutFired = true;
|
|
|
|
|
+
|
|
|
|
|
+ // Close the dialog when timeout fires
|
|
|
|
|
+ if (app.TopRunnableView == dialog)
|
|
|
|
|
+ {
|
|
|
|
|
+ app.RequestStop (dialog);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
// After 100ms, start the nested run loop
|
|
// After 100ms, start the nested run loop
|
|
|
app.AddTimeout (
|
|
app.AddTimeout (
|
|
|
- TimeSpan.FromMilliseconds (100),
|
|
|
|
|
- () =>
|
|
|
|
|
- {
|
|
|
|
|
- output.WriteLine ("Starting nested run...");
|
|
|
|
|
- nestedRunStarted = true;
|
|
|
|
|
|
|
+ TimeSpan.FromMilliseconds (100),
|
|
|
|
|
+ () =>
|
|
|
|
|
+ {
|
|
|
|
|
+ output.WriteLine ("Starting nested run...");
|
|
|
|
|
+ nestedRunStarted = true;
|
|
|
|
|
|
|
|
- // This blocks until the dialog is closed (by the timeout at 200ms)
|
|
|
|
|
- app.Run (dialog);
|
|
|
|
|
|
|
+ // This blocks until the dialog is closed (by the timeout at 200ms)
|
|
|
|
|
+ app.Run (dialog);
|
|
|
|
|
|
|
|
- output.WriteLine ("Nested run ended");
|
|
|
|
|
- nestedRunEnded = true;
|
|
|
|
|
|
|
+ output.WriteLine ("Nested run ended");
|
|
|
|
|
+ nestedRunEnded = true;
|
|
|
|
|
|
|
|
- // Stop the main window after nested run completes
|
|
|
|
|
- app.RequestStop ();
|
|
|
|
|
|
|
+ // Stop the main window after nested run completes
|
|
|
|
|
+ app.RequestStop ();
|
|
|
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
// Act - Start the main run loop
|
|
// Act - Start the main run loop
|
|
|
app.Run (mainWindow);
|
|
app.Run (mainWindow);
|
|
@@ -129,112 +229,130 @@ public class NestedRunTimeoutTests (ITestOutputHelper output)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
[Fact]
|
|
[Fact]
|
|
|
- public void Multiple_Timeouts_Fire_In_Correct_Order_With_Nested_Run ()
|
|
|
|
|
|
|
+ public void Timeout_Fires_With_Single_Session ()
|
|
|
{
|
|
{
|
|
|
// Arrange
|
|
// Arrange
|
|
|
- using IApplication? app = Application.Create (example: false);
|
|
|
|
|
- app.Init ("FakeDriver");
|
|
|
|
|
|
|
+ using IApplication? app = Application.Create ();
|
|
|
|
|
|
|
|
- var executionOrder = new List<string> ();
|
|
|
|
|
|
|
+ app.Init ("FakeDriver");
|
|
|
|
|
|
|
|
|
|
+ // Create a simple window for the main run loop
|
|
|
var mainWindow = new Window { Title = "Main Window" };
|
|
var mainWindow = new Window { Title = "Main Window" };
|
|
|
- var dialog = new Dialog { Title = "Nested Dialog", Buttons = [new Button { Text = "Ok" }] };
|
|
|
|
|
|
|
|
|
|
- // Schedule a safety timeout that will ensure the app quits if test hangs
|
|
|
|
|
|
|
+ // Schedule a timeout that will ensure the app quits
|
|
|
var requestStopTimeoutFired = false;
|
|
var requestStopTimeoutFired = false;
|
|
|
|
|
+
|
|
|
app.AddTimeout (
|
|
app.AddTimeout (
|
|
|
- TimeSpan.FromMilliseconds (10000),
|
|
|
|
|
|
|
+ TimeSpan.FromMilliseconds (100),
|
|
|
() =>
|
|
() =>
|
|
|
{
|
|
{
|
|
|
- output.WriteLine ($"SAFETY: RequestStop Timeout fired - test took too long!");
|
|
|
|
|
|
|
+ output.WriteLine ("RequestStop Timeout fired!");
|
|
|
requestStopTimeoutFired = true;
|
|
requestStopTimeoutFired = true;
|
|
|
app.RequestStop ();
|
|
app.RequestStop ();
|
|
|
|
|
+
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
- // Schedule multiple timeouts
|
|
|
|
|
- app.AddTimeout (
|
|
|
|
|
- TimeSpan.FromMilliseconds (100),
|
|
|
|
|
- () =>
|
|
|
|
|
- {
|
|
|
|
|
- executionOrder.Add ("Timeout1-100ms");
|
|
|
|
|
- output.WriteLine ("Timeout1 fired at 100ms");
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- app.AddTimeout (
|
|
|
|
|
- TimeSpan.FromMilliseconds (200),
|
|
|
|
|
- () =>
|
|
|
|
|
- {
|
|
|
|
|
- executionOrder.Add ("Timeout2-200ms-StartNestedRun");
|
|
|
|
|
- output.WriteLine ("Timeout2 fired at 200ms - Starting nested run");
|
|
|
|
|
|
|
+ // Act - Start the main run loop
|
|
|
|
|
+ app.Run (mainWindow);
|
|
|
|
|
|
|
|
- // Start nested run
|
|
|
|
|
- app.Run (dialog);
|
|
|
|
|
|
|
+ // Assert
|
|
|
|
|
+ Assert.True (requestStopTimeoutFired, "RequestStop Timeout should have fired");
|
|
|
|
|
|
|
|
- executionOrder.Add ("Timeout2-NestedRunEnded");
|
|
|
|
|
- output.WriteLine ("Nested run ended");
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ mainWindow.Dispose ();
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- app.AddTimeout (
|
|
|
|
|
- TimeSpan.FromMilliseconds (300),
|
|
|
|
|
- () =>
|
|
|
|
|
- {
|
|
|
|
|
- executionOrder.Add ("Timeout3-300ms-InNestedRun");
|
|
|
|
|
- output.WriteLine ($"Timeout3 fired at 300ms - TopRunnable: {app.TopRunnableView?.Title}");
|
|
|
|
|
|
|
+ [Fact]
|
|
|
|
|
+ public void Timeout_Queue_Persists_Across_Nested_Runs ()
|
|
|
|
|
+ {
|
|
|
|
|
+ // Verify that the timeout queue is not cleared when nested runs start/end
|
|
|
|
|
|
|
|
- // This should fire while dialog is running
|
|
|
|
|
- Assert.Equal (dialog, app.TopRunnableView);
|
|
|
|
|
|
|
+ // Arrange
|
|
|
|
|
+ using IApplication? app = Application.Create ();
|
|
|
|
|
+ app.Init ("FakeDriver");
|
|
|
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ // Schedule a safety timeout that will ensure the app quits if test hangs
|
|
|
|
|
+ var requestStopTimeoutFired = false;
|
|
|
|
|
|
|
|
app.AddTimeout (
|
|
app.AddTimeout (
|
|
|
- TimeSpan.FromMilliseconds (400),
|
|
|
|
|
- () =>
|
|
|
|
|
- {
|
|
|
|
|
- executionOrder.Add ("Timeout4-400ms-CloseDialog");
|
|
|
|
|
- output.WriteLine ("Timeout4 fired at 400ms - Closing dialog");
|
|
|
|
|
|
|
+ TimeSpan.FromMilliseconds (10000),
|
|
|
|
|
+ () =>
|
|
|
|
|
+ {
|
|
|
|
|
+ output.WriteLine ("SAFETY: RequestStop Timeout fired - test took too long!");
|
|
|
|
|
+ requestStopTimeoutFired = true;
|
|
|
|
|
+ app.RequestStop ();
|
|
|
|
|
|
|
|
- // Close the dialog
|
|
|
|
|
- app.RequestStop (dialog);
|
|
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ var mainWindow = new Window { Title = "Main Window" };
|
|
|
|
|
+ var dialog = new Dialog { Title = "Dialog", Buttons = [new() { Text = "Ok" }] };
|
|
|
|
|
|
|
|
- app.AddTimeout (
|
|
|
|
|
- TimeSpan.FromMilliseconds (500),
|
|
|
|
|
- () =>
|
|
|
|
|
- {
|
|
|
|
|
- executionOrder.Add ("Timeout5-500ms-StopMain");
|
|
|
|
|
- output.WriteLine ("Timeout5 fired at 500ms - Stopping main window");
|
|
|
|
|
|
|
+ var initialTimeoutCount = 0;
|
|
|
|
|
+ var timeoutCountDuringNestedRun = 0;
|
|
|
|
|
+ var timeoutCountAfterNestedRun = 0;
|
|
|
|
|
|
|
|
- // Stop main window
|
|
|
|
|
- app.RequestStop (mainWindow);
|
|
|
|
|
|
|
+ // Schedule 5 timeouts at different times with wider spacing
|
|
|
|
|
+ for (var i = 0; i < 5; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ int capturedI = i;
|
|
|
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ app.AddTimeout (
|
|
|
|
|
+ TimeSpan.FromMilliseconds (150 * (i + 1)), // Increased spacing from 100ms to 150ms
|
|
|
|
|
+ () =>
|
|
|
|
|
+ {
|
|
|
|
|
+ output.WriteLine ($"Timeout {capturedI} fired at {150 * (capturedI + 1)}ms");
|
|
|
|
|
+
|
|
|
|
|
+ if (capturedI == 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ initialTimeoutCount = app.TimedEvents!.Timeouts.Count;
|
|
|
|
|
+ output.WriteLine ($"Initial timeout count: {initialTimeoutCount}");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (capturedI == 1)
|
|
|
|
|
+ {
|
|
|
|
|
+ // Start nested run
|
|
|
|
|
+ output.WriteLine ("Starting nested run");
|
|
|
|
|
+ app.Run (dialog);
|
|
|
|
|
+ output.WriteLine ("Nested run ended");
|
|
|
|
|
+
|
|
|
|
|
+ timeoutCountAfterNestedRun = app.TimedEvents!.Timeouts.Count;
|
|
|
|
|
+ output.WriteLine ($"Timeout count after nested run: {timeoutCountAfterNestedRun}");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (capturedI == 2)
|
|
|
|
|
+ {
|
|
|
|
|
+ // This fires during nested run
|
|
|
|
|
+ timeoutCountDuringNestedRun = app.TimedEvents!.Timeouts.Count;
|
|
|
|
|
+ output.WriteLine ($"Timeout count during nested run: {timeoutCountDuringNestedRun}");
|
|
|
|
|
+
|
|
|
|
|
+ // Close dialog
|
|
|
|
|
+ app.RequestStop (dialog);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (capturedI == 4)
|
|
|
|
|
+ {
|
|
|
|
|
+ // Stop main window
|
|
|
|
|
+ app.RequestStop (mainWindow);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
// Act
|
|
// Act
|
|
|
app.Run (mainWindow);
|
|
app.Run (mainWindow);
|
|
|
|
|
|
|
|
- // Assert - Verify all timeouts fired in the correct order
|
|
|
|
|
- output.WriteLine ($"Execution order: {string.Join (", ", executionOrder)}");
|
|
|
|
|
|
|
+ // Assert
|
|
|
|
|
+ output.WriteLine ($"Final counts - Initial: {initialTimeoutCount}, During: {timeoutCountDuringNestedRun}, After: {timeoutCountAfterNestedRun}");
|
|
|
|
|
|
|
|
- Assert.Equal (6, executionOrder.Count); // 5 timeouts + 1 nested run end marker
|
|
|
|
|
- Assert.Equal ("Timeout1-100ms", executionOrder [0]);
|
|
|
|
|
- Assert.Equal ("Timeout2-200ms-StartNestedRun", executionOrder [1]);
|
|
|
|
|
- Assert.Equal ("Timeout3-300ms-InNestedRun", executionOrder [2]);
|
|
|
|
|
- Assert.Equal ("Timeout4-400ms-CloseDialog", executionOrder [3]);
|
|
|
|
|
- Assert.Equal ("Timeout2-NestedRunEnded", executionOrder [4]);
|
|
|
|
|
- Assert.Equal ("Timeout5-500ms-StopMain", executionOrder [5]);
|
|
|
|
|
|
|
+ // The timeout queue should have pending timeouts throughout
|
|
|
|
|
+ Assert.True (initialTimeoutCount >= 0, "Should have timeouts in queue initially");
|
|
|
|
|
+ Assert.True (timeoutCountDuringNestedRun >= 0, "Should have timeouts in queue during nested run");
|
|
|
|
|
+ Assert.True (timeoutCountAfterNestedRun >= 0, "Should have timeouts in queue after nested run");
|
|
|
|
|
|
|
|
Assert.False (requestStopTimeoutFired, "Safety timeout should NOT have fired");
|
|
Assert.False (requestStopTimeoutFired, "Safety timeout should NOT have fired");
|
|
|
|
|
|
|
@@ -251,7 +369,7 @@ public class NestedRunTimeoutTests (ITestOutputHelper output)
|
|
|
// - A subsequent timeout should still fire during the nested run (like ESC closing MessageBox)
|
|
// - A subsequent timeout should still fire during the nested run (like ESC closing MessageBox)
|
|
|
|
|
|
|
|
// Arrange
|
|
// Arrange
|
|
|
- using IApplication? app = Application.Create (example: false);
|
|
|
|
|
|
|
+ using IApplication? app = Application.Create ();
|
|
|
app.Init ("FakeDriver");
|
|
app.Init ("FakeDriver");
|
|
|
|
|
|
|
|
var enterFired = false;
|
|
var enterFired = false;
|
|
@@ -260,175 +378,85 @@ public class NestedRunTimeoutTests (ITestOutputHelper output)
|
|
|
var messageBoxClosed = false;
|
|
var messageBoxClosed = false;
|
|
|
|
|
|
|
|
var mainWindow = new Window { Title = "Login Window" };
|
|
var mainWindow = new Window { Title = "Login Window" };
|
|
|
- var messageBox = new Dialog { Title = "Success", Buttons = [new Button { Text = "Ok" }] };
|
|
|
|
|
|
|
+ var messageBox = new Dialog { Title = "Success", Buttons = [new() { Text = "Ok" }] };
|
|
|
|
|
|
|
|
// Schedule a safety timeout that will ensure the app quits if test hangs
|
|
// Schedule a safety timeout that will ensure the app quits if test hangs
|
|
|
var requestStopTimeoutFired = false;
|
|
var requestStopTimeoutFired = false;
|
|
|
|
|
+
|
|
|
app.AddTimeout (
|
|
app.AddTimeout (
|
|
|
TimeSpan.FromMilliseconds (10000),
|
|
TimeSpan.FromMilliseconds (10000),
|
|
|
() =>
|
|
() =>
|
|
|
{
|
|
{
|
|
|
- output.WriteLine ($"SAFETY: RequestStop Timeout fired - test took too long!");
|
|
|
|
|
|
|
+ output.WriteLine ("SAFETY: RequestStop Timeout fired - test took too long!");
|
|
|
requestStopTimeoutFired = true;
|
|
requestStopTimeoutFired = true;
|
|
|
app.RequestStop ();
|
|
app.RequestStop ();
|
|
|
|
|
+
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
);
|
|
);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// Schedule "Enter" timeout at 100ms
|
|
// Schedule "Enter" timeout at 100ms
|
|
|
app.AddTimeout (
|
|
app.AddTimeout (
|
|
|
- TimeSpan.FromMilliseconds (100),
|
|
|
|
|
- () =>
|
|
|
|
|
- {
|
|
|
|
|
- output.WriteLine ("Enter timeout fired - showing MessageBox");
|
|
|
|
|
- enterFired = true;
|
|
|
|
|
-
|
|
|
|
|
- // Simulate Enter key opening MessageBox
|
|
|
|
|
- messageBoxShown = true;
|
|
|
|
|
- app.Run (messageBox);
|
|
|
|
|
- messageBoxClosed = true;
|
|
|
|
|
-
|
|
|
|
|
- output.WriteLine ("MessageBox closed");
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ TimeSpan.FromMilliseconds (100),
|
|
|
|
|
+ () =>
|
|
|
|
|
+ {
|
|
|
|
|
+ output.WriteLine ("Enter timeout fired - showing MessageBox");
|
|
|
|
|
+ enterFired = true;
|
|
|
|
|
|
|
|
- // Schedule "ESC" timeout at 200ms (should fire while MessageBox is running)
|
|
|
|
|
- app.AddTimeout (
|
|
|
|
|
- TimeSpan.FromMilliseconds (200),
|
|
|
|
|
- () =>
|
|
|
|
|
- {
|
|
|
|
|
- output.WriteLine ($"ESC timeout fired - TopRunnable: {app.TopRunnableView?.Title}");
|
|
|
|
|
- escFired = true;
|
|
|
|
|
-
|
|
|
|
|
- // Simulate ESC key closing MessageBox
|
|
|
|
|
- if (app.TopRunnableView == messageBox)
|
|
|
|
|
- {
|
|
|
|
|
- output.WriteLine ("Closing MessageBox with ESC");
|
|
|
|
|
- app.RequestStop (messageBox);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- // Stop main window after MessageBox closes
|
|
|
|
|
- app.AddTimeout (
|
|
|
|
|
- TimeSpan.FromMilliseconds (300),
|
|
|
|
|
- () =>
|
|
|
|
|
- {
|
|
|
|
|
- output.WriteLine ("Stopping main window");
|
|
|
|
|
- app.RequestStop (mainWindow);
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ // Simulate Enter key opening MessageBox
|
|
|
|
|
+ messageBoxShown = true;
|
|
|
|
|
+ app.Run (messageBox);
|
|
|
|
|
+ messageBoxClosed = true;
|
|
|
|
|
|
|
|
- // Act
|
|
|
|
|
- app.Run (mainWindow);
|
|
|
|
|
-
|
|
|
|
|
- // Assert
|
|
|
|
|
- Assert.True (enterFired, "Enter timeout should have fired");
|
|
|
|
|
- Assert.True (messageBoxShown, "MessageBox should have been shown");
|
|
|
|
|
- Assert.True (escFired, "ESC timeout should have fired during MessageBox"); // THIS WAS THE BUG - NOW FIXED!
|
|
|
|
|
- Assert.True (messageBoxClosed, "MessageBox should have been closed");
|
|
|
|
|
|
|
+ output.WriteLine ("MessageBox closed");
|
|
|
|
|
|
|
|
- Assert.False (requestStopTimeoutFired, "Safety timeout should NOT have fired");
|
|
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
- messageBox.Dispose ();
|
|
|
|
|
- mainWindow.Dispose ();
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // Schedule "ESC" timeout at 200ms (should fire while MessageBox is running)
|
|
|
|
|
+ app.AddTimeout (
|
|
|
|
|
+ TimeSpan.FromMilliseconds (200),
|
|
|
|
|
+ () =>
|
|
|
|
|
+ {
|
|
|
|
|
+ output.WriteLine ($"ESC timeout fired - TopRunnable: {app.TopRunnableView?.Title}");
|
|
|
|
|
+ escFired = true;
|
|
|
|
|
|
|
|
- [Fact]
|
|
|
|
|
- public void Timeout_Queue_Persists_Across_Nested_Runs ()
|
|
|
|
|
- {
|
|
|
|
|
- // Verify that the timeout queue is not cleared when nested runs start/end
|
|
|
|
|
|
|
+ // Simulate ESC key closing MessageBox
|
|
|
|
|
+ if (app.TopRunnableView == messageBox)
|
|
|
|
|
+ {
|
|
|
|
|
+ output.WriteLine ("Closing MessageBox with ESC");
|
|
|
|
|
+ app.RequestStop (messageBox);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // Arrange
|
|
|
|
|
- using IApplication? app = Application.Create (example: false);
|
|
|
|
|
- app.Init ("FakeDriver");
|
|
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
- // Schedule a safety timeout that will ensure the app quits if test hangs
|
|
|
|
|
- var requestStopTimeoutFired = false;
|
|
|
|
|
|
|
+ // Increased delay from 300ms to 500ms to ensure nested run completes before stopping main
|
|
|
app.AddTimeout (
|
|
app.AddTimeout (
|
|
|
- TimeSpan.FromMilliseconds (10000),
|
|
|
|
|
|
|
+ TimeSpan.FromMilliseconds (500),
|
|
|
() =>
|
|
() =>
|
|
|
{
|
|
{
|
|
|
- output.WriteLine ($"SAFETY: RequestStop Timeout fired - test took too long!");
|
|
|
|
|
- requestStopTimeoutFired = true;
|
|
|
|
|
- app.RequestStop ();
|
|
|
|
|
|
|
+ output.WriteLine ("Stopping main window");
|
|
|
|
|
+ app.RequestStop (mainWindow);
|
|
|
|
|
+
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
- var mainWindow = new Window { Title = "Main Window" };
|
|
|
|
|
- var dialog = new Dialog { Title = "Dialog", Buttons = [new Button { Text = "Ok" }] };
|
|
|
|
|
-
|
|
|
|
|
- int initialTimeoutCount = 0;
|
|
|
|
|
- int timeoutCountDuringNestedRun = 0;
|
|
|
|
|
- int timeoutCountAfterNestedRun = 0;
|
|
|
|
|
-
|
|
|
|
|
- // Schedule 5 timeouts at different times
|
|
|
|
|
- for (int i = 0; i < 5; i++)
|
|
|
|
|
- {
|
|
|
|
|
- int capturedI = i;
|
|
|
|
|
- app.AddTimeout (
|
|
|
|
|
- TimeSpan.FromMilliseconds (100 * (i + 1)),
|
|
|
|
|
- () =>
|
|
|
|
|
- {
|
|
|
|
|
- output.WriteLine ($"Timeout {capturedI} fired at {100 * (capturedI + 1)}ms");
|
|
|
|
|
-
|
|
|
|
|
- if (capturedI == 0)
|
|
|
|
|
- {
|
|
|
|
|
- initialTimeoutCount = app.TimedEvents!.Timeouts.Count;
|
|
|
|
|
- output.WriteLine ($"Initial timeout count: {initialTimeoutCount}");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (capturedI == 1)
|
|
|
|
|
- {
|
|
|
|
|
- // Start nested run
|
|
|
|
|
- output.WriteLine ("Starting nested run");
|
|
|
|
|
- app.Run (dialog);
|
|
|
|
|
- output.WriteLine ("Nested run ended");
|
|
|
|
|
-
|
|
|
|
|
- timeoutCountAfterNestedRun = app.TimedEvents!.Timeouts.Count;
|
|
|
|
|
- output.WriteLine ($"Timeout count after nested run: {timeoutCountAfterNestedRun}");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (capturedI == 2)
|
|
|
|
|
- {
|
|
|
|
|
- // This fires during nested run
|
|
|
|
|
- timeoutCountDuringNestedRun = app.TimedEvents!.Timeouts.Count;
|
|
|
|
|
- output.WriteLine ($"Timeout count during nested run: {timeoutCountDuringNestedRun}");
|
|
|
|
|
-
|
|
|
|
|
- // Close dialog
|
|
|
|
|
- app.RequestStop (dialog);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (capturedI == 4)
|
|
|
|
|
- {
|
|
|
|
|
- // Stop main window
|
|
|
|
|
- app.RequestStop (mainWindow);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
// Act
|
|
// Act
|
|
|
app.Run (mainWindow);
|
|
app.Run (mainWindow);
|
|
|
|
|
|
|
|
// Assert
|
|
// Assert
|
|
|
- output.WriteLine ($"Final counts - Initial: {initialTimeoutCount}, During: {timeoutCountDuringNestedRun}, After: {timeoutCountAfterNestedRun}");
|
|
|
|
|
-
|
|
|
|
|
- // The timeout queue should have pending timeouts throughout
|
|
|
|
|
- Assert.True (initialTimeoutCount >= 0, "Should have timeouts in queue initially");
|
|
|
|
|
- Assert.True (timeoutCountDuringNestedRun >= 0, "Should have timeouts in queue during nested run");
|
|
|
|
|
- Assert.True (timeoutCountAfterNestedRun >= 0, "Should have timeouts in queue after nested run");
|
|
|
|
|
|
|
+ Assert.True (enterFired, "Enter timeout should have fired");
|
|
|
|
|
+ Assert.True (messageBoxShown, "MessageBox should have been shown");
|
|
|
|
|
+ Assert.True (escFired, "ESC timeout should have fired during MessageBox"); // THIS WAS THE BUG - NOW FIXED!
|
|
|
|
|
+ Assert.True (messageBoxClosed, "MessageBox should have been closed");
|
|
|
|
|
|
|
|
Assert.False (requestStopTimeoutFired, "Safety timeout should NOT have fired");
|
|
Assert.False (requestStopTimeoutFired, "Safety timeout should NOT have fired");
|
|
|
|
|
|
|
|
- dialog.Dispose ();
|
|
|
|
|
|
|
+ messageBox.Dispose ();
|
|
|
mainWindow.Dispose ();
|
|
mainWindow.Dispose ();
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|