mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
[fuchsia] Add support for the 'device' command using the SDK (#31910)
This commit is contained in:
parent
06973f58a7
commit
6a69f8c98c
@ -1 +1 @@
|
|||||||
Ow5Xdviq7OwKr7XNuf-Bw0nBMeAr849mFn7gc_RUpzUC
|
mfXzGfxNWcf6BHsv083b56vQcj96yCo0exBFBdjE4gMC
|
||||||
|
@ -72,7 +72,7 @@ Future<T> runInContext<T>(
|
|||||||
DoctorValidatorsProvider: () => DoctorValidatorsProvider.defaultInstance,
|
DoctorValidatorsProvider: () => DoctorValidatorsProvider.defaultInstance,
|
||||||
EmulatorManager: () => EmulatorManager(),
|
EmulatorManager: () => EmulatorManager(),
|
||||||
FuchsiaSdk: () => FuchsiaSdk(),
|
FuchsiaSdk: () => FuchsiaSdk(),
|
||||||
FuchsiaArtifacts: () => FuchsiaArtifacts(),
|
FuchsiaArtifacts: () => FuchsiaArtifacts.find(),
|
||||||
FuchsiaWorkflow: () => FuchsiaWorkflow(),
|
FuchsiaWorkflow: () => FuchsiaWorkflow(),
|
||||||
Flags: () => const EmptyFlags(),
|
Flags: () => const EmptyFlags(),
|
||||||
FlutterVersion: () => FlutterVersion(const SystemClock()),
|
FlutterVersion: () => FlutterVersion(const SystemClock()),
|
||||||
|
@ -35,7 +35,8 @@ Future<VMService> _kDefaultFuchsiaIsolateDiscoveryConnector(Uri uri) {
|
|||||||
class _FuchsiaLogReader extends DeviceLogReader {
|
class _FuchsiaLogReader extends DeviceLogReader {
|
||||||
_FuchsiaLogReader(this._device, [this._app]);
|
_FuchsiaLogReader(this._device, [this._app]);
|
||||||
|
|
||||||
static final RegExp _flutterLogOutput = RegExp(r'INFO: \w+\(flutter\): ');
|
// \S matches non-whitespace characters.
|
||||||
|
static final RegExp _flutterLogOutput = RegExp(r'INFO: \S+\(flutter\): ');
|
||||||
|
|
||||||
FuchsiaDevice _device;
|
FuchsiaDevice _device;
|
||||||
ApplicationPackage _app;
|
ApplicationPackage _app;
|
||||||
@ -46,7 +47,7 @@ class _FuchsiaLogReader extends DeviceLogReader {
|
|||||||
Stream<String> _logLines;
|
Stream<String> _logLines;
|
||||||
@override
|
@override
|
||||||
Stream<String> get logLines {
|
Stream<String> get logLines {
|
||||||
_logLines ??= _processLogs(fuchsiaSdk.syslogs());
|
_logLines ??= _processLogs(fuchsiaSdk.syslogs(_device.id));
|
||||||
return _logLines;
|
return _logLines;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,8 +58,8 @@ class _FuchsiaLogReader extends DeviceLogReader {
|
|||||||
// Determine if line comes from flutter, and optionally whether it matches
|
// Determine if line comes from flutter, and optionally whether it matches
|
||||||
// the correct fuchsia module.
|
// the correct fuchsia module.
|
||||||
final RegExp matchRegExp = _app == null
|
final RegExp matchRegExp = _app == null
|
||||||
? _flutterLogOutput
|
? _flutterLogOutput
|
||||||
: RegExp('INFO: ${_app.name}\\(flutter\\): ');
|
: RegExp('INFO: ${_app.name}\\(flutter\\): ');
|
||||||
return Stream<String>.eventTransformed(
|
return Stream<String>.eventTransformed(
|
||||||
lines,
|
lines,
|
||||||
(Sink<String> outout) => _FuchsiaLogSink(outout, matchRegExp, startTime),
|
(Sink<String> outout) => _FuchsiaLogSink(outout, matchRegExp, startTime),
|
||||||
@ -90,16 +91,19 @@ class _FuchsiaLogSink implements EventSink<String> {
|
|||||||
if (logTime.millisecondsSinceEpoch < _startTime.millisecondsSinceEpoch) {
|
if (logTime.millisecondsSinceEpoch < _startTime.millisecondsSinceEpoch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_outputSink.add('[${logTime.toLocal()}] Flutter: ${line.split(_matchRegExp).last}');
|
_outputSink.add(
|
||||||
|
'[${logTime.toLocal()}] Flutter: ${line.split(_matchRegExp).last}');
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void addError(Object error, [ StackTrace stackTrace ]) {
|
void addError(Object error, [StackTrace stackTrace]) {
|
||||||
_outputSink.addError(error, stackTrace);
|
_outputSink.addError(error, stackTrace);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void close() { _outputSink.close(); }
|
void close() {
|
||||||
|
_outputSink.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FuchsiaDevices extends PollingDeviceDiscovery {
|
class FuchsiaDevices extends PollingDeviceDiscovery {
|
||||||
@ -146,7 +150,7 @@ List<FuchsiaDevice> parseListDevices(String text) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class FuchsiaDevice extends Device {
|
class FuchsiaDevice extends Device {
|
||||||
FuchsiaDevice(String id, { this.name }) : super(id);
|
FuchsiaDevice(String id, {this.name}) : super(id);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get supportsHotReload => true;
|
bool get supportsHotReload => true;
|
||||||
@ -191,7 +195,8 @@ class FuchsiaDevice extends Device {
|
|||||||
bool prebuiltApplication = false,
|
bool prebuiltApplication = false,
|
||||||
bool usesTerminalUi = true,
|
bool usesTerminalUi = true,
|
||||||
bool ipv6 = false,
|
bool ipv6 = false,
|
||||||
}) => Future<void>.error('unimplemented');
|
}) =>
|
||||||
|
Future<void>.error('unimplemented');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> stopApp(ApplicationPackage app) async {
|
Future<bool> stopApp(ApplicationPackage app) async {
|
||||||
@ -206,15 +211,17 @@ class FuchsiaDevice extends Device {
|
|||||||
Future<String> get sdkNameAndVersion async => 'Fuchsia';
|
Future<String> get sdkNameAndVersion async => 'Fuchsia';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DeviceLogReader getLogReader({ ApplicationPackage app }) => _logReader ??= _FuchsiaLogReader(this, app);
|
DeviceLogReader getLogReader({ApplicationPackage app}) =>
|
||||||
|
_logReader ??= _FuchsiaLogReader(this, app);
|
||||||
_FuchsiaLogReader _logReader;
|
_FuchsiaLogReader _logReader;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DevicePortForwarder get portForwarder => _portForwarder ??= _FuchsiaPortForwarder(this);
|
DevicePortForwarder get portForwarder =>
|
||||||
|
_portForwarder ??= _FuchsiaPortForwarder(this);
|
||||||
_FuchsiaPortForwarder _portForwarder;
|
_FuchsiaPortForwarder _portForwarder;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void clearLogs() { }
|
void clearLogs() {}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get supportsScreenshot => false;
|
bool get supportsScreenshot => false;
|
||||||
@ -234,7 +241,8 @@ class FuchsiaDevice extends Device {
|
|||||||
Future<List<int>> servicePorts() async {
|
Future<List<int>> servicePorts() async {
|
||||||
final String findOutput = await shell('find /hub -name vmservice-port');
|
final String findOutput = await shell('find /hub -name vmservice-port');
|
||||||
if (findOutput.trim() == '') {
|
if (findOutput.trim() == '') {
|
||||||
throwToolExit('No Dart Observatories found. Are you running a debug build?');
|
throwToolExit(
|
||||||
|
'No Dart Observatories found. Are you running a debug build?');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final List<int> ports = <int>[];
|
final List<int> ports = <int>[];
|
||||||
@ -259,9 +267,15 @@ class FuchsiaDevice extends Device {
|
|||||||
/// Run `command` on the Fuchsia device shell.
|
/// Run `command` on the Fuchsia device shell.
|
||||||
Future<String> shell(String command) async {
|
Future<String> shell(String command) async {
|
||||||
final RunResult result = await runAsync(<String>[
|
final RunResult result = await runAsync(<String>[
|
||||||
'ssh', '-F', fuchsiaArtifacts.sshConfig.absolute.path, id, command]);
|
'ssh',
|
||||||
|
'-F',
|
||||||
|
fuchsiaArtifacts.sshConfig.absolute.path,
|
||||||
|
id,
|
||||||
|
command
|
||||||
|
]);
|
||||||
if (result.exitCode != 0) {
|
if (result.exitCode != 0) {
|
||||||
throwToolExit('Command failed: $command\nstdout: ${result.stdout}\nstderr: ${result.stderr}');
|
throwToolExit(
|
||||||
|
'Command failed: $command\nstdout: ${result.stdout}\nstderr: ${result.stderr}');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return result.stdout;
|
return result.stdout;
|
||||||
@ -302,7 +316,9 @@ class FuchsiaDevice extends Device {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
FuchsiaIsolateDiscoveryProtocol getIsolateDiscoveryProtocol(String isolateName) => FuchsiaIsolateDiscoveryProtocol(this, isolateName);
|
FuchsiaIsolateDiscoveryProtocol getIsolateDiscoveryProtocol(
|
||||||
|
String isolateName) =>
|
||||||
|
FuchsiaIsolateDiscoveryProtocol(this, isolateName);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool isSupportedForProject(FlutterProject flutterProject) => true;
|
bool isSupportedForProject(FlutterProject flutterProject) => true;
|
||||||
@ -341,6 +357,7 @@ class FuchsiaIsolateDiscoveryProtocol {
|
|||||||
return uri;
|
return uri;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Uri _uri;
|
Uri _uri;
|
||||||
|
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@ -379,8 +396,8 @@ 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(_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();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -402,13 +419,22 @@ class _FuchsiaPortForwarder extends DevicePortForwarder {
|
|||||||
final Map<int, Process> _processes = <int, Process>{};
|
final Map<int, Process> _processes = <int, Process>{};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<int> forward(int devicePort, { int hostPort }) async {
|
Future<int> forward(int devicePort, {int hostPort}) async {
|
||||||
hostPort ??= await _findPort();
|
hostPort ??= await _findPort();
|
||||||
// Note: the provided command works around a bug in -N, see US-515
|
// Note: the provided command works around a bug in -N, see US-515
|
||||||
// for more explanation.
|
// for more explanation.
|
||||||
final List<String> command = <String>[
|
final List<String> command = <String>[
|
||||||
'ssh', '-6', '-F', fuchsiaArtifacts.sshConfig.absolute.path, '-nNT', '-vvv', '-f',
|
'ssh',
|
||||||
'-L', '$hostPort:$_ipv4Loopback:$devicePort', device.id, 'true',
|
'-6',
|
||||||
|
'-F',
|
||||||
|
fuchsiaArtifacts.sshConfig.absolute.path,
|
||||||
|
'-nNT',
|
||||||
|
'-vvv',
|
||||||
|
'-f',
|
||||||
|
'-L',
|
||||||
|
'$hostPort:$_ipv4Loopback:$devicePort',
|
||||||
|
device.id,
|
||||||
|
'true',
|
||||||
];
|
];
|
||||||
final Process process = await processManager.start(command);
|
final Process process = await processManager.start(command);
|
||||||
unawaited(process.exitCode.then((int exitCode) {
|
unawaited(process.exitCode.then((int exitCode) {
|
||||||
@ -431,8 +457,16 @@ class _FuchsiaPortForwarder extends DevicePortForwarder {
|
|||||||
final Process process = _processes.remove(forwardedPort.hostPort);
|
final Process process = _processes.remove(forwardedPort.hostPort);
|
||||||
process?.kill();
|
process?.kill();
|
||||||
final List<String> command = <String>[
|
final List<String> command = <String>[
|
||||||
'ssh', '-F', fuchsiaArtifacts.sshConfig.absolute.path, '-O', 'cancel', '-vvv',
|
'ssh',
|
||||||
'-L', '${forwardedPort.hostPort}:$_ipv4Loopback:${forwardedPort.devicePort}', device.id];
|
'-F',
|
||||||
|
fuchsiaArtifacts.sshConfig.absolute.path,
|
||||||
|
'-O',
|
||||||
|
'cancel',
|
||||||
|
'-vvv',
|
||||||
|
'-L',
|
||||||
|
'${forwardedPort.hostPort}:$_ipv4Loopback:${forwardedPort.devicePort}',
|
||||||
|
device.id
|
||||||
|
];
|
||||||
final ProcessResult result = await processManager.run(command);
|
final ProcessResult result = await processManager.run(command);
|
||||||
if (result.exitCode != 0) {
|
if (result.exitCode != 0) {
|
||||||
throwToolExit(result.stderr);
|
throwToolExit(result.stderr);
|
||||||
@ -449,8 +483,9 @@ class _FuchsiaPortForwarder extends DevicePortForwarder {
|
|||||||
// Failures are signaled by a return value of 0 from this function.
|
// Failures are signaled by a return value of 0 from this function.
|
||||||
printTrace('_findPort failed: $e');
|
printTrace('_findPort failed: $e');
|
||||||
}
|
}
|
||||||
if (serverSocket != null)
|
if (serverSocket != null) {
|
||||||
await serverSocket.close();
|
await serverSocket.close();
|
||||||
|
}
|
||||||
return port;
|
return port;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,10 @@ import 'dart:async';
|
|||||||
import '../base/context.dart';
|
import '../base/context.dart';
|
||||||
import '../base/file_system.dart';
|
import '../base/file_system.dart';
|
||||||
import '../base/io.dart';
|
import '../base/io.dart';
|
||||||
|
import '../base/platform.dart';
|
||||||
import '../base/process.dart';
|
import '../base/process.dart';
|
||||||
import '../base/process_manager.dart';
|
import '../base/process_manager.dart';
|
||||||
|
import '../cache.dart';
|
||||||
import '../convert.dart';
|
import '../convert.dart';
|
||||||
import '../globals.dart';
|
import '../globals.dart';
|
||||||
|
|
||||||
@ -23,8 +25,6 @@ FuchsiaArtifacts get fuchsiaArtifacts => context.get<FuchsiaArtifacts>();
|
|||||||
/// This workflow assumes development within the fuchsia source tree,
|
/// This workflow assumes development within the fuchsia source tree,
|
||||||
/// including a working fx command-line tool in the user's PATH.
|
/// including a working fx command-line tool in the user's PATH.
|
||||||
class FuchsiaSdk {
|
class FuchsiaSdk {
|
||||||
static const List<String> _syslogCommand = <String>['fx', 'syslog', '--clock', 'Local'];
|
|
||||||
|
|
||||||
/// Example output:
|
/// Example output:
|
||||||
/// $ dev_finder list -full
|
/// $ dev_finder list -full
|
||||||
/// > 192.168.42.56 paper-pulp-bush-angel
|
/// > 192.168.42.56 paper-pulp-bush-angel
|
||||||
@ -42,19 +42,33 @@ class FuchsiaSdk {
|
|||||||
/// Returns the fuchsia system logs for an attached device.
|
/// Returns the fuchsia system logs for an attached device.
|
||||||
///
|
///
|
||||||
/// Does not currently support multiple attached devices.
|
/// Does not currently support multiple attached devices.
|
||||||
Stream<String> syslogs() {
|
Stream<String> syslogs(String id) {
|
||||||
Process process;
|
Process process;
|
||||||
try {
|
try {
|
||||||
final StreamController<String> controller = StreamController<String>(onCancel: () {
|
final StreamController<String> controller =
|
||||||
|
StreamController<String>(onCancel: () {
|
||||||
process.kill();
|
process.kill();
|
||||||
});
|
});
|
||||||
processManager.start(_syslogCommand).then((Process newProcess) {
|
if (fuchsiaArtifacts.sshConfig == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const String remoteCommand = 'log_listener --clock Local';
|
||||||
|
final List<String> cmd = <String>[
|
||||||
|
'ssh',
|
||||||
|
'-F',
|
||||||
|
fuchsiaArtifacts.sshConfig.absolute.path,
|
||||||
|
id,
|
||||||
|
remoteCommand
|
||||||
|
];
|
||||||
|
processManager.start(cmd).then((Process newProcess) {
|
||||||
if (controller.isClosed) {
|
if (controller.isClosed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
process = newProcess;
|
process = newProcess;
|
||||||
process.exitCode.whenComplete(controller.close);
|
process.exitCode.whenComplete(controller.close);
|
||||||
controller.addStream(process.stdout.transform(utf8.decoder).transform(const LineSplitter()));
|
controller.addStream(process.stdout
|
||||||
|
.transform(utf8.decoder)
|
||||||
|
.transform(const LineSplitter()));
|
||||||
});
|
});
|
||||||
return controller.stream;
|
return controller.stream;
|
||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
@ -69,6 +83,35 @@ class FuchsiaArtifacts {
|
|||||||
/// Creates a new [FuchsiaArtifacts].
|
/// Creates a new [FuchsiaArtifacts].
|
||||||
FuchsiaArtifacts({this.sshConfig, this.devFinder});
|
FuchsiaArtifacts({this.sshConfig, this.devFinder});
|
||||||
|
|
||||||
|
/// Creates a new [FuchsiaArtifacts] using the cached Fuchsia SDK.
|
||||||
|
///
|
||||||
|
/// Finds tools under bin/cache/artifacts/fuchsia/tools.
|
||||||
|
/// Queries environment variables (first FUCHSIA_BUILD_DIR, then
|
||||||
|
/// FUCHSIA_SSH_CONFIG) to find the ssh configuration needed to talk to
|
||||||
|
/// a device.
|
||||||
|
factory FuchsiaArtifacts.find() {
|
||||||
|
final String fuchsia = Cache.instance.getArtifactDirectory('fuchsia').path;
|
||||||
|
final String tools = fs.path.join(fuchsia, 'tools');
|
||||||
|
|
||||||
|
// If FUCHSIA_BUILD_DIR is defined, then look for the ssh_config dir
|
||||||
|
// relative to it. Next, if FUCHSIA_SSH_CONFIG is defined, then use it.
|
||||||
|
// TODO(zra): Consider passing the ssh config path in with a flag.
|
||||||
|
File sshConfig;
|
||||||
|
if (platform.environment.containsKey(_kFuchsiaBuildDir)) {
|
||||||
|
sshConfig = fs.file(fs.path.join(
|
||||||
|
platform.environment[_kFuchsiaSshConfig], 'ssh-keys', 'ssh_config'));
|
||||||
|
} else if (platform.environment.containsKey(_kFuchsiaSshConfig)) {
|
||||||
|
sshConfig = fs.file(platform.environment[_kFuchsiaSshConfig]);
|
||||||
|
}
|
||||||
|
return FuchsiaArtifacts(
|
||||||
|
sshConfig: sshConfig,
|
||||||
|
devFinder: fs.file(fs.path.join(tools, 'dev_finder')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const String _kFuchsiaSshConfig = 'FUCHSIA_SSH_CONFIG';
|
||||||
|
static const String _kFuchsiaBuildDir = 'FUCHSIA_BUILD_DIR';
|
||||||
|
|
||||||
/// The location of the SSH configuration file used to interact with a
|
/// The location of the SSH configuration file used to interact with a
|
||||||
/// Fuchsia device.
|
/// Fuchsia device.
|
||||||
final File sshConfig;
|
final File sshConfig;
|
||||||
|
@ -66,7 +66,8 @@ void main() {
|
|||||||
any,
|
any,
|
||||||
environment: anyNamed('environment'),
|
environment: anyNamed('environment'),
|
||||||
workingDirectory: anyNamed('workingDirectory'),
|
workingDirectory: anyNamed('workingDirectory'),
|
||||||
)).thenAnswer((Invocation invocation) => Future<ProcessResult>.value(mockProcessResult));
|
)).thenAnswer((Invocation invocation) =>
|
||||||
|
Future<ProcessResult>.value(mockProcessResult));
|
||||||
when(mockProcessResult.exitCode).thenReturn(1);
|
when(mockProcessResult.exitCode).thenReturn(1);
|
||||||
when<String>(mockProcessResult.stdout).thenReturn('');
|
when<String>(mockProcessResult.stdout).thenReturn('');
|
||||||
when<String>(mockProcessResult.stderr).thenReturn('');
|
when<String>(mockProcessResult.stderr).thenReturn('');
|
||||||
@ -79,7 +80,8 @@ void main() {
|
|||||||
any,
|
any,
|
||||||
environment: anyNamed('environment'),
|
environment: anyNamed('environment'),
|
||||||
workingDirectory: anyNamed('workingDirectory'),
|
workingDirectory: anyNamed('workingDirectory'),
|
||||||
)).thenAnswer((Invocation invocation) => Future<ProcessResult>.value(emptyStdoutProcessResult));
|
)).thenAnswer((Invocation invocation) =>
|
||||||
|
Future<ProcessResult>.value(emptyStdoutProcessResult));
|
||||||
when(emptyStdoutProcessResult.exitCode).thenReturn(0);
|
when(emptyStdoutProcessResult.exitCode).thenReturn(0);
|
||||||
when<String>(emptyStdoutProcessResult.stdout).thenReturn('');
|
when<String>(emptyStdoutProcessResult.stdout).thenReturn('');
|
||||||
when<String>(emptyStdoutProcessResult.stderr).thenReturn('');
|
when<String>(emptyStdoutProcessResult.stderr).thenReturn('');
|
||||||
@ -92,23 +94,26 @@ void main() {
|
|||||||
} on ToolExit catch (err) {
|
} on ToolExit catch (err) {
|
||||||
toolExit = err;
|
toolExit = err;
|
||||||
}
|
}
|
||||||
expect(toolExit.message, contains('No Dart Observatories found. Are you running a debug build?'));
|
expect(
|
||||||
|
toolExit.message,
|
||||||
|
contains(
|
||||||
|
'No Dart Observatories found. Are you running a debug build?'));
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
ProcessManager: () => emptyStdoutProcessManager,
|
ProcessManager: () => emptyStdoutProcessManager,
|
||||||
FuchsiaArtifacts: () => FuchsiaArtifacts(
|
FuchsiaArtifacts: () => FuchsiaArtifacts(
|
||||||
sshConfig: mockFile,
|
sshConfig: mockFile,
|
||||||
devFinder: mockFile,
|
devFinder: mockFile,
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
group('device logs', () {
|
group('device logs', () {
|
||||||
const String exampleUtcLogs = '''
|
const String exampleUtcLogs = '''
|
||||||
[2018-11-09 01:27:45][3][297950920][log] INFO: example_app(flutter): Error doing thing
|
[2018-11-09 01:27:45][3][297950920][log] INFO: example_app.cmx(flutter): Error doing thing
|
||||||
[2018-11-09 01:27:58][46257][46269][foo] INFO: Using a thing
|
[2018-11-09 01:27:58][46257][46269][foo] INFO: Using a thing
|
||||||
[2018-11-09 01:29:58][46257][46269][foo] INFO: Blah blah blah
|
[2018-11-09 01:29:58][46257][46269][foo] INFO: Blah blah blah
|
||||||
[2018-11-09 01:29:58][46257][46269][foo] INFO: other_app(flutter): Do thing
|
[2018-11-09 01:29:58][46257][46269][foo] INFO: other_app.cmx(flutter): Do thing
|
||||||
[2018-11-09 01:30:02][41175][41187][bar] INFO: Invoking a bar
|
[2018-11-09 01:30:02][41175][41187][bar] INFO: Invoking a bar
|
||||||
[2018-11-09 01:30:12][52580][52983][log] INFO: example_app(flutter): Did thing this time
|
[2018-11-09 01:30:12][52580][52983][log] INFO: example_app.cmx(flutter): Did thing this time
|
||||||
|
|
||||||
''';
|
''';
|
||||||
final MockProcessManager mockProcessManager = MockProcessManager();
|
final MockProcessManager mockProcessManager = MockProcessManager();
|
||||||
@ -116,11 +121,17 @@ void main() {
|
|||||||
Completer<int> exitCode;
|
Completer<int> exitCode;
|
||||||
StreamController<List<int>> stdout;
|
StreamController<List<int>> stdout;
|
||||||
StreamController<List<int>> stderr;
|
StreamController<List<int>> stderr;
|
||||||
when(mockProcessManager.start(any)).thenAnswer((Invocation _) => Future<Process>.value(mockProcess));
|
when(mockProcessManager.start(any))
|
||||||
|
.thenAnswer((Invocation _) => Future<Process>.value(mockProcess));
|
||||||
when(mockProcess.exitCode).thenAnswer((Invocation _) => exitCode.future);
|
when(mockProcess.exitCode).thenAnswer((Invocation _) => exitCode.future);
|
||||||
when(mockProcess.stdout).thenAnswer((Invocation _) => stdout.stream);
|
when(mockProcess.stdout).thenAnswer((Invocation _) => stdout.stream);
|
||||||
when(mockProcess.stderr).thenAnswer((Invocation _) => stderr.stream);
|
when(mockProcess.stderr).thenAnswer((Invocation _) => stderr.stream);
|
||||||
|
|
||||||
|
final MockFile devFinder = MockFile();
|
||||||
|
final MockFile sshConfig = MockFile();
|
||||||
|
when(devFinder.absolute).thenReturn(devFinder);
|
||||||
|
when(sshConfig.absolute).thenReturn(sshConfig);
|
||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
stdout = StreamController<List<int>>(sync: true);
|
stdout = StreamController<List<int>>(sync: true);
|
||||||
stderr = StreamController<List<int>>(sync: true);
|
stderr = StreamController<List<int>>(sync: true);
|
||||||
@ -133,7 +144,8 @@ void main() {
|
|||||||
|
|
||||||
testUsingContext('can be parsed for an app', () async {
|
testUsingContext('can be parsed for an app', () async {
|
||||||
final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester');
|
final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester');
|
||||||
final DeviceLogReader reader = device.getLogReader(app: FuchsiaModulePackage(name: 'example_app'));
|
final DeviceLogReader reader = device.getLogReader(
|
||||||
|
app: FuchsiaModulePackage(name: 'example_app.cmx'));
|
||||||
final List<String> logLines = <String>[];
|
final List<String> logLines = <String>[];
|
||||||
final Completer<void> lock = Completer<void>();
|
final Completer<void> lock = Completer<void>();
|
||||||
reader.logLines.listen((String line) {
|
reader.logLines.listen((String line) {
|
||||||
@ -155,11 +167,14 @@ void main() {
|
|||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
ProcessManager: () => mockProcessManager,
|
ProcessManager: () => mockProcessManager,
|
||||||
SystemClock: () => SystemClock.fixed(DateTime(2018, 11, 9, 1, 25, 45)),
|
SystemClock: () => SystemClock.fixed(DateTime(2018, 11, 9, 1, 25, 45)),
|
||||||
|
FuchsiaArtifacts: () =>
|
||||||
|
FuchsiaArtifacts(devFinder: devFinder, sshConfig: sshConfig),
|
||||||
});
|
});
|
||||||
|
|
||||||
testUsingContext('cuts off prior logs', () async {
|
testUsingContext('cuts off prior logs', () async {
|
||||||
final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester');
|
final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester');
|
||||||
final DeviceLogReader reader = device.getLogReader(app: FuchsiaModulePackage(name: 'example_app'));
|
final DeviceLogReader reader = device.getLogReader(
|
||||||
|
app: FuchsiaModulePackage(name: 'example_app.cmx'));
|
||||||
final List<String> logLines = <String>[];
|
final List<String> logLines = <String>[];
|
||||||
final Completer<void> lock = Completer<void>();
|
final Completer<void> lock = Completer<void>();
|
||||||
reader.logLines.listen((String line) {
|
reader.logLines.listen((String line) {
|
||||||
@ -178,6 +193,8 @@ void main() {
|
|||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
ProcessManager: () => mockProcessManager,
|
ProcessManager: () => mockProcessManager,
|
||||||
SystemClock: () => SystemClock.fixed(DateTime(2018, 11, 9, 1, 29, 45)),
|
SystemClock: () => SystemClock.fixed(DateTime(2018, 11, 9, 1, 29, 45)),
|
||||||
|
FuchsiaArtifacts: () =>
|
||||||
|
FuchsiaArtifacts(devFinder: devFinder, sshConfig: sshConfig),
|
||||||
});
|
});
|
||||||
|
|
||||||
testUsingContext('can be parsed for all apps', () async {
|
testUsingContext('can be parsed for all apps', () async {
|
||||||
@ -205,12 +222,15 @@ void main() {
|
|||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
ProcessManager: () => mockProcessManager,
|
ProcessManager: () => mockProcessManager,
|
||||||
SystemClock: () => SystemClock.fixed(DateTime(2018, 11, 9, 1, 25, 45)),
|
SystemClock: () => SystemClock.fixed(DateTime(2018, 11, 9, 1, 25, 45)),
|
||||||
|
FuchsiaArtifacts: () =>
|
||||||
|
FuchsiaArtifacts(devFinder: devFinder, sshConfig: sshConfig),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group(FuchsiaIsolateDiscoveryProtocol, () {
|
group(FuchsiaIsolateDiscoveryProtocol, () {
|
||||||
Future<Uri> findUri(List<MockFlutterView> views, String expectedIsolateName) {
|
Future<Uri> findUri(
|
||||||
|
List<MockFlutterView> views, String expectedIsolateName) {
|
||||||
final MockPortForwarder portForwarder = MockPortForwarder();
|
final MockPortForwarder portForwarder = MockPortForwarder();
|
||||||
final MockVMService vmService = MockVMService();
|
final MockVMService vmService = MockVMService();
|
||||||
final MockVM vm = MockVM();
|
final MockVM vm = MockVM();
|
||||||
@ -220,33 +240,43 @@ void main() {
|
|||||||
for (MockFlutterView view in views) {
|
for (MockFlutterView view in views) {
|
||||||
view.owner = vm;
|
view.owner = vm;
|
||||||
}
|
}
|
||||||
final MockFuchsiaDevice fuchsiaDevice = MockFuchsiaDevice('123', portForwarder, false);
|
final MockFuchsiaDevice fuchsiaDevice =
|
||||||
final FuchsiaIsolateDiscoveryProtocol discoveryProtocol = FuchsiaIsolateDiscoveryProtocol(
|
MockFuchsiaDevice('123', portForwarder, false);
|
||||||
|
final FuchsiaIsolateDiscoveryProtocol discoveryProtocol =
|
||||||
|
FuchsiaIsolateDiscoveryProtocol(
|
||||||
fuchsiaDevice,
|
fuchsiaDevice,
|
||||||
expectedIsolateName,
|
expectedIsolateName,
|
||||||
(Uri uri) async => vmService,
|
(Uri uri) async => vmService,
|
||||||
true, // only poll once.
|
true, // only poll once.
|
||||||
);
|
);
|
||||||
when(fuchsiaDevice.servicePorts()).thenAnswer((Invocation invocation) async => <int>[1]);
|
when(fuchsiaDevice.servicePorts())
|
||||||
when(portForwarder.forward(1)).thenAnswer((Invocation invocation) async => 2);
|
.thenAnswer((Invocation invocation) async => <int>[1]);
|
||||||
when(vmService.getVM()).thenAnswer((Invocation invocation) => Future<void>.value(null));
|
when(portForwarder.forward(1))
|
||||||
when(vmService.refreshViews()).thenAnswer((Invocation invocation) => Future<void>.value(null));
|
.thenAnswer((Invocation invocation) async => 2);
|
||||||
|
when(vmService.getVM())
|
||||||
|
.thenAnswer((Invocation invocation) => Future<void>.value(null));
|
||||||
|
when(vmService.refreshViews())
|
||||||
|
.thenAnswer((Invocation invocation) => Future<void>.value(null));
|
||||||
when(vmService.httpAddress).thenReturn(Uri.parse('example'));
|
when(vmService.httpAddress).thenReturn(Uri.parse('example'));
|
||||||
return discoveryProtocol.uri;
|
return discoveryProtocol.uri;
|
||||||
}
|
}
|
||||||
testUsingContext('can find flutter view with matching isolate name', () async {
|
|
||||||
|
testUsingContext('can find flutter view with matching isolate name',
|
||||||
|
() async {
|
||||||
const String expectedIsolateName = 'foobar';
|
const String expectedIsolateName = 'foobar';
|
||||||
final Uri uri = await findUri(<MockFlutterView>[
|
final Uri uri = await findUri(<MockFlutterView>[
|
||||||
MockFlutterView(null), // no ui isolate.
|
MockFlutterView(null), // no ui isolate.
|
||||||
MockFlutterView(MockIsolate('wrong name')), // wrong name.
|
MockFlutterView(MockIsolate('wrong name')), // wrong name.
|
||||||
MockFlutterView(MockIsolate(expectedIsolateName)), // matching name.
|
MockFlutterView(MockIsolate(expectedIsolateName)), // matching name.
|
||||||
], expectedIsolateName);
|
], expectedIsolateName);
|
||||||
expect(uri.toString(), 'http://${InternetAddress.loopbackIPv4.address}:0/');
|
expect(
|
||||||
|
uri.toString(), 'http://${InternetAddress.loopbackIPv4.address}:0/');
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
Logger: () => StdoutLogger(),
|
Logger: () => StdoutLogger(),
|
||||||
});
|
});
|
||||||
|
|
||||||
testUsingContext('can handle flutter view without matching isolate name', () async {
|
testUsingContext('can handle flutter view without matching isolate name',
|
||||||
|
() async {
|
||||||
const String expectedIsolateName = 'foobar';
|
const String expectedIsolateName = 'foobar';
|
||||||
final Future<Uri> uri = findUri(<MockFlutterView>[
|
final Future<Uri> uri = findUri(<MockFlutterView>[
|
||||||
MockFlutterView(null), // no ui isolate.
|
MockFlutterView(null), // no ui isolate.
|
||||||
|
@ -12,37 +12,47 @@ import '../src/common.dart';
|
|||||||
import '../src/context.dart';
|
import '../src/context.dart';
|
||||||
|
|
||||||
class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils {}
|
class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils {}
|
||||||
|
|
||||||
class MockFile extends Mock implements File {}
|
class MockFile extends Mock implements File {}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
group('android workflow', () {
|
group('Fuchsia workflow', () {
|
||||||
final MockFile devFinder = MockFile();
|
final MockFile devFinder = MockFile();
|
||||||
final MockFile sshConfig = MockFile();
|
final MockFile sshConfig = MockFile();
|
||||||
when(devFinder.absolute).thenReturn(devFinder);
|
when(devFinder.absolute).thenReturn(devFinder);
|
||||||
when(sshConfig.absolute).thenReturn(sshConfig);
|
when(sshConfig.absolute).thenReturn(sshConfig);
|
||||||
|
|
||||||
testUsingContext('can not list and launch devices if there is not ssh config and dev finder', () {
|
testUsingContext(
|
||||||
|
'can not list and launch devices if there is not ssh config and dev finder',
|
||||||
|
() {
|
||||||
expect(fuchsiaWorkflow.canLaunchDevices, false);
|
expect(fuchsiaWorkflow.canLaunchDevices, false);
|
||||||
expect(fuchsiaWorkflow.canListDevices, false);
|
expect(fuchsiaWorkflow.canListDevices, false);
|
||||||
expect(fuchsiaWorkflow.canListEmulators, false);
|
expect(fuchsiaWorkflow.canListEmulators, false);
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
FuchsiaArtifacts: () => FuchsiaArtifacts(devFinder: null, sshConfig: null),
|
FuchsiaArtifacts: () =>
|
||||||
|
FuchsiaArtifacts(devFinder: null, sshConfig: null),
|
||||||
});
|
});
|
||||||
|
|
||||||
testUsingContext('can not list and launch devices if there is not ssh config and dev finder', () {
|
testUsingContext(
|
||||||
|
'can not list and launch devices if there is not ssh config and dev finder',
|
||||||
|
() {
|
||||||
expect(fuchsiaWorkflow.canLaunchDevices, false);
|
expect(fuchsiaWorkflow.canLaunchDevices, false);
|
||||||
expect(fuchsiaWorkflow.canListDevices, true);
|
expect(fuchsiaWorkflow.canListDevices, true);
|
||||||
expect(fuchsiaWorkflow.canListEmulators, false);
|
expect(fuchsiaWorkflow.canListEmulators, false);
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
FuchsiaArtifacts: () => FuchsiaArtifacts(devFinder: devFinder, sshConfig: null),
|
FuchsiaArtifacts: () =>
|
||||||
|
FuchsiaArtifacts(devFinder: devFinder, sshConfig: null),
|
||||||
});
|
});
|
||||||
|
|
||||||
testUsingContext('can list and launch devices supported if there is a `fx` command', () {
|
testUsingContext(
|
||||||
|
'can list and launch devices supported with sufficient SDK artifacts',
|
||||||
|
() {
|
||||||
expect(fuchsiaWorkflow.canLaunchDevices, true);
|
expect(fuchsiaWorkflow.canLaunchDevices, true);
|
||||||
expect(fuchsiaWorkflow.canListDevices, true);
|
expect(fuchsiaWorkflow.canListDevices, true);
|
||||||
expect(fuchsiaWorkflow.canListEmulators, false);
|
expect(fuchsiaWorkflow.canListEmulators, false);
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
FuchsiaArtifacts: () => FuchsiaArtifacts(devFinder: devFinder, sshConfig: sshConfig),
|
FuchsiaArtifacts: () =>
|
||||||
|
FuchsiaArtifacts(devFinder: devFinder, sshConfig: sshConfig),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user