diff --git a/jest.config.ts b/jest.config.ts index 382ee7ce..5718e72e 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -3,6 +3,7 @@ import type { Config } from '@jest/types' export default async (): Promise => { return { bail: 1, + forceExit: true, modulePathIgnorePatterns: ['dist', '.vscode-test'], transform: { '^.+\\.tsx?$': [ diff --git a/package-lock.json b/package-lock.json index 96bc8eca..8a89835c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,7 +37,7 @@ "ts-node": "^10.7.0", "typescript": "^4.6.2", "utility-types": "^3.10.0", - "vsce": "^2.7.0", + "vsce": "^2.15.0", "vscode-dts": "^0.3.3" }, "engines": { @@ -3778,9 +3778,9 @@ "dev": true }, "node_modules/decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", "dev": true, "engines": { "node": ">=0.10" @@ -12511,9 +12511,10 @@ } }, "node_modules/vsce": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/vsce/-/vsce-2.7.0.tgz", - "integrity": "sha512-CKU34wrQlbKDeJCRBkd1a8iwF9EvNxcYMg9hAUH6AxFGR6Wo2IKWwt3cJIcusHxx6XdjDHWlfAS/fJN30uvVnA==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/vsce/-/vsce-2.15.0.tgz", + "integrity": "sha512-P8E9LAZvBCQnoGoizw65JfGvyMqNGlHdlUXD1VAuxtvYAaHBKLBdKPnpy60XKVDAkQCfmMu53g+gq9FM+ydepw==", + "deprecated": "vsce has been renamed to @vscode/vsce. Install using @vscode/vsce instead.", "dev": true, "dependencies": { "azure-devops-node-api": "^11.0.1", @@ -15740,9 +15741,9 @@ "dev": true }, "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", "dev": true }, "decompress-response": { @@ -22344,9 +22345,9 @@ } }, "vsce": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/vsce/-/vsce-2.7.0.tgz", - "integrity": "sha512-CKU34wrQlbKDeJCRBkd1a8iwF9EvNxcYMg9hAUH6AxFGR6Wo2IKWwt3cJIcusHxx6XdjDHWlfAS/fJN30uvVnA==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/vsce/-/vsce-2.15.0.tgz", + "integrity": "sha512-P8E9LAZvBCQnoGoizw65JfGvyMqNGlHdlUXD1VAuxtvYAaHBKLBdKPnpy60XKVDAkQCfmMu53g+gq9FM+ydepw==", "dev": true, "requires": { "azure-devops-node-api": "^11.0.1", diff --git a/package.json b/package.json index 995847d5..a676beeb 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,11 @@ "command": "pester.toggleAutoRunOnSave", "title": "Pester: Toggle Auto Run on Save", "category": "PowerShell" + }, + { + "command": "pester.stopPowerShell", + "title": "Pester: Stop PowerShell background process", + "category": "PowerShell" } ], "menus": { @@ -128,7 +133,7 @@ "ts-node": "^10.7.0", "typescript": "^4.6.2", "utility-types": "^3.10.0", - "vsce": "^2.7.0", + "vsce": "^2.15.0", "vscode-dts": "^0.3.3" }, "dependencies": { diff --git a/src/extension.ts b/src/extension.ts index 4fba7f0e..d23f5520 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -65,9 +65,24 @@ export async function activate(context: ExtensionContext) { }) } + const controller = new PesterTestController(powershellExtension, context) + const stopPowerShellCommand = commands.registerCommand( + 'pester.stopPowershell', + () => { + if (controller.stopPowerShell()) { + window.showInformationMessage('PowerShell background process stopped.') + } else { + window.showWarningMessage( + 'No PowerShell background process was running !' + ) + } + } + ) + context.subscriptions.push( - new PesterTestController(powershellExtension, context), + controller, toggleAutoRunOnSaveCommand, + stopPowerShellCommand, autoRunStatusBarItem, autoRunStatusBarVisibleEvent, updateAutoRunStatusBarOnConfigChange diff --git a/src/pesterTestController.ts b/src/pesterTestController.ts index 1a57171e..5fda1e26 100644 --- a/src/pesterTestController.ts +++ b/src/pesterTestController.ts @@ -505,6 +505,7 @@ export class PesterTestController implements Disposable { log.warn( `Detected PowerShell Session change from ${this.ps.exePath} to ${exePath}. Restarting Pester Runner.` ) + this.ps.reset() } const exePathDir = exePath ? dirname(exePath) @@ -520,6 +521,9 @@ export class PesterTestController implements Disposable { // Objects from the run will return to the success stream, which we then send to the return handler const psOutput = new PSOutput() psOutput.success.on('data', returnHandler) + psOutput.success.on('close', () => { + testRun?.end() + }) if (usePSIC) { log.debug('Running Script in PSIC:', scriptPath, scriptArgs) @@ -712,6 +716,13 @@ export class PesterTestController implements Disposable { return testItems } + stopPowerShell(): boolean { + if (this.ps !== undefined) { + return this.ps.reset() + } + return false + } + dispose() { this.testController.dispose() this.returnServer.dispose() diff --git a/src/powershell.test.ts b/src/powershell.test.ts index 037ed986..e0412f79 100644 --- a/src/powershell.test.ts +++ b/src/powershell.test.ts @@ -2,7 +2,12 @@ import { execSync } from 'child_process' import ReadlineTransform from 'readline-transform' import { Readable } from 'stream' import { pipeline } from 'stream/promises' -import { createJsonParseTransform, PowerShell, PSOutput } from './powershell' +import { + createJsonParseTransform, + PowerShell, + PSOutput, + defaultPowershellExePath +} from './powershell' // jest.setTimeout(30000) @@ -79,8 +84,8 @@ describe('run', () => { it('mixed', async () => { expect.assertions(3) - const successResult = [] - const infoResult = [] + const successResult: any[] = [] + const infoResult: any[] = [] const streams = new PSOutput() streams.success .on('data', data => { @@ -152,7 +157,7 @@ describe('exec', () => { }) it('pwsh baseline', () => { - const result = execSync('pwsh -nop -c "echo hello"') + const result = execSync(`${defaultPowershellExePath} -nop -c "echo hello"`) expect(result.toString()).toMatch('hello') }) diff --git a/src/powershell.ts b/src/powershell.ts index 1256f9e1..ff23f866 100644 --- a/src/powershell.ts +++ b/src/powershell.ts @@ -145,6 +145,11 @@ export function createSplitPSOutputStream(streams: IPSOutput) { }) } +export const defaultPowershellExePath = + process.platform === 'win32' + ? 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe' + : 'pwsh' + /** Represents an instance of a PowerShell process. By default this will use pwsh if installed, and will fall back to PowerShell on Windows, * unless the exepath parameter is specified. Use the exePath parameter to specify specific powershell executables * such as pwsh-preview or a pwsh executable not located in the PATH @@ -165,7 +170,8 @@ export class PowerShell { if (path !== undefined) { this.resolvedExePath = path } else if (process.platform === 'win32') { - this.resolvedExePath = 'powershell' + this.resolvedExePath = + 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe' } else { throw new Error( 'pwsh not found in your path and you are not on Windows so PowerShell 5.1 is not an option. Did you install PowerShell first?' @@ -299,7 +305,10 @@ export class PowerShell { async exec(script: string, cancelExisting?: boolean) { const psOutput = new PSOutputUnified() await this.run(script, psOutput, undefined, cancelExisting) - await finished(psOutput.success) + + if (!psOutput.success.destroyed) { + await finished(psOutput.success) + } const result: Record[] = [] for (;;) { const output = psOutput.success.read() as Record @@ -322,18 +331,19 @@ export class PowerShell { } /** Kill any existing invocations and reset the state */ - reset() { + reset(): boolean { + let result = false if (this.psProcess !== undefined) { // We use SIGKILL to keep the behavior consistent between Windows and Linux (die immediately) this.psProcess.kill('SIGKILL') + result = true } // Initialize will reinstate the process upon next call this.psProcess = undefined + return result } dispose() { - if (this.psProcess !== undefined) { - this.psProcess.kill() - } + this.reset() } }