OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2015, 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 // VM chain tests can rely on stronger guarantees about the contents of the |
| 6 // stack traces than dart2js. |
| 7 @TestOn('dart-vm') |
| 8 |
| 9 import 'dart:async'; |
| 10 |
| 11 import 'package:stack_trace/stack_trace.dart'; |
| 12 import 'package:test/test.dart'; |
| 13 |
| 14 import '../utils.dart'; |
| 15 import 'utils.dart'; |
| 16 |
| 17 void main() { |
| 18 group('capture() with onError catches exceptions', () { |
| 19 test('thrown synchronously', () { |
| 20 return captureFuture(() => throw 'error') |
| 21 .then((chain) { |
| 22 expect(chain.traces, hasLength(1)); |
| 23 expect(chain.traces.single.frames.first, |
| 24 frameMember(startsWith('main'))); |
| 25 }); |
| 26 }); |
| 27 |
| 28 test('thrown in a microtask', () { |
| 29 return captureFuture(() => inMicrotask(() => throw 'error')) |
| 30 .then((chain) { |
| 31 // Since there was only one asynchronous operation, there should be only |
| 32 // two traces in the chain. |
| 33 expect(chain.traces, hasLength(2)); |
| 34 |
| 35 // The first frame of the first trace should be the line on which the |
| 36 // actual error was thrown. |
| 37 expect(chain.traces[0].frames.first, frameMember(startsWith('main'))); |
| 38 |
| 39 // The second trace should describe the stack when the error callback |
| 40 // was scheduled. |
| 41 expect(chain.traces[1].frames, |
| 42 contains(frameMember(startsWith('inMicrotask')))); |
| 43 }); |
| 44 }); |
| 45 |
| 46 test('thrown in a one-shot timer', () { |
| 47 return captureFuture(() => inOneShotTimer(() => throw 'error')) |
| 48 .then((chain) { |
| 49 expect(chain.traces, hasLength(2)); |
| 50 expect(chain.traces[0].frames.first, frameMember(startsWith('main'))); |
| 51 expect(chain.traces[1].frames, |
| 52 contains(frameMember(startsWith('inOneShotTimer')))); |
| 53 }); |
| 54 }); |
| 55 |
| 56 test('thrown in a periodic timer', () { |
| 57 return captureFuture(() => inPeriodicTimer(() => throw 'error')) |
| 58 .then((chain) { |
| 59 expect(chain.traces, hasLength(2)); |
| 60 expect(chain.traces[0].frames.first, frameMember(startsWith('main'))); |
| 61 expect(chain.traces[1].frames, |
| 62 contains(frameMember(startsWith('inPeriodicTimer')))); |
| 63 }); |
| 64 }); |
| 65 |
| 66 test('thrown in a nested series of asynchronous operations', () { |
| 67 return captureFuture(() { |
| 68 inPeriodicTimer(() { |
| 69 inOneShotTimer(() => inMicrotask(() => throw 'error')); |
| 70 }); |
| 71 }).then((chain) { |
| 72 expect(chain.traces, hasLength(4)); |
| 73 expect(chain.traces[0].frames.first, frameMember(startsWith('main'))); |
| 74 expect(chain.traces[1].frames, |
| 75 contains(frameMember(startsWith('inMicrotask')))); |
| 76 expect(chain.traces[2].frames, |
| 77 contains(frameMember(startsWith('inOneShotTimer')))); |
| 78 expect(chain.traces[3].frames, |
| 79 contains(frameMember(startsWith('inPeriodicTimer')))); |
| 80 }); |
| 81 }); |
| 82 |
| 83 test('thrown in a long future chain', () { |
| 84 return captureFuture(() => inFutureChain(() => throw 'error')) |
| 85 .then((chain) { |
| 86 // Despite many asynchronous operations, there's only one level of |
| 87 // nested calls, so there should be only two traces in the chain. This |
| 88 // is important; programmers expect stack trace memory consumption to be |
| 89 // O(depth of program), not O(length of program). |
| 90 expect(chain.traces, hasLength(2)); |
| 91 |
| 92 expect(chain.traces[0].frames.first, frameMember(startsWith('main'))); |
| 93 expect(chain.traces[1].frames, |
| 94 contains(frameMember(startsWith('inFutureChain')))); |
| 95 }); |
| 96 }); |
| 97 |
| 98 test('thrown in new Future()', () { |
| 99 return captureFuture(() => inNewFuture(() => throw 'error')) |
| 100 .then((chain) { |
| 101 expect(chain.traces, hasLength(3)); |
| 102 expect(chain.traces[0].frames.first, frameMember(startsWith('main'))); |
| 103 |
| 104 // The second trace is the one captured by |
| 105 // [StackZoneSpecification.errorCallback]. Because that runs |
| 106 // asynchronously within [new Future], it doesn't actually refer to the |
| 107 // source file at all. |
| 108 expect(chain.traces[1].frames, |
| 109 everyElement(frameLibrary(isNot(contains('chain_test'))))); |
| 110 |
| 111 expect(chain.traces[2].frames, |
| 112 contains(frameMember(startsWith('inNewFuture')))); |
| 113 }); |
| 114 }); |
| 115 |
| 116 test('thrown in new Future.sync()', () { |
| 117 return captureFuture(() { |
| 118 inMicrotask(() => inSyncFuture(() => throw 'error')); |
| 119 }).then((chain) { |
| 120 expect(chain.traces, hasLength(3)); |
| 121 expect(chain.traces[0].frames.first, frameMember(startsWith('main'))); |
| 122 expect(chain.traces[1].frames, |
| 123 contains(frameMember(startsWith('inSyncFuture')))); |
| 124 expect(chain.traces[2].frames, |
| 125 contains(frameMember(startsWith('inMicrotask')))); |
| 126 }); |
| 127 }); |
| 128 |
| 129 test('multiple times', () { |
| 130 var completer = new Completer(); |
| 131 var first = true; |
| 132 |
| 133 Chain.capture(() { |
| 134 inMicrotask(() => throw 'first error'); |
| 135 inPeriodicTimer(() => throw 'second error'); |
| 136 }, onError: (error, chain) { |
| 137 try { |
| 138 if (first) { |
| 139 expect(error, equals('first error')); |
| 140 expect(chain.traces[1].frames, |
| 141 contains(frameMember(startsWith('inMicrotask')))); |
| 142 first = false; |
| 143 } else { |
| 144 expect(error, equals('second error')); |
| 145 expect(chain.traces[1].frames, |
| 146 contains(frameMember(startsWith('inPeriodicTimer')))); |
| 147 completer.complete(); |
| 148 } |
| 149 } catch (error, stackTrace) { |
| 150 completer.completeError(error, stackTrace); |
| 151 } |
| 152 }); |
| 153 |
| 154 return completer.future; |
| 155 }); |
| 156 |
| 157 test('passed to a completer', () { |
| 158 var trace = new Trace.current(); |
| 159 return captureFuture(() { |
| 160 inMicrotask(() => completerErrorFuture(trace)); |
| 161 }).then((chain) { |
| 162 expect(chain.traces, hasLength(3)); |
| 163 |
| 164 // The first trace is the trace that was manually reported for the |
| 165 // error. |
| 166 expect(chain.traces.first.toString(), equals(trace.toString())); |
| 167 |
| 168 // The second trace is the trace that was captured when |
| 169 // [Completer.addError] was called. |
| 170 expect(chain.traces[1].frames, |
| 171 contains(frameMember(startsWith('completerErrorFuture')))); |
| 172 |
| 173 // The third trace is the automatically-captured trace from when the |
| 174 // microtask was scheduled. |
| 175 expect(chain.traces[2].frames, |
| 176 contains(frameMember(startsWith('inMicrotask')))); |
| 177 }); |
| 178 }); |
| 179 |
| 180 test('passed to a completer with no stack trace', () { |
| 181 return captureFuture(() { |
| 182 inMicrotask(() => completerErrorFuture()); |
| 183 }).then((chain) { |
| 184 expect(chain.traces, hasLength(2)); |
| 185 |
| 186 // The first trace is the one captured when [Completer.addError] was |
| 187 // called. |
| 188 expect(chain.traces[0].frames, |
| 189 contains(frameMember(startsWith('completerErrorFuture')))); |
| 190 |
| 191 // The second trace is the automatically-captured trace from when the |
| 192 // microtask was scheduled. |
| 193 expect(chain.traces[1].frames, |
| 194 contains(frameMember(startsWith('inMicrotask')))); |
| 195 }); |
| 196 }); |
| 197 |
| 198 test('passed to a stream controller', () { |
| 199 var trace = new Trace.current(); |
| 200 return captureFuture(() { |
| 201 inMicrotask(() => controllerErrorStream(trace).listen(null)); |
| 202 }).then((chain) { |
| 203 expect(chain.traces, hasLength(3)); |
| 204 expect(chain.traces.first.toString(), equals(trace.toString())); |
| 205 expect(chain.traces[1].frames, |
| 206 contains(frameMember(startsWith('controllerErrorStream')))); |
| 207 expect(chain.traces[2].frames, |
| 208 contains(frameMember(startsWith('inMicrotask')))); |
| 209 }); |
| 210 }); |
| 211 |
| 212 test('passed to a stream controller with no stack trace', () { |
| 213 return captureFuture(() { |
| 214 inMicrotask(() => controllerErrorStream().listen(null)); |
| 215 }).then((chain) { |
| 216 expect(chain.traces, hasLength(2)); |
| 217 expect(chain.traces[0].frames, |
| 218 contains(frameMember(startsWith('controllerErrorStream')))); |
| 219 expect(chain.traces[1].frames, |
| 220 contains(frameMember(startsWith('inMicrotask')))); |
| 221 }); |
| 222 }); |
| 223 |
| 224 test('and relays them to the parent zone', () { |
| 225 var completer = new Completer(); |
| 226 |
| 227 runZoned(() { |
| 228 Chain.capture(() { |
| 229 inMicrotask(() => throw 'error'); |
| 230 }, onError: (error, chain) { |
| 231 expect(error, equals('error')); |
| 232 expect(chain.traces[1].frames, |
| 233 contains(frameMember(startsWith('inMicrotask')))); |
| 234 throw error; |
| 235 }); |
| 236 }, onError: (error, chain) { |
| 237 try { |
| 238 expect(error, equals('error')); |
| 239 expect(chain, new isInstanceOf<Chain>()); |
| 240 expect(chain.traces[1].frames, |
| 241 contains(frameMember(startsWith('inMicrotask')))); |
| 242 completer.complete(); |
| 243 } catch (error, stackTrace) { |
| 244 completer.completeError(error, stackTrace); |
| 245 } |
| 246 }); |
| 247 |
| 248 return completer.future; |
| 249 }); |
| 250 }); |
| 251 |
| 252 test('capture() without onError passes exceptions to parent zone', () { |
| 253 var completer = new Completer(); |
| 254 |
| 255 runZoned(() { |
| 256 Chain.capture(() => inMicrotask(() => throw 'error')); |
| 257 }, onError: (error, chain) { |
| 258 try { |
| 259 expect(error, equals('error')); |
| 260 expect(chain, new isInstanceOf<Chain>()); |
| 261 expect(chain.traces[1].frames, |
| 262 contains(frameMember(startsWith('inMicrotask')))); |
| 263 completer.complete(); |
| 264 } catch (error, stackTrace) { |
| 265 completer.completeError(error, stackTrace); |
| 266 } |
| 267 }); |
| 268 |
| 269 return completer.future; |
| 270 }); |
| 271 |
| 272 group('current() within capture()', () { |
| 273 test('called in a microtask', () { |
| 274 var completer = new Completer(); |
| 275 Chain.capture(() { |
| 276 inMicrotask(() => completer.complete(new Chain.current())); |
| 277 }); |
| 278 |
| 279 return completer.future.then((chain) { |
| 280 expect(chain.traces, hasLength(2)); |
| 281 expect(chain.traces[0].frames.first, frameMember(startsWith('main'))); |
| 282 expect(chain.traces[1].frames, |
| 283 contains(frameMember(startsWith('inMicrotask')))); |
| 284 }); |
| 285 }); |
| 286 |
| 287 test('called in a one-shot timer', () { |
| 288 var completer = new Completer(); |
| 289 Chain.capture(() { |
| 290 inOneShotTimer(() => completer.complete(new Chain.current())); |
| 291 }); |
| 292 |
| 293 return completer.future.then((chain) { |
| 294 expect(chain.traces, hasLength(2)); |
| 295 expect(chain.traces[0].frames.first, frameMember(startsWith('main'))); |
| 296 expect(chain.traces[1].frames, |
| 297 contains(frameMember(startsWith('inOneShotTimer')))); |
| 298 }); |
| 299 }); |
| 300 |
| 301 test('called in a periodic timer', () { |
| 302 var completer = new Completer(); |
| 303 Chain.capture(() { |
| 304 inPeriodicTimer(() => completer.complete(new Chain.current())); |
| 305 }); |
| 306 |
| 307 return completer.future.then((chain) { |
| 308 expect(chain.traces, hasLength(2)); |
| 309 expect(chain.traces[0].frames.first, frameMember(startsWith('main'))); |
| 310 expect(chain.traces[1].frames, |
| 311 contains(frameMember(startsWith('inPeriodicTimer')))); |
| 312 }); |
| 313 }); |
| 314 |
| 315 test('called in a nested series of asynchronous operations', () { |
| 316 var completer = new Completer(); |
| 317 Chain.capture(() { |
| 318 inPeriodicTimer(() { |
| 319 inOneShotTimer(() { |
| 320 inMicrotask(() => completer.complete(new Chain.current())); |
| 321 }); |
| 322 }); |
| 323 }); |
| 324 |
| 325 return completer.future.then((chain) { |
| 326 expect(chain.traces, hasLength(4)); |
| 327 expect(chain.traces[0].frames.first, frameMember(startsWith('main'))); |
| 328 expect(chain.traces[1].frames, |
| 329 contains(frameMember(startsWith('inMicrotask')))); |
| 330 expect(chain.traces[2].frames, |
| 331 contains(frameMember(startsWith('inOneShotTimer')))); |
| 332 expect(chain.traces[3].frames, |
| 333 contains(frameMember(startsWith('inPeriodicTimer')))); |
| 334 }); |
| 335 }); |
| 336 |
| 337 test('called in a long future chain', () { |
| 338 var completer = new Completer(); |
| 339 Chain.capture(() { |
| 340 inFutureChain(() => completer.complete(new Chain.current())); |
| 341 }); |
| 342 |
| 343 return completer.future.then((chain) { |
| 344 expect(chain.traces, hasLength(2)); |
| 345 expect(chain.traces[0].frames.first, frameMember(startsWith('main'))); |
| 346 expect(chain.traces[1].frames, |
| 347 contains(frameMember(startsWith('inFutureChain')))); |
| 348 }); |
| 349 }); |
| 350 }); |
| 351 |
| 352 test('current() outside of capture() returns a chain wrapping the current ' |
| 353 'trace', () { |
| 354 // The test runner runs all tests with chains enabled, so to test without we |
| 355 // have to do some zone munging. |
| 356 return runZoned(() { |
| 357 var completer = new Completer(); |
| 358 inMicrotask(() => completer.complete(new Chain.current())); |
| 359 |
| 360 return completer.future.then((chain) { |
| 361 // Since the chain wasn't loaded within [Chain.capture], the full stack |
| 362 // chain isn't available and it just returns the current stack when |
| 363 // called. |
| 364 expect(chain.traces, hasLength(1)); |
| 365 expect(chain.traces.first.frames.first, |
| 366 frameMember(startsWith('main'))); |
| 367 }); |
| 368 }, zoneValues: {#stack_trace.stack_zone.spec: null}); |
| 369 }); |
| 370 |
| 371 group('forTrace() within capture()', () { |
| 372 test('called for a stack trace from a microtask', () { |
| 373 return Chain.capture(() { |
| 374 return chainForTrace(inMicrotask, () => throw 'error'); |
| 375 }).then((chain) { |
| 376 // Because [chainForTrace] has to set up a future chain to capture the |
| 377 // stack trace while still showing it to the zone specification, it adds |
| 378 // an additional level of async nesting and so an additional trace. |
| 379 expect(chain.traces, hasLength(3)); |
| 380 expect(chain.traces[0].frames.first, frameMember(startsWith('main'))); |
| 381 expect(chain.traces[1].frames, |
| 382 contains(frameMember(startsWith('chainForTrace')))); |
| 383 expect(chain.traces[2].frames, |
| 384 contains(frameMember(startsWith('inMicrotask')))); |
| 385 }); |
| 386 }); |
| 387 |
| 388 test('called for a stack trace from a one-shot timer', () { |
| 389 return Chain.capture(() { |
| 390 return chainForTrace(inOneShotTimer, () => throw 'error'); |
| 391 }).then((chain) { |
| 392 expect(chain.traces, hasLength(3)); |
| 393 expect(chain.traces[0].frames.first, frameMember(startsWith('main'))); |
| 394 expect(chain.traces[1].frames, |
| 395 contains(frameMember(startsWith('chainForTrace')))); |
| 396 expect(chain.traces[2].frames, |
| 397 contains(frameMember(startsWith('inOneShotTimer')))); |
| 398 }); |
| 399 }); |
| 400 |
| 401 test('called for a stack trace from a periodic timer', () { |
| 402 return Chain.capture(() { |
| 403 return chainForTrace(inPeriodicTimer, () => throw 'error'); |
| 404 }).then((chain) { |
| 405 expect(chain.traces, hasLength(3)); |
| 406 expect(chain.traces[0].frames.first, frameMember(startsWith('main'))); |
| 407 expect(chain.traces[1].frames, |
| 408 contains(frameMember(startsWith('chainForTrace')))); |
| 409 expect(chain.traces[2].frames, |
| 410 contains(frameMember(startsWith('inPeriodicTimer')))); |
| 411 }); |
| 412 }); |
| 413 |
| 414 test('called for a stack trace from a nested series of asynchronous ' |
| 415 'operations', () { |
| 416 return Chain.capture(() { |
| 417 return chainForTrace((callback) { |
| 418 inPeriodicTimer(() => inOneShotTimer(() => inMicrotask(callback))); |
| 419 }, () => throw 'error'); |
| 420 }).then((chain) { |
| 421 expect(chain.traces, hasLength(5)); |
| 422 expect(chain.traces[0].frames.first, frameMember(startsWith('main'))); |
| 423 expect(chain.traces[1].frames, |
| 424 contains(frameMember(startsWith('chainForTrace')))); |
| 425 expect(chain.traces[2].frames, |
| 426 contains(frameMember(startsWith('inMicrotask')))); |
| 427 expect(chain.traces[3].frames, |
| 428 contains(frameMember(startsWith('inOneShotTimer')))); |
| 429 expect(chain.traces[4].frames, |
| 430 contains(frameMember(startsWith('inPeriodicTimer')))); |
| 431 }); |
| 432 }); |
| 433 |
| 434 test('called for a stack trace from a long future chain', () { |
| 435 return Chain.capture(() { |
| 436 return chainForTrace(inFutureChain, () => throw 'error'); |
| 437 }).then((chain) { |
| 438 expect(chain.traces, hasLength(3)); |
| 439 expect(chain.traces[0].frames.first, frameMember(startsWith('main'))); |
| 440 expect(chain.traces[1].frames, |
| 441 contains(frameMember(startsWith('chainForTrace')))); |
| 442 expect(chain.traces[2].frames, |
| 443 contains(frameMember(startsWith('inFutureChain')))); |
| 444 }); |
| 445 }); |
| 446 |
| 447 test('called for an unregistered stack trace returns a chain wrapping that ' |
| 448 'trace', () { |
| 449 var trace; |
| 450 var chain = Chain.capture(() { |
| 451 try { |
| 452 throw 'error'; |
| 453 } catch (_, stackTrace) { |
| 454 trace = stackTrace; |
| 455 return new Chain.forTrace(stackTrace); |
| 456 } |
| 457 }); |
| 458 |
| 459 expect(chain.traces, hasLength(1)); |
| 460 expect(chain.traces.first.toString(), |
| 461 equals(new Trace.from(trace).toString())); |
| 462 }); |
| 463 }); |
| 464 |
| 465 test('forTrace() outside of capture() returns a chain wrapping the given ' |
| 466 'trace', () { |
| 467 var trace; |
| 468 var chain = Chain.capture(() { |
| 469 try { |
| 470 throw 'error'; |
| 471 } catch (_, stackTrace) { |
| 472 trace = stackTrace; |
| 473 return new Chain.forTrace(stackTrace); |
| 474 } |
| 475 }); |
| 476 |
| 477 expect(chain.traces, hasLength(1)); |
| 478 expect(chain.traces.first.toString(), |
| 479 equals(new Trace.from(trace).toString())); |
| 480 }); |
| 481 } |
OLD | NEW |