Skip to content

Commit d6c91dd

Browse files
authored
Add swift.testBuildArguments Setting (#1020)
* Add swift.testBuildArguments Setting Adds a new setting that allows users to specify extra arguments when running `swift test` and `swift build --build-tests` during a test run from inside VS Code. For example users could set `swift.testBuildArguments: ["-Xswiftc", "-DTEST"]` to enable code guarded by an `#if TEST` compiler directive. This patch required reworking how we do a build all before starting a debug session since using the premade Build All tasks do not allow for dynamically customizing arguments.
1 parent 0c7ee91 commit d6c91dd

File tree

10 files changed

+194
-129
lines changed

10 files changed

+194
-129
lines changed

assets/test/.vscode/settings.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
{
2-
"swift.disableAutoResolve": true
2+
"swift.disableAutoResolve": true,
3+
"swift.additionalTestArguments": [
4+
"-Xswiftc",
5+
"-DTEST_ARGUMENT_SET_VIA_TEST_BUILD_ARGUMENTS_SETTING"
6+
]
37
}

assets/test/defaultPackage/Tests/PackageTests/PackageTests.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import PackageLib
22
import XCTest
33

44
final class PassingXCTestSuite: XCTestCase {
5-
func testPassing() throws {}
5+
func testPassing() throws {
6+
#if !TEST_ARGUMENT_SET_VIA_TEST_BUILD_ARGUMENTS_SETTING
7+
XCTFail("Expected TEST_ARGUMENT_SET_VIA_TEST_BUILD_ARGUMENTS_SETTING to be set at compilation")
8+
#endif
9+
}
610
}
711

812
// Should not run when PassingXCTestSuite is run.
@@ -43,7 +47,11 @@ import Testing
4347

4448
@Test func topLevelTestPassing() {
4549
print("A print statement in a test.")
50+
#if !TEST_ARGUMENT_SET_VIA_TEST_BUILD_ARGUMENTS_SETTING
51+
Issue.record("Expected TEST_ARGUMENT_SET_VIA_TEST_BUILD_ARGUMENTS_SETTING to be set at compilation")
52+
#endif
4653
}
54+
4755
@Test func topLevelTestFailing() {
4856
#expect(1 == 2)
4957
}

package.json

Lines changed: 24 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -226,17 +226,24 @@
226226
"swift.path": {
227227
"type": "string",
228228
"default": "",
229-
"markdownDescription": "Override the default path of the folder containing the Swift executables. The default is to look in the `PATH` environment variable. This path is also used to search for other executables used by the extension like `sourcekit-lsp` and `lldb`.",
230-
"order": 1
229+
"markdownDescription": "Override the default path of the folder containing the Swift executables. The default is to look in the `PATH` environment variable. This path is also used to search for other executables used by the extension like `sourcekit-lsp` and `lldb`."
231230
},
232231
"swift.buildArguments": {
233232
"type": "array",
234233
"default": [],
235234
"items": {
236235
"type": "string"
237236
},
238-
"markdownDescription": "Additional arguments to pass to `swift build`. Keys and values should be provided as individual entries in the list. If you have created a copy of the build task in `tasks.json` then these build arguments will not be propogated to that task.",
239-
"order": 2
237+
"markdownDescription": "Additional arguments to pass to `swift` commands such as `swift build`, `swift package`, `swift test`, etc... Keys and values should be provided as individual entries in the list. If you have created a copy of the build task in `tasks.json` then these build arguments will not be propogated to that task."
238+
},
239+
"swift.additionalTestArguments": {
240+
"type": "array",
241+
"default": [],
242+
"items": {
243+
"type": "string"
244+
},
245+
"markdownDescription": "Additional arguments to pass to the `swift test` or `swift build` commands used when building and running tests from within VS Code.",
246+
"scope": "machine-overridable"
240247
},
241248
"swift.testEnvironmentVariables": {
242249
"type": "object",
@@ -247,8 +254,7 @@
247254
},
248255
"default": {},
249256
"markdownDescription": "Environment variables to set when running tests. To set environment variables when debugging an application you should edit the `env` field in the relevant `launch.json` configuration.",
250-
"scope": "machine-overridable",
251-
"order": 3
257+
"scope": "machine-overridable"
252258
},
253259
"swift.sanitizer": {
254260
"type": "string",
@@ -259,29 +265,25 @@
259265
"address"
260266
],
261267
"markdownDescription": "Runtime [sanitizer instrumentation](https://www.swift.org/documentation/server/guides/llvm-sanitizers.html).",
262-
"scope": "machine-overridable",
263-
"order": 4
268+
"scope": "machine-overridable"
264269
},
265270
"swift.searchSubfoldersForPackages": {
266271
"type": "boolean",
267272
"default": false,
268273
"markdownDescription": "Search sub-folders of workspace folder for Swift Packages at start up.",
269-
"scope": "machine-overridable",
270-
"order": 5
274+
"scope": "machine-overridable"
271275
},
272276
"swift.autoGenerateLaunchConfigurations": {
273277
"type": "boolean",
274278
"default": true,
275279
"markdownDescription": "When loading a `Package.swift`, auto-generate `launch.json` configurations for running any executables.",
276-
"scope": "machine-overridable",
277-
"order": 6
280+
"scope": "machine-overridable"
278281
},
279282
"swift.disableAutoResolve": {
280283
"type": "boolean",
281284
"default": false,
282285
"markdownDescription": "Disable automatic running of `swift package resolve` whenever the `Package.swift` or `Package.resolve` files are updated. This will also disable searching for command plugins and the initial test discovery process.",
283-
"scope": "machine-overridable",
284-
"order": 7
286+
"scope": "machine-overridable"
285287
},
286288
"swift.diagnosticsCollection": {
287289
"type": "string",
@@ -300,8 +302,7 @@
300302
"When merging diagnostics, give precedence to diagnostics from `swiftc`.",
301303
"When merging diagnostics, give precedence to diagnostics from `SourceKit`.",
302304
"Keep diagnostics from all providers."
303-
],
304-
"order": 8
305+
]
305306
},
306307
"swift.diagnosticsStyle": {
307308
"type": "string",
@@ -316,14 +317,12 @@
316317
"Use whichever diagnostics style `swiftc` produces by default.",
317318
"Use the `llvm` diagnostic style. This allows the parsing of \"notes\".",
318319
"Use the `swift` diagnostic style. This means that \"notes\" will not be parsed. This option has no effect in Swift versions prior to 5.10."
319-
],
320-
"order": 9
320+
]
321321
},
322322
"swift.backgroundCompilation": {
323323
"type": "boolean",
324324
"default": false,
325-
"markdownDescription": "**Experimental**: Run `swift build` in the background whenever a file is saved. It is possible the background compilation will already be running when you attempt a compile yourself, so this is disabled by default.",
326-
"order": 10
325+
"markdownDescription": "**Experimental**: Run `swift build` in the background whenever a file is saved. It is possible the background compilation will already be running when you attempt a compile yourself, so this is disabled by default."
327326
},
328327
"swift.actionAfterBuildError": {
329328
"type": "string",
@@ -337,33 +336,28 @@
337336
"Focus on Problems View",
338337
"Focus on Build Task Terminal"
339338
],
340-
"markdownDescription": "Action after a Build task generates errors.",
341-
"order": 11
339+
"markdownDescription": "Action after a Build task generates errors."
342340
},
343341
"swift.buildPath": {
344342
"type": "string",
345343
"default": "",
346-
"markdownDescription": "The path to a directory that will be used for build artifacts. This path will be added to all swift package manager commands that are executed by vscode-swift extension via `--scratch-path` option. When no value provided - nothing gets passed to swift package manager and it will use its default value of `.build` folder in the workspace.\n\nYou can use absolute path for directory or the relative path, which will use the workspace path as a base. Note that VS Code does not respect tildes (`~`) in paths which represents user home folder under *nix systems.",
347-
"order": 12
344+
"markdownDescription": "The path to a directory that will be used for build artifacts. This path will be added to all swift package manager commands that are executed by vscode-swift extension via `--scratch-path` option. When no value provided - nothing gets passed to swift package manager and it will use its default value of `.build` folder in the workspace.\n\nYou can use absolute path for directory or the relative path, which will use the workspace path as a base. Note that VS Code does not respect tildes (`~`) in paths which represents user home folder under *nix systems."
348345
},
349346
"swift.disableSwiftPackageManagerIntegration": {
350347
"type": "boolean",
351348
"default": false,
352-
"markdownDescription": "Disables automated Build Tasks, Package Dependency view, Launch configuration generation and TestExplorer.",
353-
"order": 13
349+
"markdownDescription": "Disables automated Build Tasks, Package Dependency view, Launch configuration generation and TestExplorer."
354350
},
355351
"swift.warnAboutSymlinkCreation": {
356352
"type": "boolean",
357353
"default": true,
358354
"markdownDescription": "Controls whether or not the extension will warn about being unable to create symlinks. (Windows only)",
359-
"scope": "application",
360-
"order": 14
355+
"scope": "application"
361356
},
362357
"swift.enableTerminalEnvironment": {
363358
"type": "boolean",
364359
"default": true,
365-
"markdownDescription": "Controls whether or not the extension will contribute environment variables defined in `Swift: Environment Variables` to the integrated terminal. If this is set to `true` and a custom `Swift: Path` is also set then the swift path is appended to the terminal's `PATH`.",
366-
"order": 15
360+
"markdownDescription": "Controls whether or not the extension will contribute environment variables defined in `Swift: Environment Variables` to the integrated terminal. If this is set to `true` and a custom `Swift: Path` is also set then the swift path is appended to the terminal's `PATH`."
367361
}
368362
}
369363
},

src/TestExplorer/TestRunner.ts

Lines changed: 39 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ 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 { createSwiftTask, getBuildAllTask } from "../tasks/SwiftTaskProvider";
22+
import { createSwiftTask } from "../tasks/SwiftTaskProvider";
2323
import configuration from "../configuration";
2424
import { WorkspaceContext } from "../WorkspaceContext";
2525
import {
@@ -36,8 +36,7 @@ import { TestRunArguments } from "./TestRunArguments";
3636
import { TemporaryFolder } from "../utilities/tempFolder";
3737
import { TestClass, runnableTag, upsertTestItem } from "./TestDiscovery";
3838
import { TestCoverage } from "../coverage/LcovResults";
39-
import { TestingDebugConfigurationFactory } from "../debugger/buildConfig";
40-
import { SwiftExecution } from "../tasks/SwiftExecution";
39+
import { BuildConfigurationFactory, TestingConfigurationFactory } from "../debugger/buildConfig";
4140
import { TestKind, isDebugging, isRelease } from "./TestKind";
4241
import { reduceTestItemChildren } from "./TestUtils";
4342

@@ -202,6 +201,11 @@ export class TestRunProxy {
202201
}
203202

204203
public async end() {
204+
// If the test run never started (typically due to a build error)
205+
// start it to flush any queued output, and then immediately end it.
206+
if (!this.runStarted) {
207+
this.testRunStarted();
208+
}
205209
this.testRun?.end();
206210
this.testRunCompleteEmitter.fire();
207211
}
@@ -473,7 +477,7 @@ export class TestRunner {
473477
await execFile("mkfifo", [fifoPipePath], undefined, this.folderContext);
474478
}
475479

476-
const testBuildConfig = await TestingDebugConfigurationFactory.swiftTestingConfig(
480+
const testBuildConfig = await TestingConfigurationFactory.swiftTestingConfig(
477481
this.folderContext,
478482
fifoPipePath,
479483
this.testKind,
@@ -507,7 +511,7 @@ export class TestRunner {
507511
}
508512

509513
if (this.testArgs.hasXCTests) {
510-
const testBuildConfig = await TestingDebugConfigurationFactory.xcTestConfig(
514+
const testBuildConfig = await TestingConfigurationFactory.xcTestConfig(
511515
this.folderContext,
512516
this.testKind,
513517
this.testArgs.xcTestArgs,
@@ -714,34 +718,31 @@ export class TestRunner {
714718

715719
/** Run test session inside debugger */
716720
async debugSession(token: vscode.CancellationToken, runState: TestRunnerTestRunState) {
717-
const buildAllTask = await getBuildAllTask(this.folderContext, isRelease(this.testKind));
718-
if (!buildAllTask) {
719-
return;
721+
// Perform a build all first to produce the binaries we'll run later.
722+
let buildOutput = "";
723+
try {
724+
await this.runStandardSession(
725+
token,
726+
// discard the output as we dont want to associate it with the test run.
727+
new stream.Writable({
728+
write: (chunk, encoding, next) => {
729+
buildOutput += chunk.toString();
730+
next();
731+
},
732+
}),
733+
BuildConfigurationFactory.buildAll(
734+
this.folderContext,
735+
true,
736+
isRelease(this.testKind)
737+
),
738+
this.testKind
739+
);
740+
} catch (buildExitCode) {
741+
runState.recordOutput(undefined, buildOutput);
742+
throw new Error(`Build failed with exit code ${buildExitCode}`);
720743
}
721744

722745
const subscriptions: vscode.Disposable[] = [];
723-
let buildExitCode = 0;
724-
const buildTask = vscode.tasks.onDidStartTask(e => {
725-
if (e.execution.task.name === buildAllTask.name) {
726-
const exec = e.execution.task.execution as SwiftExecution;
727-
const didCloseBuildTask = exec.onDidClose(exitCode => {
728-
buildExitCode = exitCode ?? 0;
729-
});
730-
subscriptions.push(didCloseBuildTask);
731-
}
732-
});
733-
subscriptions.push(buildTask);
734-
735-
// Perform a build all before the tests are run. We want to avoid the "Debug Anyway" dialog
736-
// since choosing that with no prior build, when debugging swift-testing tests, will cause
737-
// the extension to start listening to the fifo pipe when no results will be produced,
738-
// hanging the test run.
739-
await this.folderContext.taskQueue.queueOperation(new TaskOperation(buildAllTask));
740-
741-
if (buildExitCode !== 0) {
742-
throw new Error(`${buildAllTask.name} failed with exit code ${buildExitCode}`);
743-
}
744-
745746
const buildConfigs: Array<vscode.DebugConfiguration | undefined> = [];
746747
const fifoPipePath = this.generateFifoPipePath();
747748

@@ -753,14 +754,13 @@ export class TestRunner {
753754
await execFile("mkfifo", [fifoPipePath], undefined, this.folderContext);
754755
}
755756

756-
const swiftTestBuildConfig =
757-
await TestingDebugConfigurationFactory.swiftTestingConfig(
758-
this.folderContext,
759-
fifoPipePath,
760-
this.testKind,
761-
this.testArgs.swiftTestArgs,
762-
true
763-
);
757+
const swiftTestBuildConfig = await TestingConfigurationFactory.swiftTestingConfig(
758+
this.folderContext,
759+
fifoPipePath,
760+
this.testKind,
761+
this.testArgs.swiftTestArgs,
762+
true
763+
);
764764

765765
if (swiftTestBuildConfig !== null) {
766766
swiftTestBuildConfig.testType = TestLibrary.swiftTesting;
@@ -788,7 +788,7 @@ export class TestRunner {
788788

789789
// create launch config for testing
790790
if (this.testArgs.hasXCTests) {
791-
const xcTestBuildConfig = await TestingDebugConfigurationFactory.xcTestConfig(
791+
const xcTestBuildConfig = await TestingConfigurationFactory.xcTestConfig(
792792
this.folderContext,
793793
this.testKind,
794794
this.testArgs.xcTestArgs,

src/configuration.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ export interface DebuggerConfiguration {
5757
export interface FolderConfiguration {
5858
/** Environment variables to set when running tests */
5959
readonly testEnvironmentVariables: { [key: string]: string };
60+
/** Extra arguments to set when building tests */
61+
readonly additionalTestArguments: string[];
6062
/** search sub-folder of workspace folder for Swift Packages */
6163
readonly searchSubfoldersForPackages: boolean;
6264
/** auto-generate launch.json configurations */
@@ -119,6 +121,12 @@ const configuration = {
119121
.getConfiguration("swift", workspaceFolder)
120122
.get<{ [key: string]: string }>("testEnvironmentVariables", {});
121123
},
124+
/** Extra arguments to pass to swift test and swift build when running and debugging tests. */
125+
get additionalTestArguments(): string[] {
126+
return vscode.workspace
127+
.getConfiguration("swift", workspaceFolder)
128+
.get<string[]>("additionalTestArguments", []);
129+
},
122130
/** auto-generate launch.json configurations */
123131
get autoGenerateLaunchConfigurations(): boolean {
124132
return vscode.workspace

src/coverage/LcovResults.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { BuildFlags } from "../toolchain/BuildFlags";
2525
import { TestLibrary } from "../TestExplorer/TestRunner";
2626
import { DisposableFileCollection } from "../utilities/tempFolder";
2727
import { TargetType } from "../SwiftPackage";
28-
import { TestingDebugConfigurationFactory } from "../debugger/buildConfig";
28+
import { TestingConfigurationFactory } from "../debugger/buildConfig";
2929
import { TestKind } from "../TestExplorer/TestKind";
3030

3131
interface CodeCovFile {
@@ -144,7 +144,7 @@ export class TestCoverage {
144144
private async exportProfdata(types: TestLibrary[], mergedProfileFile: string): Promise<Buffer> {
145145
const coveredBinaries = new Set<string>();
146146
if (types.includes(TestLibrary.xctest)) {
147-
let xcTestBinary = await TestingDebugConfigurationFactory.testExecutableOutputPath(
147+
let xcTestBinary = await TestingConfigurationFactory.testExecutableOutputPath(
148148
this.folderContext,
149149
TestKind.coverage,
150150
TestLibrary.xctest
@@ -156,7 +156,7 @@ export class TestCoverage {
156156
}
157157

158158
if (types.includes(TestLibrary.swiftTesting)) {
159-
const swiftTestBinary = await TestingDebugConfigurationFactory.testExecutableOutputPath(
159+
const swiftTestBinary = await TestingConfigurationFactory.testExecutableOutputPath(
160160
this.folderContext,
161161
TestKind.coverage,
162162
TestLibrary.swiftTesting

0 commit comments

Comments
 (0)