Unverified Commit 38970f5f authored by Konstantin Scheglov's avatar Konstantin Scheglov Committed by GitHub

Add a new Device backed by flutter_tester. (#16405)

parent 03a0cd25
......@@ -17,6 +17,7 @@ import 'build_info.dart';
import 'globals.dart';
import 'ios/plist_utils.dart' as plist;
import 'ios/xcodeproj.dart';
import 'tester/flutter_tester.dart';
abstract class ApplicationPackage {
/// Package ID from the Android Manifest or equivalent.
......@@ -274,6 +275,8 @@ Future<ApplicationPackage> getApplicationPackageForPlatform(TargetPlatform platf
return applicationBinary == null
? new IOSApp.fromCurrentDirectory()
: new IOSApp.fromIpa(applicationBinary);
case TargetPlatform.tester:
return new FlutterTesterApp.fromCurrentDirectory();
case TargetPlatform.darwin_x64:
case TargetPlatform.linux_x64:
case TargetPlatform.windows_x64:
......@@ -305,6 +308,7 @@ class ApplicationPackageStore {
case TargetPlatform.linux_x64:
case TargetPlatform.windows_x64:
case TargetPlatform.fuchsia:
case TargetPlatform.tester:
return null;
}
return null;
......
......@@ -114,6 +114,7 @@ class CachedArtifacts extends Artifacts {
case TargetPlatform.linux_x64:
case TargetPlatform.windows_x64:
case TargetPlatform.fuchsia:
case TargetPlatform.tester:
return _getHostArtifactPath(artifact, platform);
}
assert(false, 'Invalid platform $platform.');
......@@ -205,6 +206,7 @@ class CachedArtifacts extends Artifacts {
case TargetPlatform.darwin_x64:
case TargetPlatform.windows_x64:
case TargetPlatform.fuchsia:
case TargetPlatform.tester:
assert(mode == null, 'Platform $platform does not support different build modes.');
return fs.path.join(engineDir, platformName);
case TargetPlatform.ios:
......
......@@ -157,6 +157,7 @@ enum TargetPlatform {
linux_x64,
windows_x64,
fuchsia,
tester,
}
String getNameForTargetPlatform(TargetPlatform platform) {
......@@ -179,6 +180,8 @@ String getNameForTargetPlatform(TargetPlatform platform) {
return 'windows-x64';
case TargetPlatform.fuchsia:
return 'fuchsia';
case TargetPlatform.tester:
return 'flutter-tester';
}
assert(false);
return null;
......
......@@ -247,6 +247,7 @@ Future<String> _buildAotSnapshot(
case TargetPlatform.linux_x64:
case TargetPlatform.windows_x64:
case TargetPlatform.fuchsia:
case TargetPlatform.tester:
assert(false);
}
......@@ -347,6 +348,7 @@ Future<String> _buildAotSnapshot(
case TargetPlatform.linux_x64:
case TargetPlatform.windows_x64:
case TargetPlatform.fuchsia:
case TargetPlatform.tester:
assert(false);
}
......
......@@ -24,6 +24,7 @@ import '../resident_runner.dart';
import '../run_cold.dart';
import '../run_hot.dart';
import '../runner/flutter_command.dart';
import '../tester/flutter_tester.dart';
import '../vmservice.dart';
const String protocolVersion = '0.2.0';
......@@ -570,6 +571,7 @@ class DeviceDomain extends Domain {
addDeviceDiscoverer(new AndroidDevices());
addDeviceDiscoverer(new IOSDevices());
addDeviceDiscoverer(new IOSSimulators());
addDeviceDiscoverer(new FlutterTesterDevices());
}
void addDeviceDiscoverer(PollingDeviceDiscovery discoverer) {
......
......@@ -16,6 +16,7 @@ import 'build_info.dart';
import 'globals.dart';
import 'ios/devices.dart';
import 'ios/simulators.dart';
import 'tester/flutter_tester.dart';
DeviceManager get deviceManager => context[DeviceManager];
......@@ -28,6 +29,7 @@ class DeviceManager {
_deviceDiscoverers.add(new AndroidDevices());
_deviceDiscoverers.add(new IOSDevices());
_deviceDiscoverers.add(new IOSSimulators());
_deviceDiscoverers.add(new FlutterTesterDevices());
}
final List<DeviceDiscovery> _deviceDiscoverers = <DeviceDiscovery>[];
......
......@@ -26,6 +26,7 @@ import '../cache.dart';
import '../dart/package_map.dart';
import '../device.dart';
import '../globals.dart';
import '../tester/flutter_tester.dart';
import '../usage.dart';
import '../version.dart';
import '../vmservice.dart';
......@@ -79,6 +80,11 @@ class FlutterCommandRunner extends CommandRunner<Null> {
help:
'Captures a bug report file to submit to the Flutter team '
'(contains local paths, device\nidentifiers, and log snippets).');
argParser.addFlag('show-test-device',
negatable: false,
hide: !verboseHelp,
help: 'List the special \'flutter-tester\' device in device listings. '
'This headless device is used to\ntest Flutter tooling.');
String packagesHelp;
if (fs.isFileSync(kPackagesFileName))
......@@ -180,6 +186,11 @@ class FlutterCommandRunner extends CommandRunner<Null> {
contextOverrides[Logger] = new VerboseLogger(logger);
}
if (topLevelResults['show-test-device'] ||
topLevelResults['device-id'] == FlutterTesterDevices.kTesterDeviceId) {
FlutterTesterDevices.showFlutterTesterDevice = true;
}
String recordTo = topLevelResults['record-to'];
String replayFrom = topLevelResults['replay-from'];
......
// 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 '../dart/package_map.dart';
import '../device.dart';
import '../globals.dart';
import '../protocol_discovery.dart';
import '../version.dart';
class FlutterTesterApp extends ApplicationPackage {
final String _directory;
factory FlutterTesterApp.fromCurrentDirectory() {
return new FlutterTesterApp._(fs.currentDirectory.path);
}
FlutterTesterApp._(String directory)
: _directory = directory,
super(id: directory);
@override
String get name => fs.path.basename(_directory);
@override
String get packagePath => fs.path.join(_directory, '.packages');
}
// TODO(scheglov): This device does not currently work with full restarts.
class FlutterTesterDevice extends Device {
final _FlutterTesterDeviceLogReader _logReader =
new _FlutterTesterDeviceLogReader();
Process _process;
FlutterTesterDevice(String deviceId) : super(deviceId);
@override
Future<bool> get isLocalEmulator async => false;
@override
String get name => 'Flutter test device';
@override
DevicePortForwarder get portForwarder => null;
@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() {}
@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;
@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 {
if (!debuggingOptions.buildInfo.isDebug) {
printError('This device only supports debug mode.');
return new 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,
'--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.hasObservatoryPort}');
}
// TODO(scheglov): Provide assets location, once it is possible.
if (mainPath != null) {
command.add(mainPath);
}
try {
printTrace('$shellPath ${command.join(' ')}');
_process = await processManager.start(command);
_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 new LaunchResult.succeeded();
final ProtocolDiscovery observatoryDiscovery =
new ProtocolDiscovery.observatory(getLogReader(),
hostPort: debuggingOptions.observatoryPort);
final Uri observatoryUri = await observatoryDiscovery.uri;
return new LaunchResult.succeeded(observatoryUri: observatoryUri);
} catch (error) {
printError('Failed to launch $package: $error');
return new 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 {
static const String kTesterDeviceId = 'flutter-tester';
static bool showFlutterTesterDevice = false;
final FlutterTesterDevice _testerDevice =
new FlutterTesterDevice(kTesterDeviceId);
FlutterTesterDevices() : super('Flutter tester');
@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 =
new 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);
}
// 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 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/tester/flutter_tester.dart';
import 'package:process/process.dart';
import 'package:test/test.dart';
import '../src/common.dart';
import '../src/context.dart';
import '../src/mocks.dart';
void main() {
MemoryFileSystem fs;
setUp(() {
fs = new MemoryFileSystem();
});
group('FlutterTesterApp', () {
testUsingContext('fromCurrentDirectory', () async {
const String projectPath = '/home/my/projects/my_project';
await fs.directory(projectPath).create(recursive: true);
fs.currentDirectory = projectPath;
final FlutterTesterApp app = new FlutterTesterApp.fromCurrentDirectory();
expect(app.name, 'my_project');
expect(app.packagePath, fs.path.join(projectPath, '.packages'));
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
});
group('FlutterTesterDevices', () {
tearDown(() {
FlutterTesterDevices.showFlutterTesterDevice = false;
});
testUsingContext('no device', () async {
final FlutterTesterDevices discoverer = new FlutterTesterDevices();
final List<Device> devices = await discoverer.devices;
expect(devices, isEmpty);
});
testUsingContext('has device', () async {
FlutterTesterDevices.showFlutterTesterDevice = true;
final FlutterTesterDevices discoverer = new FlutterTesterDevices();
final List<Device> devices = await discoverer.devices;
expect(devices, hasLength(1));
final Device device = devices.single;
expect(device, const isInstanceOf<FlutterTesterDevice>());
expect(device.id, 'flutter-tester');
});
});
group('FlutterTesterDevice', () {
FlutterTesterDevice device;
List<String> logLines;
setUp(() {
device = new FlutterTesterDevice('flutter-tester');
logLines = <String>[];
device.getLogReader().logLines.listen(logLines.add);
});
testUsingContext('getters', () async {
expect(device.id, 'flutter-tester');
expect(await device.isLocalEmulator, isFalse);
expect(device.name, 'Flutter test device');
expect(device.portForwarder, isNull);
expect(await device.targetPlatform, TargetPlatform.tester);
expect(await device.installApp(null), isTrue);
expect(await device.isAppInstalled(null), isFalse);
expect(await device.isLatestBuildInstalled(null), isFalse);
expect(await device.uninstallApp(null), isTrue);
expect(device.isSupported(), isTrue);
});
group('startApp', () {
String flutterRoot;
String flutterTesterPath;
String projectPath;
String mainPath;
MockProcessManager mockProcessManager;
MockProcess mockProcess;
final Map<Type, Generator> startOverrides = <Type, Generator>{
Platform: () => new FakePlatform(operatingSystem: 'linux'),
FileSystem: () => fs,
Cache: () => new Cache(rootOverride: fs.directory(flutterRoot)),
ProcessManager: () => mockProcessManager,
};
setUp(() {
flutterRoot = fs.path.join('home', 'me', 'flutter');
flutterTesterPath = fs.path.join(flutterRoot, 'bin', 'cache',
'artifacts', 'engine', 'linux-x64', 'flutter_tester');
final File flutterTesterFile = fs.file(flutterTesterPath);
flutterTesterFile.parent.createSync(recursive: true);
flutterTesterFile.writeAsBytesSync(const <int>[]);
projectPath = fs.path.join('home', 'me', 'hello');
mainPath = fs.path.join(projectPath, 'lin', 'main.dart');
mockProcessManager = new MockProcessManager();
mockProcessManager.processFactory =
(List<String> commands) => mockProcess;
});
testUsingContext('not debug', () async {
final LaunchResult result = await device.startApp(null,
mainPath: mainPath,
debuggingOptions: new DebuggingOptions.disabled(
const BuildInfo(BuildMode.release, null)));
expect(result.started, isFalse);
}, overrides: startOverrides);
testUsingContext('no flutter_tester', () async {
fs.file(flutterTesterPath).deleteSync();
expect(() async {
await device.startApp(null,
mainPath: mainPath,
debuggingOptions: new DebuggingOptions.disabled(
const BuildInfo(BuildMode.debug, null)));
}, throwsToolExit());
}, overrides: startOverrides);
testUsingContext('start', () async {
final Uri observatoryUri = Uri.parse('http://127.0.0.1:6666/');
mockProcess = new MockProcess(
stdout: new Stream<List<int>>.fromIterable(<List<int>>[
'''
Observatory listening on $observatoryUri
Hello!
'''
.codeUnits
]));
final LaunchResult result = await device.startApp(null,
mainPath: mainPath,
debuggingOptions: new DebuggingOptions.enabled(
const BuildInfo(BuildMode.debug, null)));
expect(result.started, isTrue);
expect(result.observatoryUri, observatoryUri);
expect(logLines.last, 'Hello!');
}, overrides: startOverrides);
});
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment