Skip to content

Commit d4561f3

Browse files
authored
Add commands to debug tests multiple times (#1763)
* Add commands to debug tests multiple times Adds two new commands, `swift.debugTestsMultipleTimes` and `swift.debugTestsUntilFailure`. They behave the same as the existing run multiple and run until failure commands, except they use the debugging profile. When debugging multiple times a build is only performed for the first iteration, after which the resulting artifacts are run multiple times. Also cleans up some of the test run console output. Issue: #1747 * Update changelog
1 parent 06c9525 commit d4561f3

File tree

8 files changed

+193
-46
lines changed

8 files changed

+193
-46
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
- New `swift.createTasksForLibraryProducts` setting that when enabled causes the extension to automatically create and provide tasks for library products ([#1741](https://github.com/swiftlang/vscode-swift/pull/1741))
88
- New `swift.outputChannelLogLevel` setting to control the verbosity of the `Swift` output channel ([#1746](https://github.com/swiftlang/vscode-swift/pull/1746))
9+
- New `swift.debugTestsMultipleTimes` and `swift.debugTestsUntilFailure` commands for debugging tests over multiple runs ([#1763](https://github.com/swiftlang/vscode-swift/pull/1763))
910
- Optionally include LLDB DAP logs in the Swift diagnostics bundle ([#1768](https://github.com/swiftlang/vscode-swift/pull/1758))
1011

1112
### Changed

package.json

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,16 @@
277277
"title": "Run Until Failure...",
278278
"category": "Swift"
279279
},
280+
{
281+
"command": "swift.debugTestsMultipleTimes",
282+
"title": "Debug Multiple Times...",
283+
"category": "Swift"
284+
},
285+
{
286+
"command": "swift.debugTestsUntilFailure",
287+
"title": "Debug Until Failure...",
288+
"category": "Swift"
289+
},
280290
{
281291
"command": "swift.pickProcess",
282292
"title": "Pick Process...",
@@ -976,6 +986,16 @@
976986
"command": "swift.runTestsUntilFailure",
977987
"group": "testExtras",
978988
"when": "testId in swift.tests"
989+
},
990+
{
991+
"command": "swift.debugTestsMultipleTimes",
992+
"group": "testExtras",
993+
"when": "testId in swift.tests"
994+
},
995+
{
996+
"command": "swift.debugTestsUntilFailure",
997+
"group": "testExtras",
998+
"when": "testId in swift.tests"
979999
}
9801000
],
9811001
"testing/item/context": [
@@ -988,6 +1008,16 @@
9881008
"command": "swift.runTestsUntilFailure",
9891009
"group": "testExtras",
9901010
"when": "testId in swift.tests"
1011+
},
1012+
{
1013+
"command": "swift.debugTestsMultipleTimes",
1014+
"group": "testExtras",
1015+
"when": "testId in swift.tests"
1016+
},
1017+
{
1018+
"command": "swift.debugTestsUntilFailure",
1019+
"group": "testExtras",
1020+
"when": "testId in swift.tests"
9911021
}
9921022
],
9931023
"file/newFile": [
@@ -1015,6 +1045,16 @@
10151045
"group": "testExtras",
10161046
"when": "false"
10171047
},
1048+
{
1049+
"command": "swift.debugTestsMultipleTimes",
1050+
"group": "testExtras",
1051+
"when": "false"
1052+
},
1053+
{
1054+
"command": "swift.debugTestsUntilFailure",
1055+
"group": "testExtras",
1056+
"when": "false"
1057+
},
10181058
{
10191059
"command": "swift.generateLaunchConfigurations",
10201060
"when": "swift.hasExecutableProduct"

src/TestExplorer/TestParsers/SwiftTestingOutputParser.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
} from "./TestEventStreamReader";
2323
import { ITestRunState } from "./TestRunState";
2424
import { TestClass } from "../TestDiscovery";
25-
import { sourceLocationToVSCodeLocation } from "../../utilities/utilities";
25+
import { colorize, sourceLocationToVSCodeLocation } from "../../utilities/utilities";
2626
import { exec } from "child_process";
2727
import { lineBreakRegex } from "../../utilities/tasks";
2828

@@ -649,15 +649,15 @@ export class SymbolRenderer {
649649
case TestSymbol.skip:
650650
case TestSymbol.difference:
651651
case TestSymbol.passWithKnownIssue:
652-
return `${SymbolRenderer.ansiEscapeCodePrefix}90m${symbol}${SymbolRenderer.resetANSIEscapeCode}`;
652+
return colorize(symbol, "grey");
653653
case TestSymbol.pass:
654-
return `${SymbolRenderer.ansiEscapeCodePrefix}92m${symbol}${SymbolRenderer.resetANSIEscapeCode}`;
654+
return colorize(symbol, "lightGreen");
655655
case TestSymbol.fail:
656-
return `${SymbolRenderer.ansiEscapeCodePrefix}91m${symbol}${SymbolRenderer.resetANSIEscapeCode}`;
656+
return colorize(symbol, "lightRed");
657657
case TestSymbol.warning:
658-
return `${SymbolRenderer.ansiEscapeCodePrefix}93m${symbol}${SymbolRenderer.resetANSIEscapeCode}`;
658+
return colorize(symbol, "lightYellow");
659659
case TestSymbol.attachment:
660-
return `${SymbolRenderer.ansiEscapeCodePrefix}94m${symbol}${SymbolRenderer.resetANSIEscapeCode}`;
660+
return colorize(symbol, "lightBlue");
661661
case TestSymbol.none:
662662
default:
663663
return symbol;

src/TestExplorer/TestRunner.ts

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,9 @@ export class TestRunProxy {
337337
public setIteration(iteration: number) {
338338
this.runState = TestRunProxy.initialTestRunState();
339339
this.iteration = iteration;
340+
if (this.testRun) {
341+
this.performAppendOutput(this.testRun, "\n\r");
342+
}
340343
}
341344

342345
public appendOutput(output: string) {
@@ -933,29 +936,34 @@ export class TestRunner {
933936
}
934937

935938
/** Run test session inside debugger */
936-
async debugSession(runState: TestRunnerTestRunState) {
937-
// Perform a build all first to produce the binaries we'll run later.
938-
let buildOutput = "";
939-
try {
940-
await this.runStandardSession(
941-
// Capture the output to print it in case of a build error.
942-
// We dont want to associate it with the test run.
943-
new stream.Writable({
944-
write: (chunk, _encoding, next) => {
945-
buildOutput += chunk.toString();
946-
next();
947-
},
948-
}),
949-
await BuildConfigurationFactory.buildAll(
950-
this.folderContext,
951-
true,
952-
isRelease(this.testKind)
953-
),
954-
this.testKind
955-
);
956-
} catch (buildExitCode) {
957-
runState.recordOutput(undefined, buildOutput);
958-
throw new Error(`Build failed with exit code ${buildExitCode}`);
939+
async debugSession(
940+
runState: TestRunnerTestRunState,
941+
performBuild: boolean = true
942+
): Promise<TestRunState> {
943+
if (performBuild) {
944+
// Perform a build all first to produce the binaries we'll run later.
945+
let buildOutput = "";
946+
try {
947+
await this.runStandardSession(
948+
// Capture the output to print it in case of a build error.
949+
// We dont want to associate it with the test run.
950+
new stream.Writable({
951+
write: (chunk, _encoding, next) => {
952+
buildOutput += chunk.toString();
953+
next();
954+
},
955+
}),
956+
await BuildConfigurationFactory.buildAll(
957+
this.folderContext,
958+
true,
959+
isRelease(this.testKind)
960+
),
961+
this.testKind
962+
);
963+
} catch (buildExitCode) {
964+
runState.recordOutput(undefined, buildOutput);
965+
throw new Error(`Build failed with exit code ${buildExitCode}`);
966+
}
959967
}
960968

961969
const testRunTime = Date.now();
@@ -1136,6 +1144,8 @@ export class TestRunner {
11361144
this.workspaceContext.logger
11371145
);
11381146
});
1147+
1148+
return this.testRun.runState;
11391149
}
11401150

11411151
/** Returns a callback that handles a chunk of stdout output from a test run. */

src/commands.ts

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ export enum Commands {
9191
UPDATE_DEPENDENCIES = "swift.updateDependencies",
9292
RUN_TESTS_MULTIPLE_TIMES = "swift.runTestsMultipleTimes",
9393
RUN_TESTS_UNTIL_FAILURE = "swift.runTestsUntilFailure",
94+
DEBUG_TESTS_MULTIPLE_TIMES = "swift.debugTestsMultipleTimes",
95+
DEBUG_TESTS_UNTIL_FAILURE = "swift.debugTestsUntilFailure",
9496
RESET_PACKAGE = "swift.resetPackage",
9597
USE_LOCAL_DEPENDENCY = "swift.useLocalDependency",
9698
UNEDIT_DEPENDENCY = "swift.uneditDependency",
@@ -144,7 +146,13 @@ export function register(ctx: WorkspaceContext): vscode.Disposable[] {
144146
async (...args: (vscode.TestItem | number)[]) => {
145147
const { testItems, count } = extractTestItemsAndCount(...args);
146148
if (ctx.currentFolder) {
147-
return await runTestMultipleTimes(ctx.currentFolder, testItems, false, count);
149+
return await runTestMultipleTimes(
150+
ctx.currentFolder,
151+
testItems,
152+
false,
153+
TestKind.standard,
154+
count
155+
);
148156
}
149157
}
150158
),
@@ -153,7 +161,44 @@ export function register(ctx: WorkspaceContext): vscode.Disposable[] {
153161
async (...args: (vscode.TestItem | number)[]) => {
154162
const { testItems, count } = extractTestItemsAndCount(...args);
155163
if (ctx.currentFolder) {
156-
return await runTestMultipleTimes(ctx.currentFolder, testItems, true, count);
164+
return await runTestMultipleTimes(
165+
ctx.currentFolder,
166+
testItems,
167+
true,
168+
TestKind.standard,
169+
count
170+
);
171+
}
172+
}
173+
),
174+
175+
vscode.commands.registerCommand(
176+
Commands.DEBUG_TESTS_MULTIPLE_TIMES,
177+
async (...args: (vscode.TestItem | number)[]) => {
178+
const { testItems, count } = extractTestItemsAndCount(...args);
179+
if (ctx.currentFolder) {
180+
return await runTestMultipleTimes(
181+
ctx.currentFolder,
182+
testItems,
183+
false,
184+
TestKind.debug,
185+
count
186+
);
187+
}
188+
}
189+
),
190+
vscode.commands.registerCommand(
191+
Commands.DEBUG_TESTS_UNTIL_FAILURE,
192+
async (...args: (vscode.TestItem | number)[]) => {
193+
const { testItems, count } = extractTestItemsAndCount(...args);
194+
if (ctx.currentFolder) {
195+
return await runTestMultipleTimes(
196+
ctx.currentFolder,
197+
testItems,
198+
true,
199+
TestKind.debug,
200+
count
201+
);
157202
}
158203
}
159204
),

src/commands/testMultipleTimes.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@
1313
//===----------------------------------------------------------------------===//
1414

1515
import * as vscode from "vscode";
16-
import { TestKind } from "../TestExplorer/TestKind";
16+
import { isDebugging, TestKind } from "../TestExplorer/TestKind";
1717
import { TestRunner, TestRunnerTestRunState, TestRunState } from "../TestExplorer/TestRunner";
1818
import { FolderContext } from "../FolderContext";
19+
import { colorize } from "../utilities/utilities";
1920

2021
/**
2122
* Runs the supplied TestItem a number of times. The user is prompted with a dialog
@@ -28,6 +29,7 @@ export async function runTestMultipleTimes(
2829
currentFolder: FolderContext,
2930
tests: vscode.TestItem[],
3031
untilFailure: boolean,
32+
kind: TestKind,
3133
count: number | undefined = undefined,
3234
testRunner?: () => Promise<TestRunState>
3335
) {
@@ -50,7 +52,7 @@ export async function runTestMultipleTimes(
5052
const token = new vscode.CancellationTokenSource();
5153
const testExplorer = currentFolder.testExplorer;
5254
const runner = new TestRunner(
53-
TestKind.standard,
55+
kind,
5456
new vscode.TestRunRequest(tests),
5557
currentFolder,
5658
testExplorer.controller,
@@ -66,11 +68,18 @@ export async function runTestMultipleTimes(
6668
const runStates: TestRunState[] = [];
6769
for (let i = 0; i < numExecutions; i++) {
6870
runner.setIteration(i);
69-
runner.testRun.appendOutput(`\x1b[36mBeginning Test Iteration #${i + 1}\x1b[0m\n`);
71+
runner.testRun.appendOutput(
72+
colorize(`Beginning Test Iteration #${i + 1}`, "cyan") + "\n\r"
73+
);
7074

71-
const runState = await (testRunner !== undefined
72-
? testRunner()
73-
: runner.runSession(testRunState));
75+
let runState: TestRunState;
76+
if (testRunner !== undefined) {
77+
runState = await testRunner();
78+
} else if (isDebugging(kind)) {
79+
runState = await runner.debugSession(testRunState, i === 0);
80+
} else {
81+
runState = await runner.runSession(testRunState);
82+
}
7483

7584
runStates.push(runState);
7685

src/utilities/utilities.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,29 @@ export async function execFile(
154154
});
155155
}
156156

157+
enum Color {
158+
red = 31,
159+
green = 32,
160+
yellow = 33,
161+
blue = 34,
162+
magenta = 35,
163+
cyan = 36,
164+
white = 37,
165+
grey = 90,
166+
lightRed = 91,
167+
lightGreen = 92,
168+
lightYellow = 93,
169+
lightBlue = 94,
170+
}
171+
172+
export function colorize(text: string, color: keyof typeof Color): string {
173+
const colorCode = Color[color];
174+
if (colorCode !== undefined) {
175+
return `\x1b[${colorCode}m${text}\x1b[0m`;
176+
}
177+
return text;
178+
}
179+
157180
export async function execFileStreamOutput(
158181
executable: string,
159182
args: string[],

test/integration-tests/commands/runTestMultipleTimes.test.ts

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { runTestMultipleTimes } from "../../../src/commands/testMultipleTimes";
1818
import { FolderContext } from "../../../src/FolderContext";
1919
import { TestRunProxy } from "../../../src/TestExplorer/TestRunner";
2020
import { activateExtensionForSuite, folderInRootWorkspace } from "../utilities/testutilities";
21+
import { TestKind } from "../../../src/TestExplorer/TestKind";
2122

2223
suite("Test Multiple Times Command Test Suite", () => {
2324
let folderContext: FolderContext;
@@ -39,13 +40,24 @@ suite("Test Multiple Times Command Test Suite", () => {
3940
});
4041

4142
test("Runs successfully after testing 0 times", async () => {
42-
const runState = await runTestMultipleTimes(folderContext, [testItem], false, 0);
43+
const runState = await runTestMultipleTimes(
44+
folderContext,
45+
[testItem],
46+
false,
47+
TestKind.standard,
48+
0
49+
);
4350
expect(runState).to.be.an("array").that.is.empty;
4451
});
4552

4653
test("Runs successfully after testing 3 times", async () => {
47-
const runState = await runTestMultipleTimes(folderContext, [testItem], false, 3, () =>
48-
Promise.resolve(TestRunProxy.initialTestRunState())
54+
const runState = await runTestMultipleTimes(
55+
folderContext,
56+
[testItem],
57+
false,
58+
TestKind.standard,
59+
3,
60+
() => Promise.resolve(TestRunProxy.initialTestRunState())
4961
);
5062

5163
expect(runState).to.deep.equal([
@@ -61,13 +73,20 @@ suite("Test Multiple Times Command Test Suite", () => {
6173
failed: [{ test: testItem, message: new vscode.TestMessage("oh no") }],
6274
};
6375
let ctr = 0;
64-
const runState = await runTestMultipleTimes(folderContext, [testItem], true, 3, () => {
65-
ctr += 1;
66-
if (ctr === 2) {
67-
return Promise.resolve(failure);
76+
const runState = await runTestMultipleTimes(
77+
folderContext,
78+
[testItem],
79+
true,
80+
TestKind.standard,
81+
3,
82+
() => {
83+
ctr += 1;
84+
if (ctr === 2) {
85+
return Promise.resolve(failure);
86+
}
87+
return Promise.resolve(TestRunProxy.initialTestRunState());
6888
}
69-
return Promise.resolve(TestRunProxy.initialTestRunState());
70-
});
89+
);
7190

7291
expect(runState).to.deep.equal([TestRunProxy.initialTestRunState(), failure]);
7392
});

0 commit comments

Comments
 (0)