// Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; import 'dart:io'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/vmservice.dart'; import 'package:json_rpc_2/json_rpc_2.dart' as rpc; import 'package:quiver/testing/async.dart'; import 'src/common.dart'; import 'src/context.dart'; import 'src/mocks.dart'; class MockPeer implements rpc.Peer { @override rpc.ErrorCallback get onUnhandledError => null; @override Future<dynamic> get done async { throw 'unexpected call to done'; } @override bool get isClosed { throw 'unexpected call to isClosed'; } @override Future<dynamic> close() async { throw 'unexpected call to close()'; } @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) { // this does get called } @override void sendNotification(String method, [ dynamic parameters ]) { throw 'unexpected call to sendNotification'; } bool isolatesEnabled = 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>[], }; } return null; } @override dynamic withBatch(dynamic callback()) { throw 'unexpected call to withBatch'; } } void main() { MockStdio mockStdio; 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(), Stdio: () => mockStdio, WebSocketConnector: () => (String url, {CompressionOptions compression}) async => throw const SocketException('test'), }); 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); 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(), Stdio: () => mockStdio, }); }); }