From b9733522dd162019e78dc161dc7aaedf442bf53a Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Tue, 14 Jan 2020 12:29:09 -0800 Subject: [PATCH] Implement takeScreenshot and add driver test for Fuchsia (#48611) --- ...lutter_driver_screenshot_test_fuchsia.dart | 14 ++ .../meta/flutter_driver_screenshot_test.cmx | 22 ++ .../lib/main.dart | 30 +-- .../lib/src/fuchsia/fuchsia_device.dart | 53 ++++- .../lib/src/fuchsia/fuchsia_sdk.dart | 7 +- .../fuchsia/fuchsia_device_test.dart | 225 ++++++++++++++++++ 6 files changed, 327 insertions(+), 24 deletions(-) create mode 100644 dev/devicelab/bin/tasks/flutter_driver_screenshot_test_fuchsia.dart create mode 100644 dev/integration_tests/flutter_driver_screenshot_test/fuchsia/meta/flutter_driver_screenshot_test.cmx diff --git a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_fuchsia.dart b/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_fuchsia.dart new file mode 100644 index 00000000000..8d97bb9ad5d --- /dev/null +++ b/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_fuchsia.dart @@ -0,0 +1,14 @@ +// 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_devicelab/framework/adb.dart'; +import 'package:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/tasks/integration_tests.dart'; + +Future main() async { + deviceOperatingSystem = DeviceOperatingSystem.fuchsia; + await task(createFlutterDriverScreenshotTest()); +} diff --git a/dev/integration_tests/flutter_driver_screenshot_test/fuchsia/meta/flutter_driver_screenshot_test.cmx b/dev/integration_tests/flutter_driver_screenshot_test/fuchsia/meta/flutter_driver_screenshot_test.cmx new file mode 100644 index 00000000000..2938f45c1da --- /dev/null +++ b/dev/integration_tests/flutter_driver_screenshot_test/fuchsia/meta/flutter_driver_screenshot_test.cmx @@ -0,0 +1,22 @@ +{ + "program": { + "data": "data/flutter_driver_screenshot_test" + }, + "sandbox": { + "services": [ + "fuchsia.cobalt.LoggerFactory", + "fuchsia.fonts.Provider", + "fuchsia.logger.LogSink", + "fuchsia.modular.Clipboard", + "fuchsia.modular.ContextWriter", + "fuchsia.modular.DeviceMap", + "fuchsia.modular.ModuleContext", + "fuchsia.sys.Environment", + "fuchsia.sys.Launcher", + "fuchsia.testing.runner.TestRunner", + "fuchsia.ui.input.ImeService", + "fuchsia.ui.policy.Presenter", + "fuchsia.ui.scenic.Scenic" + ] + } +} diff --git a/dev/integration_tests/flutter_driver_screenshot_test/lib/main.dart b/dev/integration_tests/flutter_driver_screenshot_test/lib/main.dart index 9147d16fd3b..175790812dc 100644 --- a/dev/integration_tests/flutter_driver_screenshot_test/lib/main.dart +++ b/dev/integration_tests/flutter_driver_screenshot_test/lib/main.dart @@ -71,28 +71,20 @@ class _MyHomePageState extends State<_MyHomePage> { Future _handleDriverMessage(String message) async { switch (message) { case 'device_model': - String target; + final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); switch (Theme.of(context).platform) { case TargetPlatform.iOS: - target = 'ios'; - break; - case TargetPlatform.android: - target = 'android'; - break; - default: - target = 'unsupported'; - break; - } - final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); - if (target == 'ios') { - final IosDeviceInfo iosDeviceInfo = await deviceInfo.iosInfo; - if (iosDeviceInfo.isPhysicalDevice) { - return iosDeviceInfo.utsname.machine; - } else { + final IosDeviceInfo iosDeviceInfo = await deviceInfo.iosInfo; + if (iosDeviceInfo.isPhysicalDevice) { + return iosDeviceInfo.utsname.machine; + } return 'sim_' + iosDeviceInfo.name; - } - } else if (target == 'android') { - return (await deviceInfo.androidInfo).model; + case TargetPlatform.android: + return (await deviceInfo.androidInfo).model; + case TargetPlatform.fuchsia: + return 'fuchsia'; + default: + return 'unsupported'; } break; } diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart index 75410494d29..644bbb096ea 100644 --- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart +++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart @@ -139,7 +139,7 @@ class FuchsiaDevices extends PollingDeviceDiscovery { FuchsiaDevices() : super('Fuchsia devices'); @override - bool get supportsPlatform => globals.platform.isLinux || globals.platform.isMacOS; + bool get supportsPlatform => isFuchsiaSupportedPlatform(); @override bool get canListAnything => fuchsiaWorkflow.canListDevices; @@ -442,6 +442,39 @@ class FuchsiaDevice extends Device { } } + @override + bool get supportsScreenshot => isFuchsiaSupportedPlatform(); + + @override + Future takeScreenshot(File outputFile) async { + if (outputFile.basename.split('.').last != 'ppm') { + throw '${outputFile.path} must be a .ppm file'; + } + final RunResult screencapResult = await shell('screencap > /tmp/screenshot.ppm'); + if (screencapResult.exitCode != 0) { + throw 'Could not take a screenshot on device $name:\n$screencapResult'; + } + try { + final RunResult scpResult = await scp('/tmp/screenshot.ppm', outputFile.path); + if (scpResult.exitCode != 0) { + throw 'Failed to copy screenshot from device:\n$scpResult'; + } + } finally { + try { + final RunResult deleteResult = await shell('rm /tmp/screenshot.ppm'); + if (deleteResult.exitCode != 0) { + globals.printError( + 'Failed to delete screenshot.ppm from the device:\n$deleteResult' + ); + } + } catch (_) { + globals.printError( + 'Failed to delete screenshot.ppm from the device' + ); + } + } + } + @override Future get targetPlatform async => _targetPlatform ??= await _queryTargetPlatform(); @@ -479,9 +512,6 @@ class FuchsiaDevice extends Device { @override void clearLogs() {} - @override - bool get supportsScreenshot => false; - bool get ipv6 { try { Uri.parseIPv6Address(id); @@ -545,6 +575,21 @@ class FuchsiaDevice extends Device { ]); } + /// Transfer the file [origin] from the device to [destination]. + Future scp(String origin, String destination) async { + if (fuchsiaArtifacts.sshConfig == null) { + throwToolExit('Cannot interact with device. No ssh config.\n' + 'Try setting FUCHSIA_SSH_CONFIG or FUCHSIA_BUILD_DIR.'); + } + return await processUtils.run([ + 'scp', + '-F', + fuchsiaArtifacts.sshConfig.absolute.path, + '$id:$origin', + destination, + ]); + } + /// Finds the first port running a VM matching `isolateName` from the /// provided set of `ports`. /// diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_sdk.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_sdk.dart index b5effe84b64..628e624fb7e 100644 --- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_sdk.dart +++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_sdk.dart @@ -20,6 +20,11 @@ FuchsiaSdk get fuchsiaSdk => context.get(); /// The [FuchsiaArtifacts] instance. FuchsiaArtifacts get fuchsiaArtifacts => context.get(); +/// Returns [true] if the current platform supports Fuchsia targets. +bool isFuchsiaSupportedPlatform() { + return globals.platform.isLinux || globals.platform.isMacOS; +} + /// The Fuchsia SDK shell commands. /// /// This workflow assumes development within the fuchsia source tree, @@ -110,7 +115,7 @@ class FuchsiaArtifacts { /// FUCHSIA_SSH_CONFIG) to find the ssh configuration needed to talk to /// a device. factory FuchsiaArtifacts.find() { - if (!globals.platform.isLinux && !globals.platform.isMacOS) { + if (!isFuchsiaSupportedPlatform()) { // Don't try to find the artifacts on platforms that are not supported. return FuchsiaArtifacts(); } diff --git a/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart b/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart index 6f6127e96f4..2810fcff2ab 100644 --- a/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart +++ b/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart @@ -31,6 +31,7 @@ import 'package:flutter_tools/src/vmservice.dart'; import 'package:meta/meta.dart'; import 'package:mockito/mockito.dart'; import 'package:process/process.dart'; +import 'package:platform/platform.dart'; import '../../src/common.dart'; import '../../src/context.dart'; @@ -325,6 +326,230 @@ void main() { }); }); + group('screenshot', () { + MockProcessManager mockProcessManager; + + setUp(() { + mockProcessManager = MockProcessManager(); + }); + + test('is supported on posix platforms', () { + final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester'); + expect(device.supportsScreenshot, true); + }, testOn: 'posix'); + + testUsingContext('is not supported on Windows', () { + final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester'); + expect(device.supportsScreenshot, false); + }, overrides: { + Platform: () => FakePlatform( + operatingSystem: 'windows', + ), + }); + + test('takeScreenshot throws if file isn\'t .ppm', () async { + final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester'); + await expectLater( + () => device.takeScreenshot(globals.fs.file('file.invalid')), + throwsA(equals('file.invalid must be a .ppm file')), + ); + }, testOn: 'posix'); + + testUsingContext('takeScreenshot throws if screencap failed', () async { + final FuchsiaDevice device = FuchsiaDevice('0.0.0.0', name: 'tester'); + + when(mockProcessManager.run( + const [ + 'ssh', + '-F', + '/fuchsia/out/default/.ssh', + '0.0.0.0', + 'screencap > /tmp/screenshot.ppm', + ], + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment'), + )).thenAnswer((_) async => ProcessResult(0, 1, '', '')); + + await expectLater( + () => device.takeScreenshot(globals.fs.file('file.ppm')), + throwsA(equals('Could not take a screenshot on device tester:\n')), + ); + }, overrides: { + ProcessManager: () => mockProcessManager, + Platform: () => FakePlatform( + environment: { + 'FUCHSIA_SSH_CONFIG': '/fuchsia/out/default/.ssh', + }, + operatingSystem: 'linux', + ), + }, testOn: 'posix'); + + testUsingContext('takeScreenshot throws if scp failed', () async { + final FuchsiaDevice device = FuchsiaDevice('0.0.0.0', name: 'tester'); + + when(mockProcessManager.run( + const [ + 'ssh', + '-F', + '/fuchsia/out/default/.ssh', + '0.0.0.0', + 'screencap > /tmp/screenshot.ppm', + ], + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment'), + )).thenAnswer((_) async => ProcessResult(0, 0, '', '')); + + when(mockProcessManager.run( + const [ + 'scp', + '-F', + '/fuchsia/out/default/.ssh', + '0.0.0.0:/tmp/screenshot.ppm', + 'file.ppm', + ], + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment'), + )).thenAnswer((_) async => ProcessResult(0, 1, '', '')); + + when(mockProcessManager.run( + const [ + 'ssh', + '-F', + '/fuchsia/out/default/.ssh', + '0.0.0.0', + 'rm /tmp/screenshot.ppm', + ], + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment'), + )).thenAnswer((_) async => ProcessResult(0, 0, '', '')); + + await expectLater( + () => device.takeScreenshot(globals.fs.file('file.ppm')), + throwsA(equals('Failed to copy screenshot from device:\n')), + ); + }, overrides: { + ProcessManager: () => mockProcessManager, + Platform: () => FakePlatform( + environment: { + 'FUCHSIA_SSH_CONFIG': '/fuchsia/out/default/.ssh', + }, + operatingSystem: 'linux', + ), + }, testOn: 'posix'); + + testUsingContext('takeScreenshot prints error if can\'t delete file from device', () async { + final FuchsiaDevice device = FuchsiaDevice('0.0.0.0', name: 'tester'); + + when(mockProcessManager.run( + const [ + 'ssh', + '-F', + '/fuchsia/out/default/.ssh', + '0.0.0.0', + 'screencap > /tmp/screenshot.ppm', + ], + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment'), + )).thenAnswer((_) async => ProcessResult(0, 0, '', '')); + + when(mockProcessManager.run( + const [ + 'scp', + '-F', + '/fuchsia/out/default/.ssh', + '0.0.0.0:/tmp/screenshot.ppm', + 'file.ppm', + ], + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment'), + )).thenAnswer((_) async => ProcessResult(0, 0, '', '')); + + when(mockProcessManager.run( + const [ + 'ssh', + '-F', + '/fuchsia/out/default/.ssh', + '0.0.0.0', + 'rm /tmp/screenshot.ppm', + ], + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment'), + )).thenAnswer((_) async => ProcessResult(0, 1, '', '')); + + try { + await device.takeScreenshot(globals.fs.file('file.ppm')); + } catch (_) { + assert(false); + } + expect( + testLogger.errorText, + contains('Failed to delete screenshot.ppm from the device:\n'), + ); + }, overrides: { + ProcessManager: () => mockProcessManager, + Platform: () => FakePlatform( + environment: { + 'FUCHSIA_SSH_CONFIG': '/fuchsia/out/default/.ssh', + }, + operatingSystem: 'linux', + ), + }, testOn: 'posix'); + + testUsingContext('takeScreenshot returns', () async { + final FuchsiaDevice device = FuchsiaDevice('0.0.0.0', name: 'tester'); + + when(mockProcessManager.run( + const [ + 'ssh', + '-F', + '/fuchsia/out/default/.ssh', + '0.0.0.0', + 'screencap > /tmp/screenshot.ppm', + ], + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment'), + )).thenAnswer((_) async => ProcessResult(0, 0, '', '')); + + when(mockProcessManager.run( + const [ + 'scp', + '-F', + '/fuchsia/out/default/.ssh', + '0.0.0.0:/tmp/screenshot.ppm', + 'file.ppm', + ], + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment'), + )).thenAnswer((_) async => ProcessResult(0, 0, '', '')); + + when(mockProcessManager.run( + const [ + 'ssh', + '-F', + '/fuchsia/out/default/.ssh', + '0.0.0.0', + 'rm /tmp/screenshot.ppm', + ], + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment'), + )).thenAnswer((_) async => ProcessResult(0, 0, '', '')); + + try { + await device.takeScreenshot(globals.fs.file('file.ppm')); + } catch (_) { + assert(false); + } + }, overrides: { + ProcessManager: () => mockProcessManager, + Platform: () => FakePlatform( + environment: { + 'FUCHSIA_SSH_CONFIG': '/fuchsia/out/default/.ssh', + }, + operatingSystem: 'linux', + ), + }, testOn: 'posix'); + }); + group(FuchsiaIsolateDiscoveryProtocol, () { MockPortForwarder portForwarder; MockVMService vmService;