// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. // Tests that the `Error.stackTrace` is set when thrown, not before, // and that it contains the same stack trace text as the stack trace // captured by `catch` the first time the error is thrown, // and that it doesn't change if thrown again. // (Derived from `runtime/tests/vm/dart/error_stacktrace_test.dart`.) import "package:expect/expect.dart"; import "package:async_helper/async_helper.dart"; void main() async { testSync(); testSyncStar(); // A `throw` in a `sync*` function. asyncStart(); await testAsync(); await testAsyncStar(); asyncEnd(); } void testSync() { Never throwError(Error error) => throw error; Never throwWithStack(Error error, StackTrace stack) => Error.throwWithStackTrace(error, stack); dynamic throwNSM(dynamic c) => c * 4; _testSync("sync", throwError, throwWithStack, throwNSM); } void testSyncStar() { Iterable throwErrorStream(Error error) sync* { yield throw error; } Iterable throwWithStackStream(Error error, StackTrace stack) sync* { yield Error.throwWithStackTrace(error, stack); } Iterable throwNSMStream(dynamic c) sync* { yield c * 4; } Never throwError(Error error) => throwErrorStream(error).first; Never throwWithStack(Error error, StackTrace stack) => throwWithStackStream(error, stack).first; dynamic throwNSM(dynamic c) => throwNSMStream(c).first; _testSync("sync*", throwError, throwWithStack, throwNSM); } Future testAsync() async { Future throwError(Error error) async => throw error; Future throwErrorWithStack(Error error, StackTrace stack) async => Error.throwWithStackTrace(error, stack); Future throwNSM(dynamic c) async => c * 4; return _testAsync("async", throwError, throwErrorWithStack, throwNSM); } Future testAsyncStar() async { Stream throwErrorStream(Error error) async* { yield throw error; } Future throwError(Error error) => throwErrorStream(error).first; Stream throwErrorWithStackStream( Error error, StackTrace stack) async* { yield Error.throwWithStackTrace(error, stack); } Future throwErrorWithStack(Error error, StackTrace stack) => throwErrorWithStackStream(error, stack).first; Stream throwNSMStream(dynamic c) async* { yield c * 4; } Future throwNSM(dynamic c) => throwNSMStream(c).first; return _testAsync("async*", throwError, throwErrorWithStack, throwNSM); } void _testSync( String functionKind, Never Function(Error) throwError, Never Function(Error, StackTrace) throwWithStack, dynamic Function(dynamic) throwNSM) { // Checks that an error first thrown with [firstStack] as [Error.stackTrace], // will keep that stack trace if thrown again asynchronously. void testErrorSet(String throwKind, Error error, StackTrace firstStack) { var desc = "$functionKind $throwKind"; // Was thrown with [stackTrace] as stack trace. Expect.isNotNull(error.stackTrace, "$desc throw - did not set .stackTrace"); Expect.stringEquals(firstStack.toString(), error.stackTrace.toString(), "$desc, caught stack/set stack - not same"); // Throw same error again, using `throw`, with different stack. try { throwError(error); } on Error catch (e, s) { var redesc = "$functionKind throw again"; Expect.identical(error, e, "$redesc - not same error"); Expect.notEquals(firstStack.toString(), s.toString(), "$redesc, set stack/new stack - not different"); // Did not overwrite existing `error.stackTrace`. Expect.equals(firstStack.toString(), e.stackTrace.toString(), "$redesc - changed .stackTrace"); } // Throw same error again using `Error.throwWithStackTrace`. var stack2 = StackTrace.fromString("stack test string 2"); try { throwWithStack(error, stack2); } on Error catch (e, s) { var redesc = "$functionKind E.tWST again"; Expect.identical(error, e, "$redesc - not same error"); Expect.equals(stack2.toString(), s.toString(), "$redesc, thrown stack/caught stack - not same"); Expect.notEquals(firstStack.toString(), s.toString(), "$redesc, first stack/new stack - not different"); // Did not overwrite existing `error.stackTrace`. Expect.equals(firstStack.toString(), e.stackTrace.toString(), "$redesc - changed .stackTrace"); } } { // System thrown error. try { throwNSM(NoMult()); Expect.fail("Did not throw"); } on NoSuchMethodError catch (e, s) { testErrorSet("throwNSM", e, s); } } { // User thrown error, explicit `throw`. var error = StateError("error test string"); Expect.isNull(error.stackTrace); try { throwError(error); } on Error catch (e, s) { Expect.identical(error, e, "$functionKind throw: thrown error/caught error - not same"); testErrorSet("throw", e, s); } } { // Thrown using `Error.throwWithStackTrace`. var error = StateError("error test string"); Expect.isNull(error.stackTrace); var stack = StackTrace.fromString("stack test string"); try { throwWithStack(error, stack); } on Error catch (e, s) { Expect.identical(error, e, "$functionKind E.tWST: thrown error/caught error - not same"); Expect.stringEquals(stack.toString(), s.toString(), "$functionKind E.tWST: thrown stack/caught stack - not same"); testErrorSet("E.tWST", e, s); } } } Future _testAsync( String functionKind, Future Function(Error) throwError, Future Function(Error, StackTrace) throwWithStack, Future Function(dynamic) throwNSM) async { // Checks that an error first thrown with [firstStack] as [Error.stackTrace], // will keep that stack trace if thrown again asynchronously. Future testErrorSet( String throwKind, Error error, StackTrace firstStack) async { var desc = "$functionKind $throwKind"; // Was thrown with [stackTrace] as stack trace. Expect.isNotNull(error.stackTrace, "$desc throw - did not set .stackTrace"); Expect.stringEquals(firstStack.toString(), error.stackTrace.toString(), "$desc, caught stack/set stack - not same"); // Throw same error again, using `throw`, with different stack. try { await throwError(error); } on Error catch (e, s) { var redesc = "$functionKind throw again"; Expect.identical(error, e, "$redesc - not same error"); if (functionKind != "async*") { // An async* throw happens asynchronously, so its stack trace // can be just the same short stack from the event loop every time. Expect.notEquals(firstStack.toString(), s.toString(), "$redesc, set stack/new stack - not different"); } // Did not overwrite existing `error.stackTrace`. Expect.equals(firstStack.toString(), e.stackTrace.toString(), "$redesc - changed .stackTrace"); } // Throw same error again using `Error.throwWithStackTrace`. var stack2 = StackTrace.fromString("stack test string 2"); try { await throwWithStack(error, stack2); } on Error catch (e, s) { var redesc = "$functionKind E.tWST again"; Expect.identical(error, e, "$redesc - not same error"); Expect.equals(stack2.toString(), s.toString(), "$redesc, thrown stack/caught stack - not same"); if (functionKind != "async*") { Expect.notEquals(firstStack.toString(), s.toString(), "$redesc, first stack/new stack - not different"); } // Did not overwrite existing `error.stackTrace`. Expect.equals(firstStack.toString(), e.stackTrace.toString(), "$redesc - changed .stackTrace"); } } asyncStart(); { // System thrown error. try { await throwNSM(NoMult()); Expect.fail("Did not throw"); } on NoSuchMethodError catch (e, s) { await testErrorSet("throwNSM", e, s); } } { // User thrown error, explicit `throw`. var error = StateError("error test string"); Expect.isNull(error.stackTrace); try { await throwError(error); } on Error catch (e, s) { Expect.identical(error, e, "$functionKind throw: thrown error/caught error - not same"); await testErrorSet("throw", e, s); } } { // Thrown using `Error.throwWithStackTrace`. var error = StateError("error test string"); Expect.isNull(error.stackTrace); var stack = StackTrace.fromString("stack test string"); try { await throwWithStack(error, stack); } on Error catch (e, s) { Expect.identical(error, e, "$functionKind E.tWST: thrown error/caught error - not same"); Expect.stringEquals(stack.toString(), s.toString(), "$functionKind E.tWST: thrown stack/caught stack - not same"); await testErrorSet("E.tWST", e, s); } } asyncEnd(); } // Has no `operator *`, forcing a dynamic `NoSuchMethodError` // when used in `c * 4`. class NoMult {}