Skip to content
Snippets Groups Projects
Commit 4ae01b0f authored by René Schöne's avatar René Schöne
Browse files

Resolve "Allow collection and circular attributes as endpoint targets"

- parse complete JavaUse as type for attribute targets
- enhance attribute test to cover collection and circular attributes
- enhance indexed-send test to cover NTA attributes
- compare params for list NTAs when indexed
- add Gradle task to run using parameters.txt (line-separated parameters, with #-comments)
- added documentation for usage of attributes
parent fe5bffe0
Branches
No related tags found
3 merge requests!39Version 1.1.0,!35Version 1.0.0,!32Resolve "Allow collection and circular attributes as endpoint targets"
Showing
with 350 additions and 113 deletions
...@@ -244,3 +244,14 @@ assertEquals(receiverRoot.getAList(), list("1", "other")); ...@@ -244,3 +244,14 @@ assertEquals(receiverRoot.getAList(), list("1", "other"));
// after receiving "new" on topic "some/topic/one" // after receiving "new" on topic "some/topic/one"
assertEquals(receiverRoot.getAList(), list("1", "other", "new")); assertEquals(receiverRoot.getAList(), list("1", "other", "new"));
``` ```
## Using attributes as endpoint targets
As described in the [DSL specification](/dsl), attributes can be used as endpoint targets.
They can only be used in send endpoints, and the return type of the attribute must be specified in the connect specification (because aspect files are not handled completely yet).
Currently, synthesized, inherited, collection, and circular attributes are supported.
Nonterminal attributes are best used with the "legacy" notation `/Context:Type/` within the grammar.
Please note, that serialization of Java collections of nonterminals is not supported, e.g., a `java.util.Set<ASTNode>`.
Only list nodes as defined in the grammar `/Context:Type*/` are properly recognized.
...@@ -3,3 +3,4 @@ src/gen-res/ ...@@ -3,3 +3,4 @@ src/gen-res/
src/gen/ src/gen/
out/ out/
*.class *.class
/parameters.txt
...@@ -34,7 +34,7 @@ dependencies { ...@@ -34,7 +34,7 @@ dependencies {
relast group: 'org.jastadd', name: 'relast', version: "0.3.0-137" relast group: 'org.jastadd', name: 'relast', version: "0.3.0-137"
implementation group: 'org.jastadd', name: 'relast-preprocessor', version: "${preprocessor_version}" implementation group: 'org.jastadd', name: 'relast-preprocessor', version: "${preprocessor_version}"
implementation group: 'com.github.spullara.mustache.java', name: 'compiler', version: "${mustache_java_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' api group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11'
} }
...@@ -187,6 +187,23 @@ ext { ...@@ -187,6 +187,23 @@ ext {
} }
application.mainClassName = "${mainClassName}" application.mainClassName = "${mainClassName}"
def parametersTxtFileName = 'ragconnect.base/parameters.txt'
task runParameters(type: JavaExec) {
doFirst {
if (!new File(parametersTxtFileName).exists()) {
throw new GradleException("Please create '${parametersTxtFileName}' to use this task.")
}
}
group 'application'
description 'Run using parameters.txt (line-separated with comments)'
classpath sourceSets.main.runtimeClasspath
main = "org.jastadd.ragconnect.compiler.Compiler"
try {
args new File(parametersTxtFileName).text.strip().split("\n").dropWhile { it.startsWith("#") }
} catch (FileNotFoundException ignored) { /* empty */ }
standardInput = System.in
}
jar { jar {
manifest.attributes "Main-Class": "${mainClassName}" manifest.attributes "Main-Class": "${mainClassName}"
} }
......
...@@ -506,13 +506,36 @@ aspect MustacheSendDefinition { ...@@ -506,13 +506,36 @@ aspect MustacheSendDefinition {
syn String EndpointDefinition.forwardingNTA_Name() = getEndpointTarget().forwardingNTA_Name(); syn String EndpointDefinition.forwardingNTA_Name() = getEndpointTarget().forwardingNTA_Name();
syn String EndpointDefinition.forwardingNTA_Type() = getEndpointTarget().forwardingNTA_Type(); 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 boolean EndpointDefinition.relationEndpointWithListRole() = getEndpointTarget().relationEndpointWithListRole();
syn String EndpointDefinition.senderName() = getEndpointTarget().senderName(); 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(), getIndexBasedListAccess() ? "index" : "null"));
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(), getIndexBasedListAccess() ? "index" : "null"));
}
return result;
}
syn boolean EndpointDefinition.shouldNotResetValue() = getSend() && !getEndpointTarget().hasAttributeResetMethod(); 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(); syn String EndpointDefinition.updateMethodName() = toMustache().updateMethodName();
...@@ -552,6 +575,9 @@ containingEndpointDefinition().getIndexBasedListAccess()); ...@@ -552,6 +575,9 @@ containingEndpointDefinition().getIndexBasedListAccess());
syn String EndpointTarget.senderName() = ragconnect().internalRagConnectPrefix() + "_sender_" + entityName(); syn String EndpointTarget.senderName() = ragconnect().internalRagConnectPrefix() + "_sender_" + entityName();
eq ContextFreeTypeEndpointTarget.senderName() = null; eq ContextFreeTypeEndpointTarget.senderName() = null;
syn String EndpointTarget.tokenResetMethodName() = getterMethodName() + (
typeIsList() && containingEndpointDefinition().getIndexBasedListAccess() ? "List" : "") + "_reset";
syn String MEndpointDefinition.updateMethodName(); syn String MEndpointDefinition.updateMethodName();
syn String MEndpointDefinition.writeMethodName(); syn String MEndpointDefinition.writeMethodName();
...@@ -584,6 +610,13 @@ containingEndpointDefinition().getIndexBasedListAccess()); ...@@ -584,6 +610,13 @@ containingEndpointDefinition().getIndexBasedListAccess());
syn String EndpointDefinition.typeName() = type().getName(); syn String EndpointDefinition.typeName() = type().getName();
syn String MEndpointDefinition.typeName() = getEndpointDefinition().typeName(); syn String MEndpointDefinition.typeName() = getEndpointDefinition().typeName();
static SendIncrementalObserverEntry SendIncrementalObserverEntry.of(String attributeString, boolean compareParams, Object params) {
return new SendIncrementalObserverEntry()
.setParams(params)
.setCompareParams(compareParams)
.setAttributeString(attributeString);
}
} }
aspect MustacheTokenComponent { aspect MustacheTokenComponent {
......
...@@ -15,3 +15,5 @@ MContextFreeTypeSendDefinition : MContextFreeTypeEndpointDefinition; ...@@ -15,3 +15,5 @@ MContextFreeTypeSendDefinition : MContextFreeTypeEndpointDefinition;
MInnerMappingDefinition; MInnerMappingDefinition;
rel MInnerMappingDefinition.MappingDefinition -> MappingDefinition; rel MInnerMappingDefinition.MappingDefinition -> MappingDefinition;
SendIncrementalObserverEntry ::= <Params:Object> <CompareParams:boolean> <AttributeString>;
...@@ -63,8 +63,8 @@ EndpointDefinition endpoint_definition_type ...@@ -63,8 +63,8 @@ EndpointDefinition endpoint_definition_type
EndpointTarget endpoint_target 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 {: return new UntypedEndpointTarget(type_name, child_name, false); :}
| ID.type_name DOT ID.child_name BRACKET_LEFT ID.attribute_type_name BRACKET_RIGHT | 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, true); :} {: return new UntypedEndpointTarget(type_name, child_name + ":" + attribute_type_name.prettyPrint(), true); :}
| ID.type_name {: return new UntypedEndpointTarget(type_name, "", false); :} | ID.type_name {: return new UntypedEndpointTarget(type_name, "", false); :}
; ;
......
...@@ -110,6 +110,7 @@ aspect RagConnectObserver { ...@@ -110,6 +110,7 @@ aspect RagConnectObserver {
final boolean compareParams; final boolean compareParams;
final Object params; final Object params;
final Runnable attributeCall; final Runnable attributeCall;
//final RagConnectToken connectToken;
final java.util.List<RagConnectToken> connectList = new java.util.ArrayList<>(); final java.util.List<RagConnectToken> connectList = new java.util.ArrayList<>();
RagConnectObserverEntry(ASTNode node, String attributeString, RagConnectObserverEntry(ASTNode node, String attributeString,
...@@ -126,11 +127,11 @@ aspect RagConnectObserver { ...@@ -126,11 +127,11 @@ aspect RagConnectObserver {
} }
boolean baseMembersEqualTo(ASTNode otherNode, String otherAttributeString, boolean baseMembersEqualTo(ASTNode otherNode, String otherAttributeString,
boolean otherCompareParams, Object otherParams) { boolean forceCompareParams, Object otherParams) {
return this.node.equals(otherNode) && return this.node.equals(otherNode) &&
this.attributeString.equals(otherAttributeString) && this.attributeString.equals(otherAttributeString) &&
this.compareParams == otherCompareParams && //this.compareParams == otherCompareParams &&
(!this.compareParams || java.util.Objects.equals(this.params, otherParams)); (!(this.compareParams || forceCompareParams) || java.util.Objects.equals(this.params, otherParams));
} }
} }
...@@ -162,23 +163,16 @@ aspect RagConnectObserver { ...@@ -162,23 +163,16 @@ aspect RagConnectObserver {
node.trace().setReceiver(this); node.trace().setReceiver(this);
} }
void add(RagConnectToken connectToken, ASTNode node, String attributeString, Runnable attributeCall) { void add(RagConnectToken connectToken, ASTNode node, boolean compareParams, Object params,
internal_add(connectToken, node, attributeString, false, null, attributeCall); Runnable attributeCall, String attributeString) {
}
void add(RagConnectToken connectToken, ASTNode node, String attributeString, Object params, Runnable attributeCall) {
internal_add(connectToken, node, attributeString, true, params, attributeCall);
}
private void internal_add(RagConnectToken connectToken, ASTNode node, String attributeString,
boolean compareParams, Object params, Runnable attributeCall) {
{{#configLoggingEnabledForIncremental}} {{#configLoggingEnabledForIncremental}}
{{logDebug}}("** observer add: {{log_}} on {{log_}}{{log_}}", {{logDebug}}("** observer add: {{log_}} on {{log_}}{{log_}}",
node, attributeString, (compareParams ? " (parameterized)" : "")); node, attributeString, (compareParams ? " (parameterized)" : ""));
{{/configLoggingEnabledForIncremental}} {{/configLoggingEnabledForIncremental}}
// either add to an existing entry (with same node, attribute) or create new entry // either add to an existing entry (with same node, attribute, params) or create new entry
boolean needNewEntry = true; boolean needNewEntry = true;
for (RagConnectObserverEntry entry : observedNodes) { for (RagConnectObserverEntry entry : observedNodes) {
if (entry.baseMembersEqualTo(node, attributeString, compareParams, params)) { if (entry.baseMembersEqualTo(node, attributeString, true, params)) {
entry.connectList.add(connectToken); entry.connectList.add(connectToken);
needNewEntry = false; needNewEntry = false;
break; break;
...@@ -193,16 +187,14 @@ aspect RagConnectObserver { ...@@ -193,16 +187,14 @@ aspect RagConnectObserver {
} }
void remove(RagConnectToken connectToken) { void remove(RagConnectToken connectToken) {
RagConnectObserverEntry entryToDelete = null; java.util.List<RagConnectObserverEntry> entriesToDelete = new java.util.ArrayList<>();
for (RagConnectObserverEntry entry : observedNodes) { for (RagConnectObserverEntry entry : observedNodes) {
entry.connectList.remove(connectToken); entry.connectList.remove(connectToken);
if (entry.connectList.isEmpty()) { if (entry.connectList.isEmpty()) {
entryToDelete = entry; entriesToDelete.add(entry);
}
} }
if (entryToDelete != null) {
observedNodes.remove(entryToDelete);
} }
observedNodes.removeAll(entriesToDelete);
} }
@Override @Override
...@@ -256,7 +248,7 @@ aspect RagConnectObserver { ...@@ -256,7 +248,7 @@ aspect RagConnectObserver {
{{/configLoggingEnabledForIncremental}} {{/configLoggingEnabledForIncremental}}
// iterate through list, if matching pair. could maybe be more efficient. // iterate through list, if matching pair. could maybe be more efficient.
for (RagConnectObserverEntry entry : observedNodes) { 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 // hit. call the attribute/nta-token
{{#configLoggingEnabledForIncremental}} {{#configLoggingEnabledForIncremental}}
{{logDebug}}("** observer hit: {{log_}} on {{log_}}", entry.node, entry.attributeString); {{logDebug}}("** observer hit: {{log_}} on {{log_}}", entry.node, entry.attributeString);
......
...@@ -28,6 +28,7 @@ public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParamete ...@@ -28,6 +28,7 @@ public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParamete
final String topic = {{attributeName}}().extractTopic(uri); final String topic = {{attributeName}}().extractTopic(uri);
{{senderName}}.add(() -> { {{senderName}}.add(() -> {
{{#configLoggingEnabledForWrites}} {{#configLoggingEnabledForWrites}}
{{!FIXME circular computation for collection attributes!!}}
{{logDebug}}("[Send] {{entityName}} = {{log_}} -> {{log_}}", {{getterMethodCall}}, {{connectParameterName}}); {{logDebug}}("[Send] {{entityName}} = {{log_}} -> {{log_}}", {{getterMethodCall}}, {{connectParameterName}});
{{/configLoggingEnabledForWrites}} {{/configLoggingEnabledForWrites}}
if ({{lastValueGetterCall}} != null) { if ({{lastValueGetterCall}} != null) {
...@@ -63,18 +64,20 @@ public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParamete ...@@ -63,18 +64,20 @@ public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParamete
if (success) { if (success) {
connectTokenMap.add(this, false, connectToken); connectTokenMap.add(this, false, connectToken);
{{#configIncrementalOptionActive}} {{#configIncrementalOptionActive}}
{{!todo maybe getterMethodName needs to be change for indexed send}} {{#sendIncrementalObserverEntries}}
{{observerInstanceSingletonMethodName}}().add( {{observerInstanceSingletonMethodName}}().add(
connectToken, connectToken,
this, this,
"{{getterMethodName}}{{#IndexBasedListAccess}}_int{{/IndexBasedListAccess}}", {{CompareParams}},
{{#IndexBasedListAccess}}index,{{/IndexBasedListAccess}} {{Params}},
() -> { () -> {
if (this.{{updateMethodName}}({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}})) { if (this.{{updateMethodName}}({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}})) {
this.{{writeMethodName}}({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}}); this.{{writeMethodName}}({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}});
} }
} },
"{{AttributeString}}"
); );
{{/sendIncrementalObserverEntries}}
{{/configIncrementalOptionActive}} {{/configIncrementalOptionActive}}
} }
return success; return success;
......
...@@ -46,7 +46,7 @@ dependencies { ...@@ -46,7 +46,7 @@ dependencies {
ragconnect project(':ragconnect.base') ragconnect project(':ragconnect.base')
testImplementation 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" relast group: 'org.jastadd', name: 'relast', version: "0.3.0-137"
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.4.0' testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.4.0'
......
...@@ -3,6 +3,8 @@ send SenderRoot.simple(String) ; ...@@ -3,6 +3,8 @@ send SenderRoot.simple(String) ;
send SenderRoot.transformed(int) ; send SenderRoot.transformed(int) ;
send SenderRoot.toReferenceType(A) ; send SenderRoot.toReferenceType(A) ;
send SenderRoot.toNTA(A) ; send SenderRoot.toNTA(A) ;
send SenderRoot.circularAttribute(int);
send SenderRoot.collectionAttribute(Set<String>) using SetToString;
AddSuffix maps A a to A {: AddSuffix maps A a to A {:
A result = new A(); A result = new A();
...@@ -20,6 +22,10 @@ AddPlusOne maps int i to int {: ...@@ -20,6 +22,10 @@ AddPlusOne maps int i to int {:
return i + 1; return i + 1;
:} :}
SetToString maps Set<String> set to String {:
return set.toString();
:}
receive ReceiverRoot.FromBasic; receive ReceiverRoot.FromBasic;
receive ReceiverRoot.FromSimpleNoMapping; receive ReceiverRoot.FromSimpleNoMapping;
receive ReceiverRoot.FromSimpleWithMapping using AddStringSuffix; receive ReceiverRoot.FromSimpleWithMapping using AddStringSuffix;
...@@ -29,3 +35,7 @@ receive ReceiverRoot.FromReferenceTypeNoMapping; ...@@ -29,3 +35,7 @@ receive ReceiverRoot.FromReferenceTypeNoMapping;
receive ReceiverRoot.FromReferenceTypeWithMapping using AddSuffix; receive ReceiverRoot.FromReferenceTypeWithMapping using AddSuffix;
receive ReceiverRoot.FromNTANoMapping; receive ReceiverRoot.FromNTANoMapping;
receive ReceiverRoot.FromNTAWithMapping using AddSuffix; receive ReceiverRoot.FromNTAWithMapping using AddSuffix;
receive ReceiverRoot.FromCircularNoMapping;
receive ReceiverRoot.FromCircularWithMapping using AddPlusOne;
receive ReceiverRoot.FromCollectionNoMapping;
receive ReceiverRoot.FromCollectionWithMapping using AddStringSuffix;
import java.util.Set;
import java.util.HashSet;
aspect Computation { aspect Computation {
syn String SenderRoot.basic() = getInput(); syn String SenderRoot.basic() = getInput();
syn String SenderRoot.simple() = getInput() + "Post"; syn String SenderRoot.simple() = getInput() + "Post";
...@@ -18,6 +20,12 @@ aspect Computation { ...@@ -18,6 +20,12 @@ aspect Computation {
result.setInner(inner); result.setInner(inner);
return result; 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 { aspect MakeCodeCompile {
......
Root ::= SenderRoot* ReceiverRoot; Root ::= SenderRoot* ReceiverRoot;
SenderRoot ::= <Input> ; SenderRoot ::= <Input> A* ;
ReceiverRoot ::= ReceiverRoot ::=
<FromBasic> <FromBasic>
<FromSimpleNoMapping> <FromSimpleNoMapping>
...@@ -9,6 +9,10 @@ ReceiverRoot ::= ...@@ -9,6 +9,10 @@ ReceiverRoot ::=
FromReferenceTypeNoMapping:A FromReferenceTypeNoMapping:A
FromReferenceTypeWithMapping:A FromReferenceTypeWithMapping:A
FromNTANoMapping:A FromNTANoMapping:A
FromNTAWithMapping:A ; FromNTAWithMapping:A
<FromCircularNoMapping:int>
<FromCircularWithMapping:int>
<FromCollectionNoMapping>
<FromCollectionWithMapping> ;
A ::= <Value> Inner ; A ::= <Value> Inner ;
Inner ::= <InnerValue> ; Inner ::= <InnerValue> ;
send indexed SenderRoot.MultipleA; send indexed SenderRoot.MultipleA;
send indexed SenderRoot.MultipleAWithSuffix using AddSuffix; send indexed SenderRoot.MultipleAWithSuffix using AddSuffix;
send indexed SenderRoot.A;
send indexed SenderRoot.ComputedA using AddSuffix;
AddSuffix maps A a to A {: AddSuffix maps A a to A {:
A result = new A(); A result = new A();
...@@ -12,3 +13,5 @@ AddSuffix maps A a to A {: ...@@ -12,3 +13,5 @@ AddSuffix maps A a to A {:
receive indexed ReceiverRoot.ManyA; receive indexed ReceiverRoot.ManyA;
receive indexed ReceiverRoot.ManyAWithSuffix; receive indexed ReceiverRoot.ManyAWithSuffix;
receive indexed ReceiverRoot.FromNTA;
receive indexed ReceiverRoot.FromNTAWithSuffix;
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 { aspect NameResolution {
// overriding customID guarantees to produce the same JSON representation for equal lists // overriding customID guarantees to produce the same JSON representation for equal lists
// otherwise, the value for id is different each time // otherwise, the value for id is different each time
......
Root ::= SenderRoot ReceiverRoot; Root ::= SenderRoot ReceiverRoot;
SenderRoot ::= MultipleA:A* MultipleAWithSuffix:A* ; SenderRoot ::= MultipleA:A* MultipleAWithSuffix:A* /A*/ /ComputedA:A*/ ;
ReceiverRoot ::= ManyA:A* ManyAWithSuffix:A* ; ReceiverRoot ::= ManyA:A* ManyAWithSuffix:A* FromNTA:A* FromNTAWithSuffix:A* ;
A ::= <Value> Inner ; A ::= <Value> Inner ;
Inner ::= <InnerValue> ; Inner ::= <InnerValue> ;
...@@ -25,7 +25,7 @@ public abstract class AbstractMqttTest extends RagConnectTest { ...@@ -25,7 +25,7 @@ public abstract class AbstractMqttTest extends RagConnectTest {
/** /**
* if the initial/current value shall be sent upon connecting * if the initial/current value shall be sent upon connecting
*/ */
private boolean writeCurrentValue; protected boolean writeCurrentValue;
public boolean isWriteCurrentValue() { public boolean isWriteCurrentValue() {
return writeCurrentValue; return writeCurrentValue;
...@@ -58,7 +58,7 @@ public abstract class AbstractMqttTest extends RagConnectTest { ...@@ -58,7 +58,7 @@ public abstract class AbstractMqttTest extends RagConnectTest {
@Tag("mqtt") @Tag("mqtt")
@RepeatedIfExceptionsTest(repeats = TEST_REPETITIONS) @RepeatedIfExceptionsTest(repeats = TEST_REPETITIONS)
public final void testCommunicateSendInitialValue() throws IOException, InterruptedException { public void testCommunicateSendInitialValue() throws IOException, InterruptedException {
this.writeCurrentValue = true; this.writeCurrentValue = true;
createModel(); createModel();
...@@ -76,7 +76,7 @@ public abstract class AbstractMqttTest extends RagConnectTest { ...@@ -76,7 +76,7 @@ public abstract class AbstractMqttTest extends RagConnectTest {
@Tag("mqtt") @Tag("mqtt")
@RepeatedIfExceptionsTest(repeats = TEST_REPETITIONS) @RepeatedIfExceptionsTest(repeats = TEST_REPETITIONS)
public final void testCommunicateOnlyUpdatedValue() throws IOException, InterruptedException { public void testCommunicateOnlyUpdatedValue() throws IOException, InterruptedException {
this.writeCurrentValue = false; this.writeCurrentValue = false;
createModel(); createModel();
......
...@@ -9,8 +9,10 @@ import java.util.Objects; ...@@ -9,8 +9,10 @@ import java.util.Objects;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.function.Supplier;
import static java.util.function.Predicate.isEqual; 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.jastadd.ragconnect.tests.TestUtils.*;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
...@@ -32,15 +34,22 @@ public class AttributeTest extends AbstractMqttTest { ...@@ -32,15 +34,22 @@ public class AttributeTest extends AbstractMqttTest {
private static final String TOPIC_REFERENCE_TYPE_WITH_MAPPING = "attr/a/ref/mapped"; 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_NO_MAPPING = "attr/a/nta/plain";
private static final String TOPIC_NTA_WITH_MAPPING = "attr/a/nta/mapped"; 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 = "initial";
private static final String INITIAL_STRING_FOR_INT = "1"; 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_BASIC = "basic";
private static final String CHECK_SIMPLE = "simple"; private static final String CHECK_SIMPLE = "simple";
private static final String CHECK_TRANSFORMED = "transformed"; private static final String CHECK_TRANSFORMED = "transformed";
private static final String CHECK_A = "a"; private static final String CHECK_A = "a";
private static final String CHECK_NTA = "nta"; 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 MqttHandler handler;
private ReceiverData data; private ReceiverData data;
...@@ -82,6 +91,8 @@ public class AttributeTest extends AbstractMqttTest { ...@@ -82,6 +91,8 @@ public class AttributeTest extends AbstractMqttTest {
.setCheckForString(CHECK_TRANSFORMED, this::checkTransformed) .setCheckForString(CHECK_TRANSFORMED, this::checkTransformed)
.setCheckForString(CHECK_A, this::checkA) .setCheckForString(CHECK_A, this::checkA)
.setCheckForString(CHECK_NTA, this::checkNta) .setCheckForString(CHECK_NTA, this::checkNta)
.setCheckForString(CHECK_CIRCULAR, this::checkCircular)
.setCheckForString(CHECK_COLLECTION, this::checkCollection)
; ;
// connect receive // connect receive
...@@ -94,14 +105,22 @@ public class AttributeTest extends AbstractMqttTest { ...@@ -94,14 +105,22 @@ public class AttributeTest extends AbstractMqttTest {
assertTrue(receiverRoot.connectFromReferenceTypeWithMapping(mqttUri(TOPIC_REFERENCE_TYPE_WITH_MAPPING))); assertTrue(receiverRoot.connectFromReferenceTypeWithMapping(mqttUri(TOPIC_REFERENCE_TYPE_WITH_MAPPING)));
assertTrue(receiverRoot.connectFromNTANoMapping(mqttUri(TOPIC_NTA_NO_MAPPING))); assertTrue(receiverRoot.connectFromNTANoMapping(mqttUri(TOPIC_NTA_NO_MAPPING)));
assertTrue(receiverRoot.connectFromNTAWithMapping(mqttUri(TOPIC_NTA_WITH_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) // connect send, and wait to receive (if writeCurrentValue is set)
assertTrue(senderString.connectBasic(mqttUri(TOPIC_BASIC), isWriteCurrentValue())); assertTrue(senderString.connectBasic(mqttUri(TOPIC_BASIC), isWriteCurrentValue()));
assertTrue(senderString.connectSimple(mqttUri(TOPIC_SIMPLE_NO_MAPPING), isWriteCurrentValue())); assertTrue(senderString.connectSimple(mqttUri(TOPIC_SIMPLE_NO_MAPPING), isWriteCurrentValue()));
assertTrue(senderString.connectSimple(mqttUri(TOPIC_SIMPLE_WITH_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_NO_MAPPING), isWriteCurrentValue()));
assertTrue(senderInt.connectTransformed(mqttUri(TOPIC_TRANSFORMED_WITH_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_NO_MAPPING), isWriteCurrentValue()));
assertTrue(senderA.connectToReferenceType(mqttUri(TOPIC_REFERENCE_TYPE_WITH_MAPPING), isWriteCurrentValue())); assertTrue(senderA.connectToReferenceType(mqttUri(TOPIC_REFERENCE_TYPE_WITH_MAPPING), isWriteCurrentValue()));
...@@ -111,58 +130,68 @@ public class AttributeTest extends AbstractMqttTest { ...@@ -111,58 +130,68 @@ public class AttributeTest extends AbstractMqttTest {
waitForValue(senderString.basic(), receiverRoot::getFromBasic); waitForValue(senderString.basic(), receiverRoot::getFromBasic);
waitForValue(senderString.simple(), receiverRoot::getFromSimpleNoMapping); waitForValue(senderString.simple(), receiverRoot::getFromSimpleNoMapping);
waitForValue(senderInt.transformed(), receiverRoot::getFromTransformedNoMapping); waitForValue(senderInt.transformed(), receiverRoot::getFromTransformedNoMapping);
waitForNonNull(receiverRoot::getFromCollectionNoMapping);
waitForNonNull(receiverRoot::getFromReferenceTypeNoMapping); waitForNonNull(receiverRoot::getFromReferenceTypeNoMapping);
waitForNonNull(receiverRoot::getFromNTANoMapping); waitForNonNull(receiverRoot::getFromNTANoMapping);
} }
@Override @Override
protected void communicateSendInitialValue() throws IOException, InterruptedException { protected void communicateSendInitialValue() throws IOException, InterruptedException {
// basic, simple(2) <-- senderString // 13 = basic, simple(2), collection(2), transformed(2), circular(2), ref-type(2), nta(2)
// transformed(2) <-- senderInt checker.addToNumberOfValues(13)
// ref-type(2), nta(2) <-- senderA
checker.addToNumberOfValues(9)
.put(CHECK_BASIC, INITIAL_STRING) .put(CHECK_BASIC, INITIAL_STRING)
.put(CHECK_SIMPLE, INITIAL_STRING + "Post") .put(CHECK_SIMPLE, INITIAL_STRING + "Post")
.put(CHECK_TRANSFORMED, INITIAL_STRING_FOR_INT) .put(CHECK_TRANSFORMED, INITIAL_STRING_FOR_INT)
.put(CHECK_A, INITIAL_STRING) .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(); communicateBoth();
} }
@Override @Override
protected void communicateOnlyUpdatedValue() throws IOException, InterruptedException { 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) checker.put(CHECK_BASIC, (String) null)
.put(CHECK_SIMPLE, (String) null) .put(CHECK_SIMPLE, (String) null)
.put(CHECK_TRANSFORMED, (String) null) .put(CHECK_TRANSFORMED, (String) null)
.put(CHECK_A, (String) null) .put(CHECK_A, (String) null)
.put(CHECK_NTA, (String) null); .put(CHECK_NTA, (String) null)
.put(CHECK_COLLECTION, (String) null);
communicateBoth(); communicateBoth();
} }
private void communicateBoth() throws IOException { private void communicateBoth() throws IOException {
// basic, simple(2), collection(2) <-- senderString
// transformed(2), circular(2) <-- senderInt
// ref-type(2), nta(2) <-- senderA
checker.check(); checker.check();
senderString.setInput("test-01"); senderString.setInput("test-01");
checker.addToNumberOfValues(3) checker.addToNumberOfValues(5) // basic, simple(2), collection(2)
.put(CHECK_BASIC, "test-01") .put(CHECK_BASIC, "test-01")
.put(CHECK_SIMPLE, "test-01Post") .put(CHECK_SIMPLE, "test-01Post")
.put(CHECK_COLLECTION, "[test-01]")
.check(); .check();
// no change for same value
senderString.setInput("test-01"); senderString.setInput("test-01");
checker.check(); checker.check();
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"); senderInt.setInput("20");
checker.addToNumberOfValues(2) checker.addToNumberOfValues(4) // transformed(2), circular(2)
.put(CHECK_TRANSFORMED, "20") .put(CHECK_TRANSFORMED, "20")
.put(CHECK_CIRCULAR, "22")
.check(); .check();
senderA.setInput("test-03"); senderA.setInput("test-03");
checker.addToNumberOfValues(4) checker.addToNumberOfValues(4) // ref-type(2), nta(2)
.put(CHECK_A, "test-03") .put(CHECK_A, "test-03")
.put(CHECK_NTA, "test-03") .put(CHECK_NTA, "test-03")
.check(); .check();
...@@ -170,14 +199,22 @@ public class AttributeTest extends AbstractMqttTest { ...@@ -170,14 +199,22 @@ public class AttributeTest extends AbstractMqttTest {
assertTrue(senderString.disconnectSimple(mqttUri(TOPIC_SIMPLE_NO_MAPPING))); assertTrue(senderString.disconnectSimple(mqttUri(TOPIC_SIMPLE_NO_MAPPING)));
assertTrue(senderString.disconnectSimple(mqttUri(TOPIC_SIMPLE_WITH_MAPPING))); assertTrue(senderString.disconnectSimple(mqttUri(TOPIC_SIMPLE_WITH_MAPPING)));
senderString.setInput("test-04"); senderString.setInput("test-04");
checker.incNumberOfValues() checker.addToNumberOfValues(3) // basic, collection(2)
.put(CHECK_BASIC, "test-04") .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(); .check();
assertTrue(senderA.disconnectToNTA(mqttUri(TOPIC_NTA_NO_MAPPING))); assertTrue(senderA.disconnectToNTA(mqttUri(TOPIC_NTA_NO_MAPPING)));
senderA.setInput("test-05"); senderA.setInput("test-06");
checker.addToNumberOfValues(3) checker.addToNumberOfValues(3)
.put(CHECK_A, "test-05") .put(CHECK_A, "test-06")
.check(); .check();
} }
...@@ -208,18 +245,17 @@ public class AttributeTest extends AbstractMqttTest { ...@@ -208,18 +245,17 @@ public class AttributeTest extends AbstractMqttTest {
} }
private void checkTransformed(String name, String expected) { 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) { if (expected != null) {
assertEquals(Integer.parseInt(expected), assertEquals(Integer.parseInt(expected), noMapping.get(), name);
receiverRoot.getFromTransformedNoMapping(), "transformed"); assertEquals(Integer.parseInt(expected) + 1, withMapping.get(), name + " mapped");
assertEquals(Integer.parseInt(expected) + 1,
receiverRoot.getFromTransformedWithMapping(), "transformed mapped");
} else { } else {
assertEquals(0, assertEquals(0, noMapping.get(), name + " null");
receiverRoot.getFromTransformedNoMapping(), "transformed null"); assertEquals(0, withMapping.get(), name + " mapped null");
assertEquals(0,
receiverRoot.getFromTransformedWithMapping(), "transformed mapped null");
} }
} }
private void checkA(String name, String expected) { private void checkA(String name, String expected) {
...@@ -245,6 +281,30 @@ public class AttributeTest extends AbstractMqttTest { ...@@ -245,6 +281,30 @@ 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) {
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) { private void assertA(String expectedValue, String expectedInner, A actual, String message) {
assertEquals(expectedValue, actual.getValue(), message + " value"); assertEquals(expectedValue, actual.getValue(), message + " value");
assertEquals(expectedInner, actual.getInner().getInnerValue(), message + " inner"); assertEquals(expectedInner, actual.getInner().getInnerValue(), message + " inner");
......
package org.jastadd.ragconnect.tests; package org.jastadd.ragconnect.tests;
import indexedSendInc.ast.*; import indexedSendInc.ast.*;
import io.github.artsok.RepeatedIfExceptionsTest;
import org.assertj.core.api.Assertions; import org.assertj.core.api.Assertions;
import org.assertj.core.groups.Tuple;
import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Tag;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.function.Supplier;
import static org.assertj.core.groups.Tuple.tuple; import static org.assertj.core.groups.Tuple.tuple;
import static org.jastadd.ragconnect.tests.TestUtils.*; import static org.jastadd.ragconnect.tests.TestUtils.*;
...@@ -33,6 +36,8 @@ public class IndexedSendTest extends AbstractMqttTest { ...@@ -33,6 +36,8 @@ public class IndexedSendTest extends AbstractMqttTest {
private static final String CHECK_MANY_A = "many-a"; private static final String CHECK_MANY_A = "many-a";
private static final String CHECK_WITH_SUFFIX = "many-a-with-suffix"; private static final String CHECK_WITH_SUFFIX = "many-a-with-suffix";
private boolean connectNTAsInstead;
private MqttHandler handler; private MqttHandler handler;
private ReceiverData data; private ReceiverData data;
private TestUtils.TestChecker checker; private TestUtils.TestChecker checker;
...@@ -83,70 +88,99 @@ public class IndexedSendTest extends AbstractMqttTest { ...@@ -83,70 +88,99 @@ public class IndexedSendTest extends AbstractMqttTest {
checker = new TestChecker(); checker = new TestChecker();
checker.setActualNumberOfValues(() -> data.numberOfValues) checker.setActualNumberOfValues(() -> data.numberOfValues)
.setCheckForTuple(CHECK_MANY_A, (name, expected) -> .setCheckForTuple(CHECK_MANY_A, (name, expected) ->
Assertions.assertThat(receiverRoot.getManyAList()).extracting("Value") checkList(name, expected, receiverRoot::getManyAList))
.as(name)
.containsExactlyElementsOf(expected.toList()))
.setCheckForTuple(CHECK_WITH_SUFFIX, (name, expected) -> .setCheckForTuple(CHECK_WITH_SUFFIX, (name, expected) ->
Assertions.assertThat(receiverRoot.getManyAWithSuffixList()).extracting("Value") checkList(name, expected, receiverRoot::getManyAWithSuffixList));
.as(name)
.containsExactlyElementsOf(expected.toList()));
// connect receive // connect receive
assertTrue(receiverRoot.connectManyA(mqttUri(TOPIC_A_MANY_NORMAL_WILDCARD))); assertTrue(receiverRoot.connectManyA(mqttUri(TOPIC_A_MANY_NORMAL_WILDCARD)));
assertTrue(receiverRoot.connectManyAWithSuffix(mqttUri(TOPIC_A_MANY_SUFFIX_WILDCARD))); assertTrue(receiverRoot.connectManyAWithSuffix(mqttUri(TOPIC_A_MANY_SUFFIX_WILDCARD)));
// connect send, and wait to receive (if writeCurrentValue is set) // connect send, and wait to receive (if writeCurrentValue is set)
assertTrue(senderRoot.connectMultipleA(mqttUri(TOPIC_A_MANY_NORMAL_0), 0, isWriteCurrentValue())); assertConnectAOrMultipleA(TOPIC_A_MANY_NORMAL_0, 0);
waitForValue(receiverRoot::getNumManyA, 1); waitForValue(receiverRoot::getNumManyA, 1);
assertTrue(senderRoot.connectMultipleA(mqttUri(TOPIC_A_MANY_NORMAL_1), 1, isWriteCurrentValue())); assertConnectAOrMultipleA(TOPIC_A_MANY_NORMAL_1, 1);
waitForValue(receiverRoot::getNumManyA, 2); waitForValue(receiverRoot::getNumManyA, 2);
assertTrue(senderRoot.connectMultipleAWithSuffix(mqttUri(TOPIC_A_MANY_SUFFIX_0), 0, isWriteCurrentValue())); assertConnectComputedAOrMultipleAWithSuffix(TOPIC_A_MANY_SUFFIX_0, 0);
waitForValue(receiverRoot::getNumManyAWithSuffix, 1); waitForValue(receiverRoot::getNumManyAWithSuffix, 1);
assertTrue(senderRoot.connectMultipleAWithSuffix(mqttUri(TOPIC_A_MANY_SUFFIX_1), 1, isWriteCurrentValue())); assertConnectComputedAOrMultipleAWithSuffix(TOPIC_A_MANY_SUFFIX_1, 1);
waitForValue(receiverRoot::getNumManyAWithSuffix, 2); waitForValue(receiverRoot::getNumManyAWithSuffix, 2);
} }
private void assertConnectAOrMultipleA(String topic, int index) throws IOException {
assertTrue(connectNTAsInstead ?
senderRoot.connectA(mqttUri(topic), index, isWriteCurrentValue()) :
senderRoot.connectMultipleA(mqttUri(topic), index, isWriteCurrentValue()));
}
private void assertConnectComputedAOrMultipleAWithSuffix(String topic, int index) throws IOException {
assertTrue(connectNTAsInstead ?
senderRoot.connectComputedA(mqttUri(topic), index, isWriteCurrentValue()) :
senderRoot.connectMultipleAWithSuffix(mqttUri(topic), index, isWriteCurrentValue()));
}
private void assertDisconnectComputedAOrMultipleAWithSuffix(String topic) throws IOException {
assertTrue(connectNTAsInstead ?
senderRoot.disconnectComputedA(mqttUri(topic)) :
senderRoot.disconnectMultipleAWithSuffix(mqttUri(topic)));
}
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) { private void waitForValue(Callable<Integer> callable, int expectedValue) {
if (isWriteCurrentValue()) { if (isWriteCurrentValue()) {
awaitMqtt().until(callable, Predicate.isEqual(expectedValue)); awaitMqtt().until(callable, Predicate.isEqual(expectedValue));
} }
} }
@Override @Tag("mqtt")
protected void communicateSendInitialValue() throws IOException, InterruptedException { @RepeatedIfExceptionsTest(repeats = TEST_REPETITIONS)
checker.addToNumberOfValues(4) public void testCommunicateSendInitialValueWithNTAs() throws IOException, InterruptedException {
.put(CHECK_MANY_A, tuple("am0", "am1")) this.writeCurrentValue = true;
.put(CHECK_WITH_SUFFIX, tuple("am0post", "am1post")); this.connectNTAsInstead = true;
listA0.setValue("changedValue"); try {
checker.incNumberOfValues().put(CHECK_MANY_A, tuple("changedValue", "am1")).check(); createModel();
setupReceiverAndConnect();
// setting same value must not change data, and must not trigger a new sent message logger.info("Calling communicateSendInitialValue");
listA0.setValue("changedValue"); communicateSendInitialValue();
checker.check(); } finally {
this.connectNTAsInstead = false;
}
}
listA1.setValue(""); @Tag("mqtt")
checker.incNumberOfValues().put(CHECK_MANY_A, tuple("changedValue", "")).check(); @RepeatedIfExceptionsTest(repeats = TEST_REPETITIONS)
public void testCommunicateOnlyUpdatedValueWithNTAs() throws IOException, InterruptedException {
this.writeCurrentValue = false;
this.connectNTAsInstead = true;
listA1InSuffix.setValue("re"); try {
checker.incNumberOfValues().put(CHECK_WITH_SUFFIX, tuple("am0post", "repost")).check(); createModel();
setupReceiverAndConnect();
// adding a new element does not automatically send it logger.info("Calling communicateOnlyUpdatedValue");
A listA3InSuffix = createA("out"); communicateOnlyUpdatedValue();
senderRoot.addMultipleAWithSuffix(listA3InSuffix); } finally {
checker.check(); this.connectNTAsInstead = false;
}
}
// only after connecting it, the element gets sent @Override
assertTrue(senderRoot.connectMultipleAWithSuffix(mqttUri(TOPIC_A_MANY_SUFFIX_2), 2, true)); protected void communicateSendInitialValue() throws IOException, InterruptedException {
checker.incNumberOfValues().put(CHECK_WITH_SUFFIX, tuple("am0post", "repost", "outpost")).check(); checker.addToNumberOfValues(4)
.put(CHECK_MANY_A, tuple("am0", "am1"))
.put(CHECK_WITH_SUFFIX, tuple("am0post", "am1post"));
// after successful disconnect, no messages will be sent communicateBoth("am1", "am0post");
assertTrue(senderRoot.disconnectMultipleAWithSuffix(mqttUri(TOPIC_A_MANY_SUFFIX_0)));
listA0InSuffix.setValue("willBeIgnored");
checker.check();
} }
@Override @Override
...@@ -154,10 +188,10 @@ public class IndexedSendTest extends AbstractMqttTest { ...@@ -154,10 +188,10 @@ public class IndexedSendTest extends AbstractMqttTest {
checker.put(CHECK_MANY_A, tuple()) checker.put(CHECK_MANY_A, tuple())
.put(CHECK_WITH_SUFFIX, 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.ManyA <-- Root.MultipleA
// Sink.ManyAWithSuffix <-- Root.MultipleAWithSuffix // Sink.ManyAWithSuffix <-- Root.MultipleAWithSuffix
checker.check(); checker.check();
...@@ -165,7 +199,10 @@ public class IndexedSendTest extends AbstractMqttTest { ...@@ -165,7 +199,10 @@ public class IndexedSendTest extends AbstractMqttTest {
assertEquals(listA0.getValue(), senderRoot._ragconnect_MultipleA(0).getValue()); assertEquals(listA0.getValue(), senderRoot._ragconnect_MultipleA(0).getValue());
listA0.setValue("changedValue"); listA0.setValue("changedValue");
assertEquals(listA0.getValue(), senderRoot._ragconnect_MultipleA(0).getValue()); 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();
// setting same value must not change data, and must not trigger a new sent message // setting same value must not change data, and must not trigger a new sent message
listA0.setValue("changedValue"); listA0.setValue("changedValue");
...@@ -176,21 +213,44 @@ public class IndexedSendTest extends AbstractMqttTest { ...@@ -176,21 +213,44 @@ public class IndexedSendTest extends AbstractMqttTest {
// first element in suffix-list // first element in suffix-list
listA1InSuffix.setValue("re"); 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 // adding a new element does not automatically send it
A listA3InSuffix = createA("out"); A listA2InSuffix = createA("out");
senderRoot.addMultipleAWithSuffix(listA3InSuffix); senderRoot.addMultipleAWithSuffix(listA2InSuffix);
checker.check(); checker.check();
// only after connecting it, the element gets sent // only after connecting it, the element gets sent (for SendInitialValue case)
assertTrue(senderRoot.connectMultipleAWithSuffix(mqttUri(TOPIC_A_MANY_SUFFIX_2), 2, true)); assertConnectComputedAOrMultipleAWithSuffix(TOPIC_A_MANY_SUFFIX_2, 2);
checker.incNumberOfValues().put(CHECK_WITH_SUFFIX, tuple("repost", "outpost")).check(); if (isWriteCurrentValue()) {
checker.incNumberOfValues()
.put(CHECK_WITH_SUFFIX, suffixAtIndex0 != null ? tuple(suffixAtIndex0, "repost", "outpost") : tuple("repost", "outpost"));
}
checker.check();
// after successful disconnect, no messages will be sent // changing the value of the newly added element will send it
assertTrue(senderRoot.disconnectMultipleAWithSuffix(mqttUri(TOPIC_A_MANY_SUFFIX_0))); listA2InSuffix.setValue("goal");
checker.incNumberOfValues()
.put(CHECK_WITH_SUFFIX, suffixAtIndex0 != null ? tuple(suffixAtIndex0, "repost", "goalpost") : tuple("repost", "goalpost"));
checker.check();
// after successful disconnect for index 0, no messages will be sent
assertDisconnectComputedAOrMultipleAWithSuffix(TOPIC_A_MANY_SUFFIX_0);
listA0InSuffix.setValue("willBeIgnored"); listA0InSuffix.setValue("willBeIgnored");
checker.check(); checker.check();
// for index 1 (not disconnected), messages will be sent still
listA1InSuffix.setValue("sign");
checker.incNumberOfValues()
.put(CHECK_WITH_SUFFIX, suffixAtIndex0 != null ? tuple(suffixAtIndex0, "signpost", "goalpost") : tuple("signpost", "goalpost"))
.check();
// after successful disconnect for index 1, no messages will be sent anymore
assertDisconnectComputedAOrMultipleAWithSuffix(TOPIC_A_MANY_SUFFIX_1);
listA1InSuffix.setValue("willBeIgnored");
checker.check();
} }
@Override @Override
......
...@@ -22,13 +22,12 @@ import java.util.*; ...@@ -22,13 +22,12 @@ import java.util.*;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static java.util.Collections.addAll;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.util.Lists.newArrayList;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
...@@ -98,6 +97,12 @@ public class TestUtils { ...@@ -98,6 +97,12 @@ public class TestUtils {
return outPath; return outPath;
} }
public static <T> String prettyPrint(Iterable<T> aList, Function<T, String> elementPrinter) {
StringJoiner sj = new StringJoiner(", ", "[", "]");
aList.forEach(element -> sj.add(elementPrinter.apply(element)));
return sj.toString();
}
public static void assertLinesMatch(String directory, String expectedName, String out) throws IOException { public static void assertLinesMatch(String directory, String expectedName, String out) throws IOException {
Path expectedPath = Paths.get(TestUtils.INPUT_DIRECTORY_PREFIX) Path expectedPath = Paths.get(TestUtils.INPUT_DIRECTORY_PREFIX)
.resolve(directory) .resolve(directory)
...@@ -226,20 +231,30 @@ public class TestUtils { ...@@ -226,20 +231,30 @@ public class TestUtils {
} }
public TestChecker setActual(String name, Callable<T> actual) { public TestChecker setActual(String name, Callable<T> actual) {
values.computeIfAbsent(name, ActualAndExpected::new).actual = actual; _computeIfAbsent(name).actual = actual;
return parent; return parent;
} }
public TestChecker setCheck(String name, BiConsumer<String, T> check) { public TestChecker setCheck(String name, BiConsumer<String, T> check) {
values.computeIfAbsent(name, ActualAndExpected::new).customCheck = check; _computeIfAbsent(name).customCheck = check;
return parent; return parent;
} }
public TestChecker put(String name, T expected) { public TestChecker put(String name, T expected) {
values.computeIfAbsent(name, ActualAndExpected::new).expected = expected; _computeIfAbsent(name).expected = expected;
return parent; 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) { ActualAndExpected<T> get(String name) {
return values.get(name); return values.get(name);
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment