Skip to content
This repository was archived by the owner on Oct 3, 2024. It is now read-only.

Commit 87ea05a

Browse files
Refactor the code which starts firefox during integration tests so that it can be reused for other things
1 parent 399c7c9 commit 87ea05a

6 files changed

Lines changed: 475 additions & 90 deletions

File tree

lib/explicitPromise.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
'use strict';
2+
3+
const explicitPromise = () => {
4+
let resolve;
5+
let reject;
6+
const promise = new Promise((_resolve, _reject) => {
7+
resolve = _resolve;
8+
reject = _reject;
9+
});
10+
return [promise, resolve, reject];
11+
};
12+
13+
module.exports = explicitPromise;
Lines changed: 26 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,33 @@ const {spawn} = require('child_process');
66
const split = require('split');
77
const {assert} = require('chai');
88

9-
const log = require('../../lib/logger')({hostname: 'test', MODULE: 'Process'});
9+
const log = require('../logger')({MODULE: 'node/Process'});
10+
const explicitPromise = require('../explicitPromise');
1011

12+
/**
13+
* Launch a system process (requires node.js) and manage its lifecycle and standard streams.
14+
*/
1115
class Process extends EventEmitter {
12-
constructor({executablePath, args, env, cwd, enableExtraFd, outputFilter = s => s}) {
16+
constructor({executablePath, args, env, cwd, enableExtraFd, outputFilter = s => s, killTimeout = 10000}) {
1317
super();
1418
this.executablePath = executablePath;
1519
this.args = args || [];
1620
this.env = env || {};
1721
this.cwd = cwd || undefined; // undefined = inherit
1822
this.enableExtraFd = Boolean(enableExtraFd || false);
1923
this.outputFilter = outputFilter;
24+
this.killTimeout = killTimeout;
2025

2126
this.runningChild = null;
22-
this.childrenLifeTime = Promise.resolve();
2327
this.lastProcessStart = NaN;
2428
this.lastProcessExit = NaN;
2529
this.processExitCount = 0;
26-
27-
this._lastStartSymbol = null;
30+
this._childLifeTime = Promise.resolve();
2831
}
2932

3033
_waitableEmit(event, ...args) {
3134
const waits = [];
3235
this.emit(event, ...args, p => waits.push(typeof p === 'function' ? p() : p));
33-
3436
return Promise.all(waits);
3537
}
3638

@@ -51,33 +53,12 @@ class Process extends EventEmitter {
5153
async start() {
5254
assert.isNotOk(this.runningChild, 'start(): The child process has already been started');
5355

54-
const lastStartSymbol = Symbol();
55-
this._lastStartSymbol = lastStartSymbol;
56-
57-
let lifeTimeResolve;
58-
let lifeTimeReject;
59-
60-
const childLifeTime = new Promise((resolve, reject) => {
61-
lifeTimeResolve = resolve;
62-
lifeTimeReject = reject;
63-
})
64-
.finally(() => {
65-
if (this._lastStartSymbol === lastStartSymbol) {
66-
this.runningChild = null;
67-
}
68-
});
69-
70-
this.childrenLifeTime = this.childrenLifeTime
71-
.finally(async () => {
72-
await childLifeTime;
73-
74-
if (this._lastStartSymbol === lastStartSymbol) {
75-
await this._waitableEmit('stopped');
76-
log.info('Process stopped');
77-
this.emit('afterStopped');
78-
}
79-
80-
return null;
56+
const [lifeTimePromise, lifeTimeResolve] = explicitPromise();
57+
this._childLifeTime = lifeTimePromise.then(async (reason) => {
58+
this.runningChild = null;
59+
await this._waitableEmit('stopped', reason);
60+
log.info('Process stopped');
61+
this.emit('afterStopped', reason);
8162
});
8263

8364
// set this value right away to make sure that we never create duplicate processes
@@ -125,14 +106,14 @@ class Process extends EventEmitter {
125106
log.info({code, executablePath, signal}, 'Process exit');
126107
this.lastProcessExit = Date.now();
127108
++this.processExitCount;
128-
lifeTimeResolve({code, signal});
109+
lifeTimeResolve(Object.freeze({error: null, code, signal}));
129110
});
130111

131112
childProcess.on('error', err => {
132113
// The process could not be spawned, or The process could not be killed, or Sending a message to the child process
133114
// failed.
134115
log.error({childPid, err, executablePath}, 'Error spawning process');
135-
lifeTimeReject(err);
116+
lifeTimeResolve(Object.freeze({error: err}));
136117
});
137118

138119
childProcess.stdout.pipe(split()).on('data', line => {
@@ -166,24 +147,21 @@ class Process extends EventEmitter {
166147
this.emit('FD3', line);
167148
});
168149
}
150+
151+
await this._waitableEmit('afterStart');
169152
}
170153
catch (err) {
171-
lifeTimeReject(err);
154+
lifeTimeResolve(Object.freeze({error: err}));
172155
throw err;
173156
}
174-
175-
// try to throw if the child immediately fails to start:
176-
await Promise.race([Promise.delay(10), childLifeTime]);
177-
await this._waitableEmit('afterStart');
178157
}
179158

180159
async stop() {
181160
if (!this.runningChild ||
182161
this.runningChild.stopping ||
183162
!this.runningChild.childProcess) {
184163
// already stopping or there was an error creating the childProcess
185-
await this.childrenLifeTime.catch(() => {});
186-
164+
await this.waitForChildStop();
187165
return;
188166
}
189167

@@ -199,11 +177,15 @@ class Process extends EventEmitter {
199177
// SIGKILL = forceful termination
200178
log.info({signal: 'SIGKILL'}, 'Stopping process');
201179
childProcess.kill('SIGKILL');
202-
}, 10000);
180+
}, this.killTimeout);
203181

204-
await this.childrenLifeTime.catch(() => {});
182+
await this.waitForChildStop();
205183
clearTimeout(killTimer);
206184
}
185+
186+
async waitForChildStop() {
187+
await this._childLifeTime;
188+
}
207189
}
208190

209191
module.exports = Process;

lib/node/firefoxProcess.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
'use strict';
2+
const Promise = require('bluebird');
3+
4+
const Process = require('./Process');
5+
const log = require('../logger')({MODULE: 'node/firefoxProcess'});
6+
7+
const outputFilter = line => {
8+
// hide a lot of useless noise during test runs
9+
10+
// Linux:
11+
// (firefox:6881): GLib-GObject-CRITICAL **: g_object_ref: assertion 'object->ref_count > 0' failed
12+
// (firefox:6881): GConf-WARNING **: Client failed to connect to the D-BUS daemon:
13+
// //bin/dbus-launch terminated abnormally without any error message
14+
15+
// OS X:
16+
// 2017-01-01 12:34:56.789 plugin-container[17512:1629725] *** CFMessagePort: bootstrap_register(): failed 1100 (0x44c)
17+
// 'Permission denied', port = 0xb03f, name = 'com.apple.tsm.portname'
18+
// See /usr/include/servers/bootstrap_defs.h for the error codes.
19+
// Unable to read VR Path Registry from /Users/FOO/Library/Application Support/OpenVR/.openvr/openvrpaths.vrpath
20+
if (
21+
/^\(.*?firefox.*?\): (?:GLib-GObject-CRITICAL|GConf-WARNING) /.test(line) ||
22+
line === '//bin/dbus-launch terminated abnormally without any error message' ||
23+
/plugin-container.*?\*\*\* CFMessagePort: bootstrap_register\(\): failed 1100/.test(line) ||
24+
line === 'See /usr/include/servers/bootstrap_defs.h for the error codes.' ||
25+
/^Unable to read VR Path Registry from /.test(line)
26+
) {
27+
return null; // skip the log line
28+
}
29+
30+
return line;
31+
};
32+
33+
const startFirefox = ({firefoxPath, profilePath, headless = false}) => Promise.try(async () => {
34+
log.debug({firefoxPath, profilePath}, 'Starting firefox...');
35+
const args = [
36+
'--no-remote',
37+
'--profile',
38+
profilePath,
39+
];
40+
41+
if (headless) {
42+
args.push('--headless');
43+
}
44+
45+
const firefoxProcess = new Process({
46+
executablePath: firefoxPath,
47+
args,
48+
outputFilter,
49+
});
50+
await firefoxProcess.start();
51+
return firefoxProcess;
52+
})
53+
.disposer(async firefoxProcess => {
54+
log.debug({firefoxPath, profilePath}, 'Stopping firefox...');
55+
await firefoxProcess.stop();
56+
});
57+
58+
module.exports = {startFirefox};

0 commit comments

Comments
 (0)