blob: 91b3eba48e685d80465489b1684afa12bdffc08c [file] [log] [blame]
// Copyright 2018 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// ignore_for_file: public_member_api_docs
/// A minimal, dependency-free emulation layer for a subset of the
/// unittest/test API used by the language and core library.
///
/// Compared with `minitest.dart`, this library supports and expects
/// asynchronous tests. It uses a `Zone` per test to associate a test name with
/// the failure.
///
/// It does not support `setUp` or `tearDown` methods,
/// and matchers are severely restricted.
///
/// A number of our language and core library tests were written against the
/// unittest package, which is now deprecated in favor of the new test package.
/// The latter is much better feature-wise, but is also quite complex and has
/// many dependencies. For low-level language and core library tests, we don't
/// want to have to pull in a large number of dependencies and be able to run
/// them correctly in order to run a test, so we want to test them against
/// something simpler.
///
/// When possible, we just use the tiny expect library. But to avoid rewriting
/// all of the existing tests that use unittest, they can instead use this,
/// which shims the unittest API with as little code as possible and calls into
/// expect.
///
/// Eventually, it would be good to refactor those tests to use the expect
/// package directly and remove this.
library;
import 'dart:async';
import 'package:async_helper/async_helper.dart';
import 'package:expect/expect.dart';
void group(String name, void Function() body) {
final oldName = _pushName(name);
try {
body();
} finally {
_popName(oldName);
}
}
void test(String name, dynamic Function() body) {
final oldName = _pushName(name);
asyncStart();
final result = runZoned(body, zoneValues: {_testToken: _currentName});
if (result is Future<dynamic>) {
result.then((_) {
asyncEnd();
});
} else {
// Ensure all tests get to be set up first.
scheduleMicrotask(asyncEnd);
}
_popName(oldName);
}
void expect(dynamic value, dynamic matcher, {String reason = ''}) {
Matcher m;
if (matcher is _Matcher) {
m = matcher.call;
} else if (matcher is! Matcher) {
m = equals(matcher);
} else {
m = matcher;
}
m(value);
}
R Function() expectAsync0<R>(R Function() f, {int count = 1}) {
asyncStart(count);
return () {
final result = f();
asyncEnd();
return result;
};
}
R Function(A) expectAsync1<R, A>(R Function(A) f, {int count = 1}) {
asyncStart(count);
return (A a) {
final result = f(a);
asyncEnd();
return result;
};
}
R Function(A, B) expectAsync2<R, A, B>(R Function(A, B) f, {int count = 1}) {
asyncStart(count);
return (A a, B b) {
final result = f(a, b);
asyncEnd();
return result;
};
}
dynamic expectAsync(Function f, {int count = 1}) {
final f2 = f; // Avoid type-promoting f, we want dynamic invocations.
if (f2 is void Function(Never, Never, Never, Never, Never)) {
asyncStart(count);
return ([Object? a, Object? b, Object? c, Object? d, Object? e]) {
final result = f(a, b, c, d, e); // ignore: avoid_dynamic_calls
asyncEnd();
return result;
};
}
if (f2 is void Function(Never, Never, Never, Never)) {
asyncStart(count);
return ([Object? a, Object? b, Object? c, Object? d]) {
final result = f(a, b, c, d); // ignore: avoid_dynamic_calls
asyncEnd();
return result;
};
}
if (f2 is void Function(Never, Never, Never)) {
asyncStart(count);
return ([Object? a, Object? b, Object? c]) {
final result = f(a, b, c); // ignore: avoid_dynamic_calls
asyncEnd();
return result;
};
}
if (f2 is void Function(Never, Never)) {
asyncStart(count);
return ([Object? a, Object? b]) {
final result = f(a, b); // ignore: avoid_dynamic_calls
asyncEnd();
return result;
};
}
if (f2 is void Function(Never)) {
asyncStart(count);
return ([Object? a]) {
final result = f(a); // ignore: avoid_dynamic_calls
asyncEnd();
return result;
};
}
if (f2 is void Function()) {
asyncStart(count);
return () {
final result = f2();
asyncEnd();
return result;
};
}
throw UnsupportedError(
'expectAsync only accepts up to five argument functions');
}
// Matchers
typedef Matcher = void Function(dynamic);
Matcher same(dynamic o) => (v) {
Expect.identical(o, v);
};
Matcher equals(dynamic o) => (v) {
Expect.deepEquals(o, v);
};
Matcher greaterThan(num n) => (dynamic v) {
Expect.type<num>(v);
final num value = v as num;
if (value > n) {
return;
}
Expect.fail('$v is not greater than $n');
};
Matcher greaterThanOrEqualTo(num n) => (dynamic v) {
Expect.type<num>(v);
final num value = v as num;
if (value >= n) {
return;
}
Expect.fail('$v is not greater than $n');
};
Matcher lessThan(num n) => (dynamic v) {
Expect.type<num>(v);
final num value = v as num;
if (value < n) {
return;
}
Expect.fail('$v is not less than $n');
};
Matcher lessThanOrEqualTo(num n) => (dynamic v) {
Expect.type<num>(v);
final num value = v as num;
if (value <= n) {
return;
}
Expect.fail('$v is not less than $n');
};
Matcher predicate(bool Function(dynamic value) fn, [String description = '']) =>
(dynamic v) {
Expect.isTrue(fn(v), description);
};
Matcher anyOf(List<String> expected) => (dynamic actual) {
for (final string in expected) {
if (actual == string) {
return;
}
}
Expect.fail('Expected $actual to be one of $expected.');
};
void isTrue(dynamic v) {
Expect.isTrue(v);
}
void isFalse(dynamic v) {
Expect.isFalse(v);
}
void isNull(dynamic o) {
Expect.isNull(o);
}
void _checkThrow<T extends Object>(
dynamic v, void Function(dynamic error) onError) {
if (v is Future) {
asyncStart();
v.then((_) {
Expect.fail('Did not throw');
}, onError: (Object e, s) {
if (e is! T) {
throw e;
}
onError(e);
asyncEnd();
});
return;
}
Expect.throws<T>(v as void Function(), (dynamic e) {
onError(e);
return true;
});
}
void returnsNormally(dynamic o) {
try {
Expect.type<void Function()>(o);
(o as void Function())();
} catch (error, trace) {
Expect.fail(
'Expected function to return normally, but threw:\n$error\n\n$trace');
}
}
void throws(dynamic v) {
_checkThrow<Object>(v, (_) {});
}
Matcher throwsA(dynamic matcher) => (dynamic o) {
_checkThrow<Object>(o, (error) {
expect(error, matcher);
});
};
Matcher completion(dynamic matcher) => (dynamic o) {
Expect.type<Future<dynamic>>(o);
final Future<dynamic> future = o as Future<dynamic>;
asyncStart();
future.then((value) {
expect(value, matcher);
asyncEnd();
});
};
void completes(dynamic o) {
Expect.type<Future<dynamic>>(o);
final Future<dynamic> future = o as Future<dynamic>;
asyncStart();
future.then<void>(asyncSuccess);
}
void isMap(dynamic o) {
Expect.type<Map<dynamic, dynamic>>(o);
}
void isList(dynamic o) {
Expect.type<List<dynamic>>(o);
}
void isNotNull(dynamic o) {
Expect.isNotNull(o);
}
abstract class _Matcher {
void call(dynamic o);
}
// ignore: camel_case_types
class isInstanceOf<T> implements _Matcher {
@override
void call(dynamic o) {
Expect.type<T>(o);
}
}
void throwsArgumentError(dynamic v) {
_checkThrow<ArgumentError>(v, (_) {});
}
void throwsStateError(dynamic v) {
_checkThrow<StateError>(v, (_) {});
}
void fail(String message) {
Expect.fail(message);
}
/// Key for zone value holding current test name.
final _testToken = Object();
bool _initializedTestNameCallback = false;
/// The current combined name of the nesting [group] or [test].
String _currentName = '';
String _pushName(String newName) {
// Look up the current test name from the zone created for the test.
if (!_initializedTestNameCallback) {
ExpectException.setTestNameCallback(
() => Zone.current[_testToken] as String);
_initializedTestNameCallback = true;
}
final oldName = _currentName;
if (oldName == '') {
_currentName = newName;
} else {
_currentName = '$oldName $newName';
}
return oldName;
}
void _popName(String oldName) {
_currentName = oldName;
}