diff --git a/Assets/Tests/Editor/CompileResponseFactoryTests.cs b/Assets/Tests/Editor/CompileResponseFactoryTests.cs index 24cf2b3e2b..a3ac89f1e2 100644 --- a/Assets/Tests/Editor/CompileResponseFactoryTests.cs +++ b/Assets/Tests/Editor/CompileResponseFactoryTests.cs @@ -110,6 +110,48 @@ public void CreateResponse_WhenForceCompileHasOutcome_ExplainsNullDetails() Assert.That(response.Message, Is.EqualTo(ForceCompileUnknownResult.MessageText)); } + [Test] + public void CreateResponse_WhenIndeterminateNonForceCompileHasCounts_PreservesCountsWithoutIssues() + { + // Verifies indeterminate non-force results keep observed counts while withholding unreliable issue lists. + CompilerMessage error = new CompilerMessage + { + type = CompilerMessageType.Error, + message = "error", + file = "Assets/Test.cs", + line = 12 + }; + CompilerMessage warning = new CompilerMessage + { + type = CompilerMessageType.Warning, + message = "warning", + file = "Assets/Test.cs", + line = 15 + }; + CompileResult result = new CompileResult( + success: null, + errorCount: 1, + warningCount: 1, + completedAt: DateTime.Now, + messages: new[] { error, warning }, + errors: new[] { error }, + warnings: new[] { warning }, + isIndeterminate: true, + message: null); + + CompileResponse response = + CompileResponseFactory.CreateResponse(result, forceRecompile: false); + + Assert.That(response.Success, Is.Null); + Assert.That(response.ErrorCount, Is.EqualTo(1)); + Assert.That(response.WarningCount, Is.EqualTo(1)); + Assert.That(response.Errors, Is.Null); + Assert.That(response.Warnings, Is.Null); + Assert.That( + response.Message, + Is.EqualTo("Compilation status is unknown. Use get-logs to inspect the compiler output.")); + } + [Test] public void CreateResponse_WhenForceCompileHasPreservedFailure_MapsDetailedIssues() { diff --git a/Assets/Tests/Editor/CompileResultSessionRecorderTests.cs b/Assets/Tests/Editor/CompileResultSessionRecorderTests.cs new file mode 100644 index 0000000000..b70ab59283 --- /dev/null +++ b/Assets/Tests/Editor/CompileResultSessionRecorderTests.cs @@ -0,0 +1,79 @@ +using System; +using NUnit.Framework; +using UnityEditor.Compilation; + +using io.github.hatayama.UnityCliLoop.Domain; +using io.github.hatayama.UnityCliLoop.FirstPartyTools; +using io.github.hatayama.UnityCliLoop.Infrastructure; + +namespace io.github.hatayama.UnityCliLoop.Tests.Editor +{ + /// + /// Tests the single compile result recording seam used by delayed CLI polling. + /// + [TestFixture] + public sealed class CompileResultSessionRecorderTests + { + [Test] + public void RecordCompileResult_WhenPendingRequestExists_StoresResponseAndClearsPendingRequest() + { + // Verifies raw compile results are shaped and persisted through the recorder seam once. + UnityCliLoopCompileResultSessionRepository compileResultSessionRepository = + UnityCliLoopEditorSessionStateTestFactory.CreateCompileResultSessionRepository(); + UnityCliLoopPendingCompileSessionRepository pendingCompileSessionRepository = + UnityCliLoopEditorSessionStateTestFactory.CreatePendingCompileSessionRepository(); + UnityCliLoopCompileSessionLifecycleService compileSessionLifecycleService = + UnityCliLoopEditorSessionStateTestFactory.CreateCompileSessionLifecycleService(); + UnityCliLoopEditorSessionStateSnapshot originalSnapshot = + UnityCliLoopEditorSessionStateTestFactory.CaptureSnapshot(); + UnityCliLoopEditorSessionStateTestFactory.ClearAll(); + + try + { + compileSessionLifecycleService.MarkPendingCompileRequest( + "compile_test_request", + forceRecompile: false, + markedAtUtc: DateTime.UtcNow); + CompilerMessage warning = new CompilerMessage + { + type = CompilerMessageType.Warning, + message = "warning", + file = "Assets/Test.cs", + line = 15 + }; + CompileResult result = new CompileResult( + success: true, + errorCount: 0, + warningCount: 1, + completedAt: DateTime.Now, + messages: new[] { warning }, + errors: Array.Empty(), + warnings: new[] { warning }); + + CompileResponse response = CompileResultSessionRecorder.RecordCompileResult( + compileResultSessionRepository, + pendingCompileSessionRepository, + "compile_test_request", + forceRecompile: false, + result, + "compile_test_request"); + + UnityCliLoopStoredCompileResult storedResult = + compileResultSessionRepository.GetCompileResult("compile_test_request"); + UnityCliLoopPendingCompileRequest pendingRequest = + UnityCliLoopEditorSessionStateTestFactory.GetSinglePendingCompileRequest( + pendingCompileSessionRepository); + + Assert.That(response.Success, Is.True); + Assert.That(response.WarningCount, Is.EqualTo(1)); + Assert.That(storedResult.ResultJson, Does.Contain("\"Success\":true")); + Assert.That(storedResult.ResultJson, Does.Contain("\"Warnings\":[")); + Assert.That(pendingRequest.HasRequest, Is.False); + } + finally + { + originalSnapshot.Restore(); + } + } + } +} diff --git a/Assets/Tests/Editor/CompileResultSessionRecorderTests.cs.meta b/Assets/Tests/Editor/CompileResultSessionRecorderTests.cs.meta new file mode 100644 index 0000000000..37fd4bdc69 --- /dev/null +++ b/Assets/Tests/Editor/CompileResultSessionRecorderTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b82c193eb8b44b78b9ecd8a80be693aa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/Editor/CompileSessionResultStoreTests.cs b/Assets/Tests/Editor/CompileSessionResultStoreTests.cs index 1d5972f722..ef0938d60a 100644 --- a/Assets/Tests/Editor/CompileSessionResultStoreTests.cs +++ b/Assets/Tests/Editor/CompileSessionResultStoreTests.cs @@ -1,5 +1,6 @@ using System; using NUnit.Framework; +using UnityEditor.Compilation; using io.github.hatayama.UnityCliLoop.Domain; using io.github.hatayama.UnityCliLoop.FirstPartyTools; @@ -65,5 +66,68 @@ public void StoreCompileResult_WhenResultIsPersisted_UsesPascalCaseJson() originalSnapshot.Restore(); } } + + [Test] + public void StoreCompileResult_WhenEquivalentNormalResponsesAreStoredTwice_WritesByteIdenticalJson() + { + // Verifies equivalent normal responses serialize to stable bytes across repeated storage calls. + UnityCliLoopCompileResultSessionRepository compileResultSessionRepository = + UnityCliLoopEditorSessionStateTestFactory.CreateCompileResultSessionRepository(); + UnityCliLoopPendingCompileSessionRepository pendingCompileSessionRepository = + UnityCliLoopEditorSessionStateTestFactory.CreatePendingCompileSessionRepository(); + UnityCliLoopEditorSessionStateSnapshot originalSnapshot = + UnityCliLoopEditorSessionStateTestFactory.CaptureSnapshot(); + UnityCliLoopEditorSessionStateTestFactory.ClearAll(); + + try + { + CompilerMessage warning = new CompilerMessage + { + type = CompilerMessageType.Warning, + message = "warning", + file = "Assets/Test.cs", + line = 15 + }; + CompileResult result = new CompileResult( + success: true, + errorCount: 0, + warningCount: 1, + completedAt: DateTime.Now, + messages: new[] { warning }, + errors: Array.Empty(), + warnings: new[] { warning }); + CompileResponse firstResponse = + CompileResponseFactory.CreateResponse(result, forceRecompile: false); + CompileResponse secondResponse = + CompileResponseFactory.CreateResponse(result, forceRecompile: false); + + CompileSessionResultStore.StoreCompileResult( + compileResultSessionRepository, + pendingCompileSessionRepository, + "compile_test_request", + forceRecompile: false, + firstResponse, + "compile_test_request"); + string firstJson = + compileResultSessionRepository.GetCompileResult("compile_test_request").ResultJson; + + CompileSessionResultStore.StoreCompileResult( + compileResultSessionRepository, + pendingCompileSessionRepository, + "compile_test_request", + forceRecompile: false, + secondResponse, + "compile_test_request"); + string secondJson = + compileResultSessionRepository.GetCompileResult("compile_test_request").ResultJson; + + Assert.That(firstJson, Is.EqualTo(secondJson)); + Assert.That(firstJson, Does.Contain("\"ProjectRoot\":")); + } + finally + { + originalSnapshot.Restore(); + } + } } } diff --git a/Assets/Tests/Editor/CompileUseCaseTests.cs b/Assets/Tests/Editor/CompileUseCaseTests.cs new file mode 100644 index 0000000000..4c7970c3e5 --- /dev/null +++ b/Assets/Tests/Editor/CompileUseCaseTests.cs @@ -0,0 +1,147 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; +using UnityEditor.Compilation; + +using io.github.hatayama.UnityCliLoop.Domain; +using io.github.hatayama.UnityCliLoop.FirstPartyTools; +using io.github.hatayama.UnityCliLoop.Infrastructure; + +namespace io.github.hatayama.UnityCliLoop.Tests.Editor +{ + /// + /// Tests compile use case orchestration around delayed result persistence. + /// + [TestFixture] + public sealed class CompileUseCaseTests + { + [Test] + public async Task CompileAsync_WhenExecutionLayerStoresDelayedSuccess_DoesNotStoreResultAgain() + { + // Verifies the UseCase success path preserves the execution layer's single delayed-result write. + UnityCliLoopCompileResultSessionRepository innerCompileResultSessionRepository = + UnityCliLoopEditorSessionStateTestFactory.CreateCompileResultSessionRepository(); + CountingCompileResultSessionRepository compileResultSessionRepository = + new(innerCompileResultSessionRepository); + UnityCliLoopPendingCompileSessionRepository pendingCompileSessionRepository = + UnityCliLoopEditorSessionStateTestFactory.CreatePendingCompileSessionRepository(); + UnityCliLoopCompileSessionLifecycleService compileSessionLifecycleService = + new( + UnityCliLoopEditorSessionStateTestFactory.CreateSessionFlagsRepository(), + compileResultSessionRepository, + pendingCompileSessionRepository); + UnityCliLoopEditorSessionStateSnapshot originalSnapshot = + UnityCliLoopEditorSessionStateTestFactory.CaptureSnapshot(); + UnityCliLoopEditorSessionStateTestFactory.ClearAll(); + + try + { + CompileSchema request = new() + { + WaitForDomainReload = true, + RequestId = "compile_test_request", + ForceRecompile = false, + ReloadExternalSceneChanges = true + }; + CompileResult executionResult = CreateSuccessfulCompileResult(); + CompileUseCase useCase = new( + compileSessionLifecycleService, + compileResultSessionRepository, + pendingCompileSessionRepository); + useCase.SetCompilationExecutionForTesting((compileRequest, ct) => + { + ct.ThrowIfCancellationRequested(); + CompileResultSessionRecorder.RecordCompileResult( + compileResultSessionRepository, + pendingCompileSessionRepository, + compileRequest.RequestId, + compileRequest.ForceRecompile, + executionResult, + compileRequest.RequestId); + return Task.FromResult(executionResult); + }); + + CompileResponse response = await useCase.CompileAsync(request, CancellationToken.None); + + Assert.That(compileResultSessionRepository.StoreCount, Is.EqualTo(1)); + Assert.That(response.Success, Is.True); + Assert.That(response.ProjectRoot, Is.Not.Empty); + UnityCliLoopStoredCompileResult storedResult = + compileResultSessionRepository.GetCompileResult("compile_test_request"); + Assert.That(storedResult.ResultJson, Does.Contain("\"ProjectRoot\":")); + } + finally + { + originalSnapshot.Restore(); + } + } + + private static CompileResult CreateSuccessfulCompileResult() + { + CompilerMessage warning = new() + { + type = CompilerMessageType.Warning, + message = "warning", + file = "Assets/Test.cs", + line = 15 + }; + return new CompileResult( + success: true, + errorCount: 0, + warningCount: 1, + completedAt: DateTime.Now, + messages: new[] { warning }, + errors: Array.Empty(), + warnings: new[] { warning }); + } + + private sealed class CountingCompileResultSessionRepository : ICompileResultSessionRepository + { + private readonly ICompileResultSessionRepository _inner; + + internal CountingCompileResultSessionRepository(ICompileResultSessionRepository inner) + { + Assert.That(inner, Is.Not.Null); + _inner = inner; + } + + internal int StoreCount { get; private set; } + + public void StoreCompileResult( + string requestId, + bool forceRecompile, + string resultJson, + DateTime completedAtUtc) + { + StoreCount++; + _inner.StoreCompileResult(requestId, forceRecompile, resultJson, completedAtUtc); + } + + public UnityCliLoopStoredCompileResult GetCompileResult(string requestId) + { + return _inner.GetCompileResult(requestId); + } + + public UnityCliLoopStoredCompileResult GetStoredCompileResult() + { + return _inner.GetStoredCompileResult(); + } + + public UnityCliLoopStoredCompileResult[] GetStoredCompileResults() + { + return _inner.GetStoredCompileResults(); + } + + public void ClearCompileResult() + { + _inner.ClearCompileResult(); + } + + public bool ClearExpiredCompileResult(DateTime utcNow, TimeSpan lifetime) + { + return _inner.ClearExpiredCompileResult(utcNow, lifetime); + } + } + } +} diff --git a/Assets/Tests/Editor/CompileUseCaseTests.cs.meta b/Assets/Tests/Editor/CompileUseCaseTests.cs.meta new file mode 100644 index 0000000000..858500f3c7 --- /dev/null +++ b/Assets/Tests/Editor/CompileUseCaseTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1db098aa67194612a97e96410ff1b1e9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/src/Editor/FirstPartyTools/Compile/CompileController.cs b/Packages/src/Editor/FirstPartyTools/Compile/CompileController.cs index 9ebcd3b52f..df8c267b45 100644 --- a/Packages/src/Editor/FirstPartyTools/Compile/CompileController.cs +++ b/Packages/src/Editor/FirstPartyTools/Compile/CompileController.cs @@ -118,7 +118,7 @@ public async Task TryCompileAsync(bool forceRecompile, Cancellati "compile_controller_validation_failed", validation.ErrorMessage, new { force_recompile = forceRecompile }); - return new CompileResult( + CompileResult result = new CompileResult( success: false, errorCount: 0, warningCount: 0, @@ -129,6 +129,13 @@ public async Task TryCompileAsync(bool forceRecompile, Cancellati isIndeterminate: true, message: validation.ErrorMessage ); + RecordCompileResultIfNeeded( + result, + BuildCompileControllerStateContext(new Dictionary + { + ["validation_failed_before_request"] = true + })); + return result; } (bool CanProceed, string Message, string[] ScenePaths) sceneChangeResult = @@ -143,7 +150,15 @@ public async Task TryCompileAsync(bool forceRecompile, Cancellati reload_external_scene_changes = _reloadExternalSceneChanges, scene_paths = sceneChangeResult.ScenePaths }); - return CompileResultFactory.CreateExternalSceneChangeFailureResult(sceneChangeResult); + CompileResult result = + CompileResultFactory.CreateExternalSceneChangeFailureResult(sceneChangeResult); + RecordCompileResultIfNeeded( + result, + BuildCompileControllerStateContext(new Dictionary + { + ["external_scene_change_failed_before_request"] = true + })); + return result; } _isCompiling = true; @@ -523,14 +538,12 @@ private void RecordCompileResultIfNeeded( return; } - CompileResponse response = - CompileResponseFactory.CreateResponse(result, _resultRecordingContext.ForceRecompile); - CompileSessionResultStore.StoreCompileResult( + CompileResultSessionRecorder.RecordCompileResult( _compileResultSessionRepository, _pendingCompileSessionRepository, _resultRecordingContext.RequestId, _resultRecordingContext.ForceRecompile, - response, + result, _resultRecordingContext.RequestId); completionContext["result_recorded_in_session_state"] = true; completionContext["request_id"] = _resultRecordingContext.RequestId; diff --git a/Packages/src/Editor/FirstPartyTools/Compile/CompileResultRecordingContext.cs b/Packages/src/Editor/FirstPartyTools/Compile/CompileResultRecordingContext.cs index 38b85c4d3e..59582df48d 100644 --- a/Packages/src/Editor/FirstPartyTools/Compile/CompileResultRecordingContext.cs +++ b/Packages/src/Editor/FirstPartyTools/Compile/CompileResultRecordingContext.cs @@ -27,7 +27,7 @@ internal static CompileResultRecordingContext Create(CompileSchema request) { Debug.Assert(request != null, "request must not be null"); - if (!request.WaitForDomainReload || string.IsNullOrWhiteSpace(request.RequestId)) + if (!CanRecord(request)) { return Disabled(); } @@ -37,5 +37,11 @@ internal static CompileResultRecordingContext Create(CompileSchema request) request.RequestId, request.ForceRecompile); } + + internal static bool CanRecord(CompileSchema request) + { + Debug.Assert(request != null, "request must not be null"); + return request.WaitForDomainReload && !string.IsNullOrWhiteSpace(request.RequestId); + } } } diff --git a/Packages/src/Editor/FirstPartyTools/Compile/CompileResultSessionRecorder.cs b/Packages/src/Editor/FirstPartyTools/Compile/CompileResultSessionRecorder.cs new file mode 100644 index 0000000000..de6034ccf6 --- /dev/null +++ b/Packages/src/Editor/FirstPartyTools/Compile/CompileResultSessionRecorder.cs @@ -0,0 +1,59 @@ +using System; +using System.Diagnostics; + +using io.github.hatayama.UnityCliLoop.Domain; + +namespace io.github.hatayama.UnityCliLoop.FirstPartyTools +{ + /// + /// Owns compile result shaping and SessionState persistence for delayed CLI polling. + /// + internal static class CompileResultSessionRecorder + { + internal static CompileResponse RecordCompileResult( + ICompileResultSessionRepository compileResultSessionRepository, + IPendingCompileSessionRepository pendingCompileSessionRepository, + string requestId, + bool forceRecompile, + CompileResult result, + string correlationId) + { + Debug.Assert(compileResultSessionRepository != null, "compileResultSessionRepository must not be null"); + Debug.Assert(pendingCompileSessionRepository != null, "pendingCompileSessionRepository must not be null"); + Debug.Assert(!string.IsNullOrWhiteSpace(requestId), "requestId must not be null or whitespace"); + Debug.Assert(result != null, "result must not be null"); + + CompileResponse response = CompileResponseFactory.CreateResponse(result, forceRecompile); + return RecordCompileResponse( + compileResultSessionRepository, + pendingCompileSessionRepository, + requestId, + forceRecompile, + response, + correlationId); + } + + internal static CompileResponse RecordCompileResponse( + ICompileResultSessionRepository compileResultSessionRepository, + IPendingCompileSessionRepository pendingCompileSessionRepository, + string requestId, + bool forceRecompile, + CompileResponse response, + string correlationId) + { + Debug.Assert(compileResultSessionRepository != null, "compileResultSessionRepository must not be null"); + Debug.Assert(pendingCompileSessionRepository != null, "pendingCompileSessionRepository must not be null"); + Debug.Assert(!string.IsNullOrWhiteSpace(requestId), "requestId must not be null or whitespace"); + Debug.Assert(response != null, "response must not be null"); + + CompileSessionResultStore.StoreCompileResult( + compileResultSessionRepository, + pendingCompileSessionRepository, + requestId, + forceRecompile, + response, + correlationId); + return response; + } + } +} diff --git a/Packages/src/Editor/FirstPartyTools/Compile/CompileResultSessionRecorder.cs.meta b/Packages/src/Editor/FirstPartyTools/Compile/CompileResultSessionRecorder.cs.meta new file mode 100644 index 0000000000..1e6cc21c53 --- /dev/null +++ b/Packages/src/Editor/FirstPartyTools/Compile/CompileResultSessionRecorder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 402fa4a2acac4d169ece80639e9195bc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/src/Editor/FirstPartyTools/Compile/CompileSessionResultStore.cs b/Packages/src/Editor/FirstPartyTools/Compile/CompileSessionResultStore.cs index e383721824..280fc4b1f3 100644 --- a/Packages/src/Editor/FirstPartyTools/Compile/CompileSessionResultStore.cs +++ b/Packages/src/Editor/FirstPartyTools/Compile/CompileSessionResultStore.cs @@ -29,7 +29,7 @@ internal static void StoreCompileResult( throw new ArgumentNullException(nameof(compileResultSessionRepository)); IPendingCompileSessionRepository pendingCompileRepository = pendingCompileSessionRepository ?? throw new ArgumentNullException(nameof(pendingCompileSessionRepository)); - result.ProjectRoot = UnityCliLoopPathResolver.GetProjectRoot(); + StampProjectRoot(result); string resultJson = JsonConvert.SerializeObject( result, Formatting.None, @@ -63,5 +63,11 @@ internal static void StoreCompileResult( }, correlationId); } + + internal static void StampProjectRoot(CompileResponse result) + { + Debug.Assert(result != null, "result must not be null"); + result.ProjectRoot = UnityCliLoopPathResolver.GetProjectRoot(); + } } } diff --git a/Packages/src/Editor/FirstPartyTools/Compile/CompileUseCase.cs b/Packages/src/Editor/FirstPartyTools/Compile/CompileUseCase.cs index 34fbcff6bd..c66223dabd 100644 --- a/Packages/src/Editor/FirstPartyTools/Compile/CompileUseCase.cs +++ b/Packages/src/Editor/FirstPartyTools/Compile/CompileUseCase.cs @@ -21,6 +21,7 @@ public class CompileUseCase private readonly UnityCliLoopCompileSessionLifecycleService _compileSessionLifecycleService; private readonly ICompileResultSessionRepository _compileResultSessionRepository; private readonly IPendingCompileSessionRepository _pendingCompileSessionRepository; + private Func> _executeCompilationAsync; public CompileUseCase( UnityCliLoopCompileSessionLifecycleService compileSessionLifecycleService, @@ -37,6 +38,17 @@ public CompileUseCase( throw new ArgumentNullException(nameof(compileResultSessionRepository)); _pendingCompileSessionRepository = pendingCompileSessionRepository ?? throw new ArgumentNullException(nameof(pendingCompileSessionRepository)); + _executeCompilationAsync = ExecuteCompilationWithDefaultServiceAsync; + } + + /// + /// Replaces compilation execution for tests that must not start Unity's real compilation pipeline. + /// + internal void SetCompilationExecutionForTesting(Func> executeCompilationAsync) + { + Debug.Assert(executeCompilationAsync != null, "executeCompilationAsync must not be null"); + _executeCompilationAsync = executeCompilationAsync ?? + throw new ArgumentNullException(nameof(executeCompilationAsync)); } /// @@ -79,7 +91,7 @@ public async Task CompileAsync(CompileSchema request, Cancellat errors: new[] { new CompileIssue(preparation.ErrorMessage, "", 0) }, warnings: Array.Empty()); CompileResponse persistedResponse = - StoreResponseIfNeeded(request, response, correlationId); + StorePreControllerResponseIfNeeded(request, response, correlationId); return persistedResponse; } @@ -110,7 +122,7 @@ public async Task CompileAsync(CompileSchema request, Cancellat errors: new[] { new CompileIssue("Play Mode did not exit within 5 seconds; compilation aborted.", "", 0) }, warnings: Array.Empty()); CompileResponse persistedResponse = - StoreResponseIfNeeded(request, response, correlationId); + StorePreControllerResponseIfNeeded(request, response, correlationId); return persistedResponse; } } @@ -133,23 +145,19 @@ public async Task CompileAsync(CompileSchema request, Cancellat errors: new[] { new CompileIssue(validation.ErrorMessage, "", 0) }, warnings: Array.Empty()); CompileResponse persistedResponse = - StoreResponseIfNeeded(request, response, correlationId); + StorePreControllerResponseIfNeeded(request, response, correlationId); return persistedResponse; } // 3. Compilation execution ct.ThrowIfCancellationRequested(); - CompilationExecutionService executionService = new( - _compileResultSessionRepository, - _pendingCompileSessionRepository); - CompileResult result = await executionService.ExecuteCompilationAsync(request, ct); + CompileResult result = await _executeCompilationAsync(request, ct); // 4. Result formatting CompileResponse successResponse = CompileResponseFactory.CreateResponse(result, request.ForceRecompile); - CompileResponse persistedSuccessResponse = - StoreResponseIfNeeded(request, successResponse, correlationId); - return persistedSuccessResponse; + StampProjectRootForDelayedResponseIfNeeded(request, successResponse); + return successResponse; } private async Task WaitForPlayModeExitAsync(CancellationToken ct) @@ -183,7 +191,7 @@ private static void PrepareResultStorage(CompileSchema request) request.RequestId = CreateRequestId(); } - private CompileResponse StoreResponseIfNeeded( + private CompileResponse StorePreControllerResponseIfNeeded( CompileSchema request, CompileResponse response, string correlationId) @@ -191,22 +199,13 @@ private CompileResponse StoreResponseIfNeeded( Debug.Assert(request != null, "request must not be null"); Debug.Assert(response != null, "response must not be null"); - if (!request.WaitForDomainReload) - { - return response; - } - - if (string.IsNullOrWhiteSpace(request.RequestId)) + if (!CompileResultRecordingContext.CanRecord(request)) { - VibeLogger.LogWarning( - "compile_result_not_stored", - "Compile result was not stored because the request ID is empty.", - null, - correlationId); + LogMissingRecordableRequestIfNeeded(request, correlationId); return response; } - CompileSessionResultStore.StoreCompileResult( + CompileResultSessionRecorder.RecordCompileResponse( _compileResultSessionRepository, _pendingCompileSessionRepository, request.RequestId, @@ -216,6 +215,39 @@ private CompileResponse StoreResponseIfNeeded( return response; } + private void StampProjectRootForDelayedResponseIfNeeded( + CompileSchema request, + CompileResponse response) + { + Debug.Assert(request != null, "request must not be null"); + Debug.Assert(response != null, "response must not be null"); + + if (!CompileResultRecordingContext.CanRecord(request)) + { + return; + } + + CompileSessionResultStore.StampProjectRoot(response); + } + + private static void LogMissingRecordableRequestIfNeeded( + CompileSchema request, + string correlationId) + { + Debug.Assert(request != null, "request must not be null"); + + if (!request.WaitForDomainReload) + { + return; + } + + VibeLogger.LogWarning( + "compile_result_not_stored", + "Compile result was not stored because the request ID is empty.", + null, + correlationId); + } + private void MarkPendingCompileRequestIfNeeded( CompileSchema request, DateTime markedAtUtc, @@ -304,5 +336,15 @@ private static string CreateRequestId() string correlationId = UnityCliLoopConstants.GenerateCorrelationId(); return $"compile_{unixTimeMilliseconds}_{correlationId}"; } + + private Task ExecuteCompilationWithDefaultServiceAsync(CompileSchema request, CancellationToken ct) + { + Debug.Assert(request != null, "request must not be null"); + + CompilationExecutionService executionService = new( + _compileResultSessionRepository, + _pendingCompileSessionRepository); + return executionService.ExecuteCompilationAsync(request, ct); + } } }