Commit f31fc1bd authored by Ian Hickson's avatar Ian Hickson Committed by Dan Field

More removing of timeouts. (#33932)

parent 70400954
...@@ -7,7 +7,7 @@ import 'package:flutter_test/flutter_test.dart'; ...@@ -7,7 +7,7 @@ import 'package:flutter_test/flutter_test.dart';
void main() { void main() {
testWidgets('flutter_test timeout logic - addTime - negative', (WidgetTester tester) async { testWidgets('flutter_test timeout logic - addTime - negative', (WidgetTester tester) async {
await tester.runAsync(() async { await tester.runAsync(() async {
await Future<void>.delayed(const Duration(milliseconds: 3500)); await Future<void>.delayed(const Duration(milliseconds: 3500)); // must be more than 1000ms more than the initial timeout
}, additionalTime: const Duration(milliseconds: 200)); }, additionalTime: const Duration(milliseconds: 200));
}); }, initialTimeout: const Duration(milliseconds: 2000));
} }
...@@ -7,7 +7,7 @@ import 'package:flutter_test/flutter_test.dart'; ...@@ -7,7 +7,7 @@ import 'package:flutter_test/flutter_test.dart';
void main() { void main() {
testWidgets('flutter_test timeout logic - addTime - positive', (WidgetTester tester) async { testWidgets('flutter_test timeout logic - addTime - positive', (WidgetTester tester) async {
await tester.runAsync(() async { await tester.runAsync(() async {
await Future<void>.delayed(const Duration(milliseconds: 2500)); // must be longer than default timeout. await Future<void>.delayed(const Duration(milliseconds: 2500)); // must be longer than initial timeout below.
}, additionalTime: const Duration(milliseconds: 2000)); // default timeout is 2s, so this makes it 4s. }, additionalTime: const Duration(milliseconds: 2000)); // initial timeout is 2s, so this makes it 4s.
}); }, initialTimeout: const Duration(milliseconds: 2000));
} }
...@@ -129,9 +129,34 @@ abstract class TestWidgetsFlutterBinding extends BindingBase ...@@ -129,9 +129,34 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
bool get disableShadows => false; bool get disableShadows => false;
/// Increase the timeout for the current test by the given duration. /// Increase the timeout for the current test by the given duration.
void addTime(Duration duration) { ///
// Noop, see [AutomatedTestWidgetsFlutterBinding. addTime] for an actual implementation. /// This only matters if the test has an `initialTimeout` set on
} /// [testWidgets], and the test is running via `flutter test`. By default,
/// tests do not have such a timeout. Tests run using `flutter run` never time
/// out even if one is specified.
///
/// This method has no effect on the timeout specified via `timeout` on
/// [testWidgets]. That timeout is implemented by the `test` package.
///
/// By default, each [pump] and [pumpWidget] call increases the timeout by a
/// hundred milliseconds, and each [matchesGoldenFile] expectation increases
/// it by a minute. If there is no timeout in the first place, this has no
/// effect.
///
/// The granularity of timeouts is coarse: the time is checked once per
/// second, and only when the test is not executing. It is therefore possible
/// for a timeout to be exceeded by hundreds of milliseconds and for the test
/// to still succeed. If precise timing is required, it should be implemented
/// as a part of the test rather than relying on this mechanism.
///
/// See also:
///
/// * [testWidgets], on which a timeout can be set using the `timeout`
/// argument.
/// * [defaultTestTimeout], the maximum that the timeout can reach.
/// (That timeout is implemented by the `test` package.)
// See AutomatedTestWidgetsFlutterBinding.addTime for an actual implementation.
void addTime(Duration duration);
/// The value to set [debugCheckIntrinsicSizes] to while tests are running. /// The value to set [debugCheckIntrinsicSizes] to while tests are running.
/// ///
...@@ -183,11 +208,15 @@ abstract class TestWidgetsFlutterBinding extends BindingBase ...@@ -183,11 +208,15 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
/// The number of outstanding microtasks in the queue. /// The number of outstanding microtasks in the queue.
int get microtaskCount; int get microtaskCount;
/// The default test timeout for tests when using this binding. /// The default maximum test timeout for tests when using this binding.
///
/// This controls the default for the `timeout` argument on `testWidgets`. It
/// is 10 minutes for [AutomatedTestWidgetsFlutterBinding] (tests running
/// using `flutter test`), and unlimited for tests using
/// [LiveTestWidgetsFlutterBinding] (tests running using `flutter run`).
/// ///
/// The [AutomatedTestWidgetsFlutterBinding] layers in an additional timeout /// This is the maximum that the timeout controlled by `initialTimeout` on
/// mechanism beyond this, with much more aggressive timeouts. See /// [testWidgets] can reach when augmented using [addTime].
/// [AutomatedTestWidgetsFlutterBinding.addTime].
test_package.Timeout get defaultTestTimeout; test_package.Timeout get defaultTestTimeout;
/// The current time. /// The current time.
...@@ -233,8 +262,8 @@ abstract class TestWidgetsFlutterBinding extends BindingBase ...@@ -233,8 +262,8 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
/// ///
/// The `additionalTime` argument is used by the /// The `additionalTime` argument is used by the
/// [AutomatedTestWidgetsFlutterBinding] implementation to increase the /// [AutomatedTestWidgetsFlutterBinding] implementation to increase the
/// current timeout. See [AutomatedTestWidgetsFlutterBinding.addTime] for /// current timeout, if any. See [AutomatedTestWidgetsFlutterBinding.addTime]
/// details. The value is ignored by the [LiveTestWidgetsFlutterBinding]. /// for details.
Future<T> runAsync<T>( Future<T> runAsync<T>(
Future<T> callback(), { Future<T> callback(), {
Duration additionalTime = const Duration(milliseconds: 1000), Duration additionalTime = const Duration(milliseconds: 1000),
...@@ -422,7 +451,10 @@ abstract class TestWidgetsFlutterBinding extends BindingBase ...@@ -422,7 +451,10 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
/// The `description` is used by the [LiveTestWidgetsFlutterBinding] to /// The `description` is used by the [LiveTestWidgetsFlutterBinding] to
/// show a label on the screen during the test. The description comes from /// show a label on the screen during the test. The description comes from
/// the value passed to [testWidgets]. It must not be null. /// the value passed to [testWidgets]. It must not be null.
Future<void> runTest(Future<void> testBody(), VoidCallback invariantTester, { String description = '' }); ///
/// The `timeout` argument sets the initial timeout, if any. It can
/// be increased with [addTime]. By default there is no timeout.
Future<void> runTest(Future<void> testBody(), VoidCallback invariantTester, { String description = '', Duration timeout });
/// This is called during test execution before and after the body has been /// This is called during test execution before and after the body has been
/// executed. /// executed.
...@@ -739,10 +771,8 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { ...@@ -739,10 +771,8 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
@override @override
bool get checkIntrinsicSizes => true; bool get checkIntrinsicSizes => true;
// The timeout here is absurdly high because we do our own timeout logic and
// this is just a backstop.
@override @override
test_package.Timeout get defaultTestTimeout => const test_package.Timeout(Duration(minutes: 5)); test_package.Timeout get defaultTestTimeout => const test_package.Timeout(Duration(minutes: 10));
@override @override
bool get inTest => _currentFakeAsync != null; bool get inTest => _currentFakeAsync != null;
...@@ -933,6 +963,7 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { ...@@ -933,6 +963,7 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
void _checkTimeout(Timer timer) { void _checkTimeout(Timer timer) {
assert(_timeoutTimer == timer); assert(_timeoutTimer == timer);
assert(_timeout != null);
if (_timeoutStopwatch.elapsed > _timeout) { if (_timeoutStopwatch.elapsed > _timeout) {
_timeoutCompleter.completeError( _timeoutCompleter.completeError(
TimeoutException( TimeoutException(
...@@ -944,33 +975,10 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { ...@@ -944,33 +975,10 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
} }
} }
/// Increase the timeout for the current test by the given duration.
///
/// Tests by default time out after two seconds, but the timeout can be
/// increased before an expensive operation to allow it to complete without
/// hitting the test timeout.
///
/// By default, each [pump] and [pumpWidget] call increases the timeout by a
/// hundred milliseconds, and each [matchesGoldenFile] expectation increases
/// it by several seconds.
///
/// In general, unit tests are expected to run very fast, and this method is
/// usually not necessary.
///
/// The granularity of timeouts is coarse: the time is checked once per
/// second, and only when the test is not executing. It is therefore possible
/// for a timeout to be exceeded by hundreds of milliseconds and for the test
/// to still succeed. If precise timing is required, it should be implemented
/// as a part of the test rather than relying on this mechanism.
///
/// See also:
///
/// * [defaultTestTimeout], the maximum that the timeout can reach.
/// (That timeout is implemented by the test package.)
@override @override
void addTime(Duration duration) { void addTime(Duration duration) {
assert(_timeout != null, 'addTime can only be called during a test.'); if (_timeout != null)
_timeout += duration; _timeout += duration;
} }
@override @override
...@@ -978,7 +986,7 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { ...@@ -978,7 +986,7 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
Future<void> testBody(), Future<void> testBody(),
VoidCallback invariantTester, { VoidCallback invariantTester, {
String description = '', String description = '',
Duration timeout = const Duration(seconds: 2), Duration timeout,
}) { }) {
assert(description != null); assert(description != null);
assert(!inTest); assert(!inTest);
...@@ -986,9 +994,11 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { ...@@ -986,9 +994,11 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
assert(_clock == null); assert(_clock == null);
_timeout = timeout; _timeout = timeout;
_timeoutStopwatch = Stopwatch()..start(); if (_timeout != null) {
_timeoutTimer = Timer.periodic(const Duration(seconds: 1), _checkTimeout); _timeoutStopwatch = Stopwatch()..start();
_timeoutCompleter = Completer<void>(); _timeoutTimer = Timer.periodic(const Duration(seconds: 1), _checkTimeout);
_timeoutCompleter = Completer<void>();
}
final FakeAsync fakeAsync = FakeAsync(); final FakeAsync fakeAsync = FakeAsync();
_currentFakeAsync = fakeAsync; // reset in postTest _currentFakeAsync = fakeAsync; // reset in postTest
...@@ -997,7 +1007,7 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { ...@@ -997,7 +1007,7 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
fakeAsync.run((FakeAsync localFakeAsync) { fakeAsync.run((FakeAsync localFakeAsync) {
assert(fakeAsync == _currentFakeAsync); assert(fakeAsync == _currentFakeAsync);
assert(fakeAsync == localFakeAsync); assert(fakeAsync == localFakeAsync);
testBodyResult = _runTest(testBody, invariantTester, description, timeout: _timeoutCompleter.future); testBodyResult = _runTest(testBody, invariantTester, description, timeout: _timeoutCompleter?.future);
assert(inTest); assert(inTest);
}); });
...@@ -1051,7 +1061,7 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { ...@@ -1051,7 +1061,7 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
_clock = null; _clock = null;
_currentFakeAsync = null; _currentFakeAsync = null;
_timeoutCompleter = null; _timeoutCompleter = null;
_timeoutTimer.cancel(); _timeoutTimer?.cancel();
_timeoutTimer = null; _timeoutTimer = null;
_timeoutStopwatch = null; _timeoutStopwatch = null;
_timeout = null; _timeout = null;
...@@ -1203,6 +1213,12 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { ...@@ -1203,6 +1213,12 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
/// ``` /// ```
LiveTestWidgetsFlutterBindingFramePolicy framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fadePointers; LiveTestWidgetsFlutterBindingFramePolicy framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fadePointers;
@override
void addTime(Duration duration) {
// We don't support timeouts on the LiveTestWidgetsFlutterBinding.
// See runTest().
}
@override @override
void scheduleFrame() { void scheduleFrame() {
if (framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.benchmark) if (framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.benchmark)
...@@ -1341,6 +1357,8 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { ...@@ -1341,6 +1357,8 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
); );
}()); }());
addTime(additionalTime); // doesn't do anything since we don't actually track the timeout, but just for correctness...
_runningAsyncTasks = true; _runningAsyncTasks = true;
try { try {
return await callback(); return await callback();
...@@ -1358,11 +1376,15 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { ...@@ -1358,11 +1376,15 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
} }
@override @override
Future<void> runTest(Future<void> testBody(), VoidCallback invariantTester, { String description = '' }) async { Future<void> runTest(Future<void> testBody(), VoidCallback invariantTester, { String description = '', Duration timeout }) async {
assert(description != null); assert(description != null);
assert(!inTest); assert(!inTest);
_inTest = true; _inTest = true;
renderView._setDescription(description); renderView._setDescription(description);
// We drop the timeout on the floor in `flutter run` mode.
// We could support it, but we'd have to automatically add the entire duration of pumps
// and timers and so on, since those operate in real time when using this binding, but
// the timeouts expect them to happen near-instantaneously.
return _runTest(testBody, invariantTester, description); return _runTest(testBody, invariantTester, description);
} }
......
...@@ -1646,28 +1646,23 @@ class _MatchesReferenceImage extends AsyncMatcher { ...@@ -1646,28 +1646,23 @@ class _MatchesReferenceImage extends AsyncMatcher {
final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized(); final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
return binding.runAsync<String>(() async { return binding.runAsync<String>(() async {
final ui.Image image = await imageFuture; final ui.Image image = await imageFuture;
final ByteData bytes = await image.toByteData() final ByteData bytes = await image.toByteData();
.timeout(const Duration(seconds: 10), onTimeout: () => null); if (bytes == null)
if (bytes == null) { return 'could not be encoded.';
return 'Failed to generate an image from engine within the 10,000ms timeout.';
}
final ByteData referenceBytes = await referenceImage.toByteData() final ByteData referenceBytes = await referenceImage.toByteData();
.timeout(const Duration(seconds: 10), onTimeout: () => null); if (referenceBytes == null)
if (referenceBytes == null) { return 'could not have its reference image encoded.';
return 'Failed to generate an image from engine within the 10,000ms timeout.';
}
if (referenceImage.height != image.height || referenceImage.width != image.width) { if (referenceImage.height != image.height || referenceImage.width != image.width)
return 'does not match as width or height do not match. $image != $referenceImage'; return 'does not match as width or height do not match. $image != $referenceImage';
}
final int countDifferentPixels = _countDifferentPixels( final int countDifferentPixels = _countDifferentPixels(
Uint8List.view(bytes.buffer), Uint8List.view(bytes.buffer),
Uint8List.view(referenceBytes.buffer), Uint8List.view(referenceBytes.buffer),
); );
return countDifferentPixels == 0 ? null : 'does not match on $countDifferentPixels pixels'; return countDifferentPixels == 0 ? null : 'does not match on $countDifferentPixels pixels';
}, additionalTime: const Duration(seconds: 21)); }, additionalTime: const Duration(minutes: 1));
} }
@override @override
...@@ -1704,10 +1699,9 @@ class _MatchesGoldenFile extends AsyncMatcher { ...@@ -1704,10 +1699,9 @@ class _MatchesGoldenFile extends AsyncMatcher {
final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized(); final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
return binding.runAsync<String>(() async { return binding.runAsync<String>(() async {
final ui.Image image = await imageFuture; final ui.Image image = await imageFuture;
final ByteData bytes = await image.toByteData(format: ui.ImageByteFormat.png) final ByteData bytes = await image.toByteData(format: ui.ImageByteFormat.png);
.timeout(const Duration(seconds: 10), onTimeout: () => null);
if (bytes == null) if (bytes == null)
return 'Failed to generate screenshot from engine within the 10,000ms timeout.'; return 'could not encode screenshot.';
if (autoUpdateGoldenFiles) { if (autoUpdateGoldenFiles) {
await goldenFileComparator.update(key, bytes.buffer.asUint8List()); await goldenFileComparator.update(key, bytes.buffer.asUint8List());
return null; return null;
...@@ -1718,7 +1712,7 @@ class _MatchesGoldenFile extends AsyncMatcher { ...@@ -1718,7 +1712,7 @@ class _MatchesGoldenFile extends AsyncMatcher {
} on TestFailure catch (ex) { } on TestFailure catch (ex) {
return ex.message; return ex.message;
} }
}, additionalTime: const Duration(seconds: 11)); }, additionalTime: const Duration(minutes: 1));
} }
@override @override
......
...@@ -49,14 +49,24 @@ typedef WidgetTesterCallback = Future<void> Function(WidgetTester widgetTester); ...@@ -49,14 +49,24 @@ typedef WidgetTesterCallback = Future<void> Function(WidgetTester widgetTester);
/// ///
/// The callback can be asynchronous (using `async`/`await` or /// The callback can be asynchronous (using `async`/`await` or
/// using explicit [Future]s). /// using explicit [Future]s).
/// Tests using the [AutomatedTestWidgetsFlutterBinding] ///
/// have a default time out of two seconds, /// There are two kinds of timeouts that can be specified. The `timeout`
/// which is automatically increased for some expensive operations, /// argument specifies the backstop timeout implemented by the `test` package.
/// and can also be manually increased by calling /// If set, it should be relatively large (minutes). It defaults to ten minutes
/// [AutomatedTestWidgetsFlutterBinding.addTime]. /// for tests run by `flutter test`, and is unlimited for tests run by `flutter
/// The maximum that this timeout can reach (automatically or manually increased) /// run`; specifically, it defaults to
/// is defined by the timeout property, /// [TestWidgetsFlutterBinding.defaultTestTimeout].
/// which defaults to [TestWidgetsFlutterBinding.defaultTestTimeout]. ///
/// The `initialTimeout` argument specifies the timeout implemented by the
/// `flutter_test` package itself. If set, it may be relatively small (seconds),
/// as it is automatically increased for some expensive operations, and can also
/// be manually increased by calling
/// [AutomatedTestWidgetsFlutterBinding.addTime]. The effective maximum value of
/// this timeout (even after calling `addTime`) is the one specified by the
/// `timeout` argument.
///
/// In general, timeouts are race conditions and cause flakes, so best practice
/// is to avoid the use of timeouts in tests.
/// ///
/// If the `enableSemantics` parameter is set to `true`, /// If the `enableSemantics` parameter is set to `true`,
/// [WidgetTester.ensureSemantics] will have been called before the tester is /// [WidgetTester.ensureSemantics] will have been called before the tester is
...@@ -89,11 +99,11 @@ void testWidgets( ...@@ -89,11 +99,11 @@ void testWidgets(
WidgetTesterCallback callback, { WidgetTesterCallback callback, {
bool skip = false, bool skip = false,
test_package.Timeout timeout, test_package.Timeout timeout,
Duration initialTimeout,
bool semanticsEnabled = false, bool semanticsEnabled = false,
}) { }) {
final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized(); final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
final WidgetTester tester = WidgetTester._(binding); final WidgetTester tester = WidgetTester._(binding);
timeout ??= binding.defaultTestTimeout;
test( test(
description, description,
() { () {
...@@ -110,10 +120,11 @@ void testWidgets( ...@@ -110,10 +120,11 @@ void testWidgets(
}, },
tester._endOfTestVerifications, tester._endOfTestVerifications,
description: description ?? '', description: description ?? '',
timeout: initialTimeout,
); );
}, },
skip: skip, skip: skip,
timeout: timeout, timeout: timeout ?? binding.defaultTestTimeout,
); );
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment