Test hot reload targeting a Fuchsia device (#48472)
This commit is contained in:
parent
8b139a8c41
commit
fca5917519
237
dev/devicelab/bin/tasks/flutter_attach_test_fuchsia.dart
Normal file
237
dev/devicelab/bin/tasks/flutter_attach_test_fuchsia.dart
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
// 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 'dart:math';
|
||||||
|
|
||||||
|
import 'package:path/path.dart' as path;
|
||||||
|
import 'package:flutter_devicelab/framework/adb.dart';
|
||||||
|
import 'package:flutter_devicelab/framework/framework.dart';
|
||||||
|
import 'package:flutter_devicelab/framework/utils.dart';
|
||||||
|
|
||||||
|
void generateMain(Directory appDir, String sentinel) {
|
||||||
|
final String mainCode = '''
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_driver/driver_extension.dart';
|
||||||
|
|
||||||
|
class ReassembleListener extends StatefulWidget {
|
||||||
|
const ReassembleListener({Key key, this.child})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ReassembleListenerState createState() => _ReassembleListenerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ReassembleListenerState extends State<ReassembleListener> {
|
||||||
|
@override
|
||||||
|
initState() {
|
||||||
|
super.initState();
|
||||||
|
print('$sentinel');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void reassemble() {
|
||||||
|
super.reassemble();
|
||||||
|
print('$sentinel');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return widget.child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
runApp(
|
||||||
|
ReassembleListener(
|
||||||
|
child: Text(
|
||||||
|
'Hello, word!',
|
||||||
|
textDirection: TextDirection.rtl,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
File(path.join(appDir.path, 'lib', 'fuchsia_main.dart'))
|
||||||
|
.writeAsStringSync(mainCode, flush: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
deviceOperatingSystem = DeviceOperatingSystem.fuchsia;
|
||||||
|
|
||||||
|
task(() async {
|
||||||
|
section('Checking environment variables');
|
||||||
|
|
||||||
|
if (Platform.environment['FUCHSIA_SSH_CONFIG'] == null &&
|
||||||
|
Platform.environment['FUCHSIA_BUILD_DIR'] == null) {
|
||||||
|
throw Exception('No FUCHSIA_SSH_CONFIG or FUCHSIA_BUILD_DIR set');
|
||||||
|
}
|
||||||
|
|
||||||
|
final String flutterBinary = path.join(flutterDirectory.path, 'bin', 'flutter');
|
||||||
|
|
||||||
|
section('Downloading Fuchsia SDK and flutter runner');
|
||||||
|
|
||||||
|
// Download the Fuchsia SDK.
|
||||||
|
final int precacheResult = await exec(
|
||||||
|
flutterBinary,
|
||||||
|
<String>[
|
||||||
|
'precache',
|
||||||
|
'--fuchsia',
|
||||||
|
'--flutter_runner',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (precacheResult != 0) {
|
||||||
|
throw Exception('flutter precache failed with exit code $precacheResult');
|
||||||
|
}
|
||||||
|
|
||||||
|
final Directory fuchsiaToolDirectory =
|
||||||
|
Directory(path.join(flutterDirectory.path, 'bin', 'cache', 'artifacts', 'fuchsia', 'tools'));
|
||||||
|
if (!fuchsiaToolDirectory.existsSync()) {
|
||||||
|
throw Exception('Expected Fuchsia tool directory at ${fuchsiaToolDirectory.path}');
|
||||||
|
}
|
||||||
|
|
||||||
|
final Device device = await devices.workingDevice;
|
||||||
|
final Directory appDir = dir(path.join(
|
||||||
|
flutterDirectory.path,
|
||||||
|
'dev',
|
||||||
|
'integration_tests',
|
||||||
|
'ui',
|
||||||
|
));
|
||||||
|
|
||||||
|
await inDirectory(appDir, () async {
|
||||||
|
final Random random = Random();
|
||||||
|
final Map<String, Completer<void>> sentinelMessage = <String, Completer<void>>{
|
||||||
|
'sentinel-${random.nextInt(1<<32)}': Completer<void>(),
|
||||||
|
'sentinel-${random.nextInt(1<<32)}': Completer<void>(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Process runProcess;
|
||||||
|
Process logsProcess;
|
||||||
|
|
||||||
|
try {
|
||||||
|
section('Creating lib/fuchsia_main.dart');
|
||||||
|
|
||||||
|
generateMain(appDir, sentinelMessage.keys.toList()[0]);
|
||||||
|
|
||||||
|
section('Launching `flutter run` in ${appDir.path}');
|
||||||
|
|
||||||
|
runProcess = await startProcess(
|
||||||
|
flutterBinary,
|
||||||
|
<String>[
|
||||||
|
'run',
|
||||||
|
'--suppress-analytics',
|
||||||
|
'-d', device.deviceId,
|
||||||
|
'-t', 'lib/fuchsia_main.dart',
|
||||||
|
],
|
||||||
|
isBot: false, // We just want to test the output, not have any debugging info.
|
||||||
|
);
|
||||||
|
|
||||||
|
logsProcess = await startProcess(
|
||||||
|
flutterBinary,
|
||||||
|
<String>['logs', '--suppress-analytics', '-d', device.deviceId],
|
||||||
|
isBot: false, // We just want to test the output, not have any debugging info.
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<dynamic> eventOrExit(Future<void> event) {
|
||||||
|
return Future.any<dynamic>(<Future<dynamic>>[
|
||||||
|
event,
|
||||||
|
runProcess.exitCode,
|
||||||
|
logsProcess.exitCode,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
logsProcess.stdout
|
||||||
|
.transform<String>(utf8.decoder)
|
||||||
|
.transform<String>(const LineSplitter())
|
||||||
|
.listen((String log) {
|
||||||
|
print('logs:stdout: $log');
|
||||||
|
for (final String sentinel in sentinelMessage.keys) {
|
||||||
|
if (log.contains(sentinel)) {
|
||||||
|
if (sentinelMessage[sentinel].isCompleted) {
|
||||||
|
throw Exception(
|
||||||
|
'Expected a single `$sentinel` message in the device log, but found more than one'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
sentinelMessage[sentinel].complete();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final Completer<void> hotReloadCompleter = Completer<void>();
|
||||||
|
final Completer<void> reloadedCompleter = Completer<void>();
|
||||||
|
final RegExp observatoryRegexp = RegExp('An Observatory debugger and profiler on .+ is available at');
|
||||||
|
runProcess.stdout
|
||||||
|
.transform<String>(utf8.decoder)
|
||||||
|
.transform<String>(const LineSplitter())
|
||||||
|
.listen((String line) {
|
||||||
|
print('run:stdout: $line');
|
||||||
|
if (observatoryRegexp.hasMatch(line)) {
|
||||||
|
hotReloadCompleter.complete();
|
||||||
|
} else if (line.contains('Reloaded')) {
|
||||||
|
reloadedCompleter.complete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<String> runStderr = <String>[];
|
||||||
|
runProcess.stderr
|
||||||
|
.transform<String>(utf8.decoder)
|
||||||
|
.transform<String>(const LineSplitter())
|
||||||
|
.listen((String line) {
|
||||||
|
runStderr.add(line);
|
||||||
|
print('run:stderr: $line');
|
||||||
|
});
|
||||||
|
|
||||||
|
section('Waiting for hot reload availability');
|
||||||
|
await eventOrExit(hotReloadCompleter.future);
|
||||||
|
|
||||||
|
section('Waiting for Dart VM');
|
||||||
|
// Wait for the first message in the log from the Dart VM.
|
||||||
|
await eventOrExit(sentinelMessage.values.toList()[0].future);
|
||||||
|
|
||||||
|
// Change the dart file.
|
||||||
|
generateMain(appDir, sentinelMessage.keys.toList()[1]);
|
||||||
|
|
||||||
|
section('Hot reload');
|
||||||
|
runProcess.stdin.write('r');
|
||||||
|
runProcess.stdin.flush();
|
||||||
|
await eventOrExit(reloadedCompleter.future);
|
||||||
|
|
||||||
|
section('Waiting for Dart VM');
|
||||||
|
// Wait for the second message in the log from the Dart VM.
|
||||||
|
await eventOrExit(sentinelMessage.values.toList()[1].future);
|
||||||
|
|
||||||
|
section('Quitting flutter run');
|
||||||
|
|
||||||
|
runProcess.stdin.write('q');
|
||||||
|
runProcess.stdin.flush();
|
||||||
|
|
||||||
|
final int runExitCode = await runProcess.exitCode;
|
||||||
|
if (runExitCode != 0 || runStderr.isNotEmpty) {
|
||||||
|
throw Exception(
|
||||||
|
'flutter run exited with code $runExitCode and errors: ${runStderr.join('\n')}.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
runProcess.kill();
|
||||||
|
logsProcess.kill();
|
||||||
|
File(path.join(appDir.path, 'lib', 'fuchsia_main.dart')).deleteSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final String sentinel in sentinelMessage.keys) {
|
||||||
|
if (!sentinelMessage[sentinel].isCompleted) {
|
||||||
|
throw Exception('Expected $sentinel in the device logs.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return TaskResult.success(null);
|
||||||
|
});
|
||||||
|
}
|
@ -12,11 +12,22 @@ import 'package:path/path.dart' as path;
|
|||||||
|
|
||||||
import 'utils.dart';
|
import 'utils.dart';
|
||||||
|
|
||||||
|
|
||||||
|
/// Gets the artifact path relative to the current directory.
|
||||||
|
String getArtifactPath() {
|
||||||
|
return path.normalize(
|
||||||
|
path.join(
|
||||||
|
path.current,
|
||||||
|
'../../bin/cache/artifacts',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// The root of the API for controlling devices.
|
/// The root of the API for controlling devices.
|
||||||
DeviceDiscovery get devices => DeviceDiscovery();
|
DeviceDiscovery get devices => DeviceDiscovery();
|
||||||
|
|
||||||
/// Device operating system the test is configured to test.
|
/// Device operating system the test is configured to test.
|
||||||
enum DeviceOperatingSystem { android, ios }
|
enum DeviceOperatingSystem { android, ios, fuchsia }
|
||||||
|
|
||||||
/// Device OS to test on.
|
/// Device OS to test on.
|
||||||
DeviceOperatingSystem deviceOperatingSystem = DeviceOperatingSystem.android;
|
DeviceOperatingSystem deviceOperatingSystem = DeviceOperatingSystem.android;
|
||||||
@ -29,6 +40,8 @@ abstract class DeviceDiscovery {
|
|||||||
return AndroidDeviceDiscovery();
|
return AndroidDeviceDiscovery();
|
||||||
case DeviceOperatingSystem.ios:
|
case DeviceOperatingSystem.ios:
|
||||||
return IosDeviceDiscovery();
|
return IosDeviceDiscovery();
|
||||||
|
case DeviceOperatingSystem.fuchsia:
|
||||||
|
return FuchsiaDeviceDiscovery();
|
||||||
default:
|
default:
|
||||||
throw StateError('Unsupported device operating system: {config.deviceOperatingSystem}');
|
throw StateError('Unsupported device operating system: {config.deviceOperatingSystem}');
|
||||||
}
|
}
|
||||||
@ -198,6 +211,91 @@ class AndroidDeviceDiscovery implements DeviceDiscovery {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class FuchsiaDeviceDiscovery implements DeviceDiscovery {
|
||||||
|
factory FuchsiaDeviceDiscovery() {
|
||||||
|
return _instance ??= FuchsiaDeviceDiscovery._();
|
||||||
|
}
|
||||||
|
|
||||||
|
FuchsiaDeviceDiscovery._();
|
||||||
|
|
||||||
|
static FuchsiaDeviceDiscovery _instance;
|
||||||
|
|
||||||
|
FuchsiaDevice _workingDevice;
|
||||||
|
|
||||||
|
String get _devFinder {
|
||||||
|
final String devFinder = path.join(getArtifactPath(), 'fuchsia', 'tools', 'dev_finder');
|
||||||
|
if (!File(devFinder).existsSync()) {
|
||||||
|
throw FileSystemException('Couldn\'t find dev_finder at location $devFinder');
|
||||||
|
}
|
||||||
|
return devFinder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<FuchsiaDevice> get workingDevice async {
|
||||||
|
if (_workingDevice == null) {
|
||||||
|
await chooseWorkingDevice();
|
||||||
|
}
|
||||||
|
return _workingDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Picks the first connected Fuchsia device.
|
||||||
|
@override
|
||||||
|
Future<void> chooseWorkingDevice() async {
|
||||||
|
final List<FuchsiaDevice> allDevices = (await discoverDevices())
|
||||||
|
.map<FuchsiaDevice>((String id) => FuchsiaDevice(deviceId: id))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (allDevices.isEmpty) {
|
||||||
|
throw Exception('No Fuchsia devices detected');
|
||||||
|
}
|
||||||
|
_workingDevice = allDevices.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<String>> discoverDevices() async {
|
||||||
|
final List<String> output = (await eval(_devFinder, <String>['list', '-full']))
|
||||||
|
.trim()
|
||||||
|
.split('\n');
|
||||||
|
|
||||||
|
final List<String> devices = <String>[];
|
||||||
|
for (final String line in output) {
|
||||||
|
final List<String> parts = line.split(' ');
|
||||||
|
assert(parts.length == 2);
|
||||||
|
devices.add(parts.last); // The device id.
|
||||||
|
}
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, HealthCheckResult>> checkDevices() async {
|
||||||
|
final Map<String, HealthCheckResult> results = <String, HealthCheckResult>{};
|
||||||
|
for (final String deviceId in await discoverDevices()) {
|
||||||
|
try {
|
||||||
|
final int resolveResult = await exec(
|
||||||
|
_devFinder,
|
||||||
|
<String>[
|
||||||
|
'resolve',
|
||||||
|
'-device-limit',
|
||||||
|
'1',
|
||||||
|
deviceId,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
if (resolveResult == 0) {
|
||||||
|
results['fuchsia-device-$deviceId'] = HealthCheckResult.success();
|
||||||
|
} else {
|
||||||
|
results['fuchsia-device-$deviceId'] = HealthCheckResult.failure('Cannot resolve device $deviceId');
|
||||||
|
}
|
||||||
|
} catch (error, stacktrace) {
|
||||||
|
results['fuchsia-device-$deviceId'] = HealthCheckResult.error(error, stacktrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> performPreflightTasks() async {}
|
||||||
|
}
|
||||||
|
|
||||||
class AndroidDevice implements Device {
|
class AndroidDevice implements Device {
|
||||||
AndroidDevice({@required this.deviceId});
|
AndroidDevice({@required this.deviceId});
|
||||||
|
|
||||||
@ -392,16 +490,6 @@ class IosDeviceDiscovery implements DeviceDiscovery {
|
|||||||
_workingDevice = allDevices[math.Random().nextInt(allDevices.length)];
|
_workingDevice = allDevices[math.Random().nextInt(allDevices.length)];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the path to cached binaries relative to devicelab directory
|
|
||||||
String get _artifactDirPath {
|
|
||||||
return path.normalize(
|
|
||||||
path.join(
|
|
||||||
path.current,
|
|
||||||
'../../bin/cache/artifacts',
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a colon-separated environment variable that contains the paths
|
// Returns a colon-separated environment variable that contains the paths
|
||||||
// of linked libraries for idevice_id
|
// of linked libraries for idevice_id
|
||||||
Map<String, String> get _ideviceIdEnvironment {
|
Map<String, String> get _ideviceIdEnvironment {
|
||||||
@ -413,13 +501,13 @@ class IosDeviceDiscovery implements DeviceDiscovery {
|
|||||||
'ideviceinstaller',
|
'ideviceinstaller',
|
||||||
'ios-deploy',
|
'ios-deploy',
|
||||||
'libzip',
|
'libzip',
|
||||||
].map((String packageName) => path.join(_artifactDirPath, packageName)).join(':');
|
].map((String packageName) => path.join(getArtifactPath(), packageName)).join(':');
|
||||||
return <String, String>{'DYLD_LIBRARY_PATH': libPath};
|
return <String, String>{'DYLD_LIBRARY_PATH': libPath};
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<String>> discoverDevices() async {
|
Future<List<String>> discoverDevices() async {
|
||||||
final String ideviceIdPath = path.join(_artifactDirPath, 'libimobiledevice', 'idevice_id');
|
final String ideviceIdPath = path.join(getArtifactPath(), 'libimobiledevice', 'idevice_id');
|
||||||
final List<String> iosDeviceIDs = LineSplitter.split(await eval(ideviceIdPath, <String>['-l'], environment: _ideviceIdEnvironment))
|
final List<String> iosDeviceIDs = LineSplitter.split(await eval(ideviceIdPath, <String>['-l'], environment: _ideviceIdEnvironment))
|
||||||
.map<String>((String line) => line.trim())
|
.map<String>((String line) => line.trim())
|
||||||
.where((String line) => line.isNotEmpty)
|
.where((String line) => line.isNotEmpty)
|
||||||
@ -494,6 +582,49 @@ class IosDevice implements Device {
|
|||||||
Future<void> stop(String packageName) async {}
|
Future<void> stop(String packageName) async {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fuchsia device.
|
||||||
|
class FuchsiaDevice implements Device {
|
||||||
|
const FuchsiaDevice({ @required this.deviceId });
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String deviceId;
|
||||||
|
|
||||||
|
// TODO(egarciad): Implement these for Fuchsia.
|
||||||
|
@override
|
||||||
|
Future<bool> isAwake() async => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> isAsleep() async => false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> wakeUp() async {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> sendToSleep() async {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> togglePower() async {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> unlock() async {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> tap(int x, int y) async {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> stop(String packageName) async {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, dynamic>> getMemoryStats(String packageName) async {
|
||||||
|
throw 'Not implemented';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<String> get logcat {
|
||||||
|
throw 'Not implemented';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Path to the `adb` executable.
|
/// Path to the `adb` executable.
|
||||||
String get adbPath {
|
String get adbPath {
|
||||||
final String androidHome = Platform.environment['ANDROID_HOME'] ?? Platform.environment['ANDROID_SDK_ROOT'];
|
final String androidHome = Platform.environment['ANDROID_HOME'] ?? Platform.environment['ANDROID_SDK_ROOT'];
|
||||||
|
@ -380,6 +380,8 @@ class CompileTest {
|
|||||||
case DeviceOperatingSystem.android:
|
case DeviceOperatingSystem.android:
|
||||||
options.add('android-arm');
|
options.add('android-arm');
|
||||||
break;
|
break;
|
||||||
|
case DeviceOperatingSystem.fuchsia:
|
||||||
|
throw Exception('Unsupported option for Fuchsia devices');
|
||||||
}
|
}
|
||||||
final String compileLog = await evalFlutter('build', options: options);
|
final String compileLog = await evalFlutter('build', options: options);
|
||||||
watch.stop();
|
watch.stop();
|
||||||
@ -434,6 +436,8 @@ class CompileTest {
|
|||||||
if (reportPackageContentSizes)
|
if (reportPackageContentSizes)
|
||||||
metrics.addAll(await getSizesFromApk(apkPath));
|
metrics.addAll(await getSizesFromApk(apkPath));
|
||||||
break;
|
break;
|
||||||
|
case DeviceOperatingSystem.fuchsia:
|
||||||
|
throw Exception('Unsupported option for Fuchsia devices');
|
||||||
}
|
}
|
||||||
|
|
||||||
metrics.addAll(<String, dynamic>{
|
metrics.addAll(<String, dynamic>{
|
||||||
@ -456,6 +460,8 @@ class CompileTest {
|
|||||||
options.insert(0, 'apk');
|
options.insert(0, 'apk');
|
||||||
options.add('--target-platform=android-arm');
|
options.add('--target-platform=android-arm');
|
||||||
break;
|
break;
|
||||||
|
case DeviceOperatingSystem.fuchsia:
|
||||||
|
throw Exception('Unsupported option for Fuchsia devices');
|
||||||
}
|
}
|
||||||
watch.start();
|
watch.start();
|
||||||
await flutter('build', options: options);
|
await flutter('build', options: options);
|
||||||
|
@ -340,7 +340,7 @@ tasks:
|
|||||||
# required_agent_capabilities: ["linux/android"]
|
# required_agent_capabilities: ["linux/android"]
|
||||||
# flaky: true
|
# flaky: true
|
||||||
|
|
||||||
flutter_attach_test:
|
flutter_attach_test_android:
|
||||||
description: >
|
description: >
|
||||||
Tests the `flutter attach` command.
|
Tests the `flutter attach` command.
|
||||||
stage: devicelab
|
stage: devicelab
|
||||||
|
22
dev/integration_tests/ui/fuchsia/meta/integration_ui.cmx
Normal file
22
dev/integration_tests/ui/fuchsia/meta/integration_ui.cmx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"program": {
|
||||||
|
"data": "data/integration_ui"
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -218,7 +218,7 @@ class AttachCommand extends FlutterCommand {
|
|||||||
if (module == null) {
|
if (module == null) {
|
||||||
throwToolExit('\'--module\' is required for attaching to a Fuchsia device');
|
throwToolExit('\'--module\' is required for attaching to a Fuchsia device');
|
||||||
}
|
}
|
||||||
usesIpv6 = await device.ipv6;
|
usesIpv6 = device.ipv6;
|
||||||
FuchsiaIsolateDiscoveryProtocol isolateDiscoveryProtocol;
|
FuchsiaIsolateDiscoveryProtocol isolateDiscoveryProtocol;
|
||||||
try {
|
try {
|
||||||
isolateDiscoveryProtocol = device.getIsolateDiscoveryProtocol(module);
|
isolateDiscoveryProtocol = device.getIsolateDiscoveryProtocol(module);
|
||||||
|
@ -153,7 +153,7 @@ class FuchsiaDevices extends PollingDeviceDiscovery {
|
|||||||
if (text == null || text.isEmpty) {
|
if (text == null || text.isEmpty) {
|
||||||
return <Device>[];
|
return <Device>[];
|
||||||
}
|
}
|
||||||
final List<FuchsiaDevice> devices = parseListDevices(text);
|
final List<FuchsiaDevice> devices = await parseListDevices(text);
|
||||||
return devices;
|
return devices;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,7 +162,7 @@ class FuchsiaDevices extends PollingDeviceDiscovery {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
List<FuchsiaDevice> parseListDevices(String text) {
|
Future<List<FuchsiaDevice>> parseListDevices(String text) async {
|
||||||
final List<FuchsiaDevice> devices = <FuchsiaDevice>[];
|
final List<FuchsiaDevice> devices = <FuchsiaDevice>[];
|
||||||
for (final String rawLine in text.trim().split('\n')) {
|
for (final String rawLine in text.trim().split('\n')) {
|
||||||
final String line = rawLine.trim();
|
final String line = rawLine.trim();
|
||||||
@ -172,8 +172,15 @@ List<FuchsiaDevice> parseListDevices(String text) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final String name = words[1];
|
final String name = words[1];
|
||||||
final String id = words[0];
|
final String resolvedHost = await fuchsiaSdk.fuchsiaDevFinder.resolve(
|
||||||
devices.add(FuchsiaDevice(id, name: name));
|
name,
|
||||||
|
local: false,
|
||||||
|
);
|
||||||
|
if (resolvedHost == null) {
|
||||||
|
globals.printError('Failed to resolve host for Fuchsia device `$name`');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
devices.add(FuchsiaDevice(resolvedHost, name: name));
|
||||||
}
|
}
|
||||||
return devices;
|
return devices;
|
||||||
}
|
}
|
||||||
@ -240,7 +247,6 @@ class FuchsiaDevice extends Device {
|
|||||||
}
|
}
|
||||||
// Stop the app if it's currently running.
|
// Stop the app if it's currently running.
|
||||||
await stopApp(package);
|
await stopApp(package);
|
||||||
// Find out who the device thinks we are.
|
|
||||||
final String host = await fuchsiaSdk.fuchsiaDevFinder.resolve(
|
final String host = await fuchsiaSdk.fuchsiaDevFinder.resolve(
|
||||||
name,
|
name,
|
||||||
local: true,
|
local: true,
|
||||||
@ -249,6 +255,7 @@ class FuchsiaDevice extends Device {
|
|||||||
globals.printError('Failed to resolve host for Fuchsia device');
|
globals.printError('Failed to resolve host for Fuchsia device');
|
||||||
return LaunchResult.failed();
|
return LaunchResult.failed();
|
||||||
}
|
}
|
||||||
|
// Find out who the device thinks we are.
|
||||||
final int port = await os.findFreePort();
|
final int port = await os.findFreePort();
|
||||||
if (port == 0) {
|
if (port == 0) {
|
||||||
globals.printError('Failed to find a free port');
|
globals.printError('Failed to find a free port');
|
||||||
@ -475,11 +482,9 @@ class FuchsiaDevice extends Device {
|
|||||||
@override
|
@override
|
||||||
bool get supportsScreenshot => false;
|
bool get supportsScreenshot => false;
|
||||||
|
|
||||||
Future<bool> get ipv6 async {
|
bool get ipv6 {
|
||||||
// Workaround for https://github.com/dart-lang/sdk/issues/29456
|
|
||||||
final String fragment = (await _resolvedIp).split('%').first;
|
|
||||||
try {
|
try {
|
||||||
Uri.parseIPv6Address(fragment);
|
Uri.parseIPv6Address(id);
|
||||||
return true;
|
return true;
|
||||||
} on FormatException {
|
} on FormatException {
|
||||||
return false;
|
return false;
|
||||||
@ -525,15 +530,6 @@ class FuchsiaDevice extends Device {
|
|||||||
return ports;
|
return ports;
|
||||||
}
|
}
|
||||||
|
|
||||||
String _cachedResolvedIp;
|
|
||||||
|
|
||||||
Future<String> get _resolvedIp async {
|
|
||||||
return _cachedResolvedIp ??= await fuchsiaSdk.fuchsiaDevFinder.resolve(
|
|
||||||
name,
|
|
||||||
local: false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Run `command` on the Fuchsia device shell.
|
/// Run `command` on the Fuchsia device shell.
|
||||||
Future<RunResult> shell(String command) async {
|
Future<RunResult> shell(String command) async {
|
||||||
if (fuchsiaArtifacts.sshConfig == null) {
|
if (fuchsiaArtifacts.sshConfig == null) {
|
||||||
@ -544,7 +540,7 @@ class FuchsiaDevice extends Device {
|
|||||||
'ssh',
|
'ssh',
|
||||||
'-F',
|
'-F',
|
||||||
fuchsiaArtifacts.sshConfig.absolute.path,
|
fuchsiaArtifacts.sshConfig.absolute.path,
|
||||||
await _resolvedIp,
|
id, // Device's IP address.
|
||||||
command,
|
command,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -670,7 +666,7 @@ class FuchsiaIsolateDiscoveryProtocol {
|
|||||||
}
|
}
|
||||||
final Uri address = flutterView.owner.vmService.httpAddress;
|
final Uri address = flutterView.owner.vmService.httpAddress;
|
||||||
if (flutterView.uiIsolate.name.contains(_isolateName)) {
|
if (flutterView.uiIsolate.name.contains(_isolateName)) {
|
||||||
_foundUri.complete(await _device.ipv6
|
_foundUri.complete(_device.ipv6
|
||||||
? Uri.parse('http://[$_ipv6Loopback]:${address.port}/')
|
? Uri.parse('http://[$_ipv6Loopback]:${address.port}/')
|
||||||
: Uri.parse('http://$_ipv4Loopback:${address.port}/'));
|
: Uri.parse('http://$_ipv4Loopback:${address.port}/'));
|
||||||
_status.stop();
|
_status.stop();
|
||||||
@ -711,7 +707,7 @@ class _FuchsiaPortForwarder extends DevicePortForwarder {
|
|||||||
'-f',
|
'-f',
|
||||||
'-L',
|
'-L',
|
||||||
'$hostPort:$_ipv4Loopback:$devicePort',
|
'$hostPort:$_ipv4Loopback:$devicePort',
|
||||||
await device._resolvedIp,
|
device.id, // Device's IP address.
|
||||||
'true',
|
'true',
|
||||||
];
|
];
|
||||||
final Process process = await globals.processManager.start(command);
|
final Process process = await globals.processManager.start(command);
|
||||||
@ -743,7 +739,7 @@ class _FuchsiaPortForwarder extends DevicePortForwarder {
|
|||||||
'-vvv',
|
'-vvv',
|
||||||
'-L',
|
'-L',
|
||||||
'${forwardedPort.hostPort}:$_ipv4Loopback:${forwardedPort.devicePort}',
|
'${forwardedPort.hostPort}:$_ipv4Loopback:${forwardedPort.devicePort}',
|
||||||
await device._resolvedIp,
|
device.id, // Device's IP address.
|
||||||
];
|
];
|
||||||
final ProcessResult result = await globals.processManager.run(command);
|
final ProcessResult result = await globals.processManager.run(command);
|
||||||
if (result.exitCode != 0) {
|
if (result.exitCode != 0) {
|
||||||
@ -753,7 +749,9 @@ class _FuchsiaPortForwarder extends DevicePortForwarder {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
for (final ForwardedPort port in forwardedPorts) {
|
final List<ForwardedPort> forwardedPortsCopy =
|
||||||
|
List<ForwardedPort>.from(forwardedPorts);
|
||||||
|
for (final ForwardedPort port in forwardedPortsCopy) {
|
||||||
await unforward(port);
|
await unforward(port);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,8 @@ class FuchsiaSdk {
|
|||||||
return devices.isNotEmpty ? devices[0] : null;
|
return devices.isNotEmpty ? devices[0] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the fuchsia system logs for an attached device.
|
/// Returns the fuchsia system logs for an attached device where
|
||||||
|
/// [id] is the IP address of the device.
|
||||||
Stream<String> syslogs(String id) {
|
Stream<String> syslogs(String id) {
|
||||||
Process process;
|
Process process;
|
||||||
try {
|
try {
|
||||||
@ -72,7 +73,7 @@ class FuchsiaSdk {
|
|||||||
'ssh',
|
'ssh',
|
||||||
'-F',
|
'-F',
|
||||||
fuchsiaArtifacts.sshConfig.absolute.path,
|
fuchsiaArtifacts.sshConfig.absolute.path,
|
||||||
id,
|
id, // The device's IP.
|
||||||
remoteCommand,
|
remoteCommand,
|
||||||
];
|
];
|
||||||
globals.processManager.start(cmd).then((Process newProcess) {
|
globals.processManager.start(cmd).then((Process newProcess) {
|
||||||
|
@ -55,20 +55,24 @@ void main() {
|
|||||||
expect(device.name, name);
|
expect(device.name, name);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('parse dev_finder output', () {
|
testUsingContext('parse dev_finder output', () async {
|
||||||
const String example = '192.168.42.56 paper-pulp-bush-angel';
|
const String example = '2001:0db8:85a3:0000:0000:8a2e:0370:7334 paper-pulp-bush-angel';
|
||||||
final List<FuchsiaDevice> names = parseListDevices(example);
|
final List<FuchsiaDevice> names = await parseListDevices(example);
|
||||||
|
|
||||||
expect(names.length, 1);
|
expect(names.length, 1);
|
||||||
expect(names.first.name, 'paper-pulp-bush-angel');
|
expect(names.first.name, 'paper-pulp-bush-angel');
|
||||||
expect(names.first.id, '192.168.42.56');
|
expect(names.first.id, '192.168.42.10');
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
FuchsiaSdk: () => MockFuchsiaSdk(),
|
||||||
});
|
});
|
||||||
|
|
||||||
test('parse junk dev_finder output', () {
|
testUsingContext('parse junk dev_finder output', () async {
|
||||||
const String example = 'junk';
|
const String example = 'junk';
|
||||||
final List<FuchsiaDevice> names = parseListDevices(example);
|
final List<FuchsiaDevice> names = await parseListDevices(example);
|
||||||
|
|
||||||
expect(names.length, 0);
|
expect(names.length, 0);
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
FuchsiaSdk: () => MockFuchsiaSdk(),
|
||||||
});
|
});
|
||||||
|
|
||||||
testUsingContext('disposing device disposes the portForwarder', () async {
|
testUsingContext('disposing device disposes the portForwarder', () async {
|
||||||
@ -773,7 +777,7 @@ class MockFuchsiaDevice extends Mock implements FuchsiaDevice {
|
|||||||
final bool _ipv6;
|
final bool _ipv6;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> get ipv6 async => _ipv6;
|
bool get ipv6 => _ipv6;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final String id;
|
final String id;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user