Add a test for Class inspection (#2310)
diff --git a/dwds/test/instances/class_inspection_test.dart b/dwds/test/instances/class_inspection_test.dart
new file mode 100644
index 0000000..aa05e6d
--- /dev/null
+++ b/dwds/test/instances/class_inspection_test.dart
@@ -0,0 +1,127 @@
+// 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.
+
+@Tags(['daily'])
+@TestOn('vm')
+@Timeout(Duration(minutes: 2))
+
+import 'package:test/test.dart';
+import 'package:test_common/logging.dart';
+import 'package:test_common/test_sdk_configuration.dart';
+import 'package:vm_service/vm_service.dart';
+
+import '../fixtures/context.dart';
+import '../fixtures/project.dart';
+import '../fixtures/utilities.dart';
+import 'common/test_inspector.dart';
+
+void main() {
+ // Enable verbose logging for debugging.
+ final debug = false;
+
+ final provider = TestSdkConfigurationProvider(
+ verbose: debug,
+ );
+
+ final context =
+ TestContext(TestProject.testExperimentWithSoundNullSafety, provider);
+ final testInspector = TestInspector(context);
+
+ late VmService service;
+ late Stream<Event> stream;
+ late String isolateId;
+ late ScriptRef mainScript;
+
+ onBreakPoint(breakPointId, body) => testInspector.onBreakPoint(
+ stream,
+ isolateId,
+ mainScript,
+ breakPointId,
+ body,
+ );
+
+ getObject(instanceId) => service.getObject(isolateId, instanceId);
+
+ group('Class |', () {
+ tearDownAll(provider.dispose);
+
+ for (var compilationMode in CompilationMode.values) {
+ group('$compilationMode |', () {
+ setUpAll(() async {
+ setCurrentLogWriter(debug: debug);
+ await context.setUp(
+ testSettings: TestSettings(
+ compilationMode: compilationMode,
+ enableExpressionEvaluation: true,
+ verboseCompiler: debug,
+ ),
+ );
+ service = context.debugConnection.vmService;
+
+ final vm = await service.getVM();
+ isolateId = vm.isolates!.first.id!;
+ final scripts = await service.getScripts(isolateId);
+
+ await service.streamListen('Debug');
+ stream = service.onEvent('Debug');
+
+ mainScript = scripts.scripts!
+ .firstWhere((each) => each.uri!.contains('main.dart'));
+ });
+
+ tearDownAll(() async {
+ await context.tearDown();
+ });
+
+ setUp(() => setCurrentLogWriter(debug: debug));
+ tearDown(() => service.resume(isolateId));
+
+ group('calling getObject for an existent class', () {
+ test('returns the correct class representation', () async {
+ await onBreakPoint('testClass1Case1', (event) async {
+ // classes|dart:core|Object_Diagnosticable
+ final result = await getObject(
+ 'classes|org-dartlang-app:///web/main.dart|GreeterClass',
+ );
+ final clazz = result as Class?;
+ expect(clazz!.name, equals('GreeterClass'));
+ expect(
+ clazz.fields!.map((field) => field.name),
+ unorderedEquals([
+ 'greeteeName',
+ 'useFrench',
+ ]),
+ );
+ expect(
+ clazz.functions!.map((fn) => fn.name),
+ containsAll([
+ 'sayHello',
+ 'greetInEnglish',
+ 'greetInFrench',
+ ]),
+ );
+ });
+ });
+ });
+
+ group('calling getObject for a non-existent class', () {
+ // TODO(https://github.com/dart-lang/webdev/issues/2297): Ideally we
+ // should throw an error in this case for the client to catch instead
+ // of returning an empty class.
+ test('returns an empty class representation', () async {
+ await onBreakPoint('testClass1Case1', (event) async {
+ final result = await getObject(
+ 'classes|dart:core|Object_Diagnosticable',
+ );
+ final clazz = result as Class?;
+ expect(clazz!.name, equals('Object_Diagnosticable'));
+ expect(clazz.fields, isEmpty);
+ expect(clazz.functions, isEmpty);
+ });
+ });
+ });
+ });
+ }
+ });
+}
diff --git a/fixtures/_experimentSound/web/main.dart b/fixtures/_experimentSound/web/main.dart
index 3783bc6..b68f1d4 100644
--- a/fixtures/_experimentSound/web/main.dart
+++ b/fixtures/_experimentSound/web/main.dart
@@ -20,6 +20,8 @@
testPattern([3.14, 'b']);
testPattern([0, 1]);
testPattern2();
+ print('Classes');
+ testClass();
});
document.body!.appendText('Program is running!');
@@ -55,6 +57,11 @@
print(record); // Breakpoint: printNestedNamedLocalRecord
}
+void testClass() {
+ final greeter = GreeterClass(greeteeName: 'Charlie Brown');
+ greeter.sayHello();
+}
+
String testPattern(Object obj) {
switch (obj) {
case [var a, int n] || [int n, var a] when n == 1 && a is String:
@@ -73,3 +80,25 @@
print(firstCat); // Breakpoint: testPattern2Case2
return '$dog, $firstCat, $secondCat';
}
+
+class GreeterClass {
+ final String greeteeName;
+ final bool useFrench;
+
+ GreeterClass({
+ this.greeteeName = 'Snoopy',
+ this.useFrench = false,
+ });
+
+ void sayHello() {
+ useFrench ? greetInFrench() : greetInEnglish();
+ }
+
+ void greetInEnglish() {
+ print('Hello $greeteeName'); // Breakpoint: testClass1Case1
+ }
+
+ void greetInFrench() {
+ print('Bonjour $greeteeName');
+ }
+}