Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 9 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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

This comment was marked as off-topic.

This comment was marked as off-topic.


.PHONY: all
194 changes: 134 additions & 60 deletions test/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,39 @@ 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')
pluginName = 'llnode.dylib';
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;
Expand All @@ -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);

Expand All @@ -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)}`);

This comment was marked as off-topic.

This comment was marked as off-topic.

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'));
Expand Down
6 changes: 4 additions & 2 deletions test/frame-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading