diff --git a/pages/docs/using.md b/pages/docs/using.md index 87d91925f1fdd176faa57f724193dd24a1f5accc..5854d4708679dd27af9036336d039b0d2bb3bf8f 100644 --- a/pages/docs/using.md +++ b/pages/docs/using.md @@ -244,3 +244,14 @@ assertEquals(receiverRoot.getAList(), list("1", "other")); // after receiving "new" on topic "some/topic/one" 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. diff --git a/ragconnect.base/.gitignore b/ragconnect.base/.gitignore index 87b4cdd3d7c6a41502ca98703abeeb69a1d536fb..4c0fcfac7f2f2c14ac6e81d173d3a2f7d9d99a55 100644 --- a/ragconnect.base/.gitignore +++ b/ragconnect.base/.gitignore @@ -3,3 +3,4 @@ src/gen-res/ src/gen/ out/ *.class +/parameters.txt diff --git a/ragconnect.base/build.gradle b/ragconnect.base/build.gradle index 7dfe1e77b8599daad3915c6c94c0f995937f0edb..c138189d065693a908cd0e276dcddc3eb2320313 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' } @@ -187,6 +187,23 @@ ext { } 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 { manifest.attributes "Main-Class": "${mainClassName}" } diff --git a/ragconnect.base/src/main/jastadd/Intermediate.jadd b/ragconnect.base/src/main/jastadd/Intermediate.jadd index 95f9b865b7217091537e954fa8ac98c292e45822..1a24fa97c1c561bd841944a82231fe2fd40aad69 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(), 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 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 compareParams, Object params) { + return new SendIncrementalObserverEntry() + .setParams(params) + .setCompareParams(compareParams) + .setAttributeString(attributeString); + } } aspect MustacheTokenComponent { diff --git a/ragconnect.base/src/main/jastadd/Intermediate.relast b/ragconnect.base/src/main/jastadd/Intermediate.relast index 8fa32f92b7bc1fdc0b6fad196b82523a660dc6c2..0307d8d9a45fc40e2d6fcfa03ab06d25578687d6 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 f822bf01139341080c65f5026bd5661124cafd4e..55eac248e5f48d39aa38db6ac3cd20df61930220 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 7e054a4927b527e6d32a30e32a6bffa71d86b97f..c428021bf1eab32bf8baac8585a3f801ba8ad6ad 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,23 +163,16 @@ 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, 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) { + void add(RagConnectToken connectToken, ASTNode node, boolean compareParams, Object params, + Runnable attributeCall, String attributeString) { {{#configLoggingEnabledForIncremental}} {{logDebug}}("** observer add: {{log_}} on {{log_}}{{log_}}", node, attributeString, (compareParams ? " (parameterized)" : "")); {{/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; for (RagConnectObserverEntry entry : observedNodes) { - if (entry.baseMembersEqualTo(node, attributeString, compareParams, params)) { + if (entry.baseMembersEqualTo(node, attributeString, true, params)) { entry.connectList.add(connectToken); needNewEntry = false; break; @@ -193,16 +187,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 +248,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 abcbdffe2d4f7aaff5dfce8700cb11e72ec8d83d..2b8a6eb3536e81d9621f56866a375462562cf299 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,20 @@ 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}}, () -> { 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 ad9cff1725069ac97ba81bf008bbdaa712f18b09..5068a66dbc306052951b3b076879dc1ca3edcd83 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 03688a96bdb327076e974498d3ce62c9497f89be..a463af6c3ae24241a2dd7bc019bbbb21479a4d09 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 34b1b45e7a181d0368df2da491c25c197cc009b8..4ad766889588ea23c2abbda5d938359c7c62d4f8 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 7afc9b7745c0da1cab4af5140fdddb2e5981f3b1..e3a3a67123589a40446651ed27bab5e6494e90f8 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 3ff49e174a5afddac148e0a99374255ec0b61646..9e390cd63028cc0708969285792222a42f11b2fd 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 d37dd045dbf11754fbd9066faae1613ea2a95e97..fc8893efc9579eeebc415c2b51450f66092a2055 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 9480f18d9efcd27452e548044f4caca335d4312a..2e9924b0316578cb221e9778cc28fec83cfddc03 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 31335e5482eee4a3ef8737d28694a06a2430aba3..b8f31b89aced9f362f6b6961fd476ccea6ab7660 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 1c10bd428a8e47087e9081516a6e4e56e3eeaf77..20ff9f3f791c35b47ac24ead95990076edd41a93 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.*; @@ -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_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 +91,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 +105,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 +130,68 @@ 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(); + 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 +199,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 +245,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 +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) { 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 a15d9990c44d0856c2f1c7af3f022e422d745c71..dc1bdda4a03c1b228a1c0214811ebb00b417f004 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 @@ -1,13 +1,16 @@ package org.jastadd.ragconnect.tests; import indexedSendInc.ast.*; +import io.github.artsok.RepeatedIfExceptionsTest; import org.assertj.core.api.Assertions; +import org.assertj.core.groups.Tuple; import org.junit.jupiter.api.Tag; import java.io.IOException; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import java.util.function.Predicate; +import java.util.function.Supplier; import static org.assertj.core.groups.Tuple.tuple; import static org.jastadd.ragconnect.tests.TestUtils.*; @@ -33,6 +36,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 +88,99 @@ 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())); + assertConnectAOrMultipleA(TOPIC_A_MANY_NORMAL_0, 0); 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); - assertTrue(senderRoot.connectMultipleAWithSuffix(mqttUri(TOPIC_A_MANY_SUFFIX_0), 0, isWriteCurrentValue())); + assertConnectComputedAOrMultipleAWithSuffix(TOPIC_A_MANY_SUFFIX_0, 0); 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); } + 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) { if (isWriteCurrentValue()) { awaitMqtt().until(callable, Predicate.isEqual(expectedValue)); } } - @Override - protected void communicateSendInitialValue() throws IOException, InterruptedException { - checker.addToNumberOfValues(4) - .put(CHECK_MANY_A, tuple("am0", "am1")) - .put(CHECK_WITH_SUFFIX, tuple("am0post", "am1post")); + @Tag("mqtt") + @RepeatedIfExceptionsTest(repeats = TEST_REPETITIONS) + public void testCommunicateSendInitialValueWithNTAs() throws IOException, InterruptedException { + this.writeCurrentValue = true; + this.connectNTAsInstead = true; - listA0.setValue("changedValue"); - checker.incNumberOfValues().put(CHECK_MANY_A, tuple("changedValue", "am1")).check(); + try { + createModel(); + setupReceiverAndConnect(); - // setting same value must not change data, and must not trigger a new sent message - listA0.setValue("changedValue"); - checker.check(); + logger.info("Calling communicateSendInitialValue"); + communicateSendInitialValue(); + } finally { + this.connectNTAsInstead = false; + } + } - listA1.setValue(""); - checker.incNumberOfValues().put(CHECK_MANY_A, tuple("changedValue", "")).check(); + @Tag("mqtt") + @RepeatedIfExceptionsTest(repeats = TEST_REPETITIONS) + public void testCommunicateOnlyUpdatedValueWithNTAs() throws IOException, InterruptedException { + this.writeCurrentValue = false; + this.connectNTAsInstead = true; - listA1InSuffix.setValue("re"); - checker.incNumberOfValues().put(CHECK_WITH_SUFFIX, tuple("am0post", "repost")).check(); + try { + createModel(); + setupReceiverAndConnect(); - // adding a new element does not automatically send it - A listA3InSuffix = createA("out"); - senderRoot.addMultipleAWithSuffix(listA3InSuffix); - checker.check(); + logger.info("Calling communicateOnlyUpdatedValue"); + communicateOnlyUpdatedValue(); + } finally { + this.connectNTAsInstead = false; + } + } - // 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(); + @Override + protected void communicateSendInitialValue() throws IOException, InterruptedException { + 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 - assertTrue(senderRoot.disconnectMultipleAWithSuffix(mqttUri(TOPIC_A_MANY_SUFFIX_0))); - listA0InSuffix.setValue("willBeIgnored"); - checker.check(); + communicateBoth("am1", "am0post"); } @Override @@ -154,10 +188,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,7 +199,10 @@ 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(); // setting same value must not change data, and must not trigger a new sent message listA0.setValue("changedValue"); @@ -176,21 +213,44 @@ public class IndexedSendTest extends AbstractMqttTest { // 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"); - senderRoot.addMultipleAWithSuffix(listA3InSuffix); + A listA2InSuffix = createA("out"); + senderRoot.addMultipleAWithSuffix(listA2InSuffix); + checker.check(); + + // only after connecting it, the element gets sent (for SendInitialValue case) + assertConnectComputedAOrMultipleAWithSuffix(TOPIC_A_MANY_SUFFIX_2, 2); + if (isWriteCurrentValue()) { + checker.incNumberOfValues() + .put(CHECK_WITH_SUFFIX, suffixAtIndex0 != null ? tuple(suffixAtIndex0, "repost", "outpost") : tuple("repost", "outpost")); + } 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("repost", "outpost")).check(); + // changing the value of the newly added element will send it + listA2InSuffix.setValue("goal"); + checker.incNumberOfValues() + .put(CHECK_WITH_SUFFIX, suffixAtIndex0 != null ? tuple(suffixAtIndex0, "repost", "goalpost") : tuple("repost", "goalpost")); + checker.check(); - // after successful disconnect, no messages will be sent - assertTrue(senderRoot.disconnectMultipleAWithSuffix(mqttUri(TOPIC_A_MANY_SUFFIX_0))); + // after successful disconnect for index 0, no messages will be sent + assertDisconnectComputedAOrMultipleAWithSuffix(TOPIC_A_MANY_SUFFIX_0); listA0InSuffix.setValue("willBeIgnored"); 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 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 5e6c94ff596909e6f70a0b84e6562ab4ec171d7d..4a74e6b3a205f092a53b73897656151464f8e8e1 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,13 +22,12 @@ 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; -import static java.util.Collections.addAll; 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.fail; @@ -98,6 +97,12 @@ public class TestUtils { 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 { Path expectedPath = Paths.get(TestUtils.INPUT_DIRECTORY_PREFIX) .resolve(directory) @@ -226,20 +231,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); }