package fr.inria.atlanmod.neo4emf.tests.reflection; import static org.junit.Assert.fail; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.emf.common.util.BasicEList; import org.eclipse.emf.common.util.ECollections; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.TreeIterator; import org.eclipse.emf.ecore.EAttribute; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EClassifier; import org.eclipse.emf.ecore.EDataType; import org.eclipse.emf.ecore.EFactory; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.util.EcoreUtil; import org.junit.After; import org.junit.AfterClass; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import fr.inria.atlanmod.neo4emf.INeo4emfResource; @RunWith(Parameterized.class) public class StructuralTest { private EPackage ePackage; private EFactory eFactory; //private Map mapping; private EList<EClassifier> packageClassifiers; private INeo4emfResource neo4emfRoot; private ValueFactory valueFactory; @Parameterized.Parameters public static List<Object[]> data() { return InputData.allData("Structural"); } public StructuralTest(Object currentEPackage, Object currentEFactory, Object neoResource) { ePackage = (EPackage)currentEPackage; eFactory = (EFactory)currentEFactory; neo4emfRoot = (INeo4emfResource)neoResource; // Register the package EPackage.Registry.INSTANCE.put(ePackage.getName().toLowerCase(), ePackage); // extract classes from the package packageClassifiers = ePackage.getEClassifiers(); valueFactory = new ValueFactory(ePackage, eFactory); } @Before public void setUp() throws Exception { } @After public void tearDown() throws Exception { neo4emfRoot.getContents().clear(); } /** * Iterate through the EClass in the metamodel and instantiate them through the * factory. * Check if the instances have been correctly created and if their EAttributes are * set to their default values. */ @Test public void testCreateClassInstance() { for(EClassifier currentClassifier : packageClassifiers) { EClass currentClass = (EClass)currentClassifier; EObject classInstance = eFactory.create(currentClass); //Assert.assertNotEquals("Instance of the class \""+currentClass.getName()+"\" is null.", classInstance, null); for(EObject it : currentClass.eContents()) { if(it instanceof EStructuralFeature) { EStructuralFeature feature = (EStructuralFeature)it; if(feature.isMany()) { // There is no default value for multi-valued features, it is always // an empty EList. Assert.assertEquals("Multi-valued feature \""+feature.getName()+"\" of object \""+currentClass.getName()+"\" is not empty.", ECollections.EMPTY_ELIST, classInstance.eGet(feature)); } else { Assert.assertEquals("Feature \""+feature.getName()+"\" of object \""+currentClass.getName()+"\" is not equal to its default value", feature.getDefaultValue(), classInstance.eGet(feature)); } } } } } /** * Iterate through the EAttributes of each EClass in the metamodel and set their value to * a random generated value. * Check if the EAttributes have been correctly set (through eGet method) or have generated an * exception (in case they are unchangeable). */ @Test public void testSetAttribute() { for(EClassifier currentClassifier : packageClassifiers) { EClass currentClass = (EClass)currentClassifier; for(EObject it : currentClass.eContents()) { EObject classInstance = eFactory.create(currentClass); if(it instanceof EAttribute) { EAttribute attribute = (EAttribute)it; Object attributeValue = valueFactory.generateValue(attribute); if(attribute.isChangeable()) { classInstance.eSet(attribute,attributeValue); Assert.assertEquals("Attribute \""+attribute.getName()+"\" of object \""+currentClass.getName()+"\" not set properly.", classInstance.eGet(attribute), attributeValue); } else { try { classInstance.eSet(attribute,attributeValue); fail("Unchangeable attribute \"" +attribute.getName()+"\" of object \""+currentClass.getName()+"\" has been changed."); }catch(IllegalArgumentException e) { // Check eventual side effects even if the exception has been correctly thrown Assert.assertEquals("Unchangeable attribute \""+attribute.getName()+"\" of object \""+currentClass.getName()+"\" is no longer equal to its default value.", classInstance.eGet(attribute), attribute.getDefaultValue()); } } } } } } /** * Iterate through the EStructuralFeatures of each EClass in the metamodel and check double * addition in unique ones. * Check that the generated EStructuralFeature contains no double elements. */ @Test public void testSetOrderedFeatures() { for(EClassifier currentClassifier : packageClassifiers) { EClass currentClass = (EClass)currentClassifier; for(EObject it : currentClass.eContents()) { if(it instanceof EStructuralFeature) { EStructuralFeature feature = (EStructuralFeature)it; if(feature.isOrdered()) { if(feature.isMany()) { // Tests are only done on multi valued EStructuralFeatures, // single valued EStructuralFeatures obviously cannot contains // double elements. EObject classInstance = eFactory.create(currentClass); // Do not call generateValue, it will return a list. Object value = (Object)valueFactory.getValue(feature.getEType()); EList<Object> toSet = new BasicEList<Object>(); toSet.add(value); toSet.add(value); classInstance.eSet(feature, toSet); @SuppressWarnings("unchecked") EList<Object> inInstanceValues = (EList<Object>)classInstance.eGet(feature); assert inInstanceValues.size() <= 1 : "Unique attribute \"" + feature.getName() + "\" of object \"" + currentClass.getName() + "\" contains more element than expected (" + (inInstanceValues.size()) + " instead of 1)."; assert inInstanceValues.size() > 0 : "Unique attribute \"" + feature.getName() + "\" of object \"" + currentClass.getName() + "\" is empty, expected 1 element."; assert inInstanceValues.get(0).equals(value) : "Unique attribute \"" + feature.getName() + "\" of object \"" + currentClass.getName() + "\" is not equal to the set one"; } } } } } } /** * Iterate through the EReferences of each EClass in the metamodel and set their value to * a generated EClass instance. * Check if the EReferences have been correctly set (through eGet method) or have generated an * exception (in case they are unchangeable). */ @Test public void testSetReference() { for(EClassifier currentClassifier : packageClassifiers) { EClass currentClass = (EClass)currentClassifier; for(EObject it : currentClass.eContents()) { // loop in the contents and instantiate a model EObject classInstance = eFactory.create(currentClass); if(it instanceof EReference){ EReference reference = (EReference)it; Object referenceValue = valueFactory.generateValue(reference); checkEReferences(classInstance, reference, referenceValue); checkEOppositeReference(classInstance, reference, referenceValue); } } } } /** * Create an object with all its direct links (EAttribute and EReference) * and delete it. * The test check that * - the object has been removed from its resource set * - the EAttribute instances related to the object has been deleted * - the EReference instances related to the object has been deleted * - the opposite EReference values has been properly set to null * - the EReference with attribute "contained" has been deleted. */ @Test public void testDeleteClassInstance() { for(EClassifier currentClassifier : packageClassifiers) { EClass currentClass = (EClass)currentClassifier; EObject classInstance = eFactory.create(currentClass); neo4emfRoot.getContents().add(classInstance); // EObjects contained in classInstance. List<EObject> containedEObjects = new ArrayList<EObject>(); // EObjects related to classInstance, save also the EReference handling the // association for convenience. Map<EObject,EReference> relatedEObjects = new HashMap<EObject,EReference>(); // Populate the instance with all its direct referenced EObjects // EAttributes are not tested here because they are local to the instance // and cannot create consistency issues. for(EObject it : currentClass.eContents()) { if(it instanceof EReference) { EReference reference = (EReference)it; if(reference.isChangeable()) { Object referenceValue = valueFactory.generateValue(reference); if(referenceValue instanceof EObject) { neo4emfRoot.getContents().add((EObject)referenceValue); } else if(referenceValue instanceof EList) { neo4emfRoot.getContents().addAll((EList<EObject>)referenceValue); } else { fail("Fail to generate proper value for EReference \"" + reference.getName() + "\"."); } classInstance.eSet(reference, referenceValue); if(reference.isContainment()) { if(referenceValue instanceof EObject) { containedEObjects.add((EObject)referenceValue); } else if(referenceValue instanceof EList) { containedEObjects.addAll((EList<EObject>)referenceValue); } else { fail("Fail to generate proper value for EReference \"" + reference.getName() + "\"."); } } else { // Simple EReference without containment feature if(referenceValue instanceof EObject) { if(reference.getEOpposite() != null) { relatedEObjects.put((EObject)referenceValue,reference.getEOpposite()); } } else if(referenceValue instanceof EList) { for(EObject currentReferenceValue : (EList<EObject>)referenceValue) { if(reference.getEOpposite() != null) { relatedEObjects.put(currentReferenceValue,reference.getEOpposite()); } } } else { fail("Fail to generate proper value for EReference \"" + reference.getName() + "\"."); } } } } } EObject instanceContainer = classInstance.eContainer(); EcoreUtil.delete(classInstance); checkDeletedFromRoot(classInstance); if(instanceContainer != null) { assert !instanceContainer.eContents().contains(classInstance) : "Deleted EObject's (" + currentClass.getName() + ") parent still contains it after its deletion (eContents method)."; } // Iterate through the deleted EObject's contained elements and check they have // been correctly deleted. for(EObject containedEObject : containedEObjects) { checkDeletedFromRoot(containedEObject); } // Iterator through the objects referencing the deleted one and check the EReferences // has been correctly updated. for(EObject relatedObject : relatedEObjects.keySet()) { EReference relatedEReference = relatedEObjects.get(relatedObject); // Non changeable references are not updated when the EObject they reference to // is deleted. (strange behaviour, investigating) if(relatedEReference.isChangeable()) { Object referencedValue = relatedObject.eGet(relatedEReference); if(referencedValue instanceof EObject) { assert !referencedValue.equals(classInstance); } else if(referencedValue instanceof EList) { assert !((EList<EObject>)referencedValue).contains(classInstance); } } } // Clean the resource for the next class instance. neo4emfRoot.getContents().clear(); } } /** * Set the EReference value of classInstance to the given referenceValue and check it * has been correctly updated (or not if the reference is not changeable). * @param classInstance the EObject containing the EReference to check. * @param reference the EReference to check. * @param referenceValue the value to set and check to the EReference for the given EObject. */ private void checkEReferences(EObject classInstance, EReference reference, Object referenceValue) { if(reference.isChangeable()) { classInstance.eSet(reference,referenceValue); Assert.assertEquals("Reference \""+reference.getName()+ "\" (" + reference.getEReferenceType().getName() + ") of object \"" + classInstance.eClass().getName()+" not set properly.", classInstance.eGet(reference), referenceValue); } else { try { classInstance.eSet(reference,referenceValue); fail("Unchangeable attribute \"" + reference.getName()+ "\" of object \""+classInstance.eClass().getName()+"\" has been changed."); }catch(IllegalArgumentException e) { // Check eventual side effects even if the exception has been correctly thrown Assert.assertEquals("Unchangeable attribute \""+reference.getName()+"\" of object \""+classInstance.eClass().getName()+"\" is no longer equal to its default value.", classInstance.eGet(reference), reference.getDefaultValue()); } } } /** * Check if the opposite EReference of "reference" is consistent with its current value for * the given classInstance. * @param classInstance the EObject containing the EReference to check. * @param reference the EReference to check. * @param referenceValue the current value of the EReference. * @note the Changeable property is not verified because EReferences are updated even if * they are not changeable. */ private void checkEOppositeReference(EObject classInstance, EReference reference, Object referenceValue) { EReference oppositeReference = reference.getEOpposite(); if(oppositeReference != null) { if(reference.isChangeable()) { if(reference.isMany()) { // Iterate through all the referenced EObject and check their opposite // reference has been correctly set @SuppressWarnings("unchecked") EList<EObject> referencedEObjects = (EList<EObject>)referenceValue; for(EObject singleReferencedEObject : referencedEObjects) { assert(checkEReferenceContainment(singleReferencedEObject, oppositeReference, classInstance)); } } else { EObject referencedEObject = (EObject)referenceValue; assert(checkEReferenceContainment(referencedEObject, oppositeReference, classInstance)); } } else { // Check the opposite EReference hasn't been modified if(reference.isMany()) { @SuppressWarnings("unchecked") EList<EObject> referencedEObjects = (EList<EObject>)referenceValue; for(EObject singleReferencedEObject : referencedEObjects) { assert(!checkEReferenceContainment(singleReferencedEObject, oppositeReference, classInstance)); } } else { EObject referencedEObject = (EObject)referenceValue; assert(!checkEReferenceContainment(referencedEObject, oppositeReference, classInstance)); } } } } /** * Check that instance is contained in the referenced elements of "from" EObject * through "reference" feature. * @param from the EObject to check from * @param reference the EReference * @param instance the instance to look for * @return true if instance is contained in the referenced elements of "from", false otherwise */ private boolean checkEReferenceContainment(EObject from, EReference reference, EObject instance) { Object referenceContent = from.eGet(reference); if(reference.isMany()) { @SuppressWarnings("unchecked") EList<EObject> referencedEObjects = (EList<EObject>)referenceContent; return(referencedEObjects.contains(instance)); } else { EObject referencedEObject = (EObject)referenceContent; return(referencedEObject.equals(instance)); } } /** * Check the given EObject has been correctly deleted of its root resource. * @param classInstance the EObject to check to. */ private void checkDeletedFromRoot(EObject classInstance) { EClass currentClass = classInstance.eClass(); assert !neo4emfRoot.getContents().contains(classInstance) : "Resource previously registering the EObject \"" + currentClass.getName() + "\" still contains the EObject after its deletion (getContents method)."; assert !neo4emfRoot.getAllInstances(currentClass).contains(classInstance) : "Resource previously registering the EObject \"" + currentClass.getName() + "\" still contains the EObject after its deletion (getAllInstances method)."; TreeIterator<EObject> allContents = neo4emfRoot.getAllContents(); boolean containsClassInstance = false; while(allContents.hasNext()) { if(allContents.next().equals(classInstance)) { containsClassInstance = true; } } assert !containsClassInstance : "Resource previously registering the EObject \"" + currentClass.getName() + "\" still contains the EObject after its deletion (getAllContents method)."; } }