using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using UICatalog;
using UnitTests;
using Xunit.Abstractions;
namespace IntegrationTests.UICatalog;
public class ScenarioTests : TestsAllViews
{
public ScenarioTests (ITestOutputHelper output)
{
#if DEBUG_IDISPOSABLE
View.EnableDebugIDisposableAsserts = true;
View.Instances.Clear ();
#endif
_output = output;
}
private readonly ITestOutputHelper _output;
///
/// This runs through all Scenarios defined in UI Catalog, calling Init, Setup, and Run.
/// Should find any Scenarios which crash on load or do not respond to .
///
[Theory]
[MemberData (nameof (AllScenarioTypes))]
public void All_Scenarios_Quit_And_Init_Shutdown_Properly (Type scenarioType)
{
// Disable on Mac due to random failures related to timing issues
if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX))
{
_output.WriteLine ($"Skipping Scenario '{scenarioType}' on macOS due to random timeout failures.");
return;
}
// Force a complete reset
ApplicationImpl.SetInstance (null);
CM.Disable (true);
_output.WriteLine ($"Running Scenario '{scenarioType}'");
Scenario? scenario = null;
var scenarioName = string.Empty;
// Do not use Application.AddTimer for out-of-band watchdogs as
// they will be stopped by Shutdown/ResetState.
Timer? watchdogTimer = null;
var timeoutFired = false;
// Increase timeout for macOS - it's consistently slower
uint abortTime = 5000;
if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX))
{
abortTime = 10000;
}
var initialized = false;
var shutdownGracefully = false;
var iterationCount = 0;
Key quitKey = Application.QuitKey;
// Track if we've already unsubscribed to prevent double-removal
var iterationHandlerRemoved = false;
try
{
scenario = Activator.CreateInstance (scenarioType) as Scenario;
scenarioName = scenario!.GetName ();
Application.InitializedChanged += OnApplicationOnInitializedChanged;
Application.ForceDriver = "FakeDriver";
scenario!.Main ();
Application.ForceDriver = string.Empty;
}
finally
{
// Ensure cleanup happens regardless of how we exit
Application.InitializedChanged -= OnApplicationOnInitializedChanged;
// Remove iteration handler if it wasn't removed
if (!iterationHandlerRemoved)
{
Application.Iteration -= OnApplicationOnIteration;
iterationHandlerRemoved = true;
}
watchdogTimer?.Dispose ();
scenario?.Dispose ();
scenario = null;
ConfigurationManager.Disable (true);
}
Assert.True (initialized, $"Scenario '{scenarioName}' failed to initialize.");
if (timeoutFired)
{
_output.WriteLine ($"WARNING: Scenario '{scenarioName}' timed out after {abortTime}ms. This may indicate a performance issue on this runner.");
}
Assert.True (
shutdownGracefully,
$"Scenario '{scenarioName}' failed to quit with {quitKey} after {abortTime}ms and {iterationCount} iterations. "
+ $"TimeoutFired={timeoutFired}");
#if DEBUG_IDISPOSABLE
Assert.Empty (View.Instances);
#endif
return;
void OnApplicationOnInitializedChanged (object? s, EventArgs a)
{
if (a.Value)
{
Application.Iteration += OnApplicationOnIteration;
initialized = true;
// Use a System.Threading.Timer for the watchdog to ensure it's not affected by Application.StopAllTimers
watchdogTimer = new Timer (_ => ForceCloseCallback (), null, (int)abortTime, System.Threading.Timeout.Infinite);
}
else
{
shutdownGracefully = true;
}
_output.WriteLine ($"Initialized == {a.Value}; shutdownGracefully == {shutdownGracefully}.");
}
// If the scenario doesn't close within abortTime ms, this will force it to quit
void ForceCloseCallback ()
{
timeoutFired = true;
_output.WriteLine ($"TIMEOUT FIRED for {scenarioName} after {abortTime}ms. Attempting graceful shutdown.");
// Don't call ResetState here - let the finally block handle cleanup
// Just try to stop the application gracefully
try
{
if (Application.Initialized)
{
Application.RequestStop ();
}
}
catch (Exception ex)
{
_output.WriteLine ($"Exception during timeout callback: {ex.Message}");
}
}
void OnApplicationOnIteration (object? s, IterationEventArgs a)
{
iterationCount++;
if (Application.Initialized)
{
// Press QuitKey
quitKey = Application.QuitKey;
_output.WriteLine ($"Attempting to quit with {quitKey} after {iterationCount} iterations.");
try
{
Application.RaiseKeyDownEvent (quitKey);
}
catch (Exception ex)
{
_output.WriteLine ($"Exception raising quit key: {ex.Message}");
}
Application.Iteration -= OnApplicationOnIteration;
iterationHandlerRemoved = true;
}
}
}
public static IEnumerable