scheduler_test.dart 8.71 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;
7

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

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({ required int priority, required SchedulerBinding scheduler }) {
32 33 34 35 36
    return priority >= allowedPriority;
  }
}

void main() {
37
  late TestSchedulerBinding scheduler;
38

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
        scheduler.scheduleWarmUpFrame();
        scheduler.scheduleTask(() { taskExecuted = true; }, Priority.touch);
      },
      zoneSpecification: ZoneSpecification(
120
        createTimer: (Zone self, ZoneDelegate parent, Zone zone, Duration duration, void Function() f) {
121 122
          // Don't actually run the tasks, just record that it was scheduled.
          timerQueueTasks.add(f);
123
          return DummyTimer();
124 125 126 127 128 129 130
        },
      ),
    );

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

    // Run the timers so that the scheduler is no longer in warm-up state.
    for (final VoidCallback timer in timerQueueTasks) {
      timer();
    }
137 138 139 140

    // As events are locked, make scheduleTask execute after the test or it
    // will execute during following tests and risk failure.
    addTearDown(() => scheduler.handleEventLoopCallback());
141
  });
142 143

  test('Flutter.Frame event fired', () async {
144
    window.onReportTimings!(<FrameTiming>[FrameTiming(
145
      vsyncStart: 5000,
146 147 148 149
      buildStart: 10000,
      buildFinish: 15000,
      rasterStart: 16000,
      rasterFinish: 20000,
150
      rasterFinishWallTime: 20010,
151
      frameNumber: 1991
152
    )]);
153 154 155 156 157

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

    final Map<String, dynamic> event = events.first;
158
    expect(event['number'], 1991);
159
    expect(event['startTime'], 10000);
160
    expect(event['elapsed'], 15000);
161 162
    expect(event['build'], 5000);
    expect(event['raster'], 4000);
163
    expect(event['vsyncOverhead'], 5000);
164
  });
165

166
  test('TimingsCallback exceptions are caught', () {
167
    FlutterErrorDetails? errorCaught;
168 169 170
    FlutterError.onError = (FlutterErrorDetails details) {
      errorCaught = details;
    };
171
    SchedulerBinding.instance!.addTimingsCallback((List<FrameTiming> timings) {
172 173
      throw Exception('Test');
    });
174 175
    window.onReportTimings!(<FrameTiming>[]);
    expect(errorCaught!.exceptionAsString(), equals('Exception: Test'));
176 177
  });

178
  test('currentSystemFrameTimeStamp is the raw timestamp', () {
179 180 181
    // Undo epoch set by previous tests.
    scheduler.resetEpoch();

182 183
    late Duration lastTimeStamp;
    late Duration lastSystemTimeStamp;
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211

    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));
  });
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245

  test('Animation frame scheduled in the middle of the warm-up frame', () {
    expect(scheduler.schedulerPhase, SchedulerPhase.idle);
    final List<VoidCallback> timers = <VoidCallback>[];
    final ZoneSpecification timerInterceptor = ZoneSpecification(
      createTimer: (Zone self, ZoneDelegate parent, Zone zone, Duration duration, void Function() callback) {
        timers.add(callback);
        return DummyTimer();
      },
    );

    // Schedule a warm-up frame.
    // Expect two timers, one for begin frame, and one for draw frame.
    runZoned<void>(scheduler.scheduleWarmUpFrame, zoneSpecification: timerInterceptor);
    expect(timers.length, 2);
    final VoidCallback warmUpBeginFrame = timers.first;
    final VoidCallback warmUpDrawFrame = timers.last;
    timers.clear();

    warmUpBeginFrame();

    // Simulate an animation frame firing between warm-up begin frame and warm-up draw frame.
    // Expect a timer that reschedules the frame.
    expect(scheduler.hasScheduledFrame, isFalse);
    window.onBeginFrame!(Duration.zero);
    expect(scheduler.hasScheduledFrame, isFalse);
    window.onDrawFrame!();
    expect(scheduler.hasScheduledFrame, isFalse);

    // The draw frame part of the warm-up frame will run the post-frame
    // callback that reschedules the engine frame.
    warmUpDrawFrame();
    expect(scheduler.hasScheduledFrame, isTrue);
  });
246
}
247 248 249 250 251 252 253 254 255 256 257

class DummyTimer implements Timer {
  @override
  void cancel() {}

  @override
  bool get isActive => false;

  @override
  int get tick => 0;
}