diff --git a/.travis.yml b/.travis.yml index d6204d01..85af7dbf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,13 +3,13 @@ sudo: required dist: trusty before_install: - sudo apt-get -qq update - - sudo apt-get install lldb-3.6 lldb-3.6-dev -y + - sudo apt-get install lldb-3.9 liblldb-3.9-dev -y - git clone https://chromium.googlesource.com/external/gyp.git tools/gyp node_js: - "4" - - "5" - "6" - - "7" + - "8" + - "9" branches: only: - master diff --git a/Makefile b/Makefile index c5b163c6..874bdc8c 100644 --- a/Makefile +++ b/Makefile @@ -19,9 +19,14 @@ uninstall-linux: format: clang-format -i src/* -_travis: - ./gyp_llnode -Dlldb_dir=/usr/lib/llvm-3.6/ -f make - make -C out/ - TEST_LLDB_BINARY=`which lldb-3.6` npm test +configure: scripts/configure.js + node scripts/configure.js + +plugin: configure + ./gyp_llnode + $(MAKE) -C out/ + +_travis: plugin + TEST_LLDB_BINARY=`which lldb-3.9` TEST_LLNODE_DEBUG=true npm test .PHONY: all diff --git a/test/common.js b/test/common.js index f420bd44..8f0a8348 100644 --- a/test/common.js +++ b/test/common.js @@ -8,10 +8,19 @@ const spawn = require('child_process').spawn; const EventEmitter = require('events').EventEmitter; exports.fixturesDir = path.join(__dirname, 'fixtures'); -exports.buildDir = path.join(__dirname, '..', 'out', 'Release'); +exports.projectDir = path.join(__dirname, '..'); exports.core = path.join(os.tmpdir(), 'core'); -exports.ranges = exports.core + '.ranges'; +exports.promptDelay = 200; + +function llnodeDebug() { + // Node v4.x does not support rest + const args = Array.prototype.slice.call(arguments); + console.error.apply(console, [`[TEST][${process.pid}]`].concat(args)); +} + +const debug = exports.debug = + process.env.TEST_LLNODE_DEBUG ? llnodeDebug : () => { }; let pluginName; if (process.platform === 'darwin') @@ -19,16 +28,19 @@ if (process.platform === 'darwin') else if (process.platform === 'windows') pluginName = 'llnode.dll'; else - pluginName = path.join('lib.target', 'llnode.so'); + pluginName = 'llnode.so'; -exports.llnodePath = path.join(exports.buildDir, pluginName); +exports.llnodePath = path.join(exports.projectDir, pluginName); +exports.saveCoreTimeout = 180 * 1000; +exports.loadCoreTimeout = 20 * 1000; -function SessionOutput(session, stream) { +function SessionOutput(session, stream, timeout) { EventEmitter.call(this); this.waiting = false; this.waitQueue = []; - let buf = ''; + this.timeout = timeout || 10000; + this.session = session; stream.on('data', (data) => { buf += data; @@ -44,15 +56,15 @@ function SessionOutput(session, stream) { if (/process \d+ exited/i.test(line)) session.kill(); - else if (session.initialized) + else this.emit('line', line); - else if (/process \d+ launched/i.test(line)) - session.initialized = true; } }); // Ignore errors - stream.on('error', () => {}); + stream.on('error', (err) => { + debug('[stream error]', err); + }); } util.inherits(SessionOutput, EventEmitter); @@ -72,109 +84,171 @@ SessionOutput.prototype._unqueueWait = function _unqueueWait() { this.waitQueue.shift()(); }; -SessionOutput.prototype.wait = function wait(regexp, callback) { - if (!this._queueWait(() => { this.wait(regexp, callback); })) +SessionOutput.prototype.timeoutAfter = function timeoutAfter(timeout) { + this.timeout = timeout; +}; + +SessionOutput.prototype.wait = function wait(regexp, callback, allLines) { + if (!this._queueWait(() => { this.wait(regexp, callback, allLines); })) return; const self = this; - this.on('line', function onLine(line) { + const lines = []; + + function onLine(line) { + lines.push(line); + debug('[LINE]', line); + if (!regexp.test(line)) return; self.removeListener('line', onLine); self._unqueueWait(); + done = true; - callback(line); - }); -}; - -SessionOutput.prototype.waitBreak = function waitBreak(callback) { - this.wait(/Process \d+ stopped/i, callback); -}; - -SessionOutput.prototype.linesUntil = function linesUntil(regexp, callback) { - if (!this._queueWait(() => { this.linesUntil(regexp, callback); })) - return; - - const lines = []; - const self = this; - this.on('line', function onLine(line) { - lines.push(line); + callback(null, allLines ? lines : line); + } - if (!regexp.test(line)) + let done = false; + const check = setTimeout(() => { + if (done) return; self.removeListener('line', onLine); self._unqueueWait(); + console.error(`${'='.repeat(10)} lldb output ${'='.repeat(10)}`); + console.error(lines.join('\n')); + console.error('='.repeat(33)); + const message = `Test timeout in ${this.timeout} ` + + `waiting for ${regexp}`; + callback(new Error(message)); + }, this.timeout).unref(); + + this.on('line', onLine); +}; - callback(lines); +SessionOutput.prototype.waitBreak = function waitBreak(callback) { + this.wait(/Process \d+ stopped/i, (err) => { + if (err) + return callback(err); + + // Do not resume immediately since the process would print + // the instructions out and input sent before the stdout finish + // could be lost + setTimeout(callback, exports.promptDelay); }); }; +SessionOutput.prototype.linesUntil = function linesUntil(regexp, callback) { + this.wait(regexp, callback, true); +}; -function Session(scenario) { +function Session(options) { EventEmitter.call(this); + const timeout = parseInt(process.env.TEST_TIMEOUT) || 10000; + const lldbBin = process.env.TEST_LLDB_BINARY || 'lldb'; + const env = Object.assign({}, process.env); + + if (options.ranges) + env.LLNODE_RANGESFILE = options.ranges; + + debug('lldb binary:', lldbBin); + if (options.scenario) { + this.needToKill = true; + // lldb -- node scenario.js + const args = [ + '--', + process.execPath, + '--abort_on_uncaught_exception', + '--expose_externalize_string', + path.join(exports.fixturesDir, options.scenario) + ]; + + debug('lldb args:', args); + this.lldb = spawn(lldbBin, args, { + stdio: ['pipe', 'pipe', 'pipe'], + env: env + }); + this.lldb.stdin.write(`plugin load "${exports.llnodePath}"\n`); + this.lldb.stdin.write('run\n'); + } else if (options.core) { + this.needToKill = false; + debug('loading core', options.core); + // lldb node -c core + this.lldb = spawn(lldbBin, [], { + stdio: ['pipe', 'pipe', 'pipe'], + env: env + }); + this.lldb.stdin.write(`plugin load "${exports.llnodePath}"\n`); + this.lldb.stdin.write(`target create "${options.executable}"` + + ` --core "${options.core}"\n`); + } + this.stdout = new SessionOutput(this, this.lldb.stdout, timeout); + this.stderr = new SessionOutput(this, this.lldb.stderr, timeout); - // lldb -- node scenario.js - this.lldb = spawn(process.env.TEST_LLDB_BINARY || 'lldb', [ - '--', - process.execPath, - '--abort_on_uncaught_exception', - '--expose_externalize_string', - path.join(exports.fixturesDir, scenario) - ], { - stdio: [ 'pipe', 'pipe', 'pipe' ], - env: util._extend(util._extend({}, process.env), { - LLNODE_RANGESFILE: exports.ranges - }) + this.stderr.on('line', (line) => { + debug('[stderr]', line); }); - this.lldb.stdin.write(`plugin load "${exports.llnodePath}"\n`); - this.lldb.stdin.write('run\n'); - - this.initialized = false; - this.stdout = new SessionOutput(this, this.lldb.stdout); - this.stderr = new SessionOutput(this, this.lldb.stderr); - // Map these methods to stdout for compatibility with legacy tests. this.wait = SessionOutput.prototype.wait.bind(this.stdout); this.waitBreak = SessionOutput.prototype.waitBreak.bind(this.stdout); this.linesUntil = SessionOutput.prototype.linesUntil.bind(this.stdout); + this.timeoutAfter = SessionOutput.prototype.timeoutAfter.bind(this.stdout); } util.inherits(Session, EventEmitter); exports.Session = Session; Session.create = function create(scenario) { - return new Session(scenario); + return new Session({ scenario: scenario }); +}; + +Session.loadCore = function loadCore(executable, core, ranges) { + return new Session({ + executable: executable, + core: core, + ranges: ranges + }); +}; + +Session.prototype.waitCoreLoad = function waitCoreLoad(callback) { + this.wait(/Core file[^\n]+was loaded/, callback); }; Session.prototype.kill = function kill() { - this.lldb.kill(); - this.lldb = null; + // if a 'quit' has been sent to lldb, killing it could result in ECONNRESET + if (this.lldb.channel) { + debug('kill lldb'); + this.lldb.kill(); + this.lldb = null; + } }; Session.prototype.quit = function quit() { - this.send('kill'); + if (this.needToKill) + this.send('kill'); // kill the process launched in lldb + this.send('quit'); }; Session.prototype.send = function send(line, callback) { + debug('[SEND]', line); this.lldb.stdin.write(line + '\n', callback); }; - -exports.generateRanges = function generateRanges(cb) { +exports.generateRanges = function generateRanges(core, dest, cb) { let script; if (process.platform === 'darwin') script = path.join(__dirname, '..', 'scripts', 'otool2segments.py'); else script = path.join(__dirname, '..', 'scripts', 'readelf2segments.py'); - const proc = spawn(script, [ exports.core ], { - stdio: [ null, 'pipe', 'inherit' ] + debug('[RANGES]', `${script}, ${core}, ${dest}`); + const proc = spawn(script, [core], { + stdio: [null, 'pipe', 'inherit'] }); - proc.stdout.pipe(fs.createWriteStream(exports.ranges)); + proc.stdout.pipe(fs.createWriteStream(dest)); proc.on('exit', (status) => { cb(status === 0 ? null : new Error('Failed to generate ranges')); diff --git a/test/frame-test.js b/test/frame-test.js index f4f047fc..acb81a03 100644 --- a/test/frame-test.js +++ b/test/frame-test.js @@ -8,11 +8,13 @@ tape('v8 stack', (t) => { t.timeoutAfter(15000); const sess = common.Session.create('frame-scenario.js'); - sess.waitBreak(() => { + sess.waitBreak((err) => { + t.error(err); sess.send('v8 bt'); }); - sess.linesUntil(/eyecatcher/, (lines) => { + sess.linesUntil(/eyecatcher/, (err, lines) => { + t.error(err); lines.reverse(); t.ok(lines.length > 4, 'frame count'); // FIXME(bnoordhuis) This can fail with versions of lldb that don't diff --git a/test/inspect-test.js b/test/inspect-test.js index c432dbba..24374ef7 100644 --- a/test/inspect-test.js +++ b/test/inspect-test.js @@ -9,14 +9,16 @@ tape('v8 inspect', (t) => { const sess = common.Session.create('inspect-scenario.js'); - sess.waitBreak(() => { + sess.waitBreak((err) => { + t.error(err); sess.send('v8 bt'); }); let that = null; let fn = null; - sess.wait(/inspect-scenario.js/, (line) => { + sess.wait(/inspect-scenario.js/, (err, line) => { + t.error(err); let match = line.match(/method\(this=(0x[0-9a-f]+)[^\n]+fn=(0x[0-9a-f]+)/i); t.ok(match, 'method should have `this`'); @@ -28,11 +30,13 @@ tape('v8 inspect', (t) => { let hashmap = null; - sess.wait(/Class/, (line) => { + sess.wait(/Class/, (err, line) => { + t.error(err); t.notEqual(line.indexOf(that), -1, 'addr of `Class` should match'); }); - sess.linesUntil(/}>/, (lines) => { + sess.linesUntil(/}>/, (err, lines) => { + t.error(err); lines = lines.join('\n'); t.ok(/x=/.test(lines), '.x smi property'); t.ok(/y=123.456/.test(lines), '.y heap number property'); @@ -57,11 +61,13 @@ tape('v8 inspect', (t) => { let uint8Array = null; let buffer = null; - sess.wait(/Object/, (line) => { + sess.wait(/Object/, (err, line) => { + t.error(err); t.notEqual(line.indexOf(hashmap), -1, 'addr of `Object` should match'); }); - sess.linesUntil(/}>/, (lines) => { + sess.linesUntil(/}>/, (err, lines) => { + t.error(err); lines = lines.join('\n'); t.ok(/\[0\]=[^\n]*null/.test(lines), '[0] null element'); t.ok(/\[4\]=[^\n]*undefined/.test(lines), '[4] undefined element'); @@ -135,14 +141,16 @@ tape('v8 inspect', (t) => { sess.send(`v8 inspect -F ${cons}`); }); - sess.linesUntil(/}>/, (lines) => { + sess.linesUntil(/}>/, (err, lines) => { + t.error(err); lines = lines.join('\n'); t.ok(/source=\/regexp\//.test(lines) || /\.source=[^\n]*/.test(lines), 'regexp.source'); }); - sess.linesUntil(/">/, (lines) => { + sess.linesUntil(/">/, (err, lines) => { + t.error(err); lines = lines.join('\n'); t.notEqual( lines.indexOf('this could be a bit smaller, but v8 wants big str.' + @@ -153,7 +161,8 @@ tape('v8 inspect', (t) => { sess.send(`v8 inspect --string-length 20 ${cons}`); }); - sess.linesUntil(/">/, (lines) => { + sess.linesUntil(/">/, (err, lines) => { + t.error(err); lines = lines.join('\n'); t.notEqual( lines.indexOf('this could be a bit ...'), @@ -163,7 +172,8 @@ tape('v8 inspect', (t) => { sess.send(`v8 inspect ${thin}`); }); - sess.linesUntil(/">/, (lines) => { + sess.linesUntil(/">/, (err, lines) => { + t.error(err); lines = lines.join('\n'); t.ok( /0x[0-9a-f]+:/.test(lines), @@ -172,7 +182,8 @@ tape('v8 inspect', (t) => { sess.send(`v8 inspect ${array}`); }); - sess.linesUntil(/}>/, (lines) => { + sess.linesUntil(/}>/, (err, lines) => { + t.error(err); lines = lines.join('\n'); t.notEqual( lines.indexOf(' { sess.send(`v8 inspect --array-length 10 ${longArray}`); }); - sess.linesUntil(/}>/, (lines) => { + sess.linesUntil(/}>/, (err, lines) => { + t.error(err); lines = lines.join('\n'); t.notEqual( lines.indexOf('}>$/), - 'long array content'); - sess.send(`v8 inspect ${arrayBuffer}`); + t.ok(lines.match(/\[9\]=}>$/), 'long array content'); + sess.send(`v8 inspect ${arrayBuffer}`); }); - sess.linesUntil(/\]>/, (lines) => { + sess.linesUntil(/\]>/, (err, lines) => { + t.error(err); lines = lines.join('\n'); const re = new RegExp( '0x[0-9a-f]+:' + @@ -209,7 +220,8 @@ tape('v8 inspect', (t) => { sess.send(`v8 inspect --array-length 1 ${arrayBuffer}`); }); - sess.linesUntil(/]>/, (lines) => { + sess.linesUntil(/]>/, (err, lines) => { + t.error(err); lines = lines.join('\n'); const re = new RegExp( '0x[0-9a-f]+:' + @@ -222,7 +234,8 @@ tape('v8 inspect', (t) => { sess.send(`v8 inspect ${uint8Array}`); }); - sess.linesUntil(/]>/, (lines) => { + sess.linesUntil(/]>/, (err, lines) => { + t.error(err); lines = lines.join('\n'); const re = new RegExp( '0x[0-9a-f]+:' + @@ -236,7 +249,8 @@ tape('v8 inspect', (t) => { sess.send(`v8 inspect --array-length 1 ${uint8Array}`); }); - sess.linesUntil(/]>/, (lines) => { + sess.linesUntil(/]>/, (err, lines) => { + t.error(err); lines = lines.join('\n'); const re = new RegExp( '0x[0-9a-f]+:' + @@ -250,7 +264,8 @@ tape('v8 inspect', (t) => { sess.send(`v8 inspect ${buffer}`); }); - sess.linesUntil(/]>/, (lines) => { + sess.linesUntil(/]>/, (err, lines) => { + t.error(err); lines = lines.join('\n'); const re = new RegExp( '0x[0-9a-f]+:' + @@ -264,7 +279,8 @@ tape('v8 inspect', (t) => { sess.send(`v8 inspect --array-length 1 ${buffer}`); }); - sess.linesUntil(/]>/, (lines) => { + sess.linesUntil(/]>/, (err, lines) => { + t.error(err); lines = lines.join('\n'); const re = new RegExp( '0x[0-9a-f]+:' + @@ -279,14 +295,15 @@ tape('v8 inspect', (t) => { }); - sess.linesUntil(/^>/, (lines) => { + sess.linesUntil(/^>/, (err, lines) => { + t.error(err); lines = lines.join('\n'); // Include 'source:' and '>' to act as boundaries. (Avoid // passing if the whole file it displayed instead of just // the function we want.) const arrowSource = 'source:\n' + 'function c.hashmap.(anonymous function)(a,b)=>{a+b}\n' + - '>' + '>'; t.ok(lines.includes( arrowSource), @@ -295,17 +312,18 @@ tape('v8 inspect', (t) => { sess.send(`v8 inspect -s ${fn}`); }); - sess.linesUntil(/^>/, (lines) => { + sess.linesUntil(/^>/, (err, lines) => { + t.error(err); lines = lines.join('\n'); // Include 'source:' and '>' to act as boundaries. (Avoid // passing if the whole file it displayed instead of just // the function we want.) - const methodSource = " source:\n" + - "function method() {\n" + - " throw new Error('Uncaught');\n" + - " }\n" + - ">" + const methodSource = ' source:\n' + + 'function method() {\n' + + ' throw new Error(\'Uncaught\');\n' + + ' }\n' + + '>'; t.ok(lines.includes( methodSource), @@ -333,17 +351,21 @@ tape('v8 inspect', (t) => { } }); - sess.linesUntil(/}>/, (lines) => { - lines = lines.join('\n'); - t.ok(/internal fields/.test(lines), 'method.scopedAPI.internalFields'); - }); + if (process.version >= 'v5.0.0') { + sess.linesUntil(/}>/, (err, lines) => { + t.error(err); + lines = lines.join('\n'); + t.ok(/internal fields/.test(lines), 'method.scopedAPI.internalFields'); + }); - sess.linesUntil(/}>/, (lines) => { - lines = lines.join('\n'); - t.ok(/outerVar[^\n]+"outer variable"/.test(lines), - 'method.closure.outerVar'); + sess.linesUntil(/}>/, (err, lines) => { + t.error(err); + lines = lines.join('\n'); + t.ok(/outerVar[^\n]+"outer variable"/.test(lines), + 'method.closure.outerVar'); - sess.quit(); - t.end(); - }); + sess.quit(); + t.end(); + }); + } }); diff --git a/test/scan-test.js b/test/scan-test.js index 496c3f58..8bdbc693 100644 --- a/test/scan-test.js +++ b/test/scan-test.js @@ -1,17 +1,26 @@ 'use strict'; -// No `process save-core` on linuxes :( -if (process.platform !== 'darwin') - return; - const tape = require('tape'); - const common = require('./common'); tape('v8 findrefs and friends', (t) => { - t.timeoutAfter(90000); + t.timeoutAfter(common.saveCoreTimeout); + + // Use prepared core and executable to test + if (process.env.LLNODE_CORE && process.env.LLNODE_NODE_EXE) { + test(process.env.LLNODE_NODE_EXE, process.env.LLNODE_CORE, t); + } else if (process.platform === 'linux') { + t.skip('No `process save-core` on linux'); + t.end(); + } else { + saveCoreAndTest(t); + } +}); +function saveCoreAndTest(t) { + // Create a core and test const sess = common.Session.create('inspect-scenario.js'); + sess.timeoutAfter(common.saveCoreTimeout); sess.waitBreak(() => { sess.send(`process save-core ${common.core}`); @@ -19,30 +28,50 @@ tape('v8 findrefs and friends', (t) => { sess.send('version'); }); - sess.wait(/lldb\-/, () => { + sess.wait(/lldb-/, (err) => { + t.error(err); t.ok(true, 'Saved core'); + sess.send('target delete 0'); + sess.quit(); - sess.send(`target create -c ${common.core}`); + test(process.execPath, common.core, t); }); - - sess.wait(/Core file[^\n]+was loaded/, () => { +} + +function test(executable, core, t) { + let sess, ranges; + if (process.env.LLNODE_NO_RANGES) { + sess = common.Session.loadCore(executable, core); + } else { + ranges = core + '.ranges'; + sess = common.Session.loadCore(executable, core, ranges); + } + sess.timeoutAfter(common.loadCoreTimeout); + + sess.waitCoreLoad((err) => { + t.error(err); t.ok(true, 'Loaded core'); - common.generateRanges((err) => { - t.error(err, 'generateRanges'); + if (ranges) { + common.generateRanges(core, ranges, (err) => { + t.error(err); + t.ok(true, 'Generated ranges'); + sess.send('version'); + }); + } else { sess.send('version'); - }); + } }); - sess.wait(/lldb\-/, () => { - t.ok(true, 'Generated ranges'); - + sess.wait(/lldb-/, (err) => { + t.error(err); sess.send('v8 findjsobjects'); // Just a separator sess.send('version'); }); - sess.linesUntil(/lldb\-/, (lines) => { + sess.linesUntil(/lldb-/, (err, lines) => { + t.error(err); t.ok(/\d+ Zlib/.test(lines.join('\n')), 'Zlib should be in findjsobjects'); sess.send('v8 findjsinstances Zlib'); @@ -50,7 +79,8 @@ tape('v8 findrefs and friends', (t) => { sess.send('version'); }); - sess.linesUntil(/lldb\-/, (lines) => { + sess.linesUntil(/lldb-/, (err, lines) => { + t.error(err); // Find refs to every Zlib instance let found = false; for (let i = lines.length - 1; i >= 0; i--) { @@ -67,7 +97,9 @@ tape('v8 findrefs and friends', (t) => { sess.send('version'); }); - sess.linesUntil(/lldb\-/, (lines) => { + sess.linesUntil(/lldb-/, (err, lines) => { + t.error(err); + // `class Deflate extends Zlib` makes instances show up as // Transform objects (which Zlib inherits from) in node.js 8.0.0. // That change was reverted in https://github.com/nodejs/node/pull/13374 @@ -79,8 +111,8 @@ tape('v8 findrefs and friends', (t) => { t.ok(/Object\.holder/.test(lines.join('\n')), 'Should find reference #2'); t.ok(/\(Array\)\[1\]/.test(lines.join('\n')), 'Should find reference #3'); - sess.send('target delete 1'); + sess.send('target delete 0'); sess.quit(); t.end(); }); -}); +} diff --git a/test/stack-test.js b/test/stack-test.js index e82c473c..edff8da9 100644 --- a/test/stack-test.js +++ b/test/stack-test.js @@ -12,7 +12,8 @@ tape('v8 stack', (t) => { sess.send('v8 bt'); }); - sess.wait(/stack-scenario.js/, (line) => { + sess.wait(/stack-scenario.js/, (err, line) => { + t.error(err); t.ok(/method\(this=.*Class.*method args.*Number: 1\.23.*null/.test(line), 'Class method name and args'); @@ -21,7 +22,8 @@ tape('v8 stack', (t) => { 'Class method file pos'); }); - sess.wait(/stack-scenario.js/, (line) => { + sess.wait(/stack-scenario.js/, (err, line) => { + t.error(err); t.ok(/third\(.*third args.*Smi: 1.*Object/.test(line), 'Third function name and args'); @@ -29,7 +31,8 @@ tape('v8 stack', (t) => { t.ok(/stack-scenario.js:13:15/.test(line), 'Third function file pos'); }); - sess.wait(/stack-scenario.js/, (line) => { + sess.wait(/stack-scenario.js/, (err, line) => { + t.error(err); t.ok(/second\(.*second args.*Smi: 1/.test(line), 'Second function name and args'); @@ -37,7 +40,8 @@ tape('v8 stack', (t) => { t.ok(/stack-scenario.js:9:16/.test(line), 'Second function file pos'); }); - sess.wait(/stack-scenario.js/, (line) => { + sess.wait(/stack-scenario.js/, (err, line) => { + t.error(err); t.ok(/first/.test(line), 'first function name'); // TODO(indutny): line numbers are off diff --git a/test/usage-test.js b/test/usage-test.js index 80beefed..6e491684 100644 --- a/test/usage-test.js +++ b/test/usage-test.js @@ -2,8 +2,8 @@ const tape = require('tape'); const common = require('./common'); -function removeBlankLines(lines) { - return lines.filter((line) => { return line.trim() !== ''; }); +function containsLine(lines, re) { + return lines.some(line => re.test(line.trim())); } tape('usage messages', (t) => { @@ -11,33 +11,36 @@ tape('usage messages', (t) => { const sess = common.Session.create('inspect-scenario.js'); - sess.waitBreak(() => { + sess.waitBreak((err) => { + t.error(err); sess.send('v8 print'); }); - sess.stderr.linesUntil(/USAGE/, (lines) => { - t.ok(/^error: USAGE: v8 print expr$/.test(removeBlankLines(lines)[0]), - 'print usage message'); + sess.stderr.linesUntil(/USAGE/, (err, lines) => { + t.error(err); + const re = /^error: USAGE: v8 print expr$/; + t.ok(containsLine(lines, re), 'print usage message'); sess.send('v8 source list'); }); - sess.stderr.linesUntil(/USAGE/, (lines) => { - t.ok(/^error: USAGE: v8 source list$/.test(removeBlankLines(lines)[0]), - 'list usage message'); + sess.stderr.linesUntil(/USAGE/, (err, lines) => { + t.error(err); + const re = /^error: USAGE: v8 source list$/; + t.ok(containsLine(lines, re), 'list usage message'); sess.send('v8 findjsinstances'); }); - sess.stderr.linesUntil(/USAGE/, (lines) => { + sess.stderr.linesUntil(/USAGE/, (err, lines) => { + t.error(err); const re = /^error: USAGE: v8 findjsinstances \[flags\] instance_name$/; - - t.ok(re.test(removeBlankLines(lines)[0]), - 'findjsinstances usage message'); + t.ok(containsLine(lines, re), 'findjsinstances usage message'); sess.send('v8 findrefs'); }); - sess.stderr.linesUntil(/USAGE/, (lines) => { - t.ok(/^error: USAGE: v8 findrefs expr$/.test(removeBlankLines(lines)[0]), - 'findrefs usage message'); + sess.stderr.linesUntil(/USAGE/, (err, lines) => { + t.error(err); + const re = /^error: USAGE: v8 findrefs expr$/; + t.ok(containsLine(lines, re), 'findrefs usage message'); sess.quit(); t.end(); });