diff --git a/dwds/lib/src/servers/extension_debugger.dart b/dwds/lib/src/servers/extension_debugger.dart new file mode 100644 index 000000000..5961b3363 --- /dev/null +++ b/dwds/lib/src/servers/extension_debugger.dart @@ -0,0 +1,117 @@ +// Copyright (c) 2019, 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:convert'; + +import 'package:built_collection/built_collection.dart'; +import 'package:dwds/data/extension_request.dart'; +import 'package:sse/server/sse_handler.dart'; +import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'; + +import '../../data/serializers.dart'; + +/// A debugger backed by the Dart Debug Extension. +class ExtensionDebugger implements WipDebugger { + @override + WipConnection get connection => throw UnimplementedError(); + + /// A connection between the debugger and the background of + /// Dart Debug Extension + final SseConnection _connection; + + /// A map from id to a completer associated with an [ExtensionRequest] + final _completers = {}; + + var _completerId = 0; + + ExtensionDebugger(this._connection) { + _connection.stream.listen((data) { + var message = serializers.deserialize(jsonDecode(data)); + if (message is ExtensionResponse) { + var encodedResult = json.decode(message.result); + if (_completers[message.id] == null) { + throw StateError('Missing completer.'); + } + _completers[message.id] + .complete(WipResponse(encodedResult as Map)); + } + }, onError: (e) { + close(); + }); + } + + /// Sends a [command] with optional [params] to Dart Debug Extension + /// over the SSE connection. + @override + Future sendCommand(String command, + {Map params}) { + var completer = Completer(); + var id = newId(); + _completers[id] = completer; + _connection.sink.add(jsonEncode(serializers.serialize(ExtensionRequest( + (b) => b + ..id = id + ..command = command + ..commandParams = + BuiltMap(params ?? {}).toBuilder())))); + return completer.future; + } + + int newId() => _completerId++; + + Future close() async { + await _connection.sink.close(); + } + + @override + Future disable() => throw UnimplementedError(); + + @override + Future enable() => throw UnimplementedError(); + + @override + Stream eventStream(String method, WipEventTransformer transformer) => + throw UnimplementedError(); + + @override + Future getScriptSource(String scriptId) => throw UnimplementedError(); + + @override + Stream get onClosed => throw UnimplementedError(); + + @override + Stream get onGlobalObjectCleared => + throw UnimplementedError(); + + @override + Stream get onPaused => throw UnimplementedError(); + + @override + Stream get onResumed => throw UnimplementedError(); + + @override + Stream get onScriptParsed => throw UnimplementedError(); + + @override + Future pause() => throw UnimplementedError(); + + @override + Future resume() => throw UnimplementedError(); + + @override + Map get scripts => throw UnimplementedError(); + + @override + Future setPauseOnExceptions(PauseState state) => throw UnimplementedError(); + + @override + Future stepInto() => throw UnimplementedError(); + + @override + Future stepOut() => throw UnimplementedError(); + + @override + Future stepOver() => throw UnimplementedError(); +} diff --git a/dwds/test/extension_debugger_test.dart b/dwds/test/extension_debugger_test.dart new file mode 100644 index 000000000..73d954647 --- /dev/null +++ b/dwds/test/extension_debugger_test.dart @@ -0,0 +1,42 @@ +// Copyright (c) 2019, 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:convert'; + +import 'package:dwds/data/extension_request.dart'; +import 'package:dwds/data/serializers.dart'; +import 'package:dwds/src/servers/extension_debugger.dart'; +import 'package:pedantic/pedantic.dart'; +import 'package:test/test.dart'; + +import 'fixtures/fakes.dart'; + +FakeSseConnection connection; +ExtensionDebugger extensionDebugger; + +void main() async { + setUp(() async { + connection = FakeSseConnection(); + extensionDebugger = ExtensionDebugger(connection); + }); + + test('can send a command & receive a response', () async { + var extensionResponse = ExtensionResponse((b) => b + ..result = jsonEncode({ + 'result': {'value': 3.14} + }) + ..id = 0 + ..success = true); + var resultCompleter = Completer(); + unawaited(extensionDebugger.sendCommand('Runtime.evaluate', + params: {'expression': '\$pi'}).then((response) { + resultCompleter.complete(response); + })); + connection.controller + .add(jsonEncode(serializers.serialize(extensionResponse))); + var response = await resultCompleter.future; + expect(response.result['value'], 3.14); + }); +} diff --git a/dwds/test/fixtures/fakes.dart b/dwds/test/fixtures/fakes.dart index 5fa63fba7..2c86f4610 100644 --- a/dwds/test/fixtures/fakes.dart +++ b/dwds/test/fixtures/fakes.dart @@ -2,8 +2,14 @@ // 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 'package:async/src/stream_sink_transformer.dart'; import 'package:dwds/src/debugging/inspector.dart'; import 'package:dwds/src/utilities/domain.dart'; +import 'package:sse/server/sse_handler.dart'; +import 'package:stream_channel/src/stream_channel_transformer.dart'; +import 'package:stream_channel/stream_channel.dart'; /// A library of fake/stub implementations of our classes and their supporting /// classes (e.g. WipConnection) for unit testing. @@ -89,3 +95,46 @@ class FakeRuntime extends WipRuntime { return results[resultsReturned++]; } } + +class FakeSseConnection implements SseConnection { + @override + StreamChannel cast() => null; + + final _controller = StreamController(); + + StreamController get controller => _controller; + + @override + StreamChannel changeSink( + StreamSink Function(StreamSink sink) change) => + null; + + @override + StreamChannel changeStream( + Stream Function(Stream stream) change) => + null; + + @override + void pipe(StreamChannel other) {} + + @override + StreamSink get sink => controller.sink; + + @override + Stream get stream => controller.stream; + + @override + StreamChannel transform( + StreamChannelTransformer transformer) => + null; + + @override + StreamChannel transformSink( + StreamSinkTransformer transformer) => + null; + + @override + StreamChannel transformStream( + StreamTransformer transformer) => + null; +}