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

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