-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Asynchronous errors do not fail tests #567
Comments
@ORESoftware some relevant fixes were made in |
I'm getting the same issue with AVA 0.12.
|
Btw, can be just |
I don't think we can catch errors on next tick, because at that time test function has completed. And we also can't know where this uncaught error came from. To sum up, I'm not sure we can do anything about it, without using domains. |
@vdemedes makes sense. Presumably using |
yeah just fyi the same thing will happen if you replace setTimeout/setImmediate/process.nextTick with any other standard async fs.exists('foo', function(err){ it's all about throwing an error inside a function called asynchronously. the only real solution that I know of is the domain core module. but the Without domains you will just have to have an uncaughtException handler but @vdemedes https://github.com/vdemedes makes sense. Presumably using t.plan() would prevent the test from passing (or indeed — |
You can replace what I wrote above with the following code which is even more realistic, and you should see the same problem as above. const test = require('ava');
const fs = require('fs');
test(t => {
return new Promise(function(resolve){
fs.exists('foo', function(){
throw new Error('123');
resolve(); // won't get reached, but here for clarity
});
});
});
test(t => {
return new Promise(function(resolve){
setTimeout(function(){
throw new Error('123');
resolve(); // won't be reached, but here for clarity
}, 100);
});
});
test(t => {
return new Promise(function(resolve){
setImmediate(function(){
throw new Error('456');
resolve(); // won't be reached, but here for clarity
});
});
});
test(t => {
return new Promise(function(resolve){
process.nextTick(function(){
throw new Error('789');
resolve(); // won't be reached, but here for clarity
});
});
}); |
also, Mocha and Tape can solve this problem because they run tests serially, which means they can set the "current test" or "current hook" in the global scope, and then on an uncaughtException they can attach that exception to the current test / hook, but with asynchronous code, you simply do not know what the current test is, at the global scope |
Looking at your first example with fresh eyes, it's obvious it wouldn't work as you're trying to use async inside a sync test. Sync tests end in the current tick. If you want to use callbacks, use As for your second example. This is what I got with latest AVA:
It shows the correct line for all but However, I would say you're using it incorrectly. You shouldn't throw asynchronously inside a promise. That's what Might help if you could provide a realistic scenario where this is a problem. |
nah, it's going to break down quickly, if you move the asynchronous functions outside the test cases, we now have two functions like so var test = require('ava');
var fs = require('fs');
///////////////////////////////////////////////////////////
function checkFileExistence1(filename, cb){
fs.exists(filename, function(err){
throw new Error('123');
cb(err); // won't get reached, but here for clarity
});
}
test(t => {
return new Promise(function(resolve){
checkFileExistence1('foo',function(err){
resolve();
});
});
});
///////////////////////////////////////////////////////////////
function checkFileExistence2(filename){
return new Promise(function(resolve){
fs.exists(filename, function(err){
throw new Error('123');
resolve(); // won't get reached, but here for clarity
});
});
}
test(t => {
return new Promise(function(resolve){
return checkFileExistence2('foo');
});
});
as you can see, the line numbers are not reported accurately - meaning - all we have is an uncaught exception, and we don't know which test the errors pertain to. which is why we get this output
I created a gist for this I don't think you can solve these kinds of problems easily, so it's probably seriously best to just ignore that this is an issue for now, and perhaps future developments like asyncwrap which supposedly will replace domains will help solve the problem. As long as you have an uncaughtException handler (which this lib does of course), it should be ok (your test suite won't crash), but you won't be able to report the right test as having failed or the line number of the test that failed. Perhaps if more node.js libs get promisified (like fs core) these types of problems will go away, but I am not sure about that. |
along with the gist above, which shows a bug this also appears to be a bug, possibly related, thanks for listening, don't kill the messenger :) |
Yeah. We'll keep this open, but I doubt we'll do anything until AsyncWrap lands in Node.js. You could do what I do and just manually promisify all callback API's. It makes the tests so much nicer as a bonus. See: |
Is there a particular reason why domains cannot be used? Except, of course, that they are "soft-deprecated". It seems that simply wrapping all the test implementation functions in a |
Even an error thrown synchronously from a callback test seems to cause ava to just hang. Note that these errors do not necessarily have to come from assertions or intentionally thrown errors, but also from merely making a mistake while writing the test itself, ie. accidentally making a typo in a function call, causing errors such as test.cb('sync Error in callback-based test causes ava to hang indefinitely', () => {
process.chdr(__dirname) // TypeError: process.chdr is not a function (typo)
}) |
Domains don't exist in the browser, so relying on them becomes problematic as we move towards browser support. There's a PR to fix sync throws that will be merged shortly. Async throws remain problematic, but we've got an idea for handling that via a custom Babel plugin. I am on mobile now, but will link tp the relevant issues later. |
I confirm the bug:
This will cause ava to hang indefinitely instead of displaying an error like Not sure why it is tagged as low priority, it makes ava a pain to use to test callback-based methods. |
@loris your example triggers a This is low-priority because the problem of asynchronous errors goes way deeper than AVA, and even the workarounds we can do here require a large amount of effort. We have other, more pressing concerns. |
Just wrap callback APIs in |
@sindresorhus I tried to make it abundantly clear above that promisifying things won't solve everything, for example: const test = require('ava');
const fs = require('fs');
function asyncFn() {
return new Promise(function (resolve) {
fs.exists('foo', function () {
// some moron decides to throw here :)
throw new Error('unlikely but very very possible');
});
});
}
test(t => {
return asyncFn().then(function (res) {
t.is(res.bar, 'ok');
});
}); We run the above and we get:
90% of APIs are still callback based. if Pify/Promisifying can solve this, maybe the above example is a good one to use to demonstrate this? On this open issue thread? Nevertheless, if you don't have control of the asyncFn because it's in someone else's library, then it gets harder, but let's pretend you do have 100% control. |
the reason why your example hangs is most likely because you need to do this instead const test = require('ava');
function asyncFn(cb) {
process.nextTick(function(){ // force async here
cb(null, null);
});
}
test.cb(t => {
asyncFn((err, res) => {
t.is(res.bar, 'ok');
t.end();
});
}); the above works as expected, however, using the latest version of ava, if I omit the process.nextTick, it does hang, FYI, so you are correct in that observation. |
@ORESoftware that's a bad example. If you use |
Hey all
Just wondering if this is expected output
output:
shouldn't it be 0 passed, 3 exceptions?
The text was updated successfully, but these errors were encountered: