-
-
Notifications
You must be signed in to change notification settings - Fork 305
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
tape-catch #208
Comments
Its a bad default as it makes debugging harder. a node script should exit when an exception is thrown. tape is a node program. |
100% agreed on what @Raynos says. If you want to catch expected exceptions use const assert = require('assert')
const test = require('tape')
test('an exception', function (t) {
t.plan(1)
t.throws(compare.bind(null, 1, 2))
})
function compare (num, num) {
assert.equal(num, num)
} |
Hm. I had tests throwing exceptions and it was going unnoticed. I just noticed because the amount tests were less. Now maybe that's also because of So maybe this isn't really |
This might be the unhandled rejection problem with promises |
+1 what is the use-case for reporting a test which raised an exception as passing? |
@robhaswell testing what kind of exceptions your API throws and where is absolutely essential. |
@ljharb Absolutely I agree, yet the current behaviour doesn't support that at all. Exceptions raised are silently ignored, how does it help test what is thrown? For that you can use |
Perhaps inside a promise - but that's how promises work. Otherwise, it will terminate the process. |
This is definitely related to the fact that Node currently doesn't handle promise rejections. With many tests now being written using async/await this creates basically useless test fail messages. While this is caused by Node.js currently not handling Promise rejection (in 8 there is a warning that eventually this will just fail but it currently doesn't do that). As a way to behave inline with expectations for tests, I would suggest that tape should check to see if the test function is For those dealing with this problem right now, a simple global solution, is to require a test helper that includes the following line:
This will override Node's lack of proper error handling for Promise's. Quite frankly as this will eventually be the real behaviour of Node, I'd seriously consider just doing this all the time. |
node's not supposed to "handle" promise rejections; the developer is. The language spec requires you to be able to create unhandled rejections; there's nothing wrong with having unhandled rejections. Thus, it definitely won't become the real behavior of node by default, as that will break real code and also violate the spec. |
So I'm definitely a bit inexperienced with Node, but that does not appear to be correct as far as the current deprecation warning indicates. http://thecodebarbarian.com/unhandled-promise-rejections-in-node.js.html
That seems to indicate that at some future Node version it will eventually fail if a promise rejection is unhandled. Either way, this behaviour seems undesirable when writing unit tests to not throw unhandled promise rejections within an As a result, everywhere we want to do the following:
If we want to get useful error reports for unexpected exceptions we have to write:
If we want to get a stack trace for an unexpected error. As I've discovered event a simple syntax error in the above example will result in a useless warning from Node. I'll add that I disagree with the author's opinion that Node shouldn't change this and that unhandled promise rejections should, in fact, be silently handled. As far as I know other languages like C# that implement async/await do fail normally, as did Bluebird as the author acknowledges. If one doesn't explicitly put a try/catch around Also my apologies if I've hijacked an entirely unrelated issue. On initial glance it looked as if it might be related. |
Yes, the deprecation warning was optimistic by a few vocal folks who dislike promises; actual discussions in node core have thankfully roughly agreed that there's no way it will happen that way. Node definitely shouldn't change this - the JS language spec strictly requires that unhandled rejections be allowed to exist, without impacting the program. The relation to this thread is simply that if node has had promises since 0.12; node < 4 is EOL; node 8 has async functions; node 8 will be LTS in October. The ecosystem has moved from "promises shouldn't be required", to "promises are expected to be polyfilled everywhere". As such, it's - recently - a much stronger argument imo for |
Checking whether a return value is a promise by some mechanism and then handling it seems reasonable for async functions. Given that async functions use the built in promise and not a third party library you can do a prototype check ( e.g. It's difficult to tell whether a test block should be failed or whether the process should exit. I would favor the latter since tape has a If you dont like writing |
@Raynos that won't work cross-realm; the simplest robust way to do it is to unconditionally The process should never exit when there's an unhandled rejection in a promise, per the JS language spec. |
Yes the process should never exit. But the testing framework can have a different approach, its not an unhandled rejection. The testing framework handles all rejections and exits the test program saying one of the test blocks failed. it could continue running the other test blocks but test blocks in tape tend to be stateful and bailing on the first error in this case might make it easier to debug then continueing the program. |
That's a fair point; but "might make it easier" is where we disagree. It will make tests much cleaner if a rejection is seen as a failure rather than terminating early (just like how one test failure doesn't cause tape to exit, even when it would make other tests fail too) |
that makes sense. An async function is not run to completion so it makes sense that an await failing or a syntax error is reported as a failure instead of terminating early. |
@ljharb I agree with the test framework seeing it as a failure. Though that does kind of make me wonder: If it makes sense for a test to fail and allow testing to continue in an async exception situation, why not also synchronous one? Would it not desirable to express the result of an unhandled exception the same way regardless of the async/sync context? |
@chrisnicola that would be a reasonable choice for tape to make, but sync errors and promise rejections simply aren't the same thing, despite both the parallels and the insistence of some luminaries in the community. It's totally reasonable for a sync exception to terminate the process; it's unreasonable for a promise rejection to do so. |
I'll digress a bit just to argue that it is usually up to the author of an application to determine whether unexpected errors do or do not terminate their process. Most applications will explicitly handle and then log or report unexpected errors. Web server processes return a 500 error as well as logging them and do so without crashing. Handling unexpected errors is a pretty standard part of any application. What I don't understand is why the JS standard would deem it desirable to silently swallow unexpected errors unless the author explicitly chose to define that behaviour. However I also get that's how it works right now and it isn't going to change anytime soon. Coming back to the discussion at hand, for testing specifically, I see value in handling both unhandled rejections and exceptions the same way. That is record them as a test failure, print the stack indicating where the error happened and then continue running the rest of the tests. |
To be honest, the reason why I use tape is that it doesn't make any assumptions in the way of handling certain use cases differently then others. @chrisnicola I think the point that @ljharb pointed out is that promises are built in a way that they don't create catastrophic crashes that sync operations can create. If is outlined here and in many other places that state the problem is an issue with the language implementation, not |
@gabrielcsapo I think we're getting a bit off topic here. The issue at hand is what is useful for the test function to do when unexpected errors happen. I'm not really here to discus language decisions of Javascript. I don't think you have to make any assumptions to decide to report the details of the error to the user here. |
It seems blue-tape currently supports tape-catch style execution out of the box now, solving the originally stated issue, at least. |
Personally I don't get this argument. What isn't a program and therefore should everything behave the same way? More practically, not exiting upon exception allows me to gather feedback about the rest of the program even though I made a mistake somewhere. I test (amongst other things) to help me catch mistakes I make. I also expect myself to make them and seek to optimize with that in mind. As for making Tape behave like EG. Mocha - I agree with @gabrielcsapo in that not making assumptions is a strength (or at least provide multiple levels of abstractions and the ability to opt-out). If one does want it to behave like Mocha, that is super-easy to achieve by one self without making changes to the library. We've done as shown below for my current project and it seems to work well so far. import tape from "tape";
const testConstructorDecorator = (testConstructor) => (description, testFn) => {
testConstructor(description, (t) => {
Promise.resolve(testFn(t)).then(t.end, t.end);
});
}
const test = Object.assign(testConstructorDecorator(tape), {
only: testConstructorDecorator(tape.only),
skip: testConstructorDecorator(tape.skip)
});
export { test }; It does a tad bit more than proposed in #441, like invoking |
If you don't want to import process.on('uncaughtException', (err) => {
console.log('\x1b[31m%s\x1b[0m', '✘ Fatality! Uncaught Exception within unit tests, error thrown:');
console.log(err);
console.log('not ok 1');
console.log('\x1b[31m%s\x1b[0m', 'Force-Exiting process ...');
process.exit(1);
}); Note: You should use the above to prevent a build from passing on a CI tool. Coupled with code coverage, you can rely on the fact that some tests will not fail silently. Likewise, you will be able to find the source of the failure soon (like a circular reference that someone added in the latest build). |
In v5, rejected promises returned from tests will fail the test. I think this can be closed. |
It's a bit unfortunate that one has to resort to use
tape-catch
to have all test throwing exception to fail (why isn't that the default?). Given that I am usingblue-tape
I can only seem to use either or.The text was updated successfully, but these errors were encountered: