Files
sdk/pkg/expect/lib/expect.dart
T
Nate Biggs 779fe017ba [dart2js] Add Dart web speciailized expectation helpers.
There are many failing Dart2JS language/library tests due to differences in backend implementations.

We want to adopt a strategy that allows us to make explicit the different expectations we have of each backend.

This adds logic for the 2 most common causes of these backend related failures (numbers and implicit checks) but we can add more of these as we discover more use cases.

This is an iteration on https://dart-review.googlesource.com/c/sdk/+/293463 attempting to integrate feedback from that change.

Change-Id: Ie3a1954066199695d92881497e940385467c9a12
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/311780
Commit-Queue: Nate Biggs <natebiggs@google.com>
Reviewed-by: Stephen Adams <sra@google.com>
Reviewed-by: Lasse Nielsen <lrn@google.com>
2023-08-11 20:59:30 +00:00

781 lines
28 KiB
Dart

// Copyright (c) 2012, 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.
/// This library contains an Expect class with static methods that can be used
/// for simple unit-tests.
library expect;
/// Whether the program is running without sound null safety.
bool get hasUnsoundNullSafety => const <Null>[] is List<Object>;
/// Whether the program is running with sound null safety.
bool get hasSoundNullSafety => !hasUnsoundNullSafety;
/// Whether the test was compiled by Dart2JS as production web code.
///
/// Production code unsafely omits some type checks that are required by the
/// language, under the assumption that production code will have no type
/// errors.
///
/// For example an invalid implicit down-cast like
/// `dynamic d = 3; String s = d;` might not be caught in production code.
final dart2jsProductionMode = const bool.fromEnvironment(
'dart.tool.dart2js.types:trust',
defaultValue: false);
/// Whether the test is running in a web environment, using JavaScript numbers.
///
/// In web compiled code, Dart integers are compiled to JavaScript numbers,
/// and have different ranges and behaviors of some operations, compared to
/// native compiled code (see [int] for more details about the difference
/// between native numbers and numbers on the web).
///
/// For example, using JavaScript numbers, an `int` value like `1` also
/// implements `double` and is the same object as `1.0`. In native numbers,
/// those values are two different objects, and integers do not implement
/// `double`.
final bool webNumbers = identical(1, 1.0);
/// Expect is used for tests that do not want to make use of the
/// Dart unit test library - for example, the core language tests.
/// Third parties are discouraged from using this, and should use
/// the expect() function in the unit test library instead for
/// test assertions.
class Expect {
/// Return a slice of a string.
///
/// The slice will contain at least the substring from [start] to the lower of
/// [end] and `start + length`.
/// If the result is no more than `length - 10` characters long,
/// context may be added by extending the range of the slice, by decreasing
/// [start] and increasing [end], up to at most length characters.
/// If the start or end of the slice are not matching the start or end of
/// the string, ellipses are added before or after the slice.
/// Characters other than printable ASCII are escaped.
static String _truncateString(String string, int start, int end, int length) {
if (end - start > length) {
end = start + length;
} else if (end - start < length) {
int overflow = length - (end - start);
if (overflow > 10) overflow = 10;
// Add context.
start = start - ((overflow + 1) ~/ 2);
end = end + (overflow ~/ 2);
if (start < 0) start = 0;
if (end > string.length) end = string.length;
}
StringBuffer buf = StringBuffer();
if (start > 0) buf.write("...");
_escapeSubstring(buf, string, 0, string.length);
if (end < string.length) buf.write("...");
return buf.toString();
}
/// Return the string with characters that are not printable ASCII characters
/// escaped as either "\xXX" codes or "\uXXXX" codes.
static String _escapeString(String string) {
StringBuffer buf = StringBuffer();
_escapeSubstring(buf, string, 0, string.length);
return buf.toString();
}
static _escapeSubstring(StringBuffer buf, String string, int start, int end) {
const hexDigits = "0123456789ABCDEF";
for (int i = start; i < end; i++) {
int code = string.codeUnitAt(i);
if (0x20 <= code && code < 0x7F) {
if (code == 0x5C) {
buf.write(r"\\");
} else {
buf.writeCharCode(code);
}
} else if (code < 0x100) {
buf.write(r"\x");
buf.write(hexDigits[code >> 4]);
buf.write(hexDigits[code & 15]);
} else {
buf.write(r"\u{");
buf.write(code.toRadixString(16).toUpperCase());
buf.write(r"}");
}
}
}
/// Find the difference between two strings.
///
/// This finds the first point where two strings differ, and returns
/// a text describing the difference.
///
/// For small strings (length less than 20) nothing is done, and "" is
/// returned. Small strings can be compared visually, but for longer strings
/// only a slice containing the first difference will be shown.
static String _stringDifference(String expected, String actual) {
if (expected.length < 20 && actual.length < 20) return "";
for (int i = 0; i < expected.length && i < actual.length; i++) {
if (expected.codeUnitAt(i) != actual.codeUnitAt(i)) {
int start = i;
i++;
while (i < expected.length && i < actual.length) {
if (expected.codeUnitAt(i) == actual.codeUnitAt(i)) break;
i++;
}
int end = i;
var truncExpected = _truncateString(expected, start, end, 20);
var truncActual = _truncateString(actual, start, end, 20);
return "at index $start: Expected <$truncExpected>, "
"Found: <$truncActual>";
}
}
return "";
}
/// Checks whether the expected and actual values are equal (using `==`).
static void equals(dynamic expected, dynamic actual, [String reason = ""]) {
if (expected == actual) return;
String msg = _getMessage(reason);
if (expected is String && actual is String) {
String stringDifference = _stringDifference(expected, actual);
if (stringDifference.isNotEmpty) {
_fail("Expect.equals($stringDifference$msg) fails.");
}
_fail("Expect.equals(expected: <${_escapeString(expected)}>"
", actual: <${_escapeString(actual)}>$msg) fails.");
}
_fail("Expect.equals(expected: <$expected>, actual: <$actual>$msg) fails.");
}
/// Checks whether the actual value is a bool and its value is true.
static void isTrue(dynamic actual, [String reason = ""]) {
if (_identical(actual, true)) return;
String msg = _getMessage(reason);
_fail("Expect.isTrue($actual$msg) fails.");
}
/// Checks whether the actual value is a bool and its value is false.
static void isFalse(dynamic actual, [String reason = ""]) {
if (_identical(actual, false)) return;
String msg = _getMessage(reason);
_fail("Expect.isFalse($actual$msg) fails.");
}
/// Checks whether [actual] is null.
static void isNull(dynamic actual, [String reason = ""]) {
if (null == actual) return;
String msg = _getMessage(reason);
_fail("Expect.isNull(actual: <$actual>$msg) fails.");
}
/// Checks whether [actual] is not null.
static void isNotNull(dynamic actual, [String reason = ""]) {
if (null != actual) return;
String msg = _getMessage(reason);
_fail("Expect.isNotNull(actual: <$actual>$msg) fails.");
}
/// Checks whether the Iterable [actual] is empty.
static void isEmpty(Iterable actual, [String reason = ""]) {
if (actual.isEmpty) return;
String msg = _getMessage(reason);
_fail("Expect.isEmpty(actual: <$actual>$msg) fails.");
}
/// Checks whether the Iterable [actual] is not empty.
static void isNotEmpty(Iterable actual, [String reason = ""]) {
if (actual.isNotEmpty) return;
String msg = _getMessage(reason);
_fail("Expect.isNotEmpty(actual: <$actual>$msg) fails.");
}
/// Checks whether the expected and actual values are identical
/// (using `identical`).
static void identical(dynamic expected, dynamic actual,
[String reason = ""]) {
if (_identical(expected, actual)) return;
String msg = _getMessage(reason);
if (expected is String && actual is String) {
String note =
(expected == actual) ? ' Strings equal but not identical.' : '';
_fail("Expect.identical(expected: <${_escapeString(expected)}>"
", actual: <${_escapeString(actual)}>$msg) "
"fails.$note");
}
_fail("Expect.identical(expected: <$expected>, actual: <$actual>$msg) "
"fails.");
}
/// Finds equivalence classes of objects (by index) wrt. identity.
///
/// Returns a list of lists of identical object indices per object.
/// That is, `objects[i]` is identical to objects with indices in
/// `_findEquivalences(objects)[i]`.
///
/// Uses `[]` for objects that are only identical to themselves.
static List<List<int>> _findEquivalences(List<dynamic> objects) {
var equivalences = List<List<int>>.generate(objects.length, (_) => []);
for (int i = 0; i < objects.length; i++) {
if (equivalences[i].isNotEmpty) continue;
var o = objects[i];
for (int j = i + 1; j < objects.length; j++) {
if (equivalences[j].isNotEmpty) continue;
if (_identical(o, objects[j])) {
if (equivalences[i].isEmpty) {
equivalences[i].add(i);
}
equivalences[j] = equivalences[i]..add(j);
}
}
}
return equivalences;
}
static void _writeEquivalences(List<dynamic> objects,
List<List<int>> equivalences, StringBuffer buffer) {
var separator = "";
for (int i = 0; i < objects.length; i++) {
buffer.write(separator);
separator = ",";
var equivalence = equivalences[i];
if (equivalence.isEmpty) {
buffer.write('_');
} else {
int first = equivalence[0];
buffer
..write('#')
..write(first);
if (first == i) {
buffer
..write('=')
..write(objects[i]);
}
}
}
}
static void allIdentical(List<dynamic> objects, [String reason = ""]) {
if (objects.length <= 1) return;
String msg = _getMessage(reason);
var equivalences = _findEquivalences(objects);
var first = equivalences[0];
if (first.isNotEmpty && first.length == objects.length) return;
var buffer = StringBuffer("Expect.allIdentical([");
_writeEquivalences(objects, equivalences, buffer);
buffer
..write("]")
..write(msg)
..write(")");
_fail(buffer.toString());
}
/// Checks whether the expected and actual values are *not* identical
/// (using `identical`).
static void notIdentical(var unexpected, var actual, [String reason = ""]) {
if (!_identical(unexpected, actual)) return;
String msg = _getMessage(reason);
_fail("Expect.notIdentical(expected and actual: <$actual>$msg) fails.");
}
/// Checks that no two [objects] are `identical`.
static void allDistinct(List<dynamic> objects, [String reason = ""]) {
String msg = _getMessage(reason);
var equivalences = _findEquivalences(objects);
bool hasEquivalence = false;
for (int i = 0; i < equivalences.length; i++) {
if (equivalences[i].isNotEmpty) {
hasEquivalence = true;
break;
}
}
if (!hasEquivalence) return;
var buffer = StringBuffer("Expect.allDistinct([");
_writeEquivalences(objects, equivalences, buffer);
buffer
..write("]")
..write(msg)
..write(")");
_fail(buffer.toString());
}
// Unconditional failure.
// This function always throws, as [_fail] always throws.
// TODO(srawlins): It would be more correct to change the return type to
// `Never`, which would require refactoring many language and co19 tests.
static void fail(String msg) {
_fail("Expect.fail('$msg')");
}
/// Failure if the difference between expected and actual is greater than the
/// given tolerance. If no tolerance is given, tolerance is assumed to be the
/// value 4 significant digits smaller than the value given for expected.
static void approxEquals(num expected, num actual,
[num tolerance = -1, String reason = ""]) {
if (tolerance < 0) {
tolerance = (expected / 1e4).abs();
}
// Note: use !( <= ) rather than > so we fail on NaNs
if ((expected - actual).abs() <= tolerance) return;
String msg = _getMessage(reason);
_fail('Expect.approxEquals(expected:<$expected>, actual:<$actual>, '
'tolerance:<$tolerance>$msg) fails');
}
static void notEquals(unexpected, actual, [String reason = ""]) {
if (unexpected != actual) return;
String msg = _getMessage(reason);
_fail("Expect.notEquals(unexpected: <$unexpected>, actual:<$actual>$msg) "
"fails.");
}
/// Checks that all elements in [expected] and [actual] are equal `==`.
/// This is different than the typical check for identity equality `identical`
/// used by the standard list implementation. It should also produce nicer
/// error messages than just calling `Expect.equals(expected, actual)`.
static void listEquals(List expected, List actual, [String reason = ""]) {
String msg = _getMessage(reason);
int n = (expected.length < actual.length) ? expected.length : actual.length;
for (int i = 0; i < n; i++) {
if (expected[i] != actual[i]) {
_fail('Expect.listEquals(at index $i, '
'expected: <${expected[i]}>, actual: <${actual[i]}>$msg) fails');
}
}
// We check on length at the end in order to provide better error
// messages when an unexpected item is inserted in a list.
if (expected.length != actual.length) {
_fail('Expect.listEquals(list length, '
'expected: <${expected.length}>, actual: <${actual.length}>$msg) '
'fails: Next element <'
'${expected.length > n ? expected[n] : actual[n]}>');
}
}
/// Checks that all [expected] and [actual] have the same set of keys (using
/// the semantics of [Map.containsKey] to determine what "same" means. For
/// each key, checks that the values in both maps are equal using `==`.
static void mapEquals(Map expected, Map actual, [String reason = ""]) {
String msg = _getMessage(reason);
// Make sure all of the values are present in both, and they match.
for (final key in expected.keys) {
if (!actual.containsKey(key)) {
_fail('Expect.mapEquals(missing expected key: <$key>$msg) fails');
}
Expect.equals(expected[key], actual[key]);
}
// Make sure the actual map doesn't have any extra keys.
for (final key in actual.keys) {
if (!expected.containsKey(key)) {
_fail('Expect.mapEquals(unexpected key: <$key>$msg) fails');
}
}
}
/// Specialized equality test for strings. When the strings don't match,
/// this method shows where the mismatch starts and ends.
static void stringEquals(String expected, String actual,
[String reason = ""]) {
if (expected == actual) return;
String msg = _getMessage(reason);
String defaultMessage =
'Expect.stringEquals(expected: <$expected>", <$actual>$msg) fails';
if ((expected as dynamic) == null || (actual as dynamic) == null) {
_fail(defaultMessage);
}
// Scan from the left until we find the mismatch.
int left = 0;
int right = 0;
int eLen = expected.length;
int aLen = actual.length;
while (true) {
if (left == eLen || left == aLen || expected[left] != actual[left]) {
break;
}
left++;
}
// Scan from the right until we find the mismatch.
int eRem = eLen - left; // Remaining length ignoring left match.
int aRem = aLen - left;
while (true) {
if (right == eRem ||
right == aRem ||
expected[eLen - right - 1] != actual[aLen - right - 1]) {
break;
}
right++;
}
// First difference is at index `left`, last at `length - right - 1`
// Make useful difference message.
// Example:
// Diff (1209..1209/1246):
// ...,{"name":"[ ]FallThroug...
// ...,{"name":"[ IndexError","kind":"class"},{"name":" ]FallThroug...
// (colors would be great!)
// Make snippets of up to ten characters before and after differences.
String leftSnippet = expected.substring(left < 10 ? 0 : left - 10, left);
int rightSnippetLength = right < 10 ? right : 10;
String rightSnippet =
expected.substring(eLen - right, eLen - right + rightSnippetLength);
// Make snippets of the differences.
String eSnippet = expected.substring(left, eLen - right);
String aSnippet = actual.substring(left, aLen - right);
// If snippets are long, elide the middle.
if (eSnippet.length > 43) {
eSnippet = '${eSnippet.substring(0, 20)}...'
'${eSnippet.substring(eSnippet.length - 20)}';
}
if (aSnippet.length > 43) {
aSnippet = '${aSnippet.substring(0, 20)}...'
'${aSnippet.substring(aSnippet.length - 20)}';
}
// Add "..." before and after, unless the snippets reach the end.
String leftLead = "...";
String rightTail = "...";
if (left <= 10) leftLead = "";
if (right <= 10) rightTail = "";
String diff = '\nDiff ($left..${eLen - right}/${aLen - right}):\n'
'$leftLead$leftSnippet[ $eSnippet ]$rightSnippet$rightTail\n'
'$leftLead$leftSnippet[ $aSnippet ]$rightSnippet$rightTail';
_fail("$defaultMessage$diff");
}
/// Checks that [haystack] contains a given substring [needle].
///
/// For example, this succeeds:
///
/// Expect.contains("a", "abcdefg");
static void contains(String needle, String haystack) {
if (!haystack.contains(needle)) {
_fail("String '$needle' not found within '$haystack'");
}
}
/// Checks that [haystack] contains one of the given substrings [needles].
///
/// For example, this succeeds:
///
/// Expect.containsOneOf(["a", "h"], "abcdefg");
static void containsOneOf(Iterable<String> needles, String haystack) {
if (!needles.any((s) => haystack.contains(s))) {
_fail("None of the strings '$needles' found within '$haystack'");
}
}
/// Checks that [actual] contains a given list of [substrings] in order.
///
/// For example, this succeeds:
///
/// Expect.stringContainsInOrder("abcdefg", ["a", "c", "e"]);
static void stringContainsInOrder(String actual, List<String> substrings) {
var start = 0;
for (var s in substrings) {
start = actual.indexOf(s, start);
if (start < 0) {
_fail("String '$actual' did not contain '$s' in the expected order: "
"${substrings.map((s) => "'$s'").join(", ")}");
}
}
}
/// Checks that every element of [expected] is also in [actual], and that
/// every element of [actual] is also in [expected].
static void setEquals(Iterable expected, Iterable actual,
[String reason = ""]) {
final missingSet = Set.from(expected);
missingSet.removeAll(actual);
final extraSet = Set.from(actual);
extraSet.removeAll(expected);
if (extraSet.isEmpty && missingSet.isEmpty) return;
String msg = _getMessage(reason);
StringBuffer sb = StringBuffer("Expect.setEquals($msg) fails");
// Report any missing items.
if (missingSet.isNotEmpty) {
sb.write('\nExpected collection does not contain: ');
}
for (final val in missingSet) {
sb.write('$val ');
}
// Report any extra items.
if (extraSet.isNotEmpty) {
sb.write('\nExpected collection should not contain: ');
}
for (final val in extraSet) {
sb.write('$val ');
}
_fail(sb.toString());
}
/// Checks that [expected] is equivalent to [actual].
///
/// If the objects are iterables or maps, recurses into them.
static void deepEquals(dynamic expected, dynamic actual) {
// Early exit check for equality.
if (expected == actual) return;
if (expected is String && actual is String) {
stringEquals(expected, actual);
} else if (expected is Iterable && actual is Iterable) {
var expectedLength = expected.length;
var actualLength = actual.length;
var length =
expectedLength < actualLength ? expectedLength : actualLength;
for (var i = 0; i < length; i++) {
deepEquals(expected.elementAt(i), actual.elementAt(i));
}
// We check on length at the end in order to provide better error
// messages when an unexpected item is inserted in a list.
if (expectedLength != actualLength) {
var nextElement =
(expectedLength > length ? expected : actual).elementAt(length);
_fail('Expect.deepEquals(list length, '
'expected: <$expectedLength>, actual: <$actualLength>) '
'fails: Next element <$nextElement>');
}
} else if (expected is Map && actual is Map) {
// Make sure all of the values are present in both and match.
for (final key in expected.keys) {
if (!actual.containsKey(key)) {
_fail('Expect.deepEquals(missing expected key: <$key>) fails');
}
Expect.deepEquals(expected[key], actual[key]);
}
// Make sure the actual map doesn't have any extra keys.
for (final key in actual.keys) {
if (!expected.containsKey(key)) {
_fail('Expect.deepEquals(unexpected key: <$key>) fails');
}
}
} else {
_fail("Expect.deepEquals(expected: <$expected>, actual: <$actual>) "
"fails.");
}
}
static bool _defaultCheck([dynamic _]) => true;
/// Verifies that [computation] throws a [T].
///
/// Calls the [computation] function and fails if that call doesn't throw,
/// throws something which is not a [T], or throws a [T] which does not
/// satisfy the optional [check] function.
///
/// Returns the accepted thrown [T] object, if one is caught.
/// This value can be checked further, instead of checking it in the [check]
/// function. For example, to check the content of the thrown object,
/// you could write this:
/// ```
/// var e = Expect.throws<MyException>(myThrowingFunction);
/// Expect.isTrue(e.myMessage.contains("WARNING"));
/// ```
/// The type variable can be omitted, in which case it defaults to [Object],
/// and the (sub-)type of the object can be checked in [check] instead.
/// This was traditionally done before Dart had generic methods.
///
/// If `computation` fails another test expectation
/// (i.e., throws an [ExpectException]),
/// that exception cannot be caught and accepted by [Expect.throws].
/// The test is still considered failing.
static T throws<T extends Object>(void Function() computation,
[bool Function(T error)? check, String? reason]) {
// TODO(vsm): Make check and reason nullable or change call sites.
// Existing tests pass null to set a reason and/or pass them through
// via helpers.
// TODO(rnystrom): Using the strange form below instead of "??=" to avoid
// warnings of unnecessary null checks when analyzed as NNBD code.
reason ??= "";
String msg = reason.isEmpty ? "" : "($reason)";
if ((computation as dynamic) == null) {
// Only throws from executing the function body should count as throwing.
// The failure to even call `f` should throw outside the try/catch.
testError("Function must not be null");
}
try {
computation();
} catch (e, s) {
// A test failure doesn't count as throwing.
if (e is ExpectException) rethrow;
if (e is T && (check ?? _defaultCheck)(e)) return e;
// Throws something unexpected.
String type = "";
if (T != dynamic && T != Object) {
type = "<$T>";
}
_fail("Expect.throws$type$msg: "
"Unexpected '${Error.safeToString(e)}'\n$s");
}
_fail('Expect.throws$msg fails: Did not throw');
}
static ArgumentError throwsArgumentError(void Function() f,
[String reason = "ArgumentError"]) =>
Expect.throws<ArgumentError>(f, _defaultCheck, reason);
static AssertionError throwsAssertionError(void Function() f,
[String reason = "AssertionError"]) =>
Expect.throws<AssertionError>(f, _defaultCheck, reason);
static FormatException throwsFormatException(void Function() f,
[String reason = "FormatException"]) =>
Expect.throws<FormatException>(f, _defaultCheck, reason);
static NoSuchMethodError throwsNoSuchMethodError(void Function() f,
[String reason = "NoSuchMethodError"]) =>
Expect.throws<NoSuchMethodError>(f, _defaultCheck, reason);
static Error throwsReachabilityError(void Function() f,
[String reason = "ReachabilityError"]) =>
Expect.throws<Error>(f,
(error) => error.toString().startsWith('ReachabilityError'), reason);
static RangeError throwsRangeError(void Function() f,
[String reason = "RangeError"]) =>
Expect.throws<RangeError>(f, _defaultCheck, reason);
static StateError throwsStateError(void Function() f,
[String reason = "StateError"]) =>
Expect.throws<StateError>(f, _defaultCheck, reason);
static TypeError throwsTypeError(void Function() f,
[String reason = "TypeError"]) =>
Expect.throws<TypeError>(f, _defaultCheck, reason);
static UnsupportedError throwsUnsupportedError(void Function() f,
[String reason = "UnsupportedError"]) =>
Expect.throws<UnsupportedError>(f, _defaultCheck, reason);
/// Reports that there is an error in the test itself and not the code under
/// test.
///
/// It may be using the expect API incorrectly or failing some other
/// invariant that the test expects to be true.
static void testError(String message) {
_fail("Test error: $message");
}
/// Checks that [object] has type [T].
static void type<T>(dynamic object, [String reason = ""]) {
if (object is T) return;
String msg = _getMessage(reason);
_fail("Expect.type($object is $T$msg) fails "
"on ${Error.safeToString(object)}");
}
/// Checks that [object] does not have type [T].
static void notType<T>(dynamic object, [String reason = ""]) {
if (object is! T) return;
String msg = _getMessage(reason);
_fail("Expect.type($object is! $T$msg) fails "
"on ${Error.safeToString(object)}");
}
/// Checks that `Sub` is a subtype of `Super` at compile time and run time.
static void subtype<Sub extends Super, Super>() {
_subtypeAtRuntime<Sub, Super>();
}
/// Checks that `Sub` is a subtype of `Super` at run time.
///
/// This is similar to [subtype] but without the `Sub extends Super` generic
/// constraint, so a compiler is less likely to optimize away the `is` check
/// because the types appear to be unrelated.
static void _subtypeAtRuntime<Sub, Super>() {
if (<Sub>[] is! List<Super>) {
_fail("Expect.subtype<$Sub, $Super>: $Sub is not a subtype of $Super");
}
}
/// Checks that `Sub` is not a subtype of `Super` at run time.
static void notSubtype<Sub, Super>() {
if (<Sub>[] is List<Super>) {
_fail("Expect.notSubtype<$Sub, $Super>: $Sub is a subtype of $Super");
}
}
static String _getMessage(String reason) =>
(reason.isEmpty) ? "" : ", '$reason'";
static Never _fail(String message) {
throw ExpectException(message);
}
}
/// Used in [Expect] because [Expect.identical] shadows the real [identical].
bool _identical(a, b) => identical(a, b);
/// Exception thrown on a failed expectation check.
///
/// Always recognized by [Expect.throws] as an unexpected error.
class ExpectException {
/// Call this to provide a function that associates a test name with this
/// failure.
///
/// Used by async_helper/async_minitest.dart to inject logic to bind the
/// `group()` and `test()` name strings to a test failure.
static void setTestNameCallback(String Function() getName) {
_getTestName = getName;
}
static String Function() _getTestName = _kEmptyString;
final String message;
final String name;
ExpectException(this.message) : name = _getTestName();
@override
String toString() {
if (name != "") return 'In test "$name" $message';
return message;
}
/// Initial value for _getTestName.
static String _kEmptyString() => "";
}
/// Is true iff type assertions are enabled.
// TODO(rnystrom): Remove this once all tests are no longer using it.
final bool typeAssertionsEnabled = (() {
try {
dynamic i = 42;
String s = i; // ignore: unused_local_variable
} on TypeError {
return true;
}
return false;
})();
/// Is true iff `assert` statements are enabled.
final bool assertStatementsEnabled = (() {
bool result = false;
assert(result = true);
return result;
})();
/// Is true iff checked mode is enabled.
// TODO(rnystrom): Remove this once all tests are no longer using it.
final bool checkedModeEnabled =
typeAssertionsEnabled && assertStatementsEnabled;