From 8338fc2eaceee854a4e643dc6ef8e5d5ff03ff90 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Mon, 5 Feb 2024 22:17:27 +0000 Subject: [PATCH] [ package:vm_service ] Migrate some more tests from service/ Change-Id: I009b9ae97fb5eaeebc4bddb3813d83562302d0ec Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/349560 Reviewed-by: Derek Xu Commit-Queue: Ben Konyi --- .../test/custom_stream_listen_test.dart | 28 +++ .../test/get_supported_protocols_common.dart | 19 ++ ...get_supported_protocols_with_dds_test.dart | 19 ++ ..._supported_protocols_without_dds_test.dart | 19 ++ .../test/get_vm_timeline_rpc_test.dart | 231 ++++++++++++++++++ ...use_on_start_and_exit_with_child_test.dart | 54 ++++ 6 files changed, 370 insertions(+) create mode 100644 pkg/vm_service/test/custom_stream_listen_test.dart create mode 100644 pkg/vm_service/test/get_supported_protocols_common.dart create mode 100644 pkg/vm_service/test/get_supported_protocols_with_dds_test.dart create mode 100644 pkg/vm_service/test/get_supported_protocols_without_dds_test.dart create mode 100644 pkg/vm_service/test/get_vm_timeline_rpc_test.dart create mode 100644 pkg/vm_service/test/pause_on_start_and_exit_with_child_test.dart diff --git a/pkg/vm_service/test/custom_stream_listen_test.dart b/pkg/vm_service/test/custom_stream_listen_test.dart new file mode 100644 index 00000000000..3f0464e94c6 --- /dev/null +++ b/pkg/vm_service/test/custom_stream_listen_test.dart @@ -0,0 +1,28 @@ +// 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 = [ + // Ensure the VM service throws an exception when encountering a custom + // stream. + (VmService service) async { + try { + await service.streamListen('Foo'); + fail('Successfully listened to a custom stream, which requires DDS'); + } on RPCError catch (_) { + // Expected. + } + } +]; + +void main([args = const []]) => runVMTests( + args, + tests, + 'custom_stream_listen_test.dart', + extraArgs: ['--no-dds'], + ); diff --git a/pkg/vm_service/test/get_supported_protocols_common.dart b/pkg/vm_service/test/get_supported_protocols_common.dart new file mode 100644 index 00000000000..824a328fc49 --- /dev/null +++ b/pkg/vm_service/test/get_supported_protocols_common.dart @@ -0,0 +1,19 @@ +// 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'; + +VMTest expectedProtocolTest(List expectedProtocols) => + (VmService service) async { + final protocols = (await service.getSupportedProtocols()).protocols!; + expect(protocols.length, expectedProtocols.length); + for (final protocol in protocols) { + expect(expectedProtocols.contains(protocol.protocolName), true); + expect(protocol.minor, greaterThan(0)); + expect(protocol.major, greaterThan(0)); + } + }; diff --git a/pkg/vm_service/test/get_supported_protocols_with_dds_test.dart b/pkg/vm_service/test/get_supported_protocols_with_dds_test.dart new file mode 100644 index 00000000000..5e36483605c --- /dev/null +++ b/pkg/vm_service/test/get_supported_protocols_with_dds_test.dart @@ -0,0 +1,19 @@ +// 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 'common/test_helper.dart'; +import 'get_supported_protocols_common.dart'; + +final tests = [ + expectedProtocolTest([ + 'VM Service', + 'DDS', + ]), +]; + +void main([args = const []]) => runVMTests( + args, + tests, + 'get_supported_protocols_with_dds_test.dart', + ); diff --git a/pkg/vm_service/test/get_supported_protocols_without_dds_test.dart b/pkg/vm_service/test/get_supported_protocols_without_dds_test.dart new file mode 100644 index 00000000000..6166830656f --- /dev/null +++ b/pkg/vm_service/test/get_supported_protocols_without_dds_test.dart @@ -0,0 +1,19 @@ +// 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 'common/test_helper.dart'; +import 'get_supported_protocols_common.dart'; + +final tests = [ + expectedProtocolTest([ + 'VM Service', + ]), +]; + +void main([args = const []]) => runVMTests( + args, + tests, + 'get_supported_protocols_with_dds_test.dart', + extraArgs: ['--no-dds'], + ); diff --git a/pkg/vm_service/test/get_vm_timeline_rpc_test.dart b/pkg/vm_service/test/get_vm_timeline_rpc_test.dart new file mode 100644 index 00000000000..656d02314eb --- /dev/null +++ b/pkg/vm_service/test/get_vm_timeline_rpc_test.dart @@ -0,0 +1,231 @@ +// 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:developer' as dev; + +import 'package:test/test.dart'; +import 'package:vm_service/vm_service.dart'; + +import 'common/test_helper.dart'; + +void primeTimeline() { + dev.Timeline.startSync('apple'); + dev.Timeline.instantSync('ISYNC', arguments: {'fruit': 'banana'}); + dev.Timeline.finishSync(); + final parentTask = dev.TimelineTask.withTaskId(42); + final task = dev.TimelineTask(parent: parentTask, filterKey: 'testFilter'); + task.start('TASK1', arguments: {'task1-start-key': 'task1-start-value'}); + task.instant( + 'ITASK', + arguments: {'task1-instant-key': 'task1-instant-value'}, + ); + task.finish(arguments: {'task1-finish-key': 'task1-finish-value'}); + + final flow = dev.Flow.begin(id: 123); + dev.Timeline.startSync('peach', flow: flow); + dev.Timeline.finishSync(); + dev.Timeline.startSync('watermelon', flow: dev.Flow.step(flow.id)); + dev.Timeline.finishSync(); + dev.Timeline.startSync('pear', flow: dev.Flow.end(flow.id)); + dev.Timeline.finishSync(); +} + +List filterForDartEvents(List events) { + return events.where((event) => event.json!['cat'] == 'Dart').toList(); +} + +bool mapContains(Map map, Map submap) { + for (final key in submap.keys) { + if (map[key] != submap[key]) { + return false; + } + } + return true; +} + +bool eventsContains( + List events, + String phase, + String name, [ + Map? arguments, +]) { + for (final timelineEvent in events) { + final event = timelineEvent.json!; + if ((event['ph'] == phase) && (event['name'] == name)) { + if (arguments == null) { + return true; + } else if (mapContains(event['args'], arguments)) { + return true; + } + } + } + return false; +} + +int timeOrigin(List events) { + if (events.isEmpty) { + return 0; + } + int smallest = events.first.json!['ts']; + for (final timelineEvent in events) { + final event = timelineEvent.json!; + if (event['ts'] < smallest) { + smallest = event['ts']; + } + } + return smallest; +} + +int timeDuration(List events, int timeOrigin) { + if (events.isEmpty) { + return 0; + } + int biggestDuration = events.first.json!['ts'] - timeOrigin; + for (final timelineEvent in events) { + final event = timelineEvent.json!; + final duration = event['ts'] - timeOrigin; + if (duration > biggestDuration) { + biggestDuration = duration; + } + } + return biggestDuration; +} + +void allEventsHaveIsolateNumber(List events) { + for (final timelineEvent in events) { + final event = timelineEvent.json!; + if (event['ph'] == 'M') { + // Skip meta-data events. + continue; + } + if (event['name'] == 'Runnable' && event['ph'] == 'i') { + // Skip Runnable events which don't have an isolate. + continue; + } + if (event['cat'] == 'VM') { + // Skip VM category events which don't have an isolate. + continue; + } + if (event['cat'] == 'API') { + // Skip API category events which sometimes don't have an isolate. + continue; + } + if (event['cat'] == 'Embedder' && + (event['name'] == 'DFE::ReadScript' || + event['name'] == 'CreateIsolateGroupAndSetupHelper' || + event['name'] == 'CreateAndSetupDartDevIsolate')) { + continue; + } + final arguments = event['args']; + expect(arguments, isA()); + expect(arguments['isolateGroupId'], isA()); + if (!const ['GC', 'Compiler', 'CompilerVerbose'].contains(event['cat']) && + !const [ + 'FinishTopLevelClassLoading', + 'FinishClassLoading', + 'ProcessPendingClasses', + ].contains(event['name'])) { + expect(arguments['isolateId'], isA()); + } + } +} + +final tests = [ + (VmService service) async { + var timeline = await service.getVMTimeline(); + var traceEvents = timeline.traceEvents!; + final int numEvents = traceEvents.length; + final dartEvents = filterForDartEvents(traceEvents); + expect(dartEvents.length, greaterThanOrEqualTo(11)); + allEventsHaveIsolateNumber(dartEvents); + allEventsHaveIsolateNumber(traceEvents); + expect( + eventsContains(dartEvents, 'i', 'ISYNC', {'fruit': 'banana'}), + isTrue, + ); + expect(eventsContains(dartEvents, 'B', 'apple'), isTrue); + expect(eventsContains(dartEvents, 'E', 'apple'), isTrue); + expect( + eventsContains( + dartEvents, + 'b', + 'TASK1', + { + 'filterKey': 'testFilter', + 'task1-start-key': 'task1-start-value', + 'parentId': 42.toRadixString(16), + }, + ), + isTrue, + ); + expect( + eventsContains( + dartEvents, + 'e', + 'TASK1', + { + 'filterKey': 'testFilter', + 'task1-finish-key': 'task1-finish-value', + }, + ), + isTrue, + ); + expect( + eventsContains( + dartEvents, + 'n', + 'ITASK', + { + 'filterKey': 'testFilter', + 'task1-instant-key': 'task1-instant-value', + }, + ), + isTrue, + ); + expect(eventsContains(dartEvents, 'q', 'ITASK'), isFalse); + expect(eventsContains(dartEvents, 'B', 'peach'), isTrue); + expect(eventsContains(dartEvents, 'E', 'peach'), isTrue); + expect(eventsContains(dartEvents, 'B', 'watermelon'), isTrue); + expect(eventsContains(dartEvents, 'E', 'watermelon'), isTrue); + expect(eventsContains(dartEvents, 'B', 'pear'), isTrue); + expect(eventsContains(dartEvents, 'E', 'pear'), isTrue); + expect(eventsContains(dartEvents, 's', '123'), isTrue); + expect(eventsContains(dartEvents, 't', '123'), isTrue); + expect(eventsContains(dartEvents, 'f', '123'), isTrue); + // Calculate the time Window of Dart events. + final origin = timeOrigin(dartEvents); + final extent = timeDuration(dartEvents, origin); + // Query for the timeline with the time window for Dart events. + timeline = await service.getVMTimeline( + timeOriginMicros: origin, + timeExtentMicros: extent, + ); + traceEvents = timeline.traceEvents!; + // Verify that we received fewer events than before. + expect(traceEvents.length, lessThan(numEvents)); + // Verify that we have the same number of Dart events. + final dartEvents2 = filterForDartEvents(traceEvents); + expect(dartEvents2.length, dartEvents.length); + }, +]; + +void main([args = const []]) { + // NOTE: the original service test compiled the target program to kernel + // first, with the following comment providing an explanation: + // + // We first compile the testee to kernel and run the subprocess on the kernel + // file. That avoids cases where the testee has to run a lot of code in the + // kernel-isolate (e.g. due to ia32's kernel-service not being app-jit + // trained). We do that because otherwise the --complete-timeline will + // collect a lot of data, possibly leading to OOMs or timeouts. + // + // If this test times out on ia32, we may need to do the same. + runVMTests( + args, + tests, + 'get_vm_timeline_rpc_test.dart', + testeeBefore: primeTimeline, + extraArgs: ['--complete-timeline'], + ); +} diff --git a/pkg/vm_service/test/pause_on_start_and_exit_with_child_test.dart b/pkg/vm_service/test/pause_on_start_and_exit_with_child_test.dart new file mode 100644 index 00000000000..5bfe03cb6d0 --- /dev/null +++ b/pkg/vm_service/test/pause_on_start_and_exit_with_child_test.dart @@ -0,0 +1,54 @@ +// 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:isolate' as isolate; + +import 'package:vm_service/vm_service.dart'; +import 'common/service_test_common.dart'; +import 'common/test_helper.dart'; + +void child(message) { + print('Child got initial message'); + message.send(null); +} + +void testMain() { + final port = isolate.RawReceivePort(); + port.handler = (message) { + print('Parent got response'); + port.close(); + }; + + isolate.Isolate.spawn(child, port.sendPort); +} + +final tests = [ + hasPausedAtStart, + (VmService service, IsolateRef isolateRef) async { + // Disabling these flags will result in any new isolates spawned not + // pausing on start or exit. However, existing isolates will maintain their + // existing flags and the main isolate should pause at exit. + await service.setFlag('pause_isolates_on_start', 'false'); + await service.setFlag('pause_isolates_on_exit', 'false'); + }, + resumeIsolate, + // When the main isolate is resumed, we expect the child isolate to spawn and + // immediately exit after sending a message to the main isolate. The main + // isolate will only exit when `port` is closed after the message sent by the + // child is received. + hasStoppedAtExit, +]; + +void main([args = const []]) => runIsolateTests( + args, + tests, + 'pause_on_start_and_exit_with_child_test.dart', + testeeConcurrent: testMain, + pauseOnStart: true, + pauseOnExit: true, + extraArgs: [ + '--trace-service', + '--trace-service-verbose', + ], + );