terminal.test.ts 25.6 KB
Newer Older
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

6
import { window, Pseudoterminal, EventEmitter, TerminalDimensions, workspace, ConfigurationTarget, Disposable, UIKind, env, EnvironmentVariableMutatorType, EnvironmentVariableMutator, extensions, ExtensionContext } from 'vscode';
7
import { doesNotThrow, equal, ok, deepEqual, throws } from 'assert';
8

D
Daniel Imms 已提交
9 10 11 12
// Disable terminal tests:
// - Web https://github.com/microsoft/vscode/issues/92826
// - Remote https://github.com/microsoft/vscode/issues/96057
((env.uiKind === UIKind.Web || typeof env.remoteName !== 'undefined') ? suite.skip : suite)('vscode API - terminal', () => {
13 14
	let extensionContext: ExtensionContext;

15
	suiteSetup(async () => {
16 17 18 19
		// Trigger extension activation and grab the context as some tests depend on it
		await extensions.getExtension('vscode.vscode-api-tests')?.activate();
		extensionContext = (global as any).testExtensionContext;

20
		const config = workspace.getConfiguration('terminal.integrated');
21
		// Disable conpty in integration tests because of https://github.com/microsoft/vscode/issues/76548
22 23 24
		await config.update('windowsEnableConpty', false, ConfigurationTarget.Global);
		// Disable exit alerts as tests may trigger then and we're not testing the notifications
		await config.update('showExitAlert', false, ConfigurationTarget.Global);
D
Daniel Imms 已提交
25 26
		// Canvas can cause problems when running in a container
		await config.update('rendererType', 'dom', ConfigurationTarget.Global);
27
	});
28

29
	suite('Terminal', () => {
30 31 32 33 34 35 36
		let disposables: Disposable[] = [];

		teardown(() => {
			disposables.forEach(d => d.dispose());
			disposables.length = 0;
		});

37
		test('sendText immediately after createTerminal should not throw', (done) => {
38
			disposables.push(window.onDidOpenTerminal(term => {
D
Daniel Imms 已提交
39 40 41 42
				try {
					equal(terminal, term);
				} catch (e) {
					done(e);
D
Daniel Imms 已提交
43
					return;
D
Daniel Imms 已提交
44
				}
45
				terminal.dispose();
46 47
				disposables.push(window.onDidCloseTerminal(() => done()));
			}));
48 49 50 51
			const terminal = window.createTerminal();
			doesNotThrow(terminal.sendText.bind(terminal, 'echo "foo"'));
		});

52 53 54 55 56 57
		test('echo works in the default shell', (done) => {
			disposables.push(window.onDidOpenTerminal(term => {
				try {
					equal(terminal, term);
				} catch (e) {
					done(e);
D
Daniel Imms 已提交
58
					return;
59 60
				}
				let data = '';
61
				const dataDisposable = window.onDidWriteTerminalData(e => {
62 63 64 65
					try {
						equal(terminal, e.terminal);
					} catch (e) {
						done(e);
D
Daniel Imms 已提交
66
						return;
67 68
					}
					data += e.data;
69
					if (data.indexOf(expected) !== 0) {
70
						dataDisposable.dispose();
71 72 73
						terminal.dispose();
						disposables.push(window.onDidCloseTerminal(() => done()));
					}
74 75
				});
				disposables.push(dataDisposable);
76
			}));
77 78 79 80 81 82 83
			// Use a single character to avoid winpty/conpty issues with injected sequences
			const expected = '`';
			const terminal = window.createTerminal({
				env: {
					TEST: '`'
				}
			});
D
Daniel Imms 已提交
84
			terminal.show();
85 86 87 88 89 90 91 92
			doesNotThrow(() => {
				// Print an environment variable value so the echo statement doesn't get matched
				if (process.platform === 'win32') {
					terminal.sendText(`$env:TEST`);
				} else {
					terminal.sendText(`echo $TEST`);
				}
			});
93 94
		});

95
		test('onDidCloseTerminal event fires when terminal is disposed', (done) => {
96
			disposables.push(window.onDidOpenTerminal(term => {
D
Daniel Imms 已提交
97 98 99 100
				try {
					equal(terminal, term);
				} catch (e) {
					done(e);
D
Daniel Imms 已提交
101
					return;
D
Daniel Imms 已提交
102
				}
103
				terminal.dispose();
104 105
				disposables.push(window.onDidCloseTerminal(() => done()));
			}));
106
			const terminal = window.createTerminal();
107 108 109
		});

		test('processId immediately after createTerminal should fetch the pid', (done) => {
110
			disposables.push(window.onDidOpenTerminal(term => {
D
Daniel Imms 已提交
111 112 113 114
				try {
					equal(terminal, term);
				} catch (e) {
					done(e);
D
Daniel Imms 已提交
115
					return;
D
Daniel Imms 已提交
116
				}
117
				terminal.processId.then(id => {
D
Daniel Imms 已提交
118
					try {
D
Daniel Imms 已提交
119
						ok(id && id > 0);
D
Daniel Imms 已提交
120 121
					} catch (e) {
						done(e);
D
Daniel Imms 已提交
122
						return;
D
Daniel Imms 已提交
123
					}
124
					terminal.dispose();
125
					disposables.push(window.onDidCloseTerminal(() => done()));
126
				});
127
			}));
128
			const terminal = window.createTerminal();
129 130
		});

131
		test('name in constructor should set terminal.name', (done) => {
132
			disposables.push(window.onDidOpenTerminal(term => {
D
Daniel Imms 已提交
133 134 135 136
				try {
					equal(terminal, term);
				} catch (e) {
					done(e);
D
Daniel Imms 已提交
137
					return;
D
Daniel Imms 已提交
138
				}
139
				terminal.dispose();
140 141
				disposables.push(window.onDidCloseTerminal(() => done()));
			}));
142
			const terminal = window.createTerminal('a');
D
Daniel Imms 已提交
143 144 145 146
			try {
				equal(terminal.name, 'a');
			} catch (e) {
				done(e);
D
Daniel Imms 已提交
147
				return;
D
Daniel Imms 已提交
148
			}
149 150
		});

151 152 153 154 155 156
		test('creationOptions should be set and readonly for TerminalOptions terminals', (done) => {
			disposables.push(window.onDidOpenTerminal(term => {
				try {
					equal(terminal, term);
				} catch (e) {
					done(e);
D
Daniel Imms 已提交
157
					return;
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
				}
				terminal.dispose();
				disposables.push(window.onDidCloseTerminal(() => done()));
			}));
			const options = {
				name: 'foo',
				hideFromUser: true
			};
			const terminal = window.createTerminal(options);
			try {
				equal(terminal.name, 'foo');
				deepEqual(terminal.creationOptions, options);
				throws(() => (<any>terminal.creationOptions).name = 'bad', 'creationOptions should be readonly at runtime');
			} catch (e) {
				done(e);
D
Daniel Imms 已提交
173
				return;
174 175 176
			}
		});

177
		test('onDidOpenTerminal should fire when a terminal is created', (done) => {
178
			disposables.push(window.onDidOpenTerminal(term => {
D
Daniel Imms 已提交
179 180 181 182
				try {
					equal(term.name, 'b');
				} catch (e) {
					done(e);
D
Daniel Imms 已提交
183
					return;
D
Daniel Imms 已提交
184
				}
185
				disposables.push(window.onDidCloseTerminal(() => done()));
186
				terminal.dispose();
187
			}));
188 189
			const terminal = window.createTerminal('b');
		});
190

D
Daniel Imms 已提交
191 192 193 194 195 196
		test('exitStatus.code should be set to undefined after a terminal is disposed', (done) => {
			disposables.push(window.onDidOpenTerminal(term => {
				try {
					equal(term, terminal);
				} catch (e) {
					done(e);
D
Daniel Imms 已提交
197
					return;
D
Daniel Imms 已提交
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
				}
				disposables.push(window.onDidCloseTerminal(t => {
					try {
						deepEqual(t.exitStatus, { code: undefined });
					} catch (e) {
						done(e);
						return;
					}
					done();
				}));
				terminal.dispose();
			}));
			const terminal = window.createTerminal();
		});

213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
		// test('onDidChangeActiveTerminal should fire when new terminals are created', (done) => {
		// 	const reg1 = window.onDidChangeActiveTerminal((active: Terminal | undefined) => {
		// 		equal(active, terminal);
		// 		equal(active, window.activeTerminal);
		// 		reg1.dispose();
		// 		const reg2 = window.onDidChangeActiveTerminal((active: Terminal | undefined) => {
		// 			equal(active, undefined);
		// 			equal(active, window.activeTerminal);
		// 			reg2.dispose();
		// 			done();
		// 		});
		// 		terminal.dispose();
		// 	});
		// 	const terminal = window.createTerminal();
		// 	terminal.show();
		// });
229

230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
		// test('onDidChangeTerminalDimensions should fire when new terminals are created', (done) => {
		// 	const reg1 = window.onDidChangeTerminalDimensions(async (event: TerminalDimensionsChangeEvent) => {
		// 		equal(event.terminal, terminal1);
		// 		equal(typeof event.dimensions.columns, 'number');
		// 		equal(typeof event.dimensions.rows, 'number');
		// 		ok(event.dimensions.columns > 0);
		// 		ok(event.dimensions.rows > 0);
		// 		reg1.dispose();
		// 		let terminal2: Terminal;
		// 		const reg2 = window.onDidOpenTerminal((newTerminal) => {
		// 			// This is guarantees to fire before dimensions change event
		// 			if (newTerminal !== terminal1) {
		// 				terminal2 = newTerminal;
		// 				reg2.dispose();
		// 			}
		// 		});
		// 		let firstCalled = false;
		// 		let secondCalled = false;
		// 		const reg3 = window.onDidChangeTerminalDimensions((event: TerminalDimensionsChangeEvent) => {
		// 			if (event.terminal === terminal1) {
		// 				// The original terminal should fire dimension change after a split
		// 				firstCalled = true;
		// 			} else if (event.terminal !== terminal1) {
		// 				// The new split terminal should fire dimension change
		// 				secondCalled = true;
		// 			}
		// 			if (firstCalled && secondCalled) {
		// 				let firstDisposed = false;
		// 				let secondDisposed = false;
		// 				const reg4 = window.onDidCloseTerminal(term => {
		// 					if (term === terminal1) {
		// 						firstDisposed = true;
		// 					}
		// 					if (term === terminal2) {
		// 						secondDisposed = true;
		// 					}
		// 					if (firstDisposed && secondDisposed) {
		// 						reg4.dispose();
		// 						done();
		// 					}
		// 				});
		// 				terminal1.dispose();
		// 				terminal2.dispose();
		// 				reg3.dispose();
		// 			}
		// 		});
		// 		await timeout(500);
		// 		commands.executeCommand('workbench.action.terminal.split');
		// 	});
		// 	const terminal1 = window.createTerminal({ name: 'test' });
		// 	terminal1.show();
		// });
282

283 284 285
		suite('hideFromUser', () => {
			test('should be available to terminals API', done => {
				const terminal = window.createTerminal({ name: 'bg', hideFromUser: true });
286
				disposables.push(window.onDidOpenTerminal(t => {
D
Daniel Imms 已提交
287 288 289 290 291 292
					try {
						equal(t, terminal);
						equal(t.name, 'bg');
						ok(window.terminals.indexOf(terminal) !== -1);
					} catch (e) {
						done(e);
D
Daniel Imms 已提交
293
						return;
D
Daniel Imms 已提交
294
					}
295 296
					disposables.push(window.onDidCloseTerminal(() => {
						// reg3.dispose();
297
						done();
298
					}));
299
					terminal.dispose();
300
				}));
301 302
			});
		});
303

304 305 306 307 308
		suite('window.onDidWriteTerminalData', () => {
			test('should listen to all future terminal data events', (done) => {
				const openEvents: string[] = [];
				const dataEvents: { name: string, data: string }[] = [];
				const closeEvents: string[] = [];
309
				disposables.push(window.onDidOpenTerminal(e => openEvents.push(e.name)));
310 311

				let resolveOnceDataWritten: (() => void) | undefined;
E
Eric Amodio 已提交
312
				let resolveOnceClosed: (() => void) | undefined;
313

314
				disposables.push(window.onDidWriteTerminalData(e => {
315 316 317
					dataEvents.push({ name: e.terminal.name, data: e.data });

					resolveOnceDataWritten!();
318
				}));
319

320
				disposables.push(window.onDidCloseTerminal(e => {
321
					closeEvents.push(e.name);
D
Daniel Imms 已提交
322 323 324 325 326 327 328 329 330 331
					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']);
						}
E
Eric Amodio 已提交
332
						resolveOnceClosed!();
D
Daniel Imms 已提交
333 334
					} catch (e) {
						done(e);
335
					}
336
				}));
337 338 339

				const term1Write = new EventEmitter<string>();
				const term1Close = new EventEmitter<void>();
E
Eric Amodio 已提交
340 341 342 343 344 345 346 347 348 349 350 351 352 353
				window.createTerminal({
					name: 'test1', pty: {
						onDidWrite: term1Write.event,
						onDidClose: term1Close.event,
						open: async () => {
							term1Write.fire('write1');

							// Wait until the data is written
							await new Promise(resolve => { resolveOnceDataWritten = resolve; });

							term1Close.fire();

							// Wait until the terminal is closed
							await new Promise<void>(resolve => { resolveOnceClosed = resolve; });
D
Daniel Imms 已提交
354

E
Eric Amodio 已提交
355 356 357 358 359 360 361 362
							const term2Write = new EventEmitter<string>();
							const term2Close = new EventEmitter<void>();
							window.createTerminal({
								name: 'test2', pty: {
									onDidWrite: term2Write.event,
									onDidClose: term2Close.event,
									open: async () => {
										term2Write.fire('write2');
D
Daniel Imms 已提交
363

E
Eric Amodio 已提交
364 365
										// Wait until the data is written
										await new Promise<void>(resolve => { resolveOnceDataWritten = resolve; });
D
Daniel Imms 已提交
366

E
Eric Amodio 已提交
367
										term2Close.fire();
D
Daniel Imms 已提交
368

E
Eric Amodio 已提交
369 370 371 372 373 374 375 376 377 378 379 380
										// Wait until the terminal is closed
										await new Promise<void>(resolve => { resolveOnceClosed = resolve; });

										done();
									},
									close: () => { }
								}
							});
						},
						close: () => { }
					}
				});
381 382 383
			});
		});

D
Daniel Imms 已提交
384
		suite('Extension pty terminals', () => {
385
			test('should fire onDidOpenTerminal and onDidCloseTerminal', (done) => {
386
				disposables.push(window.onDidOpenTerminal(term => {
D
Daniel Imms 已提交
387 388 389 390
					try {
						equal(term.name, 'c');
					} catch (e) {
						done(e);
D
Daniel Imms 已提交
391
						return;
D
Daniel Imms 已提交
392
					}
393
					disposables.push(window.onDidCloseTerminal(() => done()));
394
					term.dispose();
395
				}));
396
				const pty: Pseudoterminal = {
D
Daniel Imms 已提交
397
					onDidWrite: new EventEmitter<string>().event,
398 399
					open: () => { },
					close: () => { }
400
				};
401
				window.createTerminal({ name: 'c', pty });
402 403
			});

404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449
			// The below tests depend on global UI state and each other
			// test('should not provide dimensions on start as the terminal has not been shown yet', (done) => {
			// 	const reg1 = window.onDidOpenTerminal(term => {
			// 		equal(terminal, term);
			// 		reg1.dispose();
			// 	});
			// 	const pty: Pseudoterminal = {
			// 		onDidWrite: new EventEmitter<string>().event,
			// 		open: (dimensions) => {
			// 			equal(dimensions, undefined);
			// 			const reg3 = window.onDidCloseTerminal(() => {
			// 				reg3.dispose();
			// 				done();
			// 			});
			// 			// Show a terminal and wait a brief period before dispose, this will cause
			// 			// the panel to init it's dimenisons and be provided to following terminals.
			// 			// The following test depends on this.
			// 			terminal.show();
			// 			setTimeout(() => terminal.dispose(), 200);
			// 		},
			// 		close: () => {}
			// 	};
			// 	const terminal = window.createTerminal({ name: 'foo', pty });
			// });
			// test('should provide dimensions on start as the terminal has been shown', (done) => {
			// 	const reg1 = window.onDidOpenTerminal(term => {
			// 		equal(terminal, term);
			// 		reg1.dispose();
			// 	});
			// 	const pty: Pseudoterminal = {
			// 		onDidWrite: new EventEmitter<string>().event,
			// 		open: (dimensions) => {
			// 			// This test depends on Terminal.show being called some time before such
			// 			// that the panel dimensions are initialized and cached.
			// 			ok(dimensions!.columns > 0);
			// 			ok(dimensions!.rows > 0);
			// 			const reg3 = window.onDidCloseTerminal(() => {
			// 				reg3.dispose();
			// 				done();
			// 			});
			// 			terminal.dispose();
			// 		},
			// 		close: () => {}
			// 	};
			// 	const terminal = window.createTerminal({ name: 'foo', pty });
			// });
D
Daniel Imms 已提交
450

D
Daniel Imms 已提交
451
			test('should respect dimension overrides', (done) => {
452
				disposables.push(window.onDidOpenTerminal(term => {
D
Daniel Imms 已提交
453 454 455 456
					try {
						equal(terminal, term);
					} catch (e) {
						done(e);
D
Daniel Imms 已提交
457
						return;
D
Daniel Imms 已提交
458
					}
D
Daniel Imms 已提交
459
					term.show();
460
					disposables.push(window.onDidChangeTerminalDimensions(e => {
461 462 463 464
						if (e.dimensions.columns === 0 || e.dimensions.rows === 0) {
							// HACK: Ignore the event if dimension(s) are zero (#83778)
							return;
						}
D
Daniel Imms 已提交
465 466 467 468 469 470 471 472
						// The default pty dimensions have a chance to appear here since override
						// dimensions happens after the terminal is created. If so just ignore and
						// wait for the right dimensions
						if (e.dimensions.columns === 10 || e.dimensions.rows === 5) {
							try {
								equal(e.terminal, terminal);
							} catch (e) {
								done(e);
D
Daniel Imms 已提交
473
								return;
D
Daniel Imms 已提交
474 475 476
							}
							disposables.push(window.onDidCloseTerminal(() => done()));
							terminal.dispose();
D
Daniel Imms 已提交
477
						}
478 479
					}));
				}));
D
Daniel Imms 已提交
480 481
				const writeEmitter = new EventEmitter<string>();
				const overrideDimensionsEmitter = new EventEmitter<TerminalDimensions>();
482
				const pty: Pseudoterminal = {
483
					onDidWrite: writeEmitter.event,
D
Daniel Imms 已提交
484
					onDidOverrideDimensions: overrideDimensionsEmitter.event,
D
Daniel Imms 已提交
485 486 487 488 489 490 491 492 493 494 495 496 497
					open: () => overrideDimensionsEmitter.fire({ columns: 10, rows: 5 }),
					close: () => { }
				};
				const terminal = window.createTerminal({ name: 'foo', pty });
			});

			test('exitStatus.code should be set to the exit code (undefined)', (done) => {
				disposables.push(window.onDidOpenTerminal(term => {
					try {
						equal(terminal, term);
						equal(terminal.exitStatus, undefined);
					} catch (e) {
						done(e);
D
Daniel Imms 已提交
498
						return;
D
Daniel Imms 已提交
499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515
					}
					disposables.push(window.onDidCloseTerminal(t => {
						try {
							equal(terminal, t);
							deepEqual(terminal.exitStatus, { code: undefined });
						} catch (e) {
							done(e);
							return;
						}
						done();
					}));
				}));
				const writeEmitter = new EventEmitter<string>();
				const closeEmitter = new EventEmitter<number | undefined>();
				const pty: Pseudoterminal = {
					onDidWrite: writeEmitter.event,
					onDidClose: closeEmitter.event,
516
					open: () => closeEmitter.fire(undefined),
D
Daniel Imms 已提交
517 518 519 520 521 522 523 524 525 526 527 528
					close: () => { }
				};
				const terminal = window.createTerminal({ name: 'foo', pty });
			});

			test('exitStatus.code should be set to the exit code (zero)', (done) => {
				disposables.push(window.onDidOpenTerminal(term => {
					try {
						equal(terminal, term);
						equal(terminal.exitStatus, undefined);
					} catch (e) {
						done(e);
D
Daniel Imms 已提交
529
						return;
D
Daniel Imms 已提交
530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559
					}
					disposables.push(window.onDidCloseTerminal(t => {
						try {
							equal(terminal, t);
							deepEqual(terminal.exitStatus, { code: 0 });
						} catch (e) {
							done(e);
							return;
						}
						done();
					}));
				}));
				const writeEmitter = new EventEmitter<string>();
				const closeEmitter = new EventEmitter<number | undefined>();
				const pty: Pseudoterminal = {
					onDidWrite: writeEmitter.event,
					onDidClose: closeEmitter.event,
					open: () => closeEmitter.fire(0),
					close: () => { }
				};
				const terminal = window.createTerminal({ name: 'foo', pty });
			});

			test('exitStatus.code should be set to the exit code (non-zero)', (done) => {
				disposables.push(window.onDidOpenTerminal(term => {
					try {
						equal(terminal, term);
						equal(terminal.exitStatus, undefined);
					} catch (e) {
						done(e);
D
Daniel Imms 已提交
560
						return;
D
Daniel Imms 已提交
561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577
					}
					disposables.push(window.onDidCloseTerminal(t => {
						try {
							equal(terminal, t);
							deepEqual(terminal.exitStatus, { code: 22 });
						} catch (e) {
							done(e);
							return;
						}
						done();
					}));
				}));
				const writeEmitter = new EventEmitter<string>();
				const closeEmitter = new EventEmitter<number | undefined>();
				const pty: Pseudoterminal = {
					onDidWrite: writeEmitter.event,
					onDidClose: closeEmitter.event,
578 579 580 581 582 583
					open: () => {
						// Wait 500ms as any exits that occur within 500ms of terminal launch are
						// are counted as "exiting during launch" which triggers a notification even
						// when showExitAlerts is true
						setTimeout(() => closeEmitter.fire(22), 500);
					},
584
					close: () => { }
D
Daniel Imms 已提交
585
				};
586
				const terminal = window.createTerminal({ name: 'foo', pty });
D
Daniel Imms 已提交
587
			});
588 589 590 591 592 593 594

			test('creationOptions should be set and readonly for ExtensionTerminalOptions terminals', (done) => {
				disposables.push(window.onDidOpenTerminal(term => {
					try {
						equal(terminal, term);
					} catch (e) {
						done(e);
D
Daniel Imms 已提交
595
						return;
596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615
					}
					terminal.dispose();
					disposables.push(window.onDidCloseTerminal(() => done()));
				}));
				const writeEmitter = new EventEmitter<string>();
				const pty: Pseudoterminal = {
					onDidWrite: writeEmitter.event,
					open: () => { },
					close: () => { }
				};
				const options = { name: 'foo', pty };
				const terminal = window.createTerminal(options);
				try {
					equal(terminal.name, 'foo');
					deepEqual(terminal.creationOptions, options);
					throws(() => (<any>terminal.creationOptions).name = 'bad', 'creationOptions should be readonly at runtime');
				} catch (e) {
					done(e);
				}
			});
616
		});
617

618
		suite('environmentVariableCollection', () => {
619 620 621 622 623 624 625 626 627 628 629 630
			test('should have collection variables apply to terminals immediately after setting', (done) => {
				// Text to match on before passing the test
				const expectedText = [
					'~a2~',
					'b1~b2~',
					'~c2~c1'
				];
				disposables.push(window.onDidWriteTerminalData(e => {
					try {
						equal(terminal, e.terminal);
					} catch (e) {
						done(e);
D
Daniel Imms 已提交
631
						return;
632 633 634 635 636 637 638 639 640 641 642
					}
					// Multiple expected could show up in the same data event
					while (expectedText.length > 0 && e.data.indexOf(expectedText[0]) >= 0) {
						expectedText.shift();
						// Check if all string are found, if so finish the test
						if (expectedText.length === 0) {
							disposables.push(window.onDidCloseTerminal(() => done()));
							terminal.dispose();
						}
					}
				}));
643 644
				const collection = extensionContext.environmentVariableCollection;
				disposables.push({ dispose: () => collection.clear() });
645 646 647 648 649 650 651 652 653 654
				collection.replace('A', '~a2~');
				collection.append('B', '~b2~');
				collection.prepend('C', '~c2~');
				const terminal = window.createTerminal({
					env: {
						A: 'a1',
						B: 'b1',
						C: 'c1'
					}
				});
655 656 657 658 659 660 661 662
				// Run both PowerShell and sh commands, errors don't matter we're just looking for
				// the correct output
				terminal.sendText('$env:A');
				terminal.sendText('echo $A');
				terminal.sendText('$env:B');
				terminal.sendText('echo $B');
				terminal.sendText('$env:C');
				terminal.sendText('echo $C');
663 664 665 666 667 668 669 670 671 672 673 674 675 676
			});

			test('should have collection variables apply to environment variables that don\'t exist', (done) => {
				// Text to match on before passing the test
				const expectedText = [
					'~a2~',
					'~b2~',
					'~c2~'
				];
				disposables.push(window.onDidWriteTerminalData(e => {
					try {
						equal(terminal, e.terminal);
					} catch (e) {
						done(e);
D
Daniel Imms 已提交
677
						return;
678 679 680 681 682 683 684 685 686 687 688
					}
					// Multiple expected could show up in the same data event
					while (expectedText.length > 0 && e.data.indexOf(expectedText[0]) >= 0) {
						expectedText.shift();
						// Check if all string are found, if so finish the test
						if (expectedText.length === 0) {
							disposables.push(window.onDidCloseTerminal(() => done()));
							terminal.dispose();
						}
					}
				}));
689 690
				const collection = extensionContext.environmentVariableCollection;
				disposables.push({ dispose: () => collection.clear() });
691 692 693 694 695 696 697 698 699 700
				collection.replace('A', '~a2~');
				collection.append('B', '~b2~');
				collection.prepend('C', '~c2~');
				const terminal = window.createTerminal({
					env: {
						A: null,
						B: null,
						C: null
					}
				});
701 702 703 704 705 706 707 708
				// Run both PowerShell and sh commands, errors don't matter we're just looking for
				// the correct output
				terminal.sendText('$env:A');
				terminal.sendText('echo $A');
				terminal.sendText('$env:B');
				terminal.sendText('echo $B');
				terminal.sendText('$env:C');
				terminal.sendText('echo $C');
709
			});
D
Daniel Imms 已提交
710 711 712 713 714 715 716 717 718 719 720 721

			test('should respect clearing entries', (done) => {
				// Text to match on before passing the test
				const expectedText = [
					'~a1~',
					'~b1~'
				];
				disposables.push(window.onDidWriteTerminalData(e => {
					try {
						equal(terminal, e.terminal);
					} catch (e) {
						done(e);
D
Daniel Imms 已提交
722
						return;
D
Daniel Imms 已提交
723 724 725 726 727 728 729 730 731 732 733
					}
					// Multiple expected could show up in the same data event
					while (expectedText.length > 0 && e.data.indexOf(expectedText[0]) >= 0) {
						expectedText.shift();
						// Check if all string are found, if so finish the test
						if (expectedText.length === 0) {
							disposables.push(window.onDidCloseTerminal(() => done()));
							terminal.dispose();
						}
					}
				}));
734 735
				const collection = extensionContext.environmentVariableCollection;
				disposables.push({ dispose: () => collection.clear() });
D
Daniel Imms 已提交
736 737 738 739 740 741 742 743 744
				collection.replace('A', '~a2~');
				collection.replace('B', '~a2~');
				collection.clear();
				const terminal = window.createTerminal({
					env: {
						A: '~a1~',
						B: '~b1~'
					}
				});
745 746 747 748 749 750
				// Run both PowerShell and sh commands, errors don't matter we're just looking for
				// the correct output
				terminal.sendText('$env:A');
				terminal.sendText('echo $A');
				terminal.sendText('$env:B');
				terminal.sendText('echo $B');
D
Daniel Imms 已提交
751 752 753 754 755 756 757 758 759 760 761 762 763
			});

			test('should respect deleting entries', (done) => {
				// Text to match on before passing the test
				const expectedText = [
					'~a1~',
					'~b2~'
				];
				disposables.push(window.onDidWriteTerminalData(e => {
					try {
						equal(terminal, e.terminal);
					} catch (e) {
						done(e);
D
Daniel Imms 已提交
764
						return;
D
Daniel Imms 已提交
765 766 767 768 769 770 771 772 773 774 775
					}
					// Multiple expected could show up in the same data event
					while (expectedText.length > 0 && e.data.indexOf(expectedText[0]) >= 0) {
						expectedText.shift();
						// Check if all string are found, if so finish the test
						if (expectedText.length === 0) {
							disposables.push(window.onDidCloseTerminal(() => done()));
							terminal.dispose();
						}
					}
				}));
776 777
				const collection = extensionContext.environmentVariableCollection;
				disposables.push({ dispose: () => collection.clear() });
D
Daniel Imms 已提交
778 779 780 781 782 783 784 785 786
				collection.replace('A', '~a2~');
				collection.replace('B', '~b2~');
				collection.delete('A');
				const terminal = window.createTerminal({
					env: {
						A: '~a1~',
						B: '~b2~'
					}
				});
787 788 789 790 791 792
				// Run both PowerShell and sh commands, errors don't matter we're just looking for
				// the correct output
				terminal.sendText('$env:A');
				terminal.sendText('echo $A');
				terminal.sendText('$env:B');
				terminal.sendText('echo $B');
D
Daniel Imms 已提交
793 794 795
			});

			test('get and forEach should work', () => {
796 797
				const collection = extensionContext.environmentVariableCollection;
				disposables.push({ dispose: () => collection.clear() });
D
Daniel Imms 已提交
798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815
				collection.replace('A', '~a2~');
				collection.append('B', '~b2~');
				collection.prepend('C', '~c2~');

				// Verify get
				deepEqual(collection.get('A'), { value: '~a2~', type: EnvironmentVariableMutatorType.Replace });
				deepEqual(collection.get('B'), { value: '~b2~', type: EnvironmentVariableMutatorType.Append });
				deepEqual(collection.get('C'), { value: '~c2~', type: EnvironmentVariableMutatorType.Prepend });

				// Verify forEach
				const entries: [string, EnvironmentVariableMutator][] = [];
				collection.forEach((v, m) => entries.push([v, m]));
				deepEqual(entries, [
					['A', { value: '~a2~', type: EnvironmentVariableMutatorType.Replace }],
					['B', { value: '~b2~', type: EnvironmentVariableMutatorType.Append }],
					['C', { value: '~c2~', type: EnvironmentVariableMutatorType.Prepend }]
				]);
			});
816
		});
817 818
	});
});