From 0f5d669acbc5599442f2b196e27223f66173c131 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Wed, 12 Feb 2025 14:42:39 -0500 Subject: [PATCH] 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. --- .ci.yaml | 10 ++ TESTOWNERS | 1 + .../bin/tasks/android_display_cutout.dart | 12 ++ .../lib/tasks/integration_tests.dart | 49 ++++++++- .../display_cutout_rotation/README.md | 2 +- .../integration_test/display_cutout_test.dart | 2 +- .../test_driver/display_cutout_test_test.dart | 104 ------------------ 7 files changed, 73 insertions(+), 107 deletions(-) create mode 100644 dev/devicelab/bin/tasks/android_display_cutout.dart delete mode 100644 dev/integration_tests/display_cutout_rotation/test_driver/display_cutout_test_test.dart diff --git a/.ci.yaml b/.ci.yaml index 0d1ac640cf..e87155c9f7 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -1128,6 +1128,16 @@ targets: ["devicelab", "hostonly", "linux"] 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 recipe: devicelab/devicelab_drone timeout: 60 diff --git a/TESTOWNERS b/TESTOWNERS index aa93e814ba..3df8554c93 100644 --- a/TESTOWNERS +++ b/TESTOWNERS @@ -301,6 +301,7 @@ /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/linux_desktop_impeller.dart @jonahwilliams @flutter/engine +/dev/devicelab/bin/tasks/android_display_cutout.dart @reidbaker @flutter/android ## Host only framework tests # Linux docs_deploy_beta diff --git a/dev/devicelab/bin/tasks/android_display_cutout.dart b/dev/devicelab/bin/tasks/android_display_cutout.dart new file mode 100644 index 0000000000..4003ba156a --- /dev/null +++ b/dev/devicelab/bin/tasks/android_display_cutout.dart @@ -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 main() async { + deviceOperatingSystem = DeviceOperatingSystem.android; + await task(createDisplayCutoutTest()); +} diff --git a/dev/devicelab/lib/tasks/integration_tests.dart b/dev/devicelab/lib/tasks/integration_tests.dart index d14a9ed762..184a00b286 100644 --- a/dev/devicelab/lib/tasks/integration_tests.dart +++ b/dev/devicelab/lib/tasks/integration_tests.dart @@ -139,6 +139,44 @@ TaskFunction createSolidColorTest({required bool enableImpeller}) { ).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', ['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', [ + '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', [ + 'overlay', + 'disable', + 'com.android.internal.display.cutout.emulation.tall', + ]); + } + }, + ).call; +} + TaskFunction dartDefinesTask() { return DriverTest( '${flutterDirectory.path}/dev/integration_tests/ui', @@ -217,7 +255,6 @@ class DriverTest { ...extraOptions, ]; await flutter('drive', options: options, environment: environment); - return TaskResult.success(null); }); } @@ -231,6 +268,8 @@ class IntegrationTest { this.createPlatforms = const [], this.withTalkBack = false, this.environment, + this.setup, + this.tearDown, }); final String testDirectory; @@ -240,12 +279,19 @@ class IntegrationTest { final bool withTalkBack; final Map? environment; + /// Run before flutter drive with the result from devices.workingDevice. + final Future Function(Device device)? setup; + + /// Run after flutter drive with the result from devices.workingDevice. + final Future Function(Device device)? tearDown; + Future call() { return inDirectory(testDirectory, () async { final Device device = await devices.workingDevice; await device.unlock(); final String deviceId = device.deviceId; await flutter('packages', options: ['get']); + await setup?.call(await devices.workingDevice); if (createPlatforms.isNotEmpty) { await flutter( @@ -265,6 +311,7 @@ class IntegrationTest { final List options = ['-v', '-d', deviceId, testTarget, ...extraOptions]; await flutter('test', options: options, environment: environment); + await tearDown?.call(await devices.workingDevice); if (withTalkBack) { await disableTalkBack(); diff --git a/dev/integration_tests/display_cutout_rotation/README.md b/dev/integration_tests/display_cutout_rotation/README.md index 72ccf694c8..934210c076 100644 --- a/dev/integration_tests/display_cutout_rotation/README.md +++ b/dev/integration_tests/display_cutout_rotation/README.md @@ -1,5 +1,5 @@ # 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`. diff --git a/dev/integration_tests/display_cutout_rotation/integration_test/display_cutout_test.dart b/dev/integration_tests/display_cutout_rotation/integration_test/display_cutout_test.dart index cbbc711b6d..a42117faad 100644 --- a/dev/integration_tests/display_cutout_rotation/integration_test/display_cutout_test.dart +++ b/dev/integration_tests/display_cutout_rotation/integration_test/display_cutout_test.dart @@ -14,7 +14,7 @@ void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 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". testWidgets('cutout should be on top in portrait mode', (WidgetTester tester) async { // Force rotation diff --git a/dev/integration_tests/display_cutout_rotation/test_driver/display_cutout_test_test.dart b/dev/integration_tests/display_cutout_rotation/test_driver/display_cutout_test_test.dart deleted file mode 100644 index 2dea8af05e..0000000000 --- a/dev/integration_tests/display_cutout_rotation/test_driver/display_cutout_test_test.dart +++ /dev/null @@ -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 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', ['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', [ - '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', [ - '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', [ - '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', [ - '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 result = jsonDecode(data) as Map; - 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', [ - 'shell', - 'cmd', - 'overlay', - 'disable', - 'com.android.internal.display.cutout.emulation.tall', - ]); - print('Reverting Adb changes...'); - if (shouldResetDevSettings) { - print('Disabling developer settings...'); - Process.runSync('adb', [ - 'shell', - 'settings', - 'put', - 'global', - 'development_settings_enabled', - '0', - ]); - } - } - return; -}