installed pty
[VSoRC/.git] / node_modules / node-pty / src / windowsTerminal.test.ts
1 /**
2  * Copyright (c) 2017, Daniel Imms (MIT License).
3  * Copyright (c) 2018, Microsoft Corporation (MIT License).
4  */
5
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';
11
12 interface IProcessState {
13   // Whether the PID must exist or must not exist
14   [pid: number]: boolean;
15 }
16
17 interface IWindowsProcessTreeResult {
18   name: string;
19   pid: number;
20 }
21
22 function pollForProcessState(desiredState: IProcessState, intervalMs: number = 100, timeoutMs: number = 2000): Promise<void> {
23   return new Promise<void>(resolve => {
24     let tries = 0;
25     const interval = setInterval(() => {
26       psList({ all: true }).then(ps => {
27         let success = true;
28         const pids = Object.keys(desiredState).map(k => parseInt(k, 10));
29         pids.forEach(pid => {
30           if (desiredState[pid]) {
31             if (!ps.some(p => p.pid === pid)) {
32               success = false;
33             }
34           } else {
35             if (ps.some(p => p.pid === pid)) {
36               success = false;
37             }
38           }
39         });
40         if (success) {
41           clearInterval(interval);
42           resolve();
43           return;
44         }
45         tries++;
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}`);
50           resolve();
51         }
52       });
53     }, intervalMs);
54   });
55 }
56
57 function pollForProcessTreeSize(pid: number, size: number, intervalMs: number = 100, timeoutMs: number = 2000): Promise<IWindowsProcessTreeResult[]> {
58   return new Promise<IWindowsProcessTreeResult[]>(resolve => {
59     let tries = 0;
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 };
65         })[0]);
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));
72           list.push(current);
73         }
74         const success = list.length === size;
75         if (success) {
76           clearInterval(interval);
77           resolve(list);
78           return;
79         }
80         tries++;
81         if (tries * intervalMs >= timeoutMs) {
82           clearInterval(interval);
83           assert.fail(`Bad process state, expected: ${size}, actual: ${list.length}`);
84           resolve();
85         }
86       });
87     }, intervalMs);
88   });
89 }
90
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', [], {});
96         term.kill();
97         // Add done call to deferred function queue to ensure the kill call has completed
98         (<any>term)._defer(done);
99       });
100       it('should kill the process tree', function (done: Mocha.Done): void {
101         this.timeout(5000);
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');
112           term.kill();
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);
121             done();
122           });
123         });
124       });
125     });
126
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));
133       });
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));
139             done();
140           });
141           term.destroy();
142         });
143       });
144     });
145
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);
151         }
152
153         const cmdCopiedPath = path.resolve(spaceFolder, 'cmd.exe');
154         const data = fs.readFileSync(`${process.env.windir}\\System32\\cmd.exe`);
155         fs.writeFileSync(cmdCopiedPath, data);
156
157         if (!fs.existsSync(cmdCopiedPath)) {
158           // Skip test if git bash isn't installed
159           return;
160         }
161         const term = new WindowsTerminal(cmdCopiedPath, '/c echo "hello world"', {});
162         let result = '';
163         term.on('data', (data) => {
164           result += data;
165         });
166         term.on('exit', () => {
167           assert.ok(result.indexOf('hello world') >= 1);
168           done();
169         });
170       });
171     });
172
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' }});
176         let result = '';
177         term.on('data', (data) => {
178           result += data;
179         });
180         term.on('exit', () => {
181           assert.ok(result.indexOf('BAR') >= 0);
182           done();
183         });
184       });
185     });
186
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);
192           done();
193         });
194       });
195
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);
200           done();
201         });
202       });
203     });
204   });
205 }