diff --git a/pkg/vm_service/test/field_script_other.dart b/pkg/vm_service/test/field_script_other.dart new file mode 100644 index 00000000000..8a335cadfa8 --- /dev/null +++ b/pkg/vm_service/test/field_script_other.dart @@ -0,0 +1,7 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of field_script_test; + +var otherField = 42; diff --git a/pkg/vm_service/test/field_script_test.dart b/pkg/vm_service/test/field_script_test.dart new file mode 100644 index 00000000000..830537e650a --- /dev/null +++ b/pkg/vm_service/test/field_script_test.dart @@ -0,0 +1,62 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library field_script_test; + +import 'package:vm_service/vm_service.dart'; +import 'package:test/test.dart'; + +import 'common/test_helper.dart'; +import 'common/service_test_common.dart'; + +part 'field_script_other.dart'; + +code() { + print(otherField); +} + +final tests = [ + hasPausedAtStart, + (VmService service, IsolateRef isolateRef) async { + final isolateId = isolateRef.id!; + final isolate = await service.getIsolate(isolateId); + final lib = await service.getObject( + isolateId, + isolate.rootLib!.id!, + ) as Library; + + final fields = lib.variables!; + expect(fields.length, 2); + for (final fieldRef in fields) { + final field = await service.getObject(isolateId, fieldRef.id!) as Field; + final location = field.location!; + if (field.name == 'tests') { + expect( + location.script!.uri!.endsWith('field_script_test.dart'), + true, + ); + expect(location.line, 19); + expect(location.column, 7); + } else if (field.name == 'otherField') { + expect( + location.script!.uri!.endsWith('field_script_other.dart'), + true, + ); + expect(location.line, 7); + expect(location.column, 5); + } else { + fail('Unexpected field: ${field.name}'); + } + } + } +]; + +void main([args = const []]) => runIsolateTestsSynchronous( + args, + tests, + 'field_script_test.dart', + testeeConcurrent: code, + pause_on_start: true, + pause_on_exit: true, + ); diff --git a/pkg/vm_service/test/file_service_test.dart b/pkg/vm_service/test/file_service_test.dart index c4a08d7e0b3..cef5ed8ab8a 100644 --- a/pkg/vm_service/test/file_service_test.dart +++ b/pkg/vm_service/test/file_service_test.dart @@ -1,4 +1,4 @@ -// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. @@ -7,9 +7,10 @@ import 'dart:convert'; import 'dart:developer'; import 'dart:io' as io; -import 'package:test/test.dart'; import 'package:vm_service/vm_service.dart'; +import 'package:test/test.dart'; +import 'common/expect.dart'; import 'common/test_helper.dart'; Future setupFiles() async { @@ -56,10 +57,11 @@ Future setupFiles() async { final utilFile = io.File(writeTemp); await utilFile.writeAsString('foobar'); final readTemp = io.File(writeTemp); - await readTemp.readAsString(); + final result = await readTemp.readAsString(); + Expect.equals(result, 'foobar'); } catch (e) { closeDown(); - rethrow; + throw e; } final result = jsonEncode({'type': 'foobar'}); return Future.value(ServiceExtensionResponse.result(result)); @@ -69,22 +71,19 @@ Future setupFiles() async { registerExtension('ext.dart.io.setup', setup); } -var fileTests = [ - (VmService service, IsolateRef isolate) async { - final isolateId = isolate.id!; - await service.callServiceExtension( - 'ext.dart.io.setup', - isolateId: isolate.id, - ); +final tests = [ + (VmService service, IsolateRef isolateRef) async { + final isolateId = isolateRef.id!; try { + await service.callServiceExtension('ext.dart.io.setup', + isolateId: isolateId); final result = await service.getOpenFiles(isolateId); - expect(result, isA()); - expect(result.files.length, equals(2)); + expect(result.files.length, 2); + final writing = await service.getOpenFileById( isolateId, result.files[0].id, ); - expect(writing.readBytes, 0); expect(writing.readCount, 0); expect(writing.writeCount, 3); @@ -96,6 +95,7 @@ var fileTests = [ isolateId, result.files[1].id, ); + expect(reading.readBytes, 5); expect(reading.readCount, 5); expect(reading.writeCount, 0); @@ -105,15 +105,15 @@ var fileTests = [ } finally { await service.callServiceExtension( 'ext.dart.io.cleanup', - isolateId: isolate.id, + isolateId: isolateId, ); } }, ]; -main([args = const []]) async => runIsolateTests( +void main([args = const []]) => runIsolateTests( args, - fileTests, + tests, 'file_service_test.dart', testeeBefore: setupFiles, ); diff --git a/pkg/vm_service/test/gc_test.dart b/pkg/vm_service/test/gc_test.dart new file mode 100644 index 00000000000..bd478cf6bfa --- /dev/null +++ b/pkg/vm_service/test/gc_test.dart @@ -0,0 +1,45 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; + +import 'package:vm_service/vm_service.dart'; + +import 'common/test_helper.dart'; + +void script() { + var grow; + grow = (int iterations, int size, Duration duration) { + if (iterations <= 0) { + return; + } + List.filled(size, 0); + Timer(duration, () => grow(iterations - 1, size, duration)); + }; + grow(100, 1 << 24, new Duration(seconds: 1)); +} + +final tests = [ + (VmService service, IsolateRef isolateRef) async { + Completer completer = Completer(); + // Expect at least this many GC events. + int gcCountdown = 3; + late final StreamSubscription sub; + sub = service.onGCEvent.listen((stream) { + if (--gcCountdown == 0) { + sub.cancel(); + completer.complete(); + } + }); + await service.streamListen(EventStreams.kGC); + return completer.future; + }, +]; + +void main([args = const []]) => runIsolateTests( + args, + tests, + 'gc_test.dart', + testeeConcurrent: script, + ); diff --git a/pkg/vm_service/test/get_allocation_profile_public_rpc_test.dart b/pkg/vm_service/test/get_allocation_profile_public_rpc_test.dart new file mode 100644 index 00000000000..07f513e15c0 --- /dev/null +++ b/pkg/vm_service/test/get_allocation_profile_public_rpc_test.dart @@ -0,0 +1,82 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; + +import 'package:vm_service/vm_service.dart'; +import 'package:test/test.dart'; + +import 'common/test_helper.dart'; + +void verifyMember(ClassHeapStats member) { + expect(member.json!.containsKey('_new'), false); + expect(member.json!.containsKey('_old'), false); + expect(member.json!.containsKey('_promotedInstances'), false); + expect(member.json!.containsKey('_promotedBytes'), false); + expect(member.instancesAccumulated, greaterThanOrEqualTo(0)); + expect(member.instancesCurrent, greaterThanOrEqualTo(0)); + expect(member.bytesCurrent, greaterThanOrEqualTo(0)); + expect(member.accumulatedSize, greaterThanOrEqualTo(0)); +} + +final tests = [ + (VmService service, IsolateRef isolateRef) async { + final isolateId = isolateRef.id!; + var result = await service.getAllocationProfile(isolateId); + expect(result.dateLastAccumulatorReset, isNull); + expect(result.dateLastServiceGC, isNull); + expect(result.json!.containsKey('_heaps'), false); + var members = result.members!; + expect(members, isNotEmpty); + + var member = members.first; + verifyMember(member); + + // reset. + result = await service.getAllocationProfile(isolateId, reset: true); + final firstReset = result.dateLastAccumulatorReset!; + expect(result.dateLastServiceGC, isNull); + expect(result.json!.containsKey('_heaps'), false); + + members = result.members!; + expect(members, isNotEmpty); + + member = members.first; + verifyMember(member); + + // Create an artificial delay to ensure there's a difference between the + // reset times. + await Future.delayed(const Duration(milliseconds: 100)); + + result = await service.getAllocationProfile(isolateId, reset: true); + final secondReset = result.dateLastAccumulatorReset!; + expect(secondReset, isNot(firstReset)); + + // gc. + result = await service.getAllocationProfile(isolateId, gc: true); + expect(result.dateLastAccumulatorReset, secondReset); + final firstGC = result.dateLastServiceGC!; + expect(result.json!.containsKey('_heaps'), false); + + members = result.members!; + expect(members, isNotEmpty); + + member = members.first; + verifyMember(member); + + // Create an artificial delay to ensure there's a difference between the + // GC times. + await Future.delayed(const Duration(milliseconds: 100)); + + result = await service.getAllocationProfile(isolateId, gc: true); + final secondGC = result.dateLastServiceGC!; + expect(secondGC, isNot(firstGC)); + }, +]; + +void main([args = const []]) => runIsolateTests( + args, + tests, + 'get_allocation_profile_public_rpc_test.dart', + ); diff --git a/pkg/vm_service/test/get_instances_as_array_rpc_expression_evaluation_on_internal_test.dart b/pkg/vm_service/test/get_instances_as_array_rpc_expression_evaluation_on_internal_test.dart new file mode 100644 index 00000000000..9281511ba22 --- /dev/null +++ b/pkg/vm_service/test/get_instances_as_array_rpc_expression_evaluation_on_internal_test.dart @@ -0,0 +1,49 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:test/test.dart'; +import 'package:vm_service/vm_service.dart'; + +import 'common/test_helper.dart'; + +final tests = [ + (VmService service, IsolateRef isolateRef) async { + final isolateId = isolateRef.id!; + + Future getInstancesAndExecuteExpression(ClassHeapStats member) async { + final objectId = member.classRef!.id!; + final result = await service.getInstancesAsList(isolateId, objectId); + // This has previously caused an exception like + // "RPCError(evaluate: Unexpected exception: + // FormatException: Unexpected character (at offset 329)" + try { + await service.evaluate(isolateId, result.id!, 'this'); + } on RPCError catch (e) { + expect(e.code, RPCErrorKind.kExpressionCompilationError.code); + expect( + e.details, + contains('Cannot evaluate against a VM-internal object'), + ); + return; + } + fail('Expected exception'); + } + + final result = await service.getAllocationProfile(isolateId); + final members = result.members!; + for (final member in members) { + final name = member.classRef!.name!; + if (name == 'Library') { + await getInstancesAndExecuteExpression(member); + break; + } + } + }, +]; + +void main([args = const []]) => runIsolateTests( + args, + tests, + 'get_instances_as_array_rpc_expression_evaluation_on_internal_test.dart', + ); diff --git a/pkg/vm_service/test/get_instances_as_array_rpc_test.dart b/pkg/vm_service/test/get_instances_as_array_rpc_test.dart new file mode 100644 index 00000000000..3facc9b3c20 --- /dev/null +++ b/pkg/vm_service/test/get_instances_as_array_rpc_test.dart @@ -0,0 +1,77 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:vm_service/vm_service.dart'; +import 'package:test/test.dart'; + +import 'common/test_helper.dart'; + +@pragma('vm:entry-point') +class Class {} + +@pragma('vm:entry-point') +class Subclass extends Class {} + +@pragma('vm:entry-point') +class Implementor implements Class {} + +@pragma('vm:entry-point') +var aClass; +@pragma('vm:entry-point') +var aSubclass; +@pragma('vm:entry-point') +var anImplementor; + +@pragma('vm:entry-point') +void allocate() { + aClass = Class(); + aSubclass = Subclass(); + anImplementor = Implementor(); +} + +final tests = [ + (VmService service, IsolateRef isolateRef) async { + final isolateId = isolateRef.id!; + final isolate = await service.getIsolate(isolateId); + final rootLibId = isolate.rootLib!.id!; + final rootLib = await service.getObject(isolateId, rootLibId) as Library; + + Future invoke(String selector) => service.invoke( + isolateId, + rootLibId, + selector, + const [], + ); + + Future instanceCount(String className, + {bool includeSubclasses = false, + bool includeImplementors = false}) async { + final objectId = + rootLib.classes!.singleWhere((cls) => cls.name == className).id!; + final result = await service.getInstancesAsList( + isolateId, + objectId, + includeImplementers: includeImplementors, + includeSubclasses: includeSubclasses, + ); + return result.length!; + } + + expect(await instanceCount('Class'), 0); + expect(await instanceCount('Class', includeSubclasses: true), 0); + expect(await instanceCount('Class', includeImplementors: true), 0); + + await invoke('allocate'); + + expect(await instanceCount('Class'), 1); + expect(await instanceCount('Class', includeSubclasses: true), 2); + expect(await instanceCount('Class', includeImplementors: true), 3); + }, +]; + +void main([args = const []]) => runIsolateTests( + args, + tests, + 'get_instances_as_array_rpc_test.dart', + ); diff --git a/pkg/vm_service/test/get_isolate_after_async_error_test.dart b/pkg/vm_service/test/get_isolate_after_async_error_test.dart new file mode 100644 index 00000000000..2fc192304e5 --- /dev/null +++ b/pkg/vm_service/test/get_isolate_after_async_error_test.dart @@ -0,0 +1,30 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:vm_service/vm_service.dart'; +import 'package:test/test.dart'; + +import 'common/test_helper.dart'; +import 'common/service_test_common.dart'; + +Future doThrow() async { + throw 'oh no'; +} + +final tests = [ + hasStoppedAtExit, + (VmService service, IsolateRef isolateRef) async { + final isolate = await service.getIsolate(isolateRef.id!); + expect(isolate.error, isNotNull); + expect(isolate.error!.message!.contains('oh no'), true); + } +]; + +void main([args = const []]) => runIsolateTests( + args, + tests, + 'get_isolate_after_async_error_test.dart', + pause_on_exit: true, + testeeConcurrent: doThrow, + ); diff --git a/pkg/vm_service/test/get_isolate_after_stack_overflow_error_test.dart b/pkg/vm_service/test/get_isolate_after_stack_overflow_error_test.dart new file mode 100644 index 00000000000..01779f5c487 --- /dev/null +++ b/pkg/vm_service/test/get_isolate_after_stack_overflow_error_test.dart @@ -0,0 +1,35 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:vm_service/vm_service.dart'; +import 'package:test/test.dart'; + +import 'common/test_helper.dart'; +import 'common/service_test_common.dart'; + +// Non tailable recursive function that should trigger a Stack Overflow. +num factorialGrowth([num n = 1]) { + return factorialGrowth(n + 1) * n; +} + +void nonTailableRecursion() { + factorialGrowth(); +} + +final tests = [ + hasStoppedAtExit, + (VmService service, IsolateRef isolateRef) async { + final isolate = await service.getIsolate(isolateRef.id!); + expect(isolate.error, isNotNull); + expect(isolate.error!.message!.contains('Stack Overflow'), true); + } +]; + +void main([args = const []]) => runIsolateTests( + args, + tests, + 'get_isolate_after_stack_overflow_error_test.dart', + pause_on_exit: true, + testeeConcurrent: nonTailableRecursion, + ); diff --git a/pkg/vm_service/test/get_isolate_after_sync_error_test.dart b/pkg/vm_service/test/get_isolate_after_sync_error_test.dart new file mode 100644 index 00000000000..cded23cd488 --- /dev/null +++ b/pkg/vm_service/test/get_isolate_after_sync_error_test.dart @@ -0,0 +1,30 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:vm_service/vm_service.dart'; +import 'package:test/test.dart'; + +import 'common/test_helper.dart'; +import 'common/service_test_common.dart'; + +void doThrow() { + throw 'oh no'; +} + +final tests = [ + hasStoppedAtExit, + (VmService service, IsolateRef isolateRef) async { + final isolate = await service.getIsolate(isolateRef.id!); + expect(isolate.error, isNotNull); + expect(isolate.error!.message!.contains('oh no'), true); + } +]; + +void main([args = const []]) => runIsolateTests( + args, + tests, + 'get_isolate_after_async_error_test.dart', + pause_on_exit: true, + testeeConcurrent: doThrow, + ); diff --git a/pkg/vm_service/test/get_ports_public_rpc_test.dart b/pkg/vm_service/test/get_ports_public_rpc_test.dart new file mode 100644 index 00000000000..c6b292c7298 --- /dev/null +++ b/pkg/vm_service/test/get_ports_public_rpc_test.dart @@ -0,0 +1,54 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:isolate' hide Isolate; +import 'package:vm_service/vm_service.dart'; +import 'package:test/test.dart'; + +import 'common/test_helper.dart'; + +late final RawReceivePort port1; +late final RawReceivePort port2; +late final RawReceivePort port3; + +void warmup() { + port1 = RawReceivePort(null, 'port1'); + port2 = RawReceivePort((_) {}); + port3 = RawReceivePort((_) {}, 'port3'); + port3.close(); + RawReceivePort((_) {}, 'port4'); +} + +int countNameMatches(List ports, String name) { + int matches = 0; + for (final port in ports) { + if (port.debugName == name) { + matches++; + } + } + return matches; +} + +final tests = [ + (VmService service, IsolateRef isolateRef) async { + final isolateId = isolateRef.id!; + final result = await service.getPorts(isolateId); + final ports = result.ports!; + // There are at least three ports: the three created in warm up that + // weren't closed. Some OSes will have other ports open but we do not try + // and test for these. + expect(ports.length, greaterThanOrEqualTo(3)); + expect(countNameMatches(ports, 'port1'), 1); + expect(countNameMatches(ports, 'port3'), 0); + expect(countNameMatches(ports, 'port4'), 1); + expect(countNameMatches(ports, ''), greaterThanOrEqualTo(1)); + }, +]; + +void main([args = const []]) => runIsolateTests( + args, + tests, + 'get_ports_public_rpc_test.dart', + testeeBefore: warmup, + ); diff --git a/pkg/vm_service/test/get_process_memory_usage_rpc_test.dart b/pkg/vm_service/test/get_process_memory_usage_rpc_test.dart new file mode 100644 index 00000000000..8212c5cb9ae --- /dev/null +++ b/pkg/vm_service/test/get_process_memory_usage_rpc_test.dart @@ -0,0 +1,30 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:vm_service/vm_service.dart'; +import 'package:test/test.dart'; + +import 'common/test_helper.dart'; + +final tests = [ + (VmService service) async { + final result = await service.getProcessMemoryUsage(); + void checkProcessMemoryItem(ProcessMemoryItem item) { + expect(item.name, isNotNull); + expect(item.description, isNotNull); + expect(item.size, greaterThanOrEqualTo(0)); + for (final child in item.children!) { + checkProcessMemoryItem(child); + } + } + + checkProcessMemoryItem(result.root!); + }, +]; + +void main([args = const []]) => runVMTests( + args, + tests, + 'get_process_memory_usage_rpc_test.dart', + ); diff --git a/pkg/vm_service/test/get_retaining_path_rpc_test.dart b/pkg/vm_service/test/get_retaining_path_rpc_test.dart new file mode 100644 index 00000000000..13953ca14ed --- /dev/null +++ b/pkg/vm_service/test/get_retaining_path_rpc_test.dart @@ -0,0 +1,255 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:vm_service/vm_service.dart'; +import 'package:test/test.dart'; + +import 'common/test_helper.dart'; + +class _TestClass { + _TestClass(); + // Make sure these fields are not removed by the tree shaker. + @pragma("vm:entry-point") // Prevent obfuscation + dynamic x; + @pragma("vm:entry-point") // Prevent obfuscation + dynamic y; +} + +_TestClass? target1 = _TestClass(); +_TestClass? target2 = _TestClass(); +_TestClass? target3 = _TestClass(); +_TestClass? target4 = _TestClass(); +_TestClass? target5 = _TestClass(); +_TestClass? target6 = _TestClass(); +_TestClass? target7 = _TestClass(); +_TestClass? target8 = _TestClass(); + +@pragma("vm:entry-point") // Prevent obfuscation +Expando<_TestClass> expando = Expando<_TestClass>(); +@pragma("vm:entry-point") // Prevent obfuscation +_TestClass globalObject = _TestClass(); +@pragma("vm:entry-point") // Prevent obfuscation +dynamic globalList = List.filled(100, null); +@pragma("vm:entry-point") // Prevent obfuscation +dynamic globalMap1 = Map(); +@pragma("vm:entry-point") // Prevent obfuscation +dynamic globalMap2 = Map(); +@pragma("vm:entry-point") // Prevent obfuscation +_TestClass weakReachable = _TestClass(); +@pragma("vm:entry-point") // Prevent obfuscation +_TestClass weakUnreachable = _TestClass(); + +void warmup() { + globalObject.x = target1; + globalObject.y = target2; + globalList[12] = target3; + globalMap1['key'] = target4; + globalMap2[target5] = 'value'; + + // The weak reference will be traced first in DFS, but the retaining path + // include the strong reference. + weakReachable.x = WeakReference<_TestClass>(target7!); + weakReachable.y = target7; + + weakUnreachable.x = WeakReference<_TestClass>(target8!); + weakUnreachable.y = null; +} + +@pragma("vm:entry-point") // Prevent obfuscation +getGlobalObject() => globalObject; + +@pragma("vm:entry-point") // Prevent obfuscation +_TestClass? takeTarget1() { + var tmp = target1; + target1 = null; + return tmp; +} + +@pragma("vm:entry-point") // Prevent obfuscation +_TestClass? takeTarget2() { + var tmp = target2; + target2 = null; + return tmp; +} + +@pragma("vm:entry-point") // Prevent obfuscation +_TestClass? takeTarget3() { + var tmp = target3; + target3 = null; + return tmp; +} + +@pragma("vm:entry-point") // Prevent obfuscation +_TestClass? takeTarget4() { + var tmp = target4; + target4 = null; + return tmp; +} + +@pragma("vm:entry-point") // Prevent obfuscation +_TestClass? takeTarget5() { + var tmp = target5; + target5 = null; + return tmp; +} + +@pragma("vm:entry-point") // Prevent obfuscation +_TestClass? takeExpandoTarget() { + var tmp = target6; + target6 = null; + var tmp2 = _TestClass(); + expando[tmp!] = tmp2; + return tmp2; +} + +@pragma("vm:entry-point") // Prevent obfuscation +_TestClass? takeWeakReachableTarget() { + var tmp = target7; + target7 = null; + return tmp; +} + +@pragma("vm:entry-point") // Prevent obfuscation +_TestClass? takeWeakUnreachableTarget() { + var tmp = target8; + target8 = null; + return tmp; +} + +@pragma("vm:entry-point") // Prevent obfuscation +bool getTrue() => true; + +Future invoke(String selector) async { + return await rootService.invoke( + isolateId, + isolate.rootLib!.id!, + selector, + [], + ) as InstanceRef; +} + +late final VmService rootService; +late final Isolate isolate; +late final String isolateId; + +final tests = [ + // Initialization + (VmService service, IsolateRef isolateRef) async { + isolateId = isolateRef.id!; + rootService = service; + isolate = await service.getIsolate(isolateId); + }, + // simple path + (VmService service, IsolateRef isolateRef) async { + final obj = await invoke('getGlobalObject'); + final result = await service.getRetainingPath(isolateId, obj.id!, 100); + expect(result.gcRootType, 'user global'); + expect(result.elements!.length, 2); + expect((result.elements![1].value! as FieldRef).name, 'globalObject'); + }, + + (VmService service, IsolateRef isolateRef) async { + final target = await invoke('takeTarget1'); + final result = await service.getRetainingPath(isolateId, target.id!, 100); + expect(result.gcRootType, 'user global'); + final elements = result.elements!; + expect(elements.length, 3); + expect(elements[1].parentField, 'x'); + expect((elements[2].value as FieldRef).name, 'globalObject'); + }, + + (VmService service, IsolateRef isolateRef) async { + final target = await invoke('takeTarget2'); + final result = await service.getRetainingPath(isolateId, target.id!, 100); + expect(result.gcRootType, 'user global'); + final elements = result.elements!; + expect(elements.length, 3); + expect(elements[1].parentField, 'y'); + expect((elements[2].value as FieldRef).name, 'globalObject'); + }, + + (VmService service, IsolateRef isolateRef) async { + final target = await invoke('takeTarget3'); + final result = await service.getRetainingPath(isolateId, target.id!, 100); + expect(result.gcRootType, 'user global'); + final elements = result.elements!; + expect(elements.length, 3); + expect(elements[1].parentListIndex, 12); + expect((elements[2].value as FieldRef).name, 'globalList'); + }, + + (VmService service, IsolateRef isolateRef) async { + final target = await invoke('takeTarget4'); + final result = await service.getRetainingPath(isolateId, target.id!, 100); + expect(result.gcRootType, 'user global'); + final elements = result.elements!; + expect(elements.length, 3); + expect((elements[1].parentMapKey as InstanceRef).valueAsString, 'key'); + expect((elements[2].value as FieldRef).name, 'globalMap1'); + }, + + (VmService service, IsolateRef isolateRef) async { + final target = await invoke('takeTarget5'); + final result = await service.getRetainingPath(isolateId, target.id!, 100); + expect(result.gcRootType, 'user global'); + final elements = result.elements!; + expect(elements.length, 3); + expect( + (elements[1].parentMapKey as InstanceRef).classRef!.name, + '_TestClass', + ); + expect((elements[2].value as FieldRef).name, 'globalMap2'); + }, + + (VmService service, IsolateRef isolateRef) async { + // Regression test for https://github.com/dart-lang/sdk/issues/44016 + final target = await invoke('takeExpandoTarget'); + final result = await service.getRetainingPath(isolateId, target.id!, 100); + final elements = result.elements!; + expect(elements.length, 5); + expect( + (elements[1].parentMapKey as InstanceRef).classRef!.name, + '_TestClass', + ); + expect(elements[2].parentListIndex, isNotNull); + expect((elements[4].value as FieldRef).name, 'expando'); + }, + + (VmService service, IsolateRef isolateRef) async { + final target = await invoke('takeWeakReachableTarget'); + final result = await service.getRetainingPath(isolateId, target.id!, 100); + expect(result.gcRootType, 'user global'); + final elements = result.elements!; + expect(elements.length, 3); + expect(elements[1].parentField, 'y'); + expect((elements[2].value as FieldRef).name, 'weakReachable'); + }, + + (VmService service, IsolateRef isolateRef) async { + final target = await invoke('takeWeakUnreachableTarget'); + final result = await service.getRetainingPath(isolateId, target.id!, 100); + final elements = result.elements!; + expect(elements.length, 0); + }, + + // object store + (VmService service, IsolateRef isolateRef) async { + final target = await invoke('getTrue'); + final result = await service.getRetainingPath(isolateId, target.id!, 100); + expect( + result.gcRootType == 'isolate_object store' || + result.gcRootType == 'class table', + true, + ); + final elements = result.elements!; + expect(elements.length, 0); + }, +]; + +void main([args = const []]) async => runIsolateTests( + args, + tests, + 'get_retaining_path_rpc_test.dart', + testeeBefore: warmup, + ); diff --git a/pkg/vm_service/test/get_scripts_rpc_test.dart b/pkg/vm_service/test/get_scripts_rpc_test.dart new file mode 100644 index 00000000000..c3387e1b588 --- /dev/null +++ b/pkg/vm_service/test/get_scripts_rpc_test.dart @@ -0,0 +1,52 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:vm_service/vm_service.dart'; +import 'package:test/test.dart'; + +import 'common/test_helper.dart'; + +final tests = [ + (VmService service, IsolateRef isolateRef) async { + final isolateId = isolateRef.id!; + final results = await service.getScripts(isolateId); + expect(results.scripts!.length, isPositive); + }, + + (VmService service, IsolateRef isolateRef) async { + final isolateId = 'badid'; + bool caughtException = false; + try { + await service.getScripts(isolateId); + fail('Unreachable'); + } on RPCError catch (e) { + caughtException = true; + expect(e.code, RPCErrorKind.kInvalidParams.code); + expect(e.details, "getScripts: invalid 'isolateId' parameter: badid"); + } + expect(caughtException, true); + }, + + // Plausible isolate id, not found. + (VmService service, IsolateRef isolateRef) async { + final isolateId = 'isolates/9999999999'; + bool caughtException = false; + try { + await service.getScripts(isolateId); + fail('Unreachable'); + } on SentinelException catch (e) { + caughtException = true; + expect(e.callingMethod, 'getScripts'); + expect(e.sentinel.kind, SentinelKind.kCollected); + expect(e.sentinel.valueAsString, ''); + } + expect(caughtException, true); + }, +]; + +void main([args = const []]) => runIsolateTests( + args, + tests, + 'get_scripts_rpc_test.dart', + ); diff --git a/pkg/vm_service/test/get_source_report_const_coverage_lib.dart b/pkg/vm_service/test/get_source_report_const_coverage_lib.dart new file mode 100644 index 00000000000..440b9cee426 --- /dev/null +++ b/pkg/vm_service/test/get_source_report_const_coverage_lib.dart @@ -0,0 +1,12 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'get_source_report_const_coverage_test.dart'; + +void testFunction() { + const namedFoo = Foo.named3(); + const namedFoo2 = Foo.named3(); + const namedIdentical = identical(namedFoo, namedFoo2); + print('namedIdentical: $namedIdentical'); +} diff --git a/pkg/vm_service/test/get_source_report_const_coverage_test.dart b/pkg/vm_service/test/get_source_report_const_coverage_test.dart new file mode 100644 index 00000000000..ec563e4723a --- /dev/null +++ b/pkg/vm_service/test/get_source_report_const_coverage_test.dart @@ -0,0 +1,137 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:developer'; + +import 'package:vm_service/vm_service.dart'; +import 'package:test/test.dart'; + +import 'common/test_helper.dart'; +import 'common/service_test_common.dart'; + +import 'get_source_report_const_coverage_lib.dart' as lib; + +const filename = 'get_source_report_const_coverage_test'; +const expectedLinesHit = {24, 26, 30}; +const expectedLinesNotHit = {28}; + +const LINE_A = 45; + +class Foo { + final int x; + // Expect this constructor to be coverage by coverage. + const Foo([int? x]) : this.x = x ?? 42; + // Expect this constructor to be coverage by coverage too. + const Foo.named1([int? x]) : this.x = x ?? 42; + // Expect this constructor to *NOT* be coverage by coverage. + const Foo.named2([int? x]) : this.x = x ?? 42; + // Expect this constructor to be coverage by coverage too (from lib). + const Foo.named3([int? x]) : this.x = x ?? 42; +} + +void testFunction() { + const foo = Foo(); + const foo2 = Foo(); + const fooIdentical = identical(foo, foo2); + print(fooIdentical); + + const namedFoo = Foo.named1(); + const namedFoo2 = Foo.named1(); + // ignore: unused_local_variable + const namedIdentical = identical(namedFoo, namedFoo2); + print(fooIdentical); + + debugger(); // LINE_A + + // That this is called after (or at all) is not relevent for the code + // coverage of constants. + lib.testFunction(); + + print('Done'); +} + +final tests = [ + hasStoppedAtBreakpoint, + stoppedAtLine(LINE_A), + (VmService service, IsolateRef isolateRef) async { + final isolateId = isolateRef.id!; + final isolate = await service.getIsolate(isolateId); + final rootLibId = isolate.rootLib!.id!; + final rootLib = await service.getObject(isolateId, rootLibId) as Library; + Script? foundScript; + for (ScriptRef script in rootLib.scripts!) { + if (script.uri!.contains(filename)) { + foundScript = await service.getObject(isolateId, script.id!) as Script; + break; + } + } + + if (foundScript == null) { + fail('Failed to find script'); + } + + Set hits; + { + // Get report for everything; then collect for this library. + final coverage = await service.getSourceReport( + isolateId, + [SourceReportKind.kCoverage], + ); + hits = getHitsFor(coverage, filename); + final lines = {}; + for (int hit in hits) { + // We expect every hit to be translatable to line + // (i.e. tokenToLine to return non-null). + final line = foundScript.getLineNumberFromTokenPos(hit); + lines.add(line!); + } + print('Token position hits: $hits --- line hits: $lines'); + expect(lines.intersection(expectedLinesHit), expectedLinesHit); + expect(lines.intersection(expectedLinesNotHit), isEmpty); + } + { + // Now get report for the this file only. + final coverage = await service.getSourceReport( + isolateId, + [SourceReportKind.kCoverage], + scriptId: foundScript.id!, + ); + final localHits = getHitsFor(coverage, filename); + expect(localHits.length, hits.length); + expect(hits.toList()..sort(), localHits.toList()..sort()); + print(localHits); + } + }, +]; + +Set getHitsFor(SourceReport coverage, String uriContains) { + final scripts = coverage.scripts!; + final scriptIdsWanted = {}; + for (int i = 0; i < scripts.length; i++) { + final script = scripts[i]; + final scriptUri = script.uri!; + if (scriptUri.contains(uriContains)) { + scriptIdsWanted.add(i); + } + } + final ranges = coverage.ranges!; + final hits = {}; + for (final range in ranges) { + if (scriptIdsWanted.contains(range.scriptIndex!)) { + if (range.coverage != null) { + for (int hit in range.coverage!.hits!) { + hits.add(hit); + } + } + } + } + return hits; +} + +void main([args = const []]) => runIsolateTests( + args, + tests, + 'get_source_report_const_coverage_test.dart', + testeeConcurrent: testFunction, + ); diff --git a/pkg/vm_service/test/get_source_report_test.dart b/pkg/vm_service/test/get_source_report_test.dart new file mode 100644 index 00000000000..ebd6fdcd162 --- /dev/null +++ b/pkg/vm_service/test/get_source_report_test.dart @@ -0,0 +1,213 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:developer'; + +import 'package:vm_service/vm_service.dart'; +import 'package:test/test.dart'; + +import 'common/test_helper.dart'; +import 'common/service_test_common.dart'; + +const LINE_A = 25; +const LINE_B = 46; + +int globalVar = 100; + +class MyClass { + static void myFunction(int value) { + if (value < 0) { + print('negative'); + } else { + print('positive'); + } + debugger(); // LINE_A + } + + static void otherFunction(int value) { + if (value < 0) { + print('otherFunction <'); + } else { + print('otherFunction >='); + } + } +} + +void testFunction() { + MyClass.otherFunction(-100); + MyClass.myFunction(10000); +} + +class MyConstClass { + const MyConstClass(); + static const MyConstClass instance = null ?? const MyConstClass(); + + void foo() { + debugger(); // LINE_B + } +} + +void testFunction2() { + MyConstClass.instance.foo(); +} + +bool allRangesCompiled(SourceReport coverage) { + for (final range in coverage.ranges!) { + if (!range.compiled!) { + return false; + } + } + return true; +} + +final tests = [ + hasStoppedAtBreakpoint, + stoppedAtLine(LINE_A), + (VmService service, IsolateRef isolateRef) async { + final isolateId = isolateRef.id!; + final stack = await service.getStack(isolateId); + final func = stack.frames!.first.function!; + final scriptId = func.location!.script!.id!; + + final expectedRange = SourceReportRange( + scriptIndex: 0, + startPos: 478, + endPos: 632, + compiled: true, + coverage: SourceReportCoverage( + hits: const [478, 528, 579, 608], + misses: [541], + ), + ); + + // Full script + var coverage = await service.getSourceReport( + isolateId, + [SourceReportKind.kCoverage], + scriptId: scriptId, + ); + + var ranges = coverage.ranges!; + expect(ranges.length, greaterThanOrEqualTo(10)); + // TODO(bkonyi): implement operator== properly. + expect(ranges[0].toJson(), expectedRange.toJson()); + + var scripts = coverage.scripts!; + expect(coverage.scripts!.length, 1); + expect(scripts[0].uri!, endsWith('get_source_report_test.dart')); + expect(allRangesCompiled(coverage), false); + + // Force compilation. + coverage = await service.getSourceReport( + isolateId, + [SourceReportKind.kCoverage], + scriptId: scriptId, + forceCompile: true, + ); + ranges = coverage.ranges!; + expect(ranges.length, greaterThanOrEqualTo(10)); + expect(allRangesCompiled(coverage), isTrue); + + // One function + coverage = await service.getSourceReport( + isolateId, + [SourceReportKind.kCoverage], + scriptId: scriptId, + tokenPos: func.location!.tokenPos!, + endTokenPos: func.location!.endTokenPos!, + ); + ranges = coverage.ranges!; + scripts = coverage.scripts!; + expect(ranges.length, 1); + // TODO(bkonyi): implement operator== properly. + expect(ranges[0].toJson(), expectedRange.toJson()); + expect(scripts.length, 1); + expect(scripts[0].uri!, endsWith('get_source_report_test.dart')); + + // Full isolate + coverage = await service.getSourceReport( + isolateId, + [SourceReportKind.kCoverage], + ); + ranges = coverage.ranges!; + scripts = coverage.scripts!; + expect(ranges.length, greaterThan(1)); + expect(scripts.length, greaterThan(1)); + + // Full isolate + coverage = await service.getSourceReport( + isolateId, + [SourceReportKind.kCoverage], + forceCompile: true, + ); + ranges = coverage.ranges!; + scripts = coverage.scripts!; + expect(ranges.length, greaterThan(1)); + expect(scripts.length, greaterThan(1)); + + // Multiple reports (make sure enum list parameter parsing works). + coverage = await service.getSourceReport( + isolateId, + [ + SourceReportKind.kCoverage, + SourceReportKind.kPossibleBreakpoints, + '_CallSites', + ], + scriptId: scriptId, + tokenPos: func.location!.tokenPos!, + endTokenPos: func.location!.endTokenPos!, + ); + ranges = coverage.ranges!; + expect(ranges.length, 1); + final range = ranges[0]; + expect(coverage.json!['ranges'][0].containsKey('callSites'), true); + expect(range.coverage, isNotNull); + expect(range.possibleBreakpoints, isNotNull); + + // missing scriptId with tokenPos. + bool caughtException = false; + try { + await service.getSourceReport( + isolateId, + [SourceReportKind.kCoverage], + tokenPos: func.location!.tokenPos!, + ); + fail('Unreachable'); + } on RPCError catch (e) { + caughtException = true; + expect(e.code, RPCErrorKind.kInvalidParams.code); + expect( + e.details, + "getSourceReport: the 'tokenPos' parameter requires the " + "\'scriptId\' parameter"); + } + expect(caughtException, true); + + // missing scriptId with endTokenPos. + caughtException = false; + try { + await service.getSourceReport( + isolateId, + [SourceReportKind.kCoverage], + endTokenPos: func.location!.endTokenPos!, + ); + fail('Unreachable'); + } on RPCError catch (e) { + caughtException = true; + expect(e.code, RPCErrorKind.kInvalidParams.code); + expect( + e.details, + "getSourceReport: the 'endTokenPos' parameter requires the " + "\'scriptId\' parameter"); + } + expect(caughtException, true); + }, +]; + +void main([args = const []]) => runIsolateTests( + args, + tests, + 'get_source_report_test.dart', + testeeConcurrent: testFunction, + ); diff --git a/pkg/vm_service/test/get_source_report_with_mixin_lib1.dart b/pkg/vm_service/test/get_source_report_with_mixin_lib1.dart new file mode 100644 index 00000000000..3830bae0a8e --- /dev/null +++ b/pkg/vm_service/test/get_source_report_with_mixin_lib1.dart @@ -0,0 +1,15 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +mixin A { + void foo() { + print('foo'); + } +} + +mixin B { + void bar() { + print('bar'); + } +} diff --git a/pkg/vm_service/test/get_source_report_with_mixin_lib2.dart b/pkg/vm_service/test/get_source_report_with_mixin_lib2.dart new file mode 100644 index 00000000000..9a625c418da --- /dev/null +++ b/pkg/vm_service/test/get_source_report_with_mixin_lib2.dart @@ -0,0 +1,7 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'get_source_report_with_mixin_lib1.dart'; + +class Test1 with A {} diff --git a/pkg/vm_service/test/get_source_report_with_mixin_lib3.dart b/pkg/vm_service/test/get_source_report_with_mixin_lib3.dart new file mode 100644 index 00000000000..5f0de694561 --- /dev/null +++ b/pkg/vm_service/test/get_source_report_with_mixin_lib3.dart @@ -0,0 +1,7 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'get_source_report_with_mixin_lib1.dart'; + +class Test2 with B {} diff --git a/pkg/vm_service/test/get_source_report_with_mixin_test.dart b/pkg/vm_service/test/get_source_report_with_mixin_test.dart new file mode 100644 index 00000000000..11c22f4c327 --- /dev/null +++ b/pkg/vm_service/test/get_source_report_with_mixin_test.dart @@ -0,0 +1,103 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:developer'; + +import 'package:vm_service/vm_service.dart'; +import 'package:test/test.dart'; + +import 'common/test_helper.dart'; +import 'common/service_test_common.dart'; + +import 'get_source_report_with_mixin_lib2.dart'; +import 'get_source_report_with_mixin_lib3.dart'; + +const LINE_A = 26; + +const lib1Filename = 'get_source_report_with_mixin_lib1'; +const lib3Filename = 'get_source_report_with_mixin_lib3'; + +void testFunction() { + final Test1 test1 = new Test1(); + test1.foo(); + final Test2 test2 = new Test2(); + test2.bar(); + debugger(); // LINE_A + print('done'); +} + +final tests = [ + hasStoppedAtBreakpoint, + stoppedAtLine(LINE_A), + (VmService service, IsolateRef isolateRef) async { + final isolateId = isolateRef.id!; + final scripts = await service.getScripts(isolateId); + ScriptRef? foundScript; + for (ScriptRef script in scripts.scripts!) { + if (script.uri!.contains(lib1Filename)) { + foundScript = script; + break; + } + } + + if (foundScript == null) { + fail('Failed to find script'); + } + + Set hits; + { + // Get report for everything; then collect for lib1. + final coverage = await service.getSourceReport( + isolateId, + [SourceReportKind.kCoverage], + ); + hits = getHitsForLib1(coverage, lib1Filename); + expect(hits.length, greaterThanOrEqualTo(2)); + print(hits); + } + { + // Now get report for the lib1 only. + final coverage = await service.getSourceReport( + isolateId, + [SourceReportKind.kCoverage], + scriptId: foundScript.id!, + ); + final localHits = getHitsForLib1(coverage, lib1Filename); + expect(localHits.length, hits.length); + expect(hits.toList()..sort(), localHits.toList()..sort()); + print(localHits); + } + }, +]; + +Set getHitsForLib1(SourceReport coverage, String uriContains) { + final scripts = coverage.scripts!; + final scriptIdsWanted = {}; + for (int i = 0; i < scripts.length; i++) { + final script = scripts[i]; + final scriptUri = script.uri!; + if (scriptUri.contains(uriContains)) { + scriptIdsWanted.add(i); + } + } + final ranges = coverage.ranges!; + final hits = {}; + for (final range in ranges) { + if (scriptIdsWanted.contains(range.scriptIndex!)) { + if (range.coverage != null) { + for (final hit in range.coverage!.hits!) { + hits.add(hit); + } + } + } + } + return hits; +} + +void main([args = const []]) => runIsolateTests( + args, + tests, + 'get_source_report_with_mixin_test.dart', + testeeConcurrent: testFunction, + ); diff --git a/pkg/vm_service/test/get_stack_limit_rpc_test.dart b/pkg/vm_service/test/get_stack_limit_rpc_test.dart new file mode 100644 index 00000000000..eb45569e9d9 --- /dev/null +++ b/pkg/vm_service/test/get_stack_limit_rpc_test.dart @@ -0,0 +1,134 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:developer'; + +import 'package:vm_service/vm_service.dart'; +import 'package:test/test.dart'; + +import 'common/service_test_common.dart'; +import 'common/test_helper.dart'; + +Future bar(int depth) async { + if (depth == 21) { + debugger(); + return; + } + await foo(depth + 1); +} + +Future foo(int depth) async { + if (depth == 10) { + // Yield once to force the rest to run async. + await 0; + } + await bar(depth + 1); +} + +Future testMain() async { + await foo(0); +} + +void verifyStack(List frames, List expectedNames) { + for (int i = 0; i < frames.length && i < expectedNames.length; ++i) { + expect(frames[i].function!.name, expectedNames[i]); + } +} + +final tests = [ + hasStoppedAtBreakpoint, + // Get stack + (VmService service, IsolateRef isolateRef) async { + final isolateId = isolateRef.id!; + var stack = await service.getStack(isolateId); + + // Sanity check. + var frames = stack.frames!; + var asyncFrames = stack.asyncCausalFrames!; + expect(frames.length, greaterThanOrEqualTo(12)); + expect(asyncFrames.length, greaterThan(frames.length)); + expect(stack.truncated, false); + verifyStack(frames, [ + 'bar', + 'foo', + 'bar', + 'foo', + 'bar', + 'foo', + 'bar', + 'foo', + 'bar', + 'foo', + 'bar', + 'foo' + ]); + + final fullStackLength = frames.length; + + // Try a limit > actual stack depth and expect to get the full stack with + // truncated async stacks. + stack = await service.getStack(isolateId, limit: fullStackLength + 1); + frames = stack.frames!; + asyncFrames = stack.asyncCausalFrames!; + + expect(frames.length, fullStackLength); + expect(asyncFrames.length, fullStackLength + 1); + expect(stack.truncated, true); + verifyStack(frames, [ + 'bar', + 'foo', + 'bar', + 'foo', + 'bar', + 'foo', + 'bar', + 'foo', + 'bar', + 'foo', + 'bar', + 'foo' + ]); + + // Try a limit < actual stack depth and expect to get a stack of depth + // 'limit'. + stack = await service.getStack(isolateId, limit: 10); + frames = stack.frames!; + asyncFrames = stack.asyncCausalFrames!; + + expect(frames.length, 10); + expect(asyncFrames.length, 10); + expect(stack.truncated, true); + verifyStack(frames, [ + 'bar', + 'foo', + 'bar', + 'foo', + 'bar', + 'foo', + 'bar', + 'foo', + 'bar', + 'foo', + ]); + }, + // Invalid limit + (VmService service, IsolateRef isolateRef) async { + bool caughtException = false; + try { + await service.getStack(isolateRef.id!, limit: -1); + fail('Invalid parameter of -1 successful'); + } on RPCError { + // Expected. + caughtException = true; + } + expect(caughtException, true); + } +]; + +void main([args = const []]) => runIsolateTests( + args, + tests, + 'get_stack_limit_rpc_test.dart', + testeeConcurrent: testMain, + ); diff --git a/pkg/vm_service/test/get_stack_rpc_test.dart b/pkg/vm_service/test/get_stack_rpc_test.dart new file mode 100644 index 00000000000..5b3089d9b44 --- /dev/null +++ b/pkg/vm_service/test/get_stack_rpc_test.dart @@ -0,0 +1,98 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'dart:isolate'; +import 'dart:developer'; + +import 'package:vm_service/vm_service.dart'; +import 'package:test/test.dart'; + +import 'common/service_test_common.dart'; +import 'common/test_helper.dart'; + +const LINE_A = 25; + +int counter = 0; +final port = RawReceivePort(msgHandler); + +// This name is used in a test below. +void msgHandler(_) {} + +void periodicTask(_) { + port.sendPort.send(34); + debugger(message: 'fo', when: true); + counter++; + if (counter % 300 == 0) { + print('counter = $counter'); + } +} + +void startTimer() { + new Timer.periodic(const Duration(milliseconds: 10), periodicTask); +} + +final tests = [ + hasStoppedAtBreakpoint, + stoppedAtLine(LINE_A), + // Get stack + (VmService service, IsolateRef isolateRef) async { + final isolateId = isolateRef.id!; + final stack = await service.getStack(isolateId); + + // Sanity check. + final frames = stack.frames!; + expect(frames.length, greaterThanOrEqualTo(1)); + final scriptId = frames[0].location!.script!.id!; + final script = await service.getObject(isolateId, scriptId) as Script; + expect( + script.getLineNumberFromTokenPos(frames[0].location!.tokenPos!), + LINE_A, + ); + + // Iterate over frames. + int frameDepth = 0; + for (var frame in frames) { + print('checking frame $frameDepth'); + expect(frame.index, equals(frameDepth++)); + expect(frame.code, isNotNull); + expect(frame.function, isNotNull); + expect(frame.location, isNotNull); + } + + // Sanity check. + final messages = stack.messages!; + expect(messages.length, greaterThanOrEqualTo(1)); + + // Iterate over messages. + int messageDepth = 0; + // objectId of message to be handled by msgHandler. + var msgHandlerObjectId; + for (final message in messages) { + print('checking message $messageDepth'); + expect(message.index, messageDepth++); + expect(message.size, greaterThanOrEqualTo(0)); + expect(message.handler, isNotNull); + expect(message.location, isNotNull); + if (message.handler!.name!.contains('msgHandler')) { + msgHandlerObjectId = message.messageObjectId; + } + } + expect(msgHandlerObjectId, isNotNull); + + // Get object. + final object = await service.getObject( + isolateId, + msgHandlerObjectId, + ) as Instance; + expect(object.valueAsString, '34'); + } +]; + +void main([args = const []]) => runIsolateTests( + args, + tests, + 'get_stack_rpc_test.dart', + testeeBefore: startTimer, + ); diff --git a/pkg/vm_service/test/get_user_level_retaining_path_rpc_test.dart b/pkg/vm_service/test/get_user_level_retaining_path_rpc_test.dart new file mode 100644 index 00000000000..cbac9fc8dbb --- /dev/null +++ b/pkg/vm_service/test/get_user_level_retaining_path_rpc_test.dart @@ -0,0 +1,80 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:vm_service/vm_service.dart'; +import 'package:test/test.dart'; + +import 'common/test_helper.dart'; + +@pragma("vm:entry-point") // Prevent obfuscation +class _TestConst { + const _TestConst(); +} + +_TopLevelClosure() {} + +@pragma("vm:entry-point") // Prevent obfuscation +var x; +@pragma("vm:entry-point") // Prevent obfuscation +var fn; + +void warmup() { + x = const _TestConst(); + fn = _TopLevelClosure; +} + +@pragma("vm:entry-point") // Prevent obfuscation +getX() => x; + +@pragma("vm:entry-point") // Prevent obfuscation +getFn() => fn; + +Future invoke(String selector) async { + return await rootService.invoke( + isolateId, + isolate.rootLib!.id!, + selector, + [], + ) as InstanceRef; +} + +late final VmService rootService; +late final Isolate isolate; +late final String isolateId; + +final tests = [ + (VmService service, IsolateRef isolateRef) async { + isolateId = isolateRef.id!; + rootService = service; + isolate = await service.getIsolate(isolateId); + }, + // Expect a simple path through variable x instead of long path filled + // with VM objects + (VmService service, IsolateRef isolateRef) async { + final target = await invoke('getX'); + final result = await service.getRetainingPath(isolateId, target.id!, 100); + final elements = result.elements!; + expect(elements.length, 2); + expect((elements[0].value as InstanceRef).classRef!.name, '_TestConst'); + expect((elements[1].value as FieldRef).name, 'x'); + }, + + // Expect a simple path through variable fn instead of long path filled + // with VM objects + (VmService service, IsolateRef isolateRef) async { + final target = await invoke('getFn'); + final result = await service.getRetainingPath(isolateId, target.id!, 100); + final elements = result.elements!; + expect(elements.length, 2); + expect((elements[0].value as InstanceRef).classRef!.name, '_Closure'); + expect((elements[1].value as FieldRef).name, 'fn'); + } +]; + +void main([args = const []]) => runIsolateTests( + args, + tests, + 'get_user_level_retaining_path_rpc_test.dart', + testeeBefore: warmup, + ); diff --git a/pkg/vm_service/test/get_vm_rpc_test.dart b/pkg/vm_service/test/get_vm_rpc_test.dart new file mode 100644 index 00000000000..2ef892e109c --- /dev/null +++ b/pkg/vm_service/test/get_vm_rpc_test.dart @@ -0,0 +1,38 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +// VMOptions=--vm-name=Walter + +import 'dart:io'; + +import 'package:vm_service/vm_service.dart'; +import 'package:test/test.dart'; + +import 'common/test_helper.dart'; + +final tests = [ + (VmService service) async { + final vm = await service.getVM(); + expect(vm.name, equals('Walter')); + expect(vm.architectureBits, isPositive); + expect(vm.targetCPU, isA()); + expect(vm.hostCPU, isA()); + expect(vm.operatingSystem, Platform.operatingSystem); + expect(vm.version, isA()); + expect(vm.pid, isA()); + expect(vm.startTime, isPositive); + final isolates = vm.isolates!; + expect(isolates.length, isPositive); + expect(isolates[0].id, startsWith('isolates/')); + expect(isolates[0].isolateGroupId, startsWith('isolateGroups/')); + final isolateGroups = vm.isolateGroups!; + expect(isolateGroups.length, isPositive); + expect(isolateGroups[0].id, startsWith('isolateGroups/')); + }, +]; + +void main([args = const []]) => runVMTests( + args, + tests, + 'get_vm_rpc_test.dart', + ); diff --git a/pkg/vm_service/test/get_vm_timeline_micros_rpc_test.dart b/pkg/vm_service/test/get_vm_timeline_micros_rpc_test.dart new file mode 100644 index 00000000000..35b48cd3874 --- /dev/null +++ b/pkg/vm_service/test/get_vm_timeline_micros_rpc_test.dart @@ -0,0 +1,21 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:vm_service/vm_service.dart'; +import 'package:test/test.dart'; + +import 'common/test_helper.dart'; + +final tests = [ + (VmService service) async { + final result = await service.getVMTimelineMicros(); + expect(result.timestamp, isPositive); + }, +]; + +void main([args = const []]) => runVMTests( + args, + tests, + 'get_vm_timeline_micros_rpc_test.dart', + ); diff --git a/pkg/vm_service/test/allocations_test.dart b/pkg/vm_service/test/private_rpcs/allocations_test.dart similarity index 91% rename from pkg/vm_service/test/allocations_test.dart rename to pkg/vm_service/test/private_rpcs/allocations_test.dart index 9e38e57fc44..344e447544b 100644 --- a/pkg/vm_service/test/allocations_test.dart +++ b/pkg/vm_service/test/private_rpcs/allocations_test.dart @@ -1,11 +1,11 @@ -// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. import 'package:test/test.dart'; import 'package:vm_service/vm_service.dart'; -import 'common/test_helper.dart'; +import '../common/test_helper.dart'; class Foo {} diff --git a/pkg/vm_service/test/breakpoint_gc_test.dart b/pkg/vm_service/test/private_rpcs/breakpoint_gc_test.dart similarity index 94% rename from pkg/vm_service/test/breakpoint_gc_test.dart rename to pkg/vm_service/test/private_rpcs/breakpoint_gc_test.dart index a78bf701683..b64d82858ec 100644 --- a/pkg/vm_service/test/breakpoint_gc_test.dart +++ b/pkg/vm_service/test/private_rpcs/breakpoint_gc_test.dart @@ -4,8 +4,8 @@ import 'package:vm_service/vm_service.dart'; -import 'common/test_helper.dart'; -import 'common/service_test_common.dart'; +import '../common/test_helper.dart'; +import '../common/service_test_common.dart'; const int LINE_A = 18; const int LINE_B = 21; diff --git a/pkg/vm_service/test/echo_test.dart b/pkg/vm_service/test/private_rpcs/echo_test.dart similarity index 95% rename from pkg/vm_service/test/echo_test.dart rename to pkg/vm_service/test/private_rpcs/echo_test.dart index d21a524ae20..eee7e4e92b1 100644 --- a/pkg/vm_service/test/echo_test.dart +++ b/pkg/vm_service/test/private_rpcs/echo_test.dart @@ -1,11 +1,11 @@ -// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. import 'dart:async'; import 'package:test/test.dart'; import 'package:vm_service/src/vm_service.dart'; -import 'common/test_helper.dart'; +import '../common/test_helper.dart'; class EchoResponse extends Response { static EchoResponse? parse(Map? json) => diff --git a/pkg/vm_service/test/private_rpcs/get_heap_map_rpc_test.dart b/pkg/vm_service/test/private_rpcs/get_heap_map_rpc_test.dart new file mode 100644 index 00000000000..dc8bc2eabcc --- /dev/null +++ b/pkg/vm_service/test/private_rpcs/get_heap_map_rpc_test.dart @@ -0,0 +1,137 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:vm_service/src/vm_service.dart'; +import 'package:test/test.dart'; + +import '../common/test_helper.dart'; + +class Page { + Page.fromJson(Map json) + : objectStart = json['objectStart'], + objects = json['objects'].cast(); + + final String objectStart; + final List objects; +} + +class HeapMap extends Response { + static HeapMap? parse(Map? json) => + json == null ? null : HeapMap._fromJson(json); + + HeapMap._fromJson(Map json) + : freeClassId = json['freeClassId'], + unitSizeBytes = json['unitSizeBytes'], + pageSizeBytes = json['pageSizeBytes'], + classList = ClassList.parse(json['classList'])!, + pages = json['pages'].map((e) => Page.fromJson(e)).toList(); + + @override + String get type => 'HeapMap'; + + final int freeClassId; + final int unitSizeBytes; + final int pageSizeBytes; + final ClassList classList; + final List pages; +} + +enum GCType { + none, + scavenge, + markSweep, + markCompact; + + @override + String toString() { + switch (this) { + case GCType.none: + return ''; + case GCType.scavenge: + return 'scavenge'; + case GCType.markCompact: + return 'mark-compact'; + case GCType.markSweep: + return 'mark-sweep'; + } + } +} + +extension on VmService { + Future getHeapMap(String isolateId, + {GCType gc = GCType.none}) async => + await callMethod('_getHeapMap', isolateId: isolateId, args: { + if (gc != GCType.none) 'gc': gc.toString(), + }) as HeapMap; +} + +final tests = [ + (VmService service, IsolateRef isolateRef) async { + // Setup + addTypeFactory('HeapMap', HeapMap.parse); + }, + (VmService service, IsolateRef isolateRef) async { + final isolateId = isolateRef.id!; + final result = await service.getHeapMap(isolateId); + expect(result.freeClassId, isPositive); + expect(result.unitSizeBytes, isPositive); + expect(result.pageSizeBytes, isPositive); + expect(result.classList.classes, isNotNull); + expect(result.pages, isNotEmpty); + expect(result.pages[0].objectStart, isNotEmpty); + expect(result.pages[0].objects, isNotEmpty); + expect(result.pages[0].objects[0], isPositive); + }, + (VmService service, IsolateRef isolateRef) async { + final isolateId = isolateRef.id!; + final result = await service.getHeapMap( + isolateId, + gc: GCType.markCompact, + ); + expect(result.freeClassId, isPositive); + expect(result.unitSizeBytes, isPositive); + expect(result.pageSizeBytes, isPositive); + expect(result.classList.classes, isNotNull); + expect(result.pages, isNotEmpty); + expect(result.pages[0].objectStart, isNotEmpty); + expect(result.pages[0].objects, isNotEmpty); + expect(result.pages[0].objects[0], isPositive); + }, + (VmService service, IsolateRef isolateRef) async { + final isolateId = isolateRef.id!; + final result = await service.getHeapMap( + isolateId, + gc: GCType.markSweep, + ); + expect(result.freeClassId, isPositive); + expect(result.unitSizeBytes, isPositive); + expect(result.pageSizeBytes, isPositive); + expect(result.classList.classes, isNotNull); + expect(result.pages, isNotEmpty); + expect(result.pages[0].objectStart, isNotEmpty); + expect(result.pages[0].objects, isNotEmpty); + expect(result.pages[0].objects[0], isPositive); + }, + (VmService service, IsolateRef isolateRef) async { + final isolateId = isolateRef.id!; + final result = await service.getHeapMap( + isolateId, + gc: GCType.scavenge, + ); + expect(result.freeClassId, isPositive); + expect(result.unitSizeBytes, isPositive); + expect(result.pageSizeBytes, isPositive); + expect(result.classList.classes, isNotNull); + expect(result.pages, isNotEmpty); + expect(result.pages[0].objectStart, isNotEmpty); + expect(result.pages[0].objects, isNotEmpty); + expect(result.pages[0].objects[0], isPositive); + }, +]; + +void main([args = const []]) => runIsolateTests( + args, + tests, + 'get_heap_map_rpc_test.dart', + ); diff --git a/pkg/vm_service/test/get_implementation_fields_rpc_test.dart b/pkg/vm_service/test/private_rpcs/get_implementation_fields_rpc_test.dart similarity index 96% rename from pkg/vm_service/test/get_implementation_fields_rpc_test.dart rename to pkg/vm_service/test/private_rpcs/get_implementation_fields_rpc_test.dart index e7b719f57c3..a06aea71117 100644 --- a/pkg/vm_service/test/get_implementation_fields_rpc_test.dart +++ b/pkg/vm_service/test/private_rpcs/get_implementation_fields_rpc_test.dart @@ -5,7 +5,7 @@ import 'package:test/test.dart'; import 'package:vm_service/vm_service.dart'; -import 'common/test_helper.dart'; +import '../common/test_helper.dart'; Future getImplementationFields( VmService service, String isolateId, String objectId) async { diff --git a/pkg/vm_service/test/private_rpcs/get_object_store_rpc_test.dart b/pkg/vm_service/test/private_rpcs/get_object_store_rpc_test.dart new file mode 100644 index 00000000000..49bfd1442c1 --- /dev/null +++ b/pkg/vm_service/test/private_rpcs/get_object_store_rpc_test.dart @@ -0,0 +1,67 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:vm_service/vm_service.dart'; +import 'package:test/test.dart'; + +import '../common/test_helper.dart'; + +void testeeMain() {} + +// Pulled from DevTools. +class ObjectStore { + const ObjectStore({ + required this.fields, + }); + + static ObjectStore? parse(Map? json) { + if (json?['type'] != '_ObjectStore') { + return null; + } + final rawFields = json!['fields']! as Map; + return ObjectStore( + fields: rawFields.map((key, value) { + return MapEntry( + key, + createServiceObject(value, ['InstanceRef']) as ObjRef, + ); + }), + ); + } + + final Map fields; +} + +extension on VmService { + Future getObjectStore(String isolateId) async { + final result = await callMethod('_getObjectStore', isolateId: isolateId); + return ObjectStore.parse(result.json!)!; + } +} + +final tests = [ + // Get object_store. + (VmService service, IsolateRef isolateRef) async { + final isolateId = isolateRef.id!; + final objectStore = await service.getObjectStore(isolateId); + + // Sanity check. + expect(objectStore.fields, isNotEmpty); + + // Checking Closures. + final entry = objectStore.fields.keys.singleWhere( + (e) => e == 'closure_functions_', + ); + expect(entry, isNotNull); + final value = objectStore.fields[entry]! as InstanceRef; + expect(value.kind, InstanceKind.kList); + } +]; + +void main([args = const []]) => runIsolateTestsSynchronous( + args, + tests, + 'get_object_store_rpc_test.dart', + testeeBefore: testeeMain, + ); diff --git a/pkg/vm_service/test/private_rpcs/get_ports_rpc_test.dart b/pkg/vm_service/test/private_rpcs/get_ports_rpc_test.dart new file mode 100644 index 00000000000..6fa46c291cc --- /dev/null +++ b/pkg/vm_service/test/private_rpcs/get_ports_rpc_test.dart @@ -0,0 +1,61 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:isolate' hide Isolate; +import 'package:vm_service/vm_service.dart'; +import 'package:test/test.dart'; + +import '../common/test_helper.dart'; + +late final RawReceivePort port1; +late final RawReceivePort port2; + +void warmup() { + port1 = RawReceivePort(null); + port2 = RawReceivePort((_) {}); +} + +int countHandlerMatches( + List> ports, + bool Function(InstanceRef) matcher, +) { + int matches = 0; + for (final port in ports) { + if (matcher(InstanceRef.parse(port['handler'])!)) { + matches++; + } + } + return matches; +} + +bool nullMatcher(InstanceRef handler) { + return handler.kind == InstanceKind.kNull; +} + +bool closureMatcher(InstanceRef handler) { + return handler.kind == InstanceKind.kClosure; +} + +final tests = [ + (VmService service, IsolateRef isolateRef) async { + final isolateId = isolateRef.id!; + final result = + (await service.callMethod('_getPorts', isolateId: isolateId)).json!; + expect(result['type'], equals('_Ports')); + expect(result['ports'], isList); + final ports = result['ports'].cast>(); + // There are at least two ports: the two created in warm up. Some OSes + // will have other ports open but we do not try and test for these. + expect(ports.length, greaterThanOrEqualTo(2)); + expect(countHandlerMatches(ports, nullMatcher), greaterThanOrEqualTo(1)); + expect(countHandlerMatches(ports, closureMatcher), greaterThanOrEqualTo(1)); + }, +]; + +void main([args = const []]) => runIsolateTests( + args, + tests, + 'get_ports_rpc_test.dart', + testeeBefore: warmup, + ); diff --git a/pkg/vm_service/test/private_rpcs/get_retained_size_rpc_test.dart b/pkg/vm_service/test/private_rpcs/get_retained_size_rpc_test.dart new file mode 100644 index 00000000000..cc616ca2fa1 --- /dev/null +++ b/pkg/vm_service/test/private_rpcs/get_retained_size_rpc_test.dart @@ -0,0 +1,104 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:typed_data'; + +import 'package:vm_service/vm_service.dart'; +import 'package:test/test.dart'; + +import '../common/test_helper.dart'; + +const MB = 1 << 20; + +class _TestClass { + _TestClass(this.x, this.y); + // Make sure these fields are not removed by the tree shaker. + @pragma("vm:entry-point") + var x; + @pragma("vm:entry-point") + var y; +} + +@pragma("vm:entry-point") +var myVar; + +@pragma("vm:entry-point") +invoke1() => myVar = new _TestClass(null, null); + +@pragma("vm:entry-point") +invoke2() => myVar = new _TestClass(new _TestClass(null, null), null); + +@pragma("vm:entry-point") +invoke3() => myVar = new _TestClass(new WeakReference(new Uint8List(MB)), null); + +extension on VmService { + Future getRetainedSize( + String isolateId, + String targetId, + ) async { + return await callMethod('_getRetainedSize', isolateId: isolateId, args: { + 'targetId': targetId, + }) as InstanceRef; + } +} + +final tests = [ + (VmService service, IsolateRef isolateRef) async { + final isolateId = isolateRef.id!; + final isolate = await service.getIsolate(isolateId); + final rootLibId = isolate.rootLib!.id!; + + // One instance of _TestClass retained. + var evalResult = await service.invoke( + isolateId, + rootLibId, + 'invoke1', + [], + ) as InstanceRef; + var result = await service.getRetainedSize(isolateId, evalResult.id!); + expect(result.kind, InstanceKind.kInt); + final value1 = int.parse(result.valueAsString!); + expect(value1, isPositive); + + // Two instances of _TestClass retained. + evalResult = await service.invoke( + isolateId, + rootLibId, + 'invoke2', + [], + ) as InstanceRef; + result = await service.getRetainedSize(isolateId, evalResult.id!); + expect(result.kind, InstanceKind.kInt); + final value2 = int.parse(result.valueAsString!); + expect(value2, isPositive); + + // Size has doubled. + expect(value2, 2 * value1); + + // Get the retained size for class _TestClass. + result = await service.getRetainedSize(isolateId, evalResult.classRef!.id!); + expect(result.kind, InstanceKind.kInt); + final value3 = int.parse(result.valueAsString!); + expect(value3, isPositive); + expect(value3, value2); + + // Target of WeakReference not retained. + evalResult = await service.invoke( + isolateId, + rootLibId, + 'invoke3', + [], + ) as InstanceRef; + result = await service.getRetainedSize(isolateId, evalResult.id!); + expect(result.kind, InstanceKind.kInt); + final value4 = int.parse(result.valueAsString!); + expect(value4, lessThan(MB)); + }, +]; + +void main([args = const []]) => runIsolateTests( + args, + tests, + 'get_retained_size_rpc_test.dart', + );