blob: 7a79a492fc791a7c55e6ae26c396424f7566274d [file] [log] [blame]
// 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<Never> throwErrorStream(Error error) sync* {
yield throw error;
}
Iterable<Never> throwWithStackStream(Error error, StackTrace stack) sync* {
yield Error.throwWithStackTrace(error, stack);
}
Iterable<dynamic> 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<void> testAsync() async {
Future<Never> throwError(Error error) async => throw error;
Future<Never> throwErrorWithStack(Error error, StackTrace stack) async =>
Error.throwWithStackTrace(error, stack);
Future<dynamic> throwNSM(dynamic c) async => c * 4;
return _testAsync("async", throwError, throwErrorWithStack, throwNSM);
}
Future<void> testAsyncStar() async {
Stream<Never> throwErrorStream(Error error) async* {
yield throw error;
}
Future<Never> throwError(Error error) => throwErrorStream(error).first;
Stream<Never> throwErrorWithStackStream(
Error error, StackTrace stack) async* {
yield Error.throwWithStackTrace(error, stack);
}
Future<Never> throwErrorWithStack(Error error, StackTrace stack) =>
throwErrorWithStackStream(error, stack).first;
Stream<dynamic> throwNSMStream(dynamic c) async* {
yield c * 4;
}
Future<dynamic> 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<void> _testAsync(
String functionKind,
Future<Never> Function(Error) throwError,
Future<Never> Function(Error, StackTrace) throwWithStack,
Future<dynamic> 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<void> 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 {}