Skip to content

Commit

Permalink
test collection contract of non empty collection too
Browse files Browse the repository at this point in the history
  • Loading branch information
pvojtechovsky committed Sep 1, 2018
1 parent edfcbab commit cc2d83c
Showing 1 changed file with 175 additions and 70 deletions.
245 changes: 175 additions & 70 deletions src/test/java/spoon/test/parent/CollectionsContractTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
import java.util.Set;
import java.util.stream.Collectors;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static spoon.testing.utils.ModelUtils.createFactory;

Expand Down Expand Up @@ -98,10 +102,6 @@ public void testContract() throws Throwable {
// contract: check type of collection returned by getter
// read only / modifiable-detached / modifiable-attached
Class<? extends CtElement> elementClass = (Class<? extends CtElement>) mmConcept.getModelInterface().getActualClass();
CtElement testedElement = factory.Core().create(elementClass);
if (elementClass.equals(CtTypeReference.class)) {
testedElement = factory.Type().createReference(ArrayList.class);
}

List<String> problems = new ArrayList<>();
List<String> expected = new ArrayList<>();
Expand All @@ -110,31 +110,43 @@ public void testContract() throws Throwable {
if (mmProperty.getValueContainerType() == ContainerKind.SINGLE || ignoredRoles.contains(mmProperty.getRole())) {
continue;
}
Object argument = createCompatibleObject(mmProperty.getItemValueType());
CtElement[] arguments = new CtElement[] {
(CtElement) createCompatibleObject(mmProperty.getItemValueType()),
(CtElement) createCompatibleObject(mmProperty.getItemValueType())
};

RoleHandler roleHandler = RoleHandlerHelper.getRoleHandler(elementClass, mmProperty.getRole());
CollectionKind colKind = detectCollectionKind(roleHandler, testedElement, (CtElement) argument);
if (colKind != null) {
if (mmProperty.isDerived()) {
//derived properties should be unsettable
if (colKind != CollectionKind.READ_ONLY) {
//report this problem
problems.add("derived;" +mmConcept + "#" + mmProperty.getName() + ";" + colKind.name());
} else {
//collect expected collection
expected.add("derived;" +mmConcept + "#" + mmProperty.getName() + ";" + colKind.name());
}
CollectionKind[] colKind;

CtElement testedElement = factory.Core().create(elementClass);
if (elementClass.equals(CtTypeReference.class)) {
testedElement = factory.Type().createReference(ArrayList.class);
}

try {
colKind = detectCollectionKind(mmProperty, roleHandler, testedElement, arguments);
} catch (Throwable e) {
problems.add("Failed check of;" + mmConcept + "#" + mmProperty.getName() + ". " + e.getClass().getSimpleName() + " : " + e.getMessage());
continue;
}
String colKindStr = Arrays.asList(colKind).stream().map(CollectionKind::name).collect(Collectors.joining(", ", "[", "]"));
if (mmProperty.isDerived()) {
//derived properties should be unsettable
if (containsOnly(colKind, CollectionKind.READ_ONLY) == false) {
//report this problem
problems.add("derived;" + colKindStr + mmProperty.getName() + " of " + mmConcept);
} else {
//normal properties should be attached correct
if (colKind != CollectionKind.MUTABLE_ATTACHED_CORRECT) {
//report this problem
problems.add("normal;" + mmConcept + "#" + mmProperty.getName() + ";" + colKind.name());
} else {
expected.add("normal;" + mmConcept + "#" + mmProperty.getName() + ";" + colKind.name());
}
//collect expected collection
expected.add("derived;" + colKindStr + mmProperty.getName() + " of " + mmConcept);
}
} else {
problems.add("Failed check of;" + mmConcept + "#" + mmProperty.getName());
//normal properties should be attached correct
if (containsOnly(colKind, CollectionKind.MUTABLE_ATTACHED_CORRECT) == false) {
//report this problem
problems.add("normal;" + colKindStr + mmProperty.getName() + " of " + mmConcept);
} else {
expected.add("normal;" + colKindStr + mmProperty.getName() + " of " + mmConcept);
}
}
}

Expand All @@ -145,16 +157,25 @@ public void testContract() throws Throwable {
fail(String.join("\n", problems));
}
}

private boolean containsOnly(CollectionKind[] cks, CollectionKind expected) {
for (CollectionKind collectionKind : cks) {
if (collectionKind != expected) {
return false;
}
}
return true;
}

private CollectionKind detectCollectionKind(RoleHandler roleHandler, CtElement testedElement, CtElement argument) {
private CollectionKind[] detectCollectionKind(MetamodelProperty mmProperty, RoleHandler roleHandler, CtElement testedElement, CtElement... argument) {
switch(roleHandler.getContainerKind()) {
case MAP:
return detectCollectionKindOfMap(roleHandler, testedElement, argument);
return detectCollectionKindOfMap(mmProperty, roleHandler, testedElement, argument);
case LIST:
case SET:
return detectCollectionKindOfCollection(roleHandler, testedElement, argument);
return detectCollectionKindOfCollection(mmProperty, roleHandler, testedElement, argument);
case SINGLE:
return null;
throw new SpoonException("Single is not tested here");
}
throw new SpoonException("Unexpected container kind " + roleHandler.getContainerKind());
}
Expand All @@ -166,57 +187,141 @@ public void onAction(Action action) {
actions.add(action);
}
}

//Some roles have type List, but behaves as Set internally.
Set<CtRole> setRoles = new HashSet<>(Arrays.asList(CtRole.MODULE_DIRECTIVE,
CtRole.SERVICE_TYPE,
CtRole.EXPORTED_PACKAGE,
CtRole.OPENED_PACKAGE,
CtRole.REQUIRED_MODULE,
CtRole.PROVIDED_SERVICE, CtRole.BOUND, CtRole.VALUE));

private CollectionKind detectCollectionKindOfCollection(RoleHandler roleHandler, CtElement testedElement, CtElement argument) {
ChangeListener changeListener = new ChangeListener();
testedElement.getFactory().getEnvironment().setModelChangeListener(changeListener);
Collection col;
try {
col = (Collection) roleHandler.getValue(testedElement);
} catch (Exception e) {
return null;
}
try {
col.add(argument);
} catch (UnsupportedOperationException e) {
return CollectionKind.READ_ONLY;
private CollectionKind[] detectCollectionKindOfCollection(MetamodelProperty mmProperty, RoleHandler roleHandler, CtElement testedElement, CtElement... arguments) {
if (roleHandler.getRole()==CtRole.MODULE_DIRECTIVE) {
this.getClass();
}
Collection col2 = (Collection) roleHandler.getValue(testedElement);
if (col2.contains(argument) == false) {
return CollectionKind.MUTABLE_DETACHED;
}
if (argument.isParentInitialized() && argument.getParent() == testedElement) {
//the parent was set - OK
if (changeListener.actions.size() > 0) {
//the change event was sent - OK
return CollectionKind.MUTABLE_ATTACHED_CORRECT;
CollectionKind[] ck = new CollectionKind[arguments.length];
for (int i = 0; i < arguments.length; i++) {
CtElement argument = arguments[i];
CtElement parentOfArgument = getParentOrNull(argument);
Collection col;
ChangeListener changeListener = new ChangeListener();
testedElement.getFactory().getEnvironment().setModelChangeListener(changeListener);
col = (Collection) roleHandler.getValue(testedElement);
try {
col.add(argument);
} catch (UnsupportedOperationException e) {
ck[i] = CollectionKind.READ_ONLY;
boolean isSet;
try {
Collection c = roleHandler.asCollection(testedElement);
isSet = c instanceof Set || setRoles.contains(roleHandler.getRole());
c.add(argument);
} catch (UnsupportedOperationException e2) {
if (mmProperty.isDerived()) {
//OK, it is not allowed to add values into derived collection
continue;
}
throw e2;
}
Collection col2 = (Collection) roleHandler.getValue(testedElement);
if (mmProperty.isUnsettable()) {
//the setter of unsettable property can be called, but it changes nothing
assertFalse(col2.contains(argument));
assertSame(parentOfArgument, getParentOrNull(argument));
assertTrue(changeListener.actions.isEmpty());
} else {
if (i > 0 && isSet) {
//do not check second add into a Set. The second argument is often equal to first argument,
//so it is not added to Set ... but it is not interesting for this test
continue;
}
assertTrue(col2.contains(argument));
assertTrue(argument.isParentInitialized());
//getParent is not enough, because CtInvocation has CtExecutable which gets some values internally
assertTrue(argument.hasParent(testedElement));
assertTrue(changeListener.actions.size() > 0);
}
continue;
}
Collection col2 = (Collection) roleHandler.getValue(testedElement);
if (col2.contains(argument) == false) {
ck[i] = CollectionKind.MUTABLE_DETACHED;
continue;
}
if (argument.isParentInitialized() && argument.getParent() == testedElement) {
//the parent was set - OK
if (changeListener.actions.size() > 0) {
//the change event was sent - OK
ck[i] = CollectionKind.MUTABLE_ATTACHED_CORRECT;
continue;
}
}
ck[i] = CollectionKind.MUTABLE_ATTACHED_INCORRECT;
}
return CollectionKind.MUTABLE_ATTACHED_INCORRECT;
return ck;
}

private CollectionKind detectCollectionKindOfMap(RoleHandler roleHandler, CtElement testedElement, CtElement argument) {
ChangeListener changeListener = new ChangeListener();
testedElement.getFactory().getEnvironment().setModelChangeListener(changeListener);

Map<String, CtElement> col = (Map) roleHandler.getValue(testedElement);
try {
col.put("x", argument);
} catch (UnsupportedOperationException e) {
return CollectionKind.READ_ONLY;
private CtElement getParentOrNull(CtElement argument) {
if (argument.isParentInitialized()) {
return argument.getParent();
}
Map<String, CtElement> col2 = (Map<String, CtElement>) roleHandler.getValue(testedElement);
if (col2.get("x") != argument) {
return CollectionKind.MUTABLE_DETACHED;
}
if (argument.isParentInitialized() && argument.getParent() == testedElement) {
//the parent was set - OK
if (changeListener.actions.size() > 0) {
//the change event was sent - OK
return CollectionKind.MUTABLE_ATTACHED_CORRECT;
return null;
}

private CollectionKind[] detectCollectionKindOfMap(MetamodelProperty mmProperty, RoleHandler roleHandler, CtElement testedElement, CtElement... arguments) {
Map<String, CtElement> col = (Map) roleHandler.getValue(testedElement);
CollectionKind[] ck = new CollectionKind[arguments.length];
for (int i = 0; i < arguments.length; i++) {
String key = "x"+i;
CtElement argument = arguments[i];
CtElement parentOfArgument = getParentOrNull(argument);
ChangeListener changeListener = new ChangeListener();
testedElement.getFactory().getEnvironment().setModelChangeListener(changeListener);
try {
col.put(key, argument);
} catch (UnsupportedOperationException e) {
ck[i] = CollectionKind.READ_ONLY;
try {
roleHandler.asMap(testedElement).put(key, argument);
} catch (UnsupportedOperationException e2) {
if (mmProperty.isDerived()) {
//OK, it is not allowed to put values into derived collection
continue;
}
throw e2;
}
col = (Map) roleHandler.getValue(testedElement);
if (mmProperty.isUnsettable()) {
//the setter of unsettable property can be called, but it changes nothing
assertNull(col.get(key));
assertSame(parentOfArgument, getParentOrNull(argument));
assertTrue(changeListener.actions.isEmpty());
} else {
assertSame(argument, col.get(key));
assertTrue(argument.isParentInitialized());
assertSame(testedElement, argument.getParent());
assertTrue(changeListener.actions.size() > 0);
}

continue;
}
Map<String, CtElement> col2 = (Map<String, CtElement>) roleHandler.getValue(testedElement);
if (col2.get(key) != argument) {
ck[i] = CollectionKind.MUTABLE_DETACHED;
continue;
}
if (argument.isParentInitialized() && argument.getParent() == testedElement) {
//the parent was set - OK
if (changeListener.actions.size() > 0) {
//the change event was sent - OK
ck[i] = CollectionKind.MUTABLE_ATTACHED_CORRECT;
continue;
}
}
ck[i] = CollectionKind.MUTABLE_ATTACHED_INCORRECT;
}
return CollectionKind.MUTABLE_ATTACHED_INCORRECT;
return ck;
}

}

0 comments on commit cc2d83c

Please sign in to comment.