binding.dart 62.8 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'dart:async';
6
import 'dart:typed_data';
7
import 'dart:ui' as ui;
8

9
import 'package:flutter/foundation.dart';
10
import 'package:flutter/gestures.dart';
11 12
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
13
import 'package:flutter/services.dart';
14
import 'package:flutter/widgets.dart';
15
import 'package:flutter_test/flutter_test.dart' show TestWindow;
16 17
import 'package:quiver/testing/async.dart';
import 'package:quiver/time.dart';
18
// ignore: deprecated_member_use
19
import 'package:test_api/test_api.dart' as test_package;
20
import 'package:stack_trace/stack_trace.dart' as stack_trace;
21 22
import 'package:vector_math/vector_math_64.dart';

23
import '_binding_io.dart' if (dart.library.html) '_binding_web.dart' as binding;
24
import 'goldens.dart';
25
import 'platform.dart';
26
import 'stack_manipulation.dart';
27
import 'test_async_utils.dart';
28
import 'test_exception_reporter.dart';
29
import 'test_text_input.dart';
30

31 32
/// Phases that can be reached by [WidgetTester.pumpWidget] and
/// [TestWidgetsFlutterBinding.pump].
33
///
34
/// See [WidgetsBinding.drawFrame] for a more detailed description of some of
35
/// these phases.
36
enum EnginePhase {
37
  /// The build phase in the widgets library. See [BuildOwner.buildScope].
38 39 40
  build,

  /// The layout phase in the rendering library. See [PipelineOwner.flushLayout].
41
  layout,
42 43 44

  /// The compositing bits update phase in the rendering library. See
  /// [PipelineOwner.flushCompositingBits].
45
  compositingBits,
46 47

  /// The paint phase in the rendering library. See [PipelineOwner.flushPaint].
48
  paint,
49 50 51 52

  /// The compositing phase in the rendering library. See
  /// [RenderView.compositeFrame]. This is the phase in which data is sent to
  /// the GPU. If semantics are not enabled, then this is the last phase.
53
  composite,
54 55 56

  /// The semantics building phase in the rendering library. See
  /// [PipelineOwner.flushSemantics].
57
  flushSemantics,
58 59

  /// The final phase in the rendering library, wherein semantics information is
Ian Hickson's avatar
Ian Hickson committed
60
  /// sent to the embedder. See [SemanticsOwner.sendSemanticsUpdate].
61
  sendSemanticsUpdate,
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
}

/// Parts of the system that can generate pointer events that reach the test
/// binding.
///
/// This is used to identify how to handle events in the
/// [LiveTestWidgetsFlutterBinding]. See
/// [TestWidgetsFlutterBinding.dispatchEvent].
enum TestBindingEventSource {
  /// The pointer event came from the test framework itself, e.g. from a
  /// [TestGesture] created by [WidgetTester.startGesture].
  test,

  /// The pointer event came from the system, presumably as a result of the user
  /// interactive directly with the device while the test was running.
  device,
78 79
}

80
const Size _kDefaultTestViewportSize = Size(800.0, 600.0);
81

82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
/// A [BinaryMessenger] subclass that is used as the default binary messenger
/// under testing environment.
///
/// It tracks status of data sent across the Flutter platform barrier, which is
/// useful for testing frameworks to monitor and synchronize against the
/// platform messages.
class TestDefaultBinaryMessenger extends BinaryMessenger {
  /// Creates a [TestDefaultBinaryMessenger] instance.
  ///
  /// The [delegate] instance must not be null.
  TestDefaultBinaryMessenger(this.delegate): assert(delegate != null);

  /// The delegate [BinaryMessenger].
  final BinaryMessenger delegate;

  final List<Future<ByteData>> _pendingMessages = <Future<ByteData>>[];

  /// The number of incomplete/pending calls sent to the platform channels.
  int get pendingMessageCount => _pendingMessages.length;

  @override
  Future<ByteData> send(String channel, ByteData message) {
    final Future<ByteData> resultFuture = delegate.send(channel, message);
    // Removes the future itself from the [_pendingMessages] list when it
    // completes.
    if (resultFuture != null) {
      _pendingMessages.add(resultFuture);
      resultFuture.whenComplete(() => _pendingMessages.remove(resultFuture));
    }
    return resultFuture;
  }

  /// Returns a Future that completes after all the platform calls are finished.
  ///
  /// If a new platform message is sent after this method is called, this new
  /// message is not tracked. Use with [pendingMessageCount] to guarantee no
  /// pending message calls.
  Future<void> get platformMessagesFinished {
    return Future.wait<void>(_pendingMessages);
  }

  @override
  Future<void> handlePlatformMessage(
      String channel,
      ByteData data,
      ui.PlatformMessageResponseCallback callback,
  ) {
    return delegate.handlePlatformMessage(channel, data, callback);
  }

  @override
  void setMessageHandler(String channel, MessageHandler handler) {
    delegate.setMessageHandler(channel, handler);
  }

  @override
  void setMockMessageHandler(String channel, MessageHandler handler) {
    delegate.setMockMessageHandler(channel, handler);
  }
}

143 144 145 146 147 148 149 150
/// Base class for bindings used by widgets library tests.
///
/// The [ensureInitialized] method creates (if necessary) and returns
/// an instance of the appropriate subclass.
///
/// When using these bindings, certain features are disabled. For
/// example, [timeDilation] is reset to 1.0 on initialization.
abstract class TestWidgetsFlutterBinding extends BindingBase
151 152
  with ServicesBinding,
       SchedulerBinding,
153
       GestureBinding,
154
       SemanticsBinding,
155
       RendererBinding,
156
       PaintingBinding,
157
       WidgetsBinding {
158

159 160 161 162
  /// Constructor for [TestWidgetsFlutterBinding].
  ///
  /// This constructor overrides the [debugPrint] global hook to point to
  /// [debugPrintOverride], which can be overridden by subclasses.
163
  TestWidgetsFlutterBinding() : _window = TestWindow(window: ui.window) {
164
    debugPrint = debugPrintOverride;
165
    debugDisableShadows = disableShadows;
166
    debugCheckIntrinsicSizes = checkIntrinsicSizes;
167 168
  }

169 170 171 172
  @override
  TestWindow get window => _window;
  final TestWindow _window;

173 174 175 176 177
  /// The value to set [debugPrint] to while tests are running.
  ///
  /// This can be used to redirect console output from the framework, or to
  /// change the behavior of [debugPrint]. For example,
  /// [AutomatedTestWidgetsFlutterBinding] uses it to make [debugPrint]
178
  /// synchronous, disabling its normal throttling behavior.
179 180 181
  @protected
  DebugPrintCallback get debugPrintOverride => debugPrint;

182 183 184 185 186 187 188 189 190
  /// The value to set [debugDisableShadows] to while tests are running.
  ///
  /// This can be used to reduce the likelihood of golden file tests being
  /// flaky, because shadow rendering is not always deterministic. The
  /// [AutomatedTestWidgetsFlutterBinding] sets this to true, so that all tests
  /// always run with shadows disabled.
  @protected
  bool get disableShadows => false;

191
  /// Increase the timeout for the current test by the given duration.
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
  ///
  /// 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);
220

221 222 223 224 225
  /// The value to set [debugCheckIntrinsicSizes] to while tests are running.
  ///
  /// This can be used to enable additional checks. For example,
  /// [AutomatedTestWidgetsFlutterBinding] sets this to true, so that all tests
  /// always run with aggressive intrinsic sizing tests enabled.
226 227 228
  @protected
  bool get checkIntrinsicSizes => false;

229
  /// Creates and initializes the binding. This function is
230 231
  /// idempotent; calling it a second time will just return the
  /// previously-created instance.
232 233 234 235
  ///
  /// This function will use [AutomatedTestWidgetsFlutterBinding] if
  /// the test was run using `flutter test`, and
  /// [LiveTestWidgetsFlutterBinding] otherwise (e.g. if it was run
236 237 238 239 240 241 242 243 244 245 246 247 248
  /// using `flutter run`). This is determined by looking at the
  /// environment variables for a variable called `FLUTTER_TEST`.
  ///
  /// If `FLUTTER_TEST` is set with a value of 'true', then this test was
  /// invoked by `flutter test`. If `FLUTTER_TEST` is not set, or if it is set
  /// to 'false', then this test was invoked by `flutter run`.
  ///
  /// Browser environments do not currently support the
  /// [LiveTestWidgetsFlutterBinding], so this function will always set up an
  /// [AutomatedTestWidgetsFlutterBinding] when run in a web browser.
  ///
  /// The parameter `environment` is exposed to test different environment
  /// variable values, and should not be used.
249
  static WidgetsBinding ensureInitialized([@visibleForTesting Map<String, String> environment]) => binding.ensureInitialized(environment);
250

251 252
  @override
  void initInstances() {
253
    super.initInstances();
254
    timeDilation = 1.0; // just in case the developer has artificially changed it for development
255
    binding.setupHttpOverrides();
256
    _testTextInput = TestTextInput(onCleared: _resetFocusedEditable)..register();
257 258
  }

259
  @override
260
  // ignore: MUST_CALL_SUPER
261 262 263 264 265
  void initLicenses() {
    // Do not include any licenses, because we're a test, and the LICENSE file
    // doesn't get generated for tests.
  }

266 267 268 269 270
  @override
  BinaryMessenger createBinaryMessenger() {
    return TestDefaultBinaryMessenger(super.createBinaryMessenger());
  }

271
  /// Whether there is currently a test executing.
272
  bool get inTest;
273

274 275 276
  /// The number of outstanding microtasks in the queue.
  int get microtaskCount;

277 278 279 280 281 282
  /// 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`).
283
  ///
284 285
  /// This is the maximum that the timeout controlled by `initialTimeout` on
  /// [testWidgets] can reach when augmented using [addTime].
286
  test_package.Timeout get defaultTestTimeout;
287

288 289 290 291 292 293 294 295 296 297
  /// The current time.
  ///
  /// In the automated test environment (`flutter test`), this is a fake clock
  /// that begins in January 2015 at the start of the test and advances each
  /// time [pump] is called with a non-zero duration.
  ///
  /// In the live testing environment (`flutter run`), this object shows the
  /// actual current wall-clock time.
  Clock get clock;

298 299 300 301 302 303 304 305
  /// Triggers a frame sequence (build/layout/paint/etc),
  /// then flushes microtasks.
  ///
  /// If duration is set, then advances the clock by that much first.
  /// Doing this flushes microtasks.
  ///
  /// The supplied EnginePhase is the final phase reached during the pump pass;
  /// if not supplied, the whole pass is executed.
306 307 308
  ///
  /// See also [LiveTestWidgetsFlutterBindingFramePolicy], which affects how
  /// this method works when the test is run with `flutter run`.
309
  Future<void> pump([ Duration duration, EnginePhase newPhase = EnginePhase.sendSemanticsUpdate ]);
310

311
  /// Runs a `callback` that performs real asynchronous work.
312 313 314 315 316
  ///
  /// This is intended for callers who need to call asynchronous methods where
  /// the methods spawn isolates or OS threads and thus cannot be executed
  /// synchronously by calling [pump].
  ///
317 318
  /// If `callback` completes successfully, this will return the future
  /// returned by `callback`.
319
  ///
320
  /// If `callback` completes with an error, the error will be caught by the
321 322 323 324 325 326 327
  /// Flutter framework and made available via [takeException], and this method
  /// will return a future that completes will `null`.
  ///
  /// Re-entrant calls to this method are not allowed; callers of this method
  /// are required to wait for the returned future to complete before calling
  /// this method again. Attempts to do otherwise will result in a
  /// [TestFailure] error being thrown.
328 329 330
  ///
  /// The `additionalTime` argument is used by the
  /// [AutomatedTestWidgetsFlutterBinding] implementation to increase the
331 332
  /// current timeout, if any. See [AutomatedTestWidgetsFlutterBinding.addTime]
  /// for details.
333 334
  Future<T> runAsync<T>(
    Future<T> callback(), {
335
    Duration additionalTime = const Duration(milliseconds: 1000),
336
  });
337

338
  /// Artificially calls dispatchLocalesChanged on the Widget binding,
339
  /// then flushes microtasks.
340 341 342
  ///
  /// Passes only one single Locale. Use [setLocales] to pass a full preferred
  /// locales list.
343 344
  Future<void> setLocale(String languageCode, String countryCode) {
    return TestAsyncUtils.guard<void>(() async {
345
      assert(inTest);
346
      final Locale locale = Locale(languageCode, countryCode == '' ? null : countryCode);
347 348 349
      if (isBrowser) {
        return;
      }
350 351 352 353 354 355 356 357 358 359
      dispatchLocalesChanged(<Locale>[locale]);
    });
  }

  /// Artificially calls dispatchLocalesChanged on the Widget binding,
  /// then flushes microtasks.
  Future<void> setLocales(List<Locale> locales) {
    return TestAsyncUtils.guard<void>(() async {
      assert(inTest);
      dispatchLocalesChanged(locales);
360 361 362
    });
  }

363 364 365 366 367 368
  /// Re-attempts the initialization of the lifecycle state after providing
  /// test values in [TestWindow.initialLifecycleStateTestValue].
  void readTestInitialLifecycleStateFromNativeWindow() {
    readInitialLifecycleStateFromNativeWindow();
  }

369 370 371 372 373 374
  Size _surfaceSize;

  /// Artificially changes the surface size to `size` on the Widget binding,
  /// then flushes microtasks.
  ///
  /// Set to null to use the default surface size.
375 376
  Future<void> setSurfaceSize(Size size) {
    return TestAsyncUtils.guard<void>(() async {
377 378
      assert(inTest);
      if (_surfaceSize == size)
379
        return;
380 381 382 383 384 385 386
      _surfaceSize = size;
      handleMetricsChanged();
    });
  }

  @override
  ViewConfiguration createViewConfiguration() {
387 388
    final double devicePixelRatio = window.devicePixelRatio;
    final Size size = _surfaceSize ?? window.physicalSize / devicePixelRatio;
389
    return ViewConfiguration(
390 391 392 393 394
      size: size,
      devicePixelRatio: devicePixelRatio,
    );
  }

395 396 397
  /// Acts as if the application went idle.
  ///
  /// Runs all remaining microtasks, including those scheduled as a result of
398 399
  /// running them, until there are no more microtasks scheduled. Then, runs any
  /// previously scheduled timers with zero time, and completes the returned future.
400
  ///
401 402 403
  /// May result in an infinite loop or run out of memory if microtasks continue
  /// to recursively schedule new microtasks. Will not run any timers scheduled
  /// after this method was invoked, even if they are zero-time timers.
404 405 406
  Future<void> idle() {
    return TestAsyncUtils.guard<void>(() {
      final Completer<void> completer = Completer<void>();
407
      Timer.run(() {
408
        completer.complete();
409 410 411
      });
      return completer.future;
    });
412 413
  }

414
  /// Convert the given point from the global coordinate system (as used by
415 416
  /// pointer events from the device) to the coordinate system used by the
  /// tests (an 800 by 600 window).
417
  Offset globalToLocal(Offset point) => point;
418 419

  /// Convert the given point from the coordinate system used by the tests (an
420
  /// 800 by 600 window) to the global coordinate system (as used by pointer
421
  /// events from the device).
422
  Offset localToGlobal(Offset point) => point;
423

424
  @override
425 426 427
  void dispatchEvent(
    PointerEvent event,
    HitTestResult hitTestResult, {
428
    TestBindingEventSource source = TestBindingEventSource.device,
429 430
  }) {
    assert(source == TestBindingEventSource.test);
431
    super.dispatchEvent(event, hitTestResult);
432 433
  }

434 435 436 437 438 439 440 441
  /// A stub for the system's onscreen keyboard. Callers must set the
  /// [focusedEditable] before using this value.
  TestTextInput get testTextInput => _testTextInput;
  TestTextInput _testTextInput;

  /// The current client of the onscreen keyboard. Callers must pump
  /// an additional frame after setting this property to complete the
  /// the focus change.
442 443 444
  ///
  /// Instead of setting this directly, consider using
  /// [WidgetTester.showKeyboard].
445 446
  EditableTextState get focusedEditable => _focusedEditable;
  EditableTextState _focusedEditable;
447
  set focusedEditable(EditableTextState value) {
448 449 450 451 452 453 454 455
    if (_focusedEditable != value) {
      _focusedEditable = value;
      value?.requestKeyboard();
    }
  }

  void _resetFocusedEditable() {
    _focusedEditable = null;
456 457
  }

458 459 460 461 462 463 464 465 466 467 468 469 470 471 472
  /// Returns the exception most recently caught by the Flutter framework.
  ///
  /// Call this if you expect an exception during a test. If an exception is
  /// thrown and this is not called, then the exception is rethrown when
  /// the [testWidgets] call completes.
  ///
  /// If two exceptions are thrown in a row without the first one being
  /// acknowledged with a call to this method, then when the second exception is
  /// thrown, they are both dumped to the console and then the second is
  /// rethrown from the exception handler. This will likely result in the
  /// framework entering a highly unstable state and everything collapsing.
  ///
  /// It's safe to call this when there's no pending exception; it will return
  /// null in that case.
  dynamic takeException() {
473
    assert(inTest);
474
    final dynamic result = _pendingExceptionDetails?.exception;
475
    _pendingExceptionDetails = null;
476 477
    return result;
  }
478 479
  FlutterExceptionHandler _oldExceptionHandler;
  FlutterErrorDetails _pendingExceptionDetails;
480

481 482
  static const TextStyle _messageStyle = TextStyle(
    color: Color(0xFF917FFF),
Ian Hickson's avatar
Ian Hickson committed
483
    fontSize: 40.0,
484 485
  );

486 487
  static const Widget _preTestMessage = Center(
    child: Text(
488
      'Test starting...',
489
      style: _messageStyle,
Ian Hickson's avatar
Ian Hickson committed
490
      textDirection: TextDirection.ltr,
491
    ),
492 493
  );

494 495
  static const Widget _postTestMessage = Center(
    child: Text(
496
      'Test finished.',
497
      style: _messageStyle,
Ian Hickson's avatar
Ian Hickson committed
498
      textDirection: TextDirection.ltr,
499
    ),
500 501 502 503 504 505
  );

  /// Whether to include the output of debugDumpApp() when reporting
  /// test failures.
  bool showAppDumpInErrors = false;

506
  /// Call the testBody inside a [FakeAsync] scope on which [pump] can
507 508 509 510 511 512
  /// advance time.
  ///
  /// Returns a future which completes when the test has run.
  ///
  /// Called by the [testWidgets] and [benchmarkWidgets] functions to
  /// run a test.
513 514 515
  ///
  /// The `invariantTester` argument is called after the `testBody`'s [Future]
  /// completes. If it throws, then the test is marked as failed.
516 517 518 519
  ///
  /// The `description` is used by the [LiveTestWidgetsFlutterBinding] to
  /// show a label on the screen during the test. The description comes from
  /// the value passed to [testWidgets]. It must not be null.
520 521 522 523
  ///
  /// 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 });
524 525 526 527 528 529 530 531 532 533 534

  /// This is called during test execution before and after the body has been
  /// executed.
  ///
  /// It's used by [AutomatedTestWidgetsFlutterBinding] to drain the microtasks
  /// before the final [pump] that happens during test cleanup.
  void asyncBarrier() {
    TestAsyncUtils.verifyAllScopesClosed();
  }

  Zone _parentZone;
535

536
  VoidCallback _createTestCompletionHandler(String testDescription, Completer<void> completer) {
537 538 539 540 541 542 543 544 545 546
    return () {
      // This can get called twice, in the case of a Future without listeners failing, and then
      // our main future completing.
      assert(Zone.current == _parentZone);
      if (_pendingExceptionDetails != null) {
        debugPrint = debugPrintOverride; // just in case the test overrides it -- otherwise we won't see the error!
        reportTestException(_pendingExceptionDetails, testDescription);
        _pendingExceptionDetails = null;
      }
      if (!completer.isCompleted)
547
        completer.complete();
548
    };
549 550
  }

551 552 553 554 555 556 557 558 559 560 561 562
  /// Called when the framework catches an exception, even if that exception is
  /// being handled by [takeException].
  ///
  /// This is called when there is no pending exception; if multiple exceptions
  /// are thrown and [takeException] isn't used, then subsequent exceptions are
  /// logged to the console regardless (and the test will fail).
  @protected
  void reportExceptionNoticed(FlutterErrorDetails exception) {
    // By default we do nothing.
    // The LiveTestWidgetsFlutterBinding overrides this to report the exception to the console.
  }

563 564 565 566
  Future<void> _runTest(
    Future<void> testBody(),
    VoidCallback invariantTester,
    String description, {
567
    Future<void> timeout,
568
  }) {
569
    assert(description != null);
570 571 572
    assert(inTest);
    _oldExceptionHandler = FlutterError.onError;
    int _exceptionCount = 0; // number of un-taken exceptions
573
    FlutterError.onError = (FlutterErrorDetails details) {
574
      if (_pendingExceptionDetails != null) {
575
        debugPrint = debugPrintOverride; // just in case the test overrides it -- otherwise we won't see the errors!
576 577
        if (_exceptionCount == 0) {
          _exceptionCount = 2;
578
          FlutterError.dumpErrorToConsole(_pendingExceptionDetails, forceReport: true);
579 580
        } else {
          _exceptionCount += 1;
581
        }
582
        FlutterError.dumpErrorToConsole(details, forceReport: true);
583
        _pendingExceptionDetails = FlutterErrorDetails(
584
          exception: 'Multiple exceptions ($_exceptionCount) were detected during the running of the current test, and at least one was unexpected.',
585
          library: 'Flutter test framework',
586 587
        );
      } else {
588
        reportExceptionNoticed(details); // mostly this is just a hook for the LiveTestWidgetsFlutterBinding
589
        _pendingExceptionDetails = details;
590
      }
591
    };
592
    final Completer<void> testCompleter = Completer<void>();
593
    final VoidCallback testCompletionHandler = _createTestCompletionHandler(description, testCompleter);
594 595 596 597 598 599 600 601 602
    void handleUncaughtError(dynamic exception, StackTrace stack) {
      if (testCompleter.isCompleted) {
        // Well this is not a good sign.
        // Ideally, once the test has failed we would stop getting errors from the test.
        // However, if someone tries hard enough they could get in a state where this happens.
        // If we silently dropped these errors on the ground, nobody would ever know. So instead
        // we report them to the console. They don't cause test failures, but hopefully someone
        // will see them in the logs at some point.
        debugPrint = debugPrintOverride; // just in case the test overrides it -- otherwise we won't see the error!
603
        FlutterError.dumpErrorToConsole(FlutterErrorDetails(
604
          exception: exception,
605
          stack: _unmangle(stack),
606
          context: ErrorDescription('running a test (but after the test had completed)'),
607
          library: 'Flutter test framework',
608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638
        ), forceReport: true);
        return;
      }
      // This is where test failures, e.g. those in expect(), will end up.
      // Specifically, runUnaryGuarded() will call this synchronously and
      // return our return value if _runTestBody fails synchronously (which it
      // won't, so this never happens), and Future will call this when the
      // Future completes with an error and it would otherwise call listeners
      // if the listener is in a different zone (which it would be for the
      // `whenComplete` handler below), or if the Future completes with an
      // error and the future has no listeners at all.
      //
      // This handler further calls the onError handler above, which sets
      // _pendingExceptionDetails. Nothing gets printed as a result of that
      // call unless we already had an exception pending, because in general
      // we want people to be able to cause the framework to report exceptions
      // and then use takeException to verify that they were really caught.
      // Now, if we actually get here, this isn't going to be one of those
      // cases. We only get here if the test has actually failed. So, once
      // we've carefully reported it, we then immediately end the test by
      // calling the testCompletionHandler in the _parentZone.
      //
      // We have to manually call testCompletionHandler because if the Future
      // library calls us, it is maybe _instead_ of calling a registered
      // listener from a different zone. In our case, that would be instead of
      // calling the whenComplete() listener below.
      //
      // We have to call it in the parent zone because if we called it in
      // _this_ zone, the test framework would find this zone was the current
      // zone and helpfully throw the error in this zone, causing us to be
      // directly called again.
639
      DiagnosticsNode treeDump;
640
      try {
641 642 643 644
        treeDump = renderViewElement?.toDiagnosticsNode() ?? DiagnosticsNode.message('<no tree>');
        // TODO(jacobr): this is a hack to make sure the tree can safely be fully dumped.
        // Potentially everything is good enough without this case.
        treeDump.toStringDeep();
645
      } catch (exception) {
646
        treeDump = DiagnosticsNode.message('<additional error caught while dumping tree: $exception>', level: DiagnosticLevel.error);
647
      }
648 649
      final List<DiagnosticsNode> omittedFrames = <DiagnosticsNode>[];
      final int stackLinesToOmit = reportExpectCall(stack, omittedFrames);
650
      FlutterError.reportError(FlutterErrorDetails(
651 652
        exception: exception,
        stack: _unmangle(stack),
653
        context: ErrorDescription('running a test'),
654 655 656 657
        library: 'Flutter test framework',
        stackFilter: (Iterable<String> frames) {
          return FlutterError.defaultStackFilter(frames.skip(stackLinesToOmit));
        },
658
        informationCollector: () sync* {
659
          if (stackLinesToOmit > 0)
660
            yield* omittedFrames;
661
          if (showAppDumpInErrors) {
662
            yield DiagnosticsProperty<DiagnosticsNode>('At the time of the failure, the widget tree looked as follows', treeDump, linePrefix: '# ', style: DiagnosticsTreeStyle.flat);
663
          }
664
          if (description.isNotEmpty)
665
            yield DiagnosticsProperty<String>('The test description was', description, style: DiagnosticsTreeStyle.errorProperty);
666
        },
667 668 669 670 671
      ));
      assert(_parentZone != null);
      assert(_pendingExceptionDetails != null, 'A test overrode FlutterError.onError but either failed to return it to its original state, or had unexpected additional errors that it could not handle. Typically, this is caused by using expect() before restoring FlutterError.onError.');
      _parentZone.run<void>(testCompletionHandler);
    }
672
    final ZoneSpecification errorHandlingZoneSpecification = ZoneSpecification(
673 674
      handleUncaughtError: (Zone self, ZoneDelegate parent, Zone zone, dynamic exception, StackTrace stack) {
        handleUncaughtError(exception, stack);
675 676 677
      }
    );
    _parentZone = Zone.current;
678
    final Zone testZone = _parentZone.fork(specification: errorHandlingZoneSpecification);
679
    testZone.runBinary<Future<void>, Future<void> Function(), VoidCallback>(_runTestBody, testBody, invariantTester)
680
      .whenComplete(testCompletionHandler);
681
    timeout?.catchError(handleUncaughtError);
682
    return testCompleter.future;
683 684
  }

685
  Future<void> _runTestBody(Future<void> testBody(), VoidCallback invariantTester) async {
686 687
    assert(inTest);

688
    runApp(Container(key: UniqueKey(), child: _preTestMessage)); // Reset the tree to a known state.
689
    await pump();
690 691 692
    // Pretend that the first frame produced in the test body is the first frame
    // sent to the engine.
    resetFirstFrameSent();
693

694
    final bool autoUpdateGoldensBeforeTest = autoUpdateGoldenFiles && !isBrowser;
695
    final TestExceptionReporter reportTestExceptionBeforeTest = reportTestException;
696
    final ErrorWidgetBuilder errorWidgetBuilderBeforeTest = ErrorWidget.builder;
697

698
    // run the test
699
    await testBody();
700 701 702 703 704 705
    asyncBarrier(); // drains the microtasks in `flutter test` mode (when using AutomatedTestWidgetsFlutterBinding)

    if (_pendingExceptionDetails == null) {
      // We only try to clean up and verify invariants if we didn't already
      // fail. If we got an exception already, then we instead leave everything
      // alone so that we don't cause more spurious errors.
706
      runApp(Container(key: UniqueKey(), child: _postTestMessage)); // Unmount any remaining widgets.
707
      await pump();
708
      invariantTester();
709
      _verifyAutoUpdateGoldensUnset(autoUpdateGoldensBeforeTest && !isBrowser);
710
      _verifyReportTestExceptionUnset(reportTestExceptionBeforeTest);
711
      _verifyErrorWidgetBuilderUnset(errorWidgetBuilderBeforeTest);
712 713 714 715
      _verifyInvariants();
    }

    assert(inTest);
716
    asyncBarrier(); // When using AutomatedTestWidgetsFlutterBinding, this flushes the microtasks.
717 718 719 720 721 722
  }

  void _verifyInvariants() {
    assert(debugAssertNoTransientCallbacks(
      'An animation is still running even after the widget tree was disposed.'
    ));
723 724 725 726
    assert(debugAssertAllFoundationVarsUnset(
      'The value of a foundation debug variable was changed by the test.',
      debugPrintOverride: debugPrintOverride,
    ));
727 728 729
    assert(debugAssertAllGesturesVarsUnset(
      'The value of a gestures debug variable was changed by the test.',
    ));
730 731 732 733
    assert(debugAssertAllPaintingVarsUnset(
      'The value of a painting debug variable was changed by the test.',
      debugDisableShadowsOverride: disableShadows,
    ));
734
    assert(debugAssertAllRenderVarsUnset(
735 736
      'The value of a rendering debug variable was changed by the test.',
      debugCheckIntrinsicSizesOverride: checkIntrinsicSizes,
737 738
    ));
    assert(debugAssertAllWidgetVarsUnset(
739
      'The value of a widget debug variable was changed by the test.',
740 741
    ));
    assert(debugAssertAllSchedulerVarsUnset(
742
      'The value of a scheduler debug variable was changed by the test.',
743
    ));
744 745
  }

746 747 748
  void _verifyAutoUpdateGoldensUnset(bool valueBeforeTest) {
    assert(() {
      if (autoUpdateGoldenFiles != valueBeforeTest) {
749 750
        FlutterError.reportError(FlutterErrorDetails(
          exception: FlutterError(
751 752 753 754 755 756 757 758 759 760
              'The value of autoUpdateGoldenFiles was changed by the test.',
          ),
          stack: StackTrace.current,
          library: 'Flutter test framework',
        ));
      }
      return true;
    }());
  }

761 762 763 764 765 766 767 768
  void _verifyReportTestExceptionUnset(TestExceptionReporter valueBeforeTest) {
    assert(() {
      if (reportTestException != valueBeforeTest) {
        // We can't report this error to their modified reporter because we
        // can't be guaranteed that their reporter will cause the test to fail.
        // So we reset the error reporter to its initial value and then report
        // this error.
        reportTestException = valueBeforeTest;
769 770
        FlutterError.reportError(FlutterErrorDetails(
          exception: FlutterError(
771 772 773 774 775 776 777 778 779 780
            'The value of reportTestException was changed by the test.',
          ),
          stack: StackTrace.current,
          library: 'Flutter test framework',
        ));
      }
      return true;
    }());
  }

781 782 783 784 785 786 787 788 789 790 791 792 793 794 795
  void _verifyErrorWidgetBuilderUnset(ErrorWidgetBuilder valueBeforeTest) {
    assert(() {
      if (ErrorWidget.builder != valueBeforeTest) {
        FlutterError.reportError(FlutterErrorDetails(
          exception: FlutterError(
              'The value of ErrorWidget.builder was changed by the test.',
          ),
          stack: StackTrace.current,
          library: 'Flutter test framework',
        ));
      }
      return true;
    }());
  }

796 797 798 799 800 801
  /// Called by the [testWidgets] function after a test is executed.
  void postTest() {
    assert(inTest);
    FlutterError.onError = _oldExceptionHandler;
    _pendingExceptionDetails = null;
    _parentZone = null;
802
    buildOwner.focusManager = FocusManager();
803 804 805 806 807
    // Disabling the warning because @visibleForTesting doesn't take the testing
    // framework itself into account, but we don't want it visible outside of
    // tests.
    // ignore: invalid_use_of_visible_for_testing_member
    RawKeyboard.instance.clearKeysPressed();
808 809 810 811
    assert(!RendererBinding.instance.mouseTracker.mouseIsConnected,
        'The MouseTracker thinks that there is still a mouse connected, which indicates that a '
        'test has not removed the mouse pointer which it added. Call removePointer on the '
        'active mouse gesture to remove the mouse pointer.');
812 813
    // ignore: invalid_use_of_visible_for_testing_member
    RendererBinding.instance.initMouseTracker();
814 815 816 817 818 819 820 821
  }
}

/// A variant of [TestWidgetsFlutterBinding] for executing tests in
/// the `flutter test` environment.
///
/// This binding controls time, allowing tests to verify long
/// animation sequences without having to execute them in real time.
822 823 824
///
/// This class assumes it is always run in checked mode (since tests are always
/// run in checked mode).
825 826 827 828
class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
  @override
  void initInstances() {
    super.initInstances();
829
    binding.mockFlutterAssets();
830 831
  }

832
  FakeAsync _currentFakeAsync; // set in runTest; cleared in postTest
833
  Completer<void> _pendingAsyncTasks;
834 835 836

  @override
  Clock get clock => _clock;
837 838
  Clock _clock;

839 840 841
  @override
  DebugPrintCallback get debugPrintOverride => debugPrintSynchronously;

842 843 844
  @override
  bool get disableShadows => true;

845 846 847
  @override
  bool get checkIntrinsicSizes => true;

848 849 850
  /// The value of [defaultTestTimeout] can be set to `None` to enable debugging flutter tests where
  /// we would not want to timeout the test. This is expected to be used by test tooling which
  /// can detect debug mode.
851
  @override
852
  test_package.Timeout defaultTestTimeout = const test_package.Timeout(Duration(minutes: 10));
853 854

  @override
855
  bool get inTest => _currentFakeAsync != null;
856

857
  @override
858
  int get microtaskCount => _currentFakeAsync.microtaskCount;
859

860
  @override
861 862
  Future<void> pump([ Duration duration, EnginePhase newPhase = EnginePhase.sendSemanticsUpdate ]) {
    return TestAsyncUtils.guard<void>(() {
863 864 865
      assert(inTest);
      assert(_clock != null);
      if (duration != null)
866
        _currentFakeAsync.elapse(duration);
867 868
      _phase = newPhase;
      if (hasScheduledFrame) {
869
        addTime(const Duration(milliseconds: 500));
870
        _currentFakeAsync.flushMicrotasks();
871
        handleBeginFrame(Duration(
872
          milliseconds: _clock.now().millisecondsSinceEpoch,
873
        ));
874
        _currentFakeAsync.flushMicrotasks();
875
        handleDrawFrame();
876
      }
877
      _currentFakeAsync.flushMicrotasks();
878
      return Future<void>.value();
879 880 881
    });
  }

882
  @override
883 884
  Future<T> runAsync<T>(
    Future<T> callback(), {
885
    Duration additionalTime = const Duration(milliseconds: 1000),
886 887
  }) {
    assert(additionalTime != null);
888 889 890
    assert(() {
      if (_pendingAsyncTasks == null)
        return true;
891
      throw test_package.TestFailure(
892 893 894 895 896 897 898
          'Reentrant call to runAsync() denied.\n'
          'runAsync() was called, then before its future completed, it '
          'was called again. You must wait for the first returned future '
          'to complete before calling runAsync() again.'
      );
    }());

899
    final Zone realAsyncZone = Zone.current.fork(
900
      specification: ZoneSpecification(
901 902 903 904 905 906 907 908 909 910 911 912
        scheduleMicrotask: (Zone self, ZoneDelegate parent, Zone zone, void f()) {
          Zone.root.scheduleMicrotask(f);
        },
        createTimer: (Zone self, ZoneDelegate parent, Zone zone, Duration duration, void f()) {
          return Zone.root.createTimer(duration, f);
        },
        createPeriodicTimer: (Zone self, ZoneDelegate parent, Zone zone, Duration period, void f(Timer timer)) {
          return Zone.root.createPeriodicTimer(period, f);
        },
      ),
    );

913 914
    addTime(additionalTime);

915
    return realAsyncZone.run<Future<T>>(() {
916
      _pendingAsyncTasks = Completer<void>();
917
      return callback().catchError((dynamic exception, StackTrace stack) {
918
        FlutterError.reportError(FlutterErrorDetails(
919 920 921
          exception: exception,
          stack: stack,
          library: 'Flutter test framework',
922
          context: ErrorDescription('while running async test code'),
923 924 925 926 927 928 929 930 931 932 933 934 935 936
        ));
        return null;
      }).whenComplete(() {
        // We complete the _pendingAsyncTasks future successfully regardless of
        // whether an exception occurred because in the case of an exception,
        // we already reported the exception to FlutterError. Moreover,
        // completing the future with an error would trigger an unhandled
        // exception due to zone error boundaries.
        _pendingAsyncTasks.complete();
        _pendingAsyncTasks = null;
      });
    });
  }

937 938 939 940 941 942 943
  @override
  void ensureFrameCallbacksRegistered() {
    // Leave Window alone, do nothing.
    assert(window.onDrawFrame == null);
    assert(window.onBeginFrame == null);
  }

944 945 946 947 948
  @override
  void scheduleWarmUpFrame() {
    // We override the default version of this so that the application-startup warm-up frame
    // does not schedule timers which we might never get around to running.
    handleBeginFrame(null);
949
    _currentFakeAsync.flushMicrotasks();
950
    handleDrawFrame();
951
    _currentFakeAsync.flushMicrotasks();
952 953
  }

954 955 956 957 958 959 960 961
  @override
  void scheduleAttachRootWidget(Widget rootWidget) {
    // We override the default version of this so that the application-startup widget tree
    // build does not schedule timers which we might never get around to running.
    attachRootWidget(rootWidget);
    _currentFakeAsync.flushMicrotasks();
  }

962
  @override
963 964
  Future<void> idle() {
    final Future<void> result = super.idle();
965
    _currentFakeAsync.elapse(Duration.zero);
966 967 968
    return result;
  }

969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993
  int _firstFrameDeferredCount = 0;
  bool _firstFrameSent = false;

  @override
  bool get sendFramesToEngine => _firstFrameSent || _firstFrameDeferredCount == 0;

  @override
  void deferFirstFrame() {
    assert(_firstFrameDeferredCount >= 0);
    _firstFrameDeferredCount += 1;
  }

  @override
  void allowFirstFrame() {
    assert(_firstFrameDeferredCount > 0);
    _firstFrameDeferredCount -= 1;
    // Unlike in RendererBinding.allowFirstFrame we do not force a frame her
    // to give the test full control over frame scheduling.
  }

  @override
  void resetFirstFrameSent() {
    _firstFrameSent = false;
  }

994
  EnginePhase _phase = EnginePhase.sendSemanticsUpdate;
995

996
  // Cloned from RendererBinding.drawFrame() but with early-exit semantics.
997
  @override
998
  void drawFrame() {
999
    assert(inTest);
1000 1001 1002
    try {
      debugBuildingDirtyElements = true;
      buildOwner.buildScope(renderViewElement);
1003 1004 1005 1006 1007 1008 1009
      if (_phase != EnginePhase.build) {
        assert(renderView != null);
        pipelineOwner.flushLayout();
        if (_phase != EnginePhase.layout) {
          pipelineOwner.flushCompositingBits();
          if (_phase != EnginePhase.compositingBits) {
            pipelineOwner.flushPaint();
1010 1011
            if (_phase != EnginePhase.paint && sendFramesToEngine) {
              _firstFrameSent = true;
1012 1013 1014
              renderView.compositeFrame(); // this sends the bits to the GPU
              if (_phase != EnginePhase.composite) {
                pipelineOwner.flushSemantics();
1015 1016
                assert(_phase == EnginePhase.flushSemantics ||
                       _phase == EnginePhase.sendSemanticsUpdate);
1017 1018 1019 1020 1021
              }
            }
          }
        }
      }
1022
      buildOwner.finalizeTree();
1023
    } finally {
1024 1025
      debugBuildingDirtyElements = false;
    }
1026 1027
  }

1028 1029 1030
  Duration _timeout;
  Stopwatch _timeoutStopwatch;
  Timer _timeoutTimer;
1031
  Completer<void> _timeoutCompleter;
1032 1033 1034

  void _checkTimeout(Timer timer) {
    assert(_timeoutTimer == timer);
1035
    assert(_timeout != null);
1036 1037
    if (_timeoutStopwatch.elapsed > _timeout) {
      _timeoutCompleter.completeError(
1038
        TimeoutException(
1039
          'The test exceeded the timeout. It may have hung.\n'
1040
          'Consider using "tester.binding.addTime" to increase the timeout before expensive operations.',
1041 1042 1043 1044 1045 1046
          _timeout,
        ),
      );
    }
  }

1047
  @override
1048
  void addTime(Duration duration) {
1049 1050
    if (_timeout != null)
      _timeout += duration;
1051 1052
  }

1053
  @override
1054 1055 1056
  Future<void> runTest(
    Future<void> testBody(),
    VoidCallback invariantTester, {
1057
    String description = '',
1058
    Duration timeout,
1059
  }) {
1060
    assert(description != null);
1061
    assert(!inTest);
1062
    assert(_currentFakeAsync == null);
1063
    assert(_clock == null);
1064 1065

    _timeout = timeout;
1066 1067 1068 1069 1070
    if (_timeout != null) {
      _timeoutStopwatch = Stopwatch()..start();
      _timeoutTimer = Timer.periodic(const Duration(seconds: 1), _checkTimeout);
      _timeoutCompleter = Completer<void>();
    }
1071

1072
    final FakeAsync fakeAsync = FakeAsync();
1073
    _currentFakeAsync = fakeAsync; // reset in postTest
1074
    _clock = fakeAsync.getClock(DateTime.utc(2015, 1, 1));
1075
    Future<void> testBodyResult;
1076 1077 1078
    fakeAsync.run((FakeAsync localFakeAsync) {
      assert(fakeAsync == _currentFakeAsync);
      assert(fakeAsync == localFakeAsync);
1079
      testBodyResult = _runTest(testBody, invariantTester, description, timeout: _timeoutCompleter?.future);
1080 1081
      assert(inTest);
    });
1082

1083
    return Future<void>.microtask(() async {
1084 1085 1086 1087 1088 1089
      // testBodyResult is a Future that was created in the Zone of the
      // fakeAsync. This means that if we await it here, it will register a
      // microtask to handle the future _in the fake async zone_. We avoid this
      // by calling '.then' in the current zone. While flushing the microtasks
      // of the fake-zone below, the new future will be completed and can then
      // be used without fakeAsync.
1090
      final Future<void> resultFuture = testBodyResult.then<void>((_) {
1091 1092 1093
        // Do nothing.
      });

1094
      // Resolve interplay between fake async and real async calls.
1095
      fakeAsync.flushMicrotasks();
1096 1097
      while (_pendingAsyncTasks != null) {
        await _pendingAsyncTasks.future;
1098
        fakeAsync.flushMicrotasks();
1099
      }
1100
      return resultFuture;
1101
    });
1102 1103
  }

1104 1105
  @override
  void asyncBarrier() {
1106 1107
    assert(_currentFakeAsync != null);
    _currentFakeAsync.flushMicrotasks();
1108 1109
    super.asyncBarrier();
  }
1110

1111 1112 1113
  @override
  void _verifyInvariants() {
    super._verifyInvariants();
1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136

    assert(() {
      if (   _currentFakeAsync.periodicTimerCount == 0
          && _currentFakeAsync.nonPeriodicTimerCount == 0) {
        return true;
      }

      debugPrint('Pending timers:');
      for (String timerInfo in _currentFakeAsync.pendingTimersDebugInfo) {
        final int firstLineEnd = timerInfo.indexOf('\n');
        assert(firstLineEnd != -1);

        // No need to include the newline.
        final String firstLine = timerInfo.substring(0, firstLineEnd);
        final String stackTrace = timerInfo.substring(firstLineEnd + 1);

        debugPrint(firstLine);
        debugPrintStack(stackTrace: StackTrace.fromString(stackTrace));
        debugPrint('');
      }
      return false;
    }(), 'A Timer is still pending even after the widget tree was disposed.');

1137
    assert(_currentFakeAsync.microtaskCount == 0); // Shouldn't be possible.
1138 1139
  }

1140
  @override
1141
  void postTest() {
1142
    super.postTest();
1143
    assert(_currentFakeAsync != null);
1144 1145
    assert(_clock != null);
    _clock = null;
1146
    _currentFakeAsync = null;
1147
    _timeoutCompleter = null;
1148
    _timeoutTimer?.cancel();
1149 1150 1151
    _timeoutTimer = null;
    _timeoutStopwatch = null;
    _timeout = null;
1152 1153
  }

1154
}
1155

1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182
/// Available policies for how a [LiveTestWidgetsFlutterBinding] should paint
/// frames.
///
/// These values are set on the binding's
/// [LiveTestWidgetsFlutterBinding.framePolicy] property. The default is
/// [fadePointers].
enum LiveTestWidgetsFlutterBindingFramePolicy {
  /// Strictly show only frames that are explicitly pumped. This most closely
  /// matches the behavior of tests when run under `flutter test`.
  onlyPumps,

  /// Show pumped frames, and additionally schedule and run frames to fade
  /// out the pointer crosshairs and other debugging information shown by
  /// the binding.
  ///
  /// This can result in additional frames being pumped beyond those that
  /// the test itself requests, which can cause differences in behavior.
  fadePointers,

  /// Show every frame that the framework requests, even if the frames are not
  /// explicitly pumped.
  ///
  /// This can help with orienting the developer when looking at
  /// heavily-animated situations, and will almost certainly result in
  /// additional frames being pumped beyond those that the test itself requests,
  /// which can cause differences in behavior.
  fullyLive,
1183 1184 1185 1186 1187 1188

  /// Ignore any request to schedule a frame.
  ///
  /// This is intended to be used by benchmarks (hence the name) that drive the
  /// pipeline directly. It tells the binding to entirely ignore requests for a
  /// frame to be scheduled, while still allowing frames that are pumped
1189 1190
  /// directly to run (either by using [WidgetTester.pumpBenchmark] or invoking
  /// [Window.onBeginFrame] and [Window.onDrawFrame]).
1191 1192 1193 1194 1195 1196 1197
  ///
  /// The [SchedulerBinding.hasScheduledFrame] property will never be true in
  /// this mode. This can cause unexpected effects. For instance,
  /// [WidgetTester.pumpAndSettle] does not function in this mode, as it relies
  /// on the [SchedulerBinding.hasScheduledFrame] property to determine when the
  /// application has "settled".
  benchmark,
1198 1199
}

1200 1201 1202 1203 1204 1205 1206 1207
/// A variant of [TestWidgetsFlutterBinding] for executing tests in
/// the `flutter run` environment, on a device. This is intended to
/// allow interactive test development.
///
/// This is not the way to run a remote-control test. To run a test on
/// a device from a development computer, see the [flutter_driver]
/// package and the `flutter drive` command.
///
1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222
/// When running tests using `flutter run`, consider adding the
/// `--use-test-fonts` argument so that the fonts used match those used under
/// `flutter test`. (This forces all text to use the "Ahem" font, which is a
/// font that covers ASCII characters and gives them all the appearance of a
/// square whose size equals the font size.)
///
/// This binding overrides the default [SchedulerBinding] behavior to ensure
/// that tests work in the same way in this environment as they would under the
/// [AutomatedTestWidgetsFlutterBinding]. To override this (and see intermediate
/// frames that the test does not explicitly trigger), set [framePolicy] to
/// [LiveTestWidgetsFlutterBindingFramePolicy.fullyLive]. (This is likely to
/// make tests fail, though, especially if e.g. they test how many times a
/// particular widget was built.) The default behavior is to show pumped frames
/// and a few additional frames when pointers are triggered (to animate the
/// pointer crosshairs).
1223 1224 1225 1226 1227 1228 1229 1230 1231 1232
///
/// This binding does not support the [EnginePhase] argument to
/// [pump]. (There would be no point setting it to a value that
/// doesn't trigger a paint, since then you could not see anything
/// anyway.)
class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
  @override
  bool get inTest => _inTest;
  bool _inTest = false;

1233 1234 1235
  @override
  Clock get clock => const Clock();

1236 1237
  @override
  int get microtaskCount {
1238 1239
    // The Dart SDK doesn't report this number.
    assert(false, 'microtaskCount cannot be reported when running in real time');
1240 1241 1242
    return -1;
  }

1243 1244 1245
  @override
  test_package.Timeout get defaultTestTimeout => test_package.Timeout.none;

1246
  Completer<void> _pendingFrame;
1247
  bool _expectingFrame = false;
1248
  bool _viewNeedsPaint = false;
1249
  bool _runningAsyncTasks = false;
1250 1251 1252 1253 1254 1255 1256 1257

  /// Whether to have [pump] with a duration only pump a single frame
  /// (as would happen in a normal test environment using
  /// [AutomatedTestWidgetsFlutterBinding]), or whether to instead
  /// pump every frame that the system requests during any
  /// asynchronous pause in the test (as would normally happen when
  /// running an application with [WidgetsFlutterBinding]).
  ///
1258 1259 1260 1261 1262 1263 1264 1265
  /// * [LiveTestWidgetsFlutterBindingFramePolicy.fadePointers] is the default
  ///   behavior, which is to only pump once, except when there has been some
  ///   activity with [TestPointer]s, in which case those are shown and may pump
  ///   additional frames.
  ///
  /// * [LiveTestWidgetsFlutterBindingFramePolicy.onlyPumps] is the strictest
  ///   behavior, which is to only pump once. This most closely matches the
  ///   [AutomatedTestWidgetsFlutterBinding] (`flutter test`) behavior.
1266
  ///
1267 1268 1269
  /// * [LiveTestWidgetsFlutterBindingFramePolicy.fullyLive] allows all frame
  ///   requests from the engine to be serviced, even those the test did not
  ///   explicitly pump.
1270
  ///
1271 1272 1273 1274 1275 1276 1277 1278 1279 1280
  /// * [LiveTestWidgetsFlutterBindingFramePolicy.benchmark] allows all frame
  ///   requests from the engine to be serviced, and allows all frame requests
  ///   that are artificially triggered to be serviced, but prevents the
  ///   framework from requesting any frames from the engine itself. The
  ///   [SchedulerBinding.hasScheduledFrame] property will never be true in this
  ///   mode. This can cause unexpected effects. For instance,
  ///   [WidgetTester.pumpAndSettle] does not function in this mode, as it
  ///   relies on the [SchedulerBinding.hasScheduledFrame] property to determine
  ///   when the application has "settled".
  ///
1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291
  /// Setting this to anything other than
  /// [LiveTestWidgetsFlutterBindingFramePolicy.onlyPumps] means pumping extra
  /// frames, which might involve calling builders more, or calling paint
  /// callbacks more, etc, which might interfere with the test. If you know your
  /// test file wouldn't be affected by this, you can set it to
  /// [LiveTestWidgetsFlutterBindingFramePolicy.fullyLive] persistently in that
  /// particular test file. To set this to
  /// [LiveTestWidgetsFlutterBindingFramePolicy.fullyLive] while still allowing
  /// the test file to work as a normal test, add the following code to your
  /// test file at the top of your `void main() { }` function, before calls to
  /// [testWidgets]:
1292 1293 1294 1295
  ///
  /// ```dart
  /// TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
  /// if (binding is LiveTestWidgetsFlutterBinding)
1296
  ///   binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
1297
  /// ```
1298
  LiveTestWidgetsFlutterBindingFramePolicy framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fadePointers;
1299

1300 1301 1302 1303 1304 1305
  @override
  void addTime(Duration duration) {
    // We don't support timeouts on the LiveTestWidgetsFlutterBinding.
    // See runTest().
  }

1306 1307 1308 1309 1310 1311 1312
  @override
  void scheduleFrame() {
    if (framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.benchmark)
      return; // In benchmark mode, don't actually schedule any engine frames.
    super.scheduleFrame();
  }

1313 1314 1315 1316 1317 1318 1319
  @override
  void scheduleForcedFrame() {
    if (framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.benchmark)
      return; // In benchmark mode, don't actually schedule any engine frames.
    super.scheduleForcedFrame();
  }

1320 1321
  bool _doDrawThisFrame;

1322 1323
  @override
  void handleBeginFrame(Duration rawTimeStamp) {
1324
    assert(_doDrawThisFrame == null);
1325 1326
    if (_expectingFrame ||
        (framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.fullyLive) ||
1327
        (framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.benchmark) ||
1328 1329
        (framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.fadePointers && _viewNeedsPaint)) {
      _doDrawThisFrame = true;
1330
      super.handleBeginFrame(rawTimeStamp);
1331 1332 1333
    } else {
      _doDrawThisFrame = false;
    }
1334 1335 1336 1337 1338 1339 1340 1341
  }

  @override
  void handleDrawFrame() {
    assert(_doDrawThisFrame != null);
    if (_doDrawThisFrame)
      super.handleDrawFrame();
    _doDrawThisFrame = null;
1342 1343
    _viewNeedsPaint = false;
    if (_expectingFrame) { // set during pump
1344 1345 1346 1347
      assert(_pendingFrame != null);
      _pendingFrame.complete(); // unlocks the test API
      _pendingFrame = null;
      _expectingFrame = false;
1348
    } else if (framePolicy != LiveTestWidgetsFlutterBindingFramePolicy.benchmark) {
1349
      window.scheduleFrame();
1350 1351 1352
    }
  }

1353 1354 1355
  @override
  void initRenderView() {
    assert(renderView == null);
1356
    renderView = _LiveTestRenderView(
1357 1358
      configuration: createViewConfiguration(),
      onNeedPaint: _handleViewNeedsPaint,
1359
      window: window,
1360
    );
1361
    renderView.prepareInitialFrame();
1362 1363 1364
  }

  @override
1365
  _LiveTestRenderView get renderView => super.renderView as _LiveTestRenderView;
1366

1367 1368 1369 1370 1371
  void _handleViewNeedsPaint() {
    _viewNeedsPaint = true;
    renderView.markNeedsPaint();
  }

1372 1373 1374 1375 1376 1377 1378 1379 1380
  /// An object to which real device events should be routed.
  ///
  /// Normally, device events are silently dropped. However, if this property is
  /// set to a non-null value, then the events will be routed to its
  /// [HitTestDispatcher.dispatchEvent] method instead.
  ///
  /// Events dispatched by [TestGesture] are not affected by this.
  HitTestDispatcher deviceEventDispatcher;

1381
  @override
1382 1383 1384
  void dispatchEvent(
    PointerEvent event,
    HitTestResult hitTestResult, {
1385
    TestBindingEventSource source = TestBindingEventSource.device,
1386
  }) {
1387 1388 1389 1390
    switch (source) {
      case TestBindingEventSource.test:
        if (!renderView._pointers.containsKey(event.pointer)) {
          assert(event.down);
1391
          renderView._pointers[event.pointer] = _LiveTestPointerRecord(event.pointer, event.position);
1392 1393 1394 1395 1396
        } else {
          renderView._pointers[event.pointer].position = event.position;
          if (!event.down)
            renderView._pointers[event.pointer].decay = _kPointerDecay;
        }
1397
        _handleViewNeedsPaint();
1398
        super.dispatchEvent(event, hitTestResult, source: source);
1399 1400 1401
        break;
      case TestBindingEventSource.device:
        if (deviceEventDispatcher != null)
1402
          deviceEventDispatcher.dispatchEvent(event, hitTestResult);
1403
        break;
1404 1405 1406
    }
  }

1407
  @override
1408
  Future<void> pump([ Duration duration, EnginePhase newPhase = EnginePhase.sendSemanticsUpdate ]) {
1409
    assert(newPhase == EnginePhase.sendSemanticsUpdate);
1410 1411 1412
    assert(inTest);
    assert(!_expectingFrame);
    assert(_pendingFrame == null);
1413
    return TestAsyncUtils.guard<void>(() {
1414
      if (duration != null) {
1415
        Timer(duration, () {
1416 1417 1418 1419 1420 1421 1422
          _expectingFrame = true;
          scheduleFrame();
        });
      } else {
        _expectingFrame = true;
        scheduleFrame();
      }
1423
      _pendingFrame = Completer<void>();
1424 1425 1426 1427
      return _pendingFrame.future;
    });
  }

1428
  @override
1429 1430
  Future<T> runAsync<T>(
    Future<T> callback(), {
1431
    Duration additionalTime = const Duration(milliseconds: 1000),
1432
  }) async {
1433 1434 1435
    assert(() {
      if (!_runningAsyncTasks)
        return true;
1436
      throw test_package.TestFailure(
1437 1438 1439 1440 1441 1442 1443
          'Reentrant call to runAsync() denied.\n'
          'runAsync() was called, then before its future completed, it '
          'was called again. You must wait for the first returned future '
          'to complete before calling runAsync() again.'
      );
    }());

1444 1445
    addTime(additionalTime); // doesn't do anything since we don't actually track the timeout, but just for correctness...

1446 1447 1448 1449
    _runningAsyncTasks = true;
    try {
      return await callback();
    } catch (error, stack) {
1450
      FlutterError.reportError(FlutterErrorDetails(
1451 1452 1453
        exception: error,
        stack: stack,
        library: 'Flutter test framework',
1454
        context: ErrorSummary('while running async test code'),
1455 1456 1457 1458 1459 1460 1461
      ));
      return null;
    } finally {
      _runningAsyncTasks = false;
    }
  }

1462
  @override
1463
  Future<void> runTest(Future<void> testBody(), VoidCallback invariantTester, { String description = '', Duration timeout }) async {
1464
    assert(description != null);
1465 1466
    assert(!inTest);
    _inTest = true;
1467
    renderView._setDescription(description);
1468 1469 1470 1471
    // 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.
1472
    return _runTest(testBody, invariantTester, description);
1473 1474
  }

1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489
  @override
  void reportExceptionNoticed(FlutterErrorDetails exception) {
    final DebugPrintCallback testPrint = debugPrint;
    debugPrint = debugPrintOverride;
    debugPrint('(The following exception is now available via WidgetTester.takeException:)');
    FlutterError.dumpErrorToConsole(exception, forceReport: true);
    debugPrint(
      '(If WidgetTester.takeException is called, the above exception will be ignored. '
      'If it is not, then the above exception will be dumped when another exception is '
      'caught by the framework or when the test ends, whichever happens first, and then '
      'the test will fail due to having not caught or expected the exception.)'
    );
    debugPrint = testPrint;
  }

1490 1491 1492 1493 1494 1495 1496 1497 1498 1499
  @override
  void postTest() {
    super.postTest();
    assert(!_expectingFrame);
    assert(_pendingFrame == null);
    _inTest = false;
  }

  @override
  ViewConfiguration createViewConfiguration() {
1500 1501 1502 1503
    return TestViewConfiguration(
      size: _surfaceSize ?? _kDefaultTestViewportSize,
      window: window,
    );
1504 1505 1506
  }

  @override
1507
  Offset globalToLocal(Offset point) {
1508 1509
    final Matrix4 transform = renderView.configuration.toHitTestMatrix();
    final double det = transform.invert();
1510
    assert(det != 0.0);
1511
    final Offset result = MatrixUtils.transformPoint(transform, point);
1512 1513 1514 1515
    return result;
  }

  @override
1516
  Offset localToGlobal(Offset point) {
1517
    final Matrix4 transform = renderView.configuration.toHitTestMatrix();
1518 1519
    return MatrixUtils.transformPoint(transform, point);
  }
1520
}
1521

1522 1523
/// A [ViewConfiguration] that pretends the display is of a particular size. The
/// size is in logical pixels. The resulting ViewConfiguration maps the given
1524
/// size onto the actual display using the [BoxFit.contain] algorithm.
1525 1526
class TestViewConfiguration extends ViewConfiguration {
  /// Creates a [TestViewConfiguration] with the given size. Defaults to 800x600.
1527 1528 1529
  ///
  /// If a [window] instance is not provided it defaults to [ui.window].
  factory TestViewConfiguration({
1530 1531
    Size size = _kDefaultTestViewportSize,
    ui.Window window,
1532 1533 1534 1535 1536
  }) {
    return TestViewConfiguration._(size, window ?? ui.window);
  }

  TestViewConfiguration._(Size size, ui.Window window)
1537 1538
    : _paintMatrix = _getMatrix(size, window.devicePixelRatio, window),
      _hitTestMatrix = _getMatrix(size, 1.0, window),
1539 1540
      super(size: size);

1541 1542 1543 1544
  static Matrix4 _getMatrix(Size size, double devicePixelRatio, ui.Window window) {
    final double inverseRatio = devicePixelRatio / window.devicePixelRatio;
    final double actualWidth = window.physicalSize.width * inverseRatio;
    final double actualHeight = window.physicalSize.height * inverseRatio;
1545 1546
    final double desiredWidth = size.width;
    final double desiredHeight = size.height;
1547 1548 1549 1550 1551 1552 1553 1554 1555 1556
    double scale, shiftX, shiftY;
    if ((actualWidth / actualHeight) > (desiredWidth / desiredHeight)) {
      scale = actualHeight / desiredHeight;
      shiftX = (actualWidth - desiredWidth * scale) / 2.0;
      shiftY = 0.0;
    } else {
      scale = actualWidth / desiredWidth;
      shiftX = 0.0;
      shiftY = (actualHeight - desiredHeight * scale) / 2.0;
    }
1557 1558 1559
    final Matrix4 matrix = Matrix4.compose(
      Vector3(shiftX, shiftY, 0.0), // translation
      Quaternion.identity(), // rotation
1560
      Vector3(scale, scale, 1.0), // scale
1561
    );
1562
    return matrix;
1563 1564
  }

1565 1566
  final Matrix4 _paintMatrix;
  final Matrix4 _hitTestMatrix;
1567 1568

  @override
1569
  Matrix4 toMatrix() => _paintMatrix.clone();
1570

1571 1572 1573
  /// Provides the transformation matrix that converts coordinates in the test
  /// coordinate space to coordinates in logical pixels on the real display.
  ///
1574
  /// This is essentially the same as [toMatrix] but ignoring the device pixel
1575 1576 1577 1578 1579
  /// ratio.
  ///
  /// This is useful because pointers are described in logical pixels, as
  /// opposed to graphics which are expressed in physical pixels.
  Matrix4 toHitTestMatrix() => _hitTestMatrix.clone();
1580 1581 1582 1583 1584

  @override
  String toString() => 'TestViewConfiguration';
}

1585 1586 1587 1588
const int _kPointerDecay = -2;

class _LiveTestPointerRecord {
  _LiveTestPointerRecord(
1589
    this.pointer,
1590
    this.position,
1591
  ) : color = HSVColor.fromAHSV(0.8, (35.0 * pointer) % 360.0, 1.0, 1.0).toColor(),
1592 1593 1594
      decay = 1;
  final int pointer;
  final Color color;
1595
  Offset position;
1596 1597 1598 1599 1600
  int decay; // >0 means down, <0 means up, increases by one each time, removed at 0
}

class _LiveTestRenderView extends RenderView {
  _LiveTestRenderView({
1601 1602
    ViewConfiguration configuration,
    this.onNeedPaint,
1603 1604
    @required ui.Window window,
  }) : super(configuration: configuration, window: window);
1605

1606
  @override
1607
  TestViewConfiguration get configuration => super.configuration as TestViewConfiguration;
1608
  @override
1609
  set configuration(covariant TestViewConfiguration value) { super.configuration = value; }
1610

1611 1612
  final VoidCallback onNeedPaint;

1613 1614
  final Map<int, _LiveTestPointerRecord> _pointers = <int, _LiveTestPointerRecord>{};

1615
  TextPainter _label;
1616
  static const TextStyle _labelStyle = TextStyle(
1617 1618 1619 1620 1621 1622 1623 1624 1625
    fontFamily: 'sans-serif',
    fontSize: 10.0,
  );
  void _setDescription(String value) {
    assert(value != null);
    if (value.isEmpty) {
      _label = null;
      return;
    }
Ian Hickson's avatar
Ian Hickson committed
1626
    // TODO(ianh): Figure out if the test name is actually RTL.
1627 1628
    _label ??= TextPainter(textAlign: TextAlign.left, textDirection: TextDirection.ltr);
    _label.text = TextSpan(text: value, style: _labelStyle);
1629 1630 1631 1632 1633
    _label.layout();
    if (onNeedPaint != null)
      onNeedPaint();
  }

1634
  @override
1635
  bool hitTest(HitTestResult result, { Offset position }) {
1636 1637
    final Matrix4 transform = configuration.toHitTestMatrix();
    final double det = transform.invert();
1638 1639 1640 1641 1642
    assert(det != 0.0);
    position = MatrixUtils.transformPoint(transform, position);
    return super.hitTest(result, position: position);
  }

1643 1644 1645 1646 1647 1648
  @override
  void paint(PaintingContext context, Offset offset) {
    assert(offset == Offset.zero);
    super.paint(context, offset);
    if (_pointers.isNotEmpty) {
      final double radius = configuration.size.shortestSide * 0.05;
1649 1650
      final Path path = Path()
        ..addOval(Rect.fromCircle(center: Offset.zero, radius: radius))
1651 1652 1653 1654 1655
        ..moveTo(0.0, -radius * 2.0)
        ..lineTo(0.0, radius * 2.0)
        ..moveTo(-radius * 2.0, 0.0)
        ..lineTo(radius * 2.0, 0.0);
      final Canvas canvas = context.canvas;
1656
      final Paint paint = Paint()
1657 1658 1659 1660
        ..strokeWidth = radius / 10.0
        ..style = PaintingStyle.stroke;
      bool dirty = false;
      for (int pointer in _pointers.keys) {
1661
        final _LiveTestPointerRecord record = _pointers[pointer];
1662
        paint.color = record.color.withOpacity(record.decay < 0 ? (record.decay / (_kPointerDecay - 1)) : 1.0);
1663
        canvas.drawPath(path.shift(record.position), paint);
1664 1665 1666 1667 1668 1669 1670 1671
        if (record.decay < 0)
          dirty = true;
        record.decay += 1;
      }
      _pointers
        .keys
        .where((int pointer) => _pointers[pointer].decay == 0)
        .toList()
1672
        .forEach(_pointers.remove);
1673 1674
      if (dirty && onNeedPaint != null)
        scheduleMicrotask(onNeedPaint);
1675
    }
1676
    _label?.paint(context.canvas, offset - const Offset(0.0, 10.0));
1677 1678 1679
  }
}

1680 1681 1682 1683 1684 1685 1686
StackTrace _unmangle(StackTrace stack) {
  if (stack is stack_trace.Trace)
    return stack.vmTrace;
  if (stack is stack_trace.Chain)
    return stack.toTrace().vmTrace;
  return stack;
}