Skip to content

Commit 4e00a31

Browse files
committed
test: show lldb output when timing out
1 parent d7d5e2c commit 4e00a31

4 files changed

Lines changed: 194 additions & 100 deletions

File tree

test/common.js

Lines changed: 135 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,39 @@ const spawn = require('child_process').spawn;
88
const EventEmitter = require('events').EventEmitter;
99

1010
exports.fixturesDir = path.join(__dirname, 'fixtures');
11-
exports.buildDir = path.join(__dirname, '..', 'out', 'Release');
11+
exports.projectDir = path.join(__dirname, '..');
1212

1313
exports.core = path.join(os.tmpdir(), 'core');
14-
exports.ranges = exports.core + '.ranges';
14+
exports.promptDelay = 200;
15+
16+
function llnodeDebug() {
17+
// Node v4.x does not support rest
18+
const args = Array.prototype.slice.call(arguments);
19+
console.error.apply(console, [`[TEST][${process.pid}]`].concat(args));
20+
}
21+
22+
const debug = exports.debug =
23+
process.env.TEST_LLNODE_DEBUG ? llnodeDebug : () => { };
1524

1625
let pluginName;
1726
if (process.platform === 'darwin')
1827
pluginName = 'llnode.dylib';
1928
else if (process.platform === 'windows')
2029
pluginName = 'llnode.dll';
2130
else
22-
pluginName = path.join('lib.target', 'llnode.so');
31+
pluginName = 'llnode.so';
2332

24-
exports.llnodePath = path.join(exports.buildDir, pluginName);
33+
exports.llnodePath = path.join(exports.projectDir, pluginName);
34+
exports.saveCoreTimeout = 180 * 1000;
35+
exports.loadCoreTimeout = 20 * 1000;
2536

26-
function SessionOutput(session, stream) {
37+
function SessionOutput(session, stream, timeout) {
2738
EventEmitter.call(this);
2839
this.waiting = false;
2940
this.waitQueue = [];
30-
3141
let buf = '';
42+
this.timeout = timeout || 10000;
43+
this.session = session;
3244

3345
stream.on('data', (data) => {
3446
buf += data;
@@ -44,10 +56,8 @@ function SessionOutput(session, stream) {
4456

4557
if (/process \d+ exited/i.test(line))
4658
session.kill();
47-
else if (session.initialized)
59+
else
4860
this.emit('line', line);
49-
else if (/process \d+ launched/i.test(line))
50-
session.initialized = true;
5161
}
5262
});
5363

@@ -72,109 +82,173 @@ SessionOutput.prototype._unqueueWait = function _unqueueWait() {
7282
this.waitQueue.shift()();
7383
};
7484

75-
SessionOutput.prototype.wait = function wait(regexp, callback) {
76-
if (!this._queueWait(() => { this.wait(regexp, callback); }))
77-
return;
78-
79-
const self = this;
80-
this.on('line', function onLine(line) {
81-
if (!regexp.test(line))
82-
return;
83-
84-
self.removeListener('line', onLine);
85-
self._unqueueWait();
86-
87-
callback(line);
88-
});
89-
};
90-
91-
SessionOutput.prototype.waitBreak = function waitBreak(callback) {
92-
this.wait(/Process \d+ stopped/i, callback);
85+
SessionOutput.prototype.timeoutAfter = function timeoutAfter(timeout) {
86+
this.timeout = timeout;
9387
};
9488

95-
SessionOutput.prototype.linesUntil = function linesUntil(regexp, callback) {
96-
if (!this._queueWait(() => { this.linesUntil(regexp, callback); }))
89+
SessionOutput.prototype.wait = function wait(regexp, callback, allLines) {
90+
if (!this._queueWait(() => { this.wait(regexp, callback, allLines); }))
9791
return;
9892

99-
const lines = [];
10093
const self = this;
101-
this.on('line', function onLine(line) {
94+
const lines = [];
95+
96+
function onLine(line) {
10297
lines.push(line);
98+
debug('[LINE]', line);
10399

104100
if (!regexp.test(line))
105101
return;
106102

107103
self.removeListener('line', onLine);
108104
self._unqueueWait();
105+
done = true;
109106

110-
callback(lines);
107+
callback(allLines ? lines : line);
108+
}
109+
110+
let done = false;
111+
let timePassed = 0;
112+
const interval = 100;
113+
const check = setInterval(() => {
114+
timePassed += interval;
115+
if (done)
116+
clearInterval(check);
117+
118+
if (timePassed > self.timeout) {
119+
self.removeListener('line', onLine);
120+
self._unqueueWait();
121+
const message = `Test timeout in ${this.timeout} ` +
122+
`waiting for ${regexp}\n` +
123+
`\n${'='.repeat(10)} lldb output ${'='.repeat(10)}\n` +
124+
`\n${lines.join('\n')}` +
125+
`\n${'='.repeat(30)}\n`;
126+
throw new Error(message);
127+
}
128+
}, interval);
129+
130+
this.on('line', onLine);
131+
};
132+
133+
SessionOutput.prototype.waitBreak = function waitBreak(callback) {
134+
this.wait(/Process \d+ stopped/i, () => {
135+
// Do not resume immediately since the process would print
136+
// the instructions out and input sent before the stdout finish
137+
// could be lost
138+
setTimeout(callback, exports.promptDelay);
111139
});
112140
};
113141

142+
SessionOutput.prototype.linesUntil = function linesUntil(regexp, callback) {
143+
this.wait(regexp, callback, true);
144+
};
114145

115-
function Session(scenario) {
146+
function Session(options) {
116147
EventEmitter.call(this);
148+
const timeout = parseInt(process.env.TEST_TIMEOUT) || 10000;
149+
const lldbBin = process.env.TEST_LLDB_BINARY || 'lldb';
150+
const env = Object.assign({}, process.env);
151+
152+
if (options.ranges)
153+
env.LLNODE_RANGESFILE = options.ranges;
154+
155+
debug('lldb binary:', lldbBin);
156+
if (options.scenario) {
157+
this.needToKill = true;
158+
// lldb -- node scenario.js
159+
const args = [
160+
'--',
161+
process.execPath,
162+
'--abort_on_uncaught_exception',
163+
'--expose_externalize_string',
164+
path.join(exports.fixturesDir, options.scenario)
165+
];
166+
167+
debug('lldb args:', args);
168+
this.lldb = spawn(lldbBin, args, {
169+
stdio: ['pipe', 'pipe', 'pipe'],
170+
env: env
171+
});
172+
this.lldb.stdin.write(`plugin load "${exports.llnodePath}"\n`);
173+
this.lldb.stdin.write('run\n');
174+
} else if (options.core) {
175+
this.needToKill = false;
176+
debug('loading core', options.core);
177+
// lldb node -c core
178+
this.lldb = spawn(lldbBin, [], {
179+
stdio: ['pipe', 'pipe', 'pipe'],
180+
env: env
181+
});
182+
this.lldb.stdin.write(`plugin load "${exports.llnodePath}"\n`);
183+
this.lldb.stdin.write(`target create "${options.executable}"` +
184+
` --core "${options.core}"\n`);
185+
}
186+
this.stdout = new SessionOutput(this, this.lldb.stdout, timeout);
187+
this.stderr = new SessionOutput(this, this.lldb.stderr, timeout);
117188

118-
// lldb -- node scenario.js
119-
this.lldb = spawn(process.env.TEST_LLDB_BINARY || 'lldb', [
120-
'--',
121-
process.execPath,
122-
'--abort_on_uncaught_exception',
123-
'--expose_externalize_string',
124-
path.join(exports.fixturesDir, scenario)
125-
], {
126-
stdio: [ 'pipe', 'pipe', 'pipe' ],
127-
env: util._extend(util._extend({}, process.env), {
128-
LLNODE_RANGESFILE: exports.ranges
129-
})
189+
this.stderr.on('line', (line) => {
190+
debug('[stderr]', line);
130191
});
131192

132-
this.lldb.stdin.write(`plugin load "${exports.llnodePath}"\n`);
133-
this.lldb.stdin.write('run\n');
134-
135-
this.initialized = false;
136-
this.stdout = new SessionOutput(this, this.lldb.stdout);
137-
this.stderr = new SessionOutput(this, this.lldb.stderr);
138-
139193
// Map these methods to stdout for compatibility with legacy tests.
140194
this.wait = SessionOutput.prototype.wait.bind(this.stdout);
141195
this.waitBreak = SessionOutput.prototype.waitBreak.bind(this.stdout);
142196
this.linesUntil = SessionOutput.prototype.linesUntil.bind(this.stdout);
197+
this.timeoutAfter = SessionOutput.prototype.timeoutAfter.bind(this.stdout);
143198
}
144199
util.inherits(Session, EventEmitter);
145200
exports.Session = Session;
146201

147202
Session.create = function create(scenario) {
148-
return new Session(scenario);
203+
return new Session({ scenario: scenario });
204+
};
205+
206+
Session.loadCore = function loadCore(executable, core, ranges) {
207+
return new Session({
208+
executable: executable,
209+
core: core,
210+
ranges: ranges
211+
});
212+
};
213+
214+
Session.prototype.waitCoreLoad = function waitCoreLoad(callback) {
215+
this.wait(/Core file[^\n]+was loaded/, callback);
149216
};
150217

151218
Session.prototype.kill = function kill() {
152-
this.lldb.kill();
153-
this.lldb = null;
219+
// if a 'quit' has been sent to lldb, killing it could result in ECONNRESET
220+
if (this.lldb.channel) {
221+
debug('kill lldb');
222+
this.lldb.kill();
223+
this.lldb = null;
224+
}
154225
};
155226

156227
Session.prototype.quit = function quit() {
157-
this.send('kill');
228+
if (this.needToKill)
229+
this.send('kill'); // kill the process launched in lldb
230+
158231
this.send('quit');
159232
};
160233

161234
Session.prototype.send = function send(line, callback) {
235+
debug('[SEND]', line);
162236
this.lldb.stdin.write(line + '\n', callback);
163237
};
164238

165-
166-
exports.generateRanges = function generateRanges(cb) {
239+
exports.generateRanges = function generateRanges(core, dest, cb) {
167240
let script;
168241
if (process.platform === 'darwin')
169242
script = path.join(__dirname, '..', 'scripts', 'otool2segments.py');
170243
else
171244
script = path.join(__dirname, '..', 'scripts', 'readelf2segments.py');
172245

173-
const proc = spawn(script, [ exports.core ], {
174-
stdio: [ null, 'pipe', 'inherit' ]
246+
debug('[RANGES]', `${script}, ${core}, ${dest}`);
247+
const proc = spawn(script, [core], {
248+
stdio: [null, 'pipe', 'inherit']
175249
});
176250

177-
proc.stdout.pipe(fs.createWriteStream(exports.ranges));
251+
proc.stdout.pipe(fs.createWriteStream(dest));
178252

179253
proc.on('exit', (status) => {
180254
cb(status === 0 ? null : new Error('Failed to generate ranges'));

test/inspect-test.js

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -190,10 +190,8 @@ tape('v8 inspect', (t) => {
190190
lines.indexOf('<Array: length=20'),
191191
-1,
192192
'long array length');
193-
t.ok(
194-
lines.match(/\[9\]=<Smi: 5>}>$/),
195-
'long array content');
196-
sess.send(`v8 inspect ${arrayBuffer}`);
193+
t.ok(lines.match(/\[9\]=<Smi: 5>}>$/), 'long array content');
194+
sess.send(`v8 inspect ${arrayBuffer}`);
197195
});
198196

199197
sess.linesUntil(/\]>/, (lines) => {
@@ -286,7 +284,7 @@ tape('v8 inspect', (t) => {
286284
// the function we want.)
287285
const arrowSource = 'source:\n' +
288286
'function c.hashmap.(anonymous function)(a,b)=>{a+b}\n' +
289-
'>'
287+
'>';
290288

291289
t.ok(lines.includes(
292290
arrowSource),
@@ -301,11 +299,11 @@ tape('v8 inspect', (t) => {
301299
// Include 'source:' and '>' to act as boundaries. (Avoid
302300
// passing if the whole file it displayed instead of just
303301
// the function we want.)
304-
const methodSource = " source:\n" +
305-
"function method() {\n" +
306-
" throw new Error('Uncaught');\n" +
307-
" }\n" +
308-
">"
302+
const methodSource = ' source:\n' +
303+
'function method() {\n' +
304+
' throw new Error(\'Uncaught\');\n' +
305+
' }\n' +
306+
'>';
309307

310308
t.ok(lines.includes(
311309
methodSource),

0 commit comments

Comments
 (0)