| // Copyright (c) 2017, 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. |
| |
| /// Equivalence test functions for data objects. |
| |
| library dart2js.equivalence.functions; |
| |
| import 'package:expect/expect.dart'; |
| import 'package:compiler/src/common/resolution.dart'; |
| import 'package:compiler/src/common_elements.dart'; |
| import 'package:compiler/src/compiler.dart'; |
| import 'package:compiler/src/constants/values.dart'; |
| import 'package:compiler/src/elements/elements.dart'; |
| import 'package:compiler/src/elements/entities.dart'; |
| import 'package:compiler/src/elements/types.dart'; |
| import 'package:compiler/src/enqueue.dart'; |
| import 'package:compiler/src/js/js_debug.dart' as js; |
| import 'package:compiler/src/js_backend/backend.dart'; |
| import 'package:compiler/src/js_backend/backend_usage.dart'; |
| import 'package:compiler/src/js_backend/enqueuer.dart'; |
| import 'package:compiler/src/js_backend/native_data.dart'; |
| import 'package:compiler/src/js_backend/interceptor_data.dart'; |
| import 'package:compiler/src/js_emitter/code_emitter_task.dart'; |
| import 'package:compiler/src/js_emitter/model.dart'; |
| import 'package:compiler/src/serialization/equivalence.dart'; |
| import 'package:compiler/src/universe/class_set.dart'; |
| import 'package:compiler/src/universe/world_builder.dart'; |
| import 'package:compiler/src/world.dart'; |
| import 'package:js_ast/js_ast.dart' as js; |
| import 'check_helpers.dart'; |
| |
| void checkClosedWorlds(ClosedWorld closedWorld1, ClosedWorld closedWorld2, |
| {TestStrategy strategy: const TestStrategy(), |
| bool allowExtra: false, |
| bool verbose: false, |
| bool allowMissingClosureClasses: false}) { |
| if (verbose) { |
| print(closedWorld1.dump()); |
| print(closedWorld2.dump()); |
| } |
| checkClassHierarchyNodes( |
| closedWorld1, |
| closedWorld2, |
| closedWorld1 |
| .getClassHierarchyNode(closedWorld1.commonElements.objectClass), |
| closedWorld2 |
| .getClassHierarchyNode(closedWorld2.commonElements.objectClass), |
| strategy.elementEquivalence, |
| verbose: verbose, |
| allowMissingClosureClasses: allowMissingClosureClasses); |
| |
| checkNativeData(closedWorld1.nativeData, closedWorld2.nativeData, |
| strategy: strategy, allowExtra: allowExtra, verbose: verbose); |
| checkInterceptorData(closedWorld1.interceptorData, |
| closedWorld2.interceptorData, strategy.elementEquivalence, |
| verbose: verbose); |
| } |
| |
| void checkNativeData(NativeDataImpl data1, NativeDataImpl data2, |
| {TestStrategy strategy: const TestStrategy(), |
| bool allowExtra: false, |
| bool verbose: false}) { |
| checkMapEquivalence(data1, data2, 'nativeMemberName', data1.nativeMemberName, |
| data2.nativeMemberName, strategy.elementEquivalence, equality, |
| allowExtra: allowExtra); |
| |
| checkMapEquivalence( |
| data1, |
| data2, |
| 'nativeMethodBehavior', |
| data1.nativeMethodBehavior, |
| data2.nativeMethodBehavior, |
| strategy.elementEquivalence, |
| (a, b) => testNativeBehavior(a, b, strategy: strategy), |
| allowExtra: allowExtra); |
| |
| checkMapEquivalence( |
| data1, |
| data2, |
| 'nativeFieldLoadBehavior', |
| data1.nativeFieldLoadBehavior, |
| data2.nativeFieldLoadBehavior, |
| strategy.elementEquivalence, |
| (a, b) => testNativeBehavior(a, b, strategy: strategy), |
| allowExtra: allowExtra); |
| |
| checkMapEquivalence( |
| data1, |
| data2, |
| 'nativeFieldStoreBehavior', |
| data1.nativeFieldStoreBehavior, |
| data2.nativeFieldStoreBehavior, |
| strategy.elementEquivalence, |
| (a, b) => testNativeBehavior(a, b, strategy: strategy), |
| allowExtra: allowExtra); |
| |
| checkMapEquivalence( |
| data1, |
| data2, |
| 'jsInteropLibraryNames', |
| data1.jsInteropLibraries, |
| data2.jsInteropLibraries, |
| strategy.elementEquivalence, |
| equality); |
| |
| checkSetEquivalence( |
| data1, |
| data2, |
| 'anonymousJsInteropClasses', |
| data1.anonymousJsInteropClasses, |
| data2.anonymousJsInteropClasses, |
| strategy.elementEquivalence); |
| |
| checkMapEquivalence( |
| data1, |
| data2, |
| 'jsInteropClassNames', |
| data1.jsInteropClasses, |
| data2.jsInteropClasses, |
| strategy.elementEquivalence, |
| equality); |
| |
| checkMapEquivalence( |
| data1, |
| data2, |
| 'jsInteropMemberNames', |
| data1.jsInteropMembers, |
| data2.jsInteropMembers, |
| strategy.elementEquivalence, |
| equality); |
| } |
| |
| void checkInterceptorData(InterceptorDataImpl data1, InterceptorDataImpl data2, |
| bool elementEquivalence(Entity a, Entity b), |
| {bool verbose: false}) { |
| checkMapEquivalence( |
| data1, |
| data2, |
| 'interceptedElements', |
| data1.interceptedMembers, |
| data2.interceptedMembers, |
| equality, |
| (a, b) => areSetsEquivalent(a, b, elementEquivalence)); |
| |
| checkSetEquivalence(data1, data2, 'interceptedClasses', |
| data1.interceptedClasses, data2.interceptedClasses, elementEquivalence); |
| |
| checkSetEquivalence( |
| data1, |
| data2, |
| 'classesMixedIntoInterceptedClasses', |
| data1.classesMixedIntoInterceptedClasses, |
| data2.classesMixedIntoInterceptedClasses, |
| elementEquivalence); |
| } |
| |
| void checkClassHierarchyNodes( |
| ClosedWorld closedWorld1, |
| ClosedWorld closedWorld2, |
| ClassHierarchyNode node1, |
| ClassHierarchyNode node2, |
| bool elementEquivalence(Entity a, Entity b), |
| {bool verbose: false, |
| bool allowMissingClosureClasses: false}) { |
| if (verbose) { |
| print('Checking $node1 vs $node2'); |
| } |
| ClassEntity cls1 = node1.cls; |
| ClassEntity cls2 = node2.cls; |
| Expect.isTrue(elementEquivalence(cls1, cls2), |
| "Element identity mismatch for ${cls1} vs ${cls2}."); |
| Expect.equals( |
| node1.isDirectlyInstantiated, |
| node2.isDirectlyInstantiated, |
| "Value mismatch for 'isDirectlyInstantiated' " |
| "for ${cls1} vs ${cls2}."); |
| Expect.equals( |
| node1.isIndirectlyInstantiated, |
| node2.isIndirectlyInstantiated, |
| "Value mismatch for 'isIndirectlyInstantiated' " |
| "for ${node1.cls} vs ${node2.cls}."); |
| // TODO(johnniwinther): Enforce a canonical and stable order on direct |
| // subclasses. |
| for (ClassHierarchyNode child in node1.directSubclasses) { |
| bool found = false; |
| for (ClassHierarchyNode other in node2.directSubclasses) { |
| ClassEntity child1 = child.cls; |
| ClassEntity child2 = other.cls; |
| if (elementEquivalence(child1, child2)) { |
| checkClassHierarchyNodes( |
| closedWorld1, closedWorld2, child, other, elementEquivalence, |
| verbose: verbose, |
| allowMissingClosureClasses: allowMissingClosureClasses); |
| found = true; |
| break; |
| } |
| } |
| if (!found && (!child.cls.isClosure || !allowMissingClosureClasses)) { |
| if (child.isInstantiated) { |
| print('Missing subclass ${child.cls} of ${node1.cls} ' |
| 'in ${node2.directSubclasses}'); |
| print(closedWorld1.dump( |
| verbose ? closedWorld1.commonElements.objectClass : node1.cls)); |
| print(closedWorld2.dump( |
| verbose ? closedWorld2.commonElements.objectClass : node2.cls)); |
| } |
| Expect.isFalse( |
| child.isInstantiated, |
| 'Missing subclass ${child.cls} of ${node1.cls} in ' |
| '${node2.directSubclasses}'); |
| } |
| } |
| checkMixinUses( |
| closedWorld1, closedWorld2, node1.cls, node2.cls, elementEquivalence, |
| verbose: verbose); |
| ClassSet classSet1 = closedWorld1.getClassSet(cls1); |
| ClassSet classSet2 = closedWorld2.getClassSet(cls2); |
| Expect.isNotNull(classSet1, "Missing ClassSet for $cls1"); |
| Expect.isNotNull(classSet2, "Missing ClassSet for $cls2"); |
| checkClassSets( |
| closedWorld1, closedWorld2, classSet1, classSet2, elementEquivalence, |
| verbose: verbose, allowMissingClosureClasses: allowMissingClosureClasses); |
| } |
| |
| void checkClassSets( |
| ClosedWorld closedWorld1, |
| ClosedWorld closedWorld2, |
| ClassSet classSet1, |
| ClassSet classSet2, |
| bool elementEquivalence(Entity a, Entity b), |
| {bool verbose: false, |
| bool allowMissingClosureClasses: false}) { |
| for (ClassHierarchyNode child in classSet1.subtypeNodes) { |
| bool found = false; |
| for (ClassHierarchyNode other in classSet2.subtypeNodes) { |
| ClassEntity child1 = child.cls; |
| ClassEntity child2 = other.cls; |
| if (elementEquivalence(child1, child2)) { |
| found = true; |
| break; |
| } |
| } |
| if (!found && (!child.cls.isClosure || !allowMissingClosureClasses)) { |
| if (child.isInstantiated) { |
| print('Missing subtype ${child.cls} of ${classSet1.cls} ' |
| 'in ${classSet2.subtypeNodes}'); |
| print(closedWorld1.dump( |
| verbose ? closedWorld1.commonElements.objectClass : classSet1.cls)); |
| print(closedWorld2.dump( |
| verbose ? closedWorld2.commonElements.objectClass : classSet2.cls)); |
| } |
| Expect.isFalse( |
| child.isInstantiated, |
| 'Missing subclass ${child.cls} of ${classSet1.cls} in ' |
| '${classSet2.subtypeNodes}'); |
| } |
| } |
| } |
| |
| void checkMixinUses( |
| ClosedWorld closedWorld1, |
| ClosedWorld closedWorld2, |
| ClassEntity class1, |
| ClassEntity class2, |
| bool elementEquivalence(Entity a, Entity b), |
| {bool verbose: false}) { |
| checkSets(closedWorld1.mixinUsesOf(class1), closedWorld2.mixinUsesOf(class2), |
| "Mixin uses of $class1 vs $class2", elementEquivalence, |
| verbose: verbose); |
| } |
| |
| /// Check member property equivalence between all members common to [compiler1] |
| /// and [compiler2]. |
| void checkLoadedLibraryMembers( |
| Compiler compiler1, |
| Compiler compiler2, |
| bool hasProperty(Element member1), |
| void checkMemberProperties(Compiler compiler1, Element member1, |
| Compiler compiler2, Element member2, |
| {bool verbose}), |
| {bool verbose: false}) { |
| void checkMembers(Element member1, Element member2) { |
| if (member1.isClass && member2.isClass) { |
| ClassElement class1 = member1; |
| ClassElement class2 = member2; |
| if (!class1.isResolved) return; |
| |
| if (hasProperty(member1)) { |
| if (areElementsEquivalent(member1, member2)) { |
| checkMemberProperties(compiler1, member1, compiler2, member2, |
| verbose: verbose); |
| } |
| } |
| |
| class1.forEachLocalMember((m1) { |
| checkMembers(m1, class2.localLookup(m1.name)); |
| }); |
| ClassElement superclass1 = class1.superclass; |
| ClassElement superclass2 = class2.superclass; |
| while (superclass1 != null && superclass1.isUnnamedMixinApplication) { |
| for (ConstructorElement c1 in superclass1.constructors) { |
| checkMembers(c1, superclass2.lookupConstructor(c1.name)); |
| } |
| superclass1 = superclass1.superclass; |
| superclass2 = superclass2.superclass; |
| } |
| return; |
| } |
| |
| if (!hasProperty(member1)) { |
| return; |
| } |
| |
| if (member2 == null) { |
| throw 'Missing member for ${member1}'; |
| } |
| |
| if (areElementsEquivalent(member1, member2)) { |
| checkMemberProperties(compiler1, member1, compiler2, member2, |
| verbose: verbose); |
| } |
| } |
| |
| for (LibraryElement library1 in compiler1.libraryLoader.libraries) { |
| LibraryElement library2 = |
| compiler2.libraryLoader.lookupLibrary(library1.canonicalUri); |
| if (library2 != null) { |
| library1.forEachLocalMember((Element member1) { |
| checkMembers(member1, library2.localLookup(member1.name)); |
| }); |
| } |
| } |
| } |
| |
| /// Check equivalence of all resolution impacts. |
| void checkAllImpacts(Compiler compiler1, Compiler compiler2, |
| {bool verbose: false}) { |
| checkLoadedLibraryMembers(compiler1, compiler2, (Element member1) { |
| return compiler1.resolution.hasResolutionImpact(member1); |
| }, checkImpacts, verbose: verbose); |
| } |
| |
| /// Check equivalence of resolution impact for [member1] and [member2]. |
| void checkImpacts( |
| Compiler compiler1, Element member1, Compiler compiler2, Element member2, |
| {bool verbose: false}) { |
| ResolutionImpact impact1 = compiler1.resolution.getResolutionImpact(member1); |
| ResolutionImpact impact2 = compiler2.resolution.getResolutionImpact(member2); |
| |
| if (impact1 == null && impact2 == null) return; |
| |
| if (verbose) { |
| print('Checking impacts for $member1 vs $member2'); |
| } |
| |
| if (impact1 == null) { |
| throw 'Missing impact for $member1. $member2 has $impact2'; |
| } |
| if (impact2 == null) { |
| throw 'Missing impact for $member2. $member1 has $impact1'; |
| } |
| |
| testResolutionImpactEquivalence(impact1, impact2, |
| strategy: const CheckStrategy()); |
| } |
| |
| void checkAllResolvedAsts(Compiler compiler1, Compiler compiler2, |
| {bool verbose: false}) { |
| checkLoadedLibraryMembers(compiler1, compiler2, (Element member1) { |
| return member1 is ExecutableElement && |
| compiler1.resolution.hasResolvedAst(member1); |
| }, checkResolvedAsts, verbose: verbose); |
| } |
| |
| /// Check equivalence of [impact1] and [impact2]. |
| void checkResolvedAsts( |
| Compiler compiler1, Element member1, Compiler compiler2, Element member2, |
| {bool verbose: false}) { |
| if (!compiler2.serialization.isDeserialized(member2)) { |
| return; |
| } |
| ResolvedAst resolvedAst1 = compiler1.resolution.getResolvedAst(member1); |
| ResolvedAst resolvedAst2 = compiler2.serialization.getResolvedAst(member2); |
| |
| if (resolvedAst1 == null || resolvedAst2 == null) return; |
| |
| if (verbose) { |
| print('Checking resolved asts for $member1 vs $member2'); |
| } |
| |
| testResolvedAstEquivalence(resolvedAst1, resolvedAst2, const CheckStrategy()); |
| } |
| |
| void checkNativeClasses( |
| Compiler compiler1, Compiler compiler2, TestStrategy strategy) { |
| Iterable<ClassEntity> nativeClasses1 = compiler1 |
| .backend.nativeResolutionEnqueuerForTesting.nativeClassesForTesting; |
| Iterable<ClassEntity> nativeClasses2 = compiler2 |
| .backend.nativeResolutionEnqueuerForTesting.nativeClassesForTesting; |
| |
| checkSetEquivalence(compiler1, compiler2, 'nativeClasses', nativeClasses1, |
| nativeClasses2, strategy.elementEquivalence); |
| |
| Iterable<ClassEntity> liveNativeClasses1 = |
| compiler1.backend.nativeResolutionEnqueuerForTesting.liveNativeClasses; |
| Iterable<ClassEntity> liveNativeClasses2 = |
| compiler2.backend.nativeResolutionEnqueuerForTesting.liveNativeClasses; |
| |
| checkSetEquivalence(compiler1, compiler2, 'liveNativeClasses', |
| liveNativeClasses1, liveNativeClasses2, strategy.elementEquivalence); |
| } |
| |
| void checkNativeBasicData(NativeBasicDataImpl data1, NativeBasicDataImpl data2, |
| TestStrategy strategy) { |
| checkMapEquivalence( |
| data1, |
| data2, |
| 'nativeClassTagInfo', |
| data1.nativeClassTagInfo, |
| data2.nativeClassTagInfo, |
| strategy.elementEquivalence, |
| (a, b) => a == b); |
| checkSetEquivalence( |
| data1, |
| data2, |
| 'jsInteropLibraries', |
| data1.jsInteropLibraries.keys, |
| data2.jsInteropLibraries.keys, |
| strategy.elementEquivalence); |
| checkSetEquivalence( |
| data1, |
| data2, |
| 'jsInteropClasses', |
| data1.jsInteropClasses.keys, |
| data2.jsInteropClasses.keys, |
| strategy.elementEquivalence); |
| } |
| |
| void checkBackendUsage( |
| BackendUsageImpl usage1, BackendUsageImpl usage2, TestStrategy strategy) { |
| checkSetEquivalence( |
| usage1, |
| usage2, |
| 'globalClassDependencies', |
| usage1.globalClassDependencies, |
| usage2.globalClassDependencies, |
| strategy.elementEquivalence); |
| checkSetEquivalence( |
| usage1, |
| usage2, |
| 'globalFunctionDependencies', |
| usage1.globalFunctionDependencies, |
| usage2.globalFunctionDependencies, |
| strategy.elementEquivalence); |
| checkSetEquivalence( |
| usage1, |
| usage2, |
| 'helperClassesUsed', |
| usage1.helperClassesUsed, |
| usage2.helperClassesUsed, |
| strategy.elementEquivalence); |
| checkSetEquivalence( |
| usage1, |
| usage2, |
| 'helperFunctionsUsed', |
| usage1.helperFunctionsUsed, |
| usage2.helperFunctionsUsed, |
| strategy.elementEquivalence); |
| check( |
| usage1, |
| usage2, |
| 'needToInitializeIsolateAffinityTag', |
| usage1.needToInitializeIsolateAffinityTag, |
| usage2.needToInitializeIsolateAffinityTag); |
| check( |
| usage1, |
| usage2, |
| 'needToInitializeDispatchProperty', |
| usage1.needToInitializeDispatchProperty, |
| usage2.needToInitializeDispatchProperty); |
| check(usage1, usage2, 'requiresPreamble', usage1.requiresPreamble, |
| usage2.requiresPreamble); |
| check(usage1, usage2, 'isInvokeOnUsed', usage1.isInvokeOnUsed, |
| usage2.isInvokeOnUsed); |
| check(usage1, usage2, 'isRuntimeTypeUsed', usage1.isRuntimeTypeUsed, |
| usage2.isRuntimeTypeUsed); |
| check(usage1, usage2, 'isIsolateInUse', usage1.isIsolateInUse, |
| usage2.isIsolateInUse); |
| check(usage1, usage2, 'isFunctionApplyUsed', usage1.isFunctionApplyUsed, |
| usage2.isFunctionApplyUsed); |
| check(usage1, usage2, 'isNoSuchMethodUsed', usage1.isNoSuchMethodUsed, |
| usage2.isNoSuchMethodUsed); |
| } |
| |
| checkElementEnvironment(ElementEnvironment env1, ElementEnvironment env2, |
| DartTypes types1, DartTypes types2, TestStrategy strategy, |
| {bool checkConstructorBodies: false}) { |
| strategy.testElements( |
| env1, env2, 'mainLibrary', env1.mainLibrary, env2.mainLibrary); |
| strategy.testElements( |
| env1, env2, 'mainFunction', env1.mainFunction, env2.mainFunction); |
| |
| Iterable<ConstantValue> filterMetadata(Iterable<ConstantValue> constants) { |
| const skippedMetadata = const [ |
| // Annotations on patches are not included in the patched sdk. |
| 'ConstructedConstant(_Patch())', |
| 'ConstructedConstant(NoInline())', |
| |
| // Inserted by TargetImplementation. Should only be in the VM target. |
| 'ConstructedConstant(ExternalName(name=StringConstant("")))', |
| ]; |
| |
| return constants |
| .where((c) => !skippedMetadata.contains(c.toStructuredText())); |
| } |
| |
| Iterable<LibraryEntity> filterLibraries(Iterable<LibraryEntity> libraries) { |
| List<Uri> skippedLibraries = [ |
| Uri.parse('dart:mirrors'), |
| Uri.parse('dart:_js_mirrors'), |
| Uri.parse('dart:js'), |
| Uri.parse('dart:js_util'), |
| Uri.parse('dart:_chrome'), |
| Uri.parse('dart:io'), |
| Uri.parse('dart:_http'), |
| Uri.parse('dart:developer'), |
| ]; |
| return libraries.where((l) => !skippedLibraries.contains(l.canonicalUri)); |
| } |
| |
| checkMembers(MemberEntity member1, MemberEntity member2) { |
| Expect.equals(env1.isDeferredLoadLibraryGetter(member1), |
| env2.isDeferredLoadLibraryGetter(member2)); |
| |
| checkListEquivalence( |
| member1, |
| member2, |
| 'metadata', |
| filterMetadata(env1.getMemberMetadata(member1)), |
| filterMetadata(env2.getMemberMetadata(member2)), |
| strategy.testConstantValues); |
| |
| if (member1 is FunctionEntity && member2 is FunctionEntity) { |
| if (member1 is ConstructorElement && |
| member1.definingConstructor != null) { |
| // TODO(johnniwinther): Test these. Currently these are sometimes |
| // correctly typed, sometimes using dynamic instead of parameter and |
| // return types. |
| return; |
| } |
| check(member1, member2, 'getFunctionType', env1.getFunctionType(member1), |
| env2.getFunctionType(member2), strategy.typeEquivalence); |
| } |
| } |
| |
| checkSetEquivalence(env1, env2, 'libraries', filterLibraries(env1.libraries), |
| filterLibraries(env2.libraries), strategy.elementEquivalence, |
| onSameElement: (LibraryEntity lib1, LibraryEntity lib2) { |
| Expect.identical(lib1, env1.lookupLibrary(lib1.canonicalUri)); |
| Expect.identical(lib2, env2.lookupLibrary(lib2.canonicalUri)); |
| |
| // TODO(johnniwinther): Check libraryName. |
| |
| List<ClassEntity> classes2 = <ClassEntity>[]; |
| env1.forEachClass(lib1, (ClassEntity cls1) { |
| Expect.identical(cls1, env1.lookupClass(lib1, cls1.name)); |
| |
| String className = cls1.name; |
| ClassEntity cls2 = env2.lookupClass(lib2, className); |
| Expect.isNotNull(cls2, 'Missing class $className in $lib2'); |
| Expect.identical(cls2, env2.lookupClass(lib2, cls2.name)); |
| |
| check(lib1, lib2, 'class:${className}', cls1, cls2, |
| strategy.elementEquivalence); |
| |
| Expect.equals(env1.isGenericClass(cls1), env2.isGenericClass(cls2)); |
| |
| check( |
| cls1, |
| cls2, |
| 'superclass', |
| env1.getSuperClass(cls1, skipUnnamedMixinApplications: false), |
| env2.getSuperClass(cls2, skipUnnamedMixinApplications: false), |
| strategy.elementEquivalence); |
| check( |
| cls1, |
| cls2, |
| 'superclass', |
| env1.getSuperClass(cls1, skipUnnamedMixinApplications: true), |
| env2.getSuperClass(cls2, skipUnnamedMixinApplications: true), |
| strategy.elementEquivalence); |
| |
| InterfaceType thisType1 = env1.getThisType(cls1); |
| InterfaceType thisType2 = env2.getThisType(cls2); |
| check(cls1, cls2, 'thisType', thisType1, thisType2, |
| strategy.typeEquivalence); |
| check(cls1, cls2, 'rawType', env1.getRawType(cls1), env2.getRawType(cls2), |
| strategy.typeEquivalence); |
| check( |
| cls1, |
| cls2, |
| 'createInterfaceType', |
| env1.createInterfaceType(cls1, thisType1.typeArguments), |
| env2.createInterfaceType(cls2, thisType2.typeArguments), |
| strategy.typeEquivalence); |
| |
| check(cls1, cls2, 'isGenericClass', env1.isGenericClass(cls1), |
| env2.isGenericClass(cls2)); |
| check(cls1, cls2, 'isMixinApplication', env1.isMixinApplication(cls1), |
| env2.isMixinApplication(cls2)); |
| check( |
| cls1, |
| cls2, |
| 'isUnnamedMixinApplication', |
| env1.isUnnamedMixinApplication(cls1), |
| env2.isUnnamedMixinApplication(cls2)); |
| check( |
| cls1, |
| cls2, |
| 'getEffectiveMixinClass', |
| env1.getEffectiveMixinClass(cls1), |
| env2.getEffectiveMixinClass(cls2), |
| strategy.elementEquivalence); |
| |
| // TODO(johnniwinther): Check type variable bounds. |
| |
| check(cls1, cls2, 'callType', types1.getCallType(thisType1), |
| types2.getCallType(thisType2), strategy.typeEquivalence); |
| |
| List<InterfaceType> supertypes1 = <InterfaceType>[]; |
| env1.forEachSupertype(cls1, supertypes1.add); |
| List<InterfaceType> supertypes2 = <InterfaceType>[]; |
| env2.forEachSupertype(cls2, supertypes2.add); |
| checkLists(supertypes1, supertypes2, 'supertypes on $cls1, $cls2', |
| strategy.typeEquivalence); |
| |
| List<ClassEntity> mixins1 = <ClassEntity>[]; |
| env1.forEachMixin(cls1, mixins1.add); |
| List<ClassEntity> mixins2 = <ClassEntity>[]; |
| env2.forEachMixin(cls2, mixins2.add); |
| checkLists(mixins1, mixins2, 'mixins on $cls1, $cls2', |
| strategy.elementEquivalence); |
| |
| Map<MemberEntity, ClassEntity> members1 = <MemberEntity, ClassEntity>{}; |
| Map<MemberEntity, ClassEntity> members2 = <MemberEntity, ClassEntity>{}; |
| env1.forEachClassMember(cls1, |
| (ClassEntity declarer1, MemberEntity member1) { |
| if (cls1 == declarer1) { |
| Expect.identical( |
| member1, |
| env1.lookupClassMember(cls1, member1.name, |
| setter: member1.isSetter)); |
| } |
| members1[member1] = declarer1; |
| }); |
| env2.forEachClassMember(cls2, |
| (ClassEntity declarer2, MemberEntity member2) { |
| if (cls2 == declarer2) { |
| Expect.identical( |
| member2, |
| env2.lookupClassMember(cls2, member2.name, |
| setter: member2.isSetter)); |
| } |
| members2[member2] = declarer2; |
| }); |
| checkMapEquivalence(cls1, cls2, 'members', members1, members2, (a, b) { |
| bool result = strategy.elementEquivalence(a, b); |
| if (result) checkMembers(a, b); |
| return result; |
| }, strategy.elementEquivalence); |
| |
| Set<ConstructorEntity> constructors2 = new Set<ConstructorEntity>(); |
| env1.forEachConstructor(cls1, (ConstructorEntity constructor1) { |
| Expect.identical( |
| constructor1, env1.lookupConstructor(cls1, constructor1.name)); |
| |
| String constructorName = constructor1.name; |
| ConstructorEntity constructor2 = |
| env2.lookupConstructor(cls2, constructorName); |
| Expect.isNotNull( |
| constructor2, "Missing constructor for $constructor1 in $cls2 "); |
| Expect.identical( |
| constructor2, env2.lookupConstructor(cls2, constructor2.name)); |
| |
| constructors2.add(constructor2); |
| |
| check(cls1, cls2, 'constructor:${constructorName}', constructor1, |
| constructor2, strategy.elementEquivalence); |
| |
| checkMembers(constructor1, constructor2); |
| }); |
| env2.forEachConstructor(cls2, (ConstructorEntity constructor2) { |
| Expect.isTrue(constructors2.contains(constructor2), |
| "Extra constructor $constructor2 in $cls2"); |
| }); |
| |
| if (checkConstructorBodies) { |
| Set<ConstructorBodyEntity> constructorBodies1 = |
| new Set<ConstructorBodyEntity>(); |
| Set<ConstructorBodyEntity> constructorBodies2 = |
| new Set<ConstructorBodyEntity>(); |
| env1.forEachConstructorBody(cls1, |
| (ConstructorBodyEntity constructorBody1) { |
| constructorBodies1.add(constructorBody1); |
| }); |
| env2.forEachConstructorBody(cls2, |
| (ConstructorBodyEntity constructorBody2) { |
| constructorBodies2.add(constructorBody2); |
| }); |
| checkSetEquivalence(cls1, cls2, 'constructor-bodies', |
| constructorBodies1, constructorBodies2, strategy.elementEquivalence, |
| onSameElement: (ConstructorBodyEntity constructorBody1, |
| ConstructorBodyEntity constructorBody2) { |
| check(constructorBody1, constructorBody2, 'name', |
| constructorBody1.name, constructorBody2.name); |
| |
| checkMembers(constructorBody1, constructorBody2); |
| }); |
| } |
| classes2.add(cls2); |
| }); |
| env2.forEachClass(lib2, (ClassEntity cls2) { |
| Expect.isTrue(classes2.contains(cls2), "Extra class $cls2 in $lib2"); |
| }); |
| |
| Set<MemberEntity> members2 = new Set<MemberEntity>(); |
| env1.forEachLibraryMember(lib1, (MemberEntity member1) { |
| Expect.identical( |
| member1, |
| env1.lookupLibraryMember(lib1, member1.name, |
| setter: member1.isSetter)); |
| |
| String memberName = member1.name; |
| MemberEntity member2 = |
| env2.lookupLibraryMember(lib2, memberName, setter: member1.isSetter); |
| Expect.isNotNull(member2, 'Missing member for $member1 in $lib2'); |
| Expect.identical( |
| member2, |
| env2.lookupLibraryMember(lib2, member2.name, |
| setter: member2.isSetter)); |
| |
| members2.add(member2); |
| |
| check(lib1, lib2, 'member:${memberName}', member1, member2, |
| strategy.elementEquivalence); |
| |
| checkMembers(member1, member2); |
| }); |
| env2.forEachLibraryMember(lib2, (MemberEntity member2) { |
| Expect.isTrue( |
| members2.contains(member2), "Extra member $member2 in $lib2"); |
| }); |
| }); |
| // TODO(johnniwinther): Check getLocalFunctionType and getUnaliasedType ? |
| } |
| |
| bool areInstantiationInfosEquivalent( |
| InstantiationInfo info1, |
| InstantiationInfo info2, |
| bool elementEquivalence(Entity a, Entity b), |
| bool typeEquivalence(DartType a, DartType b)) { |
| checkMaps( |
| info1.instantiationMap, |
| info2.instantiationMap, |
| 'instantiationMap of\n ' |
| '${info1.instantiationMap}\nvs ${info2.instantiationMap}', |
| elementEquivalence, |
| (a, b) => areSetsEquivalent( |
| a, b, (a, b) => areInstancesEquivalent(a, b, typeEquivalence))); |
| return true; |
| } |
| |
| bool areInstancesEquivalent(Instance instance1, Instance instance2, |
| bool typeEquivalence(DartType a, DartType b)) { |
| InterfaceType type1 = instance1.type; |
| InterfaceType type2 = instance2.type; |
| return typeEquivalence(type1, type2) && |
| instance1.kind == instance2.kind && |
| instance1.isRedirection == instance2.isRedirection; |
| } |
| |
| bool areAbstractUsagesEquivalent(AbstractUsage usage1, AbstractUsage usage2) { |
| return usage1.hasSameUsage(usage2); |
| } |
| |
| bool _areEntitiesEquivalent(a, b) => areElementsEquivalent(a, b); |
| |
| void checkResolutionEnqueuers( |
| BackendUsage backendUsage1, |
| BackendUsage backendUsage2, |
| ResolutionEnqueuer enqueuer1, |
| ResolutionEnqueuer enqueuer2, |
| {bool elementEquivalence(Entity a, Entity b): _areEntitiesEquivalent, |
| bool typeEquivalence(DartType a, DartType b): areTypesEquivalent, |
| bool elementFilter(Entity element), |
| bool verbose: false, |
| List<String> skipClassUsageTesting: const <String>[]}) { |
| elementFilter ??= (_) => true; |
| |
| ResolutionWorldBuilderBase worldBuilder1 = enqueuer1.worldBuilder; |
| ResolutionWorldBuilderBase worldBuilder2 = enqueuer2.worldBuilder; |
| |
| checkSets(worldBuilder1.instantiatedTypes, worldBuilder2.instantiatedTypes, |
| "Instantiated types mismatch", typeEquivalence, |
| verbose: verbose); |
| |
| checkSets( |
| worldBuilder1.directlyInstantiatedClasses, |
| worldBuilder2.directlyInstantiatedClasses, |
| "Directly instantiated classes mismatch", |
| elementEquivalence, |
| verbose: verbose); |
| |
| checkMaps( |
| worldBuilder1.getInstantiationMap(), |
| worldBuilder2.getInstantiationMap(), |
| "Instantiated classes mismatch", |
| elementEquivalence, |
| (a, b) => areInstantiationInfosEquivalent( |
| a, b, elementEquivalence, typeEquivalence), |
| verbose: verbose); |
| |
| checkSets(enqueuer1.processedEntities, enqueuer2.processedEntities, |
| "Processed element mismatch", elementEquivalence, |
| elementFilter: elementFilter, verbose: verbose); |
| |
| checkSets(worldBuilder1.isChecks, worldBuilder2.isChecks, "Is-check mismatch", |
| typeEquivalence, |
| verbose: verbose); |
| |
| checkSets(worldBuilder1.closurizedMembers, worldBuilder2.closurizedMembers, |
| "closurizedMembers", elementEquivalence, |
| verbose: verbose); |
| checkSets(worldBuilder1.fieldSetters, worldBuilder2.fieldSetters, |
| "fieldSetters", elementEquivalence, |
| verbose: verbose); |
| checkSets( |
| worldBuilder1.methodsNeedingSuperGetter, |
| worldBuilder2.methodsNeedingSuperGetter, |
| "methodsNeedingSuperGetter", |
| elementEquivalence, |
| verbose: verbose); |
| |
| checkMaps( |
| worldBuilder1.classUsageForTesting, |
| worldBuilder2.classUsageForTesting, |
| 'classUsageForTesting', |
| elementEquivalence, |
| areAbstractUsagesEquivalent, |
| keyFilter: (c) => !skipClassUsageTesting.contains(c.name), |
| verbose: verbose); |
| |
| checkMaps( |
| worldBuilder1.staticMemberUsageForTesting, |
| worldBuilder2.staticMemberUsageForTesting, |
| 'staticMemberUsageForTesting', |
| elementEquivalence, |
| areAbstractUsagesEquivalent, |
| keyFilter: elementFilter, |
| verbose: verbose); |
| checkMaps( |
| worldBuilder1.instanceMemberUsageForTesting, |
| worldBuilder2.instanceMemberUsageForTesting, |
| 'instanceMemberUsageForTesting', |
| elementEquivalence, |
| areAbstractUsagesEquivalent, |
| verbose: verbose); |
| |
| Expect.equals(backendUsage1.isInvokeOnUsed, backendUsage2.isInvokeOnUsed, |
| "JavaScriptBackend.hasInvokeOnSupport mismatch"); |
| Expect.equals( |
| backendUsage1.isFunctionApplyUsed, |
| backendUsage1.isFunctionApplyUsed, |
| "JavaScriptBackend.hasFunctionApplySupport mismatch"); |
| Expect.equals( |
| backendUsage1.isRuntimeTypeUsed, |
| backendUsage2.isRuntimeTypeUsed, |
| "JavaScriptBackend.hasRuntimeTypeSupport mismatch"); |
| Expect.equals(backendUsage1.isIsolateInUse, backendUsage2.isIsolateInUse, |
| "JavaScriptBackend.hasIsolateSupport mismatch"); |
| } |
| |
| void checkCodegenEnqueuers(CodegenEnqueuer enqueuer1, CodegenEnqueuer enqueuer2, |
| {bool elementEquivalence(Entity a, Entity b): _areEntitiesEquivalent, |
| bool typeEquivalence(DartType a, DartType b): areTypesEquivalent, |
| bool elementFilter(Element element), |
| bool verbose: false}) { |
| CodegenWorldBuilderImpl worldBuilder1 = enqueuer1.worldBuilder; |
| CodegenWorldBuilderImpl worldBuilder2 = enqueuer2.worldBuilder; |
| |
| checkSets(worldBuilder1.instantiatedTypes, worldBuilder2.instantiatedTypes, |
| "Instantiated types mismatch", typeEquivalence, |
| verbose: verbose); |
| |
| checkSets( |
| worldBuilder1.directlyInstantiatedClasses, |
| worldBuilder2.directlyInstantiatedClasses, |
| "Directly instantiated classes mismatch", |
| elementEquivalence, |
| verbose: verbose); |
| |
| checkSets(enqueuer1.processedEntities, enqueuer2.processedEntities, |
| "Processed element mismatch", elementEquivalence, elementFilter: (e) { |
| return elementFilter != null ? elementFilter(e) : true; |
| }, verbose: verbose); |
| |
| checkSets(worldBuilder1.isChecks, worldBuilder2.isChecks, "Is-check mismatch", |
| typeEquivalence, |
| verbose: verbose); |
| |
| checkSets( |
| worldBuilder1.allReferencedStaticFields, |
| worldBuilder2.allReferencedStaticFields, |
| "Directly instantiated classes mismatch", |
| elementEquivalence, |
| verbose: verbose); |
| checkSets(worldBuilder1.closurizedMembers, worldBuilder2.closurizedMembers, |
| "closurizedMembers", elementEquivalence, |
| verbose: verbose); |
| checkSets(worldBuilder1.processedClasses, worldBuilder2.processedClasses, |
| "processedClasses", elementEquivalence, |
| verbose: verbose); |
| checkSets( |
| worldBuilder1.methodsNeedingSuperGetter, |
| worldBuilder2.methodsNeedingSuperGetter, |
| "methodsNeedingSuperGetter", |
| elementEquivalence, |
| verbose: verbose); |
| checkSets( |
| worldBuilder1.staticFunctionsNeedingGetter, |
| worldBuilder2.staticFunctionsNeedingGetter, |
| "staticFunctionsNeedingGetter", |
| elementEquivalence, |
| verbose: verbose); |
| |
| checkMaps( |
| worldBuilder1.classUsageForTesting, |
| worldBuilder2.classUsageForTesting, |
| 'classUsageForTesting', |
| elementEquivalence, |
| areAbstractUsagesEquivalent, |
| verbose: verbose); |
| checkMaps( |
| worldBuilder1.staticMemberUsageForTesting, |
| worldBuilder2.staticMemberUsageForTesting, |
| 'staticMemberUsageForTesting', |
| elementEquivalence, |
| areAbstractUsagesEquivalent, |
| verbose: verbose); |
| checkMaps( |
| worldBuilder1.instanceMemberUsageForTesting, |
| worldBuilder2.instanceMemberUsageForTesting, |
| 'instanceMemberUsageForTesting', |
| elementEquivalence, |
| areAbstractUsagesEquivalent, |
| verbose: verbose); |
| } |
| |
| // TODO(johnniwinther): Check all emitter properties. |
| void checkEmitters( |
| CodeEmitterTask emitter1, CodeEmitterTask emitter2, TestStrategy strategy, |
| {bool elementEquivalence(Entity a, Entity b): _areEntitiesEquivalent, |
| bool typeEquivalence(DartType a, DartType b): areTypesEquivalent, |
| bool elementFilter(Element element), |
| bool verbose: false}) { |
| checkEmitterPrograms(emitter1.emitter.programForTesting, |
| emitter2.emitter.programForTesting, strategy); |
| |
| checkSets( |
| emitter1.typeTestRegistry.rtiNeededClasses, |
| emitter2.typeTestRegistry.rtiNeededClasses, |
| "TypeTestRegistry rti needed classes mismatch", |
| strategy.elementEquivalence, |
| verbose: verbose); |
| |
| checkSets( |
| emitter1.typeTestRegistry.checkedFunctionTypes, |
| emitter2.typeTestRegistry.checkedFunctionTypes, |
| "TypeTestRegistry checked function types mismatch", |
| strategy.typeEquivalence, |
| verbose: verbose); |
| |
| checkSets( |
| emitter1.typeTestRegistry.checkedClasses, |
| emitter2.typeTestRegistry.checkedClasses, |
| "TypeTestRegistry checked classes mismatch", |
| strategy.elementEquivalence, |
| verbose: verbose); |
| } |
| |
| void checkEmitterPrograms( |
| Program program1, Program program2, TestStrategy strategy) { |
| checkLists(program1.fragments, program2.fragments, 'fragments', |
| (a, b) => a.outputFileName == b.outputFileName, |
| onSameElement: (a, b) => checkEmitterFragments(a, b, strategy)); |
| checkLists( |
| program1.holders, program2.holders, 'holders', (a, b) => a.name == b.name, |
| onSameElement: checkEmitterHolders); |
| check(program1, program2, 'outputContainsConstantList', |
| program1.outputContainsConstantList, program2.outputContainsConstantList); |
| check(program1, program2, 'needsNativeSupport', program1.needsNativeSupport, |
| program2.needsNativeSupport); |
| check(program1, program2, 'hasIsolateSupport', program1.hasIsolateSupport, |
| program2.hasIsolateSupport); |
| check(program1, program2, 'hasSoftDeferredClasses', |
| program1.hasSoftDeferredClasses, program2.hasSoftDeferredClasses); |
| checkMaps( |
| program1.loadMap, |
| program2.loadMap, |
| 'loadMap', |
| equality, |
| (a, b) => areSetsEquivalent( |
| a, b, (a, b) => a.outputFileName == b.outputFileName)); |
| checkMaps(program1.symbolsMap, program2.symbolsMap, 'symbolsMap', |
| (a, b) => a.key == b.key, equality); |
| |
| check( |
| program1, |
| program2, |
| 'typeToInterceptorMap', |
| program1.typeToInterceptorMap, |
| program2.typeToInterceptorMap, |
| areJsNodesEquivalent, |
| js.nodeToString); |
| check(program1, program2, 'metadata', program1.metadata, program2.metadata, |
| areJsNodesEquivalent, js.nodeToString); |
| } |
| |
| void checkEmitterFragments( |
| Fragment fragment1, Fragment fragment2, TestStrategy strategy) { |
| // TODO(johnniwinther): Check outputUnit. |
| checkLists(fragment1.libraries, fragment2.libraries, 'libraries', |
| (a, b) => a.element.canonicalUri == b.element.canonicalUri, |
| onSameElement: (a, b) => checkEmitterLibraries(a, b, strategy)); |
| checkLists(fragment1.constants, fragment2.constants, 'constants', |
| (a, b) => a.name.key == b.name.key, |
| onSameElement: (a, b) => checkEmitterConstants(a, b, strategy)); |
| checkLists(fragment1.staticNonFinalFields, fragment2.staticNonFinalFields, |
| 'staticNonFinalFields', (a, b) => a.name.key == b.name.key, |
| onSameElement: checkEmitterStaticFields); |
| checkLists( |
| fragment1.staticLazilyInitializedFields, |
| fragment2.staticLazilyInitializedFields, |
| 'staticLazilyInitializedFields', |
| (a, b) => a.name.key == b.name.key, |
| onSameElement: checkEmitterStaticFields); |
| check(fragment1, fragment2, 'isMainFragment', fragment1.isMainFragment, |
| fragment2.isMainFragment); |
| if (fragment1 is MainFragment && fragment2 is MainFragment) { |
| check(fragment1, fragment2, 'invokeMain', fragment1.invokeMain, |
| fragment2.invokeMain, areJsNodesEquivalent, js.nodeToString); |
| } else if (fragment1 is DeferredFragment && fragment2 is DeferredFragment) { |
| check(fragment1, fragment2, 'name', fragment1.name, fragment2.name); |
| } |
| } |
| |
| void checkEmitterLibraries( |
| Library library1, Library library2, TestStrategy strategy) { |
| check(library1, library2, 'uri', library1.uri, library2.uri); |
| checkLists(library1.classes, library2.classes, 'classes', |
| (a, b) => a.name.key == b.name.key, |
| onSameElement: (a, b) => checkEmitterClasses(a, b, strategy)); |
| checkLists(library1.statics, library2.statics, 'statics', |
| (a, b) => a.name.key == b.name.key, |
| onSameElement: (a, b) => checkEmitterMethods(a, b, strategy)); |
| checkLists( |
| library1.staticFieldsForReflection, |
| library2.staticFieldsForReflection, |
| 'staticFieldsForReflection on $library1/$library2', |
| (a, b) => a.name.key == b.name.key, |
| onSameElement: checkEmitterFields); |
| } |
| |
| void checkEmitterClasses(Class class1, Class class2, TestStrategy strategy) { |
| checkLists(class1.methods, class2.methods, 'methods', |
| (a, b) => a.name.key == b.name.key, |
| onSameElement: (a, b) => checkEmitterMethods(a, b, strategy)); |
| checkLists(class1.fields, class2.fields, 'fields', |
| (a, b) => a.name.key == b.name.key, |
| onSameElement: checkEmitterFields); |
| checkLists(class1.isChecks, class2.isChecks, 'isChecks', |
| (a, b) => a.name.key == b.name.key, |
| onSameElement: (a, b) => checkEmitterMethods(a, b, strategy)); |
| checkLists(class1.checkedSetters, class2.checkedSetters, 'checkedSetters', |
| (a, b) => a.name.key == b.name.key, |
| onSameElement: (a, b) => checkEmitterMethods(a, b, strategy)); |
| checkLists(class1.callStubs, class2.callStubs, 'callStubs', |
| (a, b) => a.name.key == b.name.key, |
| onSameElement: (a, b) => checkEmitterMethods(a, b, strategy)); |
| checkLists(class1.noSuchMethodStubs, class2.noSuchMethodStubs, |
| 'noSuchMethodStubs', (a, b) => a.name.key == b.name.key, |
| onSameElement: (a, b) => checkEmitterMethods(a, b, strategy)); |
| checkLists( |
| class1.staticFieldsForReflection, |
| class2.staticFieldsForReflection, |
| 'staticFieldsForReflection on $class1/$class2', |
| (a, b) => a.name.key == b.name.key, |
| onSameElement: checkEmitterFields); |
| |
| check(class1, class2, 'superclassName', class1.superclassName?.key, |
| class2.superclassName?.key); |
| check(class1, class2, 'isMixinApplication', class1.isMixinApplication, |
| class2.isMixinApplication); |
| check(class1, class2, 'hasRtiField', class1.hasRtiField, class2.hasRtiField); |
| check(class1, class2, 'onlyForRti', class1.onlyForRti, class2.onlyForRti); |
| check(class1, class2, 'isDirectlyInstantiated', class1.isDirectlyInstantiated, |
| class2.isDirectlyInstantiated); |
| check(class1, class2, 'isNative', class1.isNative, class2.isNative); |
| check(class1, class2, 'isClosureBaseClass', class1.isClosureBaseClass, |
| class2.isClosureBaseClass); |
| check(class1, class2, 'isSoftDeferred', class1.isSoftDeferred, |
| class2.isSoftDeferred); |
| check(class1, class2, 'isEager', class1.isEager, class2.isEager); |
| checkEmitterHolders(class1.holder, class2.holder); |
| } |
| |
| void checkEmitterMethods( |
| Method method1, Method method2, TestStrategy strategy) { |
| check(method1, method2, 'code', method1.code, method2.code, |
| areJsNodesEquivalent, js.nodeToString); |
| check(method1, method2, 'is ParameterStubMethod', |
| method1 is ParameterStubMethod, method2 is ParameterStubMethod); |
| if (method1 is ParameterStubMethod && method2 is ParameterStubMethod) { |
| check(method1, method2, 'callName', method1.callName?.key, |
| method2.callName?.key); |
| } |
| check(method1, method2, 'is DartMethod', method1 is DartMethod, |
| method2 is DartMethod); |
| if (method1 is DartMethod && method2 is DartMethod) { |
| check(method1, method2, 'callName', method1.callName?.key, |
| method2.callName?.key); |
| check(method1, method2, 'needsTearOff', method1.needsTearOff, |
| method2.needsTearOff); |
| check(method1, method2, 'tearOffName', method1.tearOffName?.key, |
| method2.tearOffName?.key); |
| checkLists(method1.parameterStubs, method2.parameterStubs, 'parameterStubs', |
| (a, b) => a.name.key == b.name.key, |
| onSameElement: (a, b) => checkEmitterMethods(a, b, strategy)); |
| check(method1, method2, 'canBeApplied', method1.canBeApplied, |
| method2.canBeApplied); |
| check(method1, method2, 'canBeReflected', method1.canBeReflected, |
| method2.canBeReflected); |
| check(method1, method2, 'functionType', method1.functionType, |
| method2.functionType, areJsNodesEquivalent, js.nodeToString); |
| check(method1, method2, 'requiredParameterCount', |
| method1.requiredParameterCount, method2.requiredParameterCount); |
| if (method1.optionalParameterDefaultValues == null && |
| method2.optionalParameterDefaultValues == null) { |
| // Nothing to test. |
| } else if (method1.optionalParameterDefaultValues is List && |
| method2.optionalParameterDefaultValues is List) { |
| checkLists( |
| method1.optionalParameterDefaultValues, |
| method2.optionalParameterDefaultValues, |
| 'optionalParameterDefaultValues', |
| strategy.constantValueEquivalence); |
| } else if (method1.optionalParameterDefaultValues is Map && |
| method2.optionalParameterDefaultValues is Map) { |
| checkMaps( |
| method1.optionalParameterDefaultValues, |
| method2.optionalParameterDefaultValues, |
| 'optionalParameterDefaultValues', |
| equality, |
| strategy.constantValueEquivalence); |
| } else { |
| check( |
| method1, |
| method2, |
| 'optionalParameterDefaultValues', |
| method1.optionalParameterDefaultValues, |
| method2.optionalParameterDefaultValues); |
| } |
| } |
| } |
| |
| void checkEmitterFields(Field field1, Field field2) { |
| check(field1, field2, 'accessorName', field1.accessorName?.key, |
| field2.accessorName?.key); |
| check(field1, field2, 'getterFlags', field1.getterFlags, field2.getterFlags); |
| check(field1, field2, 'setterFlags', field1.setterFlags, field2.setterFlags); |
| check(field1, field2, 'needsCheckedSetter', field1.needsCheckedSetter, |
| field2.needsCheckedSetter); |
| } |
| |
| void checkEmitterConstants( |
| Constant constant1, Constant constant2, TestStrategy strategy) { |
| checkEmitterHolders(constant1.holder, constant2.holder); |
| check(constant1, constant2, 'value', constant1.value, constant2.value, |
| strategy.constantValueEquivalence); |
| } |
| |
| void checkEmitterStaticFields(StaticField field1, StaticField field2) { |
| check(field1, field2, 'code', field1.code, field2.code, areJsNodesEquivalent, |
| js.nodeToString); |
| check(field1, field2, 'isFinal', field1.isFinal, field2.isFinal); |
| check(field1, field2, 'isLazy', field1.isLazy, field2.isLazy); |
| checkEmitterHolders(field1.holder, field2.holder); |
| } |
| |
| void checkEmitterHolders(Holder holder1, Holder holder2) { |
| check(holder1, holder2, 'name', holder1.name, holder2.name); |
| check(holder1, holder2, 'index', holder1.index, holder2.index); |
| check(holder1, holder2, 'isStaticStateHolder', holder1.isStaticStateHolder, |
| holder2.isStaticStateHolder); |
| check(holder1, holder2, 'isConstantsHolder', holder1.isConstantsHolder, |
| holder2.isConstantsHolder); |
| } |
| |
| void checkGeneratedCode(JavaScriptBackend backend1, JavaScriptBackend backend2, |
| {bool elementEquivalence(Entity a, Entity b): _areEntitiesEquivalent}) { |
| checkMaps(backend1.generatedCode, backend2.generatedCode, 'generatedCode', |
| elementEquivalence, areJsNodesEquivalent, |
| valueToString: js.nodeToString); |
| } |
| |
| bool areJsNodesEquivalent(js.Node node1, js.Node node2) { |
| return new JsEquivalenceVisitor().testNodes(node1, node2); |
| } |
| |
| class JsEquivalenceVisitor extends js.EquivalenceVisitor { |
| Map<String, String> labelsMap = <String, String>{}; |
| |
| @override |
| bool failAt(js.Node node1, js.Node node2) { |
| print('Node mismatch:'); |
| print(' ${node1 != null ? js.nodeToString(node1) : '<null>'}'); |
| print(' ${node2 != null ? js.nodeToString(node2) : '<null>'}'); |
| return false; |
| } |
| |
| @override |
| bool testValues(js.Node node1, Object value1, js.Node node2, Object value2) { |
| if (value1 != value2) { |
| print('Value mismatch:'); |
| print(' ${value1}'); |
| print(' ${value2}'); |
| print('at'); |
| print(' ${node1 != null ? js.nodeToString(node1) : '<null>'}'); |
| print(' ${node2 != null ? js.nodeToString(node2) : '<null>'}'); |
| return false; |
| } |
| return true; |
| } |
| |
| @override |
| bool testLabels(js.Node node1, String label1, js.Node node2, String label2) { |
| if (label1 == null && label2 == null) return true; |
| if (labelsMap.containsKey(label1)) { |
| String expectedValue = labelsMap[label1]; |
| if (expectedValue != label2) { |
| print('Value mismatch:'); |
| print(' ${label1}'); |
| print(' found ${label2}, expected ${expectedValue}'); |
| print('at'); |
| print(' ${js.nodeToString(node1)}'); |
| print(' ${js.nodeToString(node2)}'); |
| } |
| return expectedValue == label2; |
| } else { |
| labelsMap[label1] = label2; |
| return true; |
| } |
| } |
| } |