/******************************************************************************* * Copyright (c) 2005, 2009 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 *******************************************************************************/ package org.eclipse.ocl.uml.tests; import java.util.Collections; import java.util.Iterator; import junit.framework.AssertionFailedError; import org.eclipse.emf.common.util.BasicEList; 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; import org.eclipse.uml2.uml.Classifier; import org.eclipse.uml2.uml.Operation; import org.eclipse.uml2.uml.Parameter; import org.eclipse.uml2.uml.Property; import org.eclipse.uml2.uml.Type; /** * 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"); } /** * 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 " + " result = 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; Operation foo = apple.createOwnedOperation("foo", new BasicEList<String>(Collections.singleton("str")), new BasicEList<Type>(Collections.singleton((Type) getUMLString())), getUMLString()); Property myStr = apple.createOwnedAttribute("str", getUMLString()); try { OCLExpression<Classifier> 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<Classifier, Property> pc = (PropertyCallExp<Classifier, Property>) next; if ("str".equals(pc.getReferredProperty().getName())) { propertyCalls++; } } else if (next instanceof VariableExp<?, ?>) { @SuppressWarnings("unchecked") VariableExp<Classifier, Parameter> v = (VariableExp<Classifier, Parameter>) next; if ("str".equals(v.getReferredVariable().getName())) { variableCalls++; } } } assertEquals("property calls", 1, propertyCalls); assertEquals("variable calls", 2, variableCalls); } finally { apple.getOwnedOperations().remove(foo); apple.getOwnedAttributes().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<Classifier> expr = parseConstraint( "package ocltest context Apple::setColor(fruit : Fruit, newColor : Color) : " + "pre: color <> newColor " + "endpackage"); assertTrue(expr instanceof OperationCallExp<?, ?>); OperationCallExp<Classifier, Operation> notEquals = (OperationCallExp<Classifier, Operation>) expr; assertTrue(notEquals.getSource() instanceof PropertyCallExp<?, ?>); PropertyCallExp<Classifier, Property> propertyCall = (PropertyCallExp<Classifier, Property>) notEquals.getSource(); assertTrue(propertyCall.getSource() instanceof VariableExp<?, ?>); VariableExp<Classifier, Parameter> var = (VariableExp<Classifier, Parameter>) 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<Classifier, Parameter> forAll = (IteratorExp<Classifier, Parameter>) expr; assertTrue(forAll.getBody() instanceof OperationCallExp<?, ?>); notEquals = (OperationCallExp<Classifier, Operation>) forAll.getBody(); assertTrue(notEquals.getSource() instanceof PropertyCallExp<?, ?>); propertyCall = (PropertyCallExp<Classifier, Property>) notEquals.getSource(); assertTrue(propertyCall.getSource() instanceof VariableExp<?, ?>); var = (VariableExp<Classifier, Parameter>) 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<Classifier> expr = getBodyExpression(helper.createPrecondition("color <> newColor")); assertTrue(expr instanceof OperationCallExp<?, ?>); OperationCallExp<Classifier, Operation> notEquals = (OperationCallExp<Classifier, Operation>) expr; assertTrue(notEquals.getSource() instanceof PropertyCallExp<?, ?>); PropertyCallExp<Classifier, Property> propertyCall = (PropertyCallExp<Classifier, Property>) notEquals.getSource(); assertTrue(propertyCall.getSource() instanceof VariableExp<?, ?>); VariableExp<Classifier, Parameter> var = (VariableExp<Classifier, Parameter>) 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 = getBodyExpression(helper.createPrecondition( "Fruit.allInstances()->forAll(color <> newColor)")); assertTrue(expr instanceof IteratorExp<?, ?>); IteratorExp<Classifier, Parameter> forAll = (IteratorExp<Classifier, Parameter>) expr; assertTrue(forAll.getBody() instanceof OperationCallExp<?, ?>); notEquals = (OperationCallExp<Classifier, Operation>) forAll.getBody(); assertTrue(notEquals.getSource() instanceof PropertyCallExp<?, ?>); propertyCall = (PropertyCallExp<Classifier, Property>) notEquals.getSource(); assertTrue(propertyCall.getSource() instanceof VariableExp<?, ?>); var = (VariableExp<Classifier, Parameter>) 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<Classifier> expr = parseConstraint( "package ocltest context Apple::setColor(fruit : Fruit, newColor : Color) : " + "pre: preferredColor() <> newColor " + "endpackage"); assertTrue(expr instanceof OperationCallExp<?, ?>); OperationCallExp<Classifier, Operation> notEquals = (OperationCallExp<Classifier, Operation>) expr; assertTrue(notEquals.getSource() instanceof OperationCallExp<?, ?>); OperationCallExp<Classifier, Operation> operationCall = (OperationCallExp<Classifier, Operation>) notEquals.getSource(); assertTrue(operationCall.getSource() instanceof VariableExp<?, ?>); VariableExp<Classifier, Parameter> var = (VariableExp<Classifier, Parameter>) 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<Classifier, Parameter> forAll = (IteratorExp<Classifier, Parameter>) expr; assertTrue(forAll.getBody() instanceof OperationCallExp<?, ?>); notEquals = (OperationCallExp<Classifier, Operation>) forAll.getBody(); assertTrue(notEquals.getSource() instanceof OperationCallExp<?, ?>); operationCall = (OperationCallExp<Classifier, Operation>) notEquals.getSource(); assertTrue(operationCall.getSource() instanceof VariableExp<?, ?>); var = (VariableExp<Classifier, Parameter>) 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<Classifier> expr = getBodyExpression( helper.createPrecondition("preferredColor() <> newColor")); assertTrue(expr instanceof OperationCallExp<?, ?>); OperationCallExp<Classifier, Operation> notEquals = (OperationCallExp<Classifier, Operation>) expr; assertTrue(notEquals.getSource() instanceof OperationCallExp<?, ?>); OperationCallExp<Classifier, Operation> operationCall = (OperationCallExp<Classifier, Operation>) notEquals.getSource(); assertTrue(operationCall.getSource() instanceof VariableExp<?, ?>); VariableExp<Classifier, Parameter> var = (VariableExp<Classifier, Parameter>) 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 = getBodyExpression(helper.createPrecondition( "Fruit.allInstances()->forAll(preferredColor() <> newColor)")); assertTrue(expr instanceof IteratorExp<?, ?>); IteratorExp<Classifier, Parameter> forAll = (IteratorExp<Classifier, Parameter>) expr; assertTrue(forAll.getBody() instanceof OperationCallExp<?, ?>); notEquals = (OperationCallExp<Classifier, Operation>) forAll.getBody(); assertTrue(notEquals.getSource() instanceof OperationCallExp<?, ?>); operationCall = (OperationCallExp<Classifier, Operation>) notEquals.getSource(); assertTrue(operationCall.getSource() instanceof VariableExp<?, ?>); var = (VariableExp<Classifier, Parameter>) 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()); } } }