Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: update travis config, handle errors properly, add TEST_LLNODE_DEBUG #144

Merged
merged 5 commits into from
Nov 30, 2017
Merged
Show file tree
Hide file tree
Changes from all 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