diff --git a/src/test/java/spoon/test/parent/CollectionsContractTest.java b/src/test/java/spoon/test/parent/CollectionsContractTest.java index 795cfe713f4..2198393c950 100644 --- a/src/test/java/spoon/test/parent/CollectionsContractTest.java +++ b/src/test/java/spoon/test/parent/CollectionsContractTest.java @@ -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; @@ -98,10 +102,6 @@ public void testContract() throws Throwable { // contract: check type of collection returned by getter // read only / modifiable-detached / modifiable-attached Class elementClass = (Class) mmConcept.getModelInterface().getActualClass(); - CtElement testedElement = factory.Core().create(elementClass); - if (elementClass.equals(CtTypeReference.class)) { - testedElement = factory.Type().createReference(ArrayList.class); - } List problems = new ArrayList<>(); List expected = new ArrayList<>(); @@ -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); + } } } @@ -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()); } @@ -166,57 +187,141 @@ public void onAction(Action action) { actions.add(action); } } + + //Some roles have type List, but behaves as Set internally. + Set 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 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 col2 = (Map) 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 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 col2 = (Map) 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; } }