From 130bc9d1750a0dab358b537b79f7c1508197b086 Mon Sep 17 00:00:00 2001 From: rschoene <rene.schoene@tu-dresden.de> Date: Thu, 2 Jun 2022 16:36:48 +0200 Subject: [PATCH] WIP: Working on bugfixes for attributes - parse complete JavaUse as type for attribute targets - add new observer entries (inc. eval) for NTA and attribute targets - tests currently not passing --- ragconnect.base/build.gradle | 2 +- .../src/main/jastadd/Intermediate.jadd | 35 ++++- .../src/main/jastadd/Intermediate.relast | 2 + .../src/main/jastadd/parser/RagConnect.parser | 4 +- .../src/main/resources/ragconnect.mustache | 39 ++++-- .../main/resources/sendDefinition.mustache | 12 +- ragconnect.tests/build.gradle | 2 +- .../src/test/01-input/attribute/Test.connect | 10 ++ .../src/test/01-input/attribute/Test.jadd | 8 ++ .../src/test/01-input/attribute/Test.relast | 8 +- .../test/01-input/indexedSend/Test.connect | 5 +- .../src/test/01-input/indexedSend/Test.jadd | 18 +++ .../src/test/01-input/indexedSend/Test.relast | 4 +- .../ragconnect/tests/AbstractMqttTest.java | 6 +- .../ragconnect/tests/AttributeTest.java | 111 +++++++++++---- .../ragconnect/tests/IndexedSendTest.java | 127 ++++++++++++------ .../jastadd/ragconnect/tests/TestUtils.java | 17 ++- 17 files changed, 310 insertions(+), 100 deletions(-) diff --git a/ragconnect.base/build.gradle b/ragconnect.base/build.gradle index 7dfe1e7..7af3fc6 100644 --- a/ragconnect.base/build.gradle +++ b/ragconnect.base/build.gradle @@ -34,7 +34,7 @@ dependencies { relast group: 'org.jastadd', name: 'relast', version: "0.3.0-137" implementation group: 'org.jastadd', name: 'relast-preprocessor', version: "${preprocessor_version}" implementation group: 'com.github.spullara.mustache.java', name: 'compiler', version: "${mustache_java_version}" - runtimeOnly group: 'org.jastadd', name: 'jastadd2', version: '2.3.5-dresden' + jastadd2 group: 'org.jastadd', name: 'jastadd2', version: '2.3.5-dresden' api group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11' } diff --git a/ragconnect.base/src/main/jastadd/Intermediate.jadd b/ragconnect.base/src/main/jastadd/Intermediate.jadd index 95f9b86..dfd74a9 100644 --- a/ragconnect.base/src/main/jastadd/Intermediate.jadd +++ b/ragconnect.base/src/main/jastadd/Intermediate.jadd @@ -506,13 +506,36 @@ aspect MustacheSendDefinition { syn String EndpointDefinition.forwardingNTA_Name() = getEndpointTarget().forwardingNTA_Name(); syn String EndpointDefinition.forwardingNTA_Type() = getEndpointTarget().forwardingNTA_Type(); + syn boolean EndpointDefinition.targetIsAttribute() = getEndpointTarget().isAttributeEndpointTarget(); + + syn boolean EndpointDefinition.indexBasedAccessAndTargetIsNTA() { + return typeIsList() && getIndexBasedListAccess() && !needForwardingNTA(); + } + syn boolean EndpointDefinition.relationEndpointWithListRole() = getEndpointTarget().relationEndpointWithListRole(); syn String EndpointDefinition.senderName() = getEndpointTarget().senderName(); + syn java.util.List<SendIncrementalObserverEntry> EndpointDefinition.sendIncrementalObserverEntries() { + // todo maybe getterMethodName needs to be change for indexed send + java.util.List<SendIncrementalObserverEntry> result = new java.util.ArrayList<>(); + // "{{getterMethodName}}{{#IndexBasedListAccess}}_int{{/IndexBasedListAccess}}" + result.add(SendIncrementalObserverEntry.of(getterMethodName() + (getIndexBasedListAccess() ? "_int" : ""), + getIndexBasedListAccess(), "index")); + if (indexBasedAccessAndTargetIsNTA()) { + // "{{getterMethodName}}List" + result.add(SendIncrementalObserverEntry.of(getterMethodName() + "List", false, "index")); + } + if (targetIsAttribute()) { + // "{{parentTypeName}}_{{getterMethodName}}{{#IndexBasedListAccess}}_int{{/IndexBasedListAccess}}" + result.add(SendIncrementalObserverEntry.of(parentTypeName() + "_" + getterMethodName() + (getIndexBasedListAccess() ? "_int" : ""), getIndexBasedListAccess(), "index")); + } + return result; + } + syn boolean EndpointDefinition.shouldNotResetValue() = getSend() && !getEndpointTarget().hasAttributeResetMethod(); - syn String EndpointDefinition.tokenResetMethodName() = getterMethodName() + "_reset"; + syn String EndpointDefinition.tokenResetMethodName() = getEndpointTarget().tokenResetMethodName(); syn String EndpointDefinition.updateMethodName() = toMustache().updateMethodName(); @@ -552,6 +575,9 @@ containingEndpointDefinition().getIndexBasedListAccess()); syn String EndpointTarget.senderName() = ragconnect().internalRagConnectPrefix() + "_sender_" + entityName(); eq ContextFreeTypeEndpointTarget.senderName() = null; + syn String EndpointTarget.tokenResetMethodName() = getterMethodName() + ( + typeIsList() && containingEndpointDefinition().getIndexBasedListAccess() ? "List" : "") + "_reset"; + syn String MEndpointDefinition.updateMethodName(); syn String MEndpointDefinition.writeMethodName(); @@ -584,6 +610,13 @@ containingEndpointDefinition().getIndexBasedListAccess()); syn String EndpointDefinition.typeName() = type().getName(); syn String MEndpointDefinition.typeName() = getEndpointDefinition().typeName(); + + static SendIncrementalObserverEntry SendIncrementalObserverEntry.of(String attributeString, boolean useParams, Object params) { + return new SendIncrementalObserverEntry() + .setParams(useParams ? params : null) + .setCompareParams(useParams) + .setAttributeString(attributeString); + } } aspect MustacheTokenComponent { diff --git a/ragconnect.base/src/main/jastadd/Intermediate.relast b/ragconnect.base/src/main/jastadd/Intermediate.relast index 8fa32f9..0307d8d 100644 --- a/ragconnect.base/src/main/jastadd/Intermediate.relast +++ b/ragconnect.base/src/main/jastadd/Intermediate.relast @@ -15,3 +15,5 @@ MContextFreeTypeSendDefinition : MContextFreeTypeEndpointDefinition; MInnerMappingDefinition; rel MInnerMappingDefinition.MappingDefinition -> MappingDefinition; + +SendIncrementalObserverEntry ::= <Params:Object> <CompareParams:boolean> <AttributeString>; diff --git a/ragconnect.base/src/main/jastadd/parser/RagConnect.parser b/ragconnect.base/src/main/jastadd/parser/RagConnect.parser index f822bf0..55eac24 100644 --- a/ragconnect.base/src/main/jastadd/parser/RagConnect.parser +++ b/ragconnect.base/src/main/jastadd/parser/RagConnect.parser @@ -63,8 +63,8 @@ EndpointDefinition endpoint_definition_type EndpointTarget endpoint_target = ID.type_name DOT ID.child_name {: return new UntypedEndpointTarget(type_name, child_name, false); :} - | ID.type_name DOT ID.child_name BRACKET_LEFT ID.attribute_type_name BRACKET_RIGHT - {: return new UntypedEndpointTarget(type_name, child_name + ":" + attribute_type_name, true); :} + | ID.type_name DOT ID.child_name BRACKET_LEFT java_type_use.attribute_type_name BRACKET_RIGHT + {: return new UntypedEndpointTarget(type_name, child_name + ":" + attribute_type_name.prettyPrint(), true); :} | ID.type_name {: return new UntypedEndpointTarget(type_name, "", false); :} ; diff --git a/ragconnect.base/src/main/resources/ragconnect.mustache b/ragconnect.base/src/main/resources/ragconnect.mustache index 7e054a4..f2195dd 100644 --- a/ragconnect.base/src/main/resources/ragconnect.mustache +++ b/ragconnect.base/src/main/resources/ragconnect.mustache @@ -110,6 +110,7 @@ aspect RagConnectObserver { final boolean compareParams; final Object params; final Runnable attributeCall; + //final RagConnectToken connectToken; final java.util.List<RagConnectToken> connectList = new java.util.ArrayList<>(); RagConnectObserverEntry(ASTNode node, String attributeString, @@ -126,11 +127,11 @@ aspect RagConnectObserver { } boolean baseMembersEqualTo(ASTNode otherNode, String otherAttributeString, - boolean otherCompareParams, Object otherParams) { + boolean forceCompareParams, Object otherParams) { return this.node.equals(otherNode) && this.attributeString.equals(otherAttributeString) && - this.compareParams == otherCompareParams && - (!this.compareParams || java.util.Objects.equals(this.params, otherParams)); + //this.compareParams == otherCompareParams && + (!(this.compareParams || forceCompareParams) || java.util.Objects.equals(this.params, otherParams)); } } @@ -162,11 +163,21 @@ aspect RagConnectObserver { node.trace().setReceiver(this); } - void add(RagConnectToken connectToken, ASTNode node, String attributeString, Runnable attributeCall) { - internal_add(connectToken, node, attributeString, false, null, attributeCall); + void add(RagConnectToken connectToken, ASTNode node, Runnable attributeCall, String... attributeStrings) { + internal_add(connectToken, node, attributeStrings, false, null, attributeCall); } - void add(RagConnectToken connectToken, ASTNode node, String attributeString, Object params, Runnable attributeCall) { - internal_add(connectToken, node, attributeString, true, params, attributeCall); + void add(RagConnectToken connectToken, ASTNode node, Object params, Runnable attributeCall, String... attributeStrings) { + internal_add(connectToken, node, attributeStrings, true, params, attributeCall); + } + + private void internal_add(RagConnectToken connectToken, ASTNode node, String[] attributeStrings, + boolean compareParams, Object params, Runnable attributeCall) { + if (attributeStrings.length == 0) { + {{logWarn}}("No attribute string given to observe for {{log_}}!", connectToken.uri); + } + for (String attributeString : attributeStrings) { + internal_add(connectToken, node, attributeString, compareParams, params, attributeCall); + } } private void internal_add(RagConnectToken connectToken, ASTNode node, String attributeString, @@ -176,10 +187,12 @@ aspect RagConnectObserver { node, attributeString, (compareParams ? " (parameterized)" : "")); {{/configLoggingEnabledForIncremental}} // either add to an existing entry (with same node, attribute) or create new entry + //TODO check case, where runnable differs (and baseMembersEqual returns true). need list of runnables, and map<connectToken, runnable> to remove them! boolean needNewEntry = true; for (RagConnectObserverEntry entry : observedNodes) { - if (entry.baseMembersEqualTo(node, attributeString, compareParams, params)) { + if (entry.baseMembersEqualTo(node, attributeString, true, params)) { entry.connectList.add(connectToken); + {{logError}}("baseMembersEqualTo node: {{log_}} atrStr: {{log_}} compare: {{log_}} params: {{log_}}", node, attributeString, compareParams, params); needNewEntry = false; break; } @@ -193,16 +206,14 @@ aspect RagConnectObserver { } void remove(RagConnectToken connectToken) { - RagConnectObserverEntry entryToDelete = null; + java.util.List<RagConnectObserverEntry> entriesToDelete = new java.util.ArrayList<>(); for (RagConnectObserverEntry entry : observedNodes) { entry.connectList.remove(connectToken); if (entry.connectList.isEmpty()) { - entryToDelete = entry; + entriesToDelete.add(entry); } } - if (entryToDelete != null) { - observedNodes.remove(entryToDelete); - } + observedNodes.removeAll(entriesToDelete); } @Override @@ -256,7 +267,7 @@ aspect RagConnectObserver { {{/configLoggingEnabledForIncremental}} // iterate through list, if matching pair. could maybe be more efficient. for (RagConnectObserverEntry entry : observedNodes) { - if (entry.node.equals(node) && entry.attributeString.equals(attribute) && (!entry.compareParams || java.util.Objects.equals(entry.params, params))) { + if (entry.baseMembersEqualTo(node, attribute, false, params)) { // hit. call the attribute/nta-token {{#configLoggingEnabledForIncremental}} {{logDebug}}("** observer hit: {{log_}} on {{log_}}", entry.node, entry.attributeString); diff --git a/ragconnect.base/src/main/resources/sendDefinition.mustache b/ragconnect.base/src/main/resources/sendDefinition.mustache index abcbdff..6bfeee4 100644 --- a/ragconnect.base/src/main/resources/sendDefinition.mustache +++ b/ragconnect.base/src/main/resources/sendDefinition.mustache @@ -28,6 +28,7 @@ public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParamete final String topic = {{attributeName}}().extractTopic(uri); {{senderName}}.add(() -> { {{#configLoggingEnabledForWrites}} + {{!FIXME circular computation for collection attributes!!}} {{logDebug}}("[Send] {{entityName}} = {{log_}} -> {{log_}}", {{getterMethodCall}}, {{connectParameterName}}); {{/configLoggingEnabledForWrites}} if ({{lastValueGetterCall}} != null) { @@ -63,18 +64,19 @@ public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParamete if (success) { connectTokenMap.add(this, false, connectToken); {{#configIncrementalOptionActive}} - {{!todo maybe getterMethodName needs to be change for indexed send}} - {{observerInstanceSingletonMethodName}}().add( + {{#sendIncrementalObserverEntries}} + {{observerInstanceSingletonMethodName}}().add( connectToken, this, - "{{getterMethodName}}{{#IndexBasedListAccess}}_int{{/IndexBasedListAccess}}", - {{#IndexBasedListAccess}}index,{{/IndexBasedListAccess}} + {{#CompareParams}}{{Params}},{{/CompareParams}} () -> { if (this.{{updateMethodName}}({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}})) { this.{{writeMethodName}}({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}}); } - } + }, + "{{AttributeString}}" ); + {{/sendIncrementalObserverEntries}} {{/configIncrementalOptionActive}} } return success; diff --git a/ragconnect.tests/build.gradle b/ragconnect.tests/build.gradle index ad9cff1..5068a66 100644 --- a/ragconnect.tests/build.gradle +++ b/ragconnect.tests/build.gradle @@ -46,7 +46,7 @@ dependencies { ragconnect project(':ragconnect.base') testImplementation project(':ragconnect.base') - implementation group: 'org.jastadd', name: 'jastadd2', version: '2.3.5-dresden-5' + implementation group: 'org.jastadd', name: 'jastadd2', version: '2.3.5-dresden-7' relast group: 'org.jastadd', name: 'relast', version: "0.3.0-137" testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.4.0' diff --git a/ragconnect.tests/src/test/01-input/attribute/Test.connect b/ragconnect.tests/src/test/01-input/attribute/Test.connect index 03688a9..a463af6 100644 --- a/ragconnect.tests/src/test/01-input/attribute/Test.connect +++ b/ragconnect.tests/src/test/01-input/attribute/Test.connect @@ -3,6 +3,8 @@ send SenderRoot.simple(String) ; send SenderRoot.transformed(int) ; send SenderRoot.toReferenceType(A) ; send SenderRoot.toNTA(A) ; +send SenderRoot.circularAttribute(int); +send SenderRoot.collectionAttribute(Set<String>) using SetToString; AddSuffix maps A a to A {: A result = new A(); @@ -20,6 +22,10 @@ AddPlusOne maps int i to int {: return i + 1; :} +SetToString maps Set<String> set to String {: + return set.toString(); +:} + receive ReceiverRoot.FromBasic; receive ReceiverRoot.FromSimpleNoMapping; receive ReceiverRoot.FromSimpleWithMapping using AddStringSuffix; @@ -29,3 +35,7 @@ receive ReceiverRoot.FromReferenceTypeNoMapping; receive ReceiverRoot.FromReferenceTypeWithMapping using AddSuffix; receive ReceiverRoot.FromNTANoMapping; receive ReceiverRoot.FromNTAWithMapping using AddSuffix; +receive ReceiverRoot.FromCircularNoMapping; +receive ReceiverRoot.FromCircularWithMapping using AddPlusOne; +receive ReceiverRoot.FromCollectionNoMapping; +receive ReceiverRoot.FromCollectionWithMapping using AddStringSuffix; diff --git a/ragconnect.tests/src/test/01-input/attribute/Test.jadd b/ragconnect.tests/src/test/01-input/attribute/Test.jadd index 34b1b45..4ad7668 100644 --- a/ragconnect.tests/src/test/01-input/attribute/Test.jadd +++ b/ragconnect.tests/src/test/01-input/attribute/Test.jadd @@ -1,3 +1,5 @@ +import java.util.Set; +import java.util.HashSet; aspect Computation { syn String SenderRoot.basic() = getInput(); syn String SenderRoot.simple() = getInput() + "Post"; @@ -18,6 +20,12 @@ aspect Computation { result.setInner(inner); return result; } + syn int SenderRoot.circularAttribute() circular [0] { + return Integer.parseInt(getInput()) + 2; + } + coll Set<String> SenderRoot.collectionAttribute() [new HashSet<>()] root SenderRoot ; + A contributes getValue() to SenderRoot.collectionAttribute(); + SenderRoot contributes nta toNTA() to SenderRoot.collectionAttribute(); } aspect MakeCodeCompile { diff --git a/ragconnect.tests/src/test/01-input/attribute/Test.relast b/ragconnect.tests/src/test/01-input/attribute/Test.relast index 7afc9b7..e3a3a67 100644 --- a/ragconnect.tests/src/test/01-input/attribute/Test.relast +++ b/ragconnect.tests/src/test/01-input/attribute/Test.relast @@ -1,5 +1,5 @@ Root ::= SenderRoot* ReceiverRoot; -SenderRoot ::= <Input> ; +SenderRoot ::= <Input> A* ; ReceiverRoot ::= <FromBasic> <FromSimpleNoMapping> @@ -9,6 +9,10 @@ ReceiverRoot ::= FromReferenceTypeNoMapping:A FromReferenceTypeWithMapping:A FromNTANoMapping:A - FromNTAWithMapping:A ; + FromNTAWithMapping:A + <FromCircularNoMapping:int> + <FromCircularWithMapping:int> + <FromCollectionNoMapping> + <FromCollectionWithMapping> ; A ::= <Value> Inner ; Inner ::= <InnerValue> ; diff --git a/ragconnect.tests/src/test/01-input/indexedSend/Test.connect b/ragconnect.tests/src/test/01-input/indexedSend/Test.connect index 3ff49e1..9e390cd 100644 --- a/ragconnect.tests/src/test/01-input/indexedSend/Test.connect +++ b/ragconnect.tests/src/test/01-input/indexedSend/Test.connect @@ -1,6 +1,7 @@ send indexed SenderRoot.MultipleA; - send indexed SenderRoot.MultipleAWithSuffix using AddSuffix; +send indexed SenderRoot.A; +send indexed SenderRoot.ComputedA using AddSuffix; AddSuffix maps A a to A {: A result = new A(); @@ -12,3 +13,5 @@ AddSuffix maps A a to A {: receive indexed ReceiverRoot.ManyA; receive indexed ReceiverRoot.ManyAWithSuffix; +receive indexed ReceiverRoot.FromNTA; +receive indexed ReceiverRoot.FromNTAWithSuffix; diff --git a/ragconnect.tests/src/test/01-input/indexedSend/Test.jadd b/ragconnect.tests/src/test/01-input/indexedSend/Test.jadd index d37dd04..fc8893e 100644 --- a/ragconnect.tests/src/test/01-input/indexedSend/Test.jadd +++ b/ragconnect.tests/src/test/01-input/indexedSend/Test.jadd @@ -1,3 +1,21 @@ +aspect Computation { + syn JastAddList<A> SenderRoot.getAList() { + JastAddList<A> result = new JastAddList<>(); + getMultipleAList().forEach(a -> result.add(a.touchedTerminals())); + return result; + } + syn JastAddList<A> SenderRoot.getComputedAList() { + JastAddList<A> result = new JastAddList<>(); + getMultipleAWithSuffixList().forEach(a -> result.add(a.touchedTerminals())); + return result; + } + A A.touchedTerminals() { + getValue(); + getInner().getInnerValue(); + return this; + } +} + aspect NameResolution { // overriding customID guarantees to produce the same JSON representation for equal lists // otherwise, the value for id is different each time diff --git a/ragconnect.tests/src/test/01-input/indexedSend/Test.relast b/ragconnect.tests/src/test/01-input/indexedSend/Test.relast index 9480f18..2e9924b 100644 --- a/ragconnect.tests/src/test/01-input/indexedSend/Test.relast +++ b/ragconnect.tests/src/test/01-input/indexedSend/Test.relast @@ -1,5 +1,5 @@ Root ::= SenderRoot ReceiverRoot; -SenderRoot ::= MultipleA:A* MultipleAWithSuffix:A* ; -ReceiverRoot ::= ManyA:A* ManyAWithSuffix:A* ; +SenderRoot ::= MultipleA:A* MultipleAWithSuffix:A* /A*/ /ComputedA:A*/ ; +ReceiverRoot ::= ManyA:A* ManyAWithSuffix:A* FromNTA:A* FromNTAWithSuffix:A* ; A ::= <Value> Inner ; Inner ::= <InnerValue> ; 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 index 31335e5..b8f31b8 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractMqttTest.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractMqttTest.java @@ -25,7 +25,7 @@ public abstract class AbstractMqttTest extends RagConnectTest { /** * if the initial/current value shall be sent upon connecting */ - private boolean writeCurrentValue; + protected boolean writeCurrentValue; public boolean isWriteCurrentValue() { return writeCurrentValue; @@ -58,7 +58,7 @@ public abstract class AbstractMqttTest extends RagConnectTest { @Tag("mqtt") @RepeatedIfExceptionsTest(repeats = TEST_REPETITIONS) - public final void testCommunicateSendInitialValue() throws IOException, InterruptedException { + public void testCommunicateSendInitialValue() throws IOException, InterruptedException { this.writeCurrentValue = true; createModel(); @@ -76,7 +76,7 @@ public abstract class AbstractMqttTest extends RagConnectTest { @Tag("mqtt") @RepeatedIfExceptionsTest(repeats = TEST_REPETITIONS) - public final void testCommunicateOnlyUpdatedValue() throws IOException, InterruptedException { + public void testCommunicateOnlyUpdatedValue() throws IOException, InterruptedException { this.writeCurrentValue = false; createModel(); diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AttributeTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AttributeTest.java index 1c10bd4..a0336d4 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AttributeTest.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AttributeTest.java @@ -9,8 +9,10 @@ import java.util.Objects; 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.assertj.core.api.Assertions.assertThat; import static org.jastadd.ragconnect.tests.TestUtils.*; import static org.junit.jupiter.api.Assertions.*; @@ -20,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.*; * @author rschoene - Initial contribution */ @Tag("Incremental") +@Tag("New") public class AttributeTest extends AbstractMqttTest { private static final String TOPIC_WILDCARD = "attr/#"; @@ -32,15 +35,22 @@ public class AttributeTest extends AbstractMqttTest { private static final String TOPIC_REFERENCE_TYPE_WITH_MAPPING = "attr/a/ref/mapped"; private static final String TOPIC_NTA_NO_MAPPING = "attr/a/nta/plain"; private static final String TOPIC_NTA_WITH_MAPPING = "attr/a/nta/mapped"; + private static final String TOPIC_CIRCULAR_NO_MAPPING = "attr/a/circular/plain"; + private static final String TOPIC_CIRCULAR_WITH_MAPPING = "attr/a/circular/mapped"; + private static final String TOPIC_COLLECTION_NO_MAPPING = "attr/a/collection/plain"; + private static final String TOPIC_COLLECTION_WITH_MAPPING = "attr/a/collection/mapped"; private static final String INITIAL_STRING = "initial"; private static final String INITIAL_STRING_FOR_INT = "1"; + private static final String INITIAL_STRING_FOR_INT_PLUS_2 = Integer.toString(Integer.parseInt(INITIAL_STRING_FOR_INT) + 2); private static final String CHECK_BASIC = "basic"; private static final String CHECK_SIMPLE = "simple"; private static final String CHECK_TRANSFORMED = "transformed"; private static final String CHECK_A = "a"; private static final String CHECK_NTA = "nta"; + private static final String CHECK_CIRCULAR = "circular"; + private static final String CHECK_COLLECTION = "collection"; private MqttHandler handler; private ReceiverData data; @@ -82,6 +92,8 @@ public class AttributeTest extends AbstractMqttTest { .setCheckForString(CHECK_TRANSFORMED, this::checkTransformed) .setCheckForString(CHECK_A, this::checkA) .setCheckForString(CHECK_NTA, this::checkNta) + .setCheckForString(CHECK_CIRCULAR, this::checkCircular) + .setCheckForString(CHECK_COLLECTION, this::checkCollection) ; // connect receive @@ -94,14 +106,22 @@ public class AttributeTest extends AbstractMqttTest { assertTrue(receiverRoot.connectFromReferenceTypeWithMapping(mqttUri(TOPIC_REFERENCE_TYPE_WITH_MAPPING))); assertTrue(receiverRoot.connectFromNTANoMapping(mqttUri(TOPIC_NTA_NO_MAPPING))); assertTrue(receiverRoot.connectFromNTAWithMapping(mqttUri(TOPIC_NTA_WITH_MAPPING))); + assertTrue(receiverRoot.connectFromCircularNoMapping(mqttUri(TOPIC_CIRCULAR_NO_MAPPING))); + assertTrue(receiverRoot.connectFromCircularWithMapping(mqttUri(TOPIC_CIRCULAR_WITH_MAPPING))); + assertTrue(receiverRoot.connectFromCollectionNoMapping(mqttUri(TOPIC_COLLECTION_NO_MAPPING))); + assertTrue(receiverRoot.connectFromCollectionWithMapping(mqttUri(TOPIC_COLLECTION_WITH_MAPPING))); // connect send, and wait to receive (if writeCurrentValue is set) assertTrue(senderString.connectBasic(mqttUri(TOPIC_BASIC), isWriteCurrentValue())); assertTrue(senderString.connectSimple(mqttUri(TOPIC_SIMPLE_NO_MAPPING), isWriteCurrentValue())); assertTrue(senderString.connectSimple(mqttUri(TOPIC_SIMPLE_WITH_MAPPING), isWriteCurrentValue())); + assertTrue(senderString.connectCollectionAttribute(mqttUri(TOPIC_COLLECTION_NO_MAPPING), isWriteCurrentValue())); + assertTrue(senderString.connectCollectionAttribute(mqttUri(TOPIC_COLLECTION_WITH_MAPPING), isWriteCurrentValue())); assertTrue(senderInt.connectTransformed(mqttUri(TOPIC_TRANSFORMED_NO_MAPPING), isWriteCurrentValue())); assertTrue(senderInt.connectTransformed(mqttUri(TOPIC_TRANSFORMED_WITH_MAPPING), isWriteCurrentValue())); + assertTrue(senderInt.connectCircularAttribute(mqttUri(TOPIC_CIRCULAR_NO_MAPPING), isWriteCurrentValue())); + assertTrue(senderInt.connectCircularAttribute(mqttUri(TOPIC_CIRCULAR_WITH_MAPPING), isWriteCurrentValue())); assertTrue(senderA.connectToReferenceType(mqttUri(TOPIC_REFERENCE_TYPE_NO_MAPPING), isWriteCurrentValue())); assertTrue(senderA.connectToReferenceType(mqttUri(TOPIC_REFERENCE_TYPE_WITH_MAPPING), isWriteCurrentValue())); @@ -111,58 +131,69 @@ public class AttributeTest extends AbstractMqttTest { waitForValue(senderString.basic(), receiverRoot::getFromBasic); waitForValue(senderString.simple(), receiverRoot::getFromSimpleNoMapping); waitForValue(senderInt.transformed(), receiverRoot::getFromTransformedNoMapping); + waitForNonNull(receiverRoot::getFromCollectionNoMapping); waitForNonNull(receiverRoot::getFromReferenceTypeNoMapping); waitForNonNull(receiverRoot::getFromNTANoMapping); } @Override protected void communicateSendInitialValue() throws IOException, InterruptedException { - // basic, simple(2) <-- senderString - // transformed(2) <-- senderInt - // ref-type(2), nta(2) <-- senderA - checker.addToNumberOfValues(9) + // 13 = basic, simple(2), collection(2), transformed(2), circular(2), ref-type(2), nta(2) + checker.addToNumberOfValues(13) .put(CHECK_BASIC, INITIAL_STRING) .put(CHECK_SIMPLE, INITIAL_STRING + "Post") .put(CHECK_TRANSFORMED, INITIAL_STRING_FOR_INT) .put(CHECK_A, INITIAL_STRING) - .put(CHECK_NTA, INITIAL_STRING); + .put(CHECK_NTA, INITIAL_STRING) + .put(CHECK_CIRCULAR, INITIAL_STRING_FOR_INT_PLUS_2) + .put(CHECK_COLLECTION, "[" + INITIAL_STRING + "]"); communicateBoth(); } @Override protected void communicateOnlyUpdatedValue() throws IOException, InterruptedException { - // basic, simple(2) <-- senderString - // transformed(2) <-- senderInt - // ref-type(2), nta(2) <-- senderA checker.put(CHECK_BASIC, (String) null) .put(CHECK_SIMPLE, (String) null) .put(CHECK_TRANSFORMED, (String) null) .put(CHECK_A, (String) null) - .put(CHECK_NTA, (String) null); + .put(CHECK_NTA, (String) null) + .put(CHECK_COLLECTION, (String) null); communicateBoth(); } private void communicateBoth() throws IOException { + // basic, simple(2), collection(2) <-- senderString + // transformed(2), circular(2) <-- senderInt + // ref-type(2), nta(2) <-- senderA checker.check(); senderString.setInput("test-01"); - checker.addToNumberOfValues(3) + checker.addToNumberOfValues(5) // basic, simple(2), collection(2) .put(CHECK_BASIC, "test-01") .put(CHECK_SIMPLE, "test-01Post") + .put(CHECK_COLLECTION, "[test-01]") .check(); + // no change for same value senderString.setInput("test-01"); checker.check(); + // TODO check checks for (circular and) collection + senderString.addA(new A().setValue("test-02").setInner(new Inner().setInnerValue("inner"))); + checker.addToNumberOfValues(2) // collection(2) + .put(CHECK_COLLECTION, "[test-01, test-02]") + .check(); + senderInt.setInput("20"); - checker.addToNumberOfValues(2) + checker.addToNumberOfValues(4) // transformed(2), circular(2) .put(CHECK_TRANSFORMED, "20") + .put(CHECK_CIRCULAR, "22") .check(); senderA.setInput("test-03"); - checker.addToNumberOfValues(4) + checker.addToNumberOfValues(4) // ref-type(2), nta(2) .put(CHECK_A, "test-03") .put(CHECK_NTA, "test-03") .check(); @@ -170,14 +201,22 @@ public class AttributeTest extends AbstractMqttTest { assertTrue(senderString.disconnectSimple(mqttUri(TOPIC_SIMPLE_NO_MAPPING))); assertTrue(senderString.disconnectSimple(mqttUri(TOPIC_SIMPLE_WITH_MAPPING))); senderString.setInput("test-04"); - checker.incNumberOfValues() + checker.addToNumberOfValues(3) // basic, collection(2) .put(CHECK_BASIC, "test-04") + .put(CHECK_COLLECTION, "[test-02, test-04]") + .check(); + + assertTrue(senderString.disconnectCollectionAttribute(mqttUri(TOPIC_COLLECTION_NO_MAPPING))); + assertTrue(senderString.disconnectCollectionAttribute(mqttUri(TOPIC_COLLECTION_WITH_MAPPING))); + senderString.setInput("test-05"); + checker.incNumberOfValues() // basic + .put(CHECK_BASIC, "test-05") .check(); assertTrue(senderA.disconnectToNTA(mqttUri(TOPIC_NTA_NO_MAPPING))); - senderA.setInput("test-05"); + senderA.setInput("test-06"); checker.addToNumberOfValues(3) - .put(CHECK_A, "test-05") + .put(CHECK_A, "test-06") .check(); } @@ -208,18 +247,17 @@ public class AttributeTest extends AbstractMqttTest { } private void checkTransformed(String name, String expected) { + _checkInt(name, expected, receiverRoot::getFromTransformedNoMapping, receiverRoot::getFromTransformedWithMapping); + } + + private void _checkInt(String name, String expected, Supplier<Integer> noMapping, Supplier<Integer> withMapping) { if (expected != null) { - assertEquals(Integer.parseInt(expected), - receiverRoot.getFromTransformedNoMapping(), "transformed"); - assertEquals(Integer.parseInt(expected) + 1, - receiverRoot.getFromTransformedWithMapping(), "transformed mapped"); + assertEquals(Integer.parseInt(expected), noMapping.get(), name); + assertEquals(Integer.parseInt(expected) + 1, withMapping.get(), name + " mapped"); } else { - assertEquals(0, - receiverRoot.getFromTransformedNoMapping(), "transformed null"); - assertEquals(0, - receiverRoot.getFromTransformedWithMapping(), "transformed mapped null"); + assertEquals(0, noMapping.get(), name + " null"); + assertEquals(0, withMapping.get(), name + " mapped null"); } - } private void checkA(String name, String expected) { @@ -245,6 +283,31 @@ public class AttributeTest extends AbstractMqttTest { } } + private void checkCircular(String name, String expected) { + _checkInt(name, expected, receiverRoot::getFromCircularNoMapping, receiverRoot::getFromCircularWithMapping); + } + + private void checkCollection(String name, String expected) { + if (expected != null) { + // TODO probably need to tokenize actual and expected, and compare their elements without order + assertThat(receiverRoot.getFromCollectionWithMapping()).hasSizeGreaterThan(4).endsWith("post"); + checkCollectionContent(name, expected, receiverRoot.getFromCollectionNoMapping()); + checkCollectionContent(name + " mapped", expected, receiverRoot.getFromCollectionWithMapping().substring(0, receiverRoot.getFromCollectionWithMapping().length() - 4)); + } else { + assertEquals("", receiverRoot.getFromCollectionNoMapping(), "collection null"); + assertEquals("", receiverRoot.getFromCollectionWithMapping(), "collection mapped null"); + } + } + + private void checkCollectionContent(String name, String expected, String actual) { + assertThat(actual).as(name) + .startsWith("[") + .endsWith("]"); + String[] actualValues = actual.substring(1, actual.length() - 1).split(", "); + String[] expectedValues = expected.substring(1, expected.length() - 1).split(", "); + assertThat(actualValues).containsExactlyInAnyOrder(expectedValues); + } + private void assertA(String expectedValue, String expectedInner, A actual, String message) { assertEquals(expectedValue, actual.getValue(), message + " value"); assertEquals(expectedInner, actual.getInner().getInnerValue(), message + " inner"); diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/IndexedSendTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/IndexedSendTest.java index a15d999..d84f713 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/IndexedSendTest.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/IndexedSendTest.java @@ -2,12 +2,17 @@ package org.jastadd.ragconnect.tests; import indexedSendInc.ast.*; import org.assertj.core.api.Assertions; +import org.assertj.core.groups.Tuple; +import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; import java.io.IOException; +import java.util.StringJoiner; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import java.util.function.Predicate; +import java.util.function.Supplier; import static org.assertj.core.groups.Tuple.tuple; import static org.jastadd.ragconnect.tests.TestUtils.*; @@ -20,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; * @author rschoene - Initial contribution */ @Tag("Incremental") +@Tag("New") public class IndexedSendTest extends AbstractMqttTest { private static final String TOPIC_A_MANY_NORMAL_WILDCARD = "a-many/#"; @@ -33,6 +39,8 @@ public class IndexedSendTest extends AbstractMqttTest { private static final String CHECK_MANY_A = "many-a"; private static final String CHECK_WITH_SUFFIX = "many-a-with-suffix"; + private boolean connectNTAsInstead; + private MqttHandler handler; private ReceiverData data; private TestUtils.TestChecker checker; @@ -83,70 +91,91 @@ public class IndexedSendTest extends AbstractMqttTest { checker = new TestChecker(); checker.setActualNumberOfValues(() -> data.numberOfValues) .setCheckForTuple(CHECK_MANY_A, (name, expected) -> - Assertions.assertThat(receiverRoot.getManyAList()).extracting("Value") - .as(name) - .containsExactlyElementsOf(expected.toList())) + checkList(name, expected, receiverRoot::getManyAList)) .setCheckForTuple(CHECK_WITH_SUFFIX, (name, expected) -> - Assertions.assertThat(receiverRoot.getManyAWithSuffixList()).extracting("Value") - .as(name) - .containsExactlyElementsOf(expected.toList())); + checkList(name, expected, receiverRoot::getManyAWithSuffixList)); // connect receive assertTrue(receiverRoot.connectManyA(mqttUri(TOPIC_A_MANY_NORMAL_WILDCARD))); assertTrue(receiverRoot.connectManyAWithSuffix(mqttUri(TOPIC_A_MANY_SUFFIX_WILDCARD))); // connect send, and wait to receive (if writeCurrentValue is set) - assertTrue(senderRoot.connectMultipleA(mqttUri(TOPIC_A_MANY_NORMAL_0), 0, isWriteCurrentValue())); + assertTrue(connectNTAsInstead ? + senderRoot.connectA(mqttUri(TOPIC_A_MANY_NORMAL_0), 0, isWriteCurrentValue()) : + senderRoot.connectMultipleA(mqttUri(TOPIC_A_MANY_NORMAL_0), 0, isWriteCurrentValue())); waitForValue(receiverRoot::getNumManyA, 1); - assertTrue(senderRoot.connectMultipleA(mqttUri(TOPIC_A_MANY_NORMAL_1), 1, isWriteCurrentValue())); + assertTrue(connectNTAsInstead ? + senderRoot.connectA(mqttUri(TOPIC_A_MANY_NORMAL_1), 1, isWriteCurrentValue()) : + senderRoot.connectMultipleA(mqttUri(TOPIC_A_MANY_NORMAL_1), 1, isWriteCurrentValue())); waitForValue(receiverRoot::getNumManyA, 2); - assertTrue(senderRoot.connectMultipleAWithSuffix(mqttUri(TOPIC_A_MANY_SUFFIX_0), 0, isWriteCurrentValue())); + assertTrue(connectNTAsInstead ? + senderRoot.connectComputedA(mqttUri(TOPIC_A_MANY_SUFFIX_0), 0, isWriteCurrentValue()) : + senderRoot.connectMultipleAWithSuffix(mqttUri(TOPIC_A_MANY_SUFFIX_0), 0, isWriteCurrentValue())); waitForValue(receiverRoot::getNumManyAWithSuffix, 1); - assertTrue(senderRoot.connectMultipleAWithSuffix(mqttUri(TOPIC_A_MANY_SUFFIX_1), 1, isWriteCurrentValue())); + assertTrue(connectNTAsInstead ? + senderRoot.connectComputedA(mqttUri(TOPIC_A_MANY_SUFFIX_1), 1, isWriteCurrentValue()) : + senderRoot.connectMultipleAWithSuffix(mqttUri(TOPIC_A_MANY_SUFFIX_1), 1, isWriteCurrentValue())); waitForValue(receiverRoot::getNumManyAWithSuffix, 2); } + private void checkList(String name, Tuple expected, Supplier<JastAddList<A>> actual) { + Assertions.assertThat(actual.get()).extracting("Value") + .as(name) + .containsExactlyElementsOf(expected.toList()); + } + private void waitForValue(Callable<Integer> callable, int expectedValue) { if (isWriteCurrentValue()) { awaitMqtt().until(callable, Predicate.isEqual(expectedValue)); } } + @Tag("mqtt") +// @RepeatedIfExceptionsTest(repeats = TEST_REPETITIONS) + @Test + public void testCommunicateSendInitialValueWithNTAs() throws IOException, InterruptedException { + this.writeCurrentValue = true; + this.connectNTAsInstead = true; + + try { + createModel(); + setupReceiverAndConnect(); + + logger.info("Calling communicateSendInitialValue"); + communicateSendInitialValue(); + } finally { + this.connectNTAsInstead = false; + } + } + + @Tag("mqtt") +// @RepeatedIfExceptionsTest(repeats = TEST_REPETITIONS) + @Test + public void testCommunicateOnlyUpdatedValueWithNTAs() throws IOException, InterruptedException { + this.writeCurrentValue = false; + this.connectNTAsInstead = true; + + try { + createModel(); + setupReceiverAndConnect(); + + logger.info("Calling communicateOnlyUpdatedValue"); + communicateOnlyUpdatedValue(); + } finally { + this.connectNTAsInstead = false; + } + } + @Override protected void communicateSendInitialValue() throws IOException, InterruptedException { checker.addToNumberOfValues(4) .put(CHECK_MANY_A, tuple("am0", "am1")) .put(CHECK_WITH_SUFFIX, tuple("am0post", "am1post")); - listA0.setValue("changedValue"); - checker.incNumberOfValues().put(CHECK_MANY_A, tuple("changedValue", "am1")).check(); - - // setting same value must not change data, and must not trigger a new sent message - listA0.setValue("changedValue"); - checker.check(); - - listA1.setValue(""); - checker.incNumberOfValues().put(CHECK_MANY_A, tuple("changedValue", "")).check(); - - listA1InSuffix.setValue("re"); - checker.incNumberOfValues().put(CHECK_WITH_SUFFIX, tuple("am0post", "repost")).check(); - - // adding a new element does not automatically send it - A listA3InSuffix = createA("out"); - senderRoot.addMultipleAWithSuffix(listA3InSuffix); - checker.check(); - - // only after connecting it, the element gets sent - assertTrue(senderRoot.connectMultipleAWithSuffix(mqttUri(TOPIC_A_MANY_SUFFIX_2), 2, true)); - checker.incNumberOfValues().put(CHECK_WITH_SUFFIX, tuple("am0post", "repost", "outpost")).check(); - - // after successful disconnect, no messages will be sent - assertTrue(senderRoot.disconnectMultipleAWithSuffix(mqttUri(TOPIC_A_MANY_SUFFIX_0))); - listA0InSuffix.setValue("willBeIgnored"); - checker.check(); + communicateBoth("am1", "am0post"); } @Override @@ -154,10 +183,10 @@ public class IndexedSendTest extends AbstractMqttTest { checker.put(CHECK_MANY_A, tuple()) .put(CHECK_WITH_SUFFIX, tuple()); - communicateBoth(); + communicateBoth(null, null); } - private void communicateBoth() throws IOException { + private void communicateBoth(String manyAtIndex1, String suffixAtIndex0) throws IOException { // Sink.ManyA <-- Root.MultipleA // Sink.ManyAWithSuffix <-- Root.MultipleAWithSuffix checker.check(); @@ -165,18 +194,26 @@ public class IndexedSendTest extends AbstractMqttTest { assertEquals(listA0.getValue(), senderRoot._ragconnect_MultipleA(0).getValue()); listA0.setValue("changedValue"); assertEquals(listA0.getValue(), senderRoot._ragconnect_MultipleA(0).getValue()); - checker.incNumberOfValues().put(CHECK_MANY_A, tuple("changedValue")).check(); + + checker.incNumberOfValues() + .put(CHECK_MANY_A, manyAtIndex1 != null ? tuple("changedValue", manyAtIndex1) : tuple("changedValue")) + .check(); + + if (!connectNTAsInstead) { return; } // TODO remove after testing NTAs is complete // setting same value must not change data, and must not trigger a new sent message listA0.setValue("changedValue"); checker.check(); + logger.error(prettyPrint(senderRoot.getAList())); listA1.setValue(""); checker.incNumberOfValues().put(CHECK_MANY_A, tuple("changedValue", "")).check(); // first element in suffix-list listA1InSuffix.setValue("re"); - checker.incNumberOfValues().put(CHECK_WITH_SUFFIX, tuple("repost")).check(); + checker.incNumberOfValues() + .put(CHECK_WITH_SUFFIX, suffixAtIndex0 != null ? tuple(suffixAtIndex0, "repost") : tuple("repost")) + .check(); // adding a new element does not automatically send it A listA3InSuffix = createA("out"); @@ -185,7 +222,9 @@ public class IndexedSendTest extends AbstractMqttTest { // only after connecting it, the element gets sent assertTrue(senderRoot.connectMultipleAWithSuffix(mqttUri(TOPIC_A_MANY_SUFFIX_2), 2, true)); - checker.incNumberOfValues().put(CHECK_WITH_SUFFIX, tuple("repost", "outpost")).check(); + checker.incNumberOfValues() + .put(CHECK_WITH_SUFFIX, suffixAtIndex0 != null ? tuple(suffixAtIndex0, "repost", "outpost") : tuple("repost", "outpost")) + .check(); // after successful disconnect, no messages will be sent assertTrue(senderRoot.disconnectMultipleAWithSuffix(mqttUri(TOPIC_A_MANY_SUFFIX_0))); @@ -193,6 +232,12 @@ public class IndexedSendTest extends AbstractMqttTest { checker.check(); } + private String prettyPrint(JastAddList<A> aList) { + StringJoiner sj = new StringJoiner(", ", "[", "]"); + aList.forEach(a -> sj.add(a.getValue() + "(" + a.getInner().getInnerValue() + ")")); + return sj.toString(); + } + @Override protected void closeConnections() { if (handler != null) { 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 index 5e6c94f..7b2403f 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java @@ -22,6 +22,7 @@ import java.util.*; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; +import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -226,20 +227,30 @@ public class TestUtils { } public TestChecker setActual(String name, Callable<T> actual) { - values.computeIfAbsent(name, ActualAndExpected::new).actual = actual; + _computeIfAbsent(name).actual = actual; return parent; } public TestChecker setCheck(String name, BiConsumer<String, T> check) { - values.computeIfAbsent(name, ActualAndExpected::new).customCheck = check; + _computeIfAbsent(name).customCheck = check; return parent; } public TestChecker put(String name, T expected) { - values.computeIfAbsent(name, ActualAndExpected::new).expected = expected; + _computeIfAbsent(name).expected = expected; return parent; } + public TestChecker updateExpected(String name, Function<T, T> updater) { + ActualAndExpected<T> aae = _computeIfAbsent(name); + aae.expected = updater.apply(aae.expected); + return parent; + } + + private ActualAndExpected<T> _computeIfAbsent(String name) { + return values.computeIfAbsent(name, ActualAndExpected::new); + } + ActualAndExpected<T> get(String name) { return values.get(name); } -- GitLab