OLD | NEW |
| (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 } | |
OLD | NEW |