Skip to content

Commit

Permalink
feat: Add ability to use local file system to update file
Browse files Browse the repository at this point in the history
  • Loading branch information
dr1rrb committed Oct 19, 2023
1 parent 959216c commit 634f0d4
Show file tree
Hide file tree
Showing 9 changed files with 368 additions and 191 deletions.
6 changes: 3 additions & 3 deletions src/TestApp/shared/HotReloadTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public async Task Is_SourcesEditable(CancellationToken ct)
Assert.IsTrue(File.Exists(file));
Assert.IsTrue(File.ReadAllText(file).Contains("Original text"));

await using var _ = await HotReloadHelper.UpdateServerFile(sutPath, "Original text", "Updated text from Can_Edit_File", waitForMetadataUpdate: false, ct);
await using var _ = await HotReloadHelper.UpdateSourceFile(sutPath, "Original text", "Updated text from Can_Edit_File", waitForMetadataUpdate: false, ct);

await TestHelper.WaitFor(() => File.ReadAllText(file).Contains("Updated text from Can_Edit_File"), ct);

Expand All @@ -68,7 +68,7 @@ public async Task Is_CodeHotReload_Enabled(CancellationToken ct)

Debug.Assert(sut.Value == "42");

await using var _ = await HotReloadHelper.UpdateServerFile("../../shared/HotReloadTest_SimpleSubject.cs", "42", "43", ct);
await using var _ = await HotReloadHelper.UpdateSourceFile("../../shared/HotReloadTest_SimpleSubject.cs", "42", "43", ct);

Debug.Assert(sut.Value == "43");
}
Expand All @@ -85,7 +85,7 @@ public async Task Is_UIHotReload_Enabled(CancellationToken ct)

Assert.AreEqual("Original text", UIHelper.GetChild<TextBlock>().Text);

await using var _ = await HotReloadHelper.UpdateServerFile<HotReloadTests_Subject>("Original text", "Updated text", ct);
await using var _ = await HotReloadHelper.UpdateSourceFile<HotReloadTests_Subject>("Original text", "Updated text", ct);

await AsyncAssert.AreEqual("Updated text", () => UIHelper.GetChild<TextBlock>().Text, ct);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#if !UNO_RUNTIMETESTS_DISABLE_LIBRARY && HAS_UNO_DEVSERVER // Set as soon as the DevServer package is referenced, cf. Uno.UI.RuntimeTests.Engine.targets
#nullable enable

#if !IS_UNO_RUNTIMETEST_PROJECT
#pragma warning disable
#endif
#pragma warning disable CA1848 // Log perf

using System;
using System.Collections;
using System.IO;
using System.Net.WebSockets;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Uno.UI.RuntimeTests.Internal.Helpers;
using Uno.UI.RemoteControl; // DevServer
using Uno.UI.RemoteControl.HotReload; // DevServer
using Uno.UI.RemoteControl.HotReload.Messages; // DevServer
using Uno.UI.RemoteControl.HotReload.MetadataUpdater; // DevServer

namespace Uno.UI.RuntimeTests;

partial class HotReloadHelper
{
static partial void TryUseDevServerFileUpdater()
=> Use(new DevServerUpdater());

partial record FileEdit
{
public UpdateFile ToMessage()
=> new UpdateFile
{
FilePath = FilePath,
OldText = OldText,
NewText = NewText
};
}

private class DevServerUpdater : IFileUpdater
{
/// <inheritdoc />
public async ValueTask EnsureReady(CancellationToken ct)
{
_log.LogTrace("Getting dev-server ready ...");

if (RemoteControlClient.Instance is null)
{
throw new InvalidOperationException("Dev server is not available.");
}

var timeout = Task.Delay(ConnectionTimeout, 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"
+ "(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 processors = typeof(RemoteControlClient).GetField("_processors", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(RemoteControlClient.Instance) as IDictionary ?? throw new InvalidOperationException("Processors is null");
var processor = processors["hotreload"] as ClientHotReloadProcessor ?? throw new InvalidOperationException("HotReloadProcessor is null");
var hotReloadReady = typeof(ClientHotReloadProcessor).GetProperty("HotReloadWorkspaceLoaded", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(processor) as Task ?? throw new IOException("HotReloadWorkspaceLoaded is null");

timeout = Task.Delay(WorkspaceTimeout, ct);
if (await Task.WhenAny(timeout, hotReloadReady) == timeout)
{
throw new TimeoutException(
"Timeout while waiting for hot reload workspace to be loaded. "
+ "This usually indicates that the dev-server failed to load the solution, you will find more info in dev-server logs "
+ "(output in main app logs with [DEV_SERVER] prefix for runtime-tests run in secondary app instance).");
}

_log.LogTrace("Workspace is ready on dev-server, sending file update request ...");
}

/// <inheritdoc />
public async ValueTask<ReConnect?> Apply(FileEdit edition, CancellationToken ct)
{
try
{
await RemoteControlClient.Instance!.SendMessage(edition.ToMessage());

return null;
}
catch (WebSocketException)
{
return ct =>
{
_log.LogWarning($"WAITING {RemoteControlClient.Instance!.ConnectionRetryInterval:g} to let client reconnect before retry**.");
return Task.Delay(RemoteControlClient.Instance.ConnectionRetryInterval + TimeSpan.FromMilliseconds(100), ct);
};
}
}
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#if !UNO_RUNTIMETESTS_DISABLE_LIBRARY

#if !IS_UNO_RUNTIMETEST_PROJECT
#pragma warning disable
#endif

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Uno.UI.RuntimeTests;

partial class HotReloadHelper
{
static partial void TryUseLocalFileUpdater()
=> Use(new LocalFileUpdater());

[EditorBrowsable(EditorBrowsableState.Advanced)]
public static void UseLocalFileUpdater()
=> Use(new LocalFileUpdater());

private class LocalFileUpdater : IFileUpdater
{
/// <inheritdoc />
public async ValueTask EnsureReady(CancellationToken ct) { }

/// <inheritdoc />
public async ValueTask<ReConnect?> Apply(FileEdit edition, CancellationToken ct)
{
if (!File.Exists(edition.FilePath))
{
throw new InvalidOperationException($"Source file {edition.FilePath} does not exist!");
}

var originalContent = await File.ReadAllTextAsync(edition.FilePath, ct);
var updatedContent = originalContent.Replace(edition.OldText, edition.NewText);

await File.WriteAllTextAsync(edition.FilePath, updatedContent, ct);

return null;
}
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#if !UNO_RUNTIMETESTS_DISABLE_LIBRARY

#if !IS_UNO_RUNTIMETEST_PROJECT
#pragma warning disable
#endif

using System;
using System.Collections.Generic;
using System.Text;
using Uno.UI.RuntimeTests;

[assembly:System.Reflection.Metadata.MetadataUpdateHandlerAttribute(typeof(HotReloadHelper.MetadataUpdateHandler))]

Check failure on line 12 in src/Uno.UI.RuntimeTests.Engine.Library/Library/Helpers/HotReloadHelper.MetadataUpdateHandler.cs

View workflow job for this annotation

GitHub Actions / build

The type or namespace name 'MetadataUpdateHandlerAttributeAttribute' does not exist in the namespace 'System.Reflection.Metadata' (are you missing an assembly reference?)

Check failure on line 12 in src/Uno.UI.RuntimeTests.Engine.Library/Library/Helpers/HotReloadHelper.MetadataUpdateHandler.cs

View workflow job for this annotation

GitHub Actions / build

The type or namespace name 'MetadataUpdateHandlerAttribute' does not exist in the namespace 'System.Reflection.Metadata' (are you missing an assembly reference?)

namespace Uno.UI.RuntimeTests;

partial class HotReloadHelper
{
internal static class MetadataUpdateHandler
{
public static event EventHandler? MetadataUpdated;

internal static void UpdateApplication(Type[]? types)
{
MetadataUpdated?.Invoke(null, EventArgs.Empty);
}
}
}
#endif

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#if !UNO_RUNTIMETESTS_DISABLE_LIBRARY

#if !IS_UNO_RUNTIMETEST_PROJECT
#pragma warning disable
#endif

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Uno.UI.RuntimeTests;

public static partial class HotReloadHelper
{
private class NotSupported : IFileUpdater
{
/// <inheritdoc />
public ValueTask EnsureReady(CancellationToken ct)
=> throw new NotSupportedException("Source code file edition is not supported on this platform.");

/// <inheritdoc />
public ValueTask<ReConnect?> Apply(FileEdit edition, CancellationToken ct)
=> throw new NotSupportedException("Source code file edition is not supported on this platform.");
}
}
#endif
Loading

0 comments on commit 634f0d4

Please sign in to comment.