Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1)

Side by Side Diff: test/chain_test.dart

Issue 1218903003: Add tests for stack chains in the browser. (Closed) Base URL: git@github.com:dart-lang/stack_trace@master
Patch Set: Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « test/chain/vm_test.dart ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 // dart2js currently doesn't support chain-capturing. See sdk#15171.
6 @TestOn('vm')
7
8 import 'dart:async';
9
10 import 'package:path/path.dart' as p;
11 import 'package:stack_trace/stack_trace.dart';
12 import 'package:test/test.dart';
13
14 import 'utils.dart';
15
16 void main() {
17 group('capture() with onError catches exceptions', () {
18 test('thrown synchronously', () {
19 return captureFuture(() => throw 'error')
20 .then((chain) {
21 expect(chain.traces, hasLength(1));
22 expect(chain.traces.single.frames.first,
23 frameMember(startsWith('main')));
24 });
25 });
26
27 test('thrown in a microtask', () {
28 return captureFuture(() => inMicrotask(() => throw 'error'))
29 .then((chain) {
30 // Since there was only one asynchronous operation, there should be only
31 // two traces in the chain.
32 expect(chain.traces, hasLength(2));
33
34 // The first frame of the first trace should be the line on which the
35 // actual error was thrown.
36 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
37
38 // The second trace should describe the stack when the error callback
39 // was scheduled.
40 expect(chain.traces[1].frames,
41 contains(frameMember(startsWith('inMicrotask'))));
42 });
43 });
44
45 test('thrown in a one-shot timer', () {
46 return captureFuture(() => inOneShotTimer(() => throw 'error'))
47 .then((chain) {
48 expect(chain.traces, hasLength(2));
49 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
50 expect(chain.traces[1].frames,
51 contains(frameMember(startsWith('inOneShotTimer'))));
52 });
53 });
54
55 test('thrown in a periodic timer', () {
56 return captureFuture(() => inPeriodicTimer(() => throw 'error'))
57 .then((chain) {
58 expect(chain.traces, hasLength(2));
59 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
60 expect(chain.traces[1].frames,
61 contains(frameMember(startsWith('inPeriodicTimer'))));
62 });
63 });
64
65 test('thrown in a nested series of asynchronous operations', () {
66 return captureFuture(() {
67 inPeriodicTimer(() {
68 inOneShotTimer(() => inMicrotask(() => throw 'error'));
69 });
70 }).then((chain) {
71 expect(chain.traces, hasLength(4));
72 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
73 expect(chain.traces[1].frames,
74 contains(frameMember(startsWith('inMicrotask'))));
75 expect(chain.traces[2].frames,
76 contains(frameMember(startsWith('inOneShotTimer'))));
77 expect(chain.traces[3].frames,
78 contains(frameMember(startsWith('inPeriodicTimer'))));
79 });
80 });
81
82 test('thrown in a long future chain', () {
83 return captureFuture(() => inFutureChain(() => throw 'error'))
84 .then((chain) {
85 // Despite many asynchronous operations, there's only one level of
86 // nested calls, so there should be only two traces in the chain. This
87 // is important; programmers expect stack trace memory consumption to be
88 // O(depth of program), not O(length of program).
89 expect(chain.traces, hasLength(2));
90
91 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
92 expect(chain.traces[1].frames,
93 contains(frameMember(startsWith('inFutureChain'))));
94 });
95 });
96
97 test('thrown in new Future()', () {
98 return captureFuture(() => inNewFuture(() => throw 'error'))
99 .then((chain) {
100 expect(chain.traces, hasLength(3));
101 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
102
103 // The second trace is the one captured by
104 // [StackZoneSpecification.errorCallback]. Because that runs
105 // asynchronously within [new Future], it doesn't actually refer to the
106 // source file at all.
107 expect(chain.traces[1].frames,
108 everyElement(frameLibrary(isNot(contains('chain_test')))));
109
110 expect(chain.traces[2].frames,
111 contains(frameMember(startsWith('inNewFuture'))));
112 });
113 });
114
115 test('thrown in new Future.sync()', () {
116 return captureFuture(() {
117 inMicrotask(() => inSyncFuture(() => throw 'error'));
118 }).then((chain) {
119 expect(chain.traces, hasLength(3));
120 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
121 expect(chain.traces[1].frames,
122 contains(frameMember(startsWith('inSyncFuture'))));
123 expect(chain.traces[2].frames,
124 contains(frameMember(startsWith('inMicrotask'))));
125 });
126 });
127
128 test('multiple times', () {
129 var completer = new Completer();
130 var first = true;
131
132 Chain.capture(() {
133 inMicrotask(() => throw 'first error');
134 inPeriodicTimer(() => throw 'second error');
135 }, onError: (error, chain) {
136 try {
137 if (first) {
138 expect(error, equals('first error'));
139 expect(chain.traces[1].frames,
140 contains(frameMember(startsWith('inMicrotask'))));
141 first = false;
142 } else {
143 expect(error, equals('second error'));
144 expect(chain.traces[1].frames,
145 contains(frameMember(startsWith('inPeriodicTimer'))));
146 completer.complete();
147 }
148 } catch (error, stackTrace) {
149 completer.completeError(error, stackTrace);
150 }
151 });
152
153 return completer.future;
154 });
155
156 test('passed to a completer', () {
157 var trace = new Trace.current();
158 return captureFuture(() {
159 inMicrotask(() => completerErrorFuture(trace));
160 }).then((chain) {
161 expect(chain.traces, hasLength(3));
162
163 // The first trace is the trace that was manually reported for the
164 // error.
165 expect(chain.traces.first.toString(), equals(trace.toString()));
166
167 // The second trace is the trace that was captured when
168 // [Completer.addError] was called.
169 expect(chain.traces[1].frames,
170 contains(frameMember(startsWith('completerErrorFuture'))));
171
172 // The third trace is the automatically-captured trace from when the
173 // microtask was scheduled.
174 expect(chain.traces[2].frames,
175 contains(frameMember(startsWith('inMicrotask'))));
176 });
177 });
178
179 test('passed to a completer with no stack trace', () {
180 return captureFuture(() {
181 inMicrotask(() => completerErrorFuture());
182 }).then((chain) {
183 expect(chain.traces, hasLength(2));
184
185 // The first trace is the one captured when [Completer.addError] was
186 // called.
187 expect(chain.traces[0].frames,
188 contains(frameMember(startsWith('completerErrorFuture'))));
189
190 // The second trace is the automatically-captured trace from when the
191 // microtask was scheduled.
192 expect(chain.traces[1].frames,
193 contains(frameMember(startsWith('inMicrotask'))));
194 });
195 });
196
197 test('passed to a stream controller', () {
198 var trace = new Trace.current();
199 return captureFuture(() {
200 inMicrotask(() => controllerErrorStream(trace).listen(null));
201 }).then((chain) {
202 expect(chain.traces, hasLength(3));
203 expect(chain.traces.first.toString(), equals(trace.toString()));
204 expect(chain.traces[1].frames,
205 contains(frameMember(startsWith('controllerErrorStream'))));
206 expect(chain.traces[2].frames,
207 contains(frameMember(startsWith('inMicrotask'))));
208 });
209 });
210
211 test('passed to a stream controller with no stack trace', () {
212 return captureFuture(() {
213 inMicrotask(() => controllerErrorStream().listen(null));
214 }).then((chain) {
215 expect(chain.traces, hasLength(2));
216 expect(chain.traces[0].frames,
217 contains(frameMember(startsWith('controllerErrorStream'))));
218 expect(chain.traces[1].frames,
219 contains(frameMember(startsWith('inMicrotask'))));
220 });
221 });
222
223 test('and relays them to the parent zone', () {
224 var completer = new Completer();
225
226 runZoned(() {
227 Chain.capture(() {
228 inMicrotask(() => throw 'error');
229 }, onError: (error, chain) {
230 expect(error, equals('error'));
231 expect(chain.traces[1].frames,
232 contains(frameMember(startsWith('inMicrotask'))));
233 throw error;
234 });
235 }, onError: (error, chain) {
236 try {
237 expect(error, equals('error'));
238 expect(chain, new isInstanceOf<Chain>());
239 expect(chain.traces[1].frames,
240 contains(frameMember(startsWith('inMicrotask'))));
241 completer.complete();
242 } catch (error, stackTrace) {
243 completer.completeError(error, stackTrace);
244 }
245 });
246
247 return completer.future;
248 });
249 });
250
251 test('capture() without onError passes exceptions to parent zone', () {
252 var completer = new Completer();
253
254 runZoned(() {
255 Chain.capture(() => inMicrotask(() => throw 'error'));
256 }, onError: (error, chain) {
257 try {
258 expect(error, equals('error'));
259 expect(chain, new isInstanceOf<Chain>());
260 expect(chain.traces[1].frames,
261 contains(frameMember(startsWith('inMicrotask'))));
262 completer.complete();
263 } catch (error, stackTrace) {
264 completer.completeError(error, stackTrace);
265 }
266 });
267
268 return completer.future;
269 });
270
271 group('current() within capture()', () {
272 test('called in a microtask', () {
273 var completer = new Completer();
274 Chain.capture(() {
275 inMicrotask(() => completer.complete(new Chain.current()));
276 });
277
278 return completer.future.then((chain) {
279 expect(chain.traces, hasLength(2));
280 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
281 expect(chain.traces[1].frames,
282 contains(frameMember(startsWith('inMicrotask'))));
283 });
284 });
285
286 test('called in a one-shot timer', () {
287 var completer = new Completer();
288 Chain.capture(() {
289 inOneShotTimer(() => completer.complete(new Chain.current()));
290 });
291
292 return completer.future.then((chain) {
293 expect(chain.traces, hasLength(2));
294 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
295 expect(chain.traces[1].frames,
296 contains(frameMember(startsWith('inOneShotTimer'))));
297 });
298 });
299
300 test('called in a periodic timer', () {
301 var completer = new Completer();
302 Chain.capture(() {
303 inPeriodicTimer(() => completer.complete(new Chain.current()));
304 });
305
306 return completer.future.then((chain) {
307 expect(chain.traces, hasLength(2));
308 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
309 expect(chain.traces[1].frames,
310 contains(frameMember(startsWith('inPeriodicTimer'))));
311 });
312 });
313
314 test('called in a nested series of asynchronous operations', () {
315 var completer = new Completer();
316 Chain.capture(() {
317 inPeriodicTimer(() {
318 inOneShotTimer(() {
319 inMicrotask(() => completer.complete(new Chain.current()));
320 });
321 });
322 });
323
324 return completer.future.then((chain) {
325 expect(chain.traces, hasLength(4));
326 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
327 expect(chain.traces[1].frames,
328 contains(frameMember(startsWith('inMicrotask'))));
329 expect(chain.traces[2].frames,
330 contains(frameMember(startsWith('inOneShotTimer'))));
331 expect(chain.traces[3].frames,
332 contains(frameMember(startsWith('inPeriodicTimer'))));
333 });
334 });
335
336 test('called in a long future chain', () {
337 var completer = new Completer();
338 Chain.capture(() {
339 inFutureChain(() => completer.complete(new Chain.current()));
340 });
341
342 return completer.future.then((chain) {
343 expect(chain.traces, hasLength(2));
344 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
345 expect(chain.traces[1].frames,
346 contains(frameMember(startsWith('inFutureChain'))));
347 });
348 });
349 });
350
351 test('current() outside of capture() returns a chain wrapping the current '
352 'trace', () {
353 // The test runner runs all tests with chains enabled, so to test without we
354 // have to do some zone munging.
355 return runZoned(() {
356 var completer = new Completer();
357 inMicrotask(() => completer.complete(new Chain.current()));
358
359 return completer.future.then((chain) {
360 // Since the chain wasn't loaded within [Chain.capture], the full stack
361 // chain isn't available and it just returns the current stack when
362 // called.
363 expect(chain.traces, hasLength(1));
364 expect(chain.traces.first.frames.first,
365 frameMember(startsWith('main')));
366 });
367 }, zoneValues: {#stack_trace.stack_zone.spec: null});
368 });
369
370 group('forTrace() within capture()', () {
371 test('called for a stack trace from a microtask', () {
372 return Chain.capture(() {
373 return chainForTrace(inMicrotask, () => throw 'error');
374 }).then((chain) {
375 // Because [chainForTrace] has to set up a future chain to capture the
376 // stack trace while still showing it to the zone specification, it adds
377 // an additional level of async nesting and so an additional trace.
378 expect(chain.traces, hasLength(3));
379 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
380 expect(chain.traces[1].frames,
381 contains(frameMember(startsWith('chainForTrace'))));
382 expect(chain.traces[2].frames,
383 contains(frameMember(startsWith('inMicrotask'))));
384 });
385 });
386
387 test('called for a stack trace from a one-shot timer', () {
388 return Chain.capture(() {
389 return chainForTrace(inOneShotTimer, () => throw 'error');
390 }).then((chain) {
391 expect(chain.traces, hasLength(3));
392 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
393 expect(chain.traces[1].frames,
394 contains(frameMember(startsWith('chainForTrace'))));
395 expect(chain.traces[2].frames,
396 contains(frameMember(startsWith('inOneShotTimer'))));
397 });
398 });
399
400 test('called for a stack trace from a periodic timer', () {
401 return Chain.capture(() {
402 return chainForTrace(inPeriodicTimer, () => throw 'error');
403 }).then((chain) {
404 expect(chain.traces, hasLength(3));
405 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
406 expect(chain.traces[1].frames,
407 contains(frameMember(startsWith('chainForTrace'))));
408 expect(chain.traces[2].frames,
409 contains(frameMember(startsWith('inPeriodicTimer'))));
410 });
411 });
412
413 test('called for a stack trace from a nested series of asynchronous '
414 'operations', () {
415 return Chain.capture(() {
416 return chainForTrace((callback) {
417 inPeriodicTimer(() => inOneShotTimer(() => inMicrotask(callback)));
418 }, () => throw 'error');
419 }).then((chain) {
420 expect(chain.traces, hasLength(5));
421 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
422 expect(chain.traces[1].frames,
423 contains(frameMember(startsWith('chainForTrace'))));
424 expect(chain.traces[2].frames,
425 contains(frameMember(startsWith('inMicrotask'))));
426 expect(chain.traces[3].frames,
427 contains(frameMember(startsWith('inOneShotTimer'))));
428 expect(chain.traces[4].frames,
429 contains(frameMember(startsWith('inPeriodicTimer'))));
430 });
431 });
432
433 test('called for a stack trace from a long future chain', () {
434 return Chain.capture(() {
435 return chainForTrace(inFutureChain, () => throw 'error');
436 }).then((chain) {
437 expect(chain.traces, hasLength(3));
438 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
439 expect(chain.traces[1].frames,
440 contains(frameMember(startsWith('chainForTrace'))));
441 expect(chain.traces[2].frames,
442 contains(frameMember(startsWith('inFutureChain'))));
443 });
444 });
445
446 test('called for an unregistered stack trace returns a chain wrapping that '
447 'trace', () {
448 var trace;
449 var chain = Chain.capture(() {
450 try {
451 throw 'error';
452 } catch (_, stackTrace) {
453 trace = stackTrace;
454 return new Chain.forTrace(stackTrace);
455 }
456 });
457
458 expect(chain.traces, hasLength(1));
459 expect(chain.traces.first.toString(),
460 equals(new Trace.from(trace).toString()));
461 });
462 });
463
464 test('forTrace() outside of capture() returns a chain wrapping the given '
465 'trace', () {
466 var trace;
467 var chain = Chain.capture(() {
468 try {
469 throw 'error';
470 } catch (_, stackTrace) {
471 trace = stackTrace;
472 return new Chain.forTrace(stackTrace);
473 }
474 });
475
476 expect(chain.traces, hasLength(1));
477 expect(chain.traces.first.toString(),
478 equals(new Trace.from(trace).toString()));
479 });
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 }
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 }
OLDNEW
« no previous file with comments | « test/chain/vm_test.dart ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698