Add support for V8 and Firefox stack traces in pkg/stack_trace. R=jmesserly@google.com Review URL: https://codereview.chromium.org//18167002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/stack_trace@24569 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/pkgs/stack_trace/lib/src/frame.dart b/pkgs/stack_trace/lib/src/frame.dart index d750b91..6daca46 100644 --- a/pkgs/stack_trace/lib/src/frame.dart +++ b/pkgs/stack_trace/lib/src/frame.dart
@@ -9,9 +9,23 @@ import 'trace.dart'; -final _nativeFrameRegExp = new RegExp( +// #1 Foo._bar (file:///home/nweiz/code/stuff.dart:42:21) +final _vmFrame = new RegExp( r'^#\d+\s+([^\s].*) \((.+):(\d+):(\d+)\)$'); +// at VW.call$0 (http://pub.dartlang.org/stuff.dart.js:560:28) +// at http://pub.dartlang.org/stuff.dart.js:560:28 +final _v8Frame = new RegExp( + r'^\s*at (?:([^\s].*) \((.+):(\d+):(\d+)\)|(.+):(\d+):(\d+))$'); + +// .VW.call$0@http://pub.dartlang.org/stuff.dart.js:560 +// .VW.call$0("arg")@http://pub.dartlang.org/stuff.dart.js:560 +// .VW.call$0/name<@http://pub.dartlang.org/stuff.dart.js:560 +final _firefoxFrame = new RegExp( + r'^([^@(/]*)(?:\(.*\))?(/[^<]*<?)?(?:\(.*\))?@(.*):(\d+)$'); + +final _initialDot = new RegExp(r"^\."); + /// A single stack frame. Each frame points to a precise location in Dart code. class Frame { /// The URI of the file in which the code is located. @@ -76,13 +90,11 @@ return new Trace.current(level + 1).frames.first; } - /// Parses a string representation of a stack frame. - /// - /// [frame] should be formatted in the same way as a native stack trace frame. - factory Frame.parse(String frame) { - var match = _nativeFrameRegExp.firstMatch(frame); + /// Parses a string representation of a Dart VM stack frame. + factory Frame.parseVM(String frame) { + var match = _vmFrame.firstMatch(frame); if (match == null) { - throw new FormatException("Couldn't parse stack trace line '$frame'."); + throw new FormatException("Couldn't parse VM stack trace line '$frame'."); } var uri = Uri.parse(match[2]); @@ -90,6 +102,48 @@ return new Frame(uri, int.parse(match[3]), int.parse(match[4]), member); } + /// Parses a string representation of a Chrome/V8 stack frame. + factory Frame.parseV8(String frame) { + var match = _v8Frame.firstMatch(frame); + if (match == null) { + throw new FormatException("Couldn't parse V8 stack trace line '$frame'."); + } + + // V8 stack frames can be in two forms. + if (match[2] != null) { + // The first form looks like " at FUNCTION (URI:LINE:COL)" + var uri = Uri.parse(match[2]); + var member = match[1].replaceAll("<anonymous>", "<fn>"); + return new Frame(uri, int.parse(match[3]), int.parse(match[4]), member); + } else { + // The second form looks like " at URI:LINE:COL", and is used for + // anonymous functions. + var uri = Uri.parse(match[5]); + return new Frame(uri, int.parse(match[6]), int.parse(match[7]), "<fn>"); + } + } + + /// Parses a string representation of a Firefox stack frame. + factory Frame.parseFirefox(String frame) { + var match = _firefoxFrame.firstMatch(frame); + if (match == null) { + throw new FormatException( + "Couldn't parse Firefox stack trace line '$frame'."); + } + + var uri = Uri.parse(match[3]); + var member = match[1]; + if (member == "") { + member = "<fn>"; + } else if (match[2] != null) { + member = "$member.<fn>"; + } + // Some Firefox members have initial dots. We remove them for consistency + // with other platforms. + member = member.replaceFirst(_initialDot, ''); + return new Frame(uri, int.parse(match[4]), null, member); + } + Frame(this.uri, this.line, this.column, this.member); String toString() => '$location in $member';
diff --git a/pkgs/stack_trace/lib/src/trace.dart b/pkgs/stack_trace/lib/src/trace.dart index db0e70b..4abf55a 100644 --- a/pkgs/stack_trace/lib/src/trace.dart +++ b/pkgs/stack_trace/lib/src/trace.dart
@@ -12,6 +12,13 @@ final _terseRegExp = new RegExp(r"(-patch)?(/.*)?$"); +/// A RegExp to match Firefox's stack traces. +/// +/// Firefox's trace frames start with the name of the function in which the +/// error occurred, possibly including its parameters inside `()`. For example, +/// `.VW.call$0("arg")@http://pub.dartlang.org/stuff.dart.js:560`. +final _firefoxTrace = new RegExp(r"^([.0-9A-Za-z_$/<]*|\(.*\))*@"); + /// A stack trace, comprised of a list of stack frames. class Trace implements StackTrace { /// The stack frames that comprise this stack trace. @@ -57,9 +64,30 @@ /// Parses a string representation of a stack trace. /// - /// [trace] should be formatted in the same way as native stack traces. - Trace.parse(String trace) - : this(trace.trim().split("\n").map((line) => new Frame.parse(line))); + /// [trace] should be formatted in the same way as a Dart VM or browser stack + /// trace. + factory Trace.parse(String trace) { + if (trace.isEmpty) return new Trace(<Frame>[]); + if (trace.startsWith("Error\n")) return new Trace.parseV8(trace); + if (trace.contains(_firefoxTrace)) return new Trace.parseFirefox(trace); + + // Default to parsing the stack trace as a VM trace. This is also hit on IE + // and Safari, where the stack trace is just an empty string (issue 11257). + return new Trace.parseVM(trace); + } + + /// Parses a string representation of a Dart VM stack trace. + Trace.parseVM(String trace) + : this(trace.trim().split("\n").map((line) => new Frame.parseVM(line))); + + /// Parses a string representation of a Chrome/V8 stack trace. + Trace.parseV8(String trace) + : this(trace.split("\n").skip(1).map((line) => new Frame.parseV8(line))); + + /// Parses a string representation of a Firefox stack trace. + Trace.parseFirefox(String trace) + : this(trace.trim().split("\n") + .map((line) => new Frame.parseFirefox(line))); /// Returns a new [Trace] comprised of [frames]. Trace(Iterable<Frame> frames)
diff --git a/pkgs/stack_trace/test/frame_test.dart b/pkgs/stack_trace/test/frame_test.dart index a140670..2ab310c 100644 --- a/pkgs/stack_trace/test/frame_test.dart +++ b/pkgs/stack_trace/test/frame_test.dart
@@ -4,73 +4,190 @@ library frame_test; -import 'dart:io'; - import 'package:pathos/path.dart' as path; import 'package:stack_trace/stack_trace.dart'; import 'package:unittest/unittest.dart'; -String getStackFrame() { - try { - throw ''; - } catch (_, stackTrace) { - return stackTrace.toString().split("\n").first; - } -} - -Frame getCaller([int level]) { - if (level == null) return new Frame.caller(); - return new Frame.caller(level); -} - -Frame nestedGetCaller(int level) => getCaller(level); - void main() { - test('parses a stack frame correctly', () { - var frame = new Frame.parse("#1 Foo._bar " - "(file:///home/nweiz/code/stuff.dart:42:21)"); - expect(frame.uri, equals(Uri.parse("file:///home/nweiz/code/stuff.dart"))); - expect(frame.line, equals(42)); - expect(frame.column, equals(21)); - expect(frame.member, equals('Foo._bar')); + group('.parseVM', () { + test('parses a stack frame correctly', () { + var frame = new Frame.parseVM("#1 Foo._bar " + "(file:///home/nweiz/code/stuff.dart:42:21)"); + expect(frame.uri, + equals(Uri.parse("file:///home/nweiz/code/stuff.dart"))); + expect(frame.line, equals(42)); + expect(frame.column, equals(21)); + expect(frame.member, equals('Foo._bar')); + }); + + test('converts "<anonymous closure>" to "<fn>"', () { + String parsedMember(String member) => + new Frame.parseVM('#0 $member (foo:0:0)').member; + + expect(parsedMember('Foo.<anonymous closure>'), equals('Foo.<fn>')); + expect(parsedMember('<anonymous closure>.<anonymous closure>.bar'), + equals('<fn>.<fn>.bar')); + }); + + test('throws a FormatException for malformed frames', () { + expect(() => new Frame.parseVM(''), throwsFormatException); + expect(() => new Frame.parseVM('#1'), throwsFormatException); + expect(() => new Frame.parseVM('#1 Foo'), throwsFormatException); + expect(() => new Frame.parseVM('#1 Foo (dart:async/future.dart)'), + throwsFormatException); + expect(() => new Frame.parseVM('#1 Foo (dart:async/future.dart:10)'), + throwsFormatException); + expect(() => new Frame.parseVM('#1 (dart:async/future.dart:10:15)'), + throwsFormatException); + expect(() => new Frame.parseVM('Foo (dart:async/future.dart:10:15)'), + throwsFormatException); + }); }); - test('parses a real stack frame correctly', () { - var frame = new Frame.parse(getStackFrame()); - // TODO(nweiz): use URL-style paths when such a thing exists. - var builder = new path.Builder(style: path.Style.posix); - expect(builder.basename(frame.uri.path), equals('frame_test.dart')); - expect(frame.line, equals(15)); - expect(frame.column, equals(5)); - expect(frame.member, equals('getStackFrame')); + group('.parseV8', () { + test('parses a stack frame correctly', () { + var frame = new Frame.parseV8(" at VW.call\$0 " + "(http://pub.dartlang.org/stuff.dart.js:560:28)"); + expect(frame.uri, + equals(Uri.parse("http://pub.dartlang.org/stuff.dart.js"))); + expect(frame.line, equals(560)); + expect(frame.column, equals(28)); + expect(frame.member, equals('VW.call\$0')); + }); + + test('parses an anonymous stack frame correctly', () { + var frame = new Frame.parseV8( + " at http://pub.dartlang.org/stuff.dart.js:560:28"); + expect(frame.uri, + equals(Uri.parse("http://pub.dartlang.org/stuff.dart.js"))); + expect(frame.line, equals(560)); + expect(frame.column, equals(28)); + expect(frame.member, equals('<fn>')); + }); + + test('converts "<anonymous>" to "<fn>"', () { + String parsedMember(String member) => + new Frame.parseV8(' at $member (foo:0:0)').member; + + expect(parsedMember('Foo.<anonymous>'), equals('Foo.<fn>')); + expect(parsedMember('<anonymous>.<anonymous>.bar'), + equals('<fn>.<fn>.bar')); + }); + + test('throws a FormatException for malformed frames', () { + expect(() => new Frame.parseV8(''), throwsFormatException); + expect(() => new Frame.parseV8(' at'), throwsFormatException); + expect(() => new Frame.parseV8(' at Foo'), throwsFormatException); + expect(() => new Frame.parseV8(' at Foo (dart:async/future.dart)'), + throwsFormatException); + expect(() => new Frame.parseV8(' at Foo (dart:async/future.dart:10)'), + throwsFormatException); + expect(() => new Frame.parseV8(' at (dart:async/future.dart:10:15)'), + throwsFormatException); + expect(() => new Frame.parseV8('Foo (dart:async/future.dart:10:15)'), + throwsFormatException); + expect(() => new Frame.parseV8(' at dart:async/future.dart'), + throwsFormatException); + expect(() => new Frame.parseV8(' at dart:async/future.dart:10'), + throwsFormatException); + expect(() => new Frame.parseV8('dart:async/future.dart:10:15'), + throwsFormatException); + }); }); - test('converts "<anonymous closure>" to "<fn>"', () { - String parsedMember(String member) => - new Frame.parse('#0 $member (foo:0:0)').member; + group('.parseFirefox', () { + test('parses a simple stack frame correctly', () { + var frame = new Frame.parseFirefox( + ".VW.call\$0@http://pub.dartlang.org/stuff.dart.js:560"); + expect(frame.uri, + equals(Uri.parse("http://pub.dartlang.org/stuff.dart.js"))); + expect(frame.line, equals(560)); + expect(frame.column, isNull); + expect(frame.member, equals('VW.call\$0')); + }); - expect(parsedMember('Foo.<anonymous closure>'), equals('Foo.<fn>')); - expect(parsedMember('<anonymous closure>.<anonymous closure>.bar'), - equals('<fn>.<fn>.bar')); - }); + test('parses a simple anonymous stack frame correctly', () { + var frame = new Frame.parseFirefox( + "@http://pub.dartlang.org/stuff.dart.js:560"); + expect(frame.uri, + equals(Uri.parse("http://pub.dartlang.org/stuff.dart.js"))); + expect(frame.line, equals(560)); + expect(frame.column, isNull); + expect(frame.member, equals("<fn>")); + }); - test('throws a FormatException for malformed frames', () { - expect(() => new Frame.parse(''), throwsFormatException); - expect(() => new Frame.parse('#1'), throwsFormatException); - expect(() => new Frame.parse('#1 Foo'), throwsFormatException); - expect(() => new Frame.parse('#1 Foo (dart:async/future.dart)'), - throwsFormatException); - expect(() => new Frame.parse('#1 Foo (dart:async/future.dart:10)'), - throwsFormatException); - expect(() => new Frame.parse('#1 (dart:async/future.dart:10:15)'), - throwsFormatException); - expect(() => new Frame.parse('Foo (dart:async/future.dart:10:15)'), - throwsFormatException); + test('parses a nested anonymous stack frame correctly', () { + var frame = new Frame.parseFirefox( + ".foo/<@http://pub.dartlang.org/stuff.dart.js:560"); + expect(frame.uri, + equals(Uri.parse("http://pub.dartlang.org/stuff.dart.js"))); + expect(frame.line, equals(560)); + expect(frame.column, isNull); + expect(frame.member, equals("foo.<fn>")); + + frame = new Frame.parseFirefox( + ".foo/@http://pub.dartlang.org/stuff.dart.js:560"); + expect(frame.uri, + equals(Uri.parse("http://pub.dartlang.org/stuff.dart.js"))); + expect(frame.line, equals(560)); + expect(frame.column, isNull); + expect(frame.member, equals("foo.<fn>")); + }); + + test('parses a named nested anonymous stack frame correctly', () { + var frame = new Frame.parseFirefox( + ".foo/.name<@http://pub.dartlang.org/stuff.dart.js:560"); + expect(frame.uri, + equals(Uri.parse("http://pub.dartlang.org/stuff.dart.js"))); + expect(frame.line, equals(560)); + expect(frame.column, isNull); + expect(frame.member, equals("foo.<fn>")); + + frame = new Frame.parseFirefox( + ".foo/.name@http://pub.dartlang.org/stuff.dart.js:560"); + expect(frame.uri, + equals(Uri.parse("http://pub.dartlang.org/stuff.dart.js"))); + expect(frame.line, equals(560)); + expect(frame.column, isNull); + expect(frame.member, equals("foo.<fn>")); + }); + + test('parses a stack frame with parameters correctly', () { + var frame = new Frame.parseFirefox( + '.foo(12, "@)()/<")@http://pub.dartlang.org/stuff.dart.js:560'); + expect(frame.uri, + equals(Uri.parse("http://pub.dartlang.org/stuff.dart.js"))); + expect(frame.line, equals(560)); + expect(frame.column, isNull); + expect(frame.member, equals("foo")); + }); + + test('parses a nested anonymous stack frame with parameters correctly', () { + var frame = new Frame.parseFirefox( + '.foo(12, "@)()/<")/.fn<@' + 'http://pub.dartlang.org/stuff.dart.js:560'); + expect(frame.uri, + equals(Uri.parse("http://pub.dartlang.org/stuff.dart.js"))); + expect(frame.line, equals(560)); + expect(frame.column, isNull); + expect(frame.member, equals("foo.<fn>")); + }); + + test('throws a FormatException for malformed frames', () { + expect(() => new Frame.parseFirefox(''), throwsFormatException); + expect(() => new Frame.parseFirefox('.foo'), throwsFormatException); + expect(() => new Frame.parseFirefox('.foo@dart:async/future.dart'), + throwsFormatException); + expect(() => new Frame.parseFirefox('.foo(@dart:async/future.dart:10'), + throwsFormatException); + expect(() => new Frame.parseFirefox('@dart:async/future.dart'), + throwsFormatException); + }); }); test('only considers dart URIs to be core', () { bool isCore(String library) => - new Frame.parse('#0 Foo ($library:0:0)').isCore; + new Frame.parseVM('#0 Foo ($library:0:0)').isCore; expect(isCore('dart:core'), isTrue); expect(isCore('dart:async'), isTrue); @@ -82,39 +199,17 @@ expect(isCore('bart:core/uri.dart'), isFalse); }); - group('.caller()', () { - test('with no argument returns the parent frame', () { - expect(getCaller().member, equals('main.<fn>.<fn>')); - }); - - test('at level 0 returns the current frame', () { - expect(getCaller(0).member, equals('getCaller')); - }); - - test('at level 1 returns the current frame', () { - expect(getCaller(1).member, equals('main.<fn>.<fn>')); - }); - - test('at level 2 returns the grandparent frame', () { - expect(nestedGetCaller(2).member, equals('main.<fn>.<fn>')); - }); - - test('throws an ArgumentError for negative levels', () { - expect(() => new Frame.caller(-1), throwsArgumentError); - }); - }); - group('.library', () { test('returns the URI string for non-file URIs', () { - expect(new Frame.parse('#0 Foo (dart:async/future.dart:0:0)').library, + expect(new Frame.parseVM('#0 Foo (dart:async/future.dart:0:0)').library, equals('dart:async/future.dart')); - expect(new Frame.parse('#0 Foo ' + expect(new Frame.parseVM('#0 Foo ' '(http://dartlang.org/stuff/thing.dart:0:0)').library, equals('http://dartlang.org/stuff/thing.dart')); }); test('returns the relative path for file URIs', () { - expect(new Frame.parse('#0 Foo (foo/bar.dart:0:0)').library, + expect(new Frame.parseVM('#0 Foo (foo/bar.dart:0:0)').library, equals('foo/bar.dart')); }); }); @@ -122,27 +217,27 @@ group('.location', () { test('returns the library and line/column numbers for non-core ' 'libraries', () { - expect(new Frame.parse('#0 Foo ' + expect(new Frame.parseVM('#0 Foo ' '(http://dartlang.org/thing.dart:5:10)').location, equals('http://dartlang.org/thing.dart 5:10')); - expect(new Frame.parse('#0 Foo (foo/bar.dart:1:2)').location, + expect(new Frame.parseVM('#0 Foo (foo/bar.dart:1:2)').location, equals('foo/bar.dart 1:2')); }); }); group('.package', () { test('returns null for non-package URIs', () { - expect(new Frame.parse('#0 Foo (dart:async/future.dart:0:0)').package, + expect(new Frame.parseVM('#0 Foo (dart:async/future.dart:0:0)').package, isNull); - expect(new Frame.parse('#0 Foo ' + expect(new Frame.parseVM('#0 Foo ' '(http://dartlang.org/stuff/thing.dart:0:0)').package, isNull); }); test('returns the package name for package: URIs', () { - expect(new Frame.parse('#0 Foo (package:foo/foo.dart:0:0)').package, + expect(new Frame.parseVM('#0 Foo (package:foo/foo.dart:0:0)').package, equals('foo')); - expect(new Frame.parse('#0 Foo (package:foo/zap/bar.dart:0:0)').package, + expect(new Frame.parseVM('#0 Foo (package:foo/zap/bar.dart:0:0)').package, equals('foo')); }); }); @@ -150,13 +245,13 @@ group('.toString()', () { test('returns the library and line/column numbers for non-core ' 'libraries', () { - expect(new Frame.parse('#0 Foo (http://dartlang.org/thing.dart:5:10)') + expect(new Frame.parseVM('#0 Foo (http://dartlang.org/thing.dart:5:10)') .toString(), equals('http://dartlang.org/thing.dart 5:10 in Foo')); }); test('converts "<anonymous closure>" to "<fn>"', () { - expect(new Frame.parse('#0 Foo.<anonymous closure> ' + expect(new Frame.parseVM('#0 Foo.<anonymous closure> ' '(dart:core/uri.dart:5:10)').toString(), equals('dart:core/uri.dart 5:10 in Foo.<fn>')); });
diff --git a/pkgs/stack_trace/test/trace_test.dart b/pkgs/stack_trace/test/trace_test.dart index fcf4fe5..5d34de5 100644 --- a/pkgs/stack_trace/test/trace_test.dart +++ b/pkgs/stack_trace/test/trace_test.dart
@@ -4,8 +4,6 @@ library trace_test; -import 'dart:io'; - import 'package:pathos/path.dart' as path; import 'package:stack_trace/stack_trace.dart'; import 'package:unittest/unittest.dart'; @@ -31,61 +29,80 @@ Trace nestedGetCurrentTrace(int level) => getCurrentTrace(level); void main() { - test('parses a stack trace correctly', () { - var trace = new Trace.parse(''' -#0 Foo._bar (file:///home/nweiz/code/stuff.dart:42:21) -#1 zip.<anonymous closure>.zap (dart:async/future.dart:0:2) -#2 zip.<anonymous closure>.zap (http://pub.dartlang.org/thing.dart:1:100) -'''); + // This just shouldn't crash. + test('a native stack trace is parseable', () => new Trace.current()); - 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("http://pub.dartlang.org/thing.dart"))); - }); + group('.parse', () { + test('.parse parses a VM stack trace correctly', () { + var trace = new Trace.parse( + '#0 Foo._bar (file:///home/nweiz/code/stuff.dart:42:21)\n' + '#1 zip.<anonymous closure>.zap (dart:async/future.dart:0:2)\n' + '#2 zip.<anonymous closure>.zap (http://pub.dartlang.org/thing.' + 'dart:1:100)'); - test('parses a real stack trace correctly', () { - var trace = new Trace.parse(getStackTraceString()); - // TODO(nweiz): use URL-style paths when such a thing exists. - var builder = new path.Builder(style: path.Style.posix); - expect(builder.basename(trace.frames.first.uri.path), - equals('trace_test.dart')); - expect(trace.frames.first.member, equals('getStackTraceString')); - }); - - test('converts from a native stack trace correctly', () { - var trace = new Trace.from(getStackTraceObject()); - // TODO(nweiz): use URL-style paths when such a thing exists. - var builder = new path.Builder(style: path.Style.posix); - expect(builder.basename(trace.frames.first.uri.path), - equals('trace_test.dart')); - expect(trace.frames.first.member, equals('getStackTraceObject')); - }); - - group('.current()', () { - test('with no argument returns a trace starting at the current frame', () { - var trace = new Trace.current(); - expect(trace.frames.first.member, equals('main.<fn>.<fn>')); + 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("http://pub.dartlang.org/thing.dart"))); }); - test('at level 0 returns a trace starting at the current frame', () { - var trace = new Trace.current(0); - expect(trace.frames.first.member, equals('main.<fn>.<fn>')); + test('parses a V8 stack trace correctly', () { + var trace = new Trace.parse( + 'Error\n' + ' at Foo._bar (http://pub.dartlang.org/stuff.js:42:21)\n' + ' at http://pub.dartlang.org/stuff.js:0:2\n' + ' at zip.<anonymous>.zap ' + '(http://pub.dartlang.org/thing.js:1:100)'); + + expect(trace.frames[0].uri, + equals(Uri.parse("http://pub.dartlang.org/stuff.js"))); + expect(trace.frames[1].uri, + equals(Uri.parse("http://pub.dartlang.org/stuff.js"))); + expect(trace.frames[2].uri, + equals(Uri.parse("http://pub.dartlang.org/thing.js"))); }); - test('at level 1 returns a trace starting at the parent frame', () { - var trace = getCurrentTrace(1); - expect(trace.frames.first.member, equals('main.<fn>.<fn>')); + test('parses a Firefox stack trace correctly', () { + var trace = new Trace.parse( + 'Foo._bar@http://pub.dartlang.org/stuff.js:42\n' + 'zip/<@http://pub.dartlang.org/stuff.js:0\n' + 'zip.zap(12, "@)()/<")@http://pub.dartlang.org/thing.js:1'); + + expect(trace.frames[0].uri, + equals(Uri.parse("http://pub.dartlang.org/stuff.js"))); + expect(trace.frames[1].uri, + equals(Uri.parse("http://pub.dartlang.org/stuff.js"))); + expect(trace.frames[2].uri, + equals(Uri.parse("http://pub.dartlang.org/thing.js"))); + + trace = new Trace.parse( + 'zip/<@http://pub.dartlang.org/stuff.js:0\n' + 'Foo._bar@http://pub.dartlang.org/stuff.js:42\n' + 'zip.zap(12, "@)()/<")@http://pub.dartlang.org/thing.js:1'); + + expect(trace.frames[0].uri, + equals(Uri.parse("http://pub.dartlang.org/stuff.js"))); + expect(trace.frames[1].uri, + equals(Uri.parse("http://pub.dartlang.org/stuff.js"))); + expect(trace.frames[2].uri, + equals(Uri.parse("http://pub.dartlang.org/thing.js"))); + + trace = new Trace.parse( + 'zip.zap(12, "@)()/<")@http://pub.dartlang.org/thing.js:1\n' + 'zip/<@http://pub.dartlang.org/stuff.js:0\n' + 'Foo._bar@http://pub.dartlang.org/stuff.js:42'); + + expect(trace.frames[0].uri, + equals(Uri.parse("http://pub.dartlang.org/thing.js"))); + expect(trace.frames[1].uri, + equals(Uri.parse("http://pub.dartlang.org/stuff.js"))); + expect(trace.frames[2].uri, + equals(Uri.parse("http://pub.dartlang.org/stuff.js"))); }); - test('at level 2 returns a trace starting at the grandparent frame', () { - var trace = nestedGetCurrentTrace(2); - expect(trace.frames.first.member, equals('main.<fn>.<fn>')); - }); - - test('throws an ArgumentError for negative levels', () { - expect(() => new Trace.current(-1), throwsArgumentError); + test('parses an empty string correctly', () { + expect(new Trace.parse('').frames, isEmpty); }); });
diff --git a/pkgs/stack_trace/test/vm_test.dart b/pkgs/stack_trace/test/vm_test.dart new file mode 100644 index 0000000..38b2b80 --- /dev/null +++ b/pkgs/stack_trace/test/vm_test.dart
@@ -0,0 +1,109 @@ +// 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. + +/// This file tests stack_trace's ability to parse live stack traces. It's a +/// dual of dartium_test.dart, since method names can differ somewhat from +/// platform to platform. No similar file exists for dart2js since the specific +/// method names there are implementation details. + +import 'package:pathos/path.dart' as path; +import 'package:stack_trace/stack_trace.dart'; +import 'package:unittest/unittest.dart'; + +String getStackTraceString() { + try { + throw ''; + } catch (_, stackTrace) { + return stackTrace.toString(); + } +} + +StackTrace getStackTraceObject() { + try { + throw ''; + } catch (_, stackTrace) { + return stackTrace; + } +} + +Frame getCaller([int level]) { + if (level == null) return new Frame.caller(); + return new Frame.caller(level); +} + +Frame nestedGetCaller(int level) => getCaller(level); + +Trace getCurrentTrace([int level]) => new Trace.current(level); + +Trace nestedGetCurrentTrace(int level) => getCurrentTrace(level); + +void main() { + group('Trace', () { + test('.parse parses a real stack trace correctly', () { + var string = getStackTraceString(); + var trace = new Trace.parse(string); + var builder = new path.Builder(style: path.Style.url); + expect(builder.basename(trace.frames.first.uri.path), + equals('vm_test.dart')); + expect(trace.frames.first.member, equals('getStackTraceString')); + }); + + test('converts from a native stack trace correctly', () { + var trace = new Trace.from(getStackTraceObject()); + var builder = new path.Builder(style: path.Style.url); + expect(builder.basename(trace.frames.first.uri.path), + equals('vm_test.dart')); + expect(trace.frames.first.member, equals('getStackTraceObject')); + }); + + group('.current()', () { + test('with no argument returns a trace starting at the current frame', + () { + var trace = new Trace.current(); + expect(trace.frames.first.member, equals('main.<fn>.<fn>.<fn>')); + }); + + test('at level 0 returns a trace starting at the current frame', () { + var trace = new Trace.current(0); + expect(trace.frames.first.member, equals('main.<fn>.<fn>.<fn>')); + }); + + test('at level 1 returns a trace starting at the parent frame', () { + var trace = getCurrentTrace(1); + expect(trace.frames.first.member, equals('main.<fn>.<fn>.<fn>')); + }); + + test('at level 2 returns a trace starting at the grandparent frame', () { + var trace = nestedGetCurrentTrace(2); + expect(trace.frames.first.member, equals('main.<fn>.<fn>.<fn>')); + }); + + test('throws an ArgumentError for negative levels', () { + expect(() => new Trace.current(-1), throwsArgumentError); + }); + }); + }); + + group('Frame.caller()', () { + test('with no argument returns the parent frame', () { + expect(getCaller().member, equals('main.<fn>.<fn>')); + }); + + test('at level 0 returns the current frame', () { + expect(getCaller(0).member, equals('getCaller')); + }); + + test('at level 1 returns the current frame', () { + expect(getCaller(1).member, equals('main.<fn>.<fn>')); + }); + + test('at level 2 returns the grandparent frame', () { + expect(nestedGetCaller(2).member, equals('main.<fn>.<fn>')); + }); + + test('throws an ArgumentError for negative levels', () { + expect(() => new Frame.caller(-1), throwsArgumentError); + }); + }); +}