Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions Examples/UICatalog/UICatalog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 ();

Expand Down Expand Up @@ -451,7 +468,11 @@ void ApplicationOnInitializedChanged (object? sender, EventArgs<bool> e)
scenario.StartBenchmark ();
}

Application.ForceDriver = _forceDriver!;
// For benchmarking without ConfigurationManager, set ForceDriver directly
if (!ConfigurationManager.IsEnabled && !string.IsNullOrEmpty (_forceDriver))
{
Application.ForceDriver = _forceDriver;
}

scenario.Main ();

Expand Down
303 changes: 303 additions & 0 deletions Tests/IntegrationTests/UICatalog/ForceDriverTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
using UICatalog;
using Xunit.Abstractions;

namespace IntegrationTests.UICatalog;

/// <summary>
/// Integration tests for ForceDriver persistence when opening scenarios in UICatalog.
/// </summary>
public class ForceDriverTests
{
private readonly ITestOutputHelper _output;

public ForceDriverTests (ITestOutputHelper output)
{
_output = output;
}

/// <summary>
/// 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.
/// </summary>
[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 ();

// 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 topLevelDriverName = string.Empty;
var scenarioDriverName = string.Empty;
var iterationCount = 0;
EventHandler<IterationEventArgs>? 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 ()}' ===");

// 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<EventArgs<bool>>? initHandler = null;
EventHandler<IterationEventArgs>? 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);
}
}

/// <summary>
/// Tests that ForceDriver persists when running multiple scenarios in sequence.
/// This verifies the scenario loop in UICatalog works correctly.
/// </summary>
[Fact]
public void ForceDriver_Persists_Across_Multiple_Scenarios ()
{
// Arrange
const string expectedDriver = "fake";

ConfigurationManager.Disable (true);
Application.ResetState (true);

// Set ForceDriver in RuntimeConfig
ConfigurationManager.RuntimeConfig = $$"""
{
"Application.ForceDriver": "{{expectedDriver}}"
}
""";

// Enable ConfigurationManager
ConfigurationManager.Enable (ConfigLocations.All);

string? driver1 = null;
string? driver2 = null;
EventHandler<EventArgs<bool>>? initHandler1 = null;
EventHandler<EventArgs<bool>>? initHandler2 = null;
EventHandler<IterationEventArgs>? iterHandler1 = null;
EventHandler<IterationEventArgs>? 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}");

// Reload RuntimeConfig before scenario 2 (as the fix does)
ConfigurationManager.Load (ConfigLocations.Runtime);
ConfigurationManager.Apply ();
_output.WriteLine ("Reloaded RuntimeConfig");

// 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);
}
}
}
Loading