Skip to content

Update TestRunnerService.cs#936

Merged
Scriptwonder merged 1 commit intoCoplayDev:betafrom
Scriptwonder:issue-932
Mar 15, 2026
Merged

Update TestRunnerService.cs#936
Scriptwonder merged 1 commit intoCoplayDev:betafrom
Scriptwonder:issue-932

Conversation

@Scriptwonder
Copy link
Collaborator

@Scriptwonder Scriptwonder commented Mar 15, 2026

Fix on issue#932, prevent the two params being the same after Domain Reload or Editor Crash/Force Quit by storing a Library/ mark file and a SessionState.

Summary by Sourcery

Add a guard to persist and restore Unity play mode options across domain reloads or editor crashes during test runs to prevent leaving modified settings behind.

Enhancements:

  • Introduce a PlayModeOptionsGuard that stores original play mode options in SessionState and a Library marker file for recovery after interrupted test runs.
  • Integrate the guard into the test runner lifecycle to restore or clear play mode options when runs finish or are interrupted.

Summary by CodeRabbit

  • Bug Fixes
    • Improved test runner stability by ensuring editor settings are properly preserved and restored when domain reloads or editor crashes occur during test execution.

Copilot AI review requested due to automatic review settings March 15, 2026 21:54
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Mar 15, 2026

Reviewer's Guide

Adds a PlayModeOptionsGuard helper that persists and restores Unity Editor play mode options across domain reloads and editor crashes, and wires it into the test runner lifecycle to ensure original settings are always restored after interrupted or completed test runs.

Updated class diagram for TestRunnerService and PlayModeOptionsGuard

classDiagram

    class PlayModeOptionsGuard {
        <<static>>
        -const string KeyPending
        -const string KeyEnabled
        -const string KeyOptions
        -static string MarkerPath
        +static bool IsPending
        +static PlayModeOptionsGuard()
        +static void Save(bool originalEnabled, EnterPlayModeOptions originalOptions)
        +static void Restore()
        +static void Clear()
        -static bool TryLoad(out bool originalEnabled, out EnterPlayModeOptions originalOptions)
    }

    class TestRunnerService {
        +void RunFinished(ITestResultAdaptor result)
        -static bool EnsurePlayModeRunsWithoutDomainReload(bool originalEnterPlayModeOptionsEnabled, EnterPlayModeOptions originalEnterPlayModeOptions)
        -static void RestoreEnterPlayModeOptions(bool originalEnabled, EnterPlayModeOptions originalOptions)
        -TaskCompletionSource~TestRunPayload~ _runCompletionSource
    }

    class SessionState {
        +static void SetBool(string key, bool value)
        +static bool GetBool(string key, bool defaultValue)
        +static void SetInt(string key, int value)
        +static int GetInt(string key, int defaultValue)
    }

    class EditorSettings {
        +static bool enterPlayModeOptionsEnabled
        +static EnterPlayModeOptions enterPlayModeOptions
    }

    class McpLog {
        +static void Warn(string message)
        +static void Info(string message)
    }

    class TestRunStatus {
        +static bool IsRunning
    }

    class File {
        +static void WriteAllText(string path, string contents)
        +static bool Exists(string path)
        +static string[] ReadAllLines(string path)
        +static void Delete(string path)
    }

    TestRunnerService --> PlayModeOptionsGuard : uses
    PlayModeOptionsGuard --> SessionState : persists_state
    PlayModeOptionsGuard --> EditorSettings : restores_options
    PlayModeOptionsGuard --> McpLog : logs_status
    PlayModeOptionsGuard --> File : marker_file_io
    PlayModeOptionsGuard --> TestRunStatus : checks_IsRunning
Loading

File-Level Changes

Change Details Files
Introduce PlayModeOptionsGuard to persist and restore EnterPlayModeOptions across domain reloads and crashes.
  • Add InitializeOnLoad static PlayModeOptionsGuard class that checks on editor/domain load whether a restore is pending and restores EditorSettings if no test run is active.
  • Persist original enterPlayModeOptionsEnabled and enterPlayModeOptions to both Unity SessionState and a Library/ marker file before modifying them for test runs.
  • Implement restoration logic that sets EditorSettings back to the saved values, logs the restore, and clears all persisted state.
  • Implement Clear helper to reset the pending flag and delete the marker file with best-effort error handling.
  • Implement TryLoad helper that first reads from SessionState, then falls back to parsing the marker file contents to reconstruct the original values.
MCPForUnity/Editor/Services/TestRunnerService.cs
Integrate PlayModeOptionsGuard with the test runner lifecycle to handle both normal and interrupted runs.
  • On test run completion, if the async completion source is null (indicating the original caller was destroyed by domain reload) and a restore is pending, call PlayModeOptionsGuard.Restore to restore EditorSettings.
  • When ensuring tests run without domain reload, call PlayModeOptionsGuard.Save with the original EditorSettings before changing them so they can be restored if the run is interrupted.
  • In the normal restoration path, call PlayModeOptionsGuard.Clear after resetting EditorSettings to ensure no stale pending state remains.
MCPForUnity/Editor/Services/TestRunnerService.cs

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 15, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

This PR enhances TestRunnerService.cs with a new PlayModeOptionsGuard utility that persists and recovers Unity Editor play mode options across domain reloads and crashes using SessionState and marker files. The integration adds state-saving when entering play mode and restoration logic upon test completion to prevent permanent mutations of EditorSettings during interrupted test runs.

Changes

Cohort / File(s) Summary
PlayModeOptionsGuard Utility & TestRunnerService Integration
MCPForUnity/Editor/Services/TestRunnerService.cs
Adds internal PlayModeOptionsGuard static utility with Save/Restore/Clear/TryLoad/IsPending methods for persisting EditorSettings and play mode options via SessionState and Library marker files. Integrates recovery logic in test completion path and persistence calls when entering safe play mode to handle domain reloads and crashes gracefully.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related issues

Possibly related PRs

Poem

🐰 Through domain reloads swift and crashes dire,
The Guard preserves settings we desire,
Save before, restore when whole again—
No more lost options, much less pain!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Update TestRunnerService.cs' is vague and generic, not describing the specific change or bug fix being implemented. Replace with a more specific title that describes the actual change, such as 'Fix domain reload issue by persisting play mode options with guard utility' or 'Add PlayModeOptionsGuard to preserve editor settings across domain reloads'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The description includes issue reference and summary but lacks required template sections like Type of Change, specific Changes Made list, and Testing details.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

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.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • The MarkerPath currently uses a relative "Library" directory, which can be fragile if the working directory changes; consider resolving it via Application.dataPath (or another Unity API) to compute an absolute path under the project root.
  • In TryLoad, when the marker file is malformed or contains invalid ints, the method silently returns false; you might want to log a warning and clear the corrupt marker to avoid repeatedly hitting the same bad state.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `MarkerPath` currently uses a relative "Library" directory, which can be fragile if the working directory changes; consider resolving it via `Application.dataPath` (or another Unity API) to compute an absolute path under the project root.
- In `TryLoad`, when the marker file is malformed or contains invalid ints, the method silently returns `false`; you might want to log a warning and clear the corrupt marker to avoid repeatedly hitting the same bad state.

## Individual Comments

### Comment 1
<location path="MCPForUnity/Editor/Services/TestRunnerService.cs" line_range="119-116" />
<code_context>
+                    return false;
+                }
+
+                string[] lines = File.ReadAllLines(MarkerPath);
+                if (lines.Length < 2)
+                {
+                    return false;
+                }
+
</code_context>
<issue_to_address>
**suggestion:** Consider clearing or replacing a malformed marker file instead of silently retrying every load.

Right now, if the file exists but is malformed (too few lines or non‑integer values), `TryLoad` just returns `false` and leaves the bad file in place, so every reload hits the same failure. It would be more robust to treat this as corruption and invoke `Clear()` or delete the file so the system can recover instead of repeatedly parsing invalid content.

Suggested implementation:

```csharp
                string[] lines = File.ReadAllLines(MarkerPath);
                if (lines.Length < 2)
                {
                    // Marker file is malformed; treat as corruption and clear it
                    File.Delete(MarkerPath);
                    return false;
                }


```

To fully apply your suggestion, similar handling should be added around any parsing of `lines` later in this method. Specifically:
1. Wrap the parsing logic (e.g., `int.Parse`, `Enum.Parse`, etc.) in a `try/catch` block catching `FormatException` and `OverflowException`.
2. Inside the `catch`, delete the marker file with `File.Delete(MarkerPath);` and `return false;`.
3. Ensure this logic is only applied when the file exists (which it already is, given the preceding `File.Exists(MarkerPath)` check).
This will prevent the system from repeatedly attempting to parse a permanently malformed marker file.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

{
if (!File.Exists(MarkerPath))
{
return false;
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: Consider clearing or replacing a malformed marker file instead of silently retrying every load.

Right now, if the file exists but is malformed (too few lines or non‑integer values), TryLoad just returns false and leaves the bad file in place, so every reload hits the same failure. It would be more robust to treat this as corruption and invoke Clear() or delete the file so the system can recover instead of repeatedly parsing invalid content.

Suggested implementation:

                string[] lines = File.ReadAllLines(MarkerPath);
                if (lines.Length < 2)
                {
                    // Marker file is malformed; treat as corruption and clear it
                    File.Delete(MarkerPath);
                    return false;
                }

To fully apply your suggestion, similar handling should be added around any parsing of lines later in this method. Specifically:

  1. Wrap the parsing logic (e.g., int.Parse, Enum.Parse, etc.) in a try/catch block catching FormatException and OverflowException.
  2. Inside the catch, delete the marker file with File.Delete(MarkerPath); and return false;.
  3. Ensure this logic is only applied when the file exists (which it already is, given the preceding File.Exists(MarkerPath) check).
    This will prevent the system from repeatedly attempting to parse a permanently malformed marker file.

@Scriptwonder Scriptwonder merged commit 860099e into CoplayDev:beta Mar 15, 2026
3 of 4 checks passed
@Scriptwonder Scriptwonder deleted the issue-932 branch March 15, 2026 21:56
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes issue #932 by persisting the original Unity Play Mode enter options so they can be restored after interruptions (domain reload, crash/force-quit), preventing the editor from getting stuck with modified settings.

Changes:

  • Added PlayModeOptionsGuard to persist original EditorSettings.enterPlayModeOptionsEnabled / enterPlayModeOptions via SessionState + a Library/ marker file.
  • Restores persisted Play Mode options in RunFinished when the original RunTestsAsync caller was lost to a domain reload.
  • Clears persisted state when normal restoration runs in the RunTestsAsync finally path.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +38 to +43
// After domain reload or editor restart: if a restore is pending and no test run
// is active, restore now. TryLoad checks SessionState first, then the marker file.
if (TryLoad(out _, out _) && !TestRunStatus.IsRunning)
{
Restore();
}
Comment on lines +122 to +136
return false;
}

if (!int.TryParse(lines[0].Trim(), out int enabledInt) ||
!int.TryParse(lines[1].Trim(), out int optionsInt))
{
return false;
}

originalEnabled = enabledInt != 0;
originalOptions = (EnterPlayModeOptions)optionsInt;
return true;
}
catch
{
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.

2 participants