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', () {