Add Chain.foldFrames to pkg/stack_trace. R=rnystrom@google.com BUG=17082 Review URL: https://codereview.chromium.org//180223005 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/stack_trace@33041 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/pkgs/stack_trace/lib/src/chain.dart b/pkgs/stack_trace/lib/src/chain.dart index 4055bc8..2500e5e 100644 --- a/pkgs/stack_trace/lib/src/chain.dart +++ b/pkgs/stack_trace/lib/src/chain.dart
@@ -7,6 +7,7 @@ import 'dart:async'; import 'dart:collection'; +import 'frame.dart'; import 'stack_zone_specification.dart'; import 'trace.dart'; import 'utils.dart'; @@ -172,6 +173,32 @@ return new Chain(nonEmptyTraces); } + /// Returns a new [Chain] 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. In addition, traces that are + /// composed entirely of frames matching [predicate] are omitted. + /// + /// 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. + Chain foldFrames(bool predicate(Frame frame)) { + var foldedTraces = traces.map((trace) => trace.foldFrames(predicate)); + var nonEmptyTraces = foldedTraces.where((trace) { + // Ignore traces that contain only folded frames. These traces will be + // folded into a single frame each. + return trace.frames.length > 1; + }); + + // If all the traces contain only internal processing, preserve the last + // (top-most) one so that the chain isn't empty. + if (nonEmptyTraces.isEmpty && foldedTraces.isNotEmpty) { + return new Chain([foldedTraces.last]); + } + + return new Chain(nonEmptyTraces); + } + /// Converts [this] to a [Trace]. /// /// The trace version of a chain is just the concatenation of all the traces
diff --git a/pkgs/stack_trace/lib/src/trace.dart b/pkgs/stack_trace/lib/src/trace.dart index c1d751f..c540d78 100644 --- a/pkgs/stack_trace/lib/src/trace.dart +++ b/pkgs/stack_trace/lib/src/trace.dart
@@ -229,7 +229,7 @@ /// /// 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)) { + Trace foldFrames(bool predicate(Frame frame)) { var newFrames = <Frame>[]; for (var frame in frames.reversed) { if (!predicate(frame)) {
diff --git a/pkgs/stack_trace/pubspec.yaml b/pkgs/stack_trace/pubspec.yaml index 1e62ca6..1bd4776 100644 --- a/pkgs/stack_trace/pubspec.yaml +++ b/pkgs/stack_trace/pubspec.yaml
@@ -1,5 +1,5 @@ name: stack_trace -version: 0.9.2 +version: 0.9.2-dev author: "Dart Team <misc@dartlang.org>" homepage: http://www.dartlang.org description: >
diff --git a/pkgs/stack_trace/test/chain_test.dart b/pkgs/stack_trace/test/chain_test.dart index 9c16690..d9915eb 100644 --- a/pkgs/stack_trace/test/chain_test.dart +++ b/pkgs/stack_trace/test/chain_test.dart
@@ -387,7 +387,7 @@ 'dart:core Bar.baz\n')); }); - test("doesn't return in an empty chain", () { + test("doesn't return an empty chain", () { var chain = new Chain([ new Trace.parse( 'dart:core 10:11 Foo.bar\n' @@ -403,6 +403,67 @@ }); }); + group('Chain.foldFrames', () { + test('folds each trace', () { + var chain = new Chain([ + new Trace.parse( + 'a.dart 10:11 Foo.bar\n' + 'a.dart 10:11 Bar.baz\n' + 'b.dart 10:11 Bang.qux\n' + 'a.dart 10:11 Zip.zap\n' + 'a.dart 10:11 Zop.zoop'), + new Trace.parse( + 'a.dart 10:11 Foo.bar\n' + 'a.dart 10:11 Bar.baz\n' + 'a.dart 10:11 Bang.qux\n' + 'a.dart 10:11 Zip.zap\n' + 'b.dart 10:11 Zop.zoop') + ]); + + var folded = chain.foldFrames((frame) => frame.library == 'a.dart'); + expect(folded.toString(), equals( + 'a.dart 10:11 Bar.baz\n' + 'b.dart 10:11 Bang.qux\n' + 'a.dart 10:11 Zop.zoop\n' + '===== asynchronous gap ===========================\n' + 'a.dart 10:11 Zip.zap\n' + 'b.dart 10:11 Zop.zoop\n')); + }); + + test('eliminates completely-folded traces', () { + var chain = new Chain([ + new Trace.parse( + 'a.dart 10:11 Foo.bar\n' + 'b.dart 10:11 Bang.qux'), + new Trace.parse( + 'a.dart 10:11 Foo.bar\n' + 'a.dart 10:11 Bang.qux'), + new Trace.parse( + 'a.dart 10:11 Zip.zap\n' + 'b.dart 10:11 Zop.zoop') + ]); + + var folded = chain.foldFrames((frame) => frame.library == 'a.dart'); + expect(folded.toString(), equals( + 'a.dart 10:11 Foo.bar\n' + 'b.dart 10:11 Bang.qux\n' + '===== asynchronous gap ===========================\n' + 'a.dart 10:11 Zip.zap\n' + 'b.dart 10:11 Zop.zoop\n')); + }); + + test("doesn't return an empty trace", () { + var chain = new Chain([ + new Trace.parse( + 'a.dart 10:11 Foo.bar\n' + 'a.dart 10:11 Bang.qux') + ]); + + var folded = chain.foldFrames((frame) => frame.library == 'a.dart'); + expect(folded.toString(), equals('a.dart 10:11 Bang.qux\n')); + }); + }); + test('Chain.toTrace eliminates asynchronous gaps', () { var trace = new Chain([ new Trace.parse(