blob: 37c12e38a1b7d8d8801f245da6c845a312d65a53 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016, 2017 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
*******************************************************************************/
package org.eclipse.dltk.core.tests.model;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.dltk.core.IBuffer;
import org.eclipse.dltk.core.IModelElement;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.core.IType;
import org.eclipse.dltk.core.WorkingCopyOwner;
import org.eclipse.dltk.internal.core.ModelElement;
import org.eclipse.dltk.internal.core.SourceType;
import org.eclipse.dltk.internal.core.hierarchy.TypeHierarchy;
public class TypeHierarchyTests extends ModifyingResourceTests {
private static boolean DEBUG = false;
private static final String[] TEST_NATURE = new String[] {
"org.eclipse.dltk.core.tests.testnature" };
private ISourceModule cu = null;
private ISourceModule copy = null;
public class TestWorkingCopyOwner extends WorkingCopyOwner {
@Override
public IBuffer createBuffer(ISourceModule workingCopy) {
return new TestBuffer(workingCopy);
}
}
private final Set<IType> expectedExploredClasses = new HashSet<>();
private final Set<IType> expectedCyclicClasses = new HashSet<>();
private final FakeTypeHierarchy typeHierarchy = new FakeTypeHierarchy();
private boolean useCacheSuperclass = true;
public TypeHierarchyTests(String name) {
super(ModelTestsPlugin.PLUGIN_NAME, name);
}
public static Suite suite() {
return new Suite(TypeHierarchyTests.class);
}
@Override
protected void setUp() throws Exception {
super.setUp();
typeHierarchy.initialize(1);
expectedExploredClasses.clear();
expectedCyclicClasses.clear();
useCacheSuperclass = true;
try {
this.createScriptProject("P", TEST_NATURE, new String[] { "src" });
this.createFolder("P/src/x/y");
this.createFile("P/src/x/y/Z.txt", "");
this.cu = this.getSourceModule("P/src/x/y/Z.txt");
this.copy = cu.getWorkingCopy(null);
} catch (CoreException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
@Override
protected void tearDown() throws Exception {
if (this.copy != null)
this.copy.discardWorkingCopy();
AbstractModelTests.deleteProject("P");
super.tearDown();
}
static class FakeTypeHierarchy extends TypeHierarchy {
@Override
public void addSubtype(IType type, IType subtype) {
super.addSubtype(type, subtype);
}
@Override
protected void cacheSuperclass(IType type, IType superclass) {
super.cacheSuperclass(type, superclass);
}
@Override
public void resetClassPaths() {
super.resetClassPaths();
}
public Set<IType> getExploredClasses() {
return this.exploredClasses;
}
public Set<IType> getCyclicClasses() {
return this.cyclicClasses;
}
@Override
protected void initialize(int size) {
super.initialize(size);
}
}
private Map<String, SourceType> createFakeTypes(char begin, char end,
ModelElement modelElement) {
Map<String, SourceType> types = new HashMap<>();
for (char i = begin; i <= end; i++) {
String name = Character.toString(i);
types.put(name, new SourceType(modelElement, name));
}
return types;
}
private void populate(SourceType parent, boolean isParentOnCyclicPath,
SourceType child, boolean isChildOnCyclicPath) {
assertNotNull(parent);
assertNotNull(child);
if (useCacheSuperclass) {
typeHierarchy.cacheSuperclass(parent, child);
} else {
typeHierarchy.addSubtype(child, parent);
}
expectedExploredClasses.add(parent);
expectedExploredClasses.add(child);
if (isParentOnCyclicPath) {
expectedCyclicClasses.add(parent);
}
if (isChildOnCyclicPath) {
expectedCyclicClasses.add(child);
}
explorePartialGraphThroughSubclasses(parent, false);
explorePartialGraphThroughSubclasses(child, false);
explorePartialGraphThroughSuperclasses(parent, false);
explorePartialGraphThroughSuperclasses(child, false);
}
private void print(Set<IType> classPaths) {
Set<IType> sortedClassPaths = new TreeSet<>((arg0, arg1) -> {
if (arg0 == arg1 || arg0.equals(arg1)) {
return 0;
}
return arg0.getElementName().compareTo(arg1.getElementName());
});
sortedClassPaths.addAll(classPaths);
System.out.println(sortedClassPaths);
}
private void printCurrentState() {
if (!DEBUG) {
return;
}
print(typeHierarchy.getExploredClasses());
print(expectedExploredClasses);
System.out.println();
print(typeHierarchy.getCyclicClasses());
print(expectedCyclicClasses);
System.out.println();
}
private void exploreAllGraphThroughSubclasses(SourceType type,
boolean doReset) {
assertNotNull(type);
if (doReset) {
typeHierarchy.resetClassPaths();
}
typeHierarchy.getSubclasses(type);
printCurrentState();
assertEquals(typeHierarchy.getExploredClasses(),
expectedExploredClasses);
assertEquals(typeHierarchy.getCyclicClasses(), expectedCyclicClasses);
}
private void exploreAllGraphThroughSuperclasses(SourceType type,
boolean doReset) {
assertNotNull(type);
if (doReset) {
typeHierarchy.resetClassPaths();
}
typeHierarchy.getSuperclass(type);
printCurrentState();
assertEquals(typeHierarchy.getExploredClasses(),
expectedExploredClasses);
assertEquals(typeHierarchy.getCyclicClasses(), expectedCyclicClasses);
}
private void explorePartialGraphThroughSubclasses(SourceType type,
boolean doReset) {
assertNotNull(type);
if (doReset) {
typeHierarchy.resetClassPaths();
}
typeHierarchy.getSubclasses(type);
printCurrentState();
assertTrue(!expectedExploredClasses.contains(type)
|| !typeHierarchy.getExploredClasses().isEmpty());
assertTrue(typeHierarchy.getExploredClasses()
.containsAll(typeHierarchy.getCyclicClasses()));
assertTrue(expectedExploredClasses
.containsAll(typeHierarchy.getExploredClasses()));
assertTrue(expectedExploredClasses
.containsAll(typeHierarchy.getCyclicClasses()));
assertTrue(expectedCyclicClasses
.containsAll(typeHierarchy.getCyclicClasses()));
}
private void explorePartialGraphThroughSuperclasses(SourceType type,
boolean doReset) {
assertNotNull(type);
if (doReset) {
typeHierarchy.resetClassPaths();
}
typeHierarchy.getSuperclass(type);
printCurrentState();
assertTrue(!expectedExploredClasses.contains(type)
|| !typeHierarchy.getExploredClasses().isEmpty());
assertTrue(typeHierarchy.getExploredClasses()
.containsAll(typeHierarchy.getCyclicClasses()));
assertTrue(expectedExploredClasses
.containsAll(typeHierarchy.getExploredClasses()));
assertTrue(expectedExploredClasses
.containsAll(typeHierarchy.getCyclicClasses()));
assertTrue(expectedCyclicClasses
.containsAll(typeHierarchy.getCyclicClasses()));
}
public void testCyclicHierarchy001() throws Exception {
assertNotNull(cu);
IModelElement p = cu.getParent();
assertTrue(p instanceof ModelElement);
typeHierarchy.resetClassPaths();
Map<String, SourceType> types = createFakeTypes('A', 'U',
(ModelElement) p);
// * Class hierarchy representation, "(c)" means that the relevant class
// in on a cyclic path
// *
// * +------+
// * | L |
// * +------+
// * ^
// * | +-------------------------------------+
// * | v |
// * +------+ +------+ +------+ +------+ +------+ +------+ +---+
// +------+ +---+ +---+ +---+ |
// * +> | B(c) | <-- | A(c) | --> | H(c) | --> | | --> | J(c) | --> |
// K(c) | --> | O | --> | P(c) | --> | R | --> | S | --> | U | |
// * | +------+ +------+ +------+ | | +------+ +------+ +---+ +------+
// +---+ +---+ +---+ |
// * | | ^ | | | | | ^ |
// * | | +---------------------- | I(c) | <------------------+ | | | |
// * | v | | v v | |
// * | +------+ | | +------+ +---+ | |
// * | | C(c) | <------------------+ +- | | --------------+ | Q(c) | | T
// | ----------------+ |
// * | +------+ | | +------+ | +------+ +---+ |
// * | | | | | | |
// * | | | | +------------+ | | |
// * | v | | v | | | |
// * | +------+ +------+ +------+ | +------+ +------+ | | |
// * +- | D(c) | --> | E(c) | --> | F(c) | +> | M(c) | --> | N(c) | |
// +-------------------------------------+
// * +------+ +------+ +------+ +------+ +------+ |
// * | ^ |
// * | +---------------------------------+
// * v
// * +------+
// * | G |
// * +------+
// *
populate(types.get("A"), true, types.get("B"), true);
populate(types.get("B"), true, types.get("C"), true);
populate(types.get("C"), true, types.get("D"), true);
populate(types.get("D"), true, types.get("E"), true);
populate(types.get("E"), true, types.get("F"), true);
populate(types.get("E"), true, types.get("G"), false);
populate(types.get("F"), true, types.get("C"), true);
populate(types.get("D"), true, types.get("B"), true);
populate(types.get("A"), true, types.get("H"), true);
populate(types.get("H"), true, types.get("I"), true);
populate(types.get("I"), true, types.get("F"), true);
populate(types.get("I"), true, types.get("A"), true);
populate(types.get("I"), true, types.get("J"), true);
populate(types.get("J"), true, types.get("K"), true);
populate(types.get("K"), true, types.get("I"), true);
populate(types.get("K"), true, types.get("L"), false);
populate(types.get("K"), true, types.get("O"), false);
populate(types.get("O"), false, types.get("P"), true);
populate(types.get("P"), true, types.get("Q"), true);
populate(types.get("Q"), true, types.get("P"), true);
populate(types.get("P"), true, types.get("R"), false);
populate(types.get("I"), true, types.get("M"), true);
populate(types.get("M"), true, types.get("N"), true);
populate(types.get("N"), true, types.get("M"), true);
populate(types.get("R"), false, types.get("S"), false);
populate(types.get("R"), false, types.get("T"), false);
populate(types.get("S"), false, types.get("U"), false);
populate(types.get("T"), false, types.get("U"), false);
assertTrue(expectedExploredClasses.containsAll(expectedCyclicClasses));
exploreAllGraphThroughSuperclasses(types.get("A"), false);
exploreAllGraphThroughSuperclasses(types.get("A"), true);
exploreAllGraphThroughSuperclasses(types.get("K"), true);
explorePartialGraphThroughSuperclasses(types.get("O"), true);
exploreAllGraphThroughSuperclasses(types.get("K"), false);
explorePartialGraphThroughSuperclasses(types.get("D"), true);
for (String typeName : types.keySet()) {
explorePartialGraphThroughSuperclasses(types.get(typeName), true);
}
exploreAllGraphThroughSubclasses(types.get("B"), true);
exploreAllGraphThroughSubclasses(types.get("O"), true);
for (String typeName : types.keySet()) {
explorePartialGraphThroughSubclasses(types.get(typeName), true);
}
}
public void testCyclicHierarchy002() throws Exception {
useCacheSuperclass = false;
testCyclicHierarchy001();
}
public void testCyclicHierarchy003() throws Exception {
assertNotNull(cu);
IModelElement p = cu.getParent();
assertTrue(p instanceof ModelElement);
typeHierarchy.resetClassPaths();
Map<String, SourceType> types = createFakeTypes('A', 'Z',
(ModelElement) p);
// * Class hierarchy representation, "(c)" means that the relevant class
// in on a cyclic path
// *
// * +------+ +---+ +---+
// * +> | W(c) | --> | V | --> | H |
// * | +------+ +---+ +---+
// * | |
// * | |
// * | v
// * | +------+
// * +- | X(c) |
// * +------+
// *
populate(types.get("W"), true, types.get("X"), true);
populate(types.get("W"), true, types.get("V"), false);
populate(types.get("V"), false, types.get("H"), false);
populate(types.get("X"), true, types.get("W"), true);
assertTrue(expectedExploredClasses.containsAll(expectedCyclicClasses));
explorePartialGraphThroughSuperclasses(types.get("H"), true);
for (String typeName : types.keySet()) {
explorePartialGraphThroughSuperclasses(types.get(typeName), true);
}
for (String typeName : types.keySet()) {
explorePartialGraphThroughSubclasses(types.get(typeName), true);
}
}
public void testCyclicHierarchy004() throws Exception {
useCacheSuperclass = false;
testCyclicHierarchy003();
}
public void testNonCyclicHierarchy005() throws Exception {
assertNotNull(cu);
IModelElement p = cu.getParent();
assertTrue(p instanceof ModelElement);
typeHierarchy.resetClassPaths();
Map<String, SourceType> types = createFakeTypes('A', 'F',
(ModelElement) p);
// * Class hierarchy representation, "(c)" means that the relevant class
// in on a cyclic path
// *
// * +---+
// * | F |
// * +---+
// * ^
// * |
// * |
// * +---+ +---+ +---+ +---+
// * | A | --> | B | --> | D | --> | E |
// * +---+ +---+ +---+ +---+
// * | ^
// * | |
// * v |
// * +---+ |
// * | C | ----------------+
// * +---+
// *
populate(types.get("A"), false, types.get("B"), false);
populate(types.get("A"), false, types.get("C"), false);
populate(types.get("B"), false, types.get("D"), false);
populate(types.get("C"), false, types.get("D"), false);
populate(types.get("D"), false, types.get("E"), false);
populate(types.get("D"), false, types.get("F"), false);
assertTrue(expectedExploredClasses.containsAll(expectedCyclicClasses));
exploreAllGraphThroughSuperclasses(types.get("A"), false);
for (String typeName : types.keySet()) {
explorePartialGraphThroughSuperclasses(types.get(typeName), true);
}
for (String typeName : types.keySet()) {
explorePartialGraphThroughSubclasses(types.get(typeName), true);
}
}
public void testCyclicHierarchy006() throws Exception {
useCacheSuperclass = false;
testNonCyclicHierarchy005();
}
public void testEmptyHierarchy007() throws Exception {
assertNotNull(cu);
IModelElement p = cu.getParent();
assertTrue(p instanceof ModelElement);
typeHierarchy.resetClassPaths();
SourceType type = new SourceType((ModelElement) p, "A");
if (useCacheSuperclass) {
typeHierarchy.cacheSuperclass(type, null);
} else {
typeHierarchy.addSubtype(null, type);
}
assertTrue(typeHierarchy.getSuperclass(type).length == 0);
assertTrue(typeHierarchy.getSubclasses(type).length == 0);
assertTrue(typeHierarchy.getExploredClasses().isEmpty());
assertTrue(typeHierarchy.getCyclicClasses().isEmpty());
}
public void testEmptyHierarchy008() throws Exception {
useCacheSuperclass = false;
testEmptyHierarchy007();
}
public void testFullyCyclicHierarchy009() throws Exception {
assertNotNull(cu);
IModelElement p = cu.getParent();
assertTrue(p instanceof ModelElement);
typeHierarchy.resetClassPaths();
Map<String, SourceType> types = createFakeTypes('A', 'H',
(ModelElement) p);
// * Class hierarchy representation, "(c)" means that the relevant class
// in on a cyclic path
// *
// * +---------------------------------------------------+
// * v |
// * +------+ +------+ +------+ +------+ +------+ +------+
// * | A(c) | --> | B(c) | --> | C(c) | --> | G(c) | --> | H(c) | --> |
// F(c) |
// * +------+ +------+ +------+ +------+ +------+ +------+
// * ^ |
// * | |
// * | v
// * | +------+ +------+
// * | | D(c) | --> | E(c) |
// * | +------+ +------+
// * | |
// * +--------------------------------------+
// *
populate(types.get("A"), true, types.get("B"), true);
populate(types.get("B"), true, types.get("C"), true);
populate(types.get("C"), true, types.get("D"), true);
populate(types.get("D"), true, types.get("E"), true);
populate(types.get("E"), true, types.get("A"), true);
populate(types.get("F"), true, types.get("B"), true);
populate(types.get("C"), true, types.get("G"), true);
populate(types.get("G"), true, types.get("H"), true);
populate(types.get("H"), true, types.get("F"), true);
assertTrue(expectedExploredClasses.containsAll(expectedCyclicClasses));
assertEquals(expectedExploredClasses, expectedCyclicClasses);
for (String typeName : types.keySet()) {
exploreAllGraphThroughSuperclasses(types.get(typeName), true);
}
for (String typeName : types.keySet()) {
exploreAllGraphThroughSubclasses(types.get(typeName), true);
}
}
public void testFullyCyclicHierarchy010() throws Exception {
useCacheSuperclass = false;
testFullyCyclicHierarchy009();
}
}