Trigger display_cutout_rotation flutter driver test in ci. (#162641)

Fixes https://github.com/flutter/flutter/issues/162615

Can test 2 different ways. 
On a mac (or linux machine) with adb on the path (or android sdk set in
ANDROID_HOME) and an emulator running (or physical device attached) that
is api 30 or higher.
```
cd dev/devicelab
dart bin/test_runner.dart test -t android_display_cutout
```
OR 
```
dev/integration_tests/display_cutout_rotation
flutter drive integration_test/display_cutout_test.dart
```

Proof the test ran successfully
```
[2025-02-12 08:08:22.069817] [STDOUT] Removing Synthetic notch...
[2025-02-12 08:08:22.071147] [STDOUT] Executing "/b/s/w/ir/cache/android/sdk/platform-tools/adb -s emulator-5554 shell cmd overlay disable com.android.internal.display.cutout.emulation.tall" in "/b/s/w/ir/x/w/rc/tmpk3k3yhhp/flutter sdk/dev/integration_tests/display_cutout_rotation/" with environment {BOT: true, LANG: en_US.UTF-8}
[2025-02-12 08:08:22.862219] [STDOUT] Checking for reboot
[android_defines_test] Process terminated with exit code 0.
Task result:
{
  "success": true,
  "data": null,
  "detailFiles": [],
  "benchmarkScoreKeys": [],
  "reason": "success"
}
```

https://logs.chromium.org/logs/flutter/buildbucket/cr-buildbucket/8723125792202374961/+/u/run_android_defines_test/stdout
All checks passed
https://github.com/flutter/flutter/pull/162641/checks?check_run_id=36991537539


## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.
This commit is contained in:
Reid Baker 2025-02-12 14:42:39 -05:00 committed by GitHub
parent c3fc69d18d
commit 0f5d669acb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 73 additions and 107 deletions

View File

@ -1128,6 +1128,16 @@ targets:
["devicelab", "hostonly", "linux"] ["devicelab", "hostonly", "linux"]
task_name: linux_desktop_impeller task_name: linux_desktop_impeller
- name: Linux_android_emu android_display_cutout
recipe: devicelab/devicelab_drone
timeout: 60
bringup: true
properties:
tags: >
["devicelab", "linux"]
task_name: android_display_cutout
presubmit_max_attempts: "2"
- name: Linux android_release_builds_exclude_dev_dependencies_test - name: Linux android_release_builds_exclude_dev_dependencies_test
recipe: devicelab/devicelab_drone recipe: devicelab/devicelab_drone
timeout: 60 timeout: 60

View File

@ -301,6 +301,7 @@
/dev/devicelab/bin/tasks/windows_desktop_impeller.dart @jonahwilliams @flutter/engine /dev/devicelab/bin/tasks/windows_desktop_impeller.dart @jonahwilliams @flutter/engine
/dev/devicelab/bin/tasks/mac_desktop_impeller.dart @jonahwilliams @flutter/engine /dev/devicelab/bin/tasks/mac_desktop_impeller.dart @jonahwilliams @flutter/engine
/dev/devicelab/bin/tasks/linux_desktop_impeller.dart @jonahwilliams @flutter/engine /dev/devicelab/bin/tasks/linux_desktop_impeller.dart @jonahwilliams @flutter/engine
/dev/devicelab/bin/tasks/android_display_cutout.dart @reidbaker @flutter/android
## Host only framework tests ## Host only framework tests
# Linux docs_deploy_beta # Linux docs_deploy_beta

View File

@ -0,0 +1,12 @@
// 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 'package:flutter_devicelab/framework/devices.dart';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/tasks/integration_tests.dart';
Future<void> main() async {
deviceOperatingSystem = DeviceOperatingSystem.android;
await task(createDisplayCutoutTest());
}

View File

@ -139,6 +139,44 @@ TaskFunction createSolidColorTest({required bool enableImpeller}) {
).call; ).call;
} }
// Can run on emulator or physical android device.
// Device must have developer settings enabled.
// Device must be android api 30 or higher.
TaskFunction createDisplayCutoutTest() {
return IntegrationTest(
'${flutterDirectory.path}/dev/integration_tests/display_cutout_rotation/',
'integration_test/display_cutout_test.dart',
setup: (Device device) async {
if (device is! AndroidDevice) {
// Only android devices support this cutoutTest.
throw TaskResult.failure('This test should only target android');
}
// Test requires developer settings added in 28 and behavior added in 30.
final String sdkResult = await device.shellEval('getprop', <String>['ro.build.version.sdk']);
if (sdkResult.startsWith('2') || sdkResult.startsWith('1') || sdkResult.length == 1) {
throw TaskResult.failure('This test should only target android 30+.');
}
print('Adding Synthetic notch...');
// This command will cause any running android activity to be recreated.
await device.shellExec('cmd', <String>[
'overlay',
'enable',
'com.android.internal.display.cutout.emulation.tall',
]);
},
tearDown: (Device device) async {
if (device is AndroidDevice) {
print('Removing Synthetic notch...');
await device.shellExec('cmd', <String>[
'overlay',
'disable',
'com.android.internal.display.cutout.emulation.tall',
]);
}
},
).call;
}
TaskFunction dartDefinesTask() { TaskFunction dartDefinesTask() {
return DriverTest( return DriverTest(
'${flutterDirectory.path}/dev/integration_tests/ui', '${flutterDirectory.path}/dev/integration_tests/ui',
@ -217,7 +255,6 @@ class DriverTest {
...extraOptions, ...extraOptions,
]; ];
await flutter('drive', options: options, environment: environment); await flutter('drive', options: options, environment: environment);
return TaskResult.success(null); return TaskResult.success(null);
}); });
} }
@ -231,6 +268,8 @@ class IntegrationTest {
this.createPlatforms = const <String>[], this.createPlatforms = const <String>[],
this.withTalkBack = false, this.withTalkBack = false,
this.environment, this.environment,
this.setup,
this.tearDown,
}); });
final String testDirectory; final String testDirectory;
@ -240,12 +279,19 @@ class IntegrationTest {
final bool withTalkBack; final bool withTalkBack;
final Map<String, String>? environment; final Map<String, String>? environment;
/// Run before flutter drive with the result from devices.workingDevice.
final Future<void> Function(Device device)? setup;
/// Run after flutter drive with the result from devices.workingDevice.
final Future<void> Function(Device device)? tearDown;
Future<TaskResult> call() { Future<TaskResult> call() {
return inDirectory<TaskResult>(testDirectory, () async { return inDirectory<TaskResult>(testDirectory, () async {
final Device device = await devices.workingDevice; final Device device = await devices.workingDevice;
await device.unlock(); await device.unlock();
final String deviceId = device.deviceId; final String deviceId = device.deviceId;
await flutter('packages', options: <String>['get']); await flutter('packages', options: <String>['get']);
await setup?.call(await devices.workingDevice);
if (createPlatforms.isNotEmpty) { if (createPlatforms.isNotEmpty) {
await flutter( await flutter(
@ -265,6 +311,7 @@ class IntegrationTest {
final List<String> options = <String>['-v', '-d', deviceId, testTarget, ...extraOptions]; final List<String> options = <String>['-v', '-d', deviceId, testTarget, ...extraOptions];
await flutter('test', options: options, environment: environment); await flutter('test', options: options, environment: environment);
await tearDown?.call(await devices.workingDevice);
if (withTalkBack) { if (withTalkBack) {
await disableTalkBack(); await disableTalkBack();

View File

@ -1,5 +1,5 @@
# display_cutout_rotation # display_cutout_rotation
To run test locally use `flutter drive integration_test/display_cutout_test.dart` from this folder. To run test locally use `flutter test integration_test/display_cutout_test.dart` from this folder.
OR from `flutter/dev/devicelab` run `dart bin/test_runner.dart test -t android_display_cutout`. OR from `flutter/dev/devicelab` run `dart bin/test_runner.dart test -t android_display_cutout`.

View File

@ -14,7 +14,7 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('end-to-end test', () { group('end-to-end test', () {
// Test assumes a custom driver that enables // Test assumes that the device already has enabled
// "com.android.internal.display.cutout.emulation.tall". // "com.android.internal.display.cutout.emulation.tall".
testWidgets('cutout should be on top in portrait mode', (WidgetTester tester) async { testWidgets('cutout should be on top in portrait mode', (WidgetTester tester) async {
// Force rotation // Force rotation

View File

@ -1,104 +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 'dart:convert';
import 'dart:io';
import 'package:flutter_driver/flutter_driver.dart';
// display_cutout needs a custom driver becuase cutout manipulations needs to be
// done to a device/emulator in order for the tests to pass.
Future<void> main() async {
if (!(Platform.isLinux || Platform.isMacOS)) {
// Not a fundemental limitation, developer shortcut.
print('This test must be run on a POSIX host. Skipping...');
return;
}
final bool adbExists = Process.runSync('which', <String>['adb']).exitCode == 0;
if (!adbExists) {
print(r'This test needs ADB to exist on the $PATH.');
exitCode = 1;
return;
}
// Test requires developer settings added in 28 and behavior added in 30
final ProcessResult checkApiLevel = Process.runSync('adb', <String>[
'shell',
'getprop',
'ro.build.version.sdk',
]);
final String apiStdout = checkApiLevel.stdout.toString();
// Api level 30 or higher.
if (apiStdout.startsWith('2') || apiStdout.startsWith('1') || apiStdout.length == 1) {
print('This test must be run on api 30 or higher. Skipping...');
return;
}
// Developer settings are required on target device for cutout manipulation.
bool shouldResetDevSettings = false;
final ProcessResult checkDevSettingsResult = Process.runSync('adb', <String>[
'shell',
'settings',
'get',
'global',
'development_settings_enabled',
]);
if (checkDevSettingsResult.stdout.toString().startsWith('0')) {
print('Enabling developer settings...');
// Developer settings not enabled, enable them and mark that the origional
// state should be restored after.
shouldResetDevSettings = true;
Process.runSync('adb', <String>[
'shell',
'settings',
'put',
'global',
'development_settings_enabled',
'1',
]);
}
// Assumption of diplay_cutout_test.dart is that there is a "tall" notch.
print('Adding Synthetic notch...');
Process.runSync('adb', <String>[
'shell',
'cmd',
'overlay',
'enable',
'com.android.internal.display.cutout.emulation.tall',
]);
print('Starting test.');
try {
final FlutterDriver driver = await FlutterDriver.connect();
final String data = await driver.requestData(null, timeout: const Duration(minutes: 1));
await driver.close();
final Map<String, dynamic> result = jsonDecode(data) as Map<String, dynamic>;
print('Test finished!');
print(result);
exitCode = result['result'] == 'true' ? 0 : 1;
} catch (e) {
print(e);
exitCode = 1;
} finally {
print('Removing Synthetic notch...');
Process.runSync('adb', <String>[
'shell',
'cmd',
'overlay',
'disable',
'com.android.internal.display.cutout.emulation.tall',
]);
print('Reverting Adb changes...');
if (shouldResetDevSettings) {
print('Disabling developer settings...');
Process.runSync('adb', <String>[
'shell',
'settings',
'put',
'global',
'development_settings_enabled',
'0',
]);
}
}
return;
}