Version 2.17.0-272.0.dev
Merge commit '1af37bd92a9b08ac2d88d26c55feb1dd43382416' into 'dev'
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/compiler/request_channel.dart b/pkg/_fe_analyzer_shared/lib/src/macros/compiler/request_channel.dart
new file mode 100644
index 0000000..32cdd78
--- /dev/null
+++ b/pkg/_fe_analyzer_shared/lib/src/macros/compiler/request_channel.dart
@@ -0,0 +1,148 @@
+// Copyright (c) 2022, 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:io';
+import 'dart:typed_data';
+
+import 'package:_fe_analyzer_shared/src/macros/executor/message_grouper.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/serialization.dart';
+
+/// An instance of this class is thrown when remotely executed code throws
+/// an exception.
+class RemoteException {
+ final String message;
+ final String stackTrace;
+
+ RemoteException({
+ required this.message,
+ required this.stackTrace,
+ });
+}
+
+/// Channel for exchanging requests and responses over [Socket].
+class RequestChannel {
+ final Socket _socket;
+ final Map<int, Completer<Object?>> _requests = {};
+ final Map<String, Future<Object?> Function(Object?)> _requestHandlers = {};
+
+ int _nextRequestId = 0;
+
+ RequestChannel(this._socket) {
+ final MessageGrouper messageGrouper = new MessageGrouper(_socket);
+ messageGrouper.messageStream.map((bytes) {
+ final ByteDataDeserializer deserializer = new ByteDataDeserializer(
+ new ByteData.sublistView(bytes),
+ );
+ return deserializer.expectAny();
+ }).listen(_processMessage, cancelOnError: true, onDone: () {
+ _socket.destroy();
+ });
+ }
+
+ /// Registers the [handler] for the [method].
+ void add(String method, Future<Object?> Function(Object?) handler) {
+ _requestHandlers[method] = handler;
+ }
+
+ /// Sends a request with the given [method] and [argument], when it is
+ /// handled on the other side by the handler registered with [add], the
+ /// result is returned from the [Future].
+ ///
+ /// If an exception happens on the other side, the returned future completes
+ /// with a [RemoteException].
+ ///
+ /// The other side may do callbacks.
+ Future<T> sendRequest<T>(String method, Object? argument) async {
+ final int requestId = _nextRequestId++;
+ final Completer<Object?> completer = new Completer<Object?>();
+ _requests[requestId] = completer;
+ _writeObject({
+ 'requestId': requestId,
+ 'method': method,
+ 'argument': argument,
+ });
+
+ return await completer.future as T;
+ }
+
+ void _processMessage(Object? message) {
+ if (message is Map<Object?, Object?>) {
+ final Object? requestId = message['requestId'];
+ if (requestId != null) {
+ final Object? method = message['method'];
+ final Object? argument = message['argument'];
+ if (method != null) {
+ final Future<Object?> Function(Object?)? handler =
+ _requestHandlers[method];
+ if (handler != null) {
+ handler(argument).then((result) {
+ _writeObject({
+ 'responseId': requestId,
+ 'result': result,
+ });
+ }, onError: (e, stackTrace) {
+ _writeObject({
+ 'responseId': requestId,
+ 'exception': {
+ 'message': '$e',
+ 'stackTrace': '$stackTrace',
+ },
+ });
+ });
+ } else {
+ _writeObject({
+ 'responseId': requestId,
+ 'exception': {
+ 'message': 'No handler for: $method',
+ 'stackTrace': '${StackTrace.current}',
+ },
+ });
+ }
+ return;
+ }
+ }
+
+ final Object? responseId = message['responseId'];
+ if (responseId != null) {
+ final Completer<Object?>? completer = _requests.remove(responseId);
+ if (completer == null) {
+ return;
+ }
+
+ final Object? exception = message['exception'];
+ if (exception is Map) {
+ completer.completeError(
+ new RemoteException(
+ message: exception['message'] as String,
+ stackTrace: exception['stackTrace'] as String,
+ ),
+ );
+ return;
+ }
+
+ final Object? result = message['result'];
+ completer.complete(result);
+ return;
+ }
+ }
+
+ throw new StateError('Unexpected message: $message');
+ }
+
+ /// Sends [bytes] for [MessageGrouper].
+ void _writeBytes(List<int> bytes) {
+ final ByteData lengthByteData = new ByteData(4)..setUint32(0, bytes.length);
+ _socket.add(lengthByteData.buffer.asUint8List());
+ _socket.add(bytes);
+ _socket.flush();
+ }
+
+ /// Serializes and sends the [object].
+ void _writeObject(Object object) {
+ final ByteDataSerializer serializer = new ByteDataSerializer();
+ serializer.addAny(object);
+ _writeBytes(serializer.result);
+ }
+}
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor/serialization.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor/serialization.dart
index 50d98fd..0aece1c 100644
--- a/pkg/_fe_analyzer_shared/lib/src/macros/executor/serialization.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor/serialization.dart
@@ -278,7 +278,8 @@
@override
void addInt(int value) {
if (value >= 0x0) {
- if (value + DataKind.values.length <= 0xff) {
+ assert(DataKind.values.length < 0xff);
+ if (value <= 0xff - DataKind.values.length) {
_builder..addByte(value + DataKind.values.length);
} else if (value <= 0xff) {
_builder
@@ -380,10 +381,52 @@
}
@override
- void endList() => _builder.addByte(DataKind.endList.index);
+ void startList() => _builder.addByte(DataKind.startList.index);
@override
- void startList() => _builder.addByte(DataKind.startList.index);
+ void endList() => _builder.addByte(DataKind.endList.index);
+
+ /// Used to signal the start of an arbitrary length list of map entries.
+ void startMap() => _builder.addByte(DataKind.startMap.index);
+
+ /// Used to signal the end of an arbitrary length list of map entries.
+ void endMap() => _builder.addByte(DataKind.endMap.index);
+
+ /// Serializes a [Uint8List].
+ void addUint8List(Uint8List value) {
+ _builder.addByte(DataKind.uint8List.index);
+ addInt(value.length);
+ _builder.add(value);
+ }
+
+ /// Serializes an object with arbitrary structure. It supports `bool`,
+ /// `int`, `String`, `null`, `Uint8List`, `List`, `Map`.
+ void addAny(Object? value) {
+ if (value == null) {
+ addNull();
+ } else if (value is bool) {
+ addBool(value);
+ } else if (value is int) {
+ addInt(value);
+ } else if (value is String) {
+ addString(value);
+ } else if (value is Uint8List) {
+ addUint8List(value);
+ } else if (value is List) {
+ startList();
+ value.forEach(addAny);
+ endList();
+ } else if (value is Map) {
+ startMap();
+ for (MapEntry<Object?, Object?> entry in value.entries) {
+ addAny(entry.key);
+ addAny(entry.value);
+ }
+ endMap();
+ } else {
+ throw new ArgumentError('(${value.runtimeType}) $value');
+ }
+ }
@override
Uint8List get result => _builder.takeBytes();
@@ -494,6 +537,32 @@
_byteOffsetIncrement = 1;
}
+ /// Asserts that the current item is the start of a map.
+ ///
+ /// An example for how to read from a map is as follows:
+ ///
+ /// I know it's a map of ints to strings.
+ ///
+ /// ```
+ /// var result = <int, String>[];
+ /// deserializer.expectMap();
+ /// while (deserializer.moveNext()) {
+ /// var key = deserializer.expectInt();
+ /// deserializer.next();
+ /// var value = deserializer.expectString();
+ /// result[key] = value;
+ /// }
+ /// // We have already called `moveNext` to move past the map.
+ /// deserializer.expectBool();
+ /// ```
+ void expectMap() {
+ DataKind kind = _readKind();
+ if (kind != DataKind.startMap) {
+ throw new StateError('Expected the start to a map but found a $kind');
+ }
+ _byteOffsetIncrement = 1;
+ }
+
@override
String expectString() {
DataKind kind = _readKind();
@@ -514,6 +583,75 @@
}
}
+ /// Reads the current value as [Uint8List].
+ Uint8List expectUint8List() {
+ _byteOffsetIncrement = 1;
+ moveNext();
+ int length = expectInt();
+ int offset = _byteOffset + _byteOffsetIncrement!;
+ _byteOffsetIncrement = _byteOffsetIncrement! + length;
+ return _bytes.buffer.asUint8List(offset, length);
+ }
+
+ /// Reads the current value as an object of arbitrary structure.
+ Object? expectAny() {
+ const Set<DataKind> boolKinds = {
+ DataKind.boolFalse,
+ DataKind.boolTrue,
+ };
+
+ const Set<DataKind> intKinds = {
+ DataKind.directEncodedUint8,
+ DataKind.int8,
+ DataKind.int16,
+ DataKind.int32,
+ DataKind.int64,
+ DataKind.uint8,
+ DataKind.uint16,
+ DataKind.uint32,
+ DataKind.uint64,
+ };
+
+ const Set<DataKind> stringKinds = {
+ DataKind.oneByteString,
+ DataKind.twoByteString,
+ };
+
+ DataKind kind = _readKind();
+ if (boolKinds.contains(kind)) {
+ return expectBool();
+ } else if (kind == DataKind.nil) {
+ checkNull();
+ return null;
+ } else if (intKinds.contains(kind)) {
+ return expectInt();
+ } else if (stringKinds.contains(kind)) {
+ return expectString();
+ } else if (kind == DataKind.startList) {
+ List<Object?> result = [];
+ expectList();
+ while (moveNext()) {
+ Object? element = expectAny();
+ result.add(element);
+ }
+ return result;
+ } else if (kind == DataKind.startMap) {
+ Map<Object?, Object?> result = {};
+ expectMap();
+ while (moveNext()) {
+ Object? key = expectAny();
+ moveNext();
+ Object? value = expectAny();
+ result[key] = value;
+ }
+ return result;
+ } else if (kind == DataKind.uint8List) {
+ return expectUint8List();
+ } else {
+ throw new StateError('Expected: $kind');
+ }
+ }
+
@override
bool moveNext() {
int? increment = _byteOffsetIncrement;
@@ -524,8 +662,9 @@
_byteOffset += increment;
if (_byteOffset >= _bytes.lengthInBytes) {
return false;
- } else if (_readKind() == DataKind.endList) {
- // You don't explicitly consume list end markers.
+ } else if (_readKind() == DataKind.endList ||
+ _readKind() == DataKind.endMap) {
+ // You don't explicitly consume list/map end markers.
_byteOffsetIncrement = 1;
return false;
} else {
@@ -541,6 +680,8 @@
directEncodedUint8, // Encoded in the kind byte.
startList,
endList,
+ startMap,
+ endMap,
int8,
int16,
int32,
@@ -552,6 +693,7 @@
float64,
oneByteString,
twoByteString,
+ uint8List,
}
/// Must be set using `withSerializationMode` before doing any serialization or
diff --git a/pkg/_fe_analyzer_shared/test/macros/compiler/request_channel_test.dart b/pkg/_fe_analyzer_shared/test/macros/compiler/request_channel_test.dart
new file mode 100644
index 0000000..fca8409
--- /dev/null
+++ b/pkg/_fe_analyzer_shared/test/macros/compiler/request_channel_test.dart
@@ -0,0 +1,269 @@
+// Copyright (c) 2022, 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:io';
+import 'dart:typed_data';
+
+import 'package:_fe_analyzer_shared/src/macros/compiler/request_channel.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/serialization.dart';
+import 'package:test/test.dart';
+
+main() {
+ group('ByteDataSerializer', () {
+ group('addAny', () {
+ Uint8List write(Object? object) {
+ final serializer = ByteDataSerializer();
+ serializer.addAny(object);
+ return serializer.result;
+ }
+
+ Object? read(Uint8List bytes) {
+ final deserializer = ByteDataDeserializer(
+ new ByteData.sublistView(bytes),
+ );
+ return deserializer.expectAny();
+ }
+
+ void writeRead(Object? object) {
+ final bytes = write(object);
+ expect(read(bytes), object);
+ }
+
+ group('bool', () {
+ test('false', () {
+ writeRead(false);
+ });
+ test('true', () {
+ writeRead(true);
+ });
+ });
+
+ test('null', () {
+ writeRead(null);
+ });
+
+ group('int', () {
+ test('negative', () {
+ void writeReadInt(int value) {
+ expect(value, isNegative);
+ writeRead(value);
+ }
+
+ writeReadInt(-1);
+ writeReadInt(-2);
+ writeReadInt(-0x7F);
+ writeReadInt(-0xFF);
+ writeReadInt(-0xFFFF);
+ writeReadInt(-0xFFFFFF);
+ writeReadInt(-0xFFFFFFFF);
+ writeReadInt(-0xFFFFFFFFFF);
+ writeReadInt(-0xFFFFFFFFFFFF);
+ writeReadInt(-0xFFFFFFFFFFFFFF);
+ writeReadInt(-0x7FFFFFFFFFFFFFFF);
+ writeReadInt(0x8000000000000000);
+ });
+
+ test('non-negative', () {
+ void writeReadInt(int value) {
+ expect(value, isNonNegative);
+ writeRead(value);
+ }
+
+ writeReadInt(0);
+ writeReadInt(1);
+ writeReadInt(0x6F);
+ writeReadInt(0x7F);
+ writeReadInt(0xFF);
+ writeReadInt(0xFFFF);
+ writeReadInt(0xFFFFFF);
+ writeReadInt(0xFFFFFFFF);
+ writeReadInt(0xFFFFFFFFFF);
+ writeReadInt(0xFFFFFFFFFFFF);
+ writeReadInt(0xFFFFFFFFFFFFFF);
+ writeReadInt(0x7FFFFFFFFFFFFFFF);
+ });
+ });
+
+ group('String', () {
+ test('one-byte', () {
+ writeRead('test');
+ });
+ test('two-byte', () {
+ writeRead('проба');
+ });
+ });
+
+ group('list', () {
+ test('empty', () {
+ writeRead(<int>[]);
+ });
+ test('of int', () {
+ writeRead([1, 2, 3]);
+ });
+ test('of string', () {
+ writeRead(['a', 'b', 'c']);
+ });
+ test('mixed', () {
+ writeRead([true, 'abc', 0xAB]);
+ });
+ test('nested', () {
+ writeRead([
+ [1, 2, 3],
+ true,
+ ['foo', 'bar'],
+ false,
+ ]);
+ });
+ });
+
+ group('map', () {
+ test('empty', () {
+ writeRead(<String, int>{});
+ });
+ test('string to int', () {
+ writeRead({'a': 0x11, 'b': 0x22});
+ });
+ test('nested', () {
+ writeRead({
+ 'foo': {0: 1, 2: 3},
+ 'bar': {4: 5, 6: 7},
+ });
+ });
+ });
+
+ group('Uint8List', () {
+ test('empty', () {
+ writeRead(Uint8List(0));
+ });
+ test('5 elements', () {
+ writeRead(
+ Uint8List.fromList(
+ [0x11, 0x22, 0x33, 0x44, 0x55],
+ ),
+ );
+ });
+ test('0..4096', () {
+ for (int length = 1; length < 4096; length++) {
+ Uint8List object = Uint8List(length);
+ for (int i = 0; i < object.length; i++) {
+ object[i] = i & 0xFF;
+ }
+ Uint8List bytes = write(object);
+ Uint8List readObject = read(bytes) as Uint8List;
+ for (int i = 0; i < object.length; i++) {
+ if (object[i] != readObject[i]) {
+ fail('[length: $length][i: $i]');
+ }
+ }
+ }
+ });
+ test('nested in Map', () {
+ writeRead({
+ 'answer': 42,
+ 'result': Uint8List.fromList(
+ [0x11, 0x22, 0x33, 0x44, 0x55],
+ ),
+ 'hasErrors': false,
+ });
+ });
+ });
+
+ group('JSON', () {
+ test('command', () {
+ writeRead({
+ 'command': 'compile',
+ 'argument': {
+ 'uri': 'input.dart',
+ 'additional': ['a', 'b', 'c'],
+ },
+ });
+ });
+ });
+ });
+ });
+
+ group('RequestChannel', () {
+ Future<void> runServerClient({
+ required Future<void> Function(RequestChannel channel) server,
+ required Future<void> Function(RequestChannel channel) client,
+ }) async {
+ ServerSocket serverSocket =
+ await ServerSocket.bind(InternetAddress.loopbackIPv4, 0);
+ serverSocket.listen((socket) async {
+ await serverSocket.close();
+ await server(
+ RequestChannel(socket),
+ );
+ });
+
+ var clientSocket = await Socket.connect(
+ InternetAddress.loopbackIPv4,
+ serverSocket.port,
+ );
+ try {
+ await client(
+ RequestChannel(clientSocket),
+ );
+ } finally {
+ clientSocket.destroy();
+ }
+ }
+
+ test('exception', () async {
+ await runServerClient(
+ server: (channel) async {
+ channel.add('throwIt', (argument) async {
+ throw 'Some error';
+ });
+ },
+ client: (channel) async {
+ try {
+ await channel.sendRequest('throwIt', {});
+ fail('Expected to throw RemoteException.');
+ } on RemoteException catch (e) {
+ expect(e.message, 'Some error');
+ expect(e.stackTrace, isNotEmpty);
+ }
+ },
+ );
+ });
+
+ test('no handler', () async {
+ await runServerClient(
+ server: (channel) async {},
+ client: (channel) async {
+ try {
+ await channel.sendRequest('noSuchHandler', {});
+ fail('Expected to throw RemoteException.');
+ } on RemoteException catch (e) {
+ expect(e.message, contains('noSuchHandler'));
+ expect(e.stackTrace, isNotEmpty);
+ }
+ },
+ );
+ });
+
+ test('two-way communication', () async {
+ await runServerClient(
+ server: (channel) async {
+ channel.add('add', (argument) async {
+ if (argument is List<Object?> && argument.length == 2) {
+ Object? a = argument[0];
+ Object? b = argument[1];
+ if (a is int && b is int) {
+ int more = await channel.sendRequest<int>('more', null);
+ return a + b + more;
+ }
+ }
+ return '<bad>';
+ });
+ },
+ client: (channel) async {
+ channel.add('more', (_) async => 4);
+ expect(await channel.sendRequest('add', [2, 3]), 9);
+ },
+ );
+ });
+ });
+}
diff --git a/pkg/analysis_server/lib/src/analysis_server.dart b/pkg/analysis_server/lib/src/analysis_server.dart
index 2aec83b..ba6dc60 100644
--- a/pkg/analysis_server/lib/src/analysis_server.dart
+++ b/pkg/analysis_server/lib/src/analysis_server.dart
@@ -39,6 +39,7 @@
import 'package:analysis_server/src/server/error_notifier.dart';
import 'package:analysis_server/src/server/features.dart';
import 'package:analysis_server/src/server/sdk_configuration.dart';
+import 'package:analysis_server/src/services/execution/execution_context.dart';
import 'package:analysis_server/src/services/flutter/widget_descriptions.dart';
import 'package:analysis_server/src/utilities/process.dart';
import 'package:analysis_server/src/utilities/progress.dart';
@@ -107,6 +108,9 @@
/// The support for Flutter properties.
WidgetDescriptions flutterWidgetDescriptions = WidgetDescriptions();
+ /// The context used by the execution domain handlers.
+ final ExecutionContext executionContext = ExecutionContext();
+
/// The [Completer] that completes when analysis is complete.
Completer<void>? _onAnalysisCompleteCompleter;
@@ -221,7 +225,7 @@
EditDomainHandler(this),
SearchDomainHandler(this),
CompletionDomainHandler(this),
- ExecutionDomainHandler(this),
+ ExecutionDomainHandler(this, executionContext),
DiagnosticDomainHandler(this),
AnalyticsDomainHandler(this),
KytheDomainHandler(this),
diff --git a/pkg/analysis_server/lib/src/domain_execution.dart b/pkg/analysis_server/lib/src/domain_execution.dart
index c1e1b35..43f7db0 100644
--- a/pkg/analysis_server/lib/src/domain_execution.dart
+++ b/pkg/analysis_server/lib/src/domain_execution.dart
@@ -2,13 +2,16 @@
// 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:collection';
-
import 'package:analysis_server/protocol/protocol_constants.dart';
import 'package:analysis_server/src/analysis_server.dart';
+import 'package:analysis_server/src/handler/legacy/execution_create_context.dart';
+import 'package:analysis_server/src/handler/legacy/execution_delete_context.dart';
+import 'package:analysis_server/src/handler/legacy/execution_get_suggestions.dart';
+import 'package:analysis_server/src/handler/legacy/execution_map_uri.dart';
+import 'package:analysis_server/src/handler/legacy/execution_set_subscriptions.dart';
import 'package:analysis_server/src/protocol_server.dart';
+import 'package:analysis_server/src/services/execution/execution_context.dart';
import 'package:analysis_server/src/utilities/progress.dart';
-import 'package:analyzer/file_system/file_system.dart';
/// Instances of the class [ExecutionDomainHandler] implement a [RequestHandler]
/// that handles requests in the `execution` domain.
@@ -16,57 +19,12 @@
/// The analysis server that is using this handler to process requests.
final AnalysisServer server;
- /// The next execution context identifier to be returned.
- int nextContextId = 0;
-
- /// A table mapping execution context id's to the root of the context.
- final Map<String, String> contextMap = HashMap<String, String>();
+ /// The context used by the execution domain handlers.
+ final ExecutionContext executionContext;
/// Initialize a newly created handler to handle requests for the given
/// [server].
- ExecutionDomainHandler(this.server);
-
- /// Implement the `execution.createContext` request.
- Response createContext(Request request) {
- var file = ExecutionCreateContextParams.fromRequest(request).contextRoot;
- var contextId = (nextContextId++).toString();
- contextMap[contextId] = file;
- return ExecutionCreateContextResult(contextId).toResponse(request.id);
- }
-
- /// Implement the `execution.deleteContext` request.
- Response deleteContext(Request request) {
- var contextId = ExecutionDeleteContextParams.fromRequest(request).id;
- contextMap.remove(contextId);
- return ExecutionDeleteContextResult().toResponse(request.id);
- }
-
- /// Implement the 'execution.getSuggestions' request.
- void getSuggestions(Request request) async {
-// var params = new ExecutionGetSuggestionsParams.fromRequest(request);
-// var computer = new RuntimeCompletionComputer(
-// server.resourceProvider,
-// server.fileContentOverlay,
-// server.getAnalysisDriver(params.contextFile),
-// params.code,
-// params.offset,
-// params.contextFile,
-// params.contextOffset,
-// params.variables,
-// params.expressions);
-// RuntimeCompletionResult completionResult = await computer.compute();
-//
-// // Send the response.
-// var result = new ExecutionGetSuggestionsResult(
-// suggestions: completionResult.suggestions,
-// expressions: completionResult.expressions);
- // TODO(brianwilkerson) Re-enable this functionality after implementing a
- // way of computing suggestions that is compatible with AnalysisSession.
- var result = ExecutionGetSuggestionsResult(
- suggestions: <CompletionSuggestion>[],
- expressions: <RuntimeCompletionExpression>[]);
- server.sendResponse(result.toResponse(request.id));
- }
+ ExecutionDomainHandler(this.server, this.executionContext);
@override
Response? handleRequest(
@@ -74,76 +32,32 @@
try {
var requestName = request.method;
if (requestName == EXECUTION_REQUEST_CREATE_CONTEXT) {
- return createContext(request);
+ ExecutionCreateContextHandler(
+ server, request, cancellationToken, executionContext)
+ .handle();
+ return Response.DELAYED_RESPONSE;
} else if (requestName == EXECUTION_REQUEST_DELETE_CONTEXT) {
- return deleteContext(request);
+ ExecutionDeleteContextHandler(
+ server, request, cancellationToken, executionContext)
+ .handle();
+ return Response.DELAYED_RESPONSE;
} else if (requestName == EXECUTION_REQUEST_GET_SUGGESTIONS) {
- getSuggestions(request);
+ ExecutionGetSuggestionsHandler(server, request, cancellationToken)
+ .handle();
return Response.DELAYED_RESPONSE;
} else if (requestName == EXECUTION_REQUEST_MAP_URI) {
- return mapUri(request);
+ ExecutionMapUriHandler(
+ server, request, cancellationToken, executionContext)
+ .handle();
+ return Response.DELAYED_RESPONSE;
} else if (requestName == EXECUTION_REQUEST_SET_SUBSCRIPTIONS) {
- return setSubscriptions(request);
+ ExecutionSetSubscriptionsHandler(server, request, cancellationToken)
+ .handle();
+ return Response.DELAYED_RESPONSE;
}
} on RequestFailure catch (exception) {
return exception.response;
}
return null;
}
-
- /// Implement the 'execution.mapUri' request.
- Response mapUri(Request request) {
- var params = ExecutionMapUriParams.fromRequest(request);
- var contextId = params.id;
- var path = contextMap[contextId];
- if (path == null) {
- return Response.invalidParameter(request, 'id',
- 'There is no execution context with an id of $contextId');
- }
-
- var driver = server.getAnalysisDriver(path);
- if (driver == null) {
- return Response.invalidExecutionContext(request, contextId);
- }
- var sourceFactory = driver.sourceFactory;
-
- var file = params.file;
- var uri = params.uri;
- if (file != null) {
- if (uri != null) {
- return Response.invalidParameter(request, 'file',
- 'Either file or uri must be provided, but not both');
- }
- var resource = server.resourceProvider.getResource(file);
- if (!resource.exists) {
- return Response.invalidParameter(request, 'file', 'Must exist');
- } else if (resource is! File) {
- return Response.invalidParameter(
- request, 'file', 'Must not refer to a directory');
- }
-
- var source = driver.fsState.getFileForPath(file).source;
- if (!source.uri.isScheme('file')) {
- uri = source.uri.toString();
- } else {
- uri = sourceFactory.pathToUri(file).toString();
- }
- return ExecutionMapUriResult(uri: uri).toResponse(request.id);
- } else if (uri != null) {
- var source = sourceFactory.forUri(uri);
- if (source == null) {
- return Response.invalidParameter(request, 'uri', 'Invalid URI');
- }
- file = source.fullName;
- return ExecutionMapUriResult(file: file).toResponse(request.id);
- }
- return Response.invalidParameter(
- request, 'file', 'Either file or uri must be provided');
- }
-
- /// Implement the 'execution.setSubscriptions' request.
- Response setSubscriptions(Request request) {
- // Under the analysis driver, setSubscriptions() becomes a no-op.
- return ExecutionSetSubscriptionsResult().toResponse(request.id);
- }
}
diff --git a/pkg/analysis_server/lib/src/domain_kythe.dart b/pkg/analysis_server/lib/src/domain_kythe.dart
index 0be62d1..afa3c9e 100644
--- a/pkg/analysis_server/lib/src/domain_kythe.dart
+++ b/pkg/analysis_server/lib/src/domain_kythe.dart
@@ -4,15 +4,10 @@
import 'package:analysis_server/protocol/protocol.dart';
import 'package:analysis_server/protocol/protocol_constants.dart';
-import 'package:analysis_server/protocol/protocol_generated.dart';
import 'package:analysis_server/src/analysis_server.dart';
import 'package:analysis_server/src/domain_abstract.dart';
-import 'package:analysis_server/src/plugin/result_merger.dart';
-import 'package:analysis_server/src/services/kythe/kythe_visitors.dart';
+import 'package:analysis_server/src/handler/legacy/kythe_get_kythe_entries.dart';
import 'package:analysis_server/src/utilities/progress.dart';
-import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
-import 'package:analyzer_plugin/protocol/protocol_common.dart';
-import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
/// Instances of the class [KytheDomainHandler] implement a [RequestHandler]
/// that handles requests in the `kythe` domain.
@@ -21,62 +16,14 @@
/// [server].
KytheDomainHandler(AnalysisServer server) : super(server);
- /// Implement the `kythe.getKytheEntries` request.
- Future<void> getKytheEntries(Request request) async {
- var file = KytheGetKytheEntriesParams.fromRequest(request).file;
- var driver = server.getAnalysisDriver(file);
- if (driver == null) {
- server.sendResponse(Response.getKytheEntriesInvalidFile(request));
- } else {
- //
- // Allow plugins to start computing entries.
- //
- var requestParams = plugin.KytheGetKytheEntriesParams(file);
- var pluginFutures = server.pluginManager.broadcastRequest(
- requestParams,
- contextRoot: driver.analysisContext!.contextRoot,
- );
- //
- // Compute entries generated by server.
- //
- var allResults = <KytheGetKytheEntriesResult>[];
- var result = await server.getResolvedUnit(file);
- if (result != null) {
- var entries = <KytheEntry>[];
- // TODO(brianwilkerson) Figure out how to get the list of files.
- var files = <String>[];
- result.unit.accept(KytheDartVisitor(server.resourceProvider, entries,
- file, InheritanceManager3(), result.content));
- allResults.add(KytheGetKytheEntriesResult(entries, files));
- }
- //
- // Add the entries produced by plugins to the server-generated entries.
- //
- var responses = await waitForResponses(pluginFutures,
- requestParameters: requestParams);
- for (var response in responses) {
- var result = plugin.KytheGetKytheEntriesResult.fromResponse(response);
- allResults
- .add(KytheGetKytheEntriesResult(result.entries, result.files));
- }
- //
- // Return the result.
- //
- var merger = ResultMerger();
- var mergedResults = merger.mergeKytheEntries(allResults);
- server.sendResponse(
- KytheGetKytheEntriesResult(mergedResults.entries, mergedResults.files)
- .toResponse(request.id));
- }
- }
-
@override
Response? handleRequest(
Request request, CancellationToken cancellationToken) {
try {
var requestName = request.method;
if (requestName == KYTHE_REQUEST_GET_KYTHE_ENTRIES) {
- getKytheEntries(request);
+ KytheGetKytheEntriesHandler(server, request, cancellationToken)
+ .handle();
return Response.DELAYED_RESPONSE;
}
} on RequestFailure catch (exception) {
diff --git a/pkg/analysis_server/lib/src/handler/legacy/execution_create_context.dart b/pkg/analysis_server/lib/src/handler/legacy/execution_create_context.dart
new file mode 100644
index 0000000..3c29df2
--- /dev/null
+++ b/pkg/analysis_server/lib/src/handler/legacy/execution_create_context.dart
@@ -0,0 +1,32 @@
+// Copyright (c) 2022, 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 'package:analysis_server/protocol/protocol.dart';
+import 'package:analysis_server/protocol/protocol_generated.dart';
+import 'package:analysis_server/src/analysis_server.dart';
+import 'package:analysis_server/src/handler/legacy/legacy_handler.dart';
+import 'package:analysis_server/src/services/execution/execution_context.dart';
+import 'package:analysis_server/src/utilities/progress.dart';
+
+/// The handler for the `execution.createContext` request.
+class ExecutionCreateContextHandler extends LegacyHandler {
+ /// The context used by the execution domain handlers.
+ final ExecutionContext executionContext;
+
+ /// Initialize a newly created handler to be able to service requests for the
+ /// [server].
+ ExecutionCreateContextHandler(AnalysisServer server, Request request,
+ CancellationToken cancellationToken, this.executionContext)
+ : super(server, request, cancellationToken);
+
+ @override
+ Future<void> handle() async {
+ var file = ExecutionCreateContextParams.fromRequest(request).contextRoot;
+ var contextId = (executionContext.nextContextId++).toString();
+ executionContext.contextMap[contextId] = file;
+ sendResult(ExecutionCreateContextResult(contextId));
+ }
+}
diff --git a/pkg/analysis_server/lib/src/handler/legacy/execution_delete_context.dart b/pkg/analysis_server/lib/src/handler/legacy/execution_delete_context.dart
new file mode 100644
index 0000000..5802dde
--- /dev/null
+++ b/pkg/analysis_server/lib/src/handler/legacy/execution_delete_context.dart
@@ -0,0 +1,31 @@
+// Copyright (c) 2022, 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 'package:analysis_server/protocol/protocol.dart';
+import 'package:analysis_server/protocol/protocol_generated.dart';
+import 'package:analysis_server/src/analysis_server.dart';
+import 'package:analysis_server/src/handler/legacy/legacy_handler.dart';
+import 'package:analysis_server/src/services/execution/execution_context.dart';
+import 'package:analysis_server/src/utilities/progress.dart';
+
+/// The handler for the `execution.deleteContext` request.
+class ExecutionDeleteContextHandler extends LegacyHandler {
+ /// The context used by the execution domain handlers.
+ final ExecutionContext executionContext;
+
+ /// Initialize a newly created handler to be able to service requests for the
+ /// [server].
+ ExecutionDeleteContextHandler(AnalysisServer server, Request request,
+ CancellationToken cancellationToken, this.executionContext)
+ : super(server, request, cancellationToken);
+
+ @override
+ Future<void> handle() async {
+ var contextId = ExecutionDeleteContextParams.fromRequest(request).id;
+ executionContext.contextMap.remove(contextId);
+ sendResult(ExecutionDeleteContextResult());
+ }
+}
diff --git a/pkg/analysis_server/lib/src/handler/legacy/execution_get_suggestions.dart b/pkg/analysis_server/lib/src/handler/legacy/execution_get_suggestions.dart
new file mode 100644
index 0000000..d7413f2
--- /dev/null
+++ b/pkg/analysis_server/lib/src/handler/legacy/execution_get_suggestions.dart
@@ -0,0 +1,48 @@
+// Copyright (c) 2022, 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 'package:analysis_server/protocol/protocol.dart';
+import 'package:analysis_server/protocol/protocol_generated.dart';
+import 'package:analysis_server/src/analysis_server.dart';
+import 'package:analysis_server/src/handler/legacy/legacy_handler.dart';
+import 'package:analysis_server/src/utilities/progress.dart';
+import 'package:analyzer_plugin/protocol/protocol_common.dart';
+
+/// The handler for the `execution.getSuggestions` request.
+class ExecutionGetSuggestionsHandler extends LegacyHandler {
+ /// Initialize a newly created handler to be able to service requests for the
+ /// [server].
+ ExecutionGetSuggestionsHandler(AnalysisServer server, Request request,
+ CancellationToken cancellationToken)
+ : super(server, request, cancellationToken);
+
+ @override
+ Future<void> handle() async {
+// var params = new ExecutionGetSuggestionsParams.fromRequest(request);
+// var computer = new RuntimeCompletionComputer(
+// server.resourceProvider,
+// server.fileContentOverlay,
+// server.getAnalysisDriver(params.contextFile),
+// params.code,
+// params.offset,
+// params.contextFile,
+// params.contextOffset,
+// params.variables,
+// params.expressions);
+// RuntimeCompletionResult completionResult = await computer.compute();
+//
+// // Send the response.
+// var result = new ExecutionGetSuggestionsResult(
+// suggestions: completionResult.suggestions,
+// expressions: completionResult.expressions);
+ // TODO(brianwilkerson) Re-enable this functionality after implementing a
+ // way of computing suggestions that is compatible with AnalysisSession.
+ var result = ExecutionGetSuggestionsResult(
+ suggestions: <CompletionSuggestion>[],
+ expressions: <RuntimeCompletionExpression>[]);
+ sendResponse(result.toResponse(request.id));
+ }
+}
diff --git a/pkg/analysis_server/lib/src/handler/legacy/execution_map_uri.dart b/pkg/analysis_server/lib/src/handler/legacy/execution_map_uri.dart
new file mode 100644
index 0000000..0e84b04
--- /dev/null
+++ b/pkg/analysis_server/lib/src/handler/legacy/execution_map_uri.dart
@@ -0,0 +1,83 @@
+// Copyright (c) 2022, 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 'package:analysis_server/protocol/protocol.dart';
+import 'package:analysis_server/protocol/protocol_generated.dart';
+import 'package:analysis_server/src/analysis_server.dart';
+import 'package:analysis_server/src/handler/legacy/legacy_handler.dart';
+import 'package:analysis_server/src/services/execution/execution_context.dart';
+import 'package:analysis_server/src/utilities/progress.dart';
+import 'package:analyzer/file_system/file_system.dart';
+
+/// The handler for the `execution.mapUri` request.
+class ExecutionMapUriHandler extends LegacyHandler {
+ /// The context used by the execution domain handlers.
+ final ExecutionContext executionContext;
+
+ /// Initialize a newly created handler to be able to service requests for the
+ /// [server].
+ ExecutionMapUriHandler(AnalysisServer server, Request request,
+ CancellationToken cancellationToken, this.executionContext)
+ : super(server, request, cancellationToken);
+
+ @override
+ Future<void> handle() async {
+ var params = ExecutionMapUriParams.fromRequest(request);
+ var contextId = params.id;
+ var path = executionContext.contextMap[contextId];
+ if (path == null) {
+ sendResponse(Response.invalidParameter(request, 'id',
+ 'There is no execution context with an id of $contextId'));
+ return;
+ }
+
+ var driver = server.getAnalysisDriver(path);
+ if (driver == null) {
+ sendResponse(Response.invalidExecutionContext(request, contextId));
+ return;
+ }
+ var sourceFactory = driver.sourceFactory;
+
+ var file = params.file;
+ var uri = params.uri;
+ if (file != null) {
+ if (uri != null) {
+ sendResponse(Response.invalidParameter(request, 'file',
+ 'Either file or uri must be provided, but not both'));
+ return;
+ }
+ var resource = server.resourceProvider.getResource(file);
+ if (!resource.exists) {
+ sendResponse(Response.invalidParameter(request, 'file', 'Must exist'));
+ return;
+ } else if (resource is! File) {
+ sendResponse(Response.invalidParameter(
+ request, 'file', 'Must not refer to a directory'));
+ return;
+ }
+
+ var source = driver.fsState.getFileForPath(file).source;
+ if (!source.uri.isScheme('file')) {
+ uri = source.uri.toString();
+ } else {
+ uri = sourceFactory.pathToUri(file).toString();
+ }
+ sendResult(ExecutionMapUriResult(uri: uri));
+ return;
+ } else if (uri != null) {
+ var source = sourceFactory.forUri(uri);
+ if (source == null) {
+ sendResponse(Response.invalidParameter(request, 'uri', 'Invalid URI'));
+ return;
+ }
+ file = source.fullName;
+ sendResult(ExecutionMapUriResult(file: file));
+ return;
+ }
+ sendResponse(Response.invalidParameter(
+ request, 'file', 'Either file or uri must be provided'));
+ }
+}
diff --git a/pkg/analysis_server/lib/src/handler/legacy/execution_set_subscriptions.dart b/pkg/analysis_server/lib/src/handler/legacy/execution_set_subscriptions.dart
new file mode 100644
index 0000000..480f100a
--- /dev/null
+++ b/pkg/analysis_server/lib/src/handler/legacy/execution_set_subscriptions.dart
@@ -0,0 +1,26 @@
+// Copyright (c) 2022, 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 'package:analysis_server/protocol/protocol.dart';
+import 'package:analysis_server/protocol/protocol_generated.dart';
+import 'package:analysis_server/src/analysis_server.dart';
+import 'package:analysis_server/src/handler/legacy/legacy_handler.dart';
+import 'package:analysis_server/src/utilities/progress.dart';
+
+/// The handler for the `execution.setSubscriptions` request.
+class ExecutionSetSubscriptionsHandler extends LegacyHandler {
+ /// Initialize a newly created handler to be able to service requests for the
+ /// [server].
+ ExecutionSetSubscriptionsHandler(AnalysisServer server, Request request,
+ CancellationToken cancellationToken)
+ : super(server, request, cancellationToken);
+
+ @override
+ Future<void> handle() async {
+ // Under the analysis driver, setSubscriptions becomes a no-op.
+ sendResult(ExecutionSetSubscriptionsResult());
+ }
+}
diff --git a/pkg/analysis_server/lib/src/handler/legacy/kythe_get_kythe_entries.dart b/pkg/analysis_server/lib/src/handler/legacy/kythe_get_kythe_entries.dart
new file mode 100644
index 0000000..e9a57e8
--- /dev/null
+++ b/pkg/analysis_server/lib/src/handler/legacy/kythe_get_kythe_entries.dart
@@ -0,0 +1,75 @@
+// Copyright (c) 2022, 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 'package:analysis_server/protocol/protocol.dart';
+import 'package:analysis_server/protocol/protocol_generated.dart';
+import 'package:analysis_server/src/analysis_server.dart';
+import 'package:analysis_server/src/domain_abstract.dart';
+import 'package:analysis_server/src/handler/legacy/legacy_handler.dart';
+import 'package:analysis_server/src/plugin/result_merger.dart';
+import 'package:analysis_server/src/services/kythe/kythe_visitors.dart';
+import 'package:analysis_server/src/utilities/progress.dart';
+import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
+import 'package:analyzer_plugin/protocol/protocol_common.dart';
+import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
+
+/// The handler for the `kythe.getKytheEntries` request.
+class KytheGetKytheEntriesHandler extends LegacyHandler
+ with RequestHandlerMixin<AnalysisServer> {
+ /// Initialize a newly created handler to be able to service requests for the
+ /// [server].
+ KytheGetKytheEntriesHandler(AnalysisServer server, Request request,
+ CancellationToken cancellationToken)
+ : super(server, request, cancellationToken);
+
+ @override
+ Future<void> handle() async {
+ var file = KytheGetKytheEntriesParams.fromRequest(request).file;
+ var driver = server.getAnalysisDriver(file);
+ if (driver == null) {
+ sendResponse(Response.getKytheEntriesInvalidFile(request));
+ } else {
+ //
+ // Allow plugins to start computing entries.
+ //
+ var requestParams = plugin.KytheGetKytheEntriesParams(file);
+ var pluginFutures = server.pluginManager.broadcastRequest(
+ requestParams,
+ contextRoot: driver.analysisContext!.contextRoot,
+ );
+ //
+ // Compute entries generated by server.
+ //
+ var allResults = <KytheGetKytheEntriesResult>[];
+ var result = await server.getResolvedUnit(file);
+ if (result != null) {
+ var entries = <KytheEntry>[];
+ // TODO(brianwilkerson) Figure out how to get the list of files.
+ var files = <String>[];
+ result.unit.accept(KytheDartVisitor(server.resourceProvider, entries,
+ file, InheritanceManager3(), result.content));
+ allResults.add(KytheGetKytheEntriesResult(entries, files));
+ }
+ //
+ // Add the entries produced by plugins to the server-generated entries.
+ //
+ var responses = await waitForResponses(pluginFutures,
+ requestParameters: requestParams);
+ for (var response in responses) {
+ var result = plugin.KytheGetKytheEntriesResult.fromResponse(response);
+ allResults
+ .add(KytheGetKytheEntriesResult(result.entries, result.files));
+ }
+ //
+ // Return the result.
+ //
+ var merger = ResultMerger();
+ var mergedResults = merger.mergeKytheEntries(allResults);
+ sendResult(KytheGetKytheEntriesResult(
+ mergedResults.entries, mergedResults.files));
+ }
+ }
+}
diff --git a/pkg/analysis_server/lib/src/services/execution/execution_context.dart b/pkg/analysis_server/lib/src/services/execution/execution_context.dart
new file mode 100644
index 0000000..b3b9dd3
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/execution/execution_context.dart
@@ -0,0 +1,14 @@
+// Copyright (c) 2022, 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.
+
+class ExecutionContext {
+ /// The next execution context identifier to be returned.
+ int nextContextId = 0;
+
+ /// A table mapping execution context id's to the root of the context.
+ final Map<String, String> contextMap = {};
+
+ /// Initialize a newly created execution context.
+ ExecutionContext();
+}
diff --git a/pkg/analysis_server/test/domain_execution_test.dart b/pkg/analysis_server/test/domain_execution_test.dart
index 3bfafaf..42550dc 100644
--- a/pkg/analysis_server/test/domain_execution_test.dart
+++ b/pkg/analysis_server/test/domain_execution_test.dart
@@ -3,15 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:analysis_server/protocol/protocol_generated.dart';
-import 'package:analysis_server/src/analysis_server.dart';
import 'package:analysis_server/src/domain_execution.dart';
import 'package:analysis_server/src/protocol_server.dart';
-import 'package:analysis_server/src/server/crash_reporting_attachments.dart';
-import 'package:analysis_server/src/utilities/mocks.dart';
-import 'package:analysis_server/src/utilities/progress.dart';
-import 'package:analyzer/file_system/memory_file_system.dart';
-import 'package:analyzer/instrumentation/instrumentation.dart';
-import 'package:analyzer/src/generated/sdk.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -22,59 +15,24 @@
defineReflectiveSuite(() {
defineReflectiveTests(ExecutionDomainTest);
});
- group('ExecutionDomainHandler', () {
- var provider = MemoryResourceProvider();
- late AnalysisServer server;
- late ExecutionDomainHandler handler;
-
- setUp(() {
- server = AnalysisServer(
- MockServerChannel(),
- provider,
- AnalysisServerOptions(),
- DartSdkManager(''),
- CrashReportingAttachmentsBuilder.empty,
- InstrumentationService.NULL_SERVICE);
- handler = ExecutionDomainHandler(server);
- });
-
- group('createContext/deleteContext', () {
- test('create/delete multiple contexts', () {
- var request = ExecutionCreateContextParams('/a/b.dart').toRequest('0');
- var response = handler.handleRequest(request, NotCancelableToken())!;
- expect(response, isResponseSuccess('0'));
- var result = ExecutionCreateContextResult.fromResponse(response);
- var id0 = result.id;
-
- request = ExecutionCreateContextParams('/c/d.dart').toRequest('1');
- response = handler.handleRequest(request, NotCancelableToken())!;
- expect(response, isResponseSuccess('1'));
- result = ExecutionCreateContextResult.fromResponse(response);
- var id1 = result.id;
-
- expect(id0 == id1, isFalse);
-
- request = ExecutionDeleteContextParams(id0).toRequest('2');
- response = handler.handleRequest(request, NotCancelableToken())!;
- expect(response, isResponseSuccess('2'));
-
- request = ExecutionDeleteContextParams(id1).toRequest('3');
- response = handler.handleRequest(request, NotCancelableToken())!;
- expect(response, isResponseSuccess('3'));
- });
-
- test('delete non-existent context', () {
- var request = ExecutionDeleteContextParams('13').toRequest('0');
- var response = handler.handleRequest(request, NotCancelableToken());
- // TODO(brianwilkerson) It isn't currently specified to be an error if a
- // client attempts to delete a context that doesn't exist. Should it be?
-// expect(response, isResponseFailure('0'));
- expect(response, isResponseSuccess('0'));
- });
- });
-
- // TODO(brianwilkerson) Re-enable these tests if we re-enable the
- // execution.mapUri request.
+ // group('ExecutionDomainHandler', () {
+ // var provider = MemoryResourceProvider();
+ // late AnalysisServer server;
+ // late ExecutionDomainHandler handler;
+ //
+ // setUp(() {
+ // server = AnalysisServer(
+ // MockServerChannel(),
+ // provider,
+ // AnalysisServerOptions(),
+ // DartSdkManager(''),
+ // CrashReportingAttachmentsBuilder.empty,
+ // InstrumentationService.NULL_SERVICE);
+ // handler = ExecutionDomainHandler(server, server.executionContext);
+ // });
+ //
+ // // TODO(brianwilkerson) Re-enable these tests if we re-enable the
+ // // execution.mapUri request.
// group('mapUri', () {
// String contextId;
//
@@ -155,7 +113,7 @@
// expect(response, isResponseFailure('6'));
// });
// });
- });
+// });
}
@reflectiveTest
@@ -166,16 +124,49 @@
Future<void> setUp() async {
super.setUp();
await createProject();
- handler = ExecutionDomainHandler(server);
- _createExecutionContext(testFile);
+ handler = ExecutionDomainHandler(server, server.executionContext);
+ await _createExecutionContext(testFile);
}
@override
- void tearDown() {
- _disposeExecutionContext();
+ Future<void> tearDown() async {
+ await _disposeExecutionContext();
super.tearDown();
}
+ Future<void> test_createAndDeleteMultipleContexts() async {
+ var request = ExecutionCreateContextParams('/a/b.dart').toRequest('0');
+ var response = await waitResponse(request);
+ expect(response, isResponseSuccess('0'));
+ var result = ExecutionCreateContextResult.fromResponse(response);
+ var id0 = result.id;
+
+ request = ExecutionCreateContextParams('/c/d.dart').toRequest('1');
+ response = await waitResponse(request);
+ expect(response, isResponseSuccess('1'));
+ result = ExecutionCreateContextResult.fromResponse(response);
+ var id1 = result.id;
+
+ expect(id0 == id1, isFalse);
+
+ request = ExecutionDeleteContextParams(id0).toRequest('2');
+ response = await waitResponse(request);
+ expect(response, isResponseSuccess('2'));
+
+ request = ExecutionDeleteContextParams(id1).toRequest('3');
+ response = await waitResponse(request);
+ expect(response, isResponseSuccess('3'));
+ }
+
+ Future<void> test_deleteNonExistentContext() async {
+ var request = ExecutionDeleteContextParams('13').toRequest('0');
+ var response = await waitResponse(request);
+ // TODO(brianwilkerson) It isn't currently specified to be an error if a
+ // client attempts to delete a context that doesn't exist. Should it be?
+// expect(response, isResponseFailure('0'));
+ expect(response, isResponseSuccess('0'));
+ }
+
Future<void> test_getSuggestions() async {
var code = r'''
class A {
@@ -212,48 +203,48 @@
expect(result.suggestions, isEmpty);
}
- void test_mapUri_file() {
+ Future<void> test_mapUri_file() async {
var path = newFile2('/a/b.dart', '').path;
// map the file
- var result = _mapUri(file: path);
+ var result = await _mapUri(file: path);
expect(result.file, isNull);
expect(result.uri, Uri.file(path).toString());
}
- void test_mapUri_file_dartUriKind() {
- var path = _mapUri(uri: 'dart:async').file;
+ Future<void> test_mapUri_file_dartUriKind() async {
+ var path = (await _mapUri(uri: 'dart:async')).file;
// map file
- var result = _mapUri(file: path);
+ var result = await _mapUri(file: path);
expect(result.file, isNull);
expect(result.uri, 'dart:async');
}
- void test_mapUri_uri() {
+ Future<void> test_mapUri_uri() async {
var path = newFile2('/a/b.dart', '').path;
// map the uri
- var result = _mapUri(uri: Uri.file(path).toString());
+ var result = await _mapUri(uri: Uri.file(path).toString());
expect(result.file, convertPath('/a/b.dart'));
expect(result.uri, isNull);
}
- void _createExecutionContext(String path) {
+ Future<void> _createExecutionContext(String path) async {
var request = ExecutionCreateContextParams(path).toRequest('0');
- var response = handler.handleRequest(request, NotCancelableToken())!;
+ var response = await waitResponse(request);
expect(response, isResponseSuccess('0'));
var result = ExecutionCreateContextResult.fromResponse(response);
contextId = result.id;
}
- void _disposeExecutionContext() {
+ Future<void> _disposeExecutionContext() async {
var request = ExecutionDeleteContextParams(contextId).toRequest('1');
- var response = handler.handleRequest(request, NotCancelableToken());
+ var response = await waitResponse(request);
expect(response, isResponseSuccess('1'));
}
- ExecutionMapUriResult _mapUri({String? file, String? uri}) {
+ Future<ExecutionMapUriResult> _mapUri({String? file, String? uri}) async {
var request =
ExecutionMapUriParams(contextId, file: file, uri: uri).toRequest('2');
- var response = handler.handleRequest(request, NotCancelableToken())!;
+ var response = await waitResponse(request);
expect(response, isResponseSuccess('2'));
return ExecutionMapUriResult.fromResponse(response);
}
diff --git a/pkg/analyzer/lib/src/workspace/bazel.dart b/pkg/analyzer/lib/src/workspace/bazel.dart
index f21475c..0e2aa32 100644
--- a/pkg/analyzer/lib/src/workspace/bazel.dart
+++ b/pkg/analyzer/lib/src/workspace/bazel.dart
@@ -122,7 +122,12 @@
}
String packageName = uriPath.substring(0, slash);
+
String fileUriPart = uriPath.substring(slash + 1);
+ if (fileUriPart.isEmpty) {
+ return null;
+ }
+
String filePath = fileUriPart.replaceAll('/', _context.separator);
if (!packageName.contains('.')) {
diff --git a/pkg/analyzer/test/src/workspace/bazel_test.dart b/pkg/analyzer/test/src/workspace/bazel_test.dart
index 2aabbdc..b29037d 100644
--- a/pkg/analyzer/test/src/workspace/bazel_test.dart
+++ b/pkg/analyzer/test/src/workspace/bazel_test.dart
@@ -331,6 +331,15 @@
expect(source, isNull);
}
+ void test_resolveAbsolute_null_emptyFileUriPart() {
+ _addResources([
+ '/workspace/WORKSPACE',
+ ]);
+ var uri = Uri.parse('package:foo.bar/');
+ var source = resolver.resolveAbsolute(uri);
+ expect(source, isNull);
+ }
+
void test_resolveAbsolute_null_noSlash() {
_addResources([
'/workspace/WORKSPACE',
diff --git a/pkg/compiler/lib/src/kernel/transformations/async_lowering.dart b/pkg/compiler/lib/src/kernel/transformations/async_lowering.dart
new file mode 100644
index 0000000..ec145b0
--- /dev/null
+++ b/pkg/compiler/lib/src/kernel/transformations/async_lowering.dart
@@ -0,0 +1,111 @@
+// Copyright (c) 2022, 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.
+
+// @dart=2.12
+
+import 'package:kernel/ast.dart';
+import 'package:kernel/core_types.dart';
+import 'package:kernel/kernel.dart';
+
+class _FunctionData {
+ final List<AwaitExpression> awaits = [];
+ final List<ReturnStatement> returnStatements = [];
+
+ _FunctionData();
+}
+
+/// Handles simplification of basic 'async' functions into [Future]s.
+///
+/// The JS expansion of an async/await function is a complex state machine.
+/// In many cases 'async' functions either do not use 'await' or have very
+/// simple 'await' logic which is easily captured by [Future]. By making this
+/// transformation we avoid substantial over head from the state machine.
+class AsyncLowering {
+ final List<_FunctionData> _functions = [];
+ final CoreTypes _coreTypes;
+
+ AsyncLowering(this._coreTypes);
+
+ bool _shouldTryAsyncLowering(FunctionNode node) =>
+ node.asyncMarker == AsyncMarker.Async;
+
+ void enterFunction(FunctionNode node) {
+ _functions.add(_FunctionData());
+ }
+
+ void _exitFunction() {
+ _functions.removeLast();
+ }
+
+ void _updateFunctionBody(FunctionNode node, Statement? newBody) {
+ node.body = newBody;
+ newBody?.parent = node;
+ }
+
+ void _wrapBodySync(FunctionNode node) {
+ node.asyncMarker = AsyncMarker.Sync;
+ final futureValueType = node.futureValueType!;
+ _updateFunctionBody(
+ node,
+ ExpressionStatement(StaticInvocation(
+ _coreTypes.futureSyncFactory,
+ Arguments([
+ FunctionExpression(FunctionNode(node.body,
+ returnType: FutureOrType(
+ futureValueType, futureValueType.nullability)))
+ ], types: [
+ futureValueType
+ ]))));
+ }
+
+ void _transformAsyncFunctionNode(FunctionNode node) {
+ assert(_functions.isNotEmpty, 'Must be within a function scope.');
+ final functionData = _functions.last;
+ if (functionData.awaits.isEmpty) {
+ // There are no awaits within this function so convert to a simple
+ // Future.sync call with the function's returned expressions. We use
+ // this over Future.value because the expression can throw and async
+ // functions defer exceptions as errors on returned Future. Future.sync
+ // has the same deferred behavior.
+ //
+ // Before:
+ // Future<int> foo() async {
+ // doSomething();
+ // return 3;
+ // }
+ //
+ // After:
+ // Future<int> foo() {
+ // return Future.sync(() {
+ // doSomething();
+ // return 3;
+ // });
+ // }
+ //
+ // Edge cases to consider:
+ // 1) Function doesn't include a return expression. (e.g. Future<void>)
+ // In this case we call Future.value(null).
+ // 2) The returned expression might itself be a future. Future.sync will
+ // handle the unpacking of the returned future in that case.
+ // 3) The return type of the function is not specified. In this case we
+ // instantiate Future.value with 'dynamic'.
+ _wrapBodySync(node);
+ }
+ }
+
+ void transformFunctionNodeAndExit(FunctionNode node) {
+ if (_shouldTryAsyncLowering(node)) _transformAsyncFunctionNode(node);
+ _exitFunction();
+ }
+
+ void visitAwaitExpression(AwaitExpression expression) {
+ assert(_functions.isNotEmpty,
+ 'Awaits must be within the scope of a function.');
+ _functions.last.awaits.add(expression);
+ }
+
+ void visitReturnStatement(ReturnStatement statement) {
+ _functions.last.returnStatements.add(statement);
+ }
+}
diff --git a/pkg/compiler/lib/src/kernel/transformations/lowering.dart b/pkg/compiler/lib/src/kernel/transformations/lowering.dart
index 234eac1..ba289ae 100644
--- a/pkg/compiler/lib/src/kernel/transformations/lowering.dart
+++ b/pkg/compiler/lib/src/kernel/transformations/lowering.dart
@@ -9,6 +9,7 @@
import 'package:kernel/core_types.dart' show CoreTypes;
import '../../options.dart';
+import 'async_lowering.dart';
import 'factory_specializer.dart';
import 'late_lowering.dart';
@@ -26,13 +27,18 @@
class _Lowering extends Transformer {
final FactorySpecializer factorySpecializer;
final LateLowering _lateLowering;
+ final AsyncLowering? _asyncLowering;
Member? _currentMember;
_Lowering(
CoreTypes coreTypes, ClassHierarchy hierarchy, CompilerOptions? _options)
: factorySpecializer = FactorySpecializer(coreTypes, hierarchy),
- _lateLowering = LateLowering(coreTypes, _options);
+ _lateLowering = LateLowering(coreTypes, _options),
+ _asyncLowering =
+ (_options?.features.simpleAsyncToFuture.isEnabled ?? false)
+ ? AsyncLowering(coreTypes)
+ : null;
@override
TreeNode defaultMember(Member node) {
@@ -56,8 +62,10 @@
@override
TreeNode visitFunctionNode(FunctionNode node) {
_lateLowering.enterFunction();
+ _asyncLowering?.enterFunction(node);
node.transformChildren(this);
_lateLowering.exitFunction();
+ _asyncLowering?.transformFunctionNodeAndExit(node);
return node;
}
@@ -85,4 +93,18 @@
node.transformChildren(this);
return _lateLowering.transformField(node, _currentMember!);
}
+
+ @override
+ TreeNode visitAwaitExpression(AwaitExpression expression) {
+ _asyncLowering?.visitAwaitExpression(expression);
+ expression.transformChildren(this);
+ return expression;
+ }
+
+ @override
+ TreeNode visitReturnStatement(ReturnStatement statement) {
+ _asyncLowering?.visitReturnStatement(statement);
+ statement.transformChildren(this);
+ return statement;
+ }
}
diff --git a/pkg/compiler/lib/src/options.dart b/pkg/compiler/lib/src/options.dart
index 554f833..23f019d 100644
--- a/pkg/compiler/lib/src/options.dart
+++ b/pkg/compiler/lib/src/options.dart
@@ -85,6 +85,10 @@
/// dump-info's output.
FeatureOption newDumpInfo = FeatureOption('new-dump-info');
+ /// Whether to implement some simple async functions using Futures directly
+ /// to reduce generated code size.
+ FeatureOption simpleAsyncToFuture = FeatureOption('simple-async-to-future');
+
/// [FeatureOption]s which are shipped and cannot be toggled.
late final List<FeatureOption> shipped = [
newHolders,
@@ -97,7 +101,11 @@
];
/// [FeatureOption]s which default to disabled.
- late final List<FeatureOption> canary = [writeUtf8, newDumpInfo];
+ late final List<FeatureOption> canary = [
+ writeUtf8,
+ newDumpInfo,
+ simpleAsyncToFuture
+ ];
/// Forces canary feature on. This must run after [Option].parse.
void forceCanary() {
diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart
index 6631460..b88ee02 100644
--- a/pkg/dart2wasm/lib/code_generator.dart
+++ b/pkg/dart2wasm/lib/code_generator.dart
@@ -2092,8 +2092,23 @@
@override
w.ValueType visitAsExpression(AsExpression node, w.ValueType expectedType) {
- // TODO(joshualitt): Emit type test and throw exception on failure
- return wrap(node.operand, expectedType);
+ w.Label asCheckBlock = b.block();
+ wrap(node.operand, translator.topInfo.nullableType);
+ w.Local operand = addLocal(translator.topInfo.nullableType);
+ b.local_tee(operand);
+
+ // We lower an `as` expression to a type test, throwing a [TypeError] if
+ // the type test fails.
+ emitTypeTest(node.type, dartTypeOf(node.operand), node);
+ b.br_if(asCheckBlock);
+ b.local_get(operand);
+ _makeType(node.type, node);
+ _call(translator.stackTraceCurrent.reference);
+ _call(translator.throwAsCheckError.reference);
+ b.unreachable();
+ b.end();
+ b.local_get(operand);
+ return operand.type;
}
}
diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart
index 3bf91b4..1eacf81 100644
--- a/pkg/dart2wasm/lib/translator.dart
+++ b/pkg/dart2wasm/lib/translator.dart
@@ -95,6 +95,7 @@
late final Procedure stringEquals;
late final Procedure stringInterpolate;
late final Procedure throwNullCheckError;
+ late final Procedure throwAsCheckError;
late final Procedure mapFactory;
late final Procedure mapPut;
late final Procedure immutableMapIndexNullable;
@@ -192,6 +193,8 @@
.firstWhere((p) => p.name.text == "_interpolate");
throwNullCheckError = typeErrorClass.procedures
.firstWhere((p) => p.name.text == "_throwNullCheckError");
+ throwAsCheckError = typeErrorClass.procedures
+ .firstWhere((p) => p.name.text == "_throwAsCheckError");
mapFactory = lookupCollection("LinkedHashMap").procedures.firstWhere(
(p) => p.kind == ProcedureKind.Factory && p.name.text == "_default");
mapPut = lookupCollection("_CompactLinkedCustomHashMap")
diff --git a/pkg/dds/CHANGELOG.md b/pkg/dds/CHANGELOG.md
index 3cb8dd8..c0f272a 100644
--- a/pkg/dds/CHANGELOG.md
+++ b/pkg/dds/CHANGELOG.md
@@ -1,5 +1,8 @@
+# 2.2.1
+- Reduce latency of `streamListen` calls through improved locking behavior.
+
# 2.2.0
-- Add support for serving DevTools via `package:dds/devtools_server.dart`
+- Add support for serving DevTools via `package:dds/devtools_server.dart`.
# 2.1.7
- Re-release 2.1.6+1.
diff --git a/pkg/dds/lib/src/stream_manager.dart b/pkg/dds/lib/src/stream_manager.dart
index cde830a..057d29d 100644
--- a/pkg/dds/lib/src/stream_manager.dart
+++ b/pkg/dds/lib/src/stream_manager.dart
@@ -173,7 +173,11 @@
String stream, {
bool? includePrivates,
}) async {
- await _streamSubscriptionMutex.runGuarded(
+ // Weakly guard stream listening as it's safe to perform multiple listens
+ // on a stream concurrently. However, cancelling streams while listening
+ // to them concurrently can put things in a bad state. Use weak guarding to
+ // improve latency of stream subscription.
+ await _streamSubscriptionMutex.runGuardedWeak(
() async {
assert(stream.isNotEmpty);
bool streamNewlySubscribed = false;
diff --git a/pkg/dds/lib/src/utils/mutex.dart b/pkg/dds/lib/src/utils/mutex.dart
index b697e78..9aff468 100644
--- a/pkg/dds/lib/src/utils/mutex.dart
+++ b/pkg/dds/lib/src/utils/mutex.dart
@@ -11,9 +11,9 @@
/// Executes a block of code containing asynchronous calls atomically.
///
/// If no other asynchronous context is currently executing within
- /// [criticalSection], it will immediately be called. Otherwise, the caller
- /// will be suspended and entered into a queue to be resumed once the lock is
- /// released.
+ /// [criticalSection] or a [runGuardedWeak] scope, it will immediately be
+ /// called. Otherwise, the caller will be suspended and entered into a queue
+ /// to be resumed once the lock is released.
Future<T> runGuarded<T>(FutureOr<T> Function() criticalSection) async {
try {
await _acquireLock();
@@ -23,11 +23,49 @@
}
}
- Future<void> _acquireLock() async {
+ /// Executes a block of code containing asynchronous calls, allowing for other
+ /// weakly guarded sections to be executed concurrently.
+ ///
+ /// If no other asynchronous context is currently executing within a
+ /// [runGuarded] scope, [criticalSection] will immediately be called.
+ /// Otherwise, the caller will be suspended and entered into a queue to be
+ /// resumed once the lock is released.
+ Future<T> runGuardedWeak<T>(FutureOr<T> Function() criticalSection) async {
+ _weakGuards++;
+ if (_weakGuards == 1) {
+ // Reinitialize if this is the only weakly guarded scope.
+ _outstandingReadersCompleter = Completer<void>();
+ }
+ final result;
+ try {
+ await _acquireLock(strong: false);
+ result = await criticalSection();
+ } finally {
+ _weakGuards--;
+ if (_weakGuards == 0) {
+ // Notify callers of `runGuarded` that they can try to execute again.
+ _outstandingReadersCompleter.complete();
+ }
+ }
+ return result;
+ }
+
+ Future<void> _acquireLock({bool strong = true}) async {
+ // The lock cannot be acquired by `runGuarded` if there is outstanding
+ // execution in weakly guarded sections. Loop in case we've entered another
+ // weakly guarded scope before we've woken up.
+ while (strong && _weakGuards > 0) {
+ await _outstandingReadersCompleter.future;
+ }
if (!_locked) {
- _locked = true;
+ if (strong) {
+ // Don't actually lock for weakly guarded sections, just make sure the
+ // lock isn't held before entering.
+ _locked = true;
+ }
return;
}
+
final request = Completer<void>();
_outstandingRequests.add(request);
await request.future;
@@ -41,6 +79,8 @@
}
}
+ int _weakGuards = 0;
bool _locked = false;
+ var _outstandingReadersCompleter = Completer<void>();
final _outstandingRequests = Queue<Completer<void>>();
}
diff --git a/pkg/dds/pubspec.yaml b/pkg/dds/pubspec.yaml
index 5ad059d..1430222 100644
--- a/pkg/dds/pubspec.yaml
+++ b/pkg/dds/pubspec.yaml
@@ -3,7 +3,7 @@
A library used to spawn the Dart Developer Service, used to communicate with
a Dart VM Service instance.
-version: 2.2.0
+version: 2.2.1
homepage: https://github.com/dart-lang/sdk/tree/master/pkg/dds
diff --git a/pkg/dds/test/devtools_server/bind_test.dart b/pkg/dds/test/devtools_server/bind_test.dart
new file mode 100644
index 0000000..efb13bb
--- /dev/null
+++ b/pkg/dds/test/devtools_server/bind_test.dart
@@ -0,0 +1,51 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:test/test.dart';
+
+import 'utils/server_driver.dart';
+
+late final DevToolsServerTestController testController;
+
+void main() {
+ testController = DevToolsServerTestController();
+
+ setUp(() async {
+ await testController.setUp();
+ });
+
+ tearDown(() async {
+ await testController.tearDown();
+ });
+
+ test('can bind to next available port', () async {
+ final server1 = await DevToolsServerDriver.create(port: 8855);
+ try {
+ // Wait for the first server to start up and ensure it got the
+ // expected port.
+ final event1 = (await server1.stdout.firstWhere(
+ (map) => map!['event'] == 'server.started',
+ ))!;
+ expect(event1['params']['port'], 8855);
+
+ // Now spawn another requesting the same port and ensure it got the next
+ // port number.
+ final server2 = await DevToolsServerDriver.create(
+ port: 8855,
+ tryPorts: 2,
+ );
+ try {
+ final event2 = (await server2.stdout.firstWhere(
+ (map) => map!['event'] == 'server.started',
+ ))!;
+
+ expect(event2['params']['port'], 8856);
+ } finally {
+ server2.kill();
+ }
+ } finally {
+ server1.kill();
+ }
+ }, timeout: const Timeout.factor(10));
+}
diff --git a/pkg/dds/test/devtools_server/devtools_client_test.dart b/pkg/dds/test/devtools_server/client_test.dart
similarity index 100%
rename from pkg/dds/test/devtools_server/devtools_client_test.dart
rename to pkg/dds/test/devtools_server/client_test.dart
diff --git a/pkg/dds/test/devtools_server/devtools_server_connection_test.dart b/pkg/dds/test/devtools_server/devtools_server_connection_test.dart
deleted file mode 100644
index 5bb42a8..0000000
--- a/pkg/dds/test/devtools_server/devtools_server_connection_test.dart
+++ /dev/null
@@ -1,103 +0,0 @@
-// Copyright 2022 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:devtools_shared/devtools_test_utils.dart';
-import 'package:test/test.dart';
-
-import 'devtools_server_driver.dart';
-
-// Note: this test is broken out from devtools_server_test.dart so that the
-// tests run faster and we do not have to mark them as slow.
-
-late final DevToolsServerTestController testController;
-
-void main() {
- testController = DevToolsServerTestController();
-
- setUp(() async {
- await testController.setUp();
- });
-
- tearDown(() async {
- await testController.tearDown();
- });
-
- for (final bool useVmService in [true, false]) {
- group('Server (${useVmService ? 'VM Service' : 'API'})', () {
- test(
- 'DevTools connects back to server API and registers that it is connected',
- () async {
- // Register the VM.
- await testController.send(
- 'vm.register',
- {'uri': testController.appFixture.serviceUri.toString()},
- );
-
- // Send a request to launch DevTools in a browser.
- await testController.sendLaunchDevToolsRequest(
- useVmService: useVmService,
- );
-
- final serverResponse =
- await testController.waitForClients(requiredConnectionState: true);
- expect(serverResponse, isNotNull);
- expect(serverResponse['clients'], hasLength(1));
- expect(serverResponse['clients'][0]['hasConnection'], isTrue);
- expect(
- serverResponse['clients'][0]['vmServiceUri'],
- testController.appFixture.serviceUri.toString(),
- );
- }, timeout: const Timeout.factor(10));
-
- test('DevTools reports disconnects from a VM', () async {
- // Register the VM.
- await testController.send(
- 'vm.register',
- {'uri': testController.appFixture.serviceUri.toString()},
- );
-
- // Send a request to launch DevTools in a browser.
- await testController.sendLaunchDevToolsRequest(
- useVmService: useVmService,
- );
-
- // Wait for the DevTools to inform server that it's connected.
- await testController.waitForClients(requiredConnectionState: true);
-
- // Terminate the VM.
- await testController.appFixture.teardown();
-
- // Ensure the client is marked as disconnected.
- final serverResponse = await testController.waitForClients(
- requiredConnectionState: false,
- );
- expect(serverResponse['clients'], hasLength(1));
- expect(serverResponse['clients'][0]['hasConnection'], isFalse);
- expect(serverResponse['clients'][0]['vmServiceUri'], isNull);
- }, timeout: const Timeout.factor(20));
-
- test('server removes clients that disconnect from the API', () async {
- final event = await testController.serverStartedEvent.future;
-
- // Spawn our own Chrome process so we can terminate it.
- final devToolsUri =
- 'http://${event['params']['host']}:${event['params']['port']}';
- final chrome = await Chrome.locate()!.start(url: devToolsUri);
-
- // Wait for DevTools to inform server that it's connected.
- await testController.waitForClients();
-
- // Close the browser, which will disconnect DevTools SSE connection
- // back to the server.
- chrome.kill();
-
- // Await a long delay to wait for the SSE client to close.
- await delay(duration: const Duration(seconds: 15));
-
- // Ensure the client is completely removed from the list.
- await testController.waitForClients(expectNone: true);
- }, timeout: const Timeout.factor(20));
- });
- }
-}
diff --git a/pkg/dds/test/devtools_server/devtools_server_test.dart b/pkg/dds/test/devtools_server/devtools_server_test.dart
deleted file mode 100644
index 2a0ce68..0000000
--- a/pkg/dds/test/devtools_server/devtools_server_test.dart
+++ /dev/null
@@ -1,369 +0,0 @@
-// Copyright 2022 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:convert';
-import 'dart:io';
-
-import 'package:dds/devtools_server.dart';
-import 'package:dds/src/devtools/machine_mode_command_handler.dart';
-import 'package:devtools_shared/devtools_shared.dart';
-import 'package:devtools_shared/devtools_test_utils.dart';
-import 'package:test/test.dart';
-
-import 'devtools_server_driver.dart';
-
-late final DevToolsServerTestController testController;
-
-void main() {
- testController = DevToolsServerTestController();
-
- setUp(() async {
- await testController.setUp();
- });
-
- tearDown(() async {
- await testController.tearDown();
- });
-
- test('registers service', () async {
- final serverResponse = await testController.send(
- 'vm.register',
- {'uri': testController.appFixture.serviceUri.toString()},
- );
- expect(serverResponse['success'], isTrue);
-
- // Expect the VM service to see the launchDevTools service registered.
- expect(
- testController.registeredServices,
- contains(DevToolsServer.launchDevToolsService),
- );
- }, timeout: const Timeout.factor(10));
-
- test('can bind to next available port', () async {
- final server1 = await DevToolsServerDriver.create(port: 8855);
- try {
- // Wait for the first server to start up and ensure it got the
- // expected port.
- final event1 = (await server1.stdout.firstWhere(
- (map) => map!['event'] == 'server.started',
- ))!;
- expect(event1['params']['port'], 8855);
-
- // Now spawn another requesting the same port and ensure it got the next
- // port number.
- final server2 = await DevToolsServerDriver.create(
- port: 8855,
- tryPorts: 2,
- );
- try {
- final event2 = (await server2.stdout.firstWhere(
- (map) => map!['event'] == 'server.started',
- ))!;
-
- expect(event2['params']['port'], 8856);
- } finally {
- server2.kill();
- }
- } finally {
- server1.kill();
- }
- }, timeout: const Timeout.factor(10));
-
- test('allows embedding without flag', () async {
- final server = await DevToolsServerDriver.create();
- final httpClient = HttpClient();
- late HttpClientResponse resp;
- try {
- final startedEvent = (await server.stdout.firstWhere(
- (map) => map!['event'] == 'server.started',
- ))!;
- final host = startedEvent['params']['host'];
- final port = startedEvent['params']['port'];
-
- final req = await httpClient.get(host, port, '/');
- resp = await req.close();
- expect(resp.headers.value('x-frame-options'), isNull);
- } finally {
- httpClient.close();
- await resp.drain();
- server.kill();
- }
- }, timeout: const Timeout.factor(10));
-
- test('does not allow embedding with flag', () async {
- final server = await DevToolsServerDriver.create(
- additionalArgs: ['--no-allow-embedding'],
- );
- final httpClient = HttpClient();
- late HttpClientResponse resp;
- try {
- final startedEvent = (await server.stdout.firstWhere(
- (map) => map!['event'] == 'server.started',
- ))!;
- final host = startedEvent['params']['host'];
- final port = startedEvent['params']['port'];
-
- final req = await httpClient.get(host, port, '/');
- resp = await req.close();
- expect(resp.headers.value('x-frame-options'), 'SAMEORIGIN');
- } finally {
- httpClient.close();
- await resp.drain();
- server.kill();
- }
- }, timeout: const Timeout.factor(10));
-
- test('Analytics Survey', () async {
- var serverResponse = await testController.send('devTools.survey', {
- 'surveyRequest': 'copyAndCreateDevToolsFile',
- });
- expect(serverResponse, isNotNull);
- expect(serverResponse['success'], isTrue);
-
- serverResponse = await testController.send('devTools.survey', {
- 'surveyRequest': apiSetActiveSurvey,
- 'value': 'Q3-2019',
- });
- expect(serverResponse, isNotNull);
- expect(serverResponse['success'], isTrue);
- expect(serverResponse['activeSurvey'], 'Q3-2019');
-
- serverResponse = await testController.send('devTools.survey', {
- 'surveyRequest': apiIncrementSurveyShownCount,
- });
- expect(serverResponse, isNotNull);
- expect(serverResponse['activeSurvey'], 'Q3-2019');
- expect(serverResponse['surveyShownCount'], 1);
-
- serverResponse = await testController.send('devTools.survey', {
- 'surveyRequest': apiIncrementSurveyShownCount,
- });
- expect(serverResponse, isNotNull);
- expect(serverResponse['activeSurvey'], 'Q3-2019');
- expect(serverResponse['surveyShownCount'], 2);
-
- serverResponse = await testController.send('devTools.survey', {
- 'surveyRequest': apiGetSurveyShownCount,
- });
- expect(serverResponse, isNotNull);
- expect(serverResponse['activeSurvey'], 'Q3-2019');
- expect(serverResponse['surveyShownCount'], 2);
-
- serverResponse = await testController.send('devTools.survey', {
- 'surveyRequest': apiGetSurveyActionTaken,
- });
- expect(serverResponse, isNotNull);
- expect(serverResponse['activeSurvey'], 'Q3-2019');
- expect(serverResponse['surveyActionTaken'], isFalse);
-
- serverResponse = await testController.send('devTools.survey', {
- 'surveyRequest': apiSetSurveyActionTaken,
- 'value': json.encode(true),
- });
- expect(serverResponse, isNotNull);
- expect(serverResponse['activeSurvey'], 'Q3-2019');
- expect(serverResponse['surveyActionTaken'], isTrue);
-
- serverResponse = await testController.send('devTools.survey', {
- 'surveyRequest': MachineModeCommandHandler.restoreDevToolsFile,
- });
- expect(serverResponse, isNotNull);
- expect(serverResponse['success'], isTrue);
- expect(
- serverResponse['content'],
- '{\n'
- ' \"Q3-2019\": {\n'
- ' \"surveyActionTaken\": true,\n'
- ' \"surveyShownCount\": 2\n'
- ' }\n'
- '}\n',
- );
- }, timeout: const Timeout.factor(10));
-
- for (final bool useVmService in [true, false]) {
- group('Server (${useVmService ? 'VM Service' : 'API'})', () {
- test('can launch on a specific page', () async {
- // Register the VM.
- await testController.send(
- 'vm.register',
- {'uri': testController.appFixture.serviceUri.toString()},
- );
-
- // Send a request to launch at a certain page.
- await testController.sendLaunchDevToolsRequest(
- useVmService: useVmService,
- page: 'memory',
- );
-
- final serverResponse =
- await testController.waitForClients(requiredPage: 'memory');
- expect(serverResponse, isNotNull);
- expect(serverResponse['clients'], hasLength(1));
- expect(serverResponse['clients'][0]['hasConnection'], isTrue);
- expect(
- serverResponse['clients'][0]['vmServiceUri'],
- testController.appFixture.serviceUri.toString(),
- );
- expect(serverResponse['clients'][0]['currentPage'], 'memory');
- }, timeout: const Timeout.factor(10));
-
- test('can switch page', () async {
- await testController.send(
- 'vm.register',
- {'uri': testController.appFixture.serviceUri.toString()},
- );
-
- // Launch on the memory page and wait for the connection.
- await testController.sendLaunchDevToolsRequest(
- useVmService: useVmService,
- page: 'memory',
- );
- await testController.waitForClients(requiredPage: 'memory');
-
- // Re-launch, allowing reuse and with a different page.
- await testController.sendLaunchDevToolsRequest(
- useVmService: useVmService,
- reuseWindows: true,
- page: 'logging',
- );
-
- final serverResponse =
- await testController.waitForClients(requiredPage: 'logging');
- expect(serverResponse, isNotNull);
- expect(serverResponse['clients'], hasLength(1));
- expect(serverResponse['clients'][0]['hasConnection'], isTrue);
- expect(
- serverResponse['clients'][0]['vmServiceUri'],
- testController.appFixture.serviceUri.toString(),
- );
- expect(serverResponse['clients'][0]['currentPage'], 'logging');
- }, timeout: const Timeout.factor(20));
-
- test('Server reuses DevTools instance if already connected to same VM',
- () async {
- // Register the VM.
- await testController.send(
- 'vm.register',
- {'uri': testController.appFixture.serviceUri.toString()},
- );
-
- // Send a request to launch DevTools in a browser.
- await testController.sendLaunchDevToolsRequest(
- useVmService: useVmService,
- );
-
- {
- final serverResponse = await testController.waitForClients(
- requiredConnectionState: true,
- );
- expect(serverResponse['clients'], hasLength(1));
- }
-
- // Request again, allowing reuse, and server emits an event saying the
- // window was reused.
- final launchResponse = await testController.sendLaunchDevToolsRequest(
- useVmService: useVmService,
- reuseWindows: true,
- );
- expect(launchResponse['reused'], isTrue);
-
- // Ensure there's still only one connection (eg. we didn't spawn a new one
- // we reused the existing one).
- final serverResponse =
- await testController.waitForClients(requiredConnectionState: true);
- expect(serverResponse['clients'], hasLength(1));
- }, timeout: const Timeout.factor(20));
-
- test('Server does not reuse DevTools instance if embedded', () async {
- // Register the VM.
- await testController.send(
- 'vm.register',
- {'uri': testController.appFixture.serviceUri.toString()},
- );
-
- // Spawn an embedded version of DevTools in a browser.
- final event = await testController.serverStartedEvent.future;
- final devToolsUri =
- 'http://${event['params']['host']}:${event['params']['port']}';
- final launchUrl = '$devToolsUri/?embed=true&page=logging'
- '&uri=${Uri.encodeQueryComponent(testController.appFixture.serviceUri.toString())}';
- final chrome = await Chrome.locate()!.start(url: launchUrl);
- try {
- {
- final serverResponse = await testController.waitForClients(
- requiredConnectionState: true,
- );
- expect(serverResponse['clients'], hasLength(1));
- }
-
- // Send a request to the server to launch and ensure it did
- // not reuse the existing connection. Launch it on a different page
- // so we can easily tell once this one has connected.
- final launchResponse = await testController.sendLaunchDevToolsRequest(
- useVmService: useVmService,
- reuseWindows: true,
- page: 'memory',
- );
- expect(launchResponse['reused'], isFalse);
-
- // Ensure there's now two connections.
- final serverResponse = await testController.waitForClients(
- requiredConnectionState: true,
- requiredPage: 'memory',
- );
- expect(serverResponse['clients'], hasLength(2));
- } finally {
- chrome.kill();
- }
- }, timeout: const Timeout.factor(20));
-
- test('Server reuses DevTools instance if not connected to a VM',
- () async {
- // Register the VM.
- await testController.send(
- 'vm.register',
- {'uri': testController.appFixture.serviceUri.toString()},
- );
-
- // Send a request to launch DevTools in a browser.
- await testController.sendLaunchDevToolsRequest(
- useVmService: useVmService,
- );
-
- // Wait for the DevTools to inform server that it's connected.
- await testController.waitForClients(requiredConnectionState: true);
-
- // Terminate the VM.
- await testController.appFixture.teardown();
-
- // Ensure the client is marked as disconnected.
- await testController.waitForClients(requiredConnectionState: false);
-
- // Start up a new app.
- await testController.startApp();
- await testController.send(
- 'vm.register',
- {'uri': testController.appFixture.serviceUri.toString()},
- );
-
- // Send a new request to launch.
- await testController.sendLaunchDevToolsRequest(
- useVmService: useVmService,
- reuseWindows: true,
- notify: true,
- );
-
- // Ensure we now have a single connected client.
- final serverResponse =
- await testController.waitForClients(requiredConnectionState: true);
- expect(serverResponse['clients'], hasLength(1));
- expect(serverResponse['clients'][0]['hasConnection'], isTrue);
- expect(
- serverResponse['clients'][0]['vmServiceUri'],
- testController.appFixture.serviceUri.toString(),
- );
- }, timeout: const Timeout.factor(20));
- });
- }
-}
diff --git a/pkg/dds/test/devtools_server/embedding_test.dart b/pkg/dds/test/devtools_server/embedding_test.dart
new file mode 100644
index 0000000..99adce4
--- /dev/null
+++ b/pkg/dds/test/devtools_server/embedding_test.dart
@@ -0,0 +1,44 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:test/test.dart';
+
+import 'utils/server_driver.dart';
+
+late final DevToolsServerTestController testController;
+
+void main() {
+ testController = DevToolsServerTestController();
+
+ setUp(() async {
+ await testController.setUp();
+ });
+
+ tearDown(() async {
+ await testController.tearDown();
+ });
+
+ test('allows embedding without flag', () async {
+ final server = await DevToolsServerDriver.create();
+ final httpClient = HttpClient();
+ late HttpClientResponse resp;
+ try {
+ final startedEvent = (await server.stdout.firstWhere(
+ (map) => map!['event'] == 'server.started',
+ ))!;
+ final host = startedEvent['params']['host'];
+ final port = startedEvent['params']['port'];
+
+ final req = await httpClient.get(host, port, '/');
+ resp = await req.close();
+ expect(resp.headers.value('x-frame-options'), isNull);
+ } finally {
+ httpClient.close();
+ await resp.drain();
+ server.kill();
+ }
+ }, timeout: const Timeout.factor(10));
+}
diff --git a/pkg/dds/test/fixtures/empty_dart_app.dart b/pkg/dds/test/devtools_server/fixtures/empty_dart_app.dart
similarity index 100%
rename from pkg/dds/test/fixtures/empty_dart_app.dart
rename to pkg/dds/test/devtools_server/fixtures/empty_dart_app.dart
diff --git a/pkg/dds/test/devtools_server/instance_reuse_test.dart b/pkg/dds/test/devtools_server/instance_reuse_test.dart
new file mode 100644
index 0000000..71d7428
--- /dev/null
+++ b/pkg/dds/test/devtools_server/instance_reuse_test.dart
@@ -0,0 +1,150 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:devtools_shared/devtools_test_utils.dart';
+import 'package:test/test.dart';
+
+import 'utils/server_driver.dart';
+
+late final DevToolsServerTestController testController;
+
+void main() {
+ testController = DevToolsServerTestController();
+
+ setUp(() async {
+ await testController.setUp();
+ });
+
+ tearDown(() async {
+ await testController.tearDown();
+ });
+
+ for (final bool useVmService in [true, false]) {
+ group('Server (${useVmService ? 'VM Service' : 'API'})', () {
+ test('reuses DevTools instance if already connected to same VM',
+ () async {
+ // Register the VM.
+ await testController.send(
+ 'vm.register',
+ {'uri': testController.appFixture.serviceUri.toString()},
+ );
+
+ // Send a request to launch DevTools in a browser.
+ await testController.sendLaunchDevToolsRequest(
+ useVmService: useVmService,
+ );
+
+ {
+ final serverResponse = await testController.waitForClients(
+ requiredConnectionState: true,
+ );
+ expect(serverResponse['clients'], hasLength(1));
+ }
+
+ // Request again, allowing reuse, and server emits an event saying the
+ // window was reused.
+ final launchResponse = await testController.sendLaunchDevToolsRequest(
+ useVmService: useVmService,
+ reuseWindows: true,
+ );
+ expect(launchResponse['reused'], isTrue);
+
+ // Ensure there's still only one connection (eg. we didn't spawn a new one
+ // we reused the existing one).
+ final serverResponse =
+ await testController.waitForClients(requiredConnectionState: true);
+ expect(serverResponse['clients'], hasLength(1));
+ }, timeout: const Timeout.factor(20));
+
+ test('Server does not reuse DevTools instance if embedded', () async {
+ // Register the VM.
+ await testController.send(
+ 'vm.register',
+ {'uri': testController.appFixture.serviceUri.toString()},
+ );
+
+ // Spawn an embedded version of DevTools in a browser.
+ final event = await testController.serverStartedEvent.future;
+ final devToolsUri =
+ 'http://${event['params']['host']}:${event['params']['port']}';
+ final launchUrl = '$devToolsUri/?embed=true&page=logging'
+ '&uri=${Uri.encodeQueryComponent(testController.appFixture.serviceUri.toString())}';
+ final chrome = await Chrome.locate()!.start(url: launchUrl);
+ try {
+ {
+ final serverResponse = await testController.waitForClients(
+ requiredConnectionState: true,
+ );
+ expect(serverResponse['clients'], hasLength(1));
+ }
+
+ // Send a request to the server to launch and ensure it did
+ // not reuse the existing connection. Launch it on a different page
+ // so we can easily tell once this one has connected.
+ final launchResponse = await testController.sendLaunchDevToolsRequest(
+ useVmService: useVmService,
+ reuseWindows: true,
+ page: 'memory',
+ );
+ expect(launchResponse['reused'], isFalse);
+
+ // Ensure there's now two connections.
+ final serverResponse = await testController.waitForClients(
+ requiredConnectionState: true,
+ requiredPage: 'memory',
+ );
+ expect(serverResponse['clients'], hasLength(2));
+ } finally {
+ chrome.kill();
+ }
+ }, timeout: const Timeout.factor(20));
+
+ test('reuses DevTools instance if not connected to a VM', () async {
+ // Register the VM.
+ await testController.send(
+ 'vm.register',
+ {'uri': testController.appFixture.serviceUri.toString()},
+ );
+
+ // Send a request to launch DevTools in a browser.
+ await testController.sendLaunchDevToolsRequest(
+ useVmService: useVmService,
+ );
+
+ // Wait for the DevTools to inform server that it's connected.
+ await testController.waitForClients(requiredConnectionState: true);
+
+ // Terminate the VM.
+ await testController.appFixture.teardown();
+
+ // Ensure the client is marked as disconnected.
+ await testController.waitForClients(requiredConnectionState: false);
+
+ // Start up a new app.
+ await testController.startApp();
+ await testController.send(
+ 'vm.register',
+ {'uri': testController.appFixture.serviceUri.toString()},
+ );
+
+ // Send a new request to launch.
+ await testController.sendLaunchDevToolsRequest(
+ useVmService: useVmService,
+ reuseWindows: true,
+ notify: true,
+ );
+
+ // Ensure we now have a single connected client.
+ final serverResponse =
+ await testController.waitForClients(requiredConnectionState: true);
+ expect(serverResponse['clients'], hasLength(1));
+ expect(serverResponse['clients'][0]['hasConnection'], isTrue);
+ expect(
+ serverResponse['clients'][0]['vmServiceUri'],
+ testController.appFixture.serviceUri.toString(),
+ );
+ }, timeout: const Timeout.factor(20));
+ });
+ }
+}
diff --git a/pkg/dds/test/devtools_server/remote_control_test.dart b/pkg/dds/test/devtools_server/remote_control_test.dart
new file mode 100644
index 0000000..c93d2a0
--- /dev/null
+++ b/pkg/dds/test/devtools_server/remote_control_test.dart
@@ -0,0 +1,82 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:test/test.dart';
+
+import 'utils/server_driver.dart';
+
+late final DevToolsServerTestController testController;
+
+void main() {
+ testController = DevToolsServerTestController();
+
+ setUp(() async {
+ await testController.setUp();
+ });
+
+ tearDown(() async {
+ await testController.tearDown();
+ });
+
+ for (final bool useVmService in [true, false]) {
+ group('Server (${useVmService ? 'VM Service' : 'API'})', () {
+ test('can launch on a specific page', () async {
+ // Register the VM.
+ await testController.send(
+ 'vm.register',
+ {'uri': testController.appFixture.serviceUri.toString()},
+ );
+
+ // Send a request to launch at a certain page.
+ await testController.sendLaunchDevToolsRequest(
+ useVmService: useVmService,
+ page: 'memory',
+ );
+
+ final serverResponse =
+ await testController.waitForClients(requiredPage: 'memory');
+ expect(serverResponse, isNotNull);
+ expect(serverResponse['clients'], hasLength(1));
+ expect(serverResponse['clients'][0]['hasConnection'], isTrue);
+ expect(
+ serverResponse['clients'][0]['vmServiceUri'],
+ testController.appFixture.serviceUri.toString(),
+ );
+ expect(serverResponse['clients'][0]['currentPage'], 'memory');
+ }, timeout: const Timeout.factor(10));
+
+ test('can switch page', () async {
+ await testController.send(
+ 'vm.register',
+ {'uri': testController.appFixture.serviceUri.toString()},
+ );
+
+ // Launch on the memory page and wait for the connection.
+ await testController.sendLaunchDevToolsRequest(
+ useVmService: useVmService,
+ page: 'memory',
+ );
+ await testController.waitForClients(requiredPage: 'memory');
+
+ // Re-launch, allowing reuse and with a different page.
+ await testController.sendLaunchDevToolsRequest(
+ useVmService: useVmService,
+ reuseWindows: true,
+ page: 'logging',
+ );
+
+ final serverResponse =
+ await testController.waitForClients(requiredPage: 'logging');
+ expect(serverResponse, isNotNull);
+ expect(serverResponse['clients'], hasLength(1));
+ expect(serverResponse['clients'][0]['hasConnection'], isTrue);
+ expect(
+ serverResponse['clients'][0]['vmServiceUri'],
+ testController.appFixture.serviceUri.toString(),
+ );
+ expect(serverResponse['clients'][0]['currentPage'], 'logging');
+ }, timeout: const Timeout.factor(20));
+ });
+ }
+}
diff --git a/pkg/dds/test/devtools_server/server_connection_api_test.dart b/pkg/dds/test/devtools_server/server_connection_api_test.dart
new file mode 100644
index 0000000..e4d5ff8
--- /dev/null
+++ b/pkg/dds/test/devtools_server/server_connection_api_test.dart
@@ -0,0 +1,9 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'server_connection_common.dart';
+
+// This test reuses much of the same logic as server_connection_vm_service_test
+// but running both in the same process will result in a timeout.
+void main() => runTest(useVmService: false);
diff --git a/pkg/dds/test/devtools_server/server_connection_common.dart b/pkg/dds/test/devtools_server/server_connection_common.dart
new file mode 100644
index 0000000..f812e9a
--- /dev/null
+++ b/pkg/dds/test/devtools_server/server_connection_common.dart
@@ -0,0 +1,101 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:devtools_shared/devtools_test_utils.dart';
+import 'package:test/test.dart';
+
+import 'utils/server_driver.dart';
+
+// Note: this test is broken out from devtools_server_test.dart so that the
+// tests run faster and we do not have to mark them as slow.
+
+late final DevToolsServerTestController testController;
+
+void runTest({required bool useVmService}) {
+ testController = DevToolsServerTestController();
+
+ setUp(() async {
+ await testController.setUp();
+ });
+
+ tearDown(() async {
+ await testController.tearDown();
+ });
+
+ group('Server (${useVmService ? 'VM Service' : 'API'})', () {
+ test(
+ 'DevTools connects back to server API and registers that it is connected',
+ () async {
+ // Register the VM.
+ await testController.send(
+ 'vm.register',
+ {'uri': testController.appFixture.serviceUri.toString()},
+ );
+
+ // Send a request to launch DevTools in a browser.
+ await testController.sendLaunchDevToolsRequest(
+ useVmService: useVmService,
+ );
+
+ final serverResponse =
+ await testController.waitForClients(requiredConnectionState: true);
+ expect(serverResponse, isNotNull);
+ expect(serverResponse['clients'], hasLength(1));
+ expect(serverResponse['clients'][0]['hasConnection'], isTrue);
+ expect(
+ serverResponse['clients'][0]['vmServiceUri'],
+ testController.appFixture.serviceUri.toString(),
+ );
+ }, timeout: const Timeout.factor(10));
+
+ test('DevTools reports disconnects from a VM', () async {
+ // Register the VM.
+ await testController.send(
+ 'vm.register',
+ {'uri': testController.appFixture.serviceUri.toString()},
+ );
+
+ // Send a request to launch DevTools in a browser.
+ await testController.sendLaunchDevToolsRequest(
+ useVmService: useVmService,
+ );
+
+ // Wait for the DevTools to inform server that it's connected.
+ await testController.waitForClients(requiredConnectionState: true);
+
+ // Terminate the VM.
+ await testController.appFixture.teardown();
+
+ // Ensure the client is marked as disconnected.
+ final serverResponse = await testController.waitForClients(
+ requiredConnectionState: false,
+ );
+ expect(serverResponse['clients'], hasLength(1));
+ expect(serverResponse['clients'][0]['hasConnection'], isFalse);
+ expect(serverResponse['clients'][0]['vmServiceUri'], isNull);
+ }, timeout: const Timeout.factor(20));
+
+ test('server removes clients that disconnect from the API', () async {
+ final event = await testController.serverStartedEvent.future;
+
+ // Spawn our own Chrome process so we can terminate it.
+ final devToolsUri =
+ 'http://${event['params']['host']}:${event['params']['port']}';
+ final chrome = await Chrome.locate()!.start(url: devToolsUri);
+
+ // Wait for DevTools to inform server that it's connected.
+ await testController.waitForClients();
+
+ // Close the browser, which will disconnect DevTools SSE connection
+ // back to the server.
+ chrome.kill();
+
+ // Await a long delay to wait for the SSE client to close.
+ await delay(duration: const Duration(seconds: 15));
+
+ // Ensure the client is completely removed from the list.
+ await testController.waitForClients(expectNone: true);
+ }, timeout: const Timeout.factor(20));
+ });
+}
diff --git a/pkg/dds/test/devtools_server/server_connection_vm_service_test.dart b/pkg/dds/test/devtools_server/server_connection_vm_service_test.dart
new file mode 100644
index 0000000..ef4dfd3
--- /dev/null
+++ b/pkg/dds/test/devtools_server/server_connection_vm_service_test.dart
@@ -0,0 +1,9 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'server_connection_common.dart';
+
+// This test reuses much of the same logic as server_connection_api_test but
+// running both in the same process will result in a timeout.
+void main() => runTest(useVmService: true);
diff --git a/pkg/dds/test/devtools_server/service_registration_test.dart b/pkg/dds/test/devtools_server/service_registration_test.dart
new file mode 100644
index 0000000..fa85a4f
--- /dev/null
+++ b/pkg/dds/test/devtools_server/service_registration_test.dart
@@ -0,0 +1,36 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:dds/devtools_server.dart';
+import 'package:test/test.dart';
+
+import 'utils/server_driver.dart';
+
+late final DevToolsServerTestController testController;
+
+void main() {
+ testController = DevToolsServerTestController();
+
+ setUp(() async {
+ await testController.setUp();
+ });
+
+ tearDown(() async {
+ await testController.tearDown();
+ });
+
+ test('registers service', () async {
+ final serverResponse = await testController.send(
+ 'vm.register',
+ {'uri': testController.appFixture.serviceUri.toString()},
+ );
+ expect(serverResponse['success'], isTrue);
+
+ // Expect the VM service to see the launchDevTools service registered.
+ expect(
+ testController.registeredServices,
+ contains(DevToolsServer.launchDevToolsService),
+ );
+ }, timeout: const Timeout.factor(10));
+}
diff --git a/pkg/dds/test/devtools_server/serve_devtools.dart b/pkg/dds/test/devtools_server/utils/serve_devtools.dart
similarity index 75%
rename from pkg/dds/test/devtools_server/serve_devtools.dart
rename to pkg/dds/test/devtools_server/utils/serve_devtools.dart
index 7fbd12c..ad0d764 100644
--- a/pkg/dds/test/devtools_server/serve_devtools.dart
+++ b/pkg/dds/test/devtools_server/utils/serve_devtools.dart
@@ -6,13 +6,14 @@
import 'package:dds/devtools_server.dart';
-import '../common/test_helper.dart';
+import '../../common/test_helper.dart';
void main(List<String> args) async {
unawaited(
DevToolsServer().serveDevToolsWithArgs(
args,
- customDevToolsPath: devtoolsAppUri(prefix: '../../../../').toFilePath(),
+ customDevToolsPath:
+ devtoolsAppUri(prefix: '../../../../../').toFilePath(),
),
);
}
diff --git a/pkg/dds/test/devtools_server/devtools_server_driver.dart b/pkg/dds/test/devtools_server/utils/server_driver.dart
similarity index 97%
rename from pkg/dds/test/devtools_server/devtools_server_driver.dart
rename to pkg/dds/test/devtools_server/utils/server_driver.dart
index 9f56b81..7a2d8c7 100644
--- a/pkg/dds/test/devtools_server/devtools_server_driver.dart
+++ b/pkg/dds/test/devtools_server/utils/server_driver.dart
@@ -63,8 +63,9 @@
int? tryPorts,
List<String> additionalArgs = const [],
}) async {
- final script =
- Platform.script.resolveUri(Uri.parse('./serve_devtools.dart'));
+ final script = Platform.script.resolveUri(
+ Uri.parse('utils/serve_devtools.dart'),
+ );
final args = [
script.toFilePath(),
'--machine',
@@ -200,8 +201,8 @@
}
Future<void> startApp() async {
- final appUri = Platform.script
- .resolveUri(Uri.parse('../fixtures/empty_dart_app.dart'));
+ final appUri =
+ Platform.script.resolveUri(Uri.parse('fixtures/empty_dart_app.dart'));
appFixture = await CliAppFixture.create(appUri.toFilePath());
// Track services method names as they're registered.
diff --git a/pkg/front_end/test/spell_checking_list_code.txt b/pkg/front_end/test/spell_checking_list_code.txt
index 099e6f8..80753e6 100644
--- a/pkg/front_end/test/spell_checking_list_code.txt
+++ b/pkg/front_end/test/spell_checking_list_code.txt
@@ -190,6 +190,7 @@
ci
circuited
ck
+callbacks
cl
claim
claimed
@@ -428,6 +429,7 @@
established
estimate
eval
+exchanging
execute
executor
executors
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart
new file mode 100644
index 0000000..81dfb3a
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart
@@ -0,0 +1,8 @@
+Future<int> foo() async {
+ final c = 3;
+ return c;
+}
+
+void main() {
+ foo();
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.strong.expect b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.strong.expect
new file mode 100644
index 0000000..a9953eb
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.strong.expect
@@ -0,0 +1,12 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo() → asy::Future<core::int> async /* futureValueType= core::int */ {
+ final core::int c = 3;
+ return c;
+}
+static method main() → void {
+ self::foo();
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.strong.transformed.expect b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.strong.transformed.expect
new file mode 100644
index 0000000..a9953eb
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.strong.transformed.expect
@@ -0,0 +1,12 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo() → asy::Future<core::int> async /* futureValueType= core::int */ {
+ final core::int c = 3;
+ return c;
+}
+static method main() → void {
+ self::foo();
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.textual_outline.expect b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.textual_outline.expect
new file mode 100644
index 0000000..e611f16
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.textual_outline.expect
@@ -0,0 +1,2 @@
+Future<int> foo() async {}
+void main() {}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..e611f16
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.textual_outline_modelled.expect
@@ -0,0 +1,2 @@
+Future<int> foo() async {}
+void main() {}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.weak.expect b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.weak.expect
new file mode 100644
index 0000000..a9953eb
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.weak.expect
@@ -0,0 +1,12 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo() → asy::Future<core::int> async /* futureValueType= core::int */ {
+ final core::int c = 3;
+ return c;
+}
+static method main() → void {
+ self::foo();
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.weak.modular.expect b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.weak.modular.expect
new file mode 100644
index 0000000..a9953eb
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.weak.modular.expect
@@ -0,0 +1,12 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo() → asy::Future<core::int> async /* futureValueType= core::int */ {
+ final core::int c = 3;
+ return c;
+}
+static method main() → void {
+ self::foo();
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.weak.outline.expect b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.weak.outline.expect
new file mode 100644
index 0000000..79880de
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.weak.outline.expect
@@ -0,0 +1,9 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo() → asy::Future<core::int> async
+ ;
+static method main() → void
+ ;
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.weak.transformed.expect b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.weak.transformed.expect
new file mode 100644
index 0000000..a9953eb
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.weak.transformed.expect
@@ -0,0 +1,12 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo() → asy::Future<core::int> async /* futureValueType= core::int */ {
+ final core::int c = 3;
+ return c;
+}
+static method main() → void {
+ self::foo();
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/disabled/folder.options b/pkg/front_end/testcases/dart2js/async_lowering/disabled/folder.options
new file mode 100644
index 0000000..636a0c8
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/disabled/folder.options
@@ -0,0 +1 @@
+--target=dart2js
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/folder.options b/pkg/front_end/testcases/dart2js/async_lowering/folder.options
new file mode 100644
index 0000000..ee073f8
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/folder.options
@@ -0,0 +1,2 @@
+-D--simple-async-to-future
+--target=dart2js
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart
new file mode 100644
index 0000000..f8b5c92
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart
@@ -0,0 +1,40 @@
+// Wrap and return non-awaited expression.
+Future<int> foo1() async {
+ final c = 3;
+ return c;
+}
+
+// Add null Future return.
+Future<void> foo2() async {
+ final c = 3;
+}
+
+// Return dynamic Future when no type.
+foo3() async {
+ return 234;
+}
+
+void bar(Future<int> Function() func) {
+ func();
+}
+
+// Transform nested function even if parent is not convertible.
+Future<bool> foo4() async {
+ await Future.value(2);
+ bar(() async => 3);
+ return true;
+}
+
+// Convert multiple returns.
+Future<int> foo5(bool x) async {
+ if (x) return 123;
+ return 234;
+}
+
+void main() {
+ foo1();
+ foo2();
+ foo3();
+ foo4();
+ foo5(true);
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.strong.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.strong.expect
new file mode 100644
index 0000000..b06980a
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.strong.expect
@@ -0,0 +1,35 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo1() → asy::Future<core::int> async /* futureValueType= core::int */ {
+ final core::int c = 3;
+ return c;
+}
+static method foo2() → asy::Future<void> async /* futureValueType= void */ {
+ final core::int c = 3;
+}
+static method foo3() → dynamic async /* futureValueType= dynamic */ {
+ return 234;
+}
+static method bar(() → asy::Future<core::int> func) → void {
+ func(){() → asy::Future<core::int>};
+}
+static method foo4() → asy::Future<core::bool> async /* futureValueType= core::bool */ {
+ await asy::Future::value<core::int>(2);
+ self::bar(() → asy::Future<core::int> async /* futureValueType= core::int */ => 3);
+ return true;
+}
+static method foo5(core::bool x) → asy::Future<core::int> async /* futureValueType= core::int */ {
+ if(x)
+ return 123;
+ return 234;
+}
+static method main() → void {
+ self::foo1();
+ self::foo2();
+ self::foo3();
+ self::foo4();
+ self::foo5(true);
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.strong.transformed.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.strong.transformed.expect
new file mode 100644
index 0000000..d8ebe4a
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.strong.transformed.expect
@@ -0,0 +1,41 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo1() → asy::Future<core::int> /* futureValueType= core::int */ /* originally async */
+ asy::Future::sync<core::int>(() → FutureOr<core::int> {
+ final core::int c = 3;
+ return c;
+ });
+static method foo2() → asy::Future<void> /* futureValueType= void */ /* originally async */
+ asy::Future::sync<void>(() → FutureOr<void>? {
+ final core::int c = 3;
+ });
+static method foo3() → dynamic /* futureValueType= dynamic */ /* originally async */
+ asy::Future::sync<dynamic>(() → FutureOr<dynamic>? {
+ return 234;
+ });
+static method bar(() → asy::Future<core::int> func) → void {
+ func(){() → asy::Future<core::int>};
+}
+static method foo4() → asy::Future<core::bool> async /* futureValueType= core::bool */ {
+ await asy::Future::value<core::int>(2);
+ self::bar(() → asy::Future<core::int> /* futureValueType= core::int */ /* originally async */
+ asy::Future::sync<core::int>(() → FutureOr<core::int> => 3);
+);
+ return true;
+}
+static method foo5(core::bool x) → asy::Future<core::int> /* futureValueType= core::int */ /* originally async */
+ asy::Future::sync<core::int>(() → FutureOr<core::int> {
+ if(x)
+ return 123;
+ return 234;
+ });
+static method main() → void {
+ self::foo1();
+ self::foo2();
+ self::foo3();
+ self::foo4();
+ self::foo5(true);
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.textual_outline.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.textual_outline.expect
new file mode 100644
index 0000000..0ed1446
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.textual_outline.expect
@@ -0,0 +1,7 @@
+Future<int> foo1() async {}
+Future<void> foo2() async {}
+foo3() async {}
+void bar(Future<int> Function() func) {}
+Future<bool> foo4() async {}
+Future<int> foo5(bool x) async {}
+void main() {}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..66be86c
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.textual_outline_modelled.expect
@@ -0,0 +1,7 @@
+Future<bool> foo4() async {}
+Future<int> foo1() async {}
+Future<int> foo5(bool x) async {}
+Future<void> foo2() async {}
+foo3() async {}
+void bar(Future<int> Function() func) {}
+void main() {}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.weak.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.weak.expect
new file mode 100644
index 0000000..b06980a
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.weak.expect
@@ -0,0 +1,35 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo1() → asy::Future<core::int> async /* futureValueType= core::int */ {
+ final core::int c = 3;
+ return c;
+}
+static method foo2() → asy::Future<void> async /* futureValueType= void */ {
+ final core::int c = 3;
+}
+static method foo3() → dynamic async /* futureValueType= dynamic */ {
+ return 234;
+}
+static method bar(() → asy::Future<core::int> func) → void {
+ func(){() → asy::Future<core::int>};
+}
+static method foo4() → asy::Future<core::bool> async /* futureValueType= core::bool */ {
+ await asy::Future::value<core::int>(2);
+ self::bar(() → asy::Future<core::int> async /* futureValueType= core::int */ => 3);
+ return true;
+}
+static method foo5(core::bool x) → asy::Future<core::int> async /* futureValueType= core::int */ {
+ if(x)
+ return 123;
+ return 234;
+}
+static method main() → void {
+ self::foo1();
+ self::foo2();
+ self::foo3();
+ self::foo4();
+ self::foo5(true);
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.weak.modular.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.weak.modular.expect
new file mode 100644
index 0000000..b06980a
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.weak.modular.expect
@@ -0,0 +1,35 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo1() → asy::Future<core::int> async /* futureValueType= core::int */ {
+ final core::int c = 3;
+ return c;
+}
+static method foo2() → asy::Future<void> async /* futureValueType= void */ {
+ final core::int c = 3;
+}
+static method foo3() → dynamic async /* futureValueType= dynamic */ {
+ return 234;
+}
+static method bar(() → asy::Future<core::int> func) → void {
+ func(){() → asy::Future<core::int>};
+}
+static method foo4() → asy::Future<core::bool> async /* futureValueType= core::bool */ {
+ await asy::Future::value<core::int>(2);
+ self::bar(() → asy::Future<core::int> async /* futureValueType= core::int */ => 3);
+ return true;
+}
+static method foo5(core::bool x) → asy::Future<core::int> async /* futureValueType= core::int */ {
+ if(x)
+ return 123;
+ return 234;
+}
+static method main() → void {
+ self::foo1();
+ self::foo2();
+ self::foo3();
+ self::foo4();
+ self::foo5(true);
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.weak.outline.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.weak.outline.expect
new file mode 100644
index 0000000..56f17c2
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.weak.outline.expect
@@ -0,0 +1,19 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo1() → asy::Future<core::int> async
+ ;
+static method foo2() → asy::Future<void> async
+ ;
+static method foo3() → dynamic async
+ ;
+static method bar(() → asy::Future<core::int> func) → void
+ ;
+static method foo4() → asy::Future<core::bool> async
+ ;
+static method foo5(core::bool x) → asy::Future<core::int> async
+ ;
+static method main() → void
+ ;
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.weak.transformed.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.weak.transformed.expect
new file mode 100644
index 0000000..d8ebe4a
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.weak.transformed.expect
@@ -0,0 +1,41 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo1() → asy::Future<core::int> /* futureValueType= core::int */ /* originally async */
+ asy::Future::sync<core::int>(() → FutureOr<core::int> {
+ final core::int c = 3;
+ return c;
+ });
+static method foo2() → asy::Future<void> /* futureValueType= void */ /* originally async */
+ asy::Future::sync<void>(() → FutureOr<void>? {
+ final core::int c = 3;
+ });
+static method foo3() → dynamic /* futureValueType= dynamic */ /* originally async */
+ asy::Future::sync<dynamic>(() → FutureOr<dynamic>? {
+ return 234;
+ });
+static method bar(() → asy::Future<core::int> func) → void {
+ func(){() → asy::Future<core::int>};
+}
+static method foo4() → asy::Future<core::bool> async /* futureValueType= core::bool */ {
+ await asy::Future::value<core::int>(2);
+ self::bar(() → asy::Future<core::int> /* futureValueType= core::int */ /* originally async */
+ asy::Future::sync<core::int>(() → FutureOr<core::int> => 3);
+);
+ return true;
+}
+static method foo5(core::bool x) → asy::Future<core::int> /* futureValueType= core::int */ /* originally async */
+ asy::Future::sync<core::int>(() → FutureOr<core::int> {
+ if(x)
+ return 123;
+ return 234;
+ });
+static method main() → void {
+ self::foo1();
+ self::foo2();
+ self::foo3();
+ self::foo4();
+ self::foo5(true);
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart
new file mode 100644
index 0000000..8581d4c
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart
@@ -0,0 +1,12 @@
+Future<void> foo1() async {
+ await 6;
+}
+
+Future<int> foo2() async {
+ return await 6;
+}
+
+void main() {
+ foo1();
+ foo2();
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.strong.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.strong.expect
new file mode 100644
index 0000000..10798bd8
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.strong.expect
@@ -0,0 +1,15 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo1() → asy::Future<void> async /* futureValueType= void */ {
+ await 6;
+}
+static method foo2() → asy::Future<core::int> async /* futureValueType= core::int */ {
+ return await 6;
+}
+static method main() → void {
+ self::foo1();
+ self::foo2();
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.strong.transformed.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.strong.transformed.expect
new file mode 100644
index 0000000..10798bd8
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.strong.transformed.expect
@@ -0,0 +1,15 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo1() → asy::Future<void> async /* futureValueType= void */ {
+ await 6;
+}
+static method foo2() → asy::Future<core::int> async /* futureValueType= core::int */ {
+ return await 6;
+}
+static method main() → void {
+ self::foo1();
+ self::foo2();
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.textual_outline.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.textual_outline.expect
new file mode 100644
index 0000000..06a5c0d
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.textual_outline.expect
@@ -0,0 +1,3 @@
+Future<void> foo1() async {}
+Future<int> foo2() async {}
+void main() {}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..4ab99eed
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.textual_outline_modelled.expect
@@ -0,0 +1,3 @@
+Future<int> foo2() async {}
+Future<void> foo1() async {}
+void main() {}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.expect
new file mode 100644
index 0000000..10798bd8
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.expect
@@ -0,0 +1,15 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo1() → asy::Future<void> async /* futureValueType= void */ {
+ await 6;
+}
+static method foo2() → asy::Future<core::int> async /* futureValueType= core::int */ {
+ return await 6;
+}
+static method main() → void {
+ self::foo1();
+ self::foo2();
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.modular.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.modular.expect
new file mode 100644
index 0000000..10798bd8
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.modular.expect
@@ -0,0 +1,15 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo1() → asy::Future<void> async /* futureValueType= void */ {
+ await 6;
+}
+static method foo2() → asy::Future<core::int> async /* futureValueType= core::int */ {
+ return await 6;
+}
+static method main() → void {
+ self::foo1();
+ self::foo2();
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.outline.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.outline.expect
new file mode 100644
index 0000000..85b14b9
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.outline.expect
@@ -0,0 +1,11 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo1() → asy::Future<void> async
+ ;
+static method foo2() → asy::Future<core::int> async
+ ;
+static method main() → void
+ ;
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.transformed.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.transformed.expect
new file mode 100644
index 0000000..10798bd8
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.transformed.expect
@@ -0,0 +1,15 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo1() → asy::Future<void> async /* futureValueType= void */ {
+ await 6;
+}
+static method foo2() → asy::Future<core::int> async /* futureValueType= core::int */ {
+ return await 6;
+}
+static method main() → void {
+ self::foo1();
+ self::foo2();
+}
diff --git a/pkg/kernel/lib/core_types.dart b/pkg/kernel/lib/core_types.dart
index 4dc523e..04f3cee 100644
--- a/pkg/kernel/lib/core_types.dart
+++ b/pkg/kernel/lib/core_types.dart
@@ -178,6 +178,9 @@
late final Class futureClass = index.getClass('dart:core', 'Future');
+ late final Procedure futureSyncFactory =
+ index.getMember('dart:async', 'Future', 'sync') as Procedure;
+
// TODO(dmitryas): Remove it when FutureOrType is fully supported.
late final Class deprecatedFutureOrClass =
index.getClass('dart:async', 'FutureOr');
diff --git a/pkg/pkg.status b/pkg/pkg.status
index 38658de..2aef55f 100644
--- a/pkg/pkg.status
+++ b/pkg/pkg.status
@@ -145,8 +145,6 @@
vm_snapshot_analysis/test/*: SkipByDesign # Only meant to run on vm
[ $system == windows ]
-dds/test/devtools_server/devtools_server_connection_test: Pass, Slow
-dds/test/devtools_server/devtools_server_test: Pass, Slow
front_end/test/fasta/bootstrap_test: Skip # Issue 31902
front_end/test/fasta/strong_test: Pass, Slow, Timeout
front_end/test/fasta/text_serialization_test: Pass, Slow, Timeout
diff --git a/runtime/bin/secure_socket_utils.cc b/runtime/bin/secure_socket_utils.cc
index ad738e97..e9a742a 100644
--- a/runtime/bin/secure_socket_utils.cc
+++ b/runtime/bin/secure_socket_utils.cc
@@ -21,9 +21,13 @@
// Get the error messages from BoringSSL, and put them in buffer as a
// null-terminated string.
-void SecureSocketUtils::FetchErrorString(const SSL* ssl,
- TextBuffer* text_buffer) {
+// This function extracts all the error messages into a string and returns
+// the first error code so that this error can be passed in as the OSError
+// error code to the IOException.
+uint32_t SecureSocketUtils::FetchErrorString(const SSL* ssl,
+ TextBuffer* text_buffer) {
const char* sep = File::PathSeparator();
+ uint32_t errCode = 0;
while (true) {
const char* path = NULL;
int line = -1;
@@ -31,6 +35,9 @@
if (error == 0) {
break;
}
+ if (errCode == 0) {
+ errCode = error;
+ }
text_buffer->Printf("\n\t%s", ERR_reason_error_string(error));
if ((ssl != NULL) && (ERR_GET_LIB(error) == ERR_LIB_SSL) &&
(ERR_GET_REASON(error) == SSL_R_CERTIFICATE_VERIFY_FAILED)) {
@@ -43,6 +50,7 @@
text_buffer->Printf("(%s:%d)", path, line);
}
}
+ return errCode;
}
// Handle an error reported from the BoringSSL library.
@@ -53,7 +61,10 @@
Dart_Handle exception;
{
TextBuffer error_string(SSL_ERROR_MESSAGE_BUFFER_SIZE);
- SecureSocketUtils::FetchErrorString(ssl, &error_string);
+ uint32_t errCode = SecureSocketUtils::FetchErrorString(ssl, &error_string);
+ if (status == 0) {
+ status = errCode;
+ }
OSError os_error_struct(status, error_string.buffer(), OSError::kBoringSSL);
Dart_Handle os_error = DartUtils::NewDartOSError(&os_error_struct);
exception =
diff --git a/runtime/bin/secure_socket_utils.h b/runtime/bin/secure_socket_utils.h
index e8e306e..bf1fb6b 100644
--- a/runtime/bin/secure_socket_utils.h
+++ b/runtime/bin/secure_socket_utils.h
@@ -47,7 +47,7 @@
(ERR_GET_REASON(last_error) == PEM_R_NO_START_LINE);
}
- static void FetchErrorString(const SSL* ssl, TextBuffer* text_buffer);
+ static uint32_t FetchErrorString(const SSL* ssl, TextBuffer* text_buffer);
};
// Where the argument to the constructor is the handle for an object
diff --git a/runtime/bin/socket.cc b/runtime/bin/socket.cc
index 21bfadf..708bda4 100644
--- a/runtime/bin/socket.cc
+++ b/runtime/bin/socket.cc
@@ -914,7 +914,18 @@
Socket::GetSocketIdNativeField(Dart_GetNativeArgument(args, 0));
OSError os_error;
SocketBase::GetError(socket->fd(), &os_error);
- Dart_SetReturnValue(args, DartUtils::NewDartOSError(&os_error));
+ if (os_error.code() != 0) {
+ Dart_SetReturnValue(args, DartUtils::NewDartOSError(&os_error));
+ } else {
+ Dart_SetReturnValue(args, Dart_Null());
+ }
+}
+
+void FUNCTION_NAME(Socket_Fatal)(Dart_NativeArguments args) {
+ Dart_Handle msg = Dart_GetNativeArgument(args, 0);
+ const char* msgStr =
+ (!Dart_IsNull(msg)) ? DartUtils::GetStringValue(msg) : nullptr;
+ FATAL("Fatal error in dart:io (socket): %s", msgStr);
}
void FUNCTION_NAME(Socket_GetFD)(Dart_NativeArguments args) {
diff --git a/runtime/vm/compiler/stub_code_compiler_riscv.cc b/runtime/vm/compiler/stub_code_compiler_riscv.cc
index e63cc88..3b3715c 100644
--- a/runtime/vm/compiler/stub_code_compiler_riscv.cc
+++ b/runtime/vm/compiler/stub_code_compiler_riscv.cc
@@ -1675,18 +1675,17 @@
__ sx(T4, Address(SP, 0 * target::kWordSize));
// Atomically clear kOldAndNotRememberedBit.
- // TODO(riscv): Use amoand instead of lr/sc.
ASSERT(target::Object::tags_offset() == 0);
__ subi(T3, A0, kHeapObjectTag);
- // T3: Untagged address of header word (lr/sc do not support offsets).
- Label retry;
- __ Bind(&retry);
- __ lr(T2, Address(T3, 0));
- __ andi(TMP2, T2, 1 << target::UntaggedObject::kOldAndNotRememberedBit);
- __ beqz(TMP2, &lost_race);
- __ andi(T2, T2, ~(1 << target::UntaggedObject::kOldAndNotRememberedBit));
- __ sc(T4, T2, Address(T3, 0));
- __ bnez(T4, &retry);
+ // T3: Untagged address of header word (amo's do not support offsets).
+ __ li(TMP2, ~(1 << target::UntaggedObject::kOldAndNotRememberedBit));
+#if XLEN == 32
+ __ amoandw(TMP2, TMP2, Address(T3, 0));
+#else
+ __ amoandd(TMP2, TMP2, Address(T3, 0));
+#endif
+ __ andi(TMP2, TMP2, 1 << target::UntaggedObject::kOldAndNotRememberedBit);
+ __ beqz(TMP2, &lost_race); // Was already clear -> lost race.
// Load the StoreBuffer block out of the thread. Then load top_ out of the
// StoreBufferBlock and add the address to the pointers_.
@@ -1738,18 +1737,18 @@
__ sx(T4, Address(SP, 0 * target::kWordSize));
// Atomically clear kOldAndNotMarkedBit.
- // TODO(riscv): Use amoand instead of lr/sc.
- Label marking_retry, marking_overflow;
+ Label marking_overflow;
ASSERT(target::Object::tags_offset() == 0);
__ subi(T3, A1, kHeapObjectTag);
- // T3: Untagged address of header word (lr/sc do not support offsets).
- __ Bind(&marking_retry);
- __ lr(T2, Address(T3, 0));
- __ andi(TMP2, T2, 1 << target::UntaggedObject::kOldAndNotMarkedBit);
- __ beqz(TMP2, &lost_race);
- __ andi(T2, T2, ~(1 << target::UntaggedObject::kOldAndNotMarkedBit));
- __ sc(T4, T2, Address(T3, 0));
- __ bnez(T4, &marking_retry);
+ // T3: Untagged address of header word (amo's do not support offsets).
+ __ li(TMP2, ~(1 << target::UntaggedObject::kOldAndNotMarkedBit));
+#if XLEN == 32
+ __ amoandw(TMP2, TMP2, Address(T3, 0));
+#else
+ __ amoandd(TMP2, TMP2, Address(T3, 0));
+#endif
+ __ andi(TMP2, TMP2, 1 << target::UntaggedObject::kOldAndNotMarkedBit);
+ __ beqz(TMP2, &lost_race); // Was already clear -> lost race.
__ LoadFromOffset(T4, THR, target::Thread::marking_stack_block_offset());
__ LoadFromOffset(T2, T4, target::MarkingStackBlock::top_offset(),
diff --git a/sdk/lib/_internal/vm/bin/socket_patch.dart b/sdk/lib/_internal/vm/bin/socket_patch.dart
index 7c9925a..d0bddf8 100644
--- a/sdk/lib/_internal/vm/bin/socket_patch.dart
+++ b/sdk/lib/_internal/vm/bin/socket_patch.dart
@@ -877,8 +877,8 @@
// the SO_ERROR option at level SOL_SOCKET to determine whether
// connect() completed successfully (SO_ERROR is zero) or
// unsuccessfully.
- final OSError osError = socket.nativeGetError();
- if (osError.errorCode != 0) {
+ final osError = socket.nativeGetError();
+ if (osError != null) {
socket.close();
error ??= osError;
connectNext();
@@ -1404,8 +1404,12 @@
if (i == errorEvent) {
if (!isClosing) {
- final err = nativeGetError();
- reportError(err, null, err.message);
+ final osError = nativeGetError();
+ if (osError == null) {
+ _nativeFatal("Reporting error with OSError code of 0");
+ } else {
+ reportError(osError, null, osError.message);
+ }
}
} else if (!isClosed) {
// If the connection is closed right after it's accepted, there's a
@@ -1688,7 +1692,7 @@
@pragma("vm:external-name", "Socket_GetFD")
external int get fd;
@pragma("vm:external-name", "Socket_GetError")
- external OSError nativeGetError();
+ external OSError? nativeGetError();
@pragma("vm:external-name", "Socket_GetOption")
external nativeGetOption(int option, int protocol);
@pragma("vm:external-name", "Socket_GetRawOption")
@@ -1703,6 +1707,8 @@
@pragma("vm:external-name", "Socket_LeaveMulticast")
external void nativeLeaveMulticast(
Uint8List addr, Uint8List? interfaceAddr, int interfaceIndex);
+ @pragma("vm:external-name", "Socket_Fatal")
+ external static void _nativeFatal(msg);
}
class _RawServerSocket extends Stream<RawSocket> implements RawServerSocket {
diff --git a/sdk/lib/_internal/wasm/lib/errors_patch.dart b/sdk/lib/_internal/wasm/lib/errors_patch.dart
index 810e550..d560ac0 100644
--- a/sdk/lib/_internal/wasm/lib/errors_patch.dart
+++ b/sdk/lib/_internal/wasm/lib/errors_patch.dart
@@ -56,4 +56,13 @@
"Null check operator used on a null value", stackTrace);
return _throwObjectWithStackTrace(typeError, stackTrace);
}
+
+ @pragma("wasm:entry-point")
+ static Never _throwAsCheckError(
+ Object? operand, Type? type, StackTrace stackTrace) {
+ final typeError = _TypeError.fromMessageAndStackTrace(
+ "Type '${operand.runtimeType}' is not a subtype of type '$type' in type cast",
+ stackTrace);
+ return _throwObjectWithStackTrace(typeError, stackTrace);
+ }
}
diff --git a/tests/standalone/io/secure_socket_error_test.dart b/tests/standalone/io/secure_socket_error_test.dart
new file mode 100644
index 0000000..270ea31
--- /dev/null
+++ b/tests/standalone/io/secure_socket_error_test.dart
@@ -0,0 +1,88 @@
+// Copyright (c) 2020, 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.
+//
+// OtherResources=certificates/server_chain.pem
+// OtherResources=certificates/server_key.pem
+// OtherResources=certificates/trusted_certs.pem
+// OtherResources=certificates/server_chain.p12
+// OtherResources=certificates/server_key.p12
+// OtherResources=certificates/trusted_certs.p12
+
+import "package:async_helper/async_helper.dart";
+import "package:expect/expect.dart";
+import "package:path/path.dart";
+import "dart:async";
+import "dart:io";
+
+String localFile(path) => Platform.script.resolve(path).toFilePath();
+
+SecurityContext serverContext(String certType, String password) =>
+ new SecurityContext()
+ ..useCertificateChain(localFile('certificates/server_chain.$certType'),
+ password: password)
+ ..usePrivateKey(localFile('certificates/server_key.$certType'),
+ password: password);
+
+SecurityContext clientContext(String certType, String password) =>
+ new SecurityContext()
+ ..setTrustedCertificates(
+ localFile('certificates/trusted_certs.$certType'),
+ password: password);
+
+Future<HttpServer> startServer(String certType, String password) {
+ return HttpServer.bindSecure(
+ "localhost", 0, serverContext(certType, password),
+ backlog: 5)
+ .then((server) {
+ server.listen((HttpRequest request) {
+ request.listen((_) {}, onDone: () {
+ request.response.contentLength = 100;
+ for (int i = 0; i < 10; i++) {
+ request.response.add([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
+ }
+ request.response.close();
+ });
+ });
+ return server;
+ });
+}
+
+Future test(String certType, String password) {
+ List<int> body = <int>[];
+ Completer completer = new Completer();
+ startServer(certType, password).then((server) {
+ try {
+ SecureSocket.connect("localhost", server.port,
+ context: clientContext(certType, 'junkjunk'))
+ .then((socket) {
+ socket.write("GET / HTTP/1.0\r\nHost: localhost\r\n\r\n");
+ socket.close();
+ socket.listen((List<int> data) {
+ body.addAll(data);
+ }, onDone: () {
+ server.close();
+ completer.complete(null);
+ }, onError: (e, trace) {
+ server.close();
+ completer.complete(null);
+ });
+ });
+ } catch (e) {
+ Expect.isTrue(e is TlsException);
+ var err = (e as TlsException).osError;
+ Expect.isTrue(err is OSError);
+ Expect.isTrue(err!.errorCode != 0);
+ server.close();
+ completer.complete(null);
+ }
+ });
+ return completer.future;
+}
+
+main() async {
+ asyncStart();
+ await test('pem', 'dartdart');
+ await test('p12', 'dartdart');
+ asyncEnd();
+}
diff --git a/tests/standalone_2/io/secure_socket_error_test.dart b/tests/standalone_2/io/secure_socket_error_test.dart
new file mode 100644
index 0000000..270ea31
--- /dev/null
+++ b/tests/standalone_2/io/secure_socket_error_test.dart
@@ -0,0 +1,88 @@
+// Copyright (c) 2020, 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.
+//
+// OtherResources=certificates/server_chain.pem
+// OtherResources=certificates/server_key.pem
+// OtherResources=certificates/trusted_certs.pem
+// OtherResources=certificates/server_chain.p12
+// OtherResources=certificates/server_key.p12
+// OtherResources=certificates/trusted_certs.p12
+
+import "package:async_helper/async_helper.dart";
+import "package:expect/expect.dart";
+import "package:path/path.dart";
+import "dart:async";
+import "dart:io";
+
+String localFile(path) => Platform.script.resolve(path).toFilePath();
+
+SecurityContext serverContext(String certType, String password) =>
+ new SecurityContext()
+ ..useCertificateChain(localFile('certificates/server_chain.$certType'),
+ password: password)
+ ..usePrivateKey(localFile('certificates/server_key.$certType'),
+ password: password);
+
+SecurityContext clientContext(String certType, String password) =>
+ new SecurityContext()
+ ..setTrustedCertificates(
+ localFile('certificates/trusted_certs.$certType'),
+ password: password);
+
+Future<HttpServer> startServer(String certType, String password) {
+ return HttpServer.bindSecure(
+ "localhost", 0, serverContext(certType, password),
+ backlog: 5)
+ .then((server) {
+ server.listen((HttpRequest request) {
+ request.listen((_) {}, onDone: () {
+ request.response.contentLength = 100;
+ for (int i = 0; i < 10; i++) {
+ request.response.add([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
+ }
+ request.response.close();
+ });
+ });
+ return server;
+ });
+}
+
+Future test(String certType, String password) {
+ List<int> body = <int>[];
+ Completer completer = new Completer();
+ startServer(certType, password).then((server) {
+ try {
+ SecureSocket.connect("localhost", server.port,
+ context: clientContext(certType, 'junkjunk'))
+ .then((socket) {
+ socket.write("GET / HTTP/1.0\r\nHost: localhost\r\n\r\n");
+ socket.close();
+ socket.listen((List<int> data) {
+ body.addAll(data);
+ }, onDone: () {
+ server.close();
+ completer.complete(null);
+ }, onError: (e, trace) {
+ server.close();
+ completer.complete(null);
+ });
+ });
+ } catch (e) {
+ Expect.isTrue(e is TlsException);
+ var err = (e as TlsException).osError;
+ Expect.isTrue(err is OSError);
+ Expect.isTrue(err!.errorCode != 0);
+ server.close();
+ completer.complete(null);
+ }
+ });
+ return completer.future;
+}
+
+main() async {
+ asyncStart();
+ await test('pem', 'dartdart');
+ await test('p12', 'dartdart');
+ asyncEnd();
+}
diff --git a/tools/VERSION b/tools/VERSION
index 858518f..4bd1333 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 17
PATCH 0
-PRERELEASE 271
+PRERELEASE 272
PRERELEASE_PATCH 0
\ No newline at end of file