Skip to content

Commit

Permalink
feat: Misc fixes and enhancement to allow uno to use runtime test engine
Browse files Browse the repository at this point in the history
  • Loading branch information
dr1rrb committed Jan 15, 2024
1 parent 047f631 commit 6d37581
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Uno.UI.RuntimeTests.Engine.Skia.Gtk;

internal class Program
internal sealed class Program
{
static void Main(string[] args)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#endif

using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
Expand Down Expand Up @@ -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<TestResultKind>(Environment.GetEnvironmentVariable("UNO_RUNTIME_TESTS_OUTPUT_KIND"), ignoreCase: true, out var kind)
? kind
: TestResultKind.NUnit;
Expand All @@ -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)
Expand All @@ -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);
Expand All @@ -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('{'))
Expand All @@ -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();
}
Expand Down Expand Up @@ -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);
Expand All @@ -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;

Expand All @@ -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);

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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;

/// <summary>
/// Sets path to the dev-server host assembly (i.e. the Uno.UI.RemoteControl.Host.dll file).
/// </summary>
/// <param name="path">Path for the dev-server host assembly.</param>
[EditorBrowsable(EditorBrowsableState.Advanced)] // To be used by uno only
public static void SetDefaultPath(string path)
=> _devServerPath = path;

/// <summary>
/// Starts a new dev server instance
/// </summary>
/// <param name="ct">Cancellation token to abort the initialization of the server.</param>
/// <returns>The new dev server instance.</returns>
public static async global::System.Threading.Tasks.Task<DevServer> Start(string path, global::System.Threading.CancellationToken ct)
=> StartCore(path, GetTcpPort());

/// <summary>
/// Starts a new dev server instance
/// </summary>
/// <param name="ct">Cancellation token to abort the initialization of the server.</param>
/// <returns>The new dev server instance.</returns>
/// <remarks>
/// 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.
/// </remarks>
public static async global::System.Threading.Tasks.Task<DevServer> Start(global::System.Threading.CancellationToken ct)
{
#if !HAS_UNO_DEVSERVER
Expand All @@ -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<string> 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);

/// <summary>
/// Pulls the latest version of dev server from NuGet and returns the path to the executable
Expand Down
13 changes: 9 additions & 4 deletions src/Uno.UI.RuntimeTests.Engine.Library/Engine/UnitTestFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ static partial void TryUseDevServerFileUpdater()
partial record FileEdit
{
public UpdateFile ToMessage()
=> new UpdateFile
=> new()
{
FilePath = FilePath,
OldText = OldText,
Expand All @@ -48,28 +48,28 @@ 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<ClientHotReloadProcessor>().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")}).");
}

_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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
/// <summary>
/// The delay for the client to connect to the dev-server
/// </summary>
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);
/// <summary>
/// The delay for the server to load the workspace and let the client know it's ready
/// </summary>
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);
/// <summary>
/// The delay for the server to send metadata update once a file has been modified
/// </summary>
public static global::System.TimeSpan DefaultMetadataUpdateTimeout = global::System.TimeSpan.FromSeconds(5);

static HotReloadHelper()
{
Expand Down Expand Up @@ -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(
Expand Down

0 comments on commit 6d37581

Please sign in to comment.