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

Fix custom async matcher stack traces #7652

Merged
merged 10 commits into from
Feb 13, 2019
Merged

Conversation

theotherdon
Copy link
Contributor

@theotherdon theotherdon commented Jan 19, 2019

Summary

Fixes a bug causing custom async matcher stack traces to be lost. The issue was caused by the JestAssertionError being created after the promise for the async matcher resolved - by which point the stack containing the correct stack trace (pointing to the line where matcher was called) has been discarded. The issue was fixed by passing an error that is initialized before the promise resolves.

Test plan

I added an E2e test with a snapshot that ensures that the code fence and stack trace are displayed properly.

Before this fix, running yarn jest for a test that had a failing assertion from async matcher produced output like this:

screen shot 2019-01-19 at 9 55 22 am

After the fix, the output looks like this:

screen shot 2019-01-19 at 9 57 02 am

Fixes #7066

Don Schrimsher added 2 commits January 19, 2019 09:49
Fixes a bug causing custom async matcher stack traces to be lost. The
issue was caused by the JestAssertionError being created after the
promise for the async matcher resolved - by which point the stack
containing the correct stack trace (pointing to the line where matcher
was called) has been discarded. The issue was fixed by passing an error
that is initialized before the promise resolves.
Adds a test to verify the async custom matcher stack trace fix.
@theotherdon
Copy link
Contributor Author

theotherdon commented Jan 19, 2019

@SimenB I'm thinking that I'm going to need to update the removeInteralStackEntries function to strip out the async/generator lines. What do you think? You can see the full test failure on CircleCI.

screen shot 2019-01-19 at 10 20 10 am

@SimenB
Copy link
Member

SimenB commented Jan 19, 2019

Thanks!

I'm thinking that I'm going to need to update the removeInteralStackEntries function to strip out the async/generator lines.

I think an easier solution is to just use Promise.resolve instead of async 🙂

Copy link
Member

@SimenB SimenB left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great! Left an inline comment for a super minor thing, just fixing that (and making sure CI pass) and we can merge this 🙂

e2e/__tests__/customMatcherStackTrace.test.js Outdated Show resolved Hide resolved
@theotherdon
Copy link
Contributor Author

theotherdon commented Jan 22, 2019

@SimenB Thanks for the prompt feedback! I implemented your suggestions for the e2e test. However, I'm not sure if I quite understand what you mean in regards to using Promise.resolve. Do you mind elaborating? Do you mean to use Promise.resolve in the test so that we don't have to worry about the async lines in the stack trace? If so, isn't the point of this spec to verify what the stack trace looks like when using async/await? Perhaps I'm missing something? 🙂

I think an easier solution is to just use Promise.resolve instead of async 🙂

Uses the new wrap utility in e2e snapshot for custom
matcher stack traces.
@theotherdon
Copy link
Contributor Author

@SimenB When you have a chance, can you please take a look at my previous comment?

@SimenB
Copy link
Member

SimenB commented Feb 6, 2019

Sorry!

If so, isn't the point of this spec to verify what the stack trace looks like when using async/await?

The point is to verify that the stack is correct when a matcher returns a Promise. So async-await or return Promise is the same, with the latter leaving less junk in the stack (since it doesn't have to be transpiled)

Copy link
Member

@SimenB SimenB left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@SimenB SimenB merged commit a4a04b2 into jestjs:master Feb 13, 2019
@theotherdon
Copy link
Contributor Author

I was just about to message you to let you know that I updated the PR, but I see that you've already merged it. Thanks for the help Simen!


return asyncResult
.then(aResult => processResult(aResult))
.then(aResult => processResult(aResult, asyncError))
Copy link

@erikpuk erikpuk Oct 21, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm having some issues with this code, 8 months on. I'm almost certainly not understanding correctly, but it seems to me....

  1. asyncError is always truthy here. Even though this is the normal codepath that any async matcher will follow

  2. In processResult we just test the truthiness of asyncError to decide whether an error was thrown.

  3. That means we end up throwing a normal { message, pass } assertion result, which is not what is supposed to happen.

But.... what that suggests to me is that async custom would just be fully broken all the time, which.... I mean, I assume they're tested? So I assume I'm missing something?

But for me, they are in fact broken all the time.

Copy link
Contributor Author

@theotherdon theotherdon Oct 25, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What issue are you having? The same one that I fixed in the description or a different one? It's been a while since I worked on this, so I don't remember what we're supposed to be throwing for point 3. Can you remind me what we're supposed to be throwing instead?

asyncError will indeed always be defined if we're using async matchers. However, the only time we do something with the asyncError is when the assertion fails because of this conditional here.

A little bit of context that I'm not sure you're aware of, but I think might be helpful. We always need to pass in an error to preserve the stack trace when we're working with async code. If we don't, then the stack trace is lost when we hit the next tick in the event loop. That's why the async error is always being initialized and passed in. It's just not used unless we hit an error state. Unless I'm misunderstanding what you're saying?

@github-actions
Copy link

This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 11, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Incorrect custom async matcher error message
4 participants