mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
Merge branch 'v2_develop' of tig:tig/Terminal.Gui into v2_develop
This commit is contained in:
@@ -201,32 +201,47 @@ public class TimedEvents : ITimedEvents
|
||||
private void RunTimersImpl ()
|
||||
{
|
||||
long now = GetTimestampTicks ();
|
||||
SortedList<long, Timeout> copy;
|
||||
|
||||
// lock prevents new timeouts being added
|
||||
// after we have taken the copy but before
|
||||
// we have allocated a new list (which would
|
||||
// result in lost timeouts or errors during enumeration)
|
||||
lock (_timeoutsLockToken)
|
||||
// Process due timeouts one at a time, without blocking the entire queue
|
||||
while (true)
|
||||
{
|
||||
copy = _timeouts;
|
||||
_timeouts = new ();
|
||||
}
|
||||
Timeout? timeoutToExecute = null;
|
||||
long scheduledTime = 0;
|
||||
|
||||
foreach ((long k, Timeout timeout) in copy)
|
||||
{
|
||||
if (k < now)
|
||||
// Find the next due timeout
|
||||
lock (_timeoutsLockToken)
|
||||
{
|
||||
if (timeout.Callback! ())
|
||||
if (_timeouts.Count == 0)
|
||||
{
|
||||
AddTimeout (timeout.Span, timeout);
|
||||
break; // No more timeouts
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
lock (_timeoutsLockToken)
|
||||
|
||||
// Re-evaluate current time for each iteration
|
||||
now = GetTimestampTicks ();
|
||||
|
||||
// Check if the earliest timeout is due
|
||||
scheduledTime = _timeouts.Keys [0];
|
||||
|
||||
if (scheduledTime >= now)
|
||||
{
|
||||
_timeouts.Add (NudgeToUniqueKey (k), timeout);
|
||||
// Earliest timeout is not yet due, we're done
|
||||
break;
|
||||
}
|
||||
|
||||
// This timeout is due - remove it from the queue
|
||||
timeoutToExecute = _timeouts.Values [0];
|
||||
_timeouts.RemoveAt (0);
|
||||
}
|
||||
|
||||
// Execute the callback outside the lock
|
||||
// This allows nested Run() calls to access the timeout queue
|
||||
if (timeoutToExecute != null)
|
||||
{
|
||||
bool repeat = timeoutToExecute.Callback! ();
|
||||
|
||||
if (repeat)
|
||||
{
|
||||
AddTimeout (timeoutToExecute.Span, timeoutToExecute);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,11 +380,10 @@ public class ApplicationImplTests
|
||||
if (app.TopRunnableView != null)
|
||||
{
|
||||
app.RequestStop ();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
// Return false so the timer does not repeat
|
||||
return false;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -11,42 +11,6 @@ public class ApplicationTests (ITestOutputHelper output)
|
||||
{
|
||||
private readonly ITestOutputHelper _output = output;
|
||||
|
||||
[Fact]
|
||||
public void AddTimeout_Fires ()
|
||||
{
|
||||
IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
uint timeoutTime = 100;
|
||||
var timeoutFired = false;
|
||||
|
||||
// Setup a timeout that will fire
|
||||
app.AddTimeout (
|
||||
TimeSpan.FromMilliseconds (timeoutTime),
|
||||
() =>
|
||||
{
|
||||
timeoutFired = true;
|
||||
|
||||
// Return false so the timer does not repeat
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
// The timeout has not fired yet
|
||||
Assert.False (timeoutFired);
|
||||
|
||||
// Block the thread to prove the timeout does not fire on a background thread
|
||||
Thread.Sleep ((int)timeoutTime * 2);
|
||||
Assert.False (timeoutFired);
|
||||
|
||||
app.StopAfterFirstIteration = true;
|
||||
app.Run<Runnable> ();
|
||||
|
||||
// The timeout should have fired
|
||||
Assert.True (timeoutFired);
|
||||
|
||||
app.Dispose ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Begin_Null_Runnable_Throws ()
|
||||
@@ -281,10 +245,6 @@ public class ApplicationTests (ITestOutputHelper output)
|
||||
|
||||
void Application_Iteration (object? sender, EventArgs<IApplication?> e)
|
||||
{
|
||||
if (iteration > 0)
|
||||
{
|
||||
Assert.Fail ();
|
||||
}
|
||||
|
||||
iteration++;
|
||||
app.RequestStop ();
|
||||
|
||||
@@ -0,0 +1,462 @@
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace ApplicationTests.Timeout;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for timeout behavior with nested Application.Run() calls.
|
||||
/// These tests verify that timeouts scheduled in a parent run loop continue to fire
|
||||
/// correctly when a nested modal dialog is shown via Application.Run().
|
||||
/// </summary>
|
||||
public class NestedRunTimeoutTests (ITestOutputHelper output)
|
||||
{
|
||||
[Fact]
|
||||
public void Multiple_Timeouts_Fire_In_Correct_Order_With_Nested_Run ()
|
||||
{
|
||||
// Arrange
|
||||
using IApplication? app = Application.Create ();
|
||||
app.Init ("FakeDriver");
|
||||
|
||||
List<string> executionOrder = new ();
|
||||
|
||||
var mainWindow = new Window { Title = "Main Window" };
|
||||
var dialog = new Dialog { Title = "Nested Dialog", Buttons = [new() { Text = "Ok" }] };
|
||||
var nestedRunCompleted = false;
|
||||
|
||||
// Use iteration counter for safety instead of time-based timeout
|
||||
var iterations = 0;
|
||||
app.Iteration += IterationHandler;
|
||||
|
||||
try
|
||||
{
|
||||
// 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");
|
||||
|
||||
// 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]
|
||||
public void Timeout_Fires_In_Nested_Run ()
|
||||
{
|
||||
// Arrange
|
||||
using IApplication? app = Application.Create ();
|
||||
|
||||
app.Init ("FakeDriver");
|
||||
|
||||
var timeoutFired = false;
|
||||
var nestedRunStarted = false;
|
||||
var nestedRunEnded = false;
|
||||
|
||||
// Create a simple window for the main run loop
|
||||
var mainWindow = new Window { Title = "Main Window" };
|
||||
|
||||
// Create a dialog for the nested run loop
|
||||
var dialog = new Dialog { Title = "Nested Dialog", Buttons = [new() { Text = "Ok" }] };
|
||||
|
||||
// Schedule a safety timeout that will ensure the app quits if test hangs
|
||||
var requestStopTimeoutFired = false;
|
||||
|
||||
app.AddTimeout (
|
||||
TimeSpan.FromMilliseconds (5000),
|
||||
() =>
|
||||
{
|
||||
output.WriteLine ("SAFETY: RequestStop Timeout fired - test took too long!");
|
||||
requestStopTimeoutFired = true;
|
||||
app.RequestStop ();
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
// Schedule a timeout that will fire AFTER the nested run starts and stop the dialog
|
||||
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;
|
||||
}
|
||||
);
|
||||
|
||||
// After 100ms, start the nested run loop
|
||||
app.AddTimeout (
|
||||
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);
|
||||
|
||||
output.WriteLine ("Nested run ended");
|
||||
nestedRunEnded = true;
|
||||
|
||||
// Stop the main window after nested run completes
|
||||
app.RequestStop ();
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
// Act - Start the main run loop
|
||||
app.Run (mainWindow);
|
||||
|
||||
// Assert
|
||||
Assert.True (nestedRunStarted, "Nested run should have started");
|
||||
Assert.True (timeoutFired, "Timeout should have fired during nested run");
|
||||
Assert.True (nestedRunEnded, "Nested run should have ended");
|
||||
|
||||
Assert.False (requestStopTimeoutFired, "Safety timeout should NOT have fired");
|
||||
|
||||
dialog.Dispose ();
|
||||
mainWindow.Dispose ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Timeout_Fires_With_Single_Session ()
|
||||
{
|
||||
// Arrange
|
||||
using IApplication? app = Application.Create ();
|
||||
|
||||
app.Init ("FakeDriver");
|
||||
|
||||
// Create a simple window for the main run loop
|
||||
var mainWindow = new Window { Title = "Main Window" };
|
||||
|
||||
// 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;
|
||||
}
|
||||
);
|
||||
|
||||
// Act - Start the main run loop
|
||||
app.Run (mainWindow);
|
||||
|
||||
// Assert
|
||||
Assert.True (requestStopTimeoutFired, "RequestStop Timeout should have fired");
|
||||
|
||||
mainWindow.Dispose ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Timeout_Queue_Persists_Across_Nested_Runs ()
|
||||
{
|
||||
// Verify that the timeout queue is not cleared when nested runs start/end
|
||||
|
||||
// Arrange
|
||||
using IApplication? app = Application.Create ();
|
||||
app.Init ("FakeDriver");
|
||||
|
||||
// Schedule a safety timeout that will ensure the app quits if test hangs
|
||||
var requestStopTimeoutFired = false;
|
||||
|
||||
app.AddTimeout (
|
||||
TimeSpan.FromMilliseconds (10000),
|
||||
() =>
|
||||
{
|
||||
output.WriteLine ("SAFETY: RequestStop Timeout fired - test took too long!");
|
||||
requestStopTimeoutFired = true;
|
||||
app.RequestStop ();
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
var mainWindow = new Window { Title = "Main Window" };
|
||||
var dialog = new Dialog { Title = "Dialog", Buttons = [new() { Text = "Ok" }] };
|
||||
|
||||
var initialTimeoutCount = 0;
|
||||
var timeoutCountDuringNestedRun = 0;
|
||||
var timeoutCountAfterNestedRun = 0;
|
||||
|
||||
// Schedule 5 timeouts at different times with wider spacing
|
||||
for (var i = 0; i < 5; i++)
|
||||
{
|
||||
int capturedI = i;
|
||||
|
||||
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
|
||||
app.Run (mainWindow);
|
||||
|
||||
// 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.False (requestStopTimeoutFired, "Safety timeout should NOT have fired");
|
||||
|
||||
dialog.Dispose ();
|
||||
mainWindow.Dispose ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Timeout_Scheduled_Before_Nested_Run_Fires_During_Nested_Run ()
|
||||
{
|
||||
// This test specifically reproduces the ESC key issue scenario:
|
||||
// - Timeouts are scheduled upfront (like demo keys)
|
||||
// - A timeout fires and triggers a nested run (like Enter opening MessageBox)
|
||||
// - A subsequent timeout should still fire during the nested run (like ESC closing MessageBox)
|
||||
|
||||
// Arrange
|
||||
using IApplication? app = Application.Create ();
|
||||
app.Init ("FakeDriver");
|
||||
|
||||
var enterFired = false;
|
||||
var escFired = false;
|
||||
var messageBoxShown = false;
|
||||
var messageBoxClosed = false;
|
||||
|
||||
var mainWindow = new Window { Title = "Login Window" };
|
||||
var messageBox = new Dialog { Title = "Success", Buttons = [new() { Text = "Ok" }] };
|
||||
|
||||
// Schedule a safety timeout that will ensure the app quits if test hangs
|
||||
var requestStopTimeoutFired = false;
|
||||
|
||||
app.AddTimeout (
|
||||
TimeSpan.FromMilliseconds (10000),
|
||||
() =>
|
||||
{
|
||||
output.WriteLine ("SAFETY: RequestStop Timeout fired - test took too long!");
|
||||
requestStopTimeoutFired = true;
|
||||
app.RequestStop ();
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
// Schedule "Enter" timeout at 100ms
|
||||
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;
|
||||
}
|
||||
);
|
||||
|
||||
// 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;
|
||||
}
|
||||
);
|
||||
|
||||
// Increased delay from 300ms to 500ms to ensure nested run completes before stopping main
|
||||
app.AddTimeout (
|
||||
TimeSpan.FromMilliseconds (500),
|
||||
() =>
|
||||
{
|
||||
output.WriteLine ("Stopping main window");
|
||||
app.RequestStop (mainWindow);
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
// 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");
|
||||
|
||||
Assert.False (requestStopTimeoutFired, "Safety timeout should NOT have fired");
|
||||
|
||||
messageBox.Dispose ();
|
||||
mainWindow.Dispose ();
|
||||
}
|
||||
}
|
||||
856
Tests/UnitTestsParallelizable/Application/TimeoutTests.cs
Normal file
856
Tests/UnitTestsParallelizable/Application/TimeoutTests.cs
Normal file
@@ -0,0 +1,856 @@
|
||||
using Xunit.Abstractions;
|
||||
// ReSharper disable AccessToDisposedClosure
|
||||
#pragma warning disable xUnit1031
|
||||
|
||||
namespace ApplicationTests.Timeout;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for timeout behavior and functionality.
|
||||
/// These tests verify that timeouts fire correctly, can be added/removed,
|
||||
/// handle exceptions properly, and work with Application.Run() calls.
|
||||
/// </summary>
|
||||
public class TimeoutTests (ITestOutputHelper output)
|
||||
{
|
||||
[Fact]
|
||||
public void AddTimeout_Callback_Can_Add_New_Timeout ()
|
||||
{
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
var firstFired = false;
|
||||
var secondFired = false;
|
||||
|
||||
app.AddTimeout (
|
||||
TimeSpan.FromMilliseconds (50),
|
||||
() =>
|
||||
{
|
||||
firstFired = true;
|
||||
|
||||
// Add another timeout from within callback
|
||||
app.AddTimeout (
|
||||
TimeSpan.FromMilliseconds (50),
|
||||
() =>
|
||||
{
|
||||
secondFired = true;
|
||||
app.RequestStop ();
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
// Defensive: use iteration counter instead of time-based safety timeout
|
||||
var iterations = 0;
|
||||
app.Iteration += IterationHandler;
|
||||
|
||||
try
|
||||
{
|
||||
app.Run<Runnable> ();
|
||||
|
||||
Assert.True (firstFired);
|
||||
Assert.True (secondFired);
|
||||
}
|
||||
finally
|
||||
{
|
||||
app.Iteration -= IterationHandler;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
void IterationHandler (object? s, EventArgs<IApplication?> e)
|
||||
{
|
||||
iterations++;
|
||||
|
||||
// Stop if test objectives met or safety limit reached
|
||||
if ((firstFired && secondFired) || iterations > 1000)
|
||||
{
|
||||
app.RequestStop ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddTimeout_Exception_In_Callback_Propagates ()
|
||||
{
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
var exceptionThrown = false;
|
||||
|
||||
app.AddTimeout (
|
||||
TimeSpan.FromMilliseconds (50),
|
||||
() =>
|
||||
{
|
||||
exceptionThrown = true;
|
||||
throw new InvalidOperationException ("Test exception");
|
||||
});
|
||||
|
||||
// Defensive: use iteration counter
|
||||
var iterations = 0;
|
||||
app.Iteration += IterationHandler;
|
||||
|
||||
try
|
||||
{
|
||||
Assert.Throws<InvalidOperationException> (() => app.Run<Runnable> ());
|
||||
Assert.True (exceptionThrown, "Exception callback should have been invoked");
|
||||
}
|
||||
finally
|
||||
{
|
||||
app.Iteration -= IterationHandler;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
void IterationHandler (object? s, EventArgs<IApplication?> e)
|
||||
{
|
||||
iterations++;
|
||||
|
||||
// Safety stop if exception not thrown after many iterations
|
||||
if (iterations > 1000 && !exceptionThrown)
|
||||
{
|
||||
app.RequestStop ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddTimeout_Fires ()
|
||||
{
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
uint timeoutTime = 100;
|
||||
var timeoutFired = false;
|
||||
|
||||
// Setup a timeout that will fire
|
||||
app.AddTimeout (
|
||||
TimeSpan.FromMilliseconds (timeoutTime),
|
||||
() =>
|
||||
{
|
||||
timeoutFired = true;
|
||||
|
||||
// Return false so the timer does not repeat
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
// The timeout has not fired yet
|
||||
Assert.False (timeoutFired);
|
||||
|
||||
// Block the thread to prove the timeout does not fire on a background thread
|
||||
Thread.Sleep ((int)timeoutTime * 2);
|
||||
Assert.False (timeoutFired);
|
||||
|
||||
app.StopAfterFirstIteration = true;
|
||||
app.Run<Runnable> ();
|
||||
|
||||
// The timeout should have fired
|
||||
Assert.True (timeoutFired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddTimeout_From_Background_Thread_Fires ()
|
||||
{
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
var timeoutFired = false;
|
||||
using var taskCompleted = new ManualResetEventSlim (false);
|
||||
|
||||
Task.Run (() =>
|
||||
{
|
||||
Thread.Sleep (50); // Ensure we're on background thread
|
||||
|
||||
app.Invoke (() =>
|
||||
{
|
||||
app.AddTimeout (
|
||||
TimeSpan.FromMilliseconds (100),
|
||||
() =>
|
||||
{
|
||||
timeoutFired = true;
|
||||
taskCompleted.Set ();
|
||||
app.RequestStop ();
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// Use iteration counter for safety instead of time
|
||||
var iterations = 0;
|
||||
app.Iteration += IterationHandler;
|
||||
|
||||
try
|
||||
{
|
||||
app.Run<Runnable> ();
|
||||
|
||||
// Defensive: wait with timeout
|
||||
Assert.True (taskCompleted.Wait (TimeSpan.FromSeconds (5)), "Timeout from background thread should have completed");
|
||||
Assert.True (timeoutFired);
|
||||
}
|
||||
finally
|
||||
{
|
||||
app.Iteration -= IterationHandler;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
void IterationHandler (object? s, EventArgs<IApplication?> e)
|
||||
{
|
||||
iterations++;
|
||||
|
||||
// Safety stop
|
||||
if (iterations > 1000)
|
||||
{
|
||||
app.RequestStop ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddTimeout_High_Frequency_All_Fire ()
|
||||
{
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
const int TIMEOUT_COUNT = 50; // Reduced from 100 for performance
|
||||
var firedCount = 0;
|
||||
|
||||
for (var i = 0; i < TIMEOUT_COUNT; i++)
|
||||
{
|
||||
app.AddTimeout (
|
||||
TimeSpan.FromMilliseconds (10 + i * 5),
|
||||
() =>
|
||||
{
|
||||
Interlocked.Increment (ref firedCount);
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Use iteration counter and event completion instead of time-based safety
|
||||
var iterations = 0;
|
||||
app.Iteration += IterationHandler;
|
||||
|
||||
try
|
||||
{
|
||||
app.Run<Runnable> ();
|
||||
|
||||
Assert.Equal (TIMEOUT_COUNT, firedCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
app.Iteration -= IterationHandler;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
void IterationHandler (object? s, EventArgs<IApplication?> e)
|
||||
{
|
||||
iterations++;
|
||||
|
||||
// Stop when all timeouts fired or safety limit reached
|
||||
if (firedCount >= TIMEOUT_COUNT || iterations > 2000)
|
||||
{
|
||||
app.RequestStop ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Long_Running_Callback_Delays_Subsequent_Timeouts ()
|
||||
{
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
var firstStarted = false;
|
||||
var secondFired = false;
|
||||
var firstCompleted = false;
|
||||
|
||||
// Long-running timeout
|
||||
app.AddTimeout (
|
||||
TimeSpan.FromMilliseconds (50),
|
||||
() =>
|
||||
{
|
||||
firstStarted = true;
|
||||
Thread.Sleep (200); // Simulate long operation
|
||||
firstCompleted = true;
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
// This should fire even though first is still running
|
||||
app.AddTimeout (
|
||||
TimeSpan.FromMilliseconds (100),
|
||||
() =>
|
||||
{
|
||||
secondFired = true;
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
// Use iteration counter instead of time-based timeout
|
||||
var iterations = 0;
|
||||
app.Iteration += IterationHandler;
|
||||
|
||||
try
|
||||
{
|
||||
app.Run<Runnable> ();
|
||||
|
||||
Assert.True (firstStarted);
|
||||
Assert.True (secondFired);
|
||||
Assert.True (firstCompleted);
|
||||
}
|
||||
finally
|
||||
{
|
||||
app.Iteration -= IterationHandler;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
void IterationHandler (object? s, EventArgs<IApplication?> e)
|
||||
{
|
||||
iterations++;
|
||||
|
||||
// Stop when both complete or safety limit
|
||||
if ((firstCompleted && secondFired) || iterations > 2000)
|
||||
{
|
||||
app.RequestStop ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddTimeout_Multiple_Fire_In_Order ()
|
||||
{
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
List<int> executionOrder = new ();
|
||||
|
||||
app.AddTimeout (
|
||||
TimeSpan.FromMilliseconds (300),
|
||||
() =>
|
||||
{
|
||||
executionOrder.Add (3);
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
app.AddTimeout (
|
||||
TimeSpan.FromMilliseconds (100),
|
||||
() =>
|
||||
{
|
||||
executionOrder.Add (1);
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
app.AddTimeout (
|
||||
TimeSpan.FromMilliseconds (200),
|
||||
() =>
|
||||
{
|
||||
executionOrder.Add (2);
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
var iterations = 0;
|
||||
|
||||
app.Iteration += IterationHandler;
|
||||
|
||||
try
|
||||
{
|
||||
app.Run<Runnable> ();
|
||||
|
||||
Assert.Equal (new [] { 1, 2, 3 }, executionOrder);
|
||||
}
|
||||
finally
|
||||
{
|
||||
app.Iteration -= IterationHandler;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
void IterationHandler (object? s, EventArgs<IApplication?> e)
|
||||
{
|
||||
iterations++;
|
||||
|
||||
// Stop after timeouts fire or max iterations (defensive)
|
||||
if (executionOrder.Count == 3 || iterations > 1000)
|
||||
{
|
||||
app.RequestStop ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddTimeout_Multiple_TimeSpan_Zero_All_Fire ()
|
||||
{
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
const int TIMEOUT_COUNT = 10;
|
||||
var firedCount = 0;
|
||||
|
||||
for (var i = 0; i < TIMEOUT_COUNT; i++)
|
||||
{
|
||||
app.AddTimeout (
|
||||
TimeSpan.Zero,
|
||||
() =>
|
||||
{
|
||||
Interlocked.Increment (ref firedCount);
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
var iterations = 0;
|
||||
|
||||
app.Iteration += IterationHandler;
|
||||
|
||||
try
|
||||
{
|
||||
app.Run<Runnable> ();
|
||||
|
||||
Assert.Equal (TIMEOUT_COUNT, firedCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
app.Iteration -= IterationHandler;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
void IterationHandler (object? s, EventArgs<IApplication?> e)
|
||||
{
|
||||
iterations++;
|
||||
|
||||
// Defensive: stop after timeouts fire or max iterations
|
||||
if (firedCount == TIMEOUT_COUNT || iterations > 100)
|
||||
{
|
||||
app.RequestStop ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddTimeout_Nested_Run_Parent_Timeout_Fires ()
|
||||
{
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
var parentTimeoutFired = false;
|
||||
var childTimeoutFired = false;
|
||||
var nestedRunCompleted = false;
|
||||
|
||||
// Parent timeout - fires after child modal opens
|
||||
app.AddTimeout (
|
||||
TimeSpan.FromMilliseconds (200),
|
||||
() =>
|
||||
{
|
||||
parentTimeoutFired = true;
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
// After 100ms, open nested modal
|
||||
app.AddTimeout (
|
||||
TimeSpan.FromMilliseconds (100),
|
||||
() =>
|
||||
{
|
||||
var childRunnable = new Runnable ();
|
||||
|
||||
// Child timeout
|
||||
app.AddTimeout (
|
||||
TimeSpan.FromMilliseconds (50),
|
||||
() =>
|
||||
{
|
||||
childTimeoutFired = true;
|
||||
app.RequestStop (childRunnable);
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
app.Run (childRunnable);
|
||||
nestedRunCompleted = true;
|
||||
childRunnable.Dispose ();
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
// Use iteration counter instead of time-based safety
|
||||
var iterations = 0;
|
||||
app.Iteration += IterationHandler;
|
||||
|
||||
try
|
||||
{
|
||||
app.Run<Runnable> ();
|
||||
|
||||
Assert.True (childTimeoutFired, "Child timeout should fire during nested Run");
|
||||
Assert.True (parentTimeoutFired, "Parent timeout should continue firing during nested Run");
|
||||
Assert.True (nestedRunCompleted, "Nested run should have completed");
|
||||
}
|
||||
finally
|
||||
{
|
||||
app.Iteration -= IterationHandler;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
void IterationHandler (object? s, EventArgs<IApplication?> e)
|
||||
{
|
||||
iterations++;
|
||||
|
||||
// Stop when objectives met or safety limit
|
||||
if ((parentTimeoutFired && nestedRunCompleted) || iterations > 2000)
|
||||
{
|
||||
app.RequestStop ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddTimeout_Repeating_Fires_Multiple_Times ()
|
||||
{
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
var fireCount = 0;
|
||||
|
||||
app.AddTimeout (
|
||||
TimeSpan.FromMilliseconds (50),
|
||||
() =>
|
||||
{
|
||||
fireCount++;
|
||||
|
||||
return fireCount < 3; // Repeat 3 times
|
||||
}
|
||||
);
|
||||
|
||||
var iterations = 0;
|
||||
|
||||
app.Iteration += IterationHandler;
|
||||
|
||||
try
|
||||
{
|
||||
app.Run<Runnable> ();
|
||||
|
||||
Assert.Equal (3, fireCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
app.Iteration -= IterationHandler;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
void IterationHandler (object? s, EventArgs<IApplication?> e)
|
||||
{
|
||||
iterations++;
|
||||
|
||||
// Stop after 3 fires or max iterations (defensive)
|
||||
if (fireCount >= 3 || iterations > 1000)
|
||||
{
|
||||
app.RequestStop ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddTimeout_StopAfterFirstIteration_Immediate_Fires ()
|
||||
{
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
var timeoutFired = false;
|
||||
|
||||
app.AddTimeout (
|
||||
TimeSpan.Zero,
|
||||
() =>
|
||||
{
|
||||
timeoutFired = true;
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
app.StopAfterFirstIteration = true;
|
||||
app.Run<Runnable> ();
|
||||
|
||||
Assert.True (timeoutFired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddTimeout_TimeSpan_Zero_Fires ()
|
||||
{
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
var timeoutFired = false;
|
||||
|
||||
app.AddTimeout (
|
||||
TimeSpan.Zero,
|
||||
() =>
|
||||
{
|
||||
timeoutFired = true;
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
app.StopAfterFirstIteration = true;
|
||||
app.Run<Runnable> ();
|
||||
|
||||
Assert.True (timeoutFired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveTimeout_Already_Removed_Returns_False ()
|
||||
{
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
object? token = app.AddTimeout (TimeSpan.FromMilliseconds (100), () => false);
|
||||
|
||||
// Remove once
|
||||
bool removed1 = app.RemoveTimeout (token!);
|
||||
Assert.True (removed1);
|
||||
|
||||
// Try to remove again
|
||||
bool removed2 = app.RemoveTimeout (token!);
|
||||
Assert.False (removed2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveTimeout_Cancels_Timeout ()
|
||||
{
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
var timeoutFired = false;
|
||||
|
||||
object? token = app.AddTimeout (
|
||||
TimeSpan.FromMilliseconds (100),
|
||||
() =>
|
||||
{
|
||||
timeoutFired = true;
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
// Remove timeout before it fires
|
||||
bool removed = app.RemoveTimeout (token!);
|
||||
Assert.True (removed);
|
||||
|
||||
// Use iteration counter instead of time-based timeout
|
||||
var iterations = 0;
|
||||
app.Iteration += IterationHandler;
|
||||
|
||||
try
|
||||
{
|
||||
app.Run<Runnable> ();
|
||||
|
||||
Assert.False (timeoutFired);
|
||||
}
|
||||
finally
|
||||
{
|
||||
app.Iteration -= IterationHandler;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
void IterationHandler (object? s, EventArgs<IApplication?> e)
|
||||
{
|
||||
iterations++;
|
||||
|
||||
// Since timeout was removed, just need enough iterations to prove it won't fire
|
||||
// With 100ms timeout, give ~50 iterations which is more than enough
|
||||
if (iterations > 50)
|
||||
{
|
||||
app.RequestStop ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveTimeout_Invalid_Token_Returns_False ()
|
||||
{
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
var fakeToken = new object ();
|
||||
bool removed = app.RemoveTimeout (fakeToken);
|
||||
|
||||
Assert.False (removed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TimedEvents_GetTimeout_Invalid_Token_Returns_Null ()
|
||||
{
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
var fakeToken = new object ();
|
||||
TimeSpan? actualTimeSpan = app.TimedEvents?.GetTimeout (fakeToken);
|
||||
|
||||
Assert.Null (actualTimeSpan);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TimedEvents_GetTimeout_Returns_Correct_TimeSpan ()
|
||||
{
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
TimeSpan expectedTimeSpan = TimeSpan.FromMilliseconds (500);
|
||||
object? token = app.AddTimeout (expectedTimeSpan, () => false);
|
||||
|
||||
TimeSpan? actualTimeSpan = app.TimedEvents?.GetTimeout (token!);
|
||||
|
||||
Assert.NotNull (actualTimeSpan);
|
||||
Assert.Equal (expectedTimeSpan, actualTimeSpan.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TimedEvents_StopAll_Clears_Timeouts ()
|
||||
{
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
var firedCount = 0;
|
||||
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
app.AddTimeout (
|
||||
TimeSpan.FromMilliseconds (100),
|
||||
() =>
|
||||
{
|
||||
Interlocked.Increment (ref firedCount);
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Assert.NotEmpty (app.TimedEvents!.Timeouts);
|
||||
|
||||
app.TimedEvents.StopAll ();
|
||||
|
||||
Assert.Empty (app.TimedEvents.Timeouts);
|
||||
|
||||
// Use iteration counter for safety
|
||||
var iterations = 0;
|
||||
app.Iteration += IterationHandler;
|
||||
|
||||
try
|
||||
{
|
||||
app.Run<Runnable> ();
|
||||
|
||||
Assert.Equal (0, firedCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
app.Iteration -= IterationHandler;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
void IterationHandler (object? s, EventArgs<IApplication?> e)
|
||||
{
|
||||
iterations++;
|
||||
|
||||
// Since all timeouts were cleared, just need enough iterations to prove they won't fire
|
||||
// With 100ms timeouts, give ~50 iterations which is more than enough
|
||||
if (iterations > 50)
|
||||
{
|
||||
app.RequestStop ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TimedEvents_Timeouts_Property_Is_Thread_Safe ()
|
||||
{
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
const int THREAD_COUNT = 10;
|
||||
var addedCount = 0;
|
||||
var tasksCompleted = new CountdownEvent (THREAD_COUNT);
|
||||
|
||||
// Add timeouts from multiple threads using Invoke
|
||||
for (var i = 0; i < THREAD_COUNT; i++)
|
||||
{
|
||||
Task.Run (() =>
|
||||
{
|
||||
app.Invoke (() =>
|
||||
{
|
||||
// Add timeout with immediate execution
|
||||
app.AddTimeout (
|
||||
TimeSpan.Zero,
|
||||
() =>
|
||||
{
|
||||
Interlocked.Increment (ref addedCount);
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
tasksCompleted.Signal ();
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Use iteration counter to stop when all tasks complete
|
||||
var iterations = 0;
|
||||
app.Iteration += IterationHandler;
|
||||
|
||||
try
|
||||
{
|
||||
app.Run<Runnable> ();
|
||||
|
||||
// Verify we can safely access the Timeouts property from main thread
|
||||
int timeoutCount = app.TimedEvents?.Timeouts.Count ?? 0;
|
||||
|
||||
// Verify no exceptions occurred
|
||||
Assert.True (timeoutCount >= 0, "Should be able to access Timeouts property without exception");
|
||||
|
||||
// Verify all tasks completed and all timeouts fired
|
||||
Assert.True (tasksCompleted.IsSet, "All background tasks should have completed");
|
||||
Assert.Equal (THREAD_COUNT, addedCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
app.Iteration -= IterationHandler;
|
||||
tasksCompleted.Dispose ();
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
void IterationHandler (object? s, EventArgs<IApplication?> e)
|
||||
{
|
||||
iterations++;
|
||||
|
||||
// Stop when all tasks completed and all timeouts fired, or safety limit
|
||||
if ((tasksCompleted.IsSet && addedCount >= THREAD_COUNT) || iterations > 200)
|
||||
{
|
||||
app.RequestStop ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
<ParallelizeAssembly>true</ParallelizeAssembly>
|
||||
<ParallelizeTestCollections>true</ParallelizeTestCollections>
|
||||
<!-- Enable collection parallelism -->
|
||||
<MaxParallelThreads>unlimited</MaxParallelThreads>
|
||||
<MaxParallelThreads>2x</MaxParallelThreads>
|
||||
<!-- Or 'unlimited' / '2x' for CPU multiplier -->
|
||||
<StopOnFail>true</StopOnFail>
|
||||
<!-- Still stop on first failure -->
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<ParallelizeAssembly>true</ParallelizeAssembly>
|
||||
<ParallelizeTestCollections>true</ParallelizeTestCollections>
|
||||
<!-- Enable collection parallelism -->
|
||||
<MaxParallelThreads>unlimited</MaxParallelThreads>
|
||||
<MaxParallelThreads>2x</MaxParallelThreads>
|
||||
<!-- Or 'unlimited' / '2x' for CPU multiplier -->
|
||||
<StopOnFail>true</StopOnFail>
|
||||
<!-- Still stop on first failure -->
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
"parallelizeTestCollections": true,
|
||||
"parallelizeAssembly": true,
|
||||
"stopOnFail": false,
|
||||
"maxParallelThreads": "default"
|
||||
"maxParallelThreads": "4x"
|
||||
}
|
||||
Reference in New Issue
Block a user