More consistent unexpected error formatting (#2644)
- Include stack traces in the rejection output for more extensions which
have catch blocks for unexpected exceptions.
- Use more consistent phrasing and formatting with a line ending in
`at:` before the stack trace.
- Add indentation to all outputs that include stack traces.
- Use the `LineSplitter.split` utility method everywhere lines are split.
diff --git a/pkgs/checks/CHANGELOG.md b/pkgs/checks/CHANGELOG.md
index 834d180..27d8623 100644
--- a/pkgs/checks/CHANGELOG.md
+++ b/pkgs/checks/CHANGELOG.md
@@ -2,6 +2,7 @@
- Require Dart 3.7
- Improve speed of pretty printing for large collections.
+- Improve formatting for failures involving unexpected exceptions.
## 0.3.1
diff --git a/pkgs/checks/lib/src/describe.dart b/pkgs/checks/lib/src/describe.dart
index 88b46d9..f5cf7a5 100644
--- a/pkgs/checks/lib/src/describe.dart
+++ b/pkgs/checks/lib/src/describe.dart
@@ -76,7 +76,7 @@
} else if (object is Condition<Never>) {
return ['<A value that:', ...postfixLast('>', describe(object))];
} else {
- final value = const LineSplitter().convert(object.toString());
+ final value = LineSplitter.split(object.toString());
return isTopLevel ? prefixFirst('<', postfixLast('>', value)) : value;
}
}
diff --git a/pkgs/checks/lib/src/extensions/async.dart b/pkgs/checks/lib/src/extensions/async.dart
index 34a768b..2ff8ace 100644
--- a/pkgs/checks/lib/src/extensions/async.dart
+++ b/pkgs/checks/lib/src/extensions/async.dart
@@ -27,7 +27,7 @@
actual: ['a future that completes as an error'],
which: [
...prefixFirst('threw ', postfixLast(' at:', literal(e))),
- ...const LineSplitter().convert(st.toString()),
+ ...indent(LineSplitter.split(st.toString())),
],
);
}
@@ -57,10 +57,10 @@
onError: (Object e, StackTrace st) {
reject(
Rejection(
- actual: ['a future that completed as an error:'],
+ actual: ['a future that completed as an error'],
which: [
- ...prefixFirst('threw ', literal(e)),
- ...const LineSplitter().convert(st.toString()),
+ ...prefixFirst('threw ', postfixLast(' at:', literal(e))),
+ ...indent(LineSplitter.split(st.toString())),
],
),
);
@@ -97,7 +97,7 @@
actual: prefixFirst('completed to error ', literal(e)),
which: [
'threw an exception that is not a $E at:',
- ...const LineSplitter().convert(st.toString()),
+ ...indent(LineSplitter.split(st.toString())),
],
);
}
@@ -160,7 +160,7 @@
actual: prefixFirst('a stream with error ', literal(e)),
which: [
'emitted an error instead of a value at:',
- ...const LineSplitter().convert(st.toString()),
+ ...indent(LineSplitter.split(st.toString())),
],
);
}
@@ -208,7 +208,7 @@
actual: prefixFirst('a stream with error ', literal(e)),
which: [
'emitted an error which is not $E at:',
- ...const LineSplitter().convert(st.toString()),
+ ...indent(LineSplitter.split(st.toString())),
],
);
}
@@ -510,8 +510,11 @@
return Rejection(
actual: ['a stream'],
which: [
- ...prefixFirst('emitted an unexpected error: ', literal(e)),
- ...const LineSplitter().convert(st.toString()),
+ ...prefixFirst(
+ 'emitted an unexpected error: ',
+ postfixLast(' at:', literal(e)),
+ ),
+ ...indent(LineSplitter.split(st.toString())),
],
);
}
diff --git a/pkgs/checks/lib/src/extensions/core.dart b/pkgs/checks/lib/src/extensions/core.dart
index 8059553..9f3dd73 100644
--- a/pkgs/checks/lib/src/extensions/core.dart
+++ b/pkgs/checks/lib/src/extensions/core.dart
@@ -42,8 +42,11 @@
} catch (e, st) {
return Extracted.rejection(
which: [
- ...prefixFirst('threw while trying to read $name: ', literal(e)),
- ...const LineSplitter().convert(st.toString()),
+ ...prefixFirst(
+ 'threw while trying to read $name: ',
+ postfixLast(' at:', literal(e)),
+ ),
+ ...indent(LineSplitter.split(st.toString())),
],
);
}
diff --git a/pkgs/checks/lib/src/extensions/function.dart b/pkgs/checks/lib/src/extensions/function.dart
index c23d4a6..0fb27f3 100644
--- a/pkgs/checks/lib/src/extensions/function.dart
+++ b/pkgs/checks/lib/src/extensions/function.dart
@@ -1,6 +1,7 @@
// Copyright (c) 2022, 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.
+import 'dart:convert';
import '../../context.dart';
@@ -24,11 +25,14 @@
actual: prefixFirst('a function that returned ', literal(result)),
which: ['did not throw'],
);
- } catch (e) {
+ } catch (e, st) {
if (e is E) return Extracted.value(e as E);
return Extracted.rejection(
actual: prefixFirst('a function that threw error ', literal(e)),
- which: ['did not throw an $E'],
+ which: [
+ 'threw an exception that is not a $E at:',
+ ...indent(LineSplitter.split(st.toString())),
+ ],
);
}
});
@@ -48,8 +52,8 @@
return Extracted.rejection(
actual: ['a function that throws'],
which: [
- ...prefixFirst('threw ', literal(e)),
- ...st.toString().split('\n'),
+ ...prefixFirst('threw ', postfixLast(' at:', literal(e))),
+ ...indent(LineSplitter.split(st.toString())),
],
);
}
diff --git a/pkgs/checks/test/context_test.dart b/pkgs/checks/test/context_test.dart
index d7c5567..064dd39 100644
--- a/pkgs/checks/test/context_test.dart
+++ b/pkgs/checks/test/context_test.dart
@@ -171,7 +171,7 @@
Rejection(
which: [
...prefixFirst('threw late error', literal(error.error)),
- ...const LineSplitter().convert(
+ ...LineSplitter.split(
TestHandle.current
.formatStackTrace(error.stackTrace)
.toString(),
diff --git a/pkgs/checks/test/extensions/async_test.dart b/pkgs/checks/test/extensions/async_test.dart
index e632bc4..75647be 100644
--- a/pkgs/checks/test/extensions/async_test.dart
+++ b/pkgs/checks/test/extensions/async_test.dart
@@ -22,7 +22,7 @@
await check(_futureFail()).isRejectedByAsync(
(it) => it.completes((it) => it.equals(1)),
actual: ['a future that completes as an error'],
- which: ['threw <UnimplementedError> at:', 'fake trace'],
+ which: ['threw <UnimplementedError> at:', ' fake trace'],
);
});
test('can be described', () async {
@@ -66,7 +66,7 @@
actual: ['completed to error <UnimplementedError>'],
which: [
'threw an exception that is not a StateError at:',
- 'fake trace',
+ ' fake trace',
],
);
},
@@ -134,9 +134,9 @@
.equals('''
Expected: a Future<String> that:
does not complete
-Actual: a future that completed as an error:
-Which: threw 'error'
-fake trace''');
+Actual: a future that completed as an error
+Which: threw 'error' at:
+ fake trace''');
});
test('can be described', () async {
await check(
@@ -164,7 +164,7 @@
await check(_countingStream(1, errorAt: 0)).isRejectedByAsync(
(it) => it.emits(),
actual: ['a stream with error <UnimplementedError: Error at 1>'],
- which: ['emitted an error instead of a value at:', 'fake trace'],
+ which: ['emitted an error instead of a value at:', ' fake trace'],
);
});
test('can be described', () async {
@@ -215,7 +215,7 @@
actual: ['a stream with error <UnimplementedError: Error at 1>'],
which: [
'emitted an error which is not StateError at:',
- 'fake trace',
+ ' fake trace',
],
);
},
@@ -494,7 +494,7 @@
await check(StreamQueue(controller.stream)).isRejectedByAsync(
(it) => it.isDone(),
actual: ['a stream'],
- which: ['emitted an unexpected error: \'sad\'', 'fake trace'],
+ which: ['emitted an unexpected error: \'sad\' at:', ' fake trace'],
);
});
test('uses a transaction', () async {
diff --git a/pkgs/checks/test/extensions/core_test.dart b/pkgs/checks/test/extensions/core_test.dart
index cbcca7b..5a44da8 100644
--- a/pkgs/checks/test/extensions/core_test.dart
+++ b/pkgs/checks/test/extensions/core_test.dart
@@ -27,8 +27,8 @@
);
}, 'foo').isNotNull(),
which: [
- 'threw while trying to read foo: <UnimplementedError>',
- 'fake trace',
+ 'threw while trying to read foo: <UnimplementedError> at:',
+ ' fake trace',
],
);
});
diff --git a/pkgs/checks/test/extensions/function_test.dart b/pkgs/checks/test/extensions/function_test.dart
index aefba11..dde1600 100644
--- a/pkgs/checks/test/extensions/function_test.dart
+++ b/pkgs/checks/test/extensions/function_test.dart
@@ -21,10 +21,18 @@
);
});
test('fails for functions that throw the wrong type', () {
- check(() => throw StateError('oops!')).isRejectedBy(
+ check(() {
+ Error.throwWithStackTrace(
+ StateError('oops!'),
+ StackTrace.fromString('fake trace'),
+ );
+ }).isRejectedBy(
(it) => it.throws<ArgumentError>(),
actual: ['a function that threw error <Bad state: oops!>'],
- which: ['did not throw an ArgumentError'],
+ which: [
+ 'threw an exception that is not a ArgumentError at:',
+ ' fake trace',
+ ],
);
});
});
@@ -42,7 +50,7 @@
}).isRejectedBy(
(it) => it.returnsNormally(),
actual: ['a function that throws'],
- which: ['threw <Bad state: oops!>', 'fake trace'],
+ which: ['threw <Bad state: oops!> at:', ' fake trace'],
);
});
});