From 1f5df0a536ce9d1505b11bda52282bcb8b64bd23 Mon Sep 17 00:00:00 2001 From: rschoene <rene.schoene@tu-dresden.de> Date: Fri, 5 Jun 2020 16:44:00 +0200 Subject: [PATCH] Added Read1Write2Test, and AbstractMqttTest. - all tests have fail-fast-behaviour if mqtt broker is not connected - added some documentation on how to create new test cases - Base: Fixed logging of writes to output actual values of terminal and topic --- .../src/main/jastadd/backend/Generation.jadd | 4 +- ros2rag.tests/README.md | 16 ++ ros2rag.tests/build.gradle | 32 +++ .../test/01-input/read1write2/Example.jadd | 11 + .../test/01-input/read1write2/Example.relast | 4 + .../test/01-input/read1write2/Example.ros2rag | 18 ++ .../src/test/01-input/read1write2/README.md | 3 + .../ros2rag/tests/AbstractMqttTest.java | 35 +++ .../ros2rag/tests/DefaultOnlyReadTest.java | 2 +- .../ros2rag/tests/DefaultOnlyWriteTest.java | 2 +- .../jastadd/ros2rag/tests/ExampleTest.java | 2 +- .../ros2rag/tests/Read1Write2Test.java | 250 ++++++++++++++++++ 12 files changed, 375 insertions(+), 4 deletions(-) create mode 100644 ros2rag.tests/README.md create mode 100644 ros2rag.tests/src/test/01-input/read1write2/Example.jadd create mode 100644 ros2rag.tests/src/test/01-input/read1write2/Example.relast create mode 100644 ros2rag.tests/src/test/01-input/read1write2/Example.ros2rag create mode 100644 ros2rag.tests/src/test/01-input/read1write2/README.md create mode 100644 ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/AbstractMqttTest.java create mode 100644 ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/Read1Write2Test.java diff --git a/ros2rag.base/src/main/jastadd/backend/Generation.jadd b/ros2rag.base/src/main/jastadd/backend/Generation.jadd index 82c1ae0..7e1f988 100644 --- a/ros2rag.base/src/main/jastadd/backend/Generation.jadd +++ b/ros2rag.base/src/main/jastadd/backend/Generation.jadd @@ -208,7 +208,9 @@ aspect AspectGeneration { .append(writeMethod()).append("() {\n"); if (loggingEnabledForWrites) { sb.append(ind(2)).append("System.out.println(\"[Write] ").append(getToken().getName()) - .append(" = \" + ").append(lastValue()).append(" + \" -> ").append(writeTopic()).append("\");\n"); + .append(" = \" + ") + .append("get").append(getToken().getName()).append("() + \" -> \" + ") + .append(writeTopic()).append(");\n"); } // _mqttUpdater().publish(${writeTopic()}, ${lastValue()}); sb.append(ind(2)).append(mqttUpdaterAttribute()).append("().publish(") diff --git a/ros2rag.tests/README.md b/ros2rag.tests/README.md new file mode 100644 index 0000000..5baeac4 --- /dev/null +++ b/ros2rag.tests/README.md @@ -0,0 +1,16 @@ +# Creating a new test case + +The following things must be done: + +- Create a new directory in `src/test/01-input` and put at least grammar, the ros2rag-specification and a README.md in there. Optionally, aspect files as needed. +- Optionally, add new protobuf definitions in `src/test/proto` (they are processed automatically in this directory) +- Copy and modify the compiler instructions in `build.gradle`, e.g. `preprocessExampleTest` and `compileExampleTest` for the `example` case. Also, copy the dependsOn-Definitions. + - Change the name of the tasks + - Change the directory in `delete` spec, and in all arguments + - Add/Remove aspect files + - Change the `packageName` argument in the compile-step + - Change the `--rootNode` argument in the preprocess-step + - For debugging build problems, the `--verbose` flag in the preprocess-step can be helpful +- Create the test case itself in `src/test/java/` in the package `org.jastadd.ros2rag.tests` and import the AST files of your defined target generation package + - Extend the abstract base class `AbstractMqttTest` to have fail-fast-behaviour in case the MQTT-broker is not connected + - Remember to close all used MQTT-handling objects, like MqttUpdater and the model itself after each test diff --git a/ros2rag.tests/build.gradle b/ros2rag.tests/build.gradle index 7561d38..bedfddd 100644 --- a/ros2rag.tests/build.gradle +++ b/ros2rag.tests/build.gradle @@ -149,6 +149,38 @@ task compileDefaultOnlyWriteTest(type: RelastTest) { test.dependsOn compileDefaultOnlyWriteTest compileDefaultOnlyWriteTest.dependsOn preprocessDefaultOnlyWriteTest +// --- Test: read-1-write-2 --- +task preprocessRead1Write2Test(type: JavaExec, group: 'verification') { + doFirst { + delete 'src/test/02-after-ros2rag/read1write2/Grammar.relast', + 'src/test/02-after-ros2rag/read1write2/MqttUpdater.java', + 'src/test/02-after-ros2rag/read1write2/ROS2RAG.jadd' + } + + classpath = sourceSets.main.runtimeClasspath + main = 'org.jastadd.ros2rag.compiler.Compiler' + //noinspection GroovyAssignabilityCheck + args '--outputDir=src/test/02-after-ros2rag/read1write2', + '--inputGrammar=src/test/01-input/read1write2/Example.relast', + '--inputRos2Rag=src/test/01-input/read1write2/Example.ros2rag', + '--rootNode=A', '--verbose', + '--logReads', '--logWrites' +} + +task compileRead1Write2Test(type: RelastTest) { + useJastAddNames = true + jastAddList = 'JastAddList' + relastFiles 'src/test/02-after-ros2rag/read1write2/Grammar.relast' + grammarName = 'src/test/03-after-relast/read1write2/read1write2' + packageName = 'read1write2.ast' + moreInputFiles 'src/test/01-input/read1write2/Example.jadd', + 'src/test/02-after-ros2rag/read1write2/MqttUpdater.jadd', + 'src/test/02-after-ros2rag/read1write2/ROS2RAG.jadd' +} + +test.dependsOn compileRead1Write2Test +compileRead1Write2Test.dependsOn preprocessRead1Write2Test + clean { delete 'src/test/02-after-ros2rag/*/', 'src/test/03-after-relast/*/' } diff --git a/ros2rag.tests/src/test/01-input/read1write2/Example.jadd b/ros2rag.tests/src/test/01-input/read1write2/Example.jadd new file mode 100644 index 0000000..490772f --- /dev/null +++ b/ros2rag.tests/src/test/01-input/read1write2/Example.jadd @@ -0,0 +1,11 @@ +aspect Computation{ + // OnSameNonterminal + syn int OnSameNonterminal.getOutInteger() = Integer.parseInt(getInput()); + syn String OnSameNonterminal.getOutString() = "prefix" + getInput(); + + // OnDifferentNonterminal + syn int TheOther.getOutInteger() = Integer.parseInt(input()); + syn String TheOther.getOutString() = "prefix" + input(); + inh String TheOther.input(); + eq OnDifferentNonterminal.getTheOther().input() = getInput(); +} diff --git a/ros2rag.tests/src/test/01-input/read1write2/Example.relast b/ros2rag.tests/src/test/01-input/read1write2/Example.relast new file mode 100644 index 0000000..fa0b171 --- /dev/null +++ b/ros2rag.tests/src/test/01-input/read1write2/Example.relast @@ -0,0 +1,4 @@ +A ::= OnSameNonterminal OnDifferentNonterminal ; +OnSameNonterminal ::= <Input:String> /<OutInteger:int>/ /<OutString:String>/ ; +OnDifferentNonterminal ::= <Input:String> TheOther* ; +TheOther ::= /<OutInteger:int>/ /<OutString:String>/ ; diff --git a/ros2rag.tests/src/test/01-input/read1write2/Example.ros2rag b/ros2rag.tests/src/test/01-input/read1write2/Example.ros2rag new file mode 100644 index 0000000..fbf9489 --- /dev/null +++ b/ros2rag.tests/src/test/01-input/read1write2/Example.ros2rag @@ -0,0 +1,18 @@ +// --- update definitions --- +// OnSameNonterminal +read OnSameNonterminal.Input; +write OnSameNonterminal.OutInteger; +write OnSameNonterminal.OutString; + +// OnDifferentNonterminal +read OnDifferentNonterminal.Input; +write TheOther.OutInteger; +write TheOther.OutString; + +// --- dependency definitions --- +// OnSameNonterminal +OnSameNonterminal.OutInteger canDependOn OnSameNonterminal.Input as IntDependency; +OnSameNonterminal.OutString canDependOn OnSameNonterminal.Input as StringDependency; +// OnDifferentNonterminal +TheOther.OutInteger canDependOn OnDifferentNonterminal.Input as IntDependency; +TheOther.OutString canDependOn OnDifferentNonterminal.Input as StringDependency; diff --git a/ros2rag.tests/src/test/01-input/read1write2/README.md b/ros2rag.tests/src/test/01-input/read1write2/README.md new file mode 100644 index 0000000..197eeb5 --- /dev/null +++ b/ros2rag.tests/src/test/01-input/read1write2/README.md @@ -0,0 +1,3 @@ +# Read one - Write two + +Idea: Define Read-Update for one terminal, add dependencies to two other terminals, which each have Write-Update defined. Test, whether code gets generated correctly and that write is trigger for each of the two terminals upon read. diff --git a/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/AbstractMqttTest.java b/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/AbstractMqttTest.java new file mode 100644 index 0000000..3ca2c2d --- /dev/null +++ b/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/AbstractMqttTest.java @@ -0,0 +1,35 @@ +package org.jastadd.ros2rag.tests; + +import defaultOnlyRead.ast.MqttUpdater; +import org.junit.jupiter.api.BeforeAll; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +/** + * Base class for tests ensuring running mqtt broker. + * + * @author rschoene - Initial contribution + */ +public abstract class AbstractMqttTest { + static boolean checkDone = false; + static Boolean checkResult; + + @BeforeAll + public static void checkMqttConnection() { + if (!checkDone) { + checkDone = true; + try { + checkResult = new MqttUpdater() + .dontSendWelcomeMessage() + .setHost(TestUtils.getMqttHost()) + .waitUntilReady(2, TimeUnit.SECONDS); + } catch (IOException e) { + checkResult = false; + } + } + if (!checkResult) { + throw new IllegalStateException("Mqtt Broker not ready!"); + } + } +} diff --git a/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/DefaultOnlyReadTest.java b/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/DefaultOnlyReadTest.java index 88b51d6..39945ea 100644 --- a/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/DefaultOnlyReadTest.java +++ b/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/DefaultOnlyReadTest.java @@ -19,7 +19,7 @@ import static org.junit.Assert.assertTrue; * * @author rschoene - Initial contribution */ -public class DefaultOnlyReadTest { +public class DefaultOnlyReadTest extends AbstractMqttTest { private static final String TOPIC_NATIVE_INT = "native/int"; private static final String TOPIC_NATIVE_SHORT = "native/short"; diff --git a/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/DefaultOnlyWriteTest.java b/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/DefaultOnlyWriteTest.java index d322549..893e16c 100644 --- a/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/DefaultOnlyWriteTest.java +++ b/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/DefaultOnlyWriteTest.java @@ -17,7 +17,7 @@ import static org.junit.Assert.*; * * @author rschoene - Initial contribution */ -public class DefaultOnlyWriteTest { +public class DefaultOnlyWriteTest extends AbstractMqttTest { private static final String TOPIC_NATIVE_INT = "native/int"; private static final String TOPIC_NATIVE_SHORT = "native/short"; diff --git a/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/ExampleTest.java b/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/ExampleTest.java index c58eda7..af958fa 100644 --- a/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/ExampleTest.java +++ b/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/ExampleTest.java @@ -18,7 +18,7 @@ import static org.junit.Assert.*; * * @author rschoene - Initial contribution */ -public class ExampleTest { +public class ExampleTest extends AbstractMqttTest { private static final String TOPIC_CONFIG = "robot/config"; private static final String TOPIC_JOINT1 = "robot/arm/joint1"; diff --git a/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/Read1Write2Test.java b/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/Read1Write2Test.java new file mode 100644 index 0000000..bb5cb17 --- /dev/null +++ b/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/Read1Write2Test.java @@ -0,0 +1,250 @@ +package org.jastadd.ros2rag.tests; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import read1write2.ast.*; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; + +/** + * Test case "read-1-write-2". + * + * @author rschoene - Initial contribution + */ +public class Read1Write2Test extends AbstractMqttTest { + + private static final String TOPIC_SAME_READ = "same/read"; + private static final String TOPIC_SAME_WRITE_INT = "same/write/int"; + private static final String TOPIC_SAME_WRITE_STRING = "same/write/string"; + private static final String TOPIC_DIFFERENT_READ = "different/read"; + private static final String TOPIC_DIFFERENT_WRITE1_INT = "different/write1/int"; + private static final String TOPIC_DIFFERENT_WRITE1_STRING = "different/write1/string"; + private static final String TOPIC_DIFFERENT_WRITE2_INT = "different/write2/int"; + private static final String TOPIC_DIFFERENT_WRITE2_STRING = "different/write2/string"; + private static final String INITIAL_VALUE = "-1"; + + 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), prefixed(INITIAL_VALUE), 1, Integer.parseInt(INITIAL_VALUE), prefixed(INITIAL_VALUE)); + + // set new value + sendData("2", "3"); + + // check new value + TimeUnit.SECONDS.sleep(2); + checkData(2, 2, prefixed("2"), 2, 3, prefixed("3")); + + // set new value + sendData("4", "4"); + + // check new value + TimeUnit.SECONDS.sleep(2); + checkData(3, 4, prefixed("4"), 3, 4, prefixed("4")); + + // set new value only for same + setDataOnlySame("77"); + + // check new value + TimeUnit.SECONDS.sleep(2); + checkData(4, 77, prefixed("77"), 3, 4, prefixed("4")); + } + + 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, null, 0, null, null); + + // set new value + sendData("2", "3"); + + // check new value + TimeUnit.SECONDS.sleep(2); + checkData(1, 2, prefixed("2"), 1, 3, prefixed("3")); + + // set new value + sendData("4", "4"); + + // check new value + TimeUnit.SECONDS.sleep(2); + checkData(2, 4, prefixed("4"), 2, 4, prefixed("4")); + + // set new value only for same + setDataOnlySame("77"); + + // check new value + TimeUnit.SECONDS.sleep(2); + checkData(3, 77, prefixed("77"), 2, 4, prefixed("4")); + } + + private void createModel() { + // Setting value for Input without dependencies does not trigger any updates + model = new A(); + + onSameNonterminal = new OnSameNonterminal(); + model.setOnSameNonterminal(onSameNonterminal); + onSameNonterminal.setInput(INITIAL_VALUE); + + onDifferentNonterminal = new OnDifferentNonterminal(); + other1 = new TheOther(); + other2 = new TheOther(); + onDifferentNonterminal.addTheOther(other1); + onDifferentNonterminal.addTheOther(other2); + model.setOnDifferentNonterminal(onDifferentNonterminal); + onDifferentNonterminal.setInput(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.addIntDependency(onSameNonterminal); + onSameNonterminal.addStringDependency(onSameNonterminal); + other1.addIntDependency(onDifferentNonterminal); + other1.addStringDependency(onDifferentNonterminal); + other2.addIntDependency(onDifferentNonterminal); + other2.addStringDependency(onDifferentNonterminal); + + dataSame = new Read1Write2Test.ReceiverData(); + dataOther1 = new Read1Write2Test.ReceiverData(); + dataOther2 = new Read1Write2Test.ReceiverData(); + + handler.newConnection(TOPIC_SAME_WRITE_INT, bytes -> { + dataSame.numberOfIntValues += 1; + dataSame.lastIntValue = java.nio.ByteBuffer.wrap(bytes).getInt(); + }); + handler.newConnection(TOPIC_SAME_WRITE_STRING, bytes -> { + dataSame.numberOfStringValues += 1; + dataSame.lastStringValue = new String(bytes); + }); + + handler.newConnection(TOPIC_DIFFERENT_WRITE1_INT, bytes -> { + dataOther1.numberOfIntValues += 1; + dataOther1.lastIntValue = java.nio.ByteBuffer.wrap(bytes).getInt(); + }); + handler.newConnection(TOPIC_DIFFERENT_WRITE1_STRING, bytes -> { + dataOther1.numberOfStringValues += 1; + dataOther1.lastStringValue = new String(bytes); + }); + + handler.newConnection(TOPIC_DIFFERENT_WRITE2_INT, bytes -> { + dataOther2.numberOfIntValues += 1; + dataOther2.lastIntValue = java.nio.ByteBuffer.wrap(bytes).getInt(); + }); + handler.newConnection(TOPIC_DIFFERENT_WRITE2_STRING, bytes -> { + dataOther2.numberOfStringValues += 1; + dataOther2.lastStringValue = new String(bytes); + }); + + onSameNonterminal.connectInput(TOPIC_SAME_READ); + onSameNonterminal.connectOutInteger(TOPIC_SAME_WRITE_INT, writeCurrentValue); + onSameNonterminal.connectOutString(TOPIC_SAME_WRITE_STRING, writeCurrentValue); + + onDifferentNonterminal.connectInput(TOPIC_DIFFERENT_READ); + other1.connectOutInteger(TOPIC_DIFFERENT_WRITE1_INT, writeCurrentValue); + other1.connectOutString(TOPIC_DIFFERENT_WRITE1_STRING, writeCurrentValue); + other2.connectOutInteger(TOPIC_DIFFERENT_WRITE2_INT, writeCurrentValue); + other2.connectOutString(TOPIC_DIFFERENT_WRITE2_STRING, writeCurrentValue); + } + + private void sendData(String inputSame, String inputDifferent) { + handler.publish(TOPIC_SAME_READ, inputSame.getBytes()); + handler.publish(TOPIC_DIFFERENT_READ, inputDifferent.getBytes()); + } + + private void setDataOnlySame(String inputSame) { + handler.publish(TOPIC_SAME_READ, inputSame.getBytes()); + } + + private void checkData(int numberOfSameValues, Integer lastSameIntValue, String lastSameStringValue, + int numberOfDifferentValues, Integer lastDifferentIntValue, + String lastDifferentStringValue) { + /* 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, + lastSameStringValue); + compareData(expectedDataSame, dataSame); + ReceiverData expectedDataDifferent = ReceiverData.of( + numberOfDifferentValues, + lastDifferentIntValue != null ? lastDifferentIntValue : -2, + lastDifferentStringValue); + compareData(expectedDataDifferent, dataOther1); + compareData(expectedDataDifferent, dataOther2); + } + + private void compareData(ReceiverData expectedData, + ReceiverData actual) { + assertEquals(expectedData.numberOfIntValues, actual.numberOfIntValues); + assertEquals(expectedData.numberOfStringValues, actual.numberOfStringValues); + if (expectedData.numberOfIntValues > 0) { + assertEquals(expectedData.lastIntValue, actual.lastIntValue); + } + if (expectedData.numberOfStringValues > 0) { + assertEquals(expectedData.lastStringValue, actual.lastStringValue); + } + } + + private static class ReceiverData { + int lastIntValue; + int numberOfIntValues = 0; + String lastStringValue; + int numberOfStringValues = 0; + + static ReceiverData of(int numberOfValues, int lastIntValue, String lastStringValue) { + ReceiverData result = new ReceiverData(); + result.lastIntValue = lastIntValue; + result.lastStringValue = lastStringValue; + result.numberOfIntValues = numberOfValues; + result.numberOfStringValues = numberOfValues; + return result; + } + } + +} -- GitLab