From bb93b74119f89f348c471dc18dcde3524099c663 Mon Sep 17 00:00:00 2001 From: David Wilson Date: Fri, 25 Aug 2017 07:55:41 -0700 Subject: [PATCH 1/6] Update tasks.json files for 64-bit VS Code This change modifies the path used for PowerShell on Windows due to the fact that 64-bit VS Code has been released (and is now being promoted as the primary build). We're only pointing to the System32 path for now because VS Code doesn't provide a way to specify a different path in the 32-bit build. --- .vscode/tasks.json | 24 ++++++++++++++++++------ examples/.vscode/tasks.json | 2 +- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 8bdc954f97..f608a99300 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -2,7 +2,7 @@ "version": "2.0.0", "windows": { - "command": "${env:windir}\\sysnative\\WindowsPowerShell\\v1.0\\powershell.exe", + "command": "${env:windir}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", "args": [ "-NoProfile", "-ExecutionPolicy", "Bypass" ] }, "linux": { @@ -20,28 +20,40 @@ { "taskName": "Install", "suppressTaskName": true, - "args": [ "Invoke-Build Restore" ] + "args": [ + "Invoke-Build Restore" + ] }, { "taskName": "CleanAll", "suppressTaskName": true, - "args": [ "Invoke-Build CleanAll" ] + "args": [ + "Invoke-Build CleanAll" + ] }, { "taskName": "Clean", "suppressTaskName": true, - "args": [ "Invoke-Build Clean" ] + "args": [ + "Invoke-Build Clean" + ] }, { "taskName": "BuildAll", "suppressTaskName": true, "isBuildCommand": true, - "args": [ "Invoke-Build BuildAll" ] + "args": [ + "Invoke-Build BuildAll" + ], + "problemMatcher": [] }, { "taskName": "Build", "suppressTaskName": true, - "args": [ "Invoke-Build Build" ] + "args": [ + "Invoke-Build Build" + ], + "problemMatcher": [] } ] } diff --git a/examples/.vscode/tasks.json b/examples/.vscode/tasks.json index 6452e9fd81..70ac7615ce 100644 --- a/examples/.vscode/tasks.json +++ b/examples/.vscode/tasks.json @@ -33,7 +33,7 @@ // Start PowerShell "windows": { - "command": "${env:windir}\\sysnative\\WindowsPowerShell\\v1.0\\powershell.exe", + "command": "${env:windir}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", "args": [ "-NoProfile", "-ExecutionPolicy", "Bypass" ] }, "linux": { From b6e4ccd145c224a95eaac0325be80a2dc9ae012b Mon Sep 17 00:00:00 2001 From: David Wilson Date: Fri, 25 Aug 2017 08:03:20 -0700 Subject: [PATCH 2/6] Fix #1009: Add tooltip to PS version status bar item --- src/session.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/session.ts b/src/session.ts index f6fde9de1f..d38d517a1c 100644 --- a/src/session.ts +++ b/src/session.ts @@ -460,6 +460,7 @@ export class SessionManager implements Middleware { 1); this.statusBarItem.command = this.ShowSessionMenuCommandName; + this.statusBarItem.tooltip = "Show PowerShell Session Menu"; this.statusBarItem.show(); vscode.window.onDidChangeActiveTextEditor(textEditor => { if (textEditor === undefined From 265f572e97bbc693db31fc67cbbf287308e28e9d Mon Sep 17 00:00:00 2001 From: David Wilson Date: Fri, 25 Aug 2017 13:45:59 -0700 Subject: [PATCH 3/6] Add extension unit testing infrastructure This change enables Mocha-based unit tests for the PowerShell extension using VS Code's embedded test running mode. --- .gitignore | 1 + .vscode/launch.json | 15 +++++++++++++-- .vscodeignore | 3 +++ package.json | 11 +++++++---- src/debugAdapter.ts | 2 +- src/features/Examples.ts | 2 +- src/logging.ts | 2 +- src/main.ts | 2 +- src/process.ts | 4 ++-- src/session.ts | 4 ++-- src/utils.ts | 2 +- test/index.ts | 9 +++++++++ 12 files changed, 42 insertions(+), 15 deletions(-) create mode 100644 test/index.ts diff --git a/.gitignore b/.gitignore index 989ccd324e..0a35854e2e 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ vscode-powershell.zip vscps-preview.zip *.vsix npm-debug.log +.vscode-test/ diff --git a/.vscode/launch.json b/.vscode/launch.json index 03bcd3cafb..f53cedf458 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,7 +9,7 @@ "args": [ "--extensionDevelopmentPath=${workspaceRoot}" ], "stopOnEntry": false, "sourceMaps": true, - "outFiles": ["${workspaceRoot}/out"], + "outFiles": ["${workspaceRoot}/out/src/**/*.js"], "preLaunchTask": "BuildAll" }, { @@ -20,7 +20,18 @@ "args": [ "--extensionDevelopmentPath=${workspaceRoot}" ], "stopOnEntry": false, "sourceMaps": true, - "outFiles": ["${workspaceRoot}/out"], + "outFiles": ["${workspaceRoot}/out/src/**/*.js"], + "preLaunchTask": "Build" + }, + { + "name": "Launch Extension Tests", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ], + "stopOnEntry": false, + "sourceMaps": true, + "outFiles": ["${workspaceRoot}/out/test/**/*.js"], "preLaunchTask": "Build" }, { diff --git a/.vscodeignore b/.vscodeignore index 76e2e6caf5..3decb35fad 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -1,4 +1,5 @@ .vscode/** +.vscode-test/** vscode-powershell.build.ps1 typings/** **/*.ts @@ -10,5 +11,7 @@ bin/DebugAdapter.log bin/*.vshost.* bin/PowerShell/** logs/** +out/test/** +test/** sessions/** scripts/Install-VSCode.ps1 diff --git a/package.json b/package.json index 64654b2812..4a4cb33002 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "type": "git", "url": "https://github.com/PowerShell/vscode-powershell.git" }, - "main": "./out/main", + "main": "./out/src/main", "activationEvents": [ "onLanguage:powershell", "onCommand:PowerShell.NewProjectFromTemplate", @@ -41,7 +41,9 @@ "@types/node": "^6.0.40", "typescript": "^2.0.3", "vsce": "^1.18.0", - "vscode": "^1.1.0" + "vscode": "^1.1.0", + "mocha": "^2.3.3", + "@types/mocha": "^2.2.32" }, "extensionDependencies": [ "vscode.powershell" @@ -49,7 +51,8 @@ "scripts": { "compile": "tsc -p ./", "compile-watch": "tsc -watch -p ./", - "postinstall": "node ./node_modules/vscode/bin/install" + "postinstall": "node ./node_modules/vscode/bin/install", + "test": "node ./node_modules/vscode/bin/test" }, "contributes": { "keybindings": [ @@ -188,7 +191,7 @@ "powershell" ] }, - "program": "./out/debugAdapter.js", + "program": "./out/src/debugAdapter.js", "runtime": "node", "variables": { "PickPSHostProcess": "PowerShell.PickPSHostProcess", diff --git a/src/debugAdapter.ts b/src/debugAdapter.ts index 928973828d..10940dec24 100644 --- a/src/debugAdapter.ts +++ b/src/debugAdapter.ts @@ -14,7 +14,7 @@ import { Logger } from './logging'; // named pipes or a network protocol). It is purely a naive data // relay between the two transports. -var logBasePath = path.resolve(__dirname, "../logs"); +var logBasePath = path.resolve(__dirname, "../../logs"); var debugAdapterLogWriter = fs.createWriteStream( diff --git a/src/features/Examples.ts b/src/features/Examples.ts index ec436e0b10..77862b6777 100644 --- a/src/features/Examples.ts +++ b/src/features/Examples.ts @@ -12,7 +12,7 @@ export class ExamplesFeature implements IFeature { 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/logging.ts b/src/logging.ts index 784301dfca..22774b3563 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -29,7 +29,7 @@ export class Logger { constructor() { this.logChannel = vscode.window.createOutputChannel("PowerShell Extension Logs"); - this.logBasePath = path.resolve(__dirname, "../logs"); + this.logBasePath = path.resolve(__dirname, "../../logs"); utils.ensurePathExists(this.logBasePath); this.commands = [ diff --git a/src/main.ts b/src/main.ts index 8ccb72bcb4..e2d3aa2f00 100644 --- a/src/main.ts +++ b/src/main.ts @@ -161,7 +161,7 @@ function checkForUpdatedVersion(context: vscode.ExtensionContext) { if (choice === showReleaseNotes) { vscode.commands.executeCommand( 'markdown.showPreview', - vscode.Uri.file(path.resolve(__dirname, "../CHANGELOG.md"))); + vscode.Uri.file(path.resolve(__dirname, "../../CHANGELOG.md"))); } }); } diff --git a/src/process.ts b/src/process.ts index 99e4b94734..8530ca8344 100644 --- a/src/process.ts +++ b/src/process.ts @@ -40,7 +40,7 @@ export class PowerShellProcess { let startScriptPath = path.resolve( __dirname, - '../scripts/Start-EditorServices.ps1'); + '../../scripts/Start-EditorServices.ps1'); var editorServicesLogPath = this.log.getLogFilePath(logFileName); @@ -77,7 +77,7 @@ export class PowerShellProcess { // NOTE: This batch file approach is needed temporarily until VS Code's // createTerminal API gets an argument for setting environment variables // on the launched process. - var batScriptPath = path.resolve(__dirname, '../sessions/powershell.bat'); + var batScriptPath = path.resolve(__dirname, '../../sessions/powershell.bat'); fs.writeFileSync( batScriptPath, `@set DEVPATH=${path.dirname(powerShellExePath)}\r\n@${powerShellExePath} %*`); diff --git a/src/session.ts b/src/session.ts index d38d517a1c..df997bf4fc 100644 --- a/src/session.ts +++ b/src/session.ts @@ -121,7 +121,7 @@ export class SessionManager implements Middleware { if (this.powerShellExePath) { - var bundledModulesPath = path.resolve(__dirname, "../modules"); + var bundledModulesPath = path.resolve(__dirname, "../../modules"); if (this.inDevelopmentMode) { var devBundledModulesPath = @@ -129,7 +129,7 @@ export class SessionManager implements Middleware { path.resolve( __dirname, this.sessionSettings.developer.bundledModulesPath || - "../../PowerShellEditorServices/module"); + "../../../PowerShellEditorServices/module"); // Make sure the module's bin path exists if (fs.existsSync(path.join(devBundledModulesPath, "PowerShellEditorServices/bin"))) { diff --git a/src/utils.ts b/src/utils.ts index 1fa9e0f30a..edffeb1d90 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -52,7 +52,7 @@ export interface WaitForSessionFileCallback { (details: EditorServicesSessionDetails, error: string): void; } -let sessionsFolder = path.resolve(__dirname, "..", "sessions/"); +let sessionsFolder = path.resolve(__dirname, "..", "..", "sessions/"); let sessionFilePathPrefix = path.resolve(sessionsFolder, "PSES-VSCode-" + process.env.VSCODE_PID); // Create the sessions path if it doesn't exist already diff --git a/test/index.ts b/test/index.ts new file mode 100644 index 0000000000..1d6519d0d3 --- /dev/null +++ b/test/index.ts @@ -0,0 +1,9 @@ +var testRunner = require('vscode/lib/testrunner'); + +// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for options +testRunner.configure({ + ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.) + useColors: true // colored output from test results +}); + +module.exports = testRunner; From de78edf63325b8ab88b1c77cfddcd76be49d3084 Mon Sep 17 00:00:00 2001 From: David Wilson Date: Fri, 1 Sep 2017 17:10:22 -0700 Subject: [PATCH 4/6] Fix issues with resolving correct PowerShell paths in 64-bit VS Code This change introduces a new module that contains all the logic for determining the current OS platform, process architecture and OS architecture for use in determining available Windows PowerShell EXE paths. This module helps resolve problems that were introduced by the new 64-bit distribution of Visual Studio Code. Fixes #1008 Fixes #1007 Fixes #1006 Fixes #993 --- src/platform.ts | 163 ++++++++++++++++++++++++++++++++++++++++++++++++ src/session.ts | 135 ++++++++++++++++----------------------- 2 files changed, 217 insertions(+), 81 deletions(-) create mode 100644 src/platform.ts diff --git a/src/platform.ts b/src/platform.ts new file mode 100644 index 0000000000..a148877a7d --- /dev/null +++ b/src/platform.ts @@ -0,0 +1,163 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import fs = require('fs'); +import os = require('os'); +import path = require('path'); +import vscode = require('vscode'); +import process = require('process'); +import Settings = require('./settings'); + +export enum OperatingSystem { + Unknown, + Windows, + MacOS, + Linux +} + +export interface PlatformDetails { + operatingSystem: OperatingSystem + isOS64Bit: boolean + isProcess64Bit: boolean +} + +interface PowerShellExeDetails { + versionName: string; + exePath: string; +} + +export function getPlatformDetails(): PlatformDetails { + var operatingSystem = OperatingSystem.Unknown; + + if (process.platform === "win32") { + operatingSystem = OperatingSystem.Windows; + } + else if (process.platform === "darwin") { + operatingSystem = OperatingSystem.MacOS; + } + else if (process.platform === "linux") { + operatingSystem = OperatingSystem.Linux; + } + + let isProcess64Bit = process.arch === "x64"; + + return { + operatingSystem: operatingSystem, + isOS64Bit: isProcess64Bit || process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'), + isProcess64Bit: isProcess64Bit + } +} + +export function getDefaultPowerShellPath( + platformDetails: PlatformDetails, + use32Bit: boolean = false): string | null { + + var powerShellExePath = undefined; + + // Find the path to powershell.exe based on the current platform + // and the user's desire to run the x86 version of PowerShell + if (platformDetails.operatingSystem == OperatingSystem.Windows) { + powerShellExePath = + use32Bit || !process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432') + ? System32PowerShellPath + : SysnativePowerShellPath + } + else if (platformDetails.operatingSystem == OperatingSystem.MacOS) { + powerShellExePath = "/usr/local/bin/powershell"; + } + else if (platformDetails.operatingSystem == OperatingSystem.Linux) { + powerShellExePath = "/usr/bin/powershell"; + } + + return powerShellExePath; +} + +export function getWindowsSystemPowerShellPath(systemFolderName: string) { + return `${process.env.windir}\\${systemFolderName}\\WindowsPowerShell\\v1.0\\powershell.exe` +} + +export const System32PowerShellPath = getWindowsSystemPowerShellPath('System32'); +export const SysnativePowerShellPath = getWindowsSystemPowerShellPath('Sysnative'); +export const SysWow64PowerShellPath = getWindowsSystemPowerShellPath('SysWow64'); + +const powerShell64BitPathOn32Bit = SysnativePowerShellPath.toLocaleLowerCase(); +const powerShell32BitPathOn64Bit = SysWow64PowerShellPath.toLocaleLowerCase(); + +export function fixWindowsPowerShellPath(powerShellExePath: string, platformDetails: PlatformDetails): string { + let lowerCasedPath = powerShellExePath.toLocaleLowerCase(); + + if ((platformDetails.isProcess64Bit && (lowerCasedPath === powerShell64BitPathOn32Bit)) || + (!platformDetails.isProcess64Bit && (lowerCasedPath === powerShell32BitPathOn64Bit))) { + return System32PowerShellPath; + } + + // If the path doesn't need to be fixed, return the original + return powerShellExePath; +} + +export function getPowerShellExeItems(platformDetails: PlatformDetails): PowerShellExeDetails[] { + + var paths: PowerShellExeDetails[] = []; + + const windowsPowerShell64BitLabel = "Windows PowerShell (x64)"; + const windowsPowerShell32BitLabel = "Windows PowerShell (x86)"; + + if (platformDetails.operatingSystem === OperatingSystem.Windows) { + const psCoreInstallPath = + (!platformDetails.isProcess64Bit ? process.env.ProgramW6432 : process.env.ProgramFiles) + '\\PowerShell'; + + if (platformDetails.isProcess64Bit) { + paths.push({ + versionName: windowsPowerShell64BitLabel, + exePath: System32PowerShellPath + }) + + paths.push({ + versionName: windowsPowerShell32BitLabel, + exePath: SysWow64PowerShellPath + }) + } + else { + if (platformDetails.isOS64Bit) { + paths.push({ + versionName: windowsPowerShell64BitLabel, + exePath: SysnativePowerShellPath + }) + } + + paths.push({ + versionName: windowsPowerShell32BitLabel, + exePath: System32PowerShellPath + }) + } + + if (fs.existsSync(psCoreInstallPath)) { + var psCorePaths = + fs.readdirSync(psCoreInstallPath) + .map(item => path.join(psCoreInstallPath, item)) + .filter(item => fs.lstatSync(item).isDirectory()) + .map(item => { + return { + versionName: `PowerShell Core ${path.parse(item).base}`, + exePath: path.join(item, "powershell.exe") + }; + }); + + if (psCorePaths) { + paths = paths.concat(psCorePaths); + } + } + } + else { + paths.push({ + versionName: "PowerShell Core", + exePath: + os.platform() === "darwin" + ? "/usr/local/bin/powershell" + : "/usr/bin/powershell" + }); + } + + return paths; +} diff --git a/src/session.ts b/src/session.ts index df997bf4fc..e26f6e28a7 100644 --- a/src/session.ts +++ b/src/session.ts @@ -16,12 +16,18 @@ import { IFeature } from './feature'; import { Message } from 'vscode-jsonrpc'; import { PowerShellProcess } from './process'; import { StringDecoder } from 'string_decoder'; + import { LanguageClient, LanguageClientOptions, Executable, RequestType, RequestType0, NotificationType, StreamInfo, ErrorAction, CloseAction, RevealOutputChannelOn, Middleware, ResolveCodeLensSignature } from 'vscode-languageclient'; +import { + OperatingSystem, PlatformDetails, getDefaultPowerShellPath, + getPlatformDetails, fixWindowsPowerShellPath, + getPowerShellExeItems } from './platform'; + export enum SessionStatus { NotStarted, Initializing, @@ -35,12 +41,12 @@ export class SessionManager implements Middleware { private ShowSessionMenuCommandName = "PowerShell.ShowSessionMenu"; private hostVersion: string; - private isWindowsOS: boolean; private editorServicesArgs: string; private powerShellExePath: string = ""; private sessionStatus: SessionStatus; private suppressRestartPrompt: boolean; private focusConsoleOnExecute: boolean; + private platformDetails: PlatformDetails; private extensionFeatures: IFeature[] = []; private statusBarItem: vscode.StatusBarItem; private languageServerProcess: PowerShellProcess; @@ -61,7 +67,7 @@ export class SessionManager implements Middleware { private requiredEditorServicesVersion: string, private log: Logger) { - this.isWindowsOS = os.platform() == "win32"; + this.platformDetails = getPlatformDetails(); // Get the current version of this extension this.hostVersion = @@ -542,9 +548,53 @@ export class SessionManager implements Middleware { this.sessionSettings.developer.powerShellExePath || "").trim(); + if (this.platformDetails.operatingSystem === OperatingSystem.Windows && + powerShellExePath.length > 0) { + + // Check the path bitness + let fixedPath = + fixWindowsPowerShellPath( + powerShellExePath, + this.platformDetails); + + if (fixedPath !== powerShellExePath) { + let bitness = this.platformDetails.isOS64Bit ? 64 : 32; + // Show deprecation message with fix action. + // We don't need to wait on this to complete + // because we can finish gathering the configured + // PowerShell path without the fix + vscode + .window + .showWarningMessage( + `The specified PowerShell path is incorrect for ${bitness}-bit VS Code, using '${fixedPath}' instead.`, + "Fix Setting Automatically") + .then(choice => { + if (choice) { + this.suppressRestartPrompt = true; + Settings + .change( + "powerShellExePath", + this.sessionSettings.developer.powerShellExePath, + true) + .then(() => { + return Settings.change( + "developer.powerShellExePath", + undefined, + true) + }) + .then(() => { + this.suppressRestartPrompt = false; + }); + } + }); + + powerShellExePath = fixedPath; + } + } + return powerShellExePath.length > 0 ? this.resolvePowerShellPath(powerShellExePath) - : this.getDefaultPowerShellPath(this.sessionSettings.useX86Host); + : getDefaultPowerShellPath(this.platformDetails, this.sessionSettings.useX86Host); } private changePowerShellExePath(exePath: string) { @@ -554,78 +604,6 @@ export class SessionManager implements Middleware { .then(() => this.restartSession()); } - private getPowerShellExeItems(): PowerShellExeDetails[] { - - var paths: PowerShellExeDetails[] = []; - - if (this.isWindowsOS) { - const is64Bit = process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'); - const rootInstallPath = (is64Bit ? process.env.ProgramW6432 : process.env.ProgramFiles) + '\\PowerShell'; - - if (fs.existsSync(rootInstallPath)) { - var psCorePaths = - fs.readdirSync(rootInstallPath) - .map(item => path.join(rootInstallPath, item)) - .filter(item => fs.lstatSync(item).isDirectory()) - .map(item => { - return { - versionName: `PowerShell Core ${path.parse(item).base}`, - exePath: path.join(item, "powershell.exe") - }; - }); - - if (psCorePaths) { - paths = paths.concat(psCorePaths); - } - } - - if (is64Bit) { - paths.push({ - versionName: "Windows PowerShell (x64)", - exePath: process.env.windir + '\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe' - }) - } - - paths.push({ - versionName: "Windows PowerShell (x86)", - exePath: process.env.windir + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe' - }) - } - else { - paths.push({ - versionName: "PowerShell Core", - exePath: - os.platform() === "darwin" - ? "/usr/local/bin/powershell" - : "/usr/bin/powershell" - }); - } - - return paths; - } - - private getDefaultPowerShellPath(use32Bit: boolean): string | null { - - // Find the path to powershell.exe based on the current platform - // and the user's desire to run the x86 version of PowerShell - var powerShellExePath = undefined; - - if (this.isWindowsOS) { - powerShellExePath = - use32Bit || !process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432') - ? process.env.windir + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe' - : process.env.windir + '\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe'; - } - else if (os.platform() == "darwin") { - powerShellExePath = "/usr/local/bin/powershell"; - } - else { - powerShellExePath = "/usr/bin/powershell"; - } - - return this.resolvePowerShellPath(powerShellExePath); - } - private resolvePowerShellPath(powerShellExePath: string): string { var resolvedPath = path.resolve(__dirname, powerShellExePath); @@ -679,7 +657,7 @@ export class SessionManager implements Middleware { var currentExePath = this.powerShellExePath.toLowerCase(); var powerShellItems = - this.getPowerShellExeItems() + getPowerShellExeItems(this.platformDetails) .filter(item => item.exePath.toLowerCase() !== currentExePath) .map(item => { return new SessionMenuItem( @@ -748,11 +726,6 @@ export class SessionManager implements Middleware { } } -interface PowerShellExeDetails { - versionName: string; - exePath: string; -} - class SessionMenuItem implements vscode.QuickPickItem { public description: string; From e103f939fed171b80e1c1c2187603d4a74a387cc Mon Sep 17 00:00:00 2001 From: David Wilson Date: Tue, 5 Sep 2017 12:06:45 -0700 Subject: [PATCH 5/6] Add unit tests to verify platform module behavior --- .vscode/tasks.json | 12 ++++ src/platform.ts | 34 ++++++---- src/session.ts | 4 +- test/platform.test.ts | 121 ++++++++++++++++++++++++++++++++++++ vscode-powershell.build.ps1 | 7 ++- 5 files changed, 162 insertions(+), 16 deletions(-) create mode 100644 test/platform.test.ts diff --git a/.vscode/tasks.json b/.vscode/tasks.json index f608a99300..9fa487f25f 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -54,6 +54,18 @@ "Invoke-Build Build" ], "problemMatcher": [] + }, + { + "taskName": "Test", + "suppressTaskName": true, + "args": [ + "Invoke-Build Test" + ], + "group": { + "kind": "test", + "isDefault": true + }, + "problemMatcher": [] } ] } diff --git a/src/platform.ts b/src/platform.ts index a148877a7d..adcaae1294 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -22,7 +22,7 @@ export interface PlatformDetails { isProcess64Bit: boolean } -interface PowerShellExeDetails { +export interface PowerShellExeDetails { versionName: string; exePath: string; } @@ -58,10 +58,18 @@ export function getDefaultPowerShellPath( // Find the path to powershell.exe based on the current platform // and the user's desire to run the x86 version of PowerShell if (platformDetails.operatingSystem == OperatingSystem.Windows) { - powerShellExePath = - use32Bit || !process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432') - ? System32PowerShellPath - : SysnativePowerShellPath + if (use32Bit) { + powerShellExePath = + platformDetails.isOS64Bit && platformDetails.isProcess64Bit + ? SysWow64PowerShellPath + : System32PowerShellPath + } + else { + powerShellExePath = + !platformDetails.isOS64Bit || platformDetails.isProcess64Bit + ? System32PowerShellPath + : SysnativePowerShellPath + } } else if (platformDetails.operatingSystem == OperatingSystem.MacOS) { powerShellExePath = "/usr/local/bin/powershell"; @@ -81,6 +89,9 @@ export const System32PowerShellPath = getWindowsSystemPowerShellPath('System32') export const SysnativePowerShellPath = getWindowsSystemPowerShellPath('Sysnative'); export const SysWow64PowerShellPath = getWindowsSystemPowerShellPath('SysWow64'); +export const WindowsPowerShell64BitLabel = "Windows PowerShell (x64)"; +export const WindowsPowerShell32BitLabel = "Windows PowerShell (x86)"; + const powerShell64BitPathOn32Bit = SysnativePowerShellPath.toLocaleLowerCase(); const powerShell32BitPathOn64Bit = SysWow64PowerShellPath.toLocaleLowerCase(); @@ -96,38 +107,35 @@ export function fixWindowsPowerShellPath(powerShellExePath: string, platformDeta return powerShellExePath; } -export function getPowerShellExeItems(platformDetails: PlatformDetails): PowerShellExeDetails[] { +export function getAvailablePowerShellExes(platformDetails: PlatformDetails): PowerShellExeDetails[] { var paths: PowerShellExeDetails[] = []; - const windowsPowerShell64BitLabel = "Windows PowerShell (x64)"; - const windowsPowerShell32BitLabel = "Windows PowerShell (x86)"; - if (platformDetails.operatingSystem === OperatingSystem.Windows) { const psCoreInstallPath = (!platformDetails.isProcess64Bit ? process.env.ProgramW6432 : process.env.ProgramFiles) + '\\PowerShell'; if (platformDetails.isProcess64Bit) { paths.push({ - versionName: windowsPowerShell64BitLabel, + versionName: WindowsPowerShell64BitLabel, exePath: System32PowerShellPath }) paths.push({ - versionName: windowsPowerShell32BitLabel, + versionName: WindowsPowerShell32BitLabel, exePath: SysWow64PowerShellPath }) } else { if (platformDetails.isOS64Bit) { paths.push({ - versionName: windowsPowerShell64BitLabel, + versionName: WindowsPowerShell64BitLabel, exePath: SysnativePowerShellPath }) } paths.push({ - versionName: windowsPowerShell32BitLabel, + versionName: WindowsPowerShell32BitLabel, exePath: System32PowerShellPath }) } diff --git a/src/session.ts b/src/session.ts index e26f6e28a7..bc31bddcb6 100644 --- a/src/session.ts +++ b/src/session.ts @@ -26,7 +26,7 @@ import { import { OperatingSystem, PlatformDetails, getDefaultPowerShellPath, getPlatformDetails, fixWindowsPowerShellPath, - getPowerShellExeItems } from './platform'; + getAvailablePowerShellExes } from './platform'; export enum SessionStatus { NotStarted, @@ -657,7 +657,7 @@ export class SessionManager implements Middleware { var currentExePath = this.powerShellExePath.toLowerCase(); var powerShellItems = - getPowerShellExeItems(this.platformDetails) + getAvailablePowerShellExes(this.platformDetails) .filter(item => item.exePath.toLowerCase() !== currentExePath) .map(item => { return new SessionMenuItem( diff --git a/test/platform.test.ts b/test/platform.test.ts new file mode 100644 index 0000000000..b115451e77 --- /dev/null +++ b/test/platform.test.ts @@ -0,0 +1,121 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import * as assert from 'assert'; +import * as vscode from 'vscode'; +import * as platform from '../src/platform'; + +function checkDefaultPowerShellPath(platformDetails, expectedPath) { + test("returns correct default path", () => { + assert.equal( + platform.getDefaultPowerShellPath(platformDetails), + expectedPath); + }); +} + +function checkAvailableWindowsPowerShellPaths( + platformDetails: platform.PlatformDetails, + expectedPaths: platform.PowerShellExeDetails[]) { + test("correctly enumerates available Windows PowerShell paths", () => { + + // The system may return PowerShell Core paths so only + // enumerate the first list items. + let enumeratedPaths = platform.getAvailablePowerShellExes(platformDetails); + for (var i; i < expectedPaths.length; i++) { + assert.equal(enumeratedPaths[i], expectedPaths[i]); + } + }); +} + +function checkFixedWindowsPowerShellpath(platformDetails, inputPath, expectedPath) { + test("fixes incorrect Windows PowerShell Sys* path", () => { + assert.equal( + platform.fixWindowsPowerShellPath(inputPath, platformDetails), + expectedPath); + }); +} + +suite("Platform module", () => { + + suite("64-bit Windows, 64-bit VS Code", () => { + let platformDetails: platform.PlatformDetails = { + operatingSystem: platform.OperatingSystem.Windows, + isOS64Bit: true, + isProcess64Bit: true + }; + + checkDefaultPowerShellPath( + platformDetails, + platform.System32PowerShellPath); + + checkAvailableWindowsPowerShellPaths( + platformDetails, + [ + { + versionName: platform.WindowsPowerShell64BitLabel, + exePath: platform.System32PowerShellPath + }, + { + versionName: platform.WindowsPowerShell32BitLabel, + exePath: platform.SysWow64PowerShellPath + } + ]); + + checkFixedWindowsPowerShellpath( + platformDetails, + platform.SysnativePowerShellPath, + platform.System32PowerShellPath); + }); + + suite("64-bit Windows, 32-bit VS Code", () => { + let platformDetails: platform.PlatformDetails = { + operatingSystem: platform.OperatingSystem.Windows, + isOS64Bit: true, + isProcess64Bit: false + }; + + checkDefaultPowerShellPath( + platformDetails, + platform.SysnativePowerShellPath); + + checkAvailableWindowsPowerShellPaths( + platformDetails, + [ + { + versionName: platform.WindowsPowerShell64BitLabel, + exePath: platform.SysnativePowerShellPath + }, + { + versionName: platform.WindowsPowerShell32BitLabel, + exePath: platform.System32PowerShellPath + } + ]); + + checkFixedWindowsPowerShellpath( + platformDetails, + platform.SysWow64PowerShellPath, + platform.System32PowerShellPath); + }); + + suite("32-bit Windows, 32-bit VS Code", () => { + let platformDetails: platform.PlatformDetails = { + operatingSystem: platform.OperatingSystem.Windows, + isOS64Bit: false, + isProcess64Bit: false + }; + + checkDefaultPowerShellPath( + platformDetails, + platform.System32PowerShellPath); + + checkAvailableWindowsPowerShellPaths( + platformDetails, + [ + { + versionName: platform.WindowsPowerShell32BitLabel, + exePath: platform.System32PowerShellPath + } + ]); + }); +}); diff --git a/vscode-powershell.build.ps1 b/vscode-powershell.build.ps1 index bd3163be30..4ba4e4481e 100644 --- a/vscode-powershell.build.ps1 +++ b/vscode-powershell.build.ps1 @@ -92,6 +92,11 @@ task BuildEditorServices { task BuildAll BuildEditorServices, Build -Before Package +task Test Build, { + Write-Host "`n### Running extension tests" -ForegroundColor Green + exec { & npm run test } +} + task Package { if ($script:psesBuildScriptPath) { @@ -112,4 +117,4 @@ task UploadArtifacts -If { $env:AppVeyor } { } # The default task is to run the entire CI build -task . GetExtensionVersion, CleanAll, BuildAll, Package, UploadArtifacts +task . GetExtensionVersion, CleanAll, BuildAll, Test, Package, UploadArtifacts From 6e3b17971149fa25e32f56b00e2f386c8358eb34 Mon Sep 17 00:00:00 2001 From: David Wilson Date: Tue, 5 Sep 2017 12:47:21 -0700 Subject: [PATCH 6/6] PowerShell session commands should activate extension This change fixes #1020 which states that PowerShell session commands should activate the extension if it isn't already started. These commands are useful for activating a PowerShell session if the user doesn't currently have a PowerShell file open. --- package.json | 4 +++- src/session.ts | 10 +--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 4a4cb33002..6c2cd53bc5 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,9 @@ "onCommand:PowerShell.StartDebugSession", "onCommand:PowerShell.PickPSHostProcess", "onCommand:PowerShell.SpecifyScriptArgs", - "onCommand:PowerShell.ShowSessionConsole" + "onCommand:PowerShell.ShowSessionConsole", + "onCommand:PowerShell.ShowSessionMenu", + "onCommand:PowerShell.RestartSession" ], "dependencies": { "vscode-languageclient": "3.3.0-alpha.6" diff --git a/src/session.ts b/src/session.ts index bc31bddcb6..cff60f1336 100644 --- a/src/session.ts +++ b/src/session.ts @@ -628,14 +628,6 @@ export class SessionManager implements Middleware { private showSessionMenu() { var menuItems: SessionMenuItem[] = []; - if (this.sessionStatus === SessionStatus.Initializing || - this.sessionStatus === SessionStatus.NotStarted || - this.sessionStatus === SessionStatus.Stopping) { - - // Don't show a menu for these states - return; - } - if (this.sessionStatus === SessionStatus.Running) { menuItems = [ new SessionMenuItem( @@ -655,7 +647,7 @@ export class SessionManager implements Middleware { ]; } - var currentExePath = this.powerShellExePath.toLowerCase(); + var currentExePath = (this.powerShellExePath || "").toLowerCase(); var powerShellItems = getAvailablePowerShellExes(this.platformDetails) .filter(item => item.exePath.toLowerCase() !== currentExePath)