[ DDS ] Migrate DDS specific tests from service/ and update package:dds_service_extensions to 1.7.0
TEST=migrated tests Change-Id: Id77f46c7e348614f707fc4d50d8a3101120b48ff Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/347941 Reviewed-by: Derek Xu <derekx@google.com> Commit-Queue: Ben Konyi <bkonyi@google.com>
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2024, 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:dds_service_extensions/dds_service_extensions.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:vm_service/vm_service.dart';
|
||||
|
||||
import 'common/test_helper.dart';
|
||||
|
||||
final tests = <VMTest>[
|
||||
(VmService service) async {
|
||||
final defaultClientName = 'client1';
|
||||
final clientName = 'agent-007';
|
||||
var result = await service.getClientName();
|
||||
expect(result.name, defaultClientName);
|
||||
|
||||
// Set the name for this client.
|
||||
await service.setClientName(clientName);
|
||||
|
||||
// Check it was set properly.
|
||||
result = await service.getClientName();
|
||||
expect(result.name, clientName);
|
||||
|
||||
// Check clearing works properly.
|
||||
await service.setClientName();
|
||||
|
||||
result = await service.getClientName();
|
||||
expect(result.name, defaultClientName);
|
||||
},
|
||||
];
|
||||
|
||||
void main([args = const <String>[]]) => runVMTests(
|
||||
args,
|
||||
tests,
|
||||
'client_name_rpc_test.dart',
|
||||
);
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) 2024, 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 'client_resume_approvals_common.dart';
|
||||
import 'common/service_test_common.dart';
|
||||
import 'common/test_helper.dart';
|
||||
|
||||
const String clientName = 'TestClient';
|
||||
const String otherClientName = 'OtherTestClient';
|
||||
|
||||
void fooBar() {
|
||||
int i = 0;
|
||||
print(i);
|
||||
}
|
||||
|
||||
final test = <IsolateTest>[
|
||||
hasPausedAtStart,
|
||||
(VmService service, IsolateRef isolate) async {
|
||||
final isolateId = isolate.id!;
|
||||
final client1 = await createClient(
|
||||
service: service,
|
||||
clientName: clientName,
|
||||
onPauseStart: true,
|
||||
);
|
||||
|
||||
final client2 = await createClient(
|
||||
service: service,
|
||||
clientName: otherClientName,
|
||||
onPauseStart: true,
|
||||
);
|
||||
|
||||
// Give resume approval for client1 to ensure approval state is cleaned up
|
||||
// properly when both client1 and client2 have disconnected.
|
||||
await client1.resume(isolateId);
|
||||
await hasPausedAtStart(service, isolate);
|
||||
|
||||
// Once client1 is disconnected, we should still be paused.
|
||||
client1.dispose();
|
||||
await hasPausedAtStart(service, isolate);
|
||||
|
||||
// Once client2 disconnects, there are no clients which require resume
|
||||
// approval. Since there were no resume requests made by clients which are
|
||||
// still connected, the isolate remains paused.
|
||||
client2.dispose();
|
||||
await hasPausedAtStart(service, isolate);
|
||||
|
||||
await service.resume(isolateId);
|
||||
},
|
||||
hasStoppedAtExit,
|
||||
];
|
||||
|
||||
void main([args = const <String>[]]) => runIsolateTests(
|
||||
args,
|
||||
test,
|
||||
'client_resume_approvals_approve_then_disconnect_test.dart',
|
||||
testeeConcurrent: fooBar,
|
||||
pauseOnStart: true,
|
||||
pauseOnExit: true,
|
||||
);
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) 2024, 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:dds_service_extensions/dds_service_extensions.dart';
|
||||
import 'package:vm_service/vm_service.dart';
|
||||
import 'package:vm_service/vm_service_io.dart';
|
||||
|
||||
Future<VmService> createClient({
|
||||
required VmService service,
|
||||
required String clientName,
|
||||
bool onPauseStart = false,
|
||||
bool onPauseExit = false,
|
||||
bool onPauseReload = false,
|
||||
}) async {
|
||||
final client = await vmServiceConnectUri(service.wsUri!);
|
||||
await client.setClientName(clientName);
|
||||
await client.requirePermissionToResume(
|
||||
onPauseStart: onPauseStart,
|
||||
onPauseExit: onPauseExit,
|
||||
onPauseReload: onPauseReload,
|
||||
);
|
||||
return client;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2024, 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 'client_resume_approvals_common.dart';
|
||||
import 'common/service_test_common.dart';
|
||||
import 'common/test_helper.dart';
|
||||
|
||||
const String clientName = 'TestClient';
|
||||
const String otherClientName = 'OtherTestClient';
|
||||
|
||||
void fooBar() {
|
||||
int i = 0;
|
||||
print(i);
|
||||
}
|
||||
|
||||
final test = <IsolateTest>[
|
||||
// Multiple clients, disconnect client awaiting approval.
|
||||
hasPausedAtStart,
|
||||
(VmService service, IsolateRef isolate) async {
|
||||
final isolateId = isolate.id!;
|
||||
final client1 = await createClient(
|
||||
service: service,
|
||||
clientName: clientName,
|
||||
onPauseStart: true,
|
||||
);
|
||||
final client2 = await createClient(
|
||||
service: service,
|
||||
clientName: otherClientName,
|
||||
onPauseStart: true,
|
||||
);
|
||||
|
||||
// Send a resume request on the test client so we'll resume once the other
|
||||
// clients which require approval disconnect.
|
||||
await service.resume(isolateId);
|
||||
await hasPausedAtStart(service, isolate);
|
||||
|
||||
// Once client1 is disconnected, we should still be paused.
|
||||
await client1.dispose();
|
||||
await hasPausedAtStart(service, isolate);
|
||||
|
||||
// Once client2 disconnects, there are no clients which require resume
|
||||
// approval. Ensure we resume immediately so we don't deadlock waiting for
|
||||
// approvals from disconnected clients.
|
||||
await client2.dispose();
|
||||
},
|
||||
hasStoppedAtExit,
|
||||
];
|
||||
|
||||
void main([args = const <String>[]]) => runIsolateTests(
|
||||
args,
|
||||
test,
|
||||
'client_resume_approvals_disconnect_test.dart',
|
||||
testeeConcurrent: fooBar,
|
||||
pauseOnStart: true,
|
||||
pauseOnExit: true,
|
||||
);
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2024, 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 'client_resume_approvals_common.dart';
|
||||
import 'common/service_test_common.dart';
|
||||
import 'common/test_helper.dart';
|
||||
|
||||
const String clientName = 'TestClient';
|
||||
|
||||
void fooBar() {
|
||||
int i = 0;
|
||||
print(i);
|
||||
}
|
||||
|
||||
final test = <IsolateTest>[
|
||||
// Multiple clients, same client names.
|
||||
(VmService service, IsolateRef isolateRef) async {
|
||||
// ignore: unused_local_variable
|
||||
final client1 = await createClient(
|
||||
service: service,
|
||||
clientName: clientName,
|
||||
onPauseStart: true,
|
||||
);
|
||||
final client2 = await createClient(
|
||||
service: service,
|
||||
clientName: clientName,
|
||||
);
|
||||
await hasPausedAtStart(service, isolateRef);
|
||||
await resumeIsolate(client2, isolateRef);
|
||||
},
|
||||
hasStoppedAtExit,
|
||||
];
|
||||
|
||||
void main([args = const <String>[]]) => runIsolateTests(
|
||||
args,
|
||||
test,
|
||||
'client_resume_approvals_identical_names_test.dart',
|
||||
testeeConcurrent: fooBar,
|
||||
pauseOnStart: true,
|
||||
pauseOnExit: true,
|
||||
);
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) 2024, 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:dds_service_extensions/dds_service_extensions.dart';
|
||||
import 'package:vm_service/vm_service.dart';
|
||||
|
||||
import 'client_resume_approvals_common.dart';
|
||||
import 'common/service_test_common.dart';
|
||||
import 'common/test_helper.dart';
|
||||
|
||||
const String clientName = 'TestClient';
|
||||
const String otherClientName = 'OtherTestClient';
|
||||
const String dummyClientName = 'DummyClient';
|
||||
|
||||
void fooBar() {
|
||||
int i = 0;
|
||||
print(i);
|
||||
}
|
||||
|
||||
final test = <IsolateTest>[
|
||||
// Multiple clients, different client names.
|
||||
(VmService service, IsolateRef isolateRef) async {
|
||||
final isolateId = isolateRef.id!;
|
||||
final client1 = await createClient(
|
||||
service: service,
|
||||
clientName: clientName,
|
||||
onPauseStart: true,
|
||||
onPauseExit: true,
|
||||
);
|
||||
final client2 = await createClient(
|
||||
service: service,
|
||||
clientName: otherClientName,
|
||||
);
|
||||
// ignore: unused_local_variable
|
||||
final client3 = await createClient(
|
||||
service: service,
|
||||
clientName: 'DummyClient',
|
||||
);
|
||||
|
||||
await hasPausedAtStart(service, isolateRef);
|
||||
await client2.resume(isolateId);
|
||||
await hasPausedAtStart(service, isolateRef);
|
||||
await client1.resume(isolateId);
|
||||
await hasStoppedAtExit(service, isolateRef);
|
||||
await client2.requirePermissionToResume(
|
||||
onPauseExit: true,
|
||||
);
|
||||
await client1.resume(isolateId);
|
||||
await hasStoppedAtExit(service, isolateRef);
|
||||
await client2.resume(isolateId);
|
||||
},
|
||||
];
|
||||
|
||||
void main([args = const <String>[]]) => runIsolateTests(
|
||||
args,
|
||||
test,
|
||||
'client_resume_approvals_multiple_names_test.dart',
|
||||
testeeConcurrent: fooBar,
|
||||
pauseOnStart: true,
|
||||
pauseOnExit: true,
|
||||
);
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) 2024, 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:dds_service_extensions/dds_service_extensions.dart';
|
||||
import 'package:vm_service/vm_service.dart';
|
||||
import 'package:vm_service/vm_service_io.dart';
|
||||
|
||||
import 'client_resume_approvals_common.dart';
|
||||
import 'common/service_test_common.dart';
|
||||
import 'common/test_helper.dart';
|
||||
|
||||
const String clientName = 'TestClient';
|
||||
const String otherClientName = 'OtherTestClient';
|
||||
const String dummyClientName = 'DummyClient';
|
||||
|
||||
void fooBar() {
|
||||
int i = 0;
|
||||
print(i);
|
||||
}
|
||||
|
||||
final test = <IsolateTest>[
|
||||
// Remove required approvals via name change.
|
||||
(VmService service, IsolateRef isolateRef) async {
|
||||
final isolateId = isolateRef.id!;
|
||||
|
||||
// Create two clients with the same name.
|
||||
final client1 = await createClient(
|
||||
service: service,
|
||||
clientName: clientName,
|
||||
onPauseStart: true,
|
||||
);
|
||||
// Don't use the helper so we don't call `requirePermissionToResume`
|
||||
final client2 = await vmServiceConnectUri(service.wsUri!);
|
||||
await client2.setClientName(clientName);
|
||||
|
||||
final client3 = await createClient(
|
||||
service: service,
|
||||
clientName: otherClientName,
|
||||
);
|
||||
|
||||
// Check that client3 can't resume the isolate on its own.
|
||||
await hasPausedAtStart(service, isolateRef);
|
||||
await client3.resume(isolateId);
|
||||
await hasPausedAtStart(service, isolateRef);
|
||||
|
||||
// Change the name of client1. Since client2 has the same name that client1
|
||||
// originally had, the service still requires approval to resume the
|
||||
// isolate.
|
||||
await client1.setClientName('foobar');
|
||||
await hasPausedAtStart(service, isolateRef);
|
||||
await client2.setClientName('baz');
|
||||
},
|
||||
hasStoppedAtExit,
|
||||
];
|
||||
|
||||
void main([args = const <String>[]]) => runIsolateTests(
|
||||
args,
|
||||
test,
|
||||
'client_resume_approvals_name_change_test.dart',
|
||||
testeeConcurrent: fooBar,
|
||||
pauseOnStart: true,
|
||||
pauseOnExit: true,
|
||||
);
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright (c) 2024, 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 'client_resume_approvals_common.dart';
|
||||
import 'common/service_test_common.dart';
|
||||
import 'common/test_helper.dart';
|
||||
|
||||
const String clientName = 'TestClient';
|
||||
const String otherClientName = 'OtherTestClient';
|
||||
|
||||
void fooBar() {
|
||||
// ignore: unused_local_variable
|
||||
int i = 0;
|
||||
while (true) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
late VmService client1;
|
||||
late VmService client2;
|
||||
|
||||
final test = <IsolateTest>[
|
||||
// Multiple clients, hot reload approval.
|
||||
(VmService service, IsolateRef isolateRef) async {
|
||||
client1 = await createClient(
|
||||
service: service,
|
||||
clientName: clientName,
|
||||
onPauseReload: true,
|
||||
);
|
||||
client2 = await createClient(
|
||||
service: service,
|
||||
clientName: otherClientName,
|
||||
onPauseReload: true,
|
||||
);
|
||||
},
|
||||
hasPausedAtStart,
|
||||
// Paused on start, resume.
|
||||
resumeIsolate,
|
||||
// Reload and then pause.
|
||||
reloadSources(pause: true),
|
||||
hasStoppedPostRequest,
|
||||
(VmService service, IsolateRef isolateRef) async {
|
||||
final isolateId = isolateRef.id!;
|
||||
// Check that client2 can't resume the isolate on its own.
|
||||
await client2.resume(isolateId);
|
||||
await hasStoppedPostRequest(service, isolateRef);
|
||||
await resumeIsolate(client1, isolateRef);
|
||||
},
|
||||
];
|
||||
|
||||
void main([args = const <String>[]]) => runIsolateTests(
|
||||
args,
|
||||
test,
|
||||
'client_resume_approvals_reload_test.dart',
|
||||
testeeConcurrent: fooBar,
|
||||
pauseOnStart: true,
|
||||
);
|
||||
@@ -0,0 +1,717 @@
|
||||
// Copyright (c) 2024, 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.
|
||||
|
||||
// NOTE: this file was originally copied from package:vm_service.
|
||||
|
||||
library service_test_common;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:path/path.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:vm_service/vm_service.dart';
|
||||
|
||||
typedef IsolateTest = Future<void> Function(
|
||||
VmService service,
|
||||
IsolateRef isolate,
|
||||
);
|
||||
typedef VMTest = Future<void> Function(VmService service);
|
||||
|
||||
Future<void> smartNext(VmService service, IsolateRef isolateRef) async {
|
||||
print('smartNext');
|
||||
final isolate = await service.getIsolate(isolateRef.id!);
|
||||
final Event event = isolate.pauseEvent!;
|
||||
if (event.kind == EventKind.kPauseBreakpoint) {
|
||||
// TODO(bkonyi): remove needless refetching of isolate object.
|
||||
if (event.atAsyncSuspension ?? false) {
|
||||
return asyncNext(service, isolateRef);
|
||||
} else {
|
||||
return syncNext(service, isolateRef);
|
||||
}
|
||||
} else {
|
||||
throw 'The program is already running';
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> asyncNext(VmService service, IsolateRef isolateRef) async {
|
||||
print('asyncNext');
|
||||
final id = isolateRef.id!;
|
||||
final isolate = await service.getIsolate(id);
|
||||
final event = isolate.pauseEvent!;
|
||||
if (event.kind == EventKind.kPauseBreakpoint) {
|
||||
final dynamic event = isolate.pauseEvent;
|
||||
if (!event.atAsyncSuspension) {
|
||||
throw 'No async continuation at this location';
|
||||
} else {
|
||||
await service.resume(id, step: 'OverAsyncSuspension');
|
||||
}
|
||||
} else {
|
||||
throw 'The program is already running';
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> syncNext(VmService service, IsolateRef isolateRef) async {
|
||||
print('syncNext');
|
||||
final id = isolateRef.id!;
|
||||
final isolate = await service.getIsolate(id);
|
||||
final event = isolate.pauseEvent!;
|
||||
if (event.kind == EventKind.kPauseBreakpoint) {
|
||||
await service.resume(id, step: 'Over');
|
||||
} else {
|
||||
throw 'The program is already running';
|
||||
}
|
||||
}
|
||||
|
||||
// WARNING: interleaving calls based on hasPausedFor using Future.wait() may
|
||||
// cause the debug stream to be cancelled after one of the checks completes.
|
||||
// If another check is waiting on an event, it will no longer be notified of
|
||||
// the event, causing the test to hang.
|
||||
Future<void> hasPausedFor(
|
||||
VmService service,
|
||||
IsolateRef isolateRef,
|
||||
String kind,
|
||||
) async {
|
||||
Completer<dynamic>? completer = Completer();
|
||||
late StreamSubscription<Event> subscription;
|
||||
subscription = service.onDebugEvent.listen((event) async {
|
||||
print('subscription event: $event');
|
||||
if ((isolateRef.id == event.isolate!.id) && (event.kind == kind)) {
|
||||
if (completer != null) {
|
||||
try {
|
||||
await service.streamCancel(EventStreams.kDebug);
|
||||
} catch (_) {/* swallow exception */} finally {
|
||||
await subscription.cancel();
|
||||
completer?.complete();
|
||||
print('complete');
|
||||
completer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await _subscribeDebugStream(service);
|
||||
|
||||
// Pause may have happened before we subscribed.
|
||||
final id = isolateRef.id!;
|
||||
final isolate = await service.getIsolate(id);
|
||||
final event = isolate.pauseEvent!;
|
||||
print(event);
|
||||
if (event.kind == kind) {
|
||||
if (completer != null) {
|
||||
try {
|
||||
await service.streamCancel(EventStreams.kDebug);
|
||||
} catch (_) {/* swallow exception */} finally {
|
||||
await subscription.cancel();
|
||||
completer?.complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
return completer?.future; // Will complete when breakpoint hit.
|
||||
}
|
||||
|
||||
// WARNING: interleaving calls based on hasPausedFor using Future.wait() may
|
||||
// cause the debug stream to be cancelled after one of the checks completes.
|
||||
// If another check is waiting on an event, it will no longer be notified of
|
||||
// the event, causing the test to hang.
|
||||
Future<void> hasStoppedAtBreakpoint(VmService service, IsolateRef isolate) {
|
||||
return hasPausedFor(service, isolate, EventKind.kPauseBreakpoint);
|
||||
}
|
||||
|
||||
// WARNING: interleaving calls based on hasPausedFor using Future.wait() may
|
||||
// cause the debug stream to be cancelled after one of the checks completes.
|
||||
// If another check is waiting on an event, it will no longer be notified of
|
||||
// the event, causing the test to hang.
|
||||
Future<void> hasStoppedPostRequest(VmService service, IsolateRef isolate) {
|
||||
return hasPausedFor(service, isolate, EventKind.kPausePostRequest);
|
||||
}
|
||||
|
||||
// WARNING: interleaving calls based on hasPausedFor using Future.wait() may
|
||||
// cause the debug stream to be cancelled after one of the checks completes.
|
||||
// If another check is waiting on an event, it will no longer be notified of
|
||||
// the event, causing the test to hang.
|
||||
Future<void> hasStoppedWithUnhandledException(
|
||||
VmService service,
|
||||
IsolateRef isolate,
|
||||
) {
|
||||
return hasPausedFor(service, isolate, EventKind.kPauseException);
|
||||
}
|
||||
|
||||
// WARNING: interleaving calls based on hasPausedFor using Future.wait() may
|
||||
// cause the debug stream to be cancelled after one of the checks completes.
|
||||
// If another check is waiting on an event, it will no longer be notified of
|
||||
// the event, causing the test to hang.
|
||||
Future<void> hasStoppedAtExit(VmService service, IsolateRef isolate) {
|
||||
return hasPausedFor(service, isolate, EventKind.kPauseExit);
|
||||
}
|
||||
|
||||
// WARNING: interleaving calls based on hasPausedFor using Future.wait() may
|
||||
// cause the debug stream to be cancelled after one of the checks completes.
|
||||
// If another check is waiting on an event, it will no longer be notified of
|
||||
// the event, causing the test to hang.
|
||||
Future<void> hasPausedAtStart(VmService service, IsolateRef isolate) {
|
||||
return hasPausedFor(service, isolate, EventKind.kPauseStart);
|
||||
}
|
||||
|
||||
Future<void> markDartColonLibrariesDebuggable(
|
||||
VmService service,
|
||||
IsolateRef isolateRef,
|
||||
) async {
|
||||
final isolateId = isolateRef.id!;
|
||||
final isolate = await service.getIsolate(isolateId);
|
||||
final requests = <Future>[];
|
||||
for (final libRef in isolate.libraries!) {
|
||||
final lib = await service.getObject(isolateId, libRef.id!) as Library;
|
||||
if (lib.uri!.startsWith('dart:') && !lib.uri!.startsWith('dart:_')) {
|
||||
requests.add(service.setLibraryDebuggable(isolateId, lib.id!, true));
|
||||
}
|
||||
}
|
||||
await Future.wait(requests);
|
||||
}
|
||||
|
||||
// Currying is your friend.
|
||||
IsolateTest setBreakpointAtLine(int line) {
|
||||
return (VmService service, IsolateRef isolateRef) async {
|
||||
print('Setting breakpoint for line $line');
|
||||
final isolateId = isolateRef.id!;
|
||||
final isolate = await service.getIsolate(isolateId);
|
||||
final Library lib =
|
||||
(await service.getObject(isolateId, isolate.rootLib!.id!)) as Library;
|
||||
final script = lib.scripts!.first;
|
||||
|
||||
final Breakpoint bpt =
|
||||
await service.addBreakpoint(isolateId, script.id!, line);
|
||||
print('Breakpoint is $bpt');
|
||||
};
|
||||
}
|
||||
|
||||
IsolateTest setBreakpointAtUriAndLine(String uri, int line) {
|
||||
return (VmService service, IsolateRef isolateRef) async {
|
||||
print('Setting breakpoint for line $line in $uri');
|
||||
final Breakpoint bpt =
|
||||
await service.addBreakpointWithScriptUri(isolateRef.id!, uri, line);
|
||||
print('Breakpoint is $bpt');
|
||||
expect(bpt, isNotNull);
|
||||
};
|
||||
}
|
||||
|
||||
IsolateTest setBreakpointAtLineColumn(int line, int column) {
|
||||
return (VmService service, IsolateRef isolateRef) async {
|
||||
print('Setting breakpoint for line $line column $column');
|
||||
final isolateId = isolateRef.id!;
|
||||
final isolate = await service.getIsolate(isolateId);
|
||||
final lib =
|
||||
await service.getObject(isolateId, isolate.rootLib!.id!) as Library;
|
||||
final ScriptRef script = lib.scripts!.firstWhere((s) => s.uri == lib.uri);
|
||||
final Breakpoint bpt = await service.addBreakpoint(
|
||||
isolateId,
|
||||
script.id!,
|
||||
line,
|
||||
column: column,
|
||||
);
|
||||
print('Breakpoint is $bpt');
|
||||
expect(bpt, isNotNull);
|
||||
};
|
||||
}
|
||||
|
||||
IsolateTest stoppedAtLine(int line) {
|
||||
return (VmService service, IsolateRef isolateRef) async {
|
||||
print('Checking we are at line $line');
|
||||
|
||||
// Make sure that the isolate has stopped.
|
||||
final id = isolateRef.id!;
|
||||
final isolate = await service.getIsolate(id);
|
||||
final event = isolate.pauseEvent!;
|
||||
expect(event.kind != EventKind.kResume, isTrue);
|
||||
|
||||
final stack = await service.getStack(id);
|
||||
|
||||
final frames = stack.frames!;
|
||||
expect(frames.length, greaterThanOrEqualTo(1));
|
||||
|
||||
final top = frames[0];
|
||||
final Script script =
|
||||
(await service.getObject(id, top.location!.script!.id!)) as Script;
|
||||
final int actualLine =
|
||||
script.getLineNumberFromTokenPos(top.location!.tokenPos!)!;
|
||||
if (actualLine != line) {
|
||||
print('Actual: $actualLine Line: $line');
|
||||
final sb = StringBuffer();
|
||||
sb.write('Expected to be at line $line but actually at line $actualLine');
|
||||
sb.write('\nFull stack trace:\n');
|
||||
for (Frame f in frames) {
|
||||
sb.write(
|
||||
' $f [${script.getLineNumberFromTokenPos(f.location!.tokenPos!)}]\n',
|
||||
);
|
||||
}
|
||||
throw sb.toString();
|
||||
} else {
|
||||
print('Program is stopped at line: $line');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Future<void> resumeIsolate(VmService service, IsolateRef isolate) async {
|
||||
final Completer completer = Completer();
|
||||
late StreamSubscription<Event> subscription;
|
||||
bool cancelStreamAfterResume = false;
|
||||
subscription = service.onDebugEvent.listen((event) async {
|
||||
if (event.kind == EventKind.kResume) {
|
||||
try {
|
||||
if (cancelStreamAfterResume) {
|
||||
await service.streamCancel(EventStreams.kDebug);
|
||||
}
|
||||
} catch (_) {/* swallow exception */} finally {
|
||||
await subscription.cancel();
|
||||
completer.complete();
|
||||
}
|
||||
}
|
||||
});
|
||||
cancelStreamAfterResume = await _subscribeDebugStream(service);
|
||||
await service.resume(isolate.id!);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
Future<bool> _subscribeDebugStream(VmService service) async {
|
||||
try {
|
||||
await service.streamListen(EventStreams.kDebug);
|
||||
return true;
|
||||
} catch (_) {
|
||||
/* swallow exception */
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _unsubscribeDebugStream(VmService service) async {
|
||||
try {
|
||||
await service.streamCancel(EventStreams.kDebug);
|
||||
} catch (_) {
|
||||
/* swallow exception */
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> resumeAndAwaitEvent(
|
||||
VmService service,
|
||||
IsolateRef isolateRef,
|
||||
String streamId,
|
||||
Function(Event) onEvent,
|
||||
) async {
|
||||
final completer = Completer<void>();
|
||||
late final StreamSubscription sub;
|
||||
sub = service.onEvent(streamId).listen((event) async {
|
||||
await onEvent(event);
|
||||
await sub.cancel();
|
||||
await service.streamCancel(streamId);
|
||||
completer.complete();
|
||||
});
|
||||
|
||||
await service.streamListen(streamId);
|
||||
await service.resume(isolateRef.id!);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
IsolateTest resumeIsolateAndAwaitEvent(
|
||||
String streamId,
|
||||
Function(Event) onEvent,
|
||||
) {
|
||||
return (VmService service, IsolateRef isolate) async =>
|
||||
resumeAndAwaitEvent(service, isolate, streamId, onEvent);
|
||||
}
|
||||
|
||||
Future<void> stepOver(VmService service, IsolateRef isolateRef) async {
|
||||
await _subscribeDebugStream(service);
|
||||
await service.resume(isolateRef.id!, step: 'Over');
|
||||
await hasStoppedAtBreakpoint(service, isolateRef);
|
||||
await _unsubscribeDebugStream(service);
|
||||
}
|
||||
|
||||
Future<void> stepInto(VmService service, IsolateRef isolateRef) async {
|
||||
await _subscribeDebugStream(service);
|
||||
await service.resume(isolateRef.id!, step: 'Into');
|
||||
await hasStoppedAtBreakpoint(service, isolateRef);
|
||||
await _unsubscribeDebugStream(service);
|
||||
}
|
||||
|
||||
Future<void> stepOut(VmService service, IsolateRef isolateRef) async {
|
||||
await _subscribeDebugStream(service);
|
||||
await service.resume(isolateRef.id!, step: 'Out');
|
||||
await hasStoppedAtBreakpoint(service, isolateRef);
|
||||
await _unsubscribeDebugStream(service);
|
||||
}
|
||||
|
||||
IsolateTest resumeProgramRecordingStops(
|
||||
List<String> recordStops,
|
||||
bool includeCaller,
|
||||
) {
|
||||
return (VmService service, IsolateRef isolateRef) async {
|
||||
final completer = Completer<void>();
|
||||
|
||||
late StreamSubscription subscription;
|
||||
subscription = service.onDebugEvent.listen((event) async {
|
||||
if (event.kind == EventKind.kPauseBreakpoint) {
|
||||
final stack = await service.getStack(isolateRef.id!);
|
||||
expect(stack.frames!.length, greaterThanOrEqualTo(2));
|
||||
|
||||
String brokeAt =
|
||||
await _locationToString(service, isolateRef, stack.frames![0]);
|
||||
if (includeCaller) {
|
||||
brokeAt =
|
||||
'$brokeAt (${await _locationToString(service, isolateRef, stack.frames![1])})';
|
||||
}
|
||||
recordStops.add(brokeAt);
|
||||
await service.resume(isolateRef.id!);
|
||||
} else if (event.kind == EventKind.kPauseExit) {
|
||||
await subscription.cancel();
|
||||
await service.streamCancel(EventStreams.kDebug);
|
||||
completer.complete();
|
||||
}
|
||||
});
|
||||
|
||||
await service.streamListen(EventStreams.kDebug);
|
||||
await service.resume(isolateRef.id!);
|
||||
return completer.future;
|
||||
};
|
||||
}
|
||||
|
||||
Future<String> _locationToString(
|
||||
VmService service,
|
||||
IsolateRef isolateRef,
|
||||
Frame frame,
|
||||
) async {
|
||||
final location = frame.location!;
|
||||
final Script script =
|
||||
await service.getObject(isolateRef.id!, location.script!.id!) as Script;
|
||||
final scriptName = basename(script.uri!);
|
||||
final tokenPos = location.tokenPos!;
|
||||
final line = script.getLineNumberFromTokenPos(tokenPos);
|
||||
final column = script.getColumnNumberFromTokenPos(tokenPos);
|
||||
return '$scriptName:$line:$column';
|
||||
}
|
||||
|
||||
IsolateTest runStepThroughProgramRecordingStops(List<String> recordStops) {
|
||||
return (VmService service, IsolateRef isolateRef) async {
|
||||
final completer = Completer<void>();
|
||||
|
||||
late StreamSubscription subscription;
|
||||
subscription = service.onDebugEvent.listen((event) async {
|
||||
if (event.kind == EventKind.kPauseBreakpoint) {
|
||||
final isolate = await service.getIsolate(isolateRef.id!);
|
||||
final frame = isolate.pauseEvent!.topFrame!;
|
||||
recordStops.add(await _locationToString(service, isolateRef, frame));
|
||||
if (event.atAsyncSuspension ?? false) {
|
||||
await service.resume(
|
||||
isolateRef.id!,
|
||||
step: StepOption.kOverAsyncSuspension,
|
||||
);
|
||||
} else {
|
||||
await service.resume(isolateRef.id!, step: StepOption.kOver);
|
||||
}
|
||||
} else if (event.kind == EventKind.kPauseExit) {
|
||||
await subscription.cancel();
|
||||
await service.streamCancel(EventStreams.kDebug);
|
||||
completer.complete();
|
||||
}
|
||||
});
|
||||
await service.streamListen(EventStreams.kDebug);
|
||||
await service.resume(isolateRef.id!);
|
||||
return completer.future;
|
||||
};
|
||||
}
|
||||
|
||||
IsolateTest runStepIntoThroughProgramRecordingStops(List<String> recordStops) {
|
||||
return (VmService service, IsolateRef isolateRef) async {
|
||||
final completer = Completer<void>();
|
||||
|
||||
late StreamSubscription subscription;
|
||||
subscription = service.onDebugEvent.listen((event) async {
|
||||
if (event.kind == EventKind.kPauseBreakpoint) {
|
||||
final isolate = await service.getIsolate(isolateRef.id!);
|
||||
final frame = isolate.pauseEvent!.topFrame!;
|
||||
recordStops.add(await _locationToString(service, isolateRef, frame));
|
||||
await service.resume(isolateRef.id!, step: StepOption.kInto);
|
||||
} else if (event.kind == EventKind.kPauseExit) {
|
||||
await subscription.cancel();
|
||||
await service.streamCancel(EventStreams.kDebug);
|
||||
completer.complete();
|
||||
}
|
||||
});
|
||||
await service.streamListen(EventStreams.kDebug);
|
||||
await service.resume(isolateRef.id!);
|
||||
return completer.future;
|
||||
};
|
||||
}
|
||||
|
||||
IsolateTest checkRecordedStops(
|
||||
List<String> recordStops,
|
||||
List<String> expectedStops, {
|
||||
bool removeDuplicates = false,
|
||||
bool debugPrint = false,
|
||||
String? debugPrintFile,
|
||||
int? debugPrintLine,
|
||||
}) {
|
||||
return (VmService service, IsolateRef isolate) async {
|
||||
if (debugPrint) {
|
||||
for (int i = 0; i < recordStops.length; i++) {
|
||||
final String line = recordStops[i];
|
||||
String output = line;
|
||||
final int firstColon = line.indexOf(':');
|
||||
final int lastColon = line.lastIndexOf(':');
|
||||
if (debugPrintFile != null &&
|
||||
debugPrintLine != null &&
|
||||
firstColon > 0 &&
|
||||
lastColon > 0) {
|
||||
final int lineNumber =
|
||||
int.parse(line.substring(firstColon + 1, lastColon));
|
||||
final int relativeLineNumber = lineNumber - debugPrintLine;
|
||||
final columnNumber = line.substring(lastColon + 1);
|
||||
final file = line.substring(0, firstColon);
|
||||
if (file == debugPrintFile) {
|
||||
output = '\$file:\${LINE+$relativeLineNumber}:$columnNumber';
|
||||
}
|
||||
}
|
||||
final String comma = i == recordStops.length - 1 ? '' : ',';
|
||||
print("'$output'$comma");
|
||||
}
|
||||
}
|
||||
if (removeDuplicates) {
|
||||
recordStops = removeAdjacentDuplicates(recordStops);
|
||||
expectedStops = removeAdjacentDuplicates(expectedStops);
|
||||
}
|
||||
|
||||
// Single stepping may record extra stops.
|
||||
// Allow the extra ones as long as the expected ones are recorded.
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
while (i < recordStops.length && j < expectedStops.length) {
|
||||
if (recordStops[i] != expectedStops[j]) {
|
||||
// Check if recordStops[i] is an extra stop.
|
||||
int k = i + 1;
|
||||
while (k < recordStops.length && recordStops[k] != expectedStops[j]) {
|
||||
k++;
|
||||
}
|
||||
if (k < recordStops.length) {
|
||||
// Allow and ignore extra recorded stops from i to k-1.
|
||||
i = k;
|
||||
} else {
|
||||
// This will report an error.
|
||||
expect(recordStops[i], expectedStops[j]);
|
||||
}
|
||||
}
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
|
||||
expect(
|
||||
recordStops.length >= expectedStops.length,
|
||||
true,
|
||||
reason: 'Expects at least ${expectedStops.length} breaks, '
|
||||
'got ${recordStops.length}.',
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
List<String> removeAdjacentDuplicates(List<String> fromList) {
|
||||
final List<String> result = <String>[];
|
||||
String? latestLine;
|
||||
for (String s in fromList) {
|
||||
if (s == latestLine) continue;
|
||||
latestLine = s;
|
||||
result.add(s);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Waits for ServiceProtocolInfo.serverUri to be populated.
|
||||
Future<ServiceProtocolInfo> waitForServiceInfo() async {
|
||||
print('Waiting for the VM service URI to become available...');
|
||||
var info = await Service.getInfo();
|
||||
while (info.serverUri == null) {
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
info = await Service.getInfo();
|
||||
}
|
||||
print('VM service URI has become available: ${info.serverUri}');
|
||||
return info;
|
||||
}
|
||||
|
||||
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,
|
||||
String expression,
|
||||
String expected, {
|
||||
Map<String, String>? scope,
|
||||
String? kind,
|
||||
int topFrame = 0,
|
||||
}) async {
|
||||
final result = await service.evaluateInFrame(
|
||||
isolateId,
|
||||
topFrame,
|
||||
expression,
|
||||
scope: scope,
|
||||
) as InstanceRef;
|
||||
expect(result.valueAsString, expected);
|
||||
if (kind != null) {
|
||||
expect(result.kind!, kind);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> evaluateAndExpect(
|
||||
VmService service,
|
||||
String isolateId,
|
||||
String targetId,
|
||||
String expression,
|
||||
String expected, {
|
||||
Map<String, String>? scope,
|
||||
String? kind,
|
||||
}) async {
|
||||
final result = await service.evaluate(
|
||||
isolateId,
|
||||
targetId,
|
||||
expression,
|
||||
scope: scope,
|
||||
) as InstanceRef;
|
||||
expect(result.valueAsString, expected);
|
||||
if (kind != null) {
|
||||
expect(result.kind!, kind);
|
||||
}
|
||||
}
|
||||
|
||||
Future<HeapSnapshotGraph> fetchHeapSnapshot(
|
||||
VmService service,
|
||||
IsolateRef isolateRef,
|
||||
) async {
|
||||
final isolateId = isolateRef.id!;
|
||||
final completer = Completer<void>();
|
||||
late final StreamSubscription sub;
|
||||
final data = <ByteData>[];
|
||||
sub = service.onHeapSnapshotEvent.listen((event) async {
|
||||
data.add(event.data!);
|
||||
if (event.last == true) {
|
||||
await sub.cancel();
|
||||
await service.streamCancel(EventStreams.kHeapSnapshot);
|
||||
completer.complete();
|
||||
}
|
||||
});
|
||||
await service.streamListen(EventStreams.kHeapSnapshot);
|
||||
await service.requestHeapSnapshot(isolateId);
|
||||
await completer.future;
|
||||
return HeapSnapshotGraph.fromChunks(data);
|
||||
}
|
||||
|
||||
IsolateTest reloadSources({bool pause = false}) {
|
||||
return (VmService service, IsolateRef isolateRef) async {
|
||||
await service.reloadSources(isolateRef.id!, pause: pause);
|
||||
};
|
||||
}
|
||||
|
||||
IsolateTest hasLocalVarInTopStackFrame(String varName) {
|
||||
return (VmService service, IsolateRef isolateRef) async {
|
||||
print("Checking we have variable '$varName' in the top frame");
|
||||
|
||||
final isolateId = isolateRef.id!;
|
||||
// Make sure that the isolate has stopped.
|
||||
final isolate = await service.getIsolate(isolateId);
|
||||
expect(isolate.pauseEvent, isNotNull);
|
||||
expect(isolate.pauseEvent!.kind, isNot(EventKind.kResume));
|
||||
|
||||
final stack = await service.getStack(isolateId);
|
||||
final frames = stack.frames!;
|
||||
expect(frames.length, greaterThanOrEqualTo(1));
|
||||
|
||||
final top = frames[0];
|
||||
final vars = top.vars!;
|
||||
for (final variable in vars) {
|
||||
if (variable.name == varName) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
final sb = StringBuffer();
|
||||
sb.write('Expected to find $varName in top awaiter stack frame, found ');
|
||||
if (vars.isEmpty) {
|
||||
sb.writeln('no variables');
|
||||
} else {
|
||||
sb.writeln('these instead:');
|
||||
for (var variable in vars) {
|
||||
sb.writeln('\t${variable.name}');
|
||||
}
|
||||
}
|
||||
throw sb.toString();
|
||||
};
|
||||
}
|
||||
|
||||
IsolateTest stoppedInFunction(String functionName) {
|
||||
return (VmService service, IsolateRef isolateRef) async {
|
||||
print('Checking we are in function: $functionName');
|
||||
|
||||
final isolateId = isolateRef.id!;
|
||||
final stack = await service.getStack(isolateId);
|
||||
|
||||
final frames = stack.frames!;
|
||||
expect(frames, isNotEmpty);
|
||||
|
||||
final topFrame = frames[0];
|
||||
final function = await service.getObject(
|
||||
isolateId,
|
||||
topFrame.function!.id!,
|
||||
) as Func;
|
||||
final name = function.name!;
|
||||
if (name != functionName) {
|
||||
final sb = StringBuffer();
|
||||
sb.writeln(
|
||||
'Expected to be in function $functionName but '
|
||||
'actually in function $name',
|
||||
);
|
||||
sb.writeln('Full stack trace:');
|
||||
for (final frame in frames) {
|
||||
final func = await service.getObject(
|
||||
isolateId,
|
||||
frame.function!.id!,
|
||||
) as Func;
|
||||
final ownerName = func.owner.name!;
|
||||
sb.write(' $frame [${func.name}] [$ownerName]\n');
|
||||
}
|
||||
throw sb.toString();
|
||||
} else {
|
||||
print('Program is stopped in function: $functionName');
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,25 +1,48 @@
|
||||
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
|
||||
// Copyright (c) 2024, 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.
|
||||
|
||||
// NOTE: this file was originally copied from package:vm_service.
|
||||
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
library test_helper;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:io' as io;
|
||||
|
||||
import 'package:test/test.dart';
|
||||
import 'package:vm_service/vm_service.dart';
|
||||
import 'package:vm_service/vm_service_io.dart';
|
||||
|
||||
import 'service_test_common.dart';
|
||||
|
||||
export 'service_test_common.dart' show IsolateTest, VMTest;
|
||||
|
||||
/// The extra arguments to use
|
||||
const List<String> extraDebuggingArgs = [];
|
||||
|
||||
/// Will be set to the http address of the VM's service protocol before
|
||||
/// any tests are invoked.
|
||||
late String serviceHttpAddress;
|
||||
late String serviceWebsocketAddress;
|
||||
|
||||
const String _TESTEE_ENV_KEY = 'SERVICE_TEST_TESTEE';
|
||||
const Map<String, String> _TESTEE_SPAWN_ENV = {_TESTEE_ENV_KEY: 'true'};
|
||||
|
||||
late Uri remoteVmServiceUri;
|
||||
|
||||
Future<Process> spawnDartProcess(
|
||||
Future<io.Process> spawnDartProcess(
|
||||
String script, {
|
||||
bool serveObservatory = true,
|
||||
bool pauseOnStart = true,
|
||||
bool disableServiceAuthCodes = false,
|
||||
}) async {
|
||||
final executable = Platform.executable;
|
||||
final tmpDir = await Directory.systemTemp.createTemp('dart_service');
|
||||
final executable = io.Platform.executable;
|
||||
final tmpDir = await io.Directory.systemTemp.createTemp('dart_service');
|
||||
final serviceInfoUri = tmpDir.uri.resolve('service_info.json');
|
||||
final serviceInfoFile = await File.fromUri(serviceInfoUri).create();
|
||||
final serviceInfoFile = await io.File.fromUri(serviceInfoUri).create();
|
||||
|
||||
final arguments = [
|
||||
'--disable-dart-dev',
|
||||
@@ -28,10 +51,10 @@ Future<Process> spawnDartProcess(
|
||||
if (pauseOnStart) '--pause-isolates-on-start',
|
||||
if (disableServiceAuthCodes) '--disable-service-auth-codes',
|
||||
'--write-service-info=$serviceInfoUri',
|
||||
...Platform.executableArguments,
|
||||
Platform.script.resolve(script).toString(),
|
||||
...io.Platform.executableArguments,
|
||||
io.Platform.script.resolve(script).toString(),
|
||||
];
|
||||
final process = await Process.start(executable, arguments);
|
||||
final process = await io.Process.start(executable, arguments);
|
||||
process.stdout
|
||||
.transform(utf8.decoder)
|
||||
.listen((line) => print('TESTEE OUT: $line'));
|
||||
@@ -70,5 +93,492 @@ Future<void> executeUntilNextPause(VmService service) async {
|
||||
/// will resolve to the sdk/ directory (e.g. '../../../').
|
||||
Uri devtoolsAppUri({required String prefix}) {
|
||||
const pathFromSdkDirectory = 'third_party/devtools/web';
|
||||
return Platform.script.resolve('$prefix$pathFromSdkDirectory');
|
||||
return io.Platform.script.resolve('$prefix$pathFromSdkDirectory');
|
||||
}
|
||||
|
||||
bool _isTestee() {
|
||||
return io.Platform.environment.containsKey(_TESTEE_ENV_KEY);
|
||||
}
|
||||
|
||||
Uri _getTestUri(String script) {
|
||||
if (io.Platform.script.isScheme('data')) {
|
||||
// If running from pub we can assume that we're in the root of the package
|
||||
// directory.
|
||||
return Uri.parse('test/$script');
|
||||
} else if (io.Platform.script.toFilePath().endsWith('out.aotsnapshot')) {
|
||||
// We're running an AOT test. In this case, we need to use the exact URI we
|
||||
// launched with.
|
||||
return io.Platform.script;
|
||||
} else {
|
||||
// Resolve the script to ensure that test will fail if the provided script
|
||||
// name doesn't match the actual script.
|
||||
return io.Platform.script.resolve(script);
|
||||
}
|
||||
}
|
||||
|
||||
class _ServiceTesteeRunner {
|
||||
Future<void> run({
|
||||
Function()? testeeBefore,
|
||||
Function()? testeeConcurrent,
|
||||
bool pauseOnStart = false,
|
||||
bool pauseOnExit = false,
|
||||
}) async {
|
||||
if (!pauseOnStart) {
|
||||
if (testeeBefore != null) {
|
||||
final result = testeeBefore();
|
||||
if (result is Future) {
|
||||
await result;
|
||||
}
|
||||
}
|
||||
print(''); // Print blank line to signal that testeeBefore has run.
|
||||
}
|
||||
if (testeeConcurrent != null) {
|
||||
final result = testeeConcurrent();
|
||||
if (result is Future) {
|
||||
await result;
|
||||
}
|
||||
}
|
||||
if (!pauseOnExit) {
|
||||
// Wait around for the process to be killed.
|
||||
await io.stdin.first.then((_) => io.exit(0));
|
||||
}
|
||||
}
|
||||
|
||||
void runSync({
|
||||
void Function()? testeeBeforeSync,
|
||||
void Function()? testeeConcurrentSync,
|
||||
bool pauseOnStart = false,
|
||||
bool pauseOnExit = false,
|
||||
}) {
|
||||
if (!pauseOnStart) {
|
||||
if (testeeBeforeSync != null) {
|
||||
testeeBeforeSync();
|
||||
}
|
||||
print(''); // Print blank line to signal that testeeBefore has run.
|
||||
}
|
||||
if (testeeConcurrentSync != null) {
|
||||
testeeConcurrentSync();
|
||||
}
|
||||
if (!pauseOnExit) {
|
||||
// Wait around for the process to be killed.
|
||||
io.stdin.first.then((_) => io.exit(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _ServiceTesteeLauncher {
|
||||
io.Process? process;
|
||||
List<String> args;
|
||||
|
||||
bool killedByTester = false;
|
||||
final _exitCodeCompleter = Completer<int>();
|
||||
|
||||
_ServiceTesteeLauncher(String script)
|
||||
: args = [_getTestUri(script).toFilePath()];
|
||||
|
||||
Future<int> get exitCode => _exitCodeCompleter.future;
|
||||
|
||||
// Spawn the testee process.
|
||||
Future<io.Process> _spawnProcess(
|
||||
bool pauseOnStart,
|
||||
bool pauseOnExit,
|
||||
bool pauseOnUnhandledExceptions,
|
||||
bool testeeControlsServer,
|
||||
bool useAuthToken,
|
||||
List<String>? experiments,
|
||||
List<String>? extraArgs,
|
||||
) {
|
||||
return _spawnDartProcess(
|
||||
pauseOnStart,
|
||||
pauseOnExit,
|
||||
pauseOnUnhandledExceptions,
|
||||
testeeControlsServer,
|
||||
useAuthToken,
|
||||
experiments,
|
||||
extraArgs,
|
||||
);
|
||||
}
|
||||
|
||||
Future<io.Process> _spawnDartProcess(
|
||||
bool pauseOnStart,
|
||||
bool pauseOnExit,
|
||||
bool pauseOnUnhandledExceptions,
|
||||
bool testeeControlsServer,
|
||||
bool useAuthToken,
|
||||
List<String>? experiments,
|
||||
List<String>? extraArgs,
|
||||
) {
|
||||
final String dartExecutable = io.Platform.executable;
|
||||
|
||||
final fullArgs = <String>[];
|
||||
if (pauseOnStart) {
|
||||
fullArgs.add('--pause-isolates-on-start');
|
||||
}
|
||||
if (pauseOnExit) {
|
||||
fullArgs.add('--pause-isolates-on-exit');
|
||||
}
|
||||
if (!useAuthToken) {
|
||||
fullArgs.add('--disable-service-auth-codes');
|
||||
}
|
||||
if (pauseOnUnhandledExceptions) {
|
||||
fullArgs.add('--pause-isolates-on-unhandled-exceptions');
|
||||
}
|
||||
fullArgs.add('--profiler');
|
||||
if (experiments != null) {
|
||||
fullArgs.addAll(experiments.map((e) => '--enable-experiment=$e'));
|
||||
}
|
||||
if (extraArgs != null) {
|
||||
fullArgs.addAll(extraArgs);
|
||||
}
|
||||
|
||||
fullArgs.addAll(io.Platform.executableArguments);
|
||||
if (!testeeControlsServer) {
|
||||
fullArgs.add('--enable-vm-service:0');
|
||||
}
|
||||
fullArgs.addAll(args);
|
||||
return _spawnCommon(dartExecutable, fullArgs, <String, String>{});
|
||||
}
|
||||
|
||||
Future<io.Process> _spawnCommon(
|
||||
String executable,
|
||||
List<String> arguments,
|
||||
Map<String, String> dartEnvironment,
|
||||
) {
|
||||
final environment = _TESTEE_SPAWN_ENV;
|
||||
final bashEnvironment = StringBuffer();
|
||||
environment.forEach((k, v) => bashEnvironment.write('$k=$v '));
|
||||
dartEnvironment.forEach((k, v) {
|
||||
arguments.insert(0, '-D$k=$v');
|
||||
});
|
||||
print('** Launching $bashEnvironment$executable ${arguments.join(' ')}');
|
||||
return io.Process.start(
|
||||
executable,
|
||||
arguments,
|
||||
environment: environment,
|
||||
);
|
||||
}
|
||||
|
||||
Future<Uri> launch(
|
||||
bool pauseOnStart,
|
||||
bool pauseOnExit,
|
||||
bool pauseOnUnhandledExceptions,
|
||||
bool testeeControlsServer,
|
||||
bool useAuthToken,
|
||||
List<String>? experiments,
|
||||
List<String>? extraArgs,
|
||||
) {
|
||||
return _spawnProcess(
|
||||
pauseOnStart,
|
||||
pauseOnExit,
|
||||
pauseOnUnhandledExceptions,
|
||||
testeeControlsServer,
|
||||
useAuthToken,
|
||||
experiments,
|
||||
extraArgs,
|
||||
).then((p) {
|
||||
final Completer<Uri> completer = Completer<Uri>();
|
||||
process = p;
|
||||
Uri? uri;
|
||||
bool blank = false;
|
||||
var first = true;
|
||||
process!.stdout
|
||||
.transform(utf8.decoder)
|
||||
.transform(LineSplitter())
|
||||
.listen((line) {
|
||||
const kDartVMServiceListening = 'The Dart VM service is listening on ';
|
||||
if (line.startsWith(kDartVMServiceListening)) {
|
||||
uri = Uri.parse(line.substring(kDartVMServiceListening.length));
|
||||
}
|
||||
if (pauseOnStart || line == '') {
|
||||
// Received blank line.
|
||||
blank = true;
|
||||
}
|
||||
if ((uri != null) && (blank == true) && (first == true)) {
|
||||
completer.complete(uri!);
|
||||
// Stop repeat completions.
|
||||
first = false;
|
||||
print('** Signaled to run test queries on $uri');
|
||||
}
|
||||
io.stdout.write('>testee>out> $line\n');
|
||||
});
|
||||
process!.stderr
|
||||
.transform(utf8.decoder)
|
||||
.transform(LineSplitter())
|
||||
.listen((line) {
|
||||
io.stdout.write('>testee>err> $line\n');
|
||||
});
|
||||
process!.exitCode.then(_exitCodeCompleter.complete);
|
||||
return completer.future;
|
||||
});
|
||||
}
|
||||
|
||||
void requestExit() {
|
||||
if (process != null) {
|
||||
print('** Killing script');
|
||||
if (process!.kill()) {
|
||||
killedByTester = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setupAddresses(Uri /*!*/ serverAddress) {
|
||||
serviceWebsocketAddress =
|
||||
'ws://${serverAddress.authority}${serverAddress.path}ws';
|
||||
serviceHttpAddress = 'http://${serverAddress.authority}${serverAddress.path}';
|
||||
}
|
||||
|
||||
class _ServiceTesterRunner {
|
||||
Future<void> run({
|
||||
List<String>? mainArgs,
|
||||
List<String>? extraArgs,
|
||||
List<String>? experiments,
|
||||
List<VMTest>? vmTests,
|
||||
List<IsolateTest>? isolateTests,
|
||||
required String scriptName,
|
||||
bool pauseOnStart = false,
|
||||
bool pauseOnExit = false,
|
||||
bool verboseVm = false,
|
||||
bool pauseOnUnhandledExceptions = false,
|
||||
bool testeeControlsServer = false,
|
||||
bool useAuthToken = false,
|
||||
bool allowForNonZeroExitCode = false,
|
||||
VmServiceFactory serviceFactory = VmService.defaultFactory,
|
||||
}) async {
|
||||
final process = _ServiceTesteeLauncher(scriptName);
|
||||
late VmService vm;
|
||||
late IsolateRef isolate;
|
||||
setUp(() async {
|
||||
await process
|
||||
.launch(
|
||||
pauseOnStart,
|
||||
pauseOnExit,
|
||||
pauseOnUnhandledExceptions,
|
||||
testeeControlsServer,
|
||||
useAuthToken,
|
||||
experiments,
|
||||
extraArgs,
|
||||
)
|
||||
.then((Uri serverAddress) async {
|
||||
if (mainArgs!.contains('--gdb')) {
|
||||
final pid = process.process!.pid;
|
||||
final wait = Duration(seconds: 10);
|
||||
print('Testee has pid $pid, waiting $wait before continuing');
|
||||
io.sleep(wait);
|
||||
}
|
||||
setupAddresses(serverAddress);
|
||||
vm = await vmServiceConnectUriWithFactory(
|
||||
serviceWebsocketAddress,
|
||||
vmServiceFactory: serviceFactory,
|
||||
);
|
||||
print('Done loading VM');
|
||||
isolate = await getFirstIsolate(vm);
|
||||
});
|
||||
});
|
||||
|
||||
final name = _getTestUri(scriptName).pathSegments.last;
|
||||
|
||||
test(
|
||||
name,
|
||||
() async {
|
||||
// Run vm tests.
|
||||
if (vmTests != null) {
|
||||
var testIndex = 1;
|
||||
final totalTests = vmTests.length;
|
||||
for (var t in vmTests) {
|
||||
print('$name [$testIndex/$totalTests]');
|
||||
await t(vm);
|
||||
testIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
// Run isolate tests.
|
||||
if (isolateTests != null) {
|
||||
var testIndex = 1;
|
||||
final totalTests = isolateTests.length;
|
||||
for (var t in isolateTests) {
|
||||
print('$name [$testIndex/$totalTests]');
|
||||
await t(vm, isolate);
|
||||
testIndex++;
|
||||
}
|
||||
}
|
||||
},
|
||||
retry: 0,
|
||||
timeout: Timeout.none,
|
||||
);
|
||||
|
||||
tearDown(() {
|
||||
print('All service tests completed successfully.');
|
||||
process.requestExit();
|
||||
});
|
||||
|
||||
final exitCode = await process.exitCode;
|
||||
if (exitCode != 0) {
|
||||
if (!(process.killedByTester || allowForNonZeroExitCode)) {
|
||||
throw 'Testee exited with unexpected exitCode: $exitCode';
|
||||
}
|
||||
}
|
||||
print('** Process exited: $exitCode');
|
||||
}
|
||||
|
||||
Future<IsolateRef> getFirstIsolate(VmService service) async {
|
||||
var vm = await service.getVM();
|
||||
final vmIsolates = vm.isolates!;
|
||||
if (vmIsolates.isNotEmpty) {
|
||||
return vmIsolates.first;
|
||||
}
|
||||
Completer<dynamic>? completer = Completer();
|
||||
late StreamSubscription subscription;
|
||||
subscription = service.onIsolateEvent.listen((Event event) async {
|
||||
if (completer == null) {
|
||||
await subscription.cancel();
|
||||
return;
|
||||
}
|
||||
if (event.kind == EventKind.kIsolateRunnable) {
|
||||
vm = await service.getVM();
|
||||
await subscription.cancel();
|
||||
await service.streamCancel(EventStreams.kIsolate);
|
||||
completer!.complete(event.isolate!);
|
||||
completer = null;
|
||||
}
|
||||
});
|
||||
await service.streamListen(EventStreams.kIsolate);
|
||||
|
||||
// The isolate may have started before we subscribed.
|
||||
vm = await service.getVM();
|
||||
if (vmIsolates.isNotEmpty) {
|
||||
await subscription.cancel();
|
||||
completer!.complete(vmIsolates.first);
|
||||
completer = null;
|
||||
}
|
||||
return (await completer!.future) as IsolateRef;
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs [tests] in sequence, each of which should take an [Isolate] and
|
||||
/// return a [Future]. Code for setting up state can run before and/or
|
||||
/// concurrently with the tests. Uses [mainArgs] to determine whether
|
||||
/// to run tests or testee in this invocation of the script.
|
||||
Future<void> runIsolateTests(
|
||||
List<String> mainArgs,
|
||||
List<IsolateTest> tests,
|
||||
String scriptName, {
|
||||
Function()? testeeBefore,
|
||||
Function()? testeeConcurrent,
|
||||
bool pauseOnStart = false,
|
||||
bool pauseOnExit = false,
|
||||
bool verboseVm = false,
|
||||
bool pauseOnUnhandledExceptions = false,
|
||||
bool testeeControlsServer = false,
|
||||
bool useAuthToken = false,
|
||||
bool allowForNonZeroExitCode = false,
|
||||
List<String>? experiments,
|
||||
List<String>? extraArgs,
|
||||
}) async {
|
||||
assert(!pauseOnStart || testeeBefore == null);
|
||||
if (_isTestee()) {
|
||||
await _ServiceTesteeRunner().run(
|
||||
testeeBefore: testeeBefore,
|
||||
testeeConcurrent: testeeConcurrent,
|
||||
pauseOnStart: pauseOnStart,
|
||||
pauseOnExit: pauseOnExit,
|
||||
);
|
||||
} else {
|
||||
await _ServiceTesterRunner().run(
|
||||
mainArgs: mainArgs,
|
||||
scriptName: scriptName,
|
||||
extraArgs: extraArgs,
|
||||
isolateTests: tests,
|
||||
pauseOnStart: pauseOnStart,
|
||||
pauseOnExit: pauseOnExit,
|
||||
verboseVm: verboseVm,
|
||||
experiments: experiments,
|
||||
pauseOnUnhandledExceptions: pauseOnUnhandledExceptions,
|
||||
testeeControlsServer: testeeControlsServer,
|
||||
useAuthToken: useAuthToken,
|
||||
allowForNonZeroExitCode: allowForNonZeroExitCode,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs [tests] in sequence, each of which should take an [Isolate] and
|
||||
/// return a [Future]. Code for setting up state can run before and/or
|
||||
/// concurrently with the tests. Uses [mainArgs] to determine whether
|
||||
/// to run tests or testee in this invocation of the script.
|
||||
///
|
||||
/// This is a special version of this test harness specifically for the
|
||||
/// pause_on_unhandled_exceptions_test, which cannot properly function
|
||||
/// in an async context (because exceptions are *always* handled in async
|
||||
/// functions).
|
||||
void runIsolateTestsSynchronous(
|
||||
List<String> mainArgs,
|
||||
List<IsolateTest> tests,
|
||||
String scriptName, {
|
||||
void Function()? testeeBefore,
|
||||
void Function()? testeeConcurrent,
|
||||
bool pauseOnStart = false,
|
||||
bool pauseOnExit = false,
|
||||
bool verboseVm = false,
|
||||
bool pauseOnUnhandledExceptions = false,
|
||||
List<String>? extraArgs,
|
||||
}) {
|
||||
assert(!pauseOnStart || testeeBefore == null);
|
||||
if (_isTestee()) {
|
||||
_ServiceTesteeRunner().runSync(
|
||||
testeeBeforeSync: testeeBefore,
|
||||
testeeConcurrentSync: testeeConcurrent,
|
||||
pauseOnStart: pauseOnStart,
|
||||
pauseOnExit: pauseOnExit,
|
||||
);
|
||||
} else {
|
||||
_ServiceTesterRunner().run(
|
||||
mainArgs: mainArgs,
|
||||
scriptName: scriptName,
|
||||
extraArgs: extraArgs,
|
||||
isolateTests: tests,
|
||||
pauseOnStart: pauseOnStart,
|
||||
pauseOnExit: pauseOnExit,
|
||||
verboseVm: verboseVm,
|
||||
pauseOnUnhandledExceptions: pauseOnUnhandledExceptions,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs [tests] in sequence, each of which should take an [Isolate] and
|
||||
/// return a [Future]. Code for setting up state can run before and/or
|
||||
/// concurrently with the tests. Uses [mainArgs] to determine whether
|
||||
/// to run tests or testee in this invocation of the script.
|
||||
Future<void> runVMTests(
|
||||
List<String> mainArgs,
|
||||
List<VMTest> tests,
|
||||
String scriptName, {
|
||||
Function()? testeeBefore,
|
||||
Function()? testeeConcurrent,
|
||||
bool pauseOnStart = false,
|
||||
bool pauseOnExit = false,
|
||||
bool verboseVm = false,
|
||||
bool pauseOnUnhandledExceptions = false,
|
||||
List<String>? extraArgs,
|
||||
VmServiceFactory serviceFactory = VmService.defaultFactory,
|
||||
}) async {
|
||||
if (_isTestee()) {
|
||||
await _ServiceTesteeRunner().run(
|
||||
testeeBefore: testeeBefore,
|
||||
testeeConcurrent: testeeConcurrent,
|
||||
pauseOnStart: pauseOnStart,
|
||||
pauseOnExit: pauseOnExit,
|
||||
);
|
||||
} else {
|
||||
await _ServiceTesterRunner().run(
|
||||
mainArgs: mainArgs,
|
||||
scriptName: scriptName,
|
||||
extraArgs: extraArgs,
|
||||
vmTests: tests,
|
||||
pauseOnStart: pauseOnStart,
|
||||
pauseOnExit: pauseOnExit,
|
||||
verboseVm: verboseVm,
|
||||
pauseOnUnhandledExceptions: pauseOnUnhandledExceptions,
|
||||
serviceFactory: serviceFactory,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) 2024, 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:test/test.dart';
|
||||
import 'package:vm_service/vm_service.dart';
|
||||
|
||||
import 'common/test_helper.dart';
|
||||
|
||||
final tests = <VMTest>[
|
||||
// Ensure the DDS allows for listening to a custom stream.
|
||||
(VmService service) async {
|
||||
try {
|
||||
await service.streamListen('Foo');
|
||||
} catch (e) {
|
||||
fail('Unable to subscribe to a custom stream: $e');
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
void main([args = const <String>[]]) => runVMTests(
|
||||
args,
|
||||
tests,
|
||||
'custom_stream_listen_test.dart',
|
||||
);
|
||||
+13
-15
@@ -1,42 +1,39 @@
|
||||
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
|
||||
// Copyright (c) 2024, 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:dds/dds.dart';
|
||||
import 'package:observatory/service_io.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:vm_service/vm_service.dart';
|
||||
|
||||
import 'test_helper.dart';
|
||||
import 'common/test_helper.dart';
|
||||
|
||||
final tests = <VMTest>[
|
||||
(VM vm) async {
|
||||
(VmService service) async {
|
||||
late DartDevelopmentService dds;
|
||||
final waitForDDS = Completer<void>();
|
||||
final serviceMessageCompleter = Completer<void>();
|
||||
|
||||
// The original VM service client is connected.
|
||||
expect(vm.isConnected, true);
|
||||
|
||||
// A service event is sent to all existing clients when DDS connects before
|
||||
// their connection is closed.
|
||||
await vm.listenEventStream('Service', (ServiceEvent event) async {
|
||||
service.onServiceEvent.listen((event) async {
|
||||
// Wait for dds to be set before checking the server's URI.
|
||||
await waitForDDS.future;
|
||||
final message =
|
||||
'A Dart Developer Service instance has connected and this direct '
|
||||
'connection to the VM service will now be closed. Please reconnect to '
|
||||
'the Dart Development Service at ${dds.uri}.';
|
||||
expect(event.kind, ServiceEvent.kDartDevelopmentServiceConnected);
|
||||
expect(event.message, message);
|
||||
expect(event.uri, dds.uri);
|
||||
expect(event.kind, 'DartDevelopmentServiceConnected');
|
||||
expect(event.json!['message'], message);
|
||||
expect(event.json!['uri'], dds.uri.toString());
|
||||
serviceMessageCompleter.complete();
|
||||
});
|
||||
|
||||
// Start DDS, which should result in the original VM service client being
|
||||
// disconnected from the VM service.
|
||||
final remote = Uri.parse(vm.target.networkAddress);
|
||||
final remote = Uri.parse(service.wsUri!);
|
||||
dds = await DartDevelopmentService.startDartDevelopmentService(
|
||||
remote.replace(
|
||||
scheme: 'http',
|
||||
@@ -49,13 +46,14 @@ final tests = <VMTest>[
|
||||
waitForDDS.complete();
|
||||
expect(dds.isRunning, true);
|
||||
await serviceMessageCompleter.future;
|
||||
await vm.onDisconnect;
|
||||
await service.onDone;
|
||||
await dds.shutdown();
|
||||
}
|
||||
];
|
||||
|
||||
main(args) async => runVMTests(
|
||||
void main([args = const <String>[]]) => runVMTests(
|
||||
args,
|
||||
tests,
|
||||
enableDds: false,
|
||||
'dds_disconnects_existing_clients_test.dart',
|
||||
extraArgs: ['--no-dds'],
|
||||
);
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) 2024, 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:developer';
|
||||
|
||||
import 'package:test/test.dart';
|
||||
import 'package:vm_service/vm_service.dart';
|
||||
|
||||
import 'common/service_test_common.dart';
|
||||
import 'common/test_helper.dart';
|
||||
|
||||
Future testMain() async {
|
||||
// Post a total of 9 events
|
||||
for (int i = 1; i <= 9; ++i) {
|
||||
postEvent('Test', {
|
||||
'id': i,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
final tests = <IsolateTest>[
|
||||
hasPausedAtStart,
|
||||
resumeIsolate,
|
||||
(VmService service, IsolateRef isolateRef) async {
|
||||
final completer = Completer<void>();
|
||||
int i = 1;
|
||||
service.onExtensionEvent.listen((event) async {
|
||||
expect(event.extensionKind, 'Test');
|
||||
expect(event.extensionData!.data['id'], i);
|
||||
i++;
|
||||
|
||||
if (i == 10) {
|
||||
await service.streamCancel(EventStreams.kExtension);
|
||||
completer.complete();
|
||||
} else if (i > 10) {
|
||||
fail('Too many "Test" extension events');
|
||||
}
|
||||
});
|
||||
await service.streamListen(EventStreams.kExtension);
|
||||
await completer.future;
|
||||
},
|
||||
];
|
||||
|
||||
void main([args = const <String>[]]) => runIsolateTests(
|
||||
args,
|
||||
tests,
|
||||
'extension_event_history_test.dart',
|
||||
testeeConcurrent: testMain,
|
||||
pauseOnStart: true,
|
||||
pauseOnExit: true,
|
||||
);
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) 2024, 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:dds_service_extensions/dds_service_extensions.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:vm_service/vm_service.dart';
|
||||
|
||||
import 'common/test_helper.dart';
|
||||
|
||||
void fooBar() {}
|
||||
|
||||
final test = <IsolateTest>[
|
||||
(VmService service, IsolateRef isolate) async {
|
||||
// Each client has a default name based on the order of connection to the
|
||||
// service.
|
||||
var clientName = await service.getClientName();
|
||||
expect(clientName.name, 'client1');
|
||||
|
||||
// Set a custom client name and check it was set properly.
|
||||
await service.setClientName('foobar');
|
||||
clientName = await service.getClientName();
|
||||
expect(clientName.name, 'foobar');
|
||||
|
||||
// Clear the client name and check that we're using the default again.
|
||||
await service.setClientName();
|
||||
clientName = await service.getClientName();
|
||||
expect(clientName.name, 'client1');
|
||||
},
|
||||
];
|
||||
|
||||
void main([args = const <String>[]]) => runIsolateTests(
|
||||
args,
|
||||
test,
|
||||
'get_client_name_rpc_test.dart',
|
||||
testeeConcurrent: fooBar,
|
||||
pauseOnStart: true,
|
||||
);
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright (c) 2024, 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:developer';
|
||||
|
||||
import 'package:dds_service_extensions/dds_service_extensions.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:vm_service/vm_service.dart';
|
||||
|
||||
import 'common/service_test_common.dart';
|
||||
import 'common/test_helper.dart';
|
||||
|
||||
const kMaxLogHistorySize = 100000;
|
||||
const kExpectedMaxLogIndex = kMaxLogHistorySize + 10;
|
||||
|
||||
void testMain() {
|
||||
// Log a total of 100,010 messages
|
||||
for (int i = 1; i <= kExpectedMaxLogIndex; i++) {
|
||||
log('All work and no play makes Ben a dull boy ($i)');
|
||||
}
|
||||
debugger();
|
||||
}
|
||||
|
||||
final tests = <IsolateTest>[
|
||||
hasPausedAtStart,
|
||||
(VmService service, IsolateRef isolateRef) async {
|
||||
final isolateId = isolateRef.id!;
|
||||
final initialSize = (await service.getLogHistorySize(isolateId)).size;
|
||||
try {
|
||||
await service.setLogHistorySize(isolateId, kMaxLogHistorySize + 1);
|
||||
} on RPCError catch (e) {
|
||||
expect(e.message, "'size' must be less than $kMaxLogHistorySize");
|
||||
}
|
||||
expect((await service.getLogHistorySize(isolateId)).size, initialSize);
|
||||
},
|
||||
(VmService service, IsolateRef isolateRef) async {
|
||||
await service.setLogHistorySize(isolateRef.id!, kMaxLogHistorySize);
|
||||
expect(
|
||||
(await service.getLogHistorySize(isolateRef.id!)).size,
|
||||
kMaxLogHistorySize,
|
||||
);
|
||||
},
|
||||
resumeIsolate,
|
||||
// Wait for the process to finish logging
|
||||
hasStoppedAtBreakpoint,
|
||||
(VmService service, IsolateRef isolateRef) async {
|
||||
final completer = Completer<void>();
|
||||
// We've logged kMaxLogHistorySize + 10 messages, but we only expect to
|
||||
// receive kMaxLogHistorySize logs..
|
||||
int i = 11;
|
||||
service.onLoggingEvent.listen((event) async {
|
||||
expect(
|
||||
event.logRecord!.message!.valueAsString,
|
||||
'All work and no play makes Ben a dull boy ($i)',
|
||||
);
|
||||
if (i == kExpectedMaxLogIndex) {
|
||||
await service.streamCancel(EventStreams.kLogging);
|
||||
completer.complete();
|
||||
}
|
||||
i++;
|
||||
});
|
||||
// Subscribing to the Logging stream will cause all the log events to be
|
||||
// sent immediately.
|
||||
await service.streamListen(EventStreams.kLogging);
|
||||
await completer.future;
|
||||
}
|
||||
];
|
||||
|
||||
void main([args = const <String>[]]) => runIsolateTests(
|
||||
args,
|
||||
tests,
|
||||
'log_history_size_gigantic_test.dart',
|
||||
testeeConcurrent: testMain,
|
||||
pauseOnStart: true,
|
||||
pauseOnExit: true,
|
||||
);
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright (c) 2024, 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:developer';
|
||||
|
||||
import 'package:dds_service_extensions/dds_service_extensions.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:vm_service/vm_service.dart';
|
||||
import 'package:vm_service/vm_service_io.dart';
|
||||
|
||||
import 'common/service_test_common.dart';
|
||||
import 'common/test_helper.dart';
|
||||
|
||||
void testMain() {
|
||||
// Log a total of 9 messages
|
||||
for (int i = 1; i <= 9; ++i) {
|
||||
log('log$i');
|
||||
}
|
||||
debugger();
|
||||
log('log10');
|
||||
}
|
||||
|
||||
final tests = <IsolateTest>[
|
||||
hasPausedAtStart,
|
||||
resumeIsolate,
|
||||
(VmService service, IsolateRef isolateRef) async {
|
||||
final isolateId = isolateRef.id!;
|
||||
// Check that resizing does the right thing.
|
||||
await service.setLogHistorySize(isolateId, 10);
|
||||
expect((await service.getLogHistorySize(isolateId)).size, 10);
|
||||
|
||||
final completer = Completer<void>();
|
||||
|
||||
int i = 1;
|
||||
service.onLoggingEvent.listen((event) async {
|
||||
expect(event.logRecord!.message!.valueAsString, 'log$i');
|
||||
i++;
|
||||
|
||||
if (i == 10) {
|
||||
await service.streamCancel(EventStreams.kLogging);
|
||||
completer.complete();
|
||||
} else if (i > 10) {
|
||||
fail('Too many log messages');
|
||||
}
|
||||
});
|
||||
await service.streamListen(EventStreams.kLogging);
|
||||
await completer.future;
|
||||
},
|
||||
(VmService service, IsolateRef isolateRef) async {
|
||||
// Resize to be smaller
|
||||
final isolateId = isolateRef.id!;
|
||||
// Check that resizing does the right thing.
|
||||
await service.setLogHistorySize(isolateId, 5);
|
||||
expect((await service.getLogHistorySize(isolateId)).size, 5);
|
||||
},
|
||||
resumeIsolate,
|
||||
(VmService service, IsolateRef isolateRef) async {
|
||||
final completer = Completer<void>();
|
||||
|
||||
// Create a new client as we want to get log messages from the entire
|
||||
// history buffer.
|
||||
final client = await vmServiceConnectUri(service.wsUri!);
|
||||
|
||||
int i = 6;
|
||||
client.onLoggingEvent.listen((event) async {
|
||||
expect(event.logRecord!.message!.valueAsString, 'log$i');
|
||||
i++;
|
||||
|
||||
if (i == 11) {
|
||||
await client.streamCancel(EventStreams.kLogging);
|
||||
completer.complete();
|
||||
} else if (i > 11) {
|
||||
fail('Too many log messages');
|
||||
}
|
||||
});
|
||||
await client.streamListen(EventStreams.kLogging);
|
||||
await completer.future;
|
||||
client.dispose();
|
||||
},
|
||||
];
|
||||
|
||||
void main([args = const <String>[]]) => runIsolateTests(
|
||||
args,
|
||||
tests,
|
||||
'log_history_size_simple_test.dart',
|
||||
testeeConcurrent: testMain,
|
||||
pauseOnStart: true,
|
||||
pauseOnExit: true,
|
||||
);
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright (c) 2024, 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:developer';
|
||||
|
||||
import 'package:dds_service_extensions/dds_service_extensions.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:vm_service/vm_service.dart';
|
||||
|
||||
import 'common/service_test_common.dart';
|
||||
import 'common/test_helper.dart';
|
||||
|
||||
void testMain() {
|
||||
// Initial logging history should be 0, so these messages won't be buffered.
|
||||
log('log1');
|
||||
log('log2');
|
||||
|
||||
// Setting the log history length does not apply retroactively.
|
||||
debugger();
|
||||
|
||||
// Log a total of 30 messages
|
||||
for (int i = 3; i <= 30; ++i) {
|
||||
log('log$i');
|
||||
}
|
||||
}
|
||||
|
||||
late final String isolateId;
|
||||
|
||||
final tests = <IsolateTest>[
|
||||
hasPausedAtStart,
|
||||
(VmService service, IsolateRef isolateRef) async {
|
||||
isolateId = isolateRef.id!;
|
||||
await service.setLogHistorySize(isolateId, 0);
|
||||
expect((await service.getLogHistorySize(isolateId)).size, 0);
|
||||
},
|
||||
resumeIsolate,
|
||||
hasStoppedAtBreakpoint,
|
||||
(VmService service, IsolateRef isolateRef) async {
|
||||
await service.setLogHistorySize(isolateId, 20);
|
||||
expect((await service.getLogHistorySize(isolateId)).size, 20);
|
||||
},
|
||||
resumeIsolate,
|
||||
(VmService service, IsolateRef isolateRef) async {
|
||||
final completer = Completer<void>();
|
||||
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
|
||||
// With the log history set to 20, the first log message should be 'log11'.
|
||||
int i = 11;
|
||||
service.onLoggingEvent.listen((event) async {
|
||||
expect(event.logRecord!.message!.valueAsString, 'log$i');
|
||||
i++;
|
||||
|
||||
if (i == 30) {
|
||||
await service.streamCancel(EventStreams.kLogging);
|
||||
completer.complete();
|
||||
}
|
||||
});
|
||||
await service.streamListen(EventStreams.kLogging);
|
||||
await completer.future;
|
||||
},
|
||||
(VmService service, IsolateRef isolateRef) async {
|
||||
try {
|
||||
// Try to set an invalid history size
|
||||
await service.setLogHistorySize(isolateId, -1);
|
||||
fail('Successfully set invalid size');
|
||||
} on RPCError catch (e) {
|
||||
expect(e.message, "'size' must be greater or equal to zero");
|
||||
}
|
||||
expect((await service.getLogHistorySize(isolateId)).size, 20);
|
||||
}
|
||||
];
|
||||
|
||||
void main([args = const <String>[]]) => runIsolateTests(
|
||||
args,
|
||||
tests,
|
||||
'log_history_size_test.dart',
|
||||
testeeConcurrent: testMain,
|
||||
pauseOnStart: true,
|
||||
pauseOnExit: true,
|
||||
);
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) 2024, 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 'package:test/test.dart';
|
||||
import 'package:vm_service/vm_service.dart';
|
||||
|
||||
import 'common/service_test_common.dart';
|
||||
import 'common/test_helper.dart';
|
||||
|
||||
void testMain() {
|
||||
// Log a total of 9 messages
|
||||
for (int i = 1; i <= 9; ++i) {
|
||||
print('Stdout log$i');
|
||||
stderr.writeln('Stderr log$i');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> streamHistoryTest(
|
||||
VmService service,
|
||||
IsolateRef isolateRef,
|
||||
String stream,
|
||||
) async {
|
||||
final completer = Completer<void>();
|
||||
int i = 1;
|
||||
service.onEvent(stream).listen((event) async {
|
||||
final string = decodeBase64(event.bytes!);
|
||||
if (stream == EventStreams.kStdout) {
|
||||
if (!string.startsWith(stream)) {
|
||||
// Likely "The Dart VM service is listening..." or one of the other
|
||||
// messages printed when the VM service is enabled.
|
||||
return;
|
||||
}
|
||||
expect(string, '$stream log$i\n');
|
||||
} else {
|
||||
// Newlines are sent as separate events for some reason. Ignore them.
|
||||
if (!string.startsWith(stream)) {
|
||||
return;
|
||||
}
|
||||
expect(string, '$stream log$i');
|
||||
}
|
||||
i++;
|
||||
|
||||
if (i == 10) {
|
||||
await service.streamCancel(stream);
|
||||
completer.complete();
|
||||
} else if (i > 10) {
|
||||
fail('Too many log messages');
|
||||
}
|
||||
});
|
||||
await service.streamListen(stream);
|
||||
await completer.future;
|
||||
}
|
||||
|
||||
final tests = <IsolateTest>[
|
||||
hasPausedAtStart,
|
||||
resumeIsolate,
|
||||
(VmService service, IsolateRef isolateRef) async {
|
||||
await streamHistoryTest(service, isolateRef, EventStreams.kStdout);
|
||||
},
|
||||
(VmService service, IsolateRef isolateRef) async {
|
||||
await streamHistoryTest(service, isolateRef, EventStreams.kStderr);
|
||||
},
|
||||
];
|
||||
|
||||
void main([args = const <String>[]]) => runIsolateTests(
|
||||
args,
|
||||
tests,
|
||||
'stdout_stderr_history_test.dart',
|
||||
testeeConcurrent: testMain,
|
||||
pauseOnStart: true,
|
||||
pauseOnExit: true,
|
||||
);
|
||||
@@ -1,3 +1,13 @@
|
||||
# 1.7.0
|
||||
- Added:
|
||||
- `ClientName`
|
||||
- `DdsExtension.getClientName`
|
||||
- `DdsExtension.getLogHistorySize`
|
||||
- `DdsExtension.setClientName`
|
||||
- `DdsExtension.setLogHistorySize`
|
||||
- `DdsExtension.requirePermissionToResume`
|
||||
- `Size`
|
||||
|
||||
# 1.6.3
|
||||
- Updated `vm_service` version to `^14.0.0`.
|
||||
|
||||
|
||||
@@ -95,6 +95,32 @@ extension DdsExtension on VmService {
|
||||
);
|
||||
}
|
||||
|
||||
/// The [getLogHistorySize] RPC is used to retrieve the current size of the
|
||||
/// log history buffer.
|
||||
///
|
||||
/// If the returned [Size] is zero, then log history is disabled.
|
||||
Future<Size> getLogHistorySize(String isolateId) async {
|
||||
// No version check needed, present since v1.0 of the protocol.
|
||||
return _callHelper<Size>('getLogHistorySize', args: {
|
||||
'isolateId': isolateId,
|
||||
});
|
||||
}
|
||||
|
||||
/// The [setLogHistorySize] RPC is used to set the size of the ring buffer
|
||||
/// used for caching a limited set of historical log messages.
|
||||
///
|
||||
/// If [size] is 0, logging history will be disabled.
|
||||
///
|
||||
/// The maximum history size is 100,000 messages, with the default set to
|
||||
/// 10,000 messages.
|
||||
Future<Success> setLogHistorySize(String isolateId, int size) async {
|
||||
// No version check needed, present since v1.0 of the protocol.
|
||||
return _callHelper<Success>('setLogHistorySize', args: {
|
||||
'isolateId': isolateId,
|
||||
'size': size,
|
||||
});
|
||||
}
|
||||
|
||||
/// Retrieve the event history for `stream`.
|
||||
///
|
||||
/// If `stream` does not have event history collected, a parameter error is
|
||||
@@ -176,6 +202,83 @@ extension DdsExtension on VmService {
|
||||
Stream<Event> get onExtensionEventWithHistory =>
|
||||
onEventWithHistory('Extension');
|
||||
|
||||
/// The [getClientName] RPC is used to retrieve the name associated with the
|
||||
/// currently connected VM service client.
|
||||
///
|
||||
/// If no name was previously set through the [setClientName] RPC, a default
|
||||
/// name will be returned.
|
||||
Future<ClientName> getClientName() async {
|
||||
// No version check needed, present since v1.0 of the protocol.
|
||||
return _callHelper<ClientName>(
|
||||
'getClientName',
|
||||
);
|
||||
}
|
||||
|
||||
/// The [setClientName] RPC is used to set a name to be associated with the
|
||||
/// currently connected VM service client.
|
||||
///
|
||||
/// If the [name] parameter is a non-empty string, [name] will become the new
|
||||
/// name associated with the client. If [name] is an empty string, the
|
||||
/// client's name will be reset to its default name.
|
||||
Future<Success> setClientName([String name = '']) async {
|
||||
// No version check needed, present since v1.0 of the protocol.
|
||||
return _callHelper<Success>(
|
||||
'setClientName',
|
||||
args: {
|
||||
'name': name,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// The [requirePermissionToResume] RPC is used to change the pause/resume
|
||||
/// behavior of isolates.
|
||||
///
|
||||
/// This provides a way for the VM service to wait for approval to resume
|
||||
/// from some set of clients. This is useful for clients which want to
|
||||
/// perform some operation on an isolate after a pause without it being
|
||||
/// resumed by another client.
|
||||
///
|
||||
/// If the [onPauseStart] parameter is `true`, isolates will not resume after
|
||||
/// pausing on start until the client sends a `resume` request and all other
|
||||
/// clients which need to provide resume approval for this pause type have
|
||||
/// done so.
|
||||
///
|
||||
/// If the [onPauseReload] parameter is `true`, isolates will not resume
|
||||
/// after pausing after a reload until the client sends a `resume` request
|
||||
/// and all other clients which need to provide resume approval for this
|
||||
/// pause type have done so.
|
||||
///
|
||||
/// If the [onPauseExit] parameter is `true`, isolates will not resume after
|
||||
/// pausing on exit until the client sends a `resume` request and all other
|
||||
/// clients which need to provide resume approval for this pause type have
|
||||
/// done so.
|
||||
///
|
||||
/// **Important Notes:**
|
||||
///
|
||||
/// - All clients with the same client name share resume permissions. Only a
|
||||
/// single client of a given name is required to provide resume approval.
|
||||
/// - When a client requiring approval disconnects from the service, a paused
|
||||
/// isolate may resume if all other clients requiring resume approval have
|
||||
/// already given approval. In the case that no other client requires
|
||||
/// resume approval for the current pause event, the isolate will be
|
||||
/// resumed if at least one other client has attempted to resume the
|
||||
/// isolate.
|
||||
Future<Success> requirePermissionToResume({
|
||||
bool onPauseStart = false,
|
||||
bool onPauseReload = false,
|
||||
bool onPauseExit = false,
|
||||
}) async {
|
||||
// No version check needed, present since v1.0 of the protocol.
|
||||
return _callHelper<Success>(
|
||||
'requirePermissionToResume',
|
||||
args: {
|
||||
'onPauseStart': onPauseStart,
|
||||
'onPauseReload': onPauseReload,
|
||||
'onPauseExit': onPauseExit,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> _versionCheck(int major, int minor) async {
|
||||
_ddsVersion ??= await getDartDevelopmentServiceVersion();
|
||||
return ((_ddsVersion!.major == major && _ddsVersion!.minor! >= minor) ||
|
||||
@@ -203,10 +306,38 @@ extension DdsExtension on VmService {
|
||||
AvailableCachedCpuSamples.parse,
|
||||
);
|
||||
addTypeFactory('CachedCpuSamples', CachedCpuSamples.parse);
|
||||
addTypeFactory('Size', Size.parse);
|
||||
addTypeFactory('ClientName', ClientName.parse);
|
||||
_factoriesRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple object representing the name of a DDS client.
|
||||
///
|
||||
/// See [DdsExtension.getClientName] and [DdsExtension.setClientName].
|
||||
class ClientName extends Response {
|
||||
static ClientName? parse(Map<String, dynamic>? json) =>
|
||||
json == null ? null : ClientName._fromJson(json);
|
||||
|
||||
ClientName({required this.name});
|
||||
|
||||
ClientName._fromJson(Map<String, dynamic> json) : name = json['name'];
|
||||
|
||||
final String name;
|
||||
}
|
||||
|
||||
/// A simple object representing a size response.
|
||||
class Size extends Response {
|
||||
static Size? parse(Map<String, dynamic>? json) =>
|
||||
json == null ? null : Size._fromJson(json);
|
||||
|
||||
Size({required this.size});
|
||||
|
||||
Size._fromJson(Map<String, dynamic> json) : size = json['size'];
|
||||
|
||||
final int size;
|
||||
}
|
||||
|
||||
/// A collection of historical [Event]s from some stream.
|
||||
class StreamHistory extends Response {
|
||||
static StreamHistory? parse(Map<String, dynamic>? json) =>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: dds_service_extensions
|
||||
version: 1.6.3
|
||||
version: 1.7.0
|
||||
description: >-
|
||||
Extension methods for `package:vm_service`, used to make requests a
|
||||
Dart Development Service (DDS) instance.
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
// 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.
|
||||
|
||||
import 'package:observatory/service_io.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'test_helper.dart';
|
||||
|
||||
var tests = <VMTest>[
|
||||
(VM vm) async {
|
||||
final defaultClientName = 'client1';
|
||||
final clientName = 'agent-007';
|
||||
var result = await vm.invokeRpcNoUpgrade('getClientName', {});
|
||||
expect(result['type'], 'ClientName');
|
||||
expect(result['name'], defaultClientName);
|
||||
|
||||
// Set the name for this client.
|
||||
result = await vm.invokeRpcNoUpgrade(
|
||||
'setClientName',
|
||||
{
|
||||
'name': clientName,
|
||||
},
|
||||
);
|
||||
expect(result['type'], 'Success');
|
||||
|
||||
// Check it was set properly.
|
||||
result = await vm.invokeRpcNoUpgrade('getClientName', {});
|
||||
expect(result['type'], 'ClientName');
|
||||
expect(result['name'], clientName);
|
||||
|
||||
// Check clearing works properly.
|
||||
result = await vm.invokeRpcNoUpgrade(
|
||||
'setClientName',
|
||||
{
|
||||
'name': '',
|
||||
},
|
||||
);
|
||||
expect(result['type'], 'Success');
|
||||
|
||||
result = await vm.invokeRpcNoUpgrade('getClientName', {});
|
||||
expect(result['type'], 'ClientName');
|
||||
expect(result['name'], defaultClientName);
|
||||
},
|
||||
// Try to set an invalid agent name for this client.
|
||||
(VM vm) async {
|
||||
try {
|
||||
await vm.invokeRpcNoUpgrade(
|
||||
'setClientName',
|
||||
{
|
||||
'name': 42,
|
||||
},
|
||||
);
|
||||
fail('Successfully set invalid client name');
|
||||
} on ServerRpcException {/* expected */}
|
||||
},
|
||||
// Missing parameters.
|
||||
(VM vm) async {
|
||||
try {
|
||||
await vm.invokeRpcNoUpgrade('setClientName', {});
|
||||
fail('Successfully set name with no type');
|
||||
} on ServerRpcException {/* expected */}
|
||||
},
|
||||
];
|
||||
|
||||
main(args) async => runVMTests(
|
||||
args,
|
||||
tests,
|
||||
enableService: false,
|
||||
);
|
||||
-70
@@ -1,70 +0,0 @@
|
||||
// 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.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:observatory/service_io.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'client_resume_approvals_common.dart';
|
||||
import 'service_test_common.dart';
|
||||
import 'test_helper.dart';
|
||||
|
||||
void fooBar() {
|
||||
int i = 0;
|
||||
print(i);
|
||||
}
|
||||
|
||||
late WebSocketVM client1;
|
||||
late WebSocketVM client2;
|
||||
|
||||
final test = <IsolateTest>[
|
||||
hasPausedAtStart,
|
||||
(Isolate isolate) async {
|
||||
client1 = await createClient(isolate.owner as WebSocketVM);
|
||||
await setRequireApprovalForResume(
|
||||
client1,
|
||||
isolate,
|
||||
pauseOnStart: true,
|
||||
pauseOnExit: true,
|
||||
);
|
||||
client2 = await createClient(
|
||||
isolate.owner as WebSocketVM,
|
||||
clientName: otherClientName,
|
||||
);
|
||||
await setRequireApprovalForResume(
|
||||
client2,
|
||||
isolate,
|
||||
pauseOnStart: true,
|
||||
pauseOnExit: true,
|
||||
);
|
||||
|
||||
// Give resume approval for client1 to ensure approval state is cleaned up
|
||||
// properly when both client1 and client2 have disconnected.
|
||||
await resume(client1, isolate);
|
||||
expect(await isPausedAtStart(isolate), true);
|
||||
|
||||
// Once client1 is disconnected, we should still be paused.
|
||||
client1.disconnect();
|
||||
expect(await isPausedAtStart(isolate), true);
|
||||
|
||||
// Once client2 disconnects, there are no clients which require resume
|
||||
// approval. Since there were no resume requests made by clients which are
|
||||
// still connected, the isolate remains paused.
|
||||
client2.disconnect();
|
||||
expect(await isPausedAtStart(isolate), true);
|
||||
|
||||
await isolate.resume();
|
||||
},
|
||||
hasStoppedAtExit,
|
||||
];
|
||||
|
||||
Future<void> main(args) => runIsolateTests(
|
||||
args,
|
||||
test,
|
||||
testeeConcurrent: fooBar,
|
||||
pause_on_start: true,
|
||||
pause_on_exit: true,
|
||||
enableService: false,
|
||||
);
|
||||
@@ -1,85 +0,0 @@
|
||||
// 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.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:observatory/service_io.dart';
|
||||
import 'service_test_common.dart';
|
||||
|
||||
const String clientName = 'TestClient';
|
||||
const String otherClientName = 'OtherTestClient';
|
||||
|
||||
Future<void> setClientName(WebSocketVM client, String name) async =>
|
||||
await client.invokeRpc('setClientName', {'name': name});
|
||||
|
||||
Future<WebSocketVM> createClient(WebSocketVM vm,
|
||||
{String clientName = clientName}) async {
|
||||
final client = WebSocketVM(vm.target);
|
||||
await client.load();
|
||||
await setClientName(client, clientName);
|
||||
return client;
|
||||
}
|
||||
|
||||
Future<void> setRequireApprovalForResume(
|
||||
WebSocketVM vm,
|
||||
Isolate isolate, {
|
||||
bool pauseOnStart = false,
|
||||
bool pauseOnExit = false,
|
||||
bool pauseOnReload = false,
|
||||
}) async {
|
||||
int pauseTypeMask = 0;
|
||||
if (pauseOnStart) {
|
||||
pauseTypeMask |= 1;
|
||||
}
|
||||
if (pauseOnReload) {
|
||||
pauseTypeMask |= 2;
|
||||
}
|
||||
if (pauseOnExit) {
|
||||
pauseTypeMask |= 4;
|
||||
}
|
||||
await vm.invokeRpc('requirePermissionToResume', {
|
||||
'isolateId': isolate.id,
|
||||
'pauseTypeMask': pauseTypeMask,
|
||||
'onPauseStart': pauseOnStart,
|
||||
'onPauseReload': pauseOnReload,
|
||||
'onPauseExit': pauseOnExit,
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> resume(WebSocketVM vm, Isolate isolate) async =>
|
||||
await vm.invokeRpc('resume', {
|
||||
'isolateId': isolate.id,
|
||||
});
|
||||
|
||||
Future<bool> isPausedAtStart(Isolate isolate) async {
|
||||
await isolate.reload();
|
||||
return ((isolate.pauseEvent != null) &&
|
||||
isEventOfKind(isolate.pauseEvent, ServiceEvent.kPauseStart));
|
||||
}
|
||||
|
||||
Future<bool> isPausedAtExit(Isolate isolate) async {
|
||||
await isolate.reload();
|
||||
return ((isolate.pauseEvent != null) &&
|
||||
isEventOfKind(isolate.pauseEvent, ServiceEvent.kPauseExit));
|
||||
}
|
||||
|
||||
Future<bool> isPausedPostRequest(Isolate isolate) async {
|
||||
await isolate.reload();
|
||||
return ((isolate.pauseEvent != null) &&
|
||||
isEventOfKind(isolate.pauseEvent, ServiceEvent.kPausePostRequest));
|
||||
}
|
||||
|
||||
Future<void> waitForResume(Isolate isolate) async {
|
||||
final completer = Completer<void>();
|
||||
isolate.vm.getEventStream(VM.kDebugStream).then((stream) {
|
||||
var subscription;
|
||||
subscription = stream.listen((ServiceEvent event) {
|
||||
if (event.kind == ServiceEvent.kResume) {
|
||||
subscription.cancel();
|
||||
completer.complete();
|
||||
}
|
||||
});
|
||||
});
|
||||
await completer.future;
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
// 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.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:observatory/service_io.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'client_resume_approvals_common.dart';
|
||||
import 'service_test_common.dart';
|
||||
import 'test_helper.dart';
|
||||
|
||||
void fooBar() {
|
||||
int i = 0;
|
||||
print(i);
|
||||
}
|
||||
|
||||
late WebSocketVM client1;
|
||||
late WebSocketVM client2;
|
||||
|
||||
final test = <IsolateTest>[
|
||||
// Multiple clients, disconnect client awaiting approval.
|
||||
hasPausedAtStart,
|
||||
(Isolate isolate) async {
|
||||
client1 = await createClient(isolate.owner as WebSocketVM);
|
||||
await setRequireApprovalForResume(
|
||||
client1,
|
||||
isolate,
|
||||
pauseOnStart: true,
|
||||
pauseOnExit: true,
|
||||
);
|
||||
client2 = await createClient(
|
||||
isolate.owner as WebSocketVM,
|
||||
clientName: otherClientName,
|
||||
);
|
||||
await setRequireApprovalForResume(
|
||||
client2,
|
||||
isolate,
|
||||
pauseOnStart: true,
|
||||
pauseOnExit: true,
|
||||
);
|
||||
|
||||
// Send a resume request on the test client so we'll resume once the other
|
||||
// clients which require approval disconnect.
|
||||
await isolate.resume();
|
||||
expect(await isPausedAtStart(isolate), true);
|
||||
|
||||
// Once client1 is disconnected, we should still be paused.
|
||||
client1.disconnect();
|
||||
expect(await isPausedAtStart(isolate), true);
|
||||
|
||||
// Once client2 disconnects, there are no clients which require resume
|
||||
// approval. Ensure we resume immediately so we don't deadlock waiting for
|
||||
// approvals from disconnected clients.
|
||||
client2.disconnect();
|
||||
},
|
||||
hasStoppedAtExit,
|
||||
];
|
||||
|
||||
Future<void> main(args) => runIsolateTests(
|
||||
args,
|
||||
test,
|
||||
testeeConcurrent: fooBar,
|
||||
pause_on_start: true,
|
||||
pause_on_exit: true,
|
||||
enableService: false,
|
||||
);
|
||||
@@ -1,50 +0,0 @@
|
||||
// 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.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:observatory/service_io.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'client_resume_approvals_common.dart';
|
||||
import 'service_test_common.dart';
|
||||
import 'test_helper.dart';
|
||||
|
||||
void fooBar() {
|
||||
int i = 0;
|
||||
print(i);
|
||||
}
|
||||
|
||||
late WebSocketVM client1;
|
||||
late WebSocketVM client2;
|
||||
|
||||
final sameClientNamesTest = <IsolateTest>[
|
||||
// Multiple clients, same client names.
|
||||
(Isolate isolate) async {
|
||||
final resumeFuture = waitForResume(isolate);
|
||||
|
||||
client1 = await createClient(isolate.owner as WebSocketVM);
|
||||
await setRequireApprovalForResume(
|
||||
client1,
|
||||
isolate,
|
||||
pauseOnStart: true,
|
||||
pauseOnExit: true,
|
||||
);
|
||||
client2 = await createClient(isolate.owner as WebSocketVM);
|
||||
|
||||
expect(await isPausedAtStart(isolate), true);
|
||||
await resume(client2, isolate);
|
||||
await resumeFuture;
|
||||
},
|
||||
hasStoppedAtExit,
|
||||
];
|
||||
|
||||
Future<void> main(args) => runIsolateTests(
|
||||
args,
|
||||
sameClientNamesTest,
|
||||
testeeConcurrent: fooBar,
|
||||
pause_on_start: true,
|
||||
pause_on_exit: true,
|
||||
enableService: false,
|
||||
);
|
||||
@@ -1,69 +0,0 @@
|
||||
// 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.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:observatory/service_io.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'client_resume_approvals_common.dart';
|
||||
import 'service_test_common.dart';
|
||||
import 'test_helper.dart';
|
||||
|
||||
void fooBar() {
|
||||
int i = 0;
|
||||
print(i);
|
||||
}
|
||||
|
||||
late WebSocketVM client1;
|
||||
late WebSocketVM client2;
|
||||
late WebSocketVM client3;
|
||||
|
||||
final multipleClientNamesTest = <IsolateTest>[
|
||||
// Multiple clients, different client names.
|
||||
(Isolate isolate) async {
|
||||
client1 = await createClient(isolate.owner as WebSocketVM);
|
||||
await setRequireApprovalForResume(
|
||||
client1,
|
||||
isolate,
|
||||
pauseOnStart: true,
|
||||
pauseOnExit: true,
|
||||
);
|
||||
client2 = await createClient(
|
||||
isolate.owner as WebSocketVM,
|
||||
clientName: otherClientName,
|
||||
);
|
||||
client3 = await createClient(isolate.owner as WebSocketVM,
|
||||
clientName: 'DummyClient');
|
||||
|
||||
final resumeFuture = waitForResume(isolate);
|
||||
expect(await isPausedAtStart(isolate), true);
|
||||
await resume(client2, isolate);
|
||||
expect(await isPausedAtStart(isolate), true);
|
||||
await resume(client1, isolate);
|
||||
await resumeFuture;
|
||||
expect(await isPausedAtStart(isolate), false);
|
||||
},
|
||||
hasStoppedAtExit,
|
||||
(Isolate isolate) async {
|
||||
await setRequireApprovalForResume(
|
||||
client2,
|
||||
isolate,
|
||||
pauseOnExit: true,
|
||||
);
|
||||
await resume(client1, isolate);
|
||||
expect(await isPausedAtExit(isolate), true);
|
||||
await resume(client2, isolate);
|
||||
await waitForTargetVMExit(isolate.vm);
|
||||
},
|
||||
];
|
||||
|
||||
Future<void> main(args) => runIsolateTests(
|
||||
args,
|
||||
multipleClientNamesTest,
|
||||
testeeConcurrent: fooBar,
|
||||
pause_on_start: true,
|
||||
pause_on_exit: true,
|
||||
enableService: false,
|
||||
);
|
||||
@@ -1,62 +0,0 @@
|
||||
// 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.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:observatory/service_io.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'client_resume_approvals_common.dart';
|
||||
import 'service_test_common.dart';
|
||||
import 'test_helper.dart';
|
||||
|
||||
void fooBar() {
|
||||
int i = 0;
|
||||
print(i);
|
||||
}
|
||||
|
||||
late WebSocketVM client1;
|
||||
late WebSocketVM client2;
|
||||
late WebSocketVM client3;
|
||||
|
||||
final nameChangeTest = <IsolateTest>[
|
||||
// Remove required approvals via name change.
|
||||
(Isolate isolate) async {
|
||||
waitForResume(isolate);
|
||||
|
||||
// Create two clients with the same name.
|
||||
client1 = await createClient(isolate.owner as WebSocketVM);
|
||||
client2 = await createClient(isolate.owner as WebSocketVM);
|
||||
await setRequireApprovalForResume(
|
||||
client1,
|
||||
isolate,
|
||||
pauseOnStart: true,
|
||||
pauseOnExit: true,
|
||||
);
|
||||
client3 = await createClient(isolate.owner as WebSocketVM,
|
||||
clientName: otherClientName);
|
||||
|
||||
// Check that client3 can't resume the isolate on its own.
|
||||
expect(await isPausedAtStart(isolate), true);
|
||||
await resume(client3, isolate);
|
||||
expect(await isPausedAtStart(isolate), true);
|
||||
|
||||
// Change the name of client1. Since client2 has the same name that client1
|
||||
// originally had, the service still requires approval to resume the
|
||||
// isolate.
|
||||
await setClientName(client1, 'foobar');
|
||||
expect(await isPausedAtStart(isolate), true);
|
||||
await setClientName(client2, 'baz');
|
||||
},
|
||||
hasStoppedAtExit,
|
||||
];
|
||||
|
||||
Future<void> main(args) => runIsolateTests(
|
||||
args,
|
||||
nameChangeTest,
|
||||
testeeConcurrent: fooBar,
|
||||
pause_on_start: true,
|
||||
pause_on_exit: true,
|
||||
enableService: false,
|
||||
);
|
||||
@@ -1,68 +0,0 @@
|
||||
// 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.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:observatory/service_io.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'client_resume_approvals_common.dart';
|
||||
import 'service_test_common.dart';
|
||||
import 'test_helper.dart';
|
||||
|
||||
void fooBar() {
|
||||
int _ = 0;
|
||||
while (true) {
|
||||
_++;
|
||||
}
|
||||
}
|
||||
|
||||
late WebSocketVM client1;
|
||||
late WebSocketVM client2;
|
||||
|
||||
final hotReloadTest = <IsolateTest>[
|
||||
// Multiple clients, hot reload approval.
|
||||
(Isolate isolate) async {
|
||||
waitForResume(isolate);
|
||||
|
||||
client1 = await createClient(isolate.owner as WebSocketVM);
|
||||
await setRequireApprovalForResume(
|
||||
client1,
|
||||
isolate,
|
||||
pauseOnReload: true,
|
||||
);
|
||||
client2 = await createClient(
|
||||
isolate.owner as WebSocketVM,
|
||||
clientName: otherClientName,
|
||||
);
|
||||
await setRequireApprovalForResume(
|
||||
client2,
|
||||
isolate,
|
||||
pauseOnReload: true,
|
||||
);
|
||||
},
|
||||
// Paused on start, resume.
|
||||
resumeIsolate,
|
||||
// Reload and then pause.
|
||||
reloadSources(true),
|
||||
hasStoppedPostRequest,
|
||||
(Isolate isolate) async {
|
||||
// Check that client2 can't resume the isolate on its own.
|
||||
expect(await isPausedPostRequest(isolate), true);
|
||||
await resume(client2, isolate);
|
||||
expect(await isPausedPostRequest(isolate), true);
|
||||
final resumeFuture = waitForResume(isolate);
|
||||
await resume(client1, isolate);
|
||||
await resumeFuture;
|
||||
expect(await isPausedPostRequest(isolate), false);
|
||||
},
|
||||
];
|
||||
|
||||
Future<void> main(args) => runIsolateTests(
|
||||
args,
|
||||
hotReloadTest,
|
||||
testeeConcurrent: fooBar,
|
||||
pause_on_start: true,
|
||||
enableService: false,
|
||||
);
|
||||
@@ -1,34 +0,0 @@
|
||||
// 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 'package:observatory/service_io.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'test_helper.dart';
|
||||
|
||||
Future streamListen(VM vm, String streamId) async =>
|
||||
await vm.invokeRpcNoUpgrade(
|
||||
'streamListen',
|
||||
{
|
||||
'streamId': streamId,
|
||||
},
|
||||
);
|
||||
|
||||
var tests = <VMTest>[
|
||||
// Ensure the DDS allows for listening to a custom stream
|
||||
(VM vm) async {
|
||||
try {
|
||||
await streamListen(vm, 'Foo');
|
||||
} catch (e) {
|
||||
fail('Unable to subscribe to a custom stream: $e');
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
main(args) => runVMTests(
|
||||
args,
|
||||
tests,
|
||||
enableService: false,
|
||||
enableDds: true,
|
||||
);
|
||||
@@ -1,52 +0,0 @@
|
||||
// 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.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:observatory/service_io.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'client_resume_approvals_common.dart';
|
||||
import 'service_test_common.dart';
|
||||
import 'test_helper.dart';
|
||||
|
||||
Future testMain() async {
|
||||
// Post a total of 9 events
|
||||
for (int i = 1; i <= 9; ++i) {
|
||||
postEvent('Test', {
|
||||
'id': i,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var tests = <IsolateTest>[
|
||||
isPausedAtStart,
|
||||
resumeIsolate,
|
||||
(Isolate isolate) async {
|
||||
final completer = Completer<void>();
|
||||
int i = 1;
|
||||
await subscribeToStream(isolate.vm, 'Extension', (event) async {
|
||||
expect(event.extensionKind, 'Test');
|
||||
expect(event.extensionData!['id'], i);
|
||||
i++;
|
||||
|
||||
if (i == 10) {
|
||||
await cancelStreamSubscription('Extension');
|
||||
completer.complete();
|
||||
} else if (i > 10) {
|
||||
fail('Too many log messages');
|
||||
}
|
||||
});
|
||||
await completer.future;
|
||||
},
|
||||
];
|
||||
|
||||
main(args) => runIsolateTests(
|
||||
args,
|
||||
tests,
|
||||
enableService: false, // DDS specific feature
|
||||
testeeConcurrent: testMain,
|
||||
pause_on_start: true,
|
||||
pause_on_exit: true,
|
||||
);
|
||||
@@ -1,83 +0,0 @@
|
||||
// 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.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:observatory/service_io.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'service_test_common.dart';
|
||||
import 'test_helper.dart';
|
||||
|
||||
Future testMain() async {
|
||||
// Log a total of 30 messages
|
||||
for (int i = 1; i <= maxLogHistorySize + 10; ++i) {
|
||||
log('All work and no play makes Ben a dull boy ($i)');
|
||||
}
|
||||
debugger();
|
||||
}
|
||||
|
||||
const maxLogHistorySize = 100000;
|
||||
|
||||
Future setLogHistorySize(Isolate isolate, int size) async {
|
||||
return await isolate.invokeRpcNoUpgrade('setLogHistorySize', {
|
||||
'size': size,
|
||||
});
|
||||
}
|
||||
|
||||
Future<int> getLogHistorySize(Isolate isolate) async {
|
||||
final result = await isolate.invokeRpcNoUpgrade('getLogHistorySize', {});
|
||||
expect(result['type'], 'Size');
|
||||
return result['size'] as int;
|
||||
}
|
||||
|
||||
var tests = <IsolateTest>[
|
||||
hasPausedAtStart,
|
||||
(Isolate isolate) async {
|
||||
final initialSize = await getLogHistorySize(isolate);
|
||||
try {
|
||||
await setLogHistorySize(isolate, maxLogHistorySize + 1);
|
||||
} on ServerRpcException catch (e) {
|
||||
expect(e.message, "'size' must be less than $maxLogHistorySize");
|
||||
}
|
||||
expect(await getLogHistorySize(isolate), initialSize);
|
||||
},
|
||||
(Isolate isolate) async {
|
||||
final result = await setLogHistorySize(isolate, maxLogHistorySize);
|
||||
expect(result['type'], 'Success');
|
||||
expect(await getLogHistorySize(isolate), maxLogHistorySize);
|
||||
},
|
||||
resumeIsolate,
|
||||
hasStoppedAtBreakpoint,
|
||||
(Isolate isolate) async {
|
||||
print("Starting step 6");
|
||||
final completer = Completer<void>();
|
||||
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
|
||||
int i = 11;
|
||||
await subscribeToStream(isolate.vm, 'Logging', (event) async {
|
||||
expect(
|
||||
event.logRecord!['message'].valueAsString,
|
||||
'All work and no play makes Ben a dull boy ($i)',
|
||||
);
|
||||
i++;
|
||||
|
||||
if (i == maxLogHistorySize + 10) {
|
||||
await cancelStreamSubscription('Logging');
|
||||
completer.complete();
|
||||
}
|
||||
});
|
||||
await completer.future;
|
||||
},
|
||||
];
|
||||
|
||||
main(args) => runIsolateTests(
|
||||
args,
|
||||
tests,
|
||||
enableService: false, // DDS specific feature
|
||||
testeeConcurrent: testMain,
|
||||
pause_on_start: true,
|
||||
pause_on_exit: true,
|
||||
);
|
||||
@@ -1,98 +0,0 @@
|
||||
// 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.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:observatory/service_io.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'client_resume_approvals_common.dart';
|
||||
import 'service_test_common.dart';
|
||||
import 'test_helper.dart';
|
||||
|
||||
Future testMain() async {
|
||||
// Log a total of 9 messages
|
||||
for (int i = 1; i <= 9; ++i) {
|
||||
log('log$i');
|
||||
}
|
||||
debugger();
|
||||
log('log10');
|
||||
}
|
||||
|
||||
Future setLogHistorySize(Isolate isolate, int size) async {
|
||||
return await isolate.invokeRpcNoUpgrade('setLogHistorySize', {
|
||||
'size': size,
|
||||
});
|
||||
}
|
||||
|
||||
Future<int> getLogHistorySize(Isolate isolate) async {
|
||||
final result = await isolate.invokeRpcNoUpgrade('getLogHistorySize', {});
|
||||
expect(result['type'], 'Size');
|
||||
return result['size'] as int;
|
||||
}
|
||||
|
||||
var tests = <IsolateTest>[
|
||||
isPausedAtStart,
|
||||
resumeIsolate,
|
||||
(Isolate isolate) async {
|
||||
// Check that resizing does the right thing.
|
||||
final result = await setLogHistorySize(isolate, 10);
|
||||
expect(result['type'], 'Success');
|
||||
expect(await getLogHistorySize(isolate), 10);
|
||||
|
||||
final completer = Completer<void>();
|
||||
|
||||
int i = 1;
|
||||
await subscribeToStream(isolate.vm, 'Logging', (event) async {
|
||||
expect(event.logRecord!['message'].valueAsString, 'log$i');
|
||||
i++;
|
||||
|
||||
if (i == 10) {
|
||||
await cancelStreamSubscription('Logging');
|
||||
completer.complete();
|
||||
} else if (i > 10) {
|
||||
fail('Too many log messages');
|
||||
}
|
||||
});
|
||||
await completer.future;
|
||||
},
|
||||
(Isolate isolate) async {
|
||||
// Resize to be smaller
|
||||
final result = await setLogHistorySize(isolate, 5);
|
||||
expect(result['type'], 'Success');
|
||||
expect(await getLogHistorySize(isolate), 5);
|
||||
},
|
||||
resumeIsolate,
|
||||
(Isolate isolate) async {
|
||||
final completer = Completer<void>();
|
||||
|
||||
// Create a new client as we want to get log messages from the entire
|
||||
// history buffer.
|
||||
final client = await createClient(isolate.vm as WebSocketVM);
|
||||
|
||||
int i = 6;
|
||||
await subscribeToStream(client, 'Logging', (event) async {
|
||||
expect(event.logRecord!['message'].valueAsString, 'log$i');
|
||||
i++;
|
||||
|
||||
if (i == 11) {
|
||||
await cancelStreamSubscription('Logging');
|
||||
completer.complete();
|
||||
} else if (i > 11) {
|
||||
fail('Too many log messages');
|
||||
}
|
||||
});
|
||||
await completer.future;
|
||||
client.disconnect();
|
||||
},
|
||||
];
|
||||
|
||||
main(args) => runIsolateTests(
|
||||
args,
|
||||
tests,
|
||||
enableService: false, // DDS specific feature
|
||||
testeeConcurrent: testMain,
|
||||
pause_on_start: true,
|
||||
pause_on_exit: true,
|
||||
);
|
||||
@@ -1,91 +0,0 @@
|
||||
// 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.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:observatory/service_io.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'service_test_common.dart';
|
||||
import 'test_helper.dart';
|
||||
|
||||
Future testMain() async {
|
||||
// Initial logging history should be 0, so these messages won't be buffered.
|
||||
log('log1');
|
||||
log('log2');
|
||||
|
||||
// Setting the log history length does not apply retroactively.
|
||||
debugger();
|
||||
|
||||
// Log a total of 30 messages
|
||||
for (int i = 3; i <= 30; ++i) {
|
||||
log('log$i');
|
||||
}
|
||||
}
|
||||
|
||||
Future setLogHistorySize(Isolate isolate, int size) async {
|
||||
return await isolate.invokeRpcNoUpgrade('setLogHistorySize', {
|
||||
'size': size,
|
||||
});
|
||||
}
|
||||
|
||||
Future<int> getLogHistorySize(Isolate isolate) async {
|
||||
final result = await isolate.invokeRpcNoUpgrade('getLogHistorySize', {});
|
||||
expect(result['type'], 'Size');
|
||||
return result['size'] as int;
|
||||
}
|
||||
|
||||
var tests = <IsolateTest>[
|
||||
hasPausedAtStart,
|
||||
(Isolate isolate) async {
|
||||
final result = await setLogHistorySize(isolate, 0);
|
||||
expect(result['type'], 'Success');
|
||||
expect(await getLogHistorySize(isolate), 0);
|
||||
},
|
||||
resumeIsolate,
|
||||
hasStoppedAtBreakpoint,
|
||||
(Isolate isolate) async {
|
||||
final result = await setLogHistorySize(isolate, 20);
|
||||
expect(await getLogHistorySize(isolate), 20);
|
||||
expect(result['type'], 'Success');
|
||||
},
|
||||
resumeIsolate,
|
||||
(Isolate isolate) async {
|
||||
final completer = Completer<void>();
|
||||
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
|
||||
// With the log history set to 20, the first log message should be 'log11'
|
||||
int i = 11;
|
||||
await subscribeToStream(isolate.vm, 'Logging', (event) async {
|
||||
expect(event.logRecord!['message'].valueAsString, 'log$i');
|
||||
i++;
|
||||
|
||||
if (i == 30) {
|
||||
await cancelStreamSubscription('Logging');
|
||||
completer.complete();
|
||||
}
|
||||
});
|
||||
await completer.future;
|
||||
},
|
||||
(Isolate isolate) async {
|
||||
try {
|
||||
// Try to set an invalid history size
|
||||
await setLogHistorySize(isolate, -1);
|
||||
fail('Successfully set invalid size');
|
||||
} on ServerRpcException catch (e) {
|
||||
expect(e.message, "'size' must be greater or equal to zero");
|
||||
}
|
||||
expect(await getLogHistorySize(isolate), 20);
|
||||
}
|
||||
];
|
||||
|
||||
main(args) => runIsolateTests(
|
||||
args,
|
||||
tests,
|
||||
enableService: false, // DDS specific feature
|
||||
testeeConcurrent: testMain,
|
||||
pause_on_start: true,
|
||||
pause_on_exit: true,
|
||||
);
|
||||
@@ -1,65 +0,0 @@
|
||||
// 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.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:observatory/service_io.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'client_resume_approvals_common.dart';
|
||||
import 'service_test_common.dart';
|
||||
import 'test_helper.dart';
|
||||
|
||||
Future testMain() async {
|
||||
// Log a total of 9 messages
|
||||
for (int i = 1; i <= 9; ++i) {
|
||||
print('Stdout log$i');
|
||||
stderr.writeln('Stderr log$i');
|
||||
}
|
||||
}
|
||||
|
||||
Future streamHistoryTest(Isolate isolate, String stream) async {
|
||||
final completer = Completer<void>();
|
||||
int i = 1;
|
||||
await subscribeToStream(isolate.vm, stream, (event) async {
|
||||
if (stream == 'Stdout') {
|
||||
expect(event.bytesAsString, '$stream log$i\n');
|
||||
} else {
|
||||
// Newlines are sent as separate events for some reason. Ignore them.
|
||||
if (!event.bytesAsString!.startsWith(stream)) {
|
||||
return;
|
||||
}
|
||||
expect(event.bytesAsString, '$stream log$i');
|
||||
}
|
||||
i++;
|
||||
|
||||
if (i == 10) {
|
||||
await cancelStreamSubscription(stream);
|
||||
completer.complete();
|
||||
} else if (i > 10) {
|
||||
fail('Too many log messages');
|
||||
}
|
||||
});
|
||||
await completer.future;
|
||||
}
|
||||
|
||||
var tests = <IsolateTest>[
|
||||
isPausedAtStart,
|
||||
resumeIsolate,
|
||||
(Isolate isolate) async {
|
||||
await streamHistoryTest(isolate, 'Stdout');
|
||||
},
|
||||
(Isolate isolate) async {
|
||||
await streamHistoryTest(isolate, 'Stderr');
|
||||
},
|
||||
];
|
||||
|
||||
main(args) => runIsolateTests(
|
||||
args,
|
||||
tests,
|
||||
enableService: false, // DDS specific feature
|
||||
testeeConcurrent: testMain,
|
||||
pause_on_start: true,
|
||||
pause_on_exit: true,
|
||||
);
|
||||
@@ -1,45 +0,0 @@
|
||||
// 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.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:observatory/service_io.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'test_helper.dart';
|
||||
|
||||
void fooBar() {}
|
||||
|
||||
Future<String> getClientName(Isolate isolate) async {
|
||||
final result = await isolate.vm.invokeRpcNoUpgrade('getClientName', {});
|
||||
return result['name'] as String;
|
||||
}
|
||||
|
||||
Future<void> setClientName(Isolate isolate, String name) async =>
|
||||
await isolate.vm.invokeRpcNoUpgrade('setClientName', {
|
||||
'name': name,
|
||||
});
|
||||
|
||||
final test = <IsolateTest>[
|
||||
(Isolate isolate) async {
|
||||
// Each client has a default name based on the order of connection to the
|
||||
// service.
|
||||
expect(await getClientName(isolate), 'client1');
|
||||
|
||||
// Set a custom client name and check it was set properly.
|
||||
await setClientName(isolate, 'foobar');
|
||||
expect(await getClientName(isolate), 'foobar');
|
||||
|
||||
// Clear the client name and check that we're using the default again.
|
||||
await setClientName(isolate, '');
|
||||
expect(await getClientName(isolate), 'client1');
|
||||
},
|
||||
];
|
||||
|
||||
Future<void> main(args) => runIsolateTests(
|
||||
args,
|
||||
test,
|
||||
testeeBefore: fooBar,
|
||||
enableService: false,
|
||||
);
|
||||
Reference in New Issue
Block a user