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
176 changes: 153 additions & 23 deletions Assets/Tests/Editor/DomainReloadDetectionServiceTests.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
using System.IO;

using NUnit.Framework;

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 @@ -11,27 +14,42 @@ namespace io.github.hatayama.UnityCliLoop.Tests.Editor
/// </summary>
public class DomainReloadDetectionServiceTests
{
private UnityCliLoopEditorSettingsData _originalSettings;
private UnityCliLoopEditorSettingsService _editorSettingsService;
private static readonly string SettingsFilePath =
Path.Combine(UnityCliLoopConstants.USER_SETTINGS_FOLDER, UnityCliLoopConstants.SETTINGS_FILE_NAME);

private UnityCliLoopEditorSessionStateService _sessionStateService;
private UnityCliLoopEditorSessionStateSnapshot _originalSessionState;
private IDomainReloadDetectionService _domainReloadDetectionService;
private ServerReadinessStateStore _stateStore;
private bool _settingsFileExisted;
private string _settingsFileContent;

[SetUp]
public void SetUp()
{
_editorSettingsService = UnityCliLoopEditorSettingsTestFactory.CreateService();
_originalSettings = CloneSettings(_editorSettingsService.GetSettings());
_settingsFileExisted = File.Exists(SettingsFilePath);
_settingsFileContent = _settingsFileExisted ? File.ReadAllText(SettingsFilePath) : null;
if (!Directory.Exists(UnityCliLoopConstants.USER_SETTINGS_FOLDER))
{
Directory.CreateDirectory(UnityCliLoopConstants.USER_SETTINGS_FOLDER);
}

DeleteIfExists(SettingsFilePath);
_sessionStateService = UnityCliLoopEditorSessionStateTestFactory.CreateService();
_originalSessionState = UnityCliLoopEditorSessionStateTestFactory.CaptureSnapshot(_sessionStateService);
_sessionStateService.ClearAll();
_stateStore = CreateTestStateStore();
_domainReloadDetectionService = new DomainReloadDetectionFileService(_editorSettingsService, _stateStore);
_domainReloadDetectionService = new DomainReloadDetectionFileService(_sessionStateService, _stateStore);
UnityCliLoopEditorDomainReloadStateProvider.SetDomainReloadInProgressFromMainThread(false);
}

[TearDown]
public void TearDown()
{
_editorSettingsService.SaveSettings(_originalSettings);
_originalSessionState.Restore(_sessionStateService);
UnityCliLoopEditorDomainReloadStateProvider.SetDomainReloadInProgressFromMainThread(false);
_stateStore.Delete();
RestoreFile(SettingsFilePath, _settingsFileExisted, _settingsFileContent);
}

[Test]
Expand All @@ -43,34 +61,108 @@ public void RollbackDomainReloadStart_ClearsTemporaryFlagsProviderStateAndPublis

_domainReloadDetectionService.StartDomainReload(correlationId, true);

UnityCliLoopEditorSettingsData startedSettings = _editorSettingsService.GetSettings();
Assert.That(startedSettings.isServerRunning, Is.True);
Assert.That(startedSettings.isAfterCompile, Is.True);
Assert.That(startedSettings.isDomainReloadInProgress, Is.True);
Assert.That(startedSettings.isReconnecting, Is.True);
Assert.That(startedSettings.showReconnectingUI, Is.True);
Assert.That(startedSettings.showPostCompileReconnectingUI, Is.True);
Assert.That(_sessionStateService.GetIsServerRunning(), Is.True);
Assert.That(_sessionStateService.GetIsAfterCompile(), Is.True);
Assert.That(_sessionStateService.GetIsDomainReloadInProgress(), Is.True);
Assert.That(_sessionStateService.GetIsReconnecting(), Is.True);
Assert.That(_sessionStateService.GetShowReconnectingUI(), Is.True);
Assert.That(_sessionStateService.GetShowPostCompileReconnectingUI(), Is.True);
Assert.That(provider.IsDomainReloadInProgress(), Is.True);

_domainReloadDetectionService.RollbackDomainReloadStart(correlationId);

UnityCliLoopEditorSettingsData rolledBackSettings = _editorSettingsService.GetSettings();
Assert.That(rolledBackSettings.isServerRunning, Is.True);
Assert.That(rolledBackSettings.isAfterCompile, Is.False);
Assert.That(rolledBackSettings.isDomainReloadInProgress, Is.False);
Assert.That(rolledBackSettings.isReconnecting, Is.False);
Assert.That(rolledBackSettings.showReconnectingUI, Is.False);
Assert.That(rolledBackSettings.showPostCompileReconnectingUI, Is.False);
Assert.That(_sessionStateService.GetIsServerRunning(), Is.True);
Assert.That(_sessionStateService.GetIsAfterCompile(), Is.False);
Assert.That(_sessionStateService.GetIsDomainReloadInProgress(), Is.False);
Assert.That(_sessionStateService.GetIsReconnecting(), Is.False);
Assert.That(_sessionStateService.GetShowReconnectingUI(), Is.False);
Assert.That(_sessionStateService.GetShowPostCompileReconnectingUI(), Is.False);
Assert.That(provider.IsDomainReloadInProgress(), Is.False);
ServerReadinessState state = _stateStore.Read();
Assert.That(state.Phase, Is.EqualTo("failed"));
Assert.That(state.LastError, Is.Not.Empty);
}

private static UnityCliLoopEditorSettingsData CloneSettings(UnityCliLoopEditorSettingsData settings)
[Test]
public void CompleteDomainReload_WhenLegacyReloadStateExists_MigratesRecoveryFlagsToSessionState()
{
// Verifies that the first reload after migration preserves old JSON recovery state.
UnityCliLoopEditorLegacySessionState legacySessionState = new(
isServerRunning: true,
isAfterCompile: true,
isDomainReloadInProgress: true,
isReconnecting: true,
showReconnectingUI: true,
showPostCompileReconnectingUI: true);
_domainReloadDetectionService = new DomainReloadDetectionFileService(
_sessionStateService,
_stateStore,
new TestLegacySessionStateReader(legacySessionState));

_domainReloadDetectionService.CompleteDomainReload("test-correlation");

Assert.That(_sessionStateService.GetIsServerRunning(), Is.True);
Assert.That(_sessionStateService.GetIsAfterCompile(), Is.True);
Assert.That(_sessionStateService.GetIsDomainReloadInProgress(), Is.False);
Assert.That(_sessionStateService.GetIsReconnecting(), Is.True);
Assert.That(_sessionStateService.GetShowReconnectingUI(), Is.True);
Assert.That(_sessionStateService.GetShowPostCompileReconnectingUI(), Is.True);
ServerReadinessState state = _stateStore.Read();
Assert.That(state.Phase, Is.EqualTo("recovering"));
}

[Test]
public void CompleteDomainReload_WhenLegacyStateOnlySaysRunning_IgnoresStaleRunningFlag()
{
string json = UnityEngine.JsonUtility.ToJson(settings);
return UnityEngine.JsonUtility.FromJson<UnityCliLoopEditorSettingsData>(json);
// Verifies that stale running-only JSON does not opt into recovery after the migration.
UnityCliLoopEditorLegacySessionState legacySessionState = new(
isServerRunning: true,
isAfterCompile: false,
isDomainReloadInProgress: false,
isReconnecting: false,
showReconnectingUI: false,
showPostCompileReconnectingUI: false);
_domainReloadDetectionService = new DomainReloadDetectionFileService(
_sessionStateService,
_stateStore,
new TestLegacySessionStateReader(legacySessionState));

_domainReloadDetectionService.CompleteDomainReload("test-correlation");

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

[Test]
public void CompleteDomainReload_WhenLegacyReloadStateWasMigrated_DoesNotReapplyLegacyJson()
{
// Verifies that legacy JSON recovery state is consumed after the first migration reload.
File.WriteAllText(
SettingsFilePath,
"{" +
"\"isServerRunning\":true," +
"\"isAfterCompile\":true," +
"\"isDomainReloadInProgress\":true," +
"\"isReconnecting\":true," +
"\"showReconnectingUI\":true," +
"\"showPostCompileReconnectingUI\":true" +
"}");
_domainReloadDetectionService = new DomainReloadDetectionFileService(
_sessionStateService,
_stateStore,
new UnityCliLoopEditorLegacySessionStateReader());

_domainReloadDetectionService.CompleteDomainReload("first-correlation");
_sessionStateService.ClearAll();

_domainReloadDetectionService.CompleteDomainReload("second-correlation");

Assert.That(_sessionStateService.GetIsServerRunning(), Is.False);
Assert.That(_sessionStateService.GetIsAfterCompile(), Is.False);
Assert.That(_sessionStateService.GetIsReconnecting(), Is.False);
ServerReadinessState state = _stateStore.Read();
Assert.That(state.Phase, Is.EqualTo("stopped"));
}

private static ServerReadinessStateStore CreateTestStateStore()
Expand All @@ -81,5 +173,43 @@ private static ServerReadinessStateStore CreateTestStateStore()
System.Guid.NewGuid().ToString("N"));
return new ServerReadinessStateStore(projectRoot);
}

private static void RestoreFile(string path, bool existed, string content)
{
if (existed)
{
File.WriteAllText(path, content);
return;
}

DeleteIfExists(path);
}

private static void DeleteIfExists(string path)
{
if (File.Exists(path))
{
File.Delete(path);
}
}

private sealed class TestLegacySessionStateReader : IUnityCliLoopEditorLegacySessionStateReader
{
private readonly UnityCliLoopEditorLegacySessionState _legacySessionState;

internal TestLegacySessionStateReader(UnityCliLoopEditorLegacySessionState legacySessionState)
{
_legacySessionState = legacySessionState;
}

public UnityCliLoopEditorLegacySessionState Read()
{
return _legacySessionState;
}

public void Clear()
{
}
}
}
}
56 changes: 23 additions & 33 deletions Assets/Tests/Editor/DomainReloadRecoveryUseCaseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,87 +15,77 @@ namespace io.github.hatayama.UnityCliLoop.Tests.Editor
/// </summary>
public class DomainReloadRecoveryUseCaseTests
{
private bool _originalIsServerRunning;
private UnityCliLoopEditorSettingsService _editorSettingsService;
private UnityCliLoopEditorSessionStateService _sessionStateService;
private UnityCliLoopEditorSessionStateSnapshot _originalSessionState;
private IDomainReloadDetectionService _domainReloadDetectionService;
private ServerReadinessStateStore _stateStore;

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

[TearDown]
public void TearDown()
{
// Restore original session state
_editorSettingsService.UpdateSettings(s => s with
{
isServerRunning = _originalIsServerRunning,
isAfterCompile = false,
isDomainReloadInProgress = false,
isReconnecting = false,
showReconnectingUI = false,
showPostCompileReconnectingUI = false
});

_originalSessionState.Restore(_sessionStateService);
_stateStore.Delete();
}

[Test]
public void ExecuteBeforeDomainReload_ShouldUseSessionState_WhenServerInstanceIsNull()
{
// Arrange
_editorSettingsService.SetIsServerRunning(true);
_sessionStateService.SetIsServerRunning(true);

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

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

// Assert
Assert.IsTrue(result.Success, "ExecuteBeforeDomainReload should succeed");
Assert.IsTrue(_editorSettingsService.GetIsAfterCompile(), "IsAfterCompile should be set to true");
Assert.IsTrue(_sessionStateService.GetIsAfterCompile(), "IsAfterCompile should be set to true");
}

[Test]
public void ExecuteBeforeDomainReload_ShouldNotSaveState_WhenBothInstanceAndSessionAreNotRunning()
{
// Arrange
_editorSettingsService.SetIsServerRunning(false);
_editorSettingsService.UpdateSettings(s => s with { isAfterCompile = false });
_sessionStateService.SetIsServerRunning(false);
_sessionStateService.SetIsAfterCompile(false);

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

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

// Assert
Assert.IsTrue(result.Success, "ExecuteBeforeDomainReload should succeed");
Assert.IsFalse(_editorSettingsService.GetIsAfterCompile(), "IsAfterCompile should remain false when server was not running");
Assert.IsFalse(_sessionStateService.GetIsAfterCompile(), "IsAfterCompile should remain false when server was not running");
}

[Test]
public void ExecuteBeforeDomainReload_ShouldPreferInstanceState_WhenInstanceIsRunning()
{
// Arrange
_editorSettingsService.SetIsServerRunning(true);
_sessionStateService.SetIsServerRunning(true);

TestServerInstance server = new TestServerInstance();
server.StartServer();

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

// Act
ServiceResult<string> result = useCase.ExecuteBeforeDomainReload(server);
Expand All @@ -111,7 +101,7 @@ public void ExecuteBeforeDomainReload_ShouldPreferInstanceState_WhenInstanceIsRu
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);
_sessionStateService.SetIsServerRunning(false);
_domainReloadDetectionService.StartDomainReload("test-correlation", serverIsRunning: false);

_domainReloadDetectionService.CompleteDomainReload("test-correlation");
Expand All @@ -124,13 +114,13 @@ public void CompleteDomainReload_WhenServerWasNotRunning_ShouldPublishStoppedSta
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 });
_sessionStateService.SetIsServerRunning(true);
_sessionStateService.SetIsAfterCompile(false);
TestRecoveryCoordinator recoveryCoordinator = new();
SessionRecoveryService service = new(
recoveryCoordinator,
_domainReloadDetectionService,
_editorSettingsService);
_sessionStateService);

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

Expand All @@ -151,18 +141,18 @@ private static ServerReadinessStateStore CreateTestStateStore()

private static DomainReloadRecoveryUseCase CreateUseCase(
IDomainReloadDetectionService domainReloadDetectionService,
UnityCliLoopEditorSettingsService editorSettingsService)
UnityCliLoopEditorSessionStateService sessionStateService)
{
TestRecoveryCoordinator recoveryCoordinator = new();
SessionRecoveryService sessionRecoveryService =
new SessionRecoveryService(
recoveryCoordinator,
domainReloadDetectionService,
editorSettingsService);
sessionStateService);
return new DomainReloadRecoveryUseCase(
sessionRecoveryService,
domainReloadDetectionService,
editorSettingsService);
sessionStateService);
}

/// <summary>
Expand Down
1 change: 1 addition & 0 deletions Assets/Tests/Editor/StaticFacadeStateGuardTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public sealed class StaticFacadeStateGuardTests
"Packages/src/Editor/Application/SessionRecoveryService.cs",
"Packages/src/Editor/Application/UseCases/SkillSetupUseCase.cs",
"Packages/src/Editor/Domain/UnityCliLoopEditorSettingsService.cs",
"Packages/src/Editor/Domain/UnityCliLoopEditorSessionStateService.cs",
"Packages/src/Editor/Domain/ToolSettingsService.cs",
"Packages/src/Editor/Infrastructure/Server/DomainReloadDetectionFileService.cs"
};
Expand Down
Loading
Loading