diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts index d1191614b576dc4483572ecb81616f02c8d30cb3..3df611b17b46949afe7a968a364fd03f5f6ff965 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { window, Pseudoterminal, EventEmitter, TerminalDimensions, workspace, ConfigurationTarget } from 'vscode'; +import { window, Pseudoterminal, EventEmitter, TerminalDimensions, workspace, ConfigurationTarget, Disposable } from 'vscode'; import { doesNotThrow, equal, ok, deepEqual } from 'assert'; suite('window namespace tests', () => { @@ -12,75 +12,91 @@ suite('window namespace tests', () => { await workspace.getConfiguration('terminal.integrated').update('windowsEnableConpty', false, ConfigurationTarget.Global); }); suite('Terminal', () => { + let disposables: Disposable[] = []; + + teardown(() => { + disposables.forEach(d => d.dispose()); + disposables.length = 0; + }); + test('sendText immediately after createTerminal should not throw', (done) => { - const reg1 = window.onDidOpenTerminal(term => { - equal(terminal, term); + disposables.push(window.onDidOpenTerminal(term => { + try { + equal(terminal, term); + } catch (e) { + done(e); + } terminal.dispose(); - reg1.dispose(); - const reg2 = window.onDidCloseTerminal(() => { - reg2.dispose(); - done(); - }); - }); + disposables.push(window.onDidCloseTerminal(() => done())); + })); const terminal = window.createTerminal(); doesNotThrow(terminal.sendText.bind(terminal, 'echo "foo"')); }); test('onDidCloseTerminal event fires when terminal is disposed', (done) => { - const reg1 = window.onDidOpenTerminal(term => { - equal(terminal, term); + disposables.push(window.onDidOpenTerminal(term => { + try { + equal(terminal, term); + } catch (e) { + done(e); + } terminal.dispose(); - reg1.dispose(); - const reg2 = window.onDidCloseTerminal(() => { - reg2.dispose(); - done(); - }); - }); + disposables.push(window.onDidCloseTerminal(() => done())); + })); const terminal = window.createTerminal(); }); test('processId immediately after createTerminal should fetch the pid', (done) => { - const reg1 = window.onDidOpenTerminal(term => { - equal(terminal, term); - reg1.dispose(); + disposables.push(window.onDidOpenTerminal(term => { + try { + equal(terminal, term); + } catch (e) { + done(e); + } terminal.processId.then(id => { - ok(id > 0); + try { + ok(id > 0); + } catch (e) { + done(e); + } terminal.dispose(); - const reg2 = window.onDidCloseTerminal(() => { - reg2.dispose(); - done(); - }); + disposables.push(window.onDidCloseTerminal(() => done())); }); - }); + })); const terminal = window.createTerminal(); }); test('name in constructor should set terminal.name', (done) => { - const reg1 = window.onDidOpenTerminal(term => { - equal(terminal, term); + disposables.push(window.onDidOpenTerminal(term => { + try { + equal(terminal, term); + } catch (e) { + done(e); + } terminal.dispose(); - reg1.dispose(); - const reg2 = window.onDidCloseTerminal(() => { - reg2.dispose(); - done(); - }); - }); + disposables.push(window.onDidCloseTerminal(() => done())); + })); const terminal = window.createTerminal('a'); - equal(terminal.name, 'a'); + try { + equal(terminal.name, 'a'); + } catch (e) { + done(e); + } }); test('onDidOpenTerminal should fire when a terminal is created', (done) => { - const reg1 = window.onDidOpenTerminal(term => { - equal(term.name, 'b'); - reg1.dispose(); - const reg2 = window.onDidCloseTerminal(() => { - reg2.dispose(); - done(); - }); + disposables.push(window.onDidOpenTerminal(term => { + try { + equal(term.name, 'b'); + } catch (e) { + done(e); + } + disposables.push(window.onDidCloseTerminal(() => done())); terminal.dispose(); - }); + })); const terminal = window.createTerminal('b'); }); + // test('onDidChangeActiveTerminal should fire when new terminals are created', (done) => { // const reg1 = window.onDidChangeActiveTerminal((active: Terminal | undefined) => { // equal(active, terminal); @@ -154,16 +170,20 @@ suite('window namespace tests', () => { suite('hideFromUser', () => { test('should be available to terminals API', done => { const terminal = window.createTerminal({ name: 'bg', hideFromUser: true }); - window.onDidOpenTerminal(t => { - equal(t, terminal); - equal(t.name, 'bg'); - ok(window.terminals.indexOf(terminal) !== -1); - const reg3 = window.onDidCloseTerminal(() => { - reg3.dispose(); + disposables.push(window.onDidOpenTerminal(t => { + try { + equal(t, terminal); + equal(t.name, 'bg'); + ok(window.terminals.indexOf(terminal) !== -1); + } catch (e) { + done(e); + } + disposables.push(window.onDidCloseTerminal(() => { + // reg3.dispose(); done(); - }); + })); terminal.dispose(); - }); + })); }); }); @@ -172,32 +192,34 @@ suite('window namespace tests', () => { const openEvents: string[] = []; const dataEvents: { name: string, data: string }[] = []; const closeEvents: string[] = []; - const reg1 = window.onDidOpenTerminal(e => openEvents.push(e.name)); + disposables.push(window.onDidOpenTerminal(e => openEvents.push(e.name))); let resolveOnceDataWritten: (() => void) | undefined; let resolveOnceClosed: (() => void) | undefined; - const reg2 = window.onDidWriteTerminalData(e => { + disposables.push(window.onDidWriteTerminalData(e => { dataEvents.push({ name: e.terminal.name, data: e.data }); resolveOnceDataWritten!(); - }); + })); - const reg3 = window.onDidCloseTerminal(e => { + disposables.push(window.onDidCloseTerminal(e => { closeEvents.push(e.name); - - if (closeEvents.length === 1) { - deepEqual(openEvents, ['test1']); - deepEqual(dataEvents, [{ name: 'test1', data: 'write1' }]); - deepEqual(closeEvents, ['test1']); - resolveOnceClosed!(); - } else if (closeEvents.length === 2) { - deepEqual(openEvents, ['test1', 'test2']); - deepEqual(dataEvents, [{ name: 'test1', data: 'write1' }, { name: 'test2', data: 'write2' }]); - deepEqual(closeEvents, ['test1', 'test2']); + try { + if (closeEvents.length === 1) { + deepEqual(openEvents, ['test1']); + deepEqual(dataEvents, [{ name: 'test1', data: 'write1' }]); + deepEqual(closeEvents, ['test1']); + } else if (closeEvents.length === 2) { + deepEqual(openEvents, ['test1', 'test2']); + deepEqual(dataEvents, [{ name: 'test1', data: 'write1' }, { name: 'test2', data: 'write2' }]); + deepEqual(closeEvents, ['test1', 'test2']); + } resolveOnceClosed!(); + } catch (e) { + done(e); } - }); + })); const term1Write = new EventEmitter(); const term1Close = new EventEmitter(); @@ -233,9 +255,6 @@ suite('window namespace tests', () => { // Wait until the terminal is closed await new Promise(resolve => { resolveOnceClosed = resolve; }); - reg1.dispose(); - reg2.dispose(); - reg3.dispose(); done(); }, close: () => { } @@ -250,15 +269,15 @@ suite('window namespace tests', () => { suite('Extension pty terminals', () => { test('should fire onDidOpenTerminal and onDidCloseTerminal', (done) => { - const reg1 = window.onDidOpenTerminal(term => { - equal(term.name, 'c'); - reg1.dispose(); - const reg2 = window.onDidCloseTerminal(() => { - reg2.dispose(); - done(); - }); + disposables.push(window.onDidOpenTerminal(term => { + try { + equal(term.name, 'c'); + } catch (e) { + done(e); + } + disposables.push(window.onDidCloseTerminal(() => done())); term.dispose(); - }); + })); const pty: Pseudoterminal = { onDidWrite: new EventEmitter().event, open: () => { }, @@ -315,22 +334,29 @@ suite('window namespace tests', () => { // }); test('should respect dimension overrides', (done) => { - const reg1 = window.onDidOpenTerminal(term => { - equal(terminal, term); - reg1.dispose(); + disposables.push(window.onDidOpenTerminal(term => { + try { + equal(terminal, term); + } catch (e) { + done(e); + } term.show(); - const reg2 = window.onDidChangeTerminalDimensions(e => { - equal(e.dimensions.columns, 10); - equal(e.dimensions.rows, 5); - equal(e.terminal, terminal); - reg2.dispose(); - const reg3 = window.onDidCloseTerminal(() => { - reg3.dispose(); - done(); - }); + disposables.push(window.onDidChangeTerminalDimensions(e => { + if (e.dimensions.columns === 0 || e.dimensions.rows === 0) { + // HACK: Ignore the event if dimension(s) are zero (#83778) + return; + } + try { + equal(e.dimensions.columns, 10); + equal(e.dimensions.rows, 5); + equal(e.terminal, terminal); + } catch (e) { + done(e); + } + disposables.push(window.onDidCloseTerminal(() => done())); terminal.dispose(); - }); - }); + })); + })); const writeEmitter = new EventEmitter(); const overrideDimensionsEmitter = new EventEmitter(); const pty: Pseudoterminal = { diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts index 525b5a9318c27aa497113f69a19988c41906006e..edb404b86532b426e2ef711396d6ca48e32dd1b6 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts @@ -4,14 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as vscode from 'vscode'; +import { window, tasks, Disposable, TaskDefinition, Task, EventEmitter, CustomExecution, Pseudoterminal, TaskScope, commands, Task2 } from 'vscode'; suite('workspace-namespace', () => { suite('Tasks', () => { + let disposables: Disposable[] = []; + + teardown(() => { + disposables.forEach(d => d.dispose()); + disposables.length = 0; + }); test('CustomExecution task should start and shutdown successfully', (done) => { - interface CustomTestingTaskDefinition extends vscode.TaskDefinition { + interface CustomTestingTaskDefinition extends TaskDefinition { /** * One of the task properties. This can be used to customize the task in the tasks.json */ @@ -19,45 +25,58 @@ suite('workspace-namespace', () => { } const taskType: string = 'customTesting'; const taskName = 'First custom task'; - const reg1 = vscode.window.onDidOpenTerminal(term => { - reg1.dispose(); - const reg2 = vscode.window.onDidWriteTerminalData(e => { - reg2.dispose(); - assert.equal(e.data, 'testing\r\n'); + let isPseudoterminalClosed = false; + disposables.push(window.onDidOpenTerminal(term => { + disposables.push(window.onDidWriteTerminalData(e => { + try { + assert.equal(e.data, 'testing\r\n'); + } catch (e) { + done(e); + } + disposables.push(window.onDidCloseTerminal(() => { + try { + // Pseudoterminal.close should have fired by now, additionally we want + // to make sure all events are flushed before continuing with more tests + assert.ok(isPseudoterminalClosed); + } catch (e) { + done(e); + return; + } + done(); + })); term.dispose(); - }); - }); - const taskProvider = vscode.tasks.registerTaskProvider(taskType, { + })); + })); + disposables.push(tasks.registerTaskProvider(taskType, { provideTasks: () => { - const result: vscode.Task[] = []; + const result: Task[] = []; const kind: CustomTestingTaskDefinition = { type: taskType, customProp1: 'testing task one' }; - const writeEmitter = new vscode.EventEmitter(); - const execution = new vscode.CustomExecution((): Thenable => { - const pty: vscode.Pseudoterminal = { + const writeEmitter = new EventEmitter(); + const execution = new CustomExecution((): Thenable => { + const pty: Pseudoterminal = { onDidWrite: writeEmitter.event, - open: () => { - writeEmitter.fire('testing\r\n'); - }, - close: () => { - taskProvider.dispose(); - done(); - } + open: () => writeEmitter.fire('testing\r\n'), + close: () => isPseudoterminalClosed = true }; return Promise.resolve(pty); }); - const task = new vscode.Task2(kind, vscode.TaskScope.Workspace, taskName, taskType, execution); + const task = new Task2(kind, TaskScope.Workspace, taskName, taskType, execution); result.push(task); return result; }, - resolveTask(_task: vscode.Task): vscode.Task | undefined { + resolveTask(_task: Task): Task | undefined { + try { assert.fail('resolveTask should not trigger during the test'); + } catch (e) { + done(e); + } return undefined; } - }); - vscode.commands.executeCommand('workbench.action.tasks.runTask', `${taskType}: ${taskName}`); + })); + commands.executeCommand('workbench.action.tasks.runTask', `${taskType}: ${taskName}`); }); }); }); diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 16a1cafd9779cbce3deef5b7f43a1f692807a9aa..40ecebf643b216bc6fd9944da202c4e2a610adac 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -369,7 +369,7 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ if (this._terminalProcesses[id]) { // Extension pty terminal only - when virtual process resize fires it means that the // terminal's maximum dimensions changed - this._terminalProcesses[id].resize(cols, rows); + this._terminalProcesses[id]?.resize(cols, rows); } } @@ -481,12 +481,12 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ } public $acceptProcessInput(id: number, data: string): void { - this._terminalProcesses[id].input(data); + this._terminalProcesses[id]?.input(data); } public $acceptProcessResize(id: number, cols: number, rows: number): void { try { - this._terminalProcesses[id].resize(cols, rows); + this._terminalProcesses[id]?.resize(cols, rows); } catch (error) { // We tried to write to a closed pipe / channel. if (error.code !== 'EPIPE' && error.code !== 'ERR_IPC_CHANNEL_CLOSED') { @@ -496,15 +496,15 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ } public $acceptProcessShutdown(id: number, immediate: boolean): void { - this._terminalProcesses[id].shutdown(immediate); + this._terminalProcesses[id]?.shutdown(immediate); } public $acceptProcessRequestInitialCwd(id: number): void { - this._terminalProcesses[id].getInitialCwd().then(initialCwd => this._proxy.$sendProcessInitialCwd(id, initialCwd)); + this._terminalProcesses[id]?.getInitialCwd().then(initialCwd => this._proxy.$sendProcessInitialCwd(id, initialCwd)); } public $acceptProcessRequestCwd(id: number): void { - this._terminalProcesses[id].getCwd().then(cwd => this._proxy.$sendProcessCwd(id, cwd)); + this._terminalProcesses[id]?.getCwd().then(cwd => this._proxy.$sendProcessCwd(id, cwd)); } public $acceptProcessRequestLatency(id: number): number {