2 * Copyright (c) 2017, Daniel Imms (MIT License).
3 * Copyright (c) 2018, Microsoft Corporation (MIT License).
6 import * as fs from 'fs';
7 import * as assert from 'assert';
8 import { WindowsTerminal } from './windowsTerminal';
9 import * as path from 'path';
10 import * as psList from 'ps-list';
12 interface IProcessState {
13 // Whether the PID must exist or must not exist
14 [pid: number]: boolean;
17 interface IWindowsProcessTreeResult {
22 function pollForProcessState(desiredState: IProcessState, intervalMs: number = 100, timeoutMs: number = 2000): Promise<void> {
23 return new Promise<void>(resolve => {
25 const interval = setInterval(() => {
26 psList({ all: true }).then(ps => {
28 const pids = Object.keys(desiredState).map(k => parseInt(k, 10));
30 if (desiredState[pid]) {
31 if (!ps.some(p => p.pid === pid)) {
35 if (ps.some(p => p.pid === pid)) {
41 clearInterval(interval);
46 if (tries * intervalMs >= timeoutMs) {
47 clearInterval(interval);
48 const processListing = pids.map(k => `${k}: ${desiredState[k]}`).join('\n');
49 assert.fail(`Bad process state, expected:\n${processListing}`);
57 function pollForProcessTreeSize(pid: number, size: number, intervalMs: number = 100, timeoutMs: number = 2000): Promise<IWindowsProcessTreeResult[]> {
58 return new Promise<IWindowsProcessTreeResult[]>(resolve => {
60 const interval = setInterval(() => {
61 psList({ all: true }).then(ps => {
62 const openList: IWindowsProcessTreeResult[] = [];
63 openList.push(ps.filter(p => p.pid === pid).map(p => {
64 return { name: p.name, pid: p.pid };
66 const list: IWindowsProcessTreeResult[] = [];
67 while (openList.length) {
68 const current = openList.shift();
69 ps.filter(p => p.ppid === current.pid).map(p => {
70 return { name: p.name, pid: p.pid };
71 }).forEach(p => openList.push(p));
74 const success = list.length === size;
76 clearInterval(interval);
81 if (tries * intervalMs >= timeoutMs) {
82 clearInterval(interval);
83 assert.fail(`Bad process state, expected: ${size}, actual: ${list.length}`);
91 if (process.platform === 'win32') {
92 describe('WindowsTerminal', () => {
93 describe('kill', () => {
94 it('should not crash parent process', (done) => {
95 const term = new WindowsTerminal('cmd.exe', [], {});
97 // Add done call to deferred function queue to ensure the kill call has completed
98 (<any>term)._defer(done);
100 it('should kill the process tree', function (done: Mocha.Done): void {
102 const term = new WindowsTerminal('cmd.exe', [], {});
103 // Start sub-processes
104 term.write('powershell.exe\r');
105 term.write('notepad.exe\r');
106 term.write('node.exe\r');
107 pollForProcessTreeSize(term.pid, 4, 500, 5000).then(list => {
108 assert.equal(list[0].name, 'cmd.exe');
109 assert.equal(list[1].name, 'powershell.exe');
110 assert.equal(list[2].name, 'notepad.exe');
111 assert.equal(list[3].name, 'node.exe');
113 const desiredState: IProcessState = {};
114 desiredState[list[0].pid] = false;
115 desiredState[list[1].pid] = false;
116 desiredState[list[2].pid] = true;
117 desiredState[list[3].pid] = false;
118 pollForProcessState(desiredState).then(() => {
119 // Kill notepad before done
120 process.kill(list[2].pid);
127 describe('resize', () => {
128 it('should throw a non-native exception when resizing an invalid value', () => {
129 const term = new WindowsTerminal('cmd.exe', [], {});
130 assert.throws(() => term.resize(-1, -1));
131 assert.throws(() => term.resize(0, 0));
132 assert.doesNotThrow(() => term.resize(1, 1));
134 it('should throw an non-native exception when resizing a killed terminal', (done) => {
135 const term = new WindowsTerminal('cmd.exe', [], {});
136 (<any>term)._defer(() => {
137 term.on('exit', () => {
138 assert.throws(() => term.resize(1, 1));
146 describe('Args as CommandLine', () => {
147 it('should not fail running a file containing a space in the path', (done) => {
148 const spaceFolder = path.resolve(__dirname, '..', 'fixtures', 'space folder');
149 if (!fs.existsSync(spaceFolder)) {
150 fs.mkdirSync(spaceFolder);
153 const cmdCopiedPath = path.resolve(spaceFolder, 'cmd.exe');
154 const data = fs.readFileSync(`${process.env.windir}\\System32\\cmd.exe`);
155 fs.writeFileSync(cmdCopiedPath, data);
157 if (!fs.existsSync(cmdCopiedPath)) {
158 // Skip test if git bash isn't installed
161 const term = new WindowsTerminal(cmdCopiedPath, '/c echo "hello world"', {});
163 term.on('data', (data) => {
166 term.on('exit', () => {
167 assert.ok(result.indexOf('hello world') >= 1);
173 describe('env', () => {
174 it('should set environment variables of the shell', (done) => {
175 const term = new WindowsTerminal('cmd.exe', '/C echo %FOO%', { env: { FOO: 'BAR' }});
177 term.on('data', (data) => {
180 term.on('exit', () => {
181 assert.ok(result.indexOf('BAR') >= 0);
187 describe('On close', () => {
188 it('should return process zero exit codes', (done) => {
189 const term = new WindowsTerminal('cmd.exe', '/C exit');
190 term.on('exit', (code) => {
191 assert.equal(code, 0);
196 it('should return process non-zero exit codes', (done) => {
197 const term = new WindowsTerminal('cmd.exe', '/C exit 2');
198 term.on('exit', (code) => {
199 assert.equal(code, 2);