Revert "Add an option for flutter daemon to listen on a TCP port (#95418)" (#95686)
This reverts commit 2b46ea447fc1d692752ba297722a45143385f30a.
diff --git a/packages/flutter_tools/lib/src/commands/attach.dart b/packages/flutter_tools/lib/src/commands/attach.dart
index f93d73c..39d98c2 100644
--- a/packages/flutter_tools/lib/src/commands/attach.dart
+++ b/packages/flutter_tools/lib/src/commands/attach.dart
@@ -18,7 +18,6 @@
import '../build_info.dart';
import '../commands/daemon.dart';
import '../compile.dart';
-import '../daemon.dart';
import '../device.dart';
import '../device_port_forwarder.dart';
import '../fuchsia/fuchsia_device.dart';
@@ -236,10 +235,8 @@
final Daemon daemon = boolArg('machine')
? Daemon(
- DaemonConnection(
- daemonStreams: StdioDaemonStreams(globals.stdio),
- logger: globals.logger,
- ),
+ stdinCommandStream,
+ stdoutCommandResponse,
notifyingLogger: (globals.logger is NotifyingLogger)
? globals.logger as NotifyingLogger
: NotifyingLogger(verbose: globals.logger.isVerbose, parent: globals.logger),
diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart
index cfaa273..3a10b6a 100644
--- a/packages/flutter_tools/lib/src/commands/daemon.dart
+++ b/packages/flutter_tools/lib/src/commands/daemon.dart
@@ -18,7 +18,7 @@
import '../base/terminal.dart';
import '../base/utils.dart';
import '../build_info.dart';
-import '../daemon.dart';
+import '../convert.dart';
import '../device.dart';
import '../device_port_forwarder.dart';
import '../emulator.dart';
@@ -41,13 +41,7 @@
/// It can be shutdown with a `daemon.shutdown` command (or by killing the
/// process).
class DaemonCommand extends FlutterCommand {
- DaemonCommand({ this.hidden = false }) {
- argParser.addOption(
- 'listen-on-tcp-port',
- help: 'If specified, the daemon will be listening for commands on the specified port instead of stdio.',
- valueHelp: 'port',
- );
- }
+ DaemonCommand({ this.hidden = false });
@override
final String name = 'daemon';
@@ -63,31 +57,9 @@
@override
Future<FlutterCommandResult> runCommand() async {
- if (argResults['listen-on-tcp-port'] != null) {
- int port;
- try {
- port = int.parse(stringArg('listen-on-tcp-port'));
- } on FormatException catch (error) {
- throwToolExit('Invalid port for `--listen-on-tcp-port`: $error');
- }
-
- await _DaemonServer(
- port: port,
- logger: StdoutLogger(
- terminal: globals.terminal,
- stdio: globals.stdio,
- outputPreferences: globals.outputPreferences,
- ),
- notifyingLogger: asLogger<NotifyingLogger>(globals.logger),
- ).run();
- return FlutterCommandResult.success();
- }
globals.printStatus('Starting device daemon...');
final Daemon daemon = Daemon(
- DaemonConnection(
- daemonStreams: StdioDaemonStreams(globals.stdio),
- logger: globals.logger,
- ),
+ stdinCommandStream, stdoutCommandResponse,
notifyingLogger: asLogger<NotifyingLogger>(globals.logger),
);
final int code = await daemon.onExit;
@@ -98,57 +70,14 @@
}
}
-class _DaemonServer {
- _DaemonServer({
- this.port,
- this.logger,
- this.notifyingLogger,
- });
-
- final int port;
-
- /// Stdout logger used to print general server-related errors.
- final Logger logger;
-
- // Logger that sends the message to the other end of daemon connection.
- final NotifyingLogger notifyingLogger;
-
- Future<void> run() async {
- final ServerSocket serverSocket = await ServerSocket.bind(InternetAddress.loopbackIPv4, port);
- logger.printStatus('Daemon server listening on ${serverSocket.port}');
-
- final StreamSubscription<Socket> subscription = serverSocket.listen(
- (Socket socket) async {
- // We have to listen to socket.done. Otherwise when the connection is
- // reset, we will receive an uncatchable exception.
- // https://github.com/dart-lang/sdk/issues/25518
- final Future<void> socketDone = socket.done.catchError((dynamic error, StackTrace stackTrace) {
- logger.printError('Socket error: $error');
- logger.printTrace('$stackTrace');
- });
- final Daemon daemon = Daemon(
- DaemonConnection(
- daemonStreams: TcpDaemonStreams(socket, logger: logger),
- logger: logger,
- ),
- notifyingLogger: notifyingLogger,
- );
- await daemon.onExit;
- await socketDone;
- },
- );
-
- // Wait indefinitely until the server closes.
- await subscription.asFuture<void>();
- await subscription.cancel();
- }
-}
+typedef DispatchCommand = void Function(Map<String, dynamic> command);
typedef CommandHandler = Future<dynamic> Function(Map<String, dynamic> args);
class Daemon {
Daemon(
- this.connection, {
+ Stream<Map<String, dynamic>> commandStream,
+ this.sendCommand, {
this.notifyingLogger,
this.logToStdout = false,
}) {
@@ -160,10 +89,9 @@
_registerDomain(devToolsDomain = DevToolsDomain(this));
// Start listening.
- _commandSubscription = connection.incomingCommands.listen(
+ _commandSubscription = commandStream.listen(
_handleRequest,
onDone: () {
- shutdown();
if (!_onExitCompleter.isCompleted) {
_onExitCompleter.complete(0);
}
@@ -171,15 +99,16 @@
);
}
- final DaemonConnection connection;
-
DaemonDomain daemonDomain;
AppDomain appDomain;
DeviceDomain deviceDomain;
EmulatorDomain emulatorDomain;
DevToolsDomain devToolsDomain;
StreamSubscription<Map<String, dynamic>> _commandSubscription;
+ int _outgoingRequestId = 1;
+ final Map<String, Completer<dynamic>> _outgoingRequestCompleters = <String, Completer<dynamic>>{};
+ final DispatchCommand sendCommand;
final NotifyingLogger notifyingLogger;
final bool logToStdout;
@@ -205,27 +134,62 @@
try {
final String method = request['method'] as String;
- assert(method != null);
- if (!method.contains('.')) {
- throw 'method not understood: $method';
- }
+ if (method != null) {
+ if (!method.contains('.')) {
+ throw 'method not understood: $method';
+ }
- final String prefix = method.substring(0, method.indexOf('.'));
- final String name = method.substring(method.indexOf('.') + 1);
- if (_domainMap[prefix] == null) {
- throw 'no domain for method: $method';
- }
+ final String prefix = method.substring(0, method.indexOf('.'));
+ final String name = method.substring(method.indexOf('.') + 1);
+ if (_domainMap[prefix] == null) {
+ throw 'no domain for method: $method';
+ }
- _domainMap[prefix].handleCommand(name, id, castStringKeyedMap(request['params']) ?? const <String, dynamic>{});
+ _domainMap[prefix].handleCommand(name, id, castStringKeyedMap(request['params']) ?? const <String, dynamic>{});
+ } else {
+ // If there was no 'method' field then it's a response to a daemon-to-editor request.
+ final Completer<dynamic> completer = _outgoingRequestCompleters[id.toString()];
+ if (completer == null) {
+ throw 'unexpected response with id: $id';
+ }
+ _outgoingRequestCompleters.remove(id.toString());
+
+ if (request['error'] != null) {
+ completer.completeError(request['error']);
+ } else {
+ completer.complete(request['result']);
+ }
+ }
} on Exception catch (error, trace) {
- connection.sendErrorResponse(id, _toJsonable(error), trace);
+ _send(<String, dynamic>{
+ 'id': id,
+ 'error': _toJsonable(error),
+ 'trace': '$trace',
+ });
}
}
+ Future<dynamic> sendRequest(String method, [ dynamic args ]) {
+ final Map<String, dynamic> map = <String, dynamic>{'method': method};
+ if (args != null) {
+ map['params'] = _toJsonable(args);
+ }
+
+ final int id = _outgoingRequestId++;
+ final Completer<dynamic> completer = Completer<dynamic>();
+
+ map['id'] = id.toString();
+ _outgoingRequestCompleters[id.toString()] = completer;
+
+ _send(map);
+ return completer.future;
+ }
+
+ void _send(Map<String, dynamic> map) => sendCommand(map);
+
Future<void> shutdown({ dynamic error }) async {
await devToolsDomain?.dispose();
await _commandSubscription?.cancel();
- await connection.dispose();
for (final Domain domain in _domainMap.values) {
await domain.dispose();
}
@@ -261,16 +225,30 @@
}
throw 'command not understood: $name.$command';
}).then<dynamic>((dynamic result) {
- daemon.connection.sendResponse(id, _toJsonable(result));
- }).catchError((Object error, StackTrace stackTrace) {
- daemon.connection.sendErrorResponse(id, _toJsonable(error), stackTrace);
+ if (result == null) {
+ _send(<String, dynamic>{'id': id});
+ } else {
+ _send(<String, dynamic>{'id': id, 'result': _toJsonable(result)});
+ }
+ }).catchError((dynamic error, dynamic trace) {
+ _send(<String, dynamic>{
+ 'id': id,
+ 'error': _toJsonable(error),
+ 'trace': '$trace',
+ });
});
}
void sendEvent(String name, [ dynamic args ]) {
- daemon.connection.sendEvent(name, _toJsonable(args));
+ final Map<String, dynamic> map = <String, dynamic>{'event': name};
+ if (args != null) {
+ map['params'] = _toJsonable(args);
+ }
+ _send(map);
}
+ void _send(Map<String, dynamic> map) => daemon._send(map);
+
String _getStringArg(Map<String, dynamic> args, String name, { bool required = false }) {
if (required && !args.containsKey(name)) {
throw '$name is required';
@@ -368,7 +346,7 @@
/// --web-allow-expose-url switch. The client may return the same URL back if
/// tunnelling is not required for a given URL.
Future<String> exposeUrl(String url) async {
- final dynamic res = await daemon.connection.sendRequest('app.exposeUrl', <String, String>{'url': url});
+ final dynamic res = await daemon.sendRequest('app.exposeUrl', <String, String>{'url': url});
if (res is Map<String, dynamic> && res['url'] is String) {
return res['url'] as String;
} else {
@@ -929,6 +907,35 @@
}
}
+Stream<Map<String, dynamic>> get stdinCommandStream => globals.stdio.stdin
+ .transform<String>(utf8.decoder)
+ .transform<String>(const LineSplitter())
+ .where((String line) => line.startsWith('[{') && line.endsWith('}]'))
+ .map<Map<String, dynamic>>((String line) {
+ line = line.substring(1, line.length - 1);
+ return castStringKeyedMap(json.decode(line));
+ });
+
+void stdoutCommandResponse(Map<String, dynamic> command) {
+ globals.stdio.stdoutWrite(
+ '[${jsonEncodeObject(command)}]\n',
+ fallback: (String message, dynamic error, StackTrace stack) {
+ throwToolExit('Failed to write daemon command response to stdout: $error');
+ },
+ );
+}
+
+String jsonEncodeObject(dynamic object) {
+ return json.encode(object, toEncodable: _toEncodable);
+}
+
+dynamic _toEncodable(dynamic object) {
+ if (object is OperationResult) {
+ return _operationResultToMap(object);
+ }
+ return object;
+}
+
Future<Map<String, dynamic>> _deviceToMap(Device device) async {
return <String, dynamic>{
'id': device.id,
@@ -963,7 +970,7 @@
return obj;
}
if (obj is OperationResult) {
- return _operationResultToMap(obj);
+ return obj;
}
if (obj is ToolExit) {
return obj.message;
diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart
index 2bf8050..01d34ce 100644
--- a/packages/flutter_tools/lib/src/commands/run.dart
+++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -14,7 +14,6 @@
import '../base/file_system.dart';
import '../base/utils.dart';
import '../build_info.dart';
-import '../daemon.dart';
import '../device.dart';
import '../features.dart';
import '../globals.dart' as globals;
@@ -557,10 +556,8 @@
throwToolExit('"--machine" does not support "-d all".');
}
final Daemon daemon = Daemon(
- DaemonConnection(
- daemonStreams: StdioDaemonStreams(globals.stdio),
- logger: globals.logger,
- ),
+ stdinCommandStream,
+ stdoutCommandResponse,
notifyingLogger: (globals.logger is NotifyingLogger)
? globals.logger as NotifyingLogger
: NotifyingLogger(verbose: globals.logger.isVerbose, parent: globals.logger),
diff --git a/packages/flutter_tools/lib/src/daemon.dart b/packages/flutter_tools/lib/src/daemon.dart
deleted file mode 100644
index d5be738..0000000
--- a/packages/flutter_tools/lib/src/daemon.dart
+++ /dev/null
@@ -1,241 +0,0 @@
-// 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 'base/common.dart';
-import 'base/io.dart';
-import 'base/logger.dart';
-import 'base/utils.dart';
-import 'convert.dart';
-
-/// Parse binary streams in the JSON RPC format understood by the daemon, and
-/// convert it into a stream of JSON RPC messages.
-Stream<Map<String, Object?>> _convertInputStream(Stream<List<int>> inputStream) {
- return utf8.decoder.bind(inputStream)
- .transform<String>(const LineSplitter())
- .where((String line) => line.startsWith('[{') && line.endsWith('}]'))
- .map<Map<String, Object?>?>((String line) {
- line = line.substring(1, line.length - 1);
- return castStringKeyedMap(json.decode(line));
- })
- .where((Map<String, Object?>? entry) => entry != null)
- .cast<Map<String, Object?>>();
-}
-
-/// A stream that a [DaemonConnection] uses to communicate with each other.
-abstract class DaemonStreams {
- /// Stream that contains input to the [DaemonConnection].
- Stream<Map<String, Object?>> get inputStream;
-
- /// Outputs a message through the connection.
- void send(Map<String, Object?> message);
-
- /// Cleans up any resources used.
- Future<void> dispose() async { }
-}
-
-/// A [DaemonStream] that uses stdin and stdout as the underlying streams.
-class StdioDaemonStreams extends DaemonStreams {
- StdioDaemonStreams(Stdio stdio) :
- _stdio = stdio,
- inputStream = _convertInputStream(stdio.stdin);
-
- final Stdio _stdio;
-
- @override
- final Stream<Map<String, Object?>> inputStream;
-
- @override
- void send(Map<String, Object?> message) {
- _stdio.stdoutWrite(
- '[${json.encode(message)}]\n',
- fallback: (String message, Object? error, StackTrace stack) {
- throwToolExit('Failed to write daemon command response to stdout: $error');
- },
- );
- }
-}
-
-/// A [DaemonStream] that uses [Socket] as the underlying stream.
-class TcpDaemonStreams extends DaemonStreams {
- /// Creates a [DaemonStreams] with an existing [Socket].
- TcpDaemonStreams(
- Socket socket, {
- required Logger logger,
- }): _logger = logger {
- _socket = Future<Socket>.value(_initializeSocket(socket));
- }
-
- /// Connects to a remote host and creates a [DaemonStreams] from the socket.
- TcpDaemonStreams.connect(
- String host,
- int port, {
- required Logger logger,
- }) : _logger = logger {
- _socket = Socket.connect(host, port).then(_initializeSocket);
- }
-
- late final Future<Socket> _socket;
- final StreamController<Map<String, Object?>> _commands = StreamController<Map<String, Object?>>();
- final Logger _logger;
-
- @override
- Stream<Map<String, Object?>> get inputStream => _commands.stream;
-
- @override
- void send(Map<String, Object?> message) {
- _socket.then((Socket socket) {
- try {
- socket.write('[${json.encode(message)}]\n');
- } on SocketException catch (error) {
- _logger.printError('Failed to write daemon command response to socket: $error');
- // Failed to send, close the connection
- socket.close();
- }
- });
- }
-
- Socket _initializeSocket(Socket socket) {
- _commands.addStream(_convertInputStream(socket));
- return socket;
- }
-
- @override
- Future<void> dispose() async {
- await (await _socket).close();
- }
-}
-
-/// Connection between a flutter daemon and a client.
-class DaemonConnection {
- DaemonConnection({
- required DaemonStreams daemonStreams,
- required Logger logger,
- }): _logger = logger,
- _daemonStreams = daemonStreams {
- _commandSubscription = daemonStreams.inputStream.listen(
- _handleData,
- onError: (Object error, StackTrace stackTrace) {
- // We have to listen for on error otherwise the error on the socket
- // will end up in the Zone error handler.
- // Do nothing here and let the stream close handlers handle shutting
- // down the daemon.
- }
- );
- }
-
- final DaemonStreams _daemonStreams;
-
- final Logger _logger;
-
- late final StreamSubscription<Map<String, Object?>> _commandSubscription;
-
- int _outgoingRequestId = 0;
- final Map<String, Completer<Object?>> _outgoingRequestCompleters = <String, Completer<Object?>>{};
-
- final StreamController<Map<String, Object?>> _events = StreamController<Map<String, Object?>>.broadcast();
- final StreamController<Map<String, Object?>> _incomingCommands = StreamController<Map<String, Object?>>();
-
- /// A stream that contains all the incoming requests.
- Stream<Map<String, Object?>> get incomingCommands => _incomingCommands.stream;
-
- /// Listens to the event with the event name [eventToListen].
- Stream<Object?> listenToEvent(String eventToListen) {
- return _events.stream
- .where((Map<String, Object?> event) => event['event'] == eventToListen)
- .map<Object?>((Map<String, Object?> event) => event['params']);
- }
-
- /// Sends a request to the other end of the connection.
- ///
- /// Returns a [Future] that resolves with the content.
- Future<Object?> sendRequest(String method, [Object? params]) async {
- final String id = '${++_outgoingRequestId}';
- final Completer<Object?> completer = Completer<Object?>();
- _outgoingRequestCompleters[id] = completer;
- final Map<String, Object?> data = <String, Object?>{
- 'id': id,
- 'method': method,
- if (params != null) 'params': params,
- };
- _logger.printTrace('-> Sending to daemon, id = $id, method = $method');
- _daemonStreams.send(data);
- return completer.future;
- }
-
- /// Sends a response to the other end of the connection.
- void sendResponse(Object id, [Object? result]) {
- _daemonStreams.send(<String, Object?>{
- 'id': id,
- if (result != null) 'result': result,
- });
- }
-
- /// Sends an error response to the other end of the connection.
- void sendErrorResponse(Object id, Object error, StackTrace trace) {
- _daemonStreams.send(<String, Object?>{
- 'id': id,
- 'error': error,
- 'trace': '$trace',
- });
- }
-
- /// Sends an event to the client.
- void sendEvent(String name, [ Object? params ]) {
- _daemonStreams.send(<String, Object?>{
- 'event': name,
- if (params != null) 'params': params,
- });
- }
-
- /// Handles the input from the stream.
- ///
- /// There are three kinds of data: Request, Response, Event.
- ///
- /// Request:
- /// {"id": <Object>. "method": <String>, "params": <optional, Object?>}
- ///
- /// Response:
- /// {"id": <Object>. "result": <optional, Object?>} for a successful response.
- /// {"id": <Object>. "error": <Object>, "stackTrace": <String>} for an error response.
- ///
- /// Event:
- /// {"event": <String>. "params": <optional, Object?>}
- void _handleData(Map<String, Object?> data) {
- if (data['id'] != null) {
- if (data['method'] == null) {
- // This is a response to previously sent request.
- final String id = data['id']! as String;
- if (data['error'] != null) {
- // This is an error response.
- _logger.printTrace('<- Error response received from daemon, id = $id');
- final Object error = data['error']!;
- final String stackTrace = data['stackTrace'] as String? ?? '';
- _outgoingRequestCompleters.remove(id)?.completeError(error, StackTrace.fromString(stackTrace));
- } else {
- _logger.printTrace('<- Response received from daemon, id = $id');
- final Object? result = data['result'];
- _outgoingRequestCompleters.remove(id)?.complete(result);
- }
- } else {
- _incomingCommands.add(data);
- }
- } else if (data['event'] != null) {
- // This is an event
- _logger.printTrace('<- Event received: ${data['event']}');
- _events.add(data);
- } else {
- _logger.printError('Unknown data received from daemon');
- }
- }
-
- /// Cleans up any resources used in the connection.
- Future<void> dispose() async {
- await _commandSubscription.cancel();
- await _daemonStreams.dispose();
- unawaited(_events.close());
- unawaited(_incomingCommands.close());
- }
-}
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart
index d9af9f4..fdbd23d 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart
@@ -20,7 +20,6 @@
import 'package:flutter_tools/src/base/utils.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/commands/daemon.dart';
-import 'package:flutter_tools/src/daemon.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/fuchsia/fuchsia_workflow.dart';
@@ -49,92 +48,75 @@
});
}
-class FakeDaemonStreams extends DaemonStreams {
- final StreamController<Map<String, dynamic>> inputs = StreamController<Map<String, dynamic>>();
- final StreamController<Map<String, dynamic>> outputs = StreamController<Map<String, dynamic>>();
-
- @override
- Stream<Map<String, dynamic>> get inputStream {
- return inputs.stream;
- }
-
- @override
- void send(Map<String, dynamic> message) {
- outputs.add(message);
- }
-
- @override
- Future<void> dispose() async {
- await inputs.close();
- // In some tests, outputs have no listeners. We don't wait for outputs to close.
- unawaited(outputs.close());
- }
-}
-
void main() {
Daemon daemon;
NotifyingLogger notifyingLogger;
BufferLogger bufferLogger;
group('daemon', () {
- FakeDaemonStreams daemonStreams;
- DaemonConnection daemonConnection;
setUp(() {
bufferLogger = BufferLogger.test();
notifyingLogger = NotifyingLogger(verbose: false, parent: bufferLogger);
- daemonStreams = FakeDaemonStreams();
- daemonConnection = DaemonConnection(
- daemonStreams: daemonStreams,
- logger: bufferLogger,
- );
});
- tearDown(() async {
+ tearDown(() {
if (daemon != null) {
return daemon.shutdown();
}
notifyingLogger.dispose();
- await daemonConnection.dispose();
});
testUsingContext('daemon.version command should succeed', () async {
+ final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+ final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
daemon = Daemon(
- daemonConnection,
+ commands.stream,
+ responses.add,
notifyingLogger: notifyingLogger,
);
- daemonStreams.inputs.add(<String, dynamic>{'id': 0, 'method': 'daemon.version'});
- final Map<String, dynamic> response = await daemonStreams.outputs.stream.firstWhere(_notEvent);
+ commands.add(<String, dynamic>{'id': 0, 'method': 'daemon.version'});
+ final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
expect(response['id'], 0);
expect(response['result'], isNotEmpty);
expect(response['result'], isA<String>());
+ await responses.close();
+ await commands.close();
});
testUsingContext('daemon.getSupportedPlatforms command should succeed', () async {
+ final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+ final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
daemon = Daemon(
- daemonConnection,
+ commands.stream,
+ responses.add,
notifyingLogger: notifyingLogger,
);
// Use the flutter_gallery project which has a known set of supported platforms.
final String projectPath = globals.fs.path.join(getFlutterRoot(), 'dev', 'integration_tests', 'flutter_gallery');
- daemonStreams.inputs.add(<String, dynamic>{'id': 0, 'method': 'daemon.getSupportedPlatforms', 'params': <String, Object>{'projectRoot': projectPath}});
- final Map<String, dynamic> response = await daemonStreams.outputs.stream.firstWhere(_notEvent);
+ commands.add(<String, dynamic>{'id': 0, 'method': 'daemon.getSupportedPlatforms', 'params': <String, Object>{'projectRoot': projectPath}});
+ final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
expect(response['id'], 0);
expect(response['result'], isNotEmpty);
expect((response['result'] as Map<String, dynamic>)['platforms'], <String>{'macos'});
+ await responses.close();
+ await commands.close();
}, overrides: <Type, Generator>{
// Disable Android/iOS and enable macOS to make sure result is consistent and defaults are tested off.
FeatureFlags: () => TestFeatureFlags(isAndroidEnabled: false, isIOSEnabled: false, isMacOSEnabled: true),
});
testUsingContext('printError should send daemon.logMessage event', () async {
+ final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+ final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
daemon = Daemon(
- daemonConnection,
+ commands.stream,
+ responses.add,
notifyingLogger: notifyingLogger,
);
globals.printError('daemon.logMessage test');
- final Map<String, dynamic> response = await daemonStreams.outputs.stream.firstWhere((Map<String, dynamic> map) {
+ final Map<String, dynamic> response = await responses.stream.firstWhere((Map<String, dynamic> map) {
return map['event'] == 'daemon.logMessage' && (map['params'] as Map<String, dynamic>)['level'] == 'error';
});
expect(response['id'], isNull);
@@ -142,17 +124,22 @@
final Map<String, String> logMessage = castStringKeyedMap(response['params']).cast<String, String>();
expect(logMessage['level'], 'error');
expect(logMessage['message'], 'daemon.logMessage test');
+ await responses.close();
+ await commands.close();
}, overrides: <Type, Generator>{
Logger: () => notifyingLogger,
});
testUsingContext('printWarning should send daemon.logMessage event', () async {
+ final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+ final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
daemon = Daemon(
- daemonConnection,
+ commands.stream,
+ responses.add,
notifyingLogger: notifyingLogger,
);
globals.printWarning('daemon.logMessage test');
- final Map<String, dynamic> response = await daemonStreams.outputs.stream.firstWhere((Map<String, dynamic> map) {
+ final Map<String, dynamic> response = await responses.stream.firstWhere((Map<String, dynamic> map) {
return map['event'] == 'daemon.logMessage' && (map['params'] as Map<String, dynamic>)['level'] == 'warning';
});
expect(response['id'], isNull);
@@ -160,14 +147,19 @@
final Map<String, String> logMessage = castStringKeyedMap(response['params']).cast<String, String>();
expect(logMessage['level'], 'warning');
expect(logMessage['message'], 'daemon.logMessage test');
+ await responses.close();
+ await commands.close();
}, overrides: <Type, Generator>{
Logger: () => notifyingLogger,
});
testUsingContext('printStatus should log to stdout when logToStdout is enabled', () async {
final StringBuffer buffer = await capturedConsolePrint(() {
+ final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+ final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
daemon = Daemon(
- daemonConnection,
+ commands.stream,
+ responses.add,
notifyingLogger: notifyingLogger,
logToStdout: true,
);
@@ -200,89 +192,120 @@
});
testUsingContext('daemon.shutdown command should stop daemon', () async {
+ final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+ final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
daemon = Daemon(
- daemonConnection,
+ commands.stream,
+ responses.add,
notifyingLogger: notifyingLogger,
);
- daemonStreams.inputs.add(<String, dynamic>{'id': 0, 'method': 'daemon.shutdown'});
+ commands.add(<String, dynamic>{'id': 0, 'method': 'daemon.shutdown'});
return daemon.onExit.then<void>((int code) async {
- await daemonStreams.inputs.close();
+ await commands.close();
expect(code, 0);
});
});
testUsingContext('app.restart without an appId should report an error', () async {
+ final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+ final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
daemon = Daemon(
- daemonConnection,
+ commands.stream,
+ responses.add,
notifyingLogger: notifyingLogger,
);
- daemonStreams.inputs.add(<String, dynamic>{'id': 0, 'method': 'app.restart'});
- final Map<String, dynamic> response = await daemonStreams.outputs.stream.firstWhere(_notEvent);
+ commands.add(<String, dynamic>{'id': 0, 'method': 'app.restart'});
+ final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
expect(response['id'], 0);
expect(response['error'], contains('appId is required'));
+ await responses.close();
+ await commands.close();
});
testUsingContext('ext.flutter.debugPaint via service extension without an appId should report an error', () async {
+ final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+ final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
daemon = Daemon(
- daemonConnection,
+ commands.stream,
+ responses.add,
notifyingLogger: notifyingLogger,
);
- daemonStreams.inputs.add(<String, dynamic>{
+ commands.add(<String, dynamic>{
'id': 0,
'method': 'app.callServiceExtension',
'params': <String, String>{
'methodName': 'ext.flutter.debugPaint',
},
});
- final Map<String, dynamic> response = await daemonStreams.outputs.stream.firstWhere(_notEvent);
+ final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
expect(response['id'], 0);
expect(response['error'], contains('appId is required'));
+ await responses.close();
+ await commands.close();
});
testUsingContext('app.stop without appId should report an error', () async {
+ final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+ final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
daemon = Daemon(
- daemonConnection,
+ commands.stream,
+ responses.add,
notifyingLogger: notifyingLogger,
);
- daemonStreams.inputs.add(<String, dynamic>{'id': 0, 'method': 'app.stop'});
- final Map<String, dynamic> response = await daemonStreams.outputs.stream.firstWhere(_notEvent);
+ commands.add(<String, dynamic>{'id': 0, 'method': 'app.stop'});
+ final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
expect(response['id'], 0);
expect(response['error'], contains('appId is required'));
+ await responses.close();
+ await commands.close();
});
testUsingContext('device.getDevices should respond with list', () async {
+ final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+ final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
daemon = Daemon(
- daemonConnection,
+ commands.stream,
+ responses.add,
notifyingLogger: notifyingLogger,
);
- daemonStreams.inputs.add(<String, dynamic>{'id': 0, 'method': 'device.getDevices'});
- final Map<String, dynamic> response = await daemonStreams.outputs.stream.firstWhere(_notEvent);
+ commands.add(<String, dynamic>{'id': 0, 'method': 'device.getDevices'});
+ final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
expect(response['id'], 0);
expect(response['result'], isList);
+ await responses.close();
+ await commands.close();
});
testUsingContext('device.getDevices reports available devices', () async {
+ final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+ final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
daemon = Daemon(
- daemonConnection,
+ commands.stream,
+ responses.add,
notifyingLogger: notifyingLogger,
);
final FakePollingDeviceDiscovery discoverer = FakePollingDeviceDiscovery();
daemon.deviceDomain.addDeviceDiscoverer(discoverer);
discoverer.addDevice(FakeAndroidDevice());
- daemonStreams.inputs.add(<String, dynamic>{'id': 0, 'method': 'device.getDevices'});
- final Map<String, dynamic> response = await daemonStreams.outputs.stream.firstWhere(_notEvent);
+ commands.add(<String, dynamic>{'id': 0, 'method': 'device.getDevices'});
+ final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
expect(response['id'], 0);
final dynamic result = response['result'];
expect(result, isList);
expect(result, isNotEmpty);
+ await responses.close();
+ await commands.close();
});
testUsingContext('should send device.added event when device is discovered', () async {
+ final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+ final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
daemon = Daemon(
- daemonConnection,
+ commands.stream,
+ responses.add,
notifyingLogger: notifyingLogger,
);
@@ -290,12 +313,15 @@
daemon.deviceDomain.addDeviceDiscoverer(discoverer);
discoverer.addDevice(FakeAndroidDevice());
- return daemonStreams.outputs.stream.skipWhile(_isConnectedEvent).first.then<void>((Map<String, dynamic> response) async {
+ return responses.stream.skipWhile(_isConnectedEvent).first.then<void>((Map<String, dynamic> response) async {
expect(response['event'], 'device.added');
expect(response['params'], isMap);
final Map<String, dynamic> params = castStringKeyedMap(response['params']);
expect(params['platform'], isNotEmpty); // the fake device has a platform of 'android-arm'
+
+ await responses.close();
+ await commands.close();
});
}, overrides: <Type, Generator>{
AndroidWorkflow: () => FakeAndroidWorkflow(),
@@ -304,90 +330,121 @@
});
testUsingContext('emulator.launch without an emulatorId should report an error', () async {
+ final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+ final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
daemon = Daemon(
- daemonConnection,
+ commands.stream,
+ responses.add,
notifyingLogger: notifyingLogger,
);
- daemonStreams.inputs.add(<String, dynamic>{'id': 0, 'method': 'emulator.launch'});
- final Map<String, dynamic> response = await daemonStreams.outputs.stream.firstWhere(_notEvent);
+ commands.add(<String, dynamic>{'id': 0, 'method': 'emulator.launch'});
+ final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
expect(response['id'], 0);
expect(response['error'], contains('emulatorId is required'));
+ await responses.close();
+ await commands.close();
});
testUsingContext('emulator.launch coldboot parameter must be boolean', () async {
+ final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+ final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
daemon = Daemon(
- daemonConnection,
+ commands.stream,
+ responses.add,
notifyingLogger: notifyingLogger,
);
final Map<String, dynamic> params = <String, dynamic>{'emulatorId': 'device', 'coldBoot': 1};
- daemonStreams.inputs.add(<String, dynamic>{'id': 0, 'method': 'emulator.launch', 'params': params});
- final Map<String, dynamic> response = await daemonStreams.outputs.stream.firstWhere(_notEvent);
+ commands.add(<String, dynamic>{'id': 0, 'method': 'emulator.launch', 'params': params});
+ final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
expect(response['id'], 0);
expect(response['error'], contains('coldBoot is not a bool'));
+ await responses.close();
+ await commands.close();
});
testUsingContext('emulator.getEmulators should respond with list', () async {
+ final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+ final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
daemon = Daemon(
- daemonConnection,
+ commands.stream,
+ responses.add,
notifyingLogger: notifyingLogger,
);
- daemonStreams.inputs.add(<String, dynamic>{'id': 0, 'method': 'emulator.getEmulators'});
- final Map<String, dynamic> response = await daemonStreams.outputs.stream.firstWhere(_notEvent);
+ commands.add(<String, dynamic>{'id': 0, 'method': 'emulator.getEmulators'});
+ final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
expect(response['id'], 0);
expect(response['result'], isList);
+ await responses.close();
+ await commands.close();
});
testUsingContext('daemon can send exposeUrl requests to the client', () async {
const String originalUrl = 'http://localhost:1234/';
const String mappedUrl = 'https://publichost:4321/';
+ final StreamController<Map<String, dynamic>> input = StreamController<Map<String, dynamic>>();
+ final StreamController<Map<String, dynamic>> output = StreamController<Map<String, dynamic>>();
daemon = Daemon(
- daemonConnection,
+ input.stream,
+ output.add,
notifyingLogger: notifyingLogger,
);
// Respond to any requests from the daemon to expose a URL.
- unawaited(daemonStreams.outputs.stream
+ unawaited(output.stream
.firstWhere((Map<String, dynamic> request) => request['method'] == 'app.exposeUrl')
.then((Map<String, dynamic> request) {
expect((request['params'] as Map<String, dynamic>)['url'], equals(originalUrl));
- daemonStreams.inputs.add(<String, dynamic>{'id': request['id'], 'result': <String, dynamic>{'url': mappedUrl}});
+ input.add(<String, dynamic>{'id': request['id'], 'result': <String, dynamic>{'url': mappedUrl}});
})
);
final String exposedUrl = await daemon.daemonDomain.exposeUrl(originalUrl);
expect(exposedUrl, equals(mappedUrl));
+
+ await output.close();
+ await input.close();
});
testUsingContext('devtools.serve command should return host and port on success', () async {
+ final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+ final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
daemon = Daemon(
- daemonConnection,
+ commands.stream,
+ responses.add,
notifyingLogger: notifyingLogger,
);
- daemonStreams.inputs.add(<String, dynamic>{'id': 0, 'method': 'devtools.serve'});
- final Map<String, dynamic> response = await daemonStreams.outputs.stream.firstWhere((Map<String, dynamic> response) => response['id'] == 0);
+ commands.add(<String, dynamic>{'id': 0, 'method': 'devtools.serve'});
+ final Map<String, dynamic> response = await responses.stream.firstWhere((Map<String, dynamic> response) => response['id'] == 0);
final Map<String, dynamic> result = response['result'] as Map<String, dynamic>;
expect(result, isNotEmpty);
expect(result['host'], '127.0.0.1');
expect(result['port'], 1234);
+ await responses.close();
+ await commands.close();
}, overrides: <Type, Generator>{
DevtoolsLauncher: () => FakeDevtoolsLauncher(DevToolsServerAddress('127.0.0.1', 1234)),
});
testUsingContext('devtools.serve command should return null fields if null returned', () async {
+ final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+ final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
daemon = Daemon(
- daemonConnection,
+ commands.stream,
+ responses.add,
notifyingLogger: notifyingLogger,
);
- daemonStreams.inputs.add(<String, dynamic>{'id': 0, 'method': 'devtools.serve'});
- final Map<String, dynamic> response = await daemonStreams.outputs.stream.firstWhere((Map<String, dynamic> response) => response['id'] == 0);
+ commands.add(<String, dynamic>{'id': 0, 'method': 'devtools.serve'});
+ final Map<String, dynamic> response = await responses.stream.firstWhere((Map<String, dynamic> response) => response['id'] == 0);
final Map<String, dynamic> result = response['result'] as Map<String, dynamic>;
expect(result, isNotEmpty);
expect(result['host'], null);
expect(result['port'], null);
+ await responses.close();
+ await commands.close();
}, overrides: <Type, Generator>{
DevtoolsLauncher: () => FakeDevtoolsLauncher(null),
});
@@ -426,6 +483,19 @@
expect(message.message, 'hello');
});
+ group('daemon serialization', () {
+ test('OperationResult', () {
+ expect(
+ jsonEncodeObject(OperationResult.ok),
+ '{"code":0,"message":""}',
+ );
+ expect(
+ jsonEncodeObject(OperationResult(1, 'foo')),
+ '{"code":1,"message":"foo"}',
+ );
+ });
+ });
+
group('daemon queue', () {
DebounceOperationQueue<int, String> queue;
const Duration debounceDuration = Duration(seconds: 1);
diff --git a/packages/flutter_tools/test/general.shard/daemon_test.dart b/packages/flutter_tools/test/general.shard/daemon_test.dart
deleted file mode 100644
index fb2d995..0000000
--- a/packages/flutter_tools/test/general.shard/daemon_test.dart
+++ /dev/null
@@ -1,240 +0,0 @@
-// 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:typed_data';
-
-import 'package:flutter_tools/src/base/common.dart';
-import 'package:flutter_tools/src/base/io.dart';
-import 'package:flutter_tools/src/base/logger.dart';
-import 'package:flutter_tools/src/convert.dart';
-import 'package:flutter_tools/src/daemon.dart';
-import 'package:test/fake.dart';
-
-import '../src/common.dart';
-
-class FakeDaemonStreams extends DaemonStreams {
- final StreamController<Map<String, dynamic>> inputs = StreamController<Map<String, dynamic>>();
- final StreamController<Map<String, dynamic>> outputs = StreamController<Map<String, dynamic>>();
-
- @override
- Stream<Map<String, dynamic>> get inputStream {
- return inputs.stream;
- }
-
- @override
- void send(Map<String, dynamic> message) {
- outputs.add(message);
- }
-
- @override
- Future<void> dispose() async {
- await inputs.close();
- // In some tests, outputs have no listeners. We don't wait for outputs to close.
- unawaited(outputs.close());
- }
-}
-
-void main() {
- late BufferLogger bufferLogger;
- late FakeDaemonStreams daemonStreams;
- late DaemonConnection daemonConnection;
- setUp(() {
- bufferLogger = BufferLogger.test();
- daemonStreams = FakeDaemonStreams();
- daemonConnection = DaemonConnection(
- daemonStreams: daemonStreams,
- logger: bufferLogger,
- );
- });
-
- tearDown(() async {
- await daemonConnection.dispose();
- });
-
- group('DaemonConnection receiving end', () {
- testWithoutContext('redirects input to incoming commands', () async {
- final Map<String, dynamic> commandToSend = <String, dynamic>{'id': 0, 'method': 'some_method'};
- daemonStreams.inputs.add(commandToSend);
-
- final Map<String, dynamic> commandReceived = await daemonConnection.incomingCommands.first;
- await daemonStreams.dispose();
-
- expect(commandReceived, commandToSend);
- });
-
- testWithoutContext('listenToEvent can receive the right events', () async {
- final Future<List<dynamic>> events = daemonConnection.listenToEvent('event1').toList();
-
- daemonStreams.inputs.add(<String, dynamic>{'event': 'event1', 'params': '1'});
- daemonStreams.inputs.add(<String, dynamic>{'event': 'event2', 'params': '2'});
- daemonStreams.inputs.add(<String, dynamic>{'event': 'event1', 'params': null});
- daemonStreams.inputs.add(<String, dynamic>{'event': 'event1', 'params': 3});
-
- await pumpEventQueue();
- await daemonConnection.dispose();
-
- expect(await events, <dynamic>['1', null, 3]);
- });
- });
-
- group('DaemonConnection sending end', () {
- testWithoutContext('sending requests', () async {
- unawaited(daemonConnection.sendRequest('some_method', 'param'));
- final Map<String, dynamic> data = await daemonStreams.outputs.stream.first;
- expect(data['id'], isNotNull);
- expect(data['method'], 'some_method');
- expect(data['params'], 'param');
- });
-
- testWithoutContext('sending requests without param', () async {
- unawaited(daemonConnection.sendRequest('some_method'));
- final Map<String, dynamic> data = await daemonStreams.outputs.stream.first;
- expect(data['id'], isNotNull);
- expect(data['method'], 'some_method');
- expect(data['params'], isNull);
- });
-
- testWithoutContext('sending response', () async {
- daemonConnection.sendResponse('1', 'some_data');
- final Map<String, dynamic> data = await daemonStreams.outputs.stream.first;
- expect(data['id'], '1');
- expect(data['method'], isNull);
- expect(data['error'], isNull);
- expect(data['result'], 'some_data');
- });
-
- testWithoutContext('sending response without data', () async {
- daemonConnection.sendResponse('1');
- final Map<String, dynamic> data = await daemonStreams.outputs.stream.first;
- expect(data['id'], '1');
- expect(data['method'], isNull);
- expect(data['error'], isNull);
- expect(data['result'], isNull);
- });
-
- testWithoutContext('sending error response', () async {
- daemonConnection.sendErrorResponse('1', 'error', StackTrace.fromString('stack trace'));
- final Map<String, dynamic> data = await daemonStreams.outputs.stream.first;
- expect(data['id'], '1');
- expect(data['method'], isNull);
- expect(data['error'], 'error');
- expect(data['trace'], 'stack trace');
- });
-
- testWithoutContext('sending events', () async {
- daemonConnection.sendEvent('some_event', '123');
- final Map<String, dynamic> data = await daemonStreams.outputs.stream.first;
- expect(data['id'], isNull);
- expect(data['event'], 'some_event');
- expect(data['params'], '123');
- });
-
- testWithoutContext('sending events without params', () async {
- daemonConnection.sendEvent('some_event');
- final Map<String, dynamic> data = await daemonStreams.outputs.stream.first;
- expect(data['id'], isNull);
- expect(data['event'], 'some_event');
- expect(data['params'], isNull);
- });
- });
-
- group('DaemonConnection request and response', () {
- testWithoutContext('receiving response from requests', () async {
- final Future<dynamic> requestFuture = daemonConnection.sendRequest('some_method', 'param');
- final Map<String, dynamic> data = await daemonStreams.outputs.stream.first;
-
- expect(data['id'], isNotNull);
- expect(data['method'], 'some_method');
- expect(data['params'], 'param');
-
- final String id = data['id'] as String;
- daemonStreams.inputs.add(<String, dynamic>{'id': id, 'result': '123'});
- expect(await requestFuture, '123');
- });
-
- testWithoutContext('receiving response from requests without result', () async {
- final Future<dynamic> requestFuture = daemonConnection.sendRequest('some_method', 'param');
- final Map<String, dynamic> data = await daemonStreams.outputs.stream.first;
-
- expect(data['id'], isNotNull);
- expect(data['method'], 'some_method');
- expect(data['params'], 'param');
-
- final String id = data['id'] as String;
- daemonStreams.inputs.add(<String, dynamic>{'id': id});
- expect(await requestFuture, null);
- });
-
- testWithoutContext('receiving error response from requests without result', () async {
- final Future<dynamic> requestFuture = daemonConnection.sendRequest('some_method', 'param');
- final Map<String, dynamic> data = await daemonStreams.outputs.stream.first;
-
- expect(data['id'], isNotNull);
- expect(data['method'], 'some_method');
- expect(data['params'], 'param');
-
- final String id = data['id'] as String;
- daemonStreams.inputs.add(<String, dynamic>{'id': id, 'error': 'some_error', 'trace': 'stack trace'});
- expect(requestFuture, throwsA('some_error'));
- });
- });
-
- group('TcpDaemonStreams', () {
- final Map<String, Object?> testCommand = <String, Object?>{
- 'id': 100,
- 'method': 'test',
- };
- late FakeSocket socket;
- late TcpDaemonStreams daemonStreams;
- setUp(() {
- socket = FakeSocket();
- daemonStreams = TcpDaemonStreams(socket, logger: bufferLogger);
- });
-
- test('parses the message received on the socket', () async {
- socket.controller.add(Uint8List.fromList(utf8.encode('[${jsonEncode(testCommand)}]\n')));
- final Map<String, Object?> command = await daemonStreams.inputStream.first;
- expect(command, testCommand);
- });
-
- test('sends the encoded message through the socket', () async {
- daemonStreams.send(testCommand);
- await pumpEventQueue();
- expect(socket.writtenObjects.length, 1);
- expect(socket.writtenObjects[0].toString(), '[${jsonEncode(testCommand)}]\n');
- });
-
- test('dispose calls socket.close', () async {
- await daemonStreams.dispose();
- expect(socket.closeCalled, isTrue);
- });
- });
-}
-
-class FakeSocket extends Fake implements Socket {
- bool closeCalled = false;
- final StreamController<Uint8List> controller = StreamController<Uint8List>();
- final List<Object?> writtenObjects = <Object?>[];
-
- @override
- StreamSubscription<Uint8List> listen(
- void Function(Uint8List event)? onData, {
- Function? onError,
- void Function()? onDone,
- bool? cancelOnError,
- }) {
- return controller.stream.listen(onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError);
- }
-
- @override
- void write(Object? object) {
- writtenObjects.add(object);
- }
-
- @override
- Future<void> close() async {
- closeCalled = true;
- }
-}