From 5376bba5f9b8b8cf5557c7664ef2f5f93d8edb01 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Wed, 15 Sep 2021 14:58:22 -0700 Subject: [PATCH 01/13] Fix relative paths (again) This are hard to get right, and harder to test. --- src/features/Examples.ts | 2 +- src/features/PesterTests.ts | 2 +- src/settings.ts | 4 +++- src/utils.ts | 2 +- test/features/CustomViews.test.ts | 4 ++-- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/features/Examples.ts b/src/features/Examples.ts index 94007b5d05..ef8585799b 100644 --- a/src/features/Examples.ts +++ b/src/features/Examples.ts @@ -9,7 +9,7 @@ export class ExamplesFeature implements vscode.Disposable { private examplesPath: string; constructor() { - this.examplesPath = path.resolve(__dirname, "../../examples"); + this.examplesPath = path.resolve(__dirname, "../examples"); this.command = vscode.commands.registerCommand("PowerShell.OpenExamplesFolder", () => { vscode.commands.executeCommand( "vscode.openFolder", diff --git a/src/features/PesterTests.ts b/src/features/PesterTests.ts index 35d753f3b1..eaf71691e9 100644 --- a/src/features/PesterTests.ts +++ b/src/features/PesterTests.ts @@ -18,7 +18,7 @@ export class PesterTestsFeature implements vscode.Disposable { private invokePesterStubScriptPath: string; constructor(private sessionManager: SessionManager) { - this.invokePesterStubScriptPath = path.resolve(__dirname, "../../modules/PowerShellEditorServices/InvokePesterStub.ps1"); + this.invokePesterStubScriptPath = path.resolve(__dirname, "../modules/PowerShellEditorServices/InvokePesterStub.ps1"); // File context-menu command - Run Pester Tests this.command = vscode.commands.registerCommand( diff --git a/src/settings.ts b/src/settings.ts index 1403512e95..32f5aa0d6e 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -157,6 +157,8 @@ export function load(): ISettings { const defaultDeveloperSettings: IDeveloperSettings = { featureFlags: [], + // From `/out/main.js` we go to the directory before and + // then into the other repo. bundledModulesPath: "../../PowerShellEditorServices/module", editorServicesLogLevel: "Normal", editorServicesWaitForDebugger: false, @@ -234,7 +236,7 @@ export function load(): ISettings { promptToUpdatePackageManagement: configuration.get("promptToUpdatePackageManagement", true), bundledModulesPath: - "../modules", + "../modules", // Because the extension is always at `/out/main.js` useX86Host: configuration.get("useX86Host", false), enableProfileLoading: diff --git a/src/utils.ts b/src/utils.ts index c5d3a880f9..9f333bfaec 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -45,7 +45,7 @@ export interface IEditorServicesSessionDetails { export type IReadSessionFileCallback = (details: IEditorServicesSessionDetails) => void; -const sessionsFolder = path.resolve(__dirname, "..", "..", "sessions/"); +const sessionsFolder = path.resolve(__dirname, "../sessions"); const sessionFilePathPrefix = path.resolve(sessionsFolder, "PSES-VSCode-" + process.env.VSCODE_PID); // Create the sessions path if it doesn't exist already diff --git a/test/features/CustomViews.test.ts b/test/features/CustomViews.test.ts index 57b44075a1..48816c9aaa 100644 --- a/test/features/CustomViews.test.ts +++ b/test/features/CustomViews.test.ts @@ -70,7 +70,7 @@ hello content: "console.log('asdf');", }, { - fileName: "../../testCustomViews.js", + fileName: "../testCustomViews.js", content: "console.log('asdf');", }, ], @@ -78,7 +78,7 @@ hello expectedHtmlString: ` hello - + `, }, From 486eb0b99086918247ce553af2ffc71d4cc20428 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Thu, 16 Sep 2021 15:54:52 -0700 Subject: [PATCH 02/13] Add regression tests for paths and `RunPesterTestsFromFile` command --- src/features/Examples.ts | 5 +++++ src/features/PesterTests.ts | 28 ++++++++++++++++--------- test/core/paths.test.ts | 34 +++++++++++++++++++++++++++++++ test/features/ExternalApi.test.ts | 2 +- test/features/RunCode.test.ts | 10 +++++++++ 5 files changed, 68 insertions(+), 11 deletions(-) create mode 100644 test/core/paths.test.ts diff --git a/src/features/Examples.ts b/src/features/Examples.ts index ef8585799b..cf56d6d0d8 100644 --- a/src/features/Examples.ts +++ b/src/features/Examples.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +import * as fs from "fs"; import path = require("path"); import vscode = require("vscode"); @@ -15,6 +16,10 @@ export class ExamplesFeature implements vscode.Disposable { "vscode.openFolder", vscode.Uri.file(this.examplesPath), true); + + // Return existence of the path for testing. The `vscode.openFolder` + // command should do this, but doesn't (yet). + return fs.existsSync(this.examplesPath) }); } diff --git a/src/features/PesterTests.ts b/src/features/PesterTests.ts index eaf71691e9..4d73dd8352 100644 --- a/src/features/PesterTests.ts +++ b/src/features/PesterTests.ts @@ -2,6 +2,7 @@ // Licensed under the MIT License. import * as path from "path"; +import * as fs from "fs"; import vscode = require("vscode"); import { SessionManager } from "../session"; import Settings = require("../settings"); @@ -24,19 +25,19 @@ export class PesterTestsFeature implements vscode.Disposable { this.command = vscode.commands.registerCommand( "PowerShell.RunPesterTestsFromFile", (fileUri) => { - this.launchAllTestsInActiveEditor(LaunchType.Run, fileUri); + return this.launchAllTestsInActiveEditor(LaunchType.Run, fileUri); }); // File context-menu command - Debug Pester Tests this.command = vscode.commands.registerCommand( "PowerShell.DebugPesterTestsFromFile", (fileUri) => { - this.launchAllTestsInActiveEditor(LaunchType.Debug, fileUri); + return this.launchAllTestsInActiveEditor(LaunchType.Debug, fileUri); }); // This command is provided for usage by PowerShellEditorServices (PSES) only this.command = vscode.commands.registerCommand( "PowerShell.RunPesterTests", (uriString, runInDebugger, describeBlockName?, describeBlockLineNumber?, outputPath?) => { - this.launchTests(uriString, runInDebugger, describeBlockName, describeBlockLineNumber, outputPath); + return this.launchTests(uriString, runInDebugger, describeBlockName, describeBlockLineNumber, outputPath); }); } @@ -44,10 +45,13 @@ export class PesterTestsFeature implements vscode.Disposable { this.command.dispose(); } - private launchAllTestsInActiveEditor(launchType: LaunchType, fileUri: vscode.Uri) { + private async launchAllTestsInActiveEditor( + launchType: LaunchType, + fileUri: vscode.Uri): Promise { + const uriString = (fileUri || vscode.window.activeTextEditor.document.uri).toString(); const launchConfig = this.createLaunchConfig(uriString, launchType); - this.launch(launchConfig); + return this.launch(launchConfig); } private async launchTests( @@ -55,11 +59,11 @@ export class PesterTestsFeature implements vscode.Disposable { runInDebugger: boolean, describeBlockName?: string, describeBlockLineNumber?: number, - outputPath?: string) { + outputPath?: string): Promise { const launchType = runInDebugger ? LaunchType.Debug : LaunchType.Run; const launchConfig = this.createLaunchConfig(uriString, launchType, describeBlockName, describeBlockLineNumber, outputPath); - this.launch(launchConfig); + return this.launch(launchConfig); } private createLaunchConfig( @@ -126,9 +130,9 @@ export class PesterTestsFeature implements vscode.Disposable { return launchConfig; } - private launch(launchConfig) { + private async launch(launchConfig): Promise { // Create or show the interactive console - // TODO #367: Check if "newSession" mode is configured + // TODO: #367 Check if "newSession" mode is configured vscode.commands.executeCommand("PowerShell.ShowSessionConsole", true); // Write out temporary debug session file @@ -137,6 +141,10 @@ export class PesterTestsFeature implements vscode.Disposable { this.sessionManager.getSessionDetails()); // TODO: Update to handle multiple root workspaces. - vscode.debug.startDebugging(vscode.workspace.workspaceFolders[0], launchConfig); + // + // Ensure the necessary script exists (for testing). The debugger will + // start regardless, but we also pass its success along. + return fs.existsSync(this.invokePesterStubScriptPath) + && vscode.debug.startDebugging(vscode.workspace.workspaceFolders[0], launchConfig); } } diff --git a/test/core/paths.test.ts b/test/core/paths.test.ts new file mode 100644 index 0000000000..dbe3f81668 --- /dev/null +++ b/test/core/paths.test.ts @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import * as assert from "assert"; +import * as fs from "fs"; +import * as path from "path"; +import * as vscode from "vscode"; +import { before } from "mocha"; + +// This lets us test the rest of our path assumptions against the baseline of +// this test file existing at `/out/test/core/paths.test.ts`. +const rootPath = path.resolve(__dirname, "../../../") +// tslint:disable-next-line: no-var-requires +const packageJSON: any = require(path.resolve(rootPath, "package.json")); +const extensionId = `${packageJSON.publisher}.${packageJSON.name}`; + +suite("Path assumptions", () => { + before(async () => { + const extension = vscode.extensions.getExtension(extensionId); + if (!extension.isActive) { await extension.activate(); } + }); + + test("The examples folder can be opened (and exists)", async () => { + assert(await vscode.commands.executeCommand("PowerShell.OpenExamplesFolder")); + }); + + test("The session folder is created in the right place", async () => { + assert(fs.existsSync(path.resolve(rootPath, "sessions"))); + }); + + test("The logs folder is created in the right place", async () => { + assert(fs.existsSync(path.resolve(rootPath, "logs"))); + }); +}); diff --git a/test/features/ExternalApi.test.ts b/test/features/ExternalApi.test.ts index 262f1b207f..920541b9f8 100644 --- a/test/features/ExternalApi.test.ts +++ b/test/features/ExternalApi.test.ts @@ -105,6 +105,6 @@ suite("ExternalApi feature - Other APIs", () => { assert.notStrictEqual(versionDetails.version, ""); assert.notStrictEqual(versionDetails.version, null); - // Start up can take some time... so set the time out to 30s + // Start up can take some time...so set the timeout to 30 seconds. }).timeout(30000); }); diff --git a/test/features/RunCode.test.ts b/test/features/RunCode.test.ts index 90745f3090..e2a7d92220 100644 --- a/test/features/RunCode.test.ts +++ b/test/features/RunCode.test.ts @@ -2,6 +2,8 @@ // Licensed under the MIT License. import * as assert from "assert"; +import * as fs from "fs"; +import * as path from "path"; import rewire = require("rewire"); import vscode = require("vscode"); @@ -35,4 +37,12 @@ suite("RunCode tests", () => { assert.deepEqual(actual, expected); }); + + test("Can run Pester tests from file", async () => { + const pesterTests = path.resolve(__dirname, "../../../examples/Tests/SampleModule.Tests.ps1"); + assert(fs.existsSync(pesterTests)); + await vscode.commands.executeCommand("vscode.open", vscode.Uri.file(pesterTests)); + assert(await vscode.commands.executeCommand("PowerShell.RunPesterTestsFromFile")); + // Start up can take some time...so set the timeout to 30 seconds. + }).timeout(30000); }); From ef6e4685e9b291187f642dbb75633964f24a9452 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Tue, 21 Sep 2021 14:32:37 -0700 Subject: [PATCH 03/13] Use `vscode.workspace.fs.stat` to check file existence asynchronously --- src/features/Examples.ts | 14 +++++--------- src/features/PesterTests.ts | 3 +-- src/utils.ts | 21 ++++++++++++++++++++- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/features/Examples.ts b/src/features/Examples.ts index cf56d6d0d8..96861ac9f5 100644 --- a/src/features/Examples.ts +++ b/src/features/Examples.ts @@ -1,25 +1,21 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import * as fs from "fs"; import path = require("path"); +import utils = require("../utils") import vscode = require("vscode"); export class ExamplesFeature implements vscode.Disposable { private command: vscode.Disposable; - private examplesPath: string; + private examplesPath: vscode.Uri; constructor() { - this.examplesPath = path.resolve(__dirname, "../examples"); + this.examplesPath = vscode.Uri.file(path.resolve(__dirname, "../examples")); this.command = vscode.commands.registerCommand("PowerShell.OpenExamplesFolder", () => { - vscode.commands.executeCommand( - "vscode.openFolder", - vscode.Uri.file(this.examplesPath), - true); - + vscode.commands.executeCommand("vscode.openFolder", this.examplesPath, true); // Return existence of the path for testing. The `vscode.openFolder` // command should do this, but doesn't (yet). - return fs.existsSync(this.examplesPath) + return utils.fileExists(this.examplesPath); }); } diff --git a/src/features/PesterTests.ts b/src/features/PesterTests.ts index 4d73dd8352..bd11d319eb 100644 --- a/src/features/PesterTests.ts +++ b/src/features/PesterTests.ts @@ -2,7 +2,6 @@ // Licensed under the MIT License. import * as path from "path"; -import * as fs from "fs"; import vscode = require("vscode"); import { SessionManager } from "../session"; import Settings = require("../settings"); @@ -144,7 +143,7 @@ export class PesterTestsFeature implements vscode.Disposable { // // Ensure the necessary script exists (for testing). The debugger will // start regardless, but we also pass its success along. - return fs.existsSync(this.invokePesterStubScriptPath) + return utils.fileExists(this.invokePesterStubScriptPath) && vscode.debug.startDebugging(vscode.workspace.workspaceFolders[0], launchConfig); } } diff --git a/src/utils.ts b/src/utils.ts index 9f333bfaec..e4c89b2bbe 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -6,12 +6,14 @@ import fs = require("fs"); import os = require("os"); import path = require("path"); +import vscode = require("vscode"); export const PowerShellLanguageId = "powershell"; -export function ensurePathExists(targetPath: string) { +export function ensurePathExists(targetPath: string): void { // Ensure that the path exists try { + // TODO: Use vscode.workspace.fs fs.mkdirSync(targetPath); } catch (e) { // If the exception isn't to indicate that the folder exists already, rethrow it. @@ -21,6 +23,23 @@ export function ensurePathExists(targetPath: string) { } } +// Check that the file exists in an asynchronous manner that relies solely on the VS Code API, not Node's fs library. +export async function fileExists(targetPath: string | vscode.Uri): Promise { + try { + await vscode.workspace.fs.stat( + targetPath instanceof vscode.Uri + ? targetPath + : vscode.Uri.file(targetPath)); + return true; + } catch (e) { + if (e instanceof vscode.FileSystemError.FileNotFound) { + return false; + } + throw e; + } + +} + export function getPipePath(pipeName: string) { if (os.platform() === "win32") { return "\\\\.\\pipe\\" + pipeName; From ede35f653d6fd19094b07eb32b73caf01be345ea Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Thu, 23 Sep 2021 12:20:47 -0700 Subject: [PATCH 04/13] Print Pester versions in CI --- .vsts-ci/templates/ci-general.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.vsts-ci/templates/ci-general.yml b/.vsts-ci/templates/ci-general.yml index 3bd642c534..1419e4d3fa 100644 --- a/.vsts-ci/templates/ci-general.yml +++ b/.vsts-ci/templates/ci-general.yml @@ -46,6 +46,7 @@ steps: inputs: targetType: inline script: | + Get-Module -ListAvailable Pester Install-Module InvokeBuild -Scope CurrentUser -Force Invoke-Build Write-Host "##vso[task.setvariable variable=vsixPath]$(Resolve-Path powershell-*.vsix)" From f5c6ed4c1d1136ae6a9d8b4e1f25f98819533730 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Wed, 22 Sep 2021 12:41:52 -0700 Subject: [PATCH 05/13] Print error when tests fail --- test/runTests.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/runTests.ts b/test/runTests.ts index 6bff341563..b449ae7cc4 100644 --- a/test/runTests.ts +++ b/test/runTests.ts @@ -29,7 +29,7 @@ async function main() { }); } catch (err) { // tslint:disable-next-line:no-console - console.error("Failed to run tests"); + console.error(`Failed to run tests: ${err}`); process.exit(1); } } From 36946bdd40f8003e90c025bafafae0249355caf7 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Wed, 22 Sep 2021 13:10:31 -0700 Subject: [PATCH 06/13] Create and use `test/utils.ensureExtensionIsActivated()` Cleans up some repetitive code and makes tests more stable. --- test/core/paths.test.ts | 17 ++++----------- test/features/ExternalApi.test.ts | 30 ++++++++------------------ test/features/ISECompatibility.test.ts | 6 ++++++ test/features/RunCode.test.ts | 4 ++++ test/utils.ts | 20 +++++++++++++++++ 5 files changed, 43 insertions(+), 34 deletions(-) create mode 100644 test/utils.ts diff --git a/test/core/paths.test.ts b/test/core/paths.test.ts index dbe3f81668..6772f5e6d2 100644 --- a/test/core/paths.test.ts +++ b/test/core/paths.test.ts @@ -6,29 +6,20 @@ import * as fs from "fs"; import * as path from "path"; import * as vscode from "vscode"; import { before } from "mocha"; - -// This lets us test the rest of our path assumptions against the baseline of -// this test file existing at `/out/test/core/paths.test.ts`. -const rootPath = path.resolve(__dirname, "../../../") -// tslint:disable-next-line: no-var-requires -const packageJSON: any = require(path.resolve(rootPath, "package.json")); -const extensionId = `${packageJSON.publisher}.${packageJSON.name}`; +import utils = require("../utils"); suite("Path assumptions", () => { - before(async () => { - const extension = vscode.extensions.getExtension(extensionId); - if (!extension.isActive) { await extension.activate(); } - }); + before(async () => { await utils.ensureExtensionIsActivated(); }); test("The examples folder can be opened (and exists)", async () => { assert(await vscode.commands.executeCommand("PowerShell.OpenExamplesFolder")); }); test("The session folder is created in the right place", async () => { - assert(fs.existsSync(path.resolve(rootPath, "sessions"))); + assert(fs.existsSync(path.resolve(utils.rootPath, "sessions"))); }); test("The logs folder is created in the right place", async () => { - assert(fs.existsSync(path.resolve(rootPath, "logs"))); + assert(fs.existsSync(path.resolve(utils.rootPath, "logs"))); }); }); diff --git a/test/features/ExternalApi.test.ts b/test/features/ExternalApi.test.ts index 920541b9f8..285bddf7ad 100644 --- a/test/features/ExternalApi.test.ts +++ b/test/features/ExternalApi.test.ts @@ -2,27 +2,19 @@ // Licensed under the MIT License. import * as assert from "assert"; -import * as vscode from "vscode"; import { before, beforeEach, afterEach } from "mocha"; +import utils = require("../utils"); import { IExternalPowerShellDetails, IPowerShellExtensionClient } from "../../src/features/ExternalApi"; -// tslint:disable-next-line: no-var-requires -const PackageJSON: any = require("../../../package.json"); -const testExtensionId = `${PackageJSON.publisher}.${PackageJSON.name}`; - suite("ExternalApi feature - Registration API", () => { let powerShellExtensionClient: IPowerShellExtensionClient; before(async () => { - const powershellExtension = vscode.extensions.getExtension(testExtensionId); - if (!powershellExtension.isActive) { - powerShellExtensionClient = await powershellExtension.activate(); - return; - } + const powershellExtension = await utils.ensureExtensionIsActivated(); powerShellExtensionClient = powershellExtension!.exports as IPowerShellExtensionClient; }); test("It can register and unregister an extension", () => { - const sessionId: string = powerShellExtensionClient.registerExternalExtension(testExtensionId); + const sessionId: string = powerShellExtensionClient.registerExternalExtension(utils.extensionId); assert.notStrictEqual(sessionId , ""); assert.notStrictEqual(sessionId , null); assert.strictEqual( @@ -31,7 +23,7 @@ suite("ExternalApi feature - Registration API", () => { }); test("It can register and unregister an extension with a version", () => { - const sessionId: string = powerShellExtensionClient.registerExternalExtension(testExtensionId, "v2"); + const sessionId: string = powerShellExtensionClient.registerExternalExtension(utils.extensionId, "v2"); assert.notStrictEqual(sessionId , ""); assert.notStrictEqual(sessionId , null); assert.strictEqual( @@ -48,12 +40,12 @@ suite("ExternalApi feature - Registration API", () => { }); test("It can't register the same extension twice", async () => { - const sessionId: string = powerShellExtensionClient.registerExternalExtension(testExtensionId); + const sessionId: string = powerShellExtensionClient.registerExternalExtension(utils.extensionId); try { assert.throws( - () => powerShellExtensionClient.registerExternalExtension(testExtensionId), + () => powerShellExtensionClient.registerExternalExtension(utils.extensionId), { - message: `The extension '${testExtensionId}' is already registered.` + message: `The extension '${utils.extensionId}' is already registered.` }); } finally { powerShellExtensionClient.unregisterExternalExtension(sessionId); @@ -74,16 +66,12 @@ suite("ExternalApi feature - Other APIs", () => { let powerShellExtensionClient: IPowerShellExtensionClient; before(async () => { - const powershellExtension = vscode.extensions.getExtension(testExtensionId); - if (!powershellExtension.isActive) { - powerShellExtensionClient = await powershellExtension.activate(); - return; - } + const powershellExtension = await utils.ensureExtensionIsActivated(); powerShellExtensionClient = powershellExtension!.exports as IPowerShellExtensionClient; }); beforeEach(() => { - sessionId = powerShellExtensionClient.registerExternalExtension(testExtensionId); + sessionId = powerShellExtensionClient.registerExternalExtension(utils.extensionId); }); afterEach(() => { diff --git a/test/features/ISECompatibility.test.ts b/test/features/ISECompatibility.test.ts index c8843e93ba..8b5af7d7e4 100644 --- a/test/features/ISECompatibility.test.ts +++ b/test/features/ISECompatibility.test.ts @@ -3,9 +3,13 @@ import * as assert from "assert"; import * as vscode from "vscode"; +import { before } from "mocha"; import { ISECompatibilityFeature } from "../../src/features/ISECompatibility"; +import utils = require("../utils"); suite("ISECompatibility feature", () => { + before(async () => { await utils.ensureExtensionIsActivated(); } ); + test("It sets ISE Settings", async () => { await vscode.commands.executeCommand("PowerShell.EnableISEMode"); for (const iseSetting of ISECompatibilityFeature.settings) { @@ -13,6 +17,7 @@ suite("ISECompatibility feature", () => { assert.equal(currently, iseSetting.value); } }); + test("It unsets ISE Settings", async () => { // Change state to something that DisableISEMode will change await vscode.workspace.getConfiguration("workbench").update("colorTheme", "PowerShell ISE", true); @@ -24,6 +29,7 @@ suite("ISECompatibility feature", () => { assert.notEqual(currently, iseSetting.value); } }).timeout(10000); + test("It leaves Theme after being changed after enabling ISE Mode", async () => { await vscode.commands.executeCommand("PowerShell.EnableISEMode"); assert.equal(vscode.workspace.getConfiguration("workbench").get("colorTheme"), "PowerShell ISE"); diff --git a/test/features/RunCode.test.ts b/test/features/RunCode.test.ts index e2a7d92220..a7a405a9fa 100644 --- a/test/features/RunCode.test.ts +++ b/test/features/RunCode.test.ts @@ -4,8 +4,10 @@ import * as assert from "assert"; import * as fs from "fs"; import * as path from "path"; +import { before } from "mocha"; import rewire = require("rewire"); import vscode = require("vscode"); +import utils = require("../utils"); // Setup function that is not exported. const customViews = rewire("../../src/features/RunCode"); @@ -17,6 +19,8 @@ enum LaunchType { } suite("RunCode tests", () => { + before(async () => { await utils.ensureExtensionIsActivated(); } ); + test("Can create the launch config", () => { const commandToRun: string = "Invoke-Build"; const args: string[] = ["Clean"]; diff --git a/test/utils.ts b/test/utils.ts new file mode 100644 index 0000000000..dd3fd01624 --- /dev/null +++ b/test/utils.ts @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +"use strict"; + +import * as path from "path"; +import * as vscode from "vscode"; + +// This lets us test the rest of our path assumptions against the baseline of +// this test file existing at `/out/test/utils.js`. +export const rootPath = path.resolve(__dirname, "../../") +// tslint:disable-next-line: no-var-requires +const packageJSON: any = require(path.resolve(rootPath, "package.json")); +export const extensionId = `${packageJSON.publisher}.${packageJSON.name}`; + +export async function ensureExtensionIsActivated(): Promise> { + const extension = vscode.extensions.getExtension(extensionId); + if (!extension.isActive) { await extension.activate(); } + return extension; +} From ce085e6e9ee22e5703a96b201344498eee179273 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Wed, 22 Sep 2021 14:26:15 -0700 Subject: [PATCH 07/13] Update deprecated usages of `assert` module --- test/features/CustomViews.test.ts | 2 +- test/features/ISECompatibility.test.ts | 12 ++++++------ test/features/RunCode.test.ts | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/features/CustomViews.test.ts b/test/features/CustomViews.test.ts index 48816c9aaa..dc17ee136e 100644 --- a/test/features/CustomViews.test.ts +++ b/test/features/CustomViews.test.ts @@ -129,7 +129,7 @@ hello styleSheetPaths: cssPaths, }; try { - assert.equal(htmlContentView.getContent(), testCase.expectedHtmlString); + assert.strictEqual(htmlContentView.getContent(), testCase.expectedHtmlString); } finally { jsPaths.forEach((jsPath) => fs.unlinkSync(vscode.Uri.parse(jsPath).fsPath)); cssPaths.forEach((cssPath) => fs.unlinkSync(vscode.Uri.parse(cssPath).fsPath)); diff --git a/test/features/ISECompatibility.test.ts b/test/features/ISECompatibility.test.ts index 8b5af7d7e4..92b24c67ac 100644 --- a/test/features/ISECompatibility.test.ts +++ b/test/features/ISECompatibility.test.ts @@ -14,32 +14,32 @@ suite("ISECompatibility feature", () => { await vscode.commands.executeCommand("PowerShell.EnableISEMode"); for (const iseSetting of ISECompatibilityFeature.settings) { const currently = vscode.workspace.getConfiguration(iseSetting.path).get(iseSetting.name); - assert.equal(currently, iseSetting.value); + assert.strictEqual(currently, iseSetting.value); } }); test("It unsets ISE Settings", async () => { // Change state to something that DisableISEMode will change await vscode.workspace.getConfiguration("workbench").update("colorTheme", "PowerShell ISE", true); - assert.equal(vscode.workspace.getConfiguration("workbench").get("colorTheme"), "PowerShell ISE"); + assert.strictEqual(vscode.workspace.getConfiguration("workbench").get("colorTheme"), "PowerShell ISE"); await vscode.commands.executeCommand("PowerShell.DisableISEMode"); for (const iseSetting of ISECompatibilityFeature.settings) { const currently = vscode.workspace.getConfiguration(iseSetting.path).get(iseSetting.name); - assert.notEqual(currently, iseSetting.value); + assert.notStrictEqual(currently, iseSetting.value); } }).timeout(10000); test("It leaves Theme after being changed after enabling ISE Mode", async () => { await vscode.commands.executeCommand("PowerShell.EnableISEMode"); - assert.equal(vscode.workspace.getConfiguration("workbench").get("colorTheme"), "PowerShell ISE"); + assert.strictEqual(vscode.workspace.getConfiguration("workbench").get("colorTheme"), "PowerShell ISE"); await vscode.workspace.getConfiguration("workbench").update("colorTheme", "Dark+", true); await vscode.commands.executeCommand("PowerShell.DisableISEMode"); for (const iseSetting of ISECompatibilityFeature.settings) { const currently = vscode.workspace.getConfiguration(iseSetting.path).get(iseSetting.name); - assert.notEqual(currently, iseSetting.value); + assert.notStrictEqual(currently, iseSetting.value); } - assert.equal(vscode.workspace.getConfiguration("workbench").get("colorTheme"), "Dark+"); + assert.strictEqual(vscode.workspace.getConfiguration("workbench").get("colorTheme"), "Dark+"); }).timeout(10000); }); diff --git a/test/features/RunCode.test.ts b/test/features/RunCode.test.ts index a7a405a9fa..a74392feec 100644 --- a/test/features/RunCode.test.ts +++ b/test/features/RunCode.test.ts @@ -39,7 +39,7 @@ suite("RunCode tests", () => { const actual: object = createLaunchConfig(LaunchType.Debug, commandToRun, args); - assert.deepEqual(actual, expected); + assert.deepStrictEqual(actual, expected); }); test("Can run Pester tests from file", async () => { From e83885fecc52b0c0b8acd2c24311796c13d7437f Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Wed, 22 Sep 2021 15:29:33 -0700 Subject: [PATCH 08/13] Use correct Mocha TDD functions The `before` and `after` etc. are for BDD (e.g. `describe` and `it`), but we're using TDD (e.g. `suite` and `test`). I think they're just aliases of each other, but let's be correct. --- test/core/paths.test.ts | 4 ++-- test/features/ExternalApi.test.ts | 10 +++++----- test/features/ISECompatibility.test.ts | 4 ++-- test/features/RunCode.test.ts | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/test/core/paths.test.ts b/test/core/paths.test.ts index 6772f5e6d2..176bae55b9 100644 --- a/test/core/paths.test.ts +++ b/test/core/paths.test.ts @@ -5,11 +5,11 @@ import * as assert from "assert"; import * as fs from "fs"; import * as path from "path"; import * as vscode from "vscode"; -import { before } from "mocha"; +import { suiteSetup } from "mocha"; import utils = require("../utils"); suite("Path assumptions", () => { - before(async () => { await utils.ensureExtensionIsActivated(); }); + suiteSetup(utils.ensureExtensionIsActivated); test("The examples folder can be opened (and exists)", async () => { assert(await vscode.commands.executeCommand("PowerShell.OpenExamplesFolder")); diff --git a/test/features/ExternalApi.test.ts b/test/features/ExternalApi.test.ts index 285bddf7ad..880773c46f 100644 --- a/test/features/ExternalApi.test.ts +++ b/test/features/ExternalApi.test.ts @@ -2,13 +2,13 @@ // Licensed under the MIT License. import * as assert from "assert"; -import { before, beforeEach, afterEach } from "mocha"; +import { suiteSetup, setup, teardown } from "mocha"; import utils = require("../utils"); import { IExternalPowerShellDetails, IPowerShellExtensionClient } from "../../src/features/ExternalApi"; suite("ExternalApi feature - Registration API", () => { let powerShellExtensionClient: IPowerShellExtensionClient; - before(async () => { + suiteSetup(async () => { const powershellExtension = await utils.ensureExtensionIsActivated(); powerShellExtensionClient = powershellExtension!.exports as IPowerShellExtensionClient; }); @@ -65,16 +65,16 @@ suite("ExternalApi feature - Other APIs", () => { let sessionId: string; let powerShellExtensionClient: IPowerShellExtensionClient; - before(async () => { + suiteSetup(async () => { const powershellExtension = await utils.ensureExtensionIsActivated(); powerShellExtensionClient = powershellExtension!.exports as IPowerShellExtensionClient; }); - beforeEach(() => { + setup(() => { sessionId = powerShellExtensionClient.registerExternalExtension(utils.extensionId); }); - afterEach(() => { + teardown(() => { powerShellExtensionClient.unregisterExternalExtension(sessionId); }); diff --git a/test/features/ISECompatibility.test.ts b/test/features/ISECompatibility.test.ts index 92b24c67ac..a149f51b29 100644 --- a/test/features/ISECompatibility.test.ts +++ b/test/features/ISECompatibility.test.ts @@ -3,12 +3,12 @@ import * as assert from "assert"; import * as vscode from "vscode"; -import { before } from "mocha"; +import { suiteSetup } from "mocha"; import { ISECompatibilityFeature } from "../../src/features/ISECompatibility"; import utils = require("../utils"); suite("ISECompatibility feature", () => { - before(async () => { await utils.ensureExtensionIsActivated(); } ); + suiteSetup(utils.ensureExtensionIsActivated); test("It sets ISE Settings", async () => { await vscode.commands.executeCommand("PowerShell.EnableISEMode"); diff --git a/test/features/RunCode.test.ts b/test/features/RunCode.test.ts index a74392feec..415c147d91 100644 --- a/test/features/RunCode.test.ts +++ b/test/features/RunCode.test.ts @@ -4,7 +4,7 @@ import * as assert from "assert"; import * as fs from "fs"; import * as path from "path"; -import { before } from "mocha"; +import { suiteSetup } from "mocha"; import rewire = require("rewire"); import vscode = require("vscode"); import utils = require("../utils"); @@ -19,7 +19,7 @@ enum LaunchType { } suite("RunCode tests", () => { - before(async () => { await utils.ensureExtensionIsActivated(); } ); + suiteSetup(utils.ensureExtensionIsActivated); test("Can create the launch config", () => { const commandToRun: string = "Invoke-Build"; From a9c4ab04ed029edf0807509211f74f9c82ce596b Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Wed, 22 Sep 2021 16:58:39 -0700 Subject: [PATCH 09/13] Run `EnableISEMode` and `DisableISEMode` in `setup` and `teardown` So that they're always run as expected, leaving the state clean for the next test regardless of ordering. --- test/features/ISECompatibility.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/features/ISECompatibility.test.ts b/test/features/ISECompatibility.test.ts index a149f51b29..0f9d10e360 100644 --- a/test/features/ISECompatibility.test.ts +++ b/test/features/ISECompatibility.test.ts @@ -3,15 +3,16 @@ import * as assert from "assert"; import * as vscode from "vscode"; -import { suiteSetup } from "mocha"; +import { suiteSetup, setup, teardown } from "mocha"; import { ISECompatibilityFeature } from "../../src/features/ISECompatibility"; import utils = require("../utils"); suite("ISECompatibility feature", () => { suiteSetup(utils.ensureExtensionIsActivated); + setup(async () => { await vscode.commands.executeCommand("PowerShell.EnableISEMode"); }); + teardown(async () => { await vscode.commands.executeCommand("PowerShell.DisableISEMode"); }); test("It sets ISE Settings", async () => { - await vscode.commands.executeCommand("PowerShell.EnableISEMode"); for (const iseSetting of ISECompatibilityFeature.settings) { const currently = vscode.workspace.getConfiguration(iseSetting.path).get(iseSetting.name); assert.strictEqual(currently, iseSetting.value); @@ -31,7 +32,6 @@ suite("ISECompatibility feature", () => { }).timeout(10000); test("It leaves Theme after being changed after enabling ISE Mode", async () => { - await vscode.commands.executeCommand("PowerShell.EnableISEMode"); assert.strictEqual(vscode.workspace.getConfiguration("workbench").get("colorTheme"), "PowerShell ISE"); await vscode.workspace.getConfiguration("workbench").update("colorTheme", "Dark+", true); From 04ece6dec92ab3e438b0530c5fa99c9dc894b754 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Thu, 23 Sep 2021 11:28:40 -0700 Subject: [PATCH 10/13] Improve ISE compatibility tests Save and restore the user's theme, which was annoying when running the tests via Code's debugger. Don't use the default theme "Dark+" so that the setting is actually propogated. --- test/features/ISECompatibility.test.ts | 27 +++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/test/features/ISECompatibility.test.ts b/test/features/ISECompatibility.test.ts index 0f9d10e360..7fac60591d 100644 --- a/test/features/ISECompatibility.test.ts +++ b/test/features/ISECompatibility.test.ts @@ -3,15 +3,29 @@ import * as assert from "assert"; import * as vscode from "vscode"; -import { suiteSetup, setup, teardown } from "mocha"; +import { suiteSetup, setup, suiteTeardown, teardown } from "mocha"; import { ISECompatibilityFeature } from "../../src/features/ISECompatibility"; import utils = require("../utils"); suite("ISECompatibility feature", () => { - suiteSetup(utils.ensureExtensionIsActivated); + let currentTheme: string; + + suiteSetup(async () => { + // Save user's current theme. + currentTheme = await vscode.workspace.getConfiguration("workbench").get("colorTheme"); + await utils.ensureExtensionIsActivated(); + }); + setup(async () => { await vscode.commands.executeCommand("PowerShell.EnableISEMode"); }); + teardown(async () => { await vscode.commands.executeCommand("PowerShell.DisableISEMode"); }); + suiteTeardown(async () => { + // Reset user's current theme. + await vscode.workspace.getConfiguration("workbench").update("colorTheme", currentTheme, true); + assert.strictEqual(vscode.workspace.getConfiguration("workbench").get("colorTheme"), currentTheme); + }); + test("It sets ISE Settings", async () => { for (const iseSetting of ISECompatibilityFeature.settings) { const currently = vscode.workspace.getConfiguration(iseSetting.path).get(iseSetting.name); @@ -31,15 +45,18 @@ suite("ISECompatibility feature", () => { } }).timeout(10000); - test("It leaves Theme after being changed after enabling ISE Mode", async () => { + test("It doesn't change theme when disabled if theme was manually changed after being enabled", async () => { assert.strictEqual(vscode.workspace.getConfiguration("workbench").get("colorTheme"), "PowerShell ISE"); - await vscode.workspace.getConfiguration("workbench").update("colorTheme", "Dark+", true); + // "Manually" change theme after enabling ISE mode. Use a built-in theme but not the default. + await vscode.workspace.getConfiguration("workbench").update("colorTheme", "Monokai", true); + assert.strictEqual(vscode.workspace.getConfiguration("workbench").get("colorTheme"), "Monokai"); + await vscode.commands.executeCommand("PowerShell.DisableISEMode"); for (const iseSetting of ISECompatibilityFeature.settings) { const currently = vscode.workspace.getConfiguration(iseSetting.path).get(iseSetting.name); assert.notStrictEqual(currently, iseSetting.value); } - assert.strictEqual(vscode.workspace.getConfiguration("workbench").get("colorTheme"), "Dark+"); + assert.strictEqual(vscode.workspace.getConfiguration("workbench").get("colorTheme"), "Monokai"); }).timeout(10000); }); From 785eae6b32afd33ef785333cd2065c9746837e00 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Thu, 23 Sep 2021 11:35:25 -0700 Subject: [PATCH 11/13] Skip problematic `OpenExamplesFolder` test which breaks other tests --- test/core/paths.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/core/paths.test.ts b/test/core/paths.test.ts index 176bae55b9..c74216a189 100644 --- a/test/core/paths.test.ts +++ b/test/core/paths.test.ts @@ -11,7 +11,10 @@ import utils = require("../utils"); suite("Path assumptions", () => { suiteSetup(utils.ensureExtensionIsActivated); - test("The examples folder can be opened (and exists)", async () => { + // TODO: This is skipped because it intereferes with other tests. Either + // need to find a way to close the opened folder via a Code API, or find + // another way to test this. + test.skip("The examples folder can be opened (and exists)", async () => { assert(await vscode.commands.executeCommand("PowerShell.OpenExamplesFolder")); }); From 7504386303e688274841819fc46f5b9f5725db7e Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Thu, 23 Sep 2021 11:58:16 -0700 Subject: [PATCH 12/13] Fix race condition in `RunPesterTestsFromFile` test --- test/features/RunCode.test.ts | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/test/features/RunCode.test.ts b/test/features/RunCode.test.ts index 415c147d91..fbab7d22e1 100644 --- a/test/features/RunCode.test.ts +++ b/test/features/RunCode.test.ts @@ -4,10 +4,11 @@ import * as assert from "assert"; import * as fs from "fs"; import * as path from "path"; -import { suiteSetup } from "mocha"; import rewire = require("rewire"); import vscode = require("vscode"); import utils = require("../utils"); +import { sleep } from "../../src/utils"; +import { IPowerShellExtensionClient } from "../../src/features/ExternalApi"; // Setup function that is not exported. const customViews = rewire("../../src/features/RunCode"); @@ -19,8 +20,6 @@ enum LaunchType { } suite("RunCode tests", () => { - suiteSetup(utils.ensureExtensionIsActivated); - test("Can create the launch config", () => { const commandToRun: string = "Invoke-Build"; const args: string[] = ["Clean"]; @@ -42,11 +41,33 @@ suite("RunCode tests", () => { assert.deepStrictEqual(actual, expected); }); - test("Can run Pester tests from file", async () => { + test("Can run Pester tests from file", async function() { + // PowerShell can take a while and is flaky, so try three times and set + // the timeout to thirty seconds each. + this.retries(3); + this.timeout(30000); + const pesterTests = path.resolve(__dirname, "../../../examples/Tests/SampleModule.Tests.ps1"); assert(fs.existsSync(pesterTests)); + + // Get interface to extension. + const extension = await utils.ensureExtensionIsActivated(); + const client = extension!.exports as IPowerShellExtensionClient; + const sessionId = client.registerExternalExtension(utils.extensionId); + + // Force PowerShell extension to finish connecting. This is necessary + // because we can't start the PowerShell debugger until the session is + // connected, which is different from the extension being activated. We + // also need to open the file so the command has it as its argument. await vscode.commands.executeCommand("vscode.open", vscode.Uri.file(pesterTests)); + await client.getPowerShellVersionDetails(sessionId); + client.unregisterExternalExtension(sessionId); + + // Now run the Pester tests, check the debugger started, wait a bit for + // it to run, and then kill it for safety's sake. assert(await vscode.commands.executeCommand("PowerShell.RunPesterTestsFromFile")); - // Start up can take some time...so set the timeout to 30 seconds. - }).timeout(30000); + assert(vscode.debug.activeDebugSession !== undefined); + await sleep(5000); + await vscode.debug.stopDebugging(); + }); }); From f1519f864908ff48b2fecb7e52b0f000724506bf Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Thu, 23 Sep 2021 15:59:46 -0700 Subject: [PATCH 13/13] Make test smaller --- test/features/RunCode.test.ts | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/test/features/RunCode.test.ts b/test/features/RunCode.test.ts index fbab7d22e1..226c02138c 100644 --- a/test/features/RunCode.test.ts +++ b/test/features/RunCode.test.ts @@ -6,9 +6,9 @@ import * as fs from "fs"; import * as path from "path"; import rewire = require("rewire"); import vscode = require("vscode"); +import { suiteSetup } from "mocha"; import utils = require("../utils"); import { sleep } from "../../src/utils"; -import { IPowerShellExtensionClient } from "../../src/features/ExternalApi"; // Setup function that is not exported. const customViews = rewire("../../src/features/RunCode"); @@ -20,6 +20,8 @@ enum LaunchType { } suite("RunCode tests", () => { + suiteSetup(utils.ensureExtensionIsActivated); + test("Can create the launch config", () => { const commandToRun: string = "Invoke-Build"; const args: string[] = ["Clean"]; @@ -41,27 +43,14 @@ suite("RunCode tests", () => { assert.deepStrictEqual(actual, expected); }); - test("Can run Pester tests from file", async function() { - // PowerShell can take a while and is flaky, so try three times and set - // the timeout to thirty seconds each. - this.retries(3); - this.timeout(30000); - + test("Can run Pester tests from file", async () => { const pesterTests = path.resolve(__dirname, "../../../examples/Tests/SampleModule.Tests.ps1"); assert(fs.existsSync(pesterTests)); - // Get interface to extension. - const extension = await utils.ensureExtensionIsActivated(); - const client = extension!.exports as IPowerShellExtensionClient; - const sessionId = client.registerExternalExtension(utils.extensionId); - - // Force PowerShell extension to finish connecting. This is necessary - // because we can't start the PowerShell debugger until the session is - // connected, which is different from the extension being activated. We - // also need to open the file so the command has it as its argument. + // Open the PowerShell file with Pester tests and then wait a while for + // the extension to finish connecting to the server. await vscode.commands.executeCommand("vscode.open", vscode.Uri.file(pesterTests)); - await client.getPowerShellVersionDetails(sessionId); - client.unregisterExternalExtension(sessionId); + await sleep(15000); // Now run the Pester tests, check the debugger started, wait a bit for // it to run, and then kill it for safety's sake. @@ -69,5 +58,5 @@ suite("RunCode tests", () => { assert(vscode.debug.activeDebugSession !== undefined); await sleep(5000); await vscode.debug.stopDebugging(); - }); + }).timeout(30000); });