[ package:vm_service ] Migrate service extension tests

Change-Id: If589d96d7d9549456f43fc9ed96a692737a711fe
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/332501
Commit-Queue: Ben Konyi <bkonyi@google.com>
Reviewed-by: Derek Xu <derekx@google.com>
This commit is contained in:
Ben Konyi 2023-10-31 15:53:44 +00:00 committed by Commit Queue
parent f54ecbdf31
commit 290a675add
5 changed files with 468 additions and 0 deletions

View file

@ -448,6 +448,44 @@ List<String> removeAdjacentDuplicates(List<String> fromList) {
return result;
}
typedef ServiceExtensionHandler = Future<Map<String, dynamic>> Function(
Map<String, dynamic> cb,
);
/// Registers a service extension and returns the actual service name used to
/// invoke the service.
Future<String> registerServiceHelper(
VmService primaryClient,
VmService serviceRegisterClient,
String serviceName,
ServiceExtensionHandler callback,
) async {
final serviceNameCompleter = Completer<String>();
late final StreamSubscription sub;
sub = primaryClient.onServiceEvent.listen((event) {
if (event.kind == EventKind.kServiceRegistered &&
event.method!.endsWith(serviceName)) {
serviceNameCompleter.complete(event.method!);
sub.cancel();
}
});
// TODO(bkonyi): if we end up in a situation where this call throws due to a
// prior subscription to the Service stream, we should do something similar
// to _subscribeDebugStream in this method.
await primaryClient.streamListen(EventStreams.kService);
// Register the service.
serviceRegisterClient.registerServiceCallback(serviceName, callback);
await serviceRegisterClient.registerService(serviceName, serviceName);
// Wait for the service registered event on the non-registering client to get
// the actual service name.
final actualServiceName = await serviceNameCompleter.future;
print("Service '$serviceName' registered as '$actualServiceName'");
await primaryClient.streamCancel(EventStreams.kService);
return actualServiceName;
}
Future<void> evaluateInFrameAndExpect(
VmService service,
String isolateId,

View file

@ -0,0 +1,189 @@
// Copyright (c) 2023, 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:vm_service/vm_service.dart';
import 'package:test/test.dart';
import 'package:vm_service/vm_service_io.dart';
import 'common/service_test_common.dart';
import 'common/test_helper.dart';
const successServiceName = 'successService';
const errorServiceName = 'errorService';
const serviceAlias = 'serviceAlias';
const paramKey = 'pkey';
const paramValue = 'pvalue';
const resultKey = 'rkey';
const resultValue = 'rvalue';
const errorCode = 5000;
const errorKey = 'ekey';
const errorValue = 'evalue';
const repetition = 5;
Future<void> testSuccessService(
VmService primaryClient,
VmService secondaryClient,
) async {
final successServiceRequests =
<(Map<String, dynamic>, Completer<Map<String, dynamic>>)>[];
final allRequestsReceivedCompleter = Completer<void>();
final successServiceMethod = await registerServiceHelper(
primaryClient,
secondaryClient,
successServiceName,
(params) async {
final completer = Completer<Map<String, dynamic>>();
successServiceRequests.add((params, completer));
if (successServiceRequests.length == repetition) {
allRequestsReceivedCompleter.complete();
}
return await completer.future;
},
);
// Testing parallel invocation of service which succeeds
final results = <Future<Response>>[
for (int i = 0; i < repetition; ++i)
primaryClient.callServiceExtension(
successServiceMethod,
args: {
paramKey + i.toString(): paramValue + i.toString(),
},
),
];
// Wait for all of the requests to be received before processing.
await allRequestsReceivedCompleter.future;
final completions = <Function>[];
for (final request in successServiceRequests) {
final (params, responseCompleter) = request;
final iteration = successServiceRequests.indexOf(request);
final end = iteration.toString();
// check requests while they arrive
expect(params[paramKey + end], paramValue + end);
// answer later
completions.add(() => responseCompleter.complete({
'result': {
resultKey + end: resultValue + end,
},
}));
}
// Shuffle and respond out of order.
completions.shuffle();
for (final c in completions) {
c();
}
final responses = await Future.wait(results);
for (int i = 0; i < responses.length; ++i) {
final response = responses[i];
expect(response, isNotNull);
expect(
response.json![resultKey + i.toString()],
resultValue + i.toString(),
);
}
}
Future<void> testErrorService(
VmService primaryClient,
VmService secondaryClient,
) async {
final serviceRequests =
<(Map<String, dynamic>, Completer<Map<String, dynamic>>)>[];
final allRequestsReceivedCompleter = Completer<void>();
final errorServiceMethod = await registerServiceHelper(
primaryClient,
secondaryClient,
errorServiceName,
(params) async {
final completer = Completer<Map<String, dynamic>>();
serviceRequests.add((params, completer));
if (serviceRequests.length == repetition) {
allRequestsReceivedCompleter.complete();
}
return await completer.future;
},
);
// Testing parallel invocation of service which returns an error
final results = <Future<Response>>[
for (int i = 0; i < repetition; ++i)
primaryClient.callServiceExtension(
errorServiceMethod,
args: {
paramKey + i.toString(): paramValue + i.toString(),
},
)
// We ignore these futures so that when they complete with an error
// without being awaited or do not have an error handler registered
// they won't cause an unhandled exception.
..ignore(),
];
// Wait for all of the requests to be received before processing.
await allRequestsReceivedCompleter.future;
final completions = <Function>[];
for (final request in serviceRequests) {
final (params, responseCompleter) = request;
final iteration = serviceRequests.indexOf(request);
final end = iteration.toString();
// check requests while they arrive
expect(params[paramKey + end], paramValue + end);
// answer later
completions.add(
() => responseCompleter.complete(
{
'error': {
'code': errorCode + iteration,
'data': {errorKey + end: errorValue + end},
'message': 'error message',
},
},
),
);
}
// Shuffle and respond out of order.
completions.shuffle();
for (final c in completions) {
c();
}
for (int i = 0; i < results.length; ++i) {
final response = results[i];
try {
await response;
fail('Response should be an error');
} on RPCError catch (e) {
expect(
e.data![errorKey + i.toString()],
errorValue + i.toString(),
);
}
}
}
final tests = <IsolateTest>[
(VmService primaryClient, IsolateRef isolateRef) async {
final secondaryClient = await vmServiceConnectUri(primaryClient.wsUri!);
await testSuccessService(primaryClient, secondaryClient);
await testErrorService(primaryClient, secondaryClient);
},
];
void main([args = const <String>[]]) => runIsolateTests(
args,
tests,
'external_service_asynchronous_invocation_test.dart',
);

View file

@ -0,0 +1,90 @@
// Copyright (c) 2023, 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:vm_service/vm_service.dart';
import 'package:test/test.dart';
import 'package:vm_service/vm_service_io.dart';
import 'common/service_test_common.dart';
import 'common/test_helper.dart';
const serviceName = 'disappearService';
const serviceAlias = 'serviceAlias';
const paramKey = 'pkey';
const paramValue = 'pvalue';
const repetition = 5;
final tests = <IsolateTest>[
(VmService primaryClient, IsolateRef isolateRef) async {
final secondaryClient = await vmServiceConnectUri(primaryClient.wsUri!);
final allRequestsReceivedCompleter = Completer<void>();
final requests = <Map<String, dynamic>>[];
// Register the service.
final serviceMethodName = await registerServiceHelper(
primaryClient,
secondaryClient,
serviceName,
(params) async {
final completer = Completer<Map<String, dynamic>>();
requests.add(params);
if (requests.length == repetition) {
allRequestsReceivedCompleter.complete();
}
// We never complete this future as we want to see how the client
// handles the service disappearing while there are outstanding
// requests.
return await completer.future;
},
);
// Invoke the service multiple times.
{
final results = <Future<Response>>[
for (int i = 0; i < repetition; ++i)
primaryClient.callServiceExtension(
serviceMethodName,
args: {
paramKey + i.toString(): paramValue + i.toString(),
},
),
];
// Wait for all of the requests to be received before processing.
await allRequestsReceivedCompleter.future;
// Verify the request parameters as a sanity check.
for (final params in requests) {
final iteration = requests.indexOf(params);
final end = iteration.toString();
// check requests while they arrive
expect(params[paramKey + end], paramValue + end);
}
// Disconnect the service client that registered the service extension.
await secondaryClient.dispose();
// Check that all of the outstanding requests complete with an RPC error.
for (final future in results) {
try {
await future;
fail('Service should have disappeared');
} on RPCError catch (e) {
expect(e.code, RPCErrorKind.kServiceDisappeared.code);
expect(e.message, 'Service has disappeared');
}
}
}
},
];
void main([args = const <String>[]]) => runIsolateTests(
args,
tests,
'external_service_disappear_test.dart',
);

View file

@ -0,0 +1,34 @@
// Copyright (c) 2023, 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 'package:vm_service/vm_service.dart';
import 'package:test/test.dart';
import 'common/test_helper.dart';
const serviceName = 'serviceName';
const serviceAlias = 'serviceAlias';
final tests = <IsolateTest>[
(VmService primaryClient, IsolateRef isolateRef) async {
// Register two unique services.
await primaryClient.registerService(serviceName, serviceAlias);
await primaryClient.registerService(serviceName + '2', serviceAlias + '2');
try {
// Try to register with an existing service name.
await primaryClient.registerService(serviceName, serviceAlias);
fail('Successfully registered service with duplicate name');
} on RPCError catch (e) {
expect(e.code, RPCErrorKind.kServiceAlreadyRegistered.code);
expect(e.message, 'Service already registered');
}
},
];
void main([args = const <String>[]]) => runIsolateTests(
args,
tests,
'external_service_registration_test.dart',
);

View file

@ -0,0 +1,117 @@
// Copyright (c) 2023, 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:vm_service/vm_service.dart';
import 'package:test/test.dart';
import 'package:vm_service/vm_service_io.dart';
import 'common/service_test_common.dart';
import 'common/test_helper.dart';
const successServiceName = 'successService';
const errorServiceName = 'errorService';
const serviceAlias = 'serviceAlias';
const paramKey = 'pkey';
const paramValue = 'pvalue';
const resultKey = 'rkey';
const resultValue = 'rvalue';
const errorCode = 5000;
const errorKey = 'ekey';
const errorValue = 'evalue';
const repetition = 5;
const errorMessage = 'Error message';
Future<void> testSuccessService(
VmService primaryClient,
VmService secondaryClient,
) async {
int requestCount = 0;
final successServiceMethod = await registerServiceHelper(
primaryClient,
secondaryClient,
successServiceName,
(params) async {
final i = requestCount.toString();
expect(params[paramKey + i], paramValue + i);
++requestCount;
return {
'result': {
resultKey + i: resultValue + i,
},
};
},
);
// Testing serial invocation of service which succeeds
{
for (int i = 0; i < repetition; ++i) {
final response = await primaryClient.callServiceExtension(
successServiceMethod,
args: {
paramKey + i.toString(): paramValue + i.toString(),
},
);
expect(
response.json![resultKey + i.toString()],
resultValue + i.toString(),
);
}
}
}
Future<void> testErrorService(
VmService primaryClient,
VmService secondaryClient,
) async {
int requestCount = 0;
final errorServiceMethod = await registerServiceHelper(
primaryClient,
secondaryClient,
errorServiceName,
(params) async {
final i = requestCount++;
final iStr = i.toString();
return {
'error': {
'code': errorCode + i,
'data': {errorKey + iStr: errorValue + iStr},
'message': errorMessage,
},
};
},
);
// Testing serial invocation of service which returns an error
for (int i = 0; i < repetition; ++i) {
try {
await primaryClient.callServiceExtension(
errorServiceMethod,
args: {
paramKey + i.toString(): paramValue + i.toString(),
},
);
fail('Response should be an error');
} on RPCError catch (e) {
expect(e.code, errorCode + i);
expect(e.data![errorKey + i.toString()], errorValue + i.toString());
expect(e.message, errorMessage);
}
}
}
final tests = <IsolateTest>[
(VmService primaryClient, IsolateRef isolateRef) async {
final secondaryClient = await vmServiceConnectUri(primaryClient.wsUri!);
await testSuccessService(primaryClient, secondaryClient);
await testErrorService(primaryClient, secondaryClient);
},
];
void main([args = const <String>[]]) => runIsolateTests(
args,
tests,
'external_service_asynchronous_invocation_test.dart',
);