blob: 3af20ff3e75308113b1eca5e332a945318f495d5 [file] [log] [blame]
// 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:io';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/terminal.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/version.dart';
import 'package:flutter_tools/src/vmservice.dart';
import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
import 'package:mockito/mockito.dart';
import 'package:platform/platform.dart';
import 'package:quiver/testing/async.dart';
import '../src/common.dart';
import '../src/context.dart';
import '../src/mocks.dart';
class MockPeer implements rpc.Peer {
Function _versionFn = (dynamic _) => null;
@override
rpc.ErrorCallback get onUnhandledError => null;
@override
Future<dynamic> get done async {
throw 'unexpected call to done';
}
@override
bool get isClosed => _isClosed;
@override
Future<dynamic> close() async {
_isClosed = true;
}
@override
Future<dynamic> listen() async {
// this does get called
}
@override
void registerFallback(dynamic callback(rpc.Parameters parameters)) {
throw 'unexpected call to registerFallback';
}
@override
void registerMethod(String name, Function callback) {
registeredMethods.add(name);
if (name == 'flutterVersion') {
_versionFn = callback;
}
}
@override
void sendNotification(String method, [ dynamic parameters ]) {
// this does get called
sentNotifications.putIfAbsent(method, () => <dynamic>[]).add(parameters);
}
Map<String, List<dynamic>> sentNotifications = <String, List<dynamic>>{};
List<String> registeredMethods = <String>[];
bool isolatesEnabled = false;
bool _isClosed = false;
Future<void> _getVMLatch;
Completer<void> _currentGetVMLatchCompleter;
void tripGetVMLatch() {
final Completer<void> lastCompleter = _currentGetVMLatchCompleter;
_currentGetVMLatchCompleter = Completer<void>();
_getVMLatch = _currentGetVMLatchCompleter.future;
lastCompleter?.complete();
}
int returnedFromSendRequest = 0;
@override
Future<dynamic> sendRequest(String method, [ dynamic parameters ]) async {
if (method == 'getVM') {
await _getVMLatch;
}
await Future<void>.delayed(Duration.zero);
returnedFromSendRequest += 1;
if (method == 'getVM') {
return <String, dynamic>{
'type': 'VM',
'name': 'vm',
'architectureBits': 64,
'targetCPU': 'x64',
'hostCPU': ' Intel(R) Xeon(R) CPU E5-1650 v2 @ 3.50GHz',
'version': '2.1.0-dev.7.1.flutter-45f9462398 (Fri Oct 19 19:27:56 2018 +0000) on "linux_x64"',
'_profilerMode': 'Dart',
'_nativeZoneMemoryUsage': 0,
'pid': 103707,
'startTime': 1540426121876,
'_embedder': 'Flutter',
'_maxRSS': 312614912,
'_currentRSS': 33091584,
'isolates': isolatesEnabled ? <dynamic>[
<String, dynamic>{
'type': '@Isolate',
'fixedId': true,
'id': 'isolates/242098474',
'name': 'main.dart:main()',
'number': 242098474,
},
] : <dynamic>[],
};
}
if (method == 'getIsolate') {
return <String, dynamic>{
'type': 'Isolate',
'fixedId': true,
'id': 'isolates/242098474',
'name': 'main.dart:main()',
'number': 242098474,
'_originNumber': 242098474,
'startTime': 1540488745340,
'_heaps': <String, dynamic>{
'new': <String, dynamic>{
'used': 0,
'capacity': 0,
'external': 0,
'collections': 0,
'time': 0.0,
'avgCollectionPeriodMillis': 0.0,
},
'old': <String, dynamic>{
'used': 0,
'capacity': 0,
'external': 0,
'collections': 0,
'time': 0.0,
'avgCollectionPeriodMillis': 0.0,
},
},
};
}
if (method == '_flutter.listViews') {
return <String, dynamic>{
'type': 'FlutterViewList',
'views': isolatesEnabled ? <dynamic>[
<String, dynamic>{
'type': 'FlutterView',
'id': '_flutterView/0x4a4c1f8',
'isolate': <String, dynamic>{
'type': '@Isolate',
'fixedId': true,
'id': 'isolates/242098474',
'name': 'main.dart:main()',
'number': 242098474,
},
},
] : <dynamic>[],
};
}
if (method == 'flutterVersion') {
return _versionFn(parameters);
}
return null;
}
@override
dynamic withBatch(dynamic callback()) {
throw 'unexpected call to withBatch';
}
}
void main() {
MockStdio mockStdio;
final MockFlutterVersion mockVersion = MockFlutterVersion();
group('VMService', () {
setUp(() {
mockStdio = MockStdio();
});
testUsingContext('fails connection eagerly in the connect() method', () async {
FakeAsync().run((FakeAsync time) {
bool failed = false;
final Future<VMService> future = VMService.connect(Uri.parse('http://host.invalid:9999/'));
future.whenComplete(() {
failed = true;
});
time.elapse(const Duration(seconds: 5));
expect(failed, isFalse);
expect(mockStdio.writtenToStdout.join(''), '');
expect(mockStdio.writtenToStderr.join(''), '');
time.elapse(const Duration(seconds: 5));
expect(failed, isFalse);
expect(mockStdio.writtenToStdout.join(''), 'This is taking longer than expected...\n');
expect(mockStdio.writtenToStderr.join(''), '');
});
}, overrides: <Type, Generator>{
Logger: () => StdoutLogger(
outputPreferences: OutputPreferences.test(),
stdio: mockStdio,
terminal: AnsiTerminal(
stdio: mockStdio,
platform: const LocalPlatform(),
),
timeoutConfiguration: const TimeoutConfiguration(),
),
WebSocketConnector: () => (String url, {CompressionOptions compression}) async => throw const SocketException('test'),
});
testUsingContext('closing VMService closes Peer', () async {
final MockPeer mockPeer = MockPeer();
final VMService vmService = VMService(mockPeer, null, null, null, null, null, MockDevice(), null);
expect(mockPeer.isClosed, equals(false));
await vmService.close();
expect(mockPeer.isClosed, equals(true));
});
testUsingContext('refreshViews', () {
FakeAsync().run((FakeAsync time) {
bool done = false;
final MockPeer mockPeer = MockPeer();
expect(mockPeer.returnedFromSendRequest, 0);
final VMService vmService = VMService(mockPeer, null, null, null, null, null, null, null);
expect(mockPeer.sentNotifications, contains('registerService'));
final List<String> registeredServices =
mockPeer.sentNotifications['registerService']
.map((dynamic service) => (service as Map<String, String>)['service'])
.toList();
expect(registeredServices, contains('flutterVersion'));
vmService.getVM().then((void value) { done = true; });
expect(done, isFalse);
expect(mockPeer.returnedFromSendRequest, 0);
time.elapse(Duration.zero);
expect(done, isTrue);
expect(mockPeer.returnedFromSendRequest, 1);
done = false;
mockPeer.tripGetVMLatch(); // this blocks the upcoming getVM call
final Future<void> ready = vmService.refreshViews(waitForViews: true);
ready.then((void value) { done = true; });
expect(mockPeer.returnedFromSendRequest, 1);
time.elapse(Duration.zero); // this unblocks the listViews call which returns nothing
expect(mockPeer.returnedFromSendRequest, 2);
time.elapse(const Duration(milliseconds: 50)); // the last listViews had no views, so it waits 50ms, then calls getVM
expect(done, isFalse);
expect(mockPeer.returnedFromSendRequest, 2);
mockPeer.tripGetVMLatch(); // this unblocks the getVM call
expect(mockPeer.returnedFromSendRequest, 2);
time.elapse(Duration.zero); // here getVM returns with no isolates and listViews returns no views
expect(mockPeer.returnedFromSendRequest, 4);
time.elapse(const Duration(milliseconds: 50)); // so refreshViews waits another 50ms
expect(done, isFalse);
expect(mockPeer.returnedFromSendRequest, 4);
mockPeer.tripGetVMLatch(); // this unblocks the getVM call
expect(mockPeer.returnedFromSendRequest, 4);
time.elapse(Duration.zero); // here getVM returns with no isolates and listViews returns no views
expect(mockPeer.returnedFromSendRequest, 6);
time.elapse(const Duration(milliseconds: 50)); // so refreshViews waits another 50ms
expect(done, isFalse);
expect(mockPeer.returnedFromSendRequest, 6);
mockPeer.tripGetVMLatch(); // this unblocks the getVM call
expect(mockPeer.returnedFromSendRequest, 6);
time.elapse(Duration.zero); // here getVM returns with no isolates and listViews returns no views
expect(mockPeer.returnedFromSendRequest, 8);
time.elapse(const Duration(milliseconds: 50)); // so refreshViews waits another 50ms
expect(done, isFalse);
expect(mockPeer.returnedFromSendRequest, 8);
mockPeer.tripGetVMLatch(); // this unblocks the getVM call
expect(mockPeer.returnedFromSendRequest, 8);
time.elapse(Duration.zero); // here getVM returns with no isolates and listViews returns no views
expect(mockPeer.returnedFromSendRequest, 10);
const String message = 'Flutter is taking longer than expected to report its views. Still trying...\n';
expect(mockStdio.writtenToStdout.join(''), message);
expect(mockStdio.writtenToStderr.join(''), '');
time.elapse(const Duration(milliseconds: 50)); // so refreshViews waits another 50ms
expect(done, isFalse);
expect(mockPeer.returnedFromSendRequest, 10);
mockPeer.isolatesEnabled = true;
mockPeer.tripGetVMLatch(); // this unblocks the getVM call
expect(mockPeer.returnedFromSendRequest, 10);
time.elapse(Duration.zero); // now it returns an isolate and the listViews call returns views
expect(mockPeer.returnedFromSendRequest, 13);
expect(done, isTrue);
expect(mockStdio.writtenToStdout.join(''), message);
expect(mockStdio.writtenToStderr.join(''), '');
});
}, overrides: <Type, Generator>{
Logger: () => StdoutLogger(
outputPreferences: globals.outputPreferences,
terminal: AnsiTerminal(
stdio: mockStdio,
platform: const LocalPlatform(),
),
stdio: mockStdio,
timeoutConfiguration: const TimeoutConfiguration(),
),
});
testUsingContext('registers hot UI method', () {
FakeAsync().run((FakeAsync time) {
final MockPeer mockPeer = MockPeer();
Future<void> reloadMethod({ String classId, String libraryId }) async {}
VMService(mockPeer, null, null, null, null, null, null, reloadMethod);
expect(mockPeer.registeredMethods, contains('reloadMethod'));
});
}, overrides: <Type, Generator>{
Logger: () => StdoutLogger(
outputPreferences: globals.outputPreferences,
terminal: AnsiTerminal(
stdio: mockStdio,
platform: const LocalPlatform(),
),
stdio: mockStdio,
timeoutConfiguration: const TimeoutConfiguration(),
),
});
testUsingContext('registers flutterMemoryInfo service', () {
FakeAsync().run((FakeAsync time) {
final MockDevice mockDevice = MockDevice();
final MockPeer mockPeer = MockPeer();
Future<void> reloadSources(String isolateId, { bool pause, bool force}) async {}
VMService(mockPeer, null, null, reloadSources, null, null, mockDevice, null);
expect(mockPeer.registeredMethods, contains('flutterMemoryInfo'));
});
}, overrides: <Type, Generator>{
Logger: () => StdoutLogger(
outputPreferences: globals.outputPreferences,
terminal: AnsiTerminal(
stdio: mockStdio,
platform: const LocalPlatform(),
),
stdio: mockStdio,
timeoutConfiguration: const TimeoutConfiguration(),
),
});
testUsingContext('returns correct FlutterVersion', () {
FakeAsync().run((FakeAsync time) async {
final MockPeer mockPeer = MockPeer();
VMService(mockPeer, null, null, null, null, null, MockDevice(), null);
expect(mockPeer.registeredMethods, contains('flutterVersion'));
expect(await mockPeer.sendRequest('flutterVersion'), equals(mockVersion.toJson()));
});
}, overrides: <Type, Generator>{
FlutterVersion: () => mockVersion,
});
});
}
class MockDevice extends Mock implements Device {}
class MockFlutterVersion extends Mock implements FlutterVersion {
@override
Map<String, Object> toJson() => const <String, Object>{'Mock': 'Version'};
}