Add a couple functions to package:stack_trace. This adds a function to convert a Trace to a VM-style StackTrace object, and to parse a Trace-style string into a Trace. R=jmesserly@google.com Review URL: https://codereview.chromium.org//18029023 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/stack_trace@24611 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/pkgs/stack_trace/lib/src/frame.dart b/pkgs/stack_trace/lib/src/frame.dart index 6daca46..6ba3079 100644 --- a/pkgs/stack_trace/lib/src/frame.dart +++ b/pkgs/stack_trace/lib/src/frame.dart
@@ -24,6 +24,11 @@ final _firefoxFrame = new RegExp( r'^([^@(/]*)(?:\(.*\))?(/[^<]*<?)?(?:\(.*\))?@(.*):(\d+)$'); +// foo/bar.dart 10:11 in Foo._bar +// http://dartlang.org/foo/bar.dart in Foo._bar +final _friendlyFrame = new RegExp( + r'^([^\s]+)(?: (\d+):(\d+))?\s+([^\d][^\s]*)$'); + final _initialDot = new RegExp(r"^\."); /// A single stack frame. Each frame points to a precise location in Dart code. @@ -144,6 +149,26 @@ return new Frame(uri, int.parse(match[4]), null, member); } + /// Parses this package's string representation of a stack frame. + factory Frame.parseFriendly(String frame) { + var match = _friendlyFrame.firstMatch(frame); + if (match == null) { + throw new FormatException( + "Couldn't parse package:stack_trace stack trace line '$frame'."); + } + + var uri = Uri.parse(match[1]); + // If there's no scheme, this is a relative URI. We should interpret it as + // relative to the current working directory. + if (uri.scheme == '') { + uri = path.toUri(path.absolute(path.fromUri(uri))); + } + + var line = match[2] == null ? null : int.parse(match[2]); + var column = match[3] == null ? null : int.parse(match[3]); + return new Frame(uri, line, column, match[4]); + } + Frame(this.uri, this.line, this.column, this.member); String toString() => '$location in $member';
diff --git a/pkgs/stack_trace/lib/src/lazy_trace.dart b/pkgs/stack_trace/lib/src/lazy_trace.dart index 8c44829..ae56b5f 100644 --- a/pkgs/stack_trace/lib/src/lazy_trace.dart +++ b/pkgs/stack_trace/lib/src/lazy_trace.dart
@@ -25,8 +25,7 @@ } List<Frame> get frames => _trace.frames; - String get stackTrace => _trace.stackTrace; - String get fullStackTrace => _trace.fullStackTrace; + StackTrace get vmTrace => _trace.vmTrace; Trace get terse => new LazyTrace(() => _trace.terse); Trace foldFrames(bool predicate(frame)) => new LazyTrace(() => _trace.foldFrames(predicate));
diff --git a/pkgs/stack_trace/lib/src/trace.dart b/pkgs/stack_trace/lib/src/trace.dart index db7a404..2b9d22a 100644 --- a/pkgs/stack_trace/lib/src/trace.dart +++ b/pkgs/stack_trace/lib/src/trace.dart
@@ -9,6 +9,8 @@ import 'frame.dart'; import 'lazy_trace.dart'; +import 'utils.dart'; +import 'vm_trace.dart'; final _terseRegExp = new RegExp(r"(-patch)?(/.*)?$"); @@ -19,6 +21,9 @@ /// `.VW.call$0("arg")@http://pub.dartlang.org/stuff.dart.js:560`. final _firefoxTrace = new RegExp(r"^([.0-9A-Za-z_$/<]*|\(.*\))*@"); +/// A RegExp to match this package's stack traces. +final _friendlyTrace = new RegExp(r"^[^\s]+( \d+:\d+)?\s+[^\s]+($|\n)"); + /// A stack trace, comprised of a list of stack frames. class Trace implements StackTrace { /// The stack frames that comprise this stack trace. @@ -70,6 +75,7 @@ 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); + if (trace.contains(_friendlyTrace)) return new Trace.parseFriendly(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). @@ -89,23 +95,21 @@ : this(trace.trim().split("\n") .map((line) => new Frame.parseFirefox(line))); + /// Parses this package's a string representation of a stack trace. + Trace.parseFriendly(String trace) + : this(trace.trim().split("\n") + .map((line) => new Frame.parseFriendly(line))); + /// Returns a new [Trace] comprised of [frames]. Trace(Iterable<Frame> frames) : frames = new UnmodifiableListView<Frame>(frames.toList()); - // TODO(nweiz): Keep track of which [Frame]s are part of the partial stack - // trace and only print them. - /// Returns a string representation of this stack trace. + /// Returns a VM-style [StackTrace] object. /// - /// This is identical to [toString]. It will not be formatted in the manner of - /// native stack traces. - String get stackTrace => toString(); - - /// Returns a string representation of this stack trace. - /// - /// This is identical to [toString]. It will not be formatted in the manner of - /// native stack traces. - String get fullStackTrace => toString(); + /// The return value's [toString] method will always return a string + /// representation in the Dart VM's stack trace format, regardless of what + /// platform is being used. + StackTrace get vmTrace => new VMTrace(frames); /// Returns a terser version of [this]. /// @@ -149,21 +153,7 @@ // Print out the stack trace nicely formatted. return frames.map((frame) { - return '${_padRight(frame.location, longest)} ${frame.member}\n'; + return '${padRight(frame.location, longest)} ${frame.member}\n'; }).join(); } } - -/// Returns [string] with enough spaces added to the end to make it [length] -/// characters long. -String _padRight(String string, int length) { - if (string.length >= length) return string; - - var result = new StringBuffer(); - result.write(string); - for (var i = 0; i < length - string.length; i++) { - result.write(' '); - } - - return result.toString(); -}
diff --git a/pkgs/stack_trace/lib/src/utils.dart b/pkgs/stack_trace/lib/src/utils.dart new file mode 100644 index 0000000..08b3b96 --- /dev/null +++ b/pkgs/stack_trace/lib/src/utils.dart
@@ -0,0 +1,20 @@ +// 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. + +library stack_trace.src.utils; + +/// Returns [string] with enough spaces added to the end to make it [length] +/// characters long. +String padRight(String string, int length) { + if (string.length >= length) return string; + + var result = new StringBuffer(); + result.write(string); + for (var i = 0; i < length - string.length; i++) { + result.write(' '); + } + + return result.toString(); +} +
diff --git a/pkgs/stack_trace/lib/src/vm_trace.dart b/pkgs/stack_trace/lib/src/vm_trace.dart new file mode 100644 index 0000000..8116015 --- /dev/null +++ b/pkgs/stack_trace/lib/src/vm_trace.dart
@@ -0,0 +1,31 @@ +// 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. + +library vm_trace; + +import 'frame.dart'; +import 'utils.dart'; + +/// An implementation of [StackTrace] that emulates the behavior of the VM's +/// implementation. +/// +/// In particular, when [toString] is called, this returns a string in the VM's +/// stack trace format. +class VMTrace implements StackTrace { + /// The stack frames that comprise this stack trace. + final List<Frame> frames; + + VMTrace(this.frames); + + String toString() { + var i = 1; + return frames.map((frame) { + var number = padRight("#${i++}", 8); + var member = frame.member.replaceAll("<fn>", "<anonymous closure>"); + var line = frame.line == null ? 0 : frame.line; + var column = frame.column == null ? 0 : frame.column; + return "$number$member (${frame.uri}:$line:$column)\n"; + }).join(); + } +}
diff --git a/pkgs/stack_trace/test/frame_test.dart b/pkgs/stack_trace/test/frame_test.dart index 2ab310c..f38278d 100644 --- a/pkgs/stack_trace/test/frame_test.dart +++ b/pkgs/stack_trace/test/frame_test.dart
@@ -185,6 +185,43 @@ }); }); + group('.parseFriendly', () { + test('parses a simple stack frame correctly', () { + var frame = new Frame.parseFriendly( + "http://dartlang.org/foo/bar.dart 10:11 Foo.<fn>.bar"); + expect(frame.uri, equals(Uri.parse("http://dartlang.org/foo/bar.dart"))); + expect(frame.line, equals(10)); + expect(frame.column, equals(11)); + expect(frame.member, equals('Foo.<fn>.bar')); + }); + + test('parses a stack frame with no line or column correctly', () { + var frame = new Frame.parseFriendly( + "http://dartlang.org/foo/bar.dart Foo.<fn>.bar"); + expect(frame.uri, equals(Uri.parse("http://dartlang.org/foo/bar.dart"))); + expect(frame.line, isNull); + expect(frame.column, isNull); + expect(frame.member, equals('Foo.<fn>.bar')); + }); + + test('parses a stack frame with a relative path correctly', () { + var frame = new Frame.parseFriendly("foo/bar.dart 10:11 Foo.<fn>.bar"); + expect(frame.uri, equals( + path.toUri(path.absolute(path.join('foo', 'bar.dart'))))); + expect(frame.line, equals(10)); + expect(frame.column, equals(11)); + expect(frame.member, equals('Foo.<fn>.bar')); + }); + + test('throws a FormatException for malformed frames', () { + expect(() => new Frame.parseFriendly(''), throwsFormatException); + expect(() => new Frame.parseFriendly('foo/bar.dart'), + throwsFormatException); + expect(() => new Frame.parseFriendly('foo/bar.dart 10:11'), + throwsFormatException); + }); + }); + test('only considers dart URIs to be core', () { bool isCore(String library) => new Frame.parseVM('#0 Foo ($library:0:0)').isCore;
diff --git a/pkgs/stack_trace/test/trace_test.dart b/pkgs/stack_trace/test/trace_test.dart index 95753a0..fbe11a3 100644 --- a/pkgs/stack_trace/test/trace_test.dart +++ b/pkgs/stack_trace/test/trace_test.dart
@@ -101,6 +101,22 @@ equals(Uri.parse("http://pub.dartlang.org/stuff.js"))); }); + test('parses a package:stack_trace stack trace correctly', () { + var trace = new Trace.parse( + 'http://dartlang.org/foo/bar.dart 10:11 Foo.<fn>.bar\n' + 'http://dartlang.org/foo/baz.dart Foo.<fn>.bar'); + + expect(trace.frames[0].uri, + equals(Uri.parse("http://dartlang.org/foo/bar.dart"))); + expect(trace.frames[1].uri, + equals(Uri.parse("http://dartlang.org/foo/baz.dart"))); + }); + + test('parses a real package:stack_trace stack trace correctly', () { + var traceString = new Trace.current().toString(); + expect(new Trace.parse(traceString).toString(), equals(traceString)); + }); + test('parses an empty string correctly', () { var trace = new Trace.parse(''); expect(trace.frames, isEmpty); @@ -122,14 +138,18 @@ ''')); }); - test('.stackTrace forwards to .toString()', () { - var trace = new Trace.current(); - expect(trace.stackTrace, equals(trace.toString())); - }); + test('.vmTrace returns a native-style trace', () { + var uri = path.toUri(path.absolute('foo')); + var trace = new Trace([ + new Frame(uri, 10, 20, 'Foo.<fn>'), + new Frame(Uri.parse('http://dartlang.org/foo.dart'), null, null, 'bar'), + new Frame(Uri.parse('dart:async'), 15, null, 'baz'), + ]); - test('.fullStackTrace forwards to .toString()', () { - var trace = new Trace.current(); - expect(trace.fullStackTrace, equals(trace.toString())); + expect(trace.vmTrace.toString(), equals( + '#1 Foo.<anonymous closure> ($uri:10:20)\n' + '#2 bar (http://dartlang.org/foo.dart:0:0)\n' + '#3 baz (dart:async:15:0)\n')); }); test('.terse folds core frames together bottom-up', () {