Skip to content

Commit 2519726

Browse files
authored
Add tests for Run Tests Multiple Times (#1041)
1 parent ad99175 commit 2519726

File tree

4 files changed

+148
-33
lines changed

4 files changed

+148
-33
lines changed

src/TestExplorer/TestRunner.ts

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,18 @@ export enum TestLibrary {
4545
swiftTesting = "swift-testing",
4646
}
4747

48+
export interface TestRunState {
49+
failed: {
50+
test: vscode.TestItem;
51+
message: vscode.TestMessage | readonly vscode.TestMessage[];
52+
}[];
53+
passed: vscode.TestItem[];
54+
skipped: vscode.TestItem[];
55+
errored: vscode.TestItem[];
56+
unknown: number;
57+
output: string[];
58+
}
59+
4860
export class TestRunProxy {
4961
private testRun?: vscode.TestRun;
5062
private addedTestItems: { testClass: TestClass; parentIndex: number }[] = [];
@@ -60,17 +72,14 @@ export class TestRunProxy {
6072
// Allows for introspection on the state of TestItems after a test run.
6173
public runState = TestRunProxy.initialTestRunState();
6274

63-
private static initialTestRunState() {
75+
public static initialTestRunState(): TestRunState {
6476
return {
65-
failed: [] as {
66-
test: vscode.TestItem;
67-
message: vscode.TestMessage | readonly vscode.TestMessage[];
68-
}[],
69-
passed: [] as vscode.TestItem[],
70-
skipped: [] as vscode.TestItem[],
71-
errored: [] as vscode.TestItem[],
77+
failed: [],
78+
passed: [],
79+
skipped: [],
80+
errored: [],
7281
unknown: 0,
73-
output: [] as string[],
82+
output: [],
7483
};
7584
}
7685

@@ -464,7 +473,10 @@ export class TestRunner {
464473
}
465474

466475
/** Run test session without attaching to a debugger */
467-
async runSession(token: vscode.CancellationToken, runState: TestRunnerTestRunState) {
476+
async runSession(
477+
token: vscode.CancellationToken,
478+
runState: TestRunnerTestRunState
479+
): Promise<TestRunState> {
468480
// Run swift-testing first, then XCTest.
469481
// swift-testing being parallel by default should help these run faster.
470482
if (this.testArgs.hasSwiftTestingTests) {
@@ -486,13 +498,13 @@ export class TestRunner {
486498
);
487499

488500
if (testBuildConfig === null) {
489-
return;
501+
return this.testRun.runState;
490502
}
491503

492504
const outputStream = this.testOutputWritable(TestLibrary.swiftTesting, runState);
493505
if (token.isCancellationRequested) {
494506
outputStream.end();
495-
return;
507+
return this.testRun.runState;
496508
}
497509

498510
// Watch the pipe for JSONL output and parse the events into test explorer updates.
@@ -518,13 +530,13 @@ export class TestRunner {
518530
true
519531
);
520532
if (testBuildConfig === null) {
521-
return;
533+
return this.testRun.runState;
522534
}
523535

524536
const parsedOutputStream = this.testOutputWritable(TestLibrary.xctest, runState);
525537
if (token.isCancellationRequested) {
526538
parsedOutputStream.end();
527-
return;
539+
return this.testRun.runState;
528540
}
529541

530542
// XCTestRuns are started immediately
@@ -539,6 +551,8 @@ export class TestRunner {
539551
TestLibrary.xctest
540552
);
541553
}
554+
555+
return this.testRun.runState;
542556
}
543557

544558
private async launchTests(

src/commands.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,16 @@ export function register(ctx: WorkspaceContext): vscode.Disposable[] {
7676
vscode.commands.registerCommand("swift.run", () => runBuild(ctx)),
7777
vscode.commands.registerCommand("swift.debug", () => debugBuild(ctx)),
7878
vscode.commands.registerCommand("swift.cleanBuild", () => cleanBuild(ctx)),
79-
vscode.commands.registerCommand("swift.runTestsMultipleTimes", item =>
80-
runTestMultipleTimes(ctx, item, false)
81-
),
82-
vscode.commands.registerCommand("swift.runTestsUntilFailure", item =>
83-
runTestMultipleTimes(ctx, item, true)
84-
),
79+
vscode.commands.registerCommand("swift.runTestsMultipleTimes", item => {
80+
if (ctx.currentFolder) {
81+
runTestMultipleTimes(ctx.currentFolder, item, false);
82+
}
83+
}),
84+
vscode.commands.registerCommand("swift.runTestsUntilFailure", item => {
85+
if (ctx.currentFolder) {
86+
runTestMultipleTimes(ctx.currentFolder, item, true);
87+
}
88+
}),
8589
// Note: This is only available on macOS (gated in `package.json`) because its the only OS that has the iOS SDK available.
8690
vscode.commands.registerCommand("swift.switchPlatform", () => switchPlatform()),
8791
vscode.commands.registerCommand("swift.resetPackage", () => resetPackage(ctx)),

src/commands/testMultipleTimes.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414

1515
import * as vscode from "vscode";
1616
import { TestKind } from "../TestExplorer/TestKind";
17-
import { TestRunner, TestRunnerTestRunState } from "../TestExplorer/TestRunner";
18-
import { WorkspaceContext } from "../WorkspaceContext";
17+
import { TestRunner, TestRunnerTestRunState, TestRunState } from "../TestExplorer/TestRunner";
18+
import { FolderContext } from "../FolderContext";
1919

2020
/**
2121
* Runs the supplied TestItem a number of times. The user is prompted with a dialog
@@ -25,26 +25,27 @@ import { WorkspaceContext } from "../WorkspaceContext";
2525
* @param untilFailure If `true` stop running the test if it fails
2626
*/
2727
export async function runTestMultipleTimes(
28-
ctx: WorkspaceContext,
28+
currentFolder: FolderContext,
2929
test: vscode.TestItem,
30-
untilFailure: boolean
30+
untilFailure: boolean,
31+
testRunner?: () => Promise<TestRunState>
3132
) {
3233
const str = await vscode.window.showInputBox({
3334
prompt: "Label: ",
3435
placeHolder: `${untilFailure ? "Maximum " : ""}# of times to run`,
3536
validateInput: value => (/^[1-9]\d*$/.test(value) ? undefined : "Enter an integer value"),
3637
});
3738

38-
if (!str || !ctx.currentFolder?.testExplorer) {
39+
if (!str || !currentFolder.testExplorer) {
3940
return;
4041
}
4142

4243
const numExecutions = parseInt(str);
43-
const testExplorer = ctx.currentFolder.testExplorer;
44+
const testExplorer = currentFolder.testExplorer;
4445
const runner = new TestRunner(
4546
TestKind.standard,
4647
new vscode.TestRunRequest([test]),
47-
ctx.currentFolder,
48+
currentFolder,
4849
testExplorer.controller
4950
);
5051

@@ -55,19 +56,22 @@ export async function runTestMultipleTimes(
5556

5657
vscode.commands.executeCommand("workbench.panel.testResults.view.focus");
5758

59+
const runStates: TestRunState[] = [];
5860
for (let i = 0; i < numExecutions; i++) {
5961
runner.setIteration(i);
6062
runner.testRun.appendOutput(`\x1b[36mBeginning Test Iteration #${i + 1}\x1b[0m\n`);
6163

62-
await runner.runSession(token.token, testRunState);
64+
const runState = await (testRunner !== undefined
65+
? testRunner()
66+
: runner.runSession(token.token, testRunState));
6367

64-
if (
65-
untilFailure &&
66-
(runner.testRun.runState.failed.length > 0 ||
67-
runner.testRun.runState.errored.length > 0)
68-
) {
68+
runStates.push(runState);
69+
70+
if (untilFailure && (runState.failed.length > 0 || runState.errored.length > 0)) {
6971
break;
7072
}
7173
}
7274
runner.testRun.end();
75+
76+
return runStates;
7377
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the VS Code Swift open source project
4+
//
5+
// Copyright (c) 2024 the VS Code Swift project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of VS Code Swift project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import * as vscode from "vscode";
16+
import * as assert from "assert";
17+
import { anything, when } from "ts-mockito";
18+
import { runTestMultipleTimes } from "../../../src/commands/testMultipleTimes";
19+
import { mockNamespace } from "../../unit-tests/MockUtils";
20+
import { SwiftToolchain } from "../../../src/toolchain/toolchain";
21+
import { WorkspaceContext } from "../../../src/WorkspaceContext";
22+
import { FolderContext } from "../../../src/FolderContext";
23+
import { SwiftOutputChannel } from "../../../src/ui/SwiftOutputChannel";
24+
import { testAssetWorkspaceFolder } from "../../fixtures";
25+
import { TestRunProxy } from "../../../src/TestExplorer/TestRunner";
26+
27+
suite("Test Multiple Times Command Test Suite", () => {
28+
const windowMock = mockNamespace(vscode, "window");
29+
30+
let folderContext: FolderContext;
31+
let testItem: vscode.TestItem;
32+
33+
suiteSetup(async () => {
34+
const toolchain = await SwiftToolchain.create();
35+
const workspaceContext = await WorkspaceContext.create(
36+
new SwiftOutputChannel("Swift"),
37+
toolchain
38+
);
39+
const workspaceFolder = testAssetWorkspaceFolder("diagnostics");
40+
folderContext = await workspaceContext.addPackageFolder(
41+
workspaceFolder.uri,
42+
workspaceFolder
43+
);
44+
folderContext.addTestExplorer();
45+
46+
const item = folderContext.testExplorer?.controller.createTestItem(
47+
"testId",
48+
"Test Item For Testing"
49+
);
50+
51+
assert.ok(item);
52+
testItem = item;
53+
});
54+
55+
test("Runs successfully after testing 0 times", async () => {
56+
when(windowMock.showInputBox(anything())).thenReturn(Promise.resolve("0"));
57+
const runState = await runTestMultipleTimes(folderContext, testItem, false);
58+
assert.deepStrictEqual(runState, []);
59+
});
60+
61+
test("Runs successfully after testing 3 times", async () => {
62+
when(windowMock.showInputBox(anything())).thenReturn(Promise.resolve("3"));
63+
64+
const runState = await runTestMultipleTimes(folderContext, testItem, false, () =>
65+
Promise.resolve(TestRunProxy.initialTestRunState())
66+
);
67+
68+
assert.deepStrictEqual(runState, [
69+
TestRunProxy.initialTestRunState(),
70+
TestRunProxy.initialTestRunState(),
71+
TestRunProxy.initialTestRunState(),
72+
]);
73+
});
74+
75+
test("Stops after a failure on the 2nd iteration ", async () => {
76+
when(windowMock.showInputBox(anything())).thenReturn(Promise.resolve("3"));
77+
78+
const failure = {
79+
...TestRunProxy.initialTestRunState(),
80+
failed: [{ test: testItem, message: new vscode.TestMessage("oh no") }],
81+
};
82+
let ctr = 0;
83+
const runState = await runTestMultipleTimes(folderContext, testItem, true, () => {
84+
ctr += 1;
85+
if (ctr === 2) {
86+
return Promise.resolve(failure);
87+
}
88+
return Promise.resolve(TestRunProxy.initialTestRunState());
89+
});
90+
91+
assert.deepStrictEqual(runState, [TestRunProxy.initialTestRunState(), failure]);
92+
});
93+
});

0 commit comments

Comments
 (0)