blob: df3796d2b4963e32f1c2d087859437bfb276fe8c [file] [log] [blame]
// 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.
import 'package:front_end/src/incremental/combine.dart';
import 'package:kernel/ast.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/testing/mock_sdk_program.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(CombineTest);
});
}
@reflectiveTest
class CombineTest {
Program sdk;
CoreTypes coreTypes;
Supertype get objectSuper => coreTypes.objectClass.asThisSupertype;
void setUp() {
sdk = createMockSdkProgram();
coreTypes = new CoreTypes(sdk);
}
void test_class_mergeLibrary_appendClass() {
var libraryA1 = _newLibrary('a');
libraryA1.addClass(new Class(name: 'A', supertype: objectSuper));
var libraryA2 = _newLibrary('a');
libraryA2.addClass(new Class(name: 'B', supertype: objectSuper));
var outline1 = _newOutline([libraryA1]);
var outline2 = _newOutline([libraryA2]);
_runCombineTest([outline1, outline2], (result) {
var libraryA = _getLibrary(result.program, 'a');
var classA = _getClass(libraryA, 'A');
expect(classA.members, isEmpty);
var classB = _getClass(libraryA, 'B');
expect(classB.members, isEmpty);
});
}
void test_field() {
var libraryA1 = _newLibrary('a');
libraryA1.addField(_newField('A'));
var libraryA2 = _newLibrary('a');
libraryA2.addField(_newField('B'));
var outline1 = _newOutline([libraryA1]);
var outline2 = _newOutline([libraryA2]);
_runCombineTest([outline1, outline2], (result) {
var libraryA = _getLibrary(result.program, 'a');
_getField(libraryA, 'A');
_getField(libraryA, 'B');
});
}
void test_field_skipDuplicate() {
var libraryA1 = _newLibrary('a');
libraryA1.addField(_newField('A'));
libraryA1.addField(_newField('B'));
var libraryA2 = _newLibrary('a');
libraryA2.addField(_newField('A'));
libraryA2.addField(_newField('C'));
var outline1 = _newOutline([libraryA1]);
var outline2 = _newOutline([libraryA2]);
_runCombineTest([outline1, outline2], (result) {
var libraryA = _getLibrary(result.program, 'a');
_getField(libraryA, 'A');
_getField(libraryA, 'B');
_getField(libraryA, 'C');
});
}
void test_field_updateReferences() {
var libraryA1 = _newLibrary('a');
var fieldA1A = _newField('A');
libraryA1.addField(fieldA1A);
var libraryA2 = _newLibrary('a');
var fieldA2A = _newField('A');
libraryA2.addField(fieldA2A);
var libraryB = _newLibrary('b');
libraryB.addProcedure(_newMainProcedure([
new StaticGet(fieldA2A),
new StaticSet(fieldA2A, new IntLiteral(0)),
]));
var outline1 = _newOutline([libraryA1]);
var outline2 = _newOutline([libraryA2, libraryB]);
_runCombineTest([outline1, outline2], (result) {
var libraryA = _getLibrary(result.program, 'a');
_getField(libraryA, 'A');
var libraryB = _getLibrary(result.program, 'b');
var main = _getProcedure(libraryB, 'main', '@methods');
expect((_getMainExpression(main, 0) as StaticGet).targetReference,
same(fieldA1A.reference));
expect((_getMainExpression(main, 1) as StaticSet).targetReference,
same(fieldA1A.reference));
});
}
void test_library_replaceReference() {
var libraryA1 = _newLibrary('a');
var libraryA2 = _newLibrary('a');
var libraryB = _newLibrary('b');
libraryB.dependencies.add(new LibraryDependency.import(libraryA2));
var outline1 = _newOutline([libraryA1]);
var outline2 = _newOutline([libraryA2, libraryB]);
_runCombineTest([outline1, outline2], (result) {
var libraryA = _getLibrary(result.program, 'a');
var libraryB = _getLibrary(result.program, 'b');
expect(libraryB.dependencies, hasLength(1));
expect(libraryB.dependencies[0].targetLibrary, libraryA);
});
}
void test_procedure_getter() {
var libraryA1 = _newLibrary('a');
libraryA1.addProcedure(_newGetter('A'));
var libraryA2 = _newLibrary('a');
libraryA2.addProcedure(_newGetter('B'));
var outline1 = _newOutline([libraryA1]);
var outline2 = _newOutline([libraryA2]);
_runCombineTest([outline1, outline2], (result) {
var libraryA = _getLibrary(result.program, 'a');
_getProcedure(libraryA, 'A', '@getters');
_getProcedure(libraryA, 'B', '@getters');
});
}
void test_procedure_method() {
var libraryA1 = _newLibrary('a');
libraryA1.addProcedure(_newMethod('A'));
var libraryA2 = _newLibrary('a');
libraryA2.addProcedure(_newMethod('B'));
var outline1 = _newOutline([libraryA1]);
var outline2 = _newOutline([libraryA2]);
_runCombineTest([outline1, outline2], (result) {
var libraryA = _getLibrary(result.program, 'a');
_getProcedure(libraryA, 'A', '@methods');
_getProcedure(libraryA, 'B', '@methods');
});
}
void test_procedure_method_skipDuplicate() {
var libraryA1 = _newLibrary('a');
libraryA1.addProcedure(_newMethod('A'));
libraryA1.addProcedure(_newMethod('B'));
var libraryA2 = _newLibrary('a');
libraryA2.addProcedure(_newMethod('A'));
libraryA2.addProcedure(_newMethod('C'));
var outline1 = _newOutline([libraryA1]);
var outline2 = _newOutline([libraryA2]);
_runCombineTest([outline1, outline2], (result) {
var libraryA = _getLibrary(result.program, 'a');
_getProcedure(libraryA, 'A', '@methods');
_getProcedure(libraryA, 'B', '@methods');
_getProcedure(libraryA, 'C', '@methods');
});
}
void test_procedure_method_updateReferences() {
var libraryA1 = _newLibrary('a');
var procedureA1A = _newMethod('A');
libraryA1.addProcedure(procedureA1A);
var libraryA2 = _newLibrary('a');
var procedureA2A = _newMethod('A');
libraryA2.addProcedure(procedureA2A);
var libraryB = _newLibrary('b');
libraryB.addProcedure(_newMainProcedure([
new StaticInvocation(procedureA2A, new Arguments.empty()),
]));
var outline1 = _newOutline([libraryA1]);
var outline2 = _newOutline([libraryA2, libraryB]);
_runCombineTest([outline1, outline2], (result) {
var libraryA = _getLibrary(result.program, 'a');
_getProcedure(libraryA, 'A', '@methods');
var libraryB = _getLibrary(result.program, 'b');
var main = _getProcedure(libraryB, 'main', '@methods');
expect((_getMainExpression(main, 0) as StaticInvocation).targetReference,
same(procedureA1A.reference));
});
}
void test_procedure_setter() {
var libraryA1 = _newLibrary('a');
libraryA1.addProcedure(_newSetter('A'));
var libraryA2 = _newLibrary('a');
libraryA2.addProcedure(_newSetter('B'));
var outline1 = _newOutline([libraryA1]);
var outline2 = _newOutline([libraryA2]);
_runCombineTest([outline1, outline2], (result) {
var libraryA = _getLibrary(result.program, 'a');
_getProcedure(libraryA, 'A', '@setters');
_getProcedure(libraryA, 'B', '@setters');
});
}
void test_undo_twice() {
var libraryA1 = _newLibrary('a');
libraryA1.addField(_newField('A'));
var libraryA2 = _newLibrary('a');
libraryA2.addField(_newField('B'));
var outline1 = _newOutline([libraryA1]);
var outline2 = _newOutline([libraryA2]);
var result = combine([outline1, outline2]);
result.undo();
expect(() => result.undo(), throwsStateError);
}
/// Get a single [Class] with the given [name].
/// Throw if there is not exactly one.
Class _getClass(Library library, String name) {
var results = library.classes.where((class_) => class_.name == name);
expect(results, hasLength(1), reason: 'Expected only one: $name');
Class result = results.first;
expect(result.parent, library);
expect(result.canonicalName.parent, library.canonicalName);
return result;
}
/// Get a single [Field] with the given [name].
/// Throw if there is not exactly one.
Field _getField(Library library, String name) {
var results = library.fields.where((field) => field.name.name == name);
expect(results, hasLength(1), reason: 'Expected only one: $name');
Field result = results.first;
expect(result.parent, library);
var parentName = library.canonicalName.getChild('@fields');
expect(result.canonicalName.parent, parentName);
return result;
}
/// Get a single [Library] with the given [name].
/// Throw if there is not exactly one.
Library _getLibrary(Program program, String name) {
var results = program.libraries.where((library) => library.name == name);
expect(results, hasLength(1), reason: 'Expected only one: $name');
var result = results.first;
expect(result.parent, program);
expect(result.canonicalName.parent, program.root);
return result;
}
/// Return the [Expression] in the [index]th statement of the [procedure]'s
/// block body.
Expression _getMainExpression(Procedure procedure, int index) {
Block mainBlock = procedure.function.body;
ExpressionStatement statement = mainBlock.statements[index];
return statement.expression;
}
/// Get a single [Procedure] with the given [name].
/// Throw if there is not exactly one.
Procedure _getProcedure(Library library, String name, String prefixName) {
var results =
library.procedures.where((procedure) => procedure.name.name == name);
expect(results, hasLength(1), reason: 'Expected only one: $name');
Procedure result = results.first;
expect(result.parent, library);
var parentName = library.canonicalName.getChild(prefixName);
expect(result.canonicalName.parent, parentName);
return result;
}
Field _newField(String name) {
return new Field(new Name(name));
}
Procedure _newGetter(String name) {
return new Procedure(new Name(name), ProcedureKind.Getter,
new FunctionNode(new ExpressionStatement(new IntLiteral((0)))));
}
Library _newLibrary(String name) {
var uri = Uri.parse('org-dartlang:///$name.dart');
return new Library(uri, name: name);
}
Procedure _newMainProcedure(List<Expression> expressions) {
var statements =
expressions.map((e) => new ExpressionStatement(e)).toList();
return new Procedure(new Name('main'), ProcedureKind.Method,
new FunctionNode(new Block(statements)));
}
Procedure _newMethod(String name) {
return new Procedure(new Name(name), ProcedureKind.Method,
new FunctionNode(new EmptyStatement()));
}
Program _newOutline(List<Library> libraries) {
var outline = new Program(libraries: libraries);
outline.computeCanonicalNames();
return outline;
}
Procedure _newSetter(String name) {
return new Procedure(
new Name(name),
ProcedureKind.Setter,
new FunctionNode(new EmptyStatement(),
positionalParameters: [new VariableDeclaration('_')]));
}
void _runCombineTest(
List<Program> outlines, void checkResult(CombineResult result)) {
// Store the original state.
var states = <Program, _OutlineState>{};
for (var outline in outlines) {
states[outline] = new _OutlineState(outline);
}
// Combine the outlines and check the result.
var result = combine(outlines);
checkResult(result);
// Undo and verify that the state is the same as the original.
result.undo();
states.forEach((outline, state) {
state.verifySame();
});
}
}
/// The original state of an outline, and code that validates that after some
/// manipulations (e.g. combine and undo) the state stays the same.
class _OutlineState {
final Program outline;
final initialCollector = new _StateCollector();
_OutlineState(this.outline) {
outline.accept(initialCollector);
}
void verifySame() {
var collector = new _StateCollector();
outline.accept(collector);
expect(collector.nodes, initialCollector.nodes);
expect(collector.references, initialCollector.references);
initialCollector.libraryParents.forEach((library, outline) {
expect(library.canonicalName.parent, outline.root);
expect(library.parent, outline);
});
initialCollector.nodeParents.forEach((child, parent) {
expect(child.parent, parent);
if (child is Member) {
var qualifier = CanonicalName.getMemberQualifier(child);
var parentName = parent.canonicalName.getChild(qualifier);
expect(child.canonicalName.parent, parentName);
} else {
expect(child.canonicalName.parent, parent.canonicalName);
}
});
}
}
class _StateCollector extends RecursiveVisitor {
final List<Node> nodes = [];
final Map<NamedNode, NamedNode> nodeParents = {};
final Map<Library, Program> libraryParents = {};
final List<Reference> references = [];
@override
void defaultMemberReference(Member node) {
references.add(node.reference);
}
@override
void defaultNode(Node node) {
nodes.add(node);
if (node is Library) {
libraryParents[node] = node.parent as Program;
} else if (node is NamedNode) {
nodeParents[node] = node.parent as NamedNode;
}
super.defaultNode(node);
}
@override
void visitClassReference(Class node) {
references.add(node.reference);
}
@override
visitLibraryDependency(LibraryDependency node) {
references.add(node.importedLibraryReference);
super.visitLibraryDependency(node);
}
@override
void visitTypedefReference(Typedef node) {
references.add(node.reference);
}
}