binding.dart 41.3 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
       // Services binding omitted to avoid dragging in the licenses code.
90
       WidgetsBinding {
91

92 93 94 95
  /// Constructor for [TestWidgetsFlutterBinding].
  ///
  /// This constructor overrides the [debugPrint] global hook to point to
  /// [debugPrintOverride], which can be overridden by subclasses.
96 97
  TestWidgetsFlutterBinding() {
    debugPrint = debugPrintOverride;
98
    debugCheckIntrinsicSizes = checkIntrinsicSizes;
99 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]
  /// synchronous, disabling its normal throttling behaviour.
107 108 109
  @protected
  DebugPrintCallback get debugPrintOverride => debugPrint;

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

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

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

153
  /// Whether there is currently a test executing.
154
  bool get inTest;
155

156 157 158
  /// The number of outstanding microtasks in the queue.
  int get microtaskCount;

159 160
  /// The default test timeout for tests when using this binding.
  test_package.Timeout get defaultTestTimeout;
161

162 163 164 165 166 167 168 169 170 171
  /// 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;

172 173 174 175 176 177 178 179
  /// 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.
180 181 182
  ///
  /// See also [LiveTestWidgetsFlutterBindingFramePolicy], which affects how
  /// this method works when the test is run with `flutter run`.
183
  Future<Null> pump([ Duration duration, EnginePhase newPhase = EnginePhase.sendSemanticsUpdate ]);
184 185 186

  /// Artificially calls dispatchLocaleChanged on the Widget binding,
  /// then flushes microtasks.
187 188 189
  Future<Null> setLocale(String languageCode, String countryCode) {
    return TestAsyncUtils.guard(() async {
      assert(inTest);
190
      final Locale locale = new Locale(languageCode, countryCode);
191 192 193 194 195 196 197 198
      dispatchLocaleChanged(locale);
      return null;
    });
  }

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

215 216 217
  /// Convert the given point from the global coodinate system (as used by
  /// pointer events from the device) to the coordinate system used by the
  /// tests (an 800 by 600 window).
218
  Offset globalToLocal(Offset point) => point;
219 220 221 222

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

225 226 227 228 229 230 231 232
  @override
  void dispatchEvent(PointerEvent event, HitTestResult result, {
    TestBindingEventSource source: TestBindingEventSource.device
  }) {
    assert(source == TestBindingEventSource.test);
    super.dispatchEvent(event, result);
  }

233 234 235 236 237 238 239 240 241 242
  /// 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;
243
  set focusedEditable(EditableTextState value) {
244
    _focusedEditable = value..requestKeyboard();
245 246
  }

247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
  /// 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() {
262
    assert(inTest);
263
    final dynamic result = _pendingExceptionDetails?.exception;
264
    _pendingExceptionDetails = null;
265 266
    return result;
  }
267 268
  FlutterExceptionHandler _oldExceptionHandler;
  FlutterErrorDetails _pendingExceptionDetails;
269

270 271
  static const TextStyle _kMessageStyle = const TextStyle(
    color: const Color(0xFF917FFF),
Ian Hickson's avatar
Ian Hickson committed
272
    fontSize: 40.0,
273 274
  );

275 276
  static final Widget _kPreTestMessage = const Center(
    child: const Text(
277
      'Test starting...',
Ian Hickson's avatar
Ian Hickson committed
278 279
      style: _kMessageStyle,
      textDirection: TextDirection.ltr,
280 281 282
    )
  );

283 284
  static final Widget _kPostTestMessage = const Center(
    child: const Text(
285
      'Test finished.',
Ian Hickson's avatar
Ian Hickson committed
286 287
      style: _kMessageStyle,
      textDirection: TextDirection.ltr,
288 289 290 291 292 293 294
    )
  );

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

295
  /// Call the testBody inside a [FakeAsync] scope on which [pump] can
296 297 298 299 300 301
  /// advance time.
  ///
  /// Returns a future which completes when the test has run.
  ///
  /// Called by the [testWidgets] and [benchmarkWidgets] functions to
  /// run a test.
302 303 304
  ///
  /// The `invariantTester` argument is called after the `testBody`'s [Future]
  /// completes. If it throws, then the test is marked as failed.
305 306 307 308 309
  ///
  /// 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: '' });
310 311 312 313 314 315 316 317 318 319 320 321

  /// 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;
322
  String _currentTestDescription; // set from _runTest to _testCompletionHandler
323 324 325 326 327 328 329

  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) {
330
      debugPrint = debugPrintOverride; // just in case the test overrides it -- otherwise we won't see the error!
331 332 333 334 335 336
      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...
337 338 339 340
      String additional = '';
      if (_currentTestDescription != '')
        additional = '\nThe test description was: $_currentTestDescription';
      test_package.registerException('Test failed. See exception logs above.$additional', _EmptyStack.instance);
341 342
      _pendingExceptionDetails = null;
    }
343
    _currentTestDescription = null;
344 345 346 347
    if (!_currentTestCompleter.isCompleted)
      _currentTestCompleter.complete(null);
  }

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

457
  Future<Null> _runTestBody(Future<Null> testBody(), VoidCallback invariantTester) async {
458 459 460 461 462 463
    assert(inTest);

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

    // run the test
464
    await testBody();
465 466 467 468 469 470 471 472
    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();
473
      invariantTester();
474 475 476 477 478 479 480 481 482 483 484
      _verifyInvariants();
    }

    assert(inTest);
    return null;
  }

  void _verifyInvariants() {
    assert(debugAssertNoTransientCallbacks(
      'An animation is still running even after the widget tree was disposed.'
    ));
485 486 487 488
    assert(debugAssertAllFoundationVarsUnset(
      'The value of a foundation debug variable was changed by the test.',
      debugPrintOverride: debugPrintOverride,
    ));
489 490 491
    assert(debugAssertAllGesturesVarsUnset(
      'The value of a gestures debug variable was changed by the test.',
    ));
492
    assert(debugAssertAllRenderVarsUnset(
493 494
      'The value of a rendering debug variable was changed by the test.',
      debugCheckIntrinsicSizesOverride: checkIntrinsicSizes,
495 496
    ));
    assert(debugAssertAllWidgetVarsUnset(
497
      'The value of a widget debug variable was changed by the test.',
498 499
    ));
    assert(debugAssertAllSchedulerVarsUnset(
500
      'The value of a scheduler debug variable was changed by the test.',
501
    ));
502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518
  }

  /// 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.
519 520 521
///
/// This class assumes it is always run in checked mode (since tests are always
/// run in checked mode).
522 523 524 525 526
class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
  @override
  void initInstances() {
    super.initInstances();
    ui.window.onBeginFrame = null;
527
    ui.window.onDrawFrame = null;
528 529 530
  }

  FakeAsync _fakeAsync;
531 532 533

  @override
  Clock get clock => _clock;
534 535
  Clock _clock;

536 537 538
  @override
  DebugPrintCallback get debugPrintOverride => debugPrintSynchronously;

539 540 541
  @override
  bool get checkIntrinsicSizes => true;

542 543 544 545 546 547
  @override
  test_package.Timeout get defaultTestTimeout => const test_package.Timeout(const Duration(seconds: 5));

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

548 549 550
  @override
  int get microtaskCount => _fakeAsync.microtaskCount;

551
  @override
552
  Future<Null> pump([ Duration duration, EnginePhase newPhase = EnginePhase.sendSemanticsUpdate ]) {
553 554 555 556 557 558 559
    return TestAsyncUtils.guard(() {
      assert(inTest);
      assert(_clock != null);
      if (duration != null)
        _fakeAsync.elapse(duration);
      _phase = newPhase;
      if (hasScheduledFrame) {
560
        _fakeAsync.flushMicrotasks();
561
        handleBeginFrame(new Duration(
562
          milliseconds: _clock.now().millisecondsSinceEpoch,
563
        ));
564 565
        _fakeAsync.flushMicrotasks();
        handleDrawFrame();
566 567 568 569 570 571
      }
      _fakeAsync.flushMicrotasks();
      return new Future<Null>.value();
    });
  }

572 573 574 575 576 577 578 579 580
  @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();
  }

581 582
  @override
  Future<Null> idle() {
583
    final Future<Null> result = super.idle();
584
    _fakeAsync.elapse(const Duration());
585 586 587
    return result;
  }

588
  EnginePhase _phase = EnginePhase.sendSemanticsUpdate;
589

590
  // Cloned from RendererBinding.drawFrame() but with early-exit semantics.
591
  @override
592
  void drawFrame() {
593
    assert(inTest);
594 595 596
    try {
      debugBuildingDirtyElements = true;
      buildOwner.buildScope(renderViewElement);
597 598 599 600 601 602 603 604 605 606 607
      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();
608 609
                assert(_phase == EnginePhase.flushSemantics ||
                       _phase == EnginePhase.sendSemanticsUpdate);
610 611 612 613 614
              }
            }
          }
        }
      }
615
      buildOwner.finalizeTree();
616
    } finally {
617 618
      debugBuildingDirtyElements = false;
    }
619 620 621
  }

  @override
622 623
  Future<Null> runTest(Future<Null> testBody(), VoidCallback invariantTester, { String description: '' }) {
    assert(description != null);
624 625 626 627 628
    assert(!inTest);
    assert(_fakeAsync == null);
    assert(_clock == null);
    _fakeAsync = new FakeAsync();
    _clock = _fakeAsync.getClock(new DateTime.utc(2015, 1, 1));
629
    Future<Null> testBodyResult;
630 631
    _fakeAsync.run((FakeAsync fakeAsync) {
      assert(fakeAsync == _fakeAsync);
632
      testBodyResult = _runTest(testBody, invariantTester, description);
633 634
      assert(inTest);
    });
635
    // testBodyResult is a Future that was created in the Zone of the fakeAsync.
636 637 638 639
    // 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.
640
    return new Future<Null>.value(testBodyResult);
641 642
  }

643 644 645 646 647 648
  @override
  void asyncBarrier() {
    assert(_fakeAsync != null);
    _fakeAsync.flushMicrotasks();
    super.asyncBarrier();
  }
649

650 651 652
  @override
  void _verifyInvariants() {
    super._verifyInvariants();
653 654 655 656 657 658 659 660
    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.'
    );
661
    assert(_fakeAsync.microtaskCount == 0); // Shouldn't be possible.
662 663
  }

664
  @override
665
  void postTest() {
666
    super.postTest();
667 668 669 670 671 672
    assert(_fakeAsync != null);
    assert(_clock != null);
    _clock = null;
    _fakeAsync = null;
  }

673
}
674

675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703
/// 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,
}

704 705 706 707 708 709 710 711
/// 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.
///
712 713 714 715 716 717 718 719 720 721 722 723 724 725 726
/// 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).
727 728 729 730 731 732 733 734 735 736
///
/// 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;

737 738 739
  @override
  Clock get clock => const Clock();

740 741 742 743 744 745 746 747
  @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;
  }

748 749 750 751 752
  @override
  test_package.Timeout get defaultTestTimeout => test_package.Timeout.none;

  Completer<Null> _pendingFrame;
  bool _expectingFrame = false;
753
  bool _viewNeedsPaint = false;
754 755 756 757 758 759 760 761

  /// 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]).
  ///
762 763 764 765 766 767 768 769
  /// * [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.
770
  ///
771 772 773
  /// * [LiveTestWidgetsFlutterBindingFramePolicy.fullyLive] allows all frame
  ///   requests from the engine to be serviced, even those the test did not
  ///   explicitly pump.
774
  ///
775 776 777 778 779 780 781 782 783 784 785
  /// 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]:
786 787 788 789
  ///
  /// ```dart
  /// TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
  /// if (binding is LiveTestWidgetsFlutterBinding)
790
  ///   binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
791
  /// ```
792
  LiveTestWidgetsFlutterBindingFramePolicy framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fadePointers;
793

794 795
  bool _doDrawThisFrame;

796 797
  @override
  void handleBeginFrame(Duration rawTimeStamp) {
798
    assert(_doDrawThisFrame == null);
799 800
    if (_expectingFrame ||
        (framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.fullyLive) ||
801 802
        (framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.fadePointers && _viewNeedsPaint)) {
      _doDrawThisFrame = true;
803
      super.handleBeginFrame(rawTimeStamp);
804 805 806
    } else {
      _doDrawThisFrame = false;
    }
807 808 809 810 811 812 813 814
  }

  @override
  void handleDrawFrame() {
    assert(_doDrawThisFrame != null);
    if (_doDrawThisFrame)
      super.handleDrawFrame();
    _doDrawThisFrame = null;
815 816
    _viewNeedsPaint = false;
    if (_expectingFrame) { // set during pump
817 818 819 820 821 822 823 824 825
      assert(_pendingFrame != null);
      _pendingFrame.complete(); // unlocks the test API
      _pendingFrame = null;
      _expectingFrame = false;
    } else {
      ui.window.scheduleFrame();
    }
  }

826 827 828
  @override
  void initRenderView() {
    assert(renderView == null);
829 830 831 832
    renderView = new _LiveTestRenderView(
      configuration: createViewConfiguration(),
      onNeedPaint: _handleViewNeedsPaint,
    );
833 834 835 836 837 838
    renderView.scheduleInitialFrame();
  }

  @override
  _LiveTestRenderView get renderView => super.renderView;

839 840 841 842 843
  void _handleViewNeedsPaint() {
    _viewNeedsPaint = true;
    renderView.markNeedsPaint();
  }

844 845 846 847 848 849 850 851 852
  /// 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;

853 854 855 856
  @override
  void dispatchEvent(PointerEvent event, HitTestResult result, {
    TestBindingEventSource source: TestBindingEventSource.device
  }) {
857 858 859 860 861 862 863 864 865 866
    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;
        }
867
        _handleViewNeedsPaint();
868 869 870 871 872 873
        super.dispatchEvent(event, result, source: source);
        break;
      case TestBindingEventSource.device:
        if (deviceEventDispatcher != null)
          deviceEventDispatcher.dispatchEvent(event, result);
        break;
874 875 876
    }
  }

877
  @override
878 879
  Future<Null> pump([ Duration duration, EnginePhase newPhase = EnginePhase.sendSemanticsUpdate ]) {
    assert(newPhase == EnginePhase.sendSemanticsUpdate);
880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898
    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
899 900
  Future<Null> runTest(Future<Null> testBody(), VoidCallback invariantTester, { String description: '' }) async {
    assert(description != null);
901 902
    assert(!inTest);
    _inTest = true;
903 904
    renderView._setDescription(description);
    return _runTest(testBody, invariantTester, description);
905 906 907 908 909 910 911 912 913 914 915 916
  }

  @override
  void postTest() {
    super.postTest();
    assert(!_expectingFrame);
    assert(_pendingFrame == null);
    _inTest = false;
  }

  @override
  ViewConfiguration createViewConfiguration() {
917
    return new TestViewConfiguration();
918 919 920
  }

  @override
921
  Offset globalToLocal(Offset point) {
922 923
    final Matrix4 transform = renderView.configuration.toHitTestMatrix();
    final double det = transform.invert();
924
    assert(det != 0.0);
925
    final Offset result = MatrixUtils.transformPoint(transform, point);
926 927 928 929
    return result;
  }

  @override
930
  Offset localToGlobal(Offset point) {
931
    final Matrix4 transform = renderView.configuration.toHitTestMatrix();
932 933
    return MatrixUtils.transformPoint(transform, point);
  }
934
}
935

936 937
/// A [ViewConfiguration] that pretends the display is of a particular size. The
/// size is in logical pixels. The resulting ViewConfiguration maps the given
938
/// size onto the actual display using the [BoxFit.contain] algorithm.
939 940 941 942 943 944 945 946
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) {
947 948
    final double actualWidth = ui.window.physicalSize.width;
    final double actualHeight = ui.window.physicalSize.height;
949 950
    final double desiredWidth = size.width;
    final double desiredHeight = size.height;
951 952 953 954 955 956 957 958 959 960 961 962 963
    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
964
      new Vector3(scale, scale, 1.0) // scale
965
    );
966
    return matrix;
967 968
  }

969 970
  final Matrix4 _paintMatrix;
  final Matrix4 _hitTestMatrix;
971 972

  @override
973
  Matrix4 toMatrix() => _paintMatrix.clone();
974

975 976 977 978 979 980 981 982 983 984 985
  /// Provides the transformation matrix that converts coordinates in the test
  /// coordinate space to coordinates in logical pixels on the real display.
  ///
  /// This is essenitally the same as [toMatrix] but ignoring the device pixel
  /// ratio.
  ///
  /// This is useful because pointers are described in logical pixels, as
  /// opposed to graphics which are expressed in physical pixels.
  // TODO(ianh): We should make graphics and pointers use the same coordinate space.
  //             See: https://github.com/flutter/flutter/issues/1360
  Matrix4 toHitTestMatrix() => _hitTestMatrix.clone();
986 987 988 989 990

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

991 992 993 994
const int _kPointerDecay = -2;

class _LiveTestPointerRecord {
  _LiveTestPointerRecord(
995
    this.pointer,
996
    this.position
997
  ) : color = new HSVColor.fromAHSV(0.8, (35.0 * pointer) % 360.0, 1.0, 1.0).toColor(),
998 999 1000
      decay = 1;
  final int pointer;
  final Color color;
1001
  Offset position;
1002 1003 1004 1005 1006
  int decay; // >0 means down, <0 means up, increases by one each time, removed at 0
}

class _LiveTestRenderView extends RenderView {
  _LiveTestRenderView({
1007 1008
    ViewConfiguration configuration,
    this.onNeedPaint,
1009 1010
  }) : super(configuration: configuration);

1011
  @override
1012 1013
  TestViewConfiguration get configuration => super.configuration;
  @override
1014
  set configuration(covariant TestViewConfiguration value) { super.configuration = value; }
1015

1016 1017
  final VoidCallback onNeedPaint;

1018 1019
  final Map<int, _LiveTestPointerRecord> _pointers = <int, _LiveTestPointerRecord>{};

1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030
  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
1031 1032
    // TODO(ianh): Figure out if the test name is actually RTL.
    _label ??= new TextPainter(textAlign: TextAlign.left, textDirection: TextDirection.ltr);
1033 1034 1035 1036 1037 1038
    _label.text = new TextSpan(text: value, style: _labelStyle);
    _label.layout();
    if (onNeedPaint != null)
      onNeedPaint();
  }

1039
  @override
1040
  bool hitTest(HitTestResult result, { Offset position }) {
1041 1042
    final Matrix4 transform = configuration.toHitTestMatrix();
    final double det = transform.invert();
1043 1044 1045 1046 1047
    assert(det != 0.0);
    position = MatrixUtils.transformPoint(transform, position);
    return super.hitTest(result, position: position);
  }

1048 1049 1050 1051 1052 1053 1054
  @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()
1055
        ..addOval(new Rect.fromCircle(center: Offset.zero, radius: radius))
1056 1057 1058 1059 1060 1061 1062 1063 1064 1065
        ..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) {
1066
        final _LiveTestPointerRecord record = _pointers[pointer];
1067
        paint.color = record.color.withOpacity(record.decay < 0 ? (record.decay / (_kPointerDecay - 1)) : 1.0);
1068
        canvas.drawPath(path.shift(record.position), paint);
1069 1070 1071 1072 1073 1074 1075 1076
        if (record.decay < 0)
          dirty = true;
        record.decay += 1;
      }
      _pointers
        .keys
        .where((int pointer) => _pointers[pointer].decay == 0)
        .toList()
1077
        .forEach(_pointers.remove);
1078 1079
      if (dirty && onNeedPaint != null)
        scheduleMicrotask(onNeedPaint);
1080
    }
1081
    _label?.paint(context.canvas, offset - const Offset(0.0, 10.0));
1082 1083 1084
  }
}

1085 1086 1087 1088 1089
class _EmptyStack implements StackTrace {
  const _EmptyStack._();
  static const _EmptyStack instance = const _EmptyStack._();
  @override
  String toString() => '';
1090
}
1091 1092 1093 1094 1095 1096 1097 1098

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