/*******************************************************************************
* Copyright (c) 2005, 2011, 2011 IBM Corporation, Zeligsoft Inc., 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
*
* Contributors:
* IBM - Initial API and implementation
* Zeligsoft - Bug 248869
* Axel Uhl (SAP AG) - Bug 342644
*******************************************************************************/
package org.eclipse.ocl.ecore.tests;
import java.util.Collections;
import java.util.Iterator;
import junit.framework.AssertionFailedError;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EOperation;
import org.eclipse.emf.ecore.EParameter;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcoreFactory;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.ocl.ParserException;
import org.eclipse.ocl.expressions.IteratorExp;
import org.eclipse.ocl.expressions.OCLExpression;
import org.eclipse.ocl.expressions.OperationCallExp;
import org.eclipse.ocl.expressions.PropertyCallExp;
import org.eclipse.ocl.expressions.VariableExp;
/**
* Tests support for operation constraints (pre/post/body).
*
* @author Christian W. Damus (cdamus)
*/
@SuppressWarnings("nls")
public class OperationConstraintsTest extends AbstractTestSuite {
/**
* Tests a very simple precondition, with a package and operation context.
*/
public void test_simplePrecondition() {
parseConstraint(
"package ocltest context Fruit::ripen(color : Color) : Boolean " +
"pre: color <> Color::black " +
"endpackage");
}
/**
* Tests a very simple postcondition, with a package and operation context
* and using the "self" variable to disambiguate the name "color".
*/
public void test_simplePostcondition() {
parseConstraint(
"package ocltest context Fruit::ripen(color : Color) : Boolean " +
"post: self.color = color " +
"endpackage");
}
/**
* Now that body expressions can be specified as OCL intended, we can use
* them to evaluate operations at run-time.
*/
public void test_passingOclInvalidToOperation() {
parseConstraint(
"package ocltest context Fruit::ripen(color : Color) : Boolean " +
"body: color.oclIsInvalid() " +
"endpackage");
OCLExpression<EClassifier> query = parse(
"package ocltest context Apple " +
"inv: self.ripen(if 1.3 / 0 > 1.3 then Color::black else Color::red endif) " +
"endpackage");
EObject anApple = fruitFactory.create(apple);
anApple.eSet(fruit_color, color_black);
assertTrue((Boolean) evaluate(query, anApple));
}
/**
* Tests a postcondition containing the "@ pre" construct.
*/
public void test_postcondition_atPre() {
parseConstraint(
"package ocltest context Fruit::ripen(c : Color) : Boolean " +
"post: color <> color@ pre implies color = c " +
"endpackage");
}
/**
* Tests a postcondition containing the "@pre" construct on an operation call.
*/
public void test_postcondition_atPre_operation() {
parseConstraint(
"package ocltest context Fruit::ripen(c : Color) : Boolean " +
"post: preferredColor() <> preferredColor@pre() implies color = c " +
"endpackage");
}
/**
* Tests availability of the special "result" variable in postconditions.
*/
public void test_postcondition_result() {
parseConstraint(
"package ocltest context Fruit::ripen(c : Color) : Boolean " +
"post: result implies color <> color@pre " +
"endpackage");
}
/**
* Tests that in case of multiple constraints, each defines its own namespace
* for local variables (we don't get errors on multiple declarations).
*/
public void test_namespaces() {
parseConstraint(
"package ocltest context Fruit::ripen(c : Color) : Boolean " +
"pre notBlack: let ok : Boolean = c <> Color::black in " +
" ok " +
"pre different: let ok : Boolean = c <> color in " +
" ok " +
"body: let b : Boolean = c <> color in " +
" b implies color <> Color::black " +
"post worked: result implies color <> color@pre " +
"endpackage");
}
/**
* Checks that we correctly reject illegal use of "@pre" in precondition
* constraints.
*/
public void test_illegalUseOfAtPre_precondition() {
AssertionFailedError err = null;
try {
parseConstraint(
"package ocltest context Fruit::ripen(c : Color) : Boolean " +
"pre: c <> color@pre " +
"endpackage");
} catch (AssertionFailedError e) {
// success
err = e;
System.out.println("Got expected error: " + e.getLocalizedMessage());
}
assertNotNull("Should not have succeeded in validating illegal @pre", err);
}
/**
* Checks that we correctly reject illegal use of "@pre" in invariant
* constraints.
*/
public void test_illegalUseOfAtPre_invariant() {
AssertionFailedError err = null;
try {
parseConstraint(
"package ocltest context Fruit " +
"inv: color@pre <> Color::black " +
"endpackage");
} catch (AssertionFailedError e) {
// success
err = e;
System.out.println("Got expected error: " + e.getLocalizedMessage());
}
assertNotNull("Should not have succeeded in validating illegal @pre", err);
}
/**
* Checks that we correctly reject illegal use of "@pre" in postcondition
* constraints (where the "@pre" is not used on a model property expression).
*/
public void test_illegalUseOfAtPre_postcondition() {
AssertionFailedError err = null;
try {
parseConstraint(
"package ocltest context Fruit::ripen(c : Color) : Boolean " +
"post: let oldColor : Color = color in c <> oldColor@pre " +
"endpackage");
} catch (AssertionFailedError e) {
err = e;
System.out.println("Got expected error: " + e.getLocalizedMessage());
}
assertNotNull("Should not have succeeded in validating illegal @pre", err);
}
/**
* Tests that local variables correctly hide attributes of the context
* classifier.
*/
public void test_variablesHidingAttributes() {
parseConstraint(
"package ocltest context Fruit::ripen(c : Color) : Boolean " +
"pre: let color : Boolean = (c = self.color) in " +
" color implies c <> Color::black " +
"endpackage");
}
/**
* Tests that parameters are in the same scope as local variables (cannot
* use same names).
*/
public void test_parametersAreLocalVariables() {
AssertionFailedError err = null;
try {
parseConstraint(
"package ocltest context Fruit::ripen(c : Color) : Boolean " +
"pre: color : Boolean = (c = self.color) in " +
" color implies c <> Color::black " +
"endpackage");
} catch (AssertionFailedError e) {
err = e;
System.out.println("Got expected error: " + e.getLocalizedMessage());
}
assertNotNull(err);
}
/**
* Tests that body conditions are correctly checked for conformance to the
* operation type.
*/
public void test_bodyConditionConformance() {
AssertionFailedError err = null;
try {
// try a scenario with no common supertype
parseConstraint(
"package ocltest context Fruit::preferredColor() : Color " +
"body: result = (if true then 'red' else 'brown' endif) " +
"endpackage");
} catch (AssertionFailedError e) {
err = e;
System.out.println("Got expected error: " + e.getLocalizedMessage());
}
assertNotNull(err);
err = null;
try {
// try a scenario with a common supertype
parseConstraint(
"package ocltest context Apple::newApple() : Apple " +
"body: result = self.newFruit() " +
"endpackage");
} catch (AssertionFailedError e) {
err = e;
System.out.println("Got expected error: " + e.getLocalizedMessage());
}
assertNotNull(err);
err = null;
try {
// this scenario is OK
parseConstraint(
"package ocltest context Apple::newFruit() : Fruit " +
"body: result = self.newApple() " +
"endpackage");
} catch (AssertionFailedError e) {
err = e;
System.err.println("Got unexpected error: " + e.getLocalizedMessage());
}
assertNull(err);
}
/**
* Tests that body conditions are not allowed for void operations.
*/
public void test_bodyConditionVoidOperation() {
AssertionFailedError err = null;
try {
parseConstraint(
"package ocltest context Apple::label(newLabel : String) : " +
"body: result = (if true then 'Spy' else 'Spartan' endif) " +
"endpackage");
} catch (AssertionFailedError e) {
err = e;
System.out.println("Got expected error: " + e.getLocalizedMessage());
}
assertNotNull(err);
}
/**
* Tests that when a parameter name (or any other local variable, for that
* matter) coincides with an attribute name, we correctly distinguish
* references to the variable from references to the attribute.
*/
public void test_parameterNameCoincidesWithAttributeName_140008() {
expectModified = true;
EOperation foo = EcoreFactory.eINSTANCE.createEOperation();
foo.setName("foo");
EParameter parm = EcoreFactory.eINSTANCE.createEParameter();
parm.setName("str");
parm.setEType(EcorePackage.Literals.ESTRING);
foo.getEParameters().add(parm);
foo.setEType(EcorePackage.Literals.ESTRING);
apple.getEOperations().add(foo);
EAttribute myStr = EcoreFactory.eINSTANCE.createEAttribute();
myStr.setName("str");
myStr.setEType(EcorePackage.Literals.ESTRING);
apple.getEStructuralFeatures().add(myStr);
try {
OCLExpression<EClassifier> expr = parseConstraint(
"package ocltest context Apple::foo(str : String) : String " +
"body: result = (if str = self.str then '' else str endif) " +
"endpackage");
int propertyCalls = 0;
int variableCalls = 0;
for (Iterator<?> iter = EcoreUtil.getAllContents(Collections.singleton(expr)); iter.hasNext();) {
Object next = iter.next();
if (next instanceof PropertyCallExp<?, ?>) {
@SuppressWarnings("unchecked")
PropertyCallExp<EClassifier, EStructuralFeature> pc =
(PropertyCallExp<EClassifier, EStructuralFeature>) next;
if ("str".equals(pc.getReferredProperty().getName())) {
propertyCalls++;
}
} else if (next instanceof VariableExp<?, ?>) {
@SuppressWarnings("unchecked")
VariableExp<EClassifier, EParameter> v =
(VariableExp<EClassifier, EParameter>) next;
if ("str".equals(v.getReferredVariable().getName())) {
variableCalls++;
}
}
}
assertEquals("property calls", 1, propertyCalls);
assertEquals("variable calls", 2, variableCalls);
} finally {
apple.getEOperations().remove(foo);
apple.getEStructuralFeatures().remove(myStr);
}
}
/**
* Tests that an expression resolves the implicit source of a property call
* correctly when the environment has additional variable names such as
* parameters that define the same property. This test parses raw OCL.
*/
@SuppressWarnings("unchecked")
public void test_implicitPropertySourceLookup_raw_151234() {
OCLExpression<EClassifier> expr = parseConstraint(
"package ocltest context Apple::setColor(fruit : Fruit, newColor : Color) : " +
"pre: color <> newColor " +
"endpackage");
assertTrue(expr instanceof OperationCallExp<?, ?>);
OperationCallExp<EClassifier, EOperation> notEquals =
(OperationCallExp<EClassifier, EOperation>) expr;
assertTrue(notEquals.getSource() instanceof PropertyCallExp<?, ?>);
PropertyCallExp<EClassifier, EStructuralFeature> propertyCall =
(PropertyCallExp<EClassifier, EStructuralFeature>) notEquals.getSource();
assertTrue(propertyCall.getSource() instanceof VariableExp<?, ?>);
VariableExp<EClassifier, EParameter> var =
(VariableExp<EClassifier, EParameter>) propertyCall.getSource();
// we did not resolve against "fruit", which also has a color property
assertEquals("self", var.getReferredVariable().getName());
// now check the resolution of implicit iterator variables as sources
expr = parseConstraint(
"package ocltest context Apple::setColor(fruit : Fruit, newColor : Color) : " +
"pre: Fruit.allInstances()->forAll(color <> newColor) " +
"endpackage");
assertTrue(expr instanceof IteratorExp<?, ?>);
IteratorExp<EClassifier, EParameter> forAll =
(IteratorExp<EClassifier, EParameter>) expr;
assertTrue(forAll.getBody() instanceof OperationCallExp<?, ?>);
notEquals = (OperationCallExp<EClassifier, EOperation>) forAll.getBody();
assertTrue(notEquals.getSource() instanceof PropertyCallExp<?, ?>);
propertyCall = (PropertyCallExp<EClassifier, EStructuralFeature>) notEquals.getSource();
assertTrue(propertyCall.getSource() instanceof VariableExp<?, ?>);
var = (VariableExp<EClassifier, EParameter>) propertyCall.getSource();
// we did not resolve against "fruit", which also has a color property
assertTrue(var.getReferredVariable().getName().startsWith("temp"));
}
/**
* Tests that an expression resolves the implicit source of a property call
* correctly when the environment has additional variable names such as
* parameters that define the same property. This test uses a helper.
*/
@SuppressWarnings("unchecked")
public void test_implicitPropertySourceLookup_helper_151234() {
try {
helper.setOperationContext(fruit, fruit_setColor);
OCLExpression<EClassifier> expr =
helper.createPrecondition("color <> newColor").getSpecification().getBodyExpression();
assertTrue(expr instanceof OperationCallExp<?, ?>);
OperationCallExp<EClassifier, EOperation> notEquals =
(OperationCallExp<EClassifier, EOperation>) expr;
assertTrue(notEquals.getSource() instanceof PropertyCallExp<?, ?>);
PropertyCallExp<EClassifier, EStructuralFeature> propertyCall =
(PropertyCallExp<EClassifier, EStructuralFeature>) notEquals.getSource();
assertTrue(propertyCall.getSource() instanceof VariableExp<?, ?>);
VariableExp<EClassifier, EParameter> var =
(VariableExp<EClassifier, EParameter>) propertyCall.getSource();
// we did not resolve against "fruit", which also has a color property
assertEquals("self", var.getReferredVariable().getName());
// now check the resolution of implicit iterator variables as sources
expr = helper.createPrecondition(
"Fruit.allInstances()->forAll(color <> newColor)").getSpecification().getBodyExpression();
assertTrue(expr instanceof IteratorExp<?, ?>);
IteratorExp<EClassifier, EParameter> forAll = (IteratorExp<EClassifier, EParameter>) expr;
assertTrue(forAll.getBody() instanceof OperationCallExp<?, ?>);
notEquals = (OperationCallExp<EClassifier, EOperation>) forAll.getBody();
assertTrue(notEquals.getSource() instanceof PropertyCallExp<?, ?>);
propertyCall = (PropertyCallExp<EClassifier, EStructuralFeature>) notEquals.getSource();
assertTrue(propertyCall.getSource() instanceof VariableExp<?, ?>);
var = (VariableExp<EClassifier, EParameter>) propertyCall.getSource();
// we did not resolve against "fruit", which also has a color property
assertTrue(var.getReferredVariable().getName().startsWith("temp"));
} catch (ParserException e) {
fail("Parse failed: " + e.getLocalizedMessage());
}
}
/**
* Tests that an expression resolves the implicit source of an operation call
* correctly when the environment has additional variable names such as
* parameters that define the same property. This test parses raw OCL.
*/
@SuppressWarnings("unchecked")
public void test_implicitOperationSourceLookup_raw_151234() {
OCLExpression<EClassifier> expr = parseConstraint(
"package ocltest context Apple::setColor(fruit : Fruit, newColor : Color) : " +
"pre: preferredColor() <> newColor " +
"endpackage");
assertTrue(expr instanceof OperationCallExp<?, ?>);
OperationCallExp<EClassifier, EOperation> notEquals =
(OperationCallExp<EClassifier, EOperation>) expr;
assertTrue(notEquals.getSource() instanceof OperationCallExp<?, ?>);
OperationCallExp<EClassifier, EOperation> operationCall =
(OperationCallExp<EClassifier, EOperation>) notEquals.getSource();
assertTrue(operationCall.getSource() instanceof VariableExp<?, ?>);
VariableExp<EClassifier, EParameter> var =
(VariableExp<EClassifier, EParameter>) operationCall.getSource();
// we did not resolve against "fruit", which also has a color property
assertEquals("self", var.getReferredVariable().getName());
// now check the resolution of implicit iterator variables as sources
expr = parseConstraint(
"package ocltest context Apple::setColor(fruit : Fruit, newColor : Color) : " +
"pre: Fruit.allInstances()->forAll(preferredColor() <> newColor) " +
"endpackage");
assertTrue(expr instanceof IteratorExp<?, ?>);
IteratorExp<EClassifier, EParameter> forAll = (IteratorExp<EClassifier, EParameter>) expr;
assertTrue(forAll.getBody() instanceof OperationCallExp<?, ?>);
notEquals = (OperationCallExp<EClassifier, EOperation>) forAll.getBody();
assertTrue(notEquals.getSource() instanceof OperationCallExp<?, ?>);
operationCall = (OperationCallExp<EClassifier, EOperation>) notEquals.getSource();
assertTrue(operationCall.getSource() instanceof VariableExp<?, ?>);
var = (VariableExp<EClassifier, EParameter>) operationCall.getSource();
// we did not resolve against "fruit", which also has a color property
assertTrue(var.getReferredVariable().getName().startsWith("temp"));
}
/**
* Tests that an expression resolves the implicit source of an operation call
* correctly when the environment has additional variable names such as
* parameters that define the same property. This test uses a helper.
*/
@SuppressWarnings("unchecked")
public void test_implicitOperationSourceLookup_helper_151234() {
try {
helper.setOperationContext(fruit, fruit_setColor);
OCLExpression<EClassifier> expr = helper.createPrecondition(
"preferredColor() <> newColor").getSpecification().getBodyExpression();
assertTrue(expr instanceof OperationCallExp<?, ?>);
OperationCallExp<EClassifier, EOperation> notEquals =
(OperationCallExp<EClassifier, EOperation>) expr;
assertTrue(notEquals.getSource() instanceof OperationCallExp<?, ?>);
OperationCallExp<EClassifier, EOperation> operationCall =
(OperationCallExp<EClassifier, EOperation>) notEquals.getSource();
assertTrue(operationCall.getSource() instanceof VariableExp<?, ?>);
VariableExp<EClassifier, EParameter> var =
(VariableExp<EClassifier, EParameter>) operationCall.getSource();
// we did not resolve against "fruit", which also has a color property
assertEquals("self", var.getReferredVariable().getName());
// now check the resolution of implicit iterator variables as sources
expr = helper.createPrecondition(
"Fruit.allInstances()->forAll(preferredColor() <> newColor)").getSpecification().getBodyExpression();
assertTrue(expr instanceof IteratorExp<?, ?>);
IteratorExp<EClassifier, EParameter> forAll =
(IteratorExp<EClassifier, EParameter>) expr;
assertTrue(forAll.getBody() instanceof OperationCallExp<?, ?>);
notEquals = (OperationCallExp<EClassifier, EOperation>) forAll.getBody();
assertTrue(notEquals.getSource() instanceof OperationCallExp<?, ?>);
operationCall = (OperationCallExp<EClassifier, EOperation>) notEquals.getSource();
assertTrue(operationCall.getSource() instanceof VariableExp<?, ?>);
var = (VariableExp<EClassifier, EParameter>) operationCall.getSource();
// we did not resolve against "fruit", which also has a color property
assertTrue(var.getReferredVariable().getName().startsWith("temp"));
} catch (ParserException e) {
fail("Parse failed: " + e.getLocalizedMessage());
}
}
/**
* Tests that body conditions are correctly checked for conformance to the
* operation type when they are specified as OCL body expressions, not as
* UML body constraints.
*/
public void test_bodyExpressionConformance_185345() {
AssertionFailedError err = null;
try {
// try a scenario with no common supertype
parseConstraint(
"package ocltest context Fruit::preferredColor() : Color " +
"body: if true then 'red' else 'brown' endif " +
"endpackage");
} catch (AssertionFailedError e) {
err = e;
System.out.println("Got expected error: " + e.getLocalizedMessage());
}
assertNotNull(err);
err = null;
try {
// try a scenario with a common supertype
parseConstraint(
"package ocltest context Apple::newApple() : Apple " +
"body: self.newFruit() " +
"endpackage");
} catch (AssertionFailedError e) {
err = e;
System.out.println("Got expected error: " + e.getLocalizedMessage());
}
assertNotNull(err);
err = null;
try {
// this scenario is OK
parseConstraint(
"package ocltest context Apple::newFruit() : Fruit " +
"body: self.newApple() " +
"endpackage");
} catch (AssertionFailedError e) {
err = e;
System.err.println("Got unexpected error: " + e.getLocalizedMessage());
}
assertNull(err);
}
/**
* Tests that OCL body expressions cannot reference the result of the
* operation.
*/
public void test_bodyExpressionResultUsage_185345() {
AssertionFailedError err = null;
try {
// not allowed to reference the result variable
parseConstraint(
"package ocltest context Apple::preferredLabel(text : String) : String " +
"body: if true then text else result endif " +
"endpackage");
} catch (AssertionFailedError e) {
err = e;
System.out.println("Got expected error: " + e.getLocalizedMessage());
}
assertNotNull(err);
err = null;
// unless, of course, 'result' is a parameter name
parseConstraint(
"package ocltest context Apple::preferredLabel(result : String) : String " +
"body: if true then 'me' else result endif " +
"endpackage");
}
/**
* Now that body expressions can be specified as OCL intended, we can use
* them to evaluate operations at run-time.
*/
public void test_bodyExpressionEvaluation_185345() {
parseConstraint(
"package ocltest context Fruit::preferredColor() : Color " +
"body: if color = Color::black then Color::red else Color::black endif " +
"endpackage");
OCLExpression<EClassifier> query = parse(
"package ocltest context Apple " +
"inv: self.preferredColor() " +
"endpackage");
EObject anApple = fruitFactory.create(apple);
anApple.eSet(fruit_color, color_black);
assertSame(color_red, evaluate(query, anApple));
anApple.eSet(fruit_color, color_green);
assertSame(color_black, evaluate(query, anApple));
}
}