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