| // 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:dwds/src/config/tool_configuration.dart'; |
| import 'package:dwds/src/debugging/inspector.dart'; |
| 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 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'; |
| |
| import '../../fixtures/context.dart'; |
| import '../../fixtures/project.dart'; |
| import 'test_inspector.dart'; |
| |
| void runTypeSystemVerificationTests({ |
| required TestSdkConfigurationProvider provider, |
| required CompilationMode compilationMode, |
| required bool canaryFeatures, |
| required bool debug, |
| }) { |
| final project = TestProject.testScopesWithSoundNullSafety; |
| |
| group('$compilationMode |', () { |
| final context = TestContext(project, provider); |
| late AppInspector inspector; |
| |
| setUpAll(() async { |
| setCurrentLogWriter(debug: debug); |
| await context.setUp( |
| canaryFeatures: canaryFeatures, |
| compilationMode: compilationMode, |
| ); |
| final chromeProxyService = context.service; |
| inspector = chromeProxyService.inspector; |
| }); |
| |
| tearDownAll(() async { |
| await context.tearDown(); |
| }); |
| |
| final url = 'org-dartlang-app:///example/scopes/main.dart'; |
| |
| String libraryName(CompilationMode compilationMode) => |
| compilationMode == CompilationMode.frontendServer |
| ? "example/scopes/main.dart" |
| : "example/scopes/main"; |
| |
| String libraryVariableTypeExpression( |
| String variable, |
| CompilationMode compilationMode, |
| ) => |
| ''' |
| (function() { |
| var dart = ${globalToolConfiguration.loadStrategy.loadModuleSnippet}('dart_sdk').dart; |
| var libraryName = '${libraryName(compilationMode)}'; |
| var library = dart.getModuleLibraries(libraryName)['$url']; |
| var x = library['$variable']; |
| return dart.getReifiedType(x); |
| })(); |
| '''; |
| |
| group('compiler', () { |
| setUp(() => setCurrentLogWriter(debug: debug)); |
| |
| test('uses correct type system', () async { |
| final remoteObject = await inspector.jsEvaluate( |
| libraryVariableTypeExpression( |
| 'libraryPublicFinal', |
| compilationMode, |
| ), |
| ); |
| expect( |
| remoteObject.json['className'], |
| canaryFeatures || |
| dartSdkIsAtLeast( |
| newDdcTypeSystemVersion, |
| ) |
| ? 'dart_rti.Rti.new' |
| : 'Function', |
| ); |
| }); |
| }); |
| }); |
| } |
| |
| void runTests({ |
| required TestSdkConfigurationProvider provider, |
| required CompilationMode compilationMode, |
| required bool canaryFeatures, |
| required bool debug, |
| }) { |
| final project = TestProject.testScopesWithSoundNullSafety; |
| final context = TestContext(project, provider); |
| |
| late AppInspector inspector; |
| |
| group('$compilationMode |', () { |
| setUpAll(() async { |
| setCurrentLogWriter(debug: debug); |
| await context.setUp( |
| compilationMode: compilationMode, |
| canaryFeatures: canaryFeatures, |
| ); |
| final chromeProxyService = context.service; |
| inspector = chromeProxyService.inspector; |
| }); |
| |
| tearDownAll(() async { |
| await context.tearDown(); |
| }); |
| |
| final url = 'org-dartlang-app:///example/scopes/main.dart'; |
| |
| String libraryName(CompilationMode compilationMode) => |
| compilationMode == CompilationMode.frontendServer |
| ? "example/scopes/main.dart" |
| : "example/scopes/main"; |
| |
| String libraryVariableExpression( |
| String variable, |
| CompilationMode compilationMode, |
| ) => |
| '${globalToolConfiguration.loadStrategy.loadModuleSnippet}("dart_sdk").dart.' |
| 'getModuleLibraries("${libraryName(compilationMode)}")' |
| '["$url"]["$variable"];'; |
| |
| String newInterceptorsExpression(String type) => |
| 'new (require("dart_sdk")._interceptors.$type).new()'; |
| |
| final String newDartError = 'new (require("dart_sdk").dart).DartError'; |
| |
| /// A reference to the the variable `libraryPublicFinal`, an instance of |
| /// `MyTestClass`. |
| Future<RemoteObject> libraryPublicFinal(CompilationMode compilationMode) => |
| inspector.jsEvaluate( |
| libraryVariableExpression('libraryPublicFinal', compilationMode), |
| ); |
| |
| /// A reference to the the variable `libraryPublic`, a List of Strings. |
| Future<RemoteObject> libraryPublic(CompilationMode compilationMode) => |
| inspector.jsEvaluate( |
| libraryVariableExpression('libraryPublic', compilationMode), |
| ); |
| |
| group('instanceRef', () { |
| setUp(() => setCurrentLogWriter(debug: debug)); |
| |
| test('for a null', () async { |
| final remoteObject = await libraryPublicFinal(compilationMode); |
| final nullVariable = |
| await inspector.loadField(remoteObject, 'notFinal'); |
| final ref = await inspector.instanceRefFor(nullVariable); |
| expect(ref!.valueAsString, 'null'); |
| expect(ref.kind, InstanceKind.kNull); |
| final classRef = ref.classRef!; |
| expect(classRef.name, 'Null'); |
| expect(classRef.id, 'classes|dart:core|Null'); |
| expect(inspector.isDisplayableObject(ref), isTrue); |
| }); |
| |
| test('for a double', () async { |
| final remoteObject = await libraryPublicFinal(compilationMode); |
| final count = await inspector.loadField(remoteObject, 'count'); |
| final ref = await inspector.instanceRefFor(count); |
| expect(ref!.valueAsString, '0'); |
| expect(ref.kind, InstanceKind.kDouble); |
| final classRef = ref.classRef!; |
| expect(classRef.name, 'Double'); |
| expect(classRef.id, 'classes|dart:core|Double'); |
| expect(inspector.isDisplayableObject(ref), isTrue); |
| }); |
| |
| test('for an object', () async { |
| final remoteObject = await libraryPublicFinal(compilationMode); |
| final count = await inspector.loadField(remoteObject, 'myselfField'); |
| final ref = await inspector.instanceRefFor(count); |
| expect(ref!.kind, InstanceKind.kPlainInstance); |
| final classRef = ref.classRef!; |
| expect(classRef.name, 'MyTestClass<dynamic>'); |
| expect( |
| classRef.id, |
| 'classes|org-dartlang-app:///example/scopes/main.dart' |
| '|MyTestClass<dynamic>'); |
| expect(inspector.isDisplayableObject(ref), isTrue); |
| }); |
| |
| test('for a closure', () async { |
| final remoteObject = await libraryPublicFinal(compilationMode); |
| final properties = |
| await inspector.getProperties(remoteObject.objectId!); |
| final closure = |
| properties.firstWhere((property) => property.name == 'closure'); |
| final ref = await inspector.instanceRefFor(closure.value!); |
| final functionName = ref!.closureFunction!.name; |
| // Older SDKs do not contain function names |
| if (functionName != 'Closure') { |
| expect(functionName, 'someFunction'); |
| } |
| expect(ref.kind, InstanceKind.kClosure); |
| expect(inspector.isDisplayableObject(ref), isTrue); |
| }); |
| |
| test('for a list', () async { |
| final remoteObject = await libraryPublic(compilationMode); |
| final ref = await inspector.instanceRefFor(remoteObject); |
| expect(ref!.length, greaterThan(0)); |
| expect(ref.kind, InstanceKind.kList); |
| expect(ref.classRef!.name, matchListClassName('String')); |
| expect(inspector.isDisplayableObject(ref), isTrue); |
| }); |
| |
| test('for map', () async { |
| final remoteObject = await inspector |
| .jsEvaluate(libraryVariableExpression('map', compilationMode)); |
| final ref = await inspector.instanceRefFor(remoteObject); |
| expect(ref!.length, 2); |
| expect(ref.kind, InstanceKind.kMap); |
| expect(ref.classRef!.name, 'LinkedMap<Object, Object>'); |
| expect(inspector.isDisplayableObject(ref), isTrue); |
| }); |
| |
| test('for an IdentityMap', () async { |
| final remoteObject = await inspector.jsEvaluate( |
| libraryVariableExpression('identityMap', compilationMode), |
| ); |
| final ref = await inspector.instanceRefFor(remoteObject); |
| expect(ref!.length, 2); |
| expect(ref.kind, InstanceKind.kMap); |
| expect(ref.classRef!.name, 'IdentityMap<String, int>'); |
| expect(inspector.isDisplayableObject(ref), isTrue); |
| }); |
| |
| test('for a Dart error', () async { |
| final remoteObject = await inspector.jsEvaluate(newDartError); |
| final ref = await inspector.instanceRefFor(remoteObject); |
| expect(ref!.kind, InstanceKind.kPlainInstance); |
| expect(ref.classRef!.name, 'NativeError'); |
| expect(inspector.isDisplayableObject(ref), isFalse); |
| expect(inspector.isNativeJsError(ref), isTrue); |
| expect(inspector.isNativeJsObject(ref), isFalse); |
| }); |
| |
| test('for a native JavaScript error', () async { |
| final remoteObject = await inspector |
| .jsEvaluate(newInterceptorsExpression('NativeError')); |
| final ref = await inspector.instanceRefFor(remoteObject); |
| expect(ref!.kind, InstanceKind.kPlainInstance); |
| expect(ref.classRef!.name, 'NativeError'); |
| expect(inspector.isDisplayableObject(ref), isFalse); |
| expect(inspector.isNativeJsError(ref), isTrue); |
| expect(inspector.isNativeJsObject(ref), isFalse); |
| }); |
| |
| test('for a native JavaScript type error', () async { |
| final remoteObject = await inspector |
| .jsEvaluate(newInterceptorsExpression('JSNoSuchMethodError')); |
| final ref = await inspector.instanceRefFor(remoteObject); |
| expect(ref!.kind, InstanceKind.kPlainInstance); |
| expect(ref.classRef!.name, 'JSNoSuchMethodError'); |
| expect(inspector.isDisplayableObject(ref), isFalse); |
| expect(inspector.isNativeJsError(ref), isTrue); |
| expect(inspector.isNativeJsObject(ref), isFalse); |
| }); |
| |
| test('for a native JavaScript object', () async { |
| final remoteObject = await inspector |
| .jsEvaluate(newInterceptorsExpression('LegacyJavaScriptObject')); |
| final ref = await inspector.instanceRefFor(remoteObject); |
| expect(ref!.kind, InstanceKind.kPlainInstance); |
| expect(ref.classRef!.name, 'LegacyJavaScriptObject'); |
| expect(inspector.isDisplayableObject(ref), isFalse); |
| expect(inspector.isNativeJsError(ref), isFalse); |
| expect(inspector.isNativeJsObject(ref), isTrue); |
| }); |
| }); |
| |
| group('instance', () { |
| setUp(() => setCurrentLogWriter(debug: debug)); |
| test('for an object', () async { |
| final remoteObject = await libraryPublicFinal(compilationMode); |
| final instance = await inspector.instanceFor(remoteObject); |
| expect(instance!.kind, InstanceKind.kPlainInstance); |
| final classRef = instance.classRef!; |
| expect(classRef, isNotNull); |
| expect(classRef.name, 'MyTestClass<dynamic>'); |
| final boundFieldNames = instance.fields! |
| .map((boundField) => boundField.decl!.name) |
| .toList(); |
| expect(boundFieldNames, [ |
| '_privateField', |
| 'abstractField', |
| 'closure', |
| 'count', |
| 'message', |
| 'myselfField', |
| 'notFinal', |
| 'tornOff', |
| ]); |
| final fieldNames = |
| instance.fields!.map((boundField) => boundField.name).toList(); |
| expect(boundFieldNames, fieldNames); |
| for (var field in instance.fields!) { |
| expect(field.name, isNotNull); |
| expect(field.decl!.declaredType, isNotNull); |
| } |
| expect(inspector.isDisplayableObject(instance), isTrue); |
| }); |
| |
| test('for closure', () async { |
| final remoteObject = await libraryPublicFinal(compilationMode); |
| final properties = |
| await inspector.getProperties(remoteObject.objectId!); |
| final closure = |
| properties.firstWhere((property) => property.name == 'closure'); |
| final instance = await inspector.instanceFor(closure.value!); |
| expect(instance!.kind, InstanceKind.kClosure); |
| expect(instance.classRef!.name, 'Closure'); |
| expect(inspector.isDisplayableObject(instance), isTrue); |
| }); |
| |
| test('for a nested object', () async { |
| final libraryRemoteObject = await libraryPublicFinal(compilationMode); |
| final fieldRemoteObject = |
| await inspector.loadField(libraryRemoteObject, 'myselfField'); |
| final instance = await inspector.instanceFor(fieldRemoteObject); |
| expect(instance!.kind, InstanceKind.kPlainInstance); |
| final classRef = instance.classRef!; |
| expect(classRef, isNotNull); |
| expect(classRef.name, 'MyTestClass<dynamic>'); |
| expect(inspector.isDisplayableObject(instance), isTrue); |
| }); |
| |
| test('for a list', () async { |
| final remote = await libraryPublic(compilationMode); |
| final instance = await inspector.instanceFor(remote); |
| expect(instance!.kind, InstanceKind.kList); |
| final classRef = instance.classRef!; |
| expect(classRef, isNotNull); |
| expect(classRef.name, matchListClassName('String')); |
| final first = instance.elements![0]; |
| expect(first.valueAsString, 'library'); |
| expect(inspector.isDisplayableObject(instance), isTrue); |
| }); |
| |
| test('for a map', () async { |
| final remote = await inspector |
| .jsEvaluate(libraryVariableExpression('map', compilationMode)); |
| final instance = await inspector.instanceFor(remote); |
| expect(instance!.kind, InstanceKind.kMap); |
| final classRef = instance.classRef!; |
| expect(classRef.name, 'LinkedMap<Object, Object>'); |
| final first = instance.associations![0].value as InstanceRef; |
| expect(first.kind, InstanceKind.kList); |
| expect(first.length, 3); |
| final second = instance.associations![1].value as InstanceRef; |
| expect(second.kind, InstanceKind.kString); |
| expect(second.valueAsString, 'something'); |
| expect(inspector.isDisplayableObject(instance), isTrue); |
| }); |
| |
| test('for an identityMap', () async { |
| final remote = await inspector.jsEvaluate( |
| libraryVariableExpression('identityMap', compilationMode), |
| ); |
| final instance = await inspector.instanceFor(remote); |
| expect(instance!.kind, InstanceKind.kMap); |
| final classRef = instance.classRef!; |
| expect(classRef.name, 'IdentityMap<String, int>'); |
| final first = instance.associations![0].value; |
| expect(first.valueAsString, '1'); |
| expect(inspector.isDisplayableObject(instance), isTrue); |
| }); |
| |
| test('for a Dart error', () async { |
| final remoteObject = await inspector.jsEvaluate(newDartError); |
| final instance = await inspector.instanceFor(remoteObject); |
| expect(instance!.kind, InstanceKind.kPlainInstance); |
| expect(instance.classRef!.name, 'NativeError'); |
| expect(inspector.isDisplayableObject(instance), isFalse); |
| expect(inspector.isNativeJsError(instance), isTrue); |
| expect(inspector.isNativeJsObject(instance), isFalse); |
| }); |
| |
| test('for a native JavaScript error', () async { |
| final remoteObject = await inspector |
| .jsEvaluate(newInterceptorsExpression('NativeError')); |
| final instance = await inspector.instanceFor(remoteObject); |
| expect(instance!.kind, InstanceKind.kPlainInstance); |
| expect(instance.classRef!.name, 'NativeError'); |
| expect(inspector.isDisplayableObject(instance), isFalse); |
| expect(inspector.isNativeJsError(instance), isTrue); |
| expect(inspector.isNativeJsObject(instance), isFalse); |
| }); |
| |
| test('for a native JavaScript type error', () async { |
| final remoteObject = await inspector |
| .jsEvaluate(newInterceptorsExpression('JSNoSuchMethodError')); |
| final instance = await inspector.instanceFor(remoteObject); |
| expect(instance!.kind, InstanceKind.kPlainInstance); |
| expect(instance.classRef!.name, 'JSNoSuchMethodError'); |
| expect(inspector.isDisplayableObject(instance), isFalse); |
| expect(inspector.isNativeJsError(instance), isTrue); |
| expect(inspector.isNativeJsObject(instance), isFalse); |
| }); |
| |
| test('for a native JavaScript object', () async { |
| final remoteObject = await inspector |
| .jsEvaluate(newInterceptorsExpression('LegacyJavaScriptObject')); |
| final instance = await inspector.instanceFor(remoteObject); |
| expect(instance!.kind, InstanceKind.kPlainInstance); |
| expect(instance.classRef!.name, 'LegacyJavaScriptObject'); |
| expect(inspector.isDisplayableObject(instance), isFalse); |
| expect(inspector.isNativeJsError(instance), isFalse); |
| expect(inspector.isNativeJsObject(instance), isTrue); |
| }); |
| }); |
| }); |
| } |