Skip to content

(DO NOT MERGE): Reliable output #213

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

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a3308d2
Handle empty results from test files. Fix #198
jamestalmage Nov 13, 2015
d9c2de0
wip - report unhandledRejections
jamestalmage Nov 13, 2015
e7fd056
unhandledRejections should fail the build
jamestalmage Nov 13, 2015
2952115
wait for forked streams to end before resolving promise
jamestalmage Nov 13, 2015
13ffacf
turn off appveyor fast_finish
jamestalmage Nov 13, 2015
37f4367
add npm-debug.log as an appveyor artifact
jamestalmage Nov 13, 2015
91c6700
wait for io streams to end even when fork promise is rejected
jamestalmage Nov 13, 2015
c74a70c
basically working need to rebase onto PR#208
jamestalmage Nov 14, 2015
4c90d71
fix remaining test errors
jamestalmage Nov 14, 2015
2d274a2
drop unnecessary changes
jamestalmage Nov 14, 2015
308c93e
mitigate windows errors
jamestalmage Nov 14, 2015
5360804
defer exit on promise.catch(error) to give stdout time to catch up
jamestalmage Nov 14, 2015
effb4ef
add process.stdout.write trick to see if it works for error exit
jamestalmage Nov 14, 2015
53a0d95
trigger build
jamestalmage Nov 14, 2015
2e6031f
add extra delay on appveyor only
jamestalmage Nov 14, 2015
f548c2e
trigger build
jamestalmage Nov 14, 2015
25122f0
delay for every appveyor build
jamestalmage Nov 14, 2015
1075802
add retry logic to appveyor scripts (npm install fails all the time)
jamestalmage Nov 14, 2015
2873238
trigger build
jamestalmage Nov 14, 2015
1a15ae5
trigger build
jamestalmage Nov 14, 2015
bb70796
retry on tests
jamestalmage Nov 14, 2015
edd370a
trigger build
jamestalmage Nov 14, 2015
2ee9660
trigger build
jamestalmage Nov 14, 2015
3682480
trigger build
jamestalmage Nov 14, 2015
efa0313
trigger build
jamestalmage Nov 14, 2015
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 appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ environment:
install:
- ps: Install-Product node $env:nodejs_version
- set CI=true
- npm -g install npm@latest
- npm -g install npm@latest || (timeout 30 && npm -g install npm@latest)
- set PATH=%APPDATA%\npm;%PATH%
- npm install
- npm install || (timeout 30 && npm install)
matrix:
fast_finish: true
build: off
Expand All @@ -19,4 +19,4 @@ clone_depth: 1
test_script:
- node --version
- npm --version
- npm run test-win
- npm run test-win || (timeout 30 && npm run test-win)
39 changes: 33 additions & 6 deletions cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ var chalk = require('chalk');
var Promise = require('bluebird');
var fork = require('./lib/fork');
var log = require('./lib/logger');
var delayBeforeExit = 0;

if (process.env.APPVEYOR) {
delayBeforeExit = 500;
}

// Bluebird specific
Promise.longStackTraces();
Expand Down Expand Up @@ -45,11 +50,20 @@ var cli = meow({

var testCount = 0;
var fileCount = 0;
var unhandledRejectionCount = 0;
var uncaughtExceptionCount = 0;
var errors = [];

function error(err) {
console.error(err.stack);
process.exit(1);
function error(error) {
log.unexpectedExit(error);

// TODO: figure out why this needs to be here to
// correctly flush the output when multiple test files
process.stdout.write('');

setTimeout(function () {
process.exit(1);
}, delayBeforeExit);
}

function prefixTitle(file) {
Expand Down Expand Up @@ -116,11 +130,24 @@ function run(file) {
return fork(args)
.on('stats', stats)
.on('test', test)
.on('unhandledRejections', rejections)
.on('uncaughtException', uncaughtException)
.on('data', function (data) {
process.stdout.write(data);
});
}

function rejections(data) {
var r = data.unhandledRejections;
unhandledRejectionCount += r.length;
log.unhandledRejections(data.file, r);
}

function uncaughtException(data) {
uncaughtExceptionCount++;
log.uncaughtException(data.file, data.uncaughtException);
}

function sum(arr, key) {
var result = 0;

Expand All @@ -145,7 +172,7 @@ function exit(results) {
var failed = sum(stats, 'failCount');

log.write();
log.report(passed, failed);
log.report(passed, failed, unhandledRejectionCount, uncaughtExceptionCount);
log.write();

if (failed > 0) {
Expand All @@ -158,8 +185,8 @@ function exit(results) {

// timeout required to correctly flush stderr on Node 0.10 Windows
setTimeout(function () {
process.exit(failed > 0 ? 1 : 0);
}, 0);
process.exit(failed > 0 || unhandledRejectionCount > 0 || uncaughtExceptionCount > 0 ? 1 : 0);
}, delayBeforeExit);
}

function init(files) {
Expand Down
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
'use strict';
require('./lib/babel').avaRequired();
var setImmediate = require('set-immediate-shim');
var hasFlag = require('has-flag');
var chalk = require('chalk');
Expand Down
69 changes: 67 additions & 2 deletions lib/babel.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
'use strict';
var loudRejection = require('loud-rejection/api')(process);
var resolveFrom = require('resolve-from');
var createEspowerPlugin = require('babel-plugin-espower/create');
var requireFromString = require('require-from-string');
var destroyCircular = require('destroy-circular');

var hasGenerators = parseInt(process.version.slice(1), 10) > 0;
var testPath = process.argv[2];
Expand All @@ -24,13 +26,76 @@ var options = {
]
};

process.on('uncaughtException', function (exception) {
exception = serializeValue(exception);
if (process.send) {
process.send({
name: 'uncaughtException',
data: {
uncaughtException: exception
}
});
} else {
console.log({name: 'uncaughtException', uncaughtException: exception});
}
});

var avaRequired;

module.exports = {
avaRequired: function () {
avaRequired = true;
}
};

var transpiled = babel.transformFileSync(testPath, options);
requireFromString(transpiled.code, testPath, {
appendPaths: module.paths
});

if (!avaRequired) {
throw new Error('No tests found in ' + testPath + ', make sure to import "ava" at the top of your test file');
}

process.on('message', function (message) {
if (message['ava-kill-command']) {
process.exit(0);
var command = message['ava-child-process-command'];
if (command) {
process.emit('ava-' + command, message.data);
}
});

process.on('ava-kill', function () {
process.exit(0);
});

function serializeValue(value) {
if (typeof value === 'object') {
return destroyCircular(value);
}
if (typeof value === 'function') {
return '[Function ' + value.name + ']';
}
return value;
}

process.on('ava-cleanup', function () {
var unhandled = loudRejection.currentlyUnhandled();
if (unhandled.length) {
unhandled = unhandled.map(function (entry) {
return serializeValue(entry.reason);
});
process.send({
name: 'unhandledRejections',
data: {
unhandledRejections: unhandled
}
});
}

setTimeout(function () {
process.send({
name: 'cleaned-up',
data: {}
});
}, 100);
});
50 changes: 45 additions & 5 deletions lib/fork.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@ module.exports = function (args) {
cwd: path.dirname(file)
};

var start = Date.now();
var ps = childProcess.fork(babel, args, options);

function send(command, data) {
ps.send({'ava-child-process-command': command, 'data': data});
}

var promise = new Promise(function (resolve, reject) {
var testResults;

Expand All @@ -26,16 +31,37 @@ module.exports = function (args) {

// after all tests are finished and results received
// kill the forked process, so AVA can exit safely
ps.send({'ava-kill-command': true});
send('cleanup', true);
});

ps.on('cleaned-up', function () {
send('kill', true);
});

ps.on('uncaughtException', function () {
setTimeout(function () {
send('cleanup', true);
});
});

ps.on('error', reject);

ps.on('exit', function (code) {
if (code > 0 && code !== 143) {
reject(new Error(file + ' exited with a non-zero exit code: ' + code));
} else {
} else if (testResults) {
if (!testResults.tests.length) {
testResults.stats.failCount++;
testResults.tests.push({
duration: Date.now() - start,
title: file,
error: new Error('No tests for ' + file),
type: 'test'
});
}
resolve(testResults);
} else {
reject(new Error('Never got test results from: ' + file));
}
});
});
Expand All @@ -56,11 +82,25 @@ module.exports = function (args) {
ps.emit('data', data);
});

promise.on = function () {
ps.on.apply(ps, arguments);
var stdout = new Promise(function (resolve) {
ps.stdout.on('end', resolve);
});

var stderr = new Promise(function (resolve) {
ps.stderr.on('end', resolve);
});

var endPromise = Promise.all([promise.reflect(), stdout, stderr]).then(function () {
return promise;
});

endPromise.on = function () {
ps.on.apply(ps, arguments);

return endPromise;
};

return promise;
endPromise.send = send;

return endPromise;
};
39 changes: 35 additions & 4 deletions lib/logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,22 +58,53 @@ x.errors = function (results) {
var i = 0;

results.forEach(function (result) {
if (!(result.error && result.error.message)) {
if (!result.error) {
return;
}

i++;

log.writelpad(chalk.red(i + '.', result.title));
log.writelpad(chalk.red(beautifyStack(result.error.stack)));
log.write();
logError(result.error);
});
};

x.report = function (passed, failed) {
x.report = function (passed, failed, unhandled, uncaught) {
if (failed > 0) {
log.writelpad(chalk.red(failed, plur('test', failed), 'failed'));
} else {
log.writelpad(chalk.green(passed, plur('test', passed), 'passed'));
}
if (unhandled > 0) {
log.writelpad(chalk.red(unhandled, 'unhandled', plur('rejection', unhandled)));
}
if (uncaught > 0) {
log.writelpad(chalk.red(uncaught, 'uncaught', plur('exception', uncaught)));
}
};

x.unhandledRejections = function (file, rejections) {
if (!(rejections && rejections.length)) {
return;
}
rejections.forEach(function (rejection) {
log.write(chalk.red('Unhandled Rejection: ', file));
logError(rejection);
});
};

x.uncaughtException = function (file, error) {
log.write(chalk.red('Uncaught Exception: ', file));
logError(error);
};

function logError(error) {
if (error.stack) {
log.writelpad(chalk.red(beautifyStack(error.stack)));
} else {
log.writelpad(chalk.red(JSON.stringify(error)));
}
log.write();
}

x.unexpectedExit = logError;
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"globby": "^3.0.1",
"has-flag": "^1.0.0",
"is-generator": "^1.0.2",
"loud-rejection": "^1.2.0",
"meow": "^3.3.0",
"plur": "^2.0.0",
"power-assert-formatter": "^1.3.0",
Expand Down
8 changes: 8 additions & 0 deletions test/fixture/empty.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
__
____ _____ _______/ |_ ___.__.
_/ __ \ / \\____ \ __< | |
\ ___/| Y Y \ |_> > | \___ |
\___ >__|_| / __/|__| / ____|
\/ \/|__| \/
*/
1 change: 1 addition & 0 deletions test/fixture/immediate-0-exit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
process.exit(0);
9 changes: 9 additions & 0 deletions test/fixture/loud-rejection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const test = require('../../');

test('creates an unhandled rejection', t => {
Promise.reject(new Error(`You can't handle this!`));

setTimeout(function () {
t.end();
}, 0);
});
1 change: 1 addition & 0 deletions test/fixture/no-tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import test from '../../';
7 changes: 7 additions & 0 deletions test/fixture/uncaught-exception.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const test = require('../../');

test('throw an uncaught exception', t => {
setImmediate(() => {
throw new Error(`Can't catch me!`)
});
});
9 changes: 5 additions & 4 deletions test/fork.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,15 @@ test('resolves promise with tests info', function (t) {
});

test('rejects on error and streams output', function (t) {
var buffer = '';
t.plan(2);

fork(fixture('broken.js'))
.on('data', function (data) {
buffer += data;
.on('uncaughtException', function (data) {
var error = data.uncaughtException;
t.ok(/no such file or directory/.test(error.message));
})
.catch(function () {
t.ok(/no such file or directory/.test(buffer));
t.pass();
t.end();
});
});
Expand Down
Loading