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 |