mirror of
https://github.com/flutter/flutter
synced 2024-10-01 05:54:08 +00:00
Refactor external_ui
→ external_textures
(#142062)
This PR makes no _behavioral_ changes to executed code, and instead focuses on organization and naming: 1. Almost[^1] anything named `external_ui` is renamed `external_textures` 1. Extended the README to explain the intent of the test, as well as how to run it 1. Renamed `main.dart` and `main_test.dart` to `frame_rate_main.dart` and `frame_rate_test.dart` (we'll add more) 1. Did some refactoring of the test to make it more obvious what is being asserted (i.e. `widgetBuilds` and friends) Given how complex (and in-flux) this directory is, I'm also requesting either John, Jonah or I review any changes. [^1]: Except the name of the `.ci.yaml` task, i.e. `name: Linux_pixel_7pro external_ui_integration_test` because I'm apparently not able to change that without creating a new task as `bringup: true` and playing a bit of a dance. Maybe that's worth doing though (in future PRs)?
This commit is contained in:
parent
fb4eef1c9c
commit
2e2042ff8e
4
.ci.yaml
4
.ci.yaml
|
@ -2175,7 +2175,7 @@ targets:
|
|||
properties:
|
||||
tags: >
|
||||
["devicelab", "android", "linux", "pixel", "7pro"]
|
||||
task_name: external_ui_integration_test
|
||||
task_name: external_textures_integration_test
|
||||
|
||||
# linux motog4 benchmark
|
||||
- name: Linux_android fading_child_animation_perf__timeline_summary
|
||||
|
@ -4281,7 +4281,7 @@ targets:
|
|||
properties:
|
||||
tags: >
|
||||
["devicelab", "ios", "mac"]
|
||||
task_name: external_ui_integration_test_ios
|
||||
task_name: external_textures_integration_test_ios
|
||||
ignore_flakiness: "true"
|
||||
|
||||
- name: Mac_ios route_test_ios
|
||||
|
|
|
@ -9,3 +9,4 @@
|
|||
/packages/flutter_tools/pubspec.yaml @christopherfujino
|
||||
/packages/flutter_tools/templates/module/ios/ @jmagman
|
||||
/packages/flutter_tools/templates/**/Podfile* @jmagman
|
||||
/dev/integration_tests/external_textures/** @matanlurey @johnmccutchan @jonahwilliams
|
||||
|
|
|
@ -129,7 +129,7 @@
|
|||
/dev/devicelab/bin/tasks/cull_opacity_perf__timeline_summary.dart @zanderso @flutter/engine
|
||||
/dev/devicelab/bin/tasks/drive_perf_debug_warning.dart @zanderso @flutter/engine
|
||||
/dev/devicelab/bin/tasks/embedded_android_views_integration_test.dart @stuartmorgan @flutter/plugin
|
||||
/dev/devicelab/bin/tasks/external_ui_integration_test.dart @zanderso @flutter/engine
|
||||
/dev/devicelab/bin/tasks/external_textures_integration_test.dart @zanderso @flutter/engine
|
||||
/dev/devicelab/bin/tasks/fading_child_animation_perf__timeline_summary.dart @zanderso @flutter/engine
|
||||
/dev/devicelab/bin/tasks/fast_scroll_large_images__memory.dart @zanderso @flutter/engine
|
||||
/dev/devicelab/bin/tasks/flavors_test.dart @zanderso @flutter/tool
|
||||
|
@ -174,7 +174,7 @@
|
|||
/dev/devicelab/bin/tasks/complex_layout_scroll_perf_bad_ios__timeline_summary.dart @jonahwilliams @flutter/engine
|
||||
/dev/devicelab/bin/tasks/complex_layout_scroll_perf_ios__timeline_summary.dart @vashworth @flutter/engine
|
||||
/dev/devicelab/bin/tasks/cubic_bezier_perf_ios_sksl_warmup__timeline_summary.dart @zanderso @flutter/engine
|
||||
/dev/devicelab/bin/tasks/external_ui_integration_test_ios.dart @zanderso @flutter/engine
|
||||
/dev/devicelab/bin/tasks/external_textures_integration_test_ios.dart @zanderso @flutter/engine
|
||||
/dev/devicelab/bin/tasks/flavors_test_ios.dart @vashworth @flutter/tool
|
||||
/dev/devicelab/bin/tasks/flavors_test_ios_xcode_debug.dart @vashworth @flutter/tool
|
||||
/dev/devicelab/bin/tasks/flutter_gallery__transition_perf_e2e_ios.dart @zanderso @flutter/engine
|
||||
|
|
|
@ -8,5 +8,5 @@ import 'package:flutter_devicelab/tasks/integration_tests.dart';
|
|||
|
||||
Future<void> main() async {
|
||||
deviceOperatingSystem = DeviceOperatingSystem.android;
|
||||
await task(createExternalUiIntegrationTest());
|
||||
await task(createExternalTexturesIntegrationTest());
|
||||
}
|
|
@ -8,5 +8,5 @@ import 'package:flutter_devicelab/tasks/integration_tests.dart';
|
|||
|
||||
Future<void> main() async {
|
||||
deviceOperatingSystem = DeviceOperatingSystem.ios;
|
||||
await task(createExternalUiIntegrationTest());
|
||||
await task(createExternalTexturesIntegrationTest());
|
||||
}
|
|
@ -40,9 +40,9 @@ TaskFunction createIntegrationTestFlavorsTest({Map<String, String>? environment}
|
|||
).call;
|
||||
}
|
||||
|
||||
TaskFunction createExternalUiIntegrationTest() {
|
||||
TaskFunction createExternalTexturesIntegrationTest() {
|
||||
return DriverTest(
|
||||
'${flutterDirectory.path}/dev/integration_tests/external_ui',
|
||||
'${flutterDirectory.path}/dev/integration_tests/external_textures',
|
||||
'lib/main.dart',
|
||||
).call;
|
||||
}
|
||||
|
|
47
dev/integration_tests/external_textures/README.md
Normal file
47
dev/integration_tests/external_textures/README.md
Normal file
|
@ -0,0 +1,47 @@
|
|||
# external_textures
|
||||
|
||||
Tests external texture rendering between a native[^1] platform and Flutter.
|
||||
|
||||
Part of Flutter's API for [plugins](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin) includes passing _external textures_, or textures
|
||||
created outside of Flutter, to Flutter, typically using the [`Texture`][texture]
|
||||
widget. This is useful for plugins that render video, or for plugins that
|
||||
interact with the camera.
|
||||
|
||||
For example:
|
||||
|
||||
- [`packages/camera`][camera]
|
||||
- [`packages/video_player`][video_player]
|
||||
|
||||
[texture]: https://api.flutter.dev/flutter/widgets/Texture-class.html
|
||||
[camera]: https://github.com/flutter/packages/tree/8255fbed74465425a1ec06a1804225e705e29f52/packages/camera
|
||||
[video_player]: https://github.com/flutter/packages/tree/8255fbed74465425a1ec06a1804225e705e29f52/packages/video_player
|
||||
|
||||
Because external textures are created outside of Flutter, there is often subtle
|
||||
translation that needs to happen between the native platform and Flutter, which
|
||||
is hard to observe. These integration tests are designed to help catch these
|
||||
subtle translation issues.
|
||||
|
||||
## How it works
|
||||
|
||||
- Each `lib/*_main.dart` file is a Flutter app instrumenting a test case.
|
||||
- There is a cooresponding `test_driver/*_test.dart` that runs assertions.
|
||||
|
||||
To run the test cases locally, use `flutter drive`[^2]:
|
||||
|
||||
```shell
|
||||
flutter drive lib/frame_rate_main.dart --driver test_driver/frame_rate_test.dart
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> On CI, the test cases are run within our [device lab](../../devicelab/README.md).
|
||||
>
|
||||
> See [`devicelab/lib/tasks/integration_tests.dart`](../../devicelab/lib/tasks/integration_tests.dart)
|
||||
> and search for `createExternalTexturesIntegrationTest`.
|
||||
>
|
||||
> The actual tests are run by task runners:
|
||||
>
|
||||
> - [Android](../../devicelab/bin/tasks/external_textures_integration_test.dart)
|
||||
> - [iOS](../../devicelab/bin/tasks/external_textures_integration_test_ios.dart)
|
||||
|
||||
[^1]: Only iOS and Android.
|
||||
[^2]: Unfortunately documentation is quite limited. See [#142021](https://github.com/flutter/flutter/issues/142021).
|
|
@ -9,7 +9,7 @@ found in the LICENSE file. -->
|
|||
|
||||
<application
|
||||
android:name="${applicationName}"
|
||||
android:label="external_ui">
|
||||
android:label="external_textures">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleTop"
|
|
@ -11,7 +11,7 @@
|
|||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>external_ui</string>
|
||||
<string>external_textures</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
|
@ -1,4 +1,4 @@
|
|||
name: external_ui
|
||||
name: external_textures
|
||||
description: A test of Flutter integrating external UIs.
|
||||
|
||||
environment:
|
|
@ -0,0 +1,73 @@
|
|||
// Copyright 2014 The Flutter 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_driver/flutter_driver.dart';
|
||||
import 'package:test/test.dart' hide TypeMatcher, isInstanceOf;
|
||||
|
||||
final RegExp _calibrationRegExp = RegExp('Flutter frame rate is (.*)fps');
|
||||
final RegExp _statsRegExp = RegExp('Produced: (.*)fps\nConsumed: (.*)fps\nWidget builds: (.*)');
|
||||
const Duration _samplingTime = Duration(seconds: 8);
|
||||
|
||||
Future<void> main() async {
|
||||
late final FlutterDriver driver;
|
||||
|
||||
setUpAll(() async {
|
||||
driver = await FlutterDriver.connect();
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
await driver.close();
|
||||
});
|
||||
|
||||
// Verifies we consume texture frames at a rate close to the minimum of the
|
||||
// rate at which they are produced and Flutter's frame rate. In addition,
|
||||
// it verifies that widget builds are not triggered by external texture
|
||||
// frames.
|
||||
test('renders frames from the device at a rate similar to the frames produced', () async {
|
||||
final SerializableFinder fab = find.byValueKey('fab');
|
||||
final SerializableFinder summary = find.byValueKey('summary');
|
||||
|
||||
// Wait for calibration to complete and fab to appear.
|
||||
await driver.waitFor(fab);
|
||||
|
||||
final String calibrationResult = await driver.getText(summary);
|
||||
final Match? matchCalibration = _calibrationRegExp.matchAsPrefix(calibrationResult);
|
||||
expect(matchCalibration, isNotNull);
|
||||
final double flutterFrameRate = double.parse(matchCalibration?.group(1) ?? '0');
|
||||
|
||||
// Texture frame stats at 0.5x Flutter frame rate
|
||||
await driver.tap(fab);
|
||||
await Future<void>.delayed(_samplingTime);
|
||||
await driver.tap(fab);
|
||||
|
||||
final String statsSlow = await driver.getText(summary);
|
||||
final Match matchSlow = _statsRegExp.matchAsPrefix(statsSlow)!;
|
||||
expect(matchSlow, isNotNull);
|
||||
|
||||
double framesProduced = double.parse(matchSlow.group(1)!);
|
||||
expect(framesProduced, closeTo(flutterFrameRate / 2.0, 5.0));
|
||||
double framesConsumed = double.parse(matchSlow.group(2)!);
|
||||
expect(framesConsumed, closeTo(flutterFrameRate / 2.0, 5.0));
|
||||
int widgetBuilds = int.parse(matchSlow.group(3)!);
|
||||
expect(widgetBuilds, 1);
|
||||
|
||||
// Texture frame stats at 2.0x Flutter frame rate
|
||||
await driver.tap(fab);
|
||||
await Future<void>.delayed(_samplingTime);
|
||||
await driver.tap(fab);
|
||||
|
||||
final String statsFast = await driver.getText(summary);
|
||||
final Match matchFast = _statsRegExp.matchAsPrefix(statsFast)!;
|
||||
expect(matchFast, isNotNull);
|
||||
|
||||
framesProduced = double.parse(matchFast.group(1)!);
|
||||
expect(framesProduced, closeTo(flutterFrameRate * 2.0, 5.0));
|
||||
framesConsumed = double.parse(matchFast.group(2)!);
|
||||
expect(framesConsumed, closeTo(flutterFrameRate, 10.0));
|
||||
widgetBuilds = int.parse(matchSlow.group(3)!);
|
||||
expect(widgetBuilds, 1);
|
||||
}, timeout: Timeout.none);
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
# external_ui
|
||||
|
||||
A Flutter project for testing external texture rendering.
|
|
@ -1,68 +0,0 @@
|
|||
// Copyright 2014 The Flutter 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_driver/flutter_driver.dart';
|
||||
import 'package:test/test.dart' hide TypeMatcher, isInstanceOf;
|
||||
|
||||
final RegExp calibrationRegExp = RegExp('Flutter frame rate is (.*)fps');
|
||||
final RegExp statsRegExp = RegExp('Produced: (.*)fps\nConsumed: (.*)fps\nWidget builds: (.*)');
|
||||
const Duration samplingTime = Duration(seconds: 8);
|
||||
|
||||
Future<void> main() async {
|
||||
group('texture suite', () {
|
||||
late FlutterDriver driver;
|
||||
|
||||
setUpAll(() async {
|
||||
driver = await FlutterDriver.connect();
|
||||
});
|
||||
|
||||
// This test verifies that we can consume texture frames at a rate
|
||||
// close to the minimum of the rate at which they are produced
|
||||
// and Flutter's frame rate. It also verifies that we do not rebuild the
|
||||
// Widget tree during texture consumption. The test starts by measuring
|
||||
// Flutter's frame rate.
|
||||
test('texture rendering', () async {
|
||||
final SerializableFinder fab = find.byValueKey('fab');
|
||||
final SerializableFinder summary = find.byValueKey('summary');
|
||||
|
||||
// Wait for calibration to complete and fab to appear.
|
||||
await driver.waitFor(fab);
|
||||
|
||||
final String calibrationResult = await driver.getText(summary);
|
||||
final Match? matchCalibration = calibrationRegExp.matchAsPrefix(calibrationResult);
|
||||
expect(matchCalibration, isNotNull);
|
||||
final double flutterFrameRate = double.parse(matchCalibration?.group(1) ?? '0');
|
||||
|
||||
// Texture frame stats at 0.5x Flutter frame rate
|
||||
await driver.tap(fab);
|
||||
await Future<void>.delayed(samplingTime);
|
||||
await driver.tap(fab);
|
||||
|
||||
final String statsSlow = await driver.getText(summary);
|
||||
final Match matchSlow = statsRegExp.matchAsPrefix(statsSlow)!;
|
||||
expect(matchSlow, isNotNull);
|
||||
expect(double.parse(matchSlow.group(1)!), closeTo(flutterFrameRate / 2.0, 5.0));
|
||||
expect(double.parse(matchSlow.group(2)!), closeTo(flutterFrameRate / 2.0, 5.0));
|
||||
expect(int.parse(matchSlow.group(3)!), 1);
|
||||
|
||||
// Texture frame stats at 2.0x Flutter frame rate
|
||||
await driver.tap(fab);
|
||||
await Future<void>.delayed(samplingTime);
|
||||
await driver.tap(fab);
|
||||
|
||||
final String statsFast = await driver.getText(summary);
|
||||
final Match matchFast = statsRegExp.matchAsPrefix(statsFast)!;
|
||||
expect(matchFast, isNotNull);
|
||||
expect(double.parse(matchFast.group(1)!), closeTo(flutterFrameRate * 2.0, 5.0));
|
||||
expect(double.parse(matchFast.group(2)!), closeTo(flutterFrameRate, 10.0));
|
||||
expect(int.parse(matchFast.group(3)!), 1);
|
||||
}, timeout: Timeout.none);
|
||||
|
||||
tearDownAll(() async {
|
||||
driver.close();
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue