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

5
import 'dart:async';
6
import 'dart:ui' show window, FrameTiming;
7

8
import 'package:flutter/foundation.dart';
9
import 'package:flutter/scheduler.dart';
10
import 'package:flutter/services.dart';
11 12

import '../flutter_test_alternative.dart';
13
import 'scheduler_tester.dart';
14

15
class TestSchedulerBinding extends BindingBase with SchedulerBinding, ServicesBinding {
16 17 18
  final Map<String, List<Map<String, dynamic>>> eventsDispatched = <String, List<Map<String, dynamic>>>{};

  @override
19
  void postEvent(String eventKind, Map<String, dynamic> eventData) {
20 21 22 23 24 25 26
    getEventsDispatched(eventKind).add(eventData);
  }

  List<Map<String, dynamic>> getEventsDispatched(String eventKind) {
    return eventsDispatched.putIfAbsent(eventKind, () => <Map<String, dynamic>>[]);
  }
}
Ian Hickson's avatar
Ian Hickson committed
27

Ian Hickson's avatar
Ian Hickson committed
28
class TestStrategy {
29 30
  int allowedPriority = 10000;

31
  bool shouldRunTaskWithPriority({ int priority, SchedulerBinding scheduler }) {
32 33 34 35 36
    return priority >= allowedPriority;
  }
}

void main() {
37 38
  TestSchedulerBinding scheduler;

39
  setUpAll(() {
40
    scheduler = TestSchedulerBinding();
41 42
  });

Ian Hickson's avatar
Ian Hickson committed
43
  test('Tasks are executed in the right order', () {
44
    final TestStrategy strategy = TestStrategy();
Ian Hickson's avatar
Ian Hickson committed
45
    scheduler.schedulingStrategy = strategy.shouldRunTaskWithPriority;
46 47
    final List<int> input = <int>[2, 23, 23, 11, 0, 80, 3];
    final List<int> executedTasks = <int>[];
48 49

    void scheduleAddingTask(int x) {
50
      scheduler.scheduleTask(() { executedTasks.add(x); }, Priority.idle + x);
51 52
    }

53
    input.forEach(scheduleAddingTask);
Ian Hickson's avatar
Ian Hickson committed
54

55
    strategy.allowedPriority = 100;
Ian Hickson's avatar
Ian Hickson committed
56
    for (int i = 0; i < 3; i += 1)
57
      expect(scheduler.handleEventLoopCallback(), isFalse);
58 59 60
    expect(executedTasks.isEmpty, isTrue);

    strategy.allowedPriority = 50;
Ian Hickson's avatar
Ian Hickson committed
61
    for (int i = 0; i < 3; i += 1)
62 63
      expect(scheduler.handleEventLoopCallback(), i == 0 ? isTrue : isFalse);
    expect(executedTasks, hasLength(1));
64 65 66 67
    expect(executedTasks.single, equals(80));
    executedTasks.clear();

    strategy.allowedPriority = 20;
Ian Hickson's avatar
Ian Hickson committed
68
    for (int i = 0; i < 3; i += 1)
69 70
      expect(scheduler.handleEventLoopCallback(), i < 2 ? isTrue : isFalse);
    expect(executedTasks, hasLength(2));
71 72 73 74 75 76 77 78
    expect(executedTasks[0], equals(23));
    expect(executedTasks[1], equals(23));
    executedTasks.clear();

    scheduleAddingTask(99);
    scheduleAddingTask(19);
    scheduleAddingTask(5);
    scheduleAddingTask(97);
Ian Hickson's avatar
Ian Hickson committed
79
    for (int i = 0; i < 3; i += 1)
80 81
      expect(scheduler.handleEventLoopCallback(), i < 2 ? isTrue : isFalse);
    expect(executedTasks, hasLength(2));
82 83 84 85 86
    expect(executedTasks[0], equals(99));
    expect(executedTasks[1], equals(97));
    executedTasks.clear();

    strategy.allowedPriority = 10;
Ian Hickson's avatar
Ian Hickson committed
87
    for (int i = 0; i < 3; i += 1)
88 89
      expect(scheduler.handleEventLoopCallback(), i < 2 ? isTrue : isFalse);
    expect(executedTasks, hasLength(2));
90 91 92 93 94
    expect(executedTasks[0], equals(19));
    expect(executedTasks[1], equals(11));
    executedTasks.clear();

    strategy.allowedPriority = 1;
Ian Hickson's avatar
Ian Hickson committed
95
    for (int i = 0; i < 4; i += 1)
96 97
      expect(scheduler.handleEventLoopCallback(), i < 3 ? isTrue : isFalse);
    expect(executedTasks, hasLength(3));
98 99 100 101 102 103
    expect(executedTasks[0], equals(5));
    expect(executedTasks[1], equals(3));
    expect(executedTasks[2], equals(2));
    executedTasks.clear();

    strategy.allowedPriority = 0;
104 105
    expect(scheduler.handleEventLoopCallback(), isFalse);
    expect(executedTasks, hasLength(1));
106 107
    expect(executedTasks[0], equals(0));
  });
108 109

  test('2 calls to scheduleWarmUpFrame just schedules it once', () {
110
    final List<VoidCallback> timerQueueTasks = <VoidCallback>[];
111
    bool taskExecuted = false;
112 113 114
    runZoned<void>(
      () {
        // Run it twice without processing the queued tasks.
115
        scheduler.scheduleWarmUpFrame();
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
        scheduler.scheduleWarmUpFrame();
        scheduler.scheduleTask(() { taskExecuted = true; }, Priority.touch);
      },
      zoneSpecification: ZoneSpecification(
        createTimer: (Zone self, ZoneDelegate parent, Zone zone, Duration duration, void f()) {
          // Don't actually run the tasks, just record that it was scheduled.
          timerQueueTasks.add(f);
          return null;
        },
      ),
    );

    // scheduleWarmUpFrame scheduled 2 Timers, scheduleTask scheduled 0 because
    // events are locked.
    expect(timerQueueTasks.length, 2);
131
    expect(taskExecuted, false);
132
  });
133 134

  test('Flutter.Frame event fired', () async {
135
    window.onReportTimings(<FrameTiming>[FrameTiming(<int>[
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
      // build start, build finish
      10000, 15000,
      // raster start, raster finish
      16000, 20000,
    ])]);

    final List<Map<String, dynamic>> events = scheduler.getEventsDispatched('Flutter.Frame');
    expect(events, hasLength(1));

    final Map<String, dynamic> event = events.first;
    expect(event['number'], isNonNegative);
    expect(event['startTime'], 10000);
    expect(event['elapsed'], 10000);
    expect(event['build'], 5000);
    expect(event['raster'], 4000);
  });
152

153 154 155 156 157 158 159 160 161 162 163 164
  test('TimingsCallback exceptions are caught', () {
    FlutterErrorDetails errorCaught;
    FlutterError.onError = (FlutterErrorDetails details) {
      errorCaught = details;
    };
    SchedulerBinding.instance.addTimingsCallback((List<FrameTiming> timings) {
      throw Exception('Test');
    });
    window.onReportTimings(<FrameTiming>[]);
    expect(errorCaught.exceptionAsString(), equals('Exception: Test'));
  });

165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
  test('currentSystemFrameTimeStamp is the raw timestamp', () {
    Duration lastTimeStamp;
    Duration lastSystemTimeStamp;

    void frameCallback(Duration timeStamp) {
      expect(timeStamp, scheduler.currentFrameTimeStamp);
      lastTimeStamp = scheduler.currentFrameTimeStamp;
      lastSystemTimeStamp = scheduler.currentSystemFrameTimeStamp;
    }

    scheduler.scheduleFrameCallback(frameCallback);
    tick(const Duration(seconds: 2));
    expect(lastTimeStamp, Duration.zero);
    expect(lastSystemTimeStamp, const Duration(seconds: 2));

    scheduler.scheduleFrameCallback(frameCallback);
    tick(const Duration(seconds: 4));
    expect(lastTimeStamp, const Duration(seconds: 2));
    expect(lastSystemTimeStamp, const Duration(seconds: 4));

    timeDilation = 2;
    scheduler.scheduleFrameCallback(frameCallback);
    tick(const Duration(seconds: 6));
    expect(lastTimeStamp, const Duration(seconds: 2)); // timeDilation calls SchedulerBinding.resetEpoch
    expect(lastSystemTimeStamp, const Duration(seconds: 6));

    scheduler.scheduleFrameCallback(frameCallback);
    tick(const Duration(seconds: 8));
    expect(lastTimeStamp, const Duration(seconds: 3)); // 2s + (8 - 6)s / 2
    expect(lastSystemTimeStamp, const Duration(seconds: 8));
  });
196
}