OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, 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 // dart2js currently doesn't support chain-capturing. See sdk#15171. | 5 // VM chain tests can rely on stronger guarantees about the contents of the |
6 @TestOn('vm') | 6 // stack traces than dart2js. |
| 7 @TestOn('dart-vm') |
7 | 8 |
8 import 'dart:async'; | 9 import 'dart:async'; |
9 | 10 |
10 import 'package:path/path.dart' as p; | |
11 import 'package:stack_trace/stack_trace.dart'; | 11 import 'package:stack_trace/stack_trace.dart'; |
12 import 'package:test/test.dart'; | 12 import 'package:test/test.dart'; |
13 | 13 |
| 14 import '../utils.dart'; |
14 import 'utils.dart'; | 15 import 'utils.dart'; |
15 | 16 |
16 void main() { | 17 void main() { |
17 group('capture() with onError catches exceptions', () { | 18 group('capture() with onError catches exceptions', () { |
18 test('thrown synchronously', () { | 19 test('thrown synchronously', () { |
19 return captureFuture(() => throw 'error') | 20 return captureFuture(() => throw 'error') |
20 .then((chain) { | 21 .then((chain) { |
21 expect(chain.traces, hasLength(1)); | 22 expect(chain.traces, hasLength(1)); |
22 expect(chain.traces.single.frames.first, | 23 expect(chain.traces.single.frames.first, |
23 frameMember(startsWith('main'))); | 24 frameMember(startsWith('main'))); |
(...skipping 446 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
470 } catch (_, stackTrace) { | 471 } catch (_, stackTrace) { |
471 trace = stackTrace; | 472 trace = stackTrace; |
472 return new Chain.forTrace(stackTrace); | 473 return new Chain.forTrace(stackTrace); |
473 } | 474 } |
474 }); | 475 }); |
475 | 476 |
476 expect(chain.traces, hasLength(1)); | 477 expect(chain.traces, hasLength(1)); |
477 expect(chain.traces.first.toString(), | 478 expect(chain.traces.first.toString(), |
478 equals(new Trace.from(trace).toString())); | 479 equals(new Trace.from(trace).toString())); |
479 }); | 480 }); |
480 | |
481 group('Chain.parse()', () { | |
482 test('parses a real Chain', () { | |
483 return captureFuture(() => inMicrotask(() => throw 'error')) | |
484 .then((chain) { | |
485 expect(new Chain.parse(chain.toString()).toString(), | |
486 equals(chain.toString())); | |
487 }); | |
488 }); | |
489 | |
490 test('parses an empty string', () { | |
491 var chain = new Chain.parse(''); | |
492 expect(chain.traces, isEmpty); | |
493 }); | |
494 | |
495 test('parses a chain containing empty traces', () { | |
496 var chain = new Chain.parse( | |
497 '===== asynchronous gap ===========================\n' | |
498 '===== asynchronous gap ===========================\n'); | |
499 expect(chain.traces, hasLength(3)); | |
500 expect(chain.traces[0].frames, isEmpty); | |
501 expect(chain.traces[1].frames, isEmpty); | |
502 expect(chain.traces[2].frames, isEmpty); | |
503 }); | |
504 }); | |
505 | |
506 test("toString() ensures that all traces are aligned", () { | |
507 var chain = new Chain([ | |
508 new Trace.parse('short 10:11 Foo.bar\n'), | |
509 new Trace.parse('loooooooooooong 10:11 Zop.zoop') | |
510 ]); | |
511 | |
512 expect(chain.toString(), equals( | |
513 'short 10:11 Foo.bar\n' | |
514 '===== asynchronous gap ===========================\n' | |
515 'loooooooooooong 10:11 Zop.zoop\n')); | |
516 }); | |
517 | |
518 var userSlashCode = p.join('user', 'code.dart'); | |
519 group('Chain.terse', () { | |
520 test('makes each trace terse', () { | |
521 var chain = new Chain([ | |
522 new Trace.parse( | |
523 'dart:core 10:11 Foo.bar\n' | |
524 'dart:core 10:11 Bar.baz\n' | |
525 'user/code.dart 10:11 Bang.qux\n' | |
526 'dart:core 10:11 Zip.zap\n' | |
527 'dart:core 10:11 Zop.zoop'), | |
528 new Trace.parse( | |
529 'user/code.dart 10:11 Bang.qux\n' | |
530 'dart:core 10:11 Foo.bar\n' | |
531 'package:stack_trace/stack_trace.dart 10:11 Bar.baz\n' | |
532 'dart:core 10:11 Zip.zap\n' | |
533 'user/code.dart 10:11 Zop.zoop') | |
534 ]); | |
535 | |
536 expect(chain.terse.toString(), equals( | |
537 'dart:core Bar.baz\n' | |
538 '$userSlashCode 10:11 Bang.qux\n' | |
539 '===== asynchronous gap ===========================\n' | |
540 '$userSlashCode 10:11 Bang.qux\n' | |
541 'dart:core Zip.zap\n' | |
542 '$userSlashCode 10:11 Zop.zoop\n')); | |
543 }); | |
544 | |
545 test('eliminates internal-only traces', () { | |
546 var chain = new Chain([ | |
547 new Trace.parse( | |
548 'user/code.dart 10:11 Foo.bar\n' | |
549 'dart:core 10:11 Bar.baz'), | |
550 new Trace.parse( | |
551 'dart:core 10:11 Foo.bar\n' | |
552 'package:stack_trace/stack_trace.dart 10:11 Bar.baz\n' | |
553 'dart:core 10:11 Zip.zap'), | |
554 new Trace.parse( | |
555 'user/code.dart 10:11 Foo.bar\n' | |
556 'dart:core 10:11 Bar.baz') | |
557 ]); | |
558 | |
559 expect(chain.terse.toString(), equals( | |
560 '$userSlashCode 10:11 Foo.bar\n' | |
561 '===== asynchronous gap ===========================\n' | |
562 '$userSlashCode 10:11 Foo.bar\n')); | |
563 }); | |
564 | |
565 test("doesn't return an empty chain", () { | |
566 var chain = new Chain([ | |
567 new Trace.parse( | |
568 'dart:core 10:11 Foo.bar\n' | |
569 'package:stack_trace/stack_trace.dart 10:11 Bar.baz\n' | |
570 'dart:core 10:11 Zip.zap'), | |
571 new Trace.parse( | |
572 'dart:core 10:11 A.b\n' | |
573 'package:stack_trace/stack_trace.dart 10:11 C.d\n' | |
574 'dart:core 10:11 E.f') | |
575 ]); | |
576 | |
577 expect(chain.terse.toString(), equals('dart:core E.f\n')); | |
578 }); | |
579 }); | |
580 | |
581 group('Chain.foldFrames', () { | |
582 test('folds each trace', () { | |
583 var chain = new Chain([ | |
584 new Trace.parse( | |
585 'a.dart 10:11 Foo.bar\n' | |
586 'a.dart 10:11 Bar.baz\n' | |
587 'b.dart 10:11 Bang.qux\n' | |
588 'a.dart 10:11 Zip.zap\n' | |
589 'a.dart 10:11 Zop.zoop'), | |
590 new Trace.parse( | |
591 'a.dart 10:11 Foo.bar\n' | |
592 'a.dart 10:11 Bar.baz\n' | |
593 'a.dart 10:11 Bang.qux\n' | |
594 'a.dart 10:11 Zip.zap\n' | |
595 'b.dart 10:11 Zop.zoop') | |
596 ]); | |
597 | |
598 var folded = chain.foldFrames((frame) => frame.library == 'a.dart'); | |
599 expect(folded.toString(), equals( | |
600 'a.dart 10:11 Bar.baz\n' | |
601 'b.dart 10:11 Bang.qux\n' | |
602 'a.dart 10:11 Zop.zoop\n' | |
603 '===== asynchronous gap ===========================\n' | |
604 'a.dart 10:11 Zip.zap\n' | |
605 'b.dart 10:11 Zop.zoop\n')); | |
606 }); | |
607 | |
608 test('with terse: true, folds core frames as well', () { | |
609 var chain = new Chain([ | |
610 new Trace.parse( | |
611 'a.dart 10:11 Foo.bar\n' | |
612 'dart:async-patch/future.dart 10:11 Zip.zap\n' | |
613 'b.dart 10:11 Bang.qux\n' | |
614 'dart:core 10:11 Bar.baz\n' | |
615 'a.dart 10:11 Zop.zoop'), | |
616 new Trace.parse( | |
617 'a.dart 10:11 Foo.bar\n' | |
618 'a.dart 10:11 Bar.baz\n' | |
619 'a.dart 10:11 Bang.qux\n' | |
620 'a.dart 10:11 Zip.zap\n' | |
621 'b.dart 10:11 Zop.zoop') | |
622 ]); | |
623 | |
624 var folded = chain.foldFrames((frame) => frame.library == 'a.dart', | |
625 terse: true); | |
626 expect(folded.toString(), equals( | |
627 'dart:async Zip.zap\n' | |
628 'b.dart 10:11 Bang.qux\n' | |
629 'a.dart Zop.zoop\n' | |
630 '===== asynchronous gap ===========================\n' | |
631 'a.dart Zip.zap\n' | |
632 'b.dart 10:11 Zop.zoop\n')); | |
633 }); | |
634 | |
635 test('eliminates completely-folded traces', () { | |
636 var chain = new Chain([ | |
637 new Trace.parse( | |
638 'a.dart 10:11 Foo.bar\n' | |
639 'b.dart 10:11 Bang.qux'), | |
640 new Trace.parse( | |
641 'a.dart 10:11 Foo.bar\n' | |
642 'a.dart 10:11 Bang.qux'), | |
643 new Trace.parse( | |
644 'a.dart 10:11 Zip.zap\n' | |
645 'b.dart 10:11 Zop.zoop') | |
646 ]); | |
647 | |
648 var folded = chain.foldFrames((frame) => frame.library == 'a.dart'); | |
649 expect(folded.toString(), equals( | |
650 'a.dart 10:11 Foo.bar\n' | |
651 'b.dart 10:11 Bang.qux\n' | |
652 '===== asynchronous gap ===========================\n' | |
653 'a.dart 10:11 Zip.zap\n' | |
654 'b.dart 10:11 Zop.zoop\n')); | |
655 }); | |
656 | |
657 test("doesn't return an empty trace", () { | |
658 var chain = new Chain([ | |
659 new Trace.parse( | |
660 'a.dart 10:11 Foo.bar\n' | |
661 'a.dart 10:11 Bang.qux') | |
662 ]); | |
663 | |
664 var folded = chain.foldFrames((frame) => frame.library == 'a.dart'); | |
665 expect(folded.toString(), equals('a.dart 10:11 Bang.qux\n')); | |
666 }); | |
667 }); | |
668 | |
669 test('Chain.toTrace eliminates asynchronous gaps', () { | |
670 var trace = new Chain([ | |
671 new Trace.parse( | |
672 'user/code.dart 10:11 Foo.bar\n' | |
673 'dart:core 10:11 Bar.baz'), | |
674 new Trace.parse( | |
675 'user/code.dart 10:11 Foo.bar\n' | |
676 'dart:core 10:11 Bar.baz') | |
677 ]).toTrace(); | |
678 | |
679 expect(trace.toString(), equals( | |
680 '$userSlashCode 10:11 Foo.bar\n' | |
681 'dart:core 10:11 Bar.baz\n' | |
682 '$userSlashCode 10:11 Foo.bar\n' | |
683 'dart:core 10:11 Bar.baz\n')); | |
684 }); | |
685 | |
686 group('Chain.track(Future)', () { | |
687 test('forwards the future value within Chain.capture()', () { | |
688 Chain.capture(() { | |
689 expect(Chain.track(new Future.value('value')), | |
690 completion(equals('value'))); | |
691 | |
692 var trace = new Trace.current(); | |
693 expect(Chain.track(new Future.error('error', trace)) | |
694 .catchError((e, stackTrace) { | |
695 expect(e, equals('error')); | |
696 expect(stackTrace.toString(), equals(trace.toString())); | |
697 }), completes); | |
698 }); | |
699 }); | |
700 | |
701 test('forwards the future value outside of Chain.capture()', () { | |
702 expect(Chain.track(new Future.value('value')), | |
703 completion(equals('value'))); | |
704 | |
705 var trace = new Trace.current(); | |
706 expect(Chain.track(new Future.error('error', trace)) | |
707 .catchError((e, stackTrace) { | |
708 expect(e, equals('error')); | |
709 expect(stackTrace.toString(), equals(trace.toString())); | |
710 }), completes); | |
711 }); | |
712 }); | |
713 | |
714 group('Chain.track(Stream)', () { | |
715 test('forwards stream values within Chain.capture()', () { | |
716 Chain.capture(() { | |
717 var controller = new StreamController() | |
718 ..add(1)..add(2)..add(3)..close(); | |
719 expect(Chain.track(controller.stream).toList(), | |
720 completion(equals([1, 2, 3]))); | |
721 | |
722 var trace = new Trace.current(); | |
723 controller = new StreamController()..addError('error', trace); | |
724 expect(Chain.track(controller.stream).toList() | |
725 .catchError((e, stackTrace) { | |
726 expect(e, equals('error')); | |
727 expect(stackTrace.toString(), equals(trace.toString())); | |
728 }), completes); | |
729 }); | |
730 }); | |
731 | |
732 test('forwards stream values outside of Chain.capture()', () { | |
733 Chain.capture(() { | |
734 var controller = new StreamController() | |
735 ..add(1)..add(2)..add(3)..close(); | |
736 expect(Chain.track(controller.stream).toList(), | |
737 completion(equals([1, 2, 3]))); | |
738 | |
739 var trace = new Trace.current(); | |
740 controller = new StreamController()..addError('error', trace); | |
741 expect(Chain.track(controller.stream).toList() | |
742 .catchError((e, stackTrace) { | |
743 expect(e, equals('error')); | |
744 expect(stackTrace.toString(), equals(trace.toString())); | |
745 }), completes); | |
746 }); | |
747 }); | |
748 }); | |
749 } | 481 } |
750 | |
751 /// Runs [callback] in a microtask callback. | |
752 void inMicrotask(callback()) => scheduleMicrotask(callback); | |
753 | |
754 /// Runs [callback] in a one-shot timer callback. | |
755 void inOneShotTimer(callback()) => Timer.run(callback); | |
756 | |
757 /// Runs [callback] once in a periodic timer callback. | |
758 void inPeriodicTimer(callback()) { | |
759 var count = 0; | |
760 new Timer.periodic(new Duration(milliseconds: 1), (timer) { | |
761 count++; | |
762 if (count != 5) return; | |
763 timer.cancel(); | |
764 callback(); | |
765 }); | |
766 } | |
767 | |
768 /// Runs [callback] within a long asynchronous Future chain. | |
769 void inFutureChain(callback()) { | |
770 new Future(() {}) | |
771 .then((_) => new Future(() {})) | |
772 .then((_) => new Future(() {})) | |
773 .then((_) => new Future(() {})) | |
774 .then((_) => new Future(() {})) | |
775 .then((_) => callback()) | |
776 .then((_) => new Future(() {})); | |
777 } | |
778 | |
779 void inNewFuture(callback()) { | |
780 new Future(callback); | |
781 } | |
782 | |
783 void inSyncFuture(callback()) { | |
784 new Future.sync(callback); | |
785 } | |
786 | |
787 /// Returns a Future that completes to an error using a completer. | |
788 /// | |
789 /// If [trace] is passed, it's used as the stack trace for the error. | |
790 Future completerErrorFuture([StackTrace trace]) { | |
791 var completer = new Completer(); | |
792 completer.completeError('error', trace); | |
793 return completer.future; | |
794 } | |
795 | |
796 /// Returns a Stream that emits an error using a controller. | |
797 /// | |
798 /// If [trace] is passed, it's used as the stack trace for the error. | |
799 Stream controllerErrorStream([StackTrace trace]) { | |
800 var controller = new StreamController(); | |
801 controller.addError('error', trace); | |
802 return controller.stream; | |
803 } | |
804 | |
805 /// Runs [callback] within [asyncFn], then converts any errors raised into a | |
806 /// [Chain] with [Chain.forTrace]. | |
807 Future<Chain> chainForTrace(asyncFn(callback()), callback()) { | |
808 var completer = new Completer(); | |
809 asyncFn(() { | |
810 // We use `new Future.value().then(...)` here as opposed to [new Future] or | |
811 // [new Future.sync] because those methods don't pass the exception through | |
812 // the zone specification before propagating it, so there's no chance to | |
813 // attach a chain to its stack trace. See issue 15105. | |
814 new Future.value().then((_) => callback()) | |
815 .catchError(completer.completeError); | |
816 }); | |
817 return completer.future | |
818 .catchError((_, stackTrace) => new Chain.forTrace(stackTrace)); | |
819 } | |
820 | |
821 /// Runs [callback] in a [Chain.capture] zone and returns a Future that | |
822 /// completes to the stack chain for an error thrown by [callback]. | |
823 /// | |
824 /// [callback] is expected to throw the string `"error"`. | |
825 Future<Chain> captureFuture(callback()) { | |
826 var completer = new Completer<Chain>(); | |
827 Chain.capture(callback, onError: (error, chain) { | |
828 expect(error, equals('error')); | |
829 completer.complete(chain); | |
830 }); | |
831 return completer.future; | |
832 } | |
OLD | NEW |