| Index: mojo/dart/observatory_tester/tester.dart
|
| diff --git a/mojo/dart/observatory_tester/tester.dart b/mojo/dart/observatory_tester/tester.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..28a3ddd57b8fd2cb9578eb9b0fded400bbfd3ade
|
| --- /dev/null
|
| +++ b/mojo/dart/observatory_tester/tester.dart
|
| @@ -0,0 +1,187 @@
|
| +// Copyright 2015 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.
|
| +
|
| +library observatory_tester;
|
| +
|
| +// Minimal dependency Observatory heartbeat test.
|
| +
|
| +import 'dart:async';
|
| +import 'dart:convert';
|
| +import 'dart:io';
|
| +
|
| +class Expect {
|
| + static equals(a, b) {
|
| + if (a != b) {
|
| + throw 'Expected $a == $b';
|
| + }
|
| + }
|
| +
|
| + static isMap(a) {
|
| + if (a is! Map) {
|
| + throw 'Expected $a to be a Map';
|
| + }
|
| + }
|
| +
|
| + static notExecuted() {
|
| + throw 'Should not have hit';
|
| + }
|
| +
|
| + static isNotNull(a) {
|
| + if (a == null) {
|
| + throw 'Expected $a to not be null.';
|
| + }
|
| + }
|
| +}
|
| +
|
| +class Launch {
|
| + Launch(this.executable, this.arguments, this.process, this.port) {
|
| + _killRequested = false;
|
| + this.process.exitCode.then(_checkKill);
|
| + }
|
| +
|
| + void kill() {
|
| + _killRequested = true;
|
| + process.kill();
|
| + }
|
| +
|
| + void _checkKill(int exitCode) {
|
| + if (!_killRequested) {
|
| + throw 'Unexpected exit of testee. (exitCode = $exitCode)';
|
| + }
|
| + }
|
| +
|
| + final String executable;
|
| + final String arguments;
|
| + final Process process;
|
| + final int port;
|
| + bool _killRequested;
|
| +}
|
| +
|
| +class Launcher {
|
| + /// Launch [executable] with [arguments]. Returns a future to a [Launch]
|
| + /// which includes the process and port where Observatory is running.
|
| + static Future<Launch> launch(String executable,
|
| + List<String> arguments) async {
|
| + var process = await Process.start(executable, arguments);
|
| +
|
| + // Completer completes once 'Observatory listening on' message has been
|
| + // scraped and we know the port number.
|
| + var completer = new Completer();
|
| +
|
| + process.stdout.transform(UTF8.decoder)
|
| + .transform(new LineSplitter()).listen((line) {
|
| + if (line.startsWith('Observatory listening on http://')) {
|
| + RegExp portExp = new RegExp(r"\d+.\d+.\d+.\d+:(\d+)");
|
| + var port = portExp.firstMatch(line).group(1);
|
| + var portNumber = int.parse(port);
|
| + completer.complete(portNumber);
|
| + } else {
|
| + print(line);
|
| + }
|
| + });
|
| +
|
| + process.stderr.transform(UTF8.decoder)
|
| + .transform(new LineSplitter()).listen((line) {
|
| + print(line);
|
| + });
|
| +
|
| + var port = await completer.future;
|
| + return new Launch(executable, arguments, process, port);
|
| + }
|
| +}
|
| +
|
| +class ServiceHelper {
|
| + ServiceHelper(this.client) {
|
| + client.listen(_onData,
|
| + onError: _onError,
|
| + cancelOnError: true);
|
| + }
|
| +
|
| + Future<Map> invokeRPC(String method, [Map params]) async {
|
| + var key = _createKey();
|
| + var request = JSON.encode({
|
| + 'jsonrpc': '2.0',
|
| + 'method': method,
|
| + 'params': params == null ? {} : params,
|
| + 'id': key,
|
| + });
|
| + client.add(request);
|
| + var completer = new Completer();
|
| + _outstanding_requests[key] = completer;
|
| + print('-> $key ($method)');
|
| + return completer.future;
|
| + }
|
| +
|
| + String _createKey() {
|
| + var key = '$_id';
|
| + _id++;
|
| + return key;
|
| + }
|
| +
|
| + void _onData(String message) {
|
| + var response = JSON.decode(message);
|
| + var key = response['id'];
|
| + print('<- $key');
|
| + var completer = _outstanding_requests.remove(key);
|
| + assert(completer != null);
|
| + var result = response['result'];
|
| + var error = response['error'];
|
| + if (error != null) {
|
| + assert(result == null);
|
| + completer.completeError(error);
|
| + } else {
|
| + assert(result != null);
|
| + completer.complete(result);
|
| + }
|
| + }
|
| +
|
| + void _onError(error) {
|
| + print('WebSocket error: $error');
|
| + }
|
| +
|
| + final WebSocket client;
|
| + final Map<String, Completer> _outstanding_requests = <String, Completer>{};
|
| + var _id = 1;
|
| +}
|
| +
|
| +main(List<String> args) async {
|
| + var executable = args[0];
|
| + var arguments = args.sublist(1);
|
| +
|
| + print('Launching $executable with $arguments');
|
| +
|
| + var launch = await Launcher.launch(executable, arguments);
|
| +
|
| + print('Observatory is on port ${launch.port}');
|
| + var serviceUrl = 'ws://127.0.0.1:${launch.port}/ws';
|
| +
|
| + var client = await WebSocket.connect(serviceUrl);
|
| + print('Connected to $serviceUrl');
|
| +
|
| + var helper = new ServiceHelper(client);
|
| +
|
| + // Invoke getVM RPC. Verify a valid repsonse.
|
| + var vm = await helper.invokeRPC('getVM');
|
| + Expect.equals(vm['type'], 'VM');
|
| +
|
| + // Invoke a bogus RPC. Expect an error.
|
| + bool errorCaught = false;
|
| + try {
|
| + var bad = await helper.invokeRPC('BARTSIMPSON');
|
| + Expect.notExecuted();
|
| + } catch (e) {
|
| + errorCaught = true;
|
| + // Map.
|
| + Expect.isMap(e);
|
| + // Has an error code.
|
| + Expect.isNotNull(e['code']);
|
| + }
|
| + Expect.equals(errorCaught, true);
|
| +
|
| + await client.close();
|
| + print('Closed connection');
|
| +
|
| + print('Finished.');
|
| + launch.kill();
|
| +}
|
|
|