8d51737f97
PlayMode tests require entering play mode which triggers a domain reload. On large projects this can take >15s, causing the hardcoded 15s init timeout to auto-fail the test job before tests actually start. This adds an `init_timeout` parameter to `run_tests` that flows through the Python server → C# RunTests handler → TestJobManager. When set, the per-job timeout overrides the 15s default. The value is persisted across domain reloads via SessionState. Changes: - Python: Add `init_timeout` param to `run_tests()` function signature - C# RunTests: Read `initTimeout` param and pass to `StartJob()` - C# TestJobManager: Per-job `InitTimeoutMs` field with fallback to `DefaultInitializationTimeoutMs` (15s), persisted in SessionState Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
99 lines
3.8 KiB
C#
99 lines
3.8 KiB
C#
using System;
|
|
using System.Threading.Tasks;
|
|
using MCPForUnity.Editor.Helpers;
|
|
using MCPForUnity.Editor.Resources.Tests;
|
|
using MCPForUnity.Editor.Services;
|
|
using Newtonsoft.Json.Linq;
|
|
using UnityEditor.TestTools.TestRunner.Api;
|
|
|
|
namespace MCPForUnity.Editor.Tools
|
|
{
|
|
/// <summary>
|
|
/// Starts a Unity Test Runner run asynchronously and returns a job id immediately.
|
|
/// Use get_test_job(job_id) to poll status/results.
|
|
/// </summary>
|
|
[McpForUnityTool("run_tests", AutoRegister = false, Group = "testing")]
|
|
public static class RunTests
|
|
{
|
|
public static Task<object> HandleCommand(JObject @params)
|
|
{
|
|
try
|
|
{
|
|
// Check for clear_stuck action first
|
|
if (ParamCoercion.CoerceBool(@params?["clear_stuck"], false))
|
|
{
|
|
bool wasCleared = TestJobManager.ClearStuckJob();
|
|
return Task.FromResult<object>(new SuccessResponse(
|
|
wasCleared ? "Stuck job cleared." : "No running job to clear.",
|
|
new { cleared = wasCleared }
|
|
));
|
|
}
|
|
|
|
string modeStr = @params?["mode"]?.ToString();
|
|
if (string.IsNullOrWhiteSpace(modeStr))
|
|
{
|
|
modeStr = "EditMode";
|
|
}
|
|
|
|
if (!ModeParser.TryParse(modeStr, out var parsedMode, out var parseError))
|
|
{
|
|
return Task.FromResult<object>(new ErrorResponse(parseError));
|
|
}
|
|
|
|
var p = new ToolParams(@params);
|
|
bool includeDetails = p.GetBool("includeDetails");
|
|
bool includeFailedTests = p.GetBool("includeFailedTests");
|
|
|
|
var filterOptions = GetFilterOptions(@params);
|
|
long initTimeoutMs = p.GetInt("initTimeout") ?? 0;
|
|
string jobId = TestJobManager.StartJob(parsedMode.Value, filterOptions, initTimeoutMs);
|
|
|
|
return Task.FromResult<object>(new SuccessResponse("Test job started.", new
|
|
{
|
|
job_id = jobId,
|
|
status = "running",
|
|
mode = parsedMode.Value.ToString(),
|
|
include_details = includeDetails,
|
|
include_failed_tests = includeFailedTests
|
|
}));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Normalize the already-running case to a stable error token.
|
|
if (ex.Message != null && ex.Message.IndexOf("already in progress", StringComparison.OrdinalIgnoreCase) >= 0)
|
|
{
|
|
return Task.FromResult<object>(new ErrorResponse("tests_running", new { reason = "tests_running", retry_after_ms = 5000 }));
|
|
}
|
|
return Task.FromResult<object>(new ErrorResponse($"Failed to start test job: {ex.Message}"));
|
|
}
|
|
}
|
|
|
|
private static TestFilterOptions GetFilterOptions(JObject @params)
|
|
{
|
|
if (@params == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var p = new ToolParams(@params);
|
|
var testNames = p.GetStringArray("testNames");
|
|
var groupNames = p.GetStringArray("groupNames");
|
|
var categoryNames = p.GetStringArray("categoryNames");
|
|
var assemblyNames = p.GetStringArray("assemblyNames");
|
|
|
|
if (testNames == null && groupNames == null && categoryNames == null && assemblyNames == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return new TestFilterOptions
|
|
{
|
|
TestNames = testNames,
|
|
GroupNames = groupNames,
|
|
CategoryNames = categoryNames,
|
|
AssemblyNames = assemblyNames
|
|
};
|
|
}
|
|
}
|
|
}
|