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

Better StackTraces with filtering and mapping lines #2119

Closed
donaldpipowitch opened this issue May 6, 2016 · 15 comments · Fixed by karronoli/redpen#10
Closed

Better StackTraces with filtering and mapping lines #2119

donaldpipowitch opened this issue May 6, 2016 · 15 comments · Fixed by karronoli/redpen#10

Comments

@donaldpipowitch
Copy link

Let us say we have the following stack trace:

    Expected 'bar' to be 'bar1'
    assert@/Users/foo/Workspace/ws/examples/browser-js/test-dist/index.js:11066:27 <- ~/expect/lib/assert.js:29:0
    toBe@/Users/foo/Workspace/ws/examples/browser-js/test-dist/index.js:8037:28 <- ~/expect/lib/Expectation.js:86:0
    /Users/foo/Workspace/ws/examples/browser-js/test-dist/index.js:7909:75 <- test/unit.ts:31:48

First I'd like to filter stack traces from ~/except. Second I want to only see the original source (the part after <-).

I could actually hack the lib/reporter.js at line 92 like this to achieve this result:

    // filter lines
    msg = msg.split('\n').filter(line => !line.match(/~\/expect\/lib/)).join('\n');
    // map lines
    msg = msg.split('\n').map(line => line.match(/\/(.*)<-/) ? line.replace(/\/(.*)<-/, '') : line).join('\n');

The result will be:

    Expected 'bar' to be 'bar1'
     test/unit.ts:31:48

Can I achieve the same behaviour with existing APIs? Or can I add this behaviour with a pull request? I could imagine to add two new fields to the karma config (e.g. mapStackTraceLine/filterStackTraceLine) so everyone can set custom filter/map rules if needed.

@levithomason
Copy link
Contributor

levithomason commented Aug 13, 2016

I need these exact features. I don't see where the APIs would allow it, short of writing a custom reporter. I think a simple solution would be to expose a formatError function in the config. I was able to do this in reporter.js like so:

-var createErrorFormatter = function (basePath, emitter, SourceMapConsumer) {
+var createErrorFormatter = function (config, emitter, SourceMapConsumer) {
+  var basePath = config.basePath

...

    // indent every line
    if (indentation) {
      msg = indentation + msg.replace(/\n/g, '\n' + indentation)
    }

+    if (config.formatError) {
+      msg = config.formatError(msg)
+    }

    return msg + '\n'
  }
}

var createReporters = function (names, config, emitter, injector) {
-  var errorFormatter = createErrorFormatter(config.basePath, emitter, SourceMapConsumer)
+  var errorFormatter = createErrorFormatter(config, emitter, SourceMapConsumer)
  var reporters = []

Then in karma.config.js:

config.set({
  formatError(msg) {
    return msg
      .split('\n')
      .reduce((list, line) => {
        // filter out node_modules
        if (new RegExp('/~/').test(line)) return list

        // remove left hand side of "compiled <- source" string
        const indent = line.slice(0, line.indexOf('/'))
        const sliceEnd = ' <- webpack:///'
        const original = line.slice(line.indexOf(sliceEnd) + sliceEnd.length)
        return list.concat(indent + original)
      }, [])
      .join('\n')
  },
})

Turning this stack:

      AssertionError@/Users/levithomason/src/stardust/test/tests.bundle.js:296
:25 <- webpack:///~/assertion-error/index.js:74:0
      assert@/Users/levithomason/src/stardust/test/tests.bundle.js:4397:32 <- 
webpack:///~/chai/lib/chai/assertion.js:107:0
      /Users/levithomason/src/stardust/test/tests.bundle.js:8978:20 <- webpack
:///~/chai-enzyme/build/assertions/descendants.js:22:0
      descendants@/Users/levithomason/src/stardust/test/tests.bundle.js:8983:8
<- webpack:///~/chai-enzyme/build/assertions/descendants.js:27:0
      /Users/levithomason/src/stardust/test/tests.bundle.js:9525:24 <- webpack
:///~/chai-enzyme/build/ChaiWrapper.js:193:0
      /Users/levithomason/src/stardust/test/tests.bundle.js:3984:31 <- webpack
:///~/chai/lib/chai/utils/addMethod.js:41:0
      /Users/levithomason/src/stardust/test/tests.bundle.js:72950:31 <- webpac
k:///~/dirty-chai/lib/dirty-chai.js:114:0
      /Users/levithomason/src/stardust/test/tests.bundle.js:4103:39 <- webpack
:///~/chai/lib/chai/utils/overwriteMethod.js:49:0
      /Users/levithomason/src/stardust/test/tests.bundle.js:184887:116 <- webp
ack:///test/specs/views/Feed/Feed-test.js:20:0

Into this:

      test/specs/views/Feed/Feed-test.js:20:0

It seems like a simple enough solution to allow users to parse the output themselves as a workaround. @vojtajina @dignifiedquire any input on whether or not a PR would be accepted for this? It would be a huge productivity boost to be able to see concise output and not have to scroll to read the errors.

@dignifiedquire
Copy link
Member

I like this idea. I can see that this can be very useful to customise these easily.
If you do a PR, please make the default formatting the value that is set as default in the config rather than this manual check in the formatter.

@levithomason
Copy link
Contributor

levithomason commented Aug 15, 2016

Great, I'll put up a PR in a bit. Can you clarify what you mean when you say:

make the default formatting the value that is set as default in the config

?

dignifiedquire pushed a commit that referenced this issue Sep 7, 2016
Allows a `formatError` config function. The function receives the entire error message as its only argument. It returns the new error message.

Fixes #2119.
@donaldpipowitch
Copy link
Author

Thank you all, this is great.

@donaldpipowitch
Copy link
Author

It looks like msg in formatError(msg) isn't the whole error stack, but formatError is called for every line in the stack. Is this normal? E.g. I don't need to call msg.split('\n'), because it is already splitted...?

@levithomason
Copy link
Contributor

Hm, not my experience. You can see our implementation with before/after results here: https://github.com/TechnologyAdvice/stardust/pull/490

@levithomason
Copy link
Contributor

levithomason commented Sep 20, 2016

I should clarify, we do only get the error after the failure message and PhantomJS/OS version line. But it is a single string including the stack and line breaks.

@donaldpipowitch
Copy link
Author

Very strange. I wonder why my setup behaves differently. Because I get every single line for formatError and not the whole output I generate several empty new lines on my terminal when I try to filter out node modules :/

I use mocha and except. Maybe someone else has the same problem...

My output:

    ✖ dummy test 2
      PhantomJS 2.1.1 (Mac OS X 0.0.0)
    Expected 1 to equal 2

      + expected - actual

      -1
      +2

//empty line, was: assert@webpack:///~/expect/lib/assert.js:29:0 <- dist-tests/index.js:4741:27
//empty line, was: toEqual@webpack:///~/expect/lib/Expectation.js:81:0 <- dist-tests/index.js:7172:30
    ./tests/unit.tsx:27:2 // was: webpack:///tests/unit.tsx:22:31 <- dist-tests/index.js:15070:75

The two empty lines shouldn't be there...

Also very strange: my pattern is "source <- compiled" not "compiled <- source"?

@levithomason
Copy link
Contributor

Perhaps a difference in versions? We're on the karma, webpack, mocha stack.

Regarding the source / compiled, I do know the choice of devtool will affect the stack trace. It could be a different webpack devtool option will give different results. I opted for the slowest but most verbose and accurate source-map option. It gave me the best msg, method names, and ln/col numbers to work with.

Good luck!

@levithomason
Copy link
Contributor

We also ran into the issue with only receiving each line to the formatError method sometime after my last comment. I hadn't had time to get back here.

The big problem is, you then can't filter out individual lines as the formatter always returns at the minimum a line break: msg + '\n' where msg is the return of your formatError function.

If we simply return the formatError's return value, then we can at least return '' to filter out lines. I'll make another PR for this and hope it makes it in.

@levithomason
Copy link
Contributor

Note, fix posted here: #2627

@donaldpipowitch
Copy link
Author

Thank you!

@IAMtheIAM
Copy link

IAMtheIAM commented Sep 20, 2017

Can we get some example configs? I copied your code and it did nothing to the stack trace

      formatError(msg) {
         return msg
            .split('\n')
            .reduce((list, line) => {
               // filter out node_modules
               if (new RegExp('/~/').test(line)) return list

               // remove left hand side of "compiled <- source" string
               const indent = line.slice(0, line.indexOf('/'))
               const sliceEnd = ' <- webpack:///'
               const original = line.slice(line.indexOf(sliceEnd) + sliceEnd.length)
               return list.concat(indent + original)
            }, [])
            .join('\n')
      },

User should be given simple options for this, so we can get back to writing tests

@levithomason
Copy link
Contributor

See my response to your question here, rstacruz/mocha-clean#5 (comment).

The usage changed slightly due to how the stack is passed. Each line is passed one at a time, rather than the whole stack at once.

@IAMtheIAM
Copy link

IAMtheIAM commented Sep 21, 2017

Thanks!

So I used your example:

      formatError(msg) {

         // Uncommment this to get rid of the entire stack trace, but then you don't know what happened
         // return ''

         // filter out empty lines and node_modules
         if (!msg.trim() || /~/.test(msg)) return ''

         // indent the error beneath the it() message
         let newLine = `  ${msg}`

         if (newLine.includes('webpack:///')) {
            // remove webpack:///
            newLine = newLine.replace('webpack:///', '')

            // remove bundle location, showing only the source location
            newLine = newLine.slice(0, newLine.indexOf(' <- '))
         }

         return `${newLine}\n`
      },

It did clean it up quite a bit, But I'm still getting some weird mode_modules stuff.

Error: No provider for Http! in config/karma.entry.js (line 3185)
      _throwOrNull@node_modules/@angular/core/@angular/core.es5.js:2649:0
      _getByKeyDefault@node_modules/@angular/core/@angular/core.es5.js:2688:0
      _getByKey@node_modules/@angular/core/@angular/core.es5.js:2620:0
      get@node_modules/@angular/core/@angular/core.es5.js:2489:0
      resolveNgModuleDep@node_modules/@angular/core/@angular/core.es5.js:9481:0
      _createClass@node_modules/@angular/core/@angular/core.es5.js:9524:0
      _createProviderInstance$1@node_modules/@angular/core/@angular/core.es5.js:9492:0
      resolveNgModuleDep@node_modules/@angular/core/@angular/core.es5.js:9477:0
      _createClass@node_modules/@angular/core/@angular/core.es5.js:9532:0
      _createProviderInstance$1@node_modules/@angular/core/@angular/core.es5.js:9492:0
      resolveNgModuleDep@node_modules/@angular/core/@angular/core.es5.js:9477:0
      get@node_modules/@angular/core/@angular/core.es5.js:10569:0
      resolveDep@node_modules/@angular/core/@angular/core.es5.js:11072:0
      createClass@node_modules/@angular/core/@angular/core.es5.js:10928:0
      createDirectiveInstance@node_modules/@angular/core/@angular/core.es5.js:10756:21
      createViewNodes@node_modules/@angular/core/@angular/core.es5.js:12197:33
      createRootView@node_modules/@angular/core/@angular/core.es5.js:12092:0
      callWithDebugContext@node_modules/@angular/core/@angular/core.es5.js:13475:25
      debugCreateRootView@node_modules/@angular/core/@angular/core.es5.js:12792:0
      create@node_modules/@angular/core/@angular/core.es5.js:9864:25
      initComponent@node_modules/@angular/core/@angular/core/testing.es5.js:889:0
      invoke@node_modules/zone.js/dist/zone.js:392:0
      onInvoke@node_modules/zone.js/dist/proxy.js:79:0
      invoke@node_modules/zone.js/dist/zone.js:391:0
      onInvoke@node_modules/@angular/core/@angular/core.es5.js:3890:0
      invoke@node_modules/zone.js/dist/zone.js:391:0
      run@node_modules/zone.js/dist/zone.js:142:0
      run@node_modules/@angular/core/@angular/core.es5.js:3821:42
      createComponent@node_modules/@angular/core/@angular/core/testing.es5.js:892:0
      createComponent@node_modules/@angular/core/@angular/core/testing.es5.js:656:0
      src-test/app-components/app/app.spec.ts:52:40
      invoke@node_modules/zone.js/dist/zone.js:392:0
      onInvoke@node_modules/zone.js/dist/proxy.js:79:0
      invoke@node_modules/zone.js/dist/zone.js:391:0
      run@node_modules/zone.js/dist/zone.js:142:0
      node_modules/zone.js/dist/jasmine-patch.js:104:0
      execute@node_modules/zone.js/dist/jasmine-patch.js:132:0
      invokeTask@node_modules/zone.js/dist/zone.js:425:0
      runTask@node_modules/zone.js/dist/zone.js:192:0
      drainMicroTaskQueue@node_modules/zone.js/dist/zone.js:602:0
      run@node_modules/core-js/modules/es6.promise.js:66:0
      node_modules/core-js/modules/es6.promise.js:79:0
      flush@node_modules/core-js/modules/_microtask.js:18:0

So I modified it a little and it works!

      formatError(msg) {

         // Uncommment this to get rid of the entire stack trace, but then you don't know what happened
         // return ''

         // filter out empty lines and node_modules
         if (!msg.trim() || /~/.test(msg) || /node_modules/.test(msg)) return '' // < --- modified this line

         // indent the error beneath the it() message
         let newLine = `  ${msg}`

         if (newLine.includes('webpack:///')) {
            // remove webpack:///
            newLine = newLine.replace('webpack:///', '')

            // remove bundle location, showing only the source location
            newLine = newLine.slice(0, newLine.indexOf(' <- '))
         }

         return `${newLine}\n`
      },
  Error: No provider for Http! in config/karma.entry.js (line 3185)

Thanks a ton @levithomason!

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

Successfully merging a pull request may close this issue.

4 participants