// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. import 'package:path/path.dart' as path; import 'package:stack_trace/stack_trace.dart'; import 'package:test/test.dart'; void main() { // This just shouldn't crash. test('a native stack trace is parseable', Trace.current); group('.parse', () { test('.parse parses a V8 stack trace with eval statment correctly', () { var trace = Trace.parse(r'''Error at Object.eval (eval at Foo (main.dart.js:588), :3:47)'''); expect(trace.frames[0].uri, Uri.parse('main.dart.js')); expect(trace.frames[0].member, equals('Object.eval')); expect(trace.frames[0].line, equals(588)); expect(trace.frames[0].column, isNull); }); test('.parse parses a VM stack trace correctly', () { var trace = Trace.parse( '#0 Foo._bar (file:///home/nweiz/code/stuff.dart:42:21)\n' '#1 zip..zap (dart:async/future.dart:0:2)\n' '#2 zip..zap (https://pub.dev/thing.dart:1:100)', ); expect(trace.frames[0].uri, equals(Uri.parse('file:///home/nweiz/code/stuff.dart'))); expect(trace.frames[1].uri, equals(Uri.parse('dart:async/future.dart'))); expect( trace.frames[2].uri, equals(Uri.parse('https://pub.dev/thing.dart'))); }); test('parses a V8 stack trace correctly', () { var trace = Trace.parse('Error\n' ' at Foo._bar (https://example.com/stuff.js:42:21)\n' ' at https://example.com/stuff.js:0:2\n' ' at zip..zap ' '(https://pub.dev/thing.js:1:100)'); expect(trace.frames[0].uri, equals(Uri.parse('https://example.com/stuff.js'))); expect(trace.frames[1].uri, equals(Uri.parse('https://example.com/stuff.js'))); expect( trace.frames[2].uri, equals(Uri.parse('https://pub.dev/thing.js'))); trace = Trace.parse('Exception: foo\n' ' at Foo._bar (https://example.com/stuff.js:42:21)\n' ' at https://example.com/stuff.js:0:2\n' ' at zip..zap ' '(https://pub.dev/thing.js:1:100)'); expect(trace.frames[0].uri, equals(Uri.parse('https://example.com/stuff.js'))); expect(trace.frames[1].uri, equals(Uri.parse('https://example.com/stuff.js'))); expect( trace.frames[2].uri, equals(Uri.parse('https://pub.dev/thing.js'))); trace = Trace.parse('Exception: foo\n' ' bar\n' ' at Foo._bar (https://example.com/stuff.js:42:21)\n' ' at https://example.com/stuff.js:0:2\n' ' at zip..zap ' '(https://pub.dev/thing.js:1:100)'); expect(trace.frames[0].uri, equals(Uri.parse('https://example.com/stuff.js'))); expect(trace.frames[1].uri, equals(Uri.parse('https://example.com/stuff.js'))); expect( trace.frames[2].uri, equals(Uri.parse('https://pub.dev/thing.js'))); trace = Trace.parse('Exception: foo\n' ' bar\n' ' at Foo._bar (https://example.com/stuff.js:42:21)\n' ' at https://example.com/stuff.js:0:2\n' ' at (anonymous function).zip.zap ' '(https://pub.dev/thing.js:1:100)'); expect(trace.frames[0].uri, equals(Uri.parse('https://example.com/stuff.js'))); expect(trace.frames[1].uri, equals(Uri.parse('https://example.com/stuff.js'))); expect(trace.frames[1].member, equals('')); expect( trace.frames[2].uri, equals(Uri.parse('https://pub.dev/thing.js'))); expect(trace.frames[2].member, equals('.zip.zap')); }); // JavaScriptCore traces are just like V8, except that it doesn't have a // header and it starts with a tab rather than spaces. test('parses a JavaScriptCore stack trace correctly', () { var trace = Trace.parse('\tat Foo._bar (https://example.com/stuff.js:42:21)\n' '\tat https://example.com/stuff.js:0:2\n' '\tat zip..zap ' '(https://pub.dev/thing.js:1:100)'); expect(trace.frames[0].uri, equals(Uri.parse('https://example.com/stuff.js'))); expect(trace.frames[1].uri, equals(Uri.parse('https://example.com/stuff.js'))); expect( trace.frames[2].uri, equals(Uri.parse('https://pub.dev/thing.js'))); trace = Trace.parse('\tat Foo._bar (https://example.com/stuff.js:42:21)\n' '\tat \n' '\tat zip..zap ' '(https://pub.dev/thing.js:1:100)'); expect(trace.frames[0].uri, equals(Uri.parse('https://example.com/stuff.js'))); expect( trace.frames[1].uri, equals(Uri.parse('https://pub.dev/thing.js'))); }); test('parses a Firefox/Safari stack trace correctly', () { var trace = Trace.parse('Foo._bar@https://example.com/stuff.js:42\n' 'zip/<@https://example.com/stuff.js:0\n' 'zip.zap(12, "@)()/<")@https://pub.dev/thing.js:1'); expect(trace.frames[0].uri, equals(Uri.parse('https://example.com/stuff.js'))); expect(trace.frames[1].uri, equals(Uri.parse('https://example.com/stuff.js'))); expect( trace.frames[2].uri, equals(Uri.parse('https://pub.dev/thing.js'))); trace = Trace.parse('zip/<@https://example.com/stuff.js:0\n' 'Foo._bar@https://example.com/stuff.js:42\n' 'zip.zap(12, "@)()/<")@https://pub.dev/thing.js:1'); expect(trace.frames[0].uri, equals(Uri.parse('https://example.com/stuff.js'))); expect(trace.frames[1].uri, equals(Uri.parse('https://example.com/stuff.js'))); expect( trace.frames[2].uri, equals(Uri.parse('https://pub.dev/thing.js'))); trace = Trace.parse('zip.zap(12, "@)()/<")@https://pub.dev/thing.js:1\n' 'zip/<@https://example.com/stuff.js:0\n' 'Foo._bar@https://example.com/stuff.js:42'); expect( trace.frames[0].uri, equals(Uri.parse('https://pub.dev/thing.js'))); expect(trace.frames[1].uri, equals(Uri.parse('https://example.com/stuff.js'))); expect(trace.frames[2].uri, equals(Uri.parse('https://example.com/stuff.js'))); }); test('parses a Firefox/Safari stack trace containing native code correctly', () { var trace = Trace.parse('Foo._bar@https://example.com/stuff.js:42\n' 'zip/<@https://example.com/stuff.js:0\n' 'zip.zap(12, "@)()/<")@https://pub.dev/thing.js:1\n' '[native code]'); expect(trace.frames[0].uri, equals(Uri.parse('https://example.com/stuff.js'))); expect(trace.frames[1].uri, equals(Uri.parse('https://example.com/stuff.js'))); expect( trace.frames[2].uri, equals(Uri.parse('https://pub.dev/thing.js'))); expect(trace.frames.length, equals(3)); }); test('parses a Firefox/Safari stack trace without a method name correctly', () { var trace = Trace.parse('https://example.com/stuff.js:42\n' 'zip/<@https://example.com/stuff.js:0\n' 'zip.zap(12, "@)()/<")@https://pub.dev/thing.js:1'); expect(trace.frames[0].uri, equals(Uri.parse('https://example.com/stuff.js'))); expect(trace.frames[0].member, equals('')); expect(trace.frames[1].uri, equals(Uri.parse('https://example.com/stuff.js'))); expect( trace.frames[2].uri, equals(Uri.parse('https://pub.dev/thing.js'))); }); test('parses a Firefox/Safari stack trace with an empty line correctly', () { var trace = Trace.parse('Foo._bar@https://example.com/stuff.js:42\n' '\n' 'zip/<@https://example.com/stuff.js:0\n' 'zip.zap(12, "@)()/<")@https://pub.dev/thing.js:1'); expect(trace.frames[0].uri, equals(Uri.parse('https://example.com/stuff.js'))); expect(trace.frames[1].uri, equals(Uri.parse('https://example.com/stuff.js'))); expect( trace.frames[2].uri, equals(Uri.parse('https://pub.dev/thing.js'))); }); test('parses a Firefox/Safari stack trace with a column number correctly', () { var trace = Trace.parse('Foo._bar@https://example.com/stuff.js:42:2\n' 'zip/<@https://example.com/stuff.js:0\n' 'zip.zap(12, "@)()/<")@https://pub.dev/thing.js:1'); expect(trace.frames[0].uri, equals(Uri.parse('https://example.com/stuff.js'))); expect(trace.frames[0].line, equals(42)); expect(trace.frames[0].column, equals(2)); expect(trace.frames[1].uri, equals(Uri.parse('https://example.com/stuff.js'))); expect( trace.frames[2].uri, equals(Uri.parse('https://pub.dev/thing.js'))); }); test('parses a package:stack_trace stack trace correctly', () { var trace = Trace.parse('https://dart.dev/foo/bar.dart 10:11 Foo..bar\n' 'https://dart.dev/foo/baz.dart Foo..bar'); expect(trace.frames[0].uri, equals(Uri.parse('https://dart.dev/foo/bar.dart'))); expect(trace.frames[1].uri, equals(Uri.parse('https://dart.dev/foo/baz.dart'))); }); test('parses a package:stack_trace stack chain correctly', () { var trace = Trace.parse('https://dart.dev/foo/bar.dart 10:11 Foo..bar\n' 'https://dart.dev/foo/baz.dart Foo..bar\n' '===== asynchronous gap ===========================\n' 'https://dart.dev/foo/bang.dart 10:11 Foo..bar\n' 'https://dart.dev/foo/quux.dart Foo..bar'); expect(trace.frames[0].uri, equals(Uri.parse('https://dart.dev/foo/bar.dart'))); expect(trace.frames[1].uri, equals(Uri.parse('https://dart.dev/foo/baz.dart'))); expect(trace.frames[2].uri, equals(Uri.parse('https://dart.dev/foo/bang.dart'))); expect(trace.frames[3].uri, equals(Uri.parse('https://dart.dev/foo/quux.dart'))); }); test('parses a package:stack_trace stack chain with end gap correctly', () { var trace = Trace.parse( 'https://dart.dev/foo/bar.dart 10:11 Foo..bar\n' 'https://dart.dev/foo/baz.dart Foo..bar\n' 'https://dart.dev/foo/bang.dart 10:11 Foo..bar\n' 'https://dart.dev/foo/quux.dart Foo..bar===== asynchronous gap ===========================\n', ); expect(trace.frames.length, 4); expect(trace.frames[0].uri, equals(Uri.parse('https://dart.dev/foo/bar.dart'))); expect(trace.frames[1].uri, equals(Uri.parse('https://dart.dev/foo/baz.dart'))); expect(trace.frames[2].uri, equals(Uri.parse('https://dart.dev/foo/bang.dart'))); expect(trace.frames[3].uri, equals(Uri.parse('https://dart.dev/foo/quux.dart'))); }); test('parses a real package:stack_trace stack trace correctly', () { var traceString = Trace.current().toString(); expect(Trace.parse(traceString).toString(), equals(traceString)); }); test('parses an empty string correctly', () { var trace = Trace.parse(''); expect(trace.frames, isEmpty); expect(trace.toString(), equals('')); }); test('parses trace with async gap correctly', () { var trace = Trace.parse('#0 bop (file:///pull.dart:42:23)\n' '\n' '#1 twist (dart:the/future.dart:0:2)\n' '#2 main (dart:my/file.dart:4:6)\n'); expect(trace.frames.length, 3); expect(trace.frames[0].uri, equals(Uri.parse('file:///pull.dart'))); expect(trace.frames[1].uri, equals(Uri.parse('dart:the/future.dart'))); expect(trace.frames[2].uri, equals(Uri.parse('dart:my/file.dart'))); }); test('parses trace with async gap at end correctly', () { var trace = Trace.parse('#0 bop (file:///pull.dart:42:23)\n' '#1 twist (dart:the/future.dart:0:2)\n' '\n'); expect(trace.frames.length, 2); expect(trace.frames[0].uri, equals(Uri.parse('file:///pull.dart'))); expect(trace.frames[1].uri, equals(Uri.parse('dart:the/future.dart'))); }); test('parses a V8 stack frace with Wasm frames correctly', () { var trace = Trace.parse( '\tat Error._throwWithCurrentStackTrace (wasm://wasm/0006d892:wasm-function[119]:0xbaf8)\n' '\tat main (wasm://wasm/0006d892:wasm-function[792]:0x14378)\n' '\tat main tear-off trampoline (wasm://wasm/0006d892:wasm-function[794]:0x14387)\n' '\tat _invokeMain (wasm://wasm/0006d892:wasm-function[70]:0xa56c)\n' '\tat InstantiatedApp.invokeMain (/home/user/test.mjs:361:37)\n' '\tat main (/home/user/run_wasm.js:416:21)\n' '\tat async action (/home/user/run_wasm.js:353:38)\n' '\tat async eventLoop (/home/user/run_wasm.js:329:9)'); expect(trace.frames.length, 8); for (final frame in trace.frames) { expect(frame is UnparsedFrame, false); } expect(trace.frames[0].uri, Uri.parse('wasm://wasm/0006d892')); expect(trace.frames[0].line, 1); expect(trace.frames[0].column, 0xbaf8 + 1); expect(trace.frames[0].member, 'Error._throwWithCurrentStackTrace'); expect(trace.frames[4].uri, Uri.parse('file:///home/user/test.mjs')); expect(trace.frames[4].line, 361); expect(trace.frames[4].column, 37); expect(trace.frames[4].member, 'InstantiatedApp.invokeMain'); expect(trace.frames[5].uri, Uri.parse('file:///home/user/run_wasm.js')); expect(trace.frames[5].line, 416); expect(trace.frames[5].column, 21); expect(trace.frames[5].member, 'main'); }); test('parses Firefox stack frace with Wasm frames correctly', () { var trace = Trace.parse( 'Error._throwWithCurrentStackTrace@http://localhost:8080/test.wasm:wasm-function[119]:0xbaf8\n' 'main@http://localhost:8080/test.wasm:wasm-function[792]:0x14378\n' 'main tear-off trampoline@http://localhost:8080/test.wasm:wasm-function[794]:0x14387\n' '_invokeMain@http://localhost:8080/test.wasm:wasm-function[70]:0xa56c\n' 'invoke@http://localhost:8080/test.mjs:48:26'); expect(trace.frames.length, 5); for (final frame in trace.frames) { expect(frame is UnparsedFrame, false); } expect(trace.frames[0].uri, Uri.parse('http://localhost:8080/test.wasm')); expect(trace.frames[0].line, 1); expect(trace.frames[0].column, 0xbaf8 + 1); expect(trace.frames[0].member, 'Error._throwWithCurrentStackTrace'); expect(trace.frames[4].uri, Uri.parse('http://localhost:8080/test.mjs')); expect(trace.frames[4].line, 48); expect(trace.frames[4].column, 26); expect(trace.frames[4].member, 'invoke'); }); test('parses JSShell stack frace with Wasm frames correctly', () { var trace = Trace.parse( 'Error._throwWithCurrentStackTrace@/home/user/test.mjs line 29 > WebAssembly.compile:wasm-function[119]:0xbaf8\n' 'main@/home/user/test.mjs line 29 > WebAssembly.compile:wasm-function[792]:0x14378\n' 'main tear-off trampoline@/home/user/test.mjs line 29 > WebAssembly.compile:wasm-function[794]:0x14387\n' '_invokeMain@/home/user/test.mjs line 29 > WebAssembly.compile:wasm-function[70]:0xa56c\n' 'invokeMain@/home/user/test.mjs:361:37\n' 'main@/home/user/run_wasm.js:416:21\n' 'async*action@/home/user/run_wasm.js:353:44\n' 'eventLoop@/home/user/run_wasm.js:329:15\n' 'self.dartMainRunner@/home/user/run_wasm.js:354:14\n' '@/home/user/run_wasm.js:419:15'); expect(trace.frames.length, 10); for (final frame in trace.frames) { expect(frame is UnparsedFrame, false); } expect(trace.frames[0].uri, Uri.parse('file:///home/user/test.mjs')); expect(trace.frames[0].line, 1); expect(trace.frames[0].column, 0xbaf8 + 1); expect(trace.frames[0].member, 'Error._throwWithCurrentStackTrace'); expect(trace.frames[4].uri, Uri.parse('file:///home/user/test.mjs')); expect(trace.frames[4].line, 361); expect(trace.frames[4].column, 37); expect(trace.frames[4].member, 'invokeMain'); expect(trace.frames[9].uri, Uri.parse('file:///home/user/run_wasm.js')); expect(trace.frames[9].line, 419); expect(trace.frames[9].column, 15); expect(trace.frames[9].member, ''); }); test('parses Safari stack frace with Wasm frames correctly', () { var trace = Trace.parse( '.wasm-function[Error._throwWithCurrentStackTrace]@[wasm code]\n' '.wasm-function[main]@[wasm code]\n' '.wasm-function[main tear-off trampoline]@[wasm code]\n' '.wasm-function[_invokeMain]@[wasm code]\n' 'invokeMain@/home/user/test.mjs:361:48\n' '@/home/user/run_wasm.js:416:31'); expect(trace.frames.length, 6); for (final frame in trace.frames) { expect(frame is UnparsedFrame, false); } expect(trace.frames[0].uri, Uri.parse('wasm code')); expect(trace.frames[0].line, null); expect(trace.frames[0].column, null); expect(trace.frames[0].member, 'Error._throwWithCurrentStackTrace'); expect(trace.frames[4].uri, Uri.parse('file:///home/user/test.mjs')); expect(trace.frames[4].line, 361); expect(trace.frames[4].column, 48); expect(trace.frames[4].member, 'invokeMain'); expect(trace.frames[5].uri, Uri.parse('file:///home/user/run_wasm.js')); expect(trace.frames[5].line, 416); expect(trace.frames[5].column, 31); expect(trace.frames[5].member, ''); }); }); test('.toString() nicely formats the stack trace', () { var trace = Trace.parse(''' #0 Foo._bar (foo/bar.dart:42:21) #1 zip..zap (dart:async/future.dart:0:2) #2 zip..zap (https://pub.dev/thing.dart:1:100) '''); expect(trace.toString(), equals(''' ${path.join('foo', 'bar.dart')} 42:21 Foo._bar dart:async/future.dart 0:2 zip..zap https://pub.dev/thing.dart 1:100 zip..zap ''')); }); test('.vmTrace returns a native-style trace', () { var uri = path.toUri(path.absolute('foo')); var trace = Trace([ Frame(uri, 10, 20, 'Foo.'), Frame(Uri.parse('https://dart.dev/foo.dart'), null, null, 'bar'), Frame(Uri.parse('dart:async'), 15, null, 'baz'), ]); expect( trace.vmTrace.toString(), equals('#1 Foo. ($uri:10:20)\n' '#2 bar (https://dart.dev/foo.dart:0:0)\n' '#3 baz (dart:async:15:0)\n')); }); group('folding', () { group('.terse', () { test('folds core frames together bottom-up', () { var trace = Trace.parse(''' #1 top (dart:async/future.dart:0:2) #2 bottom (dart:core/uri.dart:1:100) #0 notCore (foo.dart:42:21) #3 top (dart:io:5:10) #4 bottom (dart:async-patch/future.dart:9:11) #5 alsoNotCore (bar.dart:10:20) '''); expect(trace.terse.toString(), equals(''' dart:core bottom foo.dart 42:21 notCore dart:async bottom bar.dart 10:20 alsoNotCore ''')); }); test('folds empty async frames', () { var trace = Trace.parse(''' #0 top (dart:async/future.dart:0:2) #1 empty.<_async_body> (bar.dart) #2 bottom (dart:async-patch/future.dart:9:11) #3 notCore (foo.dart:42:21) '''); expect(trace.terse.toString(), equals(''' dart:async bottom foo.dart 42:21 notCore ''')); }); test('removes the bottom-most async frame', () { var trace = Trace.parse(''' #0 notCore (foo.dart:42:21) #1 top (dart:async/future.dart:0:2) #2 bottom (dart:core/uri.dart:1:100) #3 top (dart:io:5:10) #4 bottom (dart:async-patch/future.dart:9:11) '''); expect(trace.terse.toString(), equals(''' foo.dart 42:21 notCore ''')); }); test("won't make a trace empty", () { var trace = Trace.parse(''' #1 top (dart:async/future.dart:0:2) #2 bottom (dart:core/uri.dart:1:100) '''); expect(trace.terse.toString(), equals(''' dart:core bottom ''')); }); test("won't panic on an empty trace", () { expect(Trace.parse('').terse.toString(), equals('')); }); }); group('.foldFrames', () { test('folds frames together bottom-up', () { var trace = Trace.parse(''' #0 notFoo (foo.dart:42:21) #1 fooTop (bar.dart:0:2) #2 fooBottom (foo.dart:1:100) #3 alsoNotFoo (bar.dart:10:20) #4 fooTop (dart:io/socket.dart:5:10) #5 fooBottom (dart:async-patch/future.dart:9:11) '''); var folded = trace.foldFrames((frame) => frame.member!.startsWith('foo')); expect(folded.toString(), equals(''' foo.dart 42:21 notFoo foo.dart 1:100 fooBottom bar.dart 10:20 alsoNotFoo dart:async-patch/future.dart 9:11 fooBottom ''')); }); test('will never fold unparsed frames', () { var trace = Trace.parse(r''' .g"cs$#:b";a#>sw{*{ul$"$xqwr`p %+j-?uppx<([j@#nu{{>*+$%x-={`{ !e($b{nj)zs?cgr%!;bmw.+$j+pfj~ '''); expect(trace.foldFrames((frame) => true).toString(), equals(r''' .g"cs$#:b";a#>sw{*{ul$"$xqwr`p %+j-?uppx<([j@#nu{{>*+$%x-={`{ !e($b{nj)zs?cgr%!;bmw.+$j+pfj~ ''')); }); group('with terse: true', () { test('folds core frames as well', () { var trace = Trace.parse(''' #0 notFoo (foo.dart:42:21) #1 fooTop (bar.dart:0:2) #2 coreBottom (dart:async/future.dart:0:2) #3 alsoNotFoo (bar.dart:10:20) #4 fooTop (foo.dart:9:11) #5 coreBottom (dart:async-patch/future.dart:9:11) '''); var folded = trace.foldFrames( (frame) => frame.member!.startsWith('foo'), terse: true); expect(folded.toString(), equals(''' foo.dart 42:21 notFoo dart:async coreBottom bar.dart 10:20 alsoNotFoo ''')); }); test('shortens folded frames', () { var trace = Trace.parse(''' #0 notFoo (foo.dart:42:21) #1 fooTop (bar.dart:0:2) #2 fooBottom (package:foo/bar.dart:0:2) #3 alsoNotFoo (bar.dart:10:20) #4 fooTop (foo.dart:9:11) #5 fooBottom (foo/bar.dart:9:11) #6 againNotFoo (bar.dart:20:20) '''); var folded = trace.foldFrames( (frame) => frame.member!.startsWith('foo'), terse: true); expect(folded.toString(), equals(''' foo.dart 42:21 notFoo package:foo fooBottom bar.dart 10:20 alsoNotFoo foo fooBottom bar.dart 20:20 againNotFoo ''')); }); test('removes the bottom-most folded frame', () { var trace = Trace.parse(''' #2 fooTop (package:foo/bar.dart:0:2) #3 notFoo (bar.dart:10:20) #5 fooBottom (foo/bar.dart:9:11) '''); var folded = trace.foldFrames( (frame) => frame.member!.startsWith('foo'), terse: true); expect(folded.toString(), equals(''' package:foo fooTop bar.dart 10:20 notFoo ''')); }); }); }); }); }