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
63 changes: 63 additions & 0 deletions Assets/Tests/Editor/AtomicFileWriterTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System;
using System.IO;
using NUnit.Framework;

using io.github.hatayama.UnityCliLoop.Infrastructure;

namespace io.github.hatayama.UnityCliLoop.Tests.Editor
{
public sealed class AtomicFileWriterTests
{
[Test]
public void RecoverSidecarFiles_WhenOnlyInProgressTempExists_ShouldLeaveTargetMissing()
{
// Tests that recovery does not promote a file that may still be mid-write.
string root = CreateTestRoot();
string filePath = Path.Combine(root, "state.json");
Directory.CreateDirectory(root);

try
{
File.WriteAllText(filePath + ".tmp.write", "{\"phase\":\"starting\"}");

AtomicFileWriter.RecoverSidecarFiles(filePath);

Assert.That(File.Exists(filePath), Is.False);
Assert.That(File.Exists(filePath + ".tmp.write"), Is.True);
}
finally
{
Directory.Delete(root, recursive: true);
}
}

[Test]
public void Write_WhenTargetMissing_ShouldPublishTargetAndRemoveInProgressTemp()
{
// Tests that the externally visible temp sidecar is only used after content is fully written.
string root = CreateTestRoot();
string filePath = Path.Combine(root, "state.json");
Directory.CreateDirectory(root);

try
{
AtomicFileWriter.Write(filePath, "{\"phase\":\"ready\"}");

Assert.That(File.ReadAllText(filePath), Is.EqualTo("{\"phase\":\"ready\"}"));
Assert.That(File.Exists(filePath + ".tmp.write"), Is.False);
}
finally
{
Directory.Delete(root, recursive: true);
}
}

private static string CreateTestRoot()
{
return Path.Combine(
Path.GetTempPath(),
"unity-cli-loop-tests",
Guid.NewGuid().ToString("N"));
}
}
}
11 changes: 11 additions & 0 deletions Assets/Tests/Editor/AtomicFileWriterTests.cs.meta

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

74 changes: 74 additions & 0 deletions Assets/Tests/Editor/CompilationLockFileServiceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using NUnit.Framework;

using io.github.hatayama.UnityCliLoop.Infrastructure;

namespace io.github.hatayama.UnityCliLoop.Tests.Editor
{
public class CompilationLockFileServiceTests
{
private ServerReadinessStateStore _stateStore;
private CompilationLockFileService _service;

[SetUp]
public void SetUp()
{
_stateStore = CreateTestStateStore();
_service = new CompilationLockFileService(_stateStore);
}

[TearDown]
public void TearDown()
{
_service.DeleteLockFile();
_stateStore.Delete();
}

[Test]
public void MarkCompilationFinished_WhenPreviousStateWasReady_ShouldRestoreReadyState()
{
// Verifies compile failures return CLI waiters to the already-probed ready state.
_stateStore.Write(
ServerReadinessPhase.Ready,
"previous-ready",
"server-ready",
"project-ipc-endpoint",
null);

_service.MarkCompilationStarted();

_service.MarkCompilationFinished();

ServerReadinessState state = _stateStore.Read();
Assert.That(state.Phase, Is.EqualTo("ready"));
Assert.That(state.Endpoint, Is.EqualTo("project-ipc-endpoint"));
}

[Test]
public void MarkCompilationFinished_WhenPreviousStateWasStarting_ShouldRestoreStartingState()
{
// Verifies startup recovery is not marked ready by a compile-finished callback.
_stateStore.Write(
ServerReadinessPhase.Starting,
"previous-starting",
"manual-start",
null,
null);

_service.MarkCompilationStarted();

_service.MarkCompilationFinished();

ServerReadinessState state = _stateStore.Read();
Assert.That(state.Phase, Is.EqualTo("starting"));
}

private static ServerReadinessStateStore CreateTestStateStore()
{
string projectRoot = System.IO.Path.Combine(
System.IO.Path.GetTempPath(),
"unity-cli-loop-tests",
System.Guid.NewGuid().ToString("N"));
return new ServerReadinessStateStore(projectRoot);
}
}
}
11 changes: 11 additions & 0 deletions Assets/Tests/Editor/CompilationLockFileServiceTests.cs.meta

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

14 changes: 13 additions & 1 deletion Assets/Tests/Editor/DomainReloadDetectionServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ public class DomainReloadDetectionServiceTests
private UnityCliLoopEditorSettingsData _originalSettings;
private UnityCliLoopEditorSettingsService _editorSettingsService;
private IDomainReloadDetectionService _domainReloadDetectionService;
private ServerReadinessStateStore _stateStore;

[SetUp]
public void SetUp()
{
_editorSettingsService = UnityCliLoopEditorSettingsTestFactory.CreateService();
_originalSettings = CloneSettings(_editorSettingsService.GetSettings());
_domainReloadDetectionService = new DomainReloadDetectionFileService(_editorSettingsService);
_stateStore = CreateTestStateStore();
_domainReloadDetectionService = new DomainReloadDetectionFileService(_editorSettingsService, _stateStore);
UnityCliLoopEditorDomainReloadStateProvider.SetDomainReloadInProgressFromMainThread(false);
_domainReloadDetectionService.DeleteLockFile();
}
Expand All @@ -31,6 +33,7 @@ public void TearDown()
_editorSettingsService.SaveSettings(_originalSettings);
UnityCliLoopEditorDomainReloadStateProvider.SetDomainReloadInProgressFromMainThread(false);
_domainReloadDetectionService.DeleteLockFile();
_stateStore.Delete();
}

[Test]
Expand Down Expand Up @@ -69,5 +72,14 @@ private static UnityCliLoopEditorSettingsData CloneSettings(UnityCliLoopEditorSe
string json = UnityEngine.JsonUtility.ToJson(settings);
return UnityEngine.JsonUtility.FromJson<UnityCliLoopEditorSettingsData>(json);
}

private static ServerReadinessStateStore CreateTestStateStore()
{
string projectRoot = System.IO.Path.Combine(
System.IO.Path.GetTempPath(),
"unity-cli-loop-tests",
System.Guid.NewGuid().ToString("N"));
return new ServerReadinessStateStore(projectRoot);
}
}
}
105 changes: 83 additions & 22 deletions Assets/Tests/Editor/DomainReloadRecoveryUseCaseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using io.github.hatayama.UnityCliLoop.Application;
using io.github.hatayama.UnityCliLoop.Domain;
using io.github.hatayama.UnityCliLoop.Infrastructure;
using io.github.hatayama.UnityCliLoop.ToolContracts;

namespace io.github.hatayama.UnityCliLoop.Tests.Editor
{
Expand All @@ -17,14 +18,16 @@ public class DomainReloadRecoveryUseCaseTests
private bool _originalIsServerRunning;
private UnityCliLoopEditorSettingsService _editorSettingsService;
private IDomainReloadDetectionService _domainReloadDetectionService;
private ServerReadinessStateStore _stateStore;

[SetUp]
public void SetUp()
{
// Save original session state
_editorSettingsService = UnityCliLoopEditorSettingsTestFactory.CreateService();
_originalIsServerRunning = _editorSettingsService.GetIsServerRunning();
_domainReloadDetectionService = new DomainReloadDetectionFileService(_editorSettingsService);
_stateStore = CreateTestStateStore();
_domainReloadDetectionService = new DomainReloadDetectionFileService(_editorSettingsService, _stateStore);
}

[TearDown]
Expand All @@ -43,6 +46,7 @@ public void TearDown()

// Clean up lock file created by ExecuteBeforeDomainReload
_domainReloadDetectionService.DeleteLockFile();
_stateStore.Delete();
}

[Test]
Expand Down Expand Up @@ -88,30 +92,62 @@ public void ExecuteBeforeDomainReload_ShouldPreferInstanceState_WhenInstanceIsRu
// Arrange
_editorSettingsService.SetIsServerRunning(true);

// Create a running server instance
UnityCliLoopBridgeServer server = null;
try
{
server = new UnityCliLoopBridgeServer(
_domainReloadDetectionService,
_editorSettingsService);
server.StartServer();
TestServerInstance server = new TestServerInstance();
server.StartServer();

DomainReloadRecoveryUseCase useCase = CreateUseCase(
_domainReloadDetectionService,
_editorSettingsService);
DomainReloadRecoveryUseCase useCase = CreateUseCase(
_domainReloadDetectionService,
_editorSettingsService);

// Act
ServiceResult<string> result = useCase.ExecuteBeforeDomainReload(server);
// Act
ServiceResult<string> result = useCase.ExecuteBeforeDomainReload(server);

// Assert
Assert.IsTrue(result.Success, "ExecuteBeforeDomainReload should succeed");
Assert.IsFalse(server.IsRunning, "Running server instance should be stopped before domain reload");
}
finally
{
server?.Dispose();
}
// Assert
Assert.IsTrue(result.Success, "ExecuteBeforeDomainReload should succeed");
Assert.IsFalse(server.IsRunning, "Running server instance should be stopped before domain reload");
}

[Test]
public void CompleteDomainReload_WhenServerWasNotRunning_ShouldPublishStoppedState()
{
// Verifies that a domain reload with no server to recover does not leave CLI waiters in recovering state.
_editorSettingsService.SetIsServerRunning(false);
_domainReloadDetectionService.StartDomainReload("test-correlation", serverIsRunning: false);

_domainReloadDetectionService.CompleteDomainReload("test-correlation");

ServerReadinessState state = _stateStore.Read();
Assert.That(state.Phase, Is.EqualTo("stopped"));
Assert.That(_domainReloadDetectionService.IsLockFilePresent(), Is.False);
}

[Test]
public async Task RestoreServerStateIfNeededAsync_WhenRecoveryDoesNotStartServer_ShouldFail()
{
// Verifies that recovery is only reported as successful after a running server instance exists.
_editorSettingsService.SetIsServerRunning(true);
_editorSettingsService.UpdateSettings(s => s with { isAfterCompile = false });
TestRecoveryCoordinator recoveryCoordinator = new();
SessionRecoveryService service = new(
recoveryCoordinator,
_domainReloadDetectionService,
_editorSettingsService);

ValidationResult result = await service.RestoreServerStateIfNeededAsync(CancellationToken.None);

Assert.That(result.IsValid, Is.False);
Assert.That(
result.ErrorMessage,
Is.EqualTo("Unity CLI Loop server recovery finished, but no running server instance is available."));
}

private static ServerReadinessStateStore CreateTestStateStore()
{
string projectRoot = System.IO.Path.Combine(
System.IO.Path.GetTempPath(),
"unity-cli-loop-tests",
System.Guid.NewGuid().ToString("N"));
return new ServerReadinessStateStore(projectRoot);
}

private static DomainReloadRecoveryUseCase CreateUseCase(
Expand Down Expand Up @@ -142,5 +178,30 @@ public Task StartRecoveryIfNeededAsync(bool isAfterCompile, CancellationToken ca
return Task.CompletedTask;
}
}

/// <summary>
/// Test support type used by editor and play mode fixtures.
/// </summary>
private sealed class TestServerInstance : IUnityCliLoopServerInstance
{
public bool IsRunning { get; private set; }

public string Endpoint => "test";

public void StartServer(bool clearServerStartingLockWhenReady = true)
{
IsRunning = true;
}

public void StopServer()
{
IsRunning = false;
}

public void Dispose()
{
IsRunning = false;
}
}
}
}
22 changes: 22 additions & 0 deletions Assets/Tests/Editor/ProjectIpcWarmupClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,28 @@ public void ParseContentLength_WhenPayloadExceedsLimit_Throws()
Assert.That(exception.Message, Does.Contain("invalid Content-Length"));
}

[Test]
public void ValidateJsonRpcSuccessResponse_WhenResponseContainsError_Throws()
{
// Tests that warmup response validation rejects server-side JSON-RPC errors.
ProjectIpcWarmupClient client = new();

InvalidOperationException exception = Assert.Throws<InvalidOperationException>(
() => client.ValidateJsonRpcSuccessResponse(
"{\"jsonrpc\":\"2.0\",\"id\":1,\"error\":{\"code\":-32603,\"message\":\"The installed uloop CLI is too old for this Unity package.\"}}"));

Assert.That(exception.Message, Does.Contain("too old"));
}

[Test]
public void ValidateJsonRpcSuccessResponse_WhenResponseContainsResult_DoesNotThrow()
{
// Tests that warmup response validation accepts successful JSON-RPC responses.
ProjectIpcWarmupClient client = new();

client.ValidateJsonRpcSuccessResponse("{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"ok\":true}}");
}

private static List<byte> HeaderBytes(string header)
{
return new List<byte>(Encoding.ASCII.GetBytes(header));
Expand Down
Loading
Loading