2
0
Эх сурвалжийг харах

Fixes #4139. Application.Run<T> isn't initializing properly by setting the Application.ForceDriver property (#4142)

* Fixes #4139. Application.Run<T> isn't initializing properly by setting the Application.ForceDriver property

* Trying fix unit tests

* Only to force again CI tests because I haven't errors locally

* This should pass, unless RunningUnitTests is set to false somewhere

* Fix Unix unit tests and failures via ReSharper

* Changes suggested by @tig

* Prevent empty string

* Centralize all the entry logic in the InternalInit method

* Change GetDriverTypes to return a tuple
BDisp 3 сар өмнө
parent
commit
ad1de255b1

+ 16 - 0
Examples/UICatalog/Properties/launchSettings.json

@@ -12,6 +12,10 @@
       "commandName": "Project",
       "commandLineArgs": "--driver WindowsDriver"
     },
+    "UICatalog --driver v2": {
+      "commandName": "Project",
+      "commandLineArgs": "--driver v2 -dl Trace"
+    },
     "UICatalog --driver v2win": {
       "commandName": "Project",
       "commandLineArgs": "--driver v2win -dl Trace"
@@ -32,6 +36,18 @@
       "commandLineArgs": "dotnet UICatalog.dll --driver NetDriver",
       "distributionName": ""
     },
+    "WSL: UICatalog --driver v2": {
+      "commandName": "Executable",
+      "executablePath": "wsl",
+      "commandLineArgs": "dotnet UICatalog.dll --driver v2",
+      "distributionName": ""
+    },
+    "WSL: UICatalog --driver v2net": {
+      "commandName": "Executable",
+      "executablePath": "wsl",
+      "commandLineArgs": "dotnet UICatalog.dll --driver v2net",
+      "distributionName": ""
+    },
     "Benchmark All": {
       "commandName": "Project",
       "commandLineArgs": "--benchmark"

+ 2 - 6
Examples/UICatalog/UICatalog.cs

@@ -54,7 +54,7 @@ namespace UICatalog;
 /// </remarks>
 public class UICatalog
 {
-    private static string _forceDriver = string.Empty;
+    private static string? _forceDriver = null;
 
     public static string LogFilePath { get; set; } = string.Empty;
     public static LoggingLevelSwitch LogLevelSwitch { get; } = new ();
@@ -77,11 +77,7 @@ public class UICatalog
 
         // If no driver is provided, the default driver is used.
         Option<string> driverOption = new Option<string> ("--driver", "The IConsoleDriver to use.").FromAmong (
-             Application.GetDriverTypes ()
-                        .Where (d => !typeof (IConsoleDriverFacade).IsAssignableFrom (d))
-                        .Select (d => d!.Name)
-                        .Union (["v2", "v2win", "v2net"])
-                        .ToArray ()
+             Application.GetDriverTypes ().Item2.ToArray ()!
             );
         driverOption.AddAlias ("-d");
         driverOption.AddAlias ("--d");

+ 26 - 17
Terminal.Gui/App/Application.Initialization.cs

@@ -40,11 +40,6 @@ public static partial class Application // Initialization (Init/Shutdown)
     [RequiresDynamicCode ("AOT")]
     public static void Init (IConsoleDriver? driver = null, string? driverName = null)
     {
-        if (driverName?.StartsWith ("v2") ?? false)
-        {
-            ApplicationImpl.ChangeInstance (new ApplicationV2 ());
-        }
-
         ApplicationImpl.Instance.Init (driver, driverName);
     }
 
@@ -83,12 +78,6 @@ public static partial class Application // Initialization (Init/Shutdown)
             ResetState (ignoreDisposed: true);
         }
 
-        Debug.Assert (Navigation is null);
-        Navigation = new ();
-
-        Debug.Assert(Popover is null);
-        Popover = new ();
-
         // For UnitTests
         if (driver is { })
         {
@@ -105,8 +94,6 @@ public static partial class Application // Initialization (Init/Shutdown)
             }
         }
 
-        AddKeyBindings ();
-
         // Ignore Configuration for ForceDriver if driverName is specified
         if (!string.IsNullOrEmpty (driverName))
         {
@@ -130,13 +117,21 @@ public static partial class Application // Initialization (Init/Shutdown)
             }
             else
             {
-                List<Type?> drivers = GetDriverTypes ();
+                (List<Type?> drivers, List<string?> driverTypeNames) = GetDriverTypes ();
                 Type? driverType = drivers.FirstOrDefault (t => t!.Name.Equals (ForceDriver, StringComparison.InvariantCultureIgnoreCase));
 
                 if (driverType is { })
                 {
                     Driver = (IConsoleDriver)Activator.CreateInstance (driverType)!;
                 }
+                else if (ForceDriver?.StartsWith ("v2") ?? false)
+                {
+                    ApplicationImpl.ChangeInstance (new ApplicationV2 ());
+                    ApplicationImpl.Instance.Init (driver, ForceDriver);
+                    Debug.Assert (Driver is { });
+
+                    return;
+                }
                 else
                 {
                     throw new ArgumentException (
@@ -146,6 +141,14 @@ public static partial class Application // Initialization (Init/Shutdown)
             }
         }
 
+        Debug.Assert (Navigation is null);
+        Navigation = new ();
+
+        Debug.Assert (Popover is null);
+        Popover = new ();
+
+        AddKeyBindings ();
+
         try
         {
             MainLoop = Driver!.Init ();
@@ -201,10 +204,10 @@ public static partial class Application // Initialization (Init/Shutdown)
     private static void Driver_KeyUp (object? sender, Key e) { RaiseKeyUpEvent (e); }
     private static void Driver_MouseEvent (object? sender, MouseEventArgs e) { RaiseMouseEvent (e); }
 
-    /// <summary>Gets of list of <see cref="IConsoleDriver"/> types that are available.</summary>
+    /// <summary>Gets of list of <see cref="IConsoleDriver"/> types and type names that are available.</summary>
     /// <returns></returns>
     [RequiresUnreferencedCode ("AOT")]
-    public static List<Type?> GetDriverTypes ()
+    public static (List<Type?>, List<string?>) GetDriverTypes ()
     {
         // use reflection to get the list of drivers
         List<Type?> driverTypes = new ();
@@ -220,7 +223,13 @@ public static partial class Application // Initialization (Init/Shutdown)
             }
         }
 
-        return driverTypes;
+        List<string?> driverTypeNames = driverTypes
+                                        .Where (d => !typeof (IConsoleDriverFacade).IsAssignableFrom (d))
+                                        .Select (d => d!.Name)
+                                        .Union (["v2", "v2win", "v2net"])
+                                        .ToList ()!;
+
+        return (driverTypes, driverTypeNames);
     }
 
     /// <summary>Shutdown an application initialized with <see cref="Init"/>.</summary>

+ 9 - 2
Terminal.Gui/App/ApplicationImpl.cs

@@ -34,7 +34,7 @@ public class ApplicationImpl : IApplication
     [RequiresDynamicCode ("AOT")]
     public virtual void Init (IConsoleDriver? driver = null, string? driverName = null)
     {
-        Application.InternalInit (driver, driverName);
+        Application.InternalInit (driver, string.IsNullOrWhiteSpace (driverName) ? Application.ForceDriver : driverName);
     }
 
     /// <summary>
@@ -85,7 +85,12 @@ public class ApplicationImpl : IApplication
         if (!Application.Initialized)
         {
             // Init() has NOT been called.
-            Application.InternalInit (driver, null, true);
+            Application.InternalInit (driver, Application.ForceDriver, true);
+        }
+
+        if (Instance is ApplicationV2)
+        {
+            return Instance.Run<T> (errorHandler, driver);
         }
 
         var top = new T ();
@@ -227,6 +232,8 @@ public class ApplicationImpl : IApplication
 
             Application.OnInitializedChanged (this, new (in init));
         }
+
+        _lazyInstance = new (() => new ApplicationImpl ());
     }
 
     /// <inheritdoc />

+ 7 - 1
Terminal.Gui/Drivers/NetDriver/NetEvents.cs

@@ -19,6 +19,11 @@ internal class NetEvents : IDisposable
     {
         _consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver));
 
+        if (ConsoleDriver.RunningUnitTests)
+        {
+            return;
+        }
+
         Task.Run (() =>
         {
             try
@@ -29,7 +34,8 @@ internal class NetEvents : IDisposable
             { }
         }, _netEventsDisposed.Token);
 
-        Task.Run (() => {
+        Task.Run (() =>
+        {
             try
             {
                 CheckWindowSizeChange ();

+ 13 - 1
Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs

@@ -64,7 +64,19 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
     }
 
     /// <summary>Gets the location and size of the terminal screen.</summary>
-    public Rectangle Screen => new (new (0, 0), _output.GetWindowSize ());
+    public Rectangle Screen
+    {
+        get
+        {
+            if (ConsoleDriver.RunningUnitTests)
+            {
+                // In unit tests, we don't have a real output, so we return an empty rectangle.
+                return Rectangle.Empty;
+            }
+
+            return new (new (0, 0), _output.GetWindowSize ());
+        }
+    }
 
     /// <summary>
     ///     Gets or sets the clip rectangle that <see cref="AddRune(Rune)"/> and <see cref="AddStr(string)"/> are subject

+ 15 - 1
Terminal.Gui/Drivers/V2/NetInput.cs

@@ -17,6 +17,12 @@ public class NetInput : ConsoleInput<ConsoleKeyInfo>, INetInput
     public NetInput ()
     {
         Logging.Logger.LogInformation ($"Creating {nameof (NetInput)}");
+
+        if (ConsoleDriver.RunningUnitTests)
+        {
+            return;
+        }
+
         PlatformID p = Environment.OSVersion.Platform;
 
         if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
@@ -39,7 +45,15 @@ public class NetInput : ConsoleInput<ConsoleKeyInfo>, INetInput
     }
 
     /// <inheritdoc/>
-    protected override bool Peek () { return Console.KeyAvailable; }
+    protected override bool Peek ()
+    {
+        if (ConsoleDriver.RunningUnitTests)
+        {
+            return false;
+        }
+
+        return Console.KeyAvailable;
+    }
 
     /// <inheritdoc/>
     protected override IEnumerable<ConsoleKeyInfo> Read ()

+ 15 - 1
Terminal.Gui/Drivers/V2/NetOutput.cs

@@ -47,6 +47,11 @@ public class NetOutput : IConsoleOutput
     /// <inheritdoc/>
     public void Write (IOutputBuffer buffer)
     {
+        if (ConsoleDriver.RunningUnitTests)
+        {
+            return;
+        }
+
         if (Console.WindowHeight < 1
             || buffer.Contents.Length != buffer.Rows * buffer.Cols
             || buffer.Rows != Console.WindowHeight)
@@ -197,7 +202,16 @@ public class NetOutput : IConsoleOutput
     }
 
     /// <inheritdoc/>
-    public Size GetWindowSize () { return new (Console.WindowWidth, Console.WindowHeight); }
+    public Size GetWindowSize ()
+    {
+        if (ConsoleDriver.RunningUnitTests)
+        {
+            // For unit tests, we return a default size.
+            return Size.Empty;
+        }
+
+        return new (Console.WindowWidth, Console.WindowHeight);
+    }
 
     private void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
     {

+ 5 - 0
Terminal.Gui/Drivers/V2/WindowSizeMonitor.cs

@@ -20,6 +20,11 @@ internal class WindowSizeMonitor : IWindowSizeMonitor
     /// <inheritdoc/>
     public bool Poll ()
     {
+        if (ConsoleDriver.RunningUnitTests)
+        {
+            return false;
+        }
+
         Size size = _consoleOut.GetWindowSize ();
 
         if (size != _lastSize)

+ 15 - 1
Terminal.Gui/Drivers/V2/WindowsInput.cs

@@ -38,6 +38,12 @@ internal class WindowsInput : ConsoleInput<WindowsConsole.InputRecord>, IWindows
     public WindowsInput ()
     {
         Logging.Logger.LogInformation ($"Creating {nameof (WindowsInput)}");
+
+        if (ConsoleDriver.RunningUnitTests)
+        {
+            return;
+        }
+
         _inputHandle = GetStdHandle (STD_INPUT_HANDLE);
 
         GetConsoleMode (_inputHandle, out uint v);
@@ -110,5 +116,13 @@ internal class WindowsInput : ConsoleInput<WindowsConsole.InputRecord>, IWindows
         }
     }
 
-    public override void Dispose () { SetConsoleMode (_inputHandle, _originalConsoleMode); }
+    public override void Dispose ()
+    {
+        if (ConsoleDriver.RunningUnitTests)
+        {
+            return;
+        }
+
+        SetConsoleMode (_inputHandle, _originalConsoleMode);
+    }
 }

+ 17 - 6
Terminal.Gui/Drivers/V2/WindowsOutput.cs

@@ -68,6 +68,11 @@ internal partial class WindowsOutput : IConsoleOutput
     {
         Logging.Logger.LogInformation ($"Creating {nameof (WindowsOutput)}");
 
+        if (ConsoleDriver.RunningUnitTests)
+        {
+            return;
+        }
+
         _screenBuffer = CreateConsoleScreenBuffer (
                                                    DesiredAccess.GenericRead | DesiredAccess.GenericWrite,
                                                    ShareMode.FileShareRead | ShareMode.FileShareWrite,
@@ -171,12 +176,13 @@ internal partial class WindowsOutput : IConsoleOutput
         };
 
         //size, ExtendedCharInfo [] charInfoBuffer, Coord , SmallRect window,
-        if (!WriteToConsole (
-                             new (buffer.Cols, buffer.Rows),
-                             outputBuffer,
-                             bufferCoords,
-                             damageRegion,
-                             Application.Driver!.Force16Colors))
+        if (!ConsoleDriver.RunningUnitTests
+            && !WriteToConsole (
+                                new (buffer.Cols, buffer.Rows),
+                                outputBuffer,
+                                bufferCoords,
+                                damageRegion,
+                                Application.Driver!.Force16Colors))
         {
             int err = Marshal.GetLastWin32Error ();
 
@@ -312,6 +318,11 @@ internal partial class WindowsOutput : IConsoleOutput
     /// <inheritdoc/>
     public void SetCursorVisibility (CursorVisibility visibility)
     {
+        if (ConsoleDriver.RunningUnitTests)
+        {
+            return;
+        }
+
         if (Application.Driver!.Force16Colors)
         {
             var info = new WindowsConsole.ConsoleCursorInfo

+ 3 - 1
Tests/IntegrationTests/FluentTests/FileDialogFluentTests.cs

@@ -1,4 +1,5 @@
-using System.IO.Abstractions;
+using System.Globalization;
+using System.IO.Abstractions;
 using System.IO.Abstractions.TestingHelpers;
 using System.Runtime.InteropServices;
 using TerminalGuiFluentTesting;
@@ -12,6 +13,7 @@ public class FileDialogFluentTests
 
     public FileDialogFluentTests (ITestOutputHelper outputHelper)
     {
+        CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture;
         _out = new TestOutputWriter (outputHelper);
     }
 

+ 2 - 0
Tests/IntegrationTests/FluentTests/MenuBarv2Tests.cs

@@ -1,3 +1,4 @@
+using System.Globalization;
 using System.Reflection;
 using TerminalGuiFluentTesting;
 using Xunit.Abstractions;
@@ -13,6 +14,7 @@ public class MenuBarv2Tests
 
     public MenuBarv2Tests (ITestOutputHelper outputHelper)
     {
+        CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture;
         _out = new TestOutputWriter (outputHelper);
     }
 

+ 9 - 3
Tests/IntegrationTests/FluentTests/PopverMenuTests.cs

@@ -1,4 +1,4 @@
-using System.Reflection;
+using System.Globalization;
 using TerminalGuiFluentTesting;
 using Xunit.Abstractions;
 
@@ -7,9 +7,15 @@ namespace IntegrationTests.FluentTests;
 /// <summary>
 ///     Tests for the PopoverMenu class
 /// </summary>
-public class PopoverMenuTests (ITestOutputHelper outputHelper)
+public class PopoverMenuTests
 {
-    private readonly TextWriter _out = new TestOutputWriter (outputHelper);
+    private readonly TextWriter _out;
+
+    public PopoverMenuTests (ITestOutputHelper outputHelper)
+    {
+        CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture;
+        _out = new TestOutputWriter (outputHelper);
+    }
 
     [Theory]
     [ClassData (typeof (V2TestDrivers))]

+ 94 - 1
Tests/UnitTests/Application/ApplicationTests.cs

@@ -655,7 +655,17 @@ public class ApplicationTests
         Assert.NotNull (SynchronizationContext.Current);
     }
 
-    private void Shutdown () { Application.Shutdown (); }
+    private void Shutdown ()
+    {
+        if (ApplicationImpl.Instance is ApplicationV2)
+        {
+            ApplicationImpl.Instance.Shutdown ();
+        }
+        else
+        {
+            Application.Shutdown ();
+        }
+    }
 
     #region RunTests
 
@@ -1104,6 +1114,89 @@ public class ApplicationTests
         Assert.Null (Application.Top);
     }
 
+    private class TestToplevel : Toplevel { }
+
+    [Theory]
+    [InlineData ("v2win", typeof (ConsoleDriverFacade<WindowsConsole.InputRecord>))]
+    [InlineData ("v2net", typeof (ConsoleDriverFacade<ConsoleKeyInfo>))]
+    [InlineData ("FakeDriver", typeof (FakeDriver))]
+    [InlineData ("NetDriver", typeof (NetDriver))]
+    [InlineData ("WindowsDriver", typeof (WindowsDriver))]
+    [InlineData ("CursesDriver", typeof (CursesDriver))]
+    public void Run_T_Call_Init_ForceDriver_Should_Pick_Correct_Driver (string driverName, Type expectedType)
+    {
+        Assert.True (ConsoleDriver.RunningUnitTests);
+
+        var result = false;
+
+        Task.Run (() =>
+                  {
+                      Task.Delay (300).Wait ();
+                  }).ContinueWith (
+                                   (t, _) =>
+                                   {
+                                       // no longer loading
+                                       Application.Invoke (() =>
+                                                           {
+                                                               result = true;
+                                                               Application.RequestStop ();
+                                                           });
+                                   },
+                                   TaskScheduler.FromCurrentSynchronizationContext ());
+
+        Application.ForceDriver = driverName;
+        Application.Run<TestToplevel> ();
+        Assert.NotNull (Application.Driver);
+        Assert.Equal (expectedType, Application.Driver?.GetType ());
+        Assert.NotNull (Application.Top);
+        Assert.False (Application.Top!.Running);
+        Application.Top!.Dispose ();
+        Shutdown ();
+        Assert.True (result);
+    }
+
+    [Fact]
+    public void Run_T_With_Legacy_Driver_Does_Not_Call_ResetState_After_Init ()
+    {
+        Assert.False (Application.Initialized);
+        Application.Init ();
+        Assert.True (Application.Initialized);
+        Application.Iteration += (_, _) => Application.RequestStop ();
+        Application.Run<TestToplevel> ();
+        Assert.NotNull (Application.Driver);
+        Assert.NotNull (Application.Top);
+        Assert.False (Application.Top!.Running);
+        Application.Top!.Dispose ();
+        Shutdown ();
+    }
+
+    [Fact]
+    public void Run_T_With_V2_Driver_Does_Not_Call_ResetState_After_Init ()
+    {
+        Assert.False (Application.Initialized);
+        Application.Init (null, "v2net");
+        Assert.True (Application.Initialized);
+        Task.Run (() =>
+                  {
+                      Task.Delay (300).Wait ();
+                  }).ContinueWith (
+                                   (t, _) =>
+                                   {
+                                       // no longer loading
+                                       Application.Invoke (() =>
+                                                           {
+                                                               Application.RequestStop ();
+                                                           });
+                                   },
+                                   TaskScheduler.FromCurrentSynchronizationContext ());
+        Application.Run<TestToplevel> ();
+        Assert.NotNull (Application.Driver);
+        Assert.NotNull (Application.Top);
+        Assert.False (Application.Top!.Running);
+        Application.Top!.Dispose ();
+        Shutdown ();
+    }
+
     // TODO: Add tests for Run that test errorHandler
 
     #endregion

+ 4 - 3
Tests/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs

@@ -7,6 +7,10 @@ using Moq;
 namespace UnitTests.ConsoleDrivers.V2;
 public class ApplicationV2Tests
 {
+    public ApplicationV2Tests ()
+    {
+        ConsoleDriver.RunningUnitTests = true;
+    }
 
     private ApplicationV2 NewApplicationV2 ()
     {
@@ -362,7 +366,6 @@ public class ApplicationV2Tests
                                               if (Application.Top != null)
                                               {
                                                   Application.RaiseKeyDownEvent (Application.QuitKey);
-                                                  return false;
                                               }
 
                                               return false;
@@ -575,8 +578,6 @@ public class ApplicationV2Tests
                                               {
                                                   b.NewKeyDownEvent (Key.Enter);
                                                   b.NewKeyUpEvent (Key.Enter);
-
-                                                  return false;
                                               }
 
                                               return false;

+ 5 - 3
Tests/UnitTests/ConsoleDrivers/V2/WindowSizeMonitorTests.cs

@@ -3,6 +3,11 @@
 namespace UnitTests.ConsoleDrivers.V2;
 public class WindowSizeMonitorTests
 {
+    public WindowSizeMonitorTests ()
+    {
+        ConsoleDriver.RunningUnitTests = false;
+    }
+
     [Fact]
     public void TestWindowSizeMonitor_RaisesEventWhenChanges ()
     {
@@ -70,7 +75,4 @@ public class WindowSizeMonitorTests
         Assert.Single (result);
         Assert.Equal (new Size (30, 20), result [0].Size);
     }
-
-
-
 }