@aefree/pi-unity 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +7 -8
- package/index.ts +231 -141
- package/package.json +2 -2
- package/skills/auditing-unity-agent-guidance/assets/mixed-workflow-template.md +1 -1
- package/skills/auditing-unity-agent-guidance/references/migration-policy.md +1 -1
- package/skills/unity-batchmode-tests/SKILL.md +15 -15
- package/skills/unity-pipeline-workflows/SKILL.md +8 -8
- package/src/unity-batchmode.ts +9 -9
- package/src/unity-cli.ts +40 -7
- package/src/unity-core.ts +0 -8
- package/src/unity-editor-fallback.ts +36 -0
- package/src/unity-launch.ts +3 -83
- package/src/unity-pipeline.ts +30 -6
- package/src/unity-projects.ts +2 -3
- package/src/unity-test-batch.ts +3 -1
- package/src/unity-tests.ts +285 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aefree/pi-unity",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./index.ts"
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
]
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
21
|
-
"test": "tsx tests/unity-core.test.ts && tsx tests/unity-pipeline.test.ts && tsx tests/unity-processes.test.ts && tsx tests/pi-unity-settings.test.ts && tsx tests/unity-projects.test.ts && tsx tests/unity-guidance-audit.test.ts && tsx tests/unity-batchmode.test.ts && tsx tests/unity-test-batch.test.ts && tsx tests/unity-cli.test.ts && tsx tests/unity-project-lock.test.ts && tsx tests/unity-artifact-profile.test.ts && tsx tests/unity-file-discovery-filter.test.ts && tsx tests/unity-registration.test.ts && tsx tests/unity-optional-integrations.test.ts && tsx tests/unity-package-validation.test.ts",
|
|
21
|
+
"test": "tsx tests/unity-core.test.ts && tsx tests/unity-launch.test.ts && tsx tests/unity-pipeline.test.ts && tsx tests/unity-processes.test.ts && tsx tests/pi-unity-settings.test.ts && tsx tests/unity-projects.test.ts && tsx tests/unity-guidance-audit.test.ts && tsx tests/unity-batchmode.test.ts && tsx tests/unity-test-batch.test.ts && tsx tests/unity-tests.test.ts && tsx tests/unity-cli.test.ts && tsx tests/unity-project-lock.test.ts && tsx tests/unity-artifact-profile.test.ts && tsx tests/unity-file-discovery-filter.test.ts && tsx tests/unity-registration.test.ts && tsx tests/unity-optional-integrations.test.ts && tsx tests/unity-package-validation.test.ts",
|
|
22
22
|
"eval:guidance-skill": "tsx evals/auditing-unity-agent-guidance/run-eval.ts"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
@@ -11,7 +11,7 @@ Always resolve and pass the exact Unity project-copy path. Do not route by proje
|
|
|
11
11
|
|
|
12
12
|
- Inspect: `unity_project_status`
|
|
13
13
|
- Connected compile/test: use the package's typed connected tools when available
|
|
14
|
-
- Isolated tests: `
|
|
14
|
+
- Isolated tests: `unity_run_tests`
|
|
15
15
|
- Custom isolated Editor arguments: `unity_launch_batchmode`
|
|
16
16
|
- Existing evidence: `unity_inspect_artifacts`
|
|
17
17
|
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
- Connected EditMode: `run_tests --mode editor`; asynchronous execution plus `test_status` is safest for uniform wrappers.
|
|
20
20
|
- Connected PlayMode: require `--async_tests true`, then poll `test_status` because domain reload can drop the initiating request.
|
|
21
|
-
- Isolated/report-producing: use `unity test` or the packaged `
|
|
21
|
+
- Isolated/report-producing: use `unity test` or the packaged `unity_run_tests` when NUnit XML/log artifacts are required.
|
|
22
22
|
- Preserve graphics requirements and reject zero-test, malformed, incomplete, or nested `success:false` results.
|
|
23
23
|
|
|
24
24
|
## Build and ExecuteMethod
|
|
@@ -14,7 +14,7 @@ Run Unity Test Framework tests from the command line without opening the Editor
|
|
|
14
14
|
- **Use absolute paths** for `-testResults` and `-logFile` to ensure logs are easy to find.
|
|
15
15
|
- **Unity allows only one process per project folder** - GUI Editor and batchmode/headless both count as that one process.
|
|
16
16
|
- **Do not open the GUI editor for the same project before or during batchmode runs** - `/unity-open` and `unity_open_editor` launch the full Unity Editor GUI and are not equivalent to headless batchmode.
|
|
17
|
-
- **Do not close a reachable Pipeline Editor merely to run tests** -
|
|
17
|
+
- **Do not close a reachable Pipeline Editor merely to run tests** - call `unity_run_tests`; it selects connected Pipeline for supported requests and rejects isolated-only options rather than closing the Editor.
|
|
18
18
|
- **Only close a blocking Unity Editor through `unity_launch_batchmode` safeguards after deliberately choosing isolated execution** - this is limited to cases such as required NUnit XML, unsupported connected filters/commands, or explicit isolation. Use `closeBlockingUnityProcess: true` only when `unity_project_status` shows `piUnity.allowCloseRunningUnityProcess` is enabled or the user explicitly says it is enabled; pi-unity re-scans the resolved project and never accepts arbitrary PIDs.
|
|
19
19
|
- **Use `unity_launch_batchmode` when you want to run headless Unity directly** - keep test-specific flags deliberate, especially around `-runTests` and `-quit`.
|
|
20
20
|
- **Bundle tests into one Unity batchmode turn whenever practical** - starting/stopping Unity, importing assets, and domain reloads dominate runtime. A broader single run is usually faster than many sequential one-test Unity launches, and same-project runs cannot use useful parallelism.
|
|
@@ -40,19 +40,19 @@ Run Unity Test Framework tests from the command line without opening the Editor
|
|
|
40
40
|
Use the `pi-unity` tools first instead of forming raw Unity CLI commands on the fly:
|
|
41
41
|
- `unity_project_status` to inspect lockfile/process state without launching Unity
|
|
42
42
|
- `unity_inspect_artifacts` to summarize existing Unity logs/test XML without launching Unity
|
|
43
|
-
- `
|
|
43
|
+
- `unity_run_tests` for isolated/report-producing Unity Test Framework runs with one platform and bundled filters/categories
|
|
44
44
|
- `unity_launch_batchmode` for custom headless Unity execution that needs raw Editor arguments
|
|
45
45
|
- `unity_open_editor` only when the user explicitly wants the GUI Editor
|
|
46
46
|
- `/unity-open` as the user-facing GUI launcher helper
|
|
47
47
|
|
|
48
|
-
`
|
|
48
|
+
`unity_run_tests` is the one ordinary Unity Test Framework tool. It routes supported requests to a reachable exact-copy Pipeline Editor or a closed-project isolated `unity test` run, writes a durable normalized JSON artifact, and retains native NUnit/JUnit reports when requested. It never silently switches after uncertain dispatch.
|
|
49
49
|
|
|
50
50
|
`unity_launch_batchmode` remains the default for custom agent-run headless Unity work because it already:
|
|
51
51
|
- resolves the Unity project from a direct project root, a coordination root, or another nearby folder
|
|
52
52
|
- reads `ProjectSettings/ProjectVersion.txt`
|
|
53
|
-
-
|
|
53
|
+
- treats installed `unity run` as authoritative when available: it receives the project path without `--editor-version` and reads the declared version itself, falling back only when Unity CLI is unavailable
|
|
54
54
|
- uses OS-aware standard install probing
|
|
55
|
-
-
|
|
55
|
+
- resolves only exact project-version standard installation candidates when direct Editor fallback is required
|
|
56
56
|
- strips direct-Editor flags managed by `unity run` (`-batchmode`, `-projectPath`, `-quit`) before forwarding args in Unity CLI mode
|
|
57
57
|
- supports `launcher: "editor-executable"` when Unity CLI argument forwarding differs from direct Editor executable behavior
|
|
58
58
|
- checks Unity CLI status and running Unity processes before launch
|
|
@@ -68,15 +68,15 @@ If a launch is blocked by a native Unity lockfile, call `unity_project_status` b
|
|
|
68
68
|
|
|
69
69
|
### 2. Get the Project's Unity Version
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
Before direct Editor fallback, read `ProjectSettings/ProjectVersion.txt` in the Unity project root:
|
|
72
72
|
|
|
73
73
|
```
|
|
74
74
|
m_EditorVersion: ####.#.#f#
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
### 3.
|
|
77
|
+
### 3. Resolve the exact-version Unity Editor
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
For direct Editor fallback, use only the standard locations constructed from the version declared in `ProjectVersion.txt`:
|
|
80
80
|
|
|
81
81
|
**Windows:**
|
|
82
82
|
```
|
|
@@ -89,17 +89,16 @@ C:\UnityInstalls\<version>\Editor\Unity.exe
|
|
|
89
89
|
```
|
|
90
90
|
/Applications/Unity/Hub/Editor/<version>/Unity.app/Contents/MacOS/Unity
|
|
91
91
|
/Applications/Unity/<version>/Unity.app/Contents/MacOS/Unity
|
|
92
|
-
/Applications/Unity*
|
|
93
92
|
```
|
|
94
93
|
|
|
95
|
-
If not found,
|
|
94
|
+
If the exact declared version is not found, do not use an arbitrary Editor path or a nearby version. Ask the user to install the declared version through their normal Unity installation workflow, or use Unity CLI after it can resolve that exact version.
|
|
96
95
|
|
|
97
96
|
### 4. Choose the execution route, then run tests
|
|
98
97
|
|
|
99
98
|
Route before planning a batch:
|
|
100
99
|
1. Call `unity_project_status` for the exact project copy.
|
|
101
|
-
2. If that copy is already open, Pipeline is reachable, and `run_tests` plus `test_status` are advertised, use `unity-pipeline-workflows`; do not close the Editor or invoke `
|
|
102
|
-
3. Choose isolated `
|
|
100
|
+
2. If that copy is already open, Pipeline is reachable, and `run_tests` plus `test_status` are advertised, use `unity-pipeline-workflows`; do not close the Editor or invoke `unity_run_tests` merely because the test tool is more convenient.
|
|
101
|
+
3. Choose isolated `unity_run_tests` only when the Editor is closed, connected testing is unavailable, CI/isolation is intentional, required filters are unsupported, or NUnit XML/log artifacts are required. State the reason.
|
|
103
102
|
4. If project state is uncertain, stop rather than closing the Editor or starting batchmode.
|
|
104
103
|
|
|
105
104
|
After choosing the isolated route:
|
|
@@ -108,7 +107,7 @@ After choosing the isolated route:
|
|
|
108
107
|
- when several specific tests are relevant, prefer a broader class/namespace/suite/category filter, or no `-testFilter` for the affected platform, over separate Unity launches
|
|
109
108
|
- use a single-test launch only for a quick smoke check or to isolate/rerun a known failure; do not use one-test launches as the default validation strategy
|
|
110
109
|
- do not queue multiple `unity_launch_batchmode` calls back-to-back in one agent turn; wait for the structured summary, inspect failures, and only then decide whether another Unity launch is necessary
|
|
111
|
-
- call `
|
|
110
|
+
- call `unity_run_tests` for isolated/report-producing tests; use `unity_launch_batchmode` only when custom raw Unity arguments are required
|
|
112
111
|
- pass one `testPlatform` (`EditMode` or `PlayMode`) and bundle applicable `testFilters`/`testCategories`; empty arrays mean all tests on that platform
|
|
113
112
|
- use the generated exact result/log paths from the tool report for any follow-up artifact inspection
|
|
114
113
|
- pass `closeBlockingUnityProcess: true` only after connected testing was ruled out or isolated evidence was explicitly required, a same-project Unity process is blocking the chosen run, and Pi settings enable `piUnity.allowCloseRunningUnityProcess`
|
|
@@ -124,12 +123,13 @@ After choosing the isolated route:
|
|
|
124
123
|
- exclude graphics-required screenshot/visual-capture tests from no-graphics runs
|
|
125
124
|
- prefer project test categories such as `RequiresGraphics` / `VisualCapture` when the project exposes them
|
|
126
125
|
|
|
127
|
-
|
|
126
|
+
Unity CLI project-authoritative template:
|
|
128
127
|
```
|
|
129
128
|
unity test "<ProjectPath>" --mode <EditMode|PlayMode> --filter "<Full.Test.Name>" --output "<ResultsPath>" -- -logFile "<LogPath>"
|
|
130
|
-
"<UnityEditorPath>" -batchmode -nographics -projectPath "<ProjectPath>" -runTests -testPlatform <EditMode|PlayMode> -testFilter "<Full.Test.Name>" -testResults "<ResultsPath>" -logFile "<LogPath>"
|
|
131
129
|
```
|
|
132
130
|
|
|
131
|
+
For direct Editor fallback, use `unity_launch_batchmode` with its project path and test parameters; it resolves only the declared project's exact-version standard candidates.
|
|
132
|
+
|
|
133
133
|
Fallback parameters:
|
|
134
134
|
- `-testPlatform`: `EditMode` or `PlayMode`
|
|
135
135
|
- `-testFilter`: Full test name (e.g., `MyNamespace.MyTests.TestMethodName`)
|
|
@@ -12,19 +12,19 @@ Use this workflow only for an already-running exact project copy with `com.unity
|
|
|
12
12
|
Use one typed tool call for each supported connected operation:
|
|
13
13
|
|
|
14
14
|
- `unity_pipeline_recompile` for connected script compilation.
|
|
15
|
-
- `
|
|
15
|
+
- `unity_run_tests` for one compatible `EditMode` or `PlayMode` test-name or category selection.
|
|
16
16
|
|
|
17
17
|
These tools resolve the exact copy, require advertised commands, inspect lifecycle state, dispatch once, validate identity, and poll internally with a fixed deadline. Do not recreate their wait loops with `bash`, `unity recompile_status`, or `unity test_status` calls.
|
|
18
18
|
|
|
19
|
-
A timeout or malformed response is uncertain: the Unity operation may still be running. Do not cancel, retry, launch batchmode, close the Editor, or claim a result without a new user-authorized decision. The only automatic retry is Pipeline 0.5's explicit initial-settling `Server Busy` response for `unity_pipeline_recompile` or `
|
|
19
|
+
A timeout or malformed response is uncertain: the Unity operation may still be running. Do not cancel, retry, launch batchmode, close the Editor, or claim a result without a new user-authorized decision. The only automatic retry is Pipeline 0.5's explicit initial-settling `Server Busy` response for `unity_pipeline_recompile` or `unity_run_tests`, which confirms that a main-thread command was rejected before dispatch.
|
|
20
20
|
|
|
21
21
|
## Preconditions and boundaries
|
|
22
22
|
|
|
23
23
|
1. Pass an explicit `path` when multiple project copies may be found; paths identify copies, not display names.
|
|
24
24
|
2. The typed tools require a reachable exact-copy Pipeline and advertised `editor_status` plus operation commands. A different connected client is not itself a project lock.
|
|
25
|
-
3. `unity_pipeline_recompile` never sends `editor_stop` or overrides Unity's Script Changes While Playing preference. Known recompile-and-continue, stop-and-recompile, and defer policies proceed according to Unity's configured behavior. Pipeline 0.4 does not currently expose that preference, so the tool reports the unavailable policy while allowing recompilation to proceed. `
|
|
25
|
+
3. `unity_pipeline_recompile` never sends `editor_stop` or overrides Unity's Script Changes While Playing preference. Known recompile-and-continue, stop-and-recompile, and defer policies proceed according to Unity's configured behavior. Pipeline 0.4 does not currently expose that preference, so the tool reports the unavailable policy while allowing recompilation to proceed. `unity_run_tests` may dispatch advertised `editor_stop` when needed, then verifies Edit Mode before running tests. The tools never enter Play Mode, pause, save, launch, or close Unity; recompilation may perform Unity's normal asset refresh/import and script-change behavior.
|
|
26
26
|
4. Test success requires a well-formed terminal result, a known positive executed count, and zero failures. An asynchronous initiation with `Total: 0` and `running` is nonterminal.
|
|
27
|
-
5.
|
|
27
|
+
5. Routine tool output remains compact. Complete bounded terminal test records are persisted immediately in the durable normalized JSON artifact before Pipeline status can be displaced.
|
|
28
28
|
|
|
29
29
|
## Compile
|
|
30
30
|
|
|
@@ -32,11 +32,11 @@ Call `unity_pipeline_recompile` with optional `path` and `timeoutSeconds` (defau
|
|
|
32
32
|
|
|
33
33
|
## Focused tests
|
|
34
34
|
|
|
35
|
-
Call `
|
|
35
|
+
Call `unity_run_tests` with:
|
|
36
36
|
|
|
37
37
|
- required `testPlatform`: `EditMode` or `PlayMode`;
|
|
38
|
-
- optional `
|
|
39
|
-
- optional `path
|
|
38
|
+
- optional `testFilters` or `testCategories`: at most one selector family and one selector for connected execution;
|
|
39
|
+
- optional `execution`, `path`, and `timeoutSeconds` (default 600, maximum 3600).
|
|
40
40
|
- before running PlayMode tests, check the Game View focus setting. Set it to Play Unfocused for the test run, then restore the previous setting afterward.
|
|
41
41
|
|
|
42
42
|
The tool treats `no_tests`, idle, and not-started statuses as safe inactivity, detects a pre-existing active connected test before dispatch, and stops rather than claiming or replacing active work. It captures returned mode/filter/run identity fields when available and stops as uncertain if status is clearly displaced by another run.
|
|
@@ -47,6 +47,6 @@ Normally call the typed tools, not raw CLI commands. If a typed tool is unavaila
|
|
|
47
47
|
|
|
48
48
|
## When not to use connected tools
|
|
49
49
|
|
|
50
|
-
Use `
|
|
50
|
+
Use `unity_run_tests` with `execution: "isolated"` for a closed project, intentional isolation/CI, multiple selectors, retries, sharding, coverage, or required NUnit/JUnit evidence. A single category is supported connected when Pipeline advertises the command; do not claim broader connected selector support. Do not use batchmode as an automatic fallback after an uncertain connected dispatch.
|
|
51
51
|
|
|
52
52
|
Use the typed compile/test tools when their polling and terminal evidence fit the task. Advertised Pipeline `eval` remains available through `unity_pipeline_eval` for bounded project-specific inspection or operations outside those typed workflows; its `timeoutSeconds` range is 1–86,400 seconds, and a timeout remains uncertain without cancellation or retry. It is an assistance surface, not a forbidden fallback or a substitute for the typed tools' completion protocol. Eval compiles arbitrary C# with Roslyn on the Editor main thread, so ordinary properties and local-variable snippets are valid; it is not expression-only or reliably statically read-only. Prefer typed tools for their stronger evidence, but let user intent and project guidance govern mutations. Lifecycle, persistent-setting, destructive, asset, scene-save, package, build, and test mutations require explicit authorization.
|
package/src/unity-batchmode.ts
CHANGED
|
@@ -18,6 +18,7 @@ export type UnityFailedTest = {
|
|
|
18
18
|
stackTrace?: string;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
export type UnityParsedTestCase = { name: string; status: string; durationSeconds?: number; message?: string; stackTrace?: string };
|
|
21
22
|
export type UnityParsedTestResults = {
|
|
22
23
|
total?: number;
|
|
23
24
|
passed?: number;
|
|
@@ -26,6 +27,8 @@ export type UnityParsedTestResults = {
|
|
|
26
27
|
inconclusive?: number;
|
|
27
28
|
durationSeconds?: number;
|
|
28
29
|
failedTests: UnityFailedTest[];
|
|
30
|
+
/** Complete bounded per-test evidence for normalized artifacts, never routine tool output. */
|
|
31
|
+
tests: UnityParsedTestCase[];
|
|
29
32
|
};
|
|
30
33
|
|
|
31
34
|
export type UnityBatchmodeArtifacts = {
|
|
@@ -108,6 +111,7 @@ export function parseUnityTestResultsXml(xml: string): UnityParsedTestResults |
|
|
|
108
111
|
|
|
109
112
|
const rootAttributes = parseAttributes(testRunMatch[1] ?? "");
|
|
110
113
|
const failedTests: UnityFailedTest[] = [];
|
|
114
|
+
const tests: UnityParsedTestCase[] = [];
|
|
111
115
|
|
|
112
116
|
const testCaseRegex = /<test-case\b([^>]*)>([\s\S]*?)<\/test-case>/gi;
|
|
113
117
|
for (const match of xml.matchAll(testCaseRegex)) {
|
|
@@ -116,17 +120,12 @@ export function parseUnityTestResultsXml(xml: string): UnityParsedTestResults |
|
|
|
116
120
|
const result = String(attributes.result ?? attributes.label ?? "").toLowerCase();
|
|
117
121
|
const success = String(attributes.success ?? "").toLowerCase();
|
|
118
122
|
const isFailure = result === "failed" || success === "false";
|
|
119
|
-
if (!isFailure) continue;
|
|
120
|
-
|
|
121
123
|
const failureMessage = body.match(/<message[^>]*>([\s\S]*?)<\/message>/i);
|
|
122
124
|
const stackTrace = body.match(/<stack-trace[^>]*>([\s\S]*?)<\/stack-trace>/i);
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
stackTrace: truncateEvidence(decodeXmlText(stackTrace?.[1]), 4_000),
|
|
128
|
-
});
|
|
129
|
-
}
|
|
125
|
+
const name = truncateEvidence(attributes.fullname ?? attributes.name ?? "(unknown test)", 1_000) ?? "(unknown test)";
|
|
126
|
+
if (tests.length < 2_000) tests.push({ name, status: attributes.result ?? attributes.label ?? "Unknown", ...(parseOptionalNumber(attributes.duration) === undefined ? {} : { durationSeconds: parseOptionalNumber(attributes.duration) }), ...(truncateEvidence(decodeXmlText(failureMessage?.[1]), 4_000) ? { message: truncateEvidence(decodeXmlText(failureMessage?.[1]), 4_000) } : {}), ...(truncateEvidence(decodeXmlText(stackTrace?.[1]), 8_000) ? { stackTrace: truncateEvidence(decodeXmlText(stackTrace?.[1]), 8_000) } : {}) });
|
|
127
|
+
if (!isFailure) continue;
|
|
128
|
+
if (failedTests.length < 50) failedTests.push({ name, message: truncateEvidence(decodeXmlText(failureMessage?.[1]), 1_000), stackTrace: truncateEvidence(decodeXmlText(stackTrace?.[1]), 4_000) });
|
|
130
129
|
}
|
|
131
130
|
|
|
132
131
|
const skipped = parseOptionalNumber(rootAttributes.skipped) ?? parseOptionalNumber(rootAttributes.inconclusive);
|
|
@@ -139,6 +138,7 @@ export function parseUnityTestResultsXml(xml: string): UnityParsedTestResults |
|
|
|
139
138
|
inconclusive: parseOptionalNumber(rootAttributes.inconclusive),
|
|
140
139
|
durationSeconds: parseOptionalNumber(rootAttributes.duration),
|
|
141
140
|
failedTests,
|
|
141
|
+
tests,
|
|
142
142
|
};
|
|
143
143
|
if (parsed.total === undefined && parsed.passed === undefined && parsed.failed === undefined && parsed.failedTests.length === 0) {
|
|
144
144
|
return null;
|
package/src/unity-cli.ts
CHANGED
|
@@ -13,8 +13,7 @@ export type UnityCliCommand = {
|
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
export type UnityCliLaunchOptions = {
|
|
16
|
-
|
|
17
|
-
editorPath?: string;
|
|
16
|
+
editorVersionOverride?: string;
|
|
18
17
|
timeoutSeconds?: number;
|
|
19
18
|
cliCommand?: string;
|
|
20
19
|
useGraphics?: boolean;
|
|
@@ -22,6 +21,19 @@ export type UnityCliLaunchOptions = {
|
|
|
22
21
|
automated?: boolean;
|
|
23
22
|
};
|
|
24
23
|
|
|
24
|
+
export type UnityCliTestOptions = UnityCliLaunchOptions & {
|
|
25
|
+
testPlatform: "EditMode" | "PlayMode";
|
|
26
|
+
testFilters?: string[];
|
|
27
|
+
testCategories?: string[];
|
|
28
|
+
retries?: number;
|
|
29
|
+
rerunFailed?: boolean;
|
|
30
|
+
shard?: string;
|
|
31
|
+
shardInventoryPath?: string;
|
|
32
|
+
reportPaths?: { nunit?: string; junit?: string; log?: string };
|
|
33
|
+
coverage?: boolean;
|
|
34
|
+
coverageOptions?: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
25
37
|
export type UnityCliPipelineInstance = {
|
|
26
38
|
projectPath: string;
|
|
27
39
|
pid: number | null;
|
|
@@ -71,11 +83,8 @@ function unityCliBaseArgs(): string[] {
|
|
|
71
83
|
}
|
|
72
84
|
|
|
73
85
|
function appendUnityCliEditorOptions(args: string[], options: UnityCliLaunchOptions): void {
|
|
74
|
-
if (options.
|
|
75
|
-
args.push("--editor-version", options.
|
|
76
|
-
}
|
|
77
|
-
if (options.editorPath?.trim()) {
|
|
78
|
-
args.push("--editor-path", options.editorPath.trim());
|
|
86
|
+
if (options.editorVersionOverride?.trim()) {
|
|
87
|
+
args.push("--editor-version", options.editorVersionOverride.trim());
|
|
79
88
|
}
|
|
80
89
|
}
|
|
81
90
|
|
|
@@ -116,6 +125,30 @@ export function normalizeUnityCliForwardedArgs(extraEditorArgs: string[] = []):
|
|
|
116
125
|
return normalized;
|
|
117
126
|
}
|
|
118
127
|
|
|
128
|
+
export function createUnityCliTestCommand(projectRoot: string, options: UnityCliTestOptions): UnityCliCommand {
|
|
129
|
+
const args = [...unityCliBaseArgs(), "test", projectRoot, "--mode", options.testPlatform];
|
|
130
|
+
appendUnityCliEditorOptions(args, options);
|
|
131
|
+
if (options.timeoutSeconds !== undefined) args.push("--timeout", String(options.timeoutSeconds));
|
|
132
|
+
if (options.testFilters?.length) args.push("--filter", options.testFilters.join(";"));
|
|
133
|
+
if (options.retries) args.push("--retries", String(options.retries));
|
|
134
|
+
if (options.rerunFailed) args.push("--rerun-failed");
|
|
135
|
+
if (options.shard) args.push("--shard", options.shard);
|
|
136
|
+
if (options.shardInventoryPath) args.push("--shard-inventory", options.shardInventoryPath);
|
|
137
|
+
const nunit = options.reportPaths?.nunit;
|
|
138
|
+
const junit = options.reportPaths?.junit;
|
|
139
|
+
if (nunit && junit) args.push("--output", nunit, "--report-format", "nunit,junit", "--junit-output", junit);
|
|
140
|
+
else if (junit) args.push("--output", junit, "--report-format", "junit");
|
|
141
|
+
else if (nunit) args.push("--output", nunit);
|
|
142
|
+
if (options.coverage) args.push("--coverage");
|
|
143
|
+
if (options.coverageOptions) args.push("--coverage-options", options.coverageOptions);
|
|
144
|
+
const editorArgs: string[] = [];
|
|
145
|
+
if (!options.useGraphics) editorArgs.push("-nographics");
|
|
146
|
+
if (options.testCategories?.length) editorArgs.push("-testCategory", options.testCategories.join(";"));
|
|
147
|
+
if (options.reportPaths?.log) editorArgs.push("-logFile", options.reportPaths.log);
|
|
148
|
+
if (editorArgs.length > 0) args.push("--", ...editorArgs);
|
|
149
|
+
return { command: resolveUnityCliCommand(options), args };
|
|
150
|
+
}
|
|
151
|
+
|
|
119
152
|
export function createUnityCliRunCommand(projectRoot: string, extraEditorArgs: string[] = [], options: UnityCliLaunchOptions = {}): UnityCliCommand {
|
|
120
153
|
const args = [...unityCliBaseArgs(), "run", projectRoot];
|
|
121
154
|
const forwardedArgs = normalizeUnityCliForwardedArgs(applyDefaultUnityBatchmodeArgs(extraEditorArgs, { useGraphics: options.useGraphics }));
|
package/src/unity-core.ts
CHANGED
|
@@ -27,14 +27,6 @@ export function parseUnityVersionText(contents: string): string | null {
|
|
|
27
27
|
return match ? match[1] : null;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
export function normalizeUnityEditorOverride(editorPath: string, platform: SupportedPlatform): string {
|
|
31
|
-
const normalized = path.normalize(editorPath.trim());
|
|
32
|
-
if (platform === "darwin" && normalized.toLowerCase().endsWith(".app")) {
|
|
33
|
-
return path.join(normalized, "Contents", "MacOS", "Unity");
|
|
34
|
-
}
|
|
35
|
-
return normalized;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
30
|
export function buildUnityEditorCandidates(
|
|
39
31
|
version: string,
|
|
40
32
|
platform: SupportedPlatform = process.platform,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
buildUnityBatchmodeArgs,
|
|
5
|
+
buildUnityEditorCandidates,
|
|
6
|
+
buildUnityOpenEditorArgs,
|
|
7
|
+
type SupportedPlatform,
|
|
8
|
+
type UnityOpenEditorArgsOptions,
|
|
9
|
+
} from "./unity-core";
|
|
10
|
+
|
|
11
|
+
/** Direct-executable compatibility fallback. Unity CLI routes must not import this module. */
|
|
12
|
+
export async function resolveUnityEditorPath(
|
|
13
|
+
unityVersion: string,
|
|
14
|
+
options: { platform?: SupportedPlatform; homeDir?: string; access?: (candidate: string) => Promise<void> } = {},
|
|
15
|
+
): Promise<string> {
|
|
16
|
+
const autoCandidates = buildUnityEditorCandidates(unityVersion, options.platform ?? process.platform, options.homeDir);
|
|
17
|
+
const access = options.access ?? ((candidate: string) => fs.access(candidate, fs.constants.X_OK));
|
|
18
|
+
for (const candidate of autoCandidates) {
|
|
19
|
+
try { await access(candidate); return path.normalize(candidate); } catch { /* Try next exact-version candidate. */ }
|
|
20
|
+
}
|
|
21
|
+
throw new Error(`Could not find a Unity Editor executable for Unity ${unityVersion}. Install that exact Editor version through your normal Unity installation workflow, or install Unity CLI so it can resolve the project version.`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function launchUnityEditorDetached(editorPath: string, projectRoot: string, options: UnityOpenEditorArgsOptions = {}): { pid: number | undefined; args: string[]; command: string } {
|
|
25
|
+
const args = buildUnityOpenEditorArgs(projectRoot, options);
|
|
26
|
+
const child = (awaitableSpawn)(editorPath, args, { detached: true, stdio: "ignore", windowsHide: false });
|
|
27
|
+
child.unref();
|
|
28
|
+
return { pid: child.pid, args, command: editorPath };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Kept local so this module owns direct process creation entirely.
|
|
32
|
+
import { spawn as awaitableSpawn } from "node:child_process";
|
|
33
|
+
|
|
34
|
+
export function createUnityBatchmodeCommand(editorPath: string, projectRoot: string, extraArgs: string[] = [], options: { useGraphics?: boolean } = {}): { command: string; args: string[] } {
|
|
35
|
+
return { command: editorPath, args: buildUnityBatchmodeArgs(projectRoot, extraArgs, options) };
|
|
36
|
+
}
|
package/src/unity-launch.ts
CHANGED
|
@@ -1,90 +1,10 @@
|
|
|
1
|
-
import * as fs from "node:fs/promises";
|
|
2
|
-
import * as path from "node:path";
|
|
3
1
|
import { spawn } from "node:child_process";
|
|
4
|
-
import {
|
|
5
|
-
buildUnityBatchmodeArgs,
|
|
6
|
-
buildUnityEditorCandidates,
|
|
7
|
-
buildUnityOpenEditorArgs,
|
|
8
|
-
normalizeUnityEditorOverride,
|
|
9
|
-
type SupportedPlatform,
|
|
10
|
-
type UnityOpenEditorArgsOptions,
|
|
11
|
-
} from "./unity-core";
|
|
12
2
|
import { createUnityCliOpenCommand, type UnityCliLaunchOptions } from "./unity-cli";
|
|
13
3
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
options: {
|
|
17
|
-
overridePath?: string;
|
|
18
|
-
env?: NodeJS.ProcessEnv;
|
|
19
|
-
platform?: SupportedPlatform;
|
|
20
|
-
homeDir?: string;
|
|
21
|
-
} = {},
|
|
22
|
-
): Promise<string> {
|
|
23
|
-
const platform = options.platform ?? process.platform;
|
|
24
|
-
const env = options.env ?? process.env;
|
|
25
|
-
const homeDir = options.homeDir;
|
|
26
|
-
|
|
27
|
-
const overrideCandidates = [options.overridePath, env.UNITY_EDITOR_PATH, env.UNITY_PATH]
|
|
28
|
-
.map((value) => value?.trim())
|
|
29
|
-
.filter((value): value is string => Boolean(value))
|
|
30
|
-
.map((value) => normalizeUnityEditorOverride(value, platform));
|
|
31
|
-
|
|
32
|
-
const autoCandidates = buildUnityEditorCandidates(unityVersion, platform, homeDir);
|
|
33
|
-
|
|
34
|
-
for (const candidate of [...overrideCandidates, ...autoCandidates]) {
|
|
35
|
-
try {
|
|
36
|
-
await fs.access(candidate, fs.constants.X_OK);
|
|
37
|
-
return path.normalize(candidate);
|
|
38
|
-
} catch {
|
|
39
|
-
// Try next candidate.
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
throw new Error(
|
|
44
|
-
[
|
|
45
|
-
`Could not find a Unity Editor executable for Unity ${unityVersion}.`,
|
|
46
|
-
"Set UNITY_EDITOR_PATH to an explicit executable path or pass unityEditorPath.",
|
|
47
|
-
].join(" "),
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function launchUnityEditorDetached(
|
|
52
|
-
editorPath: string,
|
|
53
|
-
projectRoot: string,
|
|
54
|
-
options: UnityOpenEditorArgsOptions = {},
|
|
55
|
-
): { pid: number | undefined; args: string[]; command: string } {
|
|
56
|
-
const args = buildUnityOpenEditorArgs(projectRoot, options);
|
|
57
|
-
const child = spawn(editorPath, args, {
|
|
58
|
-
detached: true,
|
|
59
|
-
stdio: "ignore",
|
|
60
|
-
windowsHide: false,
|
|
61
|
-
});
|
|
62
|
-
child.unref();
|
|
63
|
-
return { pid: child.pid, args, command: editorPath };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function launchUnityCliOpenDetached(
|
|
67
|
-
projectRoot: string,
|
|
68
|
-
options: UnityCliLaunchOptions = {},
|
|
69
|
-
): { pid: number | undefined; args: string[]; command: string } {
|
|
4
|
+
/** Unity CLI GUI launch path. Direct executable fallback lives in unity-editor-fallback.ts. */
|
|
5
|
+
export function launchUnityCliOpenDetached(projectRoot: string, options: UnityCliLaunchOptions = {}): { pid: number | undefined; args: string[]; command: string } {
|
|
70
6
|
const cli = createUnityCliOpenCommand(projectRoot, options);
|
|
71
|
-
const child = spawn(cli.command, cli.args, {
|
|
72
|
-
detached: true,
|
|
73
|
-
stdio: "ignore",
|
|
74
|
-
windowsHide: false,
|
|
75
|
-
});
|
|
7
|
+
const child = spawn(cli.command, cli.args, { detached: true, stdio: "ignore", windowsHide: false });
|
|
76
8
|
child.unref();
|
|
77
9
|
return { pid: child.pid, args: cli.args, command: cli.command };
|
|
78
10
|
}
|
|
79
|
-
|
|
80
|
-
export function createUnityBatchmodeCommand(
|
|
81
|
-
editorPath: string,
|
|
82
|
-
projectRoot: string,
|
|
83
|
-
extraArgs: string[] = [],
|
|
84
|
-
options: { useGraphics?: boolean } = {},
|
|
85
|
-
): { command: string; args: string[] } {
|
|
86
|
-
return {
|
|
87
|
-
command: editorPath,
|
|
88
|
-
args: buildUnityBatchmodeArgs(projectRoot, extraArgs, options),
|
|
89
|
-
};
|
|
90
|
-
}
|
package/src/unity-pipeline.ts
CHANGED
|
@@ -11,7 +11,7 @@ export const UNITY_PIPELINE_MAX_DIAGNOSTICS = 8;
|
|
|
11
11
|
export const UNITY_PIPELINE_MAX_STACK_CHARS = 600;
|
|
12
12
|
|
|
13
13
|
export type UnityPipelineCompileRequest = { projectRoot: string; unityVersion: string; timeoutSeconds?: number; allowAutonomousExitPlayMode?: boolean };
|
|
14
|
-
export type UnityPipelineTestRequest = { projectRoot: string; unityVersion: string; testPlatform: "EditMode" | "PlayMode"; testFilter?: string; timeoutSeconds?: number; allowAutonomousExitPlayMode?: boolean };
|
|
14
|
+
export type UnityPipelineTestRequest = { projectRoot: string; unityVersion: string; testPlatform: "EditMode" | "PlayMode"; testFilter?: string; testCategory?: string; timeoutSeconds?: number; allowAutonomousExitPlayMode?: boolean };
|
|
15
15
|
export type UnityPipelineProgress = (message: string) => void;
|
|
16
16
|
/** Unity's EditorSettings.ScriptChangesWhilePlaying values when a future editor_status payload supplies one. */
|
|
17
17
|
export type UnityScriptChangesWhilePlayingPolicy = "recompile_and_continue" | "stop_and_recompile" | "defer" | "unknown";
|
|
@@ -32,7 +32,9 @@ export type UnityPipelineOperationDetails = {
|
|
|
32
32
|
testFilter?: string;
|
|
33
33
|
counts?: { total: number; passed?: number; failed: number; inconclusive?: number };
|
|
34
34
|
};
|
|
35
|
-
export type
|
|
35
|
+
export type UnityPipelineTestRecord = { name: string; status: string; durationSeconds?: number; message?: string; stackTrace?: string };
|
|
36
|
+
/** testRecords are terminal evidence for the caller's durable artifact only; do not expose them in tool details. */
|
|
37
|
+
export type UnityPipelineOperationResult = { text: string; details: UnityPipelineOperationDetails; testRecords?: UnityPipelineTestRecord[] };
|
|
36
38
|
|
|
37
39
|
type RecordValue = Record<string, unknown>;
|
|
38
40
|
type ParsedEnvelope = { result: RecordValue; outerSuccess: boolean; malformed?: string };
|
|
@@ -41,6 +43,7 @@ type NormalizedTest = {
|
|
|
41
43
|
state: "inactive" | "starting" | "running" | "completed" | "failed" | "cancelled" | "uncertain";
|
|
42
44
|
total?: number; passed?: number; failed?: number; inconclusive?: number; failures: string[];
|
|
43
45
|
correlation: Record<string, string>;
|
|
46
|
+
testRecords?: UnityPipelineTestRecord[];
|
|
44
47
|
};
|
|
45
48
|
|
|
46
49
|
type PipelineDependencies = {
|
|
@@ -172,6 +175,24 @@ function summary(result: RecordValue): RecordValue | undefined {
|
|
|
172
175
|
walk(result, item => { if (!found && record(field(item, "summary"))) found = record(field(item, "summary")); });
|
|
173
176
|
return found;
|
|
174
177
|
}
|
|
178
|
+
function testRecords(result: RecordValue): UnityPipelineTestRecord[] {
|
|
179
|
+
const values: UnityPipelineTestRecord[] = [];
|
|
180
|
+
walk(result, item => {
|
|
181
|
+
for (const key of ["tests", "results", "testresults"]) {
|
|
182
|
+
const entries = field(item, key);
|
|
183
|
+
if (!Array.isArray(entries)) continue;
|
|
184
|
+
for (const entry of entries.slice(0, 2_000)) {
|
|
185
|
+
const test = record(entry); if (!test) continue;
|
|
186
|
+
const name = string(field(test, "name", "fullname", "testname"));
|
|
187
|
+
const status = string(field(test, "result", "status", "outcome"));
|
|
188
|
+
if (!name || !status) continue;
|
|
189
|
+
const durationSeconds = number(field(test, "duration", "durationseconds", "time"));
|
|
190
|
+
values.push({ name: bounded(name, 1_000), status: bounded(status, 100), ...(durationSeconds === undefined ? {} : { durationSeconds }), ...(string(field(test, "message", "error", "failuremessage")) ? { message: bounded(string(field(test, "message", "error", "failuremessage"))!, 4_000) } : {}), ...(string(field(test, "stacktrace", "stack", "trace")) ? { stackTrace: bounded(string(field(test, "stacktrace", "stack", "trace"))!, 8_000) } : {}) });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
return values.slice(0, 2_000);
|
|
195
|
+
}
|
|
175
196
|
function testFailures(result: RecordValue): string[] {
|
|
176
197
|
const values: string[] = [];
|
|
177
198
|
walk(result, item => {
|
|
@@ -223,7 +244,7 @@ export function normalizeUnityPipelineTest(output: string): NormalizedTest {
|
|
|
223
244
|
: raw === "no_tests" || raw === "idle" || raw === "not_started" || raw === "not_running" ? "inactive"
|
|
224
245
|
: raw === "running" ? "running" : raw === "starting" || raw === "queued" ? "starting"
|
|
225
246
|
: raw === "completed" || raw === "complete" || raw === "success" ? "completed" : "uncertain";
|
|
226
|
-
return { state, total, passed, failed: failedCount, inconclusive, failures: testFailures(parsed.result), correlation: correlation(parsed.result) };
|
|
247
|
+
return { state, total, passed, failed: failedCount, inconclusive, failures: testFailures(parsed.result), correlation: correlation(parsed.result), testRecords: testRecords(parsed.result) };
|
|
227
248
|
}
|
|
228
249
|
|
|
229
250
|
function editorStopSucceeded(output: string): boolean {
|
|
@@ -471,7 +492,10 @@ export async function runUnityPipelineTests(request: UnityPipelineTestRequest, d
|
|
|
471
492
|
const observed = statusOf(parseUnityPipelineEnvelope(before.stdout).result) ?? "unknown";
|
|
472
493
|
throw new Error(`Unity Pipeline returned unsupported preflight test status '${bounded(observed, 80)}'; test run not started.`);
|
|
473
494
|
}
|
|
474
|
-
|
|
495
|
+
if (request.testFilter && request.testCategory) throw new Error("Connected Pipeline cannot combine a test-name filter and category in one run; operation not started.");
|
|
496
|
+
const selectorArgs = request.testFilter ? ["--filter", request.testFilter, "--filter_type", "testName"]
|
|
497
|
+
: request.testCategory ? ["--filter", request.testCategory, "--filter_type", "category"] : [];
|
|
498
|
+
const args = ["--mode", request.testPlatform === "EditMode" ? "editor" : "playmode", ...selectorArgs, "--async_tests", "true"];
|
|
475
499
|
ensureBeforeDeadline(deadline, now, "tests before dispatch"); throwIfAborted(signal);
|
|
476
500
|
const dispatched = await dispatchMainThreadCommand(deps, projectRoot, "run_tests", args, "tests", signal, deadline, now, sleep);
|
|
477
501
|
if (dispatched.error) throw new Error("Unity Pipeline test dispatch failed; test run may not have started.");
|
|
@@ -485,7 +509,7 @@ export async function runUnityPipelineTests(request: UnityPipelineTestRequest, d
|
|
|
485
509
|
if (state.state === "completed") {
|
|
486
510
|
const counts = passingCounts(state);
|
|
487
511
|
if (!counts) throw new Error("Unity test result is terminal but lacks passing evidence (consistent positive total, passed count, and reported zero failures).");
|
|
488
|
-
return { text: `${lifecyclePrefix}Unity ${request.testPlatform} tests passed for ${projectRoot}: ${counts.total} executed, ${counts.passed} passed, 0 failed in ${elapsed(start, now).toFixed(2)}s.`, details: { projectRoot, operation: "tests", terminalState: "completed", elapsedSeconds: elapsed(start, now), ...playModeDetails(preflight), testPlatform: request.testPlatform, testFilter: request.testFilter, counts } };
|
|
512
|
+
return { text: `${lifecyclePrefix}Unity ${request.testPlatform} tests passed for ${projectRoot}: ${counts.total} executed, ${counts.passed} passed, 0 failed in ${elapsed(start, now).toFixed(2)}s.`, details: { projectRoot, operation: "tests", terminalState: "completed", elapsedSeconds: elapsed(start, now), ...playModeDetails(preflight), testPlatform: request.testPlatform, testFilter: request.testFilter ?? request.testCategory, counts }, testRecords: state.testRecords };
|
|
489
513
|
}
|
|
490
514
|
for (let poll = 0; now() < deadline; poll += 1) {
|
|
491
515
|
options.onUpdate?.(`Unity ${request.testPlatform} tests ${state.state}; ${elapsed(start, now).toFixed(1)}s elapsed.`);
|
|
@@ -507,7 +531,7 @@ export async function runUnityPipelineTests(request: UnityPipelineTestRequest, d
|
|
|
507
531
|
if (state.state !== "completed") continue;
|
|
508
532
|
const counts = passingCounts(state);
|
|
509
533
|
if (!counts) throw new Error("Unity test result is terminal but lacks passing evidence (consistent positive total, passed count, and reported zero failures).");
|
|
510
|
-
return { text: `${lifecyclePrefix}Unity ${request.testPlatform} tests passed for ${projectRoot}: ${counts.total} executed, ${counts.passed} passed, 0 failed in ${elapsed(start, now).toFixed(2)}s.`, details: { projectRoot, operation: "tests", terminalState: "completed", elapsedSeconds: elapsed(start, now), ...playModeDetails(preflight), testPlatform: request.testPlatform, testFilter: request.testFilter, counts } };
|
|
534
|
+
return { text: `${lifecyclePrefix}Unity ${request.testPlatform} tests passed for ${projectRoot}: ${counts.total} executed, ${counts.passed} passed, 0 failed in ${elapsed(start, now).toFixed(2)}s.`, details: { projectRoot, operation: "tests", terminalState: "completed", elapsedSeconds: elapsed(start, now), ...playModeDetails(preflight), testPlatform: request.testPlatform, testFilter: request.testFilter ?? request.testCategory, counts }, testRecords: state.testRecords };
|
|
511
535
|
}
|
|
512
536
|
throw timeoutMessage("tests");
|
|
513
537
|
}
|
package/src/unity-projects.ts
CHANGED
|
@@ -5,7 +5,8 @@ import { parseUnityVersionText, resolveAbsolutePath } from "./unity-core";
|
|
|
5
5
|
export type UnityProjectCandidate = {
|
|
6
6
|
projectRoot: string;
|
|
7
7
|
projectName: string;
|
|
8
|
-
|
|
8
|
+
/** Manual ProjectVersion.txt evidence, loaded only by direct/Pipeline routes. */
|
|
9
|
+
unityVersion?: string;
|
|
9
10
|
};
|
|
10
11
|
|
|
11
12
|
export type UnityProjectDiscoveryResult = {
|
|
@@ -81,7 +82,6 @@ export async function findAncestorUnityProject(startDir: string): Promise<UnityP
|
|
|
81
82
|
return {
|
|
82
83
|
projectRoot: current,
|
|
83
84
|
projectName: path.basename(current),
|
|
84
|
-
unityVersion: await readUnityVersion(current),
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
87
|
|
|
@@ -123,7 +123,6 @@ export async function discoverUnityProjects(
|
|
|
123
123
|
candidates.push({
|
|
124
124
|
projectRoot: next.dir,
|
|
125
125
|
projectName: path.basename(next.dir),
|
|
126
|
-
unityVersion: await readUnityVersion(next.dir),
|
|
127
126
|
});
|
|
128
127
|
if (candidates.length >= maxCandidates) {
|
|
129
128
|
truncated = true;
|
package/src/unity-test-batch.ts
CHANGED
|
@@ -18,6 +18,7 @@ export type UnityTestBatchPlan = {
|
|
|
18
18
|
testFilters: string[];
|
|
19
19
|
testCategories: string[];
|
|
20
20
|
testResultsPath: string;
|
|
21
|
+
junitResultsPath: string;
|
|
21
22
|
logFilePath: string;
|
|
22
23
|
args: string[];
|
|
23
24
|
};
|
|
@@ -59,6 +60,7 @@ export function createUnityTestBatchPlan(input: UnityTestBatchPlanInput): UnityT
|
|
|
59
60
|
const platformSlug = input.testPlatform.toLowerCase();
|
|
60
61
|
const basename = `unity-tests-${platformSlug}-${safeTimestamp(input.now ?? new Date())}-${token}`;
|
|
61
62
|
const testResultsPath = pathApi.join(logsRoot, `${basename}.xml`);
|
|
63
|
+
const junitResultsPath = pathApi.join(logsRoot, `${basename}.junit.xml`);
|
|
62
64
|
const logFilePath = pathApi.join(logsRoot, `${basename}.log`);
|
|
63
65
|
const args = ["-runTests", "-testPlatform", input.testPlatform];
|
|
64
66
|
if (testFilters.length > 0) args.push("-testFilter", testFilters.join(";"));
|
|
@@ -78,5 +80,5 @@ export function createUnityTestBatchPlan(input: UnityTestBatchPlanInput): UnityT
|
|
|
78
80
|
throw new Error("Unity test batch arguments must not contain -quit.");
|
|
79
81
|
}
|
|
80
82
|
|
|
81
|
-
return { testPlatform: input.testPlatform, testFilters, testCategories, testResultsPath, logFilePath, args };
|
|
83
|
+
return { testPlatform: input.testPlatform, testFilters, testCategories, testResultsPath, junitResultsPath, logFilePath, args };
|
|
82
84
|
}
|