| // Copyright (c) 2015, 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. | |
| import 'dart:async'; | |
| import 'package:path/path.dart' as p; | |
| import 'package:stack_trace/stack_trace.dart'; | |
| import 'package:test/test.dart'; | |
| import 'utils.dart'; | |
| void main() { | |
| group('Chain.parse()', () { | |
| test('parses a real Chain', () async { | |
| // ignore: only_throw_errors | |
| final chain = await captureFuture(() => inMicrotask(() => throw 'error')); | |
| expect( | |
| Chain.parse(chain.toString()).toString(), | |
| equals(chain.toString()), | |
| ); | |
| }); | |
| test('parses an empty string', () { | |
| var chain = Chain.parse(''); | |
| expect(chain.traces, isEmpty); | |
| }); | |
| test('parses a chain containing empty traces', () { | |
| var chain = | |
| Chain.parse('===== asynchronous gap ===========================\n' | |
| '===== asynchronous gap ===========================\n'); | |
| expect(chain.traces, hasLength(3)); | |
| expect(chain.traces[0].frames, isEmpty); | |
| expect(chain.traces[1].frames, isEmpty); | |
| expect(chain.traces[2].frames, isEmpty); | |
| }); | |
| test('parses a chain with VM gaps', () { | |
| final chain = | |
| Chain.parse('#1 MyClass.run (package:my_lib.dart:134:5)\n' | |
| '<asynchronous suspension>\n' | |
| '#2 main (file:///my_app.dart:9:3)\n' | |
| '<asynchronous suspension>\n'); | |
| expect(chain.traces, hasLength(2)); | |
| expect(chain.traces[0].frames, hasLength(1)); | |
| expect(chain.traces[0].frames[0].toString(), | |
| equals('package:my_lib.dart 134:5 in MyClass.run')); | |
| expect(chain.traces[1].frames, hasLength(1)); | |
| expect( | |
| chain.traces[1].frames[0].toString(), | |
| anyOf( | |
| equals('/my_app.dart 9:3 in main'), // VM | |
| equals('file:///my_app.dart 9:3 in main'), // Browser | |
| ), | |
| ); | |
| }); | |
| }); | |
| group('Chain.capture()', () { | |
| test('with onError blocks errors', () { | |
| Chain.capture(() { | |
| return Future<void>.error('oh no'); | |
| }, onError: expectAsync2((error, chain) { | |
| expect(error, equals('oh no')); | |
| expect(chain, isA<Chain>()); | |
| })).then(expectAsync1((_) {}, count: 0), | |
| onError: expectAsync2((_, __) {}, count: 0)); | |
| }); | |
| test('with no onError blocks errors', () { | |
| runZonedGuarded(() { | |
| Chain.capture(() => Future<void>.error('oh no')).then( | |
| expectAsync1((_) {}, count: 0), | |
| onError: expectAsync2((_, __) {}, count: 0)); | |
| }, expectAsync2((error, chain) { | |
| expect(error, equals('oh no')); | |
| expect(chain, isA<Chain>()); | |
| })); | |
| }); | |
| test("with errorZone: false doesn't block errors", () { | |
| expect(Chain.capture(() => Future<void>.error('oh no'), errorZone: false), | |
| throwsA('oh no')); | |
| }); | |
| test("doesn't allow onError and errorZone: false", () { | |
| expect(() => Chain.capture(() {}, onError: (_, __) {}, errorZone: false), | |
| throwsArgumentError); | |
| }); | |
| group('with when: false', () { | |
| test("with no onError doesn't block errors", () { | |
| expect(Chain.capture(() => Future<void>.error('oh no'), when: false), | |
| throwsA('oh no')); | |
| }); | |
| test('with onError blocks errors', () { | |
| Chain.capture(() { | |
| return Future<void>.error('oh no'); | |
| }, onError: expectAsync2((error, chain) { | |
| expect(error, equals('oh no')); | |
| expect(chain, isA<Chain>()); | |
| }), when: false); | |
| }); | |
| test("doesn't enable chain-tracking", () { | |
| return Chain.disable(() { | |
| return Chain.capture(() { | |
| var completer = Completer<Chain>(); | |
| inMicrotask(() { | |
| completer.complete(Chain.current()); | |
| }); | |
| return completer.future.then((chain) { | |
| expect(chain.traces, hasLength(1)); | |
| }); | |
| }, when: false); | |
| }); | |
| }); | |
| }); | |
| }); | |
| test('Chain.capture() with custom zoneValues', () { | |
| return Chain.capture(() { | |
| expect(Zone.current[#enabled], true); | |
| }, zoneValues: {#enabled: true}); | |
| }); | |
| group('Chain.disable()', () { | |
| test('disables chain-tracking', () { | |
| return Chain.disable(() { | |
| var completer = Completer<Chain>(); | |
| inMicrotask(() => completer.complete(Chain.current())); | |
| return completer.future.then((chain) { | |
| expect(chain.traces, hasLength(1)); | |
| }); | |
| }); | |
| }); | |
| test('Chain.capture() re-enables chain-tracking', () { | |
| return Chain.disable(() { | |
| return Chain.capture(() { | |
| var completer = Completer<Chain>(); | |
| inMicrotask(() => completer.complete(Chain.current())); | |
| return completer.future.then((chain) { | |
| expect(chain.traces, hasLength(2)); | |
| }); | |
| }); | |
| }); | |
| }); | |
| test('preserves parent zones of the capture zone', () { | |
| // The outer disable call turns off the test package's chain-tracking. | |
| return Chain.disable(() { | |
| return runZoned(() { | |
| return Chain.capture(() { | |
| expect(Chain.disable(() => Zone.current[#enabled]), isTrue); | |
| }); | |
| }, zoneValues: {#enabled: true}); | |
| }); | |
| }); | |
| test('preserves child zones of the capture zone', () { | |
| // The outer disable call turns off the test package's chain-tracking. | |
| return Chain.disable(() { | |
| return Chain.capture(() { | |
| return runZoned(() { | |
| expect(Chain.disable(() => Zone.current[#enabled]), isTrue); | |
| }, zoneValues: {#enabled: true}); | |
| }); | |
| }); | |
| }); | |
| test("with when: false doesn't disable", () { | |
| return Chain.capture(() { | |
| return Chain.disable(() { | |
| var completer = Completer<Chain>(); | |
| inMicrotask(() => completer.complete(Chain.current())); | |
| return completer.future.then((chain) { | |
| expect(chain.traces, hasLength(2)); | |
| }); | |
| }, when: false); | |
| }); | |
| }); | |
| }); | |
| test('toString() ensures that all traces are aligned', () { | |
| var chain = Chain([ | |
| Trace.parse('short 10:11 Foo.bar\n'), | |
| Trace.parse('loooooooooooong 10:11 Zop.zoop') | |
| ]); | |
| expect( | |
| chain.toString(), | |
| equals('short 10:11 Foo.bar\n' | |
| '===== asynchronous gap ===========================\n' | |
| 'loooooooooooong 10:11 Zop.zoop\n')); | |
| }); | |
| var userSlashCode = p.join('user', 'code.dart'); | |
| group('Chain.terse', () { | |
| test('makes each trace terse', () { | |
| var chain = Chain([ | |
| Trace.parse('dart:core 10:11 Foo.bar\n' | |
| 'dart:core 10:11 Bar.baz\n' | |
| 'user/code.dart 10:11 Bang.qux\n' | |
| 'dart:core 10:11 Zip.zap\n' | |
| 'dart:core 10:11 Zop.zoop'), | |
| Trace.parse('user/code.dart 10:11 Bang.qux\n' | |
| 'dart:core 10:11 Foo.bar\n' | |
| 'package:stack_trace/stack_trace.dart 10:11 Bar.baz\n' | |
| 'dart:core 10:11 Zip.zap\n' | |
| 'user/code.dart 10:11 Zop.zoop') | |
| ]); | |
| expect( | |
| chain.terse.toString(), | |
| equals('dart:core Bar.baz\n' | |
| '$userSlashCode 10:11 Bang.qux\n' | |
| '===== asynchronous gap ===========================\n' | |
| '$userSlashCode 10:11 Bang.qux\n' | |
| 'dart:core Zip.zap\n' | |
| '$userSlashCode 10:11 Zop.zoop\n')); | |
| }); | |
| test('eliminates internal-only traces', () { | |
| var chain = Chain([ | |
| Trace.parse('user/code.dart 10:11 Foo.bar\n' | |
| 'dart:core 10:11 Bar.baz'), | |
| Trace.parse('dart:core 10:11 Foo.bar\n' | |
| 'package:stack_trace/stack_trace.dart 10:11 Bar.baz\n' | |
| 'dart:core 10:11 Zip.zap'), | |
| Trace.parse('user/code.dart 10:11 Foo.bar\n' | |
| 'dart:core 10:11 Bar.baz') | |
| ]); | |
| expect( | |
| chain.terse.toString(), | |
| equals('$userSlashCode 10:11 Foo.bar\n' | |
| '===== asynchronous gap ===========================\n' | |
| '$userSlashCode 10:11 Foo.bar\n')); | |
| }); | |
| test("doesn't return an empty chain", () { | |
| var chain = Chain([ | |
| Trace.parse('dart:core 10:11 Foo.bar\n' | |
| 'package:stack_trace/stack_trace.dart 10:11 Bar.baz\n' | |
| 'dart:core 10:11 Zip.zap'), | |
| Trace.parse('dart:core 10:11 A.b\n' | |
| 'package:stack_trace/stack_trace.dart 10:11 C.d\n' | |
| 'dart:core 10:11 E.f') | |
| ]); | |
| expect(chain.terse.toString(), equals('dart:core E.f\n')); | |
| }); | |
| // Regression test for #9 | |
| test("doesn't crash on empty traces", () { | |
| var chain = Chain([ | |
| Trace.parse('user/code.dart 10:11 Bang.qux'), | |
| Trace([]), | |
| Trace.parse('user/code.dart 10:11 Bang.qux') | |
| ]); | |
| expect( | |
| chain.terse.toString(), | |
| equals('$userSlashCode 10:11 Bang.qux\n' | |
| '===== asynchronous gap ===========================\n' | |
| '$userSlashCode 10:11 Bang.qux\n')); | |
| }); | |
| }); | |
| group('Chain.foldFrames', () { | |
| test('folds each trace', () { | |
| var chain = Chain([ | |
| 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'), | |
| 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('with terse: true, folds core frames as well', () { | |
| var chain = Chain([ | |
| 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'), | |
| 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' | |
| '===== asynchronous gap ===========================\n' | |
| 'a.dart Zip.zap\n' | |
| 'b.dart 10:11 Zop.zoop\n')); | |
| }); | |
| test('eliminates completely-folded traces', () { | |
| var chain = Chain([ | |
| Trace.parse('a.dart 10:11 Foo.bar\n' | |
| 'b.dart 10:11 Bang.qux'), | |
| Trace.parse('a.dart 10:11 Foo.bar\n' | |
| 'a.dart 10:11 Bang.qux'), | |
| 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 = Chain([ | |
| 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 = Chain([ | |
| Trace.parse('user/code.dart 10:11 Foo.bar\n' | |
| 'dart:core 10:11 Bar.baz'), | |
| Trace.parse('user/code.dart 10:11 Foo.bar\n' | |
| 'dart:core 10:11 Bar.baz') | |
| ]).toTrace(); | |
| expect( | |
| trace.toString(), | |
| equals('$userSlashCode 10:11 Foo.bar\n' | |
| 'dart:core 10:11 Bar.baz\n' | |
| '$userSlashCode 10:11 Foo.bar\n' | |
| 'dart:core 10:11 Bar.baz\n')); | |
| }); | |
| } |