Fix behavior of handleDrawFrame() in benchmark mode. (#25049)
This commit is contained in:
parent
543f8924b4
commit
4881777203
@ -3,8 +3,6 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:ui' as ui;
|
|
||||||
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
@ -46,9 +44,7 @@ Future<void> main() async {
|
|||||||
// frames are missed, etc.
|
// frames are missed, etc.
|
||||||
// We use Timer.run to ensure there's a microtask flush in between
|
// We use Timer.run to ensure there's a microtask flush in between
|
||||||
// the two calls below.
|
// the two calls below.
|
||||||
Timer.run(() { ui.window.onBeginFrame(Duration(milliseconds: iterations * 16)); });
|
await tester.pumpBenchmark(Duration(milliseconds: iterations * 16));
|
||||||
Timer.run(() { ui.window.onDrawFrame(); });
|
|
||||||
await tester.idle(); // wait until the frame has run (also uses Timer.run)
|
|
||||||
iterations += 1;
|
iterations += 1;
|
||||||
}
|
}
|
||||||
watch.stop();
|
watch.stop();
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:ui' as ui;
|
|
||||||
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -33,27 +32,15 @@ Future<void> main() async {
|
|||||||
await tester.pump(); // Start drawer animation
|
await tester.pump(); // Start drawer animation
|
||||||
await tester.pump(const Duration(seconds: 1)); // Complete drawer animation
|
await tester.pump(const Duration(seconds: 1)); // Complete drawer animation
|
||||||
|
|
||||||
// Disable calls from the engine which would interfere with the benchmark.
|
|
||||||
ui.window.onBeginFrame = null;
|
|
||||||
ui.window.onDrawFrame = null;
|
|
||||||
|
|
||||||
final TestViewConfiguration big = TestViewConfiguration(size: const Size(360.0, 640.0));
|
final TestViewConfiguration big = TestViewConfiguration(size: const Size(360.0, 640.0));
|
||||||
final TestViewConfiguration small = TestViewConfiguration(size: const Size(355.0, 635.0));
|
final TestViewConfiguration small = TestViewConfiguration(size: const Size(355.0, 635.0));
|
||||||
final RenderView renderView = WidgetsBinding.instance.renderView;
|
final RenderView renderView = WidgetsBinding.instance.renderView;
|
||||||
binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
|
binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.benchmark;
|
||||||
|
|
||||||
watch.start();
|
watch.start();
|
||||||
while (watch.elapsed < kBenchmarkTime) {
|
while (watch.elapsed < kBenchmarkTime) {
|
||||||
renderView.configuration = (iterations % 2 == 0) ? big : small;
|
renderView.configuration = (iterations % 2 == 0) ? big : small;
|
||||||
// We don't use tester.pump() because we're trying to drive it in an
|
await tester.pumpBenchmark(Duration(milliseconds: iterations * 16));
|
||||||
// artificially high load to find out how much CPU each frame takes.
|
|
||||||
// This differs from normal benchmarks which might look at how many
|
|
||||||
// frames are missed, etc.
|
|
||||||
// We use Timer.run to ensure there's a microtask flush in between
|
|
||||||
// the two calls below.
|
|
||||||
Timer.run(() { binding.handleBeginFrame(Duration(milliseconds: iterations * 16)); });
|
|
||||||
Timer.run(() { binding.handleDrawFrame(); });
|
|
||||||
await tester.idle(); // wait until the frame has run (also uses Timer.run)
|
|
||||||
iterations += 1;
|
iterations += 1;
|
||||||
}
|
}
|
||||||
watch.stop();
|
watch.stop();
|
||||||
|
68
packages/flutter/test/scheduler/benchmarks_test.dart
Normal file
68
packages/flutter/test/scheduler/benchmarks_test.dart
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
// Copyright 2018 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.
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
class TestBinding extends LiveTestWidgetsFlutterBinding {
|
||||||
|
TestBinding();
|
||||||
|
|
||||||
|
int framesBegun = 0;
|
||||||
|
int framesDrawn = 0;
|
||||||
|
|
||||||
|
bool handleBeginFrameMicrotaskRun;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void handleBeginFrame(Duration rawTimeStamp) {
|
||||||
|
handleBeginFrameMicrotaskRun = false;
|
||||||
|
framesBegun += 1;
|
||||||
|
Future<void>.microtask(() { handleBeginFrameMicrotaskRun = true; });
|
||||||
|
super.handleBeginFrame(rawTimeStamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void handleDrawFrame() {
|
||||||
|
if (!handleBeginFrameMicrotaskRun) {
|
||||||
|
throw "Microtasks scheduled by 'handledBeginFrame' must be run before 'handleDrawFrame'.";
|
||||||
|
}
|
||||||
|
framesDrawn += 1;
|
||||||
|
super.handleDrawFrame();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> main() async {
|
||||||
|
final TestBinding binding = TestBinding();
|
||||||
|
|
||||||
|
test('test pumpBenchmark() only runs one frame', () async {
|
||||||
|
await benchmarkWidgets((WidgetTester tester) async {
|
||||||
|
const Key root = Key('root');
|
||||||
|
binding.attachRootWidget(Container(key: root));
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(binding.framesBegun, greaterThan(0));
|
||||||
|
expect(binding.framesDrawn, greaterThan(0));
|
||||||
|
|
||||||
|
final Element appState = tester.element(find.byKey(root));
|
||||||
|
binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.benchmark;
|
||||||
|
|
||||||
|
final int startFramesBegun = binding.framesBegun;
|
||||||
|
final int startFramesDrawn = binding.framesDrawn;
|
||||||
|
expect(startFramesBegun, equals(startFramesDrawn));
|
||||||
|
|
||||||
|
appState.markNeedsBuild();
|
||||||
|
|
||||||
|
await tester.pumpBenchmark(const Duration(milliseconds: 16));
|
||||||
|
|
||||||
|
final int endFramesBegun = binding.framesBegun;
|
||||||
|
final int endFramesDrawn = binding.framesDrawn;
|
||||||
|
expect(endFramesBegun, equals(endFramesDrawn));
|
||||||
|
|
||||||
|
expect(endFramesBegun, equals(startFramesBegun + 1));
|
||||||
|
expect(endFramesDrawn, equals(startFramesDrawn + 1));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -985,7 +985,8 @@ enum LiveTestWidgetsFlutterBindingFramePolicy {
|
|||||||
/// This is intended to be used by benchmarks (hence the name) that drive the
|
/// 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
|
/// pipeline directly. It tells the binding to entirely ignore requests for a
|
||||||
/// frame to be scheduled, while still allowing frames that are pumped
|
/// frame to be scheduled, while still allowing frames that are pumped
|
||||||
/// directly (invoking [Window.onBeginFrame] and [Window.onDrawFrame]) to run.
|
/// directly to run (either by using [WidgetTester.pumpBenchmark] or invoking
|
||||||
|
/// [Window.onBeginFrame] and [Window.onDrawFrame]).
|
||||||
///
|
///
|
||||||
/// The [SchedulerBinding.hasScheduledFrame] property will never be true in
|
/// The [SchedulerBinding.hasScheduledFrame] property will never be true in
|
||||||
/// this mode. This can cause unexpected effects. For instance,
|
/// this mode. This can cause unexpected effects. For instance,
|
||||||
@ -1143,8 +1144,7 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
|
|||||||
_pendingFrame.complete(); // unlocks the test API
|
_pendingFrame.complete(); // unlocks the test API
|
||||||
_pendingFrame = null;
|
_pendingFrame = null;
|
||||||
_expectingFrame = false;
|
_expectingFrame = false;
|
||||||
} else {
|
} else if (framePolicy != LiveTestWidgetsFlutterBindingFramePolicy.benchmark) {
|
||||||
assert(framePolicy != LiveTestWidgetsFlutterBindingFramePolicy.benchmark);
|
|
||||||
ui.window.scheduleFrame();
|
ui.window.scheduleFrame();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -257,6 +257,34 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
|
|||||||
return TestAsyncUtils.guard<void>(() => binding.pump(duration, phase));
|
return TestAsyncUtils.guard<void>(() => binding.pump(duration, phase));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Triggers a frame after `duration` amount of time, return as soon as the frame is drawn.
|
||||||
|
///
|
||||||
|
/// This enables driving an artificially high CPU load by rendering frames in
|
||||||
|
/// a tight loop. It must be used with the frame policy set to
|
||||||
|
/// [LiveTestWidgetsFlutterBindingFramePolicy.benchmark].
|
||||||
|
///
|
||||||
|
/// Similarly to [pump], this doesn't actually wait for `duration`, just
|
||||||
|
/// advances the clock.
|
||||||
|
Future<void> pumpBenchmark(Duration duration) async {
|
||||||
|
assert(() {
|
||||||
|
final TestWidgetsFlutterBinding widgetsBinding = binding;
|
||||||
|
return widgetsBinding is LiveTestWidgetsFlutterBinding &&
|
||||||
|
widgetsBinding.framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.benchmark;
|
||||||
|
}());
|
||||||
|
|
||||||
|
dynamic caughtException;
|
||||||
|
void handleError(dynamic error, StackTrace stackTrace) => caughtException ??= error;
|
||||||
|
|
||||||
|
Future<void>.microtask(() { binding.handleBeginFrame(duration); }).catchError(handleError);
|
||||||
|
await idle();
|
||||||
|
Future<void>.microtask(() { binding.handleDrawFrame(); }).catchError(handleError);
|
||||||
|
await idle();
|
||||||
|
|
||||||
|
if (caughtException != null) {
|
||||||
|
throw caughtException;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Repeatedly calls [pump] with the given `duration` until there are no
|
/// Repeatedly calls [pump] with the given `duration` until there are no
|
||||||
/// longer any frames scheduled. This will call [pump] at least once, even if
|
/// longer any frames scheduled. This will call [pump] at least once, even if
|
||||||
/// no frames are scheduled when the function is called, to flush any pending
|
/// no frames are scheduled when the function is called, to flush any pending
|
||||||
|
Loading…
x
Reference in New Issue
Block a user