From e382db35c9b762685789e8c28b00f0d0bb61ec1b Mon Sep 17 00:00:00 2001 From: Anna Gringauze Date: Mon, 22 Jan 2024 12:23:39 -0800 Subject: [PATCH] Fix failures on displaying getters (#2343) * Fix test faliures * Update changelog * Fixe analyzer errors * Display type instanses as common instances * Added getters checks to instance tests * Fixed analyzer errors * Add dart:_rti to the list of sdk libraries * Fix failing tests * Fixed failing tests --- dwds/CHANGELOG.md | 1 + dwds/lib/src/debugging/instance.dart | 78 +++-- dwds/lib/src/debugging/metadata/provider.dart | 1 + .../src/services/chrome_proxy_service.dart | 41 ++- dwds/test/evaluate_common.dart | 59 ++-- .../common/instance_inspection_common.dart | 63 +++- .../common/patterns_inspection_common.dart | 2 +- .../common/record_type_inspection_common.dart | 270 +++++++++++------- .../test/instances/common/test_inspector.dart | 93 ++++-- .../common/type_inspection_common.dart | 205 ++++++++++--- 10 files changed, 582 insertions(+), 231 deletions(-) diff --git a/dwds/CHANGELOG.md b/dwds/CHANGELOG.md index 568c44881..e86f19292 100644 --- a/dwds/CHANGELOG.md +++ b/dwds/CHANGELOG.md @@ -6,6 +6,7 @@ - Send untruncated `dart:developer` logs to debugging clients. - [#2333](https://github.com/dart-lang/webdev/pull/2333) - Enabling tests that run with the DDC module system and exposing `utilities/ddc_names.dart` - [#2295](https://github.com/dart-lang/webdev/pull/2295) +- Fix errors on displaying getters. - [#2343](https://github.com/dart-lang/webdev/pull/2343) ## 23.1.1 diff --git a/dwds/lib/src/debugging/instance.dart b/dwds/lib/src/debugging/instance.dart index 9db9c78c9..27fa81a1b 100644 --- a/dwds/lib/src/debugging/instance.dart +++ b/dwds/lib/src/debugging/instance.dart @@ -238,29 +238,49 @@ class InstanceHelper extends Domain { final objectId = remoteObject.objectId; if (objectId == null) return null; + final fields = await _getInstanceFields( + metaData, + remoteObject, + offset: offset, + count: count, + ); + + final result = Instance( + kind: InstanceKind.kPlainInstance, + id: objectId, + identityHashCode: remoteObject.objectId.hashCode, + classRef: metaData.classRef, + fields: fields, + ); + return result; + } + + Future> _getInstanceFields( + ClassMetaData metaData, + RemoteObject remoteObject, { + int? offset, + int? count, + }) async { + final objectId = remoteObject.objectId; + if (objectId == null) throw StateError('Object id is null for instance'); + final properties = await inspector.getProperties( objectId, offset: offset, count: count, length: metaData.length, ); + final dartProperties = await _dartFieldsFor(properties, remoteObject); - var boundFields = await Future.wait( + final boundFields = await Future.wait( dartProperties .map>((p) => _fieldFor(p, metaData.classRef)), ); - boundFields = boundFields + + return boundFields .where((bv) => inspector.isDisplayableObject(bv.value)) .toList() ..sort(_compareBoundFields); - final result = Instance( - kind: InstanceKind.kPlainInstance, - id: objectId, - identityHashCode: remoteObject.objectId.hashCode, - classRef: metaData.classRef, - fields: boundFields, - ); - return result; } int _compareBoundFields(BoundField a, BoundField b) { @@ -700,7 +720,13 @@ class InstanceHelper extends Domain { final objectId = remoteObject.objectId; if (objectId == null) return null; - final fields = await _typeFields(metaData.classRef, remoteObject); + final fields = await _getInstanceFields( + metaData, + remoteObject, + offset: offset, + count: count, + ); + return Instance( identityHashCode: objectId.hashCode, kind: InstanceKind.kType, @@ -714,36 +740,6 @@ class InstanceHelper extends Domain { ); } - /// The field types for a Dart RecordType. - /// - /// Returns a range of [count] field types, if available, starting from - /// the [offset]. - /// - /// If [offset] is `null`, assumes 0 offset. - /// If [count] is `null`, return all field types starting from the offset. - Future> _typeFields( - ClassRef classRef, - RemoteObject type, - ) async { - // Present the type as an instance of `core.Type` class and - // hide the internal implementation. - final expression = _jsRuntimeFunctionCall('getTypeFields(this)'); - - final result = await inspector.jsCallFunctionOn(type, expression, []); - final hashCodeObject = await inspector.loadField(result, 'hashCode'); - final runtimeTypeObject = await inspector.loadField(result, 'runtimeType'); - - final properties = [ - Property({'name': 'hashCode', 'value': hashCodeObject}), - Property({'name': 'runtimeType', 'value': runtimeTypeObject}), - ]; - - final boundFields = await Future.wait( - properties.map>((p) => _fieldFor(p, classRef)), - ); - return boundFields; - } - /// Return the available count of elements in the requested range. /// Return `null` if the range includes the whole object. /// [count] is the range length requested by the `getObject` call. diff --git a/dwds/lib/src/debugging/metadata/provider.dart b/dwds/lib/src/debugging/metadata/provider.dart index 677bde885..62e2455e9 100644 --- a/dwds/lib/src/debugging/metadata/provider.dart +++ b/dwds/lib/src/debugging/metadata/provider.dart @@ -44,6 +44,7 @@ class MetadataProvider { 'dart:_js_primitives', 'dart:_metadata', 'dart:_native_typed_data', + 'dart:_rti', 'dart:async', 'dart:collection', 'dart:convert', diff --git a/dwds/lib/src/services/chrome_proxy_service.dart b/dwds/lib/src/services/chrome_proxy_service.dart index 9a6051cce..33b12ea11 100644 --- a/dwds/lib/src/services/chrome_proxy_service.dart +++ b/dwds/lib/src/services/chrome_proxy_service.dart @@ -618,7 +618,35 @@ ${globalToolConfiguration.loadStrategy.loadModuleSnippet}("dart_sdk").developer. await isCompilerInitialized; _checkIsolate('evaluate', isolateId); - final library = await inspector.getLibrary(targetId); + late Obj object; + try { + object = await inspector.getObject(targetId); + } catch (_) { + return ErrorRef( + kind: 'error', + message: 'Evaluate is called on an unsupported target:' + '$targetId', + id: createId(), + ); + } + + final library = + object is Library ? object : inspector.isolate.rootLib; + + if (object is Instance) { + // Evaluate is called on a target - convert this to a dart + // expression and scope by adding a target variable to the + // expression and the scope, for example: + // + // Library: 'package:hello_world/main.dart' + // Expression: 'hashCode' => 'x.hashCode' + // Scope: {} => { 'x' : targetId } + + final target = _newVariableForScope(scope); + expression = '$target.$expression'; + scope = (scope ?? {})..addAll({target: targetId}); + } + return await _getEvaluationResult( isolateId, () => evaluator.evaluateExpression( @@ -631,7 +659,7 @@ ${globalToolConfiguration.loadStrategy.loadModuleSnippet}("dart_sdk").developer. ); } throw RPCError( - 'evaluateInFrame', + 'evaluate', RPCErrorKind.kInvalidRequest.code, 'Expression evaluation is not supported for this configuration.', ); @@ -640,6 +668,15 @@ ${globalToolConfiguration.loadStrategy.loadModuleSnippet}("dart_sdk").developer. ); } + String _newVariableForScope(Map? scope) { + // Find a new variable not in scope. + var candidate = 'x'; + while (scope?.containsKey(candidate) ?? false) { + candidate += '\$1'; + } + return candidate; + } + @override Future evaluateInFrame( String isolateId, diff --git a/dwds/test/evaluate_common.dart b/dwds/test/evaluate_common.dart index 7c3c9c984..2b48fe5fc 100644 --- a/dwds/test/evaluate_common.dart +++ b/dwds/test/evaluate_common.dart @@ -321,7 +321,7 @@ void testAll({ await getInstanceRef(event.topFrame!.index!, 'stream'); final instance = await getInstance(instanceRef); - expect(instance, matchInstance('_AsBroadcastStream')); + expect(instance, matchInstanceClassName('_AsBroadcastStream')); }); }); @@ -668,24 +668,24 @@ void testAll({ tearDown(() async {}); evaluate( - libraryId, + targetId, expr, { scope, }) async => await context.service.evaluate( isolateId, - libraryId, + targetId, expr, scope: scope, ); getInstanceRef( - libraryId, + targetId, expr, { scope, }) async { final result = await evaluate( - libraryId, + targetId, expr, scope: scope, ); @@ -699,6 +699,28 @@ void testAll({ return isolate.rootLib!.id!; } + test( + 'RecordType getters', + () async { + final libraryId = getRootLibraryId(); + + final type = await getInstanceRef(libraryId, '(0,1).runtimeType'); + final result = await getInstanceRef(type.id, 'hashCode'); + + expect(result, matchInstanceRefKind('Double')); + }, + skip: 'https://github.com/dart-lang/sdk/issues/54609', + ); + + test('Object getters', () async { + final libraryId = getRootLibraryId(); + + final type = await getInstanceRef(libraryId, 'Object()'); + final result = await getInstanceRef(type.id, 'hashCode'); + + expect(result, matchInstanceRefKind('Double')); + }); + test('with scope', () async { final libraryId = getRootLibraryId(); @@ -762,15 +784,13 @@ void testAll({ final evaluation2 = evaluate(libraryId, 'MainClass(1,1).toString()'); final results = await Future.wait([evaluation1, evaluation2]); - expect( results[0], - matchErrorRef(contains('No batch result object ID')), - ); - expect( - results[1], - matchErrorRef(contains('No batch result object ID')), + matchErrorRef( + contains('Evaluate is called on an unsupported target'), + ), ); + expect(results[1], matchInstanceRef('1, 1')); }); test('with scope override', () async { @@ -902,13 +922,20 @@ Future _setBreakpointInInjectedClient(WipDebugger debugger) async { return result.json['result']['breakpointId']; } -Matcher matchInstanceRef(dynamic value) => isA().having( - (instance) => instance.valueAsString, - 'valueAsString', - value, +Matcher matchInstanceRefKind(String kind) => + isA().having((instance) => instance.kind, 'kind', kind); + +Matcher matchInstanceRef(dynamic value) => isA() + .having((instance) => instance.valueAsString, 'valueAsString', value); + +Matcher matchInstanceClassName(dynamic className) => isA().having( + (instance) => instance.classRef!.name, + 'class name', + className, ); -Matcher matchInstance(dynamic className) => isA().having( +Matcher matchInstanceRefClassName(dynamic className) => + isA().having( (instance) => instance.classRef!.name, 'class name', className, diff --git a/dwds/test/instances/common/instance_inspection_common.dart b/dwds/test/instances/common/instance_inspection_common.dart index 5a0a499ed..312c6a5b6 100644 --- a/dwds/test/instances/common/instance_inspection_common.dart +++ b/dwds/test/instances/common/instance_inspection_common.dart @@ -189,14 +189,32 @@ void runTests({ final instanceId = instanceRef.id!; expect(await getObject(instanceId), matchListInstance(type: 'int')); - expect(await getFields(instanceRef), [0, 1, 2]); - expect(await getFields(instanceRef, offset: 1, count: 0), []); - expect(await getFields(instanceRef, offset: 0), [0, 1, 2]); - expect(await getFields(instanceRef, offset: 0, count: 1), [0]); - expect(await getFields(instanceRef, offset: 1), [1, 2]); - expect(await getFields(instanceRef, offset: 1, count: 1), [1]); - expect(await getFields(instanceRef, offset: 1, count: 3), [1, 2]); - expect(await getFields(instanceRef, offset: 3, count: 3), []); + expect( + await getFields(instanceRef), + {0: 0.0, 1: 1.0, 2: 2.0}, + ); + expect(await getFields(instanceRef, offset: 1, count: 0), {}); + expect( + await getFields(instanceRef, offset: 0), + {0: 0.0, 1: 1.0, 2: 2.0}, + ); + expect( + await getFields(instanceRef, offset: 0, count: 1), + {0: 0.0}, + ); + expect( + await getFields(instanceRef, offset: 1), + {0: 1.0, 1: 2.0}, + ); + expect( + await getFields(instanceRef, offset: 1, count: 1), + {0: 1.0}, + ); + expect( + await getFields(instanceRef, offset: 1, count: 3), + {0: 1.0, 1: 2.0}, + ); + expect(await getFields(instanceRef, offset: 3, count: 3), {}); }); }); @@ -284,13 +302,28 @@ void runTests({ matchSetInstance(type: '_HashSet'), ); - expect(await getFields(instanceRef), [1, 4, 5, 7]); - expect(await getFields(instanceRef, offset: 0), [1, 4, 5, 7]); - expect(await getFields(instanceRef, offset: 1, count: 2), [4, 5]); - expect(await getFields(instanceRef, offset: 2), [5, 7]); - expect(await getFields(instanceRef, offset: 2, count: 10), [5, 7]); - expect(await getFields(instanceRef, offset: 1, count: 0), []); - expect(await getFields(instanceRef, offset: 10, count: 2), []); + expect( + await getFields(instanceRef), + {0: 1.0, 1: 4.0, 2: 5.0, 3: 7.0}, + ); + expect( + await getFields(instanceRef, offset: 0), + {0: 1.0, 1: 4.0, 2: 5.0, 3: 7.0}, + ); + expect( + await getFields(instanceRef, offset: 1, count: 2), + {0: 4.0, 1: 5.0}, + ); + expect( + await getFields(instanceRef, offset: 2), + {0: 5.0, 1: 7.0}, + ); + expect( + await getFields(instanceRef, offset: 2, count: 10), + {0: 5.0, 1: 7.0}, + ); + expect(await getFields(instanceRef, offset: 1, count: 0), {}); + expect(await getFields(instanceRef, offset: 10, count: 2), {}); }); }); diff --git a/dwds/test/instances/common/patterns_inspection_common.dart b/dwds/test/instances/common/patterns_inspection_common.dart index b4e14c41d..9a303a632 100644 --- a/dwds/test/instances/common/patterns_inspection_common.dart +++ b/dwds/test/instances/common/patterns_inspection_common.dart @@ -105,7 +105,7 @@ void runTests({ final frame = event.topFrame!; final frameIndex = frame.index!; final instanceRef = await getInstanceRef(frameIndex, 'obj'); - expect(await getFields(instanceRef), [0, 1]); + expect(await getFields(instanceRef), {0: 0.0, 1: 1.0}); expect(await getFrameVariables(frame), { 'obj': matchListInstance(type: 'int'), diff --git a/dwds/test/instances/common/record_type_inspection_common.dart b/dwds/test/instances/common/record_type_inspection_common.dart index 1d41bdbff..7aca5699a 100644 --- a/dwds/test/instances/common/record_type_inspection_common.dart +++ b/dwds/test/instances/common/record_type_inspection_common.dart @@ -5,6 +5,7 @@ import 'package:test/test.dart'; import 'package:test_common/logging.dart'; import 'package:test_common/test_sdk_configuration.dart'; +import 'package:test_common/utilities.dart'; import 'package:vm_service/vm_service.dart'; import '../../fixtures/context.dart'; @@ -43,9 +44,17 @@ void runTests({ getDisplayedFields(InstanceRef ref) => testInspector.getDisplayedFields(isolateId, ref); + getDisplayedGetters(InstanceRef ref) => + testInspector.getDisplayedGetters(isolateId, ref); + getElements(String instanceId) => testInspector.getElements(isolateId, instanceId); + final matchDisplayedTypeObjectGetters = { + 'hashCode': matches('[0-9]*'), + 'runtimeType': matchTypeClassName, + }; + group('$compilationMode |', () { setUpAll(() async { setCurrentLogWriter(debug: debug); @@ -78,30 +87,25 @@ void runTests({ setUp(() => setCurrentLogWriter(debug: debug)); tearDown(() => service.resume(isolateId)); - test( - 'simple record type', - () async { - await onBreakPoint( - 'printSimpleLocalRecord', - (event) async { - final frame = event.topFrame!.index!; - final instanceRef = - await getInstanceRef(frame, 'record.runtimeType'); - final instanceId = instanceRef.id!; - - expect(instanceRef, matchRecordTypeInstanceRef(length: 2)); - expect( - await getObject(instanceId), - matchRecordTypeInstance(length: 2), - ); - - final classId = instanceRef.classRef!.id; - expect(await getObject(classId), matchRecordTypeClass); - }, - ); - }, - skip: 'https://github.com/dart-lang/webdev/issues/2351', - ); + test('simple record type', () async { + await onBreakPoint( + 'printSimpleLocalRecord', + (event) async { + final frame = event.topFrame!.index!; + final instanceRef = await getInstanceRef(frame, 'record.runtimeType'); + final instanceId = instanceRef.id!; + + expect(instanceRef, matchRecordTypeInstanceRef(length: 2)); + expect( + await getObject(instanceId), + matchRecordTypeInstance(length: 2), + ); + + final classId = instanceRef.classRef!.id; + expect(await getObject(classId), matchRecordTypeClass); + }, + ); + }); test('simple record type elements', () async { await onBreakPoint('printSimpleLocalRecord', (event) async { @@ -115,11 +119,27 @@ void runTests({ ); expect( await getDisplayedFields(instanceRef), - ['bool', 'int'], + {1: 'bool', 2: 'int'}, ); }); }); + test( + 'simple record type getters', + () async { + await onBreakPoint('printSimpleLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = await getInstanceRef(frame, 'record.runtimeType'); + + expect( + await getDisplayedGetters(instanceRef), + matchDisplayedTypeObjectGetters, + ); + }); + }, + skip: !dartSdkIsAtLeast('3.4.0-56.0.dev'), + ); + test('simple record type display', () async { await onBreakPoint('printSimpleLocalRecord', (event) async { final frame = event.topFrame!.index!; @@ -155,7 +175,6 @@ void runTests({ expect(await getObject(classId), matchRecordTypeClass); }); }, - skip: 'https://github.com/dart-lang/webdev/issues/2351', ); test('complex record type elements', () async { @@ -174,11 +193,27 @@ void runTests({ ); expect( await getDisplayedFields(instanceRef), - ['bool', 'int', 'IdentityMap'], + {1: 'bool', 2: 'int', 3: 'IdentityMap'}, ); }); }); + test( + 'complex record type getters', + () async { + await onBreakPoint('printComplexLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = await getInstanceRef(frame, 'record.runtimeType'); + + expect( + await getDisplayedGetters(instanceRef), + matchDisplayedTypeObjectGetters, + ); + }); + }, + skip: !dartSdkIsAtLeast('3.4.0-56.0.dev'), + ); + test('complex record type display', () async { await onBreakPoint('printComplexLocalRecord', (event) async { final frame = event.topFrame!.index!; @@ -196,28 +231,24 @@ void runTests({ }); }); - test( - 'complex record type with named fields ', - () async { - await onBreakPoint('printComplexNamedLocalRecord', (event) async { - final frame = event.topFrame!.index!; - final instanceRef = await getInstanceRef(frame, 'record.runtimeType'); - final instanceId = instanceRef.id!; + test('complex record type with named fields ', () async { + await onBreakPoint('printComplexNamedLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = await getInstanceRef(frame, 'record.runtimeType'); + final instanceId = instanceRef.id!; - expect(instanceRef, matchRecordTypeInstanceRef(length: 3)); - expect( - await getObject(instanceId), - matchRecordTypeInstance(length: 3), - ); + expect(instanceRef, matchRecordTypeInstanceRef(length: 3)); + expect( + await getObject(instanceId), + matchRecordTypeInstance(length: 3), + ); - final classId = instanceRef.classRef!.id; - expect(await getObject(classId), matchRecordTypeClass); - }); - }, - skip: 'https://github.com/dart-lang/webdev/issues/2351', - ); + final classId = instanceRef.classRef!.id; + expect(await getObject(classId), matchRecordTypeClass); + }); + }); - test('complex record type with named fields elements', () async { + test('complex record type with named fields elements', () async { await onBreakPoint('printComplexNamedLocalRecord', (event) async { final frame = event.topFrame!.index!; final instanceRef = await getInstanceRef(frame, 'record.runtimeType'); @@ -234,11 +265,27 @@ void runTests({ expect( await getDisplayedFields(instanceRef), - ['bool', 'int', 'IdentityMap'], + {1: 'bool', 2: 'int', 'array': 'IdentityMap'}, ); }); }); + test( + 'complex record type with named fields getters', + () async { + await onBreakPoint('printComplexNamedLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = await getInstanceRef(frame, 'record.runtimeType'); + + expect( + await getDisplayedGetters(instanceRef), + matchDisplayedTypeObjectGetters, + ); + }); + }, + skip: !dartSdkIsAtLeast('3.4.0-56.0.dev'), + ); + test('complex record type with named fields display', () async { await onBreakPoint('printComplexNamedLocalRecord', (event) async { final frame = event.topFrame!.index!; @@ -274,37 +321,53 @@ void runTests({ expect(await getObject(classId), matchRecordTypeClass); }); }, - skip: 'https://github.com/dart-lang/webdev/issues/2351', ); + test('nested record type elements', () async { + await onBreakPoint('printNestedLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = await getInstanceRef(frame, 'record.runtimeType'); + final instanceId = instanceRef.id!; + + final elements = await getElements(instanceId); + expect( + elements, + [matchTypeInstance('bool'), matchRecordTypeInstance(length: 2)], + ); + expect( + await getElements(elements[1].id!), + [matchTypeInstance('bool'), matchTypeInstance('int')], + ); + expect( + await getDisplayedFields(instanceRef), + {1: 'bool', 2: '(bool, int)'}, + ); + expect( + await getDisplayedFields(elements[1]), + {1: 'bool', 2: 'int'}, + ); + }); + }); + test( - 'nested record type elements', + 'nested record type getters', () async { await onBreakPoint('printNestedLocalRecord', (event) async { final frame = event.topFrame!.index!; final instanceRef = await getInstanceRef(frame, 'record.runtimeType'); - final instanceId = instanceRef.id!; + final elements = await getElements(instanceRef.id!); - final elements = await getElements(instanceId); expect( - elements, - [matchTypeInstance('bool'), matchRecordTypeInstance(length: 2)], + await getDisplayedGetters(instanceRef), + matchDisplayedTypeObjectGetters, ); expect( - await getElements(elements[1].id!), - [matchTypeInstance('bool'), matchTypeInstance('int')], - ); - expect( - await getDisplayedFields(instanceRef), - ['bool', '(bool, int)'], - ); - expect( - await getDisplayedFields(elements[1]), - ['bool', 'int'], + await getDisplayedGetters(elements[1]), + matchDisplayedTypeObjectGetters, ); }); }, - skip: 'https://github.com/dart-lang/webdev/issues/2351', + skip: !dartSdkIsAtLeast('3.4.0-56.0.dev'), ); test('nested record type display', () async { @@ -324,53 +387,67 @@ void runTests({ }); }); - test( - 'nested record type with named fields', - () async { - await onBreakPoint('printNestedNamedLocalRecord', (event) async { - final frame = event.topFrame!.index!; - final instanceRef = await getInstanceRef(frame, 'record.runtimeType'); - final instanceId = instanceRef.id!; - final instance = await getObject(instanceId); + test('nested record type with named fields', () async { + await onBreakPoint('printNestedNamedLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = await getInstanceRef(frame, 'record.runtimeType'); + final instanceId = instanceRef.id!; + final instance = await getObject(instanceId); - expect(instanceRef, matchRecordTypeInstanceRef(length: 2)); - expect(instance, matchRecordTypeInstance(length: 2)); + expect(instanceRef, matchRecordTypeInstanceRef(length: 2)); + expect(instance, matchRecordTypeInstance(length: 2)); - final classId = instanceRef.classRef!.id; - expect(await getObject(classId), matchRecordTypeClass); - }); - }, - skip: 'https://github.com/dart-lang/webdev/issues/2351', - ); + final classId = instanceRef.classRef!.id; + expect(await getObject(classId), matchRecordTypeClass); + }); + }); + + test('nested record type with named fields elements', () async { + await onBreakPoint('printNestedNamedLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = await getInstanceRef(frame, 'record.runtimeType'); + final instanceId = instanceRef.id!; + + final elements = await getElements(instanceId); + expect( + elements, + [matchTypeInstance('bool'), matchRecordTypeInstance(length: 2)], + ); + expect( + await getElements(elements[1].id!), + [matchTypeInstance('bool'), matchTypeInstance('int')], + ); + expect( + await getDisplayedFields(instanceRef), + {1: 'bool', 'inner': '(bool, int)'}, + ); + + expect( + await getDisplayedFields(elements[1]), + {1: 'bool', 2: 'int'}, + ); + }); + }); test( - 'nested record type with named fields elements', + 'nested record type with named fields getters', () async { await onBreakPoint('printNestedNamedLocalRecord', (event) async { final frame = event.topFrame!.index!; final instanceRef = await getInstanceRef(frame, 'record.runtimeType'); - final instanceId = instanceRef.id!; + final elements = await getElements(instanceRef.id!); - final elements = await getElements(instanceId); - expect( - elements, - [matchTypeInstance('bool'), matchRecordTypeInstance(length: 2)], - ); - expect( - await getElements(elements[1].id!), - [matchTypeInstance('bool'), matchTypeInstance('int')], - ); expect( - await getDisplayedFields(instanceRef), - ['bool', '(bool, int)'], + await getDisplayedGetters(instanceRef), + matchDisplayedTypeObjectGetters, ); expect( - await getDisplayedFields(elements[1]), - ['bool', 'int'], + await getDisplayedGetters(elements[1]), + matchDisplayedTypeObjectGetters, ); }); }, - skip: 'https://github.com/dart-lang/webdev/issues/2351', + skip: !dartSdkIsAtLeast('3.4.0-56.0.dev'), ); test( @@ -397,7 +474,6 @@ void runTests({ ); }); }, - skip: 'https://github.com/dart-lang/webdev/issues/2351', ); }); } diff --git a/dwds/test/instances/common/test_inspector.dart b/dwds/test/instances/common/test_inspector.dart index dc500cd5b..92768200b 100644 --- a/dwds/test/instances/common/test_inspector.dart +++ b/dwds/test/instances/common/test_inspector.dart @@ -43,7 +43,7 @@ class TestInspector { } } - Future getFields( + Future> getFields( String isolateId, InstanceRef instanceRef, { int? offset, @@ -88,7 +88,7 @@ class TestInspector { depth--; } if (depth == 0) { - return elements == null ? fieldRefs : fieldRefs.values.toList(); + return fieldRefs; } final fieldValues = {}; @@ -100,7 +100,27 @@ class TestInspector { depth: depth, ); } - return elements == null ? fieldValues : fieldValues.values.toList(); + return fieldValues; + } + + Future> getGetters( + String isolateId, + InstanceRef instanceRef, + ) async { + final cls = + await service.getObject(isolateId, instanceRef.classRef!.id!) as Class; + final getters = + cls.functions?.where((f) => f.isGetter ?? false).toList() ?? []; + + final results = await Future.wait([ + for (var getter in getters) + service.evaluate(isolateId, instanceRef.id!, getter.name!), + ]); + + return Map.fromIterables( + getters.map((e) => e.name!), + results.map((e) => e as InstanceRef), + ); } Future getInstanceRef( @@ -161,7 +181,7 @@ class TestInspector { await service.invoke(isolateId, instanceId, 'toString', []) as InstanceRef; - Future> getDisplayedFields( + Future> getDisplayedFields( String isolateId, InstanceRef ref, ) async { @@ -173,7 +193,22 @@ class TestInspector { (await getDisplayedRef(isolateId, ref.id!)).valueAsString; final fields = await Future.wait(fieldRefs.values.map(toStringValue)); - return fields.toList(); + return Map.fromIterables(fieldRefs.keys, fields); + } + + Future> getDisplayedGetters( + String isolateId, + InstanceRef ref, + ) async { + final fieldRefs = + await getGetters(isolateId, ref) as Map; + + Future toStringValue(InstanceRef ref) async => + ref.valueAsString ?? + (await getDisplayedRef(isolateId, ref.id!)).valueAsString; + + final fields = await Future.wait(fieldRefs.values.map(toStringValue)); + return Map.fromIterables(fieldRefs.keys, fields); } Future> getElements( @@ -288,29 +323,35 @@ Matcher matchTypeInstance(dynamic name) => isA() Matcher matchRecordClass = matchClass(name: matchRecordClassName, libraryId: _dartCoreLibrary); -Matcher matchRecordTypeClass = matchClass( - name: - // See https://github.com/dart-lang/sdk/commit/67e052d7e996be8ad9d02970117ffef07eab1c77: - dartSdkIsAtLeast('3.4.0-edge.eeec4d36e3ea9b166da277a46f62d7d3b9ce645a') - ? InstanceKind.kType - : InstanceKind.kRecordType, - libraryId: _dartRuntimeLibrary, -); Matcher matchTypeClass = matchClass(name: matchTypeClassName, libraryId: _dartCoreLibrary); +/// TODO(annagrin): record type class is reported incorrectly +/// in ddc https://github.com/dart-lang/sdk/issues/54609, +/// remove when fixed. +Matcher matchRecordTypeClass = anyOf( + matchTypeClass, + matchClass(name: matchRecordTypeClassName, libraryId: _dartRuntimeLibrary), +); + Matcher matchClass({dynamic name, String? libraryId}) => isA() .having((e) => e.name, 'class name', name) .having((e) => e.library, 'library', matchLibraryRef(libraryId)); Matcher matchRecordClassRef = matchClassRef(name: matchRecordClassName, libraryId: _dartCoreLibrary); -Matcher matchRecordTypeClassRef = matchClassRef( - name: matchRecordTypeClassName, - libraryId: _dartRuntimeLibrary, + +/// TODO(annagrin): record type class is reported incorrectly +/// in ddc https://github.com/dart-lang/sdk/issues/54609, +/// remove when fixed. +Matcher matchRecordTypeClassRef = anyOf( + matchTypeClassRef, + matchClassRef(name: matchRecordTypeClassName, libraryId: _dartRuntimeLibrary), +); +Matcher matchTypeClassRef = matchClassRef( + name: matchTypeClassName, + libraryId: _dartCoreLibrary, ); -Matcher matchTypeClassRef = - matchClassRef(name: matchTypeClassName, libraryId: _dartCoreLibrary); Matcher matchListClassRef(String type) => matchClassRef( name: matchListClassName(type), libraryId: _matchListLibraryName, @@ -344,19 +385,25 @@ Object? _getValue(InstanceRef instanceRef) { } final _dartCoreLibrary = 'dart:core'; -final _dartRuntimeLibrary = 'dart:_runtime'; final _dartInterceptorsLibrary = 'dart:_interceptors'; final _dartJsHelperLibrary = 'dart:_js_helper'; final _dartCollectionLibrary = 'dart:collection'; +final _dartRuntimeLibrary = 'dart:_runtime'; final matchRecordClassName = 'Record'; -final matchRecordTypeClassName = 'RecordType'; /// Match types for old and new type systems. -/// - Old type system has `dart:_interceptors|List` and `dart:_runtime|_Type`. -/// - New type system has `dart:_interceptors|JSArray` and `dart:core|Type`. -/// TODO(annagrin): update when DDC enables new type system. +/// - Old type system has +/// - for arrays: `dart:_interceptors|List` +/// - for type: `dart:_runtime|_Type`. +/// - New type system has +/// - for arrays: dart:_interceptors|JSArray`, and +/// - for type: `dart:core|Type`. +/// TODO(annagrin): remove old matchers when DDC enables new type system. +/// TODO(annagrin): `matchTypeClassName` is reported incorrectly +/// in ddc https://github.com/dart-lang/sdk/issues/54609, final matchTypeClassName = anyOf(['Type', '_Type']); +final matchRecordTypeClassName = 'RecordType'; Matcher matchListClassName(String elementType) => anyOf(['JSArray<$elementType>', 'List<$elementType>']); diff --git a/dwds/test/instances/common/type_inspection_common.dart b/dwds/test/instances/common/type_inspection_common.dart index a847314a7..70b6f3ed4 100644 --- a/dwds/test/instances/common/type_inspection_common.dart +++ b/dwds/test/instances/common/type_inspection_common.dart @@ -5,6 +5,7 @@ import 'package:test/test.dart'; import 'package:test_common/logging.dart'; import 'package:test_common/test_sdk_configuration.dart'; +import 'package:test_common/utilities.dart'; import 'package:vm_service/vm_service.dart'; import '../../fixtures/context.dart'; @@ -40,6 +41,9 @@ void runTests({ getDisplayedFields(instanceRef) => testInspector.getDisplayedFields(isolateId, instanceRef); + getDisplayedGetters(instanceRef) => + testInspector.getDisplayedGetters(isolateId, instanceRef); + getInstanceRef(frame, expression) => testInspector.getInstanceRef(isolateId, frame, expression); @@ -55,15 +59,15 @@ void runTests({ getElements(String instanceId) => testInspector.getElements(isolateId, instanceId); - final matchTypeObject = { - 'hashCode': matchPrimitiveInstanceRef(kind: InstanceKind.kDouble), - 'runtimeType': matchTypeInstanceRef(matchTypeClassName), + final matchTypeObjectFields = {}; + + final matchDisplayedTypeObjectFields = {}; + + final matchDisplayedTypeObjectGetters = { + 'hashCode': matches('[0-9]*'), + 'runtimeType': matchTypeClassName, }; - final matchDisplayedTypeObject = [ - matches('[0-9]*'), - matchTypeClassName, - ]; group('$compilationMode |', () { setUpAll(() async { setCurrentLogWriter(debug: debug); @@ -108,11 +112,33 @@ void runTests({ final classId = instanceRef.classRef!.id; expect(await getObject(classId), matchTypeClass); - expect(await getFields(instanceRef, depth: 1), matchTypeObject); - expect(await getDisplayedFields(instanceRef), matchDisplayedTypeObject); + expect( + await getFields(instanceRef, depth: 1), + matchTypeObjectFields, + ); + expect( + await getDisplayedFields(instanceRef), + matchDisplayedTypeObjectFields, + ); }); }); + test( + 'String type getters', + () async { + await onBreakPoint('printSimpleLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = await getInstanceRef(frame, "'1'.runtimeType"); + + expect( + await getDisplayedGetters(instanceRef), + matchDisplayedTypeObjectGetters, + ); + }); + }, + skip: !dartSdkIsAtLeast('3.4.0-56.0.dev'), + ); + test('int type', () async { await onBreakPoint('printSimpleLocalRecord', (event) async { final frame = event.topFrame!.index!; @@ -125,11 +151,33 @@ void runTests({ final classId = instanceRef.classRef!.id; expect(await getObject(classId), matchTypeClass); - expect(await getFields(instanceRef, depth: 1), matchTypeObject); - expect(await getDisplayedFields(instanceRef), matchDisplayedTypeObject); + expect( + await getFields(instanceRef, depth: 1), + matchTypeObjectFields, + ); + expect( + await getDisplayedFields(instanceRef), + matchDisplayedTypeObjectFields, + ); }); }); + test( + 'int type getters', + () async { + await onBreakPoint('printSimpleLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = await getInstanceRef(frame, '1.runtimeType'); + + expect( + await getDisplayedGetters(instanceRef), + matchDisplayedTypeObjectGetters, + ); + }); + }, + skip: !dartSdkIsAtLeast('3.4.0-56.0.dev'), + ); + test('list type', () async { await onBreakPoint('printSimpleLocalRecord', (event) async { final frame = event.topFrame!.index!; @@ -142,8 +190,18 @@ void runTests({ final classId = instanceRef.classRef!.id; expect(await getObject(classId), matchTypeClass); - expect(await getFields(instanceRef, depth: 1), matchTypeObject); - expect(await getDisplayedFields(instanceRef), matchDisplayedTypeObject); + expect( + await getFields(instanceRef, depth: 1), + matchTypeObjectFields, + ); + expect( + await getDisplayedFields(instanceRef), + matchDisplayedTypeObjectFields, + ); + expect( + await getDisplayedGetters(instanceRef), + matchDisplayedTypeObjectGetters, + ); }); }); @@ -160,11 +218,31 @@ void runTests({ final classId = instanceRef.classRef!.id; expect(await getObject(classId), matchTypeClass); - expect(await getFields(instanceRef, depth: 1), matchTypeObject); - expect(await getDisplayedFields(instanceRef), matchDisplayedTypeObject); + expect(await getFields(instanceRef, depth: 1), matchTypeObjectFields); + expect( + await getDisplayedFields(instanceRef), + matchDisplayedTypeObjectFields, + ); }); }); + test( + 'map type getters', + () async { + await onBreakPoint('printSimpleLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = + await getInstanceRef(frame, '{}.runtimeType'); + + expect( + await getDisplayedGetters(instanceRef), + matchDisplayedTypeObjectGetters, + ); + }); + }, + skip: !dartSdkIsAtLeast('3.4.0-56.0.dev'), + ); + test('set type', () async { await onBreakPoint('printSimpleLocalRecord', (event) async { final frame = event.topFrame!.index!; @@ -177,41 +255,76 @@ void runTests({ final classId = instanceRef.classRef!.id; expect(await getObject(classId), matchTypeClass); - expect(await getFields(instanceRef, depth: 1), matchTypeObject); - expect(await getDisplayedFields(instanceRef), matchDisplayedTypeObject); + expect( + await getFields(instanceRef, depth: 1), + matchTypeObjectFields, + ); + expect( + await getDisplayedFields(instanceRef), + matchDisplayedTypeObjectFields, + ); }); }); test( - 'record type', + 'set type getters', () async { await onBreakPoint('printSimpleLocalRecord', (event) async { final frame = event.topFrame!.index!; final instanceRef = - await getInstanceRef(frame, "(0,'a').runtimeType"); - expect(instanceRef, matchRecordTypeInstanceRef(length: 2)); + await getInstanceRef(frame, '{}.runtimeType'); - final instanceId = instanceRef.id!; - final instance = await getObject(instanceId); - expect(instance, matchRecordTypeInstance(length: 2)); expect( - await getElements(instanceId), - [matchTypeInstance('int'), matchTypeInstance('String')], + await getDisplayedGetters(instanceRef), + matchDisplayedTypeObjectGetters, ); + }); + }, + skip: !dartSdkIsAtLeast('3.4.0-56.0.dev'), + ); + + test('record type', () async { + await onBreakPoint('printSimpleLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = await getInstanceRef(frame, "(0,'a').runtimeType"); + expect(instanceRef, matchRecordTypeInstanceRef(length: 2)); + + final instanceId = instanceRef.id!; + final instance = await getObject(instanceId); + expect(instance, matchRecordTypeInstance(length: 2)); + expect( + await getElements(instanceId), + [matchTypeInstance('int'), matchTypeInstance('String')], + ); + + final classId = instanceRef.classRef!.id; + expect(await getObject(classId), matchRecordTypeClass); + expect( + await getFields(instanceRef, depth: 2), + {1: matchTypeObjectFields, 2: matchTypeObjectFields}, + ); + expect( + await getDisplayedFields(instanceRef), + {1: 'int', 2: 'String'}, + ); + }); + }); + + test( + 'record type getters', + () async { + await onBreakPoint('printSimpleLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = + await getInstanceRef(frame, "(0,'a').runtimeType"); - final classId = instanceRef.classRef!.id; - expect(await getObject(classId), matchRecordTypeClass); - expect( - await getFields(instanceRef, depth: 2), - {1: matchTypeObject, 2: matchTypeObject}, - ); expect( - await getDisplayedFields(instanceRef), - ['int', 'String'], + await getDisplayedGetters(instanceRef), + matchDisplayedTypeObjectGetters, ); }); }, - skip: 'https://github.com/dart-lang/webdev/issues/2351', + skip: !dartSdkIsAtLeast('3.4.0-56.0.dev'), ); test('class type', () async { @@ -227,9 +340,29 @@ void runTests({ final classId = instanceRef.classRef!.id; expect(await getObject(classId), matchTypeClass); - expect(await getFields(instanceRef, depth: 1), matchTypeObject); - expect(await getDisplayedFields(instanceRef), matchDisplayedTypeObject); + expect(await getFields(instanceRef, depth: 1), matchTypeObjectFields); + expect( + await getDisplayedFields(instanceRef), + matchDisplayedTypeObjectFields, + ); }); }); + + test( + 'class type getters', + () async { + await onBreakPoint('printSimpleLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = + await getInstanceRef(frame, "Uri.file('').runtimeType"); + + expect( + await getDisplayedGetters(instanceRef), + matchDisplayedTypeObjectGetters, + ); + }); + }, + skip: !dartSdkIsAtLeast('3.4.0-56.0.dev'), + ); }); }