binding.dart 44.6 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 7
import 'dart:io';
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 16
import 'package:http/http.dart' as http;
import 'package:http/testing.dart' as http;
17 18
import 'package:quiver/testing/async.dart';
import 'package:quiver/time.dart';
19
import 'package:test/test.dart' as test_package;
20
import 'package:stack_trace/stack_trace.dart' as stack_trace;
21 22 23
import 'package:vector_math/vector_math_64.dart';

import 'stack_manipulation.dart';
24
import 'test_async_utils.dart';
25
import 'test_text_input.dart';
26

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

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

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

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

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

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

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

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

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

/// 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,
89 90
       ServicesBinding,
       PaintingBinding,
91
       WidgetsBinding {
92

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

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

111 112 113 114 115
  /// 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.
116 117 118
  @protected
  bool get checkIntrinsicSizes => false;

119
  /// Creates and initializes the binding. This function is
120 121
  /// idempotent; calling it a second time will just return the
  /// previously-created instance.
122 123 124 125 126 127
  ///
  /// 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`.)
128
  static WidgetsBinding ensureInitialized() {
129 130
    if (WidgetsBinding.instance == null) {
      if (Platform.environment.containsKey('FLUTTER_TEST')) {
131
        new AutomatedTestWidgetsFlutterBinding();
132
      } else {
133
        new LiveTestWidgetsFlutterBinding();
134 135
      }
    }
136
    assert(WidgetsBinding.instance is TestWidgetsFlutterBinding);
137
    return WidgetsBinding.instance;
138 139
  }

140 141 142
  @override
  void initInstances() {
    timeDilation = 1.0; // just in case the developer has artificially changed it for development
143
    createHttpClient = () {
144
      return new http.MockClient((http.BaseRequest request) {
145
        return new Future<http.Response>.value(
146
          new http.Response('Mocked: Unavailable.', 404, request: request)
147 148 149
        );
      });
    };
150
    _testTextInput = new TestTextInput()..register();
151 152 153
    super.initInstances();
  }

154 155 156 157 158 159
  @override
  void initLicenses() {
    // Do not include any licenses, because we're a test, and the LICENSE file
    // doesn't get generated for tests.
  }

160
  /// Whether there is currently a test executing.
161
  bool get inTest;
162

163 164 165
  /// The number of outstanding microtasks in the queue.
  int get microtaskCount;

166 167
  /// The default test timeout for tests when using this binding.
  test_package.Timeout get defaultTestTimeout;
168

169 170 171 172 173 174 175 176 177 178
  /// 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;

179 180 181 182 183 184 185 186
  /// 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.
187 188 189
  ///
  /// See also [LiveTestWidgetsFlutterBindingFramePolicy], which affects how
  /// this method works when the test is run with `flutter run`.
190
  Future<Null> pump([ Duration duration, EnginePhase newPhase = EnginePhase.sendSemanticsUpdate ]);
191 192 193

  /// Artificially calls dispatchLocaleChanged on the Widget binding,
  /// then flushes microtasks.
194 195 196
  Future<Null> setLocale(String languageCode, String countryCode) {
    return TestAsyncUtils.guard(() async {
      assert(inTest);
197
      final Locale locale = new Locale(languageCode, countryCode);
198 199 200 201 202 203 204 205
      dispatchLocaleChanged(locale);
      return null;
    });
  }

  /// Acts as if the application went idle.
  ///
  /// Runs all remaining microtasks, including those scheduled as a result of
206 207
  /// running them, until there are no more microtasks scheduled. Then, runs any
  /// previously scheduled timers with zero time, and completes the returned future.
208
  ///
209 210 211
  /// 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.
212
  Future<Null> idle() {
213 214 215 216 217 218 219
    return TestAsyncUtils.guard(() {
      final Completer<Null> completer = new Completer<Null>();
      Timer.run(() {
        completer.complete(null);
      });
      return completer.future;
    });
220 221
  }

222
  /// Convert the given point from the global coordinate system (as used by
223 224
  /// pointer events from the device) to the coordinate system used by the
  /// tests (an 800 by 600 window).
225
  Offset globalToLocal(Offset point) => point;
226 227

  /// Convert the given point from the coordinate system used by the tests (an
228
  /// 800 by 600 window) to the global coordinate system (as used by pointer
229
  /// events from the device).
230
  Offset localToGlobal(Offset point) => point;
231

232 233 234 235 236 237 238 239
  @override
  void dispatchEvent(PointerEvent event, HitTestResult result, {
    TestBindingEventSource source: TestBindingEventSource.device
  }) {
    assert(source == TestBindingEventSource.test);
    super.dispatchEvent(event, result);
  }

240 241 242 243 244 245 246 247 248 249
  /// 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.
  EditableTextState get focusedEditable => _focusedEditable;
  EditableTextState _focusedEditable;
250
  set focusedEditable(EditableTextState value) {
251
    _focusedEditable = value..requestKeyboard();
252 253
  }

254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
  /// 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() {
269
    assert(inTest);
270
    final dynamic result = _pendingExceptionDetails?.exception;
271
    _pendingExceptionDetails = null;
272 273
    return result;
  }
274 275
  FlutterExceptionHandler _oldExceptionHandler;
  FlutterErrorDetails _pendingExceptionDetails;
276

277 278
  static const TextStyle _kMessageStyle = const TextStyle(
    color: const Color(0xFF917FFF),
Ian Hickson's avatar
Ian Hickson committed
279
    fontSize: 40.0,
280 281
  );

282
  static const Widget _kPreTestMessage = const Center(
283
    child: const Text(
284
      'Test starting...',
Ian Hickson's avatar
Ian Hickson committed
285 286
      style: _kMessageStyle,
      textDirection: TextDirection.ltr,
287 288 289
    )
  );

290
  static const Widget _kPostTestMessage = const Center(
291
    child: const Text(
292
      'Test finished.',
Ian Hickson's avatar
Ian Hickson committed
293 294
      style: _kMessageStyle,
      textDirection: TextDirection.ltr,
295 296 297 298 299 300 301
    )
  );

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

302
  /// Call the testBody inside a [FakeAsync] scope on which [pump] can
303 304 305 306 307 308
  /// advance time.
  ///
  /// Returns a future which completes when the test has run.
  ///
  /// Called by the [testWidgets] and [benchmarkWidgets] functions to
  /// run a test.
309 310 311
  ///
  /// The `invariantTester` argument is called after the `testBody`'s [Future]
  /// completes. If it throws, then the test is marked as failed.
312 313 314 315 316
  ///
  /// 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.
  Future<Null> runTest(Future<Null> testBody(), VoidCallback invariantTester, { String description: '' });
317 318 319 320 321 322 323 324 325 326 327 328

  /// 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;
  Completer<Null> _currentTestCompleter;
329
  String _currentTestDescription; // set from _runTest to _testCompletionHandler
330 331 332 333 334 335 336

  void _testCompletionHandler() {
    // This can get called twice, in the case of a Future without listeners failing, and then
    // our main future completing.
    assert(Zone.current == _parentZone);
    assert(_currentTestCompleter != null);
    if (_pendingExceptionDetails != null) {
337
      debugPrint = debugPrintOverride; // just in case the test overrides it -- otherwise we won't see the error!
338 339 340 341 342 343
      FlutterError.dumpErrorToConsole(_pendingExceptionDetails, forceReport: true);
      // test_package.registerException actually just calls the current zone's error handler (that
      // is to say, _parentZone's handleUncaughtError function). FakeAsync doesn't add one of those,
      // but the test package does, that's how the test package tracks errors. So really we could
      // get the same effect here by calling that error handler directly or indeed just throwing.
      // However, we call registerException because that's the semantically correct thing...
344 345 346
      String additional = '';
      if (_currentTestDescription != '')
        additional = '\nThe test description was: $_currentTestDescription';
347
      test_package.registerException('Test failed. See exception logs above.$additional', _emptyStackTrace);
348 349
      _pendingExceptionDetails = null;
    }
350
    _currentTestDescription = null;
351 352 353 354
    if (!_currentTestCompleter.isCompleted)
      _currentTestCompleter.complete(null);
  }

355 356 357 358 359 360 361 362 363 364 365 366
  /// 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.
  }

367 368
  Future<Null> _runTest(Future<Null> testBody(), VoidCallback invariantTester, String description) {
    assert(description != null);
369 370
    assert(_currentTestDescription == null);
    _currentTestDescription = description; // cleared by _testCompletionHandler
371 372 373
    assert(inTest);
    _oldExceptionHandler = FlutterError.onError;
    int _exceptionCount = 0; // number of un-taken exceptions
374
    FlutterError.onError = (FlutterErrorDetails details) {
375
      if (_pendingExceptionDetails != null) {
376
        debugPrint = debugPrintOverride; // just in case the test overrides it -- otherwise we won't see the errors!
377 378
        if (_exceptionCount == 0) {
          _exceptionCount = 2;
379
          FlutterError.dumpErrorToConsole(_pendingExceptionDetails, forceReport: true);
380 381
        } else {
          _exceptionCount += 1;
382
        }
383
        FlutterError.dumpErrorToConsole(details, forceReport: true);
384
        _pendingExceptionDetails = new FlutterErrorDetails(
385 386 387 388
          exception: 'Multiple exceptions ($_exceptionCount) were detected during the running of the current test, and at least one was unexpected.',
          library: 'Flutter test framework'
        );
      } else {
389
        reportExceptionNoticed(details); // mostly this is just a hook for the LiveTestWidgetsFlutterBinding
390
        _pendingExceptionDetails = details;
391
      }
392
    };
393
    _currentTestCompleter = new Completer<Null>();
394
    final ZoneSpecification errorHandlingZoneSpecification = new ZoneSpecification(
395 396 397 398 399 400 401 402
      handleUncaughtError: (Zone self, ZoneDelegate parent, Zone zone, dynamic exception, StackTrace stack) {
        if (_currentTestCompleter.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.
403
          debugPrint = debugPrintOverride; // just in case the test overrides it -- otherwise we won't see the error!
404 405
          FlutterError.dumpErrorToConsole(new FlutterErrorDetails(
            exception: exception,
406
            stack: _unmangle(stack),
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436
            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.
437 438 439 440 441 442
        String treeDump;
        try {
          treeDump = renderViewElement?.toStringDeep() ?? '<no tree>';
        } catch (exception) {
          treeDump = '<additional error caught while dumping tree: $exception>';
        }
443 444 445 446
        final StringBuffer expectLine = new StringBuffer();
        final int stackLinesToOmit = reportExpectCall(stack, expectLine);
        FlutterError.reportError(new FlutterErrorDetails(
          exception: exception,
447
          stack: _unmangle(stack),
448 449
          context: 'running a test',
          library: 'Flutter test framework',
450
          stackFilter: (Iterable<String> frames) {
451 452 453 454 455 456 457 458 459
            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# ")}');
            }
460 461
            if (description.isNotEmpty)
              information.writeln('The test description was:\n$description');
462 463 464
          }
        ));
        assert(_parentZone != null);
465
        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.');
466
        _parentZone.run<void>(_testCompletionHandler);
467 468 469
      }
    );
    _parentZone = Zone.current;
470
    final Zone testZone = _parentZone.fork(specification: errorHandlingZoneSpecification);
471
    testZone.runBinary(_runTestBody, testBody, invariantTester)
472 473 474
      .whenComplete(_testCompletionHandler);
    asyncBarrier(); // When using AutomatedTestWidgetsFlutterBinding, this flushes the microtasks.
    return _currentTestCompleter.future;
475 476
  }

477
  Future<Null> _runTestBody(Future<Null> testBody(), VoidCallback invariantTester) async {
478 479 480 481 482 483
    assert(inTest);

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

    // run the test
484
    await testBody();
485 486 487 488 489 490 491 492
    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.
      runApp(new Container(key: new UniqueKey(), child: _kPostTestMessage)); // Unmount any remaining widgets.
      await pump();
493
      invariantTester();
494 495 496 497 498 499 500 501 502 503 504
      _verifyInvariants();
    }

    assert(inTest);
    return null;
  }

  void _verifyInvariants() {
    assert(debugAssertNoTransientCallbacks(
      'An animation is still running even after the widget tree was disposed.'
    ));
505 506 507 508
    assert(debugAssertAllFoundationVarsUnset(
      'The value of a foundation debug variable was changed by the test.',
      debugPrintOverride: debugPrintOverride,
    ));
509 510 511
    assert(debugAssertAllGesturesVarsUnset(
      'The value of a gestures debug variable was changed by the test.',
    ));
512
    assert(debugAssertAllRenderVarsUnset(
513 514
      'The value of a rendering debug variable was changed by the test.',
      debugCheckIntrinsicSizesOverride: checkIntrinsicSizes,
515 516
    ));
    assert(debugAssertAllWidgetVarsUnset(
517
      'The value of a widget debug variable was changed by the test.',
518 519
    ));
    assert(debugAssertAllSchedulerVarsUnset(
520
      'The value of a scheduler debug variable was changed by the test.',
521
    ));
522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538
  }

  /// Called by the [testWidgets] function after a test is executed.
  void postTest() {
    assert(inTest);
    FlutterError.onError = _oldExceptionHandler;
    _pendingExceptionDetails = null;
    _currentTestCompleter = 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.
539 540 541
///
/// This class assumes it is always run in checked mode (since tests are always
/// run in checked mode).
542 543 544 545 546
class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
  @override
  void initInstances() {
    super.initInstances();
    ui.window.onBeginFrame = null;
547
    ui.window.onDrawFrame = null;
548 549 550
  }

  FakeAsync _fakeAsync;
551 552 553

  @override
  Clock get clock => _clock;
554 555
  Clock _clock;

556 557 558
  @override
  DebugPrintCallback get debugPrintOverride => debugPrintSynchronously;

559 560 561
  @override
  bool get checkIntrinsicSizes => true;

562 563 564 565 566 567
  @override
  test_package.Timeout get defaultTestTimeout => const test_package.Timeout(const Duration(seconds: 5));

  @override
  bool get inTest => _fakeAsync != null;

568 569 570
  @override
  int get microtaskCount => _fakeAsync.microtaskCount;

571
  @override
572
  Future<Null> pump([ Duration duration, EnginePhase newPhase = EnginePhase.sendSemanticsUpdate ]) {
573 574 575 576 577 578 579
    return TestAsyncUtils.guard(() {
      assert(inTest);
      assert(_clock != null);
      if (duration != null)
        _fakeAsync.elapse(duration);
      _phase = newPhase;
      if (hasScheduledFrame) {
580
        _fakeAsync.flushMicrotasks();
581
        handleBeginFrame(new Duration(
582
          milliseconds: _clock.now().millisecondsSinceEpoch,
583
        ));
584 585
        _fakeAsync.flushMicrotasks();
        handleDrawFrame();
586 587 588 589 590 591
      }
      _fakeAsync.flushMicrotasks();
      return new Future<Null>.value();
    });
  }

592 593 594 595 596 597 598
  @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);
    _fakeAsync.flushMicrotasks();
    handleDrawFrame();
599
    _fakeAsync.flushMicrotasks();
600 601
  }

602 603
  @override
  Future<Null> idle() {
604
    final Future<Null> result = super.idle();
605
    _fakeAsync.elapse(const Duration());
606 607 608
    return result;
  }

609
  EnginePhase _phase = EnginePhase.sendSemanticsUpdate;
610

611
  // Cloned from RendererBinding.drawFrame() but with early-exit semantics.
612
  @override
613
  void drawFrame() {
614
    assert(inTest);
615 616 617
    try {
      debugBuildingDirtyElements = true;
      buildOwner.buildScope(renderViewElement);
618 619 620 621 622 623 624 625 626 627 628
      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();
629 630
                assert(_phase == EnginePhase.flushSemantics ||
                       _phase == EnginePhase.sendSemanticsUpdate);
631 632 633 634 635
              }
            }
          }
        }
      }
636
      buildOwner.finalizeTree();
637
    } finally {
638 639
      debugBuildingDirtyElements = false;
    }
640 641 642
  }

  @override
643 644
  Future<Null> runTest(Future<Null> testBody(), VoidCallback invariantTester, { String description: '' }) {
    assert(description != null);
645 646 647 648 649
    assert(!inTest);
    assert(_fakeAsync == null);
    assert(_clock == null);
    _fakeAsync = new FakeAsync();
    _clock = _fakeAsync.getClock(new DateTime.utc(2015, 1, 1));
650
    Future<Null> testBodyResult;
651 652
    _fakeAsync.run((FakeAsync fakeAsync) {
      assert(fakeAsync == _fakeAsync);
653
      testBodyResult = _runTest(testBody, invariantTester, description);
654 655
      assert(inTest);
    });
656
    // testBodyResult is a Future that was created in the Zone of the fakeAsync.
657 658 659 660
    // This means that if we call .then() on it (as the test framework is about to),
    // it will register a microtask to handle the future _in the fake async zone_.
    // To avoid this, we wrap it in a Future that we've created _outside_ the fake
    // async zone.
661
    return new Future<Null>.value(testBodyResult);
662 663
  }

664 665 666 667 668 669
  @override
  void asyncBarrier() {
    assert(_fakeAsync != null);
    _fakeAsync.flushMicrotasks();
    super.asyncBarrier();
  }
670

671 672 673
  @override
  void _verifyInvariants() {
    super._verifyInvariants();
674 675 676 677 678 679 680 681
    assert(
      _fakeAsync.periodicTimerCount == 0,
      'A periodic Timer is still running even after the widget tree was disposed.'
    );
    assert(
      _fakeAsync.nonPeriodicTimerCount == 0,
      'A Timer is still pending even after the widget tree was disposed.'
    );
682
    assert(_fakeAsync.microtaskCount == 0); // Shouldn't be possible.
683 684
  }

685
  @override
686
  void postTest() {
687
    super.postTest();
688 689 690 691 692 693
    assert(_fakeAsync != null);
    assert(_clock != null);
    _clock = null;
    _fakeAsync = null;
  }

694
}
695

696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722
/// 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,
723 724 725 726 727 728 729 730 731 732 733 734 735 736

  /// 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,
737 738
}

739 740 741 742 743 744 745 746
/// 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.
///
747 748 749 750 751 752 753 754 755 756 757 758 759 760 761
/// 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).
762 763 764 765 766 767 768 769 770 771
///
/// 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;

772 773 774
  @override
  Clock get clock => const Clock();

775 776 777 778 779 780 781 782
  @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;
  }

783 784 785 786 787
  @override
  test_package.Timeout get defaultTestTimeout => test_package.Timeout.none;

  Completer<Null> _pendingFrame;
  bool _expectingFrame = false;
788
  bool _viewNeedsPaint = false;
789 790 791 792 793 794 795 796

  /// 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]).
  ///
797 798 799 800 801 802 803 804
  /// * [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.
805
  ///
806 807 808
  /// * [LiveTestWidgetsFlutterBindingFramePolicy.fullyLive] allows all frame
  ///   requests from the engine to be serviced, even those the test did not
  ///   explicitly pump.
809
  ///
810 811 812 813 814 815 816 817 818 819
  /// * [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".
  ///
820 821 822 823 824 825 826 827 828 829 830
  /// 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]:
831 832 833 834
  ///
  /// ```dart
  /// TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
  /// if (binding is LiveTestWidgetsFlutterBinding)
835
  ///   binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
836
  /// ```
837
  LiveTestWidgetsFlutterBindingFramePolicy framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fadePointers;
838

839 840 841 842 843 844 845
  @override
  void scheduleFrame() {
    if (framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.benchmark)
      return; // In benchmark mode, don't actually schedule any engine frames.
    super.scheduleFrame();
  }

846 847 848 849 850 851 852
  @override
  void scheduleForcedFrame() {
    if (framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.benchmark)
      return; // In benchmark mode, don't actually schedule any engine frames.
    super.scheduleForcedFrame();
  }

853 854
  bool _doDrawThisFrame;

855 856
  @override
  void handleBeginFrame(Duration rawTimeStamp) {
857
    assert(_doDrawThisFrame == null);
858 859
    if (_expectingFrame ||
        (framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.fullyLive) ||
860
        (framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.benchmark) ||
861 862
        (framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.fadePointers && _viewNeedsPaint)) {
      _doDrawThisFrame = true;
863
      super.handleBeginFrame(rawTimeStamp);
864 865 866
    } else {
      _doDrawThisFrame = false;
    }
867 868 869 870 871 872 873 874
  }

  @override
  void handleDrawFrame() {
    assert(_doDrawThisFrame != null);
    if (_doDrawThisFrame)
      super.handleDrawFrame();
    _doDrawThisFrame = null;
875 876
    _viewNeedsPaint = false;
    if (_expectingFrame) { // set during pump
877 878 879 880 881
      assert(_pendingFrame != null);
      _pendingFrame.complete(); // unlocks the test API
      _pendingFrame = null;
      _expectingFrame = false;
    } else {
882
      assert(framePolicy != LiveTestWidgetsFlutterBindingFramePolicy.benchmark);
883 884 885 886
      ui.window.scheduleFrame();
    }
  }

887 888 889
  @override
  void initRenderView() {
    assert(renderView == null);
890 891 892 893
    renderView = new _LiveTestRenderView(
      configuration: createViewConfiguration(),
      onNeedPaint: _handleViewNeedsPaint,
    );
894 895 896 897 898 899
    renderView.scheduleInitialFrame();
  }

  @override
  _LiveTestRenderView get renderView => super.renderView;

900 901 902 903 904
  void _handleViewNeedsPaint() {
    _viewNeedsPaint = true;
    renderView.markNeedsPaint();
  }

905 906 907 908 909 910 911 912 913
  /// 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;

914 915 916 917
  @override
  void dispatchEvent(PointerEvent event, HitTestResult result, {
    TestBindingEventSource source: TestBindingEventSource.device
  }) {
918 919 920 921 922 923 924 925 926 927
    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;
        }
928
        _handleViewNeedsPaint();
929 930 931 932 933 934
        super.dispatchEvent(event, result, source: source);
        break;
      case TestBindingEventSource.device:
        if (deviceEventDispatcher != null)
          deviceEventDispatcher.dispatchEvent(event, result);
        break;
935 936 937
    }
  }

938
  @override
939 940
  Future<Null> pump([ Duration duration, EnginePhase newPhase = EnginePhase.sendSemanticsUpdate ]) {
    assert(newPhase == EnginePhase.sendSemanticsUpdate);
941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959
    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;
    });
  }

  @override
960 961
  Future<Null> runTest(Future<Null> testBody(), VoidCallback invariantTester, { String description: '' }) async {
    assert(description != null);
962 963
    assert(!inTest);
    _inTest = true;
964 965
    renderView._setDescription(description);
    return _runTest(testBody, invariantTester, description);
966 967
  }

968 969 970 971 972 973 974 975 976 977 978 979 980 981 982
  @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;
  }

983 984 985 986 987 988 989 990 991 992
  @override
  void postTest() {
    super.postTest();
    assert(!_expectingFrame);
    assert(_pendingFrame == null);
    _inTest = false;
  }

  @override
  ViewConfiguration createViewConfiguration() {
993
    return new TestViewConfiguration();
994 995 996
  }

  @override
997
  Offset globalToLocal(Offset point) {
998 999
    final Matrix4 transform = renderView.configuration.toHitTestMatrix();
    final double det = transform.invert();
1000
    assert(det != 0.0);
1001
    final Offset result = MatrixUtils.transformPoint(transform, point);
1002 1003 1004 1005
    return result;
  }

  @override
1006
  Offset localToGlobal(Offset point) {
1007
    final Matrix4 transform = renderView.configuration.toHitTestMatrix();
1008 1009
    return MatrixUtils.transformPoint(transform, point);
  }
1010
}
1011

1012 1013
/// A [ViewConfiguration] that pretends the display is of a particular size. The
/// size is in logical pixels. The resulting ViewConfiguration maps the given
1014
/// size onto the actual display using the [BoxFit.contain] algorithm.
1015 1016 1017 1018 1019 1020 1021 1022
class TestViewConfiguration extends ViewConfiguration {
  /// Creates a [TestViewConfiguration] with the given size. Defaults to 800x600.
  TestViewConfiguration({ Size size: _kDefaultTestViewportSize })
    : _paintMatrix = _getMatrix(size, ui.window.devicePixelRatio),
      _hitTestMatrix = _getMatrix(size, 1.0),
      super(size: size);

  static Matrix4 _getMatrix(Size size, double devicePixelRatio) {
1023 1024 1025
    final double inverseRatio = devicePixelRatio / ui.window.devicePixelRatio;
    final double actualWidth = ui.window.physicalSize.width * inverseRatio;
    final double actualHeight = ui.window.physicalSize.height * inverseRatio;
1026 1027
    final double desiredWidth = size.width;
    final double desiredHeight = size.height;
1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040
    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
1041
      new Vector3(scale, scale, 1.0) // scale
1042
    );
1043
    return matrix;
1044 1045
  }

1046 1047
  final Matrix4 _paintMatrix;
  final Matrix4 _hitTestMatrix;
1048 1049

  @override
1050
  Matrix4 toMatrix() => _paintMatrix.clone();
1051

1052 1053 1054
  /// Provides the transformation matrix that converts coordinates in the test
  /// coordinate space to coordinates in logical pixels on the real display.
  ///
1055
  /// This is essentially the same as [toMatrix] but ignoring the device pixel
1056 1057 1058 1059 1060
  /// 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();
1061 1062 1063 1064 1065

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

1066 1067 1068 1069
const int _kPointerDecay = -2;

class _LiveTestPointerRecord {
  _LiveTestPointerRecord(
1070
    this.pointer,
1071
    this.position
1072
  ) : color = new HSVColor.fromAHSV(0.8, (35.0 * pointer) % 360.0, 1.0, 1.0).toColor(),
1073 1074 1075
      decay = 1;
  final int pointer;
  final Color color;
1076
  Offset position;
1077 1078 1079 1080 1081
  int decay; // >0 means down, <0 means up, increases by one each time, removed at 0
}

class _LiveTestRenderView extends RenderView {
  _LiveTestRenderView({
1082 1083
    ViewConfiguration configuration,
    this.onNeedPaint,
1084 1085
  }) : super(configuration: configuration);

1086
  @override
1087 1088
  TestViewConfiguration get configuration => super.configuration;
  @override
1089
  set configuration(covariant TestViewConfiguration value) { super.configuration = value; }
1090

1091 1092
  final VoidCallback onNeedPaint;

1093 1094
  final Map<int, _LiveTestPointerRecord> _pointers = <int, _LiveTestPointerRecord>{};

1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105
  TextPainter _label;
  static const TextStyle _labelStyle = const TextStyle(
    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
1106 1107
    // TODO(ianh): Figure out if the test name is actually RTL.
    _label ??= new TextPainter(textAlign: TextAlign.left, textDirection: TextDirection.ltr);
1108 1109 1110 1111 1112 1113
    _label.text = new TextSpan(text: value, style: _labelStyle);
    _label.layout();
    if (onNeedPaint != null)
      onNeedPaint();
  }

1114
  @override
1115
  bool hitTest(HitTestResult result, { Offset position }) {
1116 1117
    final Matrix4 transform = configuration.toHitTestMatrix();
    final double det = transform.invert();
1118 1119 1120 1121 1122
    assert(det != 0.0);
    position = MatrixUtils.transformPoint(transform, position);
    return super.hitTest(result, position: position);
  }

1123 1124 1125 1126 1127 1128 1129
  @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()
1130
        ..addOval(new Rect.fromCircle(center: Offset.zero, radius: radius))
1131 1132 1133 1134 1135 1136 1137 1138 1139 1140
        ..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) {
1141
        final _LiveTestPointerRecord record = _pointers[pointer];
1142
        paint.color = record.color.withOpacity(record.decay < 0 ? (record.decay / (_kPointerDecay - 1)) : 1.0);
1143
        canvas.drawPath(path.shift(record.position), paint);
1144 1145 1146 1147 1148 1149 1150 1151
        if (record.decay < 0)
          dirty = true;
        record.decay += 1;
      }
      _pointers
        .keys
        .where((int pointer) => _pointers[pointer].decay == 0)
        .toList()
1152
        .forEach(_pointers.remove);
1153 1154
      if (dirty && onNeedPaint != null)
        scheduleMicrotask(onNeedPaint);
1155
    }
1156
    _label?.paint(context.canvas, offset - const Offset(0.0, 10.0));
1157 1158 1159
  }
}

1160
final StackTrace _emptyStackTrace = new stack_trace.Chain(const <stack_trace.Trace>[]);
1161 1162 1163 1164 1165 1166 1167 1168

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;
}