From ff19a0035e359dcb5dc780f1cbcdeeafe6c2224e Mon Sep 17 00:00:00 2001 From: Adam Ward Date: Thu, 9 Jan 2025 14:55:57 -0500 Subject: [PATCH] Allow disabling SwiftPM sandboxing To get our tests to run in a sandbox, we need to disable sandboxing because you cannot create a new sandbox when you're already running under a sandbox - Add new `swift.disableSandbox` setting - Disable sandboxing for tasks and commands run by the extension - Disable hardware acceleration since ci.swift.org will run on x64 - Fix failing xcode watcher unit test - Increase some timeouts as build times seem slower on these nodes - Skip any LSP dependent tests for 6.0 or earlier. The LSP will only allow disabling sandboxing in 6.1+ - Disable debugging tests since need shareport permission --- .gitignore | 1 + .prettierignore | 3 + .vscode-test.js | 31 +++--- assets/test/.vscode/tasks.json | 1 + package.json | 7 +- src/commands/dependencies/unedit.ts | 7 +- src/commands/dependencies/useLocal.ts | 8 +- src/commands/resetPackage.ts | 5 +- src/configuration.ts | 11 ++ src/debugger/buildConfig.ts | 2 +- src/extension.ts | 4 +- src/tasks/SwiftPluginTaskProvider.ts | 4 +- src/tasks/SwiftTaskProvider.ts | 5 +- src/toolchain/BuildFlags.ts | 40 ++++++- src/utilities/utilities.ts | 8 +- .../BackgroundCompilation.test.ts | 2 +- .../DiagnosticsManager.test.ts | 6 +- test/integration-tests/SwiftSnippet.test.ts | 3 +- test/integration-tests/commands/build.test.ts | 3 +- test/integration-tests/debugger/lldb.test.ts | 1 + test/integration-tests/extension.test.ts | 15 +-- .../LanguageClientIntegration.test.ts | 2 +- .../tasks/SwiftPluginTaskProvider.test.ts | 51 +++++---- .../TestExplorerIntegration.test.ts | 3 + .../ui/PackageDependencyProvider.test.ts | 17 ++- .../utilities/testutilities.ts | 30 +++++- .../LanguageClientManager.test.ts | 2 +- .../tasks/SwiftPluginTaskProvider.test.ts | 4 +- .../tasks/SwiftTaskProvider.test.ts | 8 +- test/unit-tests/toolchain/BuildFlags.test.ts | 100 +++++++++++++++--- .../toolchain/SelectedXcodeWatcher.test.ts | 21 +++- 31 files changed, 309 insertions(+), 96 deletions(-) diff --git a/.gitignore b/.gitignore index a09ca1cb4..da0219e89 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ default.profraw assets/documentation-webview assets/test/**/Package.resolved assets/swift-docc-render +ud diff --git a/.prettierignore b/.prettierignore index 42ce3550c..a7aad951d 100644 --- a/.prettierignore +++ b/.prettierignore @@ -18,3 +18,6 @@ node_modules/ /coverage/ /dist/ /snippets/ + +# macOS CI +/ud/ \ No newline at end of file diff --git a/.vscode-test.js b/.vscode-test.js index 5fa7ce6ba..e0302dd36 100644 --- a/.vscode-test.js +++ b/.vscode-test.js @@ -18,12 +18,28 @@ const path = require("path"); const isCIBuild = process.env["CI"] === "1"; const isFastTestRun = process.env["FAST_TEST_RUN"] === "1"; +const dataDir = process.env["VSCODE_DATA_DIR"]; + // "env" in launch.json doesn't seem to work with vscode-test const isDebugRun = !(process.env["_"] ?? "").endsWith("node_modules/.bin/vscode-test"); // so tests don't timeout when a breakpoint is hit const timeout = isDebugRun ? Number.MAX_SAFE_INTEGER : 3000; +const launchArgs = [ + "--disable-updates", + "--disable-crash-reporter", + "--disable-workspace-trust", + "--disable-telemetry", +]; +if (dataDir) { + launchArgs.push("--user-data-dir", dataDir); +} +// GPU hardware acceleration not working on Darwin for intel +if (process.platform === "darwin" && process.arch === "x64") { + launchArgs.push("--disable-gpu"); +} + module.exports = defineConfig({ tests: [ { @@ -31,12 +47,7 @@ module.exports = defineConfig({ files: ["dist/test/common.js", "dist/test/integration-tests/**/*.test.js"], version: process.env["VSCODE_VERSION"] ?? "stable", workspaceFolder: "./assets/test", - launchArgs: [ - "--disable-updates", - "--disable-crash-reporter", - "--disable-workspace-trust", - "--disable-telemetry", - ], + launchArgs, mocha: { ui: "tdd", color: true, @@ -59,13 +70,7 @@ module.exports = defineConfig({ label: "unitTests", files: ["dist/test/common.js", "dist/test/unit-tests/**/*.test.js"], version: process.env["VSCODE_VERSION"] ?? "stable", - launchArgs: [ - "--disable-extensions", - "--disable-updates", - "--disable-crash-reporter", - "--disable-workspace-trust", - "--disable-telemetry", - ], + launchArgs: launchArgs.concat("--disable-extensions"), mocha: { ui: "tdd", color: true, diff --git a/assets/test/.vscode/tasks.json b/assets/test/.vscode/tasks.json index d91e6ff96..406dec54f 100644 --- a/assets/test/.vscode/tasks.json +++ b/assets/test/.vscode/tasks.json @@ -35,6 +35,7 @@ "command": "command_plugin", "args": ["--foo"], "cwd": "command-plugin", + "disableSandbox": true, "problemMatcher": [ "$swiftc" ], diff --git a/package.json b/package.json index e61380dca..91477c8e5 100644 --- a/package.json +++ b/package.json @@ -719,7 +719,12 @@ "swift.swiftSDK": { "type": "string", "default": "", - "markdownDescription": "The [Swift SDK](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0387-cross-compilation-destinations.md) to compile against (`--swift-sdk` parameter).", + "markdownDescription": "The [Swift SDK](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0387-cross-compilation-destinations.md) to compile against (`--swift-sdk` parameter)." + }, + "swift.disableSandox": { + "type": "boolean", + "default": false, + "markdownDescription": "Disable sandboxing when running SwiftPM commands. In most cases you should keep the sandbox enabled and leave this setting set to `false`", "order": 4 }, "swift.diagnostics": { diff --git a/src/commands/dependencies/unedit.ts b/src/commands/dependencies/unedit.ts index 521d71afd..720709edd 100644 --- a/src/commands/dependencies/unedit.ts +++ b/src/commands/dependencies/unedit.ts @@ -44,7 +44,12 @@ async function uneditFolderDependency( ) { try { const uneditOperation = new SwiftExecOperation( - ["package", "unedit", ...args, identifier], + ctx.toolchain.buildFlags.withAdditionalFlags([ + "package", + "unedit", + ...args, + identifier, + ]), folder, `Finish editing ${identifier}`, { showStatusItem: true, checkAlreadyRunning: false, log: "Unedit" }, diff --git a/src/commands/dependencies/useLocal.ts b/src/commands/dependencies/useLocal.ts index e108cee71..c75a3c6c2 100644 --- a/src/commands/dependencies/useLocal.ts +++ b/src/commands/dependencies/useLocal.ts @@ -50,7 +50,13 @@ export async function useLocalDependency( folder = folders[0]; } const task = createSwiftTask( - ["package", "edit", "--path", folder.fsPath, identifier], + ctx.toolchain.buildFlags.withAdditionalFlags([ + "package", + "edit", + "--path", + folder.fsPath, + identifier, + ]), "Edit Package Dependency", { scope: currentFolder.workspaceFolder, diff --git a/src/commands/resetPackage.ts b/src/commands/resetPackage.ts index 6d621dee2..84c0c9141 100644 --- a/src/commands/resetPackage.ts +++ b/src/commands/resetPackage.ts @@ -35,7 +35,10 @@ export async function resetPackage(ctx: WorkspaceContext) { */ export async function folderResetPackage(folderContext: FolderContext) { const task = createSwiftTask( - ["package", "reset"], + folderContext.workspaceContext.toolchain.buildFlags.withAdditionalFlags([ + "package", + "reset", + ]), "Reset Package Dependencies", { cwd: folderContext.folder, diff --git a/src/configuration.ts b/src/configuration.ts index 45bbba2f9..5f7abdf29 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -52,6 +52,8 @@ export interface DebuggerConfiguration { readonly debugAdapter: DebugAdapters; /** Return path to debug adapter */ readonly customDebugAdapterPath: string; + /** Whether or not to disable setting up the debugger */ + readonly disable: boolean; } /** workspace folder configuration */ @@ -212,6 +214,11 @@ const configuration = { get customDebugAdapterPath(): string { return vscode.workspace.getConfiguration("swift.debugger").get("path", ""); }, + get disable(): boolean { + return vscode.workspace + .getConfiguration("swift.debugger") + .get("disable", false); + }, }; }, /** Files and directories to exclude from the code coverage. */ @@ -375,6 +382,10 @@ const configuration = { .getConfiguration("swift") .get("enableTerminalEnvironment", true); }, + /** Whether or not to disable SwiftPM sandboxing */ + get disableSandbox(): boolean { + return vscode.workspace.getConfiguration("swift").get("disableSandbox", false); + }, }; export default configuration; diff --git a/src/debugger/buildConfig.ts b/src/debugger/buildConfig.ts index 956d2a356..8722336e0 100644 --- a/src/debugger/buildConfig.ts +++ b/src/debugger/buildConfig.ts @@ -535,7 +535,7 @@ export class TestingConfigurationFactory { } const swiftTestingArgs = [ - ...args, + ...this.ctx.workspaceContext.toolchain.buildFlags.withAdditionalFlags(args), "--enable-swift-testing", "--event-stream-version", "0", diff --git a/src/extension.ts b/src/extension.ts index 612ea9e3b..b87f950fc 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -128,7 +128,9 @@ export async function activate(context: vscode.ExtensionContext): Promise { const workspaceContext = await WorkspaceContext.create(context, outputChannel, toolchain); context.subscriptions.push(...commands.register(workspaceContext)); context.subscriptions.push(workspaceContext); - context.subscriptions.push(registerDebugger(workspaceContext)); + if (!configuration.debugger.disable) { + context.subscriptions.push(registerDebugger(workspaceContext)); + } context.subscriptions.push(new SelectedXcodeWatcher(outputChannel)); // listen for workspace folder changes and active text editor changes diff --git a/src/tasks/SwiftPluginTaskProvider.ts b/src/tasks/SwiftPluginTaskProvider.ts index f0339b2de..1de21d238 100644 --- a/src/tasks/SwiftPluginTaskProvider.ts +++ b/src/tasks/SwiftPluginTaskProvider.ts @@ -82,7 +82,7 @@ export class SwiftPluginTaskProvider implements vscode.TaskProvider { task.definition.command, ...task.definition.args, ]; - swiftArgs = this.workspaceContext.toolchain.buildFlags.withSwiftSDKFlags(swiftArgs); + swiftArgs = this.workspaceContext.toolchain.buildFlags.withAdditionalFlags(swiftArgs); const cwd = resolveTaskCwd(task, task.definition.cwd); const newTask = new vscode.Task( @@ -122,7 +122,7 @@ export class SwiftPluginTaskProvider implements vscode.TaskProvider { plugin.command, ...definition.args, ]; - swiftArgs = this.workspaceContext.toolchain.buildFlags.withSwiftSDKFlags(swiftArgs); + swiftArgs = this.workspaceContext.toolchain.buildFlags.withAdditionalFlags(swiftArgs); const presentation = config?.presentationOptions ?? {}; const task = new vscode.Task( diff --git a/src/tasks/SwiftTaskProvider.ts b/src/tasks/SwiftTaskProvider.ts index fbf5ba946..77bd93689 100644 --- a/src/tasks/SwiftTaskProvider.ts +++ b/src/tasks/SwiftTaskProvider.ts @@ -271,7 +271,7 @@ export function createSwiftTask( cmdEnv: { [key: string]: string } = {} ): SwiftTask { const swift = toolchain.getToolchainExecutable("swift"); - args = toolchain.buildFlags.withSwiftPackageFlags(toolchain.buildFlags.withSwiftSDKFlags(args)); + args = toolchain.buildFlags.withAdditionalFlags(args); // Add relative path current working directory const cwd = config.cwd.fsPath; @@ -422,7 +422,8 @@ export class SwiftTaskProvider implements vscode.TaskProvider { resolveTask(task: vscode.Task, token: vscode.CancellationToken): vscode.Task { // We need to create a new Task object here. // Reusing the task parameter doesn't seem to work. - const swift = this.workspaceContext.toolchain.getToolchainExecutable("swift"); + const toolchain = this.workspaceContext.toolchain; + const swift = toolchain.getToolchainExecutable("swift"); // platform specific let platform: TaskPlatformSpecificConfig | undefined; if (process.platform === "win32") { diff --git a/src/toolchain/BuildFlags.ts b/src/toolchain/BuildFlags.ts index 95f376dab..90810439d 100644 --- a/src/toolchain/BuildFlags.ts +++ b/src/toolchain/BuildFlags.ts @@ -37,7 +37,7 @@ export class BuildFlags { * * @param args original commandline arguments */ - withSwiftSDKFlags(args: string[]): string[] { + private withSwiftSDKFlags(args: string[]): string[] { switch (args[0]) { case "package": { const subcommand = args.splice(0, 2).concat(this.buildPathFlags()); @@ -192,6 +192,44 @@ export class BuildFlags { return indirect ? args.flatMap(arg => ["-Xswiftc", arg]) : args; } + /** + * Get modified swift arguments with new arguments for disabling + * sandboxing if the `swift.disableSandbox` setting is enabled. + * + * @param args original commandline arguments + */ + private withDisableSandboxFlags(args: string[]): string[] { + if (!configuration.disableSandbox) { + return args; + } + switch (args[0]) { + case "package": { + return [args[0], ...BuildFlags.disableSandboxFlags(), ...args.slice(1)]; + } + case "build": + case "run": + case "test": { + return [...args, ...BuildFlags.disableSandboxFlags()]; + } + default: + // Do nothing for other commands + return args; + } + } + + withAdditionalFlags(args: string[]): string[] { + return this.withSwiftPackageFlags( + this.withDisableSandboxFlags(this.withSwiftSDKFlags(args)) + ); + } + + /** + * Get flags for disabling sandboxing when running SwiftPM + */ + static disableSandboxFlags(): string[] { + return ["--disable-sandbox", "-Xswiftc", "-disable-sandbox"]; + } + /** * Filter argument list * @param args argument list diff --git a/src/utilities/utilities.ts b/src/utilities/utilities.ts index 31122bea1..030c91fa3 100644 --- a/src/utilities/utilities.ts +++ b/src/utilities/utilities.ts @@ -109,14 +109,14 @@ export async function execFile( options.env = { ...(options.env ?? process.env), ...runtimeEnv }; } } - return new Promise<{ stdout: string; stderr: string }>((resolve, reject) => + return new Promise<{ stdout: string; stderr: string }>((resolve, reject) => { cp.execFile(executable, args, options, (error, stdout, stderr) => { if (error) { reject(new ExecFileError(error, stdout, stderr)); } resolve({ stdout, stderr }); - }) - ); + }); + }); } export async function execFileStreamOutput( @@ -187,7 +187,7 @@ export async function execSwift( swift = toolchain.getToolchainExecutable("swift"); } if (toolchain !== "default") { - args = toolchain.buildFlags.withSwiftSDKFlags(args); + args = toolchain.buildFlags.withAdditionalFlags(args); } if (Object.keys(configuration.swiftEnvironmentVariables).length > 0) { // when adding environment vars we either combine with vars passed diff --git a/test/integration-tests/BackgroundCompilation.test.ts b/test/integration-tests/BackgroundCompilation.test.ts index 90c8660a4..13df067aa 100644 --- a/test/integration-tests/BackgroundCompilation.test.ts +++ b/test/integration-tests/BackgroundCompilation.test.ts @@ -58,5 +58,5 @@ suite("BackgroundCompilation Test Suite", () => { await vscode.workspace.save(uri); await taskPromise; - }).timeout(120000); + }).timeout(180000); }); diff --git a/test/integration-tests/DiagnosticsManager.test.ts b/test/integration-tests/DiagnosticsManager.test.ts index 5855a5d99..b0a783090 100644 --- a/test/integration-tests/DiagnosticsManager.test.ts +++ b/test/integration-tests/DiagnosticsManager.test.ts @@ -216,7 +216,7 @@ suite("DiagnosticsManager Test Suite", async function () { // after first build and can cause intermittent // failure if `swiftc` diagnostic is fixed suiteSetup(async function () { - this.timeout(2 * 60 * 1000); // Allow 2 minutes to build + this.timeout(3 * 60 * 1000); // Allow 3 minutes to build const task = createBuildAllTask(folderContext); // This return exit code and output for the task but we will omit it here // because the failures are expected and we just want the task to build @@ -1062,7 +1062,7 @@ suite("DiagnosticsManager Test Suite", async function () { assertHasDiagnostic(mainUri, expectedDiagnostic1); assertHasDiagnostic(mainUri, expectedDiagnostic2); - }).timeout(2 * 60 * 1000); // Allow 2 minutes to build + }).timeout(3 * 60 * 1000); // Allow 3 minutes to build test("Provides clang diagnostics", async () => { // Build for indexing @@ -1099,6 +1099,6 @@ suite("DiagnosticsManager Test Suite", async function () { assertHasDiagnostic(cUri, expectedDiagnostic1); assertHasDiagnostic(cUri, expectedDiagnostic2); - }).timeout(2 * 60 * 1000); // Allow 2 minutes to build + }).timeout(3 * 60 * 1000); // Allow 3 minutes to build }); }); diff --git a/test/integration-tests/SwiftSnippet.test.ts b/test/integration-tests/SwiftSnippet.test.ts index 72c594b7d..82b0342f4 100644 --- a/test/integration-tests/SwiftSnippet.test.ts +++ b/test/integration-tests/SwiftSnippet.test.ts @@ -37,7 +37,7 @@ function normalizePath(...segments: string[]): string { } suite("SwiftSnippet Test Suite @slow", function () { - this.timeout(120000); + this.timeout(180000); const uri = testAssetUri("defaultPackage/Snippets/hello.swift"); const breakpoints = [ @@ -62,6 +62,7 @@ suite("SwiftSnippet Test Suite @slow", function () { // Set a breakpoint vscode.debug.addBreakpoints(breakpoints); }, + requiresDebugger: true, }); suiteTeardown(async () => { diff --git a/test/integration-tests/commands/build.test.ts b/test/integration-tests/commands/build.test.ts index 59d6b1201..e3109e0d3 100644 --- a/test/integration-tests/commands/build.test.ts +++ b/test/integration-tests/commands/build.test.ts @@ -28,7 +28,7 @@ import { Version } from "../../../src/utilities/version"; suite("Build Commands @slow", function () { // Default timeout is a bit too short, give it a little bit more time - this.timeout(2 * 60 * 1000); + this.timeout(3 * 60 * 1000); let folderContext: FolderContext; let workspaceContext: WorkspaceContext; @@ -56,6 +56,7 @@ suite("Build Commands @slow", function () { async teardown() { await vscode.commands.executeCommand(Workbench.ACTION_CLOSEALLEDITORS); }, + requiresDebugger: true, }); test("Swift: Run Build", async () => { diff --git a/test/integration-tests/debugger/lldb.test.ts b/test/integration-tests/debugger/lldb.test.ts index aa070721c..2a66f8b2e 100644 --- a/test/integration-tests/debugger/lldb.test.ts +++ b/test/integration-tests/debugger/lldb.test.ts @@ -34,6 +34,7 @@ suite("lldb contract test suite", () => { } workspaceContext = ctx; }, + requiresDebugger: true, }); test("getLldbProcess Contract Test, make sure the command returns", async () => { diff --git a/test/integration-tests/extension.test.ts b/test/integration-tests/extension.test.ts index b9dad4080..346b34510 100644 --- a/test/integration-tests/extension.test.ts +++ b/test/integration-tests/extension.test.ts @@ -17,8 +17,10 @@ import { WorkspaceContext } from "../../src/WorkspaceContext"; import { getBuildAllTask } from "../../src/tasks/SwiftTaskProvider"; import { SwiftExecution } from "../../src/tasks/SwiftExecution"; import { activateExtensionForTest } from "./utilities/testutilities"; +import { expect } from "chai"; -suite("Extension Test Suite", () => { +suite("Extension Test Suite", function () { + this.timeout(60000); let workspaceContext: WorkspaceContext; activateExtensionForTest({ @@ -41,18 +43,19 @@ suite("Extension Test Suite", () => { }).timeout(5000);*/ }); - suite("Workspace", () => { + suite("Workspace", function () { + this.timeout(60000); /** Verify tasks.json is being loaded */ test("Tasks.json", async () => { const folder = workspaceContext.folders.find(f => f.name === "test/defaultPackage"); assert(folder); const buildAllTask = await getBuildAllTask(folder); const execution = buildAllTask.execution as SwiftExecution; - assert.strictEqual(buildAllTask.definition.type, "swift"); - assert.strictEqual(buildAllTask.name, "swift: Build All (defaultPackage)"); + expect(buildAllTask.definition.type).to.equal("swift"); + expect(buildAllTask.name).to.include("Build All (defaultPackage)"); for (const arg of ["build", "--build-tests", "--verbose"]) { assert(execution?.args.find(item => item === arg)); } - }); + }).timeout(60000); }); -}).timeout(15000); +}); diff --git a/test/integration-tests/language/LanguageClientIntegration.test.ts b/test/integration-tests/language/LanguageClientIntegration.test.ts index 3caab8c8d..883cc49a1 100644 --- a/test/integration-tests/language/LanguageClientIntegration.test.ts +++ b/test/integration-tests/language/LanguageClientIntegration.test.ts @@ -33,7 +33,7 @@ async function buildProject(ctx: WorkspaceContext, name: string) { } suite("Language Client Integration Suite @slow", function () { - this.timeout(2 * 60 * 1000); + this.timeout(3 * 60 * 1000); let clientManager: LanguageClientManager; let workspaceContext: WorkspaceContext; diff --git a/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts b/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts index 47e1bc5e9..85b17112a 100644 --- a/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts +++ b/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts @@ -30,11 +30,14 @@ import { } from "../../utilities/tasks"; import { mutable } from "../../utilities/types"; import { SwiftExecution } from "../../../src/tasks/SwiftExecution"; +import { SwiftTask } from "../../../src/tasks/SwiftTaskProvider"; -suite("SwiftPluginTaskProvider Test Suite", () => { +suite("SwiftPluginTaskProvider Test Suite", function () { let workspaceContext: WorkspaceContext; let folderContext: FolderContext; + this.timeout(60000); // Mostly only when running suite with .only + suite("settings plugin arguments", () => { activateExtensionForSuite({ async setup(ctx) { @@ -59,17 +62,20 @@ suite("SwiftPluginTaskProvider Test Suite", () => { const tasks = await vscode.tasks.fetchTasks({ type: "swift-plugin" }); const task = tasks.find(t => t.name === "command-plugin"); const swiftExecution = task?.execution as SwiftExecution; - assert.deepEqual(swiftExecution.args, [ - "package", - "--disable-sandbox", - "--allow-writing-to-package-directory", - "--allow-writing-to-directory", - "/foo", - "/bar", - "--allow-network-connections", - "all", - "command_plugin", - ]); + assert.deepEqual( + swiftExecution.args, + workspaceContext.toolchain.buildFlags.withAdditionalFlags([ + "package", + "--disable-sandbox", + "--allow-writing-to-package-directory", + "--allow-writing-to-directory", + "/foo", + "/bar", + "--allow-network-connections", + "all", + "command_plugin", + ]) + ); }); }); @@ -101,7 +107,7 @@ suite("SwiftPluginTaskProvider Test Suite", () => { const { exitCode, output } = await executeTaskAndWaitForResult(task); expect(exitCode).to.equal(0); expect(cleanOutput(output)).to.include("Hello, World!"); - }).timeout(60000); + }); test("Exit code on failure", async () => { const task = taskProvider.createSwiftPluginTask( @@ -118,20 +124,25 @@ suite("SwiftPluginTaskProvider Test Suite", () => { mutable(task.execution).command = "/definitely/not/swift"; const { exitCode, output } = await executeTaskAndWaitForResult(task); expect(exitCode, `${output}`).to.not.equal(0); - }).timeout(10000); + }); }); suite("provideTasks", () => { suite("includes command plugin provided by the extension", async () => { - let task: vscode.Task | undefined; + let task: SwiftTask | undefined; setup(async () => { const tasks = await vscode.tasks.fetchTasks({ type: "swift-plugin" }); - task = tasks.find(t => t.name === "command-plugin"); + task = tasks.find(t => t.name === "command-plugin") as SwiftTask; }); test("provides", () => { - expect(task?.detail).to.equal("swift package command_plugin"); + expect(task?.execution.args).to.deep.equal( + workspaceContext.toolchain.buildFlags.withAdditionalFlags([ + "package", + "command_plugin", + ]) + ); }); test("executes", async () => { @@ -140,7 +151,7 @@ suite("SwiftPluginTaskProvider Test Suite", () => { await vscode.tasks.executeTask(task); const exitCode = await exitPromise; expect(exitCode).to.equal(0); - }).timeout(30000); // 30 seconds to run + }); }); suite("includes command plugin provided by tasks.json", async () => { @@ -152,7 +163,7 @@ suite("SwiftPluginTaskProvider Test Suite", () => { }); test("provides", () => { - expect(task?.detail).to.equal("swift package command_plugin --foo"); + expect(task?.detail).to.include("swift package command_plugin --foo"); }); test("executes", async () => { @@ -161,7 +172,7 @@ suite("SwiftPluginTaskProvider Test Suite", () => { await vscode.tasks.executeTask(task); const exitCode = await exitPromise; expect(exitCode).to.equal(0); - }).timeout(30000); // 30 seconds to run + }); }); }); }); diff --git a/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts b/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts index 374044db0..badb3a1e6 100644 --- a/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts +++ b/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts @@ -71,6 +71,8 @@ suite("Test Explorer Suite", function () { // which starts searching the workspace for tests. await waitForTestExplorerReady(testExplorer); }, + requiresLSP: true, + requiresDebugger: true, }); suite("Debugging", function () { @@ -131,6 +133,7 @@ suite("Test Explorer Suite", function () { switch (process.platform) { case "linux": return "/usr/lib/liblldb.so"; + case "darwin": case "win32": return await (await SwiftToolchain.create()).getLLDBDebugAdapter(); default: diff --git a/test/integration-tests/ui/PackageDependencyProvider.test.ts b/test/integration-tests/ui/PackageDependencyProvider.test.ts index fdced668f..72b7ddaa3 100644 --- a/test/integration-tests/ui/PackageDependencyProvider.test.ts +++ b/test/integration-tests/ui/PackageDependencyProvider.test.ts @@ -24,18 +24,22 @@ import { getBuildAllTask, SwiftTask } from "../../../src/tasks/SwiftTaskProvider import { testAssetPath } from "../../fixtures"; import { activateExtensionForSuite, folderInRootWorkspace } from "../utilities/testutilities"; import contextKeys from "../../../src/contextKeys"; +import { FolderContext } from "../../../src/FolderContext"; +import { WorkspaceContext } from "../../../src/WorkspaceContext"; suite("PackageDependencyProvider Test Suite", function () { + let workspaceContext: WorkspaceContext; + let folderContext: FolderContext; let treeProvider: PackageDependenciesProvider; - this.timeout(2 * 60 * 1000); // Allow up to 2 minutes to build + this.timeout(3 * 60 * 1000); // Allow up to 3 minutes to build activateExtensionForSuite({ async setup(ctx) { - const workspaceContext = ctx; + workspaceContext = ctx; await waitForNoRunningTasks(); - await folderInRootWorkspace("defaultPackage", workspaceContext); - const folderContext = await folderInRootWorkspace("dependencies", workspaceContext); + folderContext = await folderInRootWorkspace("dependencies", workspaceContext); await executeTaskAndWaitForResult((await getBuildAllTask(folderContext)) as SwiftTask); + await folderContext.reload(); treeProvider = new PackageDependenciesProvider(workspaceContext); await workspaceContext.focusFolder(folderContext); }, @@ -43,7 +47,10 @@ suite("PackageDependencyProvider Test Suite", function () { contextKeys.flatDependenciesList = false; treeProvider.dispose(); }, - testAssets: ["dependencies"], + }); + + setup(async () => { + await workspaceContext.focusFolder(folderContext); }); test("Includes remote dependency", async () => { diff --git a/test/integration-tests/utilities/testutilities.ts b/test/integration-tests/utilities/testutilities.ts index 806d30bb6..3bac12678 100644 --- a/test/integration-tests/utilities/testutilities.ts +++ b/test/integration-tests/utilities/testutilities.ts @@ -22,6 +22,8 @@ import { FolderContext } from "../../../src/FolderContext"; import { waitForNoRunningTasks } from "../../utilities/tasks"; import { closeAllEditors } from "../../utilities/commands"; import { isDeepStrictEqual } from "util"; +import { Version } from "../../../src/utilities/version"; +import configuration from "../../../src/configuration"; function getRootWorkspaceFolder(): vscode.WorkspaceFolder { const result = vscode.workspace.workspaceFolders?.at(0); @@ -65,7 +67,9 @@ const extensionBootstrapper = (() => { | undefined, after: Mocha.HookFunction, teardown: ((this: Mocha.Context) => Promise) | undefined, - testAssets?: string[] + testAssets?: string[], + requiresLSP: boolean = false, + requiresDebugger: boolean = false ) { let workspaceContext: WorkspaceContext | undefined; let autoTeardown: void | (() => Promise); @@ -76,6 +80,18 @@ const extensionBootstrapper = (() => { this.currentTest, testAssets ?? ["defaultPackage"] ); + // Need the `disableSandbox` configuration which is only in 6.1 + // https://github.com/swiftlang/sourcekit-lsp/commit/7e2d12a7a0d184cc820ae6af5ddbb8aa18b1501c + if ( + process.platform === "darwin" && + workspaceContext.toolchain.swiftVersion.isLessThan(new Version(6, 1, 0)) && + requiresLSP + ) { + this.skip(); + } + if (requiresDebugger && configuration.debugger.disable) { + this.skip(); + } if (!setup) { return; } @@ -192,13 +208,17 @@ const extensionBootstrapper = (() => { ) => Promise<(() => Promise) | void>; teardown?: (this: Mocha.Context) => Promise; testAssets?: string[]; + requiresLSP?: boolean; + requiresDebugger?: boolean; }) { testRunnerSetup( mocha.before, config?.setup, mocha.after, config?.teardown, - config?.testAssets + config?.testAssets, + config?.requiresLSP, + config?.requiresDebugger ); }, @@ -209,13 +229,17 @@ const extensionBootstrapper = (() => { ) => Promise<(() => Promise) | void>; teardown?: (this: Mocha.Context) => Promise; testAssets?: string[]; + requiresLSP?: boolean; + requiresDebugger?: boolean; }) { testRunnerSetup( mocha.beforeEach, config?.setup, mocha.afterEach, config?.teardown, - config?.testAssets + config?.testAssets, + config?.requiresLSP, + config?.requiresDebugger ); }, }; diff --git a/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts b/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts index fd9009c02..a50da8e44 100644 --- a/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts +++ b/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts @@ -92,7 +92,7 @@ suite("LanguageClientManager Suite", () => { }); mockedToolchain = mockObject({ swiftVersion: new Version(6, 0, 0), - buildFlags: mockedBuildFlags, + buildFlags: mockedBuildFlags as unknown as BuildFlags, getToolchainExecutable: mockFn(s => s.withArgs("sourcekit-lsp").returns("/path/to/toolchain/bin/sourcekit-lsp") ), diff --git a/test/unit-tests/tasks/SwiftPluginTaskProvider.test.ts b/test/unit-tests/tasks/SwiftPluginTaskProvider.test.ts index 28be1b94a..762d24f3b 100644 --- a/test/unit-tests/tasks/SwiftPluginTaskProvider.test.ts +++ b/test/unit-tests/tasks/SwiftPluginTaskProvider.test.ts @@ -31,7 +31,7 @@ suite("SwiftPluginTaskProvider Unit Test Suite", () => { setup(async () => { buildFlags = mockObject({ - withSwiftSDKFlags: mockFn(s => s.callsFake(args => args)), + withAdditionalFlags: mockFn(s => s.callsFake(args => args)), }); toolchain = mockObject({ swiftVersion: new Version(6, 0, 0), @@ -192,7 +192,7 @@ suite("SwiftPluginTaskProvider Unit Test Suite", () => { }); test("includes sdk flags", async () => { - buildFlags.withSwiftSDKFlags + buildFlags.withAdditionalFlags .withArgs(match(["package", "my-plugin"])) .returns(["package", "my-plugin", "--sdk", "/path/to/sdk"]); const taskProvider = new SwiftPluginTaskProvider(instance(workspaceContext)); diff --git a/test/unit-tests/tasks/SwiftTaskProvider.test.ts b/test/unit-tests/tasks/SwiftTaskProvider.test.ts index f5a4198a3..f5523e79f 100644 --- a/test/unit-tests/tasks/SwiftTaskProvider.test.ts +++ b/test/unit-tests/tasks/SwiftTaskProvider.test.ts @@ -49,8 +49,7 @@ suite("SwiftTaskProvider Unit Test Suite", () => { setup(async () => { buildFlags = mockObject({ - withSwiftSDKFlags: mockFn(s => s.returns([])), - withSwiftPackageFlags: mockFn(s => s.returns(s.args)), + withAdditionalFlags: mockFn(s => s.callsFake(arr => arr)), }); toolchain = mockObject({ swiftVersion: new Version(6, 0, 0), @@ -184,11 +183,8 @@ suite("SwiftTaskProvider Unit Test Suite", () => { }); test("include sdk flags", () => { - buildFlags.withSwiftSDKFlags + buildFlags.withAdditionalFlags .withArgs(match(["build"])) - .returns(["build", "--sdk", "/path/to/sdk"]); - buildFlags.withSwiftPackageFlags - .withArgs(match(["build", "--sdk", "/path/to/sdk"])) .returns(["build", "--sdk", "/path/to/sdk", "--replace-scm-with-registry"]); const task = createSwiftTask( ["build"], diff --git a/test/unit-tests/toolchain/BuildFlags.test.ts b/test/unit-tests/toolchain/BuildFlags.test.ts index aba7b7f40..0d57cade7 100644 --- a/test/unit-tests/toolchain/BuildFlags.test.ts +++ b/test/unit-tests/toolchain/BuildFlags.test.ts @@ -24,6 +24,8 @@ suite("BuildFlags Test Suite", () => { let mockedToolchain: MockedObject; let buildFlags: BuildFlags; + const sandboxConfig = mockGlobalValue(configuration, "disableSandbox"); + suiteSetup(async () => { mockedToolchain = mockObject({ swiftVersion: new Version(6, 0, 0), @@ -33,6 +35,7 @@ suite("BuildFlags Test Suite", () => { setup(() => { mockedPlatform.setValue("darwin"); + sandboxConfig.setValue(false); }); suite("getDarwinTarget", () => { @@ -233,19 +236,19 @@ suite("BuildFlags Test Suite", () => { }); }); - suite("withSwiftSDKFlags", () => { + suite("withAdditionalFlags", () => { const sdkConfig = mockGlobalValue(configuration, "sdk"); test("package", () => { for (const sub of ["dump-symbol-graph", "diagnose-api-breaking-changes", "resolve"]) { sdkConfig.setValue(""); expect( - buildFlags.withSwiftSDKFlags(["package", sub, "--disable-sandbox"]) + buildFlags.withAdditionalFlags(["package", sub, "--disable-sandbox"]) ).to.deep.equal(["package", sub, "--disable-sandbox"]); sdkConfig.setValue("/some/full/path/to/sdk"); expect( - buildFlags.withSwiftSDKFlags(["package", sub, "--disable-sandbox"]) + buildFlags.withAdditionalFlags(["package", sub, "--disable-sandbox"]) ).to.deep.equal([ "package", sub, @@ -256,76 +259,139 @@ suite("BuildFlags Test Suite", () => { } sdkConfig.setValue(""); - expect( - buildFlags.withSwiftSDKFlags(["package", "init", "--disable-sandbox"]) - ).to.deep.equal(["package", "init", "--disable-sandbox"]); + expect(buildFlags.withAdditionalFlags(["package", "init"])).to.deep.equal([ + "package", + "init", + ]); sdkConfig.setValue("/some/full/path/to/sdk"); - expect( - buildFlags.withSwiftSDKFlags(["package", "init", "--disable-sandbox"]) - ).to.deep.equal(["package", "init", "--disable-sandbox"]); + expect(buildFlags.withAdditionalFlags(["package", "init"])).to.deep.equal([ + "package", + "init", + ]); + + sandboxConfig.setValue(true); + expect(buildFlags.withAdditionalFlags(["package", "init"])).to.deep.equal([ + "package", + "--disable-sandbox", + "-Xswiftc", + "-disable-sandbox", + "init", + ]); }); test("build", () => { sdkConfig.setValue(""); expect( - buildFlags.withSwiftSDKFlags(["build", "--target", "MyExecutable"]) + buildFlags.withAdditionalFlags(["build", "--target", "MyExecutable"]) ).to.deep.equal(["build", "--target", "MyExecutable"]); sdkConfig.setValue("/some/full/path/to/sdk"); expect( - buildFlags.withSwiftSDKFlags(["build", "--target", "MyExecutable"]) + buildFlags.withAdditionalFlags(["build", "--target", "MyExecutable"]) + ).to.deep.equal([ + "build", + "--sdk", + "/some/full/path/to/sdk", + "--target", + "MyExecutable", + ]); + + sandboxConfig.setValue(true); + expect( + buildFlags.withAdditionalFlags(["build", "--target", "MyExecutable"]) ).to.deep.equal([ "build", "--sdk", "/some/full/path/to/sdk", "--target", "MyExecutable", + "--disable-sandbox", + "-Xswiftc", + "-disable-sandbox", ]); }); test("run", () => { sdkConfig.setValue(""); expect( - buildFlags.withSwiftSDKFlags(["run", "--product", "MyExecutable"]) + buildFlags.withAdditionalFlags(["run", "--product", "MyExecutable"]) ).to.deep.equal(["run", "--product", "MyExecutable"]); sdkConfig.setValue("/some/full/path/to/sdk"); expect( - buildFlags.withSwiftSDKFlags(["run", "--product", "MyExecutable"]) + buildFlags.withAdditionalFlags(["run", "--product", "MyExecutable"]) + ).to.deep.equal([ + "run", + "--sdk", + "/some/full/path/to/sdk", + "--product", + "MyExecutable", + ]); + + sandboxConfig.setValue(true); + expect( + buildFlags.withAdditionalFlags(["run", "--product", "MyExecutable"]) ).to.deep.equal([ "run", "--sdk", "/some/full/path/to/sdk", "--product", "MyExecutable", + "--disable-sandbox", + "-Xswiftc", + "-disable-sandbox", ]); }); test("test", () => { sdkConfig.setValue(""); - expect(buildFlags.withSwiftSDKFlags(["test", "--filter", "MyTests"])).to.deep.equal([ + expect(buildFlags.withAdditionalFlags(["test", "--filter", "MyTests"])).to.deep.equal([ "test", "--filter", "MyTests", ]); sdkConfig.setValue("/some/full/path/to/sdk"); - expect(buildFlags.withSwiftSDKFlags(["test", "--filter", "MyTests"])).to.deep.equal([ + expect(buildFlags.withAdditionalFlags(["test", "--filter", "MyTests"])).to.deep.equal([ + "test", + "--sdk", + "/some/full/path/to/sdk", + "--filter", + "MyTests", + ]); + + sandboxConfig.setValue(true); + expect(buildFlags.withAdditionalFlags(["test", "--filter", "MyTests"])).to.deep.equal([ "test", "--sdk", "/some/full/path/to/sdk", "--filter", "MyTests", + "--disable-sandbox", + "-Xswiftc", + "-disable-sandbox", ]); }); test("other commands", () => { sdkConfig.setValue(""); - expect(buildFlags.withSwiftSDKFlags(["help", "repl"])).to.deep.equal(["help", "repl"]); + expect(buildFlags.withAdditionalFlags(["help", "repl"])).to.deep.equal([ + "help", + "repl", + ]); sdkConfig.setValue("/some/full/path/to/sdk"); - expect(buildFlags.withSwiftSDKFlags(["help", "repl"])).to.deep.equal(["help", "repl"]); + expect(buildFlags.withAdditionalFlags(["help", "repl"])).to.deep.equal([ + "help", + "repl", + ]); + + sandboxConfig.setValue(true); + expect(buildFlags.withAdditionalFlags(["help", "repl"])).to.deep.equal([ + "help", + "repl", + ]); }); }); diff --git a/test/unit-tests/toolchain/SelectedXcodeWatcher.test.ts b/test/unit-tests/toolchain/SelectedXcodeWatcher.test.ts index c87d77a4c..490bca449 100644 --- a/test/unit-tests/toolchain/SelectedXcodeWatcher.test.ts +++ b/test/unit-tests/toolchain/SelectedXcodeWatcher.test.ts @@ -16,11 +16,20 @@ import * as vscode from "vscode"; import { expect } from "chai"; import { SelectedXcodeWatcher } from "../../../src/toolchain/SelectedXcodeWatcher"; import { SwiftOutputChannel } from "../../../src/ui/SwiftOutputChannel"; -import { instance, MockedObject, mockFn, mockGlobalObject, mockObject } from "../../MockUtils"; +import { + instance, + MockedObject, + mockFn, + mockGlobalObject, + mockGlobalValue, + mockObject, +} from "../../MockUtils"; +import configuration from "../../../src/configuration"; suite("Selected Xcode Watcher", () => { const mockedVSCodeWindow = mockGlobalObject(vscode, "window"); let mockOutputChannel: MockedObject; + const pathConfig = mockGlobalValue(configuration, "path"); setup(function () { // Xcode only exists on macOS, so the SelectedXcodeWatcher is macOS-only. @@ -31,6 +40,8 @@ suite("Selected Xcode Watcher", () => { mockOutputChannel = mockObject({ appendLine: mockFn(), }); + + pathConfig.setValue(""); }); async function run(symLinksOnCallback: (string | undefined)[]) { @@ -72,4 +83,12 @@ suite("Selected Xcode Watcher", () => { "Reload Extensions" ); }); + + test("Ignores when path is explicitly set", async () => { + pathConfig.setValue("/path/to/swift/bin"); + + await run(["/foo", "/bar"]); + + expect(mockedVSCodeWindow.showWarningMessage).to.have.not.been.called; + }); });