Add a "terse" argument to foldFrames(). R=rnystrom@google.com Review URL: https://codereview.chromium.org//917423002
diff --git a/pkgs/stack_trace/CHANGELOG.md b/pkgs/stack_trace/CHANGELOG.md index edd8ba6..47bd9b1 100644 --- a/pkgs/stack_trace/CHANGELOG.md +++ b/pkgs/stack_trace/CHANGELOG.md
@@ -1,3 +1,9 @@ +## 1.2.0 + +* Add a `terse` argument to `Trace.foldFrames()` and `Chain.foldFrames()`. This + allows them to inherit the behavior of `Trace.terse` and `Chain.terse` without + having to duplicate the logic. + ## 1.1.3 * Produce nicer-looking stack chains when using the VM's async/await
diff --git a/pkgs/stack_trace/lib/src/chain.dart b/pkgs/stack_trace/lib/src/chain.dart index f027025..99726f3 100644 --- a/pkgs/stack_trace/lib/src/chain.dart +++ b/pkgs/stack_trace/lib/src/chain.dart
@@ -136,21 +136,7 @@ /// /// This calls [Trace.terse] on every trace in [traces], and discards any /// trace that contain only internal frames. - Chain get terse { - var terseTraces = traces.map((trace) => trace.terse); - var nonEmptyTraces = terseTraces.where((trace) { - // Ignore traces that contain only internal processing. - 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 && terseTraces.isNotEmpty) { - return new Chain([terseTraces.last]); - } - - return new Chain(nonEmptyTraces); - } + Chain get terse => foldFrames((_) => false, terse: true); /// Returns a new [Chain] based on [this] where multiple stack frames matching /// [predicate] are folded together. @@ -161,8 +147,13 @@ /// /// 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)); + /// + /// If [terse] is true, this will also fold together frames from the core + /// library or from this package, and simplify core library frames as in + /// [Trace.terse]. + Chain foldFrames(bool predicate(Frame frame), {bool terse: false}) { + var foldedTraces = traces.map( + (trace) => trace.foldFrames(predicate, terse: terse)); var nonEmptyTraces = foldedTraces.where((trace) { // Ignore traces that contain only folded frames. These traces will be // folded into a single frame each.
diff --git a/pkgs/stack_trace/lib/src/trace.dart b/pkgs/stack_trace/lib/src/trace.dart index 5cd09e9..53dbe33 100644 --- a/pkgs/stack_trace/lib/src/trace.dart +++ b/pkgs/stack_trace/lib/src/trace.dart
@@ -203,36 +203,43 @@ /// core library or from this package, as in [foldFrames]. Remaining core /// library frames have their libraries, "-patch" suffixes, and line numbers /// removed. - Trace get terse { - return new Trace(foldFrames((frame) { - if (frame.isCore) return true; - if (frame.package == 'stack_trace') return true; - - // Ignore async stack frames without any line or column information. These - // come from the VM's async/await implementation and represent internal - // frames. They only ever show up in stack chains and are always - // surrounded by other traces that are actually useful, so we can just get - // rid of them. - // TODO(nweiz): Get rid of this logic some time after issue 22009 is - // fixed. - if (!frame.member.contains('<async>')) return false; - return frame.line == null; - }).frames.map((frame) { - if (!frame.isCore) return frame; - var library = frame.library.replaceAll(_terseRegExp, ''); - return new Frame(Uri.parse(library), null, null, frame.member); - })); - } + /// + /// For custom folding, see [foldFrames]. + Trace get terse => foldFrames((_) => false, terse: true); /// 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. + /// [predicate] are folded together. /// - /// 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 frame)) { - var newFrames = <Frame>[]; + /// 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. + /// + /// If [terse] is true, this will also fold together frames from the core + /// library or from this package, and simplify core library frames as in + /// [Trace.terse]. + Trace foldFrames(bool predicate(Frame frame), {bool terse: false}) { + if (terse) { + var oldPredicate = predicate; + predicate = (frame) { + if (oldPredicate(frame)) return true; + + if (frame.isCore) return true; + if (frame.package == 'stack_trace') return true; + + // Ignore async stack frames without any line or column information. + // These come from the VM's async/await implementation and represent + // internal frames. They only ever show up in stack chains and are + // always surrounded by other traces that are actually useful, so we can + // just get rid of them. + // TODO(nweiz): Get rid of this logic some time after issue 22009 is + // fixed. + if (!frame.member.contains('<async>')) return false; + return frame.line == null; + }; + } + + var newFrames = []; for (var frame in frames.reversed) { if (!predicate(frame)) { newFrames.add(frame); @@ -242,6 +249,14 @@ } } + if (terse) { + newFrames = newFrames.map((frame) { + if (!frame.isCore) return frame; + var library = frame.library.replaceAll(_terseRegExp, ''); + return new Frame(Uri.parse(library), null, null, frame.member); + }).toList(); + } + return new Trace(newFrames.reversed); }
diff --git a/pkgs/stack_trace/pubspec.yaml b/pkgs/stack_trace/pubspec.yaml index ded96f6..01b1550 100644 --- a/pkgs/stack_trace/pubspec.yaml +++ b/pkgs/stack_trace/pubspec.yaml
@@ -7,7 +7,7 @@ # # When the major version is upgraded, you *must* update that version constraint # in pub to stay in sync with this. -version: 1.1.3 +version: 1.2.0 author: "Dart Team <misc@dartlang.org>" homepage: http://github.com/dart-lang/stack_trace description: >
diff --git a/pkgs/stack_trace/test/chain_test.dart b/pkgs/stack_trace/test/chain_test.dart index 6af3c6b..54bc5f5 100644 --- a/pkgs/stack_trace/test/chain_test.dart +++ b/pkgs/stack_trace/test/chain_test.dart
@@ -572,6 +572,33 @@ 'b.dart 10:11 Zop.zoop\n')); }); + test('with terse: true, folds core frames as well', () { + var chain = new Chain([ + new Trace.parse( + 'a.dart 10:11 Foo.bar\n' + 'dart:async-patch/future.dart 10:11 Zip.zap\n' + 'b.dart 10:11 Bang.qux\n' + 'dart:core 10:11 Bar.baz\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', + terse: true); + expect(folded.toString(), equals( + 'dart:async Zip.zap\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(
diff --git a/pkgs/stack_trace/test/trace_test.dart b/pkgs/stack_trace/test/trace_test.dart index c8a848b..125ba95 100644 --- a/pkgs/stack_trace/test/trace_test.dart +++ b/pkgs/stack_trace/test/trace_test.dart
@@ -315,4 +315,24 @@ dart:async-patch/future.dart 9:11 fooBottom ''')); }); + + test('.foldFrames with terse: true, folds core frames as well', () { + var trace = new Trace.parse(''' +#0 notFoo (foo.dart:42:21) +#1 fooTop (bar.dart:0:2) +#2 coreBottom (dart:async/future.dart:0:2) +#3 alsoNotFoo (bar.dart:10:20) +#4 fooTop (foo.dart:9:11) +#5 coreBottom (dart:async-patch/future.dart:9:11) +'''); + + var folded = trace.foldFrames((frame) => frame.member.startsWith('foo'), + terse: true); + expect(folded.toString(), equals(''' +foo.dart 42:21 notFoo +dart:async coreBottom +bar.dart 10:20 alsoNotFoo +dart:async coreBottom +''')); + }); }