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