| // Copyright 2018 The Chromium 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 'package:meta/meta.dart'; |
| |
| import '../application_package.dart'; |
| import '../artifacts.dart'; |
| import '../base/common.dart'; |
| import '../base/file_system.dart'; |
| import '../base/io.dart'; |
| import '../base/process_manager.dart'; |
| import '../build_info.dart'; |
| import '../bundle.dart' as bundle; |
| import '../dart/package_map.dart'; |
| import '../device.dart'; |
| import '../globals.dart'; |
| import '../protocol_discovery.dart'; |
| import '../version.dart'; |
| |
| class FlutterTesterApp extends ApplicationPackage { |
| final Directory _directory; |
| |
| factory FlutterTesterApp.fromCurrentDirectory() { |
| return FlutterTesterApp._(fs.currentDirectory); |
| } |
| |
| FlutterTesterApp._(Directory directory) |
| : _directory = directory, |
| super(id: directory.path); |
| |
| @override |
| String get name => _directory.basename; |
| |
| @override |
| File get packagesFile => _directory.childFile('.packages'); |
| } |
| |
| // TODO(scheglov): This device does not currently work with full restarts. |
| class FlutterTesterDevice extends Device { |
| FlutterTesterDevice(String deviceId) : super(deviceId); |
| |
| Process _process; |
| final DevicePortForwarder _portForwarder = _NoopPortForwarder(); |
| |
| @override |
| Future<bool> get isLocalEmulator async => false; |
| |
| @override |
| String get name => 'Flutter test device'; |
| |
| @override |
| DevicePortForwarder get portForwarder => _portForwarder; |
| |
| @override |
| Future<String> get sdkNameAndVersion async { |
| final FlutterVersion flutterVersion = FlutterVersion.instance; |
| return 'Flutter ${flutterVersion.frameworkRevisionShort}'; |
| } |
| |
| @override |
| Future<TargetPlatform> get targetPlatform async => TargetPlatform.tester; |
| |
| @override |
| void clearLogs() {} |
| |
| final _FlutterTesterDeviceLogReader _logReader = |
| _FlutterTesterDeviceLogReader(); |
| |
| @override |
| DeviceLogReader getLogReader({ApplicationPackage app}) => _logReader; |
| |
| @override |
| Future<bool> installApp(ApplicationPackage app) async => true; |
| |
| @override |
| Future<bool> isAppInstalled(ApplicationPackage app) async => false; |
| |
| @override |
| Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => false; |
| |
| @override |
| bool isSupported() => true; |
| |
| bool _isRunning = false; |
| bool get isRunning => _isRunning; |
| |
| @override |
| Future<LaunchResult> startApp( |
| ApplicationPackage package, { |
| @required String mainPath, |
| String route, |
| @required DebuggingOptions debuggingOptions, |
| Map<String, dynamic> platformArgs, |
| bool prebuiltApplication = false, |
| bool applicationNeedsRebuild = false, |
| bool usesTerminalUi = true, |
| bool ipv6 = false, |
| }) async { |
| final BuildInfo buildInfo = debuggingOptions.buildInfo; |
| |
| if (!buildInfo.isDebug) { |
| printError('This device only supports debug mode.'); |
| return LaunchResult.failed(); |
| } |
| |
| final String shellPath = artifacts.getArtifactPath(Artifact.flutterTester); |
| if (!fs.isFileSync(shellPath)) |
| throwToolExit('Cannot find Flutter shell at $shellPath'); |
| |
| final List<String> command = <String>[ |
| shellPath, |
| '--run-forever', |
| '--non-interactive', |
| '--enable-dart-profiling', |
| '--packages=${PackageMap.globalPackagesPath}', |
| ]; |
| if (debuggingOptions.debuggingEnabled) { |
| if (debuggingOptions.startPaused) |
| command.add('--start-paused'); |
| if (debuggingOptions.hasObservatoryPort) |
| command.add('--observatory-port=${debuggingOptions.observatoryPort}'); |
| } |
| |
| // Build assets and perform initial compilation. |
| final String assetDirPath = getAssetBuildDirectory(); |
| final String applicationKernelFilePath = |
| fs.path.join(getBuildDirectory(), 'flutter-tester-app.dill'); |
| await bundle.build( |
| mainPath: mainPath, |
| assetDirPath: assetDirPath, |
| applicationKernelFilePath: applicationKernelFilePath, |
| precompiledSnapshot: false, |
| trackWidgetCreation: buildInfo.trackWidgetCreation, |
| ); |
| command.add('--flutter-assets-dir=$assetDirPath'); |
| |
| // TODO(scheglov): Either remove the check, or make it fail earlier. |
| if (applicationKernelFilePath != null) { |
| command.add(applicationKernelFilePath); |
| } |
| |
| try { |
| printTrace(command.join(' ')); |
| |
| _isRunning = true; |
| _process = await processManager.start(command, |
| environment: <String, String>{ |
| 'FLUTTER_TEST': 'true', |
| }, |
| ); |
| // Setting a bool can't fail in the callback. |
| _process.exitCode.then((_) => _isRunning = false); // ignore: unawaited_futures |
| _process.stdout |
| .transform(utf8.decoder) |
| .transform(const LineSplitter()) |
| .listen((String line) { |
| _logReader.addLine(line); |
| }); |
| _process.stderr |
| .transform(utf8.decoder) |
| .transform(const LineSplitter()) |
| .listen((String line) { |
| _logReader.addLine(line); |
| }); |
| |
| if (!debuggingOptions.debuggingEnabled) |
| return LaunchResult.succeeded(); |
| |
| final ProtocolDiscovery observatoryDiscovery = ProtocolDiscovery.observatory( |
| getLogReader(), |
| hostPort: debuggingOptions.observatoryPort, |
| ); |
| |
| final Uri observatoryUri = await observatoryDiscovery.uri; |
| return LaunchResult.succeeded(observatoryUri: observatoryUri); |
| } catch (error) { |
| printError('Failed to launch $package: $error'); |
| return LaunchResult.failed(); |
| } |
| } |
| |
| @override |
| Future<bool> stopApp(ApplicationPackage app) async { |
| _process?.kill(); |
| _process = null; |
| return true; |
| } |
| |
| @override |
| Future<bool> uninstallApp(ApplicationPackage app) async => true; |
| } |
| |
| class FlutterTesterDevices extends PollingDeviceDiscovery { |
| FlutterTesterDevices() : super('Flutter tester'); |
| |
| static const String kTesterDeviceId = 'flutter-tester'; |
| |
| static bool showFlutterTesterDevice = false; |
| |
| final FlutterTesterDevice _testerDevice = |
| FlutterTesterDevice(kTesterDeviceId); |
| |
| @override |
| bool get canListAnything => true; |
| |
| @override |
| bool get supportsPlatform => true; |
| |
| @override |
| Future<List<Device>> pollingGetDevices() async { |
| return showFlutterTesterDevice ? <Device>[_testerDevice] : <Device>[]; |
| } |
| } |
| |
| class _FlutterTesterDeviceLogReader extends DeviceLogReader { |
| final StreamController<String> _logLinesController = |
| StreamController<String>.broadcast(); |
| |
| @override |
| int get appPid => 0; |
| |
| @override |
| Stream<String> get logLines => _logLinesController.stream; |
| |
| @override |
| String get name => 'flutter tester log reader'; |
| |
| void addLine(String line) => _logLinesController.add(line); |
| } |
| |
| /// A fake port forwarder that doesn't do anything. Used by flutter tester |
| /// where the VM is running on the same machine and does not need ports forwarding. |
| class _NoopPortForwarder extends DevicePortForwarder { |
| @override |
| Future<int> forward(int devicePort, {int hostPort}) { |
| if (hostPort != null && hostPort != devicePort) |
| throw 'Forwarding to a different port is not supported by flutter tester'; |
| return Future<int>.value(devicePort); |
| } |
| |
| @override |
| List<ForwardedPort> get forwardedPorts => <ForwardedPort>[]; |
| |
| @override |
| Future<Null> unforward(ForwardedPort forwardedPort) => null; |
| } |