diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 453d9297e35aaedfbc03b908516840b535e6a061..57918916854c5164785fb587774953250a772bff 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,6 +4,8 @@ variables: stages: - build # - publish +# - test +# - publish before_script: - export GRADLE_USER_HOME=`pwd`/.gradle @@ -20,7 +22,21 @@ build: - ./gradlew --console=plain --no-daemon assemble jar artifacts: paths: - - "/builds/jastadd/ragconnect/build/libs/ragconnect-*.jar" + - "/builds/jastadd/ragconnect/ragconnect.base/build/libs/ragconnect-*.jar" + expire_in: 1 week + +test: + image: openjdk:11 + stage: test + services: + - name: "eclipse-mosquitto:1.6.9" + alias: "mqtt" + script: + - ./gradlew --console=plain --no-daemon allTests + artifacts: + reports: + junit: "/builds/jastadd/ragconnect/ragconnect.tests/build/test-results/test/TEST-*.xml" + expire_in: 1 week #publish: # image: openjdk:11 diff --git a/libs/buildSrc.jar b/libs/buildSrc.jar new file mode 100644 index 0000000000000000000000000000000000000000..b0ca2cbc4fc7f12022592944d7446a429d855add Binary files /dev/null and b/libs/buildSrc.jar differ diff --git a/ragconnect.base/build.gradle b/ragconnect.base/build.gradle index 5e95789763190f9f2dac3abe7c9b59c0de28ff59..b3e89a6a7fe602f4bfa87611e3002e99b446f7c6 100644 --- a/ragconnect.base/build.gradle +++ b/ragconnect.base/build.gradle @@ -46,6 +46,12 @@ try { throw new GradleException("File ${versionFile} not found or unreadable. Aborting.", e) } +task printVersion() { + doLast { + println(version) + } +} + task newVersion() { doFirst { def props = new Properties() @@ -61,6 +67,9 @@ idea.module.generatedSourceDirs += genSrc jar { manifest { attributes "Main-Class": 'org.jastadd.ragconnect.compiler.Compiler' + + // Log4J + Java 11 compatibility, see https://stackoverflow.com/q/53049346/2493208 + attributes "Multi-Release": true } from { diff --git a/ragconnect.base/src/main/jastadd/intermediate/Generation.jadd b/ragconnect.base/src/main/jastadd/intermediate/Generation.jadd index 06d223ee1f4a3fd0cc78beeaa2a8cedf0a2bbd45..0c9190a1cfeed156c6353495f327244bef3ed4b0 100644 --- a/ragconnect.base/src/main/jastadd/intermediate/Generation.jadd +++ b/ragconnect.base/src/main/jastadd/intermediate/Generation.jadd @@ -29,6 +29,17 @@ aspect AttributesForMustache { syn String MEndpointDefinition.connectParameterName() = "uriString"; syn String MEndpointDefinition.connectMethod() = "connect" + tokenName(); + + syn String MEndpointDefinition.disconnectMethod() { + // if both (send and receive) are defined for the token, ensure methods with different names + String extra = endpointDef().lookupTokenEndpointDefinitions(token()).size() > 1 ? uniqueSuffix() : ""; + return "disconnect" + extra + tokenName(); + } + // + syn String MEndpointDefinition.uniqueSuffix(); + eq MSendDefinition.uniqueSuffix() = "Send"; + eq MReceiveDefinition.uniqueSuffix() = "Receive"; + syn TokenComponent MEndpointDefinition.token() = endpointDef().getToken(); syn boolean MEndpointDefinition.alwaysApply() = endpointDef().getAlwaysApply(); syn String MEndpointDefinition.parentTypeName() = token().containingTypeDecl().getName(); diff --git a/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java b/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java index 0c4900df464e839ab1ea3108cc82eecfc697549e..8c18f32bef6489aaea520250d0101275f314a41c 100644 --- a/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java +++ b/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java @@ -142,7 +142,9 @@ public class Compiler extends AbstractCompiler { } private void printMessage(String message) { - System.out.println(message); + if (optionVerbose.value()) { + System.out.println(message); + } } private void writeToFile(Path path, String str) throws CompilerException { diff --git a/ragconnect.base/src/main/resources/MqttHandler.jadd b/ragconnect.base/src/main/resources/MqttHandler.jadd index b6750261d9f4ad06d92721c95aa7aa6e7971849b..f5e6ba99b3058f59f6b9b40ae0d9c07f18da88c3 100644 --- a/ragconnect.base/src/main/resources/MqttHandler.jadd +++ b/ragconnect.base/src/main/resources/MqttHandler.jadd @@ -4,6 +4,7 @@ import java.util.concurrent.TimeUnit; aspect MqttHandler { public class MqttServerHandler { private final java.util.Map<String, MqttHandler> handlers = new java.util.HashMap<>(); + private final java.util.Map<ConnectToken, Object> tokensForRemoval = new java.util.HashMap<>(); private long time; private java.util.concurrent.TimeUnit unit; private String name; @@ -38,8 +39,16 @@ public class MqttServerHandler { return handler; } - public boolean newConnection(java.net.URI uri, java.util.function.Consumer<byte[]> callback) throws IOException { - return resolveHandler(uri).newConnection(extractTopic(uri), callback); + public ConnectToken newConnection(java.net.URI uri, java.util.function.Consumer<byte[]> callback) throws IOException { + ConnectToken connectToken = new ConnectToken(uri); + resolveHandler(uri).newConnection(extractTopic(uri), callback); + tokensForRemoval.put(connectToken, callback); + return connectToken; + } + + public boolean disconnect(ConnectToken connectToken) throws IOException { + MqttHandler handler = resolveHandler(connectToken.uri); + return handler != null ? handler.disconnect(extractTopic(connectToken.uri), tokensForRemoval.get(connectToken)) : false; } public void publish(java.net.URI uri, byte[] bytes) throws IOException { @@ -146,14 +155,16 @@ public class MqttHandler { } @Override - public void onPublish(org.fusesource.hawtbuf.UTF8Buffer topic, org.fusesource.hawtbuf.Buffer body, org.fusesource.mqtt.client.Callback<org.fusesource.mqtt.client.Callback<Void>> ack) { + public void onPublish(org.fusesource.hawtbuf.UTF8Buffer topic, + org.fusesource.hawtbuf.Buffer body, + org.fusesource.mqtt.client.Callback<org.fusesource.mqtt.client.Callback<Void>> ack) { + // this method is called, whenever a MQTT message is received String topicString = topic.toString(); java.util.List<java.util.function.Consumer<byte[]>> callbackList = callbacks.get(topicString); if (callbackList == null || callbackList.isEmpty()) { logger.debug("Got a message, but no callback to call. Forgot to subscribe?"); } else { byte[] message = body.toByteArray(); -// System.out.println("message = " + Arrays.toString(message)); for (java.util.function.Consumer<byte[]> callback : callbackList) { callback.accept(message); } @@ -162,13 +173,15 @@ public class MqttHandler { } @Override - public void onPublish(org.fusesource.hawtbuf.UTF8Buffer topicBuffer, org.fusesource.hawtbuf.Buffer body, Runnable ack) { + public void onPublish(org.fusesource.hawtbuf.UTF8Buffer topicBuffer, + org.fusesource.hawtbuf.Buffer body, + Runnable ack) { + // not used by this type of connection logger.warn("onPublish should not be called"); } @Override public void onFailure(Throwable cause) { -// logger.catching(cause); error.set(cause); } }); @@ -179,7 +192,11 @@ public class MqttHandler { @Override public void onSuccess(Void value) { if (MqttHandler.this.sendWelcomeMessage) { - connection.publish("components", (name + " is connected").getBytes(), org.fusesource.mqtt.client.QoS.AT_LEAST_ONCE, false, new org.fusesource.mqtt.client.Callback<Void>() { + connection.publish("components", + (name + " is connected").getBytes(), + org.fusesource.mqtt.client.QoS.AT_LEAST_ONCE, + false, + new org.fusesource.mqtt.client.Callback<Void>() { @Override public void onSuccess(Void value) { logger.debug("success sending welcome message"); @@ -198,7 +215,6 @@ public class MqttHandler { @Override public void onFailure(Throwable cause) { -// logger.error("Could not connect", cause); error.set(cause); } }); @@ -228,8 +244,6 @@ public class MqttHandler { if (readyLatch.getCount() > 0) { System.err.println("Handler not ready"); return false; -// // should maybe be something more kind than throwing an exception here -// throw new IllegalStateException("Updater not ready"); } // register callback logger.debug("new connection for {}", topic); @@ -256,6 +270,35 @@ public class MqttHandler { return true; } + public boolean disconnect(String topic, Object callback) { + java.util.List<java.util.function.Consumer<byte[]>> callbackList = callbacks.get(topic); + if (callbackList == null) { + logger.warn("Disconnect for not connected topic '{}'", topic); + return false; + } + java.util.concurrent.atomic.AtomicReference<Boolean> success = new java.util.concurrent.atomic.AtomicReference<>(); + success.set(callbackList.remove(callback)); + if (callbackList.isEmpty()) { + // no callbacks anymore for this topic, unsubscribe from mqtt + connection.getDispatchQueue().execute(() -> { + org.fusesource.hawtbuf.UTF8Buffer topicBuffer = org.fusesource.hawtbuf.Buffer.utf8(topic); + org.fusesource.hawtbuf.UTF8Buffer[] topicArray = new org.fusesource.hawtbuf.UTF8Buffer[]{topicBuffer}; + connection.unsubscribe(topicArray, new org.fusesource.mqtt.client.Callback<Void>() { + @Override + public void onSuccess(Void value) { + // empty, all good + } + + @Override + public void onFailure(Throwable cause) { + success.set(false); + } + }); + }); + } + return success.get(); + } + /** * Waits until this updater is ready to receive MQTT messages. * If it already is ready, return immediately with the value <code>true</code>. diff --git a/ragconnect.base/src/main/resources/RestHandler.jadd b/ragconnect.base/src/main/resources/RestHandler.jadd index f7b1a83304fe7aaebe97d105514c3f192d052aec..b69bf71b73ba9d4c5a9f1998ee2dd92ede77561d 100644 --- a/ragconnect.base/src/main/resources/RestHandler.jadd +++ b/ragconnect.base/src/main/resources/RestHandler.jadd @@ -2,6 +2,7 @@ import java.util.concurrent.TimeUnit;aspect RestHandler { public class RestServerHandler { private static final int DEFAULT_PORT = 4567; private final java.util.Map<Integer, RestHandler> handlers = new java.util.HashMap<>(); + private final java.util.Map<ConnectToken, Object> tokensForRemoval = new java.util.HashMap<>(); private String name; public RestServerHandler() { @@ -24,14 +25,23 @@ public class RestServerHandler { return handler; } - public boolean newPUTConnection(java.net.URI uri, java.util.function.Consumer<String> callback) { + public ConnectToken newPUTConnection(java.net.URI uri, java.util.function.Consumer<String> callback) { + ConnectToken connectToken = new ConnectToken(uri); resolveHandler(uri).newPUTConnection(uri.getPath(), callback); - return true; + tokensForRemoval.put(connectToken, callback); + return connectToken; } - public boolean newGETConnection(java.net.URI uri, SupplierWithException<String> supplier) { + public ConnectToken newGETConnection(java.net.URI uri, SupplierWithException<String> supplier) { + ConnectToken connectToken = new ConnectToken(uri); resolveHandler(uri).newGETConnection(uri.getPath(), supplier); - return true; + tokensForRemoval.put(connectToken, supplier); + return connectToken; + } + + public boolean disconnect(ConnectToken connectToken) { + RestHandler handler = resolveHandler(connectToken.uri); + return handler != null ? handler.disconnect(connectToken.uri.getPath(), tokensForRemoval.get(connectToken)) : false; } public void close() { @@ -108,6 +118,7 @@ public class RestHandler { suppliers.put(path, supplier); spark.Spark.get(path, (request, response) -> { try { + // we could check for null here in case supplier has been disconnected return supplier.get(); } catch (Exception e) { return makeError(response, 500, e.getMessage()); @@ -115,6 +126,12 @@ public class RestHandler { }); } + public boolean disconnect(String path, Object callbackOrSupplier) { + // only one will succeed (or false will be returned) + return callbacks.getOrDefault(path, java.util.Collections.emptyList()).remove(callbackOrSupplier) || + suppliers.remove(path, callbackOrSupplier); + } + private String makeError(spark.Response response, int statusCode, String message) { response.status(statusCode); return message; diff --git a/ragconnect.base/src/main/resources/handler.mustache b/ragconnect.base/src/main/resources/handler.mustache index 796f76027fd22899b60e02a2fdd08fbf0dc9ebdb..41e42387f69239f054b8ddaafa80680b13ca7591 100644 --- a/ragconnect.base/src/main/resources/handler.mustache +++ b/ragconnect.base/src/main/resources/handler.mustache @@ -14,4 +14,15 @@ aspect RagConnectHandler { {{#InUse}}{{FieldName}}.close();{{/InUse}} {{/Handlers}} } + class ConnectToken { + static java.util.concurrent.atomic.AtomicLong counter = new java.util.concurrent.atomic.AtomicLong(0); + final long id; + final java.net.URI uri; + public ConnectToken(java.net.URI uri) { + this.id = counter.incrementAndGet(); + this.uri = uri; + } + + } + static java.util.Map<ASTNode, java.util.Map<java.net.URI, ConnectToken>> ASTNode.connectTokens = new java.util.HashMap<>(); } diff --git a/ragconnect.base/src/main/resources/ragConnectVersion.properties b/ragconnect.base/src/main/resources/ragConnectVersion.properties index 02738667ad14ade7495694af0fe23995c160b019..266543a066a77220bc13c577f38b08368a2f4d40 100644 --- a/ragconnect.base/src/main/resources/ragConnectVersion.properties +++ b/ragconnect.base/src/main/resources/ragConnectVersion.properties @@ -1,2 +1,2 @@ -#Fri Jan 15 11:45:51 CET 2021 -version=0.2.4 +#Tue Jan 19 12:08:02 CET 2021 +version=0.2.5 diff --git a/ragconnect.base/src/main/resources/receiveDefinition.mustache b/ragconnect.base/src/main/resources/receiveDefinition.mustache index 95340e5446baeab28564747076c4178b06d55ee6..5294f897593efb8928e8114dd488777e82b90357 100644 --- a/ragconnect.base/src/main/resources/receiveDefinition.mustache +++ b/ragconnect.base/src/main/resources/receiveDefinition.mustache @@ -7,17 +7,46 @@ public boolean {{parentTypeName}}.{{connectMethod}}(String {{connectParameterNam {{/loggingEnabledForReads}} set{{tokenName}}({{lastResult}}); }; + ConnectToken connectToken; switch (scheme) { {{#usesMqtt}} - case "mqtt": return {{mqttHandlerAttribute}}().newConnection(uri, consumer); + case "mqtt": + connectToken = {{mqttHandlerAttribute}}().newConnection(uri, consumer); + if (connectToken == null) { + return false; + } + break; {{/usesMqtt}} {{#usesJava}} case "java": return {{javaHandlerAttribute}}().registerCallback(path, consumer); {{/usesJava}} {{#usesRest}} - case "rest": return {{restHandlerAttribute}}().newPUTConnection(uri, input -> { - consumer.accept(input.getBytes()); - }); + case "rest": + connectToken = {{restHandlerAttribute}}().newPUTConnection(uri, input -> { + consumer.accept(input.getBytes()); + }); + if (connectToken == null) { + return false; + } + break; + {{/usesRest}} + default: + System.err.println("Unknown protocol '" + scheme + "'."); + return false; + } + connectTokens.computeIfAbsent(this, astNode -> new java.util.HashMap<java.net.URI, ConnectToken>()) + .put(uri, connectToken); + return true; +} + +public boolean {{parentTypeName}}.{{disconnectMethod}}(String {{connectParameterName}}) throws java.io.IOException { + {{>handleUri}} + switch (scheme) { + {{#usesMqtt}} + case "mqtt": return {{mqttHandlerAttribute}}().disconnect(connectTokens.get(this).get(uri)); + {{/usesMqtt}} + {{#usesRest}} + case "rest": return {{restHandlerAttribute}}().disconnect(connectTokens.get(this).get(uri)); {{/usesRest}} default: System.err.println("Unknown protocol '" + scheme + "'."); diff --git a/ragconnect.base/src/main/resources/sendDefinition.mustache b/ragconnect.base/src/main/resources/sendDefinition.mustache index 248ea2db939f1da946e62ce7ce2034457adaf4cb..e564ef85453221fdcd6e5519a03f28bf43cf2927 100644 --- a/ragconnect.base/src/main/resources/sendDefinition.mustache +++ b/ragconnect.base/src/main/resources/sendDefinition.mustache @@ -35,10 +35,36 @@ public boolean {{parentTypeName}}.{{connectMethod}}(String {{connectParameterNam {{/usesJava}} {{#usesRest}} case "rest": - {{restHandlerAttribute}}().newGETConnection(uri, () -> { + ConnectToken connectToken = {{restHandlerAttribute}}().newGETConnection(uri, () -> { {{updateMethod}}(); return new String({{lastValue}}); }); + if (connectToken == null) { + return false; + } + connectTokens.computeIfAbsent(this, astNode -> new java.util.HashMap<java.net.URI, ConnectToken>()) + .put(uri, connectToken); + break; + {{/usesRest}} + default: + System.err.println("Unknown protocol '" + scheme + "'."); + return false; + } + return true; +} + +public boolean {{parentTypeName}}.{{disconnectMethod}}(String {{connectParameterName}}) throws java.io.IOException { + {{>handleUri}} + switch (scheme) { + {{#usesMqtt}} + case "mqtt": + {{sender}} = null; + {{lastValue}} = null; + break; + {{/usesMqtt}} + {{#usesRest}} + case "rest": + {{restHandlerAttribute}}().disconnect(connectTokens.get(this).get(uri)); break; {{/usesRest}} default: diff --git a/ragconnect.tests/.gitignore b/ragconnect.tests/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..87b4cdd3d7c6a41502ca98703abeeb69a1d536fb --- /dev/null +++ b/ragconnect.tests/.gitignore @@ -0,0 +1,5 @@ +build +src/gen-res/ +src/gen/ +out/ +*.class diff --git a/ragconnect.tests/build.gradle b/ragconnect.tests/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..ccc0134eb86898cd001183828f81652f2a8c2010 --- /dev/null +++ b/ragconnect.tests/build.gradle @@ -0,0 +1,340 @@ +buildscript { + repositories.mavenCentral() + dependencies { + classpath 'org.jastadd:jastaddgradle:1.13.3' + classpath fileTree(include: ['buildSrc.jar'], dir: '../libs') + } +} + +import org.jastadd.relast.plugin.RelastPlugin +import org.jastadd.relast.plugin.RelastTest + +plugins { + id 'java' + id 'java-library' + id 'idea' + id 'com.github.ben-manes.versions' version '0.36.0' + id 'com.google.protobuf' version "0.8.14" +} + +apply plugin: 'jastadd' +apply plugin: RelastPlugin + +group = 'de.tudresden.inf.st' + +repositories { + mavenCentral() + jcenter() +} + +dependencies { + implementation project(':ragconnect.base') + + runtime group: 'org.jastadd', name: 'jastadd', version: '2.3.4' + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.4.0' + testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.4.0' + testImplementation group: 'org.assertj', name: 'assertj-core', version: '3.12.1' + + // mqtt + testImplementation group: 'org.fusesource.mqtt-client', name: 'mqtt-client', version: '1.15' + + // rest and client + testImplementation group: 'com.sparkjava', name: 'spark-core', version: '2.9.2' + testImplementation group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.11.2' + testImplementation group: 'org.glassfish.jersey.core', name: 'jersey-client', version: '2.31' + testImplementation group: 'org.glassfish.jersey.inject', name: 'jersey-hk2', version: '2.31' + + testImplementation group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11' + api group: 'com.google.protobuf', name: 'protobuf-java', version: '3.0.0' +} + +test { + useJUnitPlatform { + excludeTags 'mqtt' + } + + maxHeapSize = '1G' +} + +protobuf { + protoc { + // The artifact spec for the Protobuf Compiler + artifact = 'com.google.protobuf:protoc:3.0.0' + } +} + +task allTests(type: Test, dependsOn: testClasses) { + description = 'Run every test' + group = 'verification' + + useJUnitPlatform { + includeTags 'mqtt' + } +} + +relastTest { + //noinspection GroovyAssignabilityCheck + compilerLocation = '../libs/relast.jar' +} + +File genSrc = file("src/test/java-gen") +sourceSets.test.java.srcDir genSrc +idea.module.generatedSourceDirs += genSrc + +clean { + delete 'src/test/02-after-ragconnect/*/', 'src/test/03-after-relast/*/', 'src/test/java-gen/*/' +} + +// --- Test: Example --- +task preprocessExampleTest(type: JavaExec, group: 'verification') { + doFirst { + delete 'src/test/02-after-ragconnect/example/Test.relast', + 'src/test/02-after-ragconnect/example/MqttHandler.jadd', + 'src/test/02-after-ragconnect/example/RagConnect.jadd' + } + + classpath = sourceSets.main.runtimeClasspath + main = 'org.jastadd.ragconnect.compiler.Compiler' + args '--o=src/test/02-after-ragconnect/example', + 'src/test/01-input/example/Test.relast', + 'src/test/01-input/example/Test.connect', + '--rootNode=Model', + '--logReads', '--logWrites', '--protocols=mqtt' +} + +task compileExampleTest(type: RelastTest) { + useJastAddNames = true + jastAddList = 'JastAddList' + relastFiles 'src/test/02-after-ragconnect/example/Test.relast', + 'src/test/02-after-ragconnect/example/RagConnect.relast' + grammarName = 'src/test/03-after-relast/example/example' + packageName = 'example.ast' + moreInputFiles 'src/test/01-input/example/Test.jadd', + 'src/test/02-after-ragconnect/example/MqttHandler.jadd', + 'src/test/02-after-ragconnect/example/RagConnect.jadd' +} + +compileTestJava.dependsOn compileExampleTest +compileExampleTest.dependsOn preprocessExampleTest + +// --- Test: default-only-read --- +task preprocessDefaultOnlyReadTest(type: JavaExec, group: 'verification') { + doFirst { + delete 'src/test/02-after-ragconnect/defaultOnlyRead/Test.relast', + 'src/test/02-after-ragconnect/defaultOnlyRead/MqttHandler.jadd', + 'src/test/02-after-ragconnect/defaultOnlyRead/RagConnect.jadd' + } + + classpath = sourceSets.main.runtimeClasspath + main = 'org.jastadd.ragconnect.compiler.Compiler' + args '--o=src/test/02-after-ragconnect/defaultOnlyRead', + 'src/test/01-input/defaultOnlyRead/Test.relast', + 'src/test/01-input/defaultOnlyRead/Test.connect', + '--rootNode=A', '--protocols=mqtt' +} + +task compileDefaultOnlyReadTest(type: RelastTest) { + useJastAddNames = true + jastAddList = 'JastAddList' + relastFiles 'src/test/02-after-ragconnect/defaultOnlyRead/Test.relast', + 'src/test/02-after-ragconnect/defaultOnlyRead/RagConnect.relast' + grammarName = 'src/test/03-after-relast/defaultOnlyRead/defaultOnlyRead' + packageName = 'defaultOnlyRead.ast' + moreInputFiles 'src/test/02-after-ragconnect/defaultOnlyRead/MqttHandler.jadd', + 'src/test/02-after-ragconnect/defaultOnlyRead/RagConnect.jadd' +} + +compileTestJava.dependsOn compileDefaultOnlyReadTest +compileDefaultOnlyReadTest.dependsOn preprocessDefaultOnlyReadTest + +// --- Test: default-only-write --- +task preprocessDefaultOnlyWriteTest(type: JavaExec, group: 'verification') { + doFirst { + delete 'src/test/02-after-ragconnect/defaultOnlyWrite/Test.relast', + 'src/test/02-after-ragconnect/defaultOnlyWrite/MqttHandler.jadd', + 'src/test/02-after-ragconnect/defaultOnlyWrite/RagConnect.jadd' + } + + classpath = sourceSets.main.runtimeClasspath + main = 'org.jastadd.ragconnect.compiler.Compiler' + args '--o=src/test/02-after-ragconnect/defaultOnlyWrite', + 'src/test/01-input/defaultOnlyWrite/Test.relast', + 'src/test/01-input/defaultOnlyWrite/Test.connect', + '--rootNode=A', '--protocols=mqtt' +} + +task compileDefaultOnlyWriteTest(type: RelastTest) { + useJastAddNames = true + jastAddList = 'JastAddList' + relastFiles 'src/test/02-after-ragconnect/defaultOnlyWrite/Test.relast', + 'src/test/02-after-ragconnect/defaultOnlyWrite/RagConnect.relast' + grammarName = 'src/test/03-after-relast/defaultOnlyWrite/defaultOnlyWrite' + packageName = 'defaultOnlyWrite.ast' + moreInputFiles 'src/test/01-input/defaultOnlyWrite/Test.jadd', + 'src/test/02-after-ragconnect/defaultOnlyWrite/MqttHandler.jadd', + 'src/test/02-after-ragconnect/defaultOnlyWrite/RagConnect.jadd' +} + +compileTestJava.dependsOn compileDefaultOnlyWriteTest +compileDefaultOnlyWriteTest.dependsOn preprocessDefaultOnlyWriteTest + +// --- Test: read1write2 --- +task preprocessRead1Write2Test(type: JavaExec, group: 'verification') { + doFirst { + delete 'src/test/02-after-ragconnect/read1write2/Test.relast', + 'src/test/02-after-ragconnect/read1write2/MqttHandler.jadd', + 'src/test/02-after-ragconnect/read1write2/RagConnect.jadd' + } + + classpath = sourceSets.main.runtimeClasspath + main = 'org.jastadd.ragconnect.compiler.Compiler' + args '--o=src/test/02-after-ragconnect/read1write2', + 'src/test/01-input/read1write2/Test.relast', + 'src/test/01-input/read1write2/Test.connect', + '--rootNode=A', '--protocols=mqtt' +} + +task compileRead1Write2Test(type: RelastTest) { + useJastAddNames = true + jastAddList = 'JastAddList' + relastFiles 'src/test/02-after-ragconnect/read1write2/Test.relast', + 'src/test/02-after-ragconnect/read1write2/RagConnect.relast' + grammarName = 'src/test/03-after-relast/read1write2/read1write2' + packageName = 'read1write2.ast' + moreInputFiles 'src/test/01-input/read1write2/Test.jadd', + 'src/test/02-after-ragconnect/read1write2/MqttHandler.jadd', + 'src/test/02-after-ragconnect/read1write2/RagConnect.jadd' +} + +compileTestJava.dependsOn compileRead1Write2Test +compileRead1Write2Test.dependsOn preprocessRead1Write2Test + +// --- Test: read2write1 --- +task preprocessRead2Write1Test(type: JavaExec, group: 'verification') { + doFirst { + delete 'src/test/02-after-ragconnect/read2write1/Test.relast', + 'src/test/02-after-ragconnect/read2write1/MqttHandler.jadd', + 'src/test/02-after-ragconnect/read2write1/RagConnect.jadd' + } + + classpath = sourceSets.main.runtimeClasspath + main = 'org.jastadd.ragconnect.compiler.Compiler' + args '--o=src/test/02-after-ragconnect/read2write1', + 'src/test/01-input/read2write1/Test.relast', + 'src/test/01-input/read2write1/Test.connect', + '--rootNode=A', '--protocols=mqtt' +} + +task compileRead2Write1Test(type: RelastTest) { + useJastAddNames = true + jastAddList = 'JastAddList' + relastFiles 'src/test/02-after-ragconnect/read2write1/Test.relast', + 'src/test/02-after-ragconnect/read2write1/RagConnect.relast' + grammarName = 'src/test/03-after-relast/read2write1/read2write1' + packageName = 'read2write1.ast' + moreInputFiles 'src/test/01-input/read2write1/Test.jadd', + 'src/test/02-after-ragconnect/read2write1/MqttHandler.jadd', + 'src/test/02-after-ragconnect/read2write1/RagConnect.jadd' +} + +compileTestJava.dependsOn compileRead2Write1Test +compileRead2Write1Test.dependsOn preprocessRead2Write1Test + +// --- Test: via --- +task preprocessViaTest(type: JavaExec, group: 'verification') { + doFirst { + delete 'src/test/02-after-ragconnect/via/Test.relast', + 'src/test/02-after-ragconnect/via/MqttHandler.jadd', + 'src/test/02-after-ragconnect/via/RestHandler.jadd', + 'src/test/02-after-ragconnect/via/RagConnect.jadd' + } + + classpath = sourceSets.main.runtimeClasspath + main = 'org.jastadd.ragconnect.compiler.Compiler' + args '--o=src/test/02-after-ragconnect/via', + 'src/test/01-input/via/Test.relast', + 'src/test/01-input/via/Test.connect', + '--rootNode=A', + '--protocols=mqtt,rest' +} + +task compileViaTest(type: RelastTest) { + useJastAddNames = true + jastAddList = 'JastAddList' + relastFiles 'src/test/02-after-ragconnect/via/Test.relast', + 'src/test/02-after-ragconnect/via/RagConnect.relast' + grammarName = 'src/test/03-after-relast/via/via' + packageName = 'via.ast' + moreInputFiles 'src/test/01-input/via/Test.jadd', + 'src/test/02-after-ragconnect/via/MqttHandler.jadd', + 'src/test/02-after-ragconnect/via/RestHandler.jadd', + 'src/test/02-after-ragconnect/via/RagConnect.jadd' +} + +compileTestJava.dependsOn compileViaTest +compileViaTest.dependsOn preprocessViaTest + +// --- Test: token-value-send --- +task preprocessTokenValueSendTest(type: JavaExec, group: 'verification') { + doFirst { + delete 'src/test/02-after-ragconnect/tokenValueSend/Test.relast', + 'src/test/02-after-ragconnect/tokenValueSend/MqttHandler.jadd', + 'src/test/02-after-ragconnect/tokenValueSend/RestHandler.jadd', + 'src/test/02-after-ragconnect/tokenValueSend/RagConnect.jadd' + } + + classpath = sourceSets.main.runtimeClasspath + main = 'org.jastadd.ragconnect.compiler.Compiler' + args '--o=src/test/02-after-ragconnect/tokenValueSend', + 'src/test/01-input/tokenValueSend/Test.relast', + 'src/test/01-input/tokenValueSend/Test.connect', + '--rootNode=A', + '--protocols=mqtt' +} + +task compileTokenValueSendTest(type: RelastTest) { + useJastAddNames = true + jastAddList = 'JastAddList' + relastFiles 'src/test/02-after-ragconnect/tokenValueSend/Test.relast', + 'src/test/02-after-ragconnect/tokenValueSend/RagConnect.relast' + grammarName = 'src/test/03-after-relast/tokenValueSend/tokenValueSend' + packageName = 'tokenValueSend.ast' + moreInputFiles 'src/test/01-input/tokenValueSend/Test.jadd', + 'src/test/02-after-ragconnect/tokenValueSend/MqttHandler.jadd', + 'src/test/02-after-ragconnect/tokenValueSend/RagConnect.jadd' +} + +compileTestJava.dependsOn compileTokenValueSendTest +compileTokenValueSendTest.dependsOn preprocessTokenValueSendTest + +// --- Test: tutorial --- +task preprocessTutorialTest(type: JavaExec, group: 'verification') { + doFirst { + delete 'src/test/02-after-ragconnect/tutorial/Test.relast', + 'src/test/02-after-ragconnect/tutorial/MqttHandler.jadd', + 'src/test/02-after-ragconnect/tutorial/RagConnect.jadd' + } + + classpath = sourceSets.main.runtimeClasspath + main = 'org.jastadd.ragconnect.compiler.Compiler' + args '--o=src/test/02-after-ragconnect/tutorial', + 'src/test/01-input/tutorial/Test.relast', + 'src/test/01-input/tutorial/Test.connect', + '--rootNode=A', + '--protocols=mqtt' +} + +task compileTutorialTest(type: RelastTest) { + useJastAddNames = true + jastAddList = 'JastAddList' + relastFiles 'src/test/02-after-ragconnect/tutorial/Test.relast', + 'src/test/02-after-ragconnect/tutorial/RagConnect.relast' + grammarName = 'src/test/03-after-relast/tutorial/tutorial' + packageName = 'tutorial.ast' + moreInputFiles 'src/test/01-input/tutorial/Test.jadd', + 'src/test/02-after-ragconnect/tutorial/MqttHandler.jadd', + 'src/test/02-after-ragconnect/tutorial/RagConnect.jadd' +} + +compileTestJava.dependsOn compileTutorialTest +compileTutorialTest.dependsOn preprocessTutorialTest diff --git a/ragconnect.tests/src/test/01-input/defaultOnlyRead/README.md b/ragconnect.tests/src/test/01-input/defaultOnlyRead/README.md new file mode 100644 index 0000000000000000000000000000000000000000..ee05db648e9204945245cd42ad8d802d2cdab887 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/defaultOnlyRead/README.md @@ -0,0 +1,3 @@ +# Default Only Read + +Idea: Use only `ReadFromMqttDefinition`, test all default mapping definitions from `byte[]` to primitive (and boxed) types diff --git a/ragconnect.tests/src/test/01-input/defaultOnlyRead/Test.connect b/ragconnect.tests/src/test/01-input/defaultOnlyRead/Test.connect new file mode 100644 index 0000000000000000000000000000000000000000..e274994175513ff743409068375f419b6e3141fc --- /dev/null +++ b/ragconnect.tests/src/test/01-input/defaultOnlyRead/Test.connect @@ -0,0 +1,15 @@ +// --- update definitions --- +receive NativeTypes.IntValue; +receive NativeTypes.ShortValue; +receive NativeTypes.LongValue; +receive NativeTypes.FloatValue; +receive NativeTypes.DoubleValue; +receive NativeTypes.CharValue; +receive NativeTypes.StringValue; + +receive BoxedTypes.IntValue; +receive BoxedTypes.ShortValue; +receive BoxedTypes.LongValue; +receive BoxedTypes.FloatValue; +receive BoxedTypes.DoubleValue; +receive BoxedTypes.CharValue; diff --git a/ragconnect.tests/src/test/01-input/defaultOnlyRead/Test.relast b/ragconnect.tests/src/test/01-input/defaultOnlyRead/Test.relast new file mode 100644 index 0000000000000000000000000000000000000000..30eba76b346dace40c72ca4e172b88ef206ea063 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/defaultOnlyRead/Test.relast @@ -0,0 +1,3 @@ +A ::= NativeTypes* BoxedTypes* ; +NativeTypes ::= <IntValue:int> <ShortValue:short> <LongValue:long> <FloatValue:float> <DoubleValue:double> <CharValue:char> <StringValue:String> ; +BoxedTypes ::= <IntValue:Integer> <ShortValue:Short> <LongValue:Long> <FloatValue:Float> <DoubleValue:Double> <CharValue:Character> ; diff --git a/ragconnect.tests/src/test/01-input/defaultOnlyWrite/README.md b/ragconnect.tests/src/test/01-input/defaultOnlyWrite/README.md new file mode 100644 index 0000000000000000000000000000000000000000..1bcc8d61a7854b7f9ef4bc1694f4f81caaac2aed --- /dev/null +++ b/ragconnect.tests/src/test/01-input/defaultOnlyWrite/README.md @@ -0,0 +1,3 @@ +# Default Only Write + +Idea: Use only `WriteToMqttDefintion`, test all default mapping definitions from primitive (and boxed) types to `byte[]`. diff --git a/ragconnect.tests/src/test/01-input/defaultOnlyWrite/Test.connect b/ragconnect.tests/src/test/01-input/defaultOnlyWrite/Test.connect new file mode 100644 index 0000000000000000000000000000000000000000..a48f49f60f66c3358ee513e79f2bbeb43df8d5e9 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/defaultOnlyWrite/Test.connect @@ -0,0 +1,51 @@ +// --- update definitions --- +// native types, synthesized +send NativeTypesSyn.IntValue; +send NativeTypesSyn.ShortValue; +send NativeTypesSyn.LongValue; +send NativeTypesSyn.FloatValue; +send NativeTypesSyn.DoubleValue; +send NativeTypesSyn.CharValue; +send NativeTypesSyn.StringValue; + +// boxed types, synthesized +send BoxedTypesSyn.IntValue; +send BoxedTypesSyn.ShortValue; +send BoxedTypesSyn.LongValue; +send BoxedTypesSyn.FloatValue; +send BoxedTypesSyn.DoubleValue; +send BoxedTypesSyn.CharValue; + +// --- dependency definitions --- +NativeTypesSyn.IntValue canDependOn NativeTypesSyn.DriverSyn as nativeIntDependency; +NativeTypesSyn.ShortValue canDependOn NativeTypesSyn.DriverSyn as nativeShortDependency; +NativeTypesSyn.LongValue canDependOn NativeTypesSyn.DriverSyn as nativeLongDependency; +NativeTypesSyn.FloatValue canDependOn NativeTypesSyn.DriverSyn as nativeFloatDependency; +NativeTypesSyn.DoubleValue canDependOn NativeTypesSyn.DriverSyn as nativeDoubleDependency; +NativeTypesSyn.CharValue canDependOn NativeTypesSyn.DriverSyn as nativeCharDependency; +NativeTypesSyn.StringValue canDependOn NativeTypesSyn.DriverSyn as nativeStringDependency; +BoxedTypesSyn.IntValue canDependOn BoxedTypesSyn.DriverSyn as boxedIntDependency; +BoxedTypesSyn.ShortValue canDependOn BoxedTypesSyn.DriverSyn as boxedShortDependency; +BoxedTypesSyn.LongValue canDependOn BoxedTypesSyn.DriverSyn as boxedLongDependency; +BoxedTypesSyn.FloatValue canDependOn BoxedTypesSyn.DriverSyn as boxedFloatDependency; +BoxedTypesSyn.DoubleValue canDependOn BoxedTypesSyn.DriverSyn as boxedDoubleDependency; +BoxedTypesSyn.CharValue canDependOn BoxedTypesSyn.DriverSyn as boxedCharDependency; + + +// --- inherited attributes not supported --- +//// native types, inherited +//send NativeTypesInh.IntValue; +//send NativeTypesInh.ShortValue; +//send NativeTypesInh.LongValue; +//send NativeTypesInh.FloatValue; +//send NativeTypesInh.DoubleValue; +//send NativeTypesInh.CharValue; +//send NativeTypesInh.StringValue; +// +//// boxed types, inherited +//send BoxedTypesInh.IntValue; +//send BoxedTypesInh.ShortValue; +//send BoxedTypesInh.LongValue; +//send BoxedTypesInh.FloatValue; +//send BoxedTypesInh.DoubleValue; +//send BoxedTypesInh.CharValue; diff --git a/ragconnect.tests/src/test/01-input/defaultOnlyWrite/Test.jadd b/ragconnect.tests/src/test/01-input/defaultOnlyWrite/Test.jadd new file mode 100644 index 0000000000000000000000000000000000000000..49bbf604d398f7a9d92e879b2b515c9e83dd3182 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/defaultOnlyWrite/Test.jadd @@ -0,0 +1,49 @@ +aspect Computation { + // native types, synthesized + syn int NativeTypesSyn.getIntValue() = Integer.parseInt(getDriverSyn()); + syn short NativeTypesSyn.getShortValue() = Short.parseShort(getDriverSyn()); + syn long NativeTypesSyn.getLongValue() = Long.parseLong(getDriverSyn()); + syn float NativeTypesSyn.getFloatValue() = Float.parseFloat(getDriverSyn()); + syn double NativeTypesSyn.getDoubleValue() = Double.parseDouble(getDriverSyn()); + syn char NativeTypesSyn.getCharValue() = getDriverSyn().charAt(0); + syn String NativeTypesSyn.getStringValue() = new String(getDriverSyn()); + + // boxed types, synthesized + syn Integer BoxedTypesSyn.getIntValue() = Integer.valueOf(getDriverSyn()); + syn Short BoxedTypesSyn.getShortValue() = Short.valueOf(getDriverSyn()); + syn Long BoxedTypesSyn.getLongValue() = Long.valueOf(getDriverSyn()); + syn Float BoxedTypesSyn.getFloatValue() = Float.valueOf(getDriverSyn()); + syn Double BoxedTypesSyn.getDoubleValue() = Double.valueOf(getDriverSyn()); + syn Character BoxedTypesSyn.getCharValue() = getDriverSyn().charAt(0); + +// --- inherited attributes not supported --- +// // native types, inherited +// inh int NativeTypesInh.getIntValue(); +// eq A.getNativeTypesInh().getIntValue() = Integer.parseInt(getDriverInh()); +// inh short NativeTypesInh.getShortValue(); +// eq A.getNativeTypesInh().getShortValue() = Short.parseShort(getDriverInh()); +// inh long NativeTypesInh.getLongValue(); +// eq A.getNativeTypesInh().getLongValue() = Long.parseLong(getDriverInh()); +// inh float NativeTypesInh.getFloatValue(); +// eq A.getNativeTypesInh().getFloatValue() = Float.parseFloat(getDriverInh()); +// inh double NativeTypesInh.getDoubleValue(); +// eq A.getNativeTypesInh().getDoubleValue() = Double.parseDouble(getDriverInh()); +// inh char NativeTypesInh.getCharValue(); +// eq A.getNativeTypesInh().getCharValue() = getDriverInh().charAt(0); +// inh String NativeTypesInh.getStringValue(); +// eq A.getNativeTypesInh().getStringValue() = new String(getDriverInh()); +// +// // boxed types, inherited +// inh Integer BoxedTypesInh.getIntValue(); +// eq A.getBoxedTypesInh().getIntValue() = Integer.valueOf(getDriverInh()); +// inh Short BoxedTypesInh.getShortValue(); +// eq A.getBoxedTypesInh().getShortValue() = Short.valueOf(getDriverInh()); +// inh Long BoxedTypesInh.getLongValue(); +// eq A.getBoxedTypesInh().getLongValue() = Long.valueOf(getDriverInh()); +// inh Float BoxedTypesInh.getFloatValue(); +// eq A.getBoxedTypesInh().getFloatValue() = Float.valueOf(getDriverInh()); +// inh Double BoxedTypesInh.getDoubleValue(); +// eq A.getBoxedTypesInh().getDoubleValue() = Double.valueOf(getDriverInh()); +// inh Character BoxedTypesInh.getCharValue(); +// eq A.getBoxedTypesInh().getCharValue() = getDriverInh().charAt(0); +} diff --git a/ragconnect.tests/src/test/01-input/defaultOnlyWrite/Test.relast b/ragconnect.tests/src/test/01-input/defaultOnlyWrite/Test.relast new file mode 100644 index 0000000000000000000000000000000000000000..e0fd7829b88b8fac04b64393e0262c69b4ac6a62 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/defaultOnlyWrite/Test.relast @@ -0,0 +1,11 @@ +A ::= NativeTypesSyn* BoxedTypesSyn* <DriverInh:String>; +// native types, synthesized +NativeTypesSyn ::= <DriverSyn:String> /<IntValue:int>/ /<ShortValue:short>/ /<LongValue:long>/ /<FloatValue:float>/ /<DoubleValue:double>/ /<CharValue:char>/ /<StringValue:String>/ ; +// boxed types, synthesized +BoxedTypesSyn ::= <DriverSyn:String> /<IntValue:Integer>/ /<ShortValue:Short>/ /<LongValue:Long>/ /<FloatValue:Float>/ /<DoubleValue:Double>/ /<CharValue:Character>/ ; + +// --- inherited attributes not supported --- +//// native types, inherited +//NativeTypesInh ::= /<IntValue:int>/ /<ShortValue:short>/ /<LongValue:long>/ /<FloatValue:float>/ /<DoubleValue:double>/ /<CharValue:char>/ /<StringValue:String>/ ; +//// boxed types, inherited +//BoxedTypesInh ::= /<IntValue:Integer>/ /<ShortValue:Short>/ /<LongValue:Long>/ /<FloatValue:Float>/ /<DoubleValue:Double>/ /<CharValue:Character>/ ; diff --git a/ragconnect.tests/src/test/01-input/errors/Errors.connect b/ragconnect.tests/src/test/01-input/errors/Errors.connect new file mode 100644 index 0000000000000000000000000000000000000000..bd57822a6c70198cc36698f790fa90655ed4db73 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/errors/Errors.connect @@ -0,0 +1,72 @@ +// --- update receive definitions --- +// Error: there must not be two receive definitions for the same token +receive B.DoubledValue ; +receive B.DoubledValue using IntToInt ; + +// NOT HANDLED \\ Error: the token must be resolvable within the parent type +// NOT HANDLED \\ receive B.NonExisting ; + +// Error: the Token must not be a TokenNTA (i.e., check for !Token.getNTA()) +receive B.ErrorNTA ; + +// Error: from-type of first mapping must be byte[] or a supported primitive type +receive B.ErrorTypeOfFirstMapping using ListToList ; + +// Error: to-type of last mapping must be type of the Token +receive B.ErrorTypeOfLastMapping using StringToList ; + +// Error: types of mappings must match (modulo inheritance) +receive B.ErrorTypeMismatch using StringToList, IntToInt ; + +// --- update send definitions --- +// NOT HANDLED \\ Error: the token must be resolvable within the parent type +// NOT HANDLED \\ receive C.NonExisting ; + +// Error: Token must be a TokenNTA (i.e., check for Token.getNTA()) +send C.ErrorNotNTA ; + +// Error: from-type of first mapping must be type of Token +send C.ErrorTypeOfFirstMapping using IntToInt ; + +// Error: to-type of last mapping must be byte[] or a supported primitive type +send C.ErrorTypeOfLastMapping1 using StringToList ; +send C.ErrorTypeOfLastMapping2 ; + +// Error: types of mappings must match (modulo inheritance) +send C.ErrorTypeMismatch using StringToList, IntToInt ; + +// Error: no more than one send mapping for each TokenComponent +send C.DoubledValue ; +send C.DoubledValue using IntToInt ; + +// --- dependency definitions --- +// NOT HANDLED \\ Error: Both, source and target must be resolvable within the parent type +// NOT HANDLED \\ D.SourceNonExistingTarget canDependOn D.NonExisting as NonExistingTarget ; +// NOT HANDLED \\ D.NonExisting canDependOn D.TargetNonExistingSource as NonExistingSource ; + +// Error: There must be a send update definition for the target token +D.SourceNoWriteDef canDependOn D.TargetNoWriteDef as NoWriteDef ; + +// Error: The name of a dependency definition must not be equal to a list-node on the source +D.SourceSameAsListNode canDependOn D.TargetSameAsListNode as MyList ; +send D.TargetSameAsListNode; + +// Error: There must not be two dependency definitions with the same name +D.SourceDoubledValue canDependOn D.TargetDoubledValue as DoubledValue ; +D.SourceDoubledValue canDependOn D.TargetDoubledValue as DoubledValue ; +send D.TargetDoubledValue; + +// --- mapping definitions --- +ListToList maps java.util.List<String> list to java.util.List<String> {: + return list; +:} + +StringToList maps String s to List<String> {: + java.util.List<String> result = new java.util.ArrayList<>(); + result.add(s); + return result; +:} + +IntToInt maps int number to int {: + return number + 1; +:} diff --git a/ragconnect.tests/src/test/01-input/errors/Errors.expected b/ragconnect.tests/src/test/01-input/errors/Errors.expected new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/ragconnect.tests/src/test/01-input/errors/Errors.relast b/ragconnect.tests/src/test/01-input/errors/Errors.relast new file mode 100644 index 0000000000000000000000000000000000000000..fcde7df1d2e48b9591ddea31aaa3b0a9efdd4cfa --- /dev/null +++ b/ragconnect.tests/src/test/01-input/errors/Errors.relast @@ -0,0 +1,15 @@ +A ::= B C D ; + +// read definitions +B ::= /<ErrorNTA:String>/ <ErrorTypeOfFirstMapping:String> <ErrorTypeOfLastMapping:String> <DoubledValue:int> <ErrorTypeMismatch:String> ; + +// write definitions +C ::= <ErrorNotNTA:String> /<ErrorTypeOfFirstMapping:String>/ /<ErrorTypeOfLastMapping1:String>/ /<ErrorTypeOfLastMapping2:List<String>>/ /<ErrorTypeMismatch:String>/ /<DoubledValue:int>/ ; + +// dependency definitions +D ::= <SourceNonExistingTarget> + /<TargetNonExistingSource>/ + <SourceNoWriteDef> /<TargetNoWriteDef>/ + <SourceSameAsListNode> /<TargetSameAsListNode>/ + <SourceDoubledValue> /<TargetDoubledValue>/ + MyList:D* ; diff --git a/ragconnect.tests/src/test/01-input/errors/README.md b/ragconnect.tests/src/test/01-input/errors/README.md new file mode 100644 index 0000000000000000000000000000000000000000..e8efed33eb3868441a63dc719e3e92993a2c9d49 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/errors/README.md @@ -0,0 +1,25 @@ +Ideas for errors: + +- Read-Update + - the token must be resolvable within the parent type + - the Token must not be a TokenNTA (i.e., check for `!Token.getNTA()`) + - type of first mapping must be `byte[]` + - type of last mapping must be type of the Token + - types of mappings must match (modulo inheritance) +- Write-Update + - the token must be resolvable within the parent type + - Token must be a TokenNTA (i.e., check for `Token.getNTA()`) + - type of first mapping must be type of Token + - type of last mapping must be `byte[]` + - types of mappings must match (modulo inheritance) + - no more than one write mapping for each TokenComponent +- for all type checks, there are three cases regarding the two types to check against: + 1) both are nonterminal types, check with grammar + 2) both are known classes, check with `Class.forName()` and subclass-checking-methods + 3) otherwise issue warning, that types could not be matched +- dependency-definition + - There **must be** a write update definition for the target token + - Otherwise there are missing update and write methods used in the virtual setter + - Both, source and target must be resolvable within the parent type + - The name of a dependency definition must not be equal to a list-node on the source + - There must not be two dependency definitions with the same name diff --git a/ragconnect.tests/src/test/01-input/example/README.md b/ragconnect.tests/src/test/01-input/example/README.md new file mode 100644 index 0000000000000000000000000000000000000000..7665b437a513602587474904ef08135efee13b44 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/example/README.md @@ -0,0 +1,3 @@ +# Example + +Idea: Use the motivating example from our paper as a test case, including one read, one write update definition diff --git a/ragconnect.tests/src/test/01-input/example/Test.connect b/ragconnect.tests/src/test/01-input/example/Test.connect new file mode 100644 index 0000000000000000000000000000000000000000..2a26051b743145ad8af6ad7b8ea84ad58ce1348f --- /dev/null +++ b/ragconnect.tests/src/test/01-input/example/Test.connect @@ -0,0 +1,31 @@ +/* Version 2020-07-15 */ +// --- update definitions --- +receive Link.CurrentPosition using ParseRobotState, RobotStateToIntPosition ; +send RobotArm.AppropriateSpeed using CreateSpeedMessage, SerializeRobotConfig ; + +// --- dependency definitions --- +RobotArm.AppropriateSpeed canDependOn Link.CurrentPosition as dependency1 ; + +// --- mapping definitions --- +ParseRobotState maps byte[] bytes to robot.RobotStateOuterClass.RobotState {: + TestCounter.INSTANCE.numberParseLinkState += 1; + return robot.RobotStateOuterClass.RobotState.parseFrom(bytes); +:} + +SerializeRobotConfig maps config.Config.RobotConfig rc to byte[] {: + TestCounter.INSTANCE.numberSerializeRobotConfig += 1; + return rc.toByteArray(); +:} + +RobotStateToIntPosition maps robot.RobotStateOuterClass.RobotState rs to IntPosition {: + TestCounter.INSTANCE.numberLinkStateToIntPosition += 1; + robot.RobotStateOuterClass.RobotState.Position p = rs.getPosition(); + return IntPosition.of((int) (Math.round(p.getX() * 10)), (int) (Math.round(p.getY() * 10)), (int) (Math.round(p.getZ() * 10))); +:} + +CreateSpeedMessage maps double speed to config.Config.RobotConfig {: + TestCounter.INSTANCE.numberCreateSpeedMessage += 1; + return config.Config.RobotConfig.newBuilder() + .setSpeed(speed) + .build(); +:} diff --git a/ragconnect.tests/src/test/01-input/example/Test.jadd b/ragconnect.tests/src/test/01-input/example/Test.jadd new file mode 100644 index 0000000000000000000000000000000000000000..b62b9aee7cb344c587993feefccdd64ab0bf6e4b --- /dev/null +++ b/ragconnect.tests/src/test/01-input/example/Test.jadd @@ -0,0 +1,95 @@ +aspect GrammarTypes { + public class TestCounter { + public static TestCounter INSTANCE = new TestCounter(); + public int numberParseLinkState = 0; + public int numberSerializeRobotConfig = 0; + public int numberLinkStateToIntPosition = 0; + public int numberCreateSpeedMessage = 0; + public int numberInSafetyZone = 0; + public static void reset() { + TestCounter.INSTANCE = new TestCounter(); + } + } + + public class IntPosition { + private final int x, y, z; + + private IntPosition(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + public static IntPosition of(int x, int y, int z) { + return new IntPosition(x, y, z); + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public int getZ() { + return z; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + IntPosition that = (IntPosition) o; + return x == that.x && + y == that.y && + z == that.z; + } + + @Override + public int hashCode() { + return java.util.Objects.hash(x, y, z); + } + + @Override + public String toString() { + return "(" + x + ", " + y + ", " + z + ")"; + } + } + + inh Model RobotArm.model(); + eq Model.getRobotArm().model() = this; + + inh RobotArm Link.containingRobotArm(); + eq RobotArm.getLink().containingRobotArm() = this; + eq RobotArm.getEndEffector().containingRobotArm() = this; + + syn boolean RobotArm.isInSafetyZone() { + TestCounter.INSTANCE.numberInSafetyZone += 1; + for (Link link : getLinkList()) { + if (model().getZoneModel().isInSafetyZone(link.getCurrentPosition())) { + return true; + } + } + return model().getZoneModel().isInSafetyZone(getEndEffector().getCurrentPosition()); + } + + cache ZoneModel.isInSafetyZone(IntPosition pos); + syn boolean ZoneModel.isInSafetyZone(IntPosition pos) { + for (Zone sz : getSafetyZoneList()) { + for (Coordinate coordinate : sz.getCoordinateList()) { + if (coordinate.getPosition().equals(pos)) { + return true; + } + } + } + return false; + } + + syn double RobotArm.speedLow() = 0.4d; + syn double RobotArm.speedHigh() = 1.0d; + + syn double RobotArm.getAppropriateSpeed() { + return isInSafetyZone() ? speedLow() : speedHigh(); + } +} diff --git a/ragconnect.tests/src/test/01-input/example/Test.relast b/ragconnect.tests/src/test/01-input/example/Test.relast new file mode 100644 index 0000000000000000000000000000000000000000..b8d932ae470e4bf3dc433f8610eec1f05016e595 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/example/Test.relast @@ -0,0 +1,13 @@ +Model ::= RobotArm ZoneModel ; + +ZoneModel ::= SafetyZone:Zone* ; + +Zone ::= Coordinate* ; + +RobotArm ::= Link* EndEffector /<AppropriateSpeed:double>/ ; + +Link ::= <Name:String> <CurrentPosition:IntPosition> ; + +EndEffector : Link; + +Coordinate ::= <Position:IntPosition> ; diff --git a/ragconnect.tests/src/test/01-input/example/config.json b/ragconnect.tests/src/test/01-input/example/config.json new file mode 100644 index 0000000000000000000000000000000000000000..02843152a51197a04bce4e8e54ad4ec0f703eca5 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/example/config.json @@ -0,0 +1,9 @@ +{ + "joints": [ + {"name": "Joint1", "topic": "robot/arm/joint1"}, + {"name": "Joint2", "topic": "robot/arm/joint2"}, + {"name": "EndEffector", "topic": "-", "isEndEffector": true} + ], + "robotConfigTopic": "robot/config", + "dataConfigTopic": "-" +} diff --git a/ragconnect.tests/src/test/01-input/read1write2/README.md b/ragconnect.tests/src/test/01-input/read1write2/README.md new file mode 100644 index 0000000000000000000000000000000000000000..197eeb5b1399a079bd16c9e928622732511f7ca1 --- /dev/null +++ b/ragconnect.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/ragconnect.tests/src/test/01-input/read1write2/Test.connect b/ragconnect.tests/src/test/01-input/read1write2/Test.connect new file mode 100644 index 0000000000000000000000000000000000000000..e826f1d040d1668ae5d5d56a83e5b4e5e96a7290 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/read1write2/Test.connect @@ -0,0 +1,18 @@ +// --- update definitions --- +// OnSameNonterminal +receive OnSameNonterminal.Input; +send OnSameNonterminal.OutInteger; +send OnSameNonterminal.OutString; + +// OnDifferentNonterminal +receive OnDifferentNonterminal.Input; +send TheOther.OutInteger; +send 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/ragconnect.tests/src/test/01-input/read1write2/Test.jadd b/ragconnect.tests/src/test/01-input/read1write2/Test.jadd new file mode 100644 index 0000000000000000000000000000000000000000..490772fcc7dcd1f27841c84eaa8a0231851ed6cc --- /dev/null +++ b/ragconnect.tests/src/test/01-input/read1write2/Test.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/ragconnect.tests/src/test/01-input/read1write2/Test.relast b/ragconnect.tests/src/test/01-input/read1write2/Test.relast new file mode 100644 index 0000000000000000000000000000000000000000..fa0b171c4b96f097e6812f34434fc60801bc307c --- /dev/null +++ b/ragconnect.tests/src/test/01-input/read1write2/Test.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/ragconnect.tests/src/test/01-input/read2write1/Test.connect b/ragconnect.tests/src/test/01-input/read2write1/Test.connect new file mode 100644 index 0000000000000000000000000000000000000000..4c2a9b237acb5b42f274a2f095fcb74b4132a49d --- /dev/null +++ b/ragconnect.tests/src/test/01-input/read2write1/Test.connect @@ -0,0 +1,18 @@ +// --- update definitions --- +// OnSameNonterminal +receive OnSameNonterminal.Input1; +receive OnSameNonterminal.Input2; +send OnSameNonterminal.OutInteger; + +// OnDifferentNonterminal +receive OnDifferentNonterminal.Input1; +receive OnDifferentNonterminal.Input2; +send 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/ragconnect.tests/src/test/01-input/read2write1/Test.jadd b/ragconnect.tests/src/test/01-input/read2write1/Test.jadd new file mode 100644 index 0000000000000000000000000000000000000000..4d31f1a6441311469f8f93cd150e6c8bb353877e --- /dev/null +++ b/ragconnect.tests/src/test/01-input/read2write1/Test.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/ragconnect.tests/src/test/01-input/read2write1/Test.relast b/ragconnect.tests/src/test/01-input/read2write1/Test.relast new file mode 100644 index 0000000000000000000000000000000000000000..afe0125760afe222c830f383536f6e2f97c8f656 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/read2write1/Test.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/ragconnect.tests/src/test/01-input/tokenValueSend/README.md b/ragconnect.tests/src/test/01-input/tokenValueSend/README.md new file mode 100644 index 0000000000000000000000000000000000000000..b2d0c3c8ff55186316be2984eb53ffc9f7e42d90 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/tokenValueSend/README.md @@ -0,0 +1,3 @@ +# Token Value Send + +Idea: 1) Test normal token send, 2) Test combined receive and send on same token, 3) Test combined receive, send and a dependency diff --git a/ragconnect.tests/src/test/01-input/tokenValueSend/Test.connect b/ragconnect.tests/src/test/01-input/tokenValueSend/Test.connect new file mode 100644 index 0000000000000000000000000000000000000000..bcc26d1fba55b6241227f8e37e93097ced82284b --- /dev/null +++ b/ragconnect.tests/src/test/01-input/tokenValueSend/Test.connect @@ -0,0 +1,23 @@ +// OnlySend +send OnlySend.Value using AddPostfix ; + +// ReceiveAndSend +receive ReceiveAndSend.Value using AddPrefix ; +send ReceiveAndSend.Value using AddPostfix ; + +// ReceiveSendAndDepend +receive ReceiveSendAndDepend.Value using AddPrefix ; +send ReceiveSendAndDepend.Value using AddPostfix ; +send ReceiveSendAndDepend.OtherOutput using AddPostfix ; + +ReceiveSendAndDepend.OtherOutput canDependOn ReceiveSendAndDepend.Value as dependency1 ; + +AddPrefix maps String s to String {: + System.out.println("receive " + s); + return "Pre-" + s; +:} + +AddPostfix maps String s to String {: + System.out.println("send " + s); + return s + "-Post"; +:} diff --git a/ragconnect.tests/src/test/01-input/tokenValueSend/Test.jadd b/ragconnect.tests/src/test/01-input/tokenValueSend/Test.jadd new file mode 100644 index 0000000000000000000000000000000000000000..6a2f3e0d01042ff1e30a4b48f6938e6085b4ed0d --- /dev/null +++ b/ragconnect.tests/src/test/01-input/tokenValueSend/Test.jadd @@ -0,0 +1,4 @@ +aspect Computation{ + // ReceiveSendAndDepend + syn String ReceiveSendAndDepend.getOtherOutput() = getValue() + "-T"; +} diff --git a/ragconnect.tests/src/test/01-input/tokenValueSend/Test.relast b/ragconnect.tests/src/test/01-input/tokenValueSend/Test.relast new file mode 100644 index 0000000000000000000000000000000000000000..eef6095b186555d9660186014f13bdfe68b07bab --- /dev/null +++ b/ragconnect.tests/src/test/01-input/tokenValueSend/Test.relast @@ -0,0 +1,4 @@ +A ::= OnlySend ReceiveAndSend ReceiveSendAndDepend ; +OnlySend ::= <Value:String> ; +ReceiveAndSend ::= <Value:String> ; +ReceiveSendAndDepend ::= <Value:String> /<OtherOutput:String>/ ; diff --git a/ragconnect.tests/src/test/01-input/tutorial/README.md b/ragconnect.tests/src/test/01-input/tutorial/README.md new file mode 100644 index 0000000000000000000000000000000000000000..09995a98b03cf08c364b670b820fedbfd0674cba --- /dev/null +++ b/ragconnect.tests/src/test/01-input/tutorial/README.md @@ -0,0 +1,3 @@ +# Tutorial + +Idea: Test the example from the [documentation](https://jastadd.pages.st.inf.tu-dresden.de/ragconnect/using.html) diff --git a/ragconnect.tests/src/test/01-input/tutorial/Test.connect b/ragconnect.tests/src/test/01-input/tutorial/Test.connect new file mode 100644 index 0000000000000000000000000000000000000000..c6bdc7b3c4f5dcf661ce2ce757301e42eb39a1a7 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/tutorial/Test.connect @@ -0,0 +1,13 @@ +// endpoint definitions +receive A.Input ; +send A.OutputOnA ; +send B.OutputOnB using Transformation ; + +// mapping definitions +Transformation maps String s to String {: + return s + "postfix"; +:} + +// dependency definitions +A.OutputOnA canDependOn A.Input as dependencyA ; +B.OutputOnB canDependOn A.Input as dependencyB ; diff --git a/ragconnect.tests/src/test/01-input/tutorial/Test.jadd b/ragconnect.tests/src/test/01-input/tutorial/Test.jadd new file mode 100644 index 0000000000000000000000000000000000000000..4cb9dbf4def3102924b530c58ece410e73fa255b --- /dev/null +++ b/ragconnect.tests/src/test/01-input/tutorial/Test.jadd @@ -0,0 +1,7 @@ +aspect Computation { + syn String A.getOutputOnA() = "a" + getInput(); + + syn String B.getOutputOnB() = "b" + input(); + inh String B.input(); + eq A.getB().input() = getInput(); +} diff --git a/ragconnect.tests/src/test/01-input/tutorial/Test.relast b/ragconnect.tests/src/test/01-input/tutorial/Test.relast new file mode 100644 index 0000000000000000000000000000000000000000..9009e2787814bb8020d1459fc5d9758f9cc1ca06 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/tutorial/Test.relast @@ -0,0 +1,2 @@ +A ::= <Input:String> /<OutputOnA:String>/ B* ; +B ::= /<OutputOnB:String>/ ; diff --git a/ragconnect.tests/src/test/01-input/via/Test.connect b/ragconnect.tests/src/test/01-input/via/Test.connect new file mode 100644 index 0000000000000000000000000000000000000000..e4d2862a651dd6e68f67d2a492b6105a15f0a77c --- /dev/null +++ b/ragconnect.tests/src/test/01-input/via/Test.connect @@ -0,0 +1,33 @@ +receive A.Mqtt2MqttInput using MarkMqttInput ; +receive A.Rest2RestInput using MarkRestInput ; +receive A.Mqtt2RestInput using MarkMqttInput ; +receive A.Rest2MqttInput using MarkRestInput ; +receive A.Both2BothInput ; + +send A.Mqtt2MqttOutput using MarkMqttOutput ; +send A.Rest2RestOutput using MarkRestOutput ; +send A.Mqtt2RestOutput using MarkRestOutput ; +send A.Rest2MqttOutput using MarkMqttOutput ; +send A.Both2RestOutput using MarkRestOutput ; +send A.Both2MqttOutput using MarkMqttOutput ; + +A.Mqtt2MqttOutput canDependOn A.Mqtt2MqttInput as dependencyMqtt2Mqtt ; +A.Rest2RestOutput canDependOn A.Rest2RestInput as dependencyRest2Rest ; +A.Mqtt2RestOutput canDependOn A.Mqtt2RestInput as dependencyMqtt2Rest ; +A.Rest2MqttOutput canDependOn A.Rest2MqttInput as dependencyRest2Mqtt ; +A.Both2RestOutput canDependOn A.Both2BothInput as dependencyBoth2Rest ; +A.Both2MqttOutput canDependOn A.Both2BothInput as dependencyBoth2Mqtt ; + +MarkMqttInput maps String s to String {: + return "FromMqtt-" + s; +:} +MarkRestInput maps String s to String {: + return "FromRest-" + s; +:} + +MarkMqttOutput maps String s to String {: + return s + "-ToMqtt"; +:} +MarkRestOutput maps String s to String {: + return s + "-ToRest"; +:} diff --git a/ragconnect.tests/src/test/01-input/via/Test.jadd b/ragconnect.tests/src/test/01-input/via/Test.jadd new file mode 100644 index 0000000000000000000000000000000000000000..9d25387d26e651fe25a937a56a863311b4b7b69a --- /dev/null +++ b/ragconnect.tests/src/test/01-input/via/Test.jadd @@ -0,0 +1,8 @@ +aspect Computation { + syn lazy String A.getMqtt2MqttOutput() = getMqtt2MqttInput() + "-M2M" ; + syn lazy String A.getRest2RestOutput() = getRest2RestInput() + "-R2R" ; + syn lazy String A.getMqtt2RestOutput() = getMqtt2RestInput() + "-M2R" ; + syn lazy String A.getRest2MqttOutput() = getRest2MqttInput() + "-R2M" ; + syn lazy String A.getBoth2MqttOutput() = getBoth2BothInput() + "-B2M" ; + syn lazy String A.getBoth2RestOutput() = getBoth2BothInput() + "-B2R" ; +} diff --git a/ragconnect.tests/src/test/01-input/via/Test.relast b/ragconnect.tests/src/test/01-input/via/Test.relast new file mode 100644 index 0000000000000000000000000000000000000000..1e707fbbc04b1204381351d0c38e826fcbcc8b80 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/via/Test.relast @@ -0,0 +1,5 @@ +A ::= <Mqtt2MqttInput> /<Mqtt2MqttOutput>/ + <Rest2RestInput> /<Rest2RestOutput>/ + <Mqtt2RestInput> /<Mqtt2RestOutput>/ + <Rest2MqttInput> /<Rest2MqttOutput>/ + <Both2BothInput> /<Both2MqttOutput>/ /<Both2RestOutput>/; diff --git a/ragconnect.tests/src/test/02-after-ragconnect/.gitignore b/ragconnect.tests/src/test/02-after-ragconnect/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d6b7ef32c8478a48c3994dcadc86837f4371184d --- /dev/null +++ b/ragconnect.tests/src/test/02-after-ragconnect/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/ragconnect.tests/src/test/03-after-relast/.gitignore b/ragconnect.tests/src/test/03-after-relast/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d6b7ef32c8478a48c3994dcadc86837f4371184d --- /dev/null +++ b/ragconnect.tests/src/test/03-after-relast/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/ragconnect.tests/src/test/java-gen/.gitignore b/ragconnect.tests/src/test/java-gen/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d6b7ef32c8478a48c3994dcadc86837f4371184d --- /dev/null +++ b/ragconnect.tests/src/test/java-gen/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractMqttTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractMqttTest.java new file mode 100644 index 0000000000000000000000000000000000000000..36baf2cf5ffda1fbb6225daf8f054a40c3a2c331 --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractMqttTest.java @@ -0,0 +1,104 @@ +package org.jastadd.ragconnect.tests; + +import defaultOnlyRead.ast.MqttHandler; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +/** + * Base class for tests ensuring running mqtt broker. + * + * @author rschoene - Initial contribution + */ +@Tag("mqtt") +public abstract class AbstractMqttTest { + + static boolean checkDone = false; + static Boolean checkResult; + + @BeforeAll + public static void checkMqttConnection() { + if (!checkDone) { + checkDone = true; + try { + checkResult = new MqttHandler() + .dontSendWelcomeMessage() + .setHost(TestUtils.getMqttHost()) + .waitUntilReady(2, TimeUnit.SECONDS); + } catch (IOException e) { + checkResult = false; + } + } + if (!checkResult) { + throw new IllegalStateException("Mqtt Broker not ready!"); + } + } + + @Test + public final void buildModel() { + createModel(); + } + + @Tag("mqtt") + @Test + public final void testCommunicateSendInitialValue() throws IOException, InterruptedException { + createModel(); + setupReceiverAndConnect(true); + + communicateSendInitialValue(); + } + + protected abstract void communicateSendInitialValue() throws InterruptedException; + + @Tag("mqtt") + @Test + public final void testCommunicateOnlyUpdatedValue() throws IOException, InterruptedException { + createModel(); + setupReceiverAndConnect(false); + + communicateOnlyUpdatedValue(); + } + + protected abstract void communicateOnlyUpdatedValue() throws InterruptedException; + + + protected abstract void createModel(); + + /** + * Begin with this snippet + * <pre> + * model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS); + * + * handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost()); + * assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS)); + * </pre> + * + * And then add dependencies, initialise receiver, add connections to those receivers, + * and finally call generated connect* methods on model elements. + * @param writeCurrentValue + */ + protected abstract void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException; + + @AfterEach + public void alwaysCloseConnections() { + closeConnections(); + } + + /** + * Write the following snippet (using your correct handler and model): + * <pre> + * if (handler != null) { + * handler.close(); + * } + * if (model != null) { + * model.ragconnectCloseConnections(); + * } + * </pre> + */ + protected abstract void closeConnections(); + +} diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/DefaultOnlyReadTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/DefaultOnlyReadTest.java new file mode 100644 index 0000000000000000000000000000000000000000..65302cfa47f0cd83788385ab0d644d2627868a9c --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/DefaultOnlyReadTest.java @@ -0,0 +1,138 @@ +package org.jastadd.ragconnect.tests; + +import defaultOnlyRead.ast.A; +import defaultOnlyRead.ast.BoxedTypes; +import defaultOnlyRead.ast.MqttHandler; +import defaultOnlyRead.ast.NativeTypes; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.concurrent.TimeUnit; + +import static org.jastadd.ragconnect.tests.TestUtils.mqttUri; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test case "defaultOnlyRead". + * + * @author rschoene - Initial contribution + */ +public class DefaultOnlyReadTest extends AbstractMqttTest { + + private static final String TOPIC_NATIVE_INT = "native/int"; + private static final String TOPIC_NATIVE_SHORT = "native/short"; + private static final String TOPIC_NATIVE_LONG = "native/long"; + private static final String TOPIC_NATIVE_FLOAT = "native/float"; + private static final String TOPIC_NATIVE_DOUBLE = "native/double"; + private static final String TOPIC_NATIVE_CHAR = "native/char"; + private static final String TOPIC_NATIVE_STRING = "native/string"; + + private static final String TOPIC_BOXED_INTEGER = "boxed/Integer"; + private static final String TOPIC_BOXED_SHORT = "boxed/Short"; + private static final String TOPIC_BOXED_LONG = "boxed/Long"; + private static final String TOPIC_BOXED_FLOAT = "boxed/Float"; + private static final String TOPIC_BOXED_DOUBLE = "boxed/Double"; + private static final String TOPIC_BOXED_CHARACTER = "boxed/Character"; + + private A model; + private NativeTypes integers; + private NativeTypes floats; + private NativeTypes chars; + private BoxedTypes allBoxed; + private MqttHandler sender; + + @Override + public void closeConnections() { + if (sender != null) { + sender.close(); + } + if (model != null) { + model.ragconnectCloseConnections(); + } + } + + @Override + protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException { + model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS); + + integers.connectIntValue(mqttUri(TOPIC_NATIVE_INT)); + integers.connectShortValue(mqttUri(TOPIC_NATIVE_SHORT)); + integers.connectLongValue(mqttUri(TOPIC_NATIVE_LONG)); + floats.connectFloatValue(mqttUri(TOPIC_NATIVE_FLOAT)); + floats.connectDoubleValue(mqttUri(TOPIC_NATIVE_DOUBLE)); + chars.connectCharValue(mqttUri(TOPIC_NATIVE_CHAR)); + chars.connectStringValue(mqttUri(TOPIC_NATIVE_STRING)); + allBoxed.connectIntValue(mqttUri(TOPIC_BOXED_INTEGER)); + allBoxed.connectShortValue(mqttUri(TOPIC_BOXED_SHORT)); + allBoxed.connectLongValue(mqttUri(TOPIC_BOXED_LONG)); + allBoxed.connectFloatValue(mqttUri(TOPIC_BOXED_FLOAT)); + allBoxed.connectDoubleValue(mqttUri(TOPIC_BOXED_DOUBLE)); + allBoxed.connectCharValue(mqttUri(TOPIC_BOXED_CHARACTER)); + + sender = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost()); + assertTrue(sender.waitUntilReady(2, TimeUnit.SECONDS)); + } + + @Override + protected void communicateOnlyUpdatedValue() throws InterruptedException { + final int expectedIntValue = 1; + final short expectedShortValue = 2; + final long expectedLongValue = 3L; + final float expectedFloatValue = 4.1f; + final double expectedDoubleValue = 5.2; + final char expectedCharValue = 'c'; + final String expectedStringValue = "6.3"; + + sender.publish(TOPIC_NATIVE_INT, ByteBuffer.allocate(4).putInt(expectedIntValue).array()); + sender.publish(TOPIC_NATIVE_SHORT, ByteBuffer.allocate(2).putShort(expectedShortValue).array()); + sender.publish(TOPIC_NATIVE_LONG, ByteBuffer.allocate(8).putLong(expectedLongValue).array()); + sender.publish(TOPIC_NATIVE_FLOAT, ByteBuffer.allocate(4).putFloat(expectedFloatValue).array()); + sender.publish(TOPIC_NATIVE_DOUBLE, ByteBuffer.allocate(8).putDouble(expectedDoubleValue).array()); + sender.publish(TOPIC_NATIVE_CHAR, ByteBuffer.allocate(2).putChar(expectedCharValue).array()); + sender.publish(TOPIC_NATIVE_STRING, expectedStringValue.getBytes()); + + sender.publish(TOPIC_BOXED_INTEGER, ByteBuffer.allocate(4).putInt(expectedIntValue).array()); + sender.publish(TOPIC_BOXED_SHORT, ByteBuffer.allocate(2).putShort(expectedShortValue).array()); + sender.publish(TOPIC_BOXED_LONG, ByteBuffer.allocate(8).putLong(expectedLongValue).array()); + sender.publish(TOPIC_BOXED_FLOAT, ByteBuffer.allocate(4).putFloat(expectedFloatValue).array()); + sender.publish(TOPIC_BOXED_DOUBLE, ByteBuffer.allocate(8).putDouble(expectedDoubleValue).array()); + sender.publish(TOPIC_BOXED_CHARACTER, ByteBuffer.allocate(2).putChar(expectedCharValue).array()); + + TestUtils.waitForMqtt(); + + assertEquals(expectedIntValue, integers.getIntValue()); + assertEquals(expectedShortValue, integers.getShortValue()); + assertEquals(expectedLongValue, integers.getLongValue()); + assertEquals(expectedFloatValue, floats.getFloatValue(), TestUtils.DELTA); + assertEquals(expectedDoubleValue, floats.getDoubleValue(), TestUtils.DELTA); + assertEquals(expectedCharValue, chars.getCharValue()); + assertEquals(expectedStringValue, chars.getStringValue()); + + assertEquals(expectedIntValue, allBoxed.getIntValue().intValue()); + assertEquals(expectedShortValue, allBoxed.getShortValue().shortValue()); + assertEquals(expectedLongValue, allBoxed.getLongValue().longValue()); + assertEquals(expectedFloatValue, allBoxed.getFloatValue(), TestUtils.DELTA); + assertEquals(expectedDoubleValue, allBoxed.getDoubleValue(), TestUtils.DELTA); + assertEquals(expectedCharValue, allBoxed.getCharValue().charValue()); + } + + @Override + protected void communicateSendInitialValue() { + // empty + } + + @Override + protected void createModel() { + model = new A(); + integers = new NativeTypes(); + model.addNativeTypes(integers); + floats = new NativeTypes(); + model.addNativeTypes(floats); + chars = new NativeTypes(); + model.addNativeTypes(chars); + allBoxed = new BoxedTypes(); + model.addBoxedTypes(allBoxed); + } + +} diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/DefaultOnlyWriteTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/DefaultOnlyWriteTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c312d4c534d624f0a6c2ee891eba36ddb63be584 --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/DefaultOnlyWriteTest.java @@ -0,0 +1,310 @@ +package org.jastadd.ragconnect.tests; + +import defaultOnlyWrite.ast.A; +import defaultOnlyWrite.ast.BoxedTypesSyn; +import defaultOnlyWrite.ast.MqttHandler; +import defaultOnlyWrite.ast.NativeTypesSyn; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import static org.jastadd.ragconnect.tests.TestUtils.mqttUri; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Test case "defaultOnlyRead". + * + * @author rschoene - Initial contribution + */ +public class DefaultOnlyWriteTest extends AbstractMqttTest { + + private static final String TOPIC_NATIVE_INT = "native/int"; + private static final String TOPIC_NATIVE_SHORT = "native/short"; + private static final String TOPIC_NATIVE_LONG = "native/long"; + private static final String TOPIC_NATIVE_FLOAT = "native/float"; + private static final String TOPIC_NATIVE_DOUBLE = "native/double"; + private static final String TOPIC_NATIVE_CHAR = "native/char"; + private static final String TOPIC_NATIVE_STRING = "native/string"; + + private static final String TOPIC_BOXED_INTEGER = "boxed/Integer"; + private static final String TOPIC_BOXED_SHORT = "boxed/Short"; + private static final String TOPIC_BOXED_LONG = "boxed/Long"; + private static final String TOPIC_BOXED_FLOAT = "boxed/Float"; + private static final String TOPIC_BOXED_DOUBLE = "boxed/Double"; + private static final String TOPIC_BOXED_CHARACTER = "boxed/Character"; + + private A model; + private NativeTypesSyn nativeIntegers; + private NativeTypesSyn nativeFloats; + private NativeTypesSyn nativeChars; + private BoxedTypesSyn boxedIntegers; + private BoxedTypesSyn boxedFloats; + private BoxedTypesSyn boxedChars; + private MqttHandler receiver; + private ReceiverData data; + + @Override + public void closeConnections() { + if (receiver != null) { + receiver.close(); + } + if (model != null) { + model.ragconnectCloseConnections(); + } + } + + + + + @Override + protected void communicateSendInitialValue() throws InterruptedException { + // check initial value + TestUtils.waitForMqtt(); + checkData(1, 1, 1.1, 'a', "ab"); + + // set new value + setData("2", "2.2", "cd"); + + // check new value + TestUtils.waitForMqtt(); + checkData(2, 2, 2.2, 'c', "cd"); + + // set new value + setData("3", "3.2", "ee"); + + // check new value + TestUtils.waitForMqtt(); + checkData(3, 3, 3.2, 'e', "ee"); + } + + @Override + protected void communicateOnlyUpdatedValue() throws InterruptedException { + // check initial value (will be default values) + TestUtils.waitForMqtt(); + checkData(0, null, null, null, null); + + // set new value + setData("2", "2.2", "cd"); + + // check new value + TestUtils.waitForMqtt(); + checkData(1, 2, 2.2, 'c', "cd"); + + // set new value + setData("3", "3.2", "ee"); + + // check new value + TestUtils.waitForMqtt(); + checkData(2, 3, 3.2, 'e', "ee"); + } + + @Override + protected void createModel() { + model = new A(); + + nativeIntegers = new NativeTypesSyn(); + nativeFloats = new NativeTypesSyn(); + nativeChars = new NativeTypesSyn(); + model.addNativeTypesSyn(nativeIntegers); + model.addNativeTypesSyn(nativeFloats); + model.addNativeTypesSyn(nativeChars); + + boxedIntegers = new BoxedTypesSyn(); + boxedFloats = new BoxedTypesSyn(); + boxedChars = new BoxedTypesSyn(); + model.addBoxedTypesSyn(boxedIntegers); + model.addBoxedTypesSyn(boxedFloats); + model.addBoxedTypesSyn(boxedChars); + + setData("1", "1.1", "ab"); + } + + @Override + protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException { + model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS); + + receiver = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost()); + assertTrue(receiver.waitUntilReady(2, TimeUnit.SECONDS)); + + nativeIntegers.addNativeIntDependency(nativeIntegers); + nativeIntegers.addNativeShortDependency(nativeIntegers); + nativeIntegers.addNativeLongDependency(nativeIntegers); + nativeFloats.addNativeFloatDependency(nativeFloats); + nativeFloats.addNativeDoubleDependency(nativeFloats); + nativeChars.addNativeCharDependency(nativeChars); + nativeChars.addNativeStringDependency(nativeChars); + + boxedIntegers.addBoxedIntDependency(boxedIntegers); + boxedIntegers.addBoxedShortDependency(boxedIntegers); + boxedIntegers.addBoxedLongDependency(boxedIntegers); + boxedFloats.addBoxedFloatDependency(boxedFloats); + boxedFloats.addBoxedDoubleDependency(boxedFloats); + boxedChars.addBoxedCharDependency(boxedChars); + + data = new ReceiverData(); + + receiver.newConnection(TOPIC_NATIVE_INT, bytes -> { + data.numberOfNativeIntValues += 1; + data.lastNativeIntValue = java.nio.ByteBuffer.wrap(bytes).getInt(); + }); + receiver.newConnection(TOPIC_NATIVE_SHORT, bytes -> { + data.numberOfNativeShortValues += 1; + data.lastNativeShortValue = java.nio.ByteBuffer.wrap(bytes).getShort(); + }); + receiver.newConnection(TOPIC_NATIVE_LONG, bytes -> { + data.numberOfNativeLongValues += 1; + data.lastNativeLongValue = java.nio.ByteBuffer.wrap(bytes).getLong(); + }); + receiver.newConnection(TOPIC_NATIVE_FLOAT, bytes -> { + data.numberOfNativeFloatValues += 1; + data.lastNativeFloatValue = java.nio.ByteBuffer.wrap(bytes).getFloat(); + }); + receiver.newConnection(TOPIC_NATIVE_DOUBLE, bytes -> { + data.numberOfNativeDoubleValues += 1; + data.lastNativeDoubleValue = java.nio.ByteBuffer.wrap(bytes).getDouble(); + }); + receiver.newConnection(TOPIC_NATIVE_CHAR, bytes -> { + data.numberOfNativeCharValues += 1; + data.lastNativeCharValue = java.nio.ByteBuffer.wrap(bytes).getChar(); + }); + receiver.newConnection(TOPIC_NATIVE_STRING, bytes -> { + data.numberOfNativeStringValues += 1; + data.lastNativeStringValue = new String(bytes); + }); + receiver.newConnection(TOPIC_BOXED_INTEGER, bytes -> { + data.numberOfBoxedIntValues += 1; + data.lastBoxedIntValue = java.nio.ByteBuffer.wrap(bytes).getInt(); + }); + receiver.newConnection(TOPIC_BOXED_SHORT, bytes -> { + data.numberOfBoxedShortValues += 1; + data.lastBoxedShortValue = java.nio.ByteBuffer.wrap(bytes).getShort(); + }); + receiver.newConnection(TOPIC_BOXED_LONG, bytes -> { + data.numberOfBoxedLongValues += 1; + data.lastBoxedLongValue = java.nio.ByteBuffer.wrap(bytes).getLong(); + }); + receiver.newConnection(TOPIC_BOXED_FLOAT, bytes -> { + data.numberOfBoxedFloatValues += 1; + data.lastBoxedFloatValue = java.nio.ByteBuffer.wrap(bytes).getFloat(); + }); + receiver.newConnection(TOPIC_BOXED_DOUBLE, bytes -> { + data.numberOfBoxedDoubleValues += 1; + data.lastBoxedDoubleValue = java.nio.ByteBuffer.wrap(bytes).getDouble(); + }); + receiver.newConnection(TOPIC_BOXED_CHARACTER, bytes -> { + data.numberOfBoxedCharValues += 1; + data.lastBoxedCharValue = java.nio.ByteBuffer.wrap(bytes).getChar(); + }); + + nativeIntegers.connectIntValue(mqttUri(TOPIC_NATIVE_INT), writeCurrentValue); + nativeIntegers.connectShortValue(mqttUri(TOPIC_NATIVE_SHORT), writeCurrentValue); + nativeIntegers.connectLongValue(mqttUri(TOPIC_NATIVE_LONG), writeCurrentValue); + nativeFloats.connectFloatValue(mqttUri(TOPIC_NATIVE_FLOAT), writeCurrentValue); + nativeFloats.connectDoubleValue(mqttUri(TOPIC_NATIVE_DOUBLE), writeCurrentValue); + nativeChars.connectCharValue(mqttUri(TOPIC_NATIVE_CHAR), writeCurrentValue); + nativeChars.connectStringValue(mqttUri(TOPIC_NATIVE_STRING), writeCurrentValue); + boxedIntegers.connectIntValue(mqttUri(TOPIC_BOXED_INTEGER), writeCurrentValue); + boxedIntegers.connectShortValue(mqttUri(TOPIC_BOXED_SHORT), writeCurrentValue); + boxedIntegers.connectLongValue(mqttUri(TOPIC_BOXED_LONG), writeCurrentValue); + boxedFloats.connectFloatValue(mqttUri(TOPIC_BOXED_FLOAT), writeCurrentValue); + boxedFloats.connectDoubleValue(mqttUri(TOPIC_BOXED_DOUBLE), writeCurrentValue); + boxedChars.connectCharValue(mqttUri(TOPIC_BOXED_CHARACTER), writeCurrentValue); + } + + private void setData(String integerDriver, String floatDriver, String stringDriver) { + nativeIntegers.setDriverSyn(integerDriver); + nativeFloats.setDriverSyn(floatDriver); + nativeChars.setDriverSyn(stringDriver); + + boxedIntegers.setDriverSyn(integerDriver); + boxedFloats.setDriverSyn(floatDriver); + boxedChars.setDriverSyn(stringDriver); + } + + private void checkData(int expectedNumberOfValues, + Integer expectedInt, Double expectedDouble, + Character expectedChar, String expectedString) { + assertEquals(expectedNumberOfValues, data.numberOfNativeIntValues); + assertEquals(expectedNumberOfValues, data.numberOfNativeShortValues); + assertEquals(expectedNumberOfValues, data.numberOfNativeLongValues); + assertEquals(expectedNumberOfValues, data.numberOfNativeFloatValues); + assertEquals(expectedNumberOfValues, data.numberOfNativeDoubleValues); + assertEquals(expectedNumberOfValues, data.numberOfNativeCharValues); + assertEquals(expectedNumberOfValues, data.numberOfNativeStringValues); + + assertEquals(expectedNumberOfValues, data.numberOfBoxedIntValues); + assertEquals(expectedNumberOfValues, data.numberOfBoxedShortValues); + assertEquals(expectedNumberOfValues, data.numberOfBoxedLongValues); + assertEquals(expectedNumberOfValues, data.numberOfBoxedFloatValues); + assertEquals(expectedNumberOfValues, data.numberOfBoxedDoubleValues); + assertEquals(expectedNumberOfValues, data.numberOfBoxedCharValues); + + if (expectedInt != null) { + assertEquals(expectedInt.intValue(), data.lastNativeIntValue); + assertEquals(expectedInt.shortValue(), data.lastNativeShortValue); + assertEquals(expectedInt.longValue(), data.lastNativeLongValue); + assertEquals(expectedInt.intValue(), data.lastBoxedIntValue.intValue()); + assertEquals(expectedInt.shortValue(), data.lastBoxedShortValue.shortValue()); + assertEquals(expectedInt.longValue(), data.lastBoxedLongValue.longValue()); + } else { + assertEquals(0, data.lastNativeIntValue); + assertEquals(0, data.lastNativeShortValue); + assertEquals(0, data.lastNativeLongValue); + assertNull(data.lastBoxedIntValue); + assertNull(data.lastBoxedShortValue); + assertNull(data.lastBoxedLongValue); + } + + if (expectedDouble != null) { + assertEquals(expectedDouble.floatValue(), data.lastNativeFloatValue, TestUtils.DELTA); + assertEquals(expectedDouble, data.lastNativeDoubleValue, TestUtils.DELTA); + assertEquals(expectedDouble.floatValue(), data.lastBoxedFloatValue, TestUtils.DELTA); + assertEquals(expectedDouble, data.lastBoxedDoubleValue, TestUtils.DELTA); + } else { + assertEquals(0f, data.lastNativeFloatValue, TestUtils.DELTA); + assertEquals(0d, data.lastNativeDoubleValue, TestUtils.DELTA); + assertNull(data.lastBoxedFloatValue); + assertNull(data.lastBoxedDoubleValue); + } + + if (expectedChar != null) { + assertEquals(expectedChar.charValue(), data.lastNativeCharValue); + assertEquals(expectedChar, data.lastBoxedCharValue); + } else { + assertEquals('\0', data.lastNativeCharValue); + assertNull(data.lastBoxedCharValue); + } + assertEquals(expectedString, data.lastNativeStringValue); + } + + private static class ReceiverData { + int lastNativeIntValue; + int numberOfNativeIntValues = 0; + short lastNativeShortValue; + int numberOfNativeShortValues = 0; + long lastNativeLongValue; + int numberOfNativeLongValues = 0; + float lastNativeFloatValue; + int numberOfNativeFloatValues = 0; + double lastNativeDoubleValue; + int numberOfNativeDoubleValues = 0; + char lastNativeCharValue; + int numberOfNativeCharValues = 0; + String lastNativeStringValue; + int numberOfNativeStringValues = 0; + + Integer lastBoxedIntValue; + int numberOfBoxedIntValues = 0; + Short lastBoxedShortValue; + int numberOfBoxedShortValues = 0; + Long lastBoxedLongValue; + int numberOfBoxedLongValues = 0; + Float lastBoxedFloatValue; + int numberOfBoxedFloatValues = 0; + Double lastBoxedDoubleValue; + int numberOfBoxedDoubleValues = 0; + Character lastBoxedCharValue; + int numberOfBoxedCharValues = 0; + } + +} diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java new file mode 100644 index 0000000000000000000000000000000000000000..17d853fba71c2ee46c6c6c7b5424958af0a5f8d2 --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java @@ -0,0 +1,84 @@ +package org.jastadd.ragconnect.tests; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jastadd.ragconnect.compiler.Compiler; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import static org.jastadd.ragconnect.tests.TestUtils.exec; +import static org.jastadd.ragconnect.tests.TestUtils.readFile; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class Errors { + + private static final Logger logger = LogManager.getLogger(Errors.class); + private static final String FILENAME_PATTERN = "$FILENAME"; + private static final String INPUT_DIRECTORY = "./src/test/01-input/errors/"; + private static final String OUTPUT_DIRECTORY = "./src/test/02-after-ragconnect/errors/"; + + @BeforeAll + public static void createOutputDirectory() { + File outputDirectory = new File(OUTPUT_DIRECTORY); + assertTrue((outputDirectory.exists() && outputDirectory.isDirectory()) || outputDirectory.mkdir()); + } + + @Test + void testStandardErrors() throws IOException { + test("Errors", "A"); + } + + @SuppressWarnings("SameParameterValue") + private void test(String name, String rootNode) throws IOException { + String grammarFile = INPUT_DIRECTORY + name + ".relast"; + String ragconnectFile = INPUT_DIRECTORY + name + ".connect"; + String outFile = OUTPUT_DIRECTORY + name + ".out"; + String expectedFile = INPUT_DIRECTORY + name + ".expected"; + + try { + logger.debug("user.dir: {}", System.getProperty("user.dir")); + String[] args = { + "--o=" + OUTPUT_DIRECTORY, + grammarFile, + ragconnectFile, + "--rootNode=" + rootNode, + "--verbose" + }; + int returnValue = exec(Compiler.class, args, new File(outFile)); + Assertions.assertEquals(1, returnValue, "RagConnect did not return with value 1"); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + + String out = readFile(outFile, Charset.defaultCharset()); + String expected = readFile(expectedFile, Charset.defaultCharset()); +// if (inFiles.size() == 1) { + expected = expected.replace(FILENAME_PATTERN, name); +// } else { +// for (int i = 0; i < inFiles.size(); i++) { +// expected = expected.replace(FILENAME_PATTERN + (i + 1), inFiles.get(i)); +// } +// } + List<String> outList = Arrays.asList(out.split("\n")); + Collections.sort(outList); + List<String> expectedList = Arrays.stream(expected.split("\n")) + .sorted() + .filter(s -> !s.isEmpty() && !s.startsWith("//")) + .collect(Collectors.toList()); + + // FIXME errors not handled correctly at the moment +// Assertions.assertLinesMatch(expectedList, outList); + + logger.info("ragconnect for " + name + " returned:\n{}", out); + } + +} diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ExampleTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ExampleTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c3df78d1b17658a1327afa0f4e2f308eb5ee159b --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ExampleTest.java @@ -0,0 +1,278 @@ +package org.jastadd.ragconnect.tests; + +import com.google.protobuf.InvalidProtocolBufferException; +import config.Config.RobotConfig; +import example.ast.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import robot.RobotStateOuterClass.RobotState; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import static org.jastadd.ragconnect.tests.TestUtils.mqttUri; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Test case "example". + * + * @author rschoene - Initial contribution + */ +public class ExampleTest extends AbstractMqttTest { + + private static final String TOPIC_CONFIG = "robot/config"; + private static final String TOPIC_JOINT1 = "robot/arm/joint1"; + private static final String TOPIC_JOINT2 = "robot/arm/joint2"; + + private Model model; + private RobotArm robotArm; + private Link link1; + private Link link2; + private MqttHandler handler; + private ReceiverData data; + + @BeforeEach + public void resetTestCounter() { + TestCounter.reset(); + } + + @Override + public void closeConnections() { + if (handler != null) { + handler.close(); + } + if (model != null) { + model.ragconnectCloseConnections(); + } + } + + + + @Override + protected void communicateSendInitialValue() throws InterruptedException { + // joint is currently within the safety zone, so speed should be low + TestUtils.waitForMqtt(); + assertEquals(0, TestCounter.INSTANCE.numberParseLinkState); + assertEquals(0, TestCounter.INSTANCE.numberLinkStateToIntPosition); + assertEquals(1, TestCounter.INSTANCE.numberInSafetyZone); + assertEquals(1, TestCounter.INSTANCE.numberCreateSpeedMessage); + assertEquals(1, TestCounter.INSTANCE.numberSerializeRobotConfig); + assertEquals(1, data.numberOfConfigs); + assertFalse(data.failedLastConversion); + assertEquals(robotArm.speedLow(), data.lastConfig.getSpeed(), TestUtils.DELTA); + + // change position of the first joint out of the safety zone, second still in + sendData(TOPIC_JOINT1, 0.2f, 0.2f, 0.2f); + + // still in safety zone, so no update should have been sent + TestUtils.waitForMqtt(); + assertEquals(makePosition(2, 2, 2), link1.getCurrentPosition()); + assertEquals(1, TestCounter.INSTANCE.numberParseLinkState); + assertEquals(1, TestCounter.INSTANCE.numberLinkStateToIntPosition); + assertEquals(2, TestCounter.INSTANCE.numberInSafetyZone); + assertEquals(2, TestCounter.INSTANCE.numberCreateSpeedMessage); + assertEquals(2, TestCounter.INSTANCE.numberSerializeRobotConfig); + assertEquals(1, data.numberOfConfigs); + assertFalse(data.failedLastConversion); + assertEquals(robotArm.speedLow(), data.lastConfig.getSpeed(), TestUtils.DELTA); + + // change position of second joint also out of the safety zone, now speed must be high + sendData(TOPIC_JOINT2, 0.3f, 0.4f, 0.5f); + + TestUtils.waitForMqtt(); + assertEquals(makePosition(3, 4, 5), link2.getCurrentPosition()); + assertEquals(2, TestCounter.INSTANCE.numberParseLinkState); + assertEquals(2, TestCounter.INSTANCE.numberLinkStateToIntPosition); + assertEquals(3, TestCounter.INSTANCE.numberInSafetyZone); + assertEquals(3, TestCounter.INSTANCE.numberCreateSpeedMessage); + assertEquals(3, TestCounter.INSTANCE.numberSerializeRobotConfig); + assertEquals(2, data.numberOfConfigs); + assertFalse(data.failedLastConversion); + assertEquals(robotArm.speedHigh(), data.lastConfig.getSpeed(), TestUtils.DELTA); + + // change position of second joint, no change after mapping + sendData(TOPIC_JOINT2, 0.33f, 0.42f, 0.51f); + + TestUtils.waitForMqtt(); + assertEquals(makePosition(3, 4, 5), link2.getCurrentPosition()); + assertEquals(3, TestCounter.INSTANCE.numberParseLinkState); + assertEquals(3, TestCounter.INSTANCE.numberLinkStateToIntPosition); + assertEquals(3, TestCounter.INSTANCE.numberInSafetyZone); + assertEquals(3, TestCounter.INSTANCE.numberCreateSpeedMessage); + assertEquals(3, TestCounter.INSTANCE.numberSerializeRobotConfig); + assertEquals(2, data.numberOfConfigs); + assertFalse(data.failedLastConversion); + + // change position of second joint, still out of the safety zone, no update should be sent + sendData(TOPIC_JOINT2, 1.3f, 2.4f, 3.5f); + + TestUtils.waitForMqtt(); + assertEquals(makePosition(13, 24, 35), link2.getCurrentPosition()); + assertEquals(4, TestCounter.INSTANCE.numberParseLinkState); + assertEquals(4, TestCounter.INSTANCE.numberLinkStateToIntPosition); + assertEquals(4, TestCounter.INSTANCE.numberInSafetyZone); + assertEquals(4, TestCounter.INSTANCE.numberCreateSpeedMessage); + assertEquals(4, TestCounter.INSTANCE.numberSerializeRobotConfig); + assertEquals(2, data.numberOfConfigs); + assertFalse(data.failedLastConversion); + } + + @Override + protected void communicateOnlyUpdatedValue() throws InterruptedException { +// no value should have been sent + TestUtils.waitForMqtt(); + assertEquals(0, TestCounter.INSTANCE.numberParseLinkState); + assertEquals(0, TestCounter.INSTANCE.numberLinkStateToIntPosition); + assertEquals(1, TestCounter.INSTANCE.numberInSafetyZone); + assertEquals(1, TestCounter.INSTANCE.numberCreateSpeedMessage); + assertEquals(1, TestCounter.INSTANCE.numberSerializeRobotConfig); + assertEquals(0, data.numberOfConfigs); + + // change position of the first joint out of the safety zone, second still in + sendData(TOPIC_JOINT1, 0.2f, 0.2f, 0.2f); + + // still in safety zone, hence, no value should have been sent + TestUtils.waitForMqtt(); + assertEquals(makePosition(2, 2, 2), link1.getCurrentPosition()); + assertEquals(1, TestCounter.INSTANCE.numberParseLinkState); + assertEquals(1, TestCounter.INSTANCE.numberLinkStateToIntPosition); + assertEquals(2, TestCounter.INSTANCE.numberInSafetyZone); + assertEquals(2, TestCounter.INSTANCE.numberCreateSpeedMessage); + assertEquals(2, TestCounter.INSTANCE.numberSerializeRobotConfig); + assertEquals(0, data.numberOfConfigs); + + // change position of second joint also out of the safety zone, now speed must be high + sendData(TOPIC_JOINT2, 0.3f, 0.4f, 0.5f); + + TestUtils.waitForMqtt(); + assertEquals(makePosition(3, 4, 5), link2.getCurrentPosition()); + assertEquals(2, TestCounter.INSTANCE.numberParseLinkState); + assertEquals(2, TestCounter.INSTANCE.numberLinkStateToIntPosition); + assertEquals(3, TestCounter.INSTANCE.numberInSafetyZone); + assertEquals(3, TestCounter.INSTANCE.numberCreateSpeedMessage); + assertEquals(3, TestCounter.INSTANCE.numberSerializeRobotConfig); + assertEquals(1, data.numberOfConfigs); + assertFalse(data.failedLastConversion); + assertEquals(robotArm.speedHigh(), data.lastConfig.getSpeed(), TestUtils.DELTA); + + // change position of second joint, no change after mapping + sendData(TOPIC_JOINT2, 0.33f, 0.42f, 0.51f); + + TestUtils.waitForMqtt(); + assertEquals(makePosition(3, 4, 5), link2.getCurrentPosition()); + assertEquals(3, TestCounter.INSTANCE.numberParseLinkState); + assertEquals(3, TestCounter.INSTANCE.numberLinkStateToIntPosition); + assertEquals(3, TestCounter.INSTANCE.numberInSafetyZone); + assertEquals(3, TestCounter.INSTANCE.numberCreateSpeedMessage); + assertEquals(3, TestCounter.INSTANCE.numberSerializeRobotConfig); + assertEquals(1, data.numberOfConfigs); + assertFalse(data.failedLastConversion); + + // change position of second joint, still out of the safety zone, no update should be sent + sendData(TOPIC_JOINT2, 1.3f, 2.4f, 3.5f); + + TestUtils.waitForMqtt(); + assertEquals(makePosition(13, 24, 35), link2.getCurrentPosition()); + assertEquals(4, TestCounter.INSTANCE.numberParseLinkState); + assertEquals(4, TestCounter.INSTANCE.numberLinkStateToIntPosition); + assertEquals(4, TestCounter.INSTANCE.numberInSafetyZone); + assertEquals(4, TestCounter.INSTANCE.numberCreateSpeedMessage); + assertEquals(4, TestCounter.INSTANCE.numberSerializeRobotConfig); + assertEquals(1, data.numberOfConfigs); + assertFalse(data.failedLastConversion); + } + + @Test + public void testFailedConversion() throws IOException { + createModel(); + setupReceiverAndConnect(false); + + handler.publish(TOPIC_JOINT1, "not-a-pandaLinkState".getBytes()); + assertEquals(0, data.numberOfConfigs); + assertTrue(data.failedLastConversion); + } + + private void sendData(String topic, float x, float y, float z) { + handler.publish(topic, RobotState.newBuilder() + .setPosition(RobotState.Position.newBuilder().setX(x).setY(y).setZ(z).build()) + .build() + .toByteArray() + ); + } + + @Override + protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException { + model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS); + + handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost()); + assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS)); + + // add dependencies + robotArm.addDependency1(link1); + robotArm.addDependency1(link2); + robotArm.addDependency1(robotArm.getEndEffector()); + + data = new ReceiverData(); + + handler.newConnection(TOPIC_CONFIG, bytes -> { + data.numberOfConfigs += 1; + try { + data.lastConfig = RobotConfig.parseFrom(bytes); + data.failedLastConversion = false; + } catch (InvalidProtocolBufferException e) { + data.failedLastConversion = true; + } + }); + + robotArm.connectAppropriateSpeed(mqttUri(TOPIC_CONFIG), writeCurrentValue); + link1.connectCurrentPosition(mqttUri(TOPIC_JOINT1)); + link2.connectCurrentPosition(mqttUri(TOPIC_JOINT2)); + } + + @Override + protected void createModel() { + model = new Model(); + + ZoneModel zoneModel = new ZoneModel(); + + IntPosition firstPosition = makePosition(0, 0, 0); + IntPosition secondPosition = makePosition(-1, 0, 0); + IntPosition thirdPosition = makePosition(1, 0, 0); + + Zone safetyZone = new Zone(); + safetyZone.addCoordinate(new Coordinate(firstPosition)); + safetyZone.addCoordinate(new Coordinate(secondPosition)); + safetyZone.addCoordinate(new Coordinate(thirdPosition)); + zoneModel.addSafetyZone(safetyZone); + model.setZoneModel(zoneModel); + + robotArm = new RobotArm(); + + link1 = new Link(); + link1.setName("joint1"); + link1.setCurrentPosition(firstPosition); + + link2 = new Link(); + link2.setName("joint2"); + link2.setCurrentPosition(secondPosition); + + EndEffector endEffector = new EndEffector(); + endEffector.setName("gripper"); + endEffector.setCurrentPosition(makePosition(2, 2, 3)); + + robotArm.addLink(link1); + robotArm.addLink(link2); + robotArm.setEndEffector(endEffector); + model.setRobotArm(robotArm); + } + + private static IntPosition makePosition(int x, int y, int z) { + return IntPosition.of(x, y, z); + } + + private static class ReceiverData { + RobotConfig lastConfig; + boolean failedLastConversion = true; + int numberOfConfigs = 0; + } +} diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read1Write2Test.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read1Write2Test.java new file mode 100644 index 0000000000000000000000000000000000000000..2940aacbf26ae1a6e63cd7c763868d6f48639217 --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read1Write2Test.java @@ -0,0 +1,241 @@ +package org.jastadd.ragconnect.tests; + +import read1write2.ast.*; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import static org.jastadd.ragconnect.tests.TestUtils.mqttUri; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * 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 MqttHandler 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; + + @Override + public void closeConnections() { + if (handler != null) { + handler.close(); + } + if (model != null) { + model.ragconnectCloseConnections(); + } + } + + + + @Override + protected void communicateSendInitialValue() throws InterruptedException { + // check initial value + TestUtils.waitForMqtt(); + 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 + TestUtils.waitForMqtt(); + checkData(2, 2, prefixed("2"), 2, 3, prefixed("3")); + + // set new value + sendData("4", "4"); + + // check new value + TestUtils.waitForMqtt(); + checkData(3, 4, prefixed("4"), 3, 4, prefixed("4")); + + // set new value only for same + setDataOnlySame("77"); + + // check new value + TestUtils.waitForMqtt(); + checkData(4, 77, prefixed("77"), 3, 4, prefixed("4")); + } + + private String prefixed(String s) { + return "prefix" + s; + } + + @Override + protected void communicateOnlyUpdatedValue() throws InterruptedException { +// check initial value + TestUtils.waitForMqtt(); + checkData(0, null, null, 0, null, null); + + // set new value + sendData("2", "3"); + + // check new value + TestUtils.waitForMqtt(); + checkData(1, 2, prefixed("2"), 1, 3, prefixed("3")); + + // set new value + sendData("4", "4"); + + // check new value + TestUtils.waitForMqtt(); + checkData(2, 4, prefixed("4"), 2, 4, prefixed("4")); + + // set new value only for same + setDataOnlySame("77"); + + // check new value + TestUtils.waitForMqtt(); + checkData(3, 77, prefixed("77"), 2, 4, prefixed("4")); + } + + @Override + protected 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); + } + + @Override + protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException { + model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS); + + handler = new MqttHandler().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(mqttUri(TOPIC_SAME_READ)); + onSameNonterminal.connectOutInteger(mqttUri(TOPIC_SAME_WRITE_INT), writeCurrentValue); + onSameNonterminal.connectOutString(mqttUri(TOPIC_SAME_WRITE_STRING), writeCurrentValue); + + onDifferentNonterminal.connectInput(mqttUri(TOPIC_DIFFERENT_READ)); + other1.connectOutInteger(mqttUri(TOPIC_DIFFERENT_WRITE1_INT), writeCurrentValue); + other1.connectOutString(mqttUri(TOPIC_DIFFERENT_WRITE1_STRING), writeCurrentValue); + other2.connectOutInteger(mqttUri(TOPIC_DIFFERENT_WRITE2_INT), writeCurrentValue); + other2.connectOutString(mqttUri(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; + } + } + +} diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read2Write1Test.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read2Write1Test.java new file mode 100644 index 0000000000000000000000000000000000000000..4790b173bab7cab2d6022e35c40125af7b79ed5f --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read2Write1Test.java @@ -0,0 +1,232 @@ +package org.jastadd.ragconnect.tests; + +import read2write1.ast.*; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import static org.jastadd.ragconnect.tests.TestUtils.mqttUri; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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 MqttHandler 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; + + @Override + public void closeConnections() { + if (handler != null) { + handler.close(); + } + if (model != null) { + model.ragconnectCloseConnections(); + } + } + + + + @Override + protected void communicateSendInitialValue() throws InterruptedException { + // check initial value + TestUtils.waitForMqtt(); + 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. + TestUtils.waitForMqtt(); + checkData(2, 20, + 2, 30); + + // set new value + sendData(false, "4", false, "4"); + + // check new value. same: 2, 4. different: 3, 4. + TestUtils.waitForMqtt(); + checkData(3, 24, + 3, 34); + + // set new value only for same + setDataOnlySame(true, "77"); + + // check new value. same: 77, 4. different: 3, 4. + TestUtils.waitForMqtt(); + checkData(4, 774, + 3, 34); + } + + private String prefixed(String s) { + return "prefix" + s; + } + + @Override + protected void communicateOnlyUpdatedValue() throws InterruptedException { + // check initial value + TestUtils.waitForMqtt(); + checkData(0, null, + 0, null); + + // set new value + sendData(true, "2", true, "3"); + + // check new value. same: 2, 0. different: 3, 0. + TestUtils.waitForMqtt(); + checkData(1, 20, + 1, 30); + + // set new value + sendData(false, "4", false, "4"); + + // check new value. same: 2, 4. different: 3, 4. + TestUtils.waitForMqtt(); + checkData(2, 24, + 2, 34); + + // set new value only for same + setDataOnlySame(true, "77"); + + // check new value. same: 77, 4. different: 3, 4. + TestUtils.waitForMqtt(); + checkData(3, 774, + 2, 34); + } + + @Override + protected 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); + } + + @Override + protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException { + model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS); + + handler = new MqttHandler().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(mqttUri(TOPIC_SAME_READ1)); + onSameNonterminal.connectInput2(mqttUri(TOPIC_SAME_READ2)); + onSameNonterminal.connectOutInteger(mqttUri(TOPIC_SAME_WRITE_INT), writeCurrentValue); + + onDifferentNonterminal.connectInput1(mqttUri(TOPIC_DIFFERENT_READ1)); + onDifferentNonterminal.connectInput2(mqttUri(TOPIC_DIFFERENT_READ2)); + other1.connectOutInteger(mqttUri(TOPIC_DIFFERENT_WRITE1_INT), writeCurrentValue); + other2.connectOutInteger(mqttUri(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; + } + } + +} diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..143c74e984af40f9acf883ed7be2f3eb8adb253a --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java @@ -0,0 +1,73 @@ +package org.jastadd.ragconnect.tests; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.concurrent.TimeUnit; + +/** + * Utility methods for tests. + * + * @author rschoene - Initial contribution + */ +public class TestUtils { + + public static final double DELTA = 0.001d; + + public static String getMqttHost() { + if (System.getenv("GITLAB_CI") != null) { + // we are in the CI, so use "mqtt" as host + return "mqtt"; + } { + // else assume a locally running mqtt broker + return "localhost"; + } + } + + public static String mqttUri(String path) { + return "mqtt://" + getMqttHost() + "/" + path; + } + + public static String restUri(String path, int port) { + return "rest://localhost:" + port + "/" + path; + } + + public static int getMqttDefaultPort() { + return 1883; + } + + public static int exec(Class<?> klass, String[] args, File err) throws IOException, + InterruptedException { + String javaHome = System.getProperty("java.home"); + String javaBin = javaHome + File.separator + "bin" + File.separator + "java"; + String classpath = System.getProperty("java.class.path"); + String className = klass.getName(); + + String[] newArgs = new String[args.length + 4]; + newArgs[0] = javaBin; + newArgs[1] = "-cp"; + newArgs[2] = classpath; + newArgs[3] = className; + System.arraycopy(args, 0, newArgs, 4, args.length); + + ProcessBuilder builder = new ProcessBuilder(newArgs); +// builder.redirectOutput(err); + builder.redirectError(err); + + Process process = builder.start(); + process.waitFor(); + return process.exitValue(); + } + + public static String readFile(String path, Charset encoding) + throws IOException { + byte[] encoded = Files.readAllBytes(Paths.get(path)); + return new String(encoded, encoding); + } + + static void waitForMqtt() throws InterruptedException { + TimeUnit.SECONDS.sleep(2); + } +} diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TokenValueSendTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TokenValueSendTest.java new file mode 100644 index 0000000000000000000000000000000000000000..bc68f5bdb8b19167deb50fa54b52e933235bceb3 --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TokenValueSendTest.java @@ -0,0 +1,260 @@ +package org.jastadd.ragconnect.tests; + +import tokenValueSend.ast.*; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import static org.jastadd.ragconnect.tests.TestUtils.mqttUri; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test case "tokenValueSend". + * + * @author rschoene - Initial contribution + */ +public class TokenValueSendTest extends AbstractMqttTest { + + private static final String TOPIC_SEND_ONE = "one/value/out"; + + private static final String TOPIC_RECEIVE_TWO = "two/value/in"; + private static final String TOPIC_SEND_TWO = "two/value/out"; + + private static final String TOPIC_RECEIVE_THREE_VALUE = "three/value/in"; + private static final String TOPIC_SEND_THREE_VALUE = "three/value/out"; + private static final String TOPIC_SEND_THREE_OTHER = "three/other/out"; + + private static final String INITIAL_VALUE = "Start"; + + private MqttHandler handler; + private A model; + private OnlySend one; + private ReceiveAndSend two; + private ReceiveSendAndDepend three; + + private ReceiverData dataOne; + private ReceiverData dataTwo; + private ReceiverData dataThree; + private ReceiverData dataThreeOther; + + @Override + public void closeConnections() { + if (handler != null) { + handler.close(); + } + if (model != null) { + model.ragconnectCloseConnections(); + } + } + + @Override + protected void communicateSendInitialValue() throws InterruptedException { + // check initial value + TestUtils.waitForMqtt(); + checkData(1, "Start-Post", + 1, "Start-Post", + 1, "Start-Post", + 1, "Start-T-Post"); + + // send new value + sendData("200", "300"); + + // check new value + TestUtils.waitForMqtt(); + checkData(1, "Start-Post", + 2, "Pre-200-Post", + 2, "Pre-300-Post", + 2, "Pre-300-T-Post"); + + // set new value + setData("101", "201", "301"); + + // check new value + TestUtils.waitForMqtt(); + checkData(2, "101-Post", + 3, "201-Post", + 3, "301-Post", + 3, "301-T-Post"); + + // send the same values (will not be sent again) + setData("101", "201", "301"); + + // check new value + TestUtils.waitForMqtt(); + checkData(2, "101-Post", + 3, "201-Post", + 3, "301-Post", + 3, "301-T-Post"); + + // send values with prefixes imitating receiving + setData("102", "Pre-202", "Pre-302"); + + // check new value + TestUtils.waitForMqtt(); + checkData(3, "102-Post", + 4, "Pre-202-Post", + 4, "Pre-302-Post", + 4, "Pre-302-T-Post"); + + // send the same values (will not be sent again, because previously prefixed) + sendData("202", "302"); + + // check new value + TestUtils.waitForMqtt(); + checkData(3, "102-Post", + 4, "Pre-202-Post", + 4, "Pre-302-Post", + 4, "Pre-302-T-Post"); + } + + @Override + protected void communicateOnlyUpdatedValue() throws InterruptedException { + // check initial value + TestUtils.waitForMqtt(); + checkData(0, null, + 0, null, + 0, null, + 0, null); + + // send new value + sendData("210", "310"); + + // check new value + TestUtils.waitForMqtt(); + checkData(0, null, + 1, "Pre-210-Post", + 1, "Pre-310-Post", + 1, "Pre-310-T-Post"); + + // set new value + setData("111", "211", "311"); + + // check new value + TestUtils.waitForMqtt(); + checkData(1, "111-Post", + 2, "211-Post", + 2, "311-Post", + 2, "311-T-Post"); + + // send the same values (will not be sent again) + setData("111", "211", "311"); + + // check new value + TestUtils.waitForMqtt(); + checkData(1, "111-Post", + 2, "211-Post", + 2, "311-Post", + 2, "311-T-Post"); + + // send values with prefixes imitating receiving + setData("112", "Pre-212", "Pre-312"); + + // check new value + TestUtils.waitForMqtt(); + checkData(2, "112-Post", + 3, "Pre-212-Post", + 3, "Pre-312-Post", + 3, "Pre-312-T-Post"); + + // send the same values (will not be sent again, because previously prefixed) + sendData("212", "312"); + + // check new value + TestUtils.waitForMqtt(); + checkData(2, "112-Post", + 3, "Pre-212-Post", + 3, "Pre-312-Post", + 3, "Pre-312-T-Post"); + } + + @Override + protected void createModel() { + // Setting value for Input without dependencies does not trigger any updates + model = new A(); + + one = new OnlySend(); + one.setValue(INITIAL_VALUE); + model.setOnlySend(one); + + two = new ReceiveAndSend(); + two.setValue(INITIAL_VALUE); + model.setReceiveAndSend(two); + + three = new ReceiveSendAndDepend(); + three.setValue(INITIAL_VALUE); + model.setReceiveSendAndDepend(three); + } + + protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException { + model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS); + + handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost()); + assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS)); + + three.addDependency1(three); + + dataOne = new ReceiverData(); + dataTwo = new ReceiverData(); + dataThree = new ReceiverData(); + dataThreeOther = new ReceiverData(); + + handler.newConnection(TOPIC_SEND_ONE, bytes -> { + dataOne.numberOfStringValues += 1; + dataOne.lastStringValue = new String(bytes); + }); + handler.newConnection(TOPIC_SEND_TWO, bytes -> { + dataTwo.numberOfStringValues += 1; + dataTwo.lastStringValue = new String(bytes); + }); + + handler.newConnection(TOPIC_SEND_THREE_VALUE, bytes -> { + dataThree.numberOfStringValues += 1; + dataThree.lastStringValue = new String(bytes); + }); + handler.newConnection(TOPIC_SEND_THREE_OTHER, bytes -> { + dataThreeOther.numberOfStringValues += 1; + dataThreeOther.lastStringValue = new String(bytes); + }); + + one.connectValue(mqttUri(TOPIC_SEND_ONE), writeCurrentValue); + two.connectValue(mqttUri(TOPIC_RECEIVE_TWO)); + two.connectValue(mqttUri(TOPIC_SEND_TWO), writeCurrentValue); + three.connectValue(mqttUri(TOPIC_RECEIVE_THREE_VALUE)); + three.connectValue(mqttUri(TOPIC_SEND_THREE_VALUE), writeCurrentValue); + three.connectOtherOutput(mqttUri(TOPIC_SEND_THREE_OTHER), writeCurrentValue); + } + + private void sendData(String inputTwo, String inputThree) { + handler.publish(TOPIC_RECEIVE_TWO, inputTwo.getBytes()); + handler.publish(TOPIC_RECEIVE_THREE_VALUE, inputThree.getBytes()); + } + + private void setData(String inputOne, String inputTwo, String inputThree) { + one.setValue(inputOne); + two.setValue(inputTwo); + three.setValue(inputThree); + } + + private void checkData(int numberOfOneValues, String lastOneStringValue, + int numberOfTwoValues, String lastTwoStringValue, + int numberOfThreeValues, String lastThreeStringValue, + int numberOfOtherValues, String lastOtherStringValue + ) { + dataOne.assertEqualData(numberOfOneValues, lastOneStringValue); + dataTwo.assertEqualData(numberOfTwoValues, lastTwoStringValue); + dataThree.assertEqualData(numberOfThreeValues, lastThreeStringValue); + dataThreeOther.assertEqualData(numberOfOtherValues, lastOtherStringValue); + } + + private static class ReceiverData { + String lastStringValue; + int numberOfStringValues = 0; + + public void assertEqualData(int expectedNumberOfValues, String expectedLastValue) { + assertEquals(expectedNumberOfValues, this.numberOfStringValues); + assertEquals(expectedLastValue, this.lastStringValue); + } + } + +} diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TutorialTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TutorialTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d7f346252639630d5818af4ceeef314b7ccecd9e --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TutorialTest.java @@ -0,0 +1,68 @@ +package org.jastadd.ragconnect.tests; + +import tutorial.ast.A; +import tutorial.ast.B; +import tutorial.ast.MqttHandler; + +import java.io.IOException; + +import static org.jastadd.ragconnect.tests.TestUtils.mqttUri; + +/** + * Testcase "Tutorial". + * + * @author rschoene - Initial contribution + */ +public class TutorialTest extends AbstractMqttTest { + + private MqttHandler handler; + private A a; + private B b1; + private B b2; + + @Override + protected void closeConnections() { + if (handler != null) { + handler.close(); + } + if (a != null) { + a.ragconnectCloseConnections(); + } + } + + @Override + protected void createModel() { + a = new A(); + // set some default value for input + a.setInput(""); + b1 = new B(); + b2 = new B(); + a.addB(b1); + a.addB(b2); + } + + @Override + protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException { + // a.OutputOnA -> a.Input + a.addDependencyA(a); + // b1.OutputOnB -> a.Input + b1.addDependencyB(a); + // b2.OutputOnB -> a.Input + b2.addDependencyB(a); + + a.connectInput(mqttUri("topic/for/input")); + a.connectOutputOnA(mqttUri("a/out"), true); + b1.connectOutputOnB(mqttUri("b1/out"), true); + b2.connectOutputOnB(mqttUri("b2/out"), false); + } + + @Override + protected void communicateSendInitialValue() { + // empty + } + + @Override + protected void communicateOnlyUpdatedValue() { + // empty + } +} diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ViaTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ViaTest.java new file mode 100644 index 0000000000000000000000000000000000000000..3c173522680500703be35e96289f0a6e35117b3a --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ViaTest.java @@ -0,0 +1,324 @@ +package org.jastadd.ragconnect.tests; + +import org.junit.jupiter.api.Tag; +import via.ast.A; +import via.ast.MqttHandler; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.MediaType; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import static org.jastadd.ragconnect.tests.TestUtils.mqttUri; +import static org.jastadd.ragconnect.tests.TestUtils.restUri; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test case "via". + * + * @author rschoene - Initial contribution + */ +@Tag("rest") +public class ViaTest extends AbstractMqttTest { + + private static final int REST_PORT = 9002; + + private static final String TOPIC_MQTT_2_MQTT_RECEIVE = "mqtt2mqtt/receive"; + private static final String PATH_REST_2_REST_RECEIVE = "rest2rest/receive"; + private static final String TOPIC_MQTT_2_REST_RECEIVE = "mqtt2rest/receive"; + private static final String PATH_REST_2_MQTT_RECEIVE = "rest2mqtt/receive"; + private static final String TOPIC_BOTH_MQTT_RECEIVE = "both/send"; + private static final String PATH_BOTH_REST_RECEIVE = "both/send"; + + private static final String TOPIC_MQTT_2_MQTT_SEND = "mqtt2mqtt/send"; + private static final String PATH_REST_2_REST_SEND = "rest2rest/send"; + private static final String PATH_MQTT_2_REST_SEND = "mqtt2rest/send"; + private static final String TOPIC_REST_2_MQTT_SEND = "rest2mqtt/send"; + private static final String TOPIC_BOTH_2_MQTT_SEND = "both2mqtt/send"; + private static final String PATH_BOTH_2_REST_SEND = "both2rest/send"; + + private static final String REST_SERVER_BASE_URL = "http://localhost:" + REST_PORT + "/"; + + private MqttHandler handler; + private A model; + private ReceiverData dataMqtt2Mqtt; + private ReceiverData dataRest2Mqtt; + private WebTarget dataRest2Rest; + private WebTarget dataMqtt2Rest; + private ReceiverData dataBoth2Mqtt; + private WebTarget dataBoth2Rest; + + private WebTarget senderRest2Rest; + private WebTarget senderRest2Mqtt; + private WebTarget senderBoth2Rest; + + @Override + public void closeConnections() { + if (handler != null) { + handler.close(); + } + if (model != null) { + model.ragconnectCloseConnections(); + } + } + + + + @Override + protected void communicateSendInitialValue() throws InterruptedException { + // check initial value + TestUtils.waitForMqtt(); + checkData(1, "100-M2M-ToMqtt", + "200-R2R-ToRest", + "300-M2R-ToRest", + 1, "400-R2M-ToMqtt", + 1, "500-B2M-ToMqtt", + "500-B2R-ToRest"); + + sendData("101", "201", "301", "401"); + sendDataForBoth("501", true); + + // check new value + TestUtils.waitForMqtt(); + checkData(2, "FromMqtt-101-M2M-ToMqtt", + "FromRest-201-R2R-ToRest", + "FromMqtt-301-M2R-ToRest", + 2, "FromRest-401-R2M-ToMqtt", + 2, "501-B2M-ToMqtt", + "501-B2R-ToRest"); + + // send value only for bothInput via REST + sendDataForBoth("502", false); + + // check this value + TestUtils.waitForMqtt(); + checkData(2, "FromMqtt-101-M2M-ToMqtt", + "FromRest-201-R2R-ToRest", + "FromMqtt-301-M2R-ToRest", + 2, "FromRest-401-R2M-ToMqtt", + 3, "502-B2M-ToMqtt", + "502-B2R-ToRest"); + + // send same value only for bothInput via MQTT + sendDataForBoth("502", true); + + // check this value + TestUtils.waitForMqtt(); + checkData(2, "FromMqtt-101-M2M-ToMqtt", + "FromRest-201-R2R-ToRest", + "FromMqtt-301-M2R-ToRest", + 2, "FromRest-401-R2M-ToMqtt", + 3, "502-B2M-ToMqtt", + "502-B2R-ToRest"); + + // send values for other things + sendData("102", "202", "302", "402"); + + // check this value + TestUtils.waitForMqtt(); + checkData(3, "FromMqtt-102-M2M-ToMqtt", + "FromRest-202-R2R-ToRest", + "FromMqtt-302-M2R-ToRest", + 3, "FromRest-402-R2M-ToMqtt", + 3, "502-B2M-ToMqtt", + "502-B2R-ToRest"); + + // send same values again for other things + sendData("102", "202", "302", "402"); + + // check this value + TestUtils.waitForMqtt(); + checkData(3, "FromMqtt-102-M2M-ToMqtt", + "FromRest-202-R2R-ToRest", + "FromMqtt-302-M2R-ToRest", + 3, "FromRest-402-R2M-ToMqtt", + 3, "502-B2M-ToMqtt", + "502-B2R-ToRest"); + } + + @Override + protected void communicateOnlyUpdatedValue() throws InterruptedException { +// check initial value + TestUtils.waitForMqtt(); + checkData(0, null, + "200-R2R-ToRest", + "300-M2R-ToRest", + 0, null, + 0, null, + "500-B2R-ToRest"); + + sendData("111", "211", "311", "411"); + sendDataForBoth("511", true); + + // check new value + TestUtils.waitForMqtt(); + checkData(1, "FromMqtt-111-M2M-ToMqtt", + "FromRest-211-R2R-ToRest", + "FromMqtt-311-M2R-ToRest", + 1, "FromRest-411-R2M-ToMqtt", + 1, "511-B2M-ToMqtt", + "511-B2R-ToRest"); + + // send value only for bothInput via REST + sendDataForBoth("512", false); + + // check this value + TestUtils.waitForMqtt(); + checkData(1, "FromMqtt-111-M2M-ToMqtt", + "FromRest-211-R2R-ToRest", + "FromMqtt-311-M2R-ToRest", + 1, "FromRest-411-R2M-ToMqtt", + 2, "512-B2M-ToMqtt", + "512-B2R-ToRest"); + + // send same value only for bothInput via MQTT + sendDataForBoth("512", true); + + // check this value + TestUtils.waitForMqtt(); + checkData(1, "FromMqtt-111-M2M-ToMqtt", + "FromRest-211-R2R-ToRest", + "FromMqtt-311-M2R-ToRest", + 1, "FromRest-411-R2M-ToMqtt", + 2, "512-B2M-ToMqtt", + "512-B2R-ToRest"); + + // send values for other things + sendData("112", "212", "312", "412"); + + // check this value + TestUtils.waitForMqtt(); + checkData(2, "FromMqtt-112-M2M-ToMqtt", + "FromRest-212-R2R-ToRest", + "FromMqtt-312-M2R-ToRest", + 2, "FromRest-412-R2M-ToMqtt", + 2, "512-B2M-ToMqtt", + "512-B2R-ToRest"); + + // send same values again for other things + sendData("112", "212", "312", "412"); + + // check this value + TestUtils.waitForMqtt(); + checkData(2, "FromMqtt-112-M2M-ToMqtt", + "FromRest-212-R2R-ToRest", + "FromMqtt-312-M2R-ToRest", + 2, "FromRest-412-R2M-ToMqtt", + 2, "512-B2M-ToMqtt", + "512-B2R-ToRest"); + } + + private void sendData(String inputMqtt2Mqtt, String inputRest2Rest, String inputMqtt2Rest, String inputRest2Mqtt) { + handler.publish(TOPIC_MQTT_2_MQTT_RECEIVE, inputMqtt2Mqtt.getBytes()); + senderRest2Rest.request().put(Entity.entity(inputRest2Rest, MediaType.TEXT_PLAIN_TYPE)); + handler.publish(TOPIC_MQTT_2_REST_RECEIVE, inputMqtt2Rest.getBytes()); + senderRest2Mqtt.request().put(Entity.entity(inputRest2Mqtt, MediaType.TEXT_PLAIN_TYPE)); + } + + private void sendDataForBoth(String input, boolean useMqtt) { + if (useMqtt) { + handler.publish(TOPIC_BOTH_MQTT_RECEIVE, input.getBytes()); + } else { + senderBoth2Rest.request().put(Entity.entity(input, MediaType.TEXT_PLAIN_TYPE)); + } + } + + private void checkData(int numberOfMqtt2MqttValues, String mqtt2MqttValue, String rest2RestValue, String mqtt2RestValue, int numberOfRest2MqttValues, String rest2MqttValue, int numberOfBoth2MqttValues, String both2MqttValue, String both2RestValue) { + dataMqtt2Mqtt.assertEqualData(numberOfMqtt2MqttValues, mqtt2MqttValue); + dataRest2Mqtt.assertEqualData(numberOfRest2MqttValues, rest2MqttValue); + dataBoth2Mqtt.assertEqualData(numberOfBoth2MqttValues, both2MqttValue); + assertEquals(rest2RestValue, readRest2Rest()); + assertEquals(mqtt2RestValue, readMqtt2Rest()); + assertEquals(both2RestValue, readBoth2Rest()); + } + + private String readRest2Rest() { + return dataRest2Rest.request().get().readEntity(String.class); + } + + private String readMqtt2Rest() { + return dataMqtt2Rest.request().get().readEntity(String.class); + } + + private String readBoth2Rest() { + return dataBoth2Rest.request().get().readEntity(String.class); + } + + @Override + protected void createModel() { + // Setting value for Input without dependencies does not trigger any updates + model = new A(); + model.setMqtt2MqttInput("100"); + model.setRest2RestInput("200"); + model.setMqtt2RestInput("300"); + model.setRest2MqttInput("400"); + model.setBoth2BothInput("500"); + } + + @Override + protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException { + model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS); + + handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost()); + assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS)); + + model.addDependencyMqtt2Mqtt(model); + model.addDependencyRest2Rest(model); + model.addDependencyMqtt2Rest(model); + model.addDependencyRest2Mqtt(model); + model.addDependencyBoth2Mqtt(model); + model.addDependencyBoth2Rest(model); + + dataMqtt2Mqtt = new ReceiverData(); + dataRest2Mqtt = new ReceiverData(); + dataBoth2Mqtt = new ReceiverData(); + + handler.newConnection(TOPIC_MQTT_2_MQTT_SEND, bytes -> { + dataMqtt2Mqtt.numberOfStringValues += 1; + dataMqtt2Mqtt.lastStringValue = new String(bytes); + }); + handler.newConnection(TOPIC_REST_2_MQTT_SEND, bytes -> { + dataRest2Mqtt.numberOfStringValues += 1; + dataRest2Mqtt.lastStringValue = new String(bytes); + }); + handler.newConnection(TOPIC_BOTH_2_MQTT_SEND, bytes -> { + dataBoth2Mqtt.numberOfStringValues += 1; + dataBoth2Mqtt.lastStringValue = new String(bytes); + }); + + Client client = ClientBuilder.newClient(); + dataRest2Rest = client.target(REST_SERVER_BASE_URL + PATH_REST_2_REST_SEND); + dataMqtt2Rest = client.target(REST_SERVER_BASE_URL + PATH_MQTT_2_REST_SEND); + dataBoth2Rest = client.target(REST_SERVER_BASE_URL + PATH_BOTH_2_REST_SEND); + senderRest2Rest = client.target(REST_SERVER_BASE_URL + PATH_REST_2_REST_RECEIVE); + senderRest2Mqtt = client.target(REST_SERVER_BASE_URL + PATH_REST_2_MQTT_RECEIVE); + senderBoth2Rest = client.target(REST_SERVER_BASE_URL + PATH_BOTH_REST_RECEIVE); + + model.connectMqtt2MqttInput(mqttUri(TOPIC_MQTT_2_MQTT_RECEIVE)); + model.connectMqtt2MqttOutput(mqttUri(TOPIC_MQTT_2_MQTT_SEND), writeCurrentValue); + model.connectMqtt2RestInput(mqttUri(TOPIC_MQTT_2_REST_RECEIVE)); + model.connectMqtt2RestOutput(restUri(PATH_MQTT_2_REST_SEND, REST_PORT), writeCurrentValue); + model.connectRest2MqttInput(restUri(PATH_REST_2_MQTT_RECEIVE, REST_PORT)); + model.connectRest2MqttOutput(mqttUri(TOPIC_REST_2_MQTT_SEND), writeCurrentValue); + model.connectRest2RestInput(restUri(PATH_REST_2_REST_RECEIVE, REST_PORT)); + model.connectRest2RestOutput(restUri(PATH_REST_2_REST_SEND, REST_PORT), writeCurrentValue); + model.connectBoth2BothInput(mqttUri(TOPIC_BOTH_MQTT_RECEIVE)); + model.connectBoth2BothInput(restUri(PATH_BOTH_REST_RECEIVE, REST_PORT)); + model.connectBoth2MqttOutput(mqttUri(TOPIC_BOTH_2_MQTT_SEND), writeCurrentValue); + model.connectBoth2RestOutput(restUri(PATH_BOTH_2_REST_SEND, REST_PORT), writeCurrentValue); + } + + private static class ReceiverData { + String lastStringValue; + int numberOfStringValues = 0; + + public void assertEqualData(int expectedNumberOfValues, String expectedLastValue) { + assertEquals(expectedNumberOfValues, this.numberOfStringValues); + assertEquals(expectedLastValue, this.lastStringValue); + } + } +} diff --git a/ragconnect.tests/src/test/proto/config.proto b/ragconnect.tests/src/test/proto/config.proto new file mode 100644 index 0000000000000000000000000000000000000000..38cb5d0a25b02c96dcbdf30f685212ea972feee6 --- /dev/null +++ b/ragconnect.tests/src/test/proto/config.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +package config; + +message RobotConfig { + double speed = 1; +} \ No newline at end of file diff --git a/ragconnect.tests/src/test/proto/robot_state.proto b/ragconnect.tests/src/test/proto/robot_state.proto new file mode 100644 index 0000000000000000000000000000000000000000..6630631f16e00493be3f50307c9092e56184e9c6 --- /dev/null +++ b/ragconnect.tests/src/test/proto/robot_state.proto @@ -0,0 +1,37 @@ +syntax = "proto3"; + +package robot; + +message RobotState { + + message Position { + double x = 1; + double y = 2; + double z = 3; + } + + message Orientation { + double x = 1; + double y = 2; + double z = 3; + double w = 4; + } + + message LinearTwist { + double x = 1; + double y = 2; + double z = 3; + } + + message AngularTwist { + double x = 1; + double y = 2; + double z = 3; + } + + string name = 1; + Position position = 2; + Orientation orientation = 3; + LinearTwist linear_twist = 4; + AngularTwist angular_twist = 5; +} \ No newline at end of file diff --git a/ragconnect.tests/src/test/proto/trajectory.proto b/ragconnect.tests/src/test/proto/trajectory.proto new file mode 100644 index 0000000000000000000000000000000000000000..4a7f375e1ae37ba386f45e1149dc38990d0c7a0e --- /dev/null +++ b/ragconnect.tests/src/test/proto/trajectory.proto @@ -0,0 +1,33 @@ +syntax = "proto3"; + +package plan; + +message Trajectory { + + message Position { + double x = 1; + double y = 2; + double z = 3; + } + + message Orientation { + double x = 1; + double y = 2; + double z = 3; + double w = 4; + } + + enum PlanningMode { + FLUID = 0; + CARTESIAN = 1; + } + + message Pose { + Position position = 1; + Orientation orientation = 2; + PlanningMode mode = 3; + } + + repeated Pose pose = 1; + bool loop = 2; +} diff --git a/ragconnect.tests/src/test/resources/log4j2.xml b/ragconnect.tests/src/test/resources/log4j2.xml new file mode 100644 index 0000000000000000000000000000000000000000..4c0d4548c61b23abad6aabc6811e68cd8a928871 --- /dev/null +++ b/ragconnect.tests/src/test/resources/log4j2.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration status="INFO"> + <Appenders> + <Console name="Console" target="SYSTEM_OUT"> + <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> + </Console> + </Appenders> + <Loggers> + <Root level="debug"> + <AppenderRef ref="Console"/> + </Root> + <Logger name="org.eclipse.jetty" level="info" additivity="false"> + <AppenderRef ref="Console"/> + </Logger> + </Loggers> +</Configuration> diff --git a/settings.gradle b/settings.gradle index 63cc07e51bd795158585056c756f0dc269181ff9..e7769874a5f3186274ceecbcd445feeb656cf9a4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,3 +2,4 @@ rootProject.name = 'ragconnect' include 'relast-preprocessor' include 'ragconnect.base' +include 'ragconnect.tests'