blob: ff70ea8fcf36fcd4483be0dc1a11624b79d10d57 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2018 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* IBM - Initial API and implementation
*******************************************************************************/
package org.eclipse.ocl.ecore.tests;
import java.util.List;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EEnumLiteral;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EOperation;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EParameter;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcoreFactory;
import org.eclipse.ocl.Environment;
import org.eclipse.ocl.ecore.CallOperationAction;
import org.eclipse.ocl.ecore.Constraint;
import org.eclipse.ocl.ecore.EcoreEnvironment;
import org.eclipse.ocl.ecore.EcoreEnvironmentFactory;
import org.eclipse.ocl.ecore.OCL;
import org.eclipse.ocl.ecore.SendSignalAction;
import org.eclipse.ocl.ecore.internal.UMLReflectionImpl;
import org.eclipse.ocl.helper.Choice;
import org.eclipse.ocl.helper.ChoiceKind;
import org.eclipse.ocl.helper.ConstraintKind;
import org.eclipse.ocl.utilities.UMLReflection;
/**
* Tests for message expressions.
*
* @author Christian W. Damus (cdamus)
*/
@SuppressWarnings("nls")
public class MessagesTest
extends AbstractTestSuite {
/**
* Tests the parsing of the hasSent ("^") expression for operations.
*/
public void test_hasSent_operation() {
helper.setOperationContext(fruit, fruit_ripen);
try {
// test query operation calls without any arguments
helper.createPostcondition("self^preferredColor()");
// test non-query operation calls without any arguments
helper.createPostcondition("self ^ newFruit()");
// test operation calls with one argument. Incidentally put a more
// interesting expression as the target
helper.createPostcondition(
"Apple.allInstances()->any(true) ^ripen(Color::yellow)");
// test operation calls with multiple arguments
helper.createPostcondition(
"Fruit.allInstances()->forAll(f | self^setColor(f, Color::red))");
// test operation signature conformance matching
helper.createPostcondition(
"Apple.allInstances()->forAll(a | self^setColor(a, Color::red))");
// test operation signature conformance matching with null object type
helper.createPostcondition(
"Apple.allInstances()->forAll(a | self^setColor(null, Color::red))");
// test operation signature conformance matching with null data type
helper.createPostcondition(
"Apple.allInstances()->forAll(a | self^setColor(a, null))");
} catch (Exception e) {
fail("Failed to parse: " + e.getLocalizedMessage());
}
}
/**
* Tests the validation of the hasSent ("^") expression for operations.
*/
public void test_hasSent_operation_validation() {
helper.setOperationContext(fruit, fruit_ripen);
try {
// non-existent operation
helper.createPostcondition("self^label('foo')");
fail("Should have failed to parse unknown operation");
} catch (Exception e) {
// success
debugPrintln("Got expected error: " + e.getLocalizedMessage());
}
try {
// non-conformant argument
helper.createPostcondition("self^setColor(self, 'red')");
fail("Should have failed to parse non-conformant arg");
} catch (Exception e) {
// success
debugPrintln("Got expected error: " + e.getLocalizedMessage());
}
}
/**
* Tests parsing of the hasSent ("^") expression with unspecified value
* expressions for operations.
*/
public void test_hasSent_operation_unspecifiedValue() {
helper.setOperationContext(fruit, fruit_ripen);
try {
helper.createPostcondition(
"Apple.allInstances()->any(true)^ripen(? : Color)");
helper.createPostcondition(
"Fruit.allInstances()->forAll(f | self^setColor(? : Fruit, ? : Color))");
helper.createPostcondition(
"Apple.allInstances()->any(true)^ripen(?)");
helper.createPostcondition(
"Fruit.allInstances()->forAll(f | self^setColor(?, ?))");
} catch (Exception e) {
fail("Failed to parse: " + e.getLocalizedMessage());
}
}
/**
* Tests validation of the hasSent ("^") expression with unspecified value
* expressions for operations.
*/
public void test_hasSent_operation_unspecifiedValue_validation() {
helper.setOperationContext(fruit, fruit_ripen);
try {
// non-existent operation
helper.createPostcondition("self^label(?)");
fail("Should have failed to parse unknown operation");
} catch (Exception e) {
// success
debugPrintln("Got expected error: " + e.getLocalizedMessage());
}
try {
// non-conformant argument
helper.createPostcondition("self^setColor(self, ? : String)");
fail("Should have failed to parse non-conformant arg");
} catch (Exception e) {
// success
debugPrintln("Got expected error: " + e.getLocalizedMessage());
}
}
/**
* Tests the parsing of the sentMessages ("^^") expression for operations.
*/
public void test_sentMessages_operation() {
helper.setOperationContext(fruit, fruit_ripen);
try {
// test query operation calls without any arguments
helper.createPostcondition(
"self^^preferredColor()->notEmpty()");
// test non-query operation calls without any arguments
helper.createPostcondition("self^^newFruit()->first().oclIsKindOf(OclMessage)");
// test operation calls with one argument. Incidentally put a more
// interesting expression as the target
helper.createPostcondition(
"Apple.allInstances()->any(true)^^ripen(Color::yellow)->notEmpty()");
// test operation calls with multiple arguments
helper.createPostcondition(
"Fruit.allInstances()->collect(f | self^^setColor(f, Color::red))->notEmpty()");
// test operation signature conformance matching
helper.createPostcondition(
"Apple.allInstances()->collect(a | self^^setColor(a, Color::red))->notEmpty()");
// test operation signature conformance matching with null object type
helper.createPostcondition(
"Apple.allInstances()->collect(a | self^^setColor(null, Color::red))->notEmpty()");
// test operation signature conformance matching with null data type
helper.createPostcondition(
"Apple.allInstances()->collect(a | self^^setColor(a, null))->notEmpty()");
} catch (Exception e) {
fail("Failed to parse: " + e.getLocalizedMessage());
}
}
/**
* Tests the parsing of OclMessage expressions for operations.
*/
public void test_message_access_operation() {
helper.setOperationContext(fruit, fruit_ripen);
try {
// test type conformance
helper.createPostcondition(
"let msgs : Sequence(OclMessage) = self^^preferredColor() in " +
"msgs->forAll(m | m.hasReturned() and m.isOperationCall() and not m.isSignalSent())");
// test non-query operation calls without any arguments
helper.createPostcondition("self^^newFruit()->first().oclIsKindOf(OclMessage)");
// test operation calls with one argument. Incidentally put a more
// interesting expression as the target
helper.createPostcondition(
"Apple.allInstances()->any(true)^^ripen(Color::yellow)->notEmpty()");
// types of the OclMessage properties are determined by the actual
// operation type, not by the MessageExp arguments
helper.createPostcondition(
"let msgs : Collection(OclMessage) = Fruit.allInstances()->collect(f | self^^setColor(?, ?)) in " +
"msgs->forAll(m | m.fruit.color = Color::black implies m.newColor = Color::black)");
// return values
helper.createPostcondition(
"let msgs : Collection(OclMessage) = Fruit.allInstances()->collect(f | self^^newFruit()) in " +
"msgs->forAll(m | m.hasReturned() implies m.result().color = Color::green)");
} catch (Exception e) {
fail("Failed to parse: " + e.getLocalizedMessage());
}
}
/**
* Tests the validation of the setMessages ("^^") expression for operations.
*/
public void test_sentMessages_operation_validation() {
helper.setOperationContext(fruit, fruit_ripen);
try {
// non-existent operation
helper.createPostcondition("self^^label('foo')->notEmpty()");
fail("Should have failed to parse unknown operation");
} catch (Exception e) {
// success
debugPrintln("Got expected error: " + e.getLocalizedMessage());
}
try {
// non-conformant argument
helper.createPostcondition("self^^setColor(self, 'red')->notEmpty()");
fail("Should have failed to parse non-conformant arg");
} catch (Exception e) {
// success
debugPrintln("Got expected error: " + e.getLocalizedMessage());
}
}
/**
* Tests the parsing of the hasSent ("^") expression for signals.
*/
public void test_hasSent_signal() {
helper.setOperationContext(apple, fruit_ripen);
try {
helper.createPostcondition("self^Drop(3, self.stem)");
helper.createPostcondition("self ^ Drop(? : Real, ? : Stem)");
helper.createPostcondition("self ^Drop(? : Integer, ?)");
helper.createPostcondition("self^Drop(?, ?)");
helper.createPostcondition(
"Apple.allInstances()->any(true)^Drop(3, stem)");
helper.createPostcondition(
"Apple.allInstances()->exists(a | a^Drop(3, stem))");
helper.createPostcondition(
"Fruit.allInstances()->forAll(f | f^Drop(?, ?) implies self^Drop(?, ?))");
} catch (Exception e) {
fail("Failed to parse: " + e.getLocalizedMessage());
}
}
/**
* Tests the validation of the hasSent ("^") expression for signals.
*/
public void test_hasSent_signal_validation() {
helper.setOperationContext(fruit, fruit_ripen);
try {
// non-existent signal
helper.createPostcondition("self^Rot(3, stem)");
fail("Should have failed to parse unknown signal");
} catch (Exception e) {
// success
debugPrintln("Got expected error: " + e.getLocalizedMessage());
}
try {
// non-conformant argument
helper.createPostcondition("self^Drop('red', ?)");
fail("Should have failed to parse non-conformant arg");
} catch (Exception e) {
// success
debugPrintln("Got expected error: " + e.getLocalizedMessage());
}
}
/**
* Tests the parsing of the sentMessages ("^^") expression for signals.
*/
public void test_sentMessages_signal() {
helper.setOperationContext(fruit, fruit_ripen);
try {
helper.createPostcondition(
"self^^Drop(3, self.oclAsType(Apple).stem)->notEmpty()");
helper.createPostcondition("self^^Drop(?, ?)->first().oclIsKindOf(OclMessage)");
helper.createPostcondition(
"Apple.allInstances()->any(true)^^Drop(3, ?)->notEmpty()");
helper.createPostcondition(
"Apple.allInstances()->collect(a | self^^Drop(null, ? : Stem))->notEmpty()");
helper.createPostcondition(
"Apple.allInstances()->collect(a | self^^Drop(3, null))->notEmpty()");
} catch (Exception e) {
fail("Failed to parse: " + e.getLocalizedMessage());
}
}
/**
* Tests the parsing of OclMessage expressions for signals.
*/
public void test_message_access_signal() {
helper.setOperationContext(apple, fruit_ripen);
try {
helper.createPostcondition(
"let msgs : Sequence(OclMessage) = self^^Drop(?, ?) in " +
"msgs->forAll(m | m.isSignalSent() and not m.isOperationCall())");
helper.createPostcondition("self^^Drop(?, ?)->first().oclIsKindOf(OclMessage)");
helper.createPostcondition(
"let msgs : Collection(OclMessage) = Fruit.allInstances()->collect(f | self^^Drop(?, ?)) in " +
"msgs->forAll(m | m.delay > 0 and stem <> self.stem)");
} catch (Exception e) {
fail("Failed to parse: " + e.getLocalizedMessage());
}
}
/**
* Tests the validation of the setMessages ("^^") expression for signals.
*/
public void test_sentMessages_signal_validation() {
helper.setOperationContext(fruit, fruit_ripen);
try {
// non-existent signal
helper.createPostcondition("self^^Rot('now')->notEmpty()");
fail("Should have failed to parse unknown signal");
} catch (Exception e) {
// success
debugPrintln("Got expected error: " + e.getLocalizedMessage());
}
try {
// non-conformant argument
helper.createPostcondition("self^^Drop(self, ?)->notEmpty()");
fail("Should have failed to parse non-conformant arg");
} catch (Exception e) {
// success
debugPrintln("Got expected error: " + e.getLocalizedMessage());
}
}
/**
* Tests the presentation of operation call choices.
*/
public void test_contentAssist_operations() {
helper.setOperationContext(fruit, fruit_ripen);
try {
List<Choice> choices = helper.getSyntaxHelp(
ConstraintKind.POSTCONDITION,
"self^");
assertNotNull(choices);
assertChoice(choices, ChoiceKind.OPERATION, "newFruit");
assertChoice(choices, ChoiceKind.OPERATION, "setColor");
assertNotChoice(choices, ChoiceKind.OPERATION, "oclIsKindOf");
choices = helper.getSyntaxHelp(
ConstraintKind.POSTCONDITION, "self^^");
assertNotNull(choices);
assertChoice(choices, ChoiceKind.OPERATION, "newFruit");
assertChoice(choices, ChoiceKind.OPERATION, "setColor");
assertNotChoice(choices, ChoiceKind.OPERATION, "oclIsKindOf");
// doesn't parse
choices = helper.getSyntaxHelp(
ConstraintKind.POSTCONDITION, "self^^^");
assertNotNull(choices);
assertChoice(choices, ChoiceKind.VARIABLE, "color");
choices = helper.getSyntaxHelp(
ConstraintKind.POSTCONDITION,
"Apple.allInstances()->any(true)^");
assertNotNull(choices);
assertChoice(choices, ChoiceKind.OPERATION, "newFruit");
assertChoice(choices, ChoiceKind.OPERATION, "setColor");
assertChoice(choices, ChoiceKind.OPERATION, "label");
assertNotChoice(choices, ChoiceKind.OPERATION, "oclIsKindOf");
choices = helper.getSyntaxHelp(
ConstraintKind.POSTCONDITION,
"Apple.allInstances()->collect(a : Apple | a^^");
assertNotNull(choices);
assertChoice(choices, ChoiceKind.OPERATION, "newFruit");
assertChoice(choices, ChoiceKind.OPERATION, "setColor");
assertChoice(choices, ChoiceKind.OPERATION, "label");
assertNotChoice(choices, ChoiceKind.OPERATION, "oclIsKindOf");
} catch (Exception e) {
fail("Parse failed: " + e.getLocalizedMessage());
}
}
/**
* Tests the presentation of signal send choices.
*/
public void test_contentAssist_signals() {
helper.setOperationContext(fruit, fruit_ripen);
try {
List<Choice> choices = helper.getSyntaxHelp(
ConstraintKind.POSTCONDITION,
"self^");
assertNotNull(choices);
assertChoice(choices, ChoiceKind.SIGNAL, "Drop");
choices = helper.getSyntaxHelp(
ConstraintKind.POSTCONDITION, "self^^");
assertNotNull(choices);
assertChoice(choices, ChoiceKind.SIGNAL, "Drop");
// doesn't parse
choices = helper.getSyntaxHelp(
ConstraintKind.POSTCONDITION, "self^^^");
assertNotNull(choices);
assertChoice(choices, ChoiceKind.VARIABLE, "color");
choices = helper.getSyntaxHelp(
ConstraintKind.POSTCONDITION,
"Apple.allInstances()->any(true)^");
assertNotNull(choices);
assertChoice(choices, ChoiceKind.SIGNAL, "Drop");
choices = helper.getSyntaxHelp(
ConstraintKind.POSTCONDITION,
"Apple.allInstances()->collect(a : Apple | a^^");
assertNotNull(choices);
assertChoice(choices, ChoiceKind.SIGNAL, "Drop");
} catch (Exception e) {
fail("Parse failed: " + e.getLocalizedMessage());
}
}
//
// Test framework
//
@Override
protected OCL createOCL() {
return OCL.newInstance(new MessagingFruitEnvironmentFactory(this));
}
@Override
public void setUp() {
super.setUp();
expectModified = true;
}
public static class MessagingFruitEnvironmentFactory extends EcoreEnvironmentFactory {
protected final AbstractTestSuite suite;
public MessagingFruitEnvironmentFactory(AbstractTestSuite suite) {
this.suite = suite;
}
@Override
public EcoreEnvironment createEnvironment() {
return new MessagingFruitEnvironment(this, suite);
}
@Override
public EcoreEnvironment createEnvironment(
Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> parent) {
return new MessagingFruitEnvironment(parent, suite);
}
}
private static class MessagingFruitEnvironment extends EcoreEnvironment implements InitEnvironment {
protected final AbstractTestSuite suite;
private EClass dropSignal;
private EList<EClassifier> signals = new BasicEList<EClassifier>();
public MessagingFruitEnvironment(MessagingFruitEnvironmentFactory factory, AbstractTestSuite suite) {
super(factory, null);
this.suite = suite;
setContextPackage(suite.fruitPackage);
// init();
}
public MessagingFruitEnvironment(
Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> parent, AbstractTestSuite suite) {
super(parent);
this.suite = suite;
init();
}
public void init() {
dropSignal = EcoreFactory.eINSTANCE.createEClass();
dropSignal.setName("Drop");
EStructuralFeature property = EcoreFactory.eINSTANCE.createEAttribute();
property.setName("delay");
property.setEType(getOCLStandardLibrary().getReal());
dropSignal.getEStructuralFeatures().add(property);
property = EcoreFactory.eINSTANCE.createEReference();
property.setName("stem");
property.setEType(suite.stem);
dropSignal.getEStructuralFeatures().add(property);
signals.add(dropSignal);
suite.fruitPackage.getEClassifiers().add(dropSignal);
}
@Override
public UMLReflection<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint> getUMLReflection() {
return new UMLReflectionImpl() {
@Override
public List<EClassifier> getSignals(EClassifier owner) {
if (owner == suite.fruit || owner == suite.apple) {
return signals;
}
return super.getSignals(owner);
}};
}
}
}