diff --git a/ros2rag.tests/build.gradle b/ros2rag.tests/build.gradle index 4c7b77325d42f51dfbca73da3da2c16a4498fa4d..3357c81de76caa277188c3f953da52403d1194bb 100644 --- a/ros2rag.tests/build.gradle +++ b/ros2rag.tests/build.gradle @@ -177,6 +177,37 @@ task compileRead1Write2Test(type: RelastTest) { test.dependsOn compileRead1Write2Test compileRead1Write2Test.dependsOn preprocessRead1Write2Test +// --- Test: read2write1 --- +task preprocessRead2Write1Test(type: JavaExec, group: 'verification') { + doFirst { + delete 'src/test/02-after-ros2rag/read2write1/Grammar.relast', + 'src/test/02-after-ros2rag/read2write1/MqttUpdater.java', + 'src/test/02-after-ros2rag/read2write1/ROS2RAG.jadd' + } + + classpath = sourceSets.main.runtimeClasspath + main = 'org.jastadd.ros2rag.compiler.Compiler' + args '--outputDir=src/test/02-after-ros2rag/read2write1', + '--inputGrammar=src/test/01-input/read2write1/Example.relast', + '--inputRos2Rag=src/test/01-input/read2write1/Example.ros2rag', + '--rootNode=A', '--verbose', + '--logReads', '--logWrites' +} + +task compileRead2Write1Test(type: RelastTest) { + useJastAddNames = true + jastAddList = 'JastAddList' + relastFiles 'src/test/02-after-ros2rag/read2write1/Grammar.relast' + grammarName = 'src/test/03-after-relast/read2write1/read2write1' + packageName = 'read2write1.ast' + moreInputFiles 'src/test/01-input/read2write1/Example.jadd', + 'src/test/02-after-ros2rag/read2write1/MqttUpdater.jadd', + 'src/test/02-after-ros2rag/read2write1/ROS2RAG.jadd' +} + +test.dependsOn compileRead2Write1Test +compileRead2Write1Test.dependsOn preprocessRead2Write1Test + clean { delete 'src/test/02-after-ros2rag/*/', 'src/test/03-after-relast/*/' } diff --git a/ros2rag.tests/src/test/01-input/read2write1/Example.jadd b/ros2rag.tests/src/test/01-input/read2write1/Example.jadd new file mode 100644 index 0000000000000000000000000000000000000000..4d31f1a6441311469f8f93cd150e6c8bb353877e --- /dev/null +++ b/ros2rag.tests/src/test/01-input/read2write1/Example.jadd @@ -0,0 +1,11 @@ +aspect Computation{ + // OnSameNonterminal + syn int OnSameNonterminal.getOutInteger() = Integer.parseInt(getInput1() + getInput2()); + + // OnDifferentNonterminal + syn int TheOther.getOutInteger() = Integer.parseInt(input1() + input2()); + inh String TheOther.input1(); + eq OnDifferentNonterminal.getTheOther().input1() = getInput1(); + inh String TheOther.input2(); + eq OnDifferentNonterminal.getTheOther().input2() = getInput2(); +} diff --git a/ros2rag.tests/src/test/01-input/read2write1/Example.relast b/ros2rag.tests/src/test/01-input/read2write1/Example.relast new file mode 100644 index 0000000000000000000000000000000000000000..afe0125760afe222c830f383536f6e2f97c8f656 --- /dev/null +++ b/ros2rag.tests/src/test/01-input/read2write1/Example.relast @@ -0,0 +1,4 @@ +A ::= OnSameNonterminal OnDifferentNonterminal ; +OnSameNonterminal ::= <Input1:String> <Input2:String> /<OutInteger:int>/ ; +OnDifferentNonterminal ::= <Input1:String> <Input2:String> TheOther* ; +TheOther ::= /<OutInteger:int>/ ; diff --git a/ros2rag.tests/src/test/01-input/read2write1/Example.ros2rag b/ros2rag.tests/src/test/01-input/read2write1/Example.ros2rag new file mode 100644 index 0000000000000000000000000000000000000000..4575a5b547366d8c9737d5c8478e1a4fb821e9a6 --- /dev/null +++ b/ros2rag.tests/src/test/01-input/read2write1/Example.ros2rag @@ -0,0 +1,18 @@ +// --- update definitions --- +// OnSameNonterminal +read OnSameNonterminal.Input1; +read OnSameNonterminal.Input2; +write OnSameNonterminal.OutInteger; + +// OnDifferentNonterminal +read OnDifferentNonterminal.Input1; +read OnDifferentNonterminal.Input2; +write TheOther.OutInteger; + +// --- dependency definitions --- +// OnSameNonterminal +OnSameNonterminal.OutInteger canDependOn OnSameNonterminal.Input1 as Int1Dependency; +OnSameNonterminal.OutInteger canDependOn OnSameNonterminal.Input2 as Int2Dependency; +// OnDifferentNonterminal +TheOther.OutInteger canDependOn OnDifferentNonterminal.Input1 as Int1Dependency; +TheOther.OutInteger canDependOn OnDifferentNonterminal.Input2 as Int2Dependency; diff --git a/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/Read2Write1Test.java b/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/Read2Write1Test.java new file mode 100644 index 0000000000000000000000000000000000000000..a094b671f21c3bf168ebec37a829ec981f45b5d1 --- /dev/null +++ b/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/Read2Write1Test.java @@ -0,0 +1,241 @@ +package org.jastadd.ros2rag.tests; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import read2write1.ast.*; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Test case "read-1-write-2". + * + * @author rschoene - Initial contribution + */ +public class Read2Write1Test extends AbstractMqttTest { + + private static final String TOPIC_SAME_READ1 = "same/read1"; + private static final String TOPIC_SAME_READ2 = "same/read2"; + private static final String TOPIC_SAME_WRITE_INT = "same/write/int"; + private static final String TOPIC_DIFFERENT_READ1 = "different/read1"; + private static final String TOPIC_DIFFERENT_READ2 = "different/read2"; + private static final String TOPIC_DIFFERENT_WRITE1_INT = "different/write1/int"; + private static final String TOPIC_DIFFERENT_WRITE2_INT = "different/write2/int"; + private static final String INITIAL_VALUE = "0"; + + private MqttUpdater handler; + private A model; + private OnSameNonterminal onSameNonterminal; + private OnDifferentNonterminal onDifferentNonterminal; + private TheOther other1; + private TheOther other2; + + private ReceiverData dataSame; + private ReceiverData dataOther1; + private ReceiverData dataOther2; + + @AfterEach + public void closeConnections() { + if (handler != null) { + handler.close(); + } + if (model != null) { + model.MqttCloseConnections(); + } + } + + @Test + public void buildModel() { + createModel(); + } + + @Test + public void communicateSendInitialValue() throws IOException, InterruptedException { + createModel(); + setupReceiverAndConnect(true); + + // check initial value + TimeUnit.SECONDS.sleep(2); + checkData(1, Integer.parseInt(INITIAL_VALUE + INITIAL_VALUE), + 1, Integer.parseInt(INITIAL_VALUE + INITIAL_VALUE)); + + // set new value + sendData(true, "2", true, "3"); + + // check new value. same: 2, 0. different: 3, 0. + TimeUnit.SECONDS.sleep(2); + checkData(2, 20, + 2, 30); + + // set new value + sendData(false, "4", false, "4"); + + // check new value. same: 2, 4. different: 3, 4. + TimeUnit.SECONDS.sleep(2); + checkData(3, 24, + 3, 34); + + // set new value only for same + setDataOnlySame(true, "77"); + + // check new value. same: 77, 4. different: 3, 4. + TimeUnit.SECONDS.sleep(2); + checkData(4, 774, + 3, 34); + } + + private String prefixed(String s) { + return "prefix" + s; + } + + @Test + public void communicateOnlyUpdatedValue() throws IOException, InterruptedException { + createModel(); + setupReceiverAndConnect(false); + + // check initial value + TimeUnit.SECONDS.sleep(2); + checkData(0, null, + 0, null); + + // set new value + sendData(true, "2", true, "3"); + + // check new value. same: 2, 0. different: 3, 0. + TimeUnit.SECONDS.sleep(2); + checkData(1, 20, + 1, 30); + + // set new value + sendData(false, "4", false, "4"); + + // check new value. same: 2, 4. different: 3, 4. + TimeUnit.SECONDS.sleep(2); + checkData(2, 24, + 2, 34); + + // set new value only for same + setDataOnlySame(true, "77"); + + // check new value. same: 77, 4. different: 3, 4. + TimeUnit.SECONDS.sleep(2); + checkData(3, 774, + 2, 34); + } + + private void createModel() { + // Setting value for Input without dependencies does not trigger any updates + model = new A(); + + onSameNonterminal = new OnSameNonterminal(); + model.setOnSameNonterminal(onSameNonterminal); + onSameNonterminal.setInput1(INITIAL_VALUE); + onSameNonterminal.setInput2(INITIAL_VALUE); + + onDifferentNonterminal = new OnDifferentNonterminal(); + other1 = new TheOther(); + other2 = new TheOther(); + onDifferentNonterminal.addTheOther(other1); + onDifferentNonterminal.addTheOther(other2); + model.setOnDifferentNonterminal(onDifferentNonterminal); + onDifferentNonterminal.setInput1(INITIAL_VALUE); + onDifferentNonterminal.setInput2(INITIAL_VALUE); + } + + private void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException { + model.MqttSetHost(TestUtils.getMqttHost()); + assertTrue(model.MqttWaitUntilReady(2, TimeUnit.SECONDS)); + + handler = new MqttUpdater().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost()); + assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS)); + + onSameNonterminal.addInt1Dependency(onSameNonterminal); + onSameNonterminal.addInt2Dependency(onSameNonterminal); + other1.addInt1Dependency(onDifferentNonterminal); + other1.addInt2Dependency(onDifferentNonterminal); + other2.addInt1Dependency(onDifferentNonterminal); + other2.addInt2Dependency(onDifferentNonterminal); + + dataSame = new Read2Write1Test.ReceiverData(); + dataOther1 = new Read2Write1Test.ReceiverData(); + dataOther2 = new Read2Write1Test.ReceiverData(); + + handler.newConnection(TOPIC_SAME_WRITE_INT, bytes -> { + dataSame.numberOfIntValues += 1; + dataSame.lastIntValue = java.nio.ByteBuffer.wrap(bytes).getInt(); + }); + + handler.newConnection(TOPIC_DIFFERENT_WRITE1_INT, bytes -> { + dataOther1.numberOfIntValues += 1; + dataOther1.lastIntValue = java.nio.ByteBuffer.wrap(bytes).getInt(); + }); + + handler.newConnection(TOPIC_DIFFERENT_WRITE2_INT, bytes -> { + dataOther2.numberOfIntValues += 1; + dataOther2.lastIntValue = java.nio.ByteBuffer.wrap(bytes).getInt(); + }); + + onSameNonterminal.connectInput1(TOPIC_SAME_READ1); + onSameNonterminal.connectInput2(TOPIC_SAME_READ2); + onSameNonterminal.connectOutInteger(TOPIC_SAME_WRITE_INT, writeCurrentValue); + + onDifferentNonterminal.connectInput1(TOPIC_DIFFERENT_READ1); + onDifferentNonterminal.connectInput2(TOPIC_DIFFERENT_READ2); + other1.connectOutInteger(TOPIC_DIFFERENT_WRITE1_INT, writeCurrentValue); + other2.connectOutInteger(TOPIC_DIFFERENT_WRITE2_INT, writeCurrentValue); + } + + private void sendData(boolean useSameInput1, String inputSame, + boolean useDifferentInput1, String inputDifferent) { + handler.publish(useSameInput1 ? TOPIC_SAME_READ1 : TOPIC_SAME_READ2, + inputSame.getBytes()); + handler.publish(useDifferentInput1 ? TOPIC_DIFFERENT_READ1 : TOPIC_DIFFERENT_READ2, + inputDifferent.getBytes()); + } + + private void setDataOnlySame(boolean useSameInput1, String inputSame) { + handler.publish(useSameInput1 ? TOPIC_SAME_READ1 : TOPIC_DIFFERENT_READ2, + inputSame.getBytes()); + } + + private void checkData(int numberOfSameValues, Integer lastSameIntValue, + int numberOfDifferentValues, Integer lastDifferentIntValue) { + /* the value "-2" is never used in the test, so a test will always fail comparing to this value + especially, it is not the initial value */ + ReceiverData expectedDataSame = ReceiverData.of( + numberOfSameValues, + lastSameIntValue != null ? lastSameIntValue : -2 + ); + compareData(expectedDataSame, dataSame); + ReceiverData expectedDataDifferent = ReceiverData.of( + numberOfDifferentValues, + lastDifferentIntValue != null ? lastDifferentIntValue : -2 + ); + compareData(expectedDataDifferent, dataOther1); + compareData(expectedDataDifferent, dataOther2); + } + + private void compareData(ReceiverData expectedData, + ReceiverData actual) { + assertEquals(expectedData.numberOfIntValues, actual.numberOfIntValues); + if (expectedData.numberOfIntValues > 0) { + assertEquals(expectedData.lastIntValue, actual.lastIntValue); + } + } + + private static class ReceiverData { + int lastIntValue; + int numberOfIntValues = 0; + + static ReceiverData of(int numberOfValues, int lastIntValue) { + ReceiverData result = new ReceiverData(); + result.lastIntValue = lastIntValue; + result.numberOfIntValues = numberOfValues; + return result; + } + } + +}