blob: 2a8cfb64e5a9cf9e388b5cf7f2af0be8fdec08b4 [file] [log] [blame]
// 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),
(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.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.getterReference, other, other.getterReference);
assumeClonedReferences(
visitor, node, node.setterReference, other, other.setterReference);
}
return super.checkField(visitor, node, other);
}
}