diff --git a/ragconnect.tests/build.gradle b/ragconnect.tests/build.gradle index 468c22acdfb055d1820c30158049248e4e0b98be..e0250d81e554a31b5aa5abe2f7036200f921d59b 100644 --- a/ragconnect.tests/build.gradle +++ b/ragconnect.tests/build.gradle @@ -657,7 +657,29 @@ task compileAttributeIncremental(type: RagConnectTest) { extraOptions = JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL } } -compileAttributeIncremental.outputs.upToDateWhen { false } + +// --- Test: relation-incremental --- +task compileRelationIncremental(type: RagConnectTest) { + ragconnect { + outputDir = file('src/test/02-after-ragconnect/relationInc') + inputFiles = [file('src/test/01-input/relation/Test.relast'), + file('src/test/01-input/relation/Test.connect')] + rootNode = 'Root' + extraOptions = defaultRagConnectOptionsAnd(['--experimental-jastadd-329']) + } + relast { + useJastAddNames = true + grammarName = 'src/test/03-after-relast/relationInc/relationInc' + serializer = 'jackson' + } + jastadd { + jastAddList = 'JastAddList' + packageName = 'relationInc.ast' + inputFiles = [file('src/test/01-input/relation/Test.jadd')] + extraOptions = JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL + } +} +compileRelationIncremental.outputs.upToDateWhen { false } static ArrayList<String> defaultRagConnectOptionsAnd(ArrayList<String> options = []) { if (!options.contains('--logTarget=slf4j')) { diff --git a/ragconnect.tests/src/test/01-input/relation/README.md b/ragconnect.tests/src/test/01-input/relation/README.md new file mode 100644 index 0000000000000000000000000000000000000000..12556e1ad76171b40a2e4a8e58717dfa41d4c52b --- /dev/null +++ b/ragconnect.tests/src/test/01-input/relation/README.md @@ -0,0 +1,3 @@ +# Relation + +Idea: Use send definitions for relations. diff --git a/ragconnect.tests/src/test/01-input/relation/Test.connect b/ragconnect.tests/src/test/01-input/relation/Test.connect new file mode 100644 index 0000000000000000000000000000000000000000..f63801b9fb48d7daae3ea7415c2f89449b7d6625 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/relation/Test.connect @@ -0,0 +1,43 @@ +//send SenderRoot.MyA; +//send SenderRoot.OptionalA; +//send SenderRoot.ListA; +// +//send SenderRoot.BiMyA; +//send SenderRoot.BiOptionalA; +//send SenderRoot.BiListA; +// +//send SenderRoot.MyB using ConcatValues; +//send SenderRoot.OptionalB using ConcatValues; +//send SenderRoot.ListB using ConcatValueList; +// +//send SenderRoot.BiMyB using ConcatValues; +//send SenderRoot.BiOptionalB using ConcatValues; +//send SenderRoot.BiListB using ConcatValueList; + +ConcatValues maps B b to String {: + return b,getValue() + b.getInner().getInnerValue(); +:} + +ConcatValueList maps JastAddList<B> list to String {: + StringBuilder sb = new StringBuilder(); + for (B b : list) { + sb.append(b,getValue() + b.getInner().getInnerValue()).append(";"); + } + return sb.toString(); +:} + +receive ReceiverRoot.FromMyA; +receive ReceiverRoot.FromOptionalA; +receive ReceiverRoot.FromListA; + +receive ReceiverRoot.FromBiMyA; +receive ReceiverRoot.FromBiOptionalA; +receive ReceiverRoot.FromBiListA; + +receive ReceiverRoot.FromMyB; +receive ReceiverRoot.FromOptionalB; +receive ReceiverRoot.FromListB; + +receive ReceiverRoot.FromBiMyB; +receive ReceiverRoot.FromBiOptionalB; +receive ReceiverRoot.FromBiListB; diff --git a/ragconnect.tests/src/test/01-input/relation/Test.jadd b/ragconnect.tests/src/test/01-input/relation/Test.jadd new file mode 100644 index 0000000000000000000000000000000000000000..2f54e7f851452f24d623b8d424744ecc8544af79 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/relation/Test.jadd @@ -0,0 +1,38 @@ +aspect Computation { +} +aspect MakeCodeCompile { + +} +aspect MakeCodeWork { + public boolean SenderRoot.connectMyA(String uriString, boolean sendCurrentValue) { return true; } + public boolean SenderRoot.connectOptionalA(String uriString, boolean sendCurrentValue) { return true; } + public boolean SenderRoot.connectListA(String uriString, boolean sendCurrentValue) { return true; } + // + public boolean SenderRoot.connectBiMyA(String uriString, boolean sendCurrentValue) { return true; } + public boolean SenderRoot.connectBiOptionalA(String uriString, boolean sendCurrentValue) { return true; } + public boolean SenderRoot.connectBiListA(String uriString, boolean sendCurrentValue) { return true; } + // + public boolean SenderRoot.connectMyB(String uriString, boolean sendCurrentValue) { return true; } + public boolean SenderRoot.connectOptionalB(String uriString, boolean sendCurrentValue) { return true; } + public boolean SenderRoot.connectListB(String uriString, boolean sendCurrentValue) { return true; } + // + public boolean SenderRoot.connectBiMyB(String uriString, boolean sendCurrentValue) { return true; } + public boolean SenderRoot.connectBiOptionalB(String uriString, boolean sendCurrentValue) { return true; } + public boolean SenderRoot.connectBiListB(String uriString, boolean sendCurrentValue) { return true; } +} +aspect NameResolution { + // overriding customID guarantees to produce the same JSON representation for equal lists + // otherwise, the value for id is different each time + @Override + protected String A.customID() { + return getClass().getSimpleName() + getValue(); + } + @Override + protected String B.customID() { + return getClass().getSimpleName() + getValue(); + } + @Override + protected String Inner.customID() { + return getClass().getSimpleName() + getInnerValue(); + } +} diff --git a/ragconnect.tests/src/test/01-input/relation/Test.relast b/ragconnect.tests/src/test/01-input/relation/Test.relast new file mode 100644 index 0000000000000000000000000000000000000000..5c56aefbedb08269b0a57cfd5e3d5fe259fd7760 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/relation/Test.relast @@ -0,0 +1,28 @@ +Root ::= SenderRoot* ReceiverRoot; +SenderRoot ::= A* B* ; +rel SenderRoot.MyA -> A; +rel SenderRoot.OptionalA? -> A; +rel SenderRoot.ListA* -> A; + +rel SenderRoot.BiMyA <-> A.ToMyA; +rel SenderRoot.BiOptionalA? <-> A.ToOptionalA; +rel SenderRoot.BiListA* <-> A.ToListA; + +rel SenderRoot.MyB -> B; +rel SenderRoot.OptionalB? -> B; +rel SenderRoot.ListB* -> B; + +rel SenderRoot.BiMyB <-> B.ToMyB; +rel SenderRoot.BiOptionalB? <-> B.ToOptionalB; +rel SenderRoot.BiListB* <-> B.ToListB; + +ReceiverRoot ::= +FromMyA:A FromOptionalA:A FromListA:A +FromBiMyA:A FromBiOptionalA:A FromBiListA:A +<FromMyB:String> <FromOptionalB:String> <FromListB:String> +<FromBiMyB:String> <FromBiOptionalB:String> <FromBiListB:String> +; + +A ::= <Value> Inner ; +B ::= <Value> Inner ; +Inner ::= <InnerValue> ; diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/RelationTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/RelationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..1c857f6d8aafabd3304dc40fd7bab6c76878b0cd --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/RelationTest.java @@ -0,0 +1,219 @@ +package org.jastadd.ragconnect.tests; + +import org.assertj.core.groups.Tuple; +import org.hamcrest.Matchers; +import relationInc.ast.*; +import org.junit.jupiter.api.Tag; + +import java.io.IOException; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import static java.util.function.Predicate.isEqual; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasProperty; +import static org.jastadd.ragconnect.tests.TestUtils.*; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * Test case "relation". + * + * @author rschoene - Initial contribution + */ +@Tag("Incremental") +public class RelationTest extends AbstractMqttTest { + + private static final String TOPIC_WILDCARD = "rel/#"; + private static final String TOPIC_MY_A = "rel/my_a"; + private static final String TOPIC_OPTIONAL_A = "rel/optional_a"; + private static final String TOPIC_LIST_A = "rel/list_a"; + private static final String TOPIC_BI_MY_A = "rel/bi_my_a"; + private static final String TOPIC_BI_OPTIONAL_A = "rel/bi_optional_a"; + private static final String TOPIC_BI_LIST_A = "rel/bi_list_a"; + private static final String TOPIC_MY_B = "rel/my_b"; + private static final String TOPIC_OPTIONAL_B = "rel/optional_b"; + private static final String TOPIC_LIST_B = "rel/list_b"; + private static final String TOPIC_BI_MY_B = "rel/bi_my_b"; + private static final String TOPIC_BI_OPTIONAL_B = "rel/bi_optional_b"; + private static final String TOPIC_BI_LIST_B = "rel/bi_list_b"; + + private MqttHandler handler; + private ReceiverData data; + + private Root model; + private SenderRoot senderUni; + private SenderRoot senderBi; + private ReceiverRoot receiverRoot; + + @Override + protected void createModel() { + model = new Root(); +// model.trace().setReceiver(TestUtils::logEvent); + senderUni = new SenderRoot(); + A a1 = createA("a1"); + senderUni.addA(a1); + senderUni.setMyA(a1); + senderUni.setBiMyA(a1); + senderUni.addA(createA("a2")); + senderUni.addA(createA("a3")); + + senderBi = new SenderRoot(); + B b1 = createB("b1"); + senderUni.addB(b1); + senderUni.setMyB(b1); + senderUni.setBiMyB(b1); + senderUni.addB(createB("b2")); + senderUni.addB(createB("b3")); + + receiverRoot = new ReceiverRoot(); + model.addSenderRoot(senderUni); + model.addSenderRoot(senderBi); + model.setReceiverRoot(receiverRoot); + } + + private A createA(String value) { + return new A().setValue(value) + .setInner(new Inner().setInnerValue("inner" + value)); + } + + private B createB(String value) { + return new B().setValue(value) + .setInner(new Inner().setInnerValue("inner" + value)); + } + + @Override + protected void setupReceiverAndConnect() throws IOException, InterruptedException { + model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS); + handler = new MqttHandler().setHost(TestUtils.getMqttHost()).dontSendWelcomeMessage(); + assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS)); + + data = new ReceiverData(); + assertTrue(handler.newConnection(TOPIC_WILDCARD, bytes -> data.numberOfValues += 1)); + + // connect receive + assertTrue(receiverRoot.connectFromMyA(mqttUri(TOPIC_MY_A))); + assertTrue(receiverRoot.connectFromOptionalA(mqttUri(TOPIC_OPTIONAL_A))); + assertTrue(receiverRoot.connectFromListA(mqttUri(TOPIC_LIST_A))); + assertTrue(receiverRoot.connectFromBiMyA(mqttUri(TOPIC_BI_MY_A))); + assertTrue(receiverRoot.connectFromBiOptionalA(mqttUri(TOPIC_BI_OPTIONAL_A))); + assertTrue(receiverRoot.connectFromBiListA(mqttUri(TOPIC_BI_LIST_A))); + assertTrue(receiverRoot.connectFromMyB(mqttUri(TOPIC_MY_B))); + assertTrue(receiverRoot.connectFromOptionalB(mqttUri(TOPIC_OPTIONAL_B))); + assertTrue(receiverRoot.connectFromListB(mqttUri(TOPIC_LIST_B))); + assertTrue(receiverRoot.connectFromBiMyB(mqttUri(TOPIC_BI_MY_B))); + assertTrue(receiverRoot.connectFromBiOptionalB(mqttUri(TOPIC_BI_OPTIONAL_B))); + assertTrue(receiverRoot.connectFromBiListB(mqttUri(TOPIC_BI_LIST_B))); + + // connect send, and wait to receive (if writeCurrentValue is set) + assertTrue(senderUni.connectMyA(mqttUri(TOPIC_MY_A), isWriteCurrentValue())); + assertTrue(senderUni.connectOptionalA(mqttUri(TOPIC_OPTIONAL_A), isWriteCurrentValue())); + assertTrue(senderUni.connectListA(mqttUri(TOPIC_LIST_A), isWriteCurrentValue())); + + assertTrue(senderBi.connectBiMyA(mqttUri(TOPIC_BI_MY_A), isWriteCurrentValue())); + assertTrue(senderBi.connectBiOptionalA(mqttUri(TOPIC_BI_OPTIONAL_A), isWriteCurrentValue())); + assertTrue(senderBi.connectBiListA(mqttUri(TOPIC_BI_LIST_A), isWriteCurrentValue())); + + assertTrue(senderUni.connectMyB(mqttUri(TOPIC_MY_B), isWriteCurrentValue())); + assertTrue(senderUni.connectOptionalB(mqttUri(TOPIC_OPTIONAL_B), isWriteCurrentValue())); + assertTrue(senderUni.connectListB(mqttUri(TOPIC_LIST_B), isWriteCurrentValue())); + + assertTrue(senderBi.connectBiMyB(mqttUri(TOPIC_BI_MY_B), isWriteCurrentValue())); + assertTrue(senderBi.connectBiOptionalB(mqttUri(TOPIC_BI_OPTIONAL_B), isWriteCurrentValue())); + assertTrue(senderBi.connectBiListB(mqttUri(TOPIC_BI_LIST_B), isWriteCurrentValue())); + + waitForNonNull(receiverRoot::getFromMyA); + waitForNonNull(receiverRoot::getFromBiMyA); + waitForNonNull(receiverRoot::getFromMyB); + waitForNonNull(receiverRoot::getFromBiMyB); + } + + private <T> void waitForValue(T expectedValue, Callable<T> callable) { + if (isWriteCurrentValue()) { + awaitMqtt().until(callable, isEqual(expectedValue)); + } + } + + private <T> void waitForNonNull(Callable<T> callable) { + if (isWriteCurrentValue()) { + awaitMqtt().until(callable, Predicate.not(isEqual(null))); + } + } + + @Override + protected void communicateSendInitialValue() throws IOException, InterruptedException { + // TODO implement test + // TODO also check disconnect + } + + @Override + protected void communicateOnlyUpdatedValue() throws IOException, InterruptedException { + waitForMqtt(); + // TODO implement test + // TODO also check disconnect + } + + private void checkAs(int numberOfValues, String myA, String optionalA, Tuple listA, + String biMyA, String biOptionalA, Tuple biListA) { + awaitEquals(numberOfValues, () -> data.numberOfValues, "numberOfValues"); + +// awaitEquals(Objects.requireNonNullElse(basic, ""), +// receiverRoot::getFromBasic, "basic"); + + awaitAorNull(myA, receiverRoot::getFromMyA, "myA"); + awaitAorNull(optionalA, receiverRoot::getFromOptionalA, "myA"); + // TODO compare list + + awaitAorNull(biMyA, receiverRoot::getFromBiMyA, "biMyA"); + awaitAorNull(biOptionalA, receiverRoot::getFromBiOptionalA, "biMyA"); + // TODO compare bi-list + } + + // TODO checkBs + + private void awaitNull(Supplier<A> actual, String alias) { + awaitMqtt().alias(alias).until(() -> actual.get() == null); + } + + private <T> void awaitEquals(T expected, Callable<T> actual, String alias) { + awaitMqtt().alias(alias).until(actual, isEqual(expected)); + } + + private void awaitAorNull(String expectedValue, Callable<A> actual, String alias) { + if (expectedValue == null) { + awaitNull(convertToSupplier(actual), alias + " null"); + return; + } + String expectedInner = "inner" + expectedValue; + awaitMqtt().alias(alias).until(actual, Matchers.allOf( + hasProperty("Value", equalTo(expectedValue)), + hasProperty("Inner", hasProperty("InnerValue", equalTo(expectedInner))))); + } + + private Supplier<A> convertToSupplier(Callable<A> actual) { + return () -> { + try { + return actual.call(); + } catch (Exception e) { + fail(e); + return null; + } + }; + } + + @Override + protected void closeConnections() { + if (handler != null) { + handler.close(); + } + if (model != null) { + model.ragconnectCloseConnections(); + } + } + + private static class ReceiverData { + int numberOfValues = 0; + } +}