|
| 1 | +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file |
| 2 | +// for details. All rights reserved. Use of this source code is governed by a |
| 3 | +// BSD-style license that can be found in the LICENSE file. |
| 4 | + |
| 5 | +import 'package:dwds/src/debugging/inspector.dart'; |
| 6 | +import 'package:dwds/src/loaders/strategy.dart'; |
| 7 | +import 'package:test/test.dart'; |
| 8 | +import 'package:test_common/logging.dart'; |
| 9 | +import 'package:test_common/test_sdk_configuration.dart'; |
| 10 | +import 'package:vm_service/vm_service.dart'; |
| 11 | +import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'; |
| 12 | + |
| 13 | +import '../../fixtures/context.dart'; |
| 14 | +import '../../fixtures/project.dart'; |
| 15 | + |
| 16 | +void runTests({ |
| 17 | + required TestSdkConfigurationProvider provider, |
| 18 | + required CompilationMode compilationMode, |
| 19 | + required bool debug, |
| 20 | +}) { |
| 21 | + final context = |
| 22 | + TestContext(TestProject.testScopesWithSoundNullSafety, provider); |
| 23 | + |
| 24 | + late AppInspector inspector; |
| 25 | + |
| 26 | + group('$compilationMode |', () { |
| 27 | + setUpAll(() async { |
| 28 | + setCurrentLogWriter(debug: debug); |
| 29 | + await context.setUp(compilationMode: compilationMode); |
| 30 | + final chromeProxyService = context.service; |
| 31 | + inspector = chromeProxyService.inspector; |
| 32 | + }); |
| 33 | + |
| 34 | + tearDownAll(() async { |
| 35 | + await context.tearDown(); |
| 36 | + }); |
| 37 | + |
| 38 | + final url = 'org-dartlang-app:///example/scopes/main.dart'; |
| 39 | + |
| 40 | + String libraryName(CompilationMode compilationMode) => |
| 41 | + compilationMode == CompilationMode.frontendServer |
| 42 | + ? "example/scopes/main.dart" |
| 43 | + : "example/scopes/main"; |
| 44 | + |
| 45 | + String libraryVariableExpression( |
| 46 | + String variable, |
| 47 | + CompilationMode compilationMode, |
| 48 | + ) => |
| 49 | + '${globalLoadStrategy.loadModuleSnippet}("dart_sdk").dart.' |
| 50 | + 'getModuleLibraries("${libraryName(compilationMode)}")' |
| 51 | + '["$url"]["$variable"];'; |
| 52 | + |
| 53 | + String interceptorsNewExpression(String type) => |
| 54 | + "require('dart_sdk')._interceptors.$type['_#new#tearOff']()"; |
| 55 | + |
| 56 | + /// A reference to the the variable `libraryPublicFinal`, an instance of |
| 57 | + /// `MyTestClass`. |
| 58 | + Future<RemoteObject> libraryPublicFinal(CompilationMode compilationMode) => |
| 59 | + inspector.jsEvaluate( |
| 60 | + libraryVariableExpression('libraryPublicFinal', compilationMode), |
| 61 | + ); |
| 62 | + |
| 63 | + /// A reference to the the variable `libraryPublic`, a List of Strings. |
| 64 | + Future<RemoteObject> libraryPublic(CompilationMode compilationMode) => |
| 65 | + inspector.jsEvaluate( |
| 66 | + libraryVariableExpression('libraryPublic', compilationMode), |
| 67 | + ); |
| 68 | + |
| 69 | + group('instanceRef', () { |
| 70 | + setUp(() => setCurrentLogWriter(debug: debug)); |
| 71 | + |
| 72 | + test('for a null', () async { |
| 73 | + final remoteObject = await libraryPublicFinal(compilationMode); |
| 74 | + final nullVariable = |
| 75 | + await inspector.loadField(remoteObject, 'notFinal'); |
| 76 | + final ref = await inspector.instanceRefFor(nullVariable); |
| 77 | + expect(ref!.valueAsString, 'null'); |
| 78 | + expect(ref.kind, InstanceKind.kNull); |
| 79 | + final classRef = ref.classRef!; |
| 80 | + expect(classRef.name, 'Null'); |
| 81 | + expect(classRef.id, 'classes|dart:core|Null'); |
| 82 | + expect(inspector.isDisplayableObject(ref), isTrue); |
| 83 | + }); |
| 84 | + |
| 85 | + test('for a double', () async { |
| 86 | + final remoteObject = await libraryPublicFinal(compilationMode); |
| 87 | + final count = await inspector.loadField(remoteObject, 'count'); |
| 88 | + final ref = await inspector.instanceRefFor(count); |
| 89 | + expect(ref!.valueAsString, '0'); |
| 90 | + expect(ref.kind, InstanceKind.kDouble); |
| 91 | + final classRef = ref.classRef!; |
| 92 | + expect(classRef.name, 'Double'); |
| 93 | + expect(classRef.id, 'classes|dart:core|Double'); |
| 94 | + expect(inspector.isDisplayableObject(ref), isTrue); |
| 95 | + }); |
| 96 | + |
| 97 | + test('for a class', () async { |
| 98 | + final remoteObject = await libraryPublicFinal(compilationMode); |
| 99 | + final count = await inspector.loadField(remoteObject, 'myselfField'); |
| 100 | + final ref = await inspector.instanceRefFor(count); |
| 101 | + expect(ref!.kind, InstanceKind.kPlainInstance); |
| 102 | + final classRef = ref.classRef!; |
| 103 | + expect(classRef.name, 'MyTestClass<dynamic>'); |
| 104 | + expect( |
| 105 | + classRef.id, |
| 106 | + 'classes|org-dartlang-app:///example/scopes/main.dart' |
| 107 | + '|MyTestClass<dynamic>'); |
| 108 | + expect(inspector.isDisplayableObject(ref), isTrue); |
| 109 | + }); |
| 110 | + |
| 111 | + test('for closure', () async { |
| 112 | + final remoteObject = await libraryPublicFinal(compilationMode); |
| 113 | + final properties = |
| 114 | + await inspector.getProperties(remoteObject.objectId!); |
| 115 | + final closure = |
| 116 | + properties.firstWhere((property) => property.name == 'closure'); |
| 117 | + final ref = await inspector.instanceRefFor(closure.value!); |
| 118 | + final functionName = ref!.closureFunction!.name; |
| 119 | + // Older SDKs do not contain function names |
| 120 | + if (functionName != 'Closure') { |
| 121 | + expect(functionName, 'someFunction'); |
| 122 | + } |
| 123 | + expect(ref.kind, InstanceKind.kClosure); |
| 124 | + expect(inspector.isDisplayableObject(ref), isTrue); |
| 125 | + }); |
| 126 | + |
| 127 | + test('for a list', () async { |
| 128 | + final remoteObject = await libraryPublic(compilationMode); |
| 129 | + final ref = await inspector.instanceRefFor(remoteObject); |
| 130 | + expect(ref!.length, greaterThan(0)); |
| 131 | + expect(ref.kind, InstanceKind.kList); |
| 132 | + expect(ref.classRef!.name, 'List<String>'); |
| 133 | + expect(inspector.isDisplayableObject(ref), isTrue); |
| 134 | + }); |
| 135 | + |
| 136 | + test('for map', () async { |
| 137 | + final remoteObject = await inspector |
| 138 | + .jsEvaluate(libraryVariableExpression('map', compilationMode)); |
| 139 | + final ref = await inspector.instanceRefFor(remoteObject); |
| 140 | + expect(ref!.length, 2); |
| 141 | + expect(ref.kind, InstanceKind.kMap); |
| 142 | + expect(ref.classRef!.name, 'LinkedMap<Object, Object>'); |
| 143 | + expect(inspector.isDisplayableObject(ref), isTrue); |
| 144 | + }); |
| 145 | + |
| 146 | + test('for an IdentityMap', () async { |
| 147 | + final remoteObject = await inspector.jsEvaluate( |
| 148 | + libraryVariableExpression('identityMap', compilationMode), |
| 149 | + ); |
| 150 | + final ref = await inspector.instanceRefFor(remoteObject); |
| 151 | + expect(ref!.length, 2); |
| 152 | + expect(ref.kind, InstanceKind.kMap); |
| 153 | + expect(ref.classRef!.name, 'IdentityMap<String, int>'); |
| 154 | + expect(inspector.isDisplayableObject(ref), isTrue); |
| 155 | + }); |
| 156 | + |
| 157 | + test('for a native JavaScript error', () async { |
| 158 | + final remoteObject = await inspector |
| 159 | + .jsEvaluate(interceptorsNewExpression('NativeError')); |
| 160 | + final ref = await inspector.instanceRefFor(remoteObject); |
| 161 | + expect(ref!.kind, InstanceKind.kPlainInstance); |
| 162 | + expect(ref.classRef!.name, 'NativeError'); |
| 163 | + expect(inspector.isDisplayableObject(ref), isFalse); |
| 164 | + expect(inspector.isNativeJsError(ref), isTrue); |
| 165 | + expect(inspector.isNativeJsObject(ref), isFalse); |
| 166 | + }); |
| 167 | + |
| 168 | + test('for a native JavaScript type error', () async { |
| 169 | + final remoteObject = await inspector |
| 170 | + .jsEvaluate(interceptorsNewExpression('JSNoSuchMethodError')); |
| 171 | + final ref = await inspector.instanceRefFor(remoteObject); |
| 172 | + expect(ref!.kind, InstanceKind.kPlainInstance); |
| 173 | + expect(ref.classRef!.name, 'JSNoSuchMethodError'); |
| 174 | + expect(inspector.isDisplayableObject(ref), isFalse); |
| 175 | + expect(inspector.isNativeJsError(ref), isTrue); |
| 176 | + expect(inspector.isNativeJsObject(ref), isFalse); |
| 177 | + }); |
| 178 | + |
| 179 | + test('for a native JavaScript object', () async { |
| 180 | + final remoteObject = await inspector |
| 181 | + .jsEvaluate(interceptorsNewExpression('LegacyJavaScriptObject')); |
| 182 | + final ref = await inspector.instanceRefFor(remoteObject); |
| 183 | + expect(ref!.kind, InstanceKind.kPlainInstance); |
| 184 | + expect(ref.classRef!.name, 'LegacyJavaScriptObject'); |
| 185 | + expect(inspector.isDisplayableObject(ref), isFalse); |
| 186 | + expect(inspector.isNativeJsError(ref), isFalse); |
| 187 | + expect(inspector.isNativeJsObject(ref), isTrue); |
| 188 | + }); |
| 189 | + }); |
| 190 | + |
| 191 | + group('instance', () { |
| 192 | + setUp(() => setCurrentLogWriter(debug: debug)); |
| 193 | + test('for class object', () async { |
| 194 | + final remoteObject = await libraryPublicFinal(compilationMode); |
| 195 | + final instance = await inspector.instanceFor(remoteObject); |
| 196 | + expect(instance!.kind, InstanceKind.kPlainInstance); |
| 197 | + final classRef = instance.classRef!; |
| 198 | + expect(classRef, isNotNull); |
| 199 | + expect(classRef.name, 'MyTestClass<dynamic>'); |
| 200 | + final boundFieldNames = instance.fields! |
| 201 | + .map((boundField) => boundField.decl!.name) |
| 202 | + .toList(); |
| 203 | + expect(boundFieldNames, [ |
| 204 | + '_privateField', |
| 205 | + 'abstractField', |
| 206 | + 'closure', |
| 207 | + 'count', |
| 208 | + 'message', |
| 209 | + 'myselfField', |
| 210 | + 'notFinal', |
| 211 | + 'tornOff', |
| 212 | + ]); |
| 213 | + final fieldNames = |
| 214 | + instance.fields!.map((boundField) => boundField.name).toList(); |
| 215 | + expect(boundFieldNames, fieldNames); |
| 216 | + for (var field in instance.fields!) { |
| 217 | + expect(field.name, isNotNull); |
| 218 | + expect(field.decl!.declaredType, isNotNull); |
| 219 | + } |
| 220 | + expect(inspector.isDisplayableObject(instance), isTrue); |
| 221 | + }); |
| 222 | + |
| 223 | + test('for closure', () async { |
| 224 | + final remoteObject = await libraryPublicFinal(compilationMode); |
| 225 | + final properties = |
| 226 | + await inspector.getProperties(remoteObject.objectId!); |
| 227 | + final closure = |
| 228 | + properties.firstWhere((property) => property.name == 'closure'); |
| 229 | + final instance = await inspector.instanceFor(closure.value!); |
| 230 | + expect(instance!.kind, InstanceKind.kClosure); |
| 231 | + expect(instance.classRef!.name, 'Closure'); |
| 232 | + expect(inspector.isDisplayableObject(instance), isTrue); |
| 233 | + }); |
| 234 | + |
| 235 | + test('for a nested class', () async { |
| 236 | + final libraryRemoteObject = await libraryPublicFinal(compilationMode); |
| 237 | + final fieldRemoteObject = |
| 238 | + await inspector.loadField(libraryRemoteObject, 'myselfField'); |
| 239 | + final instance = await inspector.instanceFor(fieldRemoteObject); |
| 240 | + expect(instance!.kind, InstanceKind.kPlainInstance); |
| 241 | + final classRef = instance.classRef!; |
| 242 | + expect(classRef, isNotNull); |
| 243 | + expect(classRef.name, 'MyTestClass<dynamic>'); |
| 244 | + expect(inspector.isDisplayableObject(instance), isTrue); |
| 245 | + }); |
| 246 | + |
| 247 | + test('for a list', () async { |
| 248 | + final remote = await libraryPublic(compilationMode); |
| 249 | + final instance = await inspector.instanceFor(remote); |
| 250 | + expect(instance!.kind, InstanceKind.kList); |
| 251 | + final classRef = instance.classRef!; |
| 252 | + expect(classRef, isNotNull); |
| 253 | + expect(classRef.name, 'List<String>'); |
| 254 | + final first = instance.elements![0]; |
| 255 | + expect(first.valueAsString, 'library'); |
| 256 | + expect(inspector.isDisplayableObject(instance), isTrue); |
| 257 | + }); |
| 258 | + |
| 259 | + test('for a map', () async { |
| 260 | + final remote = await inspector |
| 261 | + .jsEvaluate(libraryVariableExpression('map', compilationMode)); |
| 262 | + final instance = await inspector.instanceFor(remote); |
| 263 | + expect(instance!.kind, InstanceKind.kMap); |
| 264 | + final classRef = instance.classRef!; |
| 265 | + expect(classRef.name, 'LinkedMap<Object, Object>'); |
| 266 | + final first = instance.associations![0].value as InstanceRef; |
| 267 | + expect(first.kind, InstanceKind.kList); |
| 268 | + expect(first.length, 3); |
| 269 | + final second = instance.associations![1].value as InstanceRef; |
| 270 | + expect(second.kind, InstanceKind.kString); |
| 271 | + expect(second.valueAsString, 'something'); |
| 272 | + expect(inspector.isDisplayableObject(instance), isTrue); |
| 273 | + }); |
| 274 | + |
| 275 | + test('for an identityMap', () async { |
| 276 | + final remote = await inspector.jsEvaluate( |
| 277 | + libraryVariableExpression('identityMap', compilationMode), |
| 278 | + ); |
| 279 | + final instance = await inspector.instanceFor(remote); |
| 280 | + expect(instance!.kind, InstanceKind.kMap); |
| 281 | + final classRef = instance.classRef!; |
| 282 | + expect(classRef.name, 'IdentityMap<String, int>'); |
| 283 | + final first = instance.associations![0].value; |
| 284 | + expect(first.valueAsString, '1'); |
| 285 | + expect(inspector.isDisplayableObject(instance), isTrue); |
| 286 | + }); |
| 287 | + |
| 288 | + test('for a class that implements List', () async { |
| 289 | + // The VM only uses kind List for SDK lists, and we follow that. |
| 290 | + final remote = await inspector |
| 291 | + .jsEvaluate(libraryVariableExpression('notAList', compilationMode)); |
| 292 | + final instance = await inspector.instanceFor(remote); |
| 293 | + expect(instance!.kind, InstanceKind.kPlainInstance); |
| 294 | + final classRef = instance.classRef!; |
| 295 | + expect(classRef.name, 'NotReallyAList'); |
| 296 | + expect(instance.elements, isNull); |
| 297 | + final field = instance.fields!.first; |
| 298 | + expect(field.decl!.name, '_internal'); |
| 299 | + expect(inspector.isDisplayableObject(instance), isTrue); |
| 300 | + }); |
| 301 | + |
| 302 | + test('for a native JavaScript error', () async { |
| 303 | + final remoteObject = await inspector |
| 304 | + .jsEvaluate(interceptorsNewExpression('NativeError')); |
| 305 | + final instance = await inspector.instanceFor(remoteObject); |
| 306 | + expect(instance!.kind, InstanceKind.kPlainInstance); |
| 307 | + expect(instance.classRef!.name, 'NativeError'); |
| 308 | + expect(inspector.isDisplayableObject(instance), isFalse); |
| 309 | + expect(inspector.isNativeJsError(instance), isTrue); |
| 310 | + expect(inspector.isNativeJsObject(instance), isFalse); |
| 311 | + }); |
| 312 | + |
| 313 | + test('for a native JavaScript type error', () async { |
| 314 | + final remoteObject = await inspector |
| 315 | + .jsEvaluate(interceptorsNewExpression('JSNoSuchMethodError')); |
| 316 | + final instance = await inspector.instanceFor(remoteObject); |
| 317 | + expect(instance!.kind, InstanceKind.kPlainInstance); |
| 318 | + expect(instance.classRef!.name, 'JSNoSuchMethodError'); |
| 319 | + expect(inspector.isDisplayableObject(instance), isFalse); |
| 320 | + expect(inspector.isNativeJsError(instance), isTrue); |
| 321 | + expect(inspector.isNativeJsObject(instance), isFalse); |
| 322 | + }); |
| 323 | + |
| 324 | + test('for a native JavaScript object', () async { |
| 325 | + final remoteObject = await inspector |
| 326 | + .jsEvaluate(interceptorsNewExpression('LegacyJavaScriptObject')); |
| 327 | + final instance = await inspector.instanceFor(remoteObject); |
| 328 | + expect(instance!.kind, InstanceKind.kPlainInstance); |
| 329 | + expect(instance.classRef!.name, 'LegacyJavaScriptObject'); |
| 330 | + expect(inspector.isDisplayableObject(instance), isFalse); |
| 331 | + expect(inspector.isNativeJsError(instance), isFalse); |
| 332 | + expect(inspector.isNativeJsObject(instance), isTrue); |
| 333 | + }); |
| 334 | + }); |
| 335 | + }); |
| 336 | +} |
0 commit comments