diff --git a/.vscode/launch.json b/.vscode/launch.json index 0200f58a..5e1219b6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -27,8 +27,7 @@ "testConfiguration": "${workspaceFolder}/.vscode-test.js", "testConfigurationLabel": "integrationTests", "args": [ - "--profile=testing-debug", - "${workspaceFolder}/assets/test" + "--profile=testing-debug" ], "outFiles": [ "${workspaceFolder}/out/**/*.js", diff --git a/assets/test/.vscode/tasks.json b/assets/test/.vscode/tasks.json index 0da525f5..e875d4d6 100644 --- a/assets/test/.vscode/tasks.json +++ b/assets/test/.vscode/tasks.json @@ -15,6 +15,20 @@ "group": "build", "label": "swift: Build All (defaultPackage)", "detail": "swift build --build-tests --verbose" + }, + { + "type": "swift", + "args": [ + "build", + "--build-tests" + ], + "cwd": "defaultPackage", + "problemMatcher": [ + "$swiftc" + ], + "group": "build", + "label": "swift: Build All from tasks.json", + "detail": "swift build --build-tests" } ] } \ No newline at end of file diff --git a/src/tasks/SwiftTaskProvider.ts b/src/tasks/SwiftTaskProvider.ts index 6bca3c81..6f59e243 100644 --- a/src/tasks/SwiftTaskProvider.ts +++ b/src/tasks/SwiftTaskProvider.ts @@ -116,8 +116,14 @@ const buildAllTaskCache = (() => { }; })(); -function buildAllTaskName(release: boolean): string { - return release ? `${SwiftTaskProvider.buildAllName} - Release` : SwiftTaskProvider.buildAllName; +function buildAllTaskName(folderContext: FolderContext, release: boolean): string { + let buildTaskName = release + ? `${SwiftTaskProvider.buildAllName} - Release` + : SwiftTaskProvider.buildAllName; + if (folderContext.relativePath.length > 0) { + buildTaskName += ` (${folderContext.relativePath})`; + } + return buildTaskName; } /** @@ -128,11 +134,7 @@ export function createBuildAllTask( release: boolean = false ): SwiftTask { const args = BuildConfigurationFactory.buildAll(folderContext, false, release).args; - let buildTaskName = buildAllTaskName(release); - - if (folderContext.relativePath.length > 0) { - buildTaskName += ` (${folderContext.relativePath})`; - } + const buildTaskName = buildAllTaskName(folderContext, release); // Create one Build All task per folder context, since this can be called multiple // times and we want the same instance each time. Otherwise, VS Code may try and execute @@ -170,11 +172,7 @@ export async function getBuildAllTask( folderContext: FolderContext, release: boolean = false ): Promise { - let buildTaskName = buildAllTaskName(release); - if (folderContext.relativePath.length > 0) { - buildTaskName += ` (${folderContext.relativePath})`; - } - + const buildTaskName = buildAllTaskName(folderContext, release); const folderWorkingDir = folderContext.workspaceFolder.uri.fsPath; // search for build all task in task.json first, that are valid for folder const workspaceTasks = (await vscode.tasks.fetchTasks()).filter(task => { diff --git a/src/ui/StatusItem.ts b/src/ui/StatusItem.ts index 4fd308ee..10742984 100644 --- a/src/ui/StatusItem.ts +++ b/src/ui/StatusItem.ts @@ -18,7 +18,7 @@ import * as path from "path"; export class RunningTask { constructor(public task: vscode.Task | string) {} get name(): string { - if (this.task instanceof vscode.Task) { + if (typeof this.task !== "string") { const folder = this.task.definition.cwd as string; if (folder) { return `${this.task.name} (${path.basename(folder)})`; @@ -115,7 +115,7 @@ export class StatusItem { */ private showTask(task: RunningTask, message?: string) { message = message ?? task.name; - if (task.task instanceof vscode.Task) { + if (typeof task.task !== "string") { this.show(`$(sync~spin) ${message}`, message, "workbench.action.tasks.showTasks"); } else { this.show(`$(sync~spin) ${message}`, message); diff --git a/test/suite/tasks/SwiftTaskProvider.test.ts b/test/suite/tasks/SwiftTaskProvider.test.ts index 3d957bcb..53d4a22a 100644 --- a/test/suite/tasks/SwiftTaskProvider.test.ts +++ b/test/suite/tasks/SwiftTaskProvider.test.ts @@ -15,23 +15,38 @@ import * as vscode from "vscode"; import * as assert from "assert"; import { WorkspaceContext } from "../../../src/WorkspaceContext"; -import { globalWorkspaceContextPromise } from "../extension.test"; -import { SwiftTaskProvider, createSwiftTask } from "../../../src/tasks/SwiftTaskProvider"; +import { folderContextPromise, globalWorkspaceContextPromise } from "../extension.test"; +import { + SwiftTaskProvider, + createSwiftTask, + createBuildAllTask, + getBuildAllTask, +} from "../../../src/tasks/SwiftTaskProvider"; import { SwiftToolchain } from "../../../src/toolchain/toolchain"; -import { SwiftExecution } from "../../../src/tasks/SwiftExecution"; -import { executeTaskAndWaitForResult, waitForNoRunningTasks } from "../../utilities"; +import { + executeTaskAndWaitForResult, + waitForEndTaskProcess, + waitForNoRunningTasks, +} from "../../utilities"; import { Version } from "../../../src/utilities/version"; +import { FolderContext } from "../../../src/FolderContext"; +import { mockNamespace } from "../../unit-tests/MockUtils"; +import { anything, when } from "ts-mockito"; suite("SwiftTaskProvider Test Suite", () => { let workspaceContext: WorkspaceContext; let toolchain: SwiftToolchain; let workspaceFolder: vscode.WorkspaceFolder; + let folderContext: FolderContext; suiteSetup(async () => { workspaceContext = await globalWorkspaceContextPromise; - toolchain = await SwiftToolchain.create(); + toolchain = workspaceContext.toolchain; assert.notEqual(workspaceContext.folders.length, 0); workspaceFolder = workspaceContext.folders[0].workspaceFolder; + + // Make sure have another folder + folderContext = await folderContextPromise("diagnostics"); }); suite("createSwiftTask", () => { @@ -39,16 +54,6 @@ suite("SwiftTaskProvider Test Suite", () => { await waitForNoRunningTasks(); }); - test("uses SwiftExecution", async () => { - const task = createSwiftTask( - ["--help"], - "help", - { cwd: workspaceFolder.uri, scope: vscode.TaskScope.Workspace }, - toolchain - ); - assert.equal(task.execution instanceof SwiftExecution, true); - }); - test("Exit code on success", async () => { const task = createSwiftTask( ["--help"], @@ -83,20 +88,53 @@ suite("SwiftTaskProvider Test Suite", () => { }); suite("provideTasks", () => { - test("includes build all task", async () => { - const taskProvider = new SwiftTaskProvider(workspaceContext); - const tasks = await taskProvider.provideTasks( - new vscode.CancellationTokenSource().token - ); - const task = tasks.find(t => t.name === "Build All (defaultPackage)"); - assert.equal(task?.detail, "swift build --build-tests -Xswiftc -diagnostic-style=llvm"); + suite("includes build all task from extension", () => { + let task: vscode.Task | undefined; + + setup(async () => { + const tasks = await vscode.tasks.fetchTasks({ type: "swift" }); + task = tasks.find(t => t.name === "Build All (defaultPackage)"); + }); + + test("provided", async () => { + assert.equal( + task?.detail, + "swift build --build-tests -Xswiftc -diagnostic-style=llvm" + ); + }); + + test("executes", async () => { + assert(task); + const exitPromise = waitForEndTaskProcess(task); + await vscode.tasks.executeTask(task); + const exitCode = await exitPromise; + assert.equal(exitCode, 0); + }).timeout(120000); // 2 minutes to build + }); + + suite("includes build all task from tasks.json", () => { + let task: vscode.Task | undefined; + + setup(async () => { + const tasks = await vscode.tasks.fetchTasks({ type: "swift" }); + task = tasks.find(t => t.name === "swift: Build All from tasks.json"); + }); + + test("provided", async () => { + assert.equal(task?.detail, "swift build --build-tests"); + }); + + test("executes", async () => { + assert(task); + const exitPromise = waitForEndTaskProcess(task); + await vscode.tasks.executeTask(task); + const exitCode = await exitPromise; + assert.equal(exitCode, 0); + }).timeout(120000); // 2 minutes to build }); test("includes product debug task", async () => { - const taskProvider = new SwiftTaskProvider(workspaceContext); - const tasks = await taskProvider.provideTasks( - new vscode.CancellationTokenSource().token - ); + const tasks = await vscode.tasks.fetchTasks({ type: "swift" }); const task = tasks.find(t => t.name === "Build Debug PackageExe (defaultPackage)"); assert.equal( task?.detail, @@ -115,37 +153,39 @@ suite("SwiftTaskProvider Test Suite", () => { "swift build -c release --product PackageExe -Xswiftc -diagnostic-style=llvm" ); }); + + test("includes additional folders", async () => { + const tasks = await vscode.tasks.fetchTasks({ type: "swift" }); + const diagnosticTasks = tasks.filter(t => t.name.endsWith("(diagnostics)")); + assert.equal(diagnosticTasks.length, 3); + }); }); - suite("resolveTask", () => { - test("uses SwiftExecution", async () => { - const taskProvider = new SwiftTaskProvider(workspaceContext); - const task = new vscode.Task( - { - type: "swift", - args: ["run", "PackageExe"], - env: { FOO: "bar" }, - cwd: workspaceFolder.uri.fsPath, - }, - workspaceFolder, - "run PackageExe", - "swift" + suite("createBuildAllTask", () => { + test("should return same task instance", async () => { + assert.strictEqual( + createBuildAllTask(folderContext), + createBuildAllTask(folderContext) ); - const resolvedTask = taskProvider.resolveTask( - task, - new vscode.CancellationTokenSource().token - ); - assert.equal(resolvedTask.execution instanceof SwiftExecution, true); - const swiftExecution = resolvedTask.execution as SwiftExecution; - assert.equal( - swiftExecution.options.cwd, - workspaceFolder.uri.fsPath, - "Sets correct cwd" + }); + + test("different task returned for release mode", async () => { + assert.notEqual( + createBuildAllTask(folderContext), + createBuildAllTask(folderContext, true) ); - assert.equal( - swiftExecution.options.env?.FOO, - "bar", - "Sets provided environment variables" + }); + }); + + suite("getBuildAllTask", () => { + const tasksMock = mockNamespace(vscode, "tasks"); + + test("creates build all task when it cannot find one", async () => { + when(tasksMock.fetchTasks()).thenReturn(Promise.resolve([])); + when(tasksMock.fetchTasks(anything())).thenReturn(Promise.resolve([])); + assert.strictEqual( + await getBuildAllTask(folderContext), + createBuildAllTask(folderContext) ); }); }); diff --git a/test/unit-tests/tasks/SwiftTaskProvider.test.ts b/test/unit-tests/tasks/SwiftTaskProvider.test.ts new file mode 100644 index 00000000..22e332f0 --- /dev/null +++ b/test/unit-tests/tasks/SwiftTaskProvider.test.ts @@ -0,0 +1,710 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2024 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import * as vscode from "vscode"; +import * as assert from "assert"; +import { WorkspaceContext } from "../../../src/WorkspaceContext"; +import { + SwiftTaskProvider, + buildOptions, + createSwiftTask, + getBuildAllTask, + platformDebugBuildOptions, +} from "../../../src/tasks/SwiftTaskProvider"; +import { SwiftToolchain } from "../../../src/toolchain/toolchain"; +import { SwiftExecution } from "../../../src/tasks/SwiftExecution"; +import { Version } from "../../../src/utilities/version"; +import { BuildFlags } from "../../../src/toolchain/BuildFlags"; +import { anything, deepEqual, instance, mock, when } from "ts-mockito"; +import { mockNamespace, mockValue } from "../MockUtils"; +import configuration from "../../../src/configuration"; +import { Sanitizer } from "../../../src/toolchain/Sanitizer"; +import { FolderContext } from "../../../src/FolderContext"; + +suite("SwiftTaskProvider Unit Test Suite", () => { + let workspaceContext: WorkspaceContext; + let workspaceFolder: vscode.WorkspaceFolder; + let toolchain: SwiftToolchain; + let buildFlags: BuildFlags; + + const platformMock = mockValue(process, "platform"); + + setup(async () => { + workspaceContext = mock(WorkspaceContext); + toolchain = mock(SwiftToolchain); + buildFlags = mock(BuildFlags); + workspaceFolder = { + uri: vscode.Uri.file("/path/to/workspace"), + name: "myWorkspace", + index: 0, + }; + when(buildFlags.withSwiftSDKFlags(anything())).thenReturn([]); + when(toolchain.swiftVersion).thenReturn(new Version(6, 0, 0)); + when(toolchain.buildFlags).thenReturn(instance(buildFlags)); + when(toolchain.getToolchainExecutable("swift")).thenReturn("/path/to/bin/swift"); + when(workspaceContext.toolchain).thenReturn(instance(toolchain)); + }); + + suite("platformDebugBuildOptions", () => { + test("windows, before 5.9", async () => { + platformMock.setValue("win32"); + when(toolchain.swiftVersion).thenReturn(new Version(5, 8, 1)); + + assert.deepEqual(platformDebugBuildOptions(instance(toolchain)), [ + "-Xswiftc", + "-g", + "-Xswiftc", + "-use-ld=lld", + "-Xlinker", + "-debug:dwarf", + ]); + }); + + test("windows, after 5.9", async () => { + platformMock.setValue("win32"); + when(toolchain.swiftVersion).thenReturn(new Version(5, 9, 0)); + const expected = ["-Xlinker", "-debug:dwarf"]; + + assert.deepEqual(platformDebugBuildOptions(instance(toolchain)), expected); + + when(toolchain.swiftVersion).thenReturn(new Version(6, 0, 0)); + + assert.deepEqual(platformDebugBuildOptions(instance(toolchain)), expected); + }); + + test("linux", async () => { + platformMock.setValue("linux"); + + assert.deepEqual(platformDebugBuildOptions(instance(toolchain)), []); + }); + + test("macOS", async () => { + platformMock.setValue("darwin"); + + assert.deepEqual(platformDebugBuildOptions(instance(toolchain)), []); + }); + }); + + suite("buildOptions", () => { + const buildArgs = mockValue(configuration, "buildArguments"); + const diagnosticsStyle = mockValue(configuration, "diagnosticsStyle"); + const sanitizerConfig = mockValue(configuration, "sanitizer"); + + setup(() => { + platformMock.setValue("darwin"); + buildArgs.setValue([]); + diagnosticsStyle.setValue("default"); + }); + + test("include debug options", async () => { + platformMock.setValue("win32"); + assert.deepEqual(buildOptions(instance(toolchain), true), ["-Xlinker", "-debug:dwarf"]); + }); + + test("don't include debug options", async () => { + platformMock.setValue("win32"); + assert.deepEqual(buildOptions(instance(toolchain), false), []); + }); + + test("include diagnostic style", async () => { + diagnosticsStyle.setValue("llvm"); + assert.deepEqual(buildOptions(instance(toolchain), false), [ + "-Xswiftc", + "-diagnostic-style=llvm", + ]); + }); + + test("include sanitizer flags", async () => { + const sanitizer = mock(Sanitizer); + when(sanitizer.buildFlags).thenReturn(["--sanitize=thread"]); + when(toolchain.sanitizer("thread")).thenReturn(instance(sanitizer)); + sanitizerConfig.setValue("thread"); + + assert.deepEqual(buildOptions(instance(toolchain), false), ["--sanitize=thread"]); + }); + + test("include build flags", async () => { + buildArgs.setValue(["-DFOO"]); + + assert.deepEqual(buildOptions(instance(toolchain), false), ["-DFOO"]); + }); + }); + + suite("createSwiftTask", () => { + const envConfig = mockValue(configuration, "swiftEnvironmentVariables"); + + test("uses SwiftExecution", async () => { + const task = createSwiftTask( + ["--help"], + "help", + { cwd: workspaceFolder.uri, scope: vscode.TaskScope.Workspace }, + instance(toolchain) + ); + assert.equal(task.execution instanceof SwiftExecution, true); + }); + + test("uses toolchain swift path", async () => { + const task = createSwiftTask( + ["--help"], + "help", + { cwd: workspaceFolder.uri, scope: vscode.TaskScope.Workspace }, + instance(toolchain) + ); + const execution = task.execution as SwiftExecution; + assert.equal(execution.command, "/path/to/bin/swift"); + }); + + test("include sdk flags", () => { + when(buildFlags.withSwiftSDKFlags(deepEqual(["build"]))).thenReturn([ + "build", + "--sdk", + "/path/to/sdk", + ]); + const task = createSwiftTask( + ["build"], + "build", + { cwd: workspaceFolder.uri, scope: vscode.TaskScope.Workspace }, + instance(toolchain) + ); + const execution = task.execution as SwiftExecution; + assert.deepEqual(execution.args, ["build", "--sdk", "/path/to/sdk"]); + }); + + test("include environment", () => { + envConfig.setValue({ FOO: "1" }); + const task = createSwiftTask( + ["--help"], + "help", + { cwd: workspaceFolder.uri, scope: vscode.TaskScope.Workspace }, + instance(toolchain), + { BAZ: "2" } + ); + const execution = task.execution as SwiftExecution; + assert.deepEqual(execution.options.env, { FOO: "1", BAZ: "2" }); + }); + + test("include presentation", () => { + envConfig.setValue({ FOO: "1" }); + const task = createSwiftTask( + ["--help"], + "help", + { + cwd: workspaceFolder.uri, + scope: vscode.TaskScope.Workspace, + presentationOptions: { reveal: vscode.TaskRevealKind.Always }, + }, + instance(toolchain), + { BAZ: "2" } + ); + assert.deepEqual(task.presentationOptions, { reveal: vscode.TaskRevealKind.Always }); + }); + + test("include group", () => { + envConfig.setValue({ FOO: "1" }); + const task = createSwiftTask( + ["--help"], + "help", + { + cwd: workspaceFolder.uri, + scope: vscode.TaskScope.Workspace, + group: vscode.TaskGroup.Build, + }, + instance(toolchain), + { BAZ: "2" } + ); + assert.equal(task.group, vscode.TaskGroup.Build); + }); + + test("include showBuildStatus", () => { + envConfig.setValue({ FOO: "1" }); + const task = createSwiftTask( + ["--help"], + "help", + { + cwd: workspaceFolder.uri, + scope: vscode.TaskScope.Workspace, + showBuildStatus: "progress", + }, + instance(toolchain), + { BAZ: "2" } + ); + assert.equal(task.definition.showBuildStatus, "progress"); + }); + + test("include disableTaskQueue", () => { + envConfig.setValue({ FOO: "1" }); + const task = createSwiftTask( + ["--help"], + "help", + { + cwd: workspaceFolder.uri, + scope: vscode.TaskScope.Workspace, + disableTaskQueue: true, + }, + instance(toolchain), + { BAZ: "2" } + ); + assert.equal(task.definition.disableTaskQueue, true); + }); + + test("include dontTriggerTestDiscovery", () => { + envConfig.setValue({ FOO: "1" }); + const task = createSwiftTask( + ["--help"], + "help", + { + cwd: workspaceFolder.uri, + scope: vscode.TaskScope.Workspace, + dontTriggerTestDiscovery: true, + }, + instance(toolchain), + { BAZ: "2" } + ); + assert.equal(task.definition.dontTriggerTestDiscovery, true); + }); + }); + + suite("resolveTask", () => { + test("uses SwiftExecution", async () => { + const taskProvider = new SwiftTaskProvider(instance(workspaceContext)); + const task = new vscode.Task( + { + type: "swift", + args: [], + }, + workspaceFolder, + "run PackageExe", + "swift" + ); + const resolvedTask = taskProvider.resolveTask( + task, + new vscode.CancellationTokenSource().token + ); + assert.equal(resolvedTask.execution instanceof SwiftExecution, true); + }); + + test("uses toolchain swift path", async () => { + const taskProvider = new SwiftTaskProvider(instance(workspaceContext)); + const task = new vscode.Task( + { + type: "swift", + args: [], + }, + workspaceFolder, + "run PackageExe", + "swift" + ); + const resolvedTask = taskProvider.resolveTask( + task, + new vscode.CancellationTokenSource().token + ); + const swiftExecution = resolvedTask.execution as SwiftExecution; + assert.equal(swiftExecution.command, "/path/to/bin/swift"); + }); + + suite("Platform cwd", () => { + test("includes macos cwd", async () => { + platformMock.setValue("darwin"); + const taskProvider = new SwiftTaskProvider(instance(workspaceContext)); + const task = new vscode.Task( + { + type: "swift", + args: [], + cwd: workspaceFolder.uri.fsPath, + macos: { + cwd: `${workspaceFolder.uri.fsPath}/macos`, + }, + }, + workspaceFolder, + "run PackageExe", + "swift" + ); + const resolvedTask = taskProvider.resolveTask( + task, + new vscode.CancellationTokenSource().token + ); + const swiftExecution = resolvedTask.execution as SwiftExecution; + assert.equal(swiftExecution.options.cwd, `${workspaceFolder.uri.fsPath}/macos`); + }); + + test("includes linux cwd", async () => { + platformMock.setValue("linux"); + const taskProvider = new SwiftTaskProvider(instance(workspaceContext)); + const task = new vscode.Task( + { + type: "swift", + args: [], + cwd: workspaceFolder.uri.fsPath, + linux: { + cwd: `${workspaceFolder.uri.fsPath}/linux`, + }, + }, + workspaceFolder, + "run PackageExe", + "swift" + ); + const resolvedTask = taskProvider.resolveTask( + task, + new vscode.CancellationTokenSource().token + ); + const swiftExecution = resolvedTask.execution as SwiftExecution; + assert.equal(swiftExecution.options.cwd, `${workspaceFolder.uri.fsPath}/linux`); + }); + + test("includes windows cwd", async () => { + platformMock.setValue("win32"); + const taskProvider = new SwiftTaskProvider(instance(workspaceContext)); + const task = new vscode.Task( + { + type: "swift", + args: [], + cwd: workspaceFolder.uri.fsPath, + windows: { + cwd: `${workspaceFolder.uri.fsPath}/windows`, + }, + }, + workspaceFolder, + "run PackageExe", + "swift" + ); + const resolvedTask = taskProvider.resolveTask( + task, + new vscode.CancellationTokenSource().token + ); + const swiftExecution = resolvedTask.execution as SwiftExecution; + assert.equal(swiftExecution.options.cwd, `${workspaceFolder.uri.fsPath}/windows`); + }); + + test("fallback default cwd", async () => { + platformMock.setValue("darwin"); + const taskProvider = new SwiftTaskProvider(instance(workspaceContext)); + const task = new vscode.Task( + { + type: "swift", + args: [], + cwd: workspaceFolder.uri.fsPath, + linux: { + cwd: `${workspaceFolder.uri.fsPath}/linux`, + }, + }, + workspaceFolder, + "run PackageExe", + "swift" + ); + const resolvedTask = taskProvider.resolveTask( + task, + new vscode.CancellationTokenSource().token + ); + const swiftExecution = resolvedTask.execution as SwiftExecution; + assert.equal(swiftExecution.options.cwd, workspaceFolder.uri.fsPath); + }); + }); + + suite("Platform env", () => { + test("includes macos env", async () => { + platformMock.setValue("darwin"); + const taskProvider = new SwiftTaskProvider(instance(workspaceContext)); + const task = new vscode.Task( + { + type: "swift", + args: [], + env: { + FOO: "bar", + }, + macos: { + env: { + FOO: "baz", + }, + }, + }, + workspaceFolder, + "run PackageExe", + "swift" + ); + const resolvedTask = taskProvider.resolveTask( + task, + new vscode.CancellationTokenSource().token + ); + const swiftExecution = resolvedTask.execution as SwiftExecution; + assert.equal(swiftExecution.options.env?.FOO, "baz"); + }); + + test("includes linux env", async () => { + platformMock.setValue("linux"); + const taskProvider = new SwiftTaskProvider(instance(workspaceContext)); + const task = new vscode.Task( + { + type: "swift", + args: [], + env: { + FOO: "bar", + }, + linux: { + env: { + FOO: "baz", + }, + }, + }, + workspaceFolder, + "run PackageExe", + "swift" + ); + const resolvedTask = taskProvider.resolveTask( + task, + new vscode.CancellationTokenSource().token + ); + const swiftExecution = resolvedTask.execution as SwiftExecution; + assert.equal(swiftExecution.options.env?.FOO, "baz"); + }); + + test("includes windows env", async () => { + platformMock.setValue("win32"); + const taskProvider = new SwiftTaskProvider(instance(workspaceContext)); + const task = new vscode.Task( + { + type: "swift", + args: [], + env: { + FOO: "bar", + }, + windows: { + env: { + FOO: "baz", + }, + }, + }, + workspaceFolder, + "run PackageExe", + "swift" + ); + const resolvedTask = taskProvider.resolveTask( + task, + new vscode.CancellationTokenSource().token + ); + const swiftExecution = resolvedTask.execution as SwiftExecution; + assert.equal(swiftExecution.options.env?.FOO, "baz"); + }); + + test("fallback default env", async () => { + platformMock.setValue("darwin"); + const taskProvider = new SwiftTaskProvider(instance(workspaceContext)); + const task = new vscode.Task( + { + type: "swift", + args: [], + env: { + FOO: "bar", + }, + linux: { + env: { + FOO: "baz", + }, + }, + }, + workspaceFolder, + "run PackageExe", + "swift" + ); + const resolvedTask = taskProvider.resolveTask( + task, + new vscode.CancellationTokenSource().token + ); + const swiftExecution = resolvedTask.execution as SwiftExecution; + assert.equal(swiftExecution.options.env?.FOO, "bar"); + }); + }); + + suite("Platform args", () => { + test("includes macos args", async () => { + platformMock.setValue("darwin"); + const taskProvider = new SwiftTaskProvider(instance(workspaceContext)); + const task = new vscode.Task( + { + type: "swift", + args: ["run", "PackageExe"], + macos: { + args: ["run", "MacosPackageExe"], + }, + }, + workspaceFolder, + "run PackageExe", + "swift" + ); + const resolvedTask = taskProvider.resolveTask( + task, + new vscode.CancellationTokenSource().token + ); + const swiftExecution = resolvedTask.execution as SwiftExecution; + assert.deepEqual(swiftExecution.args, ["run", "MacosPackageExe"]); + }); + + test("includes linux args", async () => { + platformMock.setValue("linux"); + const taskProvider = new SwiftTaskProvider(instance(workspaceContext)); + const task = new vscode.Task( + { + type: "swift", + args: ["run", "PackageExe"], + linux: { + args: ["run", "LinuxPackageExe"], + }, + }, + workspaceFolder, + "run PackageExe", + "swift" + ); + const resolvedTask = taskProvider.resolveTask( + task, + new vscode.CancellationTokenSource().token + ); + const swiftExecution = resolvedTask.execution as SwiftExecution; + assert.deepEqual(swiftExecution.args, ["run", "LinuxPackageExe"]); + }); + + test("includes windows args", async () => { + platformMock.setValue("win32"); + const taskProvider = new SwiftTaskProvider(instance(workspaceContext)); + const task = new vscode.Task( + { + type: "swift", + args: ["run", "PackageExe"], + windows: { + args: ["run", "WinPackageExe"], + }, + }, + workspaceFolder, + "run PackageExe", + "swift" + ); + const resolvedTask = taskProvider.resolveTask( + task, + new vscode.CancellationTokenSource().token + ); + const swiftExecution = resolvedTask.execution as SwiftExecution; + assert.deepEqual(swiftExecution.args, ["run", "WinPackageExe"]); + }); + + test("fallback default args", async () => { + platformMock.setValue("darwin"); + const taskProvider = new SwiftTaskProvider(instance(workspaceContext)); + const task = new vscode.Task( + { + type: "swift", + args: ["run", "PackageExe"], + linux: { + args: ["run", "LinuxPackageExe"], + }, + }, + workspaceFolder, + "run PackageExe", + "swift" + ); + const resolvedTask = taskProvider.resolveTask( + task, + new vscode.CancellationTokenSource().token + ); + const swiftExecution = resolvedTask.execution as SwiftExecution; + assert.deepEqual(swiftExecution.args, ["run", "PackageExe"]); + }); + }); + }); + + suite("getBuildAllTask", () => { + const tasksMock = mockNamespace(vscode, "tasks"); + + let folderContext: FolderContext; + let extensionTask: vscode.Task; + let workspaceTask: vscode.Task; + + setup(() => { + folderContext = mock(FolderContext); + when(folderContext.workspaceContext).thenReturn(instance(workspaceContext)); + when(folderContext.workspaceFolder).thenReturn(workspaceFolder); + when(folderContext.folder).thenReturn(workspaceFolder.uri); + when(folderContext.relativePath).thenReturn(""); + + when(tasksMock.fetchTasks()).thenReturn(Promise.resolve([])); + when(tasksMock.fetchTasks(anything())).thenReturn(Promise.resolve([])); + + extensionTask = createSwiftTask( + ["build"], + SwiftTaskProvider.buildAllName, + { + cwd: workspaceFolder.uri, + scope: workspaceFolder, + }, + instance(toolchain) + ); + workspaceTask = createSwiftTask( + ["build"], + `swift: ${SwiftTaskProvider.buildAllName}`, + { + cwd: workspaceFolder.uri, + scope: workspaceFolder, + }, + instance(toolchain) + ); + workspaceTask.source = "Workspace"; // When comes from task.json + }); + + test("returns task provided by the extension", async () => { + when(tasksMock.fetchTasks(anything())).thenReturn(Promise.resolve([extensionTask])); + assert.strictEqual(extensionTask, await getBuildAllTask(instance(folderContext))); + }); + + test("returns workspace task, matched by name", async () => { + when(tasksMock.fetchTasks()).thenReturn(Promise.resolve([workspaceTask])); + when(tasksMock.fetchTasks(anything())).thenReturn(Promise.resolve([extensionTask])); + assert.strictEqual(workspaceTask, await getBuildAllTask(instance(folderContext))); + }); + + test("returns workspace task, default build task", async () => { + const defaultBuildTask = createSwiftTask( + ["build"], + `some weird task name`, + { + cwd: workspaceFolder.uri, + scope: workspaceFolder, + }, + instance(toolchain) + ); + defaultBuildTask.source = "Workspace"; + defaultBuildTask.group = { + id: vscode.TaskGroup.Build.id, + isDefault: true, + }; + when(tasksMock.fetchTasks()).thenReturn( + Promise.resolve([defaultBuildTask, workspaceTask]) + ); + assert.strictEqual(defaultBuildTask, await getBuildAllTask(instance(folderContext))); + }); + + test("workspace task NOT default build task", async () => { + const nonDefaultBuildTask = createSwiftTask( + ["build"], + `some weird task name`, + { + cwd: workspaceFolder.uri, + scope: workspaceFolder, + }, + instance(toolchain) + ); + nonDefaultBuildTask.source = "Workspace"; + nonDefaultBuildTask.group = vscode.TaskGroup.Build; + when(tasksMock.fetchTasks()).thenReturn(Promise.resolve([nonDefaultBuildTask])); + when(tasksMock.fetchTasks(anything())).thenReturn(Promise.resolve([extensionTask])); + assert.strictEqual(extensionTask, await getBuildAllTask(instance(folderContext))); + }); + }); +}); diff --git a/test/utilities.ts b/test/utilities.ts index c8ce20e3..b6397b99 100644 --- a/test/utilities.ts +++ b/test/utilities.ts @@ -16,6 +16,14 @@ import * as vscode from "vscode"; import { SwiftTaskFixture } from "./fixtures"; import { SwiftTask } from "../src/tasks/SwiftTaskProvider"; +export type Mutable = { + -readonly [K in keyof T]: T[K]; +}; + +export function mutable(target: T): Mutable { + return target; +} + /** * Executes a {@link SwiftTask}, accumulates output, and * waits for the exit code @@ -26,7 +34,7 @@ import { SwiftTask } from "../src/tasks/SwiftTaskProvider"; export async function executeTaskAndWaitForResult( fixture: SwiftTaskFixture | SwiftTask ): Promise<{ exitCode?: number; output: string }> { - const task = (fixture instanceof vscode.Task ? fixture : fixture.task) as SwiftTask; + const task = ("task" in fixture ? fixture.task : fixture) as SwiftTask; let output = ""; const disposables = [task.execution.onDidWrite(e => (output += e))]; const promise = new Promise(res => @@ -83,8 +91,8 @@ export async function waitForClose(fixture: { * utility can be used to make sure no task is running * before starting a new test */ -export async function waitForNoRunningTasks(): Promise { - await new Promise(res => { +export function waitForNoRunningTasks(): Promise { + return new Promise(res => { if (vscode.tasks.taskExecutions.length === 0) { res(); return; @@ -98,3 +106,27 @@ export async function waitForNoRunningTasks(): Promise { }); }); } + +/** + * Ideally we would want to use {@link executeTaskAndWaitForResult} but that + * requires the tests creating the task through some means. If the + * {@link vscode.Task Task}, was provided by the extension under test, the + * {@link SwiftTask.execution} event emitters never seem to fire. + * + * @param task task to listen for close event + * @returns exitCode for task execution, undefined if terminated unexpectedly + */ +export function waitForEndTaskProcess(task: vscode.Task): Promise { + return new Promise(res => { + const disposables: vscode.Disposable[] = []; + disposables.push( + vscode.tasks.onDidEndTaskProcess(e => { + if (task.name !== e.execution.task.name) { + return; + } + disposables.forEach(d => d.dispose()); + res(e.exitCode); + }) + ); + }); +}