Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions dwds/lib/src/servers/extension_debugger.dart
Original file line number Diff line number Diff line change
@@ -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 = <int, Completer>{};

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<String, dynamic>));
}
}, onError: (e) {
close();
});
}

/// Sends a [command] with optional [params] to Dart Debug Extension
/// over the SSE connection.
@override
Future<WipResponse> sendCommand(String command,
{Map<String, dynamic> params}) {
var completer = Completer<WipResponse>();
var id = newId();
_completers[id] = completer;
_connection.sink.add(jsonEncode(serializers.serialize(ExtensionRequest(
(b) => b
..id = id
..command = command
..commandParams =
BuiltMap<String, Object>(params ?? {}).toBuilder()))));
return completer.future;
}

int newId() => _completerId++;

Future<void> close() async {
await _connection.sink.close();
}

@override
Future disable() => throw UnimplementedError();

@override
Future enable() => throw UnimplementedError();

@override
Stream<T> eventStream<T>(String method, WipEventTransformer<T> transformer) =>
throw UnimplementedError();

@override
Future<String> getScriptSource(String scriptId) => throw UnimplementedError();

@override
Stream<WipDomain> get onClosed => throw UnimplementedError();

@override
Stream<GlobalObjectClearedEvent> get onGlobalObjectCleared =>
throw UnimplementedError();

@override
Stream<DebuggerPausedEvent> get onPaused => throw UnimplementedError();

@override
Stream<DebuggerResumedEvent> get onResumed => throw UnimplementedError();

@override
Stream<ScriptParsedEvent> get onScriptParsed => throw UnimplementedError();

@override
Future pause() => throw UnimplementedError();

@override
Future resume() => throw UnimplementedError();

@override
Map<String, WipScript> 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();
}
42 changes: 42 additions & 0 deletions dwds/test/extension_debugger_test.dart
Original file line number Diff line number Diff line change
@@ -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);
});
}
49 changes: 49 additions & 0 deletions dwds/test/fixtures/fakes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -89,3 +95,46 @@ class FakeRuntime extends WipRuntime {
return results[resultsReturned++];
}
}

class FakeSseConnection implements SseConnection {
@override
StreamChannel<S> cast<S>() => null;

final _controller = StreamController<String>();

StreamController<String> get controller => _controller;

@override
StreamChannel<String> changeSink(
StreamSink<String> Function(StreamSink<String> sink) change) =>
null;

@override
StreamChannel<String> changeStream(
Stream<String> Function(Stream<String> stream) change) =>
null;

@override
void pipe(StreamChannel<String> other) {}

@override
StreamSink<String> get sink => controller.sink;

@override
Stream<String> get stream => controller.stream;

@override
StreamChannel<S> transform<S>(
StreamChannelTransformer<S, String> transformer) =>
null;

@override
StreamChannel<String> transformSink(
StreamSinkTransformer<String, String> transformer) =>
null;

@override
StreamChannel<String> transformStream(
StreamTransformer<String, String> transformer) =>
null;
}