Skip to content

Commit 8386a9a

Browse files
committed
Run tests in one step
Instead of a build followed by a test run with --skip-build, use a SwiftExecution task to do swift test with the appropriate arguments. This shows the build progress in the status bar.
1 parent 39f0dbb commit 8386a9a

File tree

4 files changed

+96
-171
lines changed

4 files changed

+96
-171
lines changed

src/TestExplorer/TestRunner.ts

Lines changed: 55 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,11 @@ import * as os from "os";
1919
import * as asyncfs from "fs/promises";
2020
import { FolderContext } from "../FolderContext";
2121
import { execFile, getErrorDescription } from "../utilities/utilities";
22-
import { getBuildAllTask } from "../tasks/SwiftTaskProvider";
22+
import { createSwiftTask } from "../tasks/SwiftTaskProvider";
2323
import configuration from "../configuration";
2424
import { WorkspaceContext } from "../WorkspaceContext";
2525
import { XCTestOutputParser } from "./TestParsers/XCTestOutputParser";
2626
import { SwiftTestingOutputParser } from "./TestParsers/SwiftTestingOutputParser";
27-
import { Version } from "../utilities/version";
2827
import { LoggingDebugAdapterTracker } from "../debugger/logTracker";
2928
import { TaskOperation } from "../tasks/TaskQueue";
3029
import { TestXUnitParser, iXUnitTestState } from "./TestXUnitParser";
@@ -34,16 +33,15 @@ import { TemporaryFolder } from "../utilities/tempFolder";
3433
import { TestClass, runnableTag, upsertTestItem } from "./TestDiscovery";
3534
import { TestCoverage } from "../coverage/LcovResults";
3635
import { DebugConfigurationFactory } from "../debugger/buildConfig";
37-
import { SwiftPtyProcess } from "../tasks/SwiftProcess";
3836

3937
/** Workspace Folder events */
4038
export enum TestKind {
4139
// run tests serially
42-
standard = "standard",
40+
standard = "Standard",
4341
// run tests in parallel
44-
parallel = "parallel",
42+
parallel = "Parallel",
4543
// run tests and extract test coverage
46-
coverage = "coverage",
44+
coverage = "Coverage",
4745
}
4846

4947
export enum TestLibrary {
@@ -309,28 +307,6 @@ export class TestRunner {
309307
async runHandler(shouldDebug: boolean, testKind: TestKind, token: vscode.CancellationToken) {
310308
const runState = new TestRunnerTestRunState(this.testRun);
311309
try {
312-
// run associated build task
313-
// don't do this if generating code test coverage data as the
314-
// `swift test --enable-code-coverage` command will rebuild everything again.
315-
if (testKind !== TestKind.coverage) {
316-
const task = await getBuildAllTask(this.folderContext);
317-
task.definition.dontTriggerTestDiscovery =
318-
this.folderContext.workspaceContext.swiftVersion.isGreaterThanOrEqual(
319-
new Version(6, 0, 0)
320-
);
321-
322-
const exitCode = await this.folderContext.taskQueue.queueOperation(
323-
new TaskOperation(task),
324-
token
325-
);
326-
327-
// if build failed then exit
328-
if (exitCode === undefined || exitCode !== 0) {
329-
await this.testRun.end();
330-
return;
331-
}
332-
}
333-
334310
if (shouldDebug) {
335311
await this.debugSession(token, runState);
336312
} else {
@@ -449,7 +425,6 @@ export class TestRunner {
449425
testBuildConfig: vscode.DebugConfiguration,
450426
testLibrary: TestLibrary
451427
) {
452-
this.testRun.appendOutput(`> Test run started at ${new Date().toLocaleString()} <\r\n\r\n`);
453428
try {
454429
switch (testKind) {
455430
case TestKind.coverage:
@@ -464,7 +439,7 @@ export class TestRunner {
464439
await this.runParallelSession(token, outputStream, testBuildConfig);
465440
break;
466441
default:
467-
await this.runStandardSession(token, outputStream, testBuildConfig);
442+
await this.runStandardSession(token, outputStream, testBuildConfig, testKind);
468443
break;
469444
}
470445
} catch (error) {
@@ -481,35 +456,53 @@ export class TestRunner {
481456
async runStandardSession(
482457
token: vscode.CancellationToken,
483458
outputStream: stream.Writable,
484-
testBuildConfig: vscode.DebugConfiguration
459+
testBuildConfig: vscode.DebugConfiguration,
460+
testKind: TestKind
485461
) {
486462
return new Promise<void>((resolve, reject) => {
487463
const args = testBuildConfig.args ?? [];
488-
let didError = false;
489-
let cancellation: vscode.Disposable;
464+
this.folderContext?.workspaceContext.outputChannel.logDiagnostic(
465+
`Exec: ${testBuildConfig.program} ${args.join(" ")}`,
466+
this.folderContext.name
467+
);
490468

491-
const exec = new SwiftPtyProcess(testBuildConfig.program, args, {
492-
cwd: testBuildConfig.cwd,
493-
env: { ...process.env, ...testBuildConfig.env },
494-
});
469+
let kindLabel: string;
470+
switch (testKind) {
471+
case TestKind.coverage:
472+
kindLabel = " With Code Coverage";
473+
break;
474+
case TestKind.parallel:
475+
kindLabel = " In Parallel";
476+
break;
477+
case TestKind.standard:
478+
kindLabel = "";
479+
}
495480

496-
exec.onDidWrite(str => {
497-
// Work around SPM still emitting progress when doing --no-build.
498-
const replaced = str.replace("[1/1] Planning build", "");
499-
outputStream.write(replaced);
500-
});
481+
const task = createSwiftTask(
482+
args,
483+
`Building and Running Tests${kindLabel}`,
484+
{
485+
cwd: this.folderContext.folder,
486+
scope: this.folderContext.workspaceFolder,
487+
prefix: this.folderContext.name,
488+
presentationOptions: { reveal: vscode.TaskRevealKind.Silent },
489+
},
490+
this.folderContext.workspaceContext.toolchain,
491+
{ ...process.env, ...testBuildConfig.env }
492+
);
501493

502-
exec.onDidThrowError(err => {
503-
didError = true;
504-
reject(err);
494+
task.execution.onDidWrite(str => {
495+
const replaced = str
496+
.replace("[1/1] Planning build", "") // Work around SPM still emitting progress when doing --no-build.
497+
.replace(
498+
/LLVM Profile Error: Failed to write file "default.profraw": Operation not permitted\r\n/gm,
499+
""
500+
); // Work around benign LLVM coverage warnings
501+
outputStream.write(replaced);
505502
});
506503

507-
exec.onDidClose(code => {
508-
// onDidClose is still called after an error
509-
if (didError) {
510-
return;
511-
}
512-
504+
let cancellation: vscode.Disposable;
505+
task.execution.onDidClose(code => {
513506
if (cancellation) {
514507
cancellation.dispose();
515508
}
@@ -522,17 +515,7 @@ export class TestRunner {
522515
}
523516
});
524517

525-
if (token) {
526-
cancellation = token.onCancellationRequested(() => {
527-
exec.kill();
528-
});
529-
}
530-
531-
this.folderContext?.workspaceContext.outputChannel.logDiagnostic(
532-
`Exec: ${testBuildConfig.program} ${args.join(" ")}`,
533-
this.folderContext.name
534-
);
535-
exec.spawn();
518+
this.folderContext.taskQueue.queueOperation(new TaskOperation(task), token);
536519
});
537520
}
538521

@@ -544,7 +527,7 @@ export class TestRunner {
544527
testLibrary: TestLibrary
545528
) {
546529
try {
547-
await this.runStandardSession(token, outputStream, testBuildConfig);
530+
await this.runStandardSession(token, outputStream, testBuildConfig, TestKind.coverage);
548531
} catch (error) {
549532
// If this isn't a standard test failure, forward the error and skip generating coverage.
550533
if (error !== 1) {
@@ -578,10 +561,15 @@ export class TestRunner {
578561
this.testRun.testRunStarted();
579562

580563
try {
581-
testBuildConfig.args = await this.runStandardSession(token, outputStream, {
582-
...testBuildConfig,
583-
args: [...args, filterArgs],
584-
});
564+
testBuildConfig.args = await this.runStandardSession(
565+
token,
566+
outputStream,
567+
{
568+
...testBuildConfig,
569+
args: [...args, filterArgs],
570+
},
571+
TestKind.parallel
572+
);
585573
} catch (error) {
586574
// If this isn't a standard test failure, forward the error and skip generating coverage.
587575
if (error !== 1) {
@@ -727,11 +715,6 @@ export class TestRunner {
727715
.then(
728716
started => {
729717
if (started) {
730-
if (config === validBuildConfigs[0]) {
731-
this.testRun.appendOutput(
732-
`> Test run started at ${new Date().toLocaleString()} <\r\n\r\n`
733-
);
734-
}
735718
// show test results pane
736719
vscode.commands.executeCommand(
737720
"testing.showMostRecentOutput"

src/coverage/LcovResults.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,6 @@ export class TestCoverage {
112112

113113
private async computeLCOVCoverage(): Promise<lcov.LcovFile[]> {
114114
if (this.lcovFiles.length === 0) {
115-
this.folderContext.workspaceContext.outputChannel.logDiagnostic(
116-
`Attempted to compute code coverage with no coverage files captured.`,
117-
this.folderContext.name
118-
);
119115
return [];
120116
}
121117

src/debugger/buildConfig.ts

Lines changed: 38 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,10 @@ export class DebugConfigurationFactory {
8686
}
8787

8888
switch (process.platform) {
89-
case "darwin":
90-
return this.buidDarwinConfig();
9189
case "win32":
9290
return this.buildWindowsConfig();
9391
default:
94-
return this.buildLinuxConfig();
92+
return this.buidDarwinConfig();
9593
}
9694
}
9795

@@ -130,54 +128,44 @@ export class DebugConfigurationFactory {
130128
: this.baseConfig.preLaunchTask,
131129
};
132130
case "XCTest":
133-
switch (this.testKind) {
134-
// If we want code coverage on XCTests we have to run with `swift test --enable-code-coverage`.
135-
case TestKind.coverage:
136-
let args = ["test", "--enable-code-coverage"];
137-
if (this.swiftVersionGreaterOrEqual(6, 0, 0)) {
138-
args = [
139-
...args,
140-
"--enable-xctest",
141-
"--disable-experimental-swift-testing",
142-
];
143-
}
144-
145-
return {
146-
...this.baseConfig,
147-
program: this.swiftProgramPath,
148-
args: this.addTestsToArgs(args),
149-
env: {
150-
...this.testEnv,
151-
...this.sanitizerRuntimeEnvironment,
152-
},
153-
};
154-
default:
155-
const swiftVersion = this.ctx.workspaceContext.toolchain.swiftVersion;
156-
if (
157-
swiftVersion.isLessThan(new Version(5, 7, 0)) &&
158-
swiftVersion.isGreaterThanOrEqual(new Version(5, 6, 0))
159-
) {
160-
// if debugging on macOS with Swift 5.6 we need to create a custom launch
161-
// configuration so we can set the system architecture
162-
return this.createDarwin56TestConfiguration();
163-
}
164-
165-
// If we don't need code coverage we can just run the built <Package>PackageTests.xctest binary with xctest.
166-
const xcTestBinary = this.xcTestBinaryPath;
167-
if (!xcTestBinary) {
168-
return null;
169-
}
131+
const swiftVersion = this.ctx.workspaceContext.toolchain.swiftVersion;
132+
if (
133+
swiftVersion.isLessThan(new Version(5, 7, 0)) &&
134+
swiftVersion.isGreaterThanOrEqual(new Version(5, 6, 0))
135+
) {
136+
// if debugging on macOS with Swift 5.6 we need to create a custom launch
137+
// configuration so we can set the system architecture
138+
return this.createDarwin56TestConfiguration();
139+
}
170140

171-
return {
172-
...this.baseConfig,
173-
program: this.xcTestBinaryPath,
174-
args:
175-
this.testList.length > 0
176-
? ["-XCTest", this.testList.join(","), this.xcTestOutputPath]
177-
: [this.xcTestOutputPath],
178-
env: this.testEnv,
179-
};
141+
let xcTestArgs = [
142+
"test",
143+
...(this.testKind === TestKind.coverage ? ["--enable-code-coverage"] : []),
144+
];
145+
if (this.swiftVersionGreaterOrEqual(6, 0, 0)) {
146+
xcTestArgs = [
147+
...xcTestArgs,
148+
"--enable-xctest",
149+
"--disable-experimental-swift-testing",
150+
];
180151
}
152+
153+
return {
154+
...this.baseConfig,
155+
program: this.swiftProgramPath,
156+
args: this.addTestsToArgs(xcTestArgs),
157+
env: {
158+
...this.testEnv,
159+
...this.sanitizerRuntimeEnvironment,
160+
SWT_SF_SYMBOLS_ENABLED: "0",
161+
},
162+
// For coverage we need to rebuild so do the build/test all in one step,
163+
// otherwise we do a build, then test, to give better progress.
164+
preLaunchTask:
165+
this.testKind === TestKind.coverage
166+
? undefined
167+
: this.baseConfig.preLaunchTask,
168+
};
181169
default:
182170
return null;
183171
}
@@ -191,7 +179,7 @@ export class DebugConfigurationFactory {
191179
case TestLibrary.xctest:
192180
switch (this.testKind) {
193181
case TestKind.coverage:
194-
return this.buildLinuxConfig();
182+
return this.buidDarwinConfig();
195183
default:
196184
const xcTestPath = this.ctx.workspaceContext.toolchain.xcTestPath;
197185
const runtimePath = this.ctx.workspaceContext.toolchain.runtimePath;
@@ -229,43 +217,6 @@ export class DebugConfigurationFactory {
229217
}
230218
}
231219
}
232-
233-
private buildLinuxConfig(): vscode.DebugConfiguration | null {
234-
switch (this.testLibrary) {
235-
case TestLibrary.swiftTesting:
236-
return this.buidDarwinConfig();
237-
case TestLibrary.xctest:
238-
switch (this.testKind) {
239-
case TestKind.coverage:
240-
let args = ["test", "--enable-code-coverage"];
241-
if (this.swiftVersionGreaterOrEqual(6, 0, 0)) {
242-
args = [
243-
...args,
244-
"--enable-xctest",
245-
"--disable-experimental-swift-testing",
246-
];
247-
}
248-
249-
return {
250-
...this.baseConfig,
251-
program: this.swiftProgramPath,
252-
args: this.addTestsToArgs(args),
253-
env: this.testEnv,
254-
preLaunchTask:
255-
this.testKind === TestKind.coverage
256-
? undefined
257-
: this.baseConfig.preLaunchTask,
258-
};
259-
default:
260-
return {
261-
...this.baseConfig,
262-
program: this.xcTestOutputPath,
263-
args: [this.testList.join(",")],
264-
env: this.testEnv,
265-
};
266-
}
267-
}
268-
}
269220
/* eslint-enable no-case-declarations */
270221

271222
/**
@@ -339,12 +290,6 @@ export class DebugConfigurationFactory {
339290

340291
private get swiftProgramPath(): string {
341292
return this.ctx.workspaceContext.toolchain.getToolchainExecutable("swift");
342-
// return path.join(this.ctx.workspaceContext.toolchain.swiftFolderPath, "swift");
343-
}
344-
345-
private get xcTestBinaryPath(): string | undefined {
346-
const xcTestPath = this.ctx.workspaceContext.toolchain.xcTestPath;
347-
return xcTestPath ? path.join(xcTestPath, "xctest") : undefined;
348293
}
349294

350295
private get buildDirectory(): string {

0 commit comments

Comments
 (0)