diff --git a/src/TestApp/winui/Uno.UI.RuntimeTests.Engine.Skia.Gtk/Program.cs b/src/TestApp/winui/Uno.UI.RuntimeTests.Engine.Skia.Gtk/Program.cs index ef5f735..56f6b11 100644 --- a/src/TestApp/winui/Uno.UI.RuntimeTests.Engine.Skia.Gtk/Program.cs +++ b/src/TestApp/winui/Uno.UI.RuntimeTests.Engine.Skia.Gtk/Program.cs @@ -4,7 +4,7 @@ namespace Uno.UI.RuntimeTests.Engine.Skia.Gtk; -internal class Program +internal sealed class Program { static void Main(string[] args) { diff --git a/src/Uno.UI.RuntimeTests.Engine.Library/Engine/ExternalRunner/RuntimeTestEmbeddedRunner.cs b/src/Uno.UI.RuntimeTests.Engine.Library/Engine/ExternalRunner/RuntimeTestEmbeddedRunner.cs index c0b9dc0..e963259 100644 --- a/src/Uno.UI.RuntimeTests.Engine.Library/Engine/ExternalRunner/RuntimeTestEmbeddedRunner.cs +++ b/src/Uno.UI.RuntimeTests.Engine.Library/Engine/ExternalRunner/RuntimeTestEmbeddedRunner.cs @@ -10,6 +10,7 @@ #endif using System; +using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.CompilerServices; @@ -49,9 +50,13 @@ private enum TestResultKind #pragma warning restore CA2255 public static void AutoStartTests() { + Trace("Initializing runtime-tests module."); + if (Environment.GetEnvironmentVariable("UNO_RUNTIME_TESTS_RUN_TESTS") is { } testsConfig && Environment.GetEnvironmentVariable("UNO_RUNTIME_TESTS_OUTPUT_PATH") is { } outputPath) { + Trace($"Application configured to start runtime-tests (Output={outputPath} | Config={testsConfig})."); + var outputKind = Enum.TryParse(Environment.GetEnvironmentVariable("UNO_RUNTIME_TESTS_OUTPUT_KIND"), ignoreCase: true, out var kind) ? kind : TestResultKind.NUnit; @@ -65,6 +70,10 @@ public static void AutoStartTests() _ = RunTestsAndExit(testsConfig, outputPath, outputKind, isSecondaryApp); } + else + { + Trace("Application has not been configured to start runtime-test, aborting runtime-test embedded runner."); + } } private static async Task RunTestsAndExit(string testsConfigRaw, string outputPath, TestResultKind outputKind, bool isSecondaryApp) @@ -73,7 +82,7 @@ private static async Task RunTestsAndExit(string testsConfigRaw, string outputPa try { - Log("Waiting for app to init before running runtime-tests"); + Log("Waiting for app to init before running runtime-tests."); #pragma warning disable CA1416 // Validate platform compatibility Console.CancelKeyPress += (_, _) => ct.Cancel(true); @@ -92,6 +101,8 @@ private static async Task RunTestsAndExit(string testsConfigRaw, string outputPa throw new InvalidOperationException("Window.Current is null or does not have any valid dispatcher"); } + Trace("Got window (and dispatcher), continuing runtime-test initialization on it."); + // While the app init, parse the tests config var config = default(UnitTestEngineConfig?); if (testsConfigRaw.StartsWith('{')) @@ -114,6 +125,8 @@ await window { try { + Trace("Got dispatcher access, init the runtime-test engine."); + await RunTests(window, config, outputPath, outputKind, isSecondaryApp, ct.Token); tcs.TrySetResult(); } @@ -158,7 +171,7 @@ private static async Task RunTests(Window window, UnitTestEngineConfig config, s } // Then override the app content by the test control - Log("Initializing runtime-tests engine."); + Trace("Initializing runtime-tests engine."); var engine = new UnitTestsControl { IsSecondaryApp = isSecondaryApp }; Window.Current.Content = engine; await UIHelper.WaitForLoaded(engine, ct); @@ -171,7 +184,7 @@ private static async Task RunTests(Window window, UnitTestEngineConfig config, s Log($"Saving runtime-tests results to {outputPath}."); switch (outputKind) { - case TestResultKind.UnoRuntimeTests: + case TestResultKind.UnoRuntimeTests: await File.WriteAllTextAsync(outputPath, JsonSerializer.Serialize(engine.Results), Encoding.UTF8, ct); break; @@ -182,6 +195,10 @@ private static async Task RunTests(Window window, UnitTestEngineConfig config, s } } + [Conditional("DEBUG")] + private static void Trace(string text) + => Console.WriteLine(text); + private static void Log(string text) => Console.WriteLine(text); diff --git a/src/Uno.UI.RuntimeTests.Engine.Library/Engine/ExternalRunner/_Private/DevServer.cs b/src/Uno.UI.RuntimeTests.Engine.Library/Engine/ExternalRunner/_Private/DevServer.cs index 7aa4aaa..1f82afb 100644 --- a/src/Uno.UI.RuntimeTests.Engine.Library/Engine/ExternalRunner/_Private/DevServer.cs +++ b/src/Uno.UI.RuntimeTests.Engine.Library/Engine/ExternalRunner/_Private/DevServer.cs @@ -1,4 +1,8 @@ -#if !UNO_RUNTIMETESTS_DISABLE_UI && HAS_UNO_WINUI // HAS_UNO_WINUI: exclude non net7 platforms +using System; +using System.ComponentModel; +using System.IO; + +#if !UNO_RUNTIMETESTS_DISABLE_UI && HAS_UNO_WINUI // HAS_UNO_WINUI: exclude non net7 platforms #nullable enable #if !IS_UNO_RUNTIMETEST_PROJECT @@ -18,17 +22,37 @@ namespace Uno.UI.RuntimeTests.Internal.Helpers; [global::System.Runtime.Versioning.SupportedOSPlatform("windows")] [global::System.Runtime.Versioning.SupportedOSPlatform("linux")] [global::System.Runtime.Versioning.SupportedOSPlatform("freeBSD")] -internal sealed partial class DevServer : global::System.IAsyncDisposable +public sealed partial class DevServer : global::System.IAsyncDisposable { private static readonly global::Microsoft.Extensions.Logging.ILogger _log = global::Uno.Extensions.LogExtensionPoint.Log(typeof(DevServer)); private static int _instance; private static string? _devServerPath; + /// + /// Sets path to the dev-server host assembly (i.e. the Uno.UI.RemoteControl.Host.dll file). + /// + /// Path for the dev-server host assembly. + [EditorBrowsable(EditorBrowsableState.Advanced)] // To be used by uno only + public static void SetDefaultPath(string path) + => _devServerPath = path; + + /// + /// Starts a new dev server instance + /// + /// Cancellation token to abort the initialization of the server. + /// The new dev server instance. + public static async global::System.Threading.Tasks.Task Start(string path, global::System.Threading.CancellationToken ct) + => StartCore(path, GetTcpPort()); + /// /// Starts a new dev server instance /// /// Cancellation token to abort the initialization of the server. /// The new dev server instance. + /// + /// The path of the dev-server will be resolved from the UNO_RUNTIME_TESTS_DEV_SERVER_PATH environment variable (path to the Uno.UI.RemoteControl.Host.dll file), + /// and if not defined, it the latest version version will be pulled from NuGet. + /// public static async global::System.Threading.Tasks.Task Start(global::System.Threading.CancellationToken ct) { #if !HAS_UNO_DEVSERVER @@ -55,7 +79,7 @@ private DevServer(global::System.Diagnostics.Process process, int port) public int Port { get; } private static async global::System.Threading.Tasks.Task GetDevServer(global::System.Threading.CancellationToken ct) - => _devServerPath ??= await PullDevServer(ct); + => _devServerPath ??= Environment.GetEnvironmentVariable("UNO_RUNTIME_TESTS_DEV_SERVER_PATH") is { Length: > 0 } path ? path : await PullDevServer(ct); /// /// Pulls the latest version of dev server from NuGet and returns the path to the executable diff --git a/src/Uno.UI.RuntimeTests.Engine.Library/Engine/UnitTestFilter.cs b/src/Uno.UI.RuntimeTests.Engine.Library/Engine/UnitTestFilter.cs index f86f2c5..635896e 100644 --- a/src/Uno.UI.RuntimeTests.Engine.Library/Engine/UnitTestFilter.cs +++ b/src/Uno.UI.RuntimeTests.Engine.Library/Engine/UnitTestFilter.cs @@ -48,15 +48,20 @@ private static IUnitTestEngineFilter Parse(string? syntax) } else { - var index = 0; + int previousIndex, index = 0; var pending = default(IUnitTestEngineFilter?); var stx = syntax.AsSpan(); do { - pending = ReadToken(pending, ref index, stx); - } while (index < syntax.Length); + previousIndex = index; + var token = ReadToken(pending, ref index, stx); + if (token is not NullFilter) + { + pending = token; // Avoids to override the current parsed filter is something went wrong + } + } while (index < syntax.Length && index > previousIndex); - return pending; + return pending ?? new NullFilter(); } } diff --git a/src/Uno.UI.RuntimeTests.Engine.Library/Library/Helpers/HotReloadHelper.DevServer.cs b/src/Uno.UI.RuntimeTests.Engine.Library/Library/Helpers/HotReloadHelper.DevServer.cs index 6f5f669..bf636ab 100644 --- a/src/Uno.UI.RuntimeTests.Engine.Library/Library/Helpers/HotReloadHelper.DevServer.cs +++ b/src/Uno.UI.RuntimeTests.Engine.Library/Library/Helpers/HotReloadHelper.DevServer.cs @@ -31,7 +31,7 @@ static partial void TryUseDevServerFileUpdater() partial record FileEdit { public UpdateFile ToMessage() - => new UpdateFile + => new() { FilePath = FilePath, OldText = OldText, @@ -48,20 +48,20 @@ public async ValueTask EnsureReady(CancellationToken ct) if (RemoteControlClient.Instance is null) { - throw new InvalidOperationException("Dev server is not available."); + throw new InvalidOperationException("Dev server is not available (make sure to reference the Uno.WinUI.DevServer package)."); } if (RemoteControlClient.Instance.Processors.OfType().FirstOrDefault() is not { } hotReload) { - throw new InvalidOperationException("App is not configured to accept hot-reload."); + throw new InvalidOperationException("App is not configured to accept hot-reload (project has to be build in debug to get processor path injected)."); } - var timeout = Task.Delay(ConnectionTimeout, ct); + var timeout = Task.Delay(DefaultConnectionTimeout, ct); if (await Task.WhenAny(timeout, RemoteControlClient.Instance.WaitForConnection(ct)) == timeout) { throw new TimeoutException( "Timeout while waiting for the app to connect to the dev-server. " - + "This usually indicates that the dev-server is not running on the expected combination of hots and port" + + "This usually indicates that the dev-server is not running on the expected combination of host and port" + "(For runtime-tests run in secondary app instance, the server should be listening for connection on " + $"{Environment.GetEnvironmentVariable("UNO_DEV_SERVER_HOST")}:{Environment.GetEnvironmentVariable("UNO_DEV_SERVER_PORT")})."); } @@ -69,7 +69,7 @@ public async ValueTask EnsureReady(CancellationToken ct) _log.LogTrace("Client connected, waiting for dev-server to load the workspace (i.e. initializing roslyn with the solution) ..."); var hotReloadReady = hotReload.WaitForWorkspaceLoaded(ct); - timeout = Task.Delay(WorkspaceTimeout, ct); + timeout = Task.Delay(DefaultWorkspaceTimeout, ct); if (await Task.WhenAny(timeout, hotReloadReady) == timeout) { throw new TimeoutException( diff --git a/src/Uno.UI.RuntimeTests.Engine.Library/Library/Helpers/HotReloadHelper.cs b/src/Uno.UI.RuntimeTests.Engine.Library/Library/Helpers/HotReloadHelper.cs index 5583e17..049ecd2 100644 --- a/src/Uno.UI.RuntimeTests.Engine.Library/Library/Helpers/HotReloadHelper.cs +++ b/src/Uno.UI.RuntimeTests.Engine.Library/Library/Helpers/HotReloadHelper.cs @@ -63,14 +63,20 @@ public interface IFileUpdater private static readonly global::Microsoft.Extensions.Logging.ILogger _log = typeof(HotReloadHelper).Log(); private static IFileUpdater _impl = new NotSupported(); - // The delay for the client to connect to the dev-server - private static global::System.TimeSpan ConnectionTimeout = global::System.TimeSpan.FromSeconds(3); + /// + /// The delay for the client to connect to the dev-server + /// + public static global::System.TimeSpan DefaultConnectionTimeout = global::System.TimeSpan.FromSeconds(3); - // The delay for the server to load the workspace and let the client know it's ready - private static global::System.TimeSpan WorkspaceTimeout = global::System.TimeSpan.FromSeconds(30); + /// + /// The delay for the server to load the workspace and let the client know it's ready + /// + public static global::System.TimeSpan DefaultWorkspaceTimeout = global::System.TimeSpan.FromSeconds(30); - // The delay for the server to send metadata update once a file has been modified - private static global::System.TimeSpan MetadataUpdateTimeout = global::System.TimeSpan.FromSeconds(5); + /// + /// The delay for the server to send metadata update once a file has been modified + /// + public static global::System.TimeSpan DefaultMetadataUpdateTimeout = global::System.TimeSpan.FromSeconds(5); static HotReloadHelper() { @@ -227,7 +233,7 @@ public static FileEdit CreateFileEdit( _log.LogTrace("File edition requested, waiting for metadata update (i.e. the code changes to be applied in the current app) ..."); - var timeout = global::System.Threading.Tasks.Task.Delay(MetadataUpdateTimeout, ct); + var timeout = global::System.Threading.Tasks.Task.Delay(DefaultMetadataUpdateTimeout, ct); if (await global::System.Threading.Tasks.Task.WhenAny(timeout, cts.Task) == timeout) { throw new global::System.TimeoutException(