Skip to content
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
37 changes: 37 additions & 0 deletions Assets/Tests/Editor/CompileStatusBridgeCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public void BuildResponse_WhenUnityIsIdleAndResultMatches_ReturnsReadyResult()
isCompiling: false,
isUpdating: false,
isDomainReloadInProgress: false,
isSnapshotCaptureHolding: false,
_compileSessionLifecycleService,
_compileResultSessionRepository,
_pendingCompileSessionRepository);
Expand All @@ -77,6 +78,7 @@ public void BuildResponse_WhenUnityIsStillCompiling_ReturnsNotReadyWithStoredRes
isCompiling: true,
isUpdating: false,
isDomainReloadInProgress: false,
isSnapshotCaptureHolding: false,
_compileSessionLifecycleService,
_compileResultSessionRepository,
_pendingCompileSessionRepository);
Expand All @@ -101,6 +103,7 @@ public void BuildResponse_WhenRequestIdDiffers_DoesNotReturnStaleResult()
isCompiling: false,
isUpdating: false,
isDomainReloadInProgress: false,
isSnapshotCaptureHolding: false,
_compileSessionLifecycleService,
_compileResultSessionRepository,
_pendingCompileSessionRepository);
Expand All @@ -124,6 +127,7 @@ public void BuildResponse_WhenUnityIsIdleAndPendingRequestHasNoReloadSignal_Wait
isCompiling: false,
isUpdating: false,
isDomainReloadInProgress: false,
isSnapshotCaptureHolding: false,
_compileSessionLifecycleService,
_compileResultSessionRepository,
_pendingCompileSessionRepository);
Expand Down Expand Up @@ -152,6 +156,7 @@ public void BuildResponse_WhenUnityIsIdleAndPendingRequestObservedReload_Returns
isCompiling: false,
isUpdating: false,
isDomainReloadInProgress: false,
isSnapshotCaptureHolding: false,
_compileSessionLifecycleService,
_compileResultSessionRepository,
_pendingCompileSessionRepository);
Expand Down Expand Up @@ -182,6 +187,37 @@ public void BuildResponse_WhenUnityIsBusyAndPendingRequestHasNoResult_WaitsForRe
isCompiling: true,
isUpdating: false,
isDomainReloadInProgress: false,
isSnapshotCaptureHolding: false,
_compileSessionLifecycleService,
_compileResultSessionRepository,
_pendingCompileSessionRepository);

Assert.That(response.Ready, Is.False);
Assert.That(response.HasResult, Is.False);
Assert.That(response.Result, Is.Null);
Assert.That(
UnityCliLoopEditorSessionStateTestFactory.GetSinglePendingCompileRequest(
_pendingCompileSessionRepository).HasRequest,
Is.True);
}

[Test]
public void BuildResponse_WhileTheSnapshotCaptureHolds_IsNotReadyAndDoesNotRecoverAPendingResult()
{
// Verifies an idle Unity still withholds the compile completion, and leaves the pending
// request for a later poll, while the hot reload source snapshot capture is running.
_compileSessionLifecycleService.MarkPendingCompileRequest(
"compile_test_request",
forceRecompile: false,
markedAtUtc: System.DateTime.UtcNow);
_pendingCompileSessionRepository.MarkPendingCompileRequestReloadObserved();

GetCompileStatusResponse response = CompileStatusBridgeCommand.BuildResponse(
"compile_test_request",
isCompiling: false,
isUpdating: false,
isDomainReloadInProgress: false,
isSnapshotCaptureHolding: true,
_compileSessionLifecycleService,
_compileResultSessionRepository,
_pendingCompileSessionRepository);
Expand Down Expand Up @@ -210,6 +246,7 @@ public void BuildResponse_WhenForceCompilePendingRequestHasNoResult_ReturnsExpla
isCompiling: false,
isUpdating: false,
isDomainReloadInProgress: false,
isSnapshotCaptureHolding: false,
_compileSessionLifecycleService,
_compileResultSessionRepository,
_pendingCompileSessionRepository);
Expand Down
75 changes: 73 additions & 2 deletions Assets/Tests/Editor/HotReload/HotReloadDomainTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ namespace io.github.hatayama.UnityCliLoop.Tests.Editor.HotReload
public class HotReloadDomainTests
{
private const string FileOne = "Assets/Tests/Editor/HotReload/DomainFileOne.cs";
// Real sources of this test assembly: one its PDB has a document for, one it has none for.
private const string CompiledFixtureFile = "Assets/Tests/Editor/HotReload/HotReloadLiveSourceBomFixture.cs";
private const string BodylessFixtureFile = "Assets/Tests/Editor/HotReload/HotReloadSnapshotBodylessFixture.cs";
private const string FileTwo = "Assets/Tests/Editor/HotReload/DomainFileTwo.cs";
private const string AddedMethodKey = "DomainHost.AddedPing(System.Int32)";
private const string OtherAddedMethodKey = "DomainHost.AddedPong()";
Expand Down Expand Up @@ -1050,6 +1053,74 @@ public void GetVerifiedSnapshotSource_WhenTheCaptureThrows_PassesTheExceptionOn(
Assert.That(thrown, Is.SameAs(captureFailure));
}

/// <summary>
/// What: while a capture runs, a live file the PDB confirms is returned without waiting for
/// the capture, so a pause point re-armed on the first update tick does not stall on it.
/// </summary>
[Test]
public void GetVerifiedSnapshotSource_WhileCaptureRuns_ReturnsTheLiveFileWhenItMatchesThePdbWithoutWaiting()
{
int ensureCount = 0;
HotReloadPausePointPort port = new HotReloadPausePointPort(
_access.Domain,
_ => RecordedSourceHash,
() => ensureCount++,
() => true);

string source = port.GetVerifiedSnapshotSource(CompiledFixtureFile, TestAssemblyDllPath());

Assert.That(source, Is.EqualTo(File.ReadAllText(Path.Combine(ProjectRoot(), CompiledFixtureFile))));
Assert.That(ensureCount, Is.EqualTo(0));
}

/// <summary>
/// What: while a capture runs, a live file the PDB cannot confirm (here it has no document)
/// makes the port wait for the capture once and read the snapshot instead.
/// </summary>
[Test]
public void GetVerifiedSnapshotSource_WhileCaptureRuns_WaitsWhenTheLiveFileDiffersFromThePdb()
{
int ensureCount = 0;
HotReloadPausePointPort port = new HotReloadPausePointPort(
_access.Domain,
_ => RecordedSourceHash,
() => ensureCount++,
() => true);

port.GetVerifiedSnapshotSource(BodylessFixtureFile, TestAssemblyDllPath());

Assert.That(ensureCount, Is.EqualTo(1));
}

/// <summary>
/// What: with no capture running, the port waits for the capture and reads the snapshot even
/// when the live file matches the PDB, so the answer is the one it gave before captures ran
/// in the background.
/// </summary>
[Test]
public void GetVerifiedSnapshotSource_WhenNoCaptureRuns_EnsuresTheCaptureEvenIfTheLiveFileMatches()
{
int ensureCount = 0;
HotReloadPausePointPort port = CreatePortWithSnapshotCapture(() => ensureCount++);

port.GetVerifiedSnapshotSource(CompiledFixtureFile, TestAssemblyDllPath());

Assert.That(ensureCount, Is.EqualTo(1));
}

private static string ProjectRoot()
{
return Path.GetFullPath(Path.Combine(Application.dataPath, ".."));
}

private static string TestAssemblyDllPath()
{
return Path.Combine(
ProjectRoot(),
HotReloadConstants.ScriptAssembliesRelativeDirectory,
typeof(HotReloadDomainTests).Assembly.GetName().Name + HotReloadConstants.CompiledAssemblyExtension);
}

// Why a generated name: an artifact assembly is compiled under a name of its own, so a
// fixture that reused a project assembly's name would not resolve the way production does.
private static Assembly CreateIntroducedTypeAssembly()
Expand All @@ -1071,12 +1142,12 @@ private static void AssertRow(

private HotReloadPausePointPort CreatePort(Func<string, string> readSourceContentHashOrNull)
{
return new HotReloadPausePointPort(_access.Domain, readSourceContentHashOrNull, () => { });
return new HotReloadPausePointPort(_access.Domain, readSourceContentHashOrNull, () => { }, () => false);
}

private HotReloadPausePointPort CreatePortWithSnapshotCapture(Action ensureSnapshotCaptured)
{
return new HotReloadPausePointPort(_access.Domain, _ => RecordedSourceHash, ensureSnapshotCaptured);
return new HotReloadPausePointPort(_access.Domain, _ => RecordedSourceHash, ensureSnapshotCaptured, () => false);
}

// Records a partially applied reload of FileOne that read RecordedWorkerSourcePath.
Expand Down
14 changes: 14 additions & 0 deletions Assets/Tests/Editor/HotReload/HotReloadLiveSourceBomFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace io.github.hatayama.UnityCliLoop.Tests.Editor.HotReload
{
/// <summary>
/// Fixture saved with a UTF-8 byte order mark, so the live-source tests can check that the live
/// file and the snapshot decode the mark the same way. Its method body gives it a PDB document.
/// </summary>
internal static class HotReloadLiveSourceBomFixture
{
internal static int Answer()
{
return 42;
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

148 changes: 148 additions & 0 deletions Assets/Tests/Editor/HotReload/HotReloadLiveSourceVerificationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
using System;
using System.IO;

using NUnit.Framework;

using UnityEngine;

using io.github.hatayama.UnityCliLoop.FirstPartyTools;

namespace io.github.hatayama.UnityCliLoop.Tests.Editor.HotReload
{
/// <summary>
/// EditMode coverage for reading a source file on disk only when the compiled assembly's PDB
/// confirms it holds the bytes the compiler read.
/// </summary>
public class HotReloadLiveSourceVerificationTests
{
private const string TestAssemblyName = "UnityCLILoop.Tests.Editor.HotReload";
private const string BomFixtureProjectRelativePath =
"Assets/Tests/Editor/HotReload/HotReloadLiveSourceBomFixture.cs";
private const string BodylessFixtureProjectRelativePath =
"Assets/Tests/Editor/HotReload/HotReloadSnapshotBodylessFixture.cs";

private string _temporaryRoot;

[SetUp]
public void SetUp()
{
_temporaryRoot = Path.Combine(Path.GetTempPath(), "uloop-live-source-" + Guid.NewGuid().ToString("N"));
}

[TearDown]
public void TearDown()
{
if (Directory.Exists(_temporaryRoot))
{
Directory.Delete(_temporaryRoot, recursive: true);
}
}

/// <summary>
/// What: a live file and a snapshot holding the same bytes, a byte order mark included, decode
/// to the same text, and the mark is not part of it.
/// </summary>
[Test]
public void LoadVerifiedLiveSourceOrNull_ReturnsTheSameTextAsTheSnapshotForTheSameBytes()
{
string dllPath = TestAssemblyDllPath();
byte[] compiledBytes = File.ReadAllBytes(Path.Combine(ProjectRoot(), BomFixtureProjectRelativePath));
Assert.That(compiledBytes[0], Is.EqualTo(0xEF), "Precondition: the fixture must start with a byte order mark.");
WriteLiveFile(BomFixtureProjectRelativePath, compiledBytes);
string snapshotDirectory = HotReloadSourceSnapshotLayout.AssemblyDirectory(
_temporaryRoot,
TestAssemblyName,
HotReloadAssemblyMvid.Read(dllPath));
Directory.CreateDirectory(snapshotDirectory);
File.WriteAllBytes(
HotReloadSourceSnapshotLayout.SourcePath(snapshotDirectory, BomFixtureProjectRelativePath),
compiledBytes);

string live = HotReloadSourceBaseline.LoadVerifiedLiveSourceAt(
_temporaryRoot,
BomFixtureProjectRelativePath,
dllPath,
HotReloadPdbDocumentIndex.Shared);
string snapshot = HotReloadSourceBaseline.LoadVerifiedSnapshotSourceAt(
_temporaryRoot,
BomFixtureProjectRelativePath,
dllPath,
HotReloadPdbDocumentIndex.Shared);

Assert.That(live, Is.Not.Null);
Assert.That(live, Is.EqualTo(snapshot));
Assert.That(live[0], Is.Not.EqualTo(''));
}

/// <summary>
/// What: a live file whose bytes differ from the checksum the PDB recorded is not returned.
/// </summary>
[Test]
public void LoadVerifiedLiveSourceAt_WhenTheLiveBytesDifferFromThePdb_ReturnsNull()
{
byte[] editedBytes = File.ReadAllBytes(Path.Combine(ProjectRoot(), BomFixtureProjectRelativePath));
editedBytes[editedBytes.Length - 1] = (byte)(editedBytes[editedBytes.Length - 1] ^ 0xFF);
WriteLiveFile(BomFixtureProjectRelativePath, editedBytes);

string live = HotReloadSourceBaseline.LoadVerifiedLiveSourceAt(
_temporaryRoot,
BomFixtureProjectRelativePath,
TestAssemblyDllPath(),
HotReloadPdbDocumentIndex.Shared);

Assert.That(live, Is.Null);
}

/// <summary>
/// What: a source that is not on disk is not returned.
/// </summary>
[Test]
public void LoadVerifiedLiveSourceAt_WhenTheFileIsMissing_ReturnsNull()
{
Directory.CreateDirectory(_temporaryRoot);

string live = HotReloadSourceBaseline.LoadVerifiedLiveSourceAt(
_temporaryRoot,
BomFixtureProjectRelativePath,
TestAssemblyDllPath(),
HotReloadPdbDocumentIndex.Shared);

Assert.That(live, Is.Null);
}

/// <summary>
/// What: a source the PDB has no document for is not returned, because nothing confirms it.
/// </summary>
[Test]
public void LoadVerifiedLiveSourceAt_WhenThePdbHasNoDocument_ReturnsNull()
{
string live = HotReloadSourceBaseline.LoadVerifiedLiveSourceAt(
ProjectRoot(),
BodylessFixtureProjectRelativePath,
TestAssemblyDllPath(),
HotReloadPdbDocumentIndex.Shared);

Assert.That(live, Is.Null);
}

private void WriteLiveFile(string projectRelativePath, byte[] bytes)
{
string livePath = Path.Combine(_temporaryRoot, projectRelativePath.Replace('/', Path.DirectorySeparatorChar));
Directory.CreateDirectory(Path.GetDirectoryName(livePath));
File.WriteAllBytes(livePath, bytes);
}

private static string ProjectRoot()
{
return Path.GetFullPath(Path.Combine(Application.dataPath, ".."));
}

private static string TestAssemblyDllPath()
{
return Path.Combine(
ProjectRoot(),
HotReloadConstants.ScriptAssembliesRelativeDirectory,
TestAssemblyName + HotReloadConstants.CompiledAssemblyExtension);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,10 @@ private static void Install(HotReloadServices services)
domain,
path => new HotReloadSourceContentHasher().TryComputeContentHashOfFileOrNull(path),
() => services.SourceSnapshotCapture.EnsureCaptured(
HotReloadConstants.SourceSnapshotCaptureTriggerPausePoint));
HotReloadConstants.SourceSnapshotCaptureTriggerPausePoint),
// The capture still runs synchronously inside EnsureCaptured, so none is ever in
// flight when a pause point asks.
() => false);
// Attaching last keeps the invariant across the gap: the resolver only starts
// answering binds once every gateway already points at the domain behind it.
domain.IntroducedTypeResolver.Resume();
Expand Down
Loading
Loading