Use the stack_trace library in scheduled_test.

This also adds a couple features to stack_trace.

Review URL: https://codereview.chromium.org//13157004

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/stack_trace@20752 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/pkgs/stack_trace/lib/src/frame.dart b/pkgs/stack_trace/lib/src/frame.dart
index 5f0777c..f05b469 100644
--- a/pkgs/stack_trace/lib/src/frame.dart
+++ b/pkgs/stack_trace/lib/src/frame.dart
@@ -46,6 +46,13 @@
     return path.relative(fileUriToPath(uri));
   }
 
+  /// Returns the name of the package this stack frame comes from, or `null` if
+  /// this stack frame doesn't come from a `package:` URL.
+  String get package {
+    if (uri.scheme != 'package') return null;
+    return uri.path.split('/').first;
+  }
+
   /// A human-friendly description of the code location.
   ///
   /// For Dart core libraries, this will omit the line and column information,
diff --git a/pkgs/stack_trace/lib/src/trace.dart b/pkgs/stack_trace/lib/src/trace.dart
index 1ab7695..43a598c 100644
--- a/pkgs/stack_trace/lib/src/trace.dart
+++ b/pkgs/stack_trace/lib/src/trace.dart
@@ -79,19 +79,32 @@
   String get fullStackTrace => toString();
 
   /// Returns a terser version of [this]. This is accomplished by folding
-  /// together multiple stack frames from the core library. If multiple such
-  /// frames appear in a row, only the last (the one directly called by user
-  /// code) is kept. Core library patches are also renamed to remove their
-  /// `-patch` suffix.
+  /// together multiple stack frames from the core library, as in [foldFrames].
+  /// Core library patches are also renamed to remove their `-patch` suffix.
   Trace get terse {
+    return new Trace(foldFrames((frame) => frame.isCore).frames.map((frame) {
+      if (!frame.isCore) return frame;
+      var library = frame.library.replaceAll(_patchRegExp, '');
+      return new Frame(
+          Uri.parse(library), frame.line, frame.column, frame.member);
+    }));
+  }
+
+  /// Returns a new [Trace] based on [this] where multiple stack frames matching
+  /// [predicate] are folded together. This means that whenever there are
+  /// multiple frames in a row that match [predicate], only the last one is
+  /// kept.
+  ///
+  /// This is useful for limiting the amount of library code that appears in a
+  /// stack trace by only showing user code and code that's called by user code.
+  Trace foldFrames(bool predicate(frame)) {
     var newFrames = <Frame>[];
     for (var frame in frames.reversed) {
-      if (!frame.isCore) {
+      if (!predicate(frame)) {
         newFrames.add(frame);
-      } else if (newFrames.isEmpty || !newFrames.last.isCore) {
-        var library = frame.library.replaceAll(_patchRegExp, '');
+      } else if (newFrames.isEmpty || !predicate(newFrames.last)) {
         newFrames.add(new Frame(
-            Uri.parse(library), frame.line, frame.column, frame.member));
+            frame.uri, frame.line, frame.column, frame.member));
       }
     }
 
diff --git a/pkgs/stack_trace/test/frame_test.dart b/pkgs/stack_trace/test/frame_test.dart
index c65f395..02bec68 100644
--- a/pkgs/stack_trace/test/frame_test.dart
+++ b/pkgs/stack_trace/test/frame_test.dart
@@ -139,6 +139,22 @@
     });
   });
 
+  group('.package', () {
+    test('returns null for non-package URIs', () {
+      expect(new Frame.parse('#0 Foo (dart:async:0:0)').package, isNull);
+      expect(new Frame.parse('#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,
+          equals('foo'));
+      expect(new Frame.parse('#0 Foo (package:foo/zap/bar.dart:0:0)').package,
+          equals('foo'));
+    });
+  });
+
   group('.toString()', () {
     test('returns the library and line/column numbers for non-core '
         'libraries', () {
diff --git a/pkgs/stack_trace/test/trace_test.dart b/pkgs/stack_trace/test/trace_test.dart
index ed8fa6b..cbbf90b 100644
--- a/pkgs/stack_trace/test/trace_test.dart
+++ b/pkgs/stack_trace/test/trace_test.dart
@@ -133,4 +133,23 @@
 dart:async      bottom
 '''));
   });
+
+  test('.foldFrames folds frames together bottom-up', () {
+    var trace = new 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:5:10)
+#5 fooBottom (dart:async-patch: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  fooBottom
+'''));
+  });
 }