blob: 5265ba0ad33860ff9e3d9e9651421aec5e1fea25 [file] [log] [blame]
// 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/expression_compiler.dart';
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:vm_service/vm_service.dart';
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
import '../../fixtures/context.dart';
import '../../fixtures/project.dart';
import '../../fixtures/utilities.dart';
import 'test_inspector.dart';
void runTypeSystemVerificationTests({
required TestSdkConfigurationProvider provider,
required CompilationMode compilationMode,
required bool canaryFeatures,
required bool debug,
}) {
final project = TestProject.testScopes;
group('$compilationMode |', () {
final context = TestContext(project, provider);
late AppInspector inspector;
setUpAll(() async {
setCurrentLogWriter(debug: debug);
await context.setUp(
testSettings: TestSettings(
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 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'], 'dart_rti.Rti.new');
});
});
});
}
void runTests({
required TestSdkConfigurationProvider provider,
required CompilationMode compilationMode,
required bool canaryFeatures,
required bool debug,
}) {
final project = TestProject.testScopes;
final context = TestContext(project, provider);
late AppInspector inspector;
group('$compilationMode |', () {
setUpAll(() async {
setCurrentLogWriter(debug: debug);
await context.setUp(
testSettings: TestSettings(
compilationMode: compilationMode,
canaryFeatures: canaryFeatures,
moduleFormat: provider.ddcModuleFormat,
),
);
final chromeProxyService = context.service;
inspector = chromeProxyService.inspector;
});
tearDownAll(() async {
await context.tearDown();
});
final libraryUri = 'org-dartlang-app:///example/scopes/main.dart';
String newInterceptorsExpression(String type) =>
'new (require("dart_sdk")._interceptors.$type).new()';
final newDartError = 'new (require("dart_sdk").dart).DartError';
/// A reference to the the variable `libraryPublicFinal`, an instance of
/// `MyTestClass`.
Future<RemoteObject> getLibraryPublicFinalRef() =>
inspector.invoke(libraryUri, 'getLibraryPublicFinal');
/// A reference to the the variable `libraryPublic`, a List of Strings.
Future<RemoteObject> getLibraryPublicRef() =>
inspector.invoke(libraryUri, 'getLibraryPublic');
/// A reference to the variable `map`.
Future<RemoteObject> getMapRef() => inspector.invoke(libraryUri, 'getMap');
/// A reference to the variable `identityMap`.
Future<RemoteObject> getIdentityMapRef() =>
inspector.invoke(libraryUri, 'getIdentityMap');
/// A reference to the variable `stream`.
Future<RemoteObject> getStreamRef() =>
inspector.invoke(libraryUri, 'getStream');
final unsupportedTestMsg =
'This test is not supported with the DDC Library '
"Bundle Format because the dartDevEmbedder doesn't let you access compiled "
'constructors at runtime.';
group('instanceRef', () {
setUp(() => setCurrentLogWriter(debug: debug));
test('for a null', () async {
final remoteObject = await getLibraryPublicFinalRef();
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 getLibraryPublicFinalRef();
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 getLibraryPublicFinalRef();
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 getLibraryPublicFinalRef();
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 getLibraryPublicRef();
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 getMapRef();
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 getIdentityMapRef();
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);
});
// Regression test for https://github.com/dart-lang/webdev/issues/2446.
test('for a stream', () async {
final remoteObject = await getStreamRef();
final ref = await inspector.instanceRefFor(remoteObject);
expect(ref!.kind, InstanceKind.kPlainInstance);
final classRef = ref.classRef!;
expect(classRef.name, '_ControllerStream<int>');
expect(classRef.id, 'classes|dart:async|_ControllerStream<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);
},
skip:
provider.ddcModuleFormat == ModuleFormat.ddc &&
canaryFeatures == true
? unsupportedTestMsg
: null,
);
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);
},
skip:
provider.ddcModuleFormat == ModuleFormat.ddc &&
canaryFeatures == true
? unsupportedTestMsg
: null,
);
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);
},
skip:
provider.ddcModuleFormat == ModuleFormat.ddc &&
canaryFeatures == true
? unsupportedTestMsg
: null,
);
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);
},
skip:
provider.ddcModuleFormat == ModuleFormat.ddc &&
canaryFeatures == true
? unsupportedTestMsg
: null,
);
});
group('instance', () {
setUp(() => setCurrentLogWriter(debug: debug));
test('for an object', () async {
final remoteObject = await getLibraryPublicFinalRef();
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 (final 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 getLibraryPublicFinalRef();
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 getLibraryPublicFinalRef();
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 getLibraryPublicRef();
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 getMapRef();
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 getIdentityMapRef();
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);
});
// Regression test for https://github.com/dart-lang/webdev/issues/2446.
test('for a stream', () async {
final remote = await getStreamRef();
final instance = await inspector.instanceFor(remote);
expect(instance!.kind, InstanceKind.kPlainInstance);
final classRef = instance.classRef!;
expect(classRef.name, '_ControllerStream<int>');
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);
},
skip:
provider.ddcModuleFormat == ModuleFormat.ddc &&
canaryFeatures == true
? unsupportedTestMsg
: null,
);
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);
},
skip:
provider.ddcModuleFormat == ModuleFormat.ddc &&
canaryFeatures == true
? unsupportedTestMsg
: null,
);
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);
},
skip:
provider.ddcModuleFormat == ModuleFormat.ddc &&
canaryFeatures == true
? unsupportedTestMsg
: null,
);
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);
},
skip:
provider.ddcModuleFormat == ModuleFormat.ddc &&
canaryFeatures == true
? unsupportedTestMsg
: null,
);
});
});
}