diff --git a/Examples/UICatalog/UICatalog.cs b/Examples/UICatalog/UICatalog.cs
index 6f7a371390..5bcbfe720a 100644
--- a/Examples/UICatalog/UICatalog.cs
+++ b/Examples/UICatalog/UICatalog.cs
@@ -62,6 +62,40 @@ public class UICatalog
public const string LOGFILE_LOCATION = "logs";
public static UICatalogCommandLineOptions Options { get; set; }
+ ///
+ /// Sets up the ForceDriver configuration for testing purposes.
+ /// This is called by UICatalogMain and can also be called by tests.
+ ///
+ /// The driver name to force
+ internal static void SetupForceDriverConfig (string? driver)
+ {
+ _forceDriver = driver;
+
+ // If a driver has been specified, set it in RuntimeConfig so it persists through Init/Shutdown cycles
+ if (!string.IsNullOrEmpty (_forceDriver))
+ {
+ ConfigurationManager.RuntimeConfig = $$"""
+ {
+ "Application.ForceDriver": "{{_forceDriver}}"
+ }
+ """;
+ }
+ }
+
+ ///
+ /// Reloads RuntimeConfig to ensure ForceDriver persists before running a scenario.
+ /// This is called in the scenario loop and can also be called by tests.
+ ///
+ internal static void ReloadForceDriverConfig ()
+ {
+ // Ensure RuntimeConfig is applied before each scenario to preserve ForceDriver setting
+ if (!Options.DontEnableConfigurationManagement && !string.IsNullOrEmpty (_forceDriver))
+ {
+ ConfigurationManager.Load (ConfigLocations.Runtime);
+ ConfigurationManager.Apply ();
+ }
+ }
+
private static int Main (string [] args)
{
Console.OutputEncoding = Encoding.Default;
@@ -342,11 +376,11 @@ private static void ConfigFileChanged (object sender, FileSystemEventArgs e)
ConfigurationManager.Apply ();
}
- private static void UICatalogMain (UICatalogCommandLineOptions options)
+ internal static void UICatalogMain (UICatalogCommandLineOptions options)
{
// By setting _forceDriver we ensure that if the user has specified a driver on the command line, it will be used
// regardless of what's in a config file.
- Application.ForceDriver = _forceDriver = options.Driver;
+ SetupForceDriverConfig (options.Driver);
// If a Scenario name has been provided on the commandline
// run it and exit when done.
@@ -412,6 +446,9 @@ private static void UICatalogMain (UICatalogCommandLineOptions options)
Application.InitializedChanged += ApplicationOnInitializedChanged;
#endif
+ // Reload RuntimeConfig to ensure ForceDriver persists (part 2 of the fix)
+ ReloadForceDriverConfig ();
+
scenario.Main ();
scenario.Dispose ();
@@ -451,7 +488,11 @@ void ApplicationOnInitializedChanged (object? sender, EventArgs e)
scenario.StartBenchmark ();
}
- Application.ForceDriver = _forceDriver!;
+ // For benchmarking without ConfigurationManager, set ForceDriver directly
+ if (!ConfigurationManager.IsEnabled && !string.IsNullOrEmpty (_forceDriver))
+ {
+ Application.ForceDriver = _forceDriver;
+ }
scenario.Main ();
diff --git a/Examples/UICatalog/UICatalog.csproj b/Examples/UICatalog/UICatalog.csproj
index 0b529c4994..3097a87c0f 100644
--- a/Examples/UICatalog/UICatalog.csproj
+++ b/Examples/UICatalog/UICatalog.csproj
@@ -11,6 +11,9 @@
2.0
Linux
+
+
+
TRACE
diff --git a/Tests/IntegrationTests/UICatalog/ForceDriverTests.cs b/Tests/IntegrationTests/UICatalog/ForceDriverTests.cs
new file mode 100644
index 0000000000..f36b5c4374
--- /dev/null
+++ b/Tests/IntegrationTests/UICatalog/ForceDriverTests.cs
@@ -0,0 +1,322 @@
+using UICatalog;
+using Xunit.Abstractions;
+
+namespace IntegrationTests.UICatalog;
+
+///
+/// Integration tests for ForceDriver persistence when opening scenarios in UICatalog.
+///
+public class ForceDriverTests
+{
+ private readonly ITestOutputHelper _output;
+
+ public ForceDriverTests (ITestOutputHelper output)
+ {
+ _output = output;
+ }
+
+ ///
+ /// Tests that ForceDriver persists when running UICatalogTop and then opening a scenario.
+ ///
+ /// This test verifies the fix for issue #4391 works correctly by calling UICatalog's actual methods.
+ ///
+ /// THE BUG: Without the fix, ForceDriver was set directly on Application, but
+ /// ConfigurationManager would override it from config files when scenarios ran.
+ ///
+ /// THE FIX has two parts (both in UICatalog.cs):
+ /// 1. SetupForceDriverConfig() - Sets ForceDriver in ConfigurationManager.RuntimeConfig
+ /// 2. ReloadForceDriverConfig() - Reloads RuntimeConfig before each scenario
+ ///
+ /// This test calls both UICatalog methods to verify the fix works.
+ /// If you remove the calls to these methods or modify UICatalog.cs to remove the fix,
+ /// this test will fail.
+ ///
+ [Fact]
+ public void ForceDriver_Persists_From_UICatalogTop_To_Scenario ()
+ {
+ // Arrange
+ const string expectedDriver = "fake";
+
+ ConfigurationManager.Disable (true);
+ Application.ResetState (true);
+
+ // Initialize UICatalog.Options (required by UICatalogTop)
+ global::UICatalog.UICatalog.Options = new UICatalogCommandLineOptions
+ {
+ Driver = expectedDriver,
+ DontEnableConfigurationManagement = false,
+ Scenario = "none",
+ BenchmarkTimeout = 2500,
+ Benchmark = false,
+ ResultsFile = string.Empty,
+ DebugLogLevel = "Warning"
+ };
+
+ // Initialize cached scenarios (required by UICatalogTop)
+ UICatalogTop.CachedScenarios = Scenario.GetScenarios ();
+
+ // Call UICatalog's setup method (this is part 1 of the fix in UICatalog.cs)
+ // This sets ForceDriver in RuntimeConfig
+ global::UICatalog.UICatalog.SetupForceDriverConfig (expectedDriver);
+
+ // Enable ConfigurationManager with all locations (as UICatalog does)
+ ConfigurationManager.Enable (ConfigLocations.All);
+
+ var topLevelDriverName = string.Empty;
+ var scenarioDriverName = string.Empty;
+ var iterationCount = 0;
+ EventHandler? iterationHandler = null;
+
+ try
+ {
+ // Phase 1: Run UICatalogTop (simulating main UI)
+ _output.WriteLine ("=== Phase 1: Running UICatalogTop ===");
+ Application.Init ();
+ topLevelDriverName = Application.Driver?.GetName () ?? string.Empty;
+ _output.WriteLine ($"UICatalogTop driver: {topLevelDriverName}");
+
+ var top = new UICatalogTop ();
+
+ // Set up to automatically select a scenario and stop
+ iterationHandler = (sender, e) =>
+ {
+ iterationCount++;
+
+ // On first iteration, select a scenario and request stop
+ if (iterationCount == 1)
+ {
+ // Select the first scenario
+ if (UICatalogTop.CachedScenarios is { Count: > 0 })
+ {
+ UICatalogTop.CachedSelectedScenario =
+ (Scenario)Activator.CreateInstance (UICatalogTop.CachedScenarios[0].GetType ())!;
+ Application.RequestStop ();
+ }
+ }
+ };
+
+ Application.Iteration += iterationHandler;
+ Application.Run (top);
+ Application.Iteration -= iterationHandler;
+
+ top.Dispose ();
+ Application.Shutdown ();
+
+ _output.WriteLine ($"Selected scenario: {UICatalogTop.CachedSelectedScenario?.GetName ()}");
+ _output.WriteLine ($"UICatalogTop completed after {iterationCount} iterations");
+
+ // Phase 2: Run the selected scenario (simulating what UICatalog.cs does)
+ if (UICatalogTop.CachedSelectedScenario is { } scenario)
+ {
+ _output.WriteLine ($"\n=== Phase 2: Running scenario '{scenario.GetName ()}' ===");
+
+ // Call UICatalog's reload method (this is part 2 of the fix in UICatalog.cs)
+ // This ensures ForceDriver persists across Init/Shutdown cycles
+ global::UICatalog.UICatalog.ReloadForceDriverConfig ();
+ _output.WriteLine ("Reloaded ForceDriver config via UICatalog.ReloadForceDriverConfig()");
+
+ // Track the driver used inside the scenario
+ string? driverInsideScenario = null;
+ EventHandler>? initHandler = null;
+ EventHandler? scenarioIterationHandler = null;
+
+ initHandler = (s, e) =>
+ {
+ if (e.Value)
+ {
+ driverInsideScenario = Application.Driver?.GetName ();
+
+ // Request stop immediately so the scenario doesn't actually run
+ scenarioIterationHandler = (_, _) =>
+ {
+ Application.RequestStop ();
+ // Remove immediately to avoid assertions in Shutdown
+ if (scenarioIterationHandler != null)
+ {
+ Application.Iteration -= scenarioIterationHandler;
+ scenarioIterationHandler = null;
+ }
+ };
+ Application.Iteration += scenarioIterationHandler;
+ }
+ };
+
+ Application.InitializedChanged += initHandler;
+
+ // Run the scenario's Main() method (this is what UICatalog does)
+ scenario.Main ();
+ scenarioDriverName = driverInsideScenario ?? string.Empty;
+
+ Application.InitializedChanged -= initHandler;
+ scenario.Dispose ();
+
+ _output.WriteLine ($"Scenario driver: {scenarioDriverName}");
+ _output.WriteLine ("Scenario completed and disposed");
+ }
+ else
+ {
+ _output.WriteLine ("ERROR: No scenario was selected");
+ Assert.Fail ("No scenario was selected");
+ }
+
+ // Assert
+ _output.WriteLine ($"\n=== Results ===");
+ _output.WriteLine ($"UICatalogTop driver: {topLevelDriverName}");
+ _output.WriteLine ($"Scenario driver: {scenarioDriverName}");
+
+ Assert.Equal (expectedDriver, topLevelDriverName);
+ Assert.Equal (expectedDriver, scenarioDriverName);
+ _output.WriteLine ($"SUCCESS: Driver '{expectedDriver}' persisted from UICatalogTop to scenario");
+ }
+ finally
+ {
+ if (iterationHandler != null)
+ {
+ Application.Iteration -= iterationHandler;
+ }
+ ConfigurationManager.Disable (true);
+ Application.ResetState (true);
+ }
+ }
+
+ ///
+ /// Tests that ForceDriver persists when running multiple scenarios in sequence.
+ ///
+ /// This verifies the fix works correctly by calling UICatalog's ReloadForceDriverConfig() method.
+ ///
+ /// THE FIX: ReloadForceDriverConfig() in UICatalog.cs reloads RuntimeConfig before each scenario.
+ /// Without calling this method, the driver would revert to platform default.
+ ///
+ [Fact]
+ public void ForceDriver_Persists_Across_Multiple_Scenarios ()
+ {
+ // Arrange
+ const string expectedDriver = "fake";
+
+ ConfigurationManager.Disable (true);
+ Application.ResetState (true);
+
+ // Initialize UICatalog.Options
+ global::UICatalog.UICatalog.Options = new UICatalogCommandLineOptions
+ {
+ Driver = expectedDriver,
+ DontEnableConfigurationManagement = false,
+ Scenario = "none",
+ BenchmarkTimeout = 2500,
+ Benchmark = false,
+ ResultsFile = string.Empty,
+ DebugLogLevel = "Warning"
+ };
+
+ // Call UICatalog's setup method (this is part 1 of the fix in UICatalog.cs)
+ global::UICatalog.UICatalog.SetupForceDriverConfig (expectedDriver);
+
+ // Enable ConfigurationManager
+ ConfigurationManager.Enable (ConfigLocations.All);
+
+ string? driver1 = null;
+ string? driver2 = null;
+ EventHandler>? initHandler1 = null;
+ EventHandler>? initHandler2 = null;
+ EventHandler? iterHandler1 = null;
+ EventHandler? iterHandler2 = null;
+
+ try
+ {
+ // Get two different scenarios to test
+ var scenarios = Scenario.GetScenarios ();
+ Assert.True (scenarios.Count >= 2, "Need at least 2 scenarios for this test");
+
+ var scenario1 = scenarios[0];
+ var scenario2 = scenarios[1];
+
+ _output.WriteLine ($"Testing with scenarios: {scenario1.GetName ()} and {scenario2.GetName ()}");
+
+ // Run scenario 1
+ initHandler1 = (s, e) =>
+ {
+ if (e.Value)
+ {
+ driver1 = Application.Driver?.GetName ();
+ iterHandler1 = (_, _) =>
+ {
+ Application.RequestStop ();
+ // Remove immediately to avoid assertions in Shutdown
+ if (iterHandler1 != null)
+ {
+ Application.Iteration -= iterHandler1;
+ iterHandler1 = null;
+ }
+ };
+ Application.Iteration += iterHandler1;
+ }
+ };
+
+ Application.InitializedChanged += initHandler1;
+ scenario1.Main ();
+ Application.InitializedChanged -= initHandler1;
+ scenario1.Dispose ();
+ _output.WriteLine ($"Scenario 1 completed with driver: {driver1}");
+
+ // Call UICatalog's reload method (this is part 2 of the fix in UICatalog.cs)
+ // This ensures ForceDriver persists across Init/Shutdown cycles
+ global::UICatalog.UICatalog.ReloadForceDriverConfig ();
+ _output.WriteLine ("Reloaded ForceDriver config via UICatalog.ReloadForceDriverConfig()");
+
+ // Run scenario 2
+ // Run scenario 2
+ initHandler2 = (s, e) =>
+ {
+ if (e.Value)
+ {
+ driver2 = Application.Driver?.GetName ();
+ iterHandler2 = (_, _) =>
+ {
+ Application.RequestStop ();
+ // Remove immediately to avoid assertions in Shutdown
+ if (iterHandler2 != null)
+ {
+ Application.Iteration -= iterHandler2;
+ iterHandler2 = null;
+ }
+ };
+ Application.Iteration += iterHandler2;
+ }
+ };
+
+ Application.InitializedChanged += initHandler2;
+ scenario2.Main ();
+ Application.InitializedChanged -= initHandler2;
+ scenario2.Dispose ();
+ _output.WriteLine ($"Scenario 2 completed with driver: {driver2}");
+
+ // Assert
+ Assert.Equal (expectedDriver, driver1);
+ Assert.Equal (expectedDriver, driver2);
+ _output.WriteLine ($"SUCCESS: Driver '{expectedDriver}' persisted across both scenarios");
+ }
+ finally
+ {
+ // Cleanup any remaining handlers
+ if (initHandler1 != null)
+ {
+ Application.InitializedChanged -= initHandler1;
+ }
+ if (iterHandler2 != null)
+ {
+ Application.InitializedChanged -= initHandler2;
+ }
+ if (iterHandler1 != null)
+ {
+ Application.Iteration -= iterHandler1;
+ }
+ if (iterHandler2 != null)
+ {
+ Application.Iteration -= iterHandler2;
+ }
+
+ ConfigurationManager.Disable (true);
+ Application.ResetState (true);
+ }
+ }
+}