mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

* Add an option for flutter daemon to listen on a TCP port Added a new class DaemonConnection to reuse the connection handling between daemon server and client, and handle connection with different medium (stdio, socket). Added a new option `listen-on-tcp-port` to the flutter daemon command, when passed, the daemon will accept commands on a port instead of stdio. * Review feedback and add test for TcpDaemonStreams * Review feedbacks
241 lines
8.4 KiB
Dart
241 lines
8.4 KiB
Dart
// 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;
|
|
}
|
|
}
|