From dbeceb1d062e68e6484b3154e4756dd41ac7893c Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Mon, 22 Jul 2019 20:06:29 +0000 Subject: [PATCH] [ VM / Service ] Pulled in vm_service_drivers from its own repo. - Updated various paths to point to the sdk repo instead of the vm_service_drivers repo. - Updated generate.dart to use the service.md from the SDK, not a copy. - Removed hidden files that are no longer needed. Change-Id: I11b1f2e32d55f1fdaaa6eb9ce34fc318716c36f9 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/109120 Reviewed-by: Devon Carew Commit-Queue: Ben Konyi --- .packages | 1 + pkg/vm_service/.gitignore | 4 + pkg/vm_service/CHANGELOG.md | 240 + pkg/vm_service/LICENSE | 26 + pkg/vm_service/README.md | 20 + pkg/vm_service/analysis_options.yaml | 6 + pkg/vm_service/example/sample_isolates.dart | 54 + pkg/vm_service/example/sample_main.dart | 50 + pkg/vm_service/example/vm_service_assert.dart | 1072 ++++ pkg/vm_service/example/vm_service_tester.dart | 219 + pkg/vm_service/java/.gitignore | 4 + pkg/vm_service/java/build.xml | 54 + pkg/vm_service/java/classpath.txt | 1 + .../java/example/sample_exception.dart | 53 + pkg/vm_service/java/example/sample_main.dart | 46 + pkg/vm_service/java/javaconfig.json | 5 + .../vm/service/RemoteServiceCompleter.java | 34 + .../vm/service/RemoteServiceRunner.java | 29 + .../dartlang/vm/service/VmServiceBase.java | 635 ++ .../vm/service/VmServiceListener.java | 33 + .../vm/service/consumer/Consumer.java | 26 + .../service/consumer/GetInstanceConsumer.java | 20 + .../service/consumer/GetLibraryConsumer.java | 20 + .../consumer/ServiceExtensionConsumer.java | 20 + .../dartlang/vm/service/element/Element.java | 73 + .../vm/service/element/ElementList.java | 56 + .../dartlang/vm/service/element/RPCError.java | 110 + .../service/internal/BlockingRequestSink.java | 81 + .../vm/service/internal/ErrorRequestSink.java | 61 + .../vm/service/internal/RequestSink.java | 33 + .../vm/service/internal/ResponseSink.java | 28 + .../vm/service/internal/VmServiceConst.java | 60 + .../internal/WebSocketRequestSink.java | 58 + .../dartlang/vm/service/logging/Logger.java | 75 + .../dartlang/vm/service/logging/Logging.java | 30 + .../vm/service/InstanceRefToString.java | 214 + .../test/org/dartlang/vm/service/OpLatch.java | 78 + .../org/dartlang/vm/service/ResultLatch.java | 31 + .../dartlang/vm/service/SampleOutPrinter.java | 93 + .../vm/service/SampleVmServiceListener.java | 86 + .../dartlang/vm/service/VmServiceTest.java | 661 ++ .../java/third_party/gson/gson-2.2.4.jar | Bin 0 -> 190418 bytes .../java/third_party/gson/gson-license.txt | 15 + .../java/third_party/guava/guava-13.0.1.jar | Bin 0 -> 1891110 bytes .../java/third_party/guava/guava-license.txt | 204 + .../weberknecht/weberknecht-0.1.5.jar | Bin 0 -> 11349 bytes .../weberknecht/weberknecht-license.txt | 15 + pkg/vm_service/java/version.properties | 1 + pkg/vm_service/lib/src/helpers.dart | 64 + .../lib/src/service_extension_registry.dart | 68 + pkg/vm_service/lib/src/stream_helpers.dart | 99 + pkg/vm_service/lib/utils.dart | 22 + pkg/vm_service/lib/vm_service.dart | 5615 +++++++++++++++++ pkg/vm_service/lib/vm_service_io.dart | 26 + pkg/vm_service/pubspec.yaml | 21 + pkg/vm_service/test/server_test.dart | 443 ++ pkg/vm_service/test/utils_test.dart | 35 + .../tool/common/generate_common.dart | 30 + pkg/vm_service/tool/common/parser.dart | 172 + .../tool/common/src_gen_common.dart | 93 + pkg/vm_service/tool/dart/generate_dart.dart | 1948 ++++++ pkg/vm_service/tool/dart/src_gen_dart.dart | 96 + pkg/vm_service/tool/generate.dart | 155 + pkg/vm_service/tool/java/generate_java.dart | 1245 ++++ pkg/vm_service/tool/java/src_gen_java.dart | 307 + pkg/vm_service/tool/service_undocumented.md | 136 + 66 files changed, 15310 insertions(+) create mode 100644 pkg/vm_service/.gitignore create mode 100644 pkg/vm_service/CHANGELOG.md create mode 100644 pkg/vm_service/LICENSE create mode 100644 pkg/vm_service/README.md create mode 100644 pkg/vm_service/analysis_options.yaml create mode 100644 pkg/vm_service/example/sample_isolates.dart create mode 100644 pkg/vm_service/example/sample_main.dart create mode 100644 pkg/vm_service/example/vm_service_assert.dart create mode 100644 pkg/vm_service/example/vm_service_tester.dart create mode 100644 pkg/vm_service/java/.gitignore create mode 100644 pkg/vm_service/java/build.xml create mode 100644 pkg/vm_service/java/classpath.txt create mode 100644 pkg/vm_service/java/example/sample_exception.dart create mode 100644 pkg/vm_service/java/example/sample_main.dart create mode 100644 pkg/vm_service/java/javaconfig.json create mode 100644 pkg/vm_service/java/src/org/dartlang/vm/service/RemoteServiceCompleter.java create mode 100644 pkg/vm_service/java/src/org/dartlang/vm/service/RemoteServiceRunner.java create mode 100644 pkg/vm_service/java/src/org/dartlang/vm/service/VmServiceBase.java create mode 100644 pkg/vm_service/java/src/org/dartlang/vm/service/VmServiceListener.java create mode 100644 pkg/vm_service/java/src/org/dartlang/vm/service/consumer/Consumer.java create mode 100644 pkg/vm_service/java/src/org/dartlang/vm/service/consumer/GetInstanceConsumer.java create mode 100644 pkg/vm_service/java/src/org/dartlang/vm/service/consumer/GetLibraryConsumer.java create mode 100644 pkg/vm_service/java/src/org/dartlang/vm/service/consumer/ServiceExtensionConsumer.java create mode 100644 pkg/vm_service/java/src/org/dartlang/vm/service/element/Element.java create mode 100644 pkg/vm_service/java/src/org/dartlang/vm/service/element/ElementList.java create mode 100644 pkg/vm_service/java/src/org/dartlang/vm/service/element/RPCError.java create mode 100644 pkg/vm_service/java/src/org/dartlang/vm/service/internal/BlockingRequestSink.java create mode 100644 pkg/vm_service/java/src/org/dartlang/vm/service/internal/ErrorRequestSink.java create mode 100644 pkg/vm_service/java/src/org/dartlang/vm/service/internal/RequestSink.java create mode 100644 pkg/vm_service/java/src/org/dartlang/vm/service/internal/ResponseSink.java create mode 100644 pkg/vm_service/java/src/org/dartlang/vm/service/internal/VmServiceConst.java create mode 100644 pkg/vm_service/java/src/org/dartlang/vm/service/internal/WebSocketRequestSink.java create mode 100644 pkg/vm_service/java/src/org/dartlang/vm/service/logging/Logger.java create mode 100644 pkg/vm_service/java/src/org/dartlang/vm/service/logging/Logging.java create mode 100644 pkg/vm_service/java/test/org/dartlang/vm/service/InstanceRefToString.java create mode 100644 pkg/vm_service/java/test/org/dartlang/vm/service/OpLatch.java create mode 100644 pkg/vm_service/java/test/org/dartlang/vm/service/ResultLatch.java create mode 100644 pkg/vm_service/java/test/org/dartlang/vm/service/SampleOutPrinter.java create mode 100644 pkg/vm_service/java/test/org/dartlang/vm/service/SampleVmServiceListener.java create mode 100644 pkg/vm_service/java/test/org/dartlang/vm/service/VmServiceTest.java create mode 100644 pkg/vm_service/java/third_party/gson/gson-2.2.4.jar create mode 100644 pkg/vm_service/java/third_party/gson/gson-license.txt create mode 100644 pkg/vm_service/java/third_party/guava/guava-13.0.1.jar create mode 100644 pkg/vm_service/java/third_party/guava/guava-license.txt create mode 100644 pkg/vm_service/java/third_party/weberknecht/weberknecht-0.1.5.jar create mode 100644 pkg/vm_service/java/third_party/weberknecht/weberknecht-license.txt create mode 100644 pkg/vm_service/java/version.properties create mode 100644 pkg/vm_service/lib/src/helpers.dart create mode 100644 pkg/vm_service/lib/src/service_extension_registry.dart create mode 100644 pkg/vm_service/lib/src/stream_helpers.dart create mode 100644 pkg/vm_service/lib/utils.dart create mode 100644 pkg/vm_service/lib/vm_service.dart create mode 100644 pkg/vm_service/lib/vm_service_io.dart create mode 100644 pkg/vm_service/pubspec.yaml create mode 100644 pkg/vm_service/test/server_test.dart create mode 100644 pkg/vm_service/test/utils_test.dart create mode 100644 pkg/vm_service/tool/common/generate_common.dart create mode 100644 pkg/vm_service/tool/common/parser.dart create mode 100644 pkg/vm_service/tool/common/src_gen_common.dart create mode 100644 pkg/vm_service/tool/dart/generate_dart.dart create mode 100644 pkg/vm_service/tool/dart/src_gen_dart.dart create mode 100644 pkg/vm_service/tool/generate.dart create mode 100644 pkg/vm_service/tool/java/generate_java.dart create mode 100644 pkg/vm_service/tool/java/src_gen_java.dart create mode 100644 pkg/vm_service/tool/service_undocumented.md diff --git a/.packages b/.packages index 3a8b750eaec..c86ddc7eaec 100644 --- a/.packages +++ b/.packages @@ -104,6 +104,7 @@ typed_data:third_party/pkg/typed_data/lib unittest:third_party/pkg/unittest/lib usage:third_party/pkg/usage/lib vm:pkg/vm/lib +vm_service:pkg/vm_service/lib watcher:third_party/pkg/watcher/lib web_components:third_party/pkg/web_components/lib web_socket_channel:third_party/pkg/web_socket_channel/lib diff --git a/pkg/vm_service/.gitignore b/pkg/vm_service/.gitignore new file mode 100644 index 00000000000..113d7832bbe --- /dev/null +++ b/pkg/vm_service/.gitignore @@ -0,0 +1,4 @@ +*.iml +.dart_tool +.packages +pubspec.lock diff --git a/pkg/vm_service/CHANGELOG.md b/pkg/vm_service/CHANGELOG.md new file mode 100644 index 00000000000..37f102670bc --- /dev/null +++ b/pkg/vm_service/CHANGELOG.md @@ -0,0 +1,240 @@ +# Changelog + +## 1.0.0 +- Migrated `vm_service_lib` into the Dart SDK. +- Renamed from `package:vm_service_lib` to `package:vm_service`. +- Switched versioning system to follow semantic versioning standards instead of + pinning versions to match the service protocol version. + +## 3.22.2 +- Fix `registerService` RPC and `Service` stream not being handled correctly. +- Fixed failing tests. + +## 3.22.1 +- **breaking**: Changed type of `library` property in `Class` objects from + `ObjectRef` to `LibraryRef`. + +## 3.22.0 +- The `registerService` RPC and `Service` stream are now public. +- `Event` has been updated to include the optional `service`, `method`, and + `alias` properties. + +## 3.21.1 +- **breaking**: Fixed issue where an `InstanceRef` of type `null` could be returned + instead of null for non-`InstanceRef` properties and return values. As a + result, some property and return types have been changed from Obj to their + correct types. + +## 3.21.0 +- support service protocol version 3.21 + +## 3.20.0+2 +- allow optional params in `getVMTimeline` + +## 3.20.0+1 +- handle null isolate ids in `callServiceExtension` +- add backwards compatibility for `InstanceSet` and `AllocationProfile` + +## 3.20.0 +- rev to 3.20.0; expose public methods added in 3.17 - 3.20 VM Service Protocol versions + +## 3.17.0+1 +- generate a list of available event streams + +## 3.17.0 +- rev to 3.17.0; expose the Logging event and the getMemoryUsage call + +## 3.15.1+2 +- fix handling of errors in registered service callbacks to return valid + JSON-RPC errors and avoid the client getting "Service Disappeared" responses + +## 3.15.1+1 +- rename `getVmWsUriFromObservatoryUri` to `convertToWebSocketUrl` +- fix an assignment issue in `evaluate` + +## 3.15.1 +- Add `getVmWsUriFromObservatoryUri`, a helper function to convert observatory URIs + into the required WebSocket URI for connecting to the VM service. + +## 3.15.0 +- support service protocol version 3.15 +- fix an issue decoding null `Script.tokenPosTable` values + +## 3.14.3-dev.4 +- Add support for the `_Service` stream in the `VmServerConnection` directly. + +## 3.14.3-dev.3 +- Add support for automatically delegating service extension requests to the + client which registered them. + - This is only for services that are registered via the vm service protocol, + services registered through `dart:developer` should be handled by the + `VmServiceInterface` implementation (which should invoke the registered + callback directly). +- Added a `ServiceExtensionRegistry` class, which tracks which clients have + registered which service extensions. +- **breaking**: Renamed `VmServer` to `VmServerConnection`. + - One `VmServerConnection` should be created _per client_ connection to the + server. These should typically all share the same underlying + `VmServiceInterface` instance, as well as the same + `ServiceExtensionRegistry` instance. + +## 3.14.3-dev.2 +- Add `callServiceExtension` method to the `VmServiceInterface` class. + - The `VmServer` will delegate all requests whose methods start with `ext.` to + that implementation. + +## 3.14.3-dev.1 +- Add `VmServiceInterface` and `VmServer` classes, which can handle routing + jsonrpc2 requests to a `VmServiceInterface` instance, and serializing the + responses back. + +## 3.14.3-dev.0 +- Add `toJson` methods to all classes. + +## 3.14.2 +- fix code generation for the `getSourceReport` call + +## 3.14.1 +- address an encoding issue with stdout / stderr text + +## 3.14.0 +- regenerate for `v3.14` +- bump to a major version numbering scheme + +## 0.3.10+2 +- work around an issue de-serializing Instance.closureContext + +## 0.3.10+1 +- fix an issue de-serializing some object types + +## 0.3.10 +- regenerate for `v3.12` +- expose `isolate.getScripts()` +- expose `isolate.getInstances()` + +## 0.3.9+2 +- handle nulls for `Script.source` +- fix a decoding issue for `Script.tokenPosTable` + +## 0.3.9+1 +- rev to version `3.9` of the spec +- expose `invoke` + +## 0.3.9 +- Rename the `Null` type to `NullVal` + +## 0.3.8 +- upgrades for Dart 2 dependencies + +## 0.3.7 +- ensure the library works with Dart 2 +- regenerate the library based on the 3.8-dev spec +- now require a minimum of a 2.0.0-dev Dart SDK +- update to not use deprecated dart:convert constants + +## 0.3.6 +- workaround for an issue with the type of @Library refs for VM objects + +## 0.3.5+1 +- bug fix for deserializing `Instance` objects + +## 0.3.5 +- improve access to the profiling APIs + +## 0.3.4 +- more strong mode runtime fixes +- expose some undocumented (and unsupported) service protocol methods + +## 0.3.3 +- fix strong mode issues at runtime (with JSLists and Lists) +- expose the ability to evaluate in the scope of another object +- expose the async causal frame info +- expose the `awaiterFrames` field +- expose the `frameIndex` param for the step call + +## 0.3.2+1 +- fix a strong mode issue in the generated Dart library + +## 0.3.2 +- expose the `PausePostRequest` event + +## 0.3.1 +- fix a parsing issue with ExtensionData + +## 0.2.4 +- expose the service protocol timeline API +- add the new `None` event type + +## 0.2.3 +- include the name of the calling method in RPC errors + +## 0.2.2 +- fixed several strong mode analysis issues + +## 0.2.1 +- upgrade to service protocol version `3.3` + +## 0.2.0 +- upgrade to service protocol version `3.2` + +## 0.1.2 +- fixed a bug with the `ServiceExtensionAdded` event + +## 0.1.1 +- expose the new 'Extension' event information + +## 0.1.0 +- rev to 0.1.0; declare first stable API version + +## 0.0.13 +- improve the toString() message for RPCError + +## 0.0.12 +- bug fix for parsing MapAssociations + +## 0.0.11 +- bug fix to the service extension API + +## 0.0.10 +- expose a service extension API + +## 0.0.9 +- update to the latest spec to capture the `Event.inspectee` field + +## 0.0.8 +- allow listening to arbitrary event types +- use Strings for the enum types (to allow for unknown enum values) + +## 0.0.7 +- make the diagnostic logging synchronous +- remove a workaround for a VM bug (fixed in 1.13.0-dev.7.3) +- several strong mode fixes + +## 0.0.6 +- added `exceptionPauseMode` to the Isolate class +- added `hashCode` and `operator==` methods to classes supporting object identity +- work around a VM bug with the `type` field of `BoundVariable` and `BoundField` + +## 0.0.5 +- added more dartdocs +- moved back to using Dart enums +- changed from optional positional params to optional named params + +## 0.0.4 +- enum redux + +## 0.0.3 +- update to use a custom enum class +- upgrade to the latest service protocol spec + +## 0.0.2 +- added the `setExceptionPauseMode` method +- fixed an issue with enum parsing + +## 0.0.1 +- first publish +- upgraded the library to the 3.0 version of the service protocol +- upgraded the library to the 2.0 version of the service protocol +- copied basic Dart API generator from Atom Dart Plugin + https://github.com/dart-atom/dartlang/tree/master/tool +- refactored Dart code to generate Java client as well as Dart client diff --git a/pkg/vm_service/LICENSE b/pkg/vm_service/LICENSE new file mode 100644 index 00000000000..de31e1a0a48 --- /dev/null +++ b/pkg/vm_service/LICENSE @@ -0,0 +1,26 @@ +Copyright 2015, the Dart project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pkg/vm_service/README.md b/pkg/vm_service/README.md new file mode 100644 index 00000000000..a87d2bcda62 --- /dev/null +++ b/pkg/vm_service/README.md @@ -0,0 +1,20 @@ +# vm_service + +[![pub package](https://img.shields.io/pub/v/vm_service.svg)](https://pub.dartlang.org/packages/vm_service) + +A library to access the VM Service Protocol. + +## Usage + +See the +[example](https://github.com/dart-lang/sdk/blob/master/pkg/vm_service/example/vm_service_tester.dart) +for a simple use of the library's API. + +The VM Service Protocol spec can be found at +[github.com/dart-lang/sdk/runtime/vm/service/service.md](https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md). + +## Features and bugs + +Please file feature requests and bugs at the [issue tracker][tracker]. + +[tracker]: https://github.com/dart-lang/sdk/issues diff --git a/pkg/vm_service/analysis_options.yaml b/pkg/vm_service/analysis_options.yaml new file mode 100644 index 00000000000..b5e3320bf60 --- /dev/null +++ b/pkg/vm_service/analysis_options.yaml @@ -0,0 +1,6 @@ +include: package:pedantic/analysis_options.1.7.0.yaml + +linter: + rules: + - directives_ordering + - prefer_generic_function_type_aliases diff --git a/pkg/vm_service/example/sample_isolates.dart b/pkg/vm_service/example/sample_isolates.dart new file mode 100644 index 00000000000..3089e137469 --- /dev/null +++ b/pkg/vm_service/example/sample_isolates.dart @@ -0,0 +1,54 @@ +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// for details. 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:isolate'; + +main(List args) async { + var arr = newArray(5); + var arr2 = newArray(417); + var hash1 = newHash(5); + var hash2 = newHash(417); + + // ignore unused + arr.length; + arr2.length; + hash1.length; + hash2.length; + + startIsolate(1); + startIsolate(2); + startIsolate(3); + startIsolate(4); + + await new Future.delayed(new Duration(seconds: 5)); + + print('at end of main...'); +} + +void startIsolate(int val) { + Isolate.spawn(isolateEntry, val); +} + +isolateEntry(message) async { + print('starting $message'); + await new Future.delayed(new Duration(seconds: message)); + print('ending $message'); +} + +List newArray(int length) { + List l = []; + for (int i = 0; i < length; i++) { + l.add('entry_$i'); + } + return l; +} + +Map newHash(int length) { + Map m = {}; + for (int i = 0; i < length; i++) { + m['entry_$i'] = i; + } + return m; +} diff --git a/pkg/vm_service/example/sample_main.dart b/pkg/vm_service/example/sample_main.dart new file mode 100644 index 00000000000..0bce431176c --- /dev/null +++ b/pkg/vm_service/example/sample_main.dart @@ -0,0 +1,50 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. 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:developer' as developer; + +void main(List args) { + String local1 = 'abcd'; + int local2 = 2; + var longList = [1, "hello", 3, 5, 7, 11, 13, 14, 15, 16, 17, 18, 19, 20]; + var deepList = [ + new Bar(), + [ + [ + [ + [ + [7] + ] + ], + "end" + ] + ] + ]; + + print('hello from main'); + + foo(1); + foo(local2); + foo(3); + foo(local1.length); + + print(longList); + print(deepList); + + developer.debugger(); + + print('exiting...'); +} + +void foo(int val) { + print('val: ${val}'); +} + +class Bar extends FooBar { + String field1 = "my string"; +} + +class FooBar { + int field2 = 47; +} diff --git a/pkg/vm_service/example/vm_service_assert.dart b/pkg/vm_service/example/vm_service_assert.dart new file mode 100644 index 00000000000..1b2bc80ebce --- /dev/null +++ b/pkg/vm_service/example/vm_service_assert.dart @@ -0,0 +1,1072 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. 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 generated file. + +/// A library for asserting correct responses from the VM Service. + +import 'package:vm_service/vm_service.dart' as vms; + +dynamic assertNotNull(dynamic obj) { + if (obj == null) throw 'assert failed'; + return obj; +} + +bool assertBool(bool obj) { + assertNotNull(obj); + return obj; +} + +int assertInt(int obj) { + assertNotNull(obj); + return obj; +} + +double assertDouble(double obj) { + assertNotNull(obj); + return obj; +} + +List assertInts(List list) { + for (int elem in list) { + assertInt(elem); + } + return list; +} + +List assertStrings(List list) { + for (String elem in list) { + assertString(elem); + } + return list; +} + +String assertString(String obj) { + assertNotNull(obj); + if (obj.isEmpty) throw 'expected non-zero length string'; + return obj; +} + +vms.Success assertSuccess(vms.Success obj) { + assertNotNull(obj); + if (obj.type != 'Success') throw 'expected Success'; + return obj; +} + +/// Assert PauseStart, PauseExit, PauseBreakpoint, PauseInterrupted, +/// PauseException, Resume, BreakpointAdded, BreakpointResolved, +/// BreakpointRemoved, and Inspect events. +vms.Event assertDebugEvent(vms.Event event) { + assertEvent(event); + if (event.kind == vms.EventKind.kPauseBreakpoint || + event.kind == vms.EventKind.kBreakpointAdded || + event.kind == vms.EventKind.kBreakpointRemoved || + event.kind == vms.EventKind.kBreakpointResolved) { + assertBreakpoint(event.breakpoint); + } + if (event.kind == vms.EventKind.kPauseBreakpoint) { + for (vms.Breakpoint elem in event.pauseBreakpoints) { + assertBreakpoint(elem); + } + } + if (event.kind == vms.EventKind.kPauseBreakpoint || + event.kind == vms.EventKind.kPauseInterrupted || + event.kind == vms.EventKind.kPauseException || + event.kind == vms.EventKind.kResume) { + // For PauseInterrupted events, there will be no top frame if the isolate is + // idle (waiting in the message loop). + // For the Resume event, the top frame is provided at all times except for + // the initial resume event that is delivered when an isolate begins + // execution. + if (event.topFrame != null || + (event.kind != vms.EventKind.kPauseInterrupted && + event.kind != vms.EventKind.kResume)) { + assertFrame(event.topFrame); + } + } + if (event.kind == vms.EventKind.kPauseException) { + assertInstanceRef(event.exception); + } + if (event.kind == vms.EventKind.kPauseBreakpoint || + event.kind == vms.EventKind.kPauseInterrupted) { + assertBool(event.atAsyncSuspension); + } + if (event.kind == vms.EventKind.kInspect) { + assertInstanceRef(event.inspectee); + } + return event; +} + +/// Assert IsolateStart, IsolateRunnable, IsolateExit, IsolateUpdate, +/// and ServiceExtensionAdded events. +vms.Event assertIsolateEvent(vms.Event event) { + assertEvent(event); + if (event.kind == vms.EventKind.kServiceExtensionAdded) { + assertString(event.extensionRPC); + } + return event; +} + +String assertCodeKind(String obj) { + if (obj == "Collected") return obj; + if (obj == "Dart") return obj; + if (obj == "Native") return obj; + if (obj == "Stub") return obj; + if (obj == "Tag") return obj; + throw "invalid CodeKind: $obj"; +} + +String assertErrorKind(String obj) { + if (obj == "InternalError") return obj; + if (obj == "LanguageError") return obj; + if (obj == "TerminationError") return obj; + if (obj == "UnhandledException") return obj; + throw "invalid ErrorKind: $obj"; +} + +String assertEventKind(String obj) { + if (obj == "BreakpointAdded") return obj; + if (obj == "BreakpointRemoved") return obj; + if (obj == "BreakpointResolved") return obj; + if (obj == "Extension") return obj; + if (obj == "GC") return obj; + if (obj == "Inspect") return obj; + if (obj == "IsolateExit") return obj; + if (obj == "IsolateReload") return obj; + if (obj == "IsolateRunnable") return obj; + if (obj == "IsolateStart") return obj; + if (obj == "IsolateUpdate") return obj; + if (obj == "Logging") return obj; + if (obj == "None") return obj; + if (obj == "PauseBreakpoint") return obj; + if (obj == "PauseException") return obj; + if (obj == "PauseExit") return obj; + if (obj == "PauseInterrupted") return obj; + if (obj == "PausePostRequest") return obj; + if (obj == "PauseStart") return obj; + if (obj == "Resume") return obj; + if (obj == "ServiceExtensionAdded") return obj; + if (obj == "ServiceRegistered") return obj; + if (obj == "ServiceUnregistered") return obj; + if (obj == "VMUpdate") return obj; + if (obj == "WriteEvent") return obj; + throw "invalid EventKind: $obj"; +} + +String assertInstanceKind(String obj) { + if (obj == "Bool") return obj; + if (obj == "BoundedType") return obj; + if (obj == "Closure") return obj; + if (obj == "Double") return obj; + if (obj == "Float32List") return obj; + if (obj == "Float32x4") return obj; + if (obj == "Float32x4List") return obj; + if (obj == "Float64List") return obj; + if (obj == "Float64x2") return obj; + if (obj == "Float64x2List") return obj; + if (obj == "Int") return obj; + if (obj == "Int16List") return obj; + if (obj == "Int32List") return obj; + if (obj == "Int32x4") return obj; + if (obj == "Int32x4List") return obj; + if (obj == "Int64List") return obj; + if (obj == "Int8List") return obj; + if (obj == "List") return obj; + if (obj == "Map") return obj; + if (obj == "MirrorReference") return obj; + if (obj == "Null") return obj; + if (obj == "PlainInstance") return obj; + if (obj == "RegExp") return obj; + if (obj == "StackTrace") return obj; + if (obj == "String") return obj; + if (obj == "Type") return obj; + if (obj == "TypeParameter") return obj; + if (obj == "TypeRef") return obj; + if (obj == "Uint16List") return obj; + if (obj == "Uint32List") return obj; + if (obj == "Uint64List") return obj; + if (obj == "Uint8ClampedList") return obj; + if (obj == "Uint8List") return obj; + if (obj == "WeakProperty") return obj; + throw "invalid InstanceKind: $obj"; +} + +String assertSentinelKind(String obj) { + if (obj == "BeingInitialized") return obj; + if (obj == "Collected") return obj; + if (obj == "Expired") return obj; + if (obj == "Free") return obj; + if (obj == "NotInitialized") return obj; + if (obj == "OptimizedOut") return obj; + throw "invalid SentinelKind: $obj"; +} + +String assertFrameKind(String obj) { + if (obj == "AsyncActivation") return obj; + if (obj == "AsyncCausal") return obj; + if (obj == "AsyncSuspensionMarker") return obj; + if (obj == "Regular") return obj; + throw "invalid FrameKind: $obj"; +} + +String assertSourceReportKind(String obj) { + if (obj == "Coverage") return obj; + if (obj == "PossibleBreakpoints") return obj; + throw "invalid SourceReportKind: $obj"; +} + +String assertExceptionPauseMode(String obj) { + if (obj == "All") return obj; + if (obj == "None") return obj; + if (obj == "Unhandled") return obj; + throw "invalid ExceptionPauseMode: $obj"; +} + +String assertStepOption(String obj) { + if (obj == "Into") return obj; + if (obj == "Out") return obj; + if (obj == "Over") return obj; + if (obj == "OverAsyncSuspension") return obj; + if (obj == "Rewind") return obj; + throw "invalid StepOption: $obj"; +} + +vms.AllocationProfile assertAllocationProfile(vms.AllocationProfile obj) { + assertNotNull(obj); + assertString(obj.type); + assertMemoryUsage(obj.memoryUsage); + assertClassHeapStatss(obj.members); + return obj; +} + +vms.BoundField assertBoundField(vms.BoundField obj) { + assertNotNull(obj); + assertFieldRef(obj.decl); + if (obj.value is vms.InstanceRef) { + assertInstanceRef(obj.value); + } else if (obj.value is vms.Sentinel) { + assertSentinel(obj.value); + } else { + throw "Unexpected value: ${obj.value}"; + } + return obj; +} + +vms.BoundVariable assertBoundVariable(vms.BoundVariable obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.name); + if (obj.value is vms.InstanceRef) { + assertInstanceRef(obj.value); + } else if (obj.value is vms.TypeArgumentsRef) { + assertTypeArgumentsRef(obj.value); + } else if (obj.value is vms.Sentinel) { + assertSentinel(obj.value); + } else { + throw "Unexpected value: ${obj.value}"; + } + assertInt(obj.declarationTokenPos); + assertInt(obj.scopeStartTokenPos); + assertInt(obj.scopeEndTokenPos); + return obj; +} + +List assertBoundVariables(List list) { + for (vms.BoundVariable elem in list) { + assertBoundVariable(elem); + } + return list; +} + +vms.Breakpoint assertBreakpoint(vms.Breakpoint obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.id); + assertInt(obj.breakpointNumber); + assertBool(obj.resolved); + if (obj.location is vms.SourceLocation) { + assertSourceLocation(obj.location); + } else if (obj.location is vms.UnresolvedSourceLocation) { + assertUnresolvedSourceLocation(obj.location); + } else { + throw "Unexpected value: ${obj.location}"; + } + return obj; +} + +List assertBreakpoints(List list) { + for (vms.Breakpoint elem in list) { + assertBreakpoint(elem); + } + return list; +} + +vms.ClassRef assertClassRef(vms.ClassRef obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.id); + assertString(obj.name); + return obj; +} + +List assertClassRefs(List list) { + for (vms.ClassRef elem in list) { + assertClassRef(elem); + } + return list; +} + +vms.Class assertClass(vms.Class obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.id); + assertString(obj.name); + assertBool(obj.isAbstract); + assertBool(obj.isConst); + assertLibraryRef(obj.library); + assertInstanceRefs(obj.interfaces); + assertFieldRefs(obj.fields); + assertFuncRefs(obj.functions); + assertClassRefs(obj.subclasses); + return obj; +} + +vms.ClassHeapStats assertClassHeapStats(vms.ClassHeapStats obj) { + assertNotNull(obj); + assertString(obj.type); + assertInt(obj.accumulatedSize); + assertInt(obj.bytesCurrent); + assertInt(obj.instancesAccumulated); + assertInt(obj.instancesCurrent); + assertClassRef(obj.classRef); + assertInts(obj.new_); + assertInts(obj.old); + assertInt(obj.promotedBytes); + assertInt(obj.promotedInstances); + return obj; +} + +List assertClassHeapStatss(List list) { + for (vms.ClassHeapStats elem in list) { + assertClassHeapStats(elem); + } + return list; +} + +vms.ClassList assertClassList(vms.ClassList obj) { + assertNotNull(obj); + assertString(obj.type); + assertClassRefs(obj.classes); + return obj; +} + +vms.CodeRef assertCodeRef(vms.CodeRef obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.id); + assertString(obj.name); + assertCodeKind(obj.kind); + return obj; +} + +List assertCodeRefs(List list) { + for (vms.CodeRef elem in list) { + assertCodeRef(elem); + } + return list; +} + +vms.Code assertCode(vms.Code obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.id); + assertString(obj.name); + assertCodeKind(obj.kind); + return obj; +} + +vms.ContextRef assertContextRef(vms.ContextRef obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.id); + assertInt(obj.length); + return obj; +} + +List assertContextRefs(List list) { + for (vms.ContextRef elem in list) { + assertContextRef(elem); + } + return list; +} + +vms.Context assertContext(vms.Context obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.id); + assertInt(obj.length); + assertContextElements(obj.variables); + return obj; +} + +vms.ContextElement assertContextElement(vms.ContextElement obj) { + assertNotNull(obj); + if (obj.value is vms.InstanceRef) { + assertInstanceRef(obj.value); + } else if (obj.value is vms.Sentinel) { + assertSentinel(obj.value); + } else { + throw "Unexpected value: ${obj.value}"; + } + return obj; +} + +List assertContextElements(List list) { + for (vms.ContextElement elem in list) { + assertContextElement(elem); + } + return list; +} + +vms.ErrorRef assertErrorRef(vms.ErrorRef obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.id); + assertErrorKind(obj.kind); + assertString(obj.message); + return obj; +} + +List assertErrorRefs(List list) { + for (vms.ErrorRef elem in list) { + assertErrorRef(elem); + } + return list; +} + +vms.Error assertError(vms.Error obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.id); + assertErrorKind(obj.kind); + assertString(obj.message); + return obj; +} + +vms.Event assertEvent(vms.Event obj) { + assertNotNull(obj); + assertString(obj.type); + assertEventKind(obj.kind); + assertInt(obj.timestamp); + return obj; +} + +vms.ExtensionData assertExtensionData(vms.ExtensionData obj) { + assertNotNull(obj); + return obj; +} + +vms.FieldRef assertFieldRef(vms.FieldRef obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.id); + assertString(obj.name); + assertObjRef(obj.owner); + assertInstanceRef(obj.declaredType); + assertBool(obj.isConst); + assertBool(obj.isFinal); + assertBool(obj.isStatic); + return obj; +} + +List assertFieldRefs(List list) { + for (vms.FieldRef elem in list) { + assertFieldRef(elem); + } + return list; +} + +vms.Field assertField(vms.Field obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.id); + assertString(obj.name); + assertObjRef(obj.owner); + assertInstanceRef(obj.declaredType); + assertBool(obj.isConst); + assertBool(obj.isFinal); + assertBool(obj.isStatic); + return obj; +} + +vms.Flag assertFlag(vms.Flag obj) { + assertNotNull(obj); + assertString(obj.name); + assertString(obj.comment); + assertBool(obj.modified); + return obj; +} + +List assertFlags(List list) { + for (vms.Flag elem in list) { + assertFlag(elem); + } + return list; +} + +vms.FlagList assertFlagList(vms.FlagList obj) { + assertNotNull(obj); + assertString(obj.type); + assertFlags(obj.flags); + return obj; +} + +vms.Frame assertFrame(vms.Frame obj) { + assertNotNull(obj); + assertString(obj.type); + assertInt(obj.index); + return obj; +} + +List assertFrames(List list) { + for (vms.Frame elem in list) { + assertFrame(elem); + } + return list; +} + +vms.FuncRef assertFuncRef(vms.FuncRef obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.id); + assertString(obj.name); + if (obj.owner is vms.LibraryRef) { + assertLibraryRef(obj.owner); + } else if (obj.owner is vms.ClassRef) { + assertClassRef(obj.owner); + } else if (obj.owner is vms.FuncRef) { + assertFuncRef(obj.owner); + } else { + throw "Unexpected value: ${obj.owner}"; + } + assertBool(obj.isStatic); + assertBool(obj.isConst); + return obj; +} + +List assertFuncRefs(List list) { + for (vms.FuncRef elem in list) { + assertFuncRef(elem); + } + return list; +} + +vms.Func assertFunc(vms.Func obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.id); + assertString(obj.name); + if (obj.owner is vms.LibraryRef) { + assertLibraryRef(obj.owner); + } else if (obj.owner is vms.ClassRef) { + assertClassRef(obj.owner); + } else if (obj.owner is vms.FuncRef) { + assertFuncRef(obj.owner); + } else { + throw "Unexpected value: ${obj.owner}"; + } + return obj; +} + +vms.InstanceRef assertInstanceRef(vms.InstanceRef obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.id); + assertInstanceKind(obj.kind); + assertClassRef(obj.classRef); + return obj; +} + +List assertInstanceRefs(List list) { + for (vms.InstanceRef elem in list) { + assertInstanceRef(elem); + } + return list; +} + +vms.Instance assertInstance(vms.Instance obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.id); + assertInstanceKind(obj.kind); + assertClassRef(obj.classRef); + return obj; +} + +vms.IsolateRef assertIsolateRef(vms.IsolateRef obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.id); + assertString(obj.number); + assertString(obj.name); + return obj; +} + +List assertIsolateRefs(List list) { + for (vms.IsolateRef elem in list) { + assertIsolateRef(elem); + } + return list; +} + +vms.Isolate assertIsolate(vms.Isolate obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.id); + assertString(obj.number); + assertString(obj.name); + assertInt(obj.startTime); + assertBool(obj.runnable); + assertInt(obj.livePorts); + assertBool(obj.pauseOnExit); + assertEvent(obj.pauseEvent); + assertLibraryRefs(obj.libraries); + assertBreakpoints(obj.breakpoints); + assertExceptionPauseMode(obj.exceptionPauseMode); + return obj; +} + +vms.InstanceSet assertInstanceSet(vms.InstanceSet obj) { + assertNotNull(obj); + assertString(obj.type); + assertInt(obj.totalCount); + assertObjRefs(obj.instances); + return obj; +} + +vms.LibraryRef assertLibraryRef(vms.LibraryRef obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.id); + assertString(obj.name); + assertString(obj.uri); + return obj; +} + +List assertLibraryRefs(List list) { + for (vms.LibraryRef elem in list) { + assertLibraryRef(elem); + } + return list; +} + +vms.Library assertLibrary(vms.Library obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.id); + assertString(obj.name); + assertString(obj.uri); + assertBool(obj.debuggable); + assertLibraryDependencies(obj.dependencies); + assertScriptRefs(obj.scripts); + assertFieldRefs(obj.variables); + assertFuncRefs(obj.functions); + assertClassRefs(obj.classes); + return obj; +} + +vms.LibraryDependency assertLibraryDependency(vms.LibraryDependency obj) { + assertNotNull(obj); + assertBool(obj.isImport); + assertBool(obj.isDeferred); + assertString(obj.prefix); + assertLibraryRef(obj.target); + return obj; +} + +List assertLibraryDependencies( + List list) { + for (vms.LibraryDependency elem in list) { + assertLibraryDependency(elem); + } + return list; +} + +vms.LogRecord assertLogRecord(vms.LogRecord obj) { + assertNotNull(obj); + assertString(obj.type); + assertInstanceRef(obj.message); + assertInt(obj.time); + assertInt(obj.level); + assertInt(obj.sequenceNumber); + assertInstanceRef(obj.loggerName); + assertInstanceRef(obj.zone); + assertInstanceRef(obj.error); + assertInstanceRef(obj.stackTrace); + return obj; +} + +vms.MapAssociation assertMapAssociation(vms.MapAssociation obj) { + assertNotNull(obj); + if (obj.key is vms.InstanceRef) { + assertInstanceRef(obj.key); + } else if (obj.key is vms.Sentinel) { + assertSentinel(obj.key); + } else { + throw "Unexpected value: ${obj.key}"; + } + if (obj.value is vms.InstanceRef) { + assertInstanceRef(obj.value); + } else if (obj.value is vms.Sentinel) { + assertSentinel(obj.value); + } else { + throw "Unexpected value: ${obj.value}"; + } + return obj; +} + +vms.MemoryUsage assertMemoryUsage(vms.MemoryUsage obj) { + assertNotNull(obj); + assertString(obj.type); + assertInt(obj.externalUsage); + assertInt(obj.heapCapacity); + assertInt(obj.heapUsage); + return obj; +} + +vms.Message assertMessage(vms.Message obj) { + assertNotNull(obj); + assertString(obj.type); + assertInt(obj.index); + assertString(obj.name); + assertString(obj.messageObjectId); + assertInt(obj.size); + return obj; +} + +List assertMessages(List list) { + for (vms.Message elem in list) { + assertMessage(elem); + } + return list; +} + +vms.NullValRef assertNullValRef(vms.NullValRef obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.id); + assertInstanceKind(obj.kind); + assertClassRef(obj.classRef); + assertString(obj.valueAsString); + return obj; +} + +List assertNullValRefs(List list) { + for (vms.NullValRef elem in list) { + assertNullValRef(elem); + } + return list; +} + +vms.NullVal assertNullVal(vms.NullVal obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.id); + assertInstanceKind(obj.kind); + assertClassRef(obj.classRef); + assertString(obj.valueAsString); + return obj; +} + +vms.ObjRef assertObjRef(vms.ObjRef obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.id); + return obj; +} + +List assertObjRefs(List list) { + for (vms.ObjRef elem in list) { + assertObjRef(elem); + } + return list; +} + +vms.Obj assertObj(vms.Obj obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.id); + return obj; +} + +vms.ReloadReport assertReloadReport(vms.ReloadReport obj) { + assertNotNull(obj); + assertString(obj.type); + assertBool(obj.success); + return obj; +} + +vms.Response assertResponse(vms.Response obj) { + assertNotNull(obj); + assertString(obj.type); + return obj; +} + +vms.Sentinel assertSentinel(vms.Sentinel obj) { + assertNotNull(obj); + assertString(obj.type); + assertSentinelKind(obj.kind); + assertString(obj.valueAsString); + return obj; +} + +vms.ScriptRef assertScriptRef(vms.ScriptRef obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.id); + assertString(obj.uri); + return obj; +} + +List assertScriptRefs(List list) { + for (vms.ScriptRef elem in list) { + assertScriptRef(elem); + } + return list; +} + +vms.Script assertScript(vms.Script obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.id); + assertString(obj.uri); + assertLibraryRef(obj.library); + return obj; +} + +vms.ScriptList assertScriptList(vms.ScriptList obj) { + assertNotNull(obj); + assertString(obj.type); + assertScriptRefs(obj.scripts); + return obj; +} + +vms.SourceLocation assertSourceLocation(vms.SourceLocation obj) { + assertNotNull(obj); + assertString(obj.type); + assertScriptRef(obj.script); + assertInt(obj.tokenPos); + return obj; +} + +vms.SourceReport assertSourceReport(vms.SourceReport obj) { + assertNotNull(obj); + assertString(obj.type); + assertSourceReportRanges(obj.ranges); + assertScriptRefs(obj.scripts); + return obj; +} + +vms.SourceReportCoverage assertSourceReportCoverage( + vms.SourceReportCoverage obj) { + assertNotNull(obj); + assertInts(obj.hits); + assertInts(obj.misses); + return obj; +} + +vms.SourceReportRange assertSourceReportRange(vms.SourceReportRange obj) { + assertNotNull(obj); + assertInt(obj.scriptIndex); + assertInt(obj.startPos); + assertInt(obj.endPos); + assertBool(obj.compiled); + return obj; +} + +List assertSourceReportRanges( + List list) { + for (vms.SourceReportRange elem in list) { + assertSourceReportRange(elem); + } + return list; +} + +vms.Stack assertStack(vms.Stack obj) { + assertNotNull(obj); + assertString(obj.type); + assertFrames(obj.frames); + assertMessages(obj.messages); + return obj; +} + +vms.Timeline assertTimeline(vms.Timeline obj) { + assertNotNull(obj); + assertString(obj.type); + assertTimelineEvents(obj.traceEvents); + assertInt(obj.timeOriginMicros); + assertInt(obj.timeExtentMicros); + return obj; +} + +vms.TimelineEvent assertTimelineEvent(vms.TimelineEvent obj) { + assertNotNull(obj); + return obj; +} + +List assertTimelineEvents(List list) { + for (vms.TimelineEvent elem in list) { + assertTimelineEvent(elem); + } + return list; +} + +vms.TimelineFlags assertTimelineFlags(vms.TimelineFlags obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.recorderName); + assertStrings(obj.availableStreams); + assertStrings(obj.recordedStreams); + return obj; +} + +vms.Timestamp assertTimestamp(vms.Timestamp obj) { + assertNotNull(obj); + assertString(obj.type); + assertInt(obj.timestamp); + return obj; +} + +vms.TypeArgumentsRef assertTypeArgumentsRef(vms.TypeArgumentsRef obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.id); + assertString(obj.name); + return obj; +} + +List assertTypeArgumentsRefs( + List list) { + for (vms.TypeArgumentsRef elem in list) { + assertTypeArgumentsRef(elem); + } + return list; +} + +vms.TypeArguments assertTypeArguments(vms.TypeArguments obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.id); + assertString(obj.name); + assertInstanceRefs(obj.types); + return obj; +} + +vms.UnresolvedSourceLocation assertUnresolvedSourceLocation( + vms.UnresolvedSourceLocation obj) { + assertNotNull(obj); + assertString(obj.type); + return obj; +} + +vms.Version assertVersion(vms.Version obj) { + assertNotNull(obj); + assertString(obj.type); + assertInt(obj.major); + assertInt(obj.minor); + return obj; +} + +vms.VMRef assertVMRef(vms.VMRef obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.name); + return obj; +} + +List assertVMRefs(List list) { + for (vms.VMRef elem in list) { + assertVMRef(elem); + } + return list; +} + +vms.VM assertVM(vms.VM obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.name); + assertInt(obj.architectureBits); + assertString(obj.targetCPU); + assertString(obj.hostCPU); + assertString(obj.version); + assertInt(obj.pid); + assertInt(obj.startTime); + assertIsolateRefs(obj.isolates); + return obj; +} + +vms.CpuProfile assert_CpuProfile(vms.CpuProfile obj) { + assertNotNull(obj); + assertString(obj.type); + assertInt(obj.sampleCount); + assertInt(obj.samplePeriod); + assertInt(obj.stackDepth); + assertDouble(obj.timeSpan); + assertInt(obj.timeOriginMicros); + assertInt(obj.timeExtentMicros); + assertCodeRegions(obj.codes); + assertProfileFunctions(obj.functions); + assertInts(obj.exclusiveCodeTrie); + assertInts(obj.inclusiveCodeTrie); + assertInts(obj.exclusiveFunctionTrie); + assertInts(obj.inclusiveFunctionTrie); + return obj; +} + +vms.CodeRegion assertCodeRegion(vms.CodeRegion obj) { + assertNotNull(obj); + assertString(obj.kind); + assertInt(obj.inclusiveTicks); + assertInt(obj.exclusiveTicks); + assertCodeRef(obj.code); + return obj; +} + +List assertCodeRegions(List list) { + for (vms.CodeRegion elem in list) { + assertCodeRegion(elem); + } + return list; +} + +vms.ProfileFunction assertProfileFunction(vms.ProfileFunction obj) { + assertNotNull(obj); + assertString(obj.kind); + assertInt(obj.inclusiveTicks); + assertInt(obj.exclusiveTicks); + assertFuncRef(obj.function); + assertInts(obj.codes); + return obj; +} + +List assertProfileFunctions( + List list) { + for (vms.ProfileFunction elem in list) { + assertProfileFunction(elem); + } + return list; +} + +vms.HeapSpace assertHeapSpace(vms.HeapSpace obj) { + assertNotNull(obj); + assertString(obj.type); + assertDouble(obj.avgCollectionPeriodMillis); + assertInt(obj.capacity); + assertInt(obj.collections); + assertInt(obj.external); + assertString(obj.name); + assertDouble(obj.time); + assertInt(obj.used); + return obj; +} diff --git a/pkg/vm_service/example/vm_service_tester.dart b/pkg/vm_service/example/vm_service_tester.dart new file mode 100644 index 00000000000..5d1f819b9d4 --- /dev/null +++ b/pkg/vm_service/example/vm_service_tester.dart @@ -0,0 +1,219 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library service_tester; + +import 'dart:async'; +import 'dart:collection'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:path/path.dart' as path; +import 'package:pedantic/pedantic.dart'; +import 'package:test/test.dart'; +import 'package:vm_service/vm_service.dart'; +import 'package:vm_service/vm_service_io.dart'; + +final String host = 'localhost'; +final int port = 7575; + +VmService serviceClient; + +void main() { + Process process; + + tearDown(() { + process?.kill(); + }); + + test('integration', () async { + String sdk = path.dirname(path.dirname(Platform.resolvedExecutable)); + + print('Using sdk at ${sdk}.'); + + // pause_isolates_on_start, pause_isolates_on_exit + process = await Process.start('${sdk}/bin/dart', [ + '--pause_isolates_on_start', + '--enable-vm-service=${port}', + '--disable-service-auth-codes', + 'example/sample_main.dart' + ]); + + print('dart process started'); + + // ignore: unawaited_futures + process.exitCode.then((code) => print('vm exited: ${code}')); + // ignore: strong_mode_down_cast_composite + process.stdout.transform(utf8.decoder).listen(print); + // ignore: strong_mode_down_cast_composite + process.stderr.transform(utf8.decoder).listen(print); + + await new Future.delayed(new Duration(milliseconds: 500)); + + serviceClient = await vmServiceConnect(host, port, log: new StdoutLog()); + + print('socket connected'); + + serviceClient.onSend.listen((str) => print('--> ${str}')); + + // The next listener will bail out if you toggle this to false, which we need + // to do for some things like the custom service registration tests. + var checkResponseJsonCompatibility = true; + serviceClient.onReceive.listen((str) { + print('<-- ${str}'); + + if (!checkResponseJsonCompatibility) return; + + // For each received event, check that we can deserialize it and + // reserialize it back to the same exact representation (minus private + // fields). + var json = jsonDecode(str); + var originalJson = json['result'] as Map; + if (originalJson == null && json['method'] == 'streamNotify') { + originalJson = json['params']['event']; + } + expect(originalJson, isNotNull, reason: 'Unrecognized event type! $json'); + + // ignore: invalid_use_of_visible_for_testing_member + var instance = + createServiceObject(originalJson, const ['Event', 'Success']); + expect(instance, isNotNull, + reason: 'failed to deserialize object $originalJson!'); + + var reserializedJson = (instance as dynamic).toJson(); + + forEachNestedMap(originalJson, (obj) { + // Private fields that we don't reproduce + obj.removeWhere((k, v) => k.startsWith('_')); + // Extra fields that aren't specified and we don't reproduce + obj.remove('isExport'); + }); + + forEachNestedMap(reserializedJson, (obj) { + // We provide explicit defaults for these, need to remove them. + obj.remove('valueAsStringIsTruncated'); + }); + + expect(reserializedJson, equals(originalJson)); + }); + + serviceClient.onIsolateEvent.listen((e) => print('onIsolateEvent: ${e}')); + serviceClient.onDebugEvent.listen((e) => print('onDebugEvent: ${e}')); + serviceClient.onGCEvent.listen((e) => print('onGCEvent: ${e}')); + serviceClient.onStdoutEvent.listen((e) => print('onStdoutEvent: ${e}')); + serviceClient.onStderrEvent.listen((e) => print('onStderrEvent: ${e}')); + + unawaited(serviceClient.streamListen(EventStreams.kIsolate)); + unawaited(serviceClient.streamListen(EventStreams.kDebug)); + unawaited(serviceClient.streamListen(EventStreams.kStdout)); + + VM vm = await serviceClient.getVM(); + print('hostCPU=${vm.hostCPU}'); + print(await serviceClient.getVersion()); + List isolates = await vm.isolates; + print(isolates); + + // Disable the json reserialization checks since custom services are not + // supported. + checkResponseJsonCompatibility = false; + await testServiceRegistration(); + checkResponseJsonCompatibility = true; + + await testScriptParse(vm.isolates.first); + await testSourceReport(vm.isolates.first); + + IsolateRef isolateRef = isolates.first; + print(await serviceClient.resume(isolateRef.id)); + + serviceClient.dispose(); + }); +} + +// Deeply traverses a map and calls [cb] with each nested map and the +// parent map. +void forEachNestedMap(Map input, Function(Map) cb) { + var queue = Queue.from([input]); + while (queue.isNotEmpty) { + var next = queue.removeFirst(); + if (next is Map) { + cb(next); + queue.addAll(next.values); + } else if (next is List) { + queue.addAll(next); + } + } +} + +Future testServiceRegistration() async { + const String serviceName = 'serviceName'; + const String serviceAlias = 'serviceAlias'; + const String movedValue = 'movedValue'; + serviceClient.registerServiceCallback(serviceName, + (Map params) async { + assert(params['input'] == movedValue); + return { + 'result': {'output': params['input']} + }; + }); + await serviceClient.registerService(serviceName, serviceAlias); + VmService otherClient = + await vmServiceConnect(host, port, log: new StdoutLog()); + Completer completer = new Completer(); + otherClient.onEvent('_Service').listen((e) async { + if (e.service == serviceName && e.kind == EventKind.kServiceRegistered) { + assert(e.alias == serviceAlias); + Response response = await serviceClient.callMethod( + e.method, + args: {'input': movedValue}, + ); + assert(response.json['output'] == movedValue); + completer.complete(); + } + }); + await otherClient.streamListen('_Service'); + await completer.future; +} + +Future testScriptParse(IsolateRef isolateRef) async { + final Isolate isolate = await serviceClient.getIsolate(isolateRef.id); + final Library rootLibrary = + await serviceClient.getObject(isolateRef.id, isolate.rootLib.id); + final ScriptRef scriptRef = rootLibrary.scripts.first; + + final Script script = + await serviceClient.getObject(isolateRef.id, scriptRef.id); + print(script); + print(script.uri); + print(script.library); + print(script.source.length); + print(script.tokenPosTable.length); +} + +Future testSourceReport(IsolateRef isolateRef) async { + final Isolate isolate = await serviceClient.getIsolate(isolateRef.id); + final Library rootLibrary = + await serviceClient.getObject(isolateRef.id, isolate.rootLib.id); + final ScriptRef scriptRef = rootLibrary.scripts.first; + + // make sure some code has run + await serviceClient.resume(isolateRef.id); + await Future.delayed(const Duration(milliseconds: 25)); + + final SourceReport sourceReport = await serviceClient.getSourceReport( + isolateRef.id, [SourceReportKind.kCoverage], + scriptId: scriptRef.id); + for (SourceReportRange range in sourceReport.ranges) { + print(' $range'); + if (range.coverage != null) { + print(' ${range.coverage}'); + } + } + print(sourceReport); +} + +class StdoutLog extends Log { + void warning(String message) => print(message); + + void severe(String message) => print(message); +} diff --git a/pkg/vm_service/java/.gitignore b/pkg/vm_service/java/.gitignore new file mode 100644 index 00000000000..d5685675be5 --- /dev/null +++ b/pkg/vm_service/java/.gitignore @@ -0,0 +1,4 @@ +build/ +dist/ +out/ +src/gen/ diff --git a/pkg/vm_service/java/build.xml b/pkg/vm_service/java/build.xml new file mode 100644 index 00000000000..d13fdff6440 --- /dev/null +++ b/pkg/vm_service/java/build.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pkg/vm_service/java/classpath.txt b/pkg/vm_service/java/classpath.txt new file mode 100644 index 00000000000..a05a02af88e --- /dev/null +++ b/pkg/vm_service/java/classpath.txt @@ -0,0 +1 @@ +third_party/gson/gson-2.2.4.jar:third_party/guava/guava-13.0.1.jar:third_party/weberknecht/weberknecht-0.1.5.jar diff --git a/pkg/vm_service/java/example/sample_exception.dart b/pkg/vm_service/java/example/sample_exception.dart new file mode 100644 index 00000000000..cfe1745fa9b --- /dev/null +++ b/pkg/vm_service/java/example/sample_exception.dart @@ -0,0 +1,53 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +void main(List args) { + // Hijinks to remove an analysis warning. + dynamic local1; + if (true) local1 = 'abcd'; + + int local2 = 2; + var longList = [1, "hello", 3, 5, 7, 11, 13, 14, 15, 16, 17, 18, 19, 20]; + var deepList = [ + new Bar(), + [ + [ + [ + [ + [7] + ] + ], + "end" + ] + ] + ]; + + print('hello from main'); + + // throw a caught exception + try { + foo(local1.baz()); + } catch (e) { + print('-----------------'); + print('caught $e'); + print('-----------------'); + } + foo(local2); + + print(longList); + print(deepList); + print('exiting...'); +} + +void foo(int val) { + print('val: ${val}'); +} + +class Bar extends FooBar { + String field1 = "my string"; +} + +class FooBar { + int field2 = 47; +} diff --git a/pkg/vm_service/java/example/sample_main.dart b/pkg/vm_service/java/example/sample_main.dart new file mode 100644 index 00000000000..da882915348 --- /dev/null +++ b/pkg/vm_service/java/example/sample_main.dart @@ -0,0 +1,46 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +void main(List args) { + String local1 = 'abcd'; + int local2 = 2; + var longList = [1, "hello", 3, 5, 7, 11, 13, 14, 15, 16, 17, 18, 19, 20]; + var deepList = [ + new Bar(), + [ + [ + [ + [ + [7] + ] + ], + "end" + ] + ] + ]; + + print('hello from main'); + + foo(1); + foo(local2); + foo(3); + foo(local1.length); + + print(longList); + print(deepList); + + print('exiting...'); +} + +void foo(int val) { + print('val: ${val}'); +} + +class Bar extends FooBar { + String field1 = "my string"; +} + +class FooBar { + int field2 = 47; +} diff --git a/pkg/vm_service/java/javaconfig.json b/pkg/vm_service/java/javaconfig.json new file mode 100644 index 00000000000..2402aaa9de8 --- /dev/null +++ b/pkg/vm_service/java/javaconfig.json @@ -0,0 +1,5 @@ +{ + "sourcePath": ["src", "test"], + "classPathFile": "classpath.txt", + "outputDirectory": "build/classes" +} diff --git a/pkg/vm_service/java/src/org/dartlang/vm/service/RemoteServiceCompleter.java b/pkg/vm_service/java/src/org/dartlang/vm/service/RemoteServiceCompleter.java new file mode 100644 index 00000000000..48ae3eae990 --- /dev/null +++ b/pkg/vm_service/java/src/org/dartlang/vm/service/RemoteServiceCompleter.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2017, the Dart project authors. + * + * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.eclipse.org/legal/epl-v10.html + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.dartlang.vm.service; + +import com.google.gson.JsonObject; + +public interface RemoteServiceCompleter { + /** + * Should be called when a service request completes successfully. + * + * @param result the result of the request + */ + void result(JsonObject result); + + /** + * Should be called when a service request completes with an error. + * + * @param code the error code generated by the request + * @param message the description of the error + * @param data [optional] the description of the error + */ + void error(int code, String message, JsonObject data); +} diff --git a/pkg/vm_service/java/src/org/dartlang/vm/service/RemoteServiceRunner.java b/pkg/vm_service/java/src/org/dartlang/vm/service/RemoteServiceRunner.java new file mode 100644 index 00000000000..cc42969d46d --- /dev/null +++ b/pkg/vm_service/java/src/org/dartlang/vm/service/RemoteServiceRunner.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2017, the Dart project authors. + * + * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.eclipse.org/legal/epl-v10.html + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.dartlang.vm.service; + +import com.google.gson.JsonObject; + +/** + * Interface used by {@link VmService} to register callbacks to services. + */ +public interface RemoteServiceRunner { + /** + * Called when a service request has been received. + * + * @param params the parameters of the request + * @param completer the completer to invoke at the end of the execution + */ + void run(JsonObject params, RemoteServiceCompleter completer); +} diff --git a/pkg/vm_service/java/src/org/dartlang/vm/service/VmServiceBase.java b/pkg/vm_service/java/src/org/dartlang/vm/service/VmServiceBase.java new file mode 100644 index 00000000000..f1797ab3383 --- /dev/null +++ b/pkg/vm_service/java/src/org/dartlang/vm/service/VmServiceBase.java @@ -0,0 +1,635 @@ +/* + * Copyright (c) 2015, the Dart project authors. + * + * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.eclipse.org/legal/epl-v10.html + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.dartlang.vm.service; + +import com.google.common.collect.Maps; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import de.roderick.weberknecht.WebSocket; +import de.roderick.weberknecht.WebSocketEventHandler; +import de.roderick.weberknecht.WebSocketException; +import de.roderick.weberknecht.WebSocketMessage; +import org.dartlang.vm.service.consumer.*; +import org.dartlang.vm.service.element.*; +import org.dartlang.vm.service.internal.RequestSink; +import org.dartlang.vm.service.internal.VmServiceConst; +import org.dartlang.vm.service.internal.WebSocketRequestSink; +import org.dartlang.vm.service.logging.Logging; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Internal {@link VmService} base class containing non-generated code. + */ +@SuppressWarnings({"unused", "WeakerAccess"}) +abstract class VmServiceBase implements VmServiceConst { + /** + * Connect to the VM observatory service via the specified URI + * + * @return an API object for interacting with the VM service (not {@code null}). + */ + public static VmService connect(final String url) throws IOException { + // Validate URL + URI uri; + try { + uri = new URI(url); + } catch (URISyntaxException e) { + throw new IOException("Invalid URL: " + url, e); + } + String wsScheme = uri.getScheme(); + if (!"ws".equals(wsScheme) && !"wss".equals(wsScheme)) { + throw new IOException("Unsupported URL scheme: " + wsScheme); + } + + // Create web socket and observatory + WebSocket webSocket; + try { + webSocket = new WebSocket(uri); + } catch (WebSocketException e) { + throw new IOException("Failed to create websocket: " + url, e); + } + final VmService vmService = new VmService(); + + // Setup event handler for forwarding responses + webSocket.setEventHandler(new WebSocketEventHandler() { + @Override + public void onClose() { + Logging.getLogger().logInformation("VM connection closed: " + url); + + vmService.connectionClosed(); + } + + @Override + public void onMessage(WebSocketMessage message) { + Logging.getLogger().logInformation("VM message: " + message.getText()); + try { + vmService.processMessage(message.getText()); + } catch (Exception e) { + Logging.getLogger().logError(e.getMessage(), e); + } + } + + @Override + public void onOpen() { + vmService.connectionOpened(); + + Logging.getLogger().logInformation("VM connection open: " + url); + } + + @Override + public void onPing() { + } + + @Override + public void onPong() { + } + }); + + // Establish WebSocket Connection + //noinspection TryWithIdenticalCatches + try { + webSocket.connect(); + } catch (WebSocketException e) { + throw new IOException("Failed to connect: " + url, e); + } catch (ArrayIndexOutOfBoundsException e) { + // The weberknecht can occasionally throw an array index exception if a connect terminates on initial connect + // (de.roderick.weberknecht.WebSocket.connect, WebSocket.java:126). + throw new IOException("Failed to connect: " + url, e); + } + vmService.requestSink = new WebSocketRequestSink(webSocket); + + // Check protocol version + final CountDownLatch latch = new CountDownLatch(1); + final String[] errMsg = new String[1]; + vmService.getVersion(new VersionConsumer() { + @Override + public void onError(RPCError error) { + String msg = "Failed to determine protocol version: " + error.getCode() + "\n message: " + + error.getMessage() + "\n details: " + error.getDetails(); + Logging.getLogger().logInformation(msg); + errMsg[0] = msg; + } + + @Override + public void received(Version response) { + int major = response.getMajor(); + int minor = response.getMinor(); + if (major != VmService.versionMajor || minor != VmService.versionMinor) { + if (major == 2 || major == 3) { + Logging.getLogger().logInformation( + "Difference in protocol version: client=" + VmService.versionMajor + "." + + VmService.versionMinor + " vm=" + major + "." + minor); + } else { + String msg = "Incompatible protocol version: client=" + VmService.versionMajor + "." + + VmService.versionMinor + " vm=" + major + "." + minor; + Logging.getLogger().logError(msg); + errMsg[0] = msg; + } + } + latch.countDown(); + } + }); + try { + if (!latch.await(5, TimeUnit.SECONDS)) { + throw new IOException("Failed to determine protocol version"); + } + if (errMsg[0] != null) { + throw new IOException(errMsg[0]); + } + } catch (InterruptedException e) { + throw new RuntimeException("Interrupted while waiting for response", e); + } + + return vmService; + } + + /** + * Connect to the VM observatory service on the given local port. + * + * @return an API object for interacting with the VM service (not {@code null}). + * + * @deprecated prefer the Url based constructor {@link VmServiceBase#connect} + */ + @Deprecated + public static VmService localConnect(int port) throws IOException { + return connect("ws://localhost:" + port + "/ws"); + } + + /** + * A mapping between {@link String} ids' and the associated {@link Consumer} that was passed when + * the request was made. Synchronize against {@link #consumerMapLock} before accessing this field. + */ + private final Map consumerMap = Maps.newHashMap(); + + /** + * The object used to synchronize access to {@link #consumerMap}. + */ + private final Object consumerMapLock = new Object(); + + /** + * The unique ID for the next request. + */ + private final AtomicInteger nextId = new AtomicInteger(); + + /** + * A list of objects to which {@link Event}s from the VM are forwarded. + */ + private final List vmListeners = new ArrayList<>(); + + /** + * A list of objects to which {@link Event}s from the VM are forwarded. + */ + private final Map remoteServiceRunners = Maps.newHashMap(); + + /** + * The channel through which observatory requests are made. + */ + RequestSink requestSink; + + /** + * Add a listener to receive {@link Event}s from the VM. + */ + public void addVmServiceListener(VmServiceListener listener) { + vmListeners.add(listener); + } + + /** + * Remove the given listener from the VM. + */ + public void removeVmServiceListener(VmServiceListener listener) { + vmListeners.remove(listener); + } + + /** + * Add a VM RemoteServiceRunner. + */ + public void addServiceRunner(String service, RemoteServiceRunner runner) { + remoteServiceRunners.put(service, runner); + } + + /** + * Remove a VM RemoteServiceRunner. + */ + public void removeServiceRunner(String service) { + remoteServiceRunners.remove(service); + } + + /** + * Disconnect from the VM observatory service. + */ + public void disconnect() { + requestSink.close(); + } + + /** + * Return the instance with the given identifier. + */ + public void getInstance(String isolateId, String instanceId, final GetInstanceConsumer consumer) { + getObject(isolateId, instanceId, new GetObjectConsumer() { + + @Override + public void onError(RPCError error) { + consumer.onError(error); + } + + @Override + public void received(Obj response) { + if (response instanceof Instance) { + consumer.received((Instance) response); + } else { + onError(RPCError.unexpected("Instance", response)); + } + } + + @Override + public void received(Sentinel response) { + onError(RPCError.unexpected("Instance", response)); + } + }); + } + + /** + * Return the library with the given identifier. + */ + public void getLibrary(String isolateId, String libraryId, final GetLibraryConsumer consumer) { + getObject(isolateId, libraryId, new GetObjectConsumer() { + + @Override + public void onError(RPCError error) { + consumer.onError(error); + } + + @Override + public void received(Obj response) { + if (response instanceof Library) { + consumer.received((Library) response); + } else { + onError(RPCError.unexpected("Library", response)); + } + } + + @Override + public void received(Sentinel response) { + onError(RPCError.unexpected("Library", response)); + } + }); + } + + public abstract void getObject(String isolateId, String objectId, GetObjectConsumer consumer); + + /** + * Invoke a specific service protocol extension method. + *

+ * See https://api.dartlang.org/stable/dart-developer/dart-developer-library.html. + */ + public void callServiceExtension(String isolateId, String method, ServiceExtensionConsumer consumer) { + JsonObject params = new JsonObject(); + params.addProperty("isolateId", isolateId); + request(method, params, consumer); + } + + /** + * Invoke a specific service protocol extension method. + *

+ * See https://api.dartlang.org/stable/dart-developer/dart-developer-library.html. + */ + public void callServiceExtension(String isolateId, String method, JsonObject params, ServiceExtensionConsumer consumer) { + params.addProperty("isolateId", isolateId); + request(method, params, consumer); + } + + /** + * Sends the request and associates the request with the passed {@link Consumer}. + */ + protected void request(String method, JsonObject params, Consumer consumer) { + + // Assemble the request + String id = Integer.toString(nextId.incrementAndGet()); + JsonObject request = new JsonObject(); + + request.addProperty(JSONRPC, JSONRPC_VERSION); + request.addProperty(ID, id); + request.addProperty(METHOD, method); + request.add(PARAMS, params); + + // Cache the consumer to receive the response + synchronized (consumerMapLock) { + consumerMap.put(id, consumer); + } + + // Send the request + requestSink.add(request); + } + + public void connectionOpened() { + for (VmServiceListener listener : new ArrayList<>(vmListeners)) { + try { + listener.connectionOpened(); + } catch (Exception e) { + Logging.getLogger().logError("Exception notifying listener", e); + } + } + } + + private void forwardEvent(String streamId, Event event) { + for (VmServiceListener listener : new ArrayList<>(vmListeners)) { + try { + listener.received(streamId, event); + } catch (Exception e) { + Logging.getLogger().logError("Exception processing event: " + streamId + ", " + event.getJson(), e); + } + } + } + + public void connectionClosed() { + for (VmServiceListener listener : new ArrayList<>(vmListeners)) { + try { + listener.connectionClosed(); + } catch (Exception e) { + Logging.getLogger().logError("Exception notifying listener", e); + } + } + } + + abstract void forwardResponse(Consumer consumer, String type, JsonObject json); + + void logUnknownResponse(Consumer consumer, JsonObject json) { + Class consumerClass = consumer.getClass(); + StringBuilder msg = new StringBuilder(); + msg.append("Expected response for ").append(consumerClass).append("\n"); + for (Class interf : consumerClass.getInterfaces()) { + msg.append(" implementing ").append(interf).append("\n"); + } + msg.append(" but received ").append(json); + Logging.getLogger().logError(msg.toString()); + } + + /** + * Process the response from the VM service and forward that response to the consumer associated + * with the response id. + */ + void processMessage(String jsonText) { + if (jsonText == null || jsonText.isEmpty()) { + return; + } + + // Decode the JSON + JsonObject json; + try { + json = (JsonObject) new JsonParser().parse(jsonText); + } catch (Exception e) { + Logging.getLogger().logError("Parse message failed: " + jsonText, e); + return; + } + + if (json.has("method")) { + if (!json.has(PARAMS)) { + final String message = "Missing " + PARAMS; + Logging.getLogger().logError(message); + final JsonObject response = new JsonObject(); + response.addProperty(JSONRPC, JSONRPC_VERSION); + final JsonObject error = new JsonObject(); + error.addProperty(CODE, INVALID_REQUEST); + error.addProperty(MESSAGE, message); + response.add(ERROR, error); + requestSink.add(response); + return; + } + if (json.has("id")) { + processRequest(json); + } else { + processNotification(json); + } + } else if (json.has("result") || json.has("error")) { + processResponse(json); + } else { + Logging.getLogger().logError("Malformed message"); + } + } + + void processRequest(JsonObject json) { + final JsonObject response = new JsonObject(); + response.addProperty(JSONRPC, JSONRPC_VERSION); + + // Get the consumer associated with this request + String id; + try { + id = json.get(ID).getAsString(); + } catch (Exception e) { + final String message = "Request malformed " + ID; + Logging.getLogger().logError(message, e); + final JsonObject error = new JsonObject(); + error.addProperty(CODE, INVALID_REQUEST); + error.addProperty(MESSAGE, message); + response.add(ERROR, error); + requestSink.add(response); + return; + } + + response.addProperty(ID, id); + + String method; + try { + method = json.get(METHOD).getAsString(); + } catch (Exception e) { + final String message = "Request malformed " + METHOD; + Logging.getLogger().logError(message, e); + final JsonObject error = new JsonObject(); + error.addProperty(CODE, INVALID_REQUEST); + error.addProperty(MESSAGE, message); + response.add(ERROR, error); + requestSink.add(response); + return; + } + + JsonObject params; + try { + params = json.get(PARAMS).getAsJsonObject(); + } catch (Exception e) { + final String message = "Request malformed " + METHOD; + Logging.getLogger().logError(message, e); + final JsonObject error = new JsonObject(); + error.addProperty(CODE, INVALID_REQUEST); + error.addProperty(MESSAGE, message); + response.add(ERROR, error); + requestSink.add(response); + return; + } + + if (!remoteServiceRunners.containsKey(method)) { + final String message = "Unknown service " + method; + Logging.getLogger().logError(message); + final JsonObject error = new JsonObject(); + error.addProperty(CODE, METHOD_NOT_FOUND); + error.addProperty(MESSAGE, message); + response.add(ERROR, error); + requestSink.add(response); + return; + } + + final RemoteServiceRunner runner = remoteServiceRunners.get(method); + try { + runner.run(params, new RemoteServiceCompleter() { + public void result(JsonObject result) { + response.add(RESULT, result); + requestSink.add(response); + } + + public void error(int code, String message, JsonObject data) { + final JsonObject error = new JsonObject(); + error.addProperty(CODE, code); + error.addProperty(MESSAGE, message); + if (data != null) { + error.add(DATA, data); + } + response.add(ERROR, error); + requestSink.add(response); + } + }); + } catch (Exception e) { + final String message = "Internal Server Error"; + Logging.getLogger().logError(message, e); + final JsonObject error = new JsonObject(); + error.addProperty(CODE, SERVER_ERROR); + error.addProperty(MESSAGE, message); + response.add(ERROR, error); + requestSink.add(response); + } + } + + private static final RemoteServiceCompleter ignoreCallback = + new RemoteServiceCompleter() { + public void result(JsonObject result) { + // ignore + } + + public void error(int code, String message, JsonObject data) { + // ignore + } + }; + + void processNotification(JsonObject json) { + String method; + try { + method = json.get(METHOD).getAsString(); + } catch (Exception e) { + Logging.getLogger().logError("Request malformed " + METHOD, e); + return; + } + JsonObject params; + try { + params = json.get(PARAMS).getAsJsonObject(); + } catch (Exception e) { + Logging.getLogger().logError("Event missing " + PARAMS, e); + return; + } + if ("streamNotify".equals(method)) { + String streamId; + try { + streamId = params.get(STREAM_ID).getAsString(); + } catch (Exception e) { + Logging.getLogger().logError("Event missing " + STREAM_ID, e); + return; + } + Event event; + try { + event = new Event(params.get(EVENT).getAsJsonObject()); + } catch (Exception e) { + Logging.getLogger().logError("Event missing " + EVENT, e); + return; + } + forwardEvent(streamId, event); + } else { + if (!remoteServiceRunners.containsKey(method)) { + Logging.getLogger().logError("Unknown service " + method); + return; + } + + final RemoteServiceRunner runner = remoteServiceRunners.get(method); + try { + runner.run(params, ignoreCallback); + } catch (Exception e) { + Logging.getLogger().logError("Internal Server Error", e); + } + } + } + + void processResponse(JsonObject json) { + JsonElement idElem = json.get(ID); + if (idElem == null) { + Logging.getLogger().logError("Response missing " + ID); + return; + } + + // Get the consumer associated with this response + String id; + try { + id = idElem.getAsString(); + } catch (Exception e) { + Logging.getLogger().logError("Response missing " + ID, e); + return; + } + Consumer consumer = consumerMap.remove(id); + if (consumer == null) { + Logging.getLogger().logError("No consumer associated with " + ID + ": " + id); + return; + } + + // Forward the response if the request was successfully executed + JsonElement resultElem = json.get(RESULT); + if (resultElem != null) { + JsonObject result; + try { + result = resultElem.getAsJsonObject(); + } catch (Exception e) { + Logging.getLogger().logError("Response has invalid " + RESULT, e); + return; + } + String responseType; + try { + responseType = result.get(TYPE).getAsString(); + } catch (Exception e) { + Logging.getLogger().logError("Response missing " + TYPE, e); + return; + } + forwardResponse(consumer, responseType, result); + return; + } + + // Forward an error if the request failed + resultElem = json.get(ERROR); + if (resultElem != null) { + JsonObject error; + try { + error = resultElem.getAsJsonObject(); + } catch (Exception e) { + Logging.getLogger().logError("Response has invalid " + RESULT, e); + return; + } + consumer.onError(new RPCError(error)); + return; + } + + Logging.getLogger().logError("Response missing " + RESULT + " and " + ERROR); + } +} diff --git a/pkg/vm_service/java/src/org/dartlang/vm/service/VmServiceListener.java b/pkg/vm_service/java/src/org/dartlang/vm/service/VmServiceListener.java new file mode 100644 index 00000000000..7e59bea964f --- /dev/null +++ b/pkg/vm_service/java/src/org/dartlang/vm/service/VmServiceListener.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2015, the Dart project authors. + * + * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.eclipse.org/legal/epl-v10.html + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.dartlang.vm.service; + +import org.dartlang.vm.service.element.Event; + +/** + * Interface used by {@link VmService} to notify others of VM events. + */ +public interface VmServiceListener { + void connectionOpened(); + + /** + * Called when a VM event has been received. + * + * @param streamId the stream identifier (e.g. {@link VmService#DEBUG_STREAM_ID} + * @param event the event + */ + void received(String streamId, Event event); + + void connectionClosed(); +} diff --git a/pkg/vm_service/java/src/org/dartlang/vm/service/consumer/Consumer.java b/pkg/vm_service/java/src/org/dartlang/vm/service/consumer/Consumer.java new file mode 100644 index 00000000000..3175d252741 --- /dev/null +++ b/pkg/vm_service/java/src/org/dartlang/vm/service/consumer/Consumer.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2015, the Dart project authors. + * + * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.eclipse.org/legal/epl-v10.html + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.dartlang.vm.service.consumer; + +import org.dartlang.vm.service.element.RPCError; + +/** + * Consumer is a common interface for all consumer interfaces. + */ +public interface Consumer { + /** + * Called if the request failed for some reason. + */ + void onError(RPCError error); +} diff --git a/pkg/vm_service/java/src/org/dartlang/vm/service/consumer/GetInstanceConsumer.java b/pkg/vm_service/java/src/org/dartlang/vm/service/consumer/GetInstanceConsumer.java new file mode 100644 index 00000000000..ae6658cd905 --- /dev/null +++ b/pkg/vm_service/java/src/org/dartlang/vm/service/consumer/GetInstanceConsumer.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2015, the Dart project authors. + * + * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.eclipse.org/legal/epl-v10.html + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.dartlang.vm.service.consumer; + +import org.dartlang.vm.service.element.Instance; + +public interface GetInstanceConsumer extends Consumer { + void received(Instance response); +} diff --git a/pkg/vm_service/java/src/org/dartlang/vm/service/consumer/GetLibraryConsumer.java b/pkg/vm_service/java/src/org/dartlang/vm/service/consumer/GetLibraryConsumer.java new file mode 100644 index 00000000000..5d61e604d3b --- /dev/null +++ b/pkg/vm_service/java/src/org/dartlang/vm/service/consumer/GetLibraryConsumer.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2015, the Dart project authors. + * + * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.eclipse.org/legal/epl-v10.html + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.dartlang.vm.service.consumer; + +import org.dartlang.vm.service.element.Library; + +public interface GetLibraryConsumer extends Consumer { + void received(Library response); +} diff --git a/pkg/vm_service/java/src/org/dartlang/vm/service/consumer/ServiceExtensionConsumer.java b/pkg/vm_service/java/src/org/dartlang/vm/service/consumer/ServiceExtensionConsumer.java new file mode 100644 index 00000000000..71b676d45dd --- /dev/null +++ b/pkg/vm_service/java/src/org/dartlang/vm/service/consumer/ServiceExtensionConsumer.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2017, the Dart project authors. + * + * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.eclipse.org/legal/epl-v10.html + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.dartlang.vm.service.consumer; + +import com.google.gson.JsonObject; + +public interface ServiceExtensionConsumer extends Consumer { + void received(JsonObject result); +} diff --git a/pkg/vm_service/java/src/org/dartlang/vm/service/element/Element.java b/pkg/vm_service/java/src/org/dartlang/vm/service/element/Element.java new file mode 100644 index 00000000000..2319998c5f7 --- /dev/null +++ b/pkg/vm_service/java/src/org/dartlang/vm/service/element/Element.java @@ -0,0 +1,73 @@ +package org.dartlang.vm.service.element; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import java.util.ArrayList; +import java.util.List; + +/** + * Superclass for all observatory elements. + */ +public class Element { + protected final JsonObject json; + + public Element(JsonObject json) { + this.json = json; + } + + /** + * Return the underlying JSON backing this element. + */ + public JsonObject getJson() { + return json; + } + + /** + * Return a specific JSON member as a list of integers. + */ + List getListInt(String memberName) { + return jsonArrayToListInt(json.getAsJsonArray(memberName)); + } + + /** + * Return a specific JSON member as a list of strings. + */ + List getListString(String memberName) { + return jsonArrayToListString(json.getAsJsonArray(memberName)); + } + + /** + * Return a specific JSON member as a list of list of integers. + */ + List> getListListInt(String memberName) { + JsonArray array = json.getAsJsonArray(memberName); + if (array == null) { + return null; + } + int size = array.size(); + List> result = new ArrayList<>(); + for (int index = 0; index < size; ++index) { + result.add(jsonArrayToListInt(array.get(index).getAsJsonArray())); + } + return result; + } + + private List jsonArrayToListInt(JsonArray array) { + int size = array.size(); + List result = new ArrayList<>(); + for (int index = 0; index < size; ++index) { + result.add(array.get(index).getAsInt()); + } + return result; + } + + private List jsonArrayToListString(JsonArray array) { + int size = array.size(); + List result = new ArrayList<>(); + for (int index = 0; index < size; ++index) { + result.add(array.get(index).getAsString()); + } + return result; + } +} diff --git a/pkg/vm_service/java/src/org/dartlang/vm/service/element/ElementList.java b/pkg/vm_service/java/src/org/dartlang/vm/service/element/ElementList.java new file mode 100644 index 00000000000..ffce45b4244 --- /dev/null +++ b/pkg/vm_service/java/src/org/dartlang/vm/service/element/ElementList.java @@ -0,0 +1,56 @@ +package org.dartlang.vm.service.element; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import java.util.Iterator; + +/** + * Simple wrapper around a {@link JsonArray} which lazily converts {@link JsonObject} elements to + * subclasses of {@link Element}. Subclasses need only implement {@link #basicGet(JsonArray, int)} + * to return an {@link Element} subclass for the {@link JsonObject} at a given index. + */ +public abstract class ElementList implements Iterable { + + private final JsonArray array; + + public ElementList(JsonArray array) { + this.array = array; + } + + public T get(int index) { + return basicGet(array, index); + } + + public boolean isEmpty() { + return size() == 0; + } + + @Override + public Iterator iterator() { + return new Iterator() { + int index = 0; + + @Override + public boolean hasNext() { + return index < size(); + } + + @Override + public T next() { + return get(index++); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + public int size() { + return array.size(); + } + + protected abstract T basicGet(JsonArray array, int index); +} diff --git a/pkg/vm_service/java/src/org/dartlang/vm/service/element/RPCError.java b/pkg/vm_service/java/src/org/dartlang/vm/service/element/RPCError.java new file mode 100644 index 00000000000..91d21d70be1 --- /dev/null +++ b/pkg/vm_service/java/src/org/dartlang/vm/service/element/RPCError.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2015, the Dart project authors. + * + * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.eclipse.org/legal/epl-v10.html + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.dartlang.vm.service.element; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import org.dartlang.vm.service.internal.VmServiceConst; + +/** + * When an RPC encounters an error, it is provided in the _error_ property of the response object. + * JSON-RPC errors always provide _code_, _message_, and _data_ properties.
+ * Here is an example error response for our [streamListen](#streamlisten) request above. This error + * would be generated if we were attempting to subscribe to the _GC_ stream multiple times from the + * same client. + * + *

+ * {
+ *   "jsonrpc": "2.0",
+ *   "error": {
+ *     "code": 103,
+ *     "message": "Stream already subscribed",
+ *     "data": {
+ *       "details": "The stream 'GC' is already subscribed"
+ *     }
+ *   }
+ *   "id": "2"
+ * }
+ * 
+ *

+ * In addition the the [error codes](http://www.jsonrpc.org/specification#error_object) specified in + * the JSON-RPC spec, we use the following application specific error codes: + * + *

+ * code | message | meaning
+ * ---- | ------- | -------
+ * 100 | Feature is disabled | The operation is unable to complete because a feature is disabled
+ * 101 | VM must be paused | This operation is only valid when the VM is paused
+ * 102 | Cannot add breakpoint | The VM is unable to add a breakpoint at the specified line or function
+ * 103 | Stream already subscribed | The client is already subscribed to the specified _streamId_
+ * 104 | Stream not subscribed | The client is not subscribed to the specified _streamId_
+ * 
+ */ +public class RPCError extends Element implements VmServiceConst { + + /** + * The response code used by the client when it receives a response from the server that it did + * not expect. For example, it requested a library element but received a list. + */ + public static final int UNEXPECTED_RESPONSE = 5; + + public static RPCError unexpected(String expectedType, Response response) { + String errMsg = "Expected type " + expectedType + " but received " + response.getType(); + if (response instanceof Sentinel) { + errMsg += ": " + ((Sentinel) response).getKind(); + } + JsonObject json = new JsonObject(); + json.addProperty("code", UNEXPECTED_RESPONSE); + json.addProperty("message", errMsg); + JsonObject data = new JsonObject(); + data.addProperty("details", errMsg); + data.add("response", response.getJson()); + json.add("data", data); + return new RPCError(json); + } + + public RPCError(JsonObject json) { + super(json); + } + + public int getCode() { + return json.get("code").getAsInt(); + } + + public String getDetails() { + JsonElement data = json.get("data"); + if (data instanceof JsonObject) { + JsonElement details = ((JsonObject) data).get("details"); + if (details != null) { + return details.getAsString(); + } + } + return null; + } + + public String getMessage() { + return json.get("message").getAsString(); + } + + public JsonObject getRequest() { + JsonElement data = json.get("data"); + if (data instanceof JsonObject) { + JsonElement request = ((JsonObject) data).get("request"); + if (request instanceof JsonObject) { + return (JsonObject) request; + } + } + return null; + } +} diff --git a/pkg/vm_service/java/src/org/dartlang/vm/service/internal/BlockingRequestSink.java b/pkg/vm_service/java/src/org/dartlang/vm/service/internal/BlockingRequestSink.java new file mode 100644 index 00000000000..cf8cd3f7950 --- /dev/null +++ b/pkg/vm_service/java/src/org/dartlang/vm/service/internal/BlockingRequestSink.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2015, the Dart project authors. + * + * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.eclipse.org/legal/epl-v10.html + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.dartlang.vm.service.internal; + +import com.google.common.collect.Lists; +import com.google.gson.JsonObject; + +import java.util.LinkedList; + +/** + * A {@link RequestSink} that enqueues all requests and can be later converted into a "passthrough" + * or an "error" {@link RequestSink}. + */ +public class BlockingRequestSink implements RequestSink { + /** + * The base {@link RequestSink} + */ + private final RequestSink base; + + /** + * A queue of requests. + */ + private final LinkedList queue = Lists.newLinkedList(); + + public BlockingRequestSink(RequestSink base) { + this.base = base; + } + + @Override + public void add(JsonObject request) { + synchronized (queue) { + queue.add(request); + } + } + + @Override + public void close() { + base.close(); + } + + /** + * Responds with an error to all the currently queued requests and return a {@link RequestSink} to + * do the same for all the future requests. + * + * @param errorResponseSink the sink to send error responses to, not {@code null} + */ + public RequestSink toErrorSink(ResponseSink errorResponseSink, String errorResponseCode, + String errorResponseMessage) { + ErrorRequestSink errorRequestSink = new ErrorRequestSink(errorResponseSink, errorResponseCode, + errorResponseMessage); + synchronized (queue) { + for (JsonObject request : queue) { + errorRequestSink.add(request); + } + } + return errorRequestSink; + } + + /** + * Returns the passthrough {@link RequestSink}. + */ + public RequestSink toPassthroughSink() { + synchronized (queue) { + for (JsonObject request : queue) { + base.add(request); + } + } + return base; + } +} diff --git a/pkg/vm_service/java/src/org/dartlang/vm/service/internal/ErrorRequestSink.java b/pkg/vm_service/java/src/org/dartlang/vm/service/internal/ErrorRequestSink.java new file mode 100644 index 00000000000..9a8fce1c295 --- /dev/null +++ b/pkg/vm_service/java/src/org/dartlang/vm/service/internal/ErrorRequestSink.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2015, the Dart project authors. + * + * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.eclipse.org/legal/epl-v10.html + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.dartlang.vm.service.internal; + +import com.google.gson.JsonObject; +import org.dartlang.vm.service.logging.Logging; + +/** + * A {@link RequestSink} that reports with an error to each request. + */ +public class ErrorRequestSink implements RequestSink, VmServiceConst { + /** + * The {@link ResponseSink} to send error responses to. + */ + private final ResponseSink responseSink; + + private final String code; + private final String message; + + public ErrorRequestSink(ResponseSink responseSink, String code, String message) { + if (responseSink == null || code == null || message == null) { + throw new IllegalArgumentException("Unexpected null argument: " + responseSink + " " + + code + " " + message); + } + this.responseSink = responseSink; + this.code = code; + this.message = message; + } + + @Override + public void add(JsonObject request) { + String id = request.getAsJsonPrimitive(ID).getAsString(); + try { + // TODO(danrubel) is this the correct format for an error response? + JsonObject error = new JsonObject(); + error.addProperty(CODE, code); + error.addProperty(MESSAGE, message); + JsonObject response = new JsonObject(); + response.addProperty(ID, id); + response.add(ERROR, error); + responseSink.add(response); + } catch (Throwable e) { + Logging.getLogger().logError(e.getMessage(), e); + } + } + + @Override + public void close() { + } +} diff --git a/pkg/vm_service/java/src/org/dartlang/vm/service/internal/RequestSink.java b/pkg/vm_service/java/src/org/dartlang/vm/service/internal/RequestSink.java new file mode 100644 index 00000000000..ef2a5630c0a --- /dev/null +++ b/pkg/vm_service/java/src/org/dartlang/vm/service/internal/RequestSink.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2015, the Dart project authors. + * + * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.eclipse.org/legal/epl-v10.html + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.dartlang.vm.service.internal; + +import com.google.gson.JsonObject; + +/** + * A destination for observatory requests. + */ +public interface RequestSink { + /** + * Put request into the sink. + * + * @param request the request to put, not {@code null}. + */ + void add(JsonObject request); + + /** + * Close the communication channel. + */ + void close(); +} diff --git a/pkg/vm_service/java/src/org/dartlang/vm/service/internal/ResponseSink.java b/pkg/vm_service/java/src/org/dartlang/vm/service/internal/ResponseSink.java new file mode 100644 index 00000000000..5ac1db12000 --- /dev/null +++ b/pkg/vm_service/java/src/org/dartlang/vm/service/internal/ResponseSink.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2015, the Dart project authors. + * + * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.eclipse.org/legal/epl-v10.html + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.dartlang.vm.service.internal; + +import com.google.gson.JsonObject; + +/** + * A destination for responses. + */ +public interface ResponseSink { + /** + * Put response into the sink. + * + * @param response the response to put, not {@code null}. + */ + void add(JsonObject response) throws Exception; +} diff --git a/pkg/vm_service/java/src/org/dartlang/vm/service/internal/VmServiceConst.java b/pkg/vm_service/java/src/org/dartlang/vm/service/internal/VmServiceConst.java new file mode 100644 index 00000000000..3659c3c80f1 --- /dev/null +++ b/pkg/vm_service/java/src/org/dartlang/vm/service/internal/VmServiceConst.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2015, the Dart project authors. + * + * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.eclipse.org/legal/epl-v10.html + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.dartlang.vm.service.internal; + +/** + * JSON constants used when communicating with the VM observatory service. + */ +public interface VmServiceConst { + static final String CODE = "code"; + static final String ERROR = "error"; + static final String EVENT = "event"; + static final String ID = "id"; + static final String MESSAGE = "message"; + static final String METHOD = "method"; + static final String PARAMS = "params"; + static final String RESULT = "result"; + static final String STREAM_ID = "streamId"; + static final String TYPE = "type"; + static final String JSONRPC = "jsonrpc"; + static final String JSONRPC_VERSION = "2.0"; + static final String DATA = "data"; + + /** + * Parse error Invalid JSON was received by the server. + * An error occurred on the server while parsing the JSON text. + */ + static final int PARSE_ERROR = -32700; + + /** + * Invalid Request The JSON sent is not a valid Request object. + */ + static final int INVALID_REQUEST = -32600; + + /** + * Method not found The method does not exist / is not available. + */ + static final int METHOD_NOT_FOUND = -32601; + + /** + * Invalid params Invalid method parameter(s). + */ + static final int INVALID_PARAMS = -32602; + + /** + * Server error Reserved for implementation-defined server-errors. + * -32000 to -32099 + */ + static final int SERVER_ERROR = -32000; +} diff --git a/pkg/vm_service/java/src/org/dartlang/vm/service/internal/WebSocketRequestSink.java b/pkg/vm_service/java/src/org/dartlang/vm/service/internal/WebSocketRequestSink.java new file mode 100644 index 00000000000..8a9ebfd5ac2 --- /dev/null +++ b/pkg/vm_service/java/src/org/dartlang/vm/service/internal/WebSocketRequestSink.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2015, the Dart project authors. + * + * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.eclipse.org/legal/epl-v10.html + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.dartlang.vm.service.internal; + +import com.google.gson.JsonObject; +import de.roderick.weberknecht.WebSocket; +import de.roderick.weberknecht.WebSocketException; +import org.dartlang.vm.service.logging.Logging; + +/** + * An {@link WebSocket} based implementation of {@link RequestSink}. + */ +public class WebSocketRequestSink implements RequestSink { + + private WebSocket webSocket; + + public WebSocketRequestSink(WebSocket webSocket) { + this.webSocket = webSocket; + } + + @Override + public void add(JsonObject json) { + String request = json.toString(); + if (webSocket == null) { + Logging.getLogger().logInformation("Dropped: " + request); + return; + } + Logging.getLogger().logInformation("Sent: " + request); + try { + webSocket.send(request); + } catch (WebSocketException e) { + Logging.getLogger().logError("Failed to send request: " + request, e); + } + } + + @Override + public void close() { + if (webSocket != null) { + try { + webSocket.close(); + } catch (WebSocketException e) { + Logging.getLogger().logError("Failed to close websocket", e); + } + webSocket = null; + } + } +} diff --git a/pkg/vm_service/java/src/org/dartlang/vm/service/logging/Logger.java b/pkg/vm_service/java/src/org/dartlang/vm/service/logging/Logger.java new file mode 100644 index 00000000000..cf1bd031484 --- /dev/null +++ b/pkg/vm_service/java/src/org/dartlang/vm/service/logging/Logger.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2012, the Dart project authors. + * + * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.eclipse.org/legal/epl-v10.html + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.dartlang.vm.service.logging; + +/** + * The interface {@code Logger} defines the behavior of objects that can be used to receive + * information about errors. Implementations usually write this information to a file, but can also + * record the information for later use (such as during testing) or even ignore the information. + */ +public interface Logger { + + /** + * Implementation of {@link Logger} that does nothing. + */ + class NullLogger implements Logger { + @Override + public void logError(String message) { + } + + @Override + public void logError(String message, Throwable exception) { + } + + @Override + public void logInformation(String message) { + } + + @Override + public void logInformation(String message, Throwable exception) { + } + } + + static final Logger NULL = new NullLogger(); + + /** + * Log the given message as an error. + * + * @param message an explanation of why the error occurred or what it means + */ + void logError(String message); + + /** + * Log the given exception as one representing an error. + * + * @param message an explanation of why the error occurred or what it means + * @param exception the exception being logged + */ + void logError(String message, Throwable exception); + + /** + * Log the given informational message. + * + * @param message an explanation of why the error occurred or what it means + */ + void logInformation(String message); + + /** + * Log the given exception as one representing an informational message. + * + * @param message an explanation of why the error occurred or what it means + * @param exception the exception being logged + */ + void logInformation(String message, Throwable exception); +} diff --git a/pkg/vm_service/java/src/org/dartlang/vm/service/logging/Logging.java b/pkg/vm_service/java/src/org/dartlang/vm/service/logging/Logging.java new file mode 100644 index 00000000000..867cd1e795d --- /dev/null +++ b/pkg/vm_service/java/src/org/dartlang/vm/service/logging/Logging.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2014, the Dart project authors. + * + * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.eclipse.org/legal/epl-v10.html + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.dartlang.vm.service.logging; + +/** + * {@code Logging} provides a global instance of {@link Logger}. + */ +public class Logging { + + private static Logger logger = Logger.NULL; + + public static Logger getLogger() { + return logger; + } + + public static void setLogger(Logger logger) { + Logging.logger = logger == null ? Logger.NULL : logger; + } +} diff --git a/pkg/vm_service/java/test/org/dartlang/vm/service/InstanceRefToString.java b/pkg/vm_service/java/test/org/dartlang/vm/service/InstanceRefToString.java new file mode 100644 index 00000000000..d5ce8f50b10 --- /dev/null +++ b/pkg/vm_service/java/test/org/dartlang/vm/service/InstanceRefToString.java @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2015, the Dart project authors. + * + * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.eclipse.org/legal/epl-v10.html + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.dartlang.vm.service; + +import org.dartlang.vm.service.consumer.GetInstanceConsumer; +import org.dartlang.vm.service.element.BoundField; +import org.dartlang.vm.service.element.ClassRef; +import org.dartlang.vm.service.element.Instance; +import org.dartlang.vm.service.element.InstanceKind; +import org.dartlang.vm.service.element.InstanceRef; +import org.dartlang.vm.service.element.Isolate; +import org.dartlang.vm.service.element.RPCError; + +/** + * Utility class for converting {@link InstanceRef} to a human readable string. + */ +public class InstanceRefToString { + private Isolate isolate; + private final VmService service; + private final OpLatch masterLatch; + + /** + * Construct a new instance for converting one or more {@link InstanceRef} to human readable + * strings. Specify an {@link OpLatch} so that this class can update the expiration time for any + * waiting thread as it makes {@link VmService} class to obtain details about each + * {@link InstanceRef}. + */ + public InstanceRefToString(Isolate isolate, VmService service, OpLatch latch) { + this.isolate = isolate; + this.service = service; + this.masterLatch = latch; + } + + /** + * Return a human readable string for the given {@link InstanceRef}. + */ + public String toString(InstanceRef ref) { + StringBuilder result = new StringBuilder(); + printInstance(result, ref, 4); + return result.toString(); + } + + /** + * Request the instance information from the {@link VmService}. + * + * @param ref the instance reference (not {@code null}) + * @return the instance or {@code null} if there was a problem. + */ + private Instance getInstance(InstanceRef ref) { + + // Request master latch extend its timeout because we are making another call to VmService + masterLatch.opWorking(); + + final ResultLatch instLatch = new ResultLatch(); + service.getInstance(isolate.getId(), ref.getId(), new GetInstanceConsumer() { + @Override + public void onError(RPCError error) { + instLatch.setValue(null); + } + + @Override + public void received(Instance instance) { + instLatch.setValue(instance); + } + }); + return instLatch.getValue(); + } + + /** + * Convert the given {@link InstanceRef} into a human readable string. + * + * @param result the buffer to which the human readable string is added + * @param ref the instance to be converted (not {@code null}) + * @param maxDepth the maximum number of recursions this method can make on itself to determine + * human readable strings for child objects + */ + private void printInstance(StringBuilder result, InstanceRef ref, int maxDepth) { + if (ref == null) { + result.append("-- no value --"); + return; + } + InstanceKind kind = ref.getKind(); + if (kind == null) { + result.append("-- unknown instance kind --"); + return; + } + switch (kind) { + case Bool: + case Double: + case Float32x4: + case Float64x2: + case Int: + case Int32x4: + case Null: + case StackTrace: + result.append(ref.getValueAsString()); + return; + case String: + result.append("'"); + // Should escape chars such as newline before printing + result.append(ref.getValueAsString()); + if (ref.getValueAsStringIsTruncated()) { + result.append("..."); + } + result.append("'"); + return; + case List: + printList(result, ref, maxDepth); + return; + case PlainInstance: + printPlainInstance(result, ref, maxDepth); + return; + case BoundedType: + case Closure: + case Float32List: + case Float32x4List: + case Float64List: + case Float64x2List: + case Int16List: + case Int32List: + case Int32x4List: + case Int64List: + case Int8List: + case Map: + case MirrorReference: + case RegExp: + case Type: + case TypeParameter: + case TypeRef: + case Uint16List: + case Uint32List: + case Uint64List: + case Uint8ClampedList: + case Uint8List: + case WeakProperty: + } + result.append("a " + kind); + } + + /** + * Convert the given list into a human readable string. + * + * @param result the buffer to which the human readable string is added + * @param ref an instance reference of type "List" (not {@code null}) + * @param maxDepth the maximum number of recursions this method can make on itself to determine + * human readable strings for child objects + */ + private void printList(StringBuilder result, InstanceRef ref, int maxDepth) { + if (maxDepth == 0) { + result.append("a List"); + return; + } + result.append("["); + Instance list = getInstance(ref); + if (list == null) { + result.append("?error?]"); + return; + } + int count = 0; + for (InstanceRef elem : list.getElements()) { + if (count > 10) { + result.append(", ..."); + break; + } + if (count > 0) { + result.append(", "); + } + ++count; + printInstance(result, elem, maxDepth - 1); + } + result.append("]"); + } + + /** + * Convert the given instance into a human readable string. + * + * @param result the buffer to which the human readable string is added + * @param ref an instance reference of type "PlainInstance" (not {@code null}) + * @param maxDepth the maximum number of recursions this method can make on itself to determine + * human readable strings for child objects + */ + private void printPlainInstance(StringBuilder result, InstanceRef ref, int maxDepth) { + ClassRef classRef = ref.getClassRef(); + String className = classRef.getName(); + if (maxDepth == 0) { + result.append("a " + className); + return; + } + result.append(className); + result.append("("); + Instance inst = getInstance(ref); + boolean first = true; + for (BoundField field : inst.getFields()) { + if (first) { + first = false; + } else { + result.append(", "); + } + printInstance(result, field.getValue(), maxDepth - 1); + } + result.append(")"); + } +} diff --git a/pkg/vm_service/java/test/org/dartlang/vm/service/OpLatch.java b/pkg/vm_service/java/test/org/dartlang/vm/service/OpLatch.java new file mode 100644 index 00000000000..1c081626eb4 --- /dev/null +++ b/pkg/vm_service/java/test/org/dartlang/vm/service/OpLatch.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2015, the Dart project authors. + * + * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.eclipse.org/legal/epl-v10.html + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.dartlang.vm.service; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * {@link OpLatch} is used by one thread to wait for another thread to complete (see + * {@link OpLatch#waitOpComplete()} and {@link OpLatch#waitAndAssertOpComplete()}). If the operation + * does not complete before the expiration time then {@link OpLatch#waitOpComplete()} returns + * {@code false} and {@link OpLatch#waitAndAssertOpComplete()} throws an exception. + */ +class OpLatch { + final CountDownLatch latch = new CountDownLatch(1); + private long endTime; + + /** + * Set or increase the time after which the operation is considered failed. This is automatically + * called by {@link #waitAndAssertOp()} and {@link #waitOpComplete()}. + */ + public void opWorking() { + endTime = System.currentTimeMillis() + 5000; + } + + /** + * Call to indicate that the operation completed successfully. + */ + void opComplete() { + latch.countDown(); + } + + /** + * Wait for the operation to complete or the time limit to expire. Periodically call + * {@link #opWorking()} to increase the expiration time. Throw a {@link RuntimeException} if the + * operation did not complete before the expiration time. + */ + void waitAndAssertOpComplete() { + if (!waitOpComplete()) { + System.out.println(">>> No response received"); + throw new RuntimeException("No response received"); + } + } + + /** + * Wait for the operation to complete or the time limit to expire. Periodically call + * {@link #opWorking()} to increase the expiration time. + * + * @return {@code true} if the operation completed, or {@code false} otherwise + */ + boolean waitOpComplete() { + opWorking(); + while (true) { + long waitTimeMillis = endTime - System.currentTimeMillis(); + if (waitTimeMillis <= 0) { + return latch.getCount() == 0; + } + try { + if (latch.await(waitTimeMillis, TimeUnit.MILLISECONDS)) { + return true; + } + } catch (InterruptedException e) { + // ignore and loop to check if timeout has changed + } + } + } +} diff --git a/pkg/vm_service/java/test/org/dartlang/vm/service/ResultLatch.java b/pkg/vm_service/java/test/org/dartlang/vm/service/ResultLatch.java new file mode 100644 index 00000000000..d16e56fff0d --- /dev/null +++ b/pkg/vm_service/java/test/org/dartlang/vm/service/ResultLatch.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2015, the Dart project authors. + * + * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.eclipse.org/legal/epl-v10.html + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.dartlang.vm.service; + +/** + * {@link ResultLatch} is used by one thread to communicate a result to another thread. + */ +public class ResultLatch extends OpLatch { + private T value; + + public T getValue() { + waitAndAssertOpComplete(); + return value; + } + + public void setValue(T value) { + this.value = value; + opComplete(); + } +} diff --git a/pkg/vm_service/java/test/org/dartlang/vm/service/SampleOutPrinter.java b/pkg/vm_service/java/test/org/dartlang/vm/service/SampleOutPrinter.java new file mode 100644 index 00000000000..e5007b09b6d --- /dev/null +++ b/pkg/vm_service/java/test/org/dartlang/vm/service/SampleOutPrinter.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2015, the Dart project authors. + * + * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.eclipse.org/legal/epl-v10.html + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.dartlang.vm.service; + +import com.google.common.base.Charsets; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +/** + * Echo the content of a stream to {@link System.out} with the given prefix. + */ +public class SampleOutPrinter { + private class LinesReaderThread extends Thread { + public LinesReaderThread() { + setName("SampleOutPrinter.LinesReaderThread - " + prefix); + setDaemon(true); + } + + @Override + public void run() { + while (true) { + String line; + try { + line = reader.readLine(); + } catch (IOException e) { + System.out.println("Exception reading sample stream"); + e.printStackTrace(); + return; + } + // check for EOF + if (line == null) { + return; + } + synchronized (currentLineLock) { + currentLine = line; + currentLineLock.notifyAll(); + } + System.out.println("[" + prefix + "] " + line); + } + } + } + + private String currentLine; + + private final Object currentLineLock = new Object(); + + private final String prefix; + private final BufferedReader reader; + + public SampleOutPrinter(String prefix, InputStream stream) { + this.prefix = prefix; + this.reader = new BufferedReader(new InputStreamReader(stream, Charsets.UTF_8)); + new LinesReaderThread().start(); + } + + public void assertEmpty() { + synchronized (currentLineLock) { + if (currentLine != null) { + throw new RuntimeException("Did not expect " + prefix + ": \"" + currentLine + "\""); + } + } + } + + public void assertLastLine(String text) { + synchronized (currentLineLock) { + if (text == null) { + if (currentLine != null) { + throw new RuntimeException("Did not expect " + prefix + ": \"" + currentLine + "\""); + } + } else { + if (currentLine == null || !currentLine.contains(text)) { + throw new RuntimeException("Expected current line to contain text\n" + + "\nexpected: [" + text + "]" + + "\nactual: [" + currentLine + "]"); + } + } + } + } +} diff --git a/pkg/vm_service/java/test/org/dartlang/vm/service/SampleVmServiceListener.java b/pkg/vm_service/java/test/org/dartlang/vm/service/SampleVmServiceListener.java new file mode 100644 index 00000000000..662ce8d9b35 --- /dev/null +++ b/pkg/vm_service/java/test/org/dartlang/vm/service/SampleVmServiceListener.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2015, the Dart project authors. + * + * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.eclipse.org/legal/epl-v10.html + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.dartlang.vm.service; + +import org.dartlang.vm.service.element.Event; +import org.dartlang.vm.service.element.EventKind; + +import java.util.Set; + +/** + * Sample VmListener for responding to state changes in the running application + */ +public class SampleVmServiceListener implements VmServiceListener { + private final Object lock = new Object(); + private String lastStreamId; + private Event lastEvent; + private final Set ignoreAll; + + SampleVmServiceListener(Set ignoreAll) { + this.ignoreAll = ignoreAll; + } + + @Override + public void connectionOpened() { + + } + + @Override + public void received(String streamId, Event event) { + synchronized (lock) { + if (ignoreAll.contains(event.getKind())) { + return; + } + if (lastStreamId != null) { + unexpectedEvent(lastStreamId, lastEvent); + } + lastStreamId = streamId; + lastEvent = event; + lock.notifyAll(); + } + } + + @Override + public void connectionClosed() { + + } + + public Event waitFor(String expectedStreamId, EventKind expectedEventKind) { + long end = System.currentTimeMillis() + 5000; + synchronized (lock) { + while (true) { + if (expectedStreamId.equals(lastStreamId) && expectedEventKind.equals(lastEvent.getKind())) { + Event event = lastEvent; + lastStreamId = null; + lastEvent = null; + return event; + } + long timeout = end - System.currentTimeMillis(); + if (timeout <= 0) { + break; + } + try { + lock.wait(timeout); + } catch (InterruptedException e) { + // ignored + } + } + } + throw new RuntimeException("Expected event: " + expectedStreamId + ", " + expectedEventKind); + } + + private void unexpectedEvent(String streamId, Event event) { + System.out.println("****** Unexpected Event: " + streamId + ", " + event.getKind()); + } +} diff --git a/pkg/vm_service/java/test/org/dartlang/vm/service/VmServiceTest.java b/pkg/vm_service/java/test/org/dartlang/vm/service/VmServiceTest.java new file mode 100644 index 00000000000..aa191edb764 --- /dev/null +++ b/pkg/vm_service/java/test/org/dartlang/vm/service/VmServiceTest.java @@ -0,0 +1,661 @@ +/* + * Copyright (c) 2015, the Dart project authors. + * + * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.eclipse.org/legal/epl-v10.html + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.dartlang.vm.service; + +import com.google.gson.JsonObject; +import org.dartlang.vm.service.consumer.*; +import org.dartlang.vm.service.element.*; +import org.dartlang.vm.service.logging.Logger; +import org.dartlang.vm.service.logging.Logging; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; + +public class VmServiceTest { + private static File dartVm; + private static File sampleDart; + private static File sampleDartWithException; + private static int vmPort = 7575; + private static Process process; + private static VmService vmService; + private static SampleOutPrinter sampleOut; + private static SampleOutPrinter sampleErr; + private static int actualVmServiceVersionMajor; + + public static void main(String[] args) { + setupLogging(); + parseArgs(args); + + try { + echoDartVmVersion(); + runSample(); + runSampleWithException(); + System.out.println("Test Complete"); + } finally { + vmDisconnect(); + stopSample(); + } + } + + private static void echoDartVmVersion() { + // Echo Dart VM version + List processArgs = new ArrayList<>(); + processArgs.add(dartVm.getAbsolutePath()); + processArgs.add("--version"); + ProcessBuilder processBuilder = new ProcessBuilder(processArgs); + try { + process = processBuilder.start(); + } catch (IOException e) { + throw new RuntimeException("Failed to launch Dart VM", e); + } + new SampleOutPrinter("version output", process.getInputStream()); + new SampleOutPrinter("version output", process.getErrorStream()); + } + + private static void finishExecution(SampleVmServiceListener vmListener, ElementList isolates) { + // Finish execution + vmResume(isolates.get(0), null); + vmListener.waitFor(VmService.DEBUG_STREAM_ID, EventKind.Resume); + + // VM pauses on exit and must be resumed to cleanly terminate process + vmListener.waitFor(VmService.DEBUG_STREAM_ID, EventKind.PauseExit); + vmResume(isolates.get(0), null); + vmListener.waitFor(VmService.ISOLATE_STREAM_ID, EventKind.IsolateExit); + waitForProcessExit(); + + sampleOut.assertLastLine("exiting"); + // TODO(devoncarew): + // vm-service: isolate(544050040) 'sample_main.dart:main()' has no debugger attached and is paused at start. + //sampleErr.assertLastLine(null); + process = null; + } + + private static boolean isWindows() { + return System.getProperty("os.name").startsWith("Win"); + } + + private static void parseArgs(String[] args) { + if (args.length != 1) { + showErrorAndExit("Expected absolute path to Dart SDK"); + } + File sdkDir = new File(args[0]); + if (!sdkDir.isDirectory()) { + showErrorAndExit("Specified directory does not exist: " + sdkDir); + } + File binDir = new File(sdkDir, "bin"); + dartVm = new File(binDir, isWindows() ? "dart.exe" : "dart"); + if (!dartVm.isFile()) { + showErrorAndExit("Cannot find Dart VM in SDK: " + dartVm); + } + File currentDir = new File(".").getAbsoluteFile(); + File projDir = currentDir; + String projName = "vm_service"; + while (!projDir.getName().equals(projName)) { + projDir = projDir.getParentFile(); + if (projDir == null) { + showErrorAndExit("Cannot find project " + projName + " from " + currentDir); + return; + } + } + sampleDart = new File(projDir, "java/example/sample_main.dart".replace("/", File.separator)); + if (!sampleDart.isFile()) { + showErrorAndExit("Cannot find sample: " + sampleDart); + } + sampleDartWithException = new File(projDir, + "java/example/sample_exception.dart".replace("/", File.separator)); + if (!sampleDartWithException.isFile()) { + showErrorAndExit("Cannot find sample: " + sampleDartWithException); + } + System.out.println("Using Dart SDK: " + sdkDir); + } + + /** + * Exercise VM service with "normal" sample. + */ + private static void runSample() { + SampleVmServiceListener vmListener = startSampleAndConnect(sampleDart); + vmGetVersion(); + ElementList isolates = vmGetVmIsolates(); + Isolate sampleIsolate = vmGetIsolate(isolates.get(0)); + Library rootLib = vmGetLibrary(sampleIsolate, sampleIsolate.getRootLib()); + vmGetScript(sampleIsolate, rootLib.getScripts().get(0)); + vmCallServiceExtension(sampleIsolate); + + // Run to breakpoint on line "foo(1);" + vmAddBreakpoint(sampleIsolate, rootLib.getScripts().get(0), 25); + vmListener.waitFor(VmService.DEBUG_STREAM_ID, EventKind.BreakpointAdded); + vmResume(isolates.get(0), null); + vmListener.waitFor(VmService.DEBUG_STREAM_ID, EventKind.Resume); + vmListener.waitFor(VmService.DEBUG_STREAM_ID, EventKind.PauseBreakpoint); + sampleOut.assertLastLine("hello"); + + // Get stack trace + vmGetStack(sampleIsolate); + + // Evaluate + vmEvaluateInFrame(sampleIsolate, 0, "deepList[0]"); + + // Get coverage information + vmGetSourceReport(sampleIsolate); + + // Step over line "foo(1);" + vmResume(isolates.get(0), StepOption.Over); + vmListener.waitFor(VmService.DEBUG_STREAM_ID, EventKind.Resume); + vmListener.waitFor(VmService.DEBUG_STREAM_ID, EventKind.PauseBreakpoint); + sampleOut.assertLastLine("val: 1"); + + finishExecution(vmListener, isolates); + } + + /** + * Exercise VM service with sample that throws exceptions. + */ + private static void runSampleWithException() { + SampleVmServiceListener vmListener = startSampleAndConnect(sampleDartWithException); + ElementList isolates = vmGetVmIsolates(); + Isolate sampleIsolate = vmGetIsolate(isolates.get(0)); + + // Run until exception occurs + vmPauseOnException(isolates.get(0), ExceptionPauseMode.All); + vmResume(isolates.get(0), null); + vmListener.waitFor(VmService.DEBUG_STREAM_ID, EventKind.Resume); + Event event = vmListener.waitFor(VmService.DEBUG_STREAM_ID, EventKind.PauseException); + InstanceRefToString convert = new InstanceRefToString(sampleIsolate, vmService, new OpLatch()); + System.out.println("Received PauseException event"); + System.out.println(" Exception: " + convert.toString(event.getException())); + System.out.println(" Top Frame:"); + showFrame(convert, event.getTopFrame()); + sampleOut.assertLastLine("hello"); + + finishExecution(vmListener, isolates); + } + + private static void setupLogging() { + Logging.setLogger(new Logger() { + @Override + public void logError(String message) { + System.out.println("Log error: " + message); + } + + @Override + public void logError(String message, Throwable exception) { + System.out.println("Log error: " + message); + if (exception != null) { + System.out.println("Log error exception: " + exception); + exception.printStackTrace(); + } + } + + @Override + public void logInformation(String message) { + System.out.println("Log info: " + message); + } + + @Override + public void logInformation(String message, Throwable exception) { + System.out.println("Log info: " + message); + if (exception != null) { + System.out.println("Log info exception: " + exception); + exception.printStackTrace(); + } + } + }); + } + + private static void showErrorAndExit(String errMsg) { + System.out.println(errMsg); + System.out.flush(); + sleep(10); + System.out.println("Usage: VmServiceTest /path/to/Dart/SDK"); + System.exit(1); + } + + private static void showFrame(InstanceRefToString convert, Frame frame) { + System.out.println(" #" + frame.getIndex() + " " + frame.getFunction().getName() + " (" + + frame.getLocation().getScript().getUri() + ")"); + for (BoundVariable var : frame.getVars()) { + InstanceRef instanceRef = (InstanceRef)var.getValue(); + System.out.println(" " + var.getName() + " = " + convert.toString(instanceRef)); + } + } + + private static void showRPCError(RPCError error) { + System.out.println(">>> Received error response"); + System.out.println(" Code: " + error.getCode()); + System.out.println(" Message: " + error.getMessage()); + System.out.println(" Details: " + error.getDetails()); + System.out.println(" Request: " + error.getRequest()); + } + + private static void showSentinel(Sentinel sentinel) { + System.out.println(">>> Received sentinel response"); + System.out.println(" Sentinel kind: " + sentinel.getKind()); + System.out.println(" Sentinel value: " + sentinel.getValueAsString()); + } + + private static void sleep(int milliseconds) { + try { + Thread.sleep(milliseconds); + } catch (InterruptedException e) { + // ignored + } + } + + private static void startSample(File dartFile) { + List processArgs; + ProcessBuilder processBuilder; + + // Use new port to prevent race conditions + // between one sample releasing a port + // and the next sample using it. + ++vmPort; + + processArgs = new ArrayList<>(); + processArgs.add(dartVm.getAbsolutePath()); + processArgs.add("--pause_isolates_on_start"); + processArgs.add("--observe"); + processArgs.add("--enable-vm-service=" + vmPort); + processArgs.add("--disable-service-auth-codes"); + processArgs.add(dartFile.getAbsolutePath()); + processBuilder = new ProcessBuilder(processArgs); + System.out.println("================================================="); + System.out.println("Launching sample: " + dartFile); + try { + process = processBuilder.start(); + } catch (IOException e) { + throw new RuntimeException("Failed to launch Dart sample", e); + } + // Echo sample application output to System.out + sampleOut = new SampleOutPrinter("stdout", process.getInputStream()); + sampleErr = new SampleOutPrinter("stderr", process.getErrorStream()); + System.out.println("Dart process started - port " + vmPort); + } + + private static SampleVmServiceListener startSampleAndConnect(File dartFile) { + startSample(dartFile); + sleep(1000); + vmConnect(); + SampleVmServiceListener vmListener = new SampleVmServiceListener( + new HashSet<>(Collections.singletonList(EventKind.BreakpointResolved))); + vmService.addVmServiceListener(vmListener); + vmStreamListen(VmService.DEBUG_STREAM_ID); + vmStreamListen(VmService.ISOLATE_STREAM_ID); + return vmListener; + } + + private static void stopSample() { + if (process == null) { + return; + } + final Process processToStop = process; + process = null; + long endTime = System.currentTimeMillis() + 5000; + while (System.currentTimeMillis() < endTime) { + try { + int exit = processToStop.exitValue(); + if (exit != 0) { + System.out.println("Sample exit code: " + exit); + } + return; + } catch (IllegalThreadStateException e) { + //$FALL-THROUGH$ + } + try { + Thread.sleep(20); + } catch (InterruptedException e) { + //$FALL-THROUGH$ + } + } + processToStop.destroy(); + System.out.println("Terminated sample process"); + } + + @SuppressWarnings("SameParameterValue") + private static void vmAddBreakpoint(Isolate isolate, ScriptRef script, int lineNum) { + final OpLatch latch = new OpLatch(); + vmService.addBreakpoint(isolate.getId(), script.getId(), lineNum, new BreakpointConsumer() { + @Override + public void onError(RPCError error) { + showRPCError(error); + } + + @Override + public void received(Breakpoint response) { + System.out.println("Received Breakpoint response"); + System.out.println(" BreakpointNumber:" + response.getBreakpointNumber()); + latch.opComplete(); + } + }); + latch.waitAndAssertOpComplete(); + } + + private static void vmConnect() { + try { + vmService = VmService.localConnect(vmPort); + } catch (IOException e) { + throw new RuntimeException("Failed to connect to the VM vmService service", e); + } + } + + private static void vmDisconnect() { + if (vmService != null) { + vmService.disconnect(); + } + } + + @SuppressWarnings("SameParameterValue") + private static void vmEvaluateInFrame(Isolate isolate, int frameIndex, String expression) { + System.out.println("Evaluating: " + expression); + final ResultLatch latch = new ResultLatch<>(); + vmService.evaluateInFrame(isolate.getId(), frameIndex, expression, new EvaluateInFrameConsumer() { + @Override + public void onError(RPCError error) { + showRPCError(error); + } + + @Override + public void received(ErrorRef response) { + showErrorAndExit(response.getMessage()); + } + + public void received(Sentinel response) { + System.out.println(response.getValueAsString()); + } + + @Override + public void received(InstanceRef response) { + System.out.println("Received InstanceRef response"); + System.out.println(" Id: " + response.getId()); + System.out.println(" Kind: " + response.getKind()); + System.out.println(" Json: " + response.getJson()); + latch.setValue(response); + } + }); + InstanceRef instanceRef = latch.getValue(); + InstanceRefToString convert = new InstanceRefToString(isolate, vmService, latch); + System.out.println("Result: " + convert.toString(instanceRef)); + } + + private static SourceReport vmGetSourceReport(Isolate isolate) { + System.out.println("Getting coverage information for " + isolate.getId()); + final long startTime = System.currentTimeMillis(); + final ResultLatch latch = new ResultLatch<>(); + vmService.getSourceReport(isolate.getId(), Collections.singletonList(SourceReportKind.Coverage), new SourceReportConsumer() { + @Override + public void onError(RPCError error) { + showRPCError(error); + } + + @Override + public void received(SourceReport response) { + System.out.println("Received SourceReport response (" + (System.currentTimeMillis() - startTime) + "ms)"); + System.out.println(" Script count: " + response.getScripts().size()); + System.out.println(" Range count: " + response.getRanges().size()); + latch.setValue(response); + } + }); + return latch.getValue(); + } + + private static Isolate vmGetIsolate(IsolateRef isolate) { + final ResultLatch latch = new ResultLatch<>(); + vmService.getIsolate(isolate.getId(), new GetIsolateConsumer() { + @Override + public void onError(RPCError error) { + showRPCError(error); + } + + @Override + public void received(Isolate response) { + System.out.println("Received Isolate response"); + System.out.println(" Id: " + response.getId()); + System.out.println(" Name: " + response.getName()); + System.out.println(" Number: " + response.getNumber()); + System.out.println(" Start Time: " + response.getStartTime()); + System.out.println(" RootLib Id: " + response.getRootLib().getId()); + System.out.println(" RootLib Uri: " + response.getRootLib().getUri()); + System.out.println(" RootLib Name: " + response.getRootLib().getName()); + System.out.println(" RootLib Json: " + response.getRootLib().getJson()); + System.out.println(" Isolate: " + response); + latch.setValue(response); + } + + @Override + public void received(Sentinel response) { + showSentinel(response); + } + }); + return latch.getValue(); + } + + private static Library vmGetLibrary(Isolate isolateId, LibraryRef library) { + final ResultLatch latch = new ResultLatch<>(); + vmService.getLibrary(isolateId.getId(), library.getId(), new GetLibraryConsumer() { + @Override + public void onError(RPCError error) { + showRPCError(error); + } + + @Override + public void received(Library response) { + System.out.println("Received GetLibrary library"); + System.out.println(" uri: " + response.getUri()); + latch.setValue(response); + } + }); + return latch.getValue(); + } + + private static void vmGetScript(Isolate isolate, ScriptRef scriptRef) { + final ResultLatch