Skip to content

Include anonymous functions in stacktraces #1508

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

Merged

Conversation

jugglinmike
Copy link
Contributor

This patch corrects Ava's reporting of stack traces which contain frames of anonymous functions. See pull request gh-1313 for the original problem description and steps to reproduce.

@neoeno originally submitted that patch in March of this year. The thread has been inactive for the 5 months since the initial review phase. This pull request is an attempt to implement that feedback and fix the bug.

Most notably, this involved implementing @novemberborn's recommendation:

My hunch is that we should extract the stack when serializing (filter out
unnecessary bits early), and beautify it in the mini and verbose reporters.
Though I'm not even sure what beautify-stack does that extract-stack
doesn't.

To fully accommodate this change, I've updated the tests for Ava's reporters to use the internal serialize-error module. This makes them somewhat more like integration tests, but this was already true to a certain extent--previously, some tests relied on the internal beautify-stack module.

We could update the tests to explicitly construct the errors as we expect them to result from the serialization process. Although that would tend to isolate the tests, it might also make them harder to maintain (as any change to the serialization format would have to be reflected in the tests themselves). For this initial draft, I opted for this factoring because the output of serialize-error seems like a pretty strong contract to depend on.

As part of the discussion of gh-1313, @sindresorhus created extract-stack, a new npm module. For reasons shared in the discussion that followed, that module is not suitable in its current form. Because of the similar-sounding functionality between modules (beatify-stack, extract-stack, clean-stack, and stack-utils), I have instead opted to rename extractStack to the slightly more precise extractFrames. Due to the re-factoring described above, I was also able to move the function definition into the beautify-stack module itself, which I believe further limits the ambiguity between module responsibilities. To shore things up a little more, I've included some documentation for beautify-stack to describe its intended behavior along with an example.

Copy link
Member

@novemberborn novemberborn left a comment

Choose a reason for hiding this comment

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

Wow, nice work @jugglinmike! This looks really good. Could you have a look at why tests are failing though?

if (typeof error.stack === 'string') {
retval.summary = error.stack.split('\n')[0];
} else {
retval.summary = typeof error === 'function' ?
Copy link
Member

Choose a reason for hiding this comment

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

What's the origin of this typeof error === 'function' check?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, this seemed a little hinky to me, too. I implemented that to satisfy an existing test which looks pretty intentional. It was introduced here: #206 . Maybe @jamestalmage can give us some perspective.

Copy link
Member

Choose a reason for hiding this comment

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

The code comment seems informative: https://github.com/avajs/ava/blob/15bd79402c55468a4b13aedceba503ee26e964a1/lib/serialize-value.js#L11:L12

This is no longer applicable I think. We catch and wrap all errors from tests, so it's only uncaught exceptions and unhandled rejections that are passed directly to serialize-error. And right now we don't even guard against throw null. Let's remove this edge-case and we can open a follow-up for handling non-Error exceptions / rejections.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The test demonstrates a case where a function value is thrown and not caught. Removing the test and this condition would prevent Ava from displaying the source of the function in such cases.

Granted, it seems extremely rare for a function value to be thrown in the first place. I just want to make sure we're on the same page before proceeding. Can you verify?

Copy link
Member

Choose a reason for hiding this comment

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

Yes let's get rid of this check.

The code needs to be more defensive here but we'll handle that in a follow-up issue.


const serializeError = require('../../lib/serialize-error');

module.exports = function (err, {type, file, stack} = {}) {
Copy link
Member

Choose a reason for hiding this comment

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

We're targeting Node.js 4 still, so this destructuring won't work unfortunately.

Perhaps just use Object.assign() without checking for values?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure thing

@jugglinmike jugglinmike mentioned this pull request Sep 16, 2017
@jugglinmike
Copy link
Contributor Author

Could you have a look at why tests are failing though?

Sure thing. This was a tricky issue, but I think I have it fixed over at #1524

@jugglinmike jugglinmike force-pushed the include-anonymous-functions-in-stacktraces branch from 9f75352 to 956cdc6 Compare October 1, 2017 22:27
jugglinmike and others added 2 commits October 1, 2017 18:30
Some functions are more anonymous than others. Node does a pretty
good job of naming functions like this:

```js
const fn = () => { throw "whoops!" } // <-- gets named `fn` in stacktraces
```

But in some cases a function genuinely has no name, e.g.

```js
const fn = () => () => { throw "whoops!" } // <-- inner function has no name!
```

This means we get stacktraces like this:

```
whoops!
  path/to/file.js:1:1
  Test.t (test.js:1:1)
```

Before this commit, the regex in `extract-stack.js` was not anticipating
stacktraces of this form, and would simply filter out the anonymous functions.
This made my life a little more awkward, as I could only see the failing test
line and the error, not the code that was actually erroring.

This commit adds another regex to `extract-stack.js` which matches on anonymous
function stacktrace lines too.
@jugglinmike jugglinmike force-pushed the include-anonymous-functions-in-stacktraces branch from 956cdc6 to 51cf610 Compare October 1, 2017 22:30
Incorporate review feedback for the prior commit. Additionally, update
test fixture file to satisfy the project's linting rules.
When the value of an uncaught exception is a function, AVA previously
displayed the source of that function in the error report. During the
review process for an unrelated change, this condition was found to be
too rare to justify the maintenance overhead. Do not attempt to display
the source of function values in these cases.
@jugglinmike jugglinmike force-pushed the include-anonymous-functions-in-stacktraces branch from 51cf610 to 9112346 Compare October 1, 2017 22:39
@jugglinmike
Copy link
Contributor Author

Okay, I've pushed up another commit to remove that check. Since it reflects an intentional but subjective change, I'd prefer if we could include it in a standalone commit. That will help future contributors understand that the change in behavior was not a regression.

There have been some recent commits to master that cause merge conflicts in this changeset. I've taken the liberty to rebase and force-push the result. In the interest of transparency, I've made the original version of this branch available here:

master...bocoup:include-anonymous-functions-in-stacktraces-orig-2

@novemberborn
Copy link
Member

Hi @jugglinmike, I haven't forgotten about you 😄 Glad to see the tests are passing, I'm hoping to merge this later this week.

@jugglinmike
Copy link
Contributor Author

No worries; I know how things get with maintaining big FOSS projects like this. Take your time, and thanks for keeping me in the loop!

Copy link
Member

@sindresorhus sindresorhus left a comment

Choose a reason for hiding this comment

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

Thanks for working on this @jugglinmike. Looks good to me.

@novemberborn novemberborn merged commit c72f4f2 into avajs:master Oct 23, 2017
@novemberborn
Copy link
Member

Thank you @jugglinmike! There should be a new release out within the next few days.

@jugglinmike
Copy link
Contributor Author

Great! And thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants