Skip to content

Commit 1124a5c

Browse files
committed
Fix GetTests Resource and Run tests tool to work as described in the Readme file
1 parent c98534f commit 1124a5c

File tree

6 files changed

+190
-323
lines changed

6 files changed

+190
-323
lines changed

Editor/Resources/GetTestsResource.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Threading.Tasks;
33
using McpUnity.Services;
44
using Newtonsoft.Json.Linq;
5+
using UnityEditor.TestTools.TestRunner.Api;
56

67
namespace McpUnity.Resources
78
{
@@ -33,18 +34,17 @@ public override async void FetchAsync(JObject parameters, TaskCompletionSource<J
3334
{
3435
// Get filter parameters
3536
string testModeFilter = parameters["testMode"]?.ToObject<string>();
36-
List<TestItemInfo> allTests = await _testRunnerService.GetAllTestsAsync(testModeFilter);
37+
List<ITestAdaptor> allTests = await _testRunnerService.GetAllTestsAsync(testModeFilter);
3738
var results = new JArray();
3839

39-
foreach (TestItemInfo test in allTests)
40+
foreach (ITestAdaptor test in allTests)
4041
{
4142
results.Add(new JObject
4243
{
4344
["name"] = test.Name,
4445
["fullName"] = test.FullName,
45-
["path"] = test.Path,
46-
["testMode"] = test.TestMode,
47-
["runState"] = test.RunState
46+
["testMode"] = test.TestMode.ToString(),
47+
["runState"] = test.RunState.ToString()
4848
});
4949
}
5050

Editor/Services/ITestRunnerService.cs

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,19 @@ namespace McpUnity.Services
1111
public interface ITestRunnerService
1212
{
1313
/// <summary>
14-
/// Get the TestRunnerApi instance
14+
/// Asynchronously retrieves all available tests using the TestRunnerApi.
1515
/// </summary>
16-
TestRunnerApi TestRunnerApi { get; }
17-
18-
/// <summary>
19-
/// Async retrieval of all tests using TestRunnerApi callbacks
20-
/// </summary>
21-
/// <param name="testMode">Optional test mode filter (EditMode, PlayMode, or empty for all)</param>
16+
/// <param name="testModeFilter">Optional test mode filter (EditMode, PlayMode, or empty for all)</param>
2217
/// <returns>List of test items matching the specified test mode, or all tests if no mode specified</returns>
23-
Task<List<TestItemInfo>> GetAllTestsAsync(string testMode = "");
18+
Task<List<ITestAdaptor>> GetAllTestsAsync(string testModeFilter = "");
2419

2520
/// <summary>
26-
/// Execute tests with the provided parameters
21+
/// Executes tests using the TestRunnerApi and returns the results as a JSON object.
2722
/// </summary>
28-
/// <param name="testMode">Test mode to run</param>
29-
/// <param name="testFilter">Optional test filter</param>
30-
/// <param name="completionSource">TaskCompletionSource to resolve when tests are complete</param>
23+
/// <param name="testMode">The test mode to run (EditMode or PlayMode).</param>
24+
/// <param name="returnOnlyFailures">If true, only failed test results are included in the output.</param>
25+
/// <param name="testFilter">A filter string to select specific tests to run.</param>
3126
/// <returns>Task that resolves with test results when tests are complete</returns>
32-
void ExecuteTests(
33-
TestMode testMode,
34-
string testFilter,
35-
TaskCompletionSource<JObject> completionSource);
27+
Task<JObject> ExecuteTestsAsync(TestMode testMode, bool returnOnlyFailures, string testFilter);
3628
}
3729
}
Lines changed: 142 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Linq;
34
using System.Threading;
45
using System.Threading.Tasks;
56
using McpUnity.Unity;
7+
using McpUnity.Utils;
68
using UnityEngine;
79
using UnityEditor;
810
using UnityEditor.TestTools.TestRunner.Api;
@@ -12,22 +14,23 @@ namespace McpUnity.Services
1214
{
1315
/// <summary>
1416
/// Service for accessing Unity Test Runner functionality
17+
/// Implements ICallbacks for TestRunnerApi.
1518
/// </summary>
16-
public class TestRunnerService : ITestRunnerService
19+
public class TestRunnerService : ITestRunnerService, ICallbacks
1720
{
1821
private readonly TestRunnerApi _testRunnerApi;
19-
20-
/// <summary>
21-
/// Get the TestRunnerApi instance
22-
/// </summary>
23-
public TestRunnerApi TestRunnerApi => _testRunnerApi;
24-
22+
private TaskCompletionSource<JObject> _tcs;
23+
private bool _returnOnlyFailures;
24+
private List<ITestResultAdaptor> _results;
25+
2526
/// <summary>
2627
/// Constructor
2728
/// </summary>
2829
public TestRunnerService()
2930
{
3031
_testRunnerApi = ScriptableObject.CreateInstance<TestRunnerApi>();
32+
33+
_testRunnerApi.RegisterCallbacks(this);
3134
}
3235

3336
[MenuItem("Tools/MCP Unity/Debug call path")]
@@ -43,131 +46,179 @@ public static async void DebugCallGetAllTests()
4346
/// <summary>
4447
/// Async retrieval of all tests using TestRunnerApi callbacks
4548
/// </summary>
46-
/// <param name="testMode">Optional test mode filter (EditMode, PlayMode, or empty for all)</param>
49+
/// <param name="testModeFilter">Optional test mode filter (EditMode, PlayMode, or empty for all)</param>
4750
/// <returns>List of test items matching the specified test mode, or all tests if no mode specified</returns>
48-
public async Task<List<TestItemInfo>> GetAllTestsAsync(string testMode = "")
51+
public async Task<List<ITestAdaptor>> GetAllTestsAsync(string testModeFilter = "")
4952
{
50-
var tests = new List<TestItemInfo>();
51-
var tcs = new TaskCompletionSource<bool>();
52-
int pending = 0;
53+
var tests = new List<ITestAdaptor>();
54+
var tasks = new List<Task<List<ITestAdaptor>>>();
5355

54-
if (string.IsNullOrEmpty(testMode) || testMode.Equals("EditMode", StringComparison.OrdinalIgnoreCase))
56+
if (string.IsNullOrEmpty(testModeFilter) || testModeFilter.Equals("EditMode", StringComparison.OrdinalIgnoreCase))
5557
{
56-
Interlocked.Increment(ref pending);
57-
_testRunnerApi.RetrieveTestList(TestMode.EditMode, adaptor =>
58-
{
59-
CollectTestItems(adaptor, tests);
60-
CheckDone();
61-
});
58+
tasks.Add(RetrieveTestsAsync(TestMode.EditMode));
6259
}
63-
if (string.IsNullOrEmpty(testMode) || testMode.Equals("PlayMode", StringComparison.OrdinalIgnoreCase))
60+
if (string.IsNullOrEmpty(testModeFilter) || testModeFilter.Equals("PlayMode", StringComparison.OrdinalIgnoreCase))
6461
{
65-
Interlocked.Increment(ref pending);
66-
_testRunnerApi.RetrieveTestList(TestMode.PlayMode, adaptor =>
67-
{
68-
CollectTestItems(adaptor, tests);
69-
CheckDone();
70-
});
62+
tasks.Add(RetrieveTestsAsync(TestMode.PlayMode));
7163
}
7264

73-
if (pending == 0)
74-
tcs.SetResult(true);
75-
76-
await tcs.Task;
65+
var results = await Task.WhenAll(tasks);
7766

78-
return tests;
79-
80-
void CheckDone()
67+
foreach (var result in results)
8168
{
82-
if (Interlocked.Decrement(ref pending) == 0)
83-
tcs.TrySetResult(true);
69+
tests.AddRange(result);
8470
}
71+
72+
return tests;
8573
}
8674

8775
/// <summary>
88-
/// Execute tests with the provided parameters
76+
/// Executes tests and returns a JSON summary.
8977
/// </summary>
90-
/// <param name="testMode">Test mode to run</param>
91-
/// <param name="testFilter">Optional test filter</param>
92-
/// <param name="completionSource">TaskCompletionSource to resolve when tests are complete</param>
78+
/// <param name="testMode">The test mode to run (EditMode or PlayMode).</param>
79+
/// <param name="returnOnlyFailures">If true, only failed test results are included in the output.</param>
80+
/// <param name="testFilter">A filter string to select specific tests to run.</param>
9381
/// <returns>Task that resolves with test results when tests are complete</returns>
94-
public async void ExecuteTests(
95-
TestMode testMode,
96-
string testFilter,
97-
TaskCompletionSource<JObject> completionSource)
82+
public async Task<JObject> ExecuteTestsAsync(TestMode testMode, bool returnOnlyFailures, string testFilter = "")
9883
{
99-
// Create filter
100-
var filter = new Filter
101-
{
102-
testMode = testMode
103-
};
104-
105-
// Apply name filter if provided
84+
_tcs = new TaskCompletionSource<JObject>();
85+
_results = new List<ITestResultAdaptor>();
86+
_returnOnlyFailures = returnOnlyFailures;
87+
var filter = new Filter { testMode = testMode };
88+
10689
if (!string.IsNullOrEmpty(testFilter))
10790
{
10891
filter.testNames = new[] { testFilter };
10992
}
110-
111-
// Execute tests
93+
11294
_testRunnerApi.Execute(new ExecutionSettings(filter));
11395

114-
// Use timeout from settings if not specified
115-
var timeoutSeconds = McpUnitySettings.Instance.RequestTimeoutSeconds;
116-
117-
Task completedTask = await Task.WhenAny(
118-
completionSource.Task,
119-
Task.Delay(TimeSpan.FromSeconds(timeoutSeconds))
120-
);
96+
return await WaitForCompletionAsync(
97+
McpUnitySettings.Instance.RequestTimeoutSeconds);
98+
}
99+
100+
/// <summary>
101+
/// Asynchronously retrieves all test adaptors for the specified test mode.
102+
/// </summary>
103+
/// <param name="mode">The test mode to retrieve tests for (EditMode or PlayMode).</param>
104+
/// <returns>A task that resolves to a list of ITestAdaptor representing all tests in the given mode.</returns>
105+
private Task<List<ITestAdaptor>> RetrieveTestsAsync(TestMode mode)
106+
{
107+
var tcs = new TaskCompletionSource<List<ITestAdaptor>>();
108+
var tests = new List<ITestAdaptor>();
121109

122-
if (completedTask != completionSource.Task)
110+
_testRunnerApi.RetrieveTestList(mode, adaptor =>
123111
{
124-
completionSource.SetResult(McpUnitySocketHandler.CreateErrorResponse(
125-
$"Test run timed out after {timeoutSeconds} seconds",
126-
"test_runner_timeout"
127-
));
128-
}
112+
CollectTestItems(adaptor, tests);
113+
tcs.SetResult(tests);
114+
});
115+
116+
return tcs.Task;
129117
}
130118

131119
/// <summary>
132120
/// Recursively collect test items from test adaptors
133121
/// </summary>
134-
private void CollectTestItems(ITestAdaptor testAdaptor, List<TestItemInfo> tests, string parentPath = "")
122+
private void CollectTestItems(ITestAdaptor testAdaptor, List<ITestAdaptor> tests)
135123
{
136124
if (testAdaptor.IsSuite)
137125
{
138126
// For suites (namespaces, classes), collect all children
139127
foreach (var child in testAdaptor.Children)
140128
{
141-
string currentPath = string.IsNullOrEmpty(parentPath) ? testAdaptor.Name : $"{parentPath}.{testAdaptor.Name}";
142-
CollectTestItems(child, tests, currentPath);
129+
CollectTestItems(child, tests);
143130
}
144131
}
145132
else
146133
{
147-
// For individual tests, add to the list
148-
string fullPath = string.IsNullOrEmpty(parentPath) ? testAdaptor.Name : $"{parentPath}.{testAdaptor.Name}";
149-
150-
tests.Add(new TestItemInfo
151-
{
152-
Name = testAdaptor.Name,
153-
FullName = testAdaptor.FullName,
154-
Path = fullPath,
155-
TestMode = testAdaptor.TestMode.ToString(),
156-
RunState = testAdaptor.RunState.ToString()
157-
});
134+
tests.Add(testAdaptor);
158135
}
159136
}
160-
}
161-
162-
/// <summary>
163-
/// Information about a test item
164-
/// </summary>
165-
public class TestItemInfo
166-
{
167-
public string Name { get; set; }
168-
public string FullName { get; set; }
169-
public string Path { get; set; }
170-
public string TestMode { get; set; }
171-
public string RunState { get; set; }
137+
138+
#region ICallbacks Implementation
139+
140+
/// <summary>
141+
/// Called when the test run starts.
142+
/// </summary>
143+
public void RunStarted(ITestAdaptor testsToRun)
144+
{
145+
McpLogger.LogInfo($"Test run started: {testsToRun?.Name}");
146+
}
147+
148+
/// <summary>
149+
/// Called when an individual test starts.
150+
/// </summary>
151+
public void TestStarted(ITestAdaptor test)
152+
{
153+
// Optionally implement per-test start logic or logging.
154+
}
155+
156+
/// <summary>
157+
/// Called when an individual test finishes.
158+
/// </summary>
159+
public void TestFinished(ITestResultAdaptor result)
160+
{
161+
_results.Add(result);
162+
}
163+
164+
/// <summary>
165+
/// Called when the test run finishes.
166+
/// </summary>
167+
public void RunFinished(ITestResultAdaptor result)
168+
{
169+
var summary = BuildResultJson(_results, result);
170+
_tcs?.TrySetResult(summary);
171+
}
172+
173+
#endregion
174+
175+
#region Helpers
176+
177+
private async Task<JObject> WaitForCompletionAsync(int timeoutSeconds)
178+
{
179+
var delayTask = Task.Delay(TimeSpan.FromSeconds(timeoutSeconds));
180+
var winner = await Task.WhenAny(_tcs.Task, delayTask);
181+
182+
if (winner != _tcs.Task)
183+
{
184+
_tcs.TrySetResult(
185+
McpUnitySocketHandler.CreateErrorResponse(
186+
$"Test run timed out after {timeoutSeconds} seconds",
187+
"test_runner_timeout"));
188+
}
189+
return await _tcs.Task;
190+
}
191+
192+
private JObject BuildResultJson(List<ITestResultAdaptor> results, ITestResultAdaptor result)
193+
{
194+
int pass = results.Count(r => r.ResultState == "Passed");
195+
int fail = results.Count(r => r.ResultState == "Failed");
196+
int skip = results.Count(r => r.ResultState == "Skipped");
197+
198+
var arr = new JArray(results
199+
.Where(r => !_returnOnlyFailures || r.ResultState == "Failed")
200+
.Select(r => new JObject {
201+
["name"] = r.Name,
202+
["fullName"] = r.FullName,
203+
["state"] = r.ResultState,
204+
["message"] = r.Message,
205+
["duration"] = r.Duration
206+
}));
207+
208+
return new JObject {
209+
["success"] = true,
210+
["type"] = "text",
211+
["message"] = $"{result.Test.Name} test run completed: {pass}/{results.Count} passed - {fail}/{results.Count} failed - {skip}/{results.Count} skipped",
212+
["resultState"] = result.ResultState,
213+
["durationSeconds"] = result.Duration,
214+
["testCount"] = results.Count,
215+
["passCount"] = pass,
216+
["failCount"] = fail,
217+
["skipCount"] = skip,
218+
["results"] = arr
219+
};
220+
}
221+
222+
#endregion
172223
}
173224
}

0 commit comments

Comments
 (0)