forked from swiftlang/vscode-swift
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathTestRunArguments.ts
263 lines (239 loc) · 10.1 KB
/
TestRunArguments.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
//===----------------------------------------------------------------------===//
//
// This source file is part of the VS Code Swift open source project
//
// Copyright (c) 2021-2024 the VS Code Swift project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of VS Code Swift project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import * as vscode from "vscode";
import { reduceTestItemChildren } from "./TestUtils";
type ProcessResult = {
testItems: vscode.TestItem[];
xcTestArgs: vscode.TestItem[];
swiftTestArgs: vscode.TestItem[];
};
/**
* Given a `TestRunRequest`, produces the lists of
* XCTests and swift-testing tests to run.
*/
export class TestRunArguments {
public testItems: vscode.TestItem[];
public xcTestArgs: string[];
public swiftTestArgs: string[];
constructor(request: vscode.TestRunRequest, isDebug: boolean) {
const { testItems, xcTestArgs, swiftTestArgs } = this.createTestLists(request, isDebug);
this.testItems = testItems;
this.xcTestArgs = this.annotateTestArgs(xcTestArgs, isDebug);
this.swiftTestArgs = this.annotateTestArgs(swiftTestArgs, isDebug);
}
public get hasXCTests(): boolean {
return this.xcTestArgs.length > 0;
}
public get hasSwiftTestingTests(): boolean {
return this.swiftTestArgs.length > 0;
}
/**
* Construct test item list from TestRequest
* @returns list of test items to run and list of test for XCTest arguments
*/
private createTestLists(request: vscode.TestRunRequest, isDebug: boolean): ProcessResult {
const includes = request.include ?? [];
const excludes = request.exclude ?? [];
return includes.reduce(this.createTestItemReducer(includes, excludes, isDebug), {
testItems: this.createIncludeParentList(includes),
xcTestArgs: [],
swiftTestArgs: [],
});
}
/**
* For all the included tests we want to collect up a list of their
* parents so they are included in the final testItems list. Otherwise
* we'll get testStart/End events for testItems we have no record of.
*/
private createIncludeParentList(includes: readonly vscode.TestItem[]): vscode.TestItem[] {
const parents = includes.reduce((map, include) => {
let parent = include.parent;
while (parent) {
map.set(parent.id, parent);
parent = parent.parent;
}
return map;
}, new Map<string, vscode.TestItem>());
return Array.from(parents.values());
}
/**
* Converts a list of TestItems to a regex test item ID. Depending on the TestItem's
* tags and whether it is a debug run the ID is converted to a regex pattern that will
* match the correct tests when passed to the `--filter` argument of `swift test`.
*/
private annotateTestArgs(testArgs: vscode.TestItem[], isDebug: boolean): string[] {
return testArgs.map(arg => {
const isTestTarget = !!arg.tags.find(tag => tag.id === "test-target");
if (isTestTarget) {
return `${arg.id}.*`;
}
const isXCTest = !!arg.tags.find(tag => tag.id === "XCTest");
const hasChildren = arg.children.size > 0;
if (isXCTest) {
const terminator = hasChildren ? "/" : "$";
// Debugging XCTests requires exact matches, so we don't need a trailing terminator.
return isDebug ? arg.id : `${arg.id}${terminator}`;
} else {
// Append a trailing slash to match a suite name exactly.
// This prevents TestTarget.MySuite matching TestTarget.MySuite2.
return `${arg.id}/`;
}
});
}
private createTestItemReducer(
include: readonly vscode.TestItem[],
exclude: readonly vscode.TestItem[],
isDebug: boolean
): (previousValue: ProcessResult, testItem: vscode.TestItem) => ProcessResult {
return (previousValue, testItem) => {
const { testItems, swiftTestArgs, xcTestArgs } = this.processTestItem(
testItem,
include,
exclude,
isDebug
);
// If no children were added we can skip adding this parent.
if (xcTestArgs.length + swiftTestArgs.length === 0) {
return previousValue;
} else if (this.itemContainsAllArgs(testItem, xcTestArgs, swiftTestArgs)) {
// If we're including every chlid in the parent, we can simplify the
// arguments and just use the parent
const { xcTestResult, swiftTestResult } = this.simplifyTestArgs(
testItem,
xcTestArgs,
swiftTestArgs,
isDebug
);
return {
testItems: [...previousValue.testItems, ...testItems],
xcTestArgs: [...previousValue.xcTestArgs, ...xcTestResult],
swiftTestArgs: [...previousValue.swiftTestArgs, ...swiftTestResult],
};
} else {
// If we've only added some of the children the append to our test list
return {
testItems: [...previousValue.testItems, ...testItems],
swiftTestArgs: [...previousValue.swiftTestArgs, ...swiftTestArgs],
xcTestArgs: [...previousValue.xcTestArgs, ...xcTestArgs],
};
}
};
}
private itemContainsAllArgs(
testItem: vscode.TestItem,
xcTestArgs: vscode.TestItem[],
swiftTestArgs: vscode.TestItem[]
): boolean {
return (
testItem.children.size > 0 &&
xcTestArgs.length + swiftTestArgs.length === testItem.children.size
);
}
private simplifyTestArgs(
testItem: vscode.TestItem,
xcTestArgs: vscode.TestItem[],
swiftTestArgs: vscode.TestItem[],
isDebug: boolean
): { xcTestResult: vscode.TestItem[]; swiftTestResult: vscode.TestItem[] } {
// If we've worked all the way up to a test target, it may have both swift-testing
// and XCTests.
const isTestTarget = !!testItem.tags.find(tag => tag.id === "test-target");
if (isTestTarget) {
// We cannot simplify away test suites leaving only the target if we are debugging,
// since the exact names of test suites to run need to be passed to the xctest binary.
// It will not debug all tests with only the target name.
if (isDebug) {
return {
xcTestResult: xcTestArgs,
swiftTestResult: swiftTestArgs,
};
}
return {
// Add a trailing .* to match a test target name exactly.
// This prevents TestTarget matching TestTarget2.
xcTestResult: xcTestArgs.length > 0 ? [testItem] : [],
swiftTestResult: swiftTestArgs.length > 0 ? [testItem] : [],
};
}
// If we've added all the children to the list of arguments, just add
// the parent instead of each individual child. This crafts a minimal set
// of test/suites that run all the test cases requested with the smallest list
// of arguments. The testItem has to have a parent to perform this optimization.
// If it does not we break the ability to run both swift testing tests and XCTests
// in the same run, since test targets can have both types of tests in them.
const isXCTest = !!testItem.tags.find(tag => tag.id === "XCTest");
return {
xcTestResult: isXCTest ? [testItem] : [],
swiftTestResult: !isXCTest ? [testItem] : [],
};
}
private processTestItem(
testItem: vscode.TestItem,
include: readonly vscode.TestItem[],
exclude: readonly vscode.TestItem[],
isDebug: boolean
): ProcessResult {
// Skip tests the user asked to exclude
if (exclude.includes(testItem)) {
return {
testItems: [],
xcTestArgs: [],
swiftTestArgs: [],
};
}
const testItems: vscode.TestItem[] = [];
const xcTestArgs: vscode.TestItem[] = [];
const swiftTestArgs: vscode.TestItem[] = [];
// If this test item is included or we are including everything
if (include.includes(testItem) || include.length === 0) {
const isXCTest = testItem.tags.find(tag => tag.id === "XCTest");
const isSwiftTestingTest = testItem.tags.find(tag => tag.id === "swift-testing");
// Collect up a list of all the test items involved in the run
// from the TestExplorer tree and store them in `testItems`. Exclude
// parameterized test result entries from this list (they don't have a uri).
if (testItem.uri !== undefined || isXCTest) {
testItems.push(testItem);
// Only add leaf items to the list of arguments to pass to the test runner.
if (this.isLeafTestItem(testItem, !!isXCTest)) {
if (isXCTest) {
xcTestArgs.push(testItem);
} else if (isSwiftTestingTest) {
swiftTestArgs.push(testItem);
}
}
}
}
return reduceTestItemChildren(
testItem.children,
this.createTestItemReducer([], exclude, isDebug),
{
testItems,
xcTestArgs,
swiftTestArgs,
}
);
}
private isLeafTestItem(testItem: vscode.TestItem, isXCTest: boolean) {
if (isXCTest) {
return testItem.children.size === 0;
}
let result = true;
testItem.children.forEach(child => {
if (child.uri) {
result = false;
}
});
return result;
}
}