Add Unity Pipeline package support
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Unity.Pipeline.Commands;
|
||||
using Unity.Pipeline.Editor.Testing;
|
||||
using Unity.Pipeline.Models;
|
||||
using UnityEditor.TestTools.TestRunner.Api;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Pipeline.Editor.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Commands for running Unity tests programmatically
|
||||
/// </summary>
|
||||
public static class TestCommands
|
||||
{
|
||||
/// <summary>
|
||||
/// Execute Unity tests with filtering options.
|
||||
/// Synchronous by default (blocks until completion), async mode available with --async-tests flag.
|
||||
/// </summary>
|
||||
[CliCommand("run_tests", "Execute Unity tests with filtering options", MainThreadRequired = true)]
|
||||
public static async Task<TestExecutionResponse> RunTests(
|
||||
[CliArg("mode", "Test mode: all, editor, playmode (default: all)")] string mode = "all",
|
||||
[CliArg("filter", "Test name filter pattern (case-insensitive partial match)")] string filter = "",
|
||||
[CliArg("filter_type", "Filter type: testName, assembly, category (default: testName)")] string filterType = "testName",
|
||||
[CliArg("include_explicit", "Include tests marked with [Explicit] attribute")] bool includeExplicit = false,
|
||||
[CliArg("async_tests", "Run asynchronously - return immediately, poll /test-status for results")] bool asyncTests = false,
|
||||
[CliArg("timeout", "Test execution timeout in seconds (default: 300)")] int timeout = 300)
|
||||
{
|
||||
// Return the full structured response (including failures and the Error field on a
|
||||
// failed run) rather than throwing: the server now awaits this Task and serializes the
|
||||
// unwrapped response, so the client receives complete, structured reporting. Throwing
|
||||
// would only surface an opaque message and discard the per-test results.
|
||||
return await PipelineTestRunner.ExecuteTestsAsync(
|
||||
mode,
|
||||
filter,
|
||||
filterType,
|
||||
includeExplicit,
|
||||
asyncTests,
|
||||
timeout);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List all available tests without executing any of them.
|
||||
/// Enumerates the test tree via TestRunnerApi.RetrieveTestList for the requested mode(s).
|
||||
/// </summary>
|
||||
[CliCommand("list_tests", "List all available tests (EditMode and/or PlayMode) without running them", MainThreadRequired = true)]
|
||||
public static async Task<TestListResponse> ListTests(
|
||||
[CliArg("mode", "Test mode: all, editor, playmode (default: all)")] string mode = "all")
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!TryGetModes(mode, out var modes))
|
||||
{
|
||||
return new TestListResponse
|
||||
{
|
||||
Success = false,
|
||||
Command = "list_tests",
|
||||
Error = $"Invalid mode '{mode}'. Use 'editor', 'playmode', or 'all'.",
|
||||
ExecutedAt = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
var tests = new List<TestListItem>();
|
||||
foreach (var testMode in modes)
|
||||
{
|
||||
tests.AddRange(await RetrieveTestsAsync(testMode));
|
||||
}
|
||||
|
||||
return new TestListResponse
|
||||
{
|
||||
Success = true,
|
||||
Command = "list_tests",
|
||||
Mode = modes.Count == 2 ? "All" : modes[0].ToString(),
|
||||
Count = tests.Count,
|
||||
Tests = tests,
|
||||
Message = $"Found {tests.Count} test(s).",
|
||||
ExecutedAt = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"[TestCommands] list_tests failed: {ex.Message}");
|
||||
return new TestListResponse
|
||||
{
|
||||
Success = false,
|
||||
Command = "list_tests",
|
||||
Error = "Failed to list tests",
|
||||
ErrorDetails = ex.ToString(),
|
||||
ExecutedAt = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get current test status for async test execution
|
||||
/// </summary>
|
||||
[CliCommand("test_status", "Get status of running async test execution", MainThreadRequired = false)]
|
||||
public static string GetTestStatus()
|
||||
{
|
||||
var status = PipelineTestRunner.GetTestStatus();
|
||||
return status ?? "{\"status\":\"no_tests\",\"message\":\"No test run in progress\"}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel running test execution
|
||||
/// </summary>
|
||||
[CliCommand("cancel_tests", "Cancel running test execution", MainThreadRequired = true)]
|
||||
public static object CancelTests()
|
||||
{
|
||||
return PipelineTestRunner.CancelTests();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolve a mode string to the concrete TestMode(s) to enumerate.
|
||||
/// </summary>
|
||||
private static bool TryGetModes(string mode, out List<TestMode> modes)
|
||||
{
|
||||
switch (mode?.ToLowerInvariant())
|
||||
{
|
||||
case null:
|
||||
case "":
|
||||
case "all":
|
||||
modes = new List<TestMode> { TestMode.EditMode, TestMode.PlayMode };
|
||||
return true;
|
||||
case "editor":
|
||||
case "editmode":
|
||||
modes = new List<TestMode> { TestMode.EditMode };
|
||||
return true;
|
||||
case "playmode":
|
||||
case "play":
|
||||
modes = new List<TestMode> { TestMode.PlayMode };
|
||||
return true;
|
||||
default:
|
||||
modes = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the test list for a single mode. RetrieveTestList enumerates the test tree (no
|
||||
/// execution) and invokes the callback on the main thread; we bridge it to a Task.
|
||||
/// </summary>
|
||||
private static Task<List<TestListItem>> RetrieveTestsAsync(TestMode testMode)
|
||||
{
|
||||
var api = ScriptableObject.CreateInstance<TestRunnerApi>();
|
||||
var tcs = new TaskCompletionSource<List<TestListItem>>();
|
||||
|
||||
api.RetrieveTestList(testMode, root =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var items = new List<TestListItem>();
|
||||
CollectLeafTests(root, testMode, items);
|
||||
tcs.SetResult(items);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
tcs.SetException(ex);
|
||||
}
|
||||
});
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Walk the test tree and collect leaf tests (actual test methods, not suites/fixtures).
|
||||
/// </summary>
|
||||
private static void CollectLeafTests(ITestAdaptor test, TestMode testMode, List<TestListItem> items)
|
||||
{
|
||||
if (test == null)
|
||||
return;
|
||||
|
||||
if (!test.HasChildren && !test.IsSuite)
|
||||
{
|
||||
items.Add(new TestListItem
|
||||
{
|
||||
FullName = test.FullName,
|
||||
Mode = testMode.ToString(),
|
||||
Assembly = test.TypeInfo?.Assembly?.GetName()?.Name,
|
||||
Categories = test.Categories?.ToList() ?? new List<string>(),
|
||||
Explicit = test.RunState == RunState.Explicit
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (test.Children != null)
|
||||
{
|
||||
foreach (var child in test.Children)
|
||||
CollectLeafTests(child, testMode, items);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response for the list_tests command: the available tests, without execution results.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class TestListResponse : CommandExecutionResponse
|
||||
{
|
||||
public string Mode { get; set; } // EditMode, PlayMode, or All
|
||||
public int Count { get; set; }
|
||||
public List<TestListItem> Tests { get; set; } = new List<TestListItem>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A single available test (no run state / outcome — this is a listing, not a result).
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class TestListItem
|
||||
{
|
||||
public string FullName { get; set; }
|
||||
public string Mode { get; set; }
|
||||
public string Assembly { get; set; }
|
||||
public List<string> Categories { get; set; } = new List<string>();
|
||||
public bool Explicit { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user