diff --git a/src/features/Examples.ts b/src/features/Examples.ts index 96861ac9f5..5b3856f1e0 100644 --- a/src/features/Examples.ts +++ b/src/features/Examples.ts @@ -15,7 +15,7 @@ export class ExamplesFeature implements vscode.Disposable { 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 utils.fileExists(this.examplesPath); + return utils.checkIfFileExists(this.examplesPath); }); } diff --git a/src/features/PesterTests.ts b/src/features/PesterTests.ts index 397ab1aba1..71c674b95c 100644 --- a/src/features/PesterTests.ts +++ b/src/features/PesterTests.ts @@ -134,7 +134,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 utils.fileExists(this.invokePesterStubScriptPath) + return utils.checkIfFileExists(this.invokePesterStubScriptPath) && vscode.debug.startDebugging(vscode.workspace.workspaceFolders?.[0], launchConfig); } } diff --git a/src/platform.ts b/src/platform.ts index b75e302f45..68abdbf443 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -1,23 +1,25 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import * as child_process from "child_process"; import * as fs from "fs"; import * as os from "os"; import * as path from "path"; import * as process from "process"; import { IPowerShellAdditionalExePathSettings } from "./settings"; +// This uses require so we can rewire it in unit tests! +// tslint:disable-next-line:no-var-requires +const utils = require("./utils") const WindowsPowerShell64BitLabel = "Windows PowerShell (x64)"; const WindowsPowerShell32BitLabel = "Windows PowerShell (x86)"; -const LinuxExePath = "/usr/bin/pwsh"; +const LinuxExePath = "/usr/bin/pwsh"; const LinuxPreviewExePath = "/usr/bin/pwsh-preview"; -const SnapExePath = "/snap/bin/pwsh"; -const SnapPreviewExePath = "/snap/bin/pwsh-preview"; +const SnapExePath = "/snap/bin/pwsh"; +const SnapPreviewExePath = "/snap/bin/pwsh-preview"; -const MacOSExePath = "/usr/local/bin/pwsh"; +const MacOSExePath = "/usr/local/bin/pwsh"; const MacOSPreviewExePath = "/usr/local/bin/pwsh-preview"; export enum OperatingSystem { @@ -36,6 +38,7 @@ export interface IPlatformDetails { export interface IPowerShellExeDetails { readonly displayName: string; readonly exePath: string; + readonly supportsProperArguments: boolean; } export function getPlatformDetails(): IPlatformDetails { @@ -97,8 +100,8 @@ export class PowerShellExeFinder { /** * Returns the first available PowerShell executable found in the search order. */ - public getFirstAvailablePowerShellInstallation(): IPowerShellExeDetails { - for (const pwsh of this.enumeratePowerShellInstallations()) { + public async getFirstAvailablePowerShellInstallation(): Promise { + for await (const pwsh of this.enumeratePowerShellInstallations()) { return pwsh; } } @@ -106,8 +109,12 @@ export class PowerShellExeFinder { /** * Get an array of all PowerShell executables found when searching for PowerShell installations. */ - public getAllAvailablePowerShellInstallations(): IPowerShellExeDetails[] { - return Array.from(this.enumeratePowerShellInstallations()); + public async getAllAvailablePowerShellInstallations(): Promise { + const array: IPowerShellExeDetails[] = []; + for await (const pwsh of this.enumeratePowerShellInstallations()) { + array.push(pwsh); + } + return array; } /** @@ -137,10 +144,10 @@ export class PowerShellExeFinder { * PowerShell items returned by this object are verified * to exist on the filesystem. */ - public *enumeratePowerShellInstallations(): Iterable { + public async *enumeratePowerShellInstallations(): AsyncIterable { // Get the default PowerShell installations first - for (const defaultPwsh of this.enumerateDefaultPowerShellInstallations()) { - if (defaultPwsh && defaultPwsh.exists()) { + for await (const defaultPwsh of this.enumerateDefaultPowerShellInstallations()) { + if (defaultPwsh && await defaultPwsh.exists()) { yield defaultPwsh; } } @@ -148,7 +155,7 @@ export class PowerShellExeFinder { // Also show any additionally configured PowerShells // These may be duplicates of the default installations, but given a different name. for (const additionalPwsh of this.enumerateAdditionalPowerShellInstallations()) { - if (additionalPwsh && additionalPwsh.exists()) { + if (additionalPwsh && await additionalPwsh.exists()) { yield additionalPwsh; } } @@ -159,7 +166,7 @@ export class PowerShellExeFinder { * Returned values may not exist, but come with an .exists property * which will check whether the executable exists. */ - private *enumerateDefaultPowerShellInstallations(): Iterable { + private async *enumerateDefaultPowerShellInstallations(): AsyncIterable { // Find PSCore stable first yield this.findPSCoreStable(); @@ -174,7 +181,7 @@ export class PowerShellExeFinder { yield this.findPSCoreWindowsInstallation({ useAlternateBitness: true }); // Also look for the MSIX/UWP installation - yield this.findPSCoreMsix(); + yield await this.findPSCoreMsix(); break; } @@ -213,7 +220,7 @@ export class PowerShellExeFinder { } /** - * Iterates through the configured additonal PowerShell executable locations, + * Iterates through the configured additional PowerShell executable locations, * without checking for their existence. */ private *enumerateAdditionalPowerShellInstallations(): Iterable { @@ -227,7 +234,7 @@ export class PowerShellExeFinder { } } - private findPSCoreStable(): IPossiblePowerShellExe { + private async findPSCoreStable(): Promise { switch (this.platformDetails.operatingSystem) { case OperatingSystem.Linux: return new PossiblePowerShellExe(LinuxExePath, "PowerShell"); @@ -236,11 +243,11 @@ export class PowerShellExeFinder { return new PossiblePowerShellExe(MacOSExePath, "PowerShell"); case OperatingSystem.Windows: - return this.findPSCoreWindowsInstallation(); + return await this.findPSCoreWindowsInstallation(); } } - private findPSCorePreview(): IPossiblePowerShellExe { + private async findPSCorePreview(): Promise { switch (this.platformDetails.operatingSystem) { case OperatingSystem.Linux: return new PossiblePowerShellExe(LinuxPreviewExePath, "PowerShell Preview"); @@ -249,7 +256,7 @@ export class PowerShellExeFinder { return new PossiblePowerShellExe(MacOSPreviewExePath, "PowerShell Preview"); case OperatingSystem.Windows: - return this.findPSCoreWindowsInstallation({ findPreview: true }); + return await this.findPSCoreWindowsInstallation({ findPreview: true }); } } @@ -260,10 +267,11 @@ export class PowerShellExeFinder { const dotnetGlobalToolExePath: string = path.join(os.homedir(), ".dotnet", "tools", exeName); - return new PossiblePowerShellExe(dotnetGlobalToolExePath, ".NET Core PowerShell Global Tool"); + // The dotnet installed version of PowerShell does not support proper argument parsing, and so it fails with our multi-line startup banner. + return new PossiblePowerShellExe(dotnetGlobalToolExePath, ".NET Core PowerShell Global Tool", undefined, false); } - private findPSCoreMsix({ findPreview }: { findPreview?: boolean } = {}): IPossiblePowerShellExe { + private async findPSCoreMsix({ findPreview }: { findPreview?: boolean } = {}): Promise { // We can't proceed if there's no LOCALAPPDATA path if (!process.env.LOCALAPPDATA) { return null; @@ -272,7 +280,7 @@ export class PowerShellExeFinder { // Find the base directory for MSIX application exe shortcuts const msixAppDir = path.join(process.env.LOCALAPPDATA, "Microsoft", "WindowsApps"); - if (!fileExistsSync(msixAppDir)) { + if (!await utils.checkIfDirectoryExists(msixAppDir)) { return null; } @@ -282,6 +290,7 @@ export class PowerShellExeFinder { : { pwshMsixDirRegex: PowerShellExeFinder.PwshMsixRegex, pwshMsixName: "PowerShell (Store)" }; // We should find only one such application, so return on the first one + // TODO: Use VS Code async fs API for this. for (const subdir of fs.readdirSync(msixAppDir)) { if (pwshMsixDirRegex.test(subdir)) { const pwshMsixPath = path.join(msixAppDir, subdir, "pwsh.exe"); @@ -301,9 +310,9 @@ export class PowerShellExeFinder { return new PossiblePowerShellExe(SnapPreviewExePath, "PowerShell Preview Snap"); } - private findPSCoreWindowsInstallation( + private async findPSCoreWindowsInstallation( { useAlternateBitness = false, findPreview = false }: - { useAlternateBitness?: boolean; findPreview?: boolean } = {}): IPossiblePowerShellExe { + { useAlternateBitness?: boolean; findPreview?: boolean } = {}): Promise { const programFilesPath: string = this.getProgramFilesPath({ useAlternateBitness }); @@ -314,13 +323,7 @@ export class PowerShellExeFinder { const powerShellInstallBaseDir = path.join(programFilesPath, "PowerShell"); // Ensure the base directory exists - try { - const powerShellInstallBaseDirLStat = fs.lstatSync(powerShellInstallBaseDir); - if (!powerShellInstallBaseDirLStat.isDirectory()) - { - return null; - } - } catch { + if (!await utils.checkIfDirectoryExists(powerShellInstallBaseDir)) { return null; } @@ -366,7 +369,7 @@ export class PowerShellExeFinder { // Now look for the file const exePath = path.join(powerShellInstallBaseDir, item, "pwsh.exe"); - if (!fs.existsSync(exePath)) { + if (!await utils.checkIfFileExists(exePath)) { continue; } @@ -413,7 +416,7 @@ export class PowerShellExeFinder { displayName = WindowsPowerShell32BitLabel; } - winPS = new PossiblePowerShellExe(winPSPath, displayName, { knownToExist: true }); + winPS = new PossiblePowerShellExe(winPSPath, displayName, true); if (useAlternateBitness) { this.alternateBitnessWinPS = winPS; @@ -479,40 +482,20 @@ export function getWindowsSystemPowerShellPath(systemFolderName: string) { "powershell.exe"); } -function fileExistsSync(filePath: string): boolean { - try { - // This will throw if the path does not exist, - // and otherwise returns a value that we don't care about - fs.lstatSync(filePath); - return true; - } catch { - return false; - } -} - interface IPossiblePowerShellExe extends IPowerShellExeDetails { - exists(): boolean; + exists(): Promise; } class PossiblePowerShellExe implements IPossiblePowerShellExe { - public readonly exePath: string; - public readonly displayName: string; - - private knownToExist: boolean; - constructor( - pathToExe: string, - installationName: string, - { knownToExist = false }: { knownToExist?: boolean } = {}) { - - this.exePath = pathToExe; - this.displayName = installationName; - this.knownToExist = knownToExist || undefined; - } + public readonly exePath: string, + public readonly displayName: string, + private knownToExist?: boolean, + public readonly supportsProperArguments: boolean = true) { } - public exists(): boolean { + public async exists(): Promise { if (this.knownToExist === undefined) { - this.knownToExist = fileExistsSync(this.exePath); + this.knownToExist = await utils.checkIfFileExists(this.exePath); } return this.knownToExist; } diff --git a/src/process.ts b/src/process.ts index 4898e8b836..1ac687d130 100644 --- a/src/process.ts +++ b/src/process.ts @@ -9,7 +9,7 @@ import vscode = require("vscode"); import { Logger } from "./logging"; import Settings = require("./settings"); import utils = require("./utils"); -import { IEditorServicesSessionDetails, SessionManager } from "./session"; +import { IEditorServicesSessionDetails } from "./session"; export class PowerShellProcess { public static escapeSingleQuotes(psPath: string): string { @@ -83,12 +83,14 @@ export class PowerShellProcess { PowerShellProcess.escapeSingleQuotes(psesModulePath) + "'; Start-EditorServices " + this.startPsesArgs; + // On Windows we unfortunately can't Base64 encode the startup command + // because it annoys some poorly implemented anti-virus scanners. if (utils.isWindows) { powerShellArgs.push( "-Command", startEditorServices); } else { - // Use -EncodedCommand for better quote support on non-Windows + // Otherwise use -EncodedCommand for better quote support. powerShellArgs.push( "-EncodedCommand", Buffer.from(startEditorServices, "utf16le").toString("base64")); diff --git a/src/session.ts b/src/session.ts index 47e2665bb6..82c4024fc9 100644 --- a/src/session.ts +++ b/src/session.ts @@ -148,9 +148,9 @@ export class SessionManager implements Middleware { this.migrateWhitespaceAroundPipeSetting(); try { - let powerShellExeDetails; + let powerShellExeDetails: IPowerShellExeDetails; if (this.sessionSettings.powerShellDefaultVersion) { - for (const details of this.powershellExeFinder.enumeratePowerShellInstallations()) { + for await (const details of this.powershellExeFinder.enumeratePowerShellInstallations()) { // Need to compare names case-insensitively, from https://stackoverflow.com/a/2140723 const wantedName = this.sessionSettings.powerShellDefaultVersion; if (wantedName.localeCompare(details.displayName, undefined, { sensitivity: "accent" }) === 0) { @@ -161,7 +161,7 @@ export class SessionManager implements Middleware { } this.PowerShellExeDetails = powerShellExeDetails || - this.powershellExeFinder.getFirstAvailablePowerShellInstallation(); + await this.powershellExeFinder.getFirstAvailablePowerShellInstallation(); } catch (e) { this.log.writeError(`Error occurred while searching for a PowerShell executable:\n${e}`); @@ -215,6 +215,13 @@ export class SessionManager implements Middleware { if (this.sessionSettings.integratedConsole.suppressStartupBanner) { this.editorServicesArgs += "-StartupBanner '' "; + } else if (utils.isWindows && !this.PowerShellExeDetails.supportsProperArguments) { + // NOTE: On Windows we don't Base64 encode the startup command + // because it annoys some poorly implemented anti-virus scanners. + // Unfortunately this means that for some installs of PowerShell + // (such as through the `dotnet` package manager), we can't include + // a multi-line startup banner as the quotes break the command. + this.editorServicesArgs += `-StartupBanner '${this.HostName} Extension v${this.HostVersion}' `; } else { const startupBanner = `${this.HostName} Extension v${this.HostVersion} Copyright (c) Microsoft Corporation. @@ -463,7 +470,7 @@ Type 'help' to get help. private registerCommands(): void { this.registeredCommands = [ vscode.commands.registerCommand("PowerShell.RestartSession", () => { this.restartSession(); }), - vscode.commands.registerCommand(this.ShowSessionMenuCommandName, () => { this.showSessionMenu(); }), + vscode.commands.registerCommand(this.ShowSessionMenuCommandName, async () => { await this.showSessionMenu(); }), vscode.workspace.onDidChangeConfiguration(async () => { await this.onConfigurationUpdated(); }), vscode.commands.registerCommand( "PowerShell.ShowSessionConsole", (isExecute?: boolean) => { this.showSessionConsole(isExecute); }), @@ -795,8 +802,8 @@ Type 'help' to get help. } } - private showSessionMenu() { - const availablePowerShellExes = this.powershellExeFinder.getAllAvailablePowerShellInstallations(); + private async showSessionMenu() { + const availablePowerShellExes = await this.powershellExeFinder.getAllAvailablePowerShellInstallations(); let sessionText: string; diff --git a/src/utils.ts b/src/utils.ts index c2d7b2f5da..14ce0491e0 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -3,30 +3,12 @@ "use strict"; -import fs = require("fs"); import os = require("os"); import path = require("path"); import vscode = require("vscode"); export const PowerShellLanguageId = "powershell"; -// 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; @@ -37,22 +19,28 @@ export function getPipePath(pipeName: string) { } } -export async function checkIfFileExists(filePath: vscode.Uri): Promise { +// Check that the file or directory exists in an asynchronous manner that relies +// solely on the VS Code API, not Node's fs library, ignoring symlinks. +async function checkIfFileOrDirectoryExists(targetPath: string | vscode.Uri, type: vscode.FileType): Promise { try { - const stat: vscode.FileStat = await vscode.workspace.fs.stat(filePath); - return stat.type === vscode.FileType.File; - } catch (e) { + const stat: vscode.FileStat = await vscode.workspace.fs.stat( + targetPath instanceof vscode.Uri + ? targetPath + : vscode.Uri.file(targetPath)); + // tslint:disable-next-line:no-bitwise + return (stat.type & type) !== 0; + } catch { + // TODO: Maybe throw if it's not a FileNotFound exception. return false; } } -export async function checkIfDirectoryExists(directoryPath: string): Promise { - try { - const stat: vscode.FileStat = await vscode.workspace.fs.stat(vscode.Uri.file(directoryPath)); - return stat.type === vscode.FileType.Directory; - } catch (e) { - return false; - } +export async function checkIfFileExists(filePath: string | vscode.Uri): Promise { + return await checkIfFileOrDirectoryExists(filePath, vscode.FileType.File); +} + +export async function checkIfDirectoryExists(directoryPath: string | vscode.Uri): Promise { + return await checkIfFileOrDirectoryExists(directoryPath, vscode.FileType.Directory); } export function getTimestampString() { diff --git a/test/core/paths.test.ts b/test/core/paths.test.ts index 7dc8eb2801..e81e7accaa 100644 --- a/test/core/paths.test.ts +++ b/test/core/paths.test.ts @@ -3,7 +3,6 @@ import * as assert from "assert"; import * as fs from "fs"; -import * as path from "path"; import * as vscode from "vscode"; import { IPowerShellExtensionClient } from "../../src/features/ExternalApi"; import utils = require("../utils"); diff --git a/test/core/platform.test.ts b/test/core/platform.test.ts index b813f60dd8..433299b6d6 100644 --- a/test/core/platform.test.ts +++ b/test/core/platform.test.ts @@ -5,8 +5,29 @@ import * as assert from "assert"; import mockFS = require("mock-fs"); import FileSystem = require("mock-fs/lib/filesystem"); import * as path from "path"; +import rewire = require("rewire"); import * as sinon from "sinon"; import * as platform from "../../src/platform"; +import * as fs from "fs"; +import * as vscode from "vscode"; + +// We have to rewire the platform module so that mock-fs can be used, as it +// overrides the fs module but not the vscode.workspace.fs module. +const platformMock = rewire("../../src/platform"); + +async function fakeCheckIfFileOrDirectoryExists(targetPath: string | vscode.Uri): Promise { + try { + fs.lstatSync(targetPath instanceof vscode.Uri ? targetPath.fsPath : targetPath); + return true; + } catch { + return false; + } +} +const utilsMock = { + checkIfFileExists: fakeCheckIfFileOrDirectoryExists, + checkIfDirectoryExists: fakeCheckIfFileOrDirectoryExists +} +platformMock.__set__("utils", utilsMock); /** * Describes a platform on which the PowerShell extension should work, @@ -56,34 +77,42 @@ if (process.platform === "win32") { { exePath: "C:\\Program Files\\PowerShell\\6\\pwsh.exe", displayName: "PowerShell (x64)", + supportsProperArguments: true }, { exePath: "C:\\Program Files (x86)\\PowerShell\\6\\pwsh.exe", displayName: "PowerShell (x86)", + supportsProperArguments: true }, { exePath: pwshMsixPath, displayName: "PowerShell (Store)", + supportsProperArguments: true }, { exePath: "C:\\Program Files\\PowerShell\\7-preview\\pwsh.exe", displayName: "PowerShell Preview (x64)", + supportsProperArguments: true }, { exePath: pwshPreviewMsixPath, displayName: "PowerShell Preview (Store)", + supportsProperArguments: true }, { exePath: "C:\\Program Files (x86)\\PowerShell\\7-preview\\pwsh.exe", displayName: "PowerShell Preview (x86)", + supportsProperArguments: true }, { exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x64)", + supportsProperArguments: true }, { exePath: "C:\\WINDOWS\\SysWOW64\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x86)", + supportsProperArguments: true }, ], filesystem: { @@ -135,10 +164,12 @@ if (process.platform === "win32") { { exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x64)", + supportsProperArguments: true }, { exePath: "C:\\WINDOWS\\SysWOW64\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x86)", + supportsProperArguments: true }, ], filesystem: { @@ -166,34 +197,42 @@ if (process.platform === "win32") { { exePath: "C:\\Program Files (x86)\\PowerShell\\6\\pwsh.exe", displayName: "PowerShell (x86)", + supportsProperArguments: true }, { exePath: "C:\\Program Files\\PowerShell\\6\\pwsh.exe", displayName: "PowerShell (x64)", + supportsProperArguments: true }, { exePath: pwshMsixPath, displayName: "PowerShell (Store)", + supportsProperArguments: true }, { exePath: "C:\\Program Files (x86)\\PowerShell\\7-preview\\pwsh.exe", displayName: "PowerShell Preview (x86)", + supportsProperArguments: true }, { exePath: pwshPreviewMsixPath, displayName: "PowerShell Preview (Store)", + supportsProperArguments: true }, { exePath: "C:\\Program Files\\PowerShell\\7-preview\\pwsh.exe", displayName: "PowerShell Preview (x64)", + supportsProperArguments: true }, { exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x86)", + supportsProperArguments: true }, { exePath: "C:\\WINDOWS\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x64)", + supportsProperArguments: true }, ], filesystem: { @@ -245,10 +284,12 @@ if (process.platform === "win32") { { exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x86)", + supportsProperArguments: true }, { exePath: "C:\\WINDOWS\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x64)", + supportsProperArguments: true }, ], filesystem: { @@ -276,22 +317,27 @@ if (process.platform === "win32") { { exePath: "C:\\Program Files (x86)\\PowerShell\\6\\pwsh.exe", displayName: "PowerShell (x86)", + supportsProperArguments: true }, { exePath: pwshMsixPath, displayName: "PowerShell (Store)", + supportsProperArguments: true }, { exePath: "C:\\Program Files (x86)\\PowerShell\\7-preview\\pwsh.exe", displayName: "PowerShell Preview (x86)", + supportsProperArguments: true }, { exePath: pwshPreviewMsixPath, displayName: "PowerShell Preview (Store)", + supportsProperArguments: true }, { exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x86)", + supportsProperArguments: true }, ], filesystem: { @@ -332,6 +378,7 @@ if (process.platform === "win32") { { exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x86)", + supportsProperArguments: true }, ], filesystem: { @@ -340,6 +387,43 @@ if (process.platform === "win32") { }, }, }, + { + name: "Windows (dotnet)", + platformDetails: { + operatingSystem: platform.OperatingSystem.Windows, + isOS64Bit: true, + isProcess64Bit: true, + }, + environmentVars: { + "USERNAME": "test", + "USERPROFILE": "C:\\Users\\test", + "ProgramFiles": "C:\\Program Files", + "ProgramFiles(x86)": "C:\\Program Files (x86)", + "windir": "C:\\WINDOWS", + }, + expectedPowerShellSequence: [ + { + exePath: "C:\\Users\\test\\.dotnet\\tools\\pwsh.exe", + displayName: ".NET Core PowerShell Global Tool", + supportsProperArguments: false + }, + { + exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + displayName: "Windows PowerShell (x64)", + supportsProperArguments: true + }, + { + exePath: "C:\\WINDOWS\\SysWOW64\\WindowsPowerShell\\v1.0\\powershell.exe", + displayName: "Windows PowerShell (x86)", + supportsProperArguments: true + }, + ], + filesystem: { + "C:\\Users\\test\\.dotnet\\tools": { + "pwsh.exe": "", + }, + }, + }, ]; } else { successTestCases = [ @@ -351,10 +435,26 @@ if (process.platform === "win32") { isProcess64Bit: true, }, expectedPowerShellSequence: [ - { exePath: "/usr/bin/pwsh", displayName: "PowerShell" }, - { exePath: "/snap/bin/pwsh", displayName: "PowerShell Snap" }, - { exePath: "/usr/bin/pwsh-preview", displayName: "PowerShell Preview" }, - { exePath: "/snap/bin/pwsh-preview", displayName: "PowerShell Preview Snap" }, + { + exePath: "/usr/bin/pwsh", + displayName: "PowerShell", + supportsProperArguments: true + }, + { + exePath: "/snap/bin/pwsh", + displayName: "PowerShell Snap", + supportsProperArguments: true + }, + { + exePath: "/usr/bin/pwsh-preview", + displayName: "PowerShell Preview", + supportsProperArguments: true + }, + { + exePath: "/snap/bin/pwsh-preview", + displayName: "PowerShell Preview Snap", + supportsProperArguments: true + }, ], filesystem: { "/usr/bin": { @@ -375,8 +475,16 @@ if (process.platform === "win32") { isProcess64Bit: true, }, expectedPowerShellSequence: [ - { exePath: "/usr/local/bin/pwsh", displayName: "PowerShell" }, - { exePath: "/usr/local/bin/pwsh-preview", displayName: "PowerShell Preview" }, + { + exePath: "/usr/local/bin/pwsh", + displayName: "PowerShell", + supportsProperArguments: true + }, + { + exePath: "/usr/local/bin/pwsh-preview", + displayName: "PowerShell Preview", + supportsProperArguments: true + }, ], filesystem: { "/usr/local/bin": { @@ -393,7 +501,11 @@ if (process.platform === "win32") { isProcess64Bit: true, }, expectedPowerShellSequence: [ - { exePath: "/usr/bin/pwsh", displayName: "PowerShell" }, + { + exePath: "/usr/bin/pwsh", + displayName: "PowerShell", + supportsProperArguments: true + }, ], filesystem: { "/usr/bin": { @@ -409,7 +521,11 @@ if (process.platform === "win32") { isProcess64Bit: true, }, expectedPowerShellSequence: [ - { exePath: "/snap/bin/pwsh", displayName: "PowerShell Snap" }, + { + exePath: "/snap/bin/pwsh", + displayName: "PowerShell Snap", + supportsProperArguments: true + }, ], filesystem: { "/snap/bin": { @@ -425,7 +541,11 @@ if (process.platform === "win32") { isProcess64Bit: true, }, expectedPowerShellSequence: [ - { exePath: "/usr/local/bin/pwsh", displayName: "PowerShell" }, + { + exePath: "/usr/local/bin/pwsh", + displayName: "PowerShell", + supportsProperArguments: true + }, ], filesystem: { "/usr/local/bin": { @@ -433,6 +553,54 @@ if (process.platform === "win32") { }, }, }, + { + name: "MacOS (dotnet)", + platformDetails: { + operatingSystem: platform.OperatingSystem.MacOS, + isOS64Bit: true, + isProcess64Bit: true, + }, + environmentVars: { + "USER": "test", + "HOME": "/Users/test", + }, + expectedPowerShellSequence: [ + { + exePath: "/Users/test/.dotnet/tools/pwsh", + displayName: ".NET Core PowerShell Global Tool", + supportsProperArguments: false + }, + ], + filesystem: { + "/Users/test/.dotnet/tools": { + pwsh: "", + }, + }, + }, + { + name: "Linux (dotnet)", + platformDetails: { + operatingSystem: platform.OperatingSystem.Linux, + isOS64Bit: true, + isProcess64Bit: true, + }, + environmentVars: { + "USER": "test", + "HOME": "/home/test", + }, + expectedPowerShellSequence: [ + { + exePath: "/home/test/.dotnet/tools/pwsh", + displayName: ".NET Core PowerShell Global Tool", + supportsProperArguments: false + }, + ], + filesystem: { + "/home/test/.dotnet/tools": { + pwsh: "", + }, + }, + }, ]; } @@ -469,7 +637,7 @@ function setupTestEnvironment(testPlatform: ITestPlatform) { describe("Platform module", function () { it("Gets the correct platform details", function () { - const platformDetails: platform.IPlatformDetails = platform.getPlatformDetails(); + const platformDetails: platform.IPlatformDetails = platformMock.getPlatformDetails(); switch (process.platform) { case "darwin": assert.strictEqual( @@ -528,26 +696,27 @@ describe("Platform module", function () { }); for (const testPlatform of successTestCases) { - it(`Finds it on ${testPlatform.name}`, function () { + it(`Finds it on ${testPlatform.name}`, async function () { setupTestEnvironment(testPlatform); - const powerShellExeFinder = new platform.PowerShellExeFinder(testPlatform.platformDetails); + const powerShellExeFinder = new platformMock.PowerShellExeFinder(testPlatform.platformDetails); - const defaultPowerShell = powerShellExeFinder.getFirstAvailablePowerShellInstallation(); + const defaultPowerShell = await powerShellExeFinder.getFirstAvailablePowerShellInstallation(); const expectedPowerShell = testPlatform.expectedPowerShellSequence[0]; assert.strictEqual(defaultPowerShell.exePath, expectedPowerShell.exePath); assert.strictEqual(defaultPowerShell.displayName, expectedPowerShell.displayName); + assert.strictEqual(defaultPowerShell.supportsProperArguments, expectedPowerShell.supportsProperArguments); }); } for (const testPlatform of errorTestCases) { - it(`Fails gracefully on ${testPlatform.name}`, function () { + it(`Fails gracefully on ${testPlatform.name}`, async function () { setupTestEnvironment(testPlatform); - const powerShellExeFinder = new platform.PowerShellExeFinder(testPlatform.platformDetails); + const powerShellExeFinder = new platformMock.PowerShellExeFinder(testPlatform.platformDetails); - const defaultPowerShell = powerShellExeFinder.getFirstAvailablePowerShellInstallation(); + const defaultPowerShell = await powerShellExeFinder.getFirstAvailablePowerShellInstallation(); assert.strictEqual(defaultPowerShell, undefined); }); } @@ -560,12 +729,12 @@ describe("Platform module", function () { }); for (const testPlatform of successTestCases) { - it(`Finds them on ${testPlatform.name}`, function () { + it(`Finds them on ${testPlatform.name}`, async function () { setupTestEnvironment(testPlatform); - const powerShellExeFinder = new platform.PowerShellExeFinder(testPlatform.platformDetails); + const powerShellExeFinder = new platformMock.PowerShellExeFinder(testPlatform.platformDetails); - const foundPowerShells = powerShellExeFinder.getAllAvailablePowerShellInstallations(); + const foundPowerShells = await powerShellExeFinder.getAllAvailablePowerShellInstallations(); for (let i = 0; i < testPlatform.expectedPowerShellSequence.length; i++) { const foundPowerShell = foundPowerShells[i]; @@ -573,6 +742,7 @@ describe("Platform module", function () { assert.strictEqual(foundPowerShell && foundPowerShell.exePath, expectedPowerShell.exePath); assert.strictEqual(foundPowerShell && foundPowerShell.displayName, expectedPowerShell.displayName); + assert.strictEqual(foundPowerShell && foundPowerShell.supportsProperArguments, expectedPowerShell.supportsProperArguments); } assert.strictEqual( @@ -583,12 +753,12 @@ describe("Platform module", function () { } for (const testPlatform of errorTestCases) { - it(`Fails gracefully on ${testPlatform.name}`, function () { + it(`Fails gracefully on ${testPlatform.name}`, async function () { setupTestEnvironment(testPlatform); - const powerShellExeFinder = new platform.PowerShellExeFinder(testPlatform.platformDetails); + const powerShellExeFinder = new platformMock.PowerShellExeFinder(testPlatform.platformDetails); - const foundPowerShells = powerShellExeFinder.getAllAvailablePowerShellInstallations(); + const foundPowerShells = await powerShellExeFinder.getAllAvailablePowerShellInstallations(); assert.strictEqual(foundPowerShells.length, 0); }); } @@ -626,7 +796,7 @@ describe("Platform module", function () { altWinPSPath = null; } - const powerShellExeFinder = new platform.PowerShellExeFinder(testPlatform.platformDetails); + const powerShellExeFinder = new platformMock.PowerShellExeFinder(testPlatform.platformDetails); assert.strictEqual(powerShellExeFinder.fixWindowsPowerShellPath(winPSPath), winPSPath); diff --git a/test/features/RunCode.test.ts b/test/features/RunCode.test.ts index d016436fe0..83b1ad5b07 100644 --- a/test/features/RunCode.test.ts +++ b/test/features/RunCode.test.ts @@ -7,7 +7,6 @@ import * as path from "path"; import rewire = require("rewire"); import vscode = require("vscode"); import utils = require("../utils"); -import { sleep } from "../../src/utils"; // Setup function that is not exported. const customViews = rewire("../../src/features/RunCode"); diff --git a/tsconfig.json b/tsconfig.json index 1bfaad4864..beb738d4ad 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,8 +4,11 @@ // the sources which the tests need). The extension is built with `esbuild`. "module": "commonjs", "outDir": "out", - "target": "ES6", - "lib": [ "ES6", "DOM" ], + "target": "ES2022", + "lib": [ + "ES2022", + "DOM" + ], "sourceMap": true, "rootDir": ".", // TODO: We need to enable stricter checking...