Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Misc fixes and enhancement to allow uno to use runtime test engine #165

Merged
merged 1 commit into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
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
Loading