| // Copyright 2013 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. |
| |
| // This is a minimal dependency heart beat test for the Dart VM Service. |
| |
| import 'dart:async'; |
| import 'dart:convert'; |
| import 'dart:io'; |
| |
| import 'launcher.dart'; |
| import 'service_client.dart'; |
| |
| class Expect { |
| static void equals(dynamic actual, dynamic expected) { |
| if (actual != expected) { |
| throw 'Expected $actual == $expected'; |
| } |
| } |
| |
| static void contains(String needle, String haystack) { |
| if (!haystack.contains(needle)) { |
| throw 'Expected $haystack to contain $needle'; |
| } |
| } |
| |
| static void isTrue(bool tf) { |
| if (tf != true) { |
| throw 'Expected $tf to be true'; |
| } |
| } |
| |
| static void isFalse(bool tf) { |
| if (tf != false) { |
| throw 'Expected $tf to be false'; |
| } |
| } |
| |
| static void notExecuted() { |
| throw 'Should not have hit'; |
| } |
| |
| static void isNotNull(dynamic a) { |
| if (a == null) { |
| throw 'Expected $a to not be null'; |
| } |
| } |
| } |
| |
| Future<String> readResponse(HttpClientResponse response) { |
| final Completer<String> completer = Completer<String>(); |
| final StringBuffer contents = StringBuffer(); |
| response.transform(utf8.decoder).listen((String data) { |
| contents.write(data); |
| }, onDone: () => completer.complete(contents.toString())); |
| return completer.future; |
| } |
| |
| // Test accessing the service protocol over http. |
| Future<Null> testHttpProtocolRequest(Uri uri) async { |
| uri = uri.replace(path: 'getVM'); |
| final HttpClient client = HttpClient(); |
| final HttpClientRequest request = await client.getUrl(uri); |
| final HttpClientResponse response = await request.close(); |
| Expect.equals(response.statusCode, 200); |
| final Map<String, dynamic> responseAsMap = |
| json.decode(await readResponse(response)) as Map<String, dynamic>; |
| Expect.equals(responseAsMap['jsonrpc'], '2.0'); |
| client.close(); |
| } |
| |
| // Test accessing the service protocol over ws. |
| Future<Null> testWebSocketProtocolRequest(Uri uri) async { |
| uri = uri.replace(scheme: 'ws', path: 'ws'); |
| final WebSocket webSocketClient = await WebSocket.connect(uri.toString()); |
| final ServiceClient serviceClient = ServiceClient(webSocketClient); |
| final Map<String, dynamic> response = await serviceClient.invokeRPC('getVM'); |
| Expect.equals(response['type'], 'VM'); |
| try { |
| await serviceClient.invokeRPC('BART_SIMPSON'); |
| Expect.notExecuted(); |
| } catch (e) { |
| // Method not found. |
| Expect.equals((e as Map<String, dynamic>)['code'], -32601); |
| } |
| } |
| |
| // Test accessing an Observatory UI asset. |
| Future<Null> testHttpAssetRequest(Uri uri) async { |
| uri = uri.replace(path: 'third_party/trace_viewer_full.html'); |
| final HttpClient client = HttpClient(); |
| final HttpClientRequest request = await client.getUrl(uri); |
| final HttpClientResponse response = await request.close(); |
| Expect.equals(response.statusCode, 200); |
| await response.drain(); |
| client.close(); |
| } |
| |
| Future<Null> testStartPaused(Uri uri) async { |
| uri = uri.replace(scheme: 'ws', path: 'ws'); |
| final WebSocket webSocketClient = await WebSocket.connect(uri.toString()); |
| final Completer<dynamic> isolateStartedId = Completer<dynamic>(); |
| final Completer<dynamic> isolatePausedId = Completer<dynamic>(); |
| final Completer<dynamic> isolateResumeId = Completer<dynamic>(); |
| final ServiceClient serviceClient = ServiceClient(webSocketClient, |
| isolateStartedId: isolateStartedId, |
| isolatePausedId: isolatePausedId, |
| isolateResumeId: isolateResumeId); |
| await serviceClient |
| .invokeRPC('streamListen', <String, String>{'streamId': 'Isolate'}); |
| await serviceClient |
| .invokeRPC('streamListen', <String, String>{'streamId': 'Debug'}); |
| |
| final Map<String, dynamic> response = await serviceClient.invokeRPC('getVM'); |
| Expect.equals(response['type'], 'VM'); |
| String isolateId; |
| final List<dynamic> isolates = response['isolates'] as List<dynamic>; |
| if (isolates.isNotEmpty) { |
| isolateId = (isolates[0] as Map<String, String>)['id']!; |
| } else { |
| // Wait until isolate starts. |
| isolateId = await isolateStartedId.future as String; |
| } |
| |
| // Grab the isolate. |
| Map<String, dynamic> isolate = |
| await serviceClient.invokeRPC('getIsolate', <String, String>{ |
| 'isolateId': isolateId, |
| }); |
| Expect.equals(isolate['type'], 'Isolate'); |
| Expect.isNotNull(isolate['pauseEvent']); |
| // If it is not runnable, wait until it becomes runnable. |
| if (isolate['pauseEvent']['kind'] == 'None') { |
| await isolatePausedId.future; |
| isolate = await serviceClient.invokeRPC('getIsolate', <String, String>{ |
| 'isolateId': isolateId, |
| }); |
| } |
| // Verify that it is paused at start. |
| Expect.equals(isolate['pauseEvent']['kind'], 'PauseStart'); |
| |
| // Resume the isolate. |
| await serviceClient.invokeRPC('resume', <String, String>{ |
| 'isolateId': isolateId, |
| }); |
| // Wait until the isolate has resumed. |
| await isolateResumeId.future; |
| final Map<String, dynamic> resumedResponse = await serviceClient |
| .invokeRPC('getIsolate', <String, String>{'isolateId': isolateId}); |
| Expect.equals(resumedResponse['type'], 'Isolate'); |
| Expect.isNotNull(resumedResponse['pauseEvent']); |
| Expect.equals(resumedResponse['pauseEvent']['kind'], 'Resume'); |
| } |
| |
| typedef TestFunction = Future<Null> Function(Uri uri); |
| |
| final List<TestFunction> basicTests = <TestFunction>[ |
| testHttpProtocolRequest, |
| testWebSocketProtocolRequest, |
| testHttpAssetRequest |
| ]; |
| |
| final List<TestFunction> startPausedTests = <TestFunction>[ |
| // TODO(engine): Investigate difference in lifecycle events. |
| // testStartPaused, |
| ]; |
| |
| Future<bool> runTests(ShellLauncher launcher, List<TestFunction> tests) async { |
| final ShellProcess? process = await launcher.launch(); |
| if (process == null) { |
| return false; |
| } |
| final Uri uri = await process.waitForVMService(); |
| try { |
| for (int i = 0; i < tests.length; i++) { |
| print('Executing test ${i + 1}/${tests.length}'); |
| await tests[i](uri); |
| } |
| } catch (e, st) { |
| print('Dart VM Service test failure: $e\n$st'); |
| exitCode = -1; |
| } |
| await process.kill(); |
| return exitCode == 0; |
| } |
| |
| Future<Null> main(List<String> args) async { |
| if (args.length < 2) { |
| print('Usage: dart ${Platform.script} ' |
| '<sky_shell_executable> <main_dart> ...'); |
| return; |
| } |
| final String shellExecutablePath = args[0]; |
| final String mainDartPath = args[1]; |
| final List<String> extraArgs = |
| args.length <= 2 ? <String>[] : args.sublist(2); |
| |
| final ShellLauncher launcher = |
| ShellLauncher(shellExecutablePath, mainDartPath, false, extraArgs); |
| |
| final ShellLauncher startPausedlauncher = |
| ShellLauncher(shellExecutablePath, mainDartPath, true, extraArgs); |
| |
| await runTests(launcher, basicTests); |
| await runTests(startPausedlauncher, startPausedTests); |
| } |