diff --git a/.gitignore b/.gitignore index 100457c404..d83b12799f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,6 @@ obj/ bin/ out/ sessions/ -test/.vscode/ - test-results.xml *.vsix *.DS_Store diff --git a/examples/.vscode/settings.json b/examples/.vscode/settings.json index fad6823648..a37044b519 100644 --- a/examples/.vscode/settings.json +++ b/examples/.vscode/settings.json @@ -3,8 +3,4 @@ // Relative paths for this setting are always relative to the workspace root dir. "powershell.scriptAnalysis.settingsPath": "./PSScriptAnalyzerSettings.psd1", "files.defaultLanguage": "powershell", - // Suppresses some first-run messages - "git.openRepositoryInParentFolders": "never", - "csharp.suppressDotnetRestoreNotification": true, - "extensions.ignoreRecommendations": true } diff --git a/extension-dev.code-workspace b/extension-dev.code-workspace index dc9f8bfeab..2a552513f0 100644 --- a/extension-dev.code-workspace +++ b/extension-dev.code-workspace @@ -21,6 +21,8 @@ ] }, "settings": { + "window.title": "PowerShell VS Code Extension Development", + "debug.onTaskErrors": "prompt", "editor.tabSize": 4, "editor.insertSpaces": true, "files.trimTrailingWhitespace": true, @@ -44,7 +46,17 @@ "powershell.codeFormatting.whitespaceBetweenParameters": true, "powershell.codeFormatting.pipelineIndentationStyle": "IncreaseIndentationForFirstPipeline", // Lock the TypeScript SDK path to the version we use - "typescript.tsdk": "Client/node_modules/typescript/lib" + "typescript.tsdk": "Client/node_modules/typescript/lib", + // Code actions like "organize imports" ignore ESLint, so we need this here + "typescript.format.semicolons": "insert", + // Enable ESLint as defaut formatter so quick fixes can be applied directly + "eslint.format.enable": true, + "[typescript]": { + "editor.defaultFormatter": "dbaeumer.vscode-eslint", + "editor.formatOnPaste": true, + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "modificationsIfAvailable" + } }, "tasks": { "version": "2.0.0", @@ -90,7 +102,7 @@ "options": { "cwd": "${workspaceFolder:Client}" }, - "command": "Invoke-Build Build", + "command": "./build.ps1", "problemMatcher": [ "$msCompile", "$tsc" @@ -106,7 +118,7 @@ "options": { "cwd": "${workspaceFolder:Client}" }, - "command": "Invoke-Build Test", + "command": "./build.ps1 -Test", "problemMatcher": [ "$msCompile", "$tsc" @@ -148,7 +160,39 @@ }, "command": "Invoke-Build ${input:serverBuildCommand}", "group": "build" - } + }, + // HACK: Can't use task type npm in workspace config: https://github.com/microsoft/vscode/issues/96086 + { + "label": "test-watch", + "icon": { + "color": "terminal.ansiCyan", + "id": "sync" + }, + "type": "shell", + "options": { + "cwd": "${workspaceFolder:Client}" + }, + "command": "npm run-script build-test-watch", + "group": "test", + "problemMatcher": "$tsc-watch", + "isBackground": true, + "dependsOn": "build-watch" // We need to also build main.js extension for testing or it leads to sourcemap errors + }, + { + "label": "build-watch", + "icon": { + "color": "terminal.ansiCyan", + "id": "sync" + }, + "type": "shell", + "options": { + "cwd": "${workspaceFolder:Client}" + }, + "command": "npm run-script build-watch", + "group": "build", + "problemMatcher": "$esbuild-watch", + "isBackground": true, + }, ], "inputs": [ { @@ -184,7 +228,52 @@ }, "launch": { "version": "0.2.0", + "compounds": [ + { + "name": "Test Extension", + "configurations": [ + "ExtensionTests", + "ExtensionTestRunner", + ], + "stopAll": true, + "presentation": { + "group": "test", + "order": 1 + }, + // This is here so instead of under TestRunner so that the attach doesn't start until the compile is complete + "preLaunchTask": "test-watch" + } + ], "configurations": [ + { + "name": "Launch Extension", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder:Client}" + ], + "env": { + "__TEST_WORKSPACE_PATH": "${workspaceFolder:Client}/examples", + }, + "sourceMaps": true, + // This speeds up source map detection and makes smartStep work correctly + "outFiles": [ + "${workspaceFolder:Client}/out/**/*.js", + "!**/node_modules/**", + "!**/.vscode-test/**" + ], + "skipFiles": [ + "/**", + "**/node_modules/**", + "**/.vscode-test/**" + ], + "presentation": { + "hidden": false, + "group": "test", + "order": 2 + } + }, { // https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md "name": "Attach to Editor Services", @@ -197,42 +286,129 @@ "searchPaths": [], "searchMicrosoftSymbolServer": true, "searchNuGetOrgSymbolServer": true + }, + "presentation": { + "hidden": false, + "group": "test", + "order": 3 } }, { - "name": "Launch Extension", + // Runs the extension in an empty temp profile that is automatically cleaned up after use + // Undocumented: https://github.com/microsoft/vscode-docs/issues/6220 + "name": "Launch Extension - Temp Profile", "type": "extensionHost", "request": "launch", "runtimeExecutable": "${execPath}", "args": [ - "--disable-extensions", - "--extensionDevelopmentPath=${workspaceFolder:Client}" + "--profile-temp", + "--extensionDevelopmentPath=${workspaceFolder:Client}", + "${workspaceFolder:Client}/examples" ], "sourceMaps": true, + // This speeds up source map detection and makes smartStep work correctly "outFiles": [ - "${workspaceFolder:Client}/out/main.js" + "${workspaceFolder:Client}/out/**/*.js", + "!**/node_modules/**", + "!**/.vscode-test/**" + ], + "skipFiles": [ + "/**", + "**/node_modules/**", + "**/.vscode-test/**" ], - "preLaunchTask": "${defaultBuildTask}", + "presentation": { + "hidden": false, + "group": "test", + "order": 2 + } }, { - "name": "Launch Extension Tests", + // Runs the extension in an isolated but persistent profile separate from the user settings + // Undocumented: https://github.com/microsoft/vscode-docs/issues/6220 + "name": "Launch Extension - Isolated Profile", "type": "extensionHost", "request": "launch", "runtimeExecutable": "${execPath}", "args": [ - // The tests require Code be opened with a workspace, which exists in - // `test`, but this has to be passed as a CLI argument, not just `cwd`. - "${workspaceFolder:Client}/test", - "--disableExtensions", + "--profile=debug", "--extensionDevelopmentPath=${workspaceFolder:Client}", - "--extensionTestsPath=${workspaceFolder:Client}/out/test/index.js", + "${workspaceFolder:Client}/examples" ], "sourceMaps": true, + // This speeds up source map detection and makes smartStep work correctly "outFiles": [ - "${workspaceFolder:Client}/out/test/**/*.js" + "${workspaceFolder:Client}/out/**/*.js", + "!**/node_modules/**", + "!**/.vscode-test/**" + ], + "skipFiles": [ + "/**", + "**/node_modules/**", + "**/.vscode-test/**" + ], + "presentation": { + "hidden": false, + "group": "test", + "order": 2 + } + }, + { + "name": "ExtensionTestRunner", + "type": "node", + "request": "launch", + "program": "${workspaceFolder:Client}/out/test/runTests.js", + "cascadeTerminateToConfigurations": [ + "ExtensionTests", + ], + // This speeds up source map detection and makes smartStep work correctly + "outFiles": [ + "${workspaceFolder:Client}/out/**/*.js", + "!**/node_modules/**", + "!**/.vscode-test/**" + ], + "skipFiles": [ + "/**", + "**/node_modules/**", + "**/.vscode-test/**" + ], + "args": [ + "59229" // Wait on this port for the separate debugger task to attach + ], + "presentation": { + "hidden": true, + }, + "internalConsoleOptions": "neverOpen", + "console": "integratedTerminal", + "autoAttachChildProcesses": false // Doesnt work with the extension host for whatever reason, hence the separate attach. + }, + { + "name": "ExtensionTests", + "type": "node", + "request": "attach", + "port": 59229, + "autoAttachChildProcesses": true, + "outputCapture": "console", + "continueOnAttach": true, + // Sometimes we may need to install extensions or reload the window which requires reconnecting + "restart": { + "delay": 1000, + "maxAttempts": 3 + }, + "presentation": { + "hidden": true, + }, + // This speeds up source map detection and makes smartStep work correctly + "outFiles": [ + "${workspaceFolder:Client}/out/**/*.js", + "!**/node_modules/**", + "!**/.vscode-test/**" + ], + "skipFiles": [ + "/**", + "**/node_modules/**", + "**/.vscode-test/**" ], - "preLaunchTask": "${defaultBuildTask}", - "internalConsoleOptions": "openOnSessionStart" } ] } diff --git a/package.json b/package.json index f2f3c8c273..154d31c71b 100644 --- a/package.json +++ b/package.json @@ -115,8 +115,11 @@ "main": "./out/main.js", "scripts": { "lint": "eslint . --ext .ts", - "build": "tsc --project tsconfig.json && esbuild ./src/main.ts --outdir=out --bundle --external:vscode --platform=node", - "test": "node ./out/test/runTests.js", + "build": "esbuild ./src/main.ts --outdir=out --bundle --external:vscode --platform=node", + "build-watch": "npm run build -- --watch", + "build-test": "tsc --incremental", + "build-test-watch": "npm run build-test -- --watch", + "test": "npm run build-test && node ./out/test/runTests.js", "package": "vsce package --no-gitHubIssueLinking", "publish": "vsce publish" }, diff --git a/test/.vscode/settings.json b/test/.vscode/settings.json deleted file mode 100644 index b731d24659..0000000000 --- a/test/.vscode/settings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "terminal.integrated.shellIntegration.enabled": false, - "powershell.enableProfileLoading": false, - "powershell.powerShellAdditionalExePaths": { - "Some PowerShell": "somePath" - }, -} diff --git a/test/TestEnvironment.code-workspace b/test/TestEnvironment.code-workspace new file mode 100644 index 0000000000..b4035e33c5 --- /dev/null +++ b/test/TestEnvironment.code-workspace @@ -0,0 +1,13 @@ +{ + // A simple test environment that suppresses some first start warnings we don't care about. + "folders": [ + { + "path": "mocks" + } + ], + "settings": { + "git.openRepositoryInParentFolders": "never", + "csharp.suppressDotnetRestoreNotification": true, + "extensions.ignoreRecommendations": true + } +} diff --git a/test/core/settings.test.ts b/test/core/settings.test.ts index 00b1c5d5ee..fcd1856a61 100644 --- a/test/core/settings.test.ts +++ b/test/core/settings.test.ts @@ -3,34 +3,32 @@ import * as assert from "assert"; import * as vscode from "vscode"; -import * as settings from "../../src/settings"; +import { Settings, getSettings, getEffectiveConfigurationTarget, changeSetting, CommentType } from "../../src/settings"; -describe("Settings module", function () { +describe("Settings E2E", function () { + this.slow(800); it("Loads without error", function () { - assert.doesNotThrow(settings.getSettings); + assert.doesNotThrow(getSettings); }); it("Loads the correct defaults", function () { - const testSettings = new settings.Settings(); - testSettings.enableProfileLoading = false; - testSettings.powerShellAdditionalExePaths = { "Some PowerShell": "somePath" }; - const actualSettings = settings.getSettings(); + const testSettings = new Settings(); + const actualSettings = getSettings(); assert.deepStrictEqual(actualSettings, testSettings); }); - it("Updates correctly", async function () { - await settings.changeSetting("helpCompletion", settings.CommentType.LineComment, false, undefined); - assert.strictEqual(settings.getSettings().helpCompletion, settings.CommentType.LineComment); + await changeSetting("helpCompletion", CommentType.LineComment, false, undefined); + assert.strictEqual(getSettings().helpCompletion, CommentType.LineComment); }); it("Gets the effective configuration target", async function () { - await settings.changeSetting("helpCompletion", settings.CommentType.LineComment, false, undefined); - let target = settings.getEffectiveConfigurationTarget("helpCompletion"); + await changeSetting("helpCompletion", CommentType.LineComment, false, undefined); + let target = getEffectiveConfigurationTarget("helpCompletion"); assert.strictEqual(target, vscode.ConfigurationTarget.Workspace); - await settings.changeSetting("helpCompletion", undefined, false, undefined); - target = settings.getEffectiveConfigurationTarget("helpCompletion"); + await changeSetting("helpCompletion", undefined, false, undefined); + target = getEffectiveConfigurationTarget("helpCompletion"); assert.strictEqual(target, undefined); }); }); diff --git a/test/features/DebugSession.test.ts b/test/features/DebugSession.test.ts index cd36993ca6..191eafa336 100644 --- a/test/features/DebugSession.test.ts +++ b/test/features/DebugSession.test.ts @@ -419,18 +419,22 @@ describe("DebugSessionFeature E2E", function slowTests() { }); describe("Binary Modules", () => { + let binaryModulePath: Uri; before(async () => { + binaryModulePath = Uri.joinPath(workspace.workspaceFolders![0].uri, "BinaryModule"); BuildBinaryModuleMock(); await ensureEditorServicesIsConnected(); }); afterEach(async () => { // Cleanup E2E testing state await debug.stopDebugging(undefined); + // Close all editors + await commands.executeCommand("workbench.action.closeAllEditors"); }); it("Debugs a binary module script", async () => { const launchScriptConfig = structuredClone(defaultDebugConfigurations[DebugConfig.LaunchScript]); - launchScriptConfig.script = "../examples/BinaryModule/BinaryModuleTest.ps1"; + launchScriptConfig.script = Uri.joinPath(binaryModulePath, "BinaryModuleTest.ps1").fsPath; launchScriptConfig.attachDotnetDebugger = true; launchScriptConfig.createTemporaryIntegratedConsole = true; const startDebugging = Sinon.spy(debug, "startDebugging"); @@ -454,8 +458,8 @@ describe("DebugSessionFeature E2E", function slowTests() { const launchScriptConfig = structuredClone(defaultDebugConfigurations[DebugConfig.LaunchCurrentFile]); launchScriptConfig.attachDotnetDebugger = true; launchScriptConfig.createTemporaryIntegratedConsole = true; - const testScriptPath = Uri.joinPath(workspace.workspaceFolders![0].uri, "mocks/BinaryModule/BinaryModuleTest.ps1"); - const cmdletSourcePath = Uri.joinPath(workspace.workspaceFolders![0].uri, "mocks/BinaryModule/TestSampleCmdletCommand.cs"); + const testScriptPath = Uri.joinPath(binaryModulePath, "BinaryModuleTest.ps1"); + const cmdletSourcePath = Uri.joinPath(binaryModulePath, "TestSampleCmdletCommand.cs"); const testScriptDocument = await workspace.openTextDocument(testScriptPath); await window.showTextDocument(testScriptDocument); @@ -463,7 +467,6 @@ describe("DebugSessionFeature E2E", function slowTests() { //We wire this up before starting the debug session so the event is registered const dotnetDebugSessionActive = WaitEvent(debug.onDidChangeActiveDebugSession, (session) => { - console.log(`Debug Session Changed: ${session?.name}`); return !!session?.name.match(/Dotnet Debugger/); }); diff --git a/test/runTests.ts b/test/runTests.ts index 961ac3819d..9057ee2026 100644 --- a/test/runTests.ts +++ b/test/runTests.ts @@ -18,24 +18,25 @@ async function main(): Promise { } try { - // The folder containing the Extension Manifest package.json - // Passed to `--extensionDevelopmentPath` + /** The folder containing the Extension Manifest package.json. Passed to `--extensionDevelopmentPath */ const extensionDevelopmentPath = path.resolve(__dirname, "../../"); - // The path to the extension test script - // Passed to --extensionTestsPath + /** The path to the extension test script. Passed to --extensionTestsPath */ const extensionTestsPath = path.resolve(__dirname, "./index"); - // The version to test. By default we test on insiders. - const vsCodeVersion = "insiders"; + /** The starting workspace/folder to open in vscode. */ + const workspacePath = process.env.__TEST_WORKSPACE_PATH ?? "test/TestEnvironment.code-workspace"; + const workspaceToOpen = path.resolve(extensionDevelopmentPath, workspacePath); - // Install Temp VSCode. We need to do this first so we can then install extensions as the runTests function doesn't give us a way to hook in to do this. + /** The version to test. By default we test on insiders. */ + const vsCodeVersion = process.env.__TEST_VSCODE_VERSION ?? "insiders"; + + /** Install a temporary vscode. This must be done ahead of RunTests in order to install extensions ahead of time. @see https://github.com/microsoft/vscode-test/blob/addc23e100b744de598220adbbf0761da870eda9/README.md?plain=1#L71-L89 **/ const testVSCodePath = await downloadAndUnzipVSCode(vsCodeVersion, undefined, new ConsoleReporter(true)); InstallExtension(testVSCodePath, "ms-dotnettools.csharp"); - // Open VSCode with the examples folder, so any UI testing can run against the examples. const launchArgs = [ - "./test" + workspaceToOpen ]; // Allow to wait for extension test debugging @@ -50,7 +51,10 @@ async function main(): Promise { launchArgs: launchArgs, // This is necessary because the tests fail if more than once // instance of Code is running. - version: vsCodeVersion + version: vsCodeVersion, + extensionTestsEnv: { + __TEST_EXTENSIONDEVELOPMENTPATH: extensionDevelopmentPath + } }); } catch (err) { console.error(`Failed to run tests: ${err}`); @@ -58,7 +62,6 @@ async function main(): Promise { } } - /** Installs an extension into an existing vscode instance. Returns the output result */ function InstallExtension(vscodeExePath: string, extensionIdOrVSIXPath: string): string { // Install the csharp extension which is required for the dotnet debugger testing diff --git a/tsconfig.json b/tsconfig.json index 4da3d70d92..4db014f3af 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,7 +17,8 @@ "noImplicitReturns": true, "noUnusedLocals": true, "noUnusedParameters": true, - "esModuleInterop": true + "esModuleInterop": true, + "allowSyntheticDefaultImports": true }, "include": [ "src", "test" ], } diff --git a/vscode-powershell.build.ps1 b/vscode-powershell.build.ps1 index 859c1711f8..20e02f803c 100644 --- a/vscode-powershell.build.ps1 +++ b/vscode-powershell.build.ps1 @@ -113,7 +113,7 @@ task Test Build, { Write-Host "`n### Running extension tests" -ForegroundColor Green Invoke-BuildExec { & npm run test } # Reset the state of files modified by tests - Invoke-BuildExec { git checkout package.json test/.vscode/settings.json} + Invoke-BuildExec { git checkout package.json test/TestEnvironment.code-workspace } } task TestEditorServices -If (Get-EditorServicesPath) {