Skip to content

fix: Prevent interrupted test runs from changing Play Mode settings#1253

Closed
hatayama wants to merge 2 commits into
mainfrom
feature/hatayama/fix-domain-reload-leak
Closed

fix: Prevent interrupted test runs from changing Play Mode settings#1253
hatayama wants to merge 2 commits into
mainfrom
feature/hatayama/fix-domain-reload-leak

Conversation

@hatayama

Copy link
Copy Markdown
Owner

Summary

  • Play Mode test runs now restore Unity Enter Play Mode settings even when the run is interrupted before cleanup finishes.
  • Nested test runs keep domain reload disabled until the last active run exits, avoiding premature restoration.

User Impact

  • Before this change, an interrupted PlayMode test run could leave Disable Domain Reload enabled in ProjectSettings/EditorSettings.asset and create a tracked settings diff.
  • After this change, local recovery state restores the original settings on the next editor load or next scoped test run, keeping project settings clean.

Changes

  • Persist a local recovery marker in UserSettings/UnityMcpSettings.json instead of UnityEditor.SessionState so recovery survives editor restarts or killed processes.
  • Restore pending Enter Play Mode settings during editor load and before a new scoped test run saves its original state.
  • Track active nested scopes so settings restore only after the final scope is disposed.
  • Add focused EditMode tests for normal cleanup, abandoned scopes, new-run recovery, and nested scope disposal.

Verification

  • node Packages/src/Cli~/dist/cli.bundle.cjs compile --force-recompile true --wait-for-domain-reload true --project-path <PROJECT_ROOT>
  • node Packages/src/Cli~/dist/cli.bundle.cjs run-tests --test-mode EditMode --filter-type regex --filter-value io.github.hatayama.uLoopMCP.DomainReloadDisableScope --save-before-run false --project-path <PROJECT_ROOT>
  • git diff --check origin/main...HEAD
  • Confirmed ProjectSettings/EditorSettings.asset has no tracked diff.

Fixes #1243

hatayama added 2 commits May 31, 2026 23:43
Save the original Enter Play Mode settings before disabling domain reload, and restore them from editor settings on Dispose or the next editor load so interrupted PlayMode test runs do not leave DisableDomainReload persisted.
Keep same-domain nested scopes active until the last Dispose while still treating pending restore data as abandoned after editor reload, preventing domain reload from being re-enabled mid-run.
@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR resolves a critical issue where DomainReloadDisableScope leaked the DisableDomainReload setting into the VCS-tracked EditorSettings.asset when PlayMode test runs were interrupted. The fix introduces a crash-safe recovery mechanism using persistent editor settings, reference counting, and editor-load initialization to restore interrupted state.

Changes

Crash-safe domain reload recovery

Layer / File(s) Summary
Persistent state schema for domain-reload recovery
Packages/src/Editor/Config/McpEditorSettings.cs
McpEditorSettingsData is extended with three new fields: domainReloadDisableScopeRestorePending, domainReloadDisableScopeOriginalOptionsEnabled, and domainReloadDisableScopeOriginalOptions to persist domain-reload scope state across editor sessions.
Recovery helper for editor-load persistence
Packages/src/Editor/Core/CoreTools/Util/DomainReloadDisableScopeRecovery.cs
New internal utility that saves current Enter Play Mode settings when a scope is created, restores them on editor load (skipping asset import workers) if a prior run was interrupted, and clears the pending-restore flag.
Reference-counted domain-reload scope with recovery delegation
Packages/src/Editor/Core/CoreTools/Util/DomainReloadDisableScope.cs
DomainReloadDisableScope is refactored from per-instance snapshot/restore to a shared reference-counted model. Constructor invokes recovery helpers on first scope creation; Dispose becomes idempotent and restores settings only when the last active scope exits. A test-only counter reset helper is added.
Test suite for crash-safe scope and recovery semantics
Assets/Tests/Editor/DomainReloadDisableScopeTests.cs
NUnit test class validates correct restore/clearing on disposal, nested scope reference counting, abandoned scope recovery, and constructor restoration semantics to ensure stored original options reflect only the current interrupted run.

Sequence Diagrams

sequenceDiagram
  participant Editor as Unity Editor Load
  participant Recovery as DomainReloadDisableScopeRecovery
  participant Settings as McpEditorSettingsData
  participant Scope as DomainReloadDisableScope

  Editor->>Recovery: InitializeOnLoadMethod (on editor load)
  Recovery->>Settings: Check domainReloadDisableScopeRestorePending flag
  alt Pending restore exists
    Recovery->>Settings: Read stored enterPlayModeOptions(Enabled)
    Recovery->>Editor: Restore EditorSettings from stored values
    Recovery->>Settings: Clear pending flag and stored options
  end

  Scope->>Recovery: Constructor (first scope created)
  Recovery->>Settings: Check if already saved via flag
  alt Not yet saved
    Recovery->>Editor: Read current EditorSettings
    Recovery->>Settings: Save current settings and set pending flag
  end
  Scope->>Editor: Apply DisableDomainReload
  
  Scope->>Recovery: Dispose (last scope disposed)
  Recovery->>Settings: Restore and clear pending via RestoreIfPending
Loading
stateDiagram-v2
  [*] --> NoScope
  
  NoScope --> FirstScopeCreated: new DomainReloadDisableScope()
  FirstScopeCreated --> RestorePending: RestoreIfPending()
  RestorePending --> SaveCurrent: SaveCurrentSettingsIfNeeded()
  SaveCurrent --> DisableDomainReload: Apply DisableDomainReload
  DisableDomainReload --> ActiveScope: _activeScopeCount = 1
  
  ActiveScope --> NestedScope: new DomainReloadDisableScope()
  NestedScope --> ActiveScope: _activeScopeCount++
  
  ActiveScope --> FirstDispose: Dispose() on first/nested scope
  FirstDispose --> ActiveScope: _activeScopeCount--
  
  ActiveScope --> LastDispose: Dispose() on last scope
  LastDispose --> RestoreSettings: RestoreIfPending()
  RestoreSettings --> [*]
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: preventing interrupted test runs from persisting Play Mode settings changes, which is the core issue addressed by the PR.
Linked Issues check ✅ Passed All coding requirements from issue #1243 are met: crash-safe storage of original settings [McpEditorSettings.cs], recovery on editor load [DomainReloadDisableScopeRecovery.cs], reference-counted nested scope tracking [DomainReloadDisableScope.cs], and comprehensive tests [DomainReloadDisableScopeTests.cs].
Out of Scope Changes check ✅ Passed All changes directly address issue #1243 requirements: settings persistence, recovery utilities, reference-counting for nested scopes, and focused test coverage. No unrelated modifications present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/hatayama/fix-domain-reload-leak

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
Assets/Tests/Editor/DomainReloadDisableScopeTests.cs (1)

35-117: ⚡ Quick win

Add one non-default round-trip test.

All current cases start from enterPlayModeOptionsEnabled = false and EnterPlayModeOptions.None, so a bug that always restores defaults would still pass. Please add one scenario that starts from a non-default user setting, such as enabled = true with a non-None option, to prove the original values survive both restore paths.

Suggested test shape
+[Test]
+public void Dispose_RestoresNonDefaultOriginalSettings()
+{
+    SetEnterPlayModeSettings(true, EnterPlayModeOptions.DisableSceneReload);
+
+    using (DomainReloadDisableScope scope = new DomainReloadDisableScope())
+    {
+        Assert.That(EditorSettings.enterPlayModeOptionsEnabled, Is.True);
+        Assert.That(EditorSettings.enterPlayModeOptions, Is.EqualTo(EnterPlayModeOptions.DisableDomainReload));
+    }
+
+    Assert.That(EditorSettings.enterPlayModeOptionsEnabled, Is.True);
+    Assert.That(EditorSettings.enterPlayModeOptions, Is.EqualTo(EnterPlayModeOptions.DisableSceneReload));
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Assets/Tests/Editor/DomainReloadDisableScopeTests.cs` around lines 35 - 117,
Add a new unit test that starts from a non-default Enter Play Mode setting
(e.g., call SetEnterPlayModeSettings(true,
EnterPlayModeOptions.DisableDomainReload)) and then exercises both restore
paths: 1) create and Dispose a DomainReloadDisableScope and assert original
settings are restored, and 2) create a DomainReloadDisableScope and simulate
abandonment by not disposing it, call
DomainReloadDisableScopeRecovery.RestoreIfPending(), and assert the original
non-default settings are restored and
McpEditorSettings.domainReloadDisableScopeRestorePending is cleared; reference
DomainReloadDisableScope, DomainReloadDisableScopeRecovery.RestoreIfPending,
SetEnterPlayModeSettings, and use System.GC.KeepAlive(scope) where appropriate
to prevent premature collection.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@Assets/Tests/Editor/DomainReloadDisableScopeTests.cs`:
- Around line 35-117: Add a new unit test that starts from a non-default Enter
Play Mode setting (e.g., call SetEnterPlayModeSettings(true,
EnterPlayModeOptions.DisableDomainReload)) and then exercises both restore
paths: 1) create and Dispose a DomainReloadDisableScope and assert original
settings are restored, and 2) create a DomainReloadDisableScope and simulate
abandonment by not disposing it, call
DomainReloadDisableScopeRecovery.RestoreIfPending(), and assert the original
non-default settings are restored and
McpEditorSettings.domainReloadDisableScopeRestorePending is cleared; reference
DomainReloadDisableScope, DomainReloadDisableScopeRecovery.RestoreIfPending,
SetEnterPlayModeSettings, and use System.GC.KeepAlive(scope) where appropriate
to prevent premature collection.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4c5aae19-d398-4ee0-8fe8-bec0293bcaa6

📥 Commits

Reviewing files that changed from the base of the PR and between 5b1b172 and 8e4f59e.

⛔ Files ignored due to path filters (2)
  • Assets/Tests/Editor/DomainReloadDisableScopeTests.cs.meta is excluded by none and included by none
  • Packages/src/Editor/Core/CoreTools/Util/DomainReloadDisableScopeRecovery.cs.meta is excluded by none and included by none
📒 Files selected for processing (4)
  • Assets/Tests/Editor/DomainReloadDisableScopeTests.cs
  • Packages/src/Editor/Config/McpEditorSettings.cs
  • Packages/src/Editor/Core/CoreTools/Util/DomainReloadDisableScope.cs
  • Packages/src/Editor/Core/CoreTools/Util/DomainReloadDisableScopeRecovery.cs

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 6 files

Re-trigger cubic

@hatayama

hatayama commented Jun 1, 2026

Copy link
Copy Markdown
Owner Author

Superseded by #1255.

@hatayama hatayama closed this Jun 1, 2026
@hatayama
hatayama deleted the feature/hatayama/fix-domain-reload-leak branch July 10, 2026 12:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DomainReloadDisableScope leaks DisableDomainReload into VCS-tracked EditorSettings.asset on interrupted PlayMode test runs (no recovery)

1 participant