Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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
85 changes: 83 additions & 2 deletions Examples/UICatalog/UICatalog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,31 @@
/// </remarks>
public class UICatalog
{
private static string? _forceDriver = null;
private static string? _forceDriver;
private static bool _forTesting;
private static bool _iterationHandlerRemoved;
private static string? _uiCatalogDriver;
private static string? _scenarioDriver;

Check warning on line 62 in Examples/UICatalog/UICatalog.cs

View workflow job for this annotation

GitHub Actions / Build All Configurations

Field 'UICatalog._scenarioDriver' is never assigned to, and will always have its default value null

Check warning on line 62 in Examples/UICatalog/UICatalog.cs

View workflow job for this annotation

GitHub Actions / Build All Configurations

Field 'UICatalog._scenarioDriver' is never assigned to, and will always have its default value null

public static string LogFilePath { get; set; } = string.Empty;
public static LoggingLevelSwitch LogLevelSwitch { get; } = new ();
public const string LOGFILE_LOCATION = "logs";
public static UICatalogCommandLineOptions Options { get; set; }

public static (string? UiCatalogDriver, string? ScenarioDriver) Run (string [] args)
{
// Flag for testing
_forTesting = true;

// Start app
Main (args);

// Unset testing flag
_forTesting = false;

return new (_uiCatalogDriver, _scenarioDriver);
}

private static int Main (string [] args)
{
Console.OutputEncoding = Encoding.Default;
Expand Down Expand Up @@ -194,6 +212,8 @@

UICatalogMain (Options);

Debug.Assert (Application.ForceDriver == string.Empty);

return 0;
}

Expand Down Expand Up @@ -247,6 +267,13 @@
/// <returns></returns>
private static Scenario RunUICatalogTopLevel ()
{
if (_iterationHandlerRemoved)
{
_iterationHandlerRemoved = false;

return null!;
}

// Run UI Catalog UI. When it exits, if _selectedScenario is != null then
// a Scenario was selected. Otherwise, the user wants to quit UI Catalog.

Expand All @@ -255,7 +282,22 @@

Application.Init (driverName: _forceDriver);

var top = Application.Run<UICatalogTop> ();
Toplevel top;

if (_forTesting)
{
top = new UICatalogTop ();
SessionToken sessionToken = Application.Begin (top);
UICatalogTop.CachedSelectedScenario = Scenario.GetScenarios () [0];
Application.End (sessionToken);

_uiCatalogDriver = Application.Driver!.GetName ();
}
else
{
top = Application.Run<UICatalogTop> ();
}

top.Dispose ();
Application.Shutdown ();
VerifyObjectsWereDisposed ();
Expand Down Expand Up @@ -412,6 +454,8 @@
Application.InitializedChanged += ApplicationOnInitializedChanged;
#endif

Application.ForceDriver = _forceDriver;

scenario.Main ();
scenario.Dispose ();

Expand All @@ -430,6 +474,43 @@
if (e.Value)
{
sw.Start ();

if (_forTesting)
{
int iterationCount = 0;
Key quitKey;

Application.Iteration += OnApplicationOnIteration;

void OnApplicationOnIteration (object? s, IterationEventArgs a)
{
iterationCount++;

if (Application.Initialized)
{
// Press QuitKey
quitKey = Application.QuitKey;

Logging.Trace (
$"Attempting to quit with {quitKey} after {iterationCount} iterations.");

try
{
Application.RaiseKeyDownEvent (quitKey);
}
catch (Exception ex)
{
Logging.Trace (
$"Exception raising quit key: {ex.Message}");
}

Application.Iteration -= OnApplicationOnIteration;
_iterationHandlerRemoved = true;

_scenarioDriver = Application.Driver?.GetName ();
}
}
}
}
else
{
Expand Down
16 changes: 15 additions & 1 deletion Terminal.Gui/App/Application.Driver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,21 @@ public static bool Force16Colors
public static string ForceDriver
{
get => ApplicationImpl.Instance.ForceDriver;
set => ApplicationImpl.Instance.ForceDriver = value;
set
{
if (!string.IsNullOrEmpty (ApplicationImpl.Instance.ForceDriver) && value != Driver?.GetName ())
{
// ForceDriver cannot be changed if it has a valid value
return;
}

if (ApplicationImpl.Instance.Initialized && value != Driver?.GetName ())
{
throw new InvalidOperationException ($"The {nameof (ForceDriver)} can only be set before initialized.");
}

ApplicationImpl.Instance.ForceDriver = value;
}
}

/// <inheritdoc cref="IApplication.Sixel"/>
Expand Down
2 changes: 1 addition & 1 deletion Terminal.Gui/App/ApplicationImpl.Driver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ private void CreateDriver (string? driverName)
bool factoryIsFake = _componentFactory is IComponentFactory<ConsoleKeyInfo>;

// Then check driverName
bool nameIsWindows = driverName?.Contains ("win", StringComparison.OrdinalIgnoreCase) ?? false;
bool nameIsWindows = driverName?.Contains ("windows", StringComparison.OrdinalIgnoreCase) ?? false;
bool nameIsDotNet = driverName?.Contains ("dotnet", StringComparison.OrdinalIgnoreCase) ?? false;
bool nameIsUnix = driverName?.Contains ("unix", StringComparison.OrdinalIgnoreCase) ?? false;
bool nameIsFake = driverName?.Contains ("fake", StringComparison.OrdinalIgnoreCase) ?? false;
Expand Down
23 changes: 19 additions & 4 deletions Terminal.Gui/App/ApplicationImpl.Lifecycle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@
_driverName = ForceDriver;
}

if (string.IsNullOrWhiteSpace (_driverName) && driver is { })
{
_driverName = driver.GetName ();
}

Debug.Assert (Navigation is null);
Navigation = new ();

Expand Down Expand Up @@ -63,7 +68,7 @@
_keyboard.PrevTabGroupKey = existingPrevTabGroupKey;
}

CreateDriver (driverName ?? _driverName);
CreateDriver (_driverName);
Screen = Driver!.Screen;
Initialized = true;

Expand Down Expand Up @@ -149,9 +154,13 @@
}
#endif

private bool _isResetingState;

Check warning on line 157 in Terminal.Gui/App/ApplicationImpl.Lifecycle.cs

View workflow job for this annotation

GitHub Actions / build / Build Debug Only

The field 'ApplicationImpl._isResetingState' is assigned but its value is never used

Check warning on line 157 in Terminal.Gui/App/ApplicationImpl.Lifecycle.cs

View workflow job for this annotation

GitHub Actions / build / Build Debug Only

The field 'ApplicationImpl._isResetingState' is assigned but its value is never used

Check warning on line 157 in Terminal.Gui/App/ApplicationImpl.Lifecycle.cs

View workflow job for this annotation

GitHub Actions / Build All Configurations

The field 'ApplicationImpl._isResetingState' is assigned but its value is never used

Check warning on line 157 in Terminal.Gui/App/ApplicationImpl.Lifecycle.cs

View workflow job for this annotation

GitHub Actions / Build All Configurations

The field 'ApplicationImpl._isResetingState' is assigned but its value is never used

Check warning on line 157 in Terminal.Gui/App/ApplicationImpl.Lifecycle.cs

View workflow job for this annotation

GitHub Actions / Build All Configurations

The field 'ApplicationImpl._isResetingState' is assigned but its value is never used

Check warning on line 157 in Terminal.Gui/App/ApplicationImpl.Lifecycle.cs

View workflow job for this annotation

GitHub Actions / Build All Configurations

The field 'ApplicationImpl._isResetingState' is assigned but its value is never used

Check warning on line 157 in Terminal.Gui/App/ApplicationImpl.Lifecycle.cs

View workflow job for this annotation

GitHub Actions / build / Build Debug Only

The field 'ApplicationImpl._isResetingState' is assigned but its value is never used

Check warning on line 157 in Terminal.Gui/App/ApplicationImpl.Lifecycle.cs

View workflow job for this annotation

GitHub Actions / build / Build Debug Only

The field 'ApplicationImpl._isResetingState' is assigned but its value is never used

/// <inheritdoc/>
public void ResetState (bool ignoreDisposed = false)
{
_isResetingState = true;

// Shutdown is the bookend for Init. As such it needs to clean up all resources
// Init created. Apps that do any threading will need to code defensively for this.
// e.g. see Issue #537
Expand Down Expand Up @@ -231,7 +240,14 @@
// === 9. Clear graphics ===
Sixel.Clear ();

// === 10. Reset synchronization context ===
// === 10. Reset ForceDriver ===
// Note: ForceDriver and Force16Colors are reset
// If they need to persist across Init/Shutdown cycles
// then the user of the library should manage that state
Force16Colors = false;
ForceDriver = string.Empty;

// === 11. Reset synchronization context ===
// IMPORTANT: Always reset sync context, even if not initialized
// This ensures cleanup works correctly even if Shutdown is called without Init
// Reset synchronization context to allow the user to run async/await,
Expand All @@ -240,8 +256,7 @@
// (https://github.com/gui-cs/Terminal.Gui/issues/1084).
SynchronizationContext.SetSynchronizationContext (null);

// Note: ForceDriver and Force16Colors are NOT reset;
// they need to persist across Init/Shutdown cycles
_isResetingState = false;
}

/// <summary>
Expand Down
1 change: 1 addition & 0 deletions Terminal.Gui/App/Mouse/MouseImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ public void ResetState ()
// Do not clear LastMousePosition; Popover's require it to stay set with last mouse pos.
CachedViewsUnderMouse.Clear ();
MouseEvent = null;
MouseGrabView = null;
}

// Mouse grab functionality merged from MouseGrabHandler
Expand Down
15 changes: 15 additions & 0 deletions Tests/IntegrationTests/UICatalog/ScenarioTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -665,4 +665,19 @@ void UpdateSettings (View view)

void LayoutCompleteHandler (object? sender, LayoutEventArgs args) { UpdateTitle (curView); }
}

[Fact]
public void ForceDriver_persists_On_Open_Scenario_With_Args ()
{
string driverName = "fake";

string [] args = ["-d", driverName];

(string? UiCatalogDriver, string? ScenarioDriver) driverNames = global::UICatalog.UICatalog.Run (args);

Assert.Equal (string.Empty, Application.ForceDriver);
Assert.Equal (driverName, driverNames.UiCatalogDriver);
Assert.Equal (driverName, driverNames.ScenarioDriver);
Assert.Equal (driverNames.UiCatalogDriver, driverNames.ScenarioDriver);
}
}
18 changes: 12 additions & 6 deletions Tests/UnitTests/FakeDriverBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace UnitTests;
/// Enables tests to create a FakeDriver for testing purposes.
/// </summary>
[Collection ("Global Test Setup")]
public abstract class FakeDriverBase
public abstract class FakeDriverBase : IDisposable
{
/// <summary>
/// Creates a new FakeDriver instance with the specified buffer size.
Expand All @@ -19,14 +19,20 @@ protected static IDriver CreateFakeDriver (int width = 80, int height = 25)
var output = new FakeOutput ();

DriverImpl driver = new (
new FakeInputProcessor (null),
new OutputBufferImpl (),
output,
new AnsiRequestScheduler (new AnsiResponseParser ()),
new SizeMonitorImpl (output));
new FakeInputProcessor (null),
new OutputBufferImpl (),
output,
new AnsiRequestScheduler (new AnsiResponseParser ()),
new SizeMonitorImpl (output));

driver.SetScreenSize (width, height);

return driver;
}

/// <inheritdoc />
public void Dispose ()
{
Application.ResetState (true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using UnitTests;

namespace UnitTests_Parallelizable.ApplicationTests;

public class ApplicationForceDriverTests : FakeDriverBase
{
[Fact]
public void ForceDriver_Does_Not_Changes_If_It_Has_Valid_Value ()
{
Assert.False (Application.Initialized);
Assert.Null (Application.Driver);
Assert.Equal (string.Empty, Application.ForceDriver);

Application.ForceDriver = "fake";
Assert.Equal ("fake", Application.ForceDriver);

Application.ForceDriver = "dotnet";
Assert.Equal ("fake", Application.ForceDriver);
}

[Fact]
public void ForceDriver_Throws_If_Initialized_Changed_To_Another_Value ()
{
IDriver driver = CreateFakeDriver ();

Assert.False (Application.Initialized);
Assert.Null (Application.Driver);
Assert.Equal (string.Empty, Application.ForceDriver);

Application.Init (driver);
Assert.True (Application.Initialized);
Assert.NotNull (Application.Driver);
Assert.Equal ("fake", Application.Driver.GetName ());
Assert.Equal (string.Empty, Application.ForceDriver);

Assert.Throws<InvalidOperationException> (() => Application.ForceDriver = "dotnet");

Application.ForceDriver = "fake";
Assert.Equal ("fake", Application.ForceDriver);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#nullable enable
using Moq;
using UnitTests.Parallelizable;

namespace UnitTests_Parallelizable.ApplicationTests;

public class ApplicationPopoverTests
public class ApplicationPopoverTests : GlobalTestSetup
{
[Fact]
public void Register_AddsPopover ()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
using System.Collections.Immutable;
using System.Diagnostics;
using System.Text;
using UnitTests.Parallelizable;

namespace UnitTests_Parallelizable.ConfigurationTests;

/// <summary>
/// Unit tests for the <see cref="DeepCloner"/> class, ensuring robust deep cloning for
/// Terminal.Gui's configuration system.
/// </summary>
public class DeepClonerTests
public class DeepClonerTests : GlobalTestSetup
{
// Test classes for complex scenarios
private class SimpleValueType
Expand Down
5 changes: 3 additions & 2 deletions Tests/UnitTestsParallelizable/Drivers/KeyCodeTests.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
using Xunit.Abstractions;
using UnitTests.Parallelizable;
using Xunit.Abstractions;

namespace UnitTests_Parallelizable.DriverTests;

public class KeyCodeTests
public class KeyCodeTests : GlobalTestSetup
{
private readonly ITestOutputHelper _output;
public KeyCodeTests (ITestOutputHelper output) { _output = output; }
Expand Down
4 changes: 2 additions & 2 deletions Tests/UnitTestsParallelizable/TestSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ public void Dispose ()
// Reset application state just in case a test changed something.
// TODO: Add an Assert to ensure none of the state of Application changed.
// TODO: Add an Assert to ensure none of the state of ConfigurationManager changed.
CheckDefaultState ();
Application.ResetState (true);
CheckDefaultState ();
}

// IMPORTANT: Ensure this matches the code in Init_ResetState_Resets_Properties
Expand All @@ -43,7 +43,7 @@ private void CheckDefaultState ()
Assert.Null (Application.Mouse.MouseGrabView);

// Don't check Application.ForceDriver
// Assert.Empty (Application.ForceDriver);
Assert.Empty (Application.ForceDriver);
// Don't check Application.Force16Colors
//Assert.False (Application.Force16Colors);
Assert.Null (Application.Driver);
Expand Down
Loading
Loading