| /******************************************************************************* |
| * 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); |
| }}; |
| } |
| } |
| } |