Skip to content

Commit

Permalink
Add integration tests for build commands (#1185)
Browse files Browse the repository at this point in the history
* Add integration tests for build commands

- Validate the workflow of user calling the
Swift: Run Build/Clean Build/Debug Build commands.
- Ensure Swift: Run Build will not get blocked by pre-set breakpoint.
- Ensure Swift: Clean Build will result in a cleaned up .build folder.
- Ensure Swift: Debug Build will stop on a breakpoint and resume.

Issue: #1184

* - Added module enum for workbench commands string constant
- Added comments for clarification
- Added utilities to listen for dap message, this is useful for test
synchronization. Code takes inspiration from
#1126

* - Rename from utilies/command.ts to utilies/commands.ts
- Minor cosmetic change to utilies/commands.ts

* - Fix a bug in updateSettings where promise is not being exlicitly
returned, causing restore of setting being not awaitable
- Make makeDebugConfigurations to be awaitable
- Change launch to also update the key for ASLR disable settings
- Make the test properly set up and reset the settings that update the
launch config
  • Loading branch information
michael-weng authored Nov 8, 2024
1 parent 38369e8 commit d6590e8
Show file tree
Hide file tree
Showing 13 changed files with 234 additions and 21 deletions.
12 changes: 9 additions & 3 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ export function registerToolchainCommands(
];
}

export enum Commands {
RUN = "swift.run",
DEBUG = "swift.debug",
CLEAN_BUILD = "swift.cleanBuild",
}

/**
* Registers this extension's commands in the given {@link vscode.ExtensionContext context}.
*/
Expand All @@ -74,9 +80,9 @@ export function register(ctx: WorkspaceContext): vscode.Disposable[] {
resolveDependencies(ctx)
),
vscode.commands.registerCommand("swift.updateDependencies", () => updateDependencies(ctx)),
vscode.commands.registerCommand("swift.run", () => runBuild(ctx)),
vscode.commands.registerCommand("swift.debug", () => debugBuild(ctx)),
vscode.commands.registerCommand("swift.cleanBuild", () => cleanBuild(ctx)),
vscode.commands.registerCommand(Commands.RUN, () => runBuild(ctx)),
vscode.commands.registerCommand(Commands.DEBUG, () => debugBuild(ctx)),
vscode.commands.registerCommand(Commands.CLEAN_BUILD, () => cleanBuild(ctx)),
vscode.commands.registerCommand("swift.runTestsMultipleTimes", item => {
if (ctx.currentFolder) {
return runTestMultipleTimes(ctx.currentFolder, item, false);
Expand Down
8 changes: 4 additions & 4 deletions src/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ import { FolderContext } from "../FolderContext";
* Executes a {@link vscode.Task task} to run swift target.
*/
export async function runBuild(ctx: WorkspaceContext) {
await debugBuildWithOptions(ctx, { noDebug: true });
return await debugBuildWithOptions(ctx, { noDebug: true });
}

/**
* Executes a {@link vscode.Task task} to debug swift target.
*/
export async function debugBuild(ctx: WorkspaceContext) {
await debugBuildWithOptions(ctx, {});
return await debugBuildWithOptions(ctx, {});
}

/**
Expand All @@ -41,7 +41,7 @@ export async function cleanBuild(ctx: WorkspaceContext) {
if (!current) {
return;
}
await folderCleanBuild(current);
return await folderCleanBuild(current);
}

/**
Expand All @@ -62,7 +62,7 @@ export async function folderCleanBuild(folderContext: FolderContext) {
folderContext.workspaceContext.toolchain
);

await executeTaskWithUI(task, "Clean Build", folderContext);
return await executeTaskWithUI(task, "Clean Build", folderContext);
}

/**
Expand Down
17 changes: 13 additions & 4 deletions src/debugger/launch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,13 @@ import { CI_DISABLE_ASLR } from "./lldb";
* @param ctx folder context to create launch configurations for
* @param yes automatically answer yes to dialogs
*/
export async function makeDebugConfigurations(ctx: FolderContext, message?: string, yes = false) {
export async function makeDebugConfigurations(
ctx: FolderContext,
message?: string,
yes = false
): Promise<boolean> {
if (!configuration.folder(ctx.workspaceFolder).autoGenerateLaunchConfigurations) {
return;
return false;
}
const wsLaunchSection = vscode.workspace.getConfiguration("launch", ctx.folder);
const launchConfigs = wsLaunchSection.get<vscode.DebugConfiguration[]>("configurations") || [];
Expand All @@ -41,6 +45,8 @@ export async function makeDebugConfigurations(ctx: FolderContext, message?: stri
"cwd",
"preLaunchTask",
"type",
"disableASLR",
"initCommands",
`env.${swiftLibraryPathKey()}`,
];
const configUpdates: { index: number; config: vscode.DebugConfiguration }[] = [];
Expand Down Expand Up @@ -96,6 +102,7 @@ export async function makeDebugConfigurations(ctx: FolderContext, message?: stri
vscode.ConfigurationTarget.WorkspaceFolder
);
}
return true;
}

// Return debug launch configuration for an executable in the given folder
Expand Down Expand Up @@ -178,15 +185,17 @@ export async function debugLaunchConfig(
config: vscode.DebugConfiguration,
options: vscode.DebugSessionOptions = {}
) {
return new Promise<void>((resolve, reject) => {
return new Promise<boolean>((resolve, reject) => {
vscode.debug.startDebugging(workspaceFolder, config, options).then(
started => {
if (started) {
const terminateSession = vscode.debug.onDidTerminateDebugSession(async () => {
// dispose terminate debug handler
terminateSession.dispose();
resolve();
resolve(true);
});
} else {
resolve(false);
}
},
reason => {
Expand Down
3 changes: 2 additions & 1 deletion src/ui/ReloadExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
//===----------------------------------------------------------------------===//

import * as vscode from "vscode";
import { Workbench } from "../utilities/commands";

/**
* Prompts the user to reload the extension in cases where we are unable to do
Expand All @@ -29,7 +30,7 @@ export async function showReloadExtensionNotification<T extends string>(
const buttons: ("Reload Extensions" | T)[] = ["Reload Extensions", ...items];
const selected = await vscode.window.showWarningMessage(message, ...buttons);
if (selected === "Reload Extensions") {
await vscode.commands.executeCommand("workbench.action.reloadWindow");
await vscode.commands.executeCommand(Workbench.ACTION_RELOADWINDOW);
}
return selected;
}
19 changes: 19 additions & 0 deletions src/utilities/commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

export enum Workbench {
ACTION_DEBUG_CONTINUE = "workbench.action.debug.continue",
ACTION_CLOSEALLEDITORS = "workbench.action.closeAllEditors",
ACTION_RELOADWINDOW = "workbench.action.reloadWindow",
}
3 changes: 2 additions & 1 deletion test/integration-tests/BackgroundCompilation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { WorkspaceContext } from "../../src/WorkspaceContext";
import { globalWorkspaceContextPromise } from "./extension.test";
import { testAssetUri } from "../fixtures";
import { waitForNoRunningTasks } from "../utilities";
import { Workbench } from "../../src/utilities/commands";

suite("BackgroundCompilation Test Suite", () => {
let workspaceContext: WorkspaceContext;
Expand All @@ -31,7 +32,7 @@ suite("BackgroundCompilation Test Suite", () => {

suiteTeardown(async () => {
await vscode.workspace.getConfiguration("swift").update("backgroundCompilation", undefined);
await vscode.commands.executeCommand("workbench.action.closeActiveEditor");
await vscode.commands.executeCommand(Workbench.ACTION_CLOSEALLEDITORS);
});

test("build all on save @slow", async () => {
Expand Down
3 changes: 2 additions & 1 deletion test/integration-tests/DiagnosticsManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { DiagnosticsManager } from "../../src/DiagnosticsManager";
import { FolderContext } from "../../src/FolderContext";
import { Version } from "../../src/utilities/version";
import { folderContextPromise, globalWorkspaceContextPromise } from "./extension.test";
import { Workbench } from "../../src/utilities/commands";

const waitForDiagnostics = (uris: vscode.Uri[], allowEmpty: boolean = true) =>
new Promise<void>(res =>
Expand Down Expand Up @@ -907,7 +908,7 @@ suite("DiagnosticsManager Test Suite", async function () {
});

teardown(async () => {
await vscode.commands.executeCommand("workbench.action.closeAllEditors");
await vscode.commands.executeCommand(Workbench.ACTION_CLOSEALLEDITORS);
});

test("Provides swift diagnostics", async () => {
Expand Down
97 changes: 97 additions & 0 deletions test/integration-tests/commands/build.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//===----------------------------------------------------------------------===//
//
// 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 fs from "fs";
import * as path from "path";
import { expect } from "chai";
import { folderContextPromise, globalWorkspaceContextPromise } from "../extension.test";
import { waitForNoRunningTasks } from "../../utilities";
import { testAssetUri } from "../../fixtures";
import { FolderContext } from "../../../src/FolderContext";
import { WorkspaceContext } from "../../../src/WorkspaceContext";
import { Commands } from "../../../src/commands";
import { makeDebugConfigurations } from "../../../src/debugger/launch";
import { Workbench } from "../../../src/utilities/commands";
import { continueSession, waitForDebugAdapterCommand } from "../../utilities/debug";
import { SettingsMap, updateSettings } from "../testexplorer/utilities";

suite("Build Commands", function () {
let folderContext: FolderContext;
let workspaceContext: WorkspaceContext;
let settingsTeardown: () => Promise<SettingsMap>;
const uri = testAssetUri("defaultPackage/Sources/PackageExe/main.swift");
const breakpoints = [
new vscode.SourceBreakpoint(new vscode.Location(uri, new vscode.Position(2, 0))),
];

suiteSetup(async function () {
workspaceContext = await globalWorkspaceContextPromise;
await waitForNoRunningTasks();
folderContext = await folderContextPromise("defaultPackage");
await workspaceContext.focusFolder(folderContext);
await vscode.window.showTextDocument(uri);
settingsTeardown = await updateSettings({
"swift.autoGenerateLaunchConfigurations": true,
});
await makeDebugConfigurations(folderContext, undefined, true);
});

suiteTeardown(async () => {
await settingsTeardown();
await vscode.commands.executeCommand(Workbench.ACTION_CLOSEALLEDITORS);
});

test("Swift: Run Build", async () => {
// A breakpoint will have not effect on the Run command.
vscode.debug.addBreakpoints(breakpoints);

const result = await vscode.commands.executeCommand(Commands.RUN);
expect(result).to.be.true;

vscode.debug.removeBreakpoints(breakpoints);
});

test("Swift: Clean Build", async () => {
const buildPath = path.join(folderContext.folder.fsPath, ".build");
const beforeItemCount = fs.readdirSync(buildPath).length;

const result = await vscode.commands.executeCommand(Commands.CLEAN_BUILD);
expect(result).to.be.true;

const afterItemCount = fs.readdirSync(buildPath).length;
// This test will run in order after the Swift: Run Build test,
// where .build folder is going to be filled with built artifacts.
// After executing the clean command the build directory is guranteed to have less entry.
expect(afterItemCount).to.be.lessThan(beforeItemCount);
});

test("Swift: Debug Build @slow", async () => {
vscode.debug.addBreakpoints(breakpoints);
// Promise used to indicate we hit the break point.
// NB: "stopped" is the exact command when debuggee has stopped due to break point,
// but "stackTrace" is the deterministic sync point we will use to make sure we can execute continue
const bpPromise = waitForDebugAdapterCommand(
"Debug PackageExe (defaultPackage)",
"stackTrace",
workspaceContext
);

const result = vscode.commands.executeCommand(Commands.DEBUG);
expect(result).to.eventually.be.true;

await bpPromise.then(() => continueSession());
vscode.debug.removeBreakpoints(breakpoints);
});
});
3 changes: 2 additions & 1 deletion test/integration-tests/editor/CommentCompletion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import * as assert from "assert";
import * as vscode from "vscode";
import { CommentCompletionProviders } from "../../../src/editor/CommentCompletion";
import { Workbench } from "../../../src/utilities/commands";

suite("CommentCompletion Test Suite", () => {
let document: vscode.TextDocument | undefined;
Expand All @@ -31,7 +32,7 @@ suite("CommentCompletion Test Suite", () => {

if (editor && document) {
await vscode.window.showTextDocument(document, editor.viewColumn);
await vscode.commands.executeCommand("workbench.action.closeActiveEditor");
await vscode.commands.executeCommand(Workbench.ACTION_CLOSEALLEDITORS);
}

provider.dispose();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
eventPromise,
gatherTests,
runTest,
SettingsMap,
setupTestExplorerTest,
waitForTestExplorerReady,
} from "./utilities";
Expand Down Expand Up @@ -51,7 +52,7 @@ suite("Test Explorer Suite", function () {
let testExplorer: TestExplorer;

suite("Debugging", function () {
let settingsTeardown: () => void;
let settingsTeardown: () => Promise<SettingsMap>;

async function runXCTest() {
const suiteId = "PackageTests.PassingXCTestSuite";
Expand Down
6 changes: 2 additions & 4 deletions test/integration-tests/testexplorer/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ export type SettingsMap = { [key: string]: unknown };
* "section.name" format, and the value is the new setting value.
* @returns A function that, when called, resets the settings back to their original values.
*/
export async function updateSettings(settings: SettingsMap): Promise<() => Promise<void>> {
export async function updateSettings(settings: SettingsMap): Promise<() => Promise<SettingsMap>> {
const applySettings = async (settings: SettingsMap) => {
const savedOriginalSettings: SettingsMap = {};
Object.keys(settings).forEach(async setting => {
Expand Down Expand Up @@ -224,9 +224,7 @@ export async function updateSettings(settings: SettingsMap): Promise<() => Promi
const savedOriginalSettings = await applySettings(settings);

// Clients call the callback to reset updated settings to their original value
return async () => {
await applySettings(savedOriginalSettings);
};
return async () => await applySettings(savedOriginalSettings);
}

/**
Expand Down
3 changes: 2 additions & 1 deletion test/unit-tests/ui/ReloadExtension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { expect } from "chai";
import { mockGlobalObject } from "../../MockUtils";
import * as vscode from "vscode";
import { showReloadExtensionNotification } from "../../../src/ui/ReloadExtension";
import { Workbench } from "../../../src/utilities/commands";

suite("showReloadExtensionNotification()", async function () {
const mockedVSCodeWindow = mockGlobalObject(vscode, "window");
Expand All @@ -38,7 +39,7 @@ suite("showReloadExtensionNotification()", async function () {
await showReloadExtensionNotification("Want to reload?");

expect(mockedVSCodeCommands.executeCommand).to.have.been.calledOnceWithExactly(
"workbench.action.reloadWindow"
Workbench.ACTION_RELOADWINDOW
);
});

Expand Down
Loading

0 comments on commit d6590e8

Please sign in to comment.