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 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')))); | |
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). | |
Bob Nystrom
2013/11/20 20:19:53
I want to understand this better. Can you walk me
nweiz
2013/11/20 21:30:12
Done.
| |
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 } | |
OLD | NEW |