Skip to content

calling TestRunStatus.MarkFinished() to trigger callbacks#1067

Open
miketalley wants to merge 2 commits intoCoplayDev:betafrom
miketalley:fix/test-run-status-leak
Open

calling TestRunStatus.MarkFinished() to trigger callbacks#1067
miketalley wants to merge 2 commits intoCoplayDev:betafrom
miketalley:fix/test-run-status-leak

Conversation

@miketalley
Copy link
Copy Markdown

@miketalley miketalley commented Apr 17, 2026

Description

When a PlayMode test job fails to initialize (test runner never reaches RunStarted), TestJobManager.GetJob auto-fails the job after InitializationTimeoutMs — but only updates the job record, not TestRunStatus. Because TestRunStatus.MarkStarted(...) was called synchronously at run dispatch and the RunStarted/RunFinished callbacks never fire, TestRunStatus._isRunning stays true forever.

Downstream effect: EditorStateCache reports tests.is_running: true, advice.blocking_reasons: ["running_tests"], and advice.ready_for_tools: false. Every subsequent run_tests, refresh_unity, and any other tool that gates on readiness returns busy: tests_running. The only recovery is restarting Unity — neither a Test Runner UI cancel nor manage_editor stop clears the flag because neither path invokes TestRunStatus.MarkFinished.

Type of Change

Bug fix (non-breaking change that fixes an issue)

Changes Made

One line, in TestJobManager.GetJob, inside the auto-fail block (right after _currentJobId = null;):

TestRunStatus.MarkFinished();

Symmetrical to the existing clear paths in TestRunnerService.RunFinished (line 204) and TestRunnerService.ExecuteAsync's catch block (line 129). Keeps TestRunStatus and the job record in sync under all completion paths.

Repro

  1. Call run_tests right after a domain reload (e.g., immediately after refresh_unity) or while Unity is unfocused enough to miss its test-init window.
  2. Wait for editor_state.tests.started_unix_ms + 15s.
  3. get_test_job returns status: failed, error: "Test job failed to initialize (tests did not start within timeout)".
  4. editor_state now shows tests.is_running: true with tests.current_job_id: null and tests.started_by: "unknown" — permanently stuck.
  5. Every subsequent run_tests returns busy: tests_running.

Summary by Sourcery

Bug Fixes:

  • Clear the running-test status via TestRunStatus.MarkFinished when a test job fails to initialize and is auto-failed, preventing the editor from remaining permanently stuck in a tests-running state.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed an issue where test jobs that time out during initialization were not being properly finalized before state was saved. Timed-out jobs are now correctly marked finished so test run data is recorded and persisted reliably when initialization delays occur.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai bot commented Apr 17, 2026

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Ensures that when a test job auto-fails due to initialization timeout, the global TestRunStatus is also marked finished so the system no longer thinks tests are perpetually running.

Sequence diagram for test job auto-fail timeout handling

sequenceDiagram
    actor User
    participant Tool as Tool_run_tests
    participant TestRunnerService
    participant TestJobManager
    participant TestRunStatus
    participant EditorStateCache

    User->>Tool: run_tests
    Tool->>TestRunnerService: RunTestsAsync
    TestRunnerService->>TestRunStatus: MarkStarted
    TestRunnerService->>TestJobManager: Dispatch test job
    TestRunnerService-->>Tool: job_id

    rect rgb(255, 230, 230)
        Note over TestJobManager,TestRunStatus: Initialization timeout auto-fail path
        Tool->>TestJobManager: GetJob(job_id)
        TestJobManager->>TestJobManager: Detect initialization timeout
        TestJobManager->>TestJobManager: Set job status failed
        TestJobManager->>TestJobManager: _currentJobId = null
        TestJobManager->>TestRunStatus: MarkFinished
        TestJobManager-->>Tool: Job status failed (failed_to_initialize)
    end

    Tool->>EditorStateCache: get_editor_state
    EditorStateCache->>TestRunStatus: isRunning
    EditorStateCache-->>Tool: tests.is_running = false
Loading

Class diagram for TestJobManager and TestRunStatus timeout fix

classDiagram
    class TestJobManager {
        <<static>>
        - string _currentJobId
        + static TestJob GetJob(string jobId)
    }

    class TestRunStatus {
        <<static>>
        - bool _isRunning
        + static void MarkStarted(string jobId)
        + static void MarkFinished()
        + static bool IsRunning()
    }

    class EditorStateCache {
        + bool tests_is_running
        + string tests_current_job_id
        + string tests_started_by
        + void RefreshFromStatus()
    }

    TestJobManager ..> TestRunStatus : uses
    EditorStateCache ..> TestRunStatus : reads
    EditorStateCache ..> TestJobManager : reads current_job_id
Loading

File-Level Changes

Change Details Files
Aligns TestRunStatus lifecycle with auto-failed test jobs due to initialization timeout.
  • In the initialization-timeout auto-fail path of GetJob, clear the current job id if it matches the requested job id.
  • Immediately after clearing the current job id, mark the TestRunStatus as finished so its running flag is reset and callbacks are triggered.
  • Ensure that this auto-fail completion path is behaviorally consistent with existing completion handling in RunFinished and ExecuteAsync catch blocks.
MCPForUnity/Editor/Services/TestJobManager.cs

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
Copy Markdown
Contributor

coderabbitai bot commented Apr 17, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 824ddd84-9f92-49df-b5b4-f1e1a99df234

📥 Commits

Reviewing files that changed from the base of the PR and between d356c5c and b36782e.

📒 Files selected for processing (1)
  • MCPForUnity/Editor/Services/TestJobManager.cs
🚧 Files skipped from review as they are similar to previous changes (1)
  • MCPForUnity/Editor/Services/TestJobManager.cs

📝 Walkthrough

Walkthrough

A single method in TestJobManager now, when auto-failing a Running job due to initialization timeout and the timed-out job is the active _currentJobId, clears _currentJobId and then calls TestRunStatus.MarkFinished() before persisting state; persistence flags and other transitions remain unchanged.

Changes

Cohort / File(s) Summary
Test Job Lifecycle
MCPForUnity/Editor/Services/TestJobManager.cs
When a TestJobStatus.Running job times out with TotalTests == null and is the active _currentJobId, _currentJobId is cleared and TestRunStatus.MarkFinished() is called before persisting session state; persistence behavior unchanged.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 A timeout came, a quiet alarm,
I cleared the job and fixed the harm,
MarkFinished() whispered, tidy and neat,
State then saved — all calm and sweet. 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: calling TestRunStatus.MarkFinished() to trigger callbacks that fix the stuck-running-tests issue.
Description check ✅ Passed The description is comprehensive and complete, covering problem statement, root cause, the specific fix, downstream effects, repro steps, and alignment with existing patterns.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Copy Markdown
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 left some high level feedback:

  • In the timeout/auto-fail block, TestRunStatus.MarkFinished() is invoked even when _currentJobId != jobId, which could prematurely clear the running flag for a newer job; consider only calling MarkFinished when the timed-out job is still the active one.
  • Given this is a subtle lifecycle edge case, consider adding a brief code comment near TestRunStatus.MarkFinished() to explain that it is needed to keep TestRunStatus in sync when initialization times out without RunStarted/RunFinished firing.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the timeout/auto-fail block, `TestRunStatus.MarkFinished()` is invoked even when `_currentJobId != jobId`, which could prematurely clear the running flag for a newer job; consider only calling `MarkFinished` when the timed-out job is still the active one.
- Given this is a subtle lifecycle edge case, consider adding a brief code comment near `TestRunStatus.MarkFinished()` to explain that it is needed to keep `TestRunStatus` in sync when initialization times out without `RunStarted`/`RunFinished` firing.

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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
MCPForUnity/Editor/Services/TestJobManager.cs (1)

505-505: Consider applying the same MarkFinished() fix to the other auto-fail paths.

The same leak can occur via three other paths that transition a Running job to Failed without going through RunFinished:

  1. FinalizeFromTask (L643-654) — the comment at L349-350 explicitly notes this is the safety net "in case RunFinished is not delivered". In exactly that case, TestRunStatus.MarkFinished() will never have been called, so _isRunning stays true.
  2. ClearStuckJob (L103-112) — manual clear path; MarkFinished is never invoked.
  3. TryRestoreFromSessionState stale-job cleanup (L220-231) — domain-reload orphan path.

Paths 2 and 3 may be partially covered by TestRunStatus being reset on domain reload, but FinalizeFromTask in particular reproduces the exact same symptom described in the PR (stuck tests.is_running: true) whenever RunFinished is dropped for reasons other than the 15s init timeout.

♻️ Suggested additions
@@ FinalizeFromTask, after setting Status = Failed/Succeeded and clearing _currentJobId
                 if (_currentJobId == jobId)
                 {
                     _currentJobId = null;
                 }
+                // Mirror the RunFinished path: ensure TestRunStatus is cleared even when
+                // this safety-net finalizer runs because RunFinished was not delivered.
+                if (existing.Status != TestJobStatus.Running)
+                {
+                    TestRunStatus.MarkFinished();
+                }
             }
             PersistToSessionState(force: true);
@@ ClearStuckJob
                 _currentJobId = null;
             }
+            TestRunStatus.MarkFinished();
             PersistToSessionState(force: true);
@@ TryRestoreFromSessionState stale-job cleanup
                             currentJob.FinishedUnixMs = now;
                             _currentJobId = null;
+                            TestRunStatus.MarkFinished();
                         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@MCPForUnity/Editor/Services/TestJobManager.cs` at line 505, The
TestRunStatus.MarkFinished() call added at the RunFinished path must also be
invoked in the other auto-fail transitions to avoid leaving _isRunning true: add
TestRunStatus.MarkFinished() into FinalizeFromTask (the safety-net path that can
run when RunFinished is dropped), into ClearStuckJob (manual clear path), and
into the stale-job cleanup in TryRestoreFromSessionState; update those functions
to call TestRunStatus.MarkFinished() immediately after you set the job to Failed
(or right before returning) so the running flag is cleared in all failure paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@MCPForUnity/Editor/Services/TestJobManager.cs`:
- Line 505: The TestRunStatus.MarkFinished() call added at the RunFinished path
must also be invoked in the other auto-fail transitions to avoid leaving
_isRunning true: add TestRunStatus.MarkFinished() into FinalizeFromTask (the
safety-net path that can run when RunFinished is dropped), into ClearStuckJob
(manual clear path), and into the stale-job cleanup in
TryRestoreFromSessionState; update those functions to call
TestRunStatus.MarkFinished() immediately after you set the job to Failed (or
right before returning) so the running flag is cleared in all failure paths.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fbdd4cb8-cdfc-4bc4-b49e-1d032ae1f707

📥 Commits

Reviewing files that changed from the base of the PR and between e64a717 and d356c5c.

📒 Files selected for processing (1)
  • MCPForUnity/Editor/Services/TestJobManager.cs

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.

1 participant