Skip to content

Commit 1ea79a9

Browse files
authored
Simpler test activation/deactivation in tests (#1214)
Favour using the new `activateExtensionForSuite` and `activateExtensionForTest` methods, which automatically deactivate the extension when the suite or test completes. These methods take optional setup/teardown methods that run on suite/test setup/teardown. If the setup method returns a promise, this is run automatically on teardown. This is useful for setup methods that modify settings, as they can now return the promise directly from `updateSettings` and any modified settings will be reverted automatically. This also prints any logs captured to the output channel if a suite/test setup/teardown method fails with an error, which was being silently lost before.
1 parent 075db6f commit 1ea79a9

23 files changed

+422
-261
lines changed

.vscode/launch.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
"${workspaceFolder}/dist/**/*.js"
3434
],
3535
"env": {
36-
"VSCODE_DEBUG": "1"
36+
"VSCODE_DEBUG": "1",
37+
"VSCODE_TEST": "1"
3738
},
3839
"preLaunchTask": "compile-tests"
3940
},

src/DiagnosticsManager.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export class DiagnosticsManager implements vscode.Disposable {
5959
private diagnosticCollection: vscode.DiagnosticCollection =
6060
vscode.languages.createDiagnosticCollection("swift");
6161
private allDiagnostics: Map<string, vscode.Diagnostic[]> = new Map();
62+
private disposed = false;
6263

6364
constructor(context: WorkspaceContext) {
6465
this.onDidChangeConfigurationDisposible = vscode.workspace.onDidChangeConfiguration(e => {
@@ -114,6 +115,10 @@ export class DiagnosticsManager implements vscode.Disposable {
114115
sourcePredicate: SourcePredicate,
115116
newDiagnostics: vscode.Diagnostic[]
116117
): void {
118+
if (this.disposed) {
119+
return;
120+
}
121+
117122
const isFromSourceKit = !sourcePredicate(DiagnosticsManager.swiftc);
118123
// Is a descrepency between SourceKit-LSP and older versions
119124
// of Swift as to whether the first letter is capitalized or not,
@@ -230,6 +235,7 @@ export class DiagnosticsManager implements vscode.Disposable {
230235
}
231236

232237
dispose() {
238+
this.disposed = true;
233239
this.diagnosticCollection.dispose();
234240
this.onDidStartTaskDisposible.dispose();
235241
this.onDidChangeConfigurationDisposible.dispose();

src/WorkspaceContext.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,14 @@ export class WorkspaceContext implements vscode.Disposable {
171171
this.lastFocusUri = vscode.window.activeTextEditor?.document.uri;
172172
}
173173

174+
async stop() {
175+
try {
176+
await this.languageClientManager.stop();
177+
} catch {
178+
// ignore
179+
}
180+
}
181+
174182
dispose() {
175183
this.folders.forEach(f => f.dispose());
176184
this.folders.length = 0;

src/extension.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export interface Api {
4747
workspaceContext?: WorkspaceContext;
4848
outputChannel: SwiftOutputChannel;
4949
activate(): Promise<Api>;
50-
deactivate(): void;
50+
deactivate(): Promise<void>;
5151
}
5252

5353
/**
@@ -114,7 +114,10 @@ export async function activate(context: vscode.ExtensionContext): Promise<Api> {
114114
workspaceContext: undefined,
115115
outputChannel,
116116
activate: () => activate(context),
117-
deactivate: () => deactivate(context),
117+
deactivate: async () => {
118+
await workspaceContext.stop();
119+
await deactivate(context);
120+
},
118121
};
119122
}
120123

@@ -257,7 +260,10 @@ export async function activate(context: vscode.ExtensionContext): Promise<Api> {
257260
workspaceContext,
258261
outputChannel,
259262
activate: () => activate(context),
260-
deactivate: () => deactivate(context),
263+
deactivate: async () => {
264+
await workspaceContext.stop();
265+
await deactivate(context);
266+
},
261267
};
262268
} catch (error) {
263269
const errorMessage = getErrorDescription(error);
@@ -269,7 +275,6 @@ export async function activate(context: vscode.ExtensionContext): Promise<Api> {
269275
}
270276

271277
async function deactivate(context: vscode.ExtensionContext): Promise<void> {
272-
console.debug("Deactivating Swift for Visual Studio Code...");
273278
contextKeys.isActivated = false;
274279
context.subscriptions.forEach(subscription => subscription.dispose());
275280
context.subscriptions.length = 0;

src/sourcekit-lsp/LanguageClientManager.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,14 +245,21 @@ export class LanguageClientManager {
245245
this.cancellationToken = new vscode.CancellationTokenSource();
246246
}
247247

248+
// The language client stops asnyhronously, so we need to wait for it to stop
249+
// instead of doing it in dispose, which must be synchronous.
250+
async stop() {
251+
if (this.languageClient && this.languageClient.state === langclient.State.Running) {
252+
await this.languageClient.dispose();
253+
}
254+
}
255+
248256
dispose() {
249257
this.cancellationToken?.cancel();
250258
this.cancellationToken?.dispose();
251259
this.legacyInlayHints?.dispose();
252260
this.peekDocuments?.dispose();
253261
this.getReferenceDocument?.dispose();
254262
this.subscriptions.forEach(item => item.dispose());
255-
this.languageClient?.stop();
256263
this.namedOutputChannels.forEach(channel => channel.dispose());
257264
}
258265

test/integration-tests/BackgroundCompilation.test.ts

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,30 +14,24 @@
1414

1515
import * as assert from "assert";
1616
import * as vscode from "vscode";
17-
import { beforeEach, afterEach } from "mocha";
1817
import { WorkspaceContext } from "../../src/WorkspaceContext";
1918
import { testAssetUri } from "../fixtures";
2019
import { waitForNoRunningTasks } from "../utilities";
2120
import { Workbench } from "../../src/utilities/commands";
22-
import { activateExtension, deactivateExtension, updateSettings } from "./utilities/testutilities";
21+
import { activateExtensionForTest, updateSettings } from "./utilities/testutilities";
2322

2423
suite("BackgroundCompilation Test Suite", () => {
2524
let workspaceContext: WorkspaceContext;
26-
let settingsTeardown: () => Promise<void>;
2725

28-
beforeEach(async function () {
29-
workspaceContext = await activateExtension(this.currentTest);
30-
31-
assert.notEqual(workspaceContext.folders.length, 0);
32-
await waitForNoRunningTasks();
33-
settingsTeardown = await updateSettings({
34-
"swift.backgroundCompilation": true,
35-
});
36-
});
37-
38-
afterEach(async () => {
39-
await settingsTeardown();
40-
await deactivateExtension();
26+
activateExtensionForTest({
27+
async setup(ctx) {
28+
workspaceContext = ctx;
29+
assert.notEqual(workspaceContext.folders.length, 0);
30+
await waitForNoRunningTasks();
31+
return await updateSettings({
32+
"swift.backgroundCompilation": true,
33+
});
34+
},
4135
});
4236

4337
suiteTeardown(async () => {

test/integration-tests/DiagnosticsManager.test.ts

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,7 @@ import { DiagnosticsManager } from "../../src/DiagnosticsManager";
2323
import { FolderContext } from "../../src/FolderContext";
2424
import { Version } from "../../src/utilities/version";
2525
import { Workbench } from "../../src/utilities/commands";
26-
import {
27-
activateExtension,
28-
deactivateExtension,
29-
folderInRootWorkspace,
30-
} from "./utilities/testutilities";
26+
import { activateExtensionForSuite, folderInRootWorkspace } from "./utilities/testutilities";
3127

3228
const waitForDiagnostics = (uris: vscode.Uri[], allowEmpty: boolean = true) =>
3329
new Promise<void>(res =>
@@ -95,26 +91,26 @@ suite("DiagnosticsManager Test Suite", async function () {
9591
let cppUri: vscode.Uri;
9692
let cppHeaderUri: vscode.Uri;
9793

98-
suiteSetup(async function () {
99-
workspaceContext = await activateExtension(this.currentTest);
100-
toolchain = workspaceContext.toolchain;
101-
workspaceFolder = testAssetWorkspaceFolder("diagnostics");
102-
cWorkspaceFolder = testAssetWorkspaceFolder("diagnosticsC");
103-
cppWorkspaceFolder = testAssetWorkspaceFolder("diagnosticsCpp");
104-
folderContext = await folderInRootWorkspace("diagnostics", workspaceContext);
105-
cFolderContext = await folderInRootWorkspace("diagnosticsC", workspaceContext);
106-
cppFolderContext = await folderInRootWorkspace("diagnosticsCpp", workspaceContext);
107-
mainUri = vscode.Uri.file(`${workspaceFolder.uri.path}/Sources/main.swift`);
108-
funcUri = vscode.Uri.file(`${workspaceFolder.uri.path}/Sources/func.swift`);
109-
cUri = vscode.Uri.file(`${cWorkspaceFolder.uri.path}/Sources/MyPoint/MyPoint.c`);
110-
cppUri = vscode.Uri.file(`${cppWorkspaceFolder.uri.path}/Sources/MyPoint/MyPoint.cpp`);
111-
cppHeaderUri = vscode.Uri.file(
112-
`${cppWorkspaceFolder.uri.path}/Sources/MyPoint/include/MyPoint.h`
113-
);
114-
});
115-
116-
suiteTeardown(async () => {
117-
await deactivateExtension();
94+
activateExtensionForSuite({
95+
async setup(ctx) {
96+
this.timeout(60000);
97+
98+
workspaceContext = ctx;
99+
toolchain = workspaceContext.toolchain;
100+
workspaceFolder = testAssetWorkspaceFolder("diagnostics");
101+
cWorkspaceFolder = testAssetWorkspaceFolder("diagnosticsC");
102+
cppWorkspaceFolder = testAssetWorkspaceFolder("diagnosticsCpp");
103+
folderContext = await folderInRootWorkspace("diagnostics", workspaceContext);
104+
cFolderContext = await folderInRootWorkspace("diagnosticsC", workspaceContext);
105+
cppFolderContext = await folderInRootWorkspace("diagnosticsCpp", workspaceContext);
106+
mainUri = vscode.Uri.file(`${workspaceFolder.uri.path}/Sources/main.swift`);
107+
funcUri = vscode.Uri.file(`${workspaceFolder.uri.path}/Sources/func.swift`);
108+
cUri = vscode.Uri.file(`${cWorkspaceFolder.uri.path}/Sources/MyPoint/MyPoint.c`);
109+
cppUri = vscode.Uri.file(`${cppWorkspaceFolder.uri.path}/Sources/MyPoint/MyPoint.cpp`);
110+
cppHeaderUri = vscode.Uri.file(
111+
`${cppWorkspaceFolder.uri.path}/Sources/MyPoint/include/MyPoint.h`
112+
);
113+
},
118114
});
119115

120116
suite("Parse diagnostics", async () => {

test/integration-tests/ExtensionActivation.test.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,13 @@
1515
import * as vscode from "vscode";
1616
import * as assert from "assert";
1717
import { afterEach } from "mocha";
18-
import { activateExtension, deactivateExtension } from "./utilities/testutilities";
18+
import {
19+
activateExtension,
20+
activateExtensionForSuite,
21+
activateExtensionForTest,
22+
deactivateExtension,
23+
} from "./utilities/testutilities";
24+
import { WorkspaceContext } from "../../src/WorkspaceContext";
1925

2026
suite("Extension Activation/Deactivation Tests", () => {
2127
suite("Extension Activation", () => {
@@ -53,4 +59,42 @@ suite("Extension Activation/Deactivation Tests", () => {
5359
assert(ext);
5460
assert.equal(workspaceContext.subscriptions.length, 0);
5561
});
62+
63+
suite("Extension Activation per suite", () => {
64+
let workspaceContext: WorkspaceContext | undefined;
65+
let capturedWorkspaceContext: WorkspaceContext | undefined;
66+
activateExtensionForSuite({
67+
async setup(ctx) {
68+
workspaceContext = ctx;
69+
},
70+
});
71+
72+
test("Assert workspace context is created", () => {
73+
assert.ok(workspaceContext);
74+
capturedWorkspaceContext = workspaceContext;
75+
});
76+
77+
test("Assert workspace context is not recreated", () => {
78+
assert.strictEqual(workspaceContext, capturedWorkspaceContext);
79+
});
80+
});
81+
82+
suite("Extension activation per test", () => {
83+
let workspaceContext: WorkspaceContext | undefined;
84+
let capturedWorkspaceContext: WorkspaceContext | undefined;
85+
activateExtensionForTest({
86+
async setup(ctx) {
87+
workspaceContext = ctx;
88+
},
89+
});
90+
91+
test("Assert workspace context is created", () => {
92+
assert.ok(workspaceContext);
93+
capturedWorkspaceContext = workspaceContext;
94+
});
95+
96+
test("Assert workspace context is recreated per test", () => {
97+
assert.notStrictEqual(workspaceContext, capturedWorkspaceContext);
98+
});
99+
});
56100
});

test/integration-tests/WorkspaceContext.test.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,16 @@ import { FolderOperation, WorkspaceContext } from "../../src/WorkspaceContext";
1919
import { createBuildAllTask } from "../../src/tasks/SwiftTaskProvider";
2020
import { Version } from "../../src/utilities/version";
2121
import { SwiftExecution } from "../../src/tasks/SwiftExecution";
22-
import { activateExtension, deactivateExtension } from "./utilities/testutilities";
22+
import { activateExtensionForSuite } from "./utilities/testutilities";
2323

2424
suite("WorkspaceContext Test Suite", () => {
2525
let workspaceContext: WorkspaceContext;
2626
const packageFolder: vscode.Uri = testAssetUri("defaultPackage");
2727

28-
suiteSetup(async function () {
29-
workspaceContext = await activateExtension();
30-
});
31-
32-
suiteTeardown(async () => {
33-
await deactivateExtension();
28+
activateExtensionForSuite({
29+
async setup(ctx) {
30+
workspaceContext = ctx;
31+
},
3432
});
3533

3634
suite("Folder Events", () => {

test/integration-tests/commands/build.test.ts

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,42 +25,40 @@ import { makeDebugConfigurations } from "../../../src/debugger/launch";
2525
import { Workbench } from "../../../src/utilities/commands";
2626
import { continueSession, waitForDebugAdapterCommand } from "../../utilities/debug";
2727
import {
28-
activateExtension,
29-
deactivateExtension,
28+
activateExtensionForSuite,
3029
folderInRootWorkspace,
3130
updateSettings,
3231
} from "../utilities/testutilities";
3332

3433
suite("Build Commands", function () {
3534
let folderContext: FolderContext;
3635
let workspaceContext: WorkspaceContext;
37-
let settingsTeardown: () => Promise<void>;
3836
let buildPath: string;
3937
const uri = testAssetUri("defaultPackage/Sources/PackageExe/main.swift");
4038
const breakpoints = [
4139
new vscode.SourceBreakpoint(new vscode.Location(uri, new vscode.Position(2, 0))),
4240
];
4341

44-
suiteSetup(async function () {
45-
workspaceContext = await activateExtension();
46-
await waitForNoRunningTasks();
47-
folderContext = await folderInRootWorkspace("defaultPackage", workspaceContext);
48-
buildPath = path.join(folderContext.folder.fsPath, ".build");
49-
await workspaceContext.focusFolder(folderContext);
50-
await vscode.window.showTextDocument(uri);
51-
settingsTeardown = await updateSettings({
52-
"swift.autoGenerateLaunchConfigurations": true,
53-
});
54-
await makeDebugConfigurations(folderContext, undefined, true);
42+
activateExtensionForSuite({
43+
async setup(ctx) {
44+
workspaceContext = ctx;
45+
await waitForNoRunningTasks();
46+
folderContext = await folderInRootWorkspace("defaultPackage", workspaceContext);
47+
buildPath = path.join(folderContext.folder.fsPath, ".build");
48+
await workspaceContext.focusFolder(folderContext);
49+
await vscode.window.showTextDocument(uri);
50+
const settingsTeardown = await updateSettings({
51+
"swift.autoGenerateLaunchConfigurations": true,
52+
});
53+
await makeDebugConfigurations(folderContext, undefined, true);
54+
return settingsTeardown;
55+
},
56+
async teardown() {
57+
await vscode.commands.executeCommand(Workbench.ACTION_CLOSEALLEDITORS);
58+
},
5559
});
5660

57-
suiteTeardown(async () => {
58-
await settingsTeardown();
59-
await vscode.commands.executeCommand(Workbench.ACTION_CLOSEALLEDITORS);
60-
await deactivateExtension();
61-
});
62-
63-
teardown(() => {
61+
teardown(async () => {
6462
// Remove the build directory after each test case
6563
if (fs.existsSync(buildPath)) {
6664
fs.rmSync(buildPath, { recursive: true, force: true });

0 commit comments

Comments
 (0)