Feature/run tests summary clean (#501)
* Optimize run_tests to return summary by default, reducing token usage by 98%
- Add includeFailedTests parameter: returns only failed/skipped test details
- Add includeDetails parameter: returns all test details (original behavior)
- Default behavior now returns summary only (~150 tokens vs ~13k tokens)
- Make results field optional in Python schema for backward compatibility
Token savings:
- Default: ~13k tokens saved (98.9% reduction)
- With failures: minimal tokens (only non-passing tests)
- Full details: same as before when explicitly requested
This prevents context bloat for typical test runs where you only need
pass/fail counts, while still allowing detailed debugging when needed.
* Add warning when run_tests filters match no tests; fix test organization
TDD Feature:
- Add warning message when filter criteria match zero tests
- New RunTestsTests.cs validates message formatting logic
- Modified RunTests.cs to append "(No tests matched the specified filters)" when total=0
Test Organization Fixes:
- Move MCPToolParameterTests.cs from EditMode/ to EditMode/Tools/ (matches folder hierarchy)
- Fix inconsistent namespaces to MCPForUnityTests.Editor.{Subfolder}:
- MCPToolParameterTests: Tests.EditMode → MCPForUnityTests.Editor.Tools
- DomainReloadResilienceTests: Tests.EditMode.Tools → MCPForUnityTests.Editor.Tools
- Matrix4x4ConverterTests: MCPForUnityTests.EditMode.Helpers → MCPForUnityTests.Editor.Helpers
* Refactor test result message formatting
* Simplify RunTests warning assertions
* Tests: de-flake cold-start EditMode runs
- Make ManageScriptableObjectTests setup yield-based with longer Unity-ready timeout
- Mark DomainReloadResilienceTests explicit to avoid triggering domain reload during Run All
main
parent
c2a6b0ac7a
commit
35a5c75596
|
|
@ -356,13 +356,33 @@ namespace MCPForUnity.Editor.Services
|
|||
public int Failed => Summary.Failed;
|
||||
public int Skipped => Summary.Skipped;
|
||||
|
||||
public object ToSerializable(string mode)
|
||||
public object ToSerializable(string mode, bool includeDetails = false, bool includeFailedTests = false)
|
||||
{
|
||||
// Determine which results to include
|
||||
IEnumerable<object> resultsToSerialize;
|
||||
if (includeDetails)
|
||||
{
|
||||
// Include all test results
|
||||
resultsToSerialize = Results.Select(r => r.ToSerializable());
|
||||
}
|
||||
else if (includeFailedTests)
|
||||
{
|
||||
// Include only failed and skipped tests
|
||||
resultsToSerialize = Results
|
||||
.Where(r => !string.Equals(r.State, "Passed", StringComparison.OrdinalIgnoreCase))
|
||||
.Select(r => r.ToSerializable());
|
||||
}
|
||||
else
|
||||
{
|
||||
// No individual test results
|
||||
resultsToSerialize = null;
|
||||
}
|
||||
|
||||
return new
|
||||
{
|
||||
mode,
|
||||
summary = Summary.ToSerializable(),
|
||||
results = Results.Select(r => r.ToSerializable()).ToList(),
|
||||
results = resultsToSerialize?.ToList(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,27 @@ namespace MCPForUnity.Editor.Tools
|
|||
// Preserve default timeout if parsing fails
|
||||
}
|
||||
|
||||
bool includeDetails = false;
|
||||
bool includeFailedTests = false;
|
||||
try
|
||||
{
|
||||
var includeDetailsToken = @params?["includeDetails"];
|
||||
if (includeDetailsToken != null && bool.TryParse(includeDetailsToken.ToString(), out var parsedIncludeDetails))
|
||||
{
|
||||
includeDetails = parsedIncludeDetails;
|
||||
}
|
||||
|
||||
var includeFailedTestsToken = @params?["includeFailedTests"];
|
||||
if (includeFailedTestsToken != null && bool.TryParse(includeFailedTestsToken.ToString(), out var parsedIncludeFailedTests))
|
||||
{
|
||||
includeFailedTests = parsedIncludeFailedTests;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Preserve defaults if parsing fails
|
||||
}
|
||||
|
||||
var filterOptions = ParseFilterOptions(@params);
|
||||
|
||||
var testService = MCPServiceLocator.Tests;
|
||||
|
|
@ -66,10 +87,9 @@ namespace MCPForUnity.Editor.Tools
|
|||
|
||||
var result = await runTask.ConfigureAwait(true);
|
||||
|
||||
string message =
|
||||
$"{parsedMode.Value} tests completed: {result.Passed}/{result.Total} passed, {result.Failed} failed, {result.Skipped} skipped";
|
||||
string message = FormatTestResultMessage(parsedMode.Value.ToString(), result);
|
||||
|
||||
var data = result.ToSerializable(parsedMode.Value.ToString());
|
||||
var data = result.ToSerializable(parsedMode.Value.ToString(), includeDetails, includeFailedTests);
|
||||
return new SuccessResponse(message, data);
|
||||
}
|
||||
|
||||
|
|
@ -100,6 +120,20 @@ namespace MCPForUnity.Editor.Tools
|
|||
};
|
||||
}
|
||||
|
||||
internal static string FormatTestResultMessage(string mode, TestRunResult result)
|
||||
{
|
||||
string message =
|
||||
$"{mode} tests completed: {result.Passed}/{result.Total} passed, {result.Failed} failed, {result.Skipped} skipped";
|
||||
|
||||
// Add warning when no tests matched the filter criteria
|
||||
if (result.Total == 0)
|
||||
{
|
||||
message += " (No tests matched the specified filters)";
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
private static string[] ParseStringArray(JObject @params, string key)
|
||||
{
|
||||
var token = @params[key];
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class RunTestsTestResult(BaseModel):
|
|||
class RunTestsResult(BaseModel):
|
||||
mode: str
|
||||
summary: RunTestsSummary
|
||||
results: list[RunTestsTestResult]
|
||||
results: list[RunTestsTestResult] | None = None
|
||||
|
||||
|
||||
class RunTestsResponse(MCPResponse):
|
||||
|
|
@ -52,6 +52,8 @@ async def run_tests(
|
|||
group_names: Annotated[list[str] | str, "Same as test_names, except it allows for Regex"] | None = None,
|
||||
category_names: Annotated[list[str] | str, "NUnit category names to filter by (tests marked with [Category] attribute)"] | None = None,
|
||||
assembly_names: Annotated[list[str] | str, "Assembly names to filter tests by"] | None = None,
|
||||
include_failed_tests: Annotated[bool, "Include details for failed/skipped tests only (default: false)"] = False,
|
||||
include_details: Annotated[bool, "Include details for all tests (default: false)"] = False,
|
||||
) -> RunTestsResponse:
|
||||
unity_instance = get_unity_instance_from_context(ctx)
|
||||
|
||||
|
|
@ -88,6 +90,12 @@ async def run_tests(
|
|||
if assembly_names_list:
|
||||
params["assemblyNames"] = assembly_names_list
|
||||
|
||||
# Add verbosity parameters
|
||||
if include_failed_tests:
|
||||
params["includeFailedTests"] = True
|
||||
if include_details:
|
||||
params["includeDetails"] = True
|
||||
|
||||
response = await send_with_unity_instance(async_send_command_with_retry, unity_instance, "run_tests", params)
|
||||
await ctx.info(f'Response {response}')
|
||||
return RunTestsResponse(**response) if isinstance(response, dict) else response
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using Newtonsoft.Json.Linq;
|
|||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MCPForUnityTests.EditMode.Helpers
|
||||
namespace MCPForUnityTests.Editor.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for Matrix4x4Converter to ensure it safely serializes matrices
|
||||
|
|
|
|||
|
|
@ -7,11 +7,13 @@ using System.IO;
|
|||
using MCPForUnity.Editor.Tools;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Tests.EditMode.Tools
|
||||
namespace MCPForUnityTests.Editor.Tools
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for domain reload resilience - ensuring MCP requests succeed even during Unity domain reloads.
|
||||
/// </summary>
|
||||
[Category("domain_reload")]
|
||||
[Explicit("Intentionally triggers script compilation/domain reload; run explicitly to avoid slowing/flaking cold-start EditMode runs.")]
|
||||
public class DomainReloadResilienceTests
|
||||
{
|
||||
private const string TempDir = "Assets/Temp/DomainReloadTests";
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ using System;
|
|||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Tests.EditMode
|
||||
namespace MCPForUnityTests.Editor.Tools
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests specifically for MCP tool parameter handling issues.
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NUnit.Framework;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using System.Threading;
|
||||
using UnityEngine.TestTools;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
using MCPForUnity.Editor.Tools;
|
||||
using MCPForUnityTests.Editor.Tools.Fixtures;
|
||||
|
|
@ -14,16 +15,17 @@ namespace MCPForUnityTests.Editor.Tools
|
|||
{
|
||||
private const string TempRoot = "Assets/Temp/ManageScriptableObjectTests";
|
||||
private const string NestedFolder = TempRoot + "/Nested/Deeper";
|
||||
private const double UnityReadyTimeoutSeconds = 180.0;
|
||||
|
||||
private string _createdAssetPath;
|
||||
private string _createdGuid;
|
||||
private string _matAPath;
|
||||
private string _matBPath;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
[UnitySetUp]
|
||||
public IEnumerator SetUp()
|
||||
{
|
||||
WaitForUnityReady();
|
||||
yield return WaitForUnityReady(UnityReadyTimeoutSeconds);
|
||||
EnsureFolder("Assets/Temp");
|
||||
// Start from a clean slate every time (prevents intermittent setup failures).
|
||||
if (AssetDatabase.IsValidFolder(TempRoot))
|
||||
|
|
@ -47,7 +49,7 @@ namespace MCPForUnityTests.Editor.Tools
|
|||
AssetDatabase.CreateAsset(new Material(shader), _matBPath);
|
||||
AssetDatabase.SaveAssets();
|
||||
AssetDatabase.Refresh();
|
||||
WaitForUnityReady();
|
||||
yield return WaitForUnityReady(UnityReadyTimeoutSeconds);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
|
|
@ -308,7 +310,7 @@ namespace MCPForUnityTests.Editor.Tools
|
|||
return result as JObject ?? JObject.FromObject(result);
|
||||
}
|
||||
|
||||
private static void WaitForUnityReady(double timeoutSeconds = 30.0)
|
||||
private static IEnumerator WaitForUnityReady(double timeoutSeconds = 30.0)
|
||||
{
|
||||
// Some EditMode tests trigger script compilation/domain reload. Tools like ManageScriptableObject
|
||||
// intentionally return "compiling_or_reloading" during these windows. Wait until Unity is stable
|
||||
|
|
@ -320,7 +322,7 @@ namespace MCPForUnityTests.Editor.Tools
|
|||
{
|
||||
Assert.Fail($"Timed out waiting for Unity to finish compiling/updating (>{timeoutSeconds:0.0}s).");
|
||||
}
|
||||
Thread.Sleep(50);
|
||||
yield return null; // yield to the editor loop so importing/compiling can actually progress
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
using System;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NUnit.Framework;
|
||||
using MCPForUnity.Editor.Services;
|
||||
|
||||
namespace MCPForUnityTests.Editor.Tools
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for RunTests tool functionality.
|
||||
/// Note: We cannot easily test the full HandleCommand because it would create
|
||||
/// recursive test runner calls. Instead, we test the message formatting logic.
|
||||
/// </summary>
|
||||
public class RunTestsTests
|
||||
{
|
||||
[Test]
|
||||
public void FormatResultMessage_WithNoTests_IncludesWarning()
|
||||
{
|
||||
// Arrange
|
||||
var summary = new TestRunSummary(
|
||||
total: 0,
|
||||
passed: 0,
|
||||
failed: 0,
|
||||
skipped: 0,
|
||||
durationSeconds: 0.0,
|
||||
resultState: "Passed"
|
||||
);
|
||||
var result = new TestRunResult(summary, new TestRunTestResult[0]);
|
||||
|
||||
// Act
|
||||
string message = MCPForUnity.Editor.Tools.RunTests.FormatTestResultMessage("EditMode", result);
|
||||
|
||||
// Assert - THIS IS THE NEW FEATURE
|
||||
Assert.IsTrue(
|
||||
message.Contains("No tests matched"),
|
||||
$"Expected warning when total=0, but got: '{message}'"
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FormatResultMessage_WithTests_NoWarning()
|
||||
{
|
||||
// Arrange
|
||||
var summary = new TestRunSummary(
|
||||
total: 5,
|
||||
passed: 4,
|
||||
failed: 1,
|
||||
skipped: 0,
|
||||
durationSeconds: 1.5,
|
||||
resultState: "Failed"
|
||||
);
|
||||
var result = new TestRunResult(summary, new TestRunTestResult[0]);
|
||||
|
||||
// Act
|
||||
string message = MCPForUnity.Editor.Tools.RunTests.FormatTestResultMessage("EditMode", result);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(
|
||||
message.Contains("No tests matched"),
|
||||
$"Should not have warning when tests exist, but got: '{message}'"
|
||||
);
|
||||
Assert.IsTrue(message.Contains("4/5 passed"), "Should contain pass ratio");
|
||||
}
|
||||
|
||||
// Use MCPForUnity.Editor.Tools.RunTests.FormatTestResultMessage directly.
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 11f8926da5b67490ab04d70d442b1c19
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Loading…
Reference in New Issue