binding.dart 60.5 KB
Newer Older
1 2 3 4
// Copyright 2016 The Chromium Authors. All rights reserved.
// 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:convert';
7 8
import 'dart:io';
import 'dart:ui' as ui;
9

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

22
import 'goldens.dart';
23
import 'stack_manipulation.dart';
24
import 'test_async_utils.dart';
25
import 'test_exception_reporter.dart';
26
import 'test_text_input.dart';
27

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

  /// The layout phase in the rendering library. See [PipelineOwner.flushLayout].
38
  layout,
39 40 41

  /// The compositing bits update phase in the rendering library. See
  /// [PipelineOwner.flushCompositingBits].
42
  compositingBits,
43 44

  /// The paint phase in the rendering library. See [PipelineOwner.flushPaint].
45
  paint,
46 47 48 49

  /// 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.
50
  composite,
51 52 53

  /// The semantics building phase in the rendering library. See
  /// [PipelineOwner.flushSemantics].
54
  flushSemantics,
55 56

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

/// 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,
75 76
}

77
const Size _kDefaultTestViewportSize = Size(800.0, 600.0);
78 79 80 81 82 83 84 85 86 87 88 89

/// 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
  with SchedulerBinding,
       GestureBinding,
       RendererBinding,
90 91
       ServicesBinding,
       PaintingBinding,
92
       WidgetsBinding {
93

94 95 96 97
  /// Constructor for [TestWidgetsFlutterBinding].
  ///
  /// This constructor overrides the [debugPrint] global hook to point to
  /// [debugPrintOverride], which can be overridden by subclasses.
98 99
  TestWidgetsFlutterBinding() {
    debugPrint = debugPrintOverride;
100
    debugDisableShadows = disableShadows;
101
    debugCheckIntrinsicSizes = checkIntrinsicSizes;
102 103
  }

104 105 106 107 108
  /// 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]
109
  /// synchronous, disabling its normal throttling behavior.
110 111 112
  @protected
  DebugPrintCallback get debugPrintOverride => debugPrint;

113 114 115 116 117 118 119 120 121
  /// 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;

122 123 124 125 126
  /// 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.
127 128 129
  @protected
  bool get checkIntrinsicSizes => false;

130
  /// Creates and initializes the binding. This function is
131 132
  /// idempotent; calling it a second time will just return the
  /// previously-created instance.
133 134 135 136 137 138
  ///
  /// This function will use [AutomatedTestWidgetsFlutterBinding] if
  /// the test was run using `flutter test`, and
  /// [LiveTestWidgetsFlutterBinding] otherwise (e.g. if it was run
  /// using `flutter run`). (This is determined by looking at the
  /// environment variables for a variable called `FLUTTER_TEST`.)
139
  static WidgetsBinding ensureInitialized() {
140 141
    if (WidgetsBinding.instance == null) {
      if (Platform.environment.containsKey('FLUTTER_TEST')) {
142
        new AutomatedTestWidgetsFlutterBinding();
143
      } else {
144
        new LiveTestWidgetsFlutterBinding();
145 146
      }
    }
147
    assert(WidgetsBinding.instance is TestWidgetsFlutterBinding);
148
    return WidgetsBinding.instance;
149 150
  }

151 152 153
  @override
  void initInstances() {
    timeDilation = 1.0; // just in case the developer has artificially changed it for development
154
    HttpOverrides.global = new _MockHttpOverrides();
155
    _testTextInput = new TestTextInput(onCleared: _resetFocusedEditable)..register();
156 157 158
    super.initInstances();
  }

159 160 161 162 163 164
  @override
  void initLicenses() {
    // Do not include any licenses, because we're a test, and the LICENSE file
    // doesn't get generated for tests.
  }

165
  /// Whether there is currently a test executing.
166
  bool get inTest;
167

168 169 170
  /// The number of outstanding microtasks in the queue.
  int get microtaskCount;

171
  /// The default test timeout for tests when using this binding.
172 173 174 175
  ///
  /// The [AutomatedTestWidgetsFlutterBinding] layers in an additional timeout
  /// mechanism beyond this, with much more aggressive timeouts. See
  /// [AutomatedTestWidgetsFlutterBinding.addTime].
176
  test_package.Timeout get defaultTestTimeout;
177

178 179 180 181 182 183 184 185 186 187
  /// 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;

188 189 190 191 192 193 194 195
  /// 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.
196 197 198
  ///
  /// See also [LiveTestWidgetsFlutterBindingFramePolicy], which affects how
  /// this method works when the test is run with `flutter run`.
199
  Future<Null> pump([ Duration duration, EnginePhase newPhase = EnginePhase.sendSemanticsUpdate ]);
200

201
  /// Runs a `callback` that performs real asynchronous work.
202 203 204 205 206
  ///
  /// 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].
  ///
207 208
  /// If `callback` completes successfully, this will return the future
  /// returned by `callback`.
209
  ///
210
  /// If `callback` completes with an error, the error will be caught by the
211 212 213 214 215 216 217
  /// 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.
218 219 220 221 222 223 224 225
  ///
  /// The `additionalTime` argument is used by the
  /// [AutomatedTestWidgetsFlutterBinding] implementation to increase the
  /// current timeout. See [AutomatedTestWidgetsFlutterBinding.addTime] for
  /// details. The value is ignored by the [LiveTestWidgetsFlutterBinding].
  Future<T> runAsync<T>(Future<T> callback(), {
    Duration additionalTime = const Duration(milliseconds: 250),
  });
226

227 228
  /// Artificially calls dispatchLocaleChanged on the Widget binding,
  /// then flushes microtasks.
229 230 231
  Future<Null> setLocale(String languageCode, String countryCode) {
    return TestAsyncUtils.guard(() async {
      assert(inTest);
232
      final Locale locale = new Locale(languageCode, countryCode);
233 234 235 236 237
      dispatchLocaleChanged(locale);
      return null;
    });
  }

238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
  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.
  Future<Null> setSurfaceSize(Size size) {
    return TestAsyncUtils.guard(() async {
      assert(inTest);
      if (_surfaceSize == size)
        return null;
      _surfaceSize = size;
      handleMetricsChanged();
      return null;
    });
  }

  @override
  ViewConfiguration createViewConfiguration() {
    final double devicePixelRatio = ui.window.devicePixelRatio;
    final Size size = _surfaceSize ?? ui.window.physicalSize / devicePixelRatio;
    return new ViewConfiguration(
      size: size,
      devicePixelRatio: devicePixelRatio,
    );
  }

265 266 267
  /// Acts as if the application went idle.
  ///
  /// Runs all remaining microtasks, including those scheduled as a result of
268 269
  /// running them, until there are no more microtasks scheduled. Then, runs any
  /// previously scheduled timers with zero time, and completes the returned future.
270
  ///
271 272 273
  /// 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.
274
  Future<Null> idle() {
275 276 277 278 279 280 281
    return TestAsyncUtils.guard(() {
      final Completer<Null> completer = new Completer<Null>();
      Timer.run(() {
        completer.complete(null);
      });
      return completer.future;
    });
282 283
  }

284
  /// Convert the given point from the global coordinate system (as used by
285 286
  /// pointer events from the device) to the coordinate system used by the
  /// tests (an 800 by 600 window).
287
  Offset globalToLocal(Offset point) => point;
288 289

  /// Convert the given point from the coordinate system used by the tests (an
290
  /// 800 by 600 window) to the global coordinate system (as used by pointer
291
  /// events from the device).
292
  Offset localToGlobal(Offset point) => point;
293

294 295
  @override
  void dispatchEvent(PointerEvent event, HitTestResult result, {
296
    TestBindingEventSource source = TestBindingEventSource.device
297 298 299 300 301
  }) {
    assert(source == TestBindingEventSource.test);
    super.dispatchEvent(event, result);
  }

302 303 304 305 306 307 308 309
  /// 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.
310 311 312
  ///
  /// Instead of setting this directly, consider using
  /// [WidgetTester.showKeyboard].
313 314
  EditableTextState get focusedEditable => _focusedEditable;
  EditableTextState _focusedEditable;
315
  set focusedEditable(EditableTextState value) {
316 317 318 319 320 321 322 323
    if (_focusedEditable != value) {
      _focusedEditable = value;
      value?.requestKeyboard();
    }
  }

  void _resetFocusedEditable() {
    _focusedEditable = null;
324 325
  }

326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
  /// 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() {
341
    assert(inTest);
342
    final dynamic result = _pendingExceptionDetails?.exception;
343
    _pendingExceptionDetails = null;
344 345
    return result;
  }
346 347
  FlutterExceptionHandler _oldExceptionHandler;
  FlutterErrorDetails _pendingExceptionDetails;
348

349 350
  static const TextStyle _messageStyle = TextStyle(
    color: Color(0xFF917FFF),
Ian Hickson's avatar
Ian Hickson committed
351
    fontSize: 40.0,
352 353
  );

354 355
  static const Widget _preTestMessage = Center(
    child: Text(
356
      'Test starting...',
357
      style: _messageStyle,
Ian Hickson's avatar
Ian Hickson committed
358
      textDirection: TextDirection.ltr,
359 360 361
    )
  );

362 363
  static const Widget _postTestMessage = Center(
    child: Text(
364
      'Test finished.',
365
      style: _messageStyle,
Ian Hickson's avatar
Ian Hickson committed
366
      textDirection: TextDirection.ltr,
367 368 369 370 371 372 373
    )
  );

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

374
  /// Call the testBody inside a [FakeAsync] scope on which [pump] can
375 376 377 378 379 380
  /// advance time.
  ///
  /// Returns a future which completes when the test has run.
  ///
  /// Called by the [testWidgets] and [benchmarkWidgets] functions to
  /// run a test.
381 382 383
  ///
  /// The `invariantTester` argument is called after the `testBody`'s [Future]
  /// completes. If it throws, then the test is marked as failed.
384 385 386 387
  ///
  /// 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.
388
  Future<Null> runTest(Future<Null> testBody(), VoidCallback invariantTester, { String description = '' });
389 390 391 392 393 394 395 396 397 398 399

  /// 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;
400 401 402 403 404 405 406 407 408 409 410 411 412 413

  VoidCallback _createTestCompletionHandler(String testDescription, Completer<Null> completer) {
    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)
        completer.complete(null);
    };
414 415
  }

416 417 418 419 420 421 422 423 424 425 426 427
  /// 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.
  }

428 429 430
  Future<Null> _runTest(Future<Null> testBody(), VoidCallback invariantTester, String description, {
    Future<Null> timeout,
  }) {
431
    assert(description != null);
432 433 434
    assert(inTest);
    _oldExceptionHandler = FlutterError.onError;
    int _exceptionCount = 0; // number of un-taken exceptions
435
    FlutterError.onError = (FlutterErrorDetails details) {
436
      if (_pendingExceptionDetails != null) {
437
        debugPrint = debugPrintOverride; // just in case the test overrides it -- otherwise we won't see the errors!
438 439
        if (_exceptionCount == 0) {
          _exceptionCount = 2;
440
          FlutterError.dumpErrorToConsole(_pendingExceptionDetails, forceReport: true);
441 442
        } else {
          _exceptionCount += 1;
443
        }
444
        FlutterError.dumpErrorToConsole(details, forceReport: true);
445
        _pendingExceptionDetails = new FlutterErrorDetails(
446 447 448 449
          exception: 'Multiple exceptions ($_exceptionCount) were detected during the running of the current test, and at least one was unexpected.',
          library: 'Flutter test framework'
        );
      } else {
450
        reportExceptionNoticed(details); // mostly this is just a hook for the LiveTestWidgetsFlutterBinding
451
        _pendingExceptionDetails = details;
452
      }
453
    };
454 455
    final Completer<Null> testCompleter = new Completer<Null>();
    final VoidCallback testCompletionHandler = _createTestCompletionHandler(description, testCompleter);
456 457 458 459 460 461 462 463 464 465
    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!
        FlutterError.dumpErrorToConsole(new FlutterErrorDetails(
466
          exception: exception,
467
          stack: _unmangle(stack),
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522
          context: 'running a test (but after the test had completed)',
          library: 'Flutter test framework'
        ), 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.
      String treeDump;
      try {
        treeDump = renderViewElement?.toStringDeep() ?? '<no tree>';
      } catch (exception) {
        treeDump = '<additional error caught while dumping tree: $exception>';
      }
      final StringBuffer expectLine = new StringBuffer();
      final int stackLinesToOmit = reportExpectCall(stack, expectLine);
      FlutterError.reportError(new FlutterErrorDetails(
        exception: exception,
        stack: _unmangle(stack),
        context: 'running a test',
        library: 'Flutter test framework',
        stackFilter: (Iterable<String> frames) {
          return FlutterError.defaultStackFilter(frames.skip(stackLinesToOmit));
        },
        informationCollector: (StringBuffer information) {
          if (stackLinesToOmit > 0)
            information.writeln(expectLine.toString());
          if (showAppDumpInErrors) {
            information.writeln('At the time of the failure, the widget tree looked as follows:');
            information.writeln('# ${treeDump.split("\n").takeWhile((String s) => s != "").join("\n# ")}');
523
          }
524 525 526 527 528 529 530 531 532 533 534
          if (description.isNotEmpty)
            information.writeln('The test description was:\n$description');
        }
      ));
      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);
    }
    final ZoneSpecification errorHandlingZoneSpecification = new ZoneSpecification(
      handleUncaughtError: (Zone self, ZoneDelegate parent, Zone zone, dynamic exception, StackTrace stack) {
        handleUncaughtError(exception, stack);
535 536 537
      }
    );
    _parentZone = Zone.current;
538
    final Zone testZone = _parentZone.fork(specification: errorHandlingZoneSpecification);
539
    testZone.runBinary(_runTestBody, testBody, invariantTester)
540
      .whenComplete(testCompletionHandler);
541
    timeout?.catchError(handleUncaughtError);
542
    return testCompleter.future;
543 544
  }

545
  Future<Null> _runTestBody(Future<Null> testBody(), VoidCallback invariantTester) async {
546 547
    assert(inTest);

548
    runApp(new Container(key: new UniqueKey(), child: _preTestMessage)); // Reset the tree to a known state.
549 550
    await pump();

551
    final bool autoUpdateGoldensBeforeTest = autoUpdateGoldenFiles;
552
    final TestExceptionReporter reportTestExceptionBeforeTest = reportTestException;
553

554
    // run the test
555
    await testBody();
556 557 558 559 560 561
    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.
562
      runApp(new Container(key: new UniqueKey(), child: _postTestMessage)); // Unmount any remaining widgets.
563
      await pump();
564
      invariantTester();
565
      _verifyAutoUpdateGoldensUnset(autoUpdateGoldensBeforeTest);
566
      _verifyReportTestExceptionUnset(reportTestExceptionBeforeTest);
567 568 569 570
      _verifyInvariants();
    }

    assert(inTest);
571
    asyncBarrier(); // When using AutomatedTestWidgetsFlutterBinding, this flushes the microtasks.
572 573 574 575 576 577 578
    return null;
  }

  void _verifyInvariants() {
    assert(debugAssertNoTransientCallbacks(
      'An animation is still running even after the widget tree was disposed.'
    ));
579 580 581 582
    assert(debugAssertAllFoundationVarsUnset(
      'The value of a foundation debug variable was changed by the test.',
      debugPrintOverride: debugPrintOverride,
    ));
583 584 585
    assert(debugAssertAllGesturesVarsUnset(
      'The value of a gestures debug variable was changed by the test.',
    ));
586 587 588 589
    assert(debugAssertAllPaintingVarsUnset(
      'The value of a painting debug variable was changed by the test.',
      debugDisableShadowsOverride: disableShadows,
    ));
590
    assert(debugAssertAllRenderVarsUnset(
591 592
      'The value of a rendering debug variable was changed by the test.',
      debugCheckIntrinsicSizesOverride: checkIntrinsicSizes,
593 594
    ));
    assert(debugAssertAllWidgetVarsUnset(
595
      'The value of a widget debug variable was changed by the test.',
596 597
    ));
    assert(debugAssertAllSchedulerVarsUnset(
598
      'The value of a scheduler debug variable was changed by the test.',
599
    ));
600 601
  }

602 603 604 605 606 607 608 609 610 611 612 613 614 615 616
  void _verifyAutoUpdateGoldensUnset(bool valueBeforeTest) {
    assert(() {
      if (autoUpdateGoldenFiles != valueBeforeTest) {
        FlutterError.reportError(new FlutterErrorDetails(
          exception: new FlutterError(
              'The value of autoUpdateGoldenFiles was changed by the test.',
          ),
          stack: StackTrace.current,
          library: 'Flutter test framework',
        ));
      }
      return true;
    }());
  }

617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636
  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;
        FlutterError.reportError(new FlutterErrorDetails(
          exception: new FlutterError(
            'The value of reportTestException was changed by the test.',
          ),
          stack: StackTrace.current,
          library: 'Flutter test framework',
        ));
      }
      return true;
    }());
  }

637 638 639 640 641 642 643 644 645 646 647 648 649 650
  /// Called by the [testWidgets] function after a test is executed.
  void postTest() {
    assert(inTest);
    FlutterError.onError = _oldExceptionHandler;
    _pendingExceptionDetails = null;
    _parentZone = null;
  }
}

/// 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.
651 652 653
///
/// This class assumes it is always run in checked mode (since tests are always
/// run in checked mode).
654 655 656 657 658
class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
  @override
  void initInstances() {
    super.initInstances();
    ui.window.onBeginFrame = null;
659
    ui.window.onDrawFrame = null;
660 661
  }

662
  FakeAsync _currentFakeAsync; // set in runTest; cleared in postTest
663
  Completer<void> _pendingAsyncTasks;
664 665 666

  @override
  Clock get clock => _clock;
667 668
  Clock _clock;

669 670 671
  @override
  DebugPrintCallback get debugPrintOverride => debugPrintSynchronously;

672 673 674
  @override
  bool get disableShadows => true;

675 676 677
  @override
  bool get checkIntrinsicSizes => true;

678 679
  // The timeout here is absurdly high because we do our own timeout logic and
  // this is just a backstop.
680
  @override
681
  test_package.Timeout get defaultTestTimeout => const test_package.Timeout(Duration(minutes: 5));
682 683

  @override
684
  bool get inTest => _currentFakeAsync != null;
685

686
  @override
687
  int get microtaskCount => _currentFakeAsync.microtaskCount;
688

689
  @override
690
  Future<Null> pump([ Duration duration, EnginePhase newPhase = EnginePhase.sendSemanticsUpdate ]) {
691 692 693 694
    return TestAsyncUtils.guard(() {
      assert(inTest);
      assert(_clock != null);
      if (duration != null)
695
        _currentFakeAsync.elapse(duration);
696 697
      _phase = newPhase;
      if (hasScheduledFrame) {
698
        addTime(const Duration(milliseconds: 100));
699
        _currentFakeAsync.flushMicrotasks();
700
        handleBeginFrame(new Duration(
701
          milliseconds: _clock.now().millisecondsSinceEpoch,
702
        ));
703
        _currentFakeAsync.flushMicrotasks();
704
        handleDrawFrame();
705
      }
706
      _currentFakeAsync.flushMicrotasks();
707 708 709 710
      return new Future<Null>.value();
    });
  }

711
  @override
712 713 714 715
  Future<T> runAsync<T>(Future<T> callback(), {
    Duration additionalTime = const Duration(milliseconds: 250),
  }) {
    assert(additionalTime != null);
716 717 718 719 720 721 722 723 724 725 726
    assert(() {
      if (_pendingAsyncTasks == null)
        return true;
      throw new test_package.TestFailure(
          '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.'
      );
    }());

727 728 729 730 731 732 733 734 735 736 737 738 739 740
    final Zone realAsyncZone = Zone.current.fork(
      specification: new ZoneSpecification(
        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);
        },
      ),
    );

741 742
    addTime(additionalTime);

743
    return realAsyncZone.run(() {
744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764
      _pendingAsyncTasks = new Completer<void>();
      return callback().catchError((dynamic exception, StackTrace stack) {
        FlutterError.reportError(new FlutterErrorDetails(
          exception: exception,
          stack: stack,
          library: 'Flutter test framework',
          context: 'while running async test code',
        ));
        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;
      });
    });
  }

765 766 767 768 769
  @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);
770
    _currentFakeAsync.flushMicrotasks();
771
    handleDrawFrame();
772
    _currentFakeAsync.flushMicrotasks();
773 774
  }

775 776
  @override
  Future<Null> idle() {
777
    final Future<Null> result = super.idle();
778
    _currentFakeAsync.elapse(const Duration());
779 780 781
    return result;
  }

782
  EnginePhase _phase = EnginePhase.sendSemanticsUpdate;
783

784
  // Cloned from RendererBinding.drawFrame() but with early-exit semantics.
785
  @override
786
  void drawFrame() {
787
    assert(inTest);
788 789 790
    try {
      debugBuildingDirtyElements = true;
      buildOwner.buildScope(renderViewElement);
791 792 793 794 795 796 797 798 799 800 801
      if (_phase != EnginePhase.build) {
        assert(renderView != null);
        pipelineOwner.flushLayout();
        if (_phase != EnginePhase.layout) {
          pipelineOwner.flushCompositingBits();
          if (_phase != EnginePhase.compositingBits) {
            pipelineOwner.flushPaint();
            if (_phase != EnginePhase.paint) {
              renderView.compositeFrame(); // this sends the bits to the GPU
              if (_phase != EnginePhase.composite) {
                pipelineOwner.flushSemantics();
802 803
                assert(_phase == EnginePhase.flushSemantics ||
                       _phase == EnginePhase.sendSemanticsUpdate);
804 805 806 807 808
              }
            }
          }
        }
      }
809
      buildOwner.finalizeTree();
810
    } finally {
811 812
      debugBuildingDirtyElements = false;
    }
813 814
  }

815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860
  Duration _timeout;
  Stopwatch _timeoutStopwatch;
  Timer _timeoutTimer;
  Completer<Null> _timeoutCompleter;

  void _checkTimeout(Timer timer) {
    assert(_timeoutTimer == timer);
    if (_timeoutStopwatch.elapsed > _timeout) {
      _timeoutCompleter.completeError(
        new TimeoutException(
          'The test exceeded the timeout. It may have hung.\n'
          'Consider using "addTime" to increase the timeout before expensive operations.',
          _timeout,
        ),
      );
    }
  }

  /// 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.)
  void addTime(Duration duration) {
    assert(_timeout != null, 'addTime can only be called during a test.');
    _timeout += duration;
  }

861
  @override
862 863 864 865
  Future<Null> runTest(Future<Null> testBody(), VoidCallback invariantTester, {
    String description = '',
    Duration timeout = const Duration(seconds: 2),
  }) {
866
    assert(description != null);
867
    assert(!inTest);
868
    assert(_currentFakeAsync == null);
869
    assert(_clock == null);
870 871 872 873 874 875

    _timeout = timeout;
    _timeoutStopwatch = new Stopwatch()..start();
    _timeoutTimer = new Timer.periodic(const Duration(seconds: 1), _checkTimeout);
    _timeoutCompleter = new Completer<Null>();

876 877 878
    final FakeAsync fakeAsync = new FakeAsync();
    _currentFakeAsync = fakeAsync; // reset in postTest
    _clock = fakeAsync.getClock(new DateTime.utc(2015, 1, 1));
879
    Future<Null> testBodyResult;
880 881 882
    fakeAsync.run((FakeAsync localFakeAsync) {
      assert(fakeAsync == _currentFakeAsync);
      assert(fakeAsync == localFakeAsync);
883
      testBodyResult = _runTest(testBody, invariantTester, description, timeout: _timeoutCompleter.future);
884 885
      assert(inTest);
    });
886 887

    return new Future<Null>.microtask(() async {
888 889 890 891 892 893 894 895 896 897
      // 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.
      final Future<Null> resultFuture = testBodyResult.then<Null>((_) {
        // Do nothing.
      });

898
      // Resolve interplay between fake async and real async calls.
899
      fakeAsync.flushMicrotasks();
900 901
      while (_pendingAsyncTasks != null) {
        await _pendingAsyncTasks.future;
902
        fakeAsync.flushMicrotasks();
903
      }
904
      return resultFuture;
905
    });
906 907
  }

908 909
  @override
  void asyncBarrier() {
910 911
    assert(_currentFakeAsync != null);
    _currentFakeAsync.flushMicrotasks();
912 913
    super.asyncBarrier();
  }
914

915 916 917
  @override
  void _verifyInvariants() {
    super._verifyInvariants();
918
    assert(
919
      _currentFakeAsync.periodicTimerCount == 0,
920 921 922
      'A periodic Timer is still running even after the widget tree was disposed.'
    );
    assert(
923
      _currentFakeAsync.nonPeriodicTimerCount == 0,
924 925
      'A Timer is still pending even after the widget tree was disposed.'
    );
926
    assert(_currentFakeAsync.microtaskCount == 0); // Shouldn't be possible.
927 928
  }

929
  @override
930
  void postTest() {
931
    super.postTest();
932
    assert(_currentFakeAsync != null);
933 934
    assert(_clock != null);
    _clock = null;
935
    _currentFakeAsync = null;
936 937 938 939 940
    _timeoutCompleter = null;
    _timeoutTimer.cancel();
    _timeoutTimer = null;
    _timeoutStopwatch = null;
    _timeout = null;
941 942
  }

943
}
944

945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971
/// 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,
972 973 974 975 976 977 978 979 980 981 982 983 984 985

  /// 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
  /// directly (invoking [Window.onBeginFrame] and [Window.onDrawFrame]) to run.
  ///
  /// 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,
986 987
}

988 989 990 991 992 993 994 995
/// 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.
///
996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010
/// 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).
1011 1012 1013 1014 1015 1016
///
/// 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 {
1017 1018 1019 1020 1021 1022
  @override
  void initInstances() {
    super.initInstances();
    assert(!autoUpdateGoldenFiles);
  }

1023 1024 1025 1026
  @override
  bool get inTest => _inTest;
  bool _inTest = false;

1027 1028 1029
  @override
  Clock get clock => const Clock();

1030 1031 1032 1033 1034 1035 1036 1037
  @override
  int get microtaskCount {
    // Unsupported until we have a wrapper around the real async API
    // https://github.com/flutter/flutter/issues/4637
    assert(false);
    return -1;
  }

1038 1039 1040 1041 1042
  @override
  test_package.Timeout get defaultTestTimeout => test_package.Timeout.none;

  Completer<Null> _pendingFrame;
  bool _expectingFrame = false;
1043
  bool _viewNeedsPaint = false;
1044
  bool _runningAsyncTasks = false;
1045 1046 1047 1048 1049 1050 1051 1052

  /// 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]).
  ///
1053 1054 1055 1056 1057 1058 1059 1060
  /// * [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.
1061
  ///
1062 1063 1064
  /// * [LiveTestWidgetsFlutterBindingFramePolicy.fullyLive] allows all frame
  ///   requests from the engine to be serviced, even those the test did not
  ///   explicitly pump.
1065
  ///
1066 1067 1068 1069 1070 1071 1072 1073 1074 1075
  /// * [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".
  ///
1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086
  /// 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]:
1087 1088 1089 1090
  ///
  /// ```dart
  /// TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
  /// if (binding is LiveTestWidgetsFlutterBinding)
1091
  ///   binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
1092
  /// ```
1093
  LiveTestWidgetsFlutterBindingFramePolicy framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fadePointers;
1094

1095 1096 1097 1098 1099 1100 1101
  @override
  void scheduleFrame() {
    if (framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.benchmark)
      return; // In benchmark mode, don't actually schedule any engine frames.
    super.scheduleFrame();
  }

1102 1103 1104 1105 1106 1107 1108
  @override
  void scheduleForcedFrame() {
    if (framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.benchmark)
      return; // In benchmark mode, don't actually schedule any engine frames.
    super.scheduleForcedFrame();
  }

1109 1110
  bool _doDrawThisFrame;

1111 1112
  @override
  void handleBeginFrame(Duration rawTimeStamp) {
1113
    assert(_doDrawThisFrame == null);
1114 1115
    if (_expectingFrame ||
        (framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.fullyLive) ||
1116
        (framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.benchmark) ||
1117 1118
        (framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.fadePointers && _viewNeedsPaint)) {
      _doDrawThisFrame = true;
1119
      super.handleBeginFrame(rawTimeStamp);
1120 1121 1122
    } else {
      _doDrawThisFrame = false;
    }
1123 1124 1125 1126 1127 1128 1129 1130
  }

  @override
  void handleDrawFrame() {
    assert(_doDrawThisFrame != null);
    if (_doDrawThisFrame)
      super.handleDrawFrame();
    _doDrawThisFrame = null;
1131 1132
    _viewNeedsPaint = false;
    if (_expectingFrame) { // set during pump
1133 1134 1135 1136 1137
      assert(_pendingFrame != null);
      _pendingFrame.complete(); // unlocks the test API
      _pendingFrame = null;
      _expectingFrame = false;
    } else {
1138
      assert(framePolicy != LiveTestWidgetsFlutterBindingFramePolicy.benchmark);
1139 1140 1141 1142
      ui.window.scheduleFrame();
    }
  }

1143 1144 1145
  @override
  void initRenderView() {
    assert(renderView == null);
1146 1147 1148 1149
    renderView = new _LiveTestRenderView(
      configuration: createViewConfiguration(),
      onNeedPaint: _handleViewNeedsPaint,
    );
1150 1151 1152 1153 1154 1155
    renderView.scheduleInitialFrame();
  }

  @override
  _LiveTestRenderView get renderView => super.renderView;

1156 1157 1158 1159 1160
  void _handleViewNeedsPaint() {
    _viewNeedsPaint = true;
    renderView.markNeedsPaint();
  }

1161 1162 1163 1164 1165 1166 1167 1168 1169
  /// 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;

1170 1171
  @override
  void dispatchEvent(PointerEvent event, HitTestResult result, {
1172
    TestBindingEventSource source = TestBindingEventSource.device
1173
  }) {
1174 1175 1176 1177 1178 1179 1180 1181 1182 1183
    switch (source) {
      case TestBindingEventSource.test:
        if (!renderView._pointers.containsKey(event.pointer)) {
          assert(event.down);
          renderView._pointers[event.pointer] = new _LiveTestPointerRecord(event.pointer, event.position);
        } else {
          renderView._pointers[event.pointer].position = event.position;
          if (!event.down)
            renderView._pointers[event.pointer].decay = _kPointerDecay;
        }
1184
        _handleViewNeedsPaint();
1185 1186 1187 1188 1189 1190
        super.dispatchEvent(event, result, source: source);
        break;
      case TestBindingEventSource.device:
        if (deviceEventDispatcher != null)
          deviceEventDispatcher.dispatchEvent(event, result);
        break;
1191 1192 1193
    }
  }

1194
  @override
1195 1196
  Future<Null> pump([ Duration duration, EnginePhase newPhase = EnginePhase.sendSemanticsUpdate ]) {
    assert(newPhase == EnginePhase.sendSemanticsUpdate);
1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214
    assert(inTest);
    assert(!_expectingFrame);
    assert(_pendingFrame == null);
    return TestAsyncUtils.guard(() {
      if (duration != null) {
        new Timer(duration, () {
          _expectingFrame = true;
          scheduleFrame();
        });
      } else {
        _expectingFrame = true;
        scheduleFrame();
      }
      _pendingFrame = new Completer<Null>();
      return _pendingFrame.future;
    });
  }

1215
  @override
1216 1217 1218
  Future<T> runAsync<T>(Future<T> callback(), {
    Duration additionalTime = const Duration(milliseconds: 250),
  }) async {
1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245
    assert(() {
      if (!_runningAsyncTasks)
        return true;
      throw new test_package.TestFailure(
          '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.'
      );
    }());

    _runningAsyncTasks = true;
    try {
      return await callback();
    } catch (error, stack) {
      FlutterError.reportError(new FlutterErrorDetails(
        exception: error,
        stack: stack,
        library: 'Flutter test framework',
        context: 'while running async test code',
      ));
      return null;
    } finally {
      _runningAsyncTasks = false;
    }
  }

1246
  @override
1247
  Future<Null> runTest(Future<Null> testBody(), VoidCallback invariantTester, { String description = '' }) async {
1248
    assert(description != null);
1249 1250
    assert(!inTest);
    _inTest = true;
1251 1252
    renderView._setDescription(description);
    return _runTest(testBody, invariantTester, description);
1253 1254
  }

1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269
  @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;
  }

1270 1271 1272 1273 1274 1275 1276 1277 1278 1279
  @override
  void postTest() {
    super.postTest();
    assert(!_expectingFrame);
    assert(_pendingFrame == null);
    _inTest = false;
  }

  @override
  ViewConfiguration createViewConfiguration() {
1280
    return new TestViewConfiguration(size: _surfaceSize ?? _kDefaultTestViewportSize);
1281 1282 1283
  }

  @override
1284
  Offset globalToLocal(Offset point) {
1285 1286
    final Matrix4 transform = renderView.configuration.toHitTestMatrix();
    final double det = transform.invert();
1287
    assert(det != 0.0);
1288
    final Offset result = MatrixUtils.transformPoint(transform, point);
1289 1290 1291 1292
    return result;
  }

  @override
1293
  Offset localToGlobal(Offset point) {
1294
    final Matrix4 transform = renderView.configuration.toHitTestMatrix();
1295 1296
    return MatrixUtils.transformPoint(transform, point);
  }
1297
}
1298

1299 1300
/// A [ViewConfiguration] that pretends the display is of a particular size. The
/// size is in logical pixels. The resulting ViewConfiguration maps the given
1301
/// size onto the actual display using the [BoxFit.contain] algorithm.
1302 1303
class TestViewConfiguration extends ViewConfiguration {
  /// Creates a [TestViewConfiguration] with the given size. Defaults to 800x600.
1304
  TestViewConfiguration({ Size size = _kDefaultTestViewportSize })
1305 1306 1307 1308 1309
    : _paintMatrix = _getMatrix(size, ui.window.devicePixelRatio),
      _hitTestMatrix = _getMatrix(size, 1.0),
      super(size: size);

  static Matrix4 _getMatrix(Size size, double devicePixelRatio) {
1310 1311 1312
    final double inverseRatio = devicePixelRatio / ui.window.devicePixelRatio;
    final double actualWidth = ui.window.physicalSize.width * inverseRatio;
    final double actualHeight = ui.window.physicalSize.height * inverseRatio;
1313 1314
    final double desiredWidth = size.width;
    final double desiredHeight = size.height;
1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327
    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;
    }
    final Matrix4 matrix = new Matrix4.compose(
      new Vector3(shiftX, shiftY, 0.0), // translation
      new Quaternion.identity(), // rotation
1328
      new Vector3(scale, scale, 1.0) // scale
1329
    );
1330
    return matrix;
1331 1332
  }

1333 1334
  final Matrix4 _paintMatrix;
  final Matrix4 _hitTestMatrix;
1335 1336

  @override
1337
  Matrix4 toMatrix() => _paintMatrix.clone();
1338

1339 1340 1341
  /// Provides the transformation matrix that converts coordinates in the test
  /// coordinate space to coordinates in logical pixels on the real display.
  ///
1342
  /// This is essentially the same as [toMatrix] but ignoring the device pixel
1343 1344 1345 1346 1347
  /// 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();
1348 1349 1350 1351 1352

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

1353 1354 1355 1356
const int _kPointerDecay = -2;

class _LiveTestPointerRecord {
  _LiveTestPointerRecord(
1357
    this.pointer,
1358
    this.position
1359
  ) : color = new HSVColor.fromAHSV(0.8, (35.0 * pointer) % 360.0, 1.0, 1.0).toColor(),
1360 1361 1362
      decay = 1;
  final int pointer;
  final Color color;
1363
  Offset position;
1364 1365 1366 1367 1368
  int decay; // >0 means down, <0 means up, increases by one each time, removed at 0
}

class _LiveTestRenderView extends RenderView {
  _LiveTestRenderView({
1369 1370
    ViewConfiguration configuration,
    this.onNeedPaint,
1371 1372
  }) : super(configuration: configuration);

1373
  @override
1374 1375
  TestViewConfiguration get configuration => super.configuration;
  @override
1376
  set configuration(covariant TestViewConfiguration value) { super.configuration = value; }
1377

1378 1379
  final VoidCallback onNeedPaint;

1380 1381
  final Map<int, _LiveTestPointerRecord> _pointers = <int, _LiveTestPointerRecord>{};

1382
  TextPainter _label;
1383
  static const TextStyle _labelStyle = TextStyle(
1384 1385 1386 1387 1388 1389 1390 1391 1392
    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
1393 1394
    // TODO(ianh): Figure out if the test name is actually RTL.
    _label ??= new TextPainter(textAlign: TextAlign.left, textDirection: TextDirection.ltr);
1395 1396 1397 1398 1399 1400
    _label.text = new TextSpan(text: value, style: _labelStyle);
    _label.layout();
    if (onNeedPaint != null)
      onNeedPaint();
  }

1401
  @override
1402
  bool hitTest(HitTestResult result, { Offset position }) {
1403 1404
    final Matrix4 transform = configuration.toHitTestMatrix();
    final double det = transform.invert();
1405 1406 1407 1408 1409
    assert(det != 0.0);
    position = MatrixUtils.transformPoint(transform, position);
    return super.hitTest(result, position: position);
  }

1410 1411 1412 1413 1414 1415 1416
  @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;
      final Path path = new Path()
1417
        ..addOval(new Rect.fromCircle(center: Offset.zero, radius: radius))
1418 1419 1420 1421 1422 1423 1424 1425 1426 1427
        ..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;
      final Paint paint = new Paint()
        ..strokeWidth = radius / 10.0
        ..style = PaintingStyle.stroke;
      bool dirty = false;
      for (int pointer in _pointers.keys) {
1428
        final _LiveTestPointerRecord record = _pointers[pointer];
1429
        paint.color = record.color.withOpacity(record.decay < 0 ? (record.decay / (_kPointerDecay - 1)) : 1.0);
1430
        canvas.drawPath(path.shift(record.position), paint);
1431 1432 1433 1434 1435 1436 1437 1438
        if (record.decay < 0)
          dirty = true;
        record.decay += 1;
      }
      _pointers
        .keys
        .where((int pointer) => _pointers[pointer].decay == 0)
        .toList()
1439
        .forEach(_pointers.remove);
1440 1441
      if (dirty && onNeedPaint != null)
        scheduleMicrotask(onNeedPaint);
1442
    }
1443
    _label?.paint(context.canvas, offset - const Offset(0.0, 10.0));
1444 1445 1446
  }
}

1447 1448 1449 1450 1451 1452 1453
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;
}
1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470

/// Provides a default [HttpClient] which always returns empty 400 responses.
///
/// If another [HttpClient] is provided using [HttpOverrides.runZoned], that will
/// take precedence over this provider.
class _MockHttpOverrides extends HttpOverrides {
  @override
  HttpClient createHttpClient(SecurityContext _) {
    return new _MockHttpClient();
  }
}

/// A mocked [HttpClient] which always returns a [_MockHttpRequest].
class _MockHttpClient implements HttpClient {
  @override
  bool autoUncompress;

1471 1472 1473
  @override
  Duration connectionTimeout;

1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498
  @override
  Duration idleTimeout;

  @override
  int maxConnectionsPerHost;

  @override
  String userAgent;

  @override
  void addCredentials(Uri url, String realm, HttpClientCredentials credentials) {}

  @override
  void addProxyCredentials(String host, int port, String realm, HttpClientCredentials credentials) {}

  @override
  set authenticate(Future<bool> Function(Uri url, String scheme, String realm) f) {}

  @override
  set authenticateProxy(Future<bool> Function(String host, int port, String scheme, String realm) f) {}

  @override
  set badCertificateCallback(bool Function(X509Certificate cert, String host, int port) callback) {}

  @override
1499
  void close({bool force = false}) {}
1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708

  @override
  Future<HttpClientRequest> delete(String host, int port, String path) {
    return new Future<HttpClientRequest>.value(new _MockHttpRequest());
  }

  @override
  Future<HttpClientRequest> deleteUrl(Uri url) {
    return new Future<HttpClientRequest>.value(new _MockHttpRequest());
  }

  @override
  set findProxy(String Function(Uri url) f) {}

  @override
  Future<HttpClientRequest> get(String host, int port, String path) {
    return new Future<HttpClientRequest>.value(new _MockHttpRequest());
  }

  @override
  Future<HttpClientRequest> getUrl(Uri url) {
    return new Future<HttpClientRequest>.value(new _MockHttpRequest());
  }

  @override
  Future<HttpClientRequest> head(String host, int port, String path) {
    return new Future<HttpClientRequest>.value(new _MockHttpRequest());
  }

  @override
  Future<HttpClientRequest> headUrl(Uri url) {
    return new Future<HttpClientRequest>.value(new _MockHttpRequest());
  }

  @override
  Future<HttpClientRequest> open(String method, String host, int port, String path) {
    return new Future<HttpClientRequest>.value(new _MockHttpRequest());
  }

  @override
  Future<HttpClientRequest> openUrl(String method, Uri url) {
    return new Future<HttpClientRequest>.value(new _MockHttpRequest());
  }

  @override
  Future<HttpClientRequest> patch(String host, int port, String path) {
    return new Future<HttpClientRequest>.value(new _MockHttpRequest());
  }

  @override
  Future<HttpClientRequest> patchUrl(Uri url) {
    return new Future<HttpClientRequest>.value(new _MockHttpRequest());
  }

  @override
  Future<HttpClientRequest> post(String host, int port, String path) {
    return new Future<HttpClientRequest>.value(new _MockHttpRequest());
  }

  @override
  Future<HttpClientRequest> postUrl(Uri url) {
    return new Future<HttpClientRequest>.value(new _MockHttpRequest());
  }

  @override
  Future<HttpClientRequest> put(String host, int port, String path) {
    return new Future<HttpClientRequest>.value(new _MockHttpRequest());
  }

  @override
  Future<HttpClientRequest> putUrl(Uri url) {
    return new Future<HttpClientRequest>.value(new _MockHttpRequest());
  }
}

/// A mocked [HttpClientRequest] which always returns a [_MockHttpClientResponse].
class _MockHttpRequest extends HttpClientRequest {
  @override
  Encoding encoding;

  @override
  final HttpHeaders headers = new _MockHttpHeaders();

  @override
  void add(List<int> data) {}

  @override
  void addError(Object error, [StackTrace stackTrace]) {}

  @override
  Future<Null> addStream(Stream<List<int>> stream) {
    return new Future<Null>.value(null);
  }

  @override
  Future<HttpClientResponse> close() {
    return new Future<HttpClientResponse>.value(new _MockHttpResponse());
  }

  @override
  HttpConnectionInfo get connectionInfo => null;

  @override
  List<Cookie> get cookies => null;

  @override
  Future<HttpClientResponse> get done => null;

  @override
  Future<Null> flush() {
    return new Future<Null>.value(null);
  }

  @override
  String get method => null;

  @override
  Uri get uri => null;

  @override
  void write(Object obj) {}

  @override
  void writeAll(Iterable<Object> objects, [String separator = '']) {}

  @override
  void writeCharCode(int charCode) {}

  @override
  void writeln([Object obj = '']) {}
}

/// A mocked [HttpClientResponse] which is empty and has a [statusCode] of 400.
class _MockHttpResponse extends Stream<List<int>> implements HttpClientResponse {
  @override
  final HttpHeaders headers = new _MockHttpHeaders();

  @override
  X509Certificate get certificate => null;

  @override
  HttpConnectionInfo get connectionInfo => null;

  @override
  int get contentLength => -1;

  @override
  List<Cookie> get cookies => null;

  @override
  Future<Socket> detachSocket() {
    return new Future<Socket>.error(new UnsupportedError('Mocked response'));
  }

  @override
  bool get isRedirect => false;

  @override
  StreamSubscription<List<int>> listen(void Function(List<int> event) onData, {Function onError, void Function() onDone, bool cancelOnError}) {
    return const Stream<List<int>>.empty().listen(onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError);
  }

  @override
  bool get persistentConnection => null;

  @override
  String get reasonPhrase => null;

  @override
  Future<HttpClientResponse> redirect([String method, Uri url, bool followLoops]) {
    return new Future<HttpClientResponse>.error(new UnsupportedError('Mocked response'));
  }

  @override
  List<RedirectInfo> get redirects => <RedirectInfo>[];

  @override
  int get statusCode => 400;
}

/// A mocked [HttpHeaders] that ignores all writes.
class _MockHttpHeaders extends HttpHeaders {
  @override
  List<String> operator [](String name) => <String>[];

  @override
  void add(String name, Object value) {}

  @override
  void clear() {}

  @override
  void forEach(void Function(String name, List<String> values) f) {}

  @override
  void noFolding(String name) {}

  @override
  void remove(String name, Object value) {}

  @override
  void removeAll(String name) {}

  @override
  void set(String name, Object value) {}

  @override
  String value(String name) => null;
}