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

Side by Side Diff: pkg/stack_trace/test/chain_test.dart

Issue 75863004: Add a stack chain class to the stack trace package. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: code review Created 7 years, 1 month 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 | Annotate | Revision Log
« no previous file with comments | « pkg/stack_trace/lib/stack_trace.dart ('k') | pkg/stack_trace/test/trace_test.dart » ('j') | 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 library chain_test;
6
7 import 'dart:async';
8
9 import 'package:stack_trace/stack_trace.dart';
10 import 'package:unittest/unittest.dart';
11
12 import 'utils.dart';
13
14 void main() {
15 group('capture() with onError catches exceptions', () {
16 test('thrown in a microtask', () {
17 return captureFuture(() => inMicrotask(() => throw 'error'))
18 .then((chain) {
19 // Since there was only one asynchronous operation, there should be only
20 // two traces in the chain.
21 expect(chain.traces, hasLength(2));
22
23 // The first frame of the first trace should be the line on which the
24 // actual error was thrown.
25 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
26
27 // The second trace should describe the stack when the error callback
28 // was scheduled.
29 expect(chain.traces[1].frames,
30 contains(frameMember(startsWith('inMicrotask'))));
31 });
32 });
33
34 test('thrown in a one-shot timer', () {
35 return captureFuture(() => inOneShotTimer(() => throw 'error'))
36 .then((chain) {
37 expect(chain.traces, hasLength(2));
38 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
39 expect(chain.traces[1].frames,
40 contains(frameMember(startsWith('inOneShotTimer'))));
41 });
42 });
43
44 test('thrown in a periodic timer', () {
45 return captureFuture(() => inPeriodicTimer(() => throw 'error'))
46 .then((chain) {
47 expect(chain.traces, hasLength(2));
48 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
49 expect(chain.traces[1].frames,
50 contains(frameMember(startsWith('inPeriodicTimer'))));
51 });
52 });
53
54 test('thrown in a nested series of asynchronous operations', () {
55 return captureFuture(() {
56 inPeriodicTimer(() {
57 inOneShotTimer(() => inMicrotask(() => throw 'error'));
58 });
59 }).then((chain) {
60 expect(chain.traces, hasLength(4));
61 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
62 expect(chain.traces[1].frames,
63 contains(frameMember(startsWith('inMicrotask'))));
64 expect(chain.traces[2].frames,
65 contains(frameMember(startsWith('inOneShotTimer'))));
66 expect(chain.traces[3].frames,
67 contains(frameMember(startsWith('inPeriodicTimer'))));
68 });
69 });
70
71 test('thrown in a long future chain', () {
72 return captureFuture(() => inFutureChain(() => throw 'error'))
73 .then((chain) {
74 // Despite many asynchronous operations, there's only one level of
75 // nested calls, so there should be only two traces in the chain. This
76 // is important; programmers expect stack trace memory consumption to be
77 // O(depth of program), not O(length of program).
78 expect(chain.traces, hasLength(2));
79
80 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
81 expect(chain.traces[1].frames,
82 contains(frameMember(startsWith('inFutureChain'))));
83 });
84 });
85
86 test('multiple times', () {
87 var completer = new Completer();
88 var first = true;
89
90 Chain.capture(() {
91 inMicrotask(() => throw 'first error');
92 inPeriodicTimer(() => throw 'second error');
93 }, onError: (error, chain) {
94 if (first) {
95 expect(error, equals('first error'));
96 expect(chain.traces[1].frames,
97 contains(frameMember(startsWith('inMicrotask'))));
98 first = false;
99 } else {
100 expect(error, equals('second error'));
101 expect(chain.traces[1].frames,
102 contains(frameMember(startsWith('inPeriodicTimer'))));
103 completer.complete();
104 }
105 });
106
107 return completer.future;
108 });
109 });
110
111 test('capture() without onError passes exceptions to parent zone', () {
112 var completer = new Completer();
113
114 runZoned(() {
115 Chain.capture(() => inMicrotask(() => throw 'error'));
116 }, onError: (error, chain) {
117 expect(error, equals('error'));
118 expect(chain, new isInstanceOf<Chain>());
119 expect(chain.traces[1].frames,
120 contains(frameMember(startsWith('inMicrotask'))));
121 completer.complete();
122 });
123
124 return completer.future;
125 });
126
127 group('current() within capture()', () {
128 test('called in a microtask', () {
129 var completer = new Completer();
130 Chain.capture(() {
131 inMicrotask(() => completer.complete(new Chain.current()));
132 });
133
134 return completer.future.then((chain) {
135 expect(chain.traces, hasLength(2));
136 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
137 expect(chain.traces[1].frames,
138 contains(frameMember(startsWith('inMicrotask'))));
139 });
140 });
141
142 test('called in a one-shot timer', () {
143 var completer = new Completer();
144 Chain.capture(() {
145 inOneShotTimer(() => completer.complete(new Chain.current()));
146 });
147
148 return completer.future.then((chain) {
149 expect(chain.traces, hasLength(2));
150 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
151 expect(chain.traces[1].frames,
152 contains(frameMember(startsWith('inOneShotTimer'))));
153 });
154 });
155
156 test('called in a periodic timer', () {
157 var completer = new Completer();
158 Chain.capture(() {
159 inPeriodicTimer(() => completer.complete(new Chain.current()));
160 });
161
162 return completer.future.then((chain) {
163 expect(chain.traces, hasLength(2));
164 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
165 expect(chain.traces[1].frames,
166 contains(frameMember(startsWith('inPeriodicTimer'))));
167 });
168 });
169
170 test('called in a nested series of asynchronous operations', () {
171 var completer = new Completer();
172 Chain.capture(() {
173 inPeriodicTimer(() {
174 inOneShotTimer(() {
175 inMicrotask(() => completer.complete(new Chain.current()));
176 });
177 });
178 });
179
180 return completer.future.then((chain) {
181 expect(chain.traces, hasLength(4));
182 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
183 expect(chain.traces[1].frames,
184 contains(frameMember(startsWith('inMicrotask'))));
185 expect(chain.traces[2].frames,
186 contains(frameMember(startsWith('inOneShotTimer'))));
187 expect(chain.traces[3].frames,
188 contains(frameMember(startsWith('inPeriodicTimer'))));
189 });
190 });
191
192 test('called in a long future chain', () {
193 var completer = new Completer();
194 Chain.capture(() {
195 inFutureChain(() => completer.complete(new Chain.current()));
196 });
197
198 return completer.future.then((chain) {
199 expect(chain.traces, hasLength(2));
200 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
201 expect(chain.traces[1].frames,
202 contains(frameMember(startsWith('inFutureChain'))));
203 });
204 });
205 });
206
207 test('current() outside of capture() returns a chain wrapping the current '
208 'trace', () {
209 var completer = new Completer();
210 inMicrotask(() => completer.complete(new Chain.current()));
211
212 return completer.future.then((chain) {
213 // Since the chain wasn't loaded within [Chain.capture], the full stack
214 // chain isn't available and it just returns the current stack when
215 // called.
216 expect(chain.traces, hasLength(1));
217 expect(chain.traces.first.frames.first, frameMember(startsWith('main')));
218 });
219 });
220
221 group('forTrace() within capture()', () {
222 test('called for a stack trace from a microtask', () {
223 return Chain.capture(() {
224 return chainForTrace(inMicrotask, () => throw 'error');
225 }).then((chain) {
226 // Because [chainForTrace] has to set up a future chain to capture the
227 // stack trace while still showing it to the zone specification, it adds
228 // an additional level of async nesting and so an additional trace.
229 expect(chain.traces, hasLength(3));
230 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
231 expect(chain.traces[1].frames,
232 contains(frameMember(startsWith('chainForTrace'))));
233 expect(chain.traces[2].frames,
234 contains(frameMember(startsWith('inMicrotask'))));
235 });
236 });
237
238 test('called for a stack trace from a one-shot timer', () {
239 return Chain.capture(() {
240 return chainForTrace(inOneShotTimer, () => throw 'error');
241 }).then((chain) {
242 expect(chain.traces, hasLength(3));
243 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
244 expect(chain.traces[1].frames,
245 contains(frameMember(startsWith('chainForTrace'))));
246 expect(chain.traces[2].frames,
247 contains(frameMember(startsWith('inOneShotTimer'))));
248 });
249 });
250
251 test('called for a stack trace from a periodic timer', () {
252 return Chain.capture(() {
253 return chainForTrace(inPeriodicTimer, () => throw 'error');
254 }).then((chain) {
255 expect(chain.traces, hasLength(3));
256 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
257 expect(chain.traces[1].frames,
258 contains(frameMember(startsWith('chainForTrace'))));
259 expect(chain.traces[2].frames,
260 contains(frameMember(startsWith('inPeriodicTimer'))));
261 });
262 });
263
264 test('called for a stack trace from a nested series of asynchronous '
265 'operations', () {
266 return Chain.capture(() {
267 return chainForTrace((callback) {
268 inPeriodicTimer(() => inOneShotTimer(() => inMicrotask(callback)));
269 }, () => throw 'error');
270 }).then((chain) {
271 expect(chain.traces, hasLength(5));
272 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
273 expect(chain.traces[1].frames,
274 contains(frameMember(startsWith('chainForTrace'))));
275 expect(chain.traces[2].frames,
276 contains(frameMember(startsWith('inMicrotask'))));
277 expect(chain.traces[3].frames,
278 contains(frameMember(startsWith('inOneShotTimer'))));
279 expect(chain.traces[4].frames,
280 contains(frameMember(startsWith('inPeriodicTimer'))));
281 });
282 });
283
284 test('called for a stack trace from a long future chain', () {
285 return Chain.capture(() {
286 return chainForTrace(inFutureChain, () => throw 'error');
287 }).then((chain) {
288 expect(chain.traces, hasLength(3));
289 expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
290 expect(chain.traces[1].frames,
291 contains(frameMember(startsWith('chainForTrace'))));
292 expect(chain.traces[2].frames,
293 contains(frameMember(startsWith('inFutureChain'))));
294 });
295 });
296
297 test('called for an unregistered stack trace returns a chain wrapping that '
298 'trace', () {
299 var trace;
300 var chain = Chain.capture(() {
301 try {
302 throw 'error';
303 } catch (_, stackTrace) {
304 trace = stackTrace;
305 return new Chain.forTrace(stackTrace);
306 }
307 });
308
309 expect(chain.traces, hasLength(1));
310 expect(chain.traces.first.toString(),
311 equals(new Trace.from(trace).toString()));
312 });
313 });
314
315 test('forTrace() outside of capture() returns a chain wrapping the given '
316 'trace', () {
317 var trace;
318 var chain = Chain.capture(() {
319 try {
320 throw 'error';
321 } catch (_, stackTrace) {
322 trace = stackTrace;
323 return new Chain.forTrace(stackTrace);
324 }
325 });
326
327 expect(chain.traces, hasLength(1));
328 expect(chain.traces.first.toString(),
329 equals(new Trace.from(trace).toString()));
330 });
331
332 test('Chain.parse() parses a real Chain', () {
333 return captureFuture(() => inMicrotask(() => throw 'error')).then((chain) {
334 expect(new Chain.parse(chain.toString()).toString(),
335 equals(chain.toString()));
336 });
337 });
338
339 group('Chain.terse', () {
340 test('makes each trace terse', () {
341 var chain = new Chain([
342 new Trace.parse(
343 'dart:core 10:11 Foo.bar\n'
344 'dart:core 10:11 Bar.baz\n'
345 'user/code.dart 10:11 Bang.qux\n'
346 'dart:core 10:11 Zip.zap\n'
347 'dart:core 10:11 Zop.zoop'),
348 new Trace.parse(
349 'user/code.dart 10:11 Bang.qux\n'
350 'dart:core 10:11 Foo.bar\n'
351 'package:stack_trace/stack_trace.dart 10:11 Bar.baz\n'
352 'dart:core 10:11 Zip.zap\n'
353 'user/code.dart 10:11 Zop.zoop')
354 ]);
355
356 expect(chain.terse.toString(), equals(
357 'dart:core Bar.baz\n'
358 'user/code.dart 10:11 Bang.qux\n'
359 'dart:core Zop.zoop\n'
360 '===== asynchronous gap ===========================\n'
361 'user/code.dart 10:11 Bang.qux\n'
362 'dart:core Zip.zap\n'
363 'user/code.dart 10:11 Zop.zoop\n'));
364 });
365
366 test('eliminates internal-only traces', () {
367 var chain = new Chain([
368 new Trace.parse(
369 'user/code.dart 10:11 Foo.bar\n'
370 'dart:core 10:11 Bar.baz'),
371 new Trace.parse(
372 'dart:core 10:11 Foo.bar\n'
373 'package:stack_trace/stack_trace.dart 10:11 Bar.baz\n'
374 'dart:core 10:11 Zip.zap'),
375 new Trace.parse(
376 'user/code.dart 10:11 Foo.bar\n'
377 'dart:core 10:11 Bar.baz')
378 ]);
379
380 expect(chain.terse.toString(), equals(
381 'user/code.dart 10:11 Foo.bar\n'
382 'dart:core Bar.baz\n'
383 '===== asynchronous gap ===========================\n'
384 'user/code.dart 10:11 Foo.bar\n'
385 'dart:core Bar.baz\n'));
386 });
387 });
388
389 test('Chain.toTrace eliminates asynchronous gaps', () {
390 var trace = new Chain([
391 new Trace.parse(
392 'user/code.dart 10:11 Foo.bar\n'
393 'dart:core 10:11 Bar.baz'),
394 new Trace.parse(
395 'user/code.dart 10:11 Foo.bar\n'
396 'dart:core 10:11 Bar.baz')
397 ]).toTrace();
398
399 expect(trace.toString(), equals(
400 'user/code.dart 10:11 Foo.bar\n'
401 'dart:core 10:11 Bar.baz\n'
402 'user/code.dart 10:11 Foo.bar\n'
403 'dart:core 10:11 Bar.baz\n'));
404 });
405
406 group('Chain.track(Future)', () {
407 test('associates the current chain with a manually-reported exception with '
408 'a stack trace', () {
409 var trace = new Trace.current();
410 return captureFuture(() {
411 inMicrotask(() => trackedErrorFuture(trace));
412 }).then((chain) {
413 expect(chain.traces, hasLength(3));
414
415 // The first trace is the trace that was manually reported for the
416 // error.
417 expect(chain.traces.first.toString(), equals(trace.toString()));
418
419 // The second trace is the trace that was captured when [Chain.track]
420 // was called.
421 expect(chain.traces[1].frames.first,
422 frameMember(startsWith('trackedErrorFuture')));
423
424 // The third trace is the automatically-captured trace from when the
425 // microtask was scheduled.
426 expect(chain.traces[2].frames,
427 contains(frameMember(startsWith('inMicrotask'))));
428 });
429 });
430
431 test('associates the current chain with a manually-reported exception with '
432 'no stack trace', () {
433 return captureFuture(() {
434 inMicrotask(() => trackedErrorFuture());
435 }).then((chain) {
436 expect(chain.traces, hasLength(3));
437
438 // The first trace is the one captured by
439 // [StackZoneSpecification.trackFuture], which should contain only
440 // stack_trace and dart: frames.
441 expect(chain.traces.first.frames,
442 everyElement(frameLibrary(isNot(contains('chain_test')))));
443
444 expect(chain.traces[1].frames.first,
445 frameMember(startsWith('trackedErrorFuture')));
446 expect(chain.traces[2].frames,
447 contains(frameMember(startsWith('inMicrotask'))));
448 });
449 });
450
451 test('forwards the future value within Chain.capture()', () {
452 Chain.capture(() {
453 expect(Chain.track(new Future.value('value')),
454 completion(equals('value')));
455
456 var trace = new Trace.current();
457 expect(Chain.track(new Future.error('error', trace))
458 .catchError((e, stackTrace) {
459 expect(e, equals('error'));
460 expect(stackTrace.toString(), equals(trace.toString()));
461 }), completes);
462 });
463 });
464
465 test('forwards the future value outside of Chain.capture()', () {
466 expect(Chain.track(new Future.value('value')),
467 completion(equals('value')));
468
469 var trace = new Trace.current();
470 expect(Chain.track(new Future.error('error', trace))
471 .catchError((e, stackTrace) {
472 expect(e, equals('error'));
473 expect(stackTrace.toString(), equals(trace.toString()));
474 }), completes);
475 });
476 });
477
478 group('Chain.track(Stream)', () {
479 test('associates the current chain with a manually-reported exception with '
480 'a stack trace', () {
481 var trace = new Trace.current();
482 return captureFuture(() {
483 inMicrotask(() => trackedErrorStream(trace).listen(null));
484 }).then((chain) {
485 expect(chain.traces, hasLength(3));
486 expect(chain.traces.first.toString(), equals(trace.toString()));
487 expect(chain.traces[1].frames.first,
488 frameMember(startsWith('trackedErrorStream')));
489 expect(chain.traces[2].frames,
490 contains(frameMember(startsWith('inMicrotask'))));
491 });
492 });
493
494 test('associates the current chain with a manually-reported exception with '
495 'no stack trace', () {
496 return captureFuture(() {
497 inMicrotask(() => trackedErrorStream().listen(null));
498 }).then((chain) {
499 expect(chain.traces, hasLength(3));
500 expect(chain.traces.first.frames,
501 everyElement(frameLibrary(isNot(contains('chain_test')))));
502 expect(chain.traces[1].frames.first,
503 frameMember(startsWith('trackedErrorStream')));
504 expect(chain.traces[2].frames,
505 contains(frameMember(startsWith('inMicrotask'))));
506 });
507 });
508
509 test('forwards stream values within Chain.capture()', () {
510 Chain.capture(() {
511 var controller = new StreamController()
512 ..add(1)..add(2)..add(3)..close();
513 expect(Chain.track(controller.stream).toList(),
514 completion(equals([1, 2, 3])));
515
516 var trace = new Trace.current();
517 controller = new StreamController()..addError('error', trace);
518 expect(Chain.track(controller.stream).toList()
519 .catchError((e, stackTrace) {
520 expect(e, equals('error'));
521 expect(stackTrace.toString(), equals(trace.toString()));
522 }), completes);
523 });
524 });
525
526 test('forwards stream values outside of Chain.capture()', () {
527 Chain.capture(() {
528 var controller = new StreamController()
529 ..add(1)..add(2)..add(3)..close();
530 expect(Chain.track(controller.stream).toList(),
531 completion(equals([1, 2, 3])));
532
533 var trace = new Trace.current();
534 controller = new StreamController()..addError('error', trace);
535 expect(Chain.track(controller.stream).toList()
536 .catchError((e, stackTrace) {
537 expect(e, equals('error'));
538 expect(stackTrace.toString(), equals(trace.toString()));
539 }), completes);
540 });
541 });
542 });
543 }
544
545 /// Runs [callback] in a microtask callback.
546 void inMicrotask(callback()) => scheduleMicrotask(callback);
547
548 /// Runs [callback] in a one-shot timer callback.
549 void inOneShotTimer(callback()) => Timer.run(callback);
550
551 /// Runs [callback] once in a periodic timer callback.
552 void inPeriodicTimer(callback()) {
553 var count = 0;
554 new Timer.periodic(new Duration(milliseconds: 1), (timer) {
555 count++;
556 if (count != 5) return;
557 timer.cancel();
558 callback();
559 });
560 }
561
562 /// Runs [callback] within a long asynchronous Future chain.
563 void inFutureChain(callback()) {
564 new Future(() {})
565 .then((_) => new Future(() {}))
566 .then((_) => new Future(() {}))
567 .then((_) => new Future(() {}))
568 .then((_) => new Future(() {}))
569 .then((_) => callback())
570 .then((_) => new Future(() {}));
571 }
572
573 /// Returns a Future that completes to an error and is wrapped in [Chain.track].
574 ///
575 /// If [trace] is passed, it's used as the stack trace for the error.
576 Future trackedErrorFuture([StackTrace trace]) {
577 var completer = new Completer();
578 completer.completeError('error', trace);
579 return Chain.track(completer.future);
580 }
581
582 /// Returns a Stream that emits an error and is wrapped in [Chain.track].
583 ///
584 /// If [trace] is passed, it's used as the stack trace for the error.
585 Stream trackedErrorStream([StackTrace trace]) {
586 var controller = new StreamController();
587 controller.addError('error', trace);
588 return Chain.track(controller.stream);
589 }
590
591 /// Runs [callback] within [asyncFn], then converts any errors raised into a
592 /// [Chain] with [Chain.forTrace].
593 Future<Chain> chainForTrace(asyncFn(callback()), callback()) {
594 var completer = new Completer();
595 asyncFn(() {
596 // We use `new Future.value().then(...)` here as opposed to [new Future] or
597 // [new Future.sync] because those methods don't pass the exception through
598 // the zone specification before propagating it, so there's no chance to
599 // attach a chain to its stack trace. See issue 15105.
600 new Future.value().then((_) => callback())
601 .catchError(completer.completeError);
602 });
603 return completer.future
604 .catchError((_, stackTrace) => new Chain.forTrace(stackTrace));
605 }
606
607 /// Runs [callback] in a [Chain.capture] zone and returns a Future that
608 /// completes to the stack chain for an error thrown by [callback].
609 ///
610 /// [callback] is expected to throw the string `"error"`.
611 Future<Chain> captureFuture(callback()) {
612 var completer = new Completer<Chain>();
613 Chain.capture(callback, onError: (error, chain) {
614 expect(error, equals('error'));
615 completer.complete(chain);
616 });
617 return completer.future;
618 }
OLDNEW
« no previous file with comments | « pkg/stack_trace/lib/stack_trace.dart ('k') | pkg/stack_trace/test/trace_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698