From 358a3e64b97ddf8a0005e801614545017fd505dc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 15 Nov 2025 16:28:23 +0000 Subject: [PATCH 1/7] Initial plan From 12d58cc082904509ef4996fb8c74b3ba1ec10d26 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 15 Nov 2025 16:37:19 +0000 Subject: [PATCH 2/7] Fix ForceDriver persistence through RuntimeConfig - Set ForceDriver in ConfigurationManager.RuntimeConfig instead of directly on Application.ForceDriver - Reload RuntimeConfig before each scenario to ensure driver persists - Remove redundant Application.ForceDriver assignment in RunScenario Co-authored-by: tig <585482+tig@users.noreply.github.com> --- Examples/UICatalog/UICatalog.cs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/Examples/UICatalog/UICatalog.cs b/Examples/UICatalog/UICatalog.cs index 6f7a371390..ae546c1bcc 100644 --- a/Examples/UICatalog/UICatalog.cs +++ b/Examples/UICatalog/UICatalog.cs @@ -346,7 +346,17 @@ private 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; + _forceDriver = options.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}}" + } + """; + } // If a Scenario name has been provided on the commandline // run it and exit when done. @@ -412,6 +422,13 @@ private static void UICatalogMain (UICatalogCommandLineOptions options) Application.InitializedChanged += ApplicationOnInitializedChanged; #endif + // Ensure RuntimeConfig is applied before each scenario to preserve ForceDriver setting + if (!Options.DontEnableConfigurationManagement && !string.IsNullOrEmpty (_forceDriver)) + { + ConfigurationManager.Load (ConfigLocations.Runtime); + ConfigurationManager.Apply (); + } + scenario.Main (); scenario.Dispose (); @@ -451,8 +468,6 @@ void ApplicationOnInitializedChanged (object? sender, EventArgs e) scenario.StartBenchmark (); } - Application.ForceDriver = _forceDriver!; - scenario.Main (); BenchmarkResults? results = null; From 560f56bc778a0f2eed1873f16e96ed0c822ebfe8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 15 Nov 2025 16:43:59 +0000 Subject: [PATCH 3/7] Handle benchmark path without ConfigurationManager - Set ForceDriver directly when ConfigurationManager is not enabled - Ensures benchmarks work correctly with --driver option Co-authored-by: tig <585482+tig@users.noreply.github.com> --- Examples/UICatalog/UICatalog.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Examples/UICatalog/UICatalog.cs b/Examples/UICatalog/UICatalog.cs index ae546c1bcc..71260edf28 100644 --- a/Examples/UICatalog/UICatalog.cs +++ b/Examples/UICatalog/UICatalog.cs @@ -468,6 +468,12 @@ void ApplicationOnInitializedChanged (object? sender, EventArgs e) scenario.StartBenchmark (); } + // For benchmarking without ConfigurationManager, set ForceDriver directly + if (!ConfigurationManager.IsEnabled && !string.IsNullOrEmpty (_forceDriver)) + { + Application.ForceDriver = _forceDriver; + } + scenario.Main (); BenchmarkResults? results = null; From 0d6debde633d5e3e5b5222c98a40831661348fa9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 15 Nov 2025 21:13:50 +0000 Subject: [PATCH 4/7] Add integration tests for ForceDriver persistence - Created ForceDriverTests.cs with two integration tests - Tests verify ForceDriver persists across Init/Shutdown cycles - Tests verify scenarios use ForceDriver when calling Application.Init() - Both tests pass successfully Co-authored-by: tig <585482+tig@users.noreply.github.com> --- .../UICatalog/ForceDriverTests.cs | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 Tests/IntegrationTests/UICatalog/ForceDriverTests.cs diff --git a/Tests/IntegrationTests/UICatalog/ForceDriverTests.cs b/Tests/IntegrationTests/UICatalog/ForceDriverTests.cs new file mode 100644 index 0000000000..81d4127b99 --- /dev/null +++ b/Tests/IntegrationTests/UICatalog/ForceDriverTests.cs @@ -0,0 +1,134 @@ +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 opening a scenario after Init/Shutdown cycles. + /// This verifies the fix for issue #4391. + /// + [Fact] + public void ForceDriver_Persists_Across_Init_Shutdown_Cycles () + { + // Arrange + const string expectedDriver = "fake"; + + ConfigurationManager.Disable (true); + Application.ResetState (true); + + // Set ForceDriver in RuntimeConfig (simulating what UICatalog does with --driver option) + ConfigurationManager.RuntimeConfig = $$""" + { + "Application.ForceDriver": "{{expectedDriver}}" + } + """; + + // Enable ConfigurationManager with all locations (as UICatalog does) + ConfigurationManager.Enable (ConfigLocations.All); + + var firstDriverName = string.Empty; + var secondDriverName = string.Empty; + + try + { + // Act - Cycle 1: Init and check driver + _output.WriteLine ("Cycle 1: First Init"); + Application.Init (); + firstDriverName = Application.Driver?.GetName () ?? string.Empty; + _output.WriteLine ($"Cycle 1 driver: {firstDriverName}"); + Application.Shutdown (); + + // Act - Cycle 2: Reload RuntimeConfig and Init again (simulating scenario opening) + _output.WriteLine ("Cycle 2: Reload RuntimeConfig and Init again"); + + // This simulates what the fix does before each scenario + ConfigurationManager.Load (ConfigLocations.Runtime); + ConfigurationManager.Apply (); + + // Scenario calls Application.Init() without parameters + Application.Init (); + secondDriverName = Application.Driver?.GetName () ?? string.Empty; + _output.WriteLine ($"Cycle 2 driver: {secondDriverName}"); + Application.Shutdown (); + + // Assert + Assert.Equal (expectedDriver, firstDriverName); + Assert.Equal (expectedDriver, secondDriverName); + _output.WriteLine ($"SUCCESS: Driver '{expectedDriver}' persisted across Init/Shutdown cycles"); + } + finally + { + ConfigurationManager.Disable (true); + Application.ResetState (true); + } + } + + /// + /// Tests that ForceDriver is used when a scenario calls Application.Init() without parameters. + /// This simulates the actual UICatalog scenario execution flow. + /// + [Fact] + public void ForceDriver_Used_By_Scenario_Init () + { + // Arrange + const string expectedDriver = "fake"; + Scenario? scenario = null; + + ConfigurationManager.Disable (true); + Application.ResetState (true); + + // Set ForceDriver in RuntimeConfig + ConfigurationManager.RuntimeConfig = $$""" + { + "Application.ForceDriver": "{{expectedDriver}}" + } + """; + + // Enable ConfigurationManager + ConfigurationManager.Enable (ConfigLocations.All); + + try + { + // Get the first available scenario + var scenarios = Scenario.GetScenarios (); + Assert.NotEmpty (scenarios); + + scenario = scenarios[0]; + var scenarioName = scenario.GetName (); + _output.WriteLine ($"Testing with scenario: {scenarioName}"); + + // Reload RuntimeConfig before scenario (as the fix does) + ConfigurationManager.Load (ConfigLocations.Runtime); + ConfigurationManager.Apply (); + + // Scenario calls Application.Init() - it should use ForceDriver + Application.Init (); + var driverName = Application.Driver?.GetName () ?? string.Empty; + _output.WriteLine ($"Scenario driver: {driverName}"); + + // Assert + Assert.Equal (expectedDriver, driverName); + _output.WriteLine ($"SUCCESS: Scenario uses ForceDriver '{expectedDriver}'"); + + Application.Shutdown (); + } + finally + { + scenario?.Dispose (); + ConfigurationManager.Disable (true); + Application.ResetState (true); + } + } +} From f1a6534dca5f6a577d576abd56d67733e9cd562f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 15 Nov 2025 21:28:13 +0000 Subject: [PATCH 5/7] Rewrite integration tests to actually run scenarios - Tests now use UICatalogTop and call scenario.Main() - First test simulates full UICatalog flow: run UICatalogTop, select scenario, run scenario - Second test runs multiple scenarios in sequence to verify driver persistence - Both tests properly clean up event handlers to avoid assertions - Tests verify the fix works correctly by actually running scenarios Co-authored-by: tig <585482+tig@users.noreply.github.com> --- .../UICatalog/ForceDriverTests.cs | 253 +++++++++++++++--- 1 file changed, 211 insertions(+), 42 deletions(-) diff --git a/Tests/IntegrationTests/UICatalog/ForceDriverTests.cs b/Tests/IntegrationTests/UICatalog/ForceDriverTests.cs index 81d4127b99..944c871b73 100644 --- a/Tests/IntegrationTests/UICatalog/ForceDriverTests.cs +++ b/Tests/IntegrationTests/UICatalog/ForceDriverTests.cs @@ -16,11 +16,11 @@ public ForceDriverTests (ITestOutputHelper output) } /// - /// Tests that ForceDriver persists when opening a scenario after Init/Shutdown cycles. - /// This verifies the fix for issue #4391. + /// Tests that ForceDriver persists when running UICatalogTop and then opening a scenario. + /// This simulates the actual UICatalog flow and verifies the fix for issue #4391. /// [Fact] - public void ForceDriver_Persists_Across_Init_Shutdown_Cycles () + public void ForceDriver_Persists_From_UICatalogTop_To_Scenario () { // Arrange const string expectedDriver = "fake"; @@ -28,6 +28,21 @@ public void ForceDriver_Persists_Across_Init_Shutdown_Cycles () 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 (); + // Set ForceDriver in RuntimeConfig (simulating what UICatalog does with --driver option) ConfigurationManager.RuntimeConfig = $$""" { @@ -38,54 +53,136 @@ public void ForceDriver_Persists_Across_Init_Shutdown_Cycles () // Enable ConfigurationManager with all locations (as UICatalog does) ConfigurationManager.Enable (ConfigLocations.All); - var firstDriverName = string.Empty; - var secondDriverName = string.Empty; + var topLevelDriverName = string.Empty; + var scenarioDriverName = string.Empty; + var iterationCount = 0; + EventHandler? iterationHandler = null; try { - // Act - Cycle 1: Init and check driver - _output.WriteLine ("Cycle 1: First Init"); + // Phase 1: Run UICatalogTop (simulating main UI) + _output.WriteLine ("=== Phase 1: Running UICatalogTop ==="); Application.Init (); - firstDriverName = Application.Driver?.GetName () ?? string.Empty; - _output.WriteLine ($"Cycle 1 driver: {firstDriverName}"); - Application.Shutdown (); + topLevelDriverName = Application.Driver?.GetName () ?? string.Empty; + _output.WriteLine ($"UICatalogTop driver: {topLevelDriverName}"); - // Act - Cycle 2: Reload RuntimeConfig and Init again (simulating scenario opening) - _output.WriteLine ("Cycle 2: Reload RuntimeConfig and Init again"); + var top = new UICatalogTop (); - // This simulates what the fix does before each scenario - ConfigurationManager.Load (ConfigLocations.Runtime); - ConfigurationManager.Apply (); + // 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; - // Scenario calls Application.Init() without parameters - Application.Init (); - secondDriverName = Application.Driver?.GetName () ?? string.Empty; - _output.WriteLine ($"Cycle 2 driver: {secondDriverName}"); + 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 ()}' ==="); + + // Reload RuntimeConfig before scenario (as the fix does) + if (!global::UICatalog.UICatalog.Options.DontEnableConfigurationManagement) + { + ConfigurationManager.Load (ConfigLocations.Runtime); + ConfigurationManager.Apply (); + _output.WriteLine ("Reloaded RuntimeConfig"); + } + + // 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 - Assert.Equal (expectedDriver, firstDriverName); - Assert.Equal (expectedDriver, secondDriverName); - _output.WriteLine ($"SUCCESS: Driver '{expectedDriver}' persisted across Init/Shutdown cycles"); + _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 is used when a scenario calls Application.Init() without parameters. - /// This simulates the actual UICatalog scenario execution flow. + /// Tests that ForceDriver persists when running multiple scenarios in sequence. + /// This verifies the scenario loop in UICatalog works correctly. /// [Fact] - public void ForceDriver_Used_By_Scenario_Init () + public void ForceDriver_Persists_Across_Multiple_Scenarios () { // Arrange const string expectedDriver = "fake"; - Scenario? scenario = null; - + ConfigurationManager.Disable (true); Application.ResetState (true); @@ -99,34 +196,106 @@ public void ForceDriver_Used_By_Scenario_Init () // 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 the first available scenario + // Get two different scenarios to test var scenarios = Scenario.GetScenarios (); - Assert.NotEmpty (scenarios); + 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; + } + }; - scenario = scenarios[0]; - var scenarioName = scenario.GetName (); - _output.WriteLine ($"Testing with scenario: {scenarioName}"); + Application.InitializedChanged += initHandler1; + scenario1.Main (); + Application.InitializedChanged -= initHandler1; + scenario1.Dispose (); + _output.WriteLine ($"Scenario 1 completed with driver: {driver1}"); - // Reload RuntimeConfig before scenario (as the fix does) + // Reload RuntimeConfig before scenario 2 (as the fix does) ConfigurationManager.Load (ConfigLocations.Runtime); ConfigurationManager.Apply (); + _output.WriteLine ("Reloaded RuntimeConfig"); - // Scenario calls Application.Init() - it should use ForceDriver - Application.Init (); - var driverName = Application.Driver?.GetName () ?? string.Empty; - _output.WriteLine ($"Scenario driver: {driverName}"); + // 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, driverName); - _output.WriteLine ($"SUCCESS: Scenario uses ForceDriver '{expectedDriver}'"); - - Application.Shutdown (); + Assert.Equal (expectedDriver, driver1); + Assert.Equal (expectedDriver, driver2); + _output.WriteLine ($"SUCCESS: Driver '{expectedDriver}' persisted across both scenarios"); } finally { - scenario?.Dispose (); + // 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); } From e809567010e837c4e075f6b7a5111784130b6a38 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 16 Nov 2025 00:19:02 +0000 Subject: [PATCH 6/7] Improve test documentation to clarify they verify the fix - Added detailed comments explaining the fix has two parts - Part 1: Set ForceDriver in RuntimeConfig (in UICatalogMain) - Part 2: Reload RuntimeConfig before each scenario (in scenario loop) - Tests simulate both parts and verify driver persists - Added note that commenting out the reload causes the test to fail, demonstrating the bug Co-authored-by: tig <585482+tig@users.noreply.github.com> --- .../UICatalog/ForceDriverTests.cs | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/Tests/IntegrationTests/UICatalog/ForceDriverTests.cs b/Tests/IntegrationTests/UICatalog/ForceDriverTests.cs index 944c871b73..a279d9f9d1 100644 --- a/Tests/IntegrationTests/UICatalog/ForceDriverTests.cs +++ b/Tests/IntegrationTests/UICatalog/ForceDriverTests.cs @@ -17,7 +17,18 @@ public ForceDriverTests (ITestOutputHelper output) /// /// Tests that ForceDriver persists when running UICatalogTop and then opening a scenario. - /// This simulates the actual UICatalog flow and verifies the fix for issue #4391. + /// + /// This test verifies the fix for issue #4391 works correctly. + /// + /// 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: + /// 1. Set ForceDriver in ConfigurationManager.RuntimeConfig (done in UICatalog.UICatalogMain) + /// 2. Reload RuntimeConfig before each scenario (done in UICatalog scenario loop) + /// + /// This test simulates both parts of the fix and verifies the driver persists. + /// Without BOTH parts of the fix, this test would fail. /// [Fact] public void ForceDriver_Persists_From_UICatalogTop_To_Scenario () @@ -43,7 +54,8 @@ public void ForceDriver_Persists_From_UICatalogTop_To_Scenario () // Initialize cached scenarios (required by UICatalogTop) UICatalogTop.CachedScenarios = Scenario.GetScenarios (); - // Set ForceDriver in RuntimeConfig (simulating what UICatalog does with --driver option) + // THE FIX (part 1): UICatalog.UICatalogMain() sets ForceDriver in RuntimeConfig + // Since we can't call the private UICatalogMain() from tests, we simulate it here ConfigurationManager.RuntimeConfig = $$""" { "Application.ForceDriver": "{{expectedDriver}}" @@ -101,12 +113,14 @@ public void ForceDriver_Persists_From_UICatalogTop_To_Scenario () { _output.WriteLine ($"\n=== Phase 2: Running scenario '{scenario.GetName ()}' ==="); - // Reload RuntimeConfig before scenario (as the fix does) - if (!global::UICatalog.UICatalog.Options.DontEnableConfigurationManagement) + // THE FIX (part 2): UICatalog reloads RuntimeConfig before each scenario + // This ensures ForceDriver persists across Init/Shutdown cycles + // NOTE: If you comment out this block, the test will fail, demonstrating the bug + if (!global::UICatalog.UICatalog.Options.DontEnableConfigurationManagement && !string.IsNullOrEmpty(global::UICatalog.UICatalog.Options.Driver)) { ConfigurationManager.Load (ConfigLocations.Runtime); ConfigurationManager.Apply (); - _output.WriteLine ("Reloaded RuntimeConfig"); + _output.WriteLine ("Reloaded RuntimeConfig (this is part 2 of the fix)"); } // Track the driver used inside the scenario @@ -175,7 +189,11 @@ public void ForceDriver_Persists_From_UICatalogTop_To_Scenario () /// /// Tests that ForceDriver persists when running multiple scenarios in sequence. - /// This verifies the scenario loop in UICatalog works correctly. + /// + /// This verifies the fix works correctly when running multiple scenarios. + /// + /// THE FIX: Reload RuntimeConfig before each scenario to preserve ForceDriver. + /// Without reloading RuntimeConfig, the driver would revert to platform default. /// [Fact] public void ForceDriver_Persists_Across_Multiple_Scenarios () @@ -186,7 +204,8 @@ public void ForceDriver_Persists_Across_Multiple_Scenarios () ConfigurationManager.Disable (true); Application.ResetState (true); - // Set ForceDriver in RuntimeConfig + // THE FIX (part 1): Set ForceDriver in RuntimeConfig + // This is what UICatalog.UICatalogMain() does when --driver is specified ConfigurationManager.RuntimeConfig = $$""" { "Application.ForceDriver": "{{expectedDriver}}" @@ -240,10 +259,11 @@ public void ForceDriver_Persists_Across_Multiple_Scenarios () scenario1.Dispose (); _output.WriteLine ($"Scenario 1 completed with driver: {driver1}"); - // Reload RuntimeConfig before scenario 2 (as the fix does) + // THE FIX (part 2): Reload RuntimeConfig before scenario 2 + // This ensures ForceDriver persists across Init/Shutdown cycles ConfigurationManager.Load (ConfigLocations.Runtime); ConfigurationManager.Apply (); - _output.WriteLine ("Reloaded RuntimeConfig"); + _output.WriteLine ("Reloaded RuntimeConfig (this is part 2 of the fix)"); // Run scenario 2 initHandler2 = (s, e) => From e8b40dc9b7c69c1aef78781e8dd162c2fb35c4cf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 16 Nov 2025 02:56:39 +0000 Subject: [PATCH 7/7] Move fix logic into UICatalog methods that tests can call - Added SetupForceDriverConfig() and ReloadForceDriverConfig() methods to UICatalog.cs - These methods contain the fix logic (parts 1 and 2) - Made methods internal and added InternalsVisibleTo for IntegrationTests - Updated UICatalogMain and scenario loop to use these methods - Updated tests to call UICatalog's methods instead of duplicating the logic - Tests now fail without the fix and pass with it, proving they test the actual UICatalog code - All 91 UICatalog integration tests pass Co-authored-by: tig <585482+tig@users.noreply.github.com> --- Examples/UICatalog/UICatalog.cs | 56 ++++++++++----- Examples/UICatalog/UICatalog.csproj | 3 + .../UICatalog/ForceDriverTests.cs | 69 +++++++++---------- 3 files changed, 75 insertions(+), 53 deletions(-) diff --git a/Examples/UICatalog/UICatalog.cs b/Examples/UICatalog/UICatalog.cs index 71260edf28..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,21 +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. - _forceDriver = options.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}}" - } - """; - } + SetupForceDriverConfig (options.Driver); // If a Scenario name has been provided on the commandline // run it and exit when done. @@ -422,12 +446,8 @@ private static void UICatalogMain (UICatalogCommandLineOptions options) Application.InitializedChanged += ApplicationOnInitializedChanged; #endif - // Ensure RuntimeConfig is applied before each scenario to preserve ForceDriver setting - if (!Options.DontEnableConfigurationManagement && !string.IsNullOrEmpty (_forceDriver)) - { - ConfigurationManager.Load (ConfigLocations.Runtime); - ConfigurationManager.Apply (); - } + // Reload RuntimeConfig to ensure ForceDriver persists (part 2 of the fix) + ReloadForceDriverConfig (); scenario.Main (); scenario.Dispose (); 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 index a279d9f9d1..f36b5c4374 100644 --- a/Tests/IntegrationTests/UICatalog/ForceDriverTests.cs +++ b/Tests/IntegrationTests/UICatalog/ForceDriverTests.cs @@ -18,17 +18,18 @@ public ForceDriverTests (ITestOutputHelper output) /// /// Tests that ForceDriver persists when running UICatalogTop and then opening a scenario. /// - /// This test verifies the fix for issue #4391 works correctly. + /// 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: - /// 1. Set ForceDriver in ConfigurationManager.RuntimeConfig (done in UICatalog.UICatalogMain) - /// 2. Reload RuntimeConfig before each scenario (done in UICatalog scenario loop) + /// 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 simulates both parts of the fix and verifies the driver persists. - /// Without BOTH parts of the fix, this test would fail. + /// 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 () @@ -54,13 +55,9 @@ public void ForceDriver_Persists_From_UICatalogTop_To_Scenario () // Initialize cached scenarios (required by UICatalogTop) UICatalogTop.CachedScenarios = Scenario.GetScenarios (); - // THE FIX (part 1): UICatalog.UICatalogMain() sets ForceDriver in RuntimeConfig - // Since we can't call the private UICatalogMain() from tests, we simulate it here - ConfigurationManager.RuntimeConfig = $$""" - { - "Application.ForceDriver": "{{expectedDriver}}" - } - """; + // 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); @@ -113,15 +110,10 @@ public void ForceDriver_Persists_From_UICatalogTop_To_Scenario () { _output.WriteLine ($"\n=== Phase 2: Running scenario '{scenario.GetName ()}' ==="); - // THE FIX (part 2): UICatalog reloads RuntimeConfig before each scenario + // Call UICatalog's reload method (this is part 2 of the fix in UICatalog.cs) // This ensures ForceDriver persists across Init/Shutdown cycles - // NOTE: If you comment out this block, the test will fail, demonstrating the bug - if (!global::UICatalog.UICatalog.Options.DontEnableConfigurationManagement && !string.IsNullOrEmpty(global::UICatalog.UICatalog.Options.Driver)) - { - ConfigurationManager.Load (ConfigLocations.Runtime); - ConfigurationManager.Apply (); - _output.WriteLine ("Reloaded RuntimeConfig (this is part 2 of the fix)"); - } + global::UICatalog.UICatalog.ReloadForceDriverConfig (); + _output.WriteLine ("Reloaded ForceDriver config via UICatalog.ReloadForceDriverConfig()"); // Track the driver used inside the scenario string? driverInsideScenario = null; @@ -190,10 +182,10 @@ public void ForceDriver_Persists_From_UICatalogTop_To_Scenario () /// /// Tests that ForceDriver persists when running multiple scenarios in sequence. /// - /// This verifies the fix works correctly when running multiple scenarios. + /// This verifies the fix works correctly by calling UICatalog's ReloadForceDriverConfig() method. /// - /// THE FIX: Reload RuntimeConfig before each scenario to preserve ForceDriver. - /// Without reloading RuntimeConfig, the driver would revert to platform default. + /// 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 () @@ -204,13 +196,20 @@ public void ForceDriver_Persists_Across_Multiple_Scenarios () ConfigurationManager.Disable (true); Application.ResetState (true); - // THE FIX (part 1): Set ForceDriver in RuntimeConfig - // This is what UICatalog.UICatalogMain() does when --driver is specified - ConfigurationManager.RuntimeConfig = $$""" - { - "Application.ForceDriver": "{{expectedDriver}}" - } - """; + // 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); @@ -259,12 +258,12 @@ public void ForceDriver_Persists_Across_Multiple_Scenarios () scenario1.Dispose (); _output.WriteLine ($"Scenario 1 completed with driver: {driver1}"); - // THE FIX (part 2): Reload RuntimeConfig before scenario 2 + // Call UICatalog's reload method (this is part 2 of the fix in UICatalog.cs) // This ensures ForceDriver persists across Init/Shutdown cycles - ConfigurationManager.Load (ConfigLocations.Runtime); - ConfigurationManager.Apply (); - _output.WriteLine ("Reloaded RuntimeConfig (this is part 2 of the fix)"); + global::UICatalog.UICatalog.ReloadForceDriverConfig (); + _output.WriteLine ("Reloaded ForceDriver config via UICatalog.ReloadForceDriverConfig()"); + // Run scenario 2 // Run scenario 2 initHandler2 = (s, e) => {