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

Adds some logging to assist with troubleshooting https://github.com/flutter/flutter/issues/152220 (which is a reapperance of flake https://github.com/flutter/flutter/issues/146879) on CI. TL;DR [the tool sometimes never receives the `IsolateRunnable` event from the vm service after calling `_flutter.runInView`](https://github.com/flutter/flutter/issues/146879#issuecomment-2118629953).
837 lines
28 KiB
Dart
837 lines
28 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 'package:fake_async/fake_async.dart';
|
|
import 'package:flutter_tools/src/base/io.dart' as io;
|
|
import 'package:flutter_tools/src/base/logger.dart';
|
|
import 'package:flutter_tools/src/convert.dart';
|
|
import 'package:flutter_tools/src/device.dart';
|
|
import 'package:flutter_tools/src/vmservice.dart';
|
|
import 'package:test/fake.dart';
|
|
import 'package:vm_service/vm_service.dart' as vm_service;
|
|
|
|
import '../src/common.dart';
|
|
import '../src/context.dart' hide testLogger;
|
|
import '../src/fake_vm_services.dart';
|
|
|
|
const String kExtensionName = 'ext.flutter.test.interestingExtension';
|
|
|
|
final vm_service.Isolate isolate = vm_service.Isolate(
|
|
id: '1',
|
|
pauseEvent: vm_service.Event(
|
|
kind: vm_service.EventKind.kResume,
|
|
timestamp: 0
|
|
),
|
|
breakpoints: <vm_service.Breakpoint>[],
|
|
libraries: <vm_service.LibraryRef>[
|
|
vm_service.LibraryRef(
|
|
id: '1',
|
|
uri: 'file:///hello_world/main.dart',
|
|
name: '',
|
|
),
|
|
],
|
|
livePorts: 0,
|
|
name: 'test',
|
|
number: '1',
|
|
pauseOnExit: false,
|
|
runnable: true,
|
|
startTime: 0,
|
|
isSystemIsolate: false,
|
|
isolateFlags: <vm_service.IsolateFlag>[],
|
|
extensionRPCs: <String>[kExtensionName],
|
|
);
|
|
|
|
final FlutterView fakeFlutterView = FlutterView(
|
|
id: 'a',
|
|
uiIsolate: isolate,
|
|
);
|
|
|
|
final FakeVmServiceRequest listViewsRequest = FakeVmServiceRequest(
|
|
method: kListViewsMethod,
|
|
jsonResponse: <String, Object>{
|
|
'views': <Object>[
|
|
fakeFlutterView.toJson(),
|
|
],
|
|
},
|
|
);
|
|
|
|
void main() {
|
|
testWithoutContext('VM Service registers reloadSources', () async {
|
|
Future<void> reloadSources(String isolateId, { bool? pause, bool? force}) async {}
|
|
|
|
final MockVMService mockVMService = MockVMService();
|
|
await setUpVmService(
|
|
reloadSources: reloadSources,
|
|
vmService: mockVMService,
|
|
);
|
|
|
|
expect(mockVMService.services, containsPair(kReloadSourcesServiceName, kFlutterToolAlias));
|
|
});
|
|
|
|
testWithoutContext('VM Service registers flutterMemoryInfo service', () async {
|
|
final FakeDevice mockDevice = FakeDevice();
|
|
|
|
final MockVMService mockVMService = MockVMService();
|
|
await setUpVmService(
|
|
device: mockDevice,
|
|
vmService: mockVMService,
|
|
);
|
|
|
|
expect(mockVMService.services, containsPair(kFlutterMemoryInfoServiceName, kFlutterToolAlias));
|
|
});
|
|
|
|
testWithoutContext('VM Service registers flutterGetSkSL service', () async {
|
|
final MockVMService mockVMService = MockVMService();
|
|
await setUpVmService(
|
|
skSLMethod: () async => 'hello',
|
|
vmService: mockVMService,
|
|
);
|
|
|
|
expect(mockVMService.services, containsPair(kFlutterGetSkSLServiceName, kFlutterToolAlias));
|
|
});
|
|
|
|
testWithoutContext('VM Service throws tool exit on service registration failure.', () async {
|
|
final MockVMService mockVMService = MockVMService()
|
|
..errorOnRegisterService = true;
|
|
|
|
await expectLater(() async => setUpVmService(
|
|
skSLMethod: () async => 'hello',
|
|
vmService: mockVMService,
|
|
), throwsToolExit());
|
|
});
|
|
|
|
testWithoutContext('VM Service throws tool exit on service registration failure with awaited future.', () async {
|
|
final MockVMService mockVMService = MockVMService()
|
|
..errorOnRegisterService = true;
|
|
|
|
await expectLater(() async => setUpVmService(
|
|
skSLMethod: () async => 'hello',
|
|
printStructuredErrorLogMethod: (vm_service.Event event) { },
|
|
vmService: mockVMService,
|
|
), throwsToolExit());
|
|
});
|
|
|
|
testWithoutContext('VM Service registers flutterPrintStructuredErrorLogMethod', () async {
|
|
final MockVMService mockVMService = MockVMService();
|
|
await setUpVmService(
|
|
printStructuredErrorLogMethod: (vm_service.Event event) async => 'hello',
|
|
vmService: mockVMService,
|
|
);
|
|
expect(mockVMService.listenedStreams, contains(vm_service.EventStreams.kExtension));
|
|
});
|
|
|
|
testWithoutContext('VM Service returns correct FlutterVersion', () async {
|
|
final MockVMService mockVMService = MockVMService();
|
|
await setUpVmService(
|
|
vmService: mockVMService,
|
|
);
|
|
|
|
expect(mockVMService.services, containsPair(kFlutterVersionServiceName, kFlutterToolAlias));
|
|
});
|
|
|
|
testUsingContext('VM Service prints messages for connection failures', () {
|
|
final BufferLogger logger = BufferLogger.test();
|
|
FakeAsync().run((FakeAsync time) {
|
|
final Uri uri = Uri.parse('ws://127.0.0.1:12345/QqL7EFEDNG0=/ws');
|
|
unawaited(connectToVmService(uri, logger: logger));
|
|
|
|
time.elapse(const Duration(seconds: 5));
|
|
expect(logger.statusText, isEmpty);
|
|
|
|
time.elapse(const Duration(minutes: 2));
|
|
|
|
final String statusText = logger.statusText;
|
|
expect(
|
|
statusText,
|
|
containsIgnoringWhitespace('Connecting to the VM Service is taking longer than expected...'),
|
|
);
|
|
expect(
|
|
statusText,
|
|
containsIgnoringWhitespace('try re-running with --host-vmservice-port'),
|
|
);
|
|
expect(
|
|
statusText,
|
|
containsIgnoringWhitespace('Exception attempting to connect to the VM Service:'),
|
|
);
|
|
expect(
|
|
statusText,
|
|
containsIgnoringWhitespace('This was attempt #50. Will retry'),
|
|
);
|
|
});
|
|
}, overrides: <Type, Generator>{
|
|
WebSocketConnector: () => failingWebSocketConnector,
|
|
});
|
|
|
|
testWithoutContext('setAssetDirectory forwards arguments correctly', () async {
|
|
final MockVMService mockVMService = MockVMService();
|
|
final FlutterVmService flutterVmService = FlutterVmService(mockVMService);
|
|
|
|
await flutterVmService.setAssetDirectory(
|
|
assetsDirectory: Uri(path: 'abc', scheme: 'file'),
|
|
viewId: 'abc',
|
|
uiIsolateId: 'def',
|
|
windows: false,
|
|
);
|
|
|
|
final ({Map<String, Object?>? args, String? isolateId}) call = mockVMService.calledMethods[kSetAssetBundlePathMethod]!.single;
|
|
expect(call.isolateId, 'def');
|
|
expect(call.args, <String, String>{
|
|
'viewId': 'abc',
|
|
'assetDirectory': '/abc',
|
|
});
|
|
});
|
|
|
|
testWithoutContext('setAssetDirectory forwards arguments correctly - windows', () async {
|
|
final MockVMService mockVMService = MockVMService();
|
|
final FlutterVmService flutterVmService = FlutterVmService(mockVMService);
|
|
|
|
await flutterVmService.setAssetDirectory(
|
|
assetsDirectory: Uri(path: 'C:/Users/Tester/AppData/Local/Temp/hello_worldb42a6da5/hello_world/build/flutter_assets', scheme: 'file'),
|
|
viewId: 'abc',
|
|
uiIsolateId: 'def',
|
|
// If windows is not set to `true`, then the file path below is incorrectly prepended with a `/` which
|
|
// causes the engine asset manager to interpret the file scheme as invalid.
|
|
windows: true,
|
|
);
|
|
|
|
final ({Map<String, Object?>? args, String? isolateId}) call = mockVMService.calledMethods[kSetAssetBundlePathMethod]!.single;
|
|
expect(call.isolateId, 'def');
|
|
expect(call.args, <String, String>{
|
|
'viewId': 'abc',
|
|
'assetDirectory': r'C:\Users\Tester\AppData\Local\Temp\hello_worldb42a6da5\hello_world\build\flutter_assets',
|
|
});
|
|
});
|
|
|
|
testWithoutContext('getSkSLs forwards arguments correctly', () async {
|
|
final MockVMService mockVMService = MockVMService();
|
|
final FlutterVmService flutterVmService = FlutterVmService(mockVMService);
|
|
|
|
await flutterVmService.getSkSLs(viewId: 'abc');
|
|
|
|
final ({Map<String, Object?>? args, String? isolateId}) call = mockVMService.calledMethods[kGetSkSLsMethod]!.single;
|
|
expect(call.isolateId, isNull);
|
|
expect(call.args, <String, String>{
|
|
'viewId': 'abc',
|
|
});
|
|
});
|
|
|
|
testWithoutContext('flushUIThreadTasks forwards arguments correctly', () async {
|
|
final MockVMService mockVMService = MockVMService();
|
|
final FlutterVmService flutterVmService = FlutterVmService(mockVMService);
|
|
|
|
await flutterVmService.flushUIThreadTasks(uiIsolateId: 'def');
|
|
|
|
final ({Map<String, Object?>? args, String? isolateId}) call = mockVMService.calledMethods[kFlushUIThreadTasksMethod]!.single;
|
|
expect(call.isolateId, isNull);
|
|
expect(call.args, <String, String>{
|
|
'isolateId': 'def',
|
|
});
|
|
});
|
|
|
|
testUsingContext('runInView forwards arguments correctly', () async {
|
|
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[
|
|
const FakeVmServiceRequest(method: 'streamListen', args: <String, Object>{
|
|
'streamId': 'Isolate',
|
|
}),
|
|
const FakeVmServiceRequest(method: kRunInViewMethod, args: <String, Object>{
|
|
'viewId': '1234',
|
|
'mainScript': 'main.dart',
|
|
'assetDirectory': 'flutter_assets/',
|
|
}),
|
|
FakeVmServiceStreamResponse(
|
|
streamId: 'Isolate',
|
|
event: vm_service.Event(
|
|
kind: vm_service.EventKind.kIsolateRunnable,
|
|
timestamp: 1,
|
|
)
|
|
),
|
|
]
|
|
);
|
|
|
|
await fakeVmServiceHost.vmService.runInView(
|
|
viewId: '1234',
|
|
main: Uri.file('main.dart'),
|
|
assetsDirectory: Uri.file('flutter_assets/'),
|
|
);
|
|
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
|
});
|
|
|
|
testWithoutContext('flutterDebugDumpSemanticsTreeInTraversalOrder handles missing method', () async {
|
|
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[
|
|
const FakeVmServiceRequest(
|
|
method: 'ext.flutter.debugDumpSemanticsTreeInTraversalOrder',
|
|
args: <String, Object>{
|
|
'isolateId': '1',
|
|
},
|
|
error: FakeRPCError(code: RPCErrorCodes.kMethodNotFound),
|
|
),
|
|
]
|
|
);
|
|
|
|
expect(await fakeVmServiceHost.vmService.flutterDebugDumpSemanticsTreeInTraversalOrder(
|
|
isolateId: '1',
|
|
), '');
|
|
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
|
});
|
|
|
|
testWithoutContext('flutterDebugDumpSemanticsTreeInInverseHitTestOrder handles missing method', () async {
|
|
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[
|
|
const FakeVmServiceRequest(
|
|
method: 'ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder',
|
|
args: <String, Object>{
|
|
'isolateId': '1',
|
|
},
|
|
error: FakeRPCError(code: RPCErrorCodes.kMethodNotFound),
|
|
),
|
|
]
|
|
);
|
|
|
|
expect(await fakeVmServiceHost.vmService.flutterDebugDumpSemanticsTreeInInverseHitTestOrder(
|
|
isolateId: '1',
|
|
), '');
|
|
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
|
});
|
|
|
|
testWithoutContext('flutterDebugDumpLayerTree handles missing method', () async {
|
|
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[
|
|
const FakeVmServiceRequest(
|
|
method: 'ext.flutter.debugDumpLayerTree',
|
|
args: <String, Object>{
|
|
'isolateId': '1',
|
|
},
|
|
error: FakeRPCError(code: RPCErrorCodes.kMethodNotFound),
|
|
),
|
|
]
|
|
);
|
|
|
|
expect(await fakeVmServiceHost.vmService.flutterDebugDumpLayerTree(
|
|
isolateId: '1',
|
|
), '');
|
|
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
|
});
|
|
|
|
testWithoutContext('flutterDebugDumpRenderTree handles missing method', () async {
|
|
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[
|
|
const FakeVmServiceRequest(
|
|
method: 'ext.flutter.debugDumpRenderTree',
|
|
args: <String, Object>{
|
|
'isolateId': '1',
|
|
},
|
|
error: FakeRPCError(code: RPCErrorCodes.kMethodNotFound),
|
|
),
|
|
]
|
|
);
|
|
|
|
expect(await fakeVmServiceHost.vmService.flutterDebugDumpRenderTree(
|
|
isolateId: '1',
|
|
), '');
|
|
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
|
});
|
|
|
|
testWithoutContext('flutterDebugDumpApp handles missing method', () async {
|
|
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[
|
|
const FakeVmServiceRequest(
|
|
method: 'ext.flutter.debugDumpApp',
|
|
args: <String, Object>{
|
|
'isolateId': '1',
|
|
},
|
|
error: FakeRPCError(code: RPCErrorCodes.kMethodNotFound),
|
|
),
|
|
]
|
|
);
|
|
|
|
expect(await fakeVmServiceHost.vmService.flutterDebugDumpApp(
|
|
isolateId: '1',
|
|
), '');
|
|
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
|
});
|
|
|
|
testWithoutContext('flutterDebugDumpFocusTree handles missing method', () async {
|
|
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[
|
|
const FakeVmServiceRequest(
|
|
method: 'ext.flutter.debugDumpFocusTree',
|
|
args: <String, Object>{
|
|
'isolateId': '1',
|
|
},
|
|
error: FakeRPCError(code: RPCErrorCodes.kMethodNotFound),
|
|
),
|
|
]
|
|
);
|
|
|
|
expect(await fakeVmServiceHost.vmService.flutterDebugDumpFocusTree(
|
|
isolateId: '1',
|
|
), '');
|
|
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
|
});
|
|
|
|
testWithoutContext('flutterDebugDumpFocusTree returns data', () async {
|
|
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[
|
|
const FakeVmServiceRequest(
|
|
method: 'ext.flutter.debugDumpFocusTree',
|
|
args: <String, Object>{
|
|
'isolateId': '1',
|
|
},
|
|
jsonResponse: <String, Object> {
|
|
'data': 'Hello world',
|
|
},
|
|
),
|
|
]
|
|
);
|
|
|
|
expect(await fakeVmServiceHost.vmService.flutterDebugDumpFocusTree(
|
|
isolateId: '1',
|
|
), 'Hello world');
|
|
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
|
});
|
|
|
|
testWithoutContext('Framework service extension invocations return null if service disappears ', () async {
|
|
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[
|
|
const FakeVmServiceRequest(
|
|
method: kGetSkSLsMethod,
|
|
args: <String, Object>{
|
|
'viewId': '1234',
|
|
},
|
|
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
|
|
),
|
|
const FakeVmServiceRequest(
|
|
method: kListViewsMethod,
|
|
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
|
|
),
|
|
const FakeVmServiceRequest(
|
|
method: kScreenshotSkpMethod,
|
|
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
|
|
),
|
|
const FakeVmServiceRequest(
|
|
method: 'setVMTimelineFlags',
|
|
args: <String, dynamic>{
|
|
'recordedStreams': <String>['test'],
|
|
},
|
|
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
|
|
),
|
|
const FakeVmServiceRequest(
|
|
method: 'getVMTimeline',
|
|
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
|
|
),
|
|
]
|
|
);
|
|
|
|
final Map<String, Object?>? skSLs = await fakeVmServiceHost.vmService.getSkSLs(
|
|
viewId: '1234',
|
|
);
|
|
expect(skSLs, isNull);
|
|
|
|
final List<FlutterView> views = await fakeVmServiceHost.vmService.getFlutterViews();
|
|
expect(views, isEmpty);
|
|
|
|
final vm_service.Response? screenshotSkp = await fakeVmServiceHost.vmService.screenshotSkp();
|
|
expect(screenshotSkp, isNull);
|
|
|
|
// Checking that this doesn't throw.
|
|
await fakeVmServiceHost.vmService.setTimelineFlags(<String>['test']);
|
|
|
|
final vm_service.Response? timeline = await fakeVmServiceHost.vmService.getTimeline();
|
|
expect(timeline, isNull);
|
|
|
|
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
|
});
|
|
|
|
testWithoutContext('getIsolateOrNull returns null if service disappears ', () async {
|
|
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[
|
|
const FakeVmServiceRequest(
|
|
method: 'getIsolate',
|
|
args: <String, Object>{
|
|
'isolateId': 'isolate/123',
|
|
},
|
|
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
|
|
),
|
|
]
|
|
);
|
|
|
|
final vm_service.Isolate? isolate = await fakeVmServiceHost.vmService.getIsolateOrNull(
|
|
'isolate/123',
|
|
);
|
|
expect(isolate, null);
|
|
|
|
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
|
});
|
|
|
|
testWithoutContext('getFlutterViews polls until a view is returned', () async {
|
|
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[
|
|
const FakeVmServiceRequest(
|
|
method: kListViewsMethod,
|
|
jsonResponse: <String, Object>{
|
|
'views': <Object>[],
|
|
},
|
|
),
|
|
const FakeVmServiceRequest(
|
|
method: kListViewsMethod,
|
|
jsonResponse: <String, Object>{
|
|
'views': <Object>[],
|
|
},
|
|
),
|
|
listViewsRequest,
|
|
]
|
|
);
|
|
|
|
expect(
|
|
await fakeVmServiceHost.vmService.getFlutterViews(
|
|
delay: Duration.zero,
|
|
),
|
|
isNotEmpty,
|
|
);
|
|
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
|
});
|
|
|
|
testWithoutContext('getFlutterViews does not poll if returnEarly is true', () async {
|
|
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[
|
|
const FakeVmServiceRequest(
|
|
method: kListViewsMethod,
|
|
jsonResponse: <String, Object>{
|
|
'views': <Object>[],
|
|
},
|
|
),
|
|
]
|
|
);
|
|
|
|
expect(
|
|
await fakeVmServiceHost.vmService.getFlutterViews(
|
|
returnEarly: true,
|
|
),
|
|
isEmpty,
|
|
);
|
|
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
|
});
|
|
|
|
group('findExtensionIsolate', () {
|
|
|
|
testWithoutContext('returns an isolate with the registered extensionRPC', () async {
|
|
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
|
|
const FakeVmServiceRequest(
|
|
method: 'streamListen',
|
|
args: <String, Object>{
|
|
'streamId': 'Isolate',
|
|
},
|
|
),
|
|
listViewsRequest,
|
|
FakeVmServiceRequest(
|
|
method: 'getIsolate',
|
|
jsonResponse: isolate.toJson(),
|
|
args: <String, Object>{
|
|
'isolateId': '1',
|
|
},
|
|
),
|
|
const FakeVmServiceRequest(
|
|
method: 'streamCancel',
|
|
args: <String, Object>{
|
|
'streamId': 'Isolate',
|
|
},
|
|
),
|
|
]);
|
|
|
|
final vm_service.IsolateRef isolateRef = await fakeVmServiceHost.vmService.findExtensionIsolate(kExtensionName);
|
|
expect(isolateRef.id, '1');
|
|
});
|
|
|
|
testWithoutContext('returns the isolate with the registered extensionRPC when there are multiple FlutterViews', () async {
|
|
const String otherExtensionName = 'ext.flutter.test.otherExtension';
|
|
|
|
// Copy the other isolate and change a few fields.
|
|
final vm_service.Isolate isolate2 = vm_service.Isolate.parse(
|
|
isolate.toJson()
|
|
..['id'] = '2'
|
|
..['extensionRPCs'] = <String>[otherExtensionName],
|
|
)!;
|
|
|
|
final FlutterView fakeFlutterView2 = FlutterView(
|
|
id: '2',
|
|
uiIsolate: isolate2,
|
|
);
|
|
|
|
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
|
|
const FakeVmServiceRequest(
|
|
method: 'streamListen',
|
|
args: <String, Object>{
|
|
'streamId': 'Isolate',
|
|
},
|
|
),
|
|
FakeVmServiceRequest(
|
|
method: kListViewsMethod,
|
|
jsonResponse: <String, Object>{
|
|
'views': <Object>[
|
|
fakeFlutterView.toJson(),
|
|
fakeFlutterView2.toJson(),
|
|
],
|
|
},
|
|
),
|
|
FakeVmServiceRequest(
|
|
method: 'getIsolate',
|
|
jsonResponse: isolate.toJson(),
|
|
args: <String, Object>{
|
|
'isolateId': '1',
|
|
},
|
|
),
|
|
FakeVmServiceRequest(
|
|
method: 'getIsolate',
|
|
jsonResponse: isolate2.toJson(),
|
|
args: <String, Object>{
|
|
'isolateId': '2',
|
|
},
|
|
),
|
|
const FakeVmServiceRequest(
|
|
method: 'streamCancel',
|
|
args: <String, Object>{
|
|
'streamId': 'Isolate',
|
|
},
|
|
),
|
|
]);
|
|
|
|
final vm_service.IsolateRef isolateRef = await fakeVmServiceHost.vmService.findExtensionIsolate(otherExtensionName);
|
|
expect(isolateRef.id, '2');
|
|
});
|
|
|
|
testWithoutContext('does not rethrow a sentinel exception if the initially queried flutter view disappears', () async {
|
|
const String otherExtensionName = 'ext.flutter.test.otherExtension';
|
|
final vm_service.Isolate? isolate2 = vm_service.Isolate.parse(
|
|
isolate.toJson()
|
|
..['id'] = '2'
|
|
..['extensionRPCs'] = <String>[otherExtensionName],
|
|
);
|
|
|
|
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
|
|
const FakeVmServiceRequest(
|
|
method: 'streamListen',
|
|
args: <String, Object>{
|
|
'streamId': 'Isolate',
|
|
},
|
|
),
|
|
FakeVmServiceRequest(
|
|
method: kListViewsMethod,
|
|
jsonResponse: <String, Object>{
|
|
'views': <Object>[
|
|
fakeFlutterView.toJson(),
|
|
],
|
|
},
|
|
),
|
|
const FakeVmServiceRequest(
|
|
method: 'getIsolate',
|
|
args: <String, Object>{
|
|
'isolateId': '1',
|
|
},
|
|
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
|
|
),
|
|
// Assume a different isolate returns.
|
|
FakeVmServiceStreamResponse(
|
|
streamId: 'Isolate',
|
|
event: vm_service.Event(
|
|
kind: vm_service.EventKind.kServiceExtensionAdded,
|
|
extensionRPC: otherExtensionName,
|
|
timestamp: 1,
|
|
isolate: isolate2,
|
|
),
|
|
),
|
|
const FakeVmServiceRequest(
|
|
method: 'streamCancel',
|
|
args: <String, Object>{
|
|
'streamId': 'Isolate',
|
|
},
|
|
),
|
|
]);
|
|
|
|
final vm_service.IsolateRef isolateRef = await fakeVmServiceHost.vmService.findExtensionIsolate(otherExtensionName);
|
|
expect(isolateRef.id, '2');
|
|
});
|
|
|
|
testWithoutContext('when the isolate stream is already subscribed, returns an isolate with the registered extensionRPC', () async {
|
|
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
|
|
const FakeVmServiceRequest(
|
|
method: 'streamListen',
|
|
args: <String, Object>{
|
|
'streamId': 'Isolate',
|
|
},
|
|
// Stream already subscribed - https://github.com/dart-lang/sdk/blob/main/runtime/vm/service/service.md#streamlisten
|
|
error: FakeRPCError(code: 103),
|
|
),
|
|
listViewsRequest,
|
|
FakeVmServiceRequest(
|
|
method: 'getIsolate',
|
|
jsonResponse: isolate.toJson()..['extensionRPCs'] = <String>[kExtensionName],
|
|
args: <String, Object>{
|
|
'isolateId': '1',
|
|
},
|
|
),
|
|
const FakeVmServiceRequest(
|
|
method: 'streamCancel',
|
|
args: <String, Object>{
|
|
'streamId': 'Isolate',
|
|
},
|
|
),
|
|
]);
|
|
|
|
final vm_service.IsolateRef isolateRef = await fakeVmServiceHost.vmService.findExtensionIsolate(kExtensionName);
|
|
expect(isolateRef.id, '1');
|
|
});
|
|
|
|
testWithoutContext('returns an isolate with a extensionRPC that is registered later', () async {
|
|
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
|
|
const FakeVmServiceRequest(
|
|
method: 'streamListen',
|
|
args: <String, Object>{
|
|
'streamId': 'Isolate',
|
|
},
|
|
),
|
|
listViewsRequest,
|
|
FakeVmServiceRequest(
|
|
method: 'getIsolate',
|
|
jsonResponse: isolate.toJson(),
|
|
args: <String, Object>{
|
|
'isolateId': '1',
|
|
},
|
|
),
|
|
FakeVmServiceStreamResponse(
|
|
streamId: 'Isolate',
|
|
event: vm_service.Event(
|
|
kind: vm_service.EventKind.kServiceExtensionAdded,
|
|
extensionRPC: kExtensionName,
|
|
timestamp: 1,
|
|
),
|
|
),
|
|
const FakeVmServiceRequest(
|
|
method: 'streamCancel',
|
|
args: <String, Object>{
|
|
'streamId': 'Isolate',
|
|
},
|
|
),
|
|
]);
|
|
|
|
final vm_service.IsolateRef isolateRef = await fakeVmServiceHost.vmService.findExtensionIsolate(kExtensionName);
|
|
expect(isolateRef.id, '1');
|
|
});
|
|
|
|
testWithoutContext('throws when the service disappears', () async {
|
|
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
|
|
const FakeVmServiceRequest(
|
|
method: 'streamListen',
|
|
args: <String, Object>{
|
|
'streamId': 'Isolate',
|
|
},
|
|
),
|
|
const FakeVmServiceRequest(
|
|
method: kListViewsMethod,
|
|
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
|
|
),
|
|
const FakeVmServiceRequest(
|
|
method: 'streamCancel',
|
|
args: <String, Object>{
|
|
'streamId': 'Isolate',
|
|
},
|
|
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
|
|
),
|
|
]);
|
|
|
|
expect(
|
|
() => fakeVmServiceHost.vmService.findExtensionIsolate(kExtensionName),
|
|
throwsA(isA<VmServiceDisappearedException>()),
|
|
);
|
|
});
|
|
|
|
testWithoutContext('throws when the service is disposed', () async {
|
|
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
|
|
|
|
await fakeVmServiceHost.vmService.dispose();
|
|
|
|
expect(
|
|
() => fakeVmServiceHost.vmService.findExtensionIsolate(kExtensionName),
|
|
throwsA(isA<VmServiceDisappearedException>()),
|
|
);
|
|
});
|
|
});
|
|
|
|
testWithoutContext('Can process log events from the vm service', () {
|
|
final vm_service.Event event = vm_service.Event(
|
|
bytes: base64.encode(utf8.encode('Hello There\n')),
|
|
timestamp: 0,
|
|
kind: vm_service.EventKind.kLogging,
|
|
);
|
|
|
|
expect(processVmServiceMessage(event), 'Hello There');
|
|
});
|
|
|
|
testUsingContext('WebSocket URL construction uses correct URI join primitives', () async {
|
|
final Completer<String> completer = Completer<String>();
|
|
openChannelForTesting = (String url, {io.CompressionOptions compression = io.CompressionOptions.compressionDefault, required Logger logger}) async {
|
|
completer.complete(url);
|
|
throw Exception('');
|
|
};
|
|
|
|
// Construct a URL that does not end in a `/`.
|
|
await expectLater(() => connectToVmService(Uri.parse('http://localhost:8181/foo'), logger: BufferLogger.test()), throwsException);
|
|
expect(await completer.future, 'ws://localhost:8181/foo/ws');
|
|
openChannelForTesting = null;
|
|
});
|
|
}
|
|
|
|
class MockVMService extends Fake implements vm_service.VmService {
|
|
final Map<String, String> services = <String, String>{};
|
|
final Map<String, vm_service.ServiceCallback> serviceCallBacks = <String, vm_service.ServiceCallback>{};
|
|
final Map<String, List<({ String? isolateId, Map<String, Object?>? args})>> calledMethods = <String, List<({Map<String, Object?>? args, String? isolateId})>>{};
|
|
final Set<String> listenedStreams = <String>{};
|
|
bool errorOnRegisterService = false;
|
|
|
|
@override
|
|
Future<vm_service.Response> callMethod(String method, {String? isolateId, Map<String, Object?>? args}) async {
|
|
calledMethods
|
|
.putIfAbsent(method, () => <({Map<String, Object?>? args, String? isolateId})>[])
|
|
.add((isolateId: isolateId, args: args));
|
|
return vm_service.Success();
|
|
}
|
|
|
|
@override
|
|
void registerServiceCallback(String service, vm_service.ServiceCallback cb) {
|
|
serviceCallBacks[service] = cb;
|
|
}
|
|
|
|
@override
|
|
Future<vm_service.Success> registerService(String service, String alias) async {
|
|
services[service] = alias;
|
|
if (errorOnRegisterService) {
|
|
throw vm_service.RPCError('registerService', 1234, 'error');
|
|
}
|
|
return vm_service.Success();
|
|
}
|
|
|
|
@override
|
|
Stream<vm_service.Event> get onExtensionEvent => const Stream<vm_service.Event>.empty();
|
|
|
|
@override
|
|
Future<vm_service.Success> streamListen(String streamId) async {
|
|
listenedStreams.add(streamId);
|
|
return vm_service.Success();
|
|
}
|
|
}
|
|
|
|
class FakeDevice extends Fake implements Device { }
|
|
|
|
/// A [WebSocketConnector] that always throws an [io.SocketException].
|
|
Future<io.WebSocket> failingWebSocketConnector(
|
|
String url, {
|
|
io.CompressionOptions? compression,
|
|
Logger? logger,
|
|
}) {
|
|
throw const io.SocketException('Failed WebSocket connection');
|
|
}
|