| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library chain_test; | 5 library chain_test; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 | 8 |
| 9 import 'package:path/path.dart' as p; | 9 import 'package:path/path.dart' as p; |
| 10 import 'package:stack_trace/stack_trace.dart'; | 10 import 'package:stack_trace/stack_trace.dart'; |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 86 // is important; programmers expect stack trace memory consumption to be | 86 // is important; programmers expect stack trace memory consumption to be |
| 87 // O(depth of program), not O(length of program). | 87 // O(depth of program), not O(length of program). |
| 88 expect(chain.traces, hasLength(2)); | 88 expect(chain.traces, hasLength(2)); |
| 89 | 89 |
| 90 expect(chain.traces[0].frames.first, frameMember(startsWith('main'))); | 90 expect(chain.traces[0].frames.first, frameMember(startsWith('main'))); |
| 91 expect(chain.traces[1].frames, | 91 expect(chain.traces[1].frames, |
| 92 contains(frameMember(startsWith('inFutureChain')))); | 92 contains(frameMember(startsWith('inFutureChain')))); |
| 93 }); | 93 }); |
| 94 }); | 94 }); |
| 95 | 95 |
| 96 test('thrown in new Future()', () { |
| 97 return captureFuture(() => inNewFuture(() => throw 'error')) |
| 98 .then((chain) { |
| 99 expect(chain.traces, hasLength(3)); |
| 100 expect(chain.traces[0].frames.first, frameMember(startsWith('main'))); |
| 101 |
| 102 // The second trace is the one captured by |
| 103 // [StackZoneSpecification.errorCallback]. Because that runs |
| 104 // asynchronously within [new Future], it doesn't actually refer to the |
| 105 // source file at all. |
| 106 expect(chain.traces[1].frames, |
| 107 everyElement(frameLibrary(isNot(contains('chain_test'))))); |
| 108 |
| 109 expect(chain.traces[2].frames, |
| 110 contains(frameMember(startsWith('inNewFuture')))); |
| 111 }); |
| 112 }); |
| 113 |
| 114 test('thrown in new Future.sync()', () { |
| 115 return captureFuture(() { |
| 116 inMicrotask(() => inSyncFuture(() => throw 'error')); |
| 117 }).then((chain) { |
| 118 expect(chain.traces, hasLength(3)); |
| 119 expect(chain.traces[0].frames.first, frameMember(startsWith('main'))); |
| 120 expect(chain.traces[1].frames, |
| 121 contains(frameMember(startsWith('inSyncFuture')))); |
| 122 expect(chain.traces[2].frames, |
| 123 contains(frameMember(startsWith('inMicrotask')))); |
| 124 }); |
| 125 }); |
| 126 |
| 96 test('multiple times', () { | 127 test('multiple times', () { |
| 97 var completer = new Completer(); | 128 var completer = new Completer(); |
| 98 var first = true; | 129 var first = true; |
| 99 | 130 |
| 100 Chain.capture(() { | 131 Chain.capture(() { |
| 101 inMicrotask(() => throw 'first error'); | 132 inMicrotask(() => throw 'first error'); |
| 102 inPeriodicTimer(() => throw 'second error'); | 133 inPeriodicTimer(() => throw 'second error'); |
| 103 }, onError: (error, chain) { | 134 }, onError: (error, chain) { |
| 104 try { | 135 try { |
| 105 if (first) { | 136 if (first) { |
| 106 expect(error, equals('first error')); | 137 expect(error, equals('first error')); |
| 107 expect(chain.traces[1].frames, | 138 expect(chain.traces[1].frames, |
| 108 contains(frameMember(startsWith('inMicrotask')))); | 139 contains(frameMember(startsWith('inMicrotask')))); |
| 109 first = false; | 140 first = false; |
| 110 } else { | 141 } else { |
| 111 expect(error, equals('second error')); | 142 expect(error, equals('second error')); |
| 112 expect(chain.traces[1].frames, | 143 expect(chain.traces[1].frames, |
| 113 contains(frameMember(startsWith('inPeriodicTimer')))); | 144 contains(frameMember(startsWith('inPeriodicTimer')))); |
| 114 completer.complete(); | 145 completer.complete(); |
| 115 } | 146 } |
| 116 } catch (error, stackTrace) { | 147 } catch (error, stackTrace) { |
| 117 completer.completeError(error, stackTrace); | 148 completer.completeError(error, stackTrace); |
| 118 } | 149 } |
| 119 }); | 150 }); |
| 120 | 151 |
| 121 return completer.future; | 152 return completer.future; |
| 122 }); | 153 }); |
| 123 | 154 |
| 155 test('passed to a completer', () { |
| 156 var trace = new Trace.current(); |
| 157 return captureFuture(() { |
| 158 inMicrotask(() => completerErrorFuture(trace)); |
| 159 }).then((chain) { |
| 160 expect(chain.traces, hasLength(3)); |
| 161 |
| 162 // The first trace is the trace that was manually reported for the |
| 163 // error. |
| 164 expect(chain.traces.first.toString(), equals(trace.toString())); |
| 165 |
| 166 // The second trace is the trace that was captured when |
| 167 // [Completer.addError] was called. |
| 168 expect(chain.traces[1].frames, |
| 169 contains(frameMember(startsWith('completerErrorFuture')))); |
| 170 |
| 171 // The third trace is the automatically-captured trace from when the |
| 172 // microtask was scheduled. |
| 173 expect(chain.traces[2].frames, |
| 174 contains(frameMember(startsWith('inMicrotask')))); |
| 175 }); |
| 176 }); |
| 177 |
| 178 test('passed to a completer with no stack trace', () { |
| 179 return captureFuture(() { |
| 180 inMicrotask(() => completerErrorFuture()); |
| 181 }).then((chain) { |
| 182 expect(chain.traces, hasLength(2)); |
| 183 |
| 184 // The first trace is the one captured when [Completer.addError] was |
| 185 // called. |
| 186 expect(chain.traces[0].frames, |
| 187 contains(frameMember(startsWith('completerErrorFuture')))); |
| 188 |
| 189 // The second trace is the automatically-captured trace from when the |
| 190 // microtask was scheduled. |
| 191 expect(chain.traces[1].frames, |
| 192 contains(frameMember(startsWith('inMicrotask')))); |
| 193 }); |
| 194 }); |
| 195 |
| 196 test('passed to a stream controller', () { |
| 197 var trace = new Trace.current(); |
| 198 return captureFuture(() { |
| 199 inMicrotask(() => controllerErrorStream(trace).listen(null)); |
| 200 }).then((chain) { |
| 201 expect(chain.traces, hasLength(3)); |
| 202 expect(chain.traces.first.toString(), equals(trace.toString())); |
| 203 expect(chain.traces[1].frames, |
| 204 contains(frameMember(startsWith('controllerErrorStream')))); |
| 205 expect(chain.traces[2].frames, |
| 206 contains(frameMember(startsWith('inMicrotask')))); |
| 207 }); |
| 208 }); |
| 209 |
| 210 test('passed to a stream controller with no stack trace', () { |
| 211 return captureFuture(() { |
| 212 inMicrotask(() => controllerErrorStream().listen(null)); |
| 213 }).then((chain) { |
| 214 expect(chain.traces, hasLength(2)); |
| 215 expect(chain.traces[0].frames, |
| 216 contains(frameMember(startsWith('controllerErrorStream')))); |
| 217 expect(chain.traces[1].frames, |
| 218 contains(frameMember(startsWith('inMicrotask')))); |
| 219 }); |
| 220 }); |
| 221 |
| 124 test('and relays them to the parent zone', () { | 222 test('and relays them to the parent zone', () { |
| 125 var completer = new Completer(); | 223 var completer = new Completer(); |
| 126 | 224 |
| 127 runZoned(() { | 225 runZoned(() { |
| 128 Chain.capture(() { | 226 Chain.capture(() { |
| 129 inMicrotask(() => throw 'error'); | 227 inMicrotask(() => throw 'error'); |
| 130 }, onError: (error, chain) { | 228 }, onError: (error, chain) { |
| 131 expect(error, equals('error')); | 229 expect(error, equals('error')); |
| 132 expect(chain.traces[1].frames, | 230 expect(chain.traces[1].frames, |
| 133 contains(frameMember(startsWith('inMicrotask')))); | 231 contains(frameMember(startsWith('inMicrotask')))); |
| (...skipping 385 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 519 ]).toTrace(); | 617 ]).toTrace(); |
| 520 | 618 |
| 521 expect(trace.toString(), equals( | 619 expect(trace.toString(), equals( |
| 522 '$userSlashCode 10:11 Foo.bar\n' | 620 '$userSlashCode 10:11 Foo.bar\n' |
| 523 'dart:core 10:11 Bar.baz\n' | 621 'dart:core 10:11 Bar.baz\n' |
| 524 '$userSlashCode 10:11 Foo.bar\n' | 622 '$userSlashCode 10:11 Foo.bar\n' |
| 525 'dart:core 10:11 Bar.baz\n')); | 623 'dart:core 10:11 Bar.baz\n')); |
| 526 }); | 624 }); |
| 527 | 625 |
| 528 group('Chain.track(Future)', () { | 626 group('Chain.track(Future)', () { |
| 529 test('associates the current chain with a manually-reported exception with ' | |
| 530 'a stack trace', () { | |
| 531 var trace = new Trace.current(); | |
| 532 return captureFuture(() { | |
| 533 inMicrotask(() => trackedErrorFuture(trace)); | |
| 534 }).then((chain) { | |
| 535 expect(chain.traces, hasLength(3)); | |
| 536 | |
| 537 // The first trace is the trace that was manually reported for the | |
| 538 // error. | |
| 539 expect(chain.traces.first.toString(), equals(trace.toString())); | |
| 540 | |
| 541 // The second trace is the trace that was captured when [Chain.track] | |
| 542 // was called. | |
| 543 expect(chain.traces[1].frames.first, | |
| 544 frameMember(startsWith('trackedErrorFuture'))); | |
| 545 | |
| 546 // The third trace is the automatically-captured trace from when the | |
| 547 // microtask was scheduled. | |
| 548 expect(chain.traces[2].frames, | |
| 549 contains(frameMember(startsWith('inMicrotask')))); | |
| 550 }); | |
| 551 }); | |
| 552 | |
| 553 test('associates the current chain with a manually-reported exception with ' | |
| 554 'no stack trace', () { | |
| 555 return captureFuture(() { | |
| 556 inMicrotask(() => trackedErrorFuture()); | |
| 557 }).then((chain) { | |
| 558 expect(chain.traces, hasLength(3)); | |
| 559 | |
| 560 // The first trace is the one captured by | |
| 561 // [StackZoneSpecification.trackFuture], which should contain only | |
| 562 // stack_trace and dart: frames. | |
| 563 expect(chain.traces.first.frames, | |
| 564 everyElement(frameLibrary(isNot(contains('chain_test'))))); | |
| 565 | |
| 566 expect(chain.traces[1].frames.first, | |
| 567 frameMember(startsWith('trackedErrorFuture'))); | |
| 568 expect(chain.traces[2].frames, | |
| 569 contains(frameMember(startsWith('inMicrotask')))); | |
| 570 }); | |
| 571 }); | |
| 572 | |
| 573 test('forwards the future value within Chain.capture()', () { | 627 test('forwards the future value within Chain.capture()', () { |
| 574 Chain.capture(() { | 628 Chain.capture(() { |
| 575 expect(Chain.track(new Future.value('value')), | 629 expect(Chain.track(new Future.value('value')), |
| 576 completion(equals('value'))); | 630 completion(equals('value'))); |
| 577 | 631 |
| 578 var trace = new Trace.current(); | 632 var trace = new Trace.current(); |
| 579 expect(Chain.track(new Future.error('error', trace)) | 633 expect(Chain.track(new Future.error('error', trace)) |
| 580 .catchError((e, stackTrace) { | 634 .catchError((e, stackTrace) { |
| 581 expect(e, equals('error')); | 635 expect(e, equals('error')); |
| 582 expect(stackTrace.toString(), equals(trace.toString())); | 636 expect(stackTrace.toString(), equals(trace.toString())); |
| 583 }), completes); | 637 }), completes); |
| 584 }); | 638 }); |
| 585 }); | 639 }); |
| 586 | 640 |
| 587 test('forwards the future value outside of Chain.capture()', () { | 641 test('forwards the future value outside of Chain.capture()', () { |
| 588 expect(Chain.track(new Future.value('value')), | 642 expect(Chain.track(new Future.value('value')), |
| 589 completion(equals('value'))); | 643 completion(equals('value'))); |
| 590 | 644 |
| 591 var trace = new Trace.current(); | 645 var trace = new Trace.current(); |
| 592 expect(Chain.track(new Future.error('error', trace)) | 646 expect(Chain.track(new Future.error('error', trace)) |
| 593 .catchError((e, stackTrace) { | 647 .catchError((e, stackTrace) { |
| 594 expect(e, equals('error')); | 648 expect(e, equals('error')); |
| 595 expect(stackTrace.toString(), equals(trace.toString())); | 649 expect(stackTrace.toString(), equals(trace.toString())); |
| 596 }), completes); | 650 }), completes); |
| 597 }); | 651 }); |
| 598 }); | 652 }); |
| 599 | 653 |
| 600 group('Chain.track(Stream)', () { | 654 group('Chain.track(Stream)', () { |
| 601 test('associates the current chain with a manually-reported exception with ' | |
| 602 'a stack trace', () { | |
| 603 var trace = new Trace.current(); | |
| 604 return captureFuture(() { | |
| 605 inMicrotask(() => trackedErrorStream(trace).listen(null)); | |
| 606 }).then((chain) { | |
| 607 expect(chain.traces, hasLength(3)); | |
| 608 expect(chain.traces.first.toString(), equals(trace.toString())); | |
| 609 expect(chain.traces[1].frames.first, | |
| 610 frameMember(startsWith('trackedErrorStream'))); | |
| 611 expect(chain.traces[2].frames, | |
| 612 contains(frameMember(startsWith('inMicrotask')))); | |
| 613 }); | |
| 614 }); | |
| 615 | |
| 616 test('associates the current chain with a manually-reported exception with ' | |
| 617 'no stack trace', () { | |
| 618 return captureFuture(() { | |
| 619 inMicrotask(() => trackedErrorStream().listen(null)); | |
| 620 }).then((chain) { | |
| 621 expect(chain.traces, hasLength(3)); | |
| 622 expect(chain.traces.first.frames, | |
| 623 everyElement(frameLibrary(isNot(contains('chain_test'))))); | |
| 624 expect(chain.traces[1].frames.first, | |
| 625 frameMember(startsWith('trackedErrorStream'))); | |
| 626 expect(chain.traces[2].frames, | |
| 627 contains(frameMember(startsWith('inMicrotask')))); | |
| 628 }); | |
| 629 }); | |
| 630 | |
| 631 test('forwards stream values within Chain.capture()', () { | 655 test('forwards stream values within Chain.capture()', () { |
| 632 Chain.capture(() { | 656 Chain.capture(() { |
| 633 var controller = new StreamController() | 657 var controller = new StreamController() |
| 634 ..add(1)..add(2)..add(3)..close(); | 658 ..add(1)..add(2)..add(3)..close(); |
| 635 expect(Chain.track(controller.stream).toList(), | 659 expect(Chain.track(controller.stream).toList(), |
| 636 completion(equals([1, 2, 3]))); | 660 completion(equals([1, 2, 3]))); |
| 637 | 661 |
| 638 var trace = new Trace.current(); | 662 var trace = new Trace.current(); |
| 639 controller = new StreamController()..addError('error', trace); | 663 controller = new StreamController()..addError('error', trace); |
| 640 expect(Chain.track(controller.stream).toList() | 664 expect(Chain.track(controller.stream).toList() |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 685 void inFutureChain(callback()) { | 709 void inFutureChain(callback()) { |
| 686 new Future(() {}) | 710 new Future(() {}) |
| 687 .then((_) => new Future(() {})) | 711 .then((_) => new Future(() {})) |
| 688 .then((_) => new Future(() {})) | 712 .then((_) => new Future(() {})) |
| 689 .then((_) => new Future(() {})) | 713 .then((_) => new Future(() {})) |
| 690 .then((_) => new Future(() {})) | 714 .then((_) => new Future(() {})) |
| 691 .then((_) => callback()) | 715 .then((_) => callback()) |
| 692 .then((_) => new Future(() {})); | 716 .then((_) => new Future(() {})); |
| 693 } | 717 } |
| 694 | 718 |
| 695 /// Returns a Future that completes to an error and is wrapped in [Chain.track]. | 719 void inNewFuture(callback()) { |
| 720 new Future(callback); |
| 721 } |
| 722 |
| 723 void inSyncFuture(callback()) { |
| 724 new Future.sync(callback); |
| 725 } |
| 726 |
| 727 /// Returns a Future that completes to an error using a completer. |
| 696 /// | 728 /// |
| 697 /// If [trace] is passed, it's used as the stack trace for the error. | 729 /// If [trace] is passed, it's used as the stack trace for the error. |
| 698 Future trackedErrorFuture([StackTrace trace]) { | 730 Future completerErrorFuture([StackTrace trace]) { |
| 699 var completer = new Completer(); | 731 var completer = new Completer(); |
| 700 completer.completeError('error', trace); | 732 completer.completeError('error', trace); |
| 701 return Chain.track(completer.future); | 733 return completer.future; |
| 702 } | 734 } |
| 703 | 735 |
| 704 /// Returns a Stream that emits an error and is wrapped in [Chain.track]. | 736 /// Returns a Stream that emits an error using a controller. |
| 705 /// | 737 /// |
| 706 /// If [trace] is passed, it's used as the stack trace for the error. | 738 /// If [trace] is passed, it's used as the stack trace for the error. |
| 707 Stream trackedErrorStream([StackTrace trace]) { | 739 Stream controllerErrorStream([StackTrace trace]) { |
| 708 var controller = new StreamController(); | 740 var controller = new StreamController(); |
| 709 controller.addError('error', trace); | 741 controller.addError('error', trace); |
| 710 return Chain.track(controller.stream); | 742 return controller.stream; |
| 711 } | 743 } |
| 712 | 744 |
| 713 /// Runs [callback] within [asyncFn], then converts any errors raised into a | 745 /// Runs [callback] within [asyncFn], then converts any errors raised into a |
| 714 /// [Chain] with [Chain.forTrace]. | 746 /// [Chain] with [Chain.forTrace]. |
| 715 Future<Chain> chainForTrace(asyncFn(callback()), callback()) { | 747 Future<Chain> chainForTrace(asyncFn(callback()), callback()) { |
| 716 var completer = new Completer(); | 748 var completer = new Completer(); |
| 717 asyncFn(() { | 749 asyncFn(() { |
| 718 // We use `new Future.value().then(...)` here as opposed to [new Future] or | 750 // We use `new Future.value().then(...)` here as opposed to [new Future] or |
| 719 // [new Future.sync] because those methods don't pass the exception through | 751 // [new Future.sync] because those methods don't pass the exception through |
| 720 // the zone specification before propagating it, so there's no chance to | 752 // the zone specification before propagating it, so there's no chance to |
| (...skipping 10 matching lines...) Expand all Loading... |
| 731 /// | 763 /// |
| 732 /// [callback] is expected to throw the string `"error"`. | 764 /// [callback] is expected to throw the string `"error"`. |
| 733 Future<Chain> captureFuture(callback()) { | 765 Future<Chain> captureFuture(callback()) { |
| 734 var completer = new Completer<Chain>(); | 766 var completer = new Completer<Chain>(); |
| 735 Chain.capture(callback, onError: (error, chain) { | 767 Chain.capture(callback, onError: (error, chain) { |
| 736 expect(error, equals('error')); | 768 expect(error, equals('error')); |
| 737 completer.complete(chain); | 769 completer.complete(chain); |
| 738 }); | 770 }); |
| 739 return completer.future; | 771 return completer.future; |
| 740 } | 772 } |
| OLD | NEW |