Chromium Code Reviews| 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 |