| // Copyright (c) 2021, 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:expect/expect.dart'; |
| import 'package:kernel/ast.dart'; |
| import 'package:kernel/clone.dart'; |
| import 'package:kernel/src/coverage.dart'; |
| import 'package:kernel/src/equivalence.dart'; |
| import 'package:kernel/src/node_creator.dart'; |
| |
| void main() { |
| testBodyCloning(); |
| testBodyCloningInContext(); |
| testMemberCloning(); |
| } |
| |
| void testBodyCloning() { |
| // TODO(johnniwinther): Add a test for cloning in context. |
| NodeCreator creator = |
| new NodeCreator(initializers: [], members: [], nodes: inBodyNodeKinds); |
| List<TreeNode> nodes = creator.generateBodies(); |
| |
| CoverageVisitor coverageVisitor = new CoverageVisitor(); |
| for (TreeNode node in nodes) { |
| node.accept(coverageVisitor); |
| CloneVisitorNotMembers cloner = new CloneVisitorNotMembers(); |
| TreeNode clone = cloner.clone(node); |
| EquivalenceResult result = checkEquivalence(node, clone, |
| strategy: const NoFileOffsetEquivalenceStrategy()); |
| if (!result.isEquivalent) { |
| print(result); |
| } |
| Expect.isTrue(result.isEquivalent, "$node"); |
| } |
| Expect.isEmpty( |
| creator.createdKinds.toSet()..removeAll(coverageVisitor.visited), |
| 'Nodes not covered in testing.'); |
| } |
| |
| void testBodyCloningInContext() { |
| NodeCreator creator = |
| new NodeCreator(initializers: [], members: [], nodes: inBodyNodeKinds); |
| List<Statement> nodes = creator.generateBodies(); |
| |
| CoverageVisitor coverageVisitor = new CoverageVisitor(); |
| for (Statement node in nodes) { |
| node.accept(coverageVisitor); |
| CloneVisitorNotMembers cloner = new CloneVisitorNotMembers(); |
| // Set up context for [statement]. |
| new Procedure(Name('foo'), ProcedureKind.Method, FunctionNode(node), |
| fileUri: dummyUri); |
| TreeNode clone = cloner.cloneInContext(node); |
| EquivalenceResult result = checkEquivalence(node, clone); |
| if (!result.isEquivalent) { |
| print(result); |
| } |
| Expect.isTrue(result.isEquivalent, "$node"); |
| } |
| Expect.isEmpty( |
| creator.createdKinds.toSet()..removeAll(coverageVisitor.visited), |
| 'Nodes not covered in testing.'); |
| } |
| |
| void testMemberCloning() { |
| NodeCreator creator = new NodeCreator(nodes: inBodyNodeKinds); |
| Component component = creator.generateComponent(); |
| |
| CoverageVisitor coverageVisitor = new CoverageVisitor(); |
| |
| void testMembers<M extends Member>( |
| Iterable<M> members, |
| M Function(CloneVisitorWithMembers, M) cloneFunction, |
| String Function(M) toStringFunction) { |
| for (M member in members) { |
| member.accept(coverageVisitor); |
| CloneVisitorWithMembers cloner = new CloneVisitorWithMembers(); |
| M clone = cloneFunction(cloner, member); |
| EquivalenceResult result = checkEquivalence(member, clone, |
| strategy: const MemberEquivalenceStrategy()); |
| if (!result.isEquivalent) { |
| print(result); |
| } |
| Expect.isTrue(result.isEquivalent, toStringFunction(member)); |
| } |
| } |
| |
| void testProcedures(Iterable<Procedure> procedures) { |
| testMembers<Procedure>( |
| procedures, |
| (cloner, procedure) => cloner.cloneProcedure(procedure, null), |
| (procedure) => "${procedure.runtimeType}(${procedure.name}):" |
| "${procedure.function.body}"); |
| } |
| |
| void testFields(Iterable<Field> fields) { |
| testMembers<Field>( |
| fields, |
| (cloner, field) => cloner.cloneField(field, null, null, null), |
| (field) => "${field.runtimeType}(${field.name}):" |
| "${field.initializer}"); |
| } |
| |
| void testConstructors(Iterable<Constructor> constructors) { |
| testMembers<Constructor>( |
| constructors, |
| (cloner, constructor) => cloner.cloneConstructor(constructor, null), |
| (constructor) => "${constructor.runtimeType}(${constructor.name}):" |
| "${constructor.initializers}:" |
| "${constructor.function.body}"); |
| } |
| |
| void testRedirectingFactories( |
| Iterable<RedirectingFactory> redirectingFactory) { |
| testMembers<RedirectingFactory>( |
| redirectingFactory, |
| (cloner, redirectingFactory) => |
| cloner.cloneRedirectingFactory(redirectingFactory, null), |
| (redirectingFactory) => |
| "${redirectingFactory.runtimeType}(${redirectingFactory.name}):" |
| "${redirectingFactory.function.body}"); |
| } |
| |
| for (Library library in component.libraries) { |
| testProcedures(library.procedures); |
| testFields(library.fields); |
| for (Class cls in library.classes) { |
| testProcedures(cls.procedures); |
| testFields(cls.fields); |
| testConstructors(cls.constructors); |
| testRedirectingFactories(cls.redirectingFactories); |
| } |
| } |
| Expect.isEmpty( |
| creator.createdKinds.toSet()..removeAll(coverageVisitor.visited), |
| 'Nodes not covered in testing.'); |
| } |
| |
| class NoFileOffsetEquivalenceStrategy extends EquivalenceStrategy { |
| const NoFileOffsetEquivalenceStrategy(); |
| |
| @override |
| bool checkTreeNode_fileOffset( |
| EquivalenceVisitor visitor, TreeNode node, TreeNode other) { |
| if (other.fileOffset == TreeNode.noOffset) return true; |
| return super.checkTreeNode_fileOffset(visitor, node, other); |
| } |
| } |
| |
| class MemberEquivalenceStrategy extends EquivalenceStrategy { |
| const MemberEquivalenceStrategy(); |
| |
| void assumeClonedReferences(EquivalenceVisitor visitor, Member member1, |
| Reference? reference1, Member member2, Reference? reference2) { |
| if (reference1 != null && reference2 != null) { |
| ReferenceName referenceName1 = ReferenceName.fromNamedNode(member1); |
| ReferenceName referenceName2 = ReferenceName.fromNamedNode(member2); |
| if (referenceName1.kind == referenceName2.kind && |
| referenceName1.memberName == referenceName2.memberName && |
| referenceName1.memberUri == referenceName2.memberUri && |
| referenceName2.declarationName == null || |
| referenceName2.libraryUri == null) { |
| visitor.assumeReferences(reference1, reference2); |
| } |
| } |
| } |
| |
| @override |
| bool checkProcedure( |
| EquivalenceVisitor visitor, Procedure? node, Object? other) { |
| if (node is Procedure && other is Procedure) { |
| assumeClonedReferences( |
| visitor, node, node.reference, other, other.reference); |
| } |
| return super.checkProcedure(visitor, node, other); |
| } |
| |
| @override |
| bool checkConstructor( |
| EquivalenceVisitor visitor, Constructor? node, Object? other) { |
| if (node is Constructor && other is Constructor) { |
| assumeClonedReferences( |
| visitor, node, node.reference, other, other.reference); |
| } |
| return super.checkConstructor(visitor, node, other); |
| } |
| |
| @override |
| bool checkRedirectingFactory( |
| EquivalenceVisitor visitor, RedirectingFactory? node, Object? other) { |
| if (node is RedirectingFactory && other is RedirectingFactory) { |
| assumeClonedReferences( |
| visitor, node, node.reference, other, other.reference); |
| } |
| return super.checkRedirectingFactory(visitor, node, other); |
| } |
| |
| @override |
| bool checkField(EquivalenceVisitor visitor, Field? node, Object? other) { |
| if (node is Field && other is Field) { |
| assumeClonedReferences( |
| visitor, node, node.fieldReference, other, other.fieldReference); |
| assumeClonedReferences( |
| visitor, node, node.getterReference, other, other.getterReference); |
| assumeClonedReferences( |
| visitor, node, node.setterReference, other, other.setterReference); |
| } |
| return super.checkField(visitor, node, other); |
| } |
| } |