aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2023-06-15 20:41:09 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2023-06-15 20:49:33 +0200
commit2599ceeccbcfb531b3a95427b1b8904caf102242 (patch)
tree9af7b51d7cafc4d966dc4915cc899fc116998c51
parentrefactor: simplified Dnf parameter directions (diff)
downloadrefinery-2599ceeccbcfb531b3a95427b1b8904caf102242.tar.gz
refinery-2599ceeccbcfb531b3a95427b1b8904caf102242.tar.zst
refinery-2599ceeccbcfb531b3a95427b1b8904caf102242.zip
refactor(query): structural equality matcher
Add the ability to create assertions without pre-processing Dnf clauses (raw matchin mode). Also fix tests broken by Dnf pre-processing.
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java5
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DeepDnfEqualityChecker.java42
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderVariableUnificationTest.java234
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToRawTest.java189
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToTest.java93
-rw-r--r--subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/MismatchDescribingDnfEqualityChecker.java32
-rw-r--r--subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/QueryMatchers.java27
-rw-r--r--subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualTo.java2
-rw-r--r--subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualToRaw.java51
9 files changed, 568 insertions, 107 deletions
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java
index 64790f42..c5b51b81 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java
@@ -119,6 +119,11 @@ public final class Dnf implements Constraint {
119 if (arity() != other.arity()) { 119 if (arity() != other.arity()) {
120 return false; 120 return false;
121 } 121 }
122 for (int i = 0; i < arity(); i++) {
123 if (!symbolicParameters.get(i).getDirection().equals(other.getSymbolicParameters().get(i).getDirection())) {
124 return false;
125 }
126 }
122 int numClauses = clauses.size(); 127 int numClauses = clauses.size();
123 if (numClauses != other.clauses.size()) { 128 if (numClauses != other.clauses.size()) {
124 return false; 129 return false;
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DeepDnfEqualityChecker.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DeepDnfEqualityChecker.java
index bb60ccc9..1eeb5723 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DeepDnfEqualityChecker.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DeepDnfEqualityChecker.java
@@ -6,6 +6,9 @@
6package tools.refinery.store.query.equality; 6package tools.refinery.store.query.equality;
7 7
8import tools.refinery.store.query.dnf.Dnf; 8import tools.refinery.store.query.dnf.Dnf;
9import tools.refinery.store.query.dnf.DnfClause;
10import tools.refinery.store.query.dnf.SymbolicParameter;
11import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.util.CycleDetectingMapper; 12import tools.refinery.store.util.CycleDetectingMapper;
10 13
11import java.util.List; 14import java.util.List;
@@ -19,6 +22,45 @@ public class DeepDnfEqualityChecker implements DnfEqualityChecker {
19 return mapper.map(new Pair(left, right)); 22 return mapper.map(new Pair(left, right));
20 } 23 }
21 24
25 public boolean dnfEqualRaw(List<SymbolicParameter> symbolicParameters,
26 List<? extends List<? extends Literal>> clauses, Dnf other) {
27 int arity = symbolicParameters.size();
28 if (arity != other.arity()) {
29 return false;
30 }
31 for (int i = 0; i < arity; i++) {
32 if (!symbolicParameters.get(i).getDirection().equals(other.getSymbolicParameters().get(i).getDirection())) {
33 return false;
34 }
35 }
36 int numClauses = clauses.size();
37 if (numClauses != other.getClauses().size()) {
38 return false;
39 }
40 for (int i = 0; i < numClauses; i++) {
41 var literalEqualityHelper = new LiteralEqualityHelper(this, symbolicParameters,
42 other.getSymbolicParameters());
43 if (!equalsWithSubstitutionRaw(literalEqualityHelper, clauses.get(i), other.getClauses().get(i))) {
44 return false;
45 }
46 }
47 return true;
48 }
49
50 private boolean equalsWithSubstitutionRaw(LiteralEqualityHelper helper, List<? extends Literal> literals,
51 DnfClause other) {
52 int size = literals.size();
53 if (size != other.literals().size()) {
54 return false;
55 }
56 for (int i = 0; i < size; i++) {
57 if (!literals.get(i).equalsWithSubstitution(helper, other.literals().get(i))) {
58 return false;
59 }
60 }
61 return true;
62 }
63
22 protected boolean doCheckEqual(Pair pair) { 64 protected boolean doCheckEqual(Pair pair) {
23 return pair.left.equalsWithSubstitution(this, pair.right); 65 return pair.left.equalsWithSubstitution(this, pair.right);
24 } 66 }
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderVariableUnificationTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderVariableUnificationTest.java
index a54ad4d6..4a85fe32 100644
--- a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderVariableUnificationTest.java
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderVariableUnificationTest.java
@@ -6,6 +6,8 @@
6package tools.refinery.store.query.dnf; 6package tools.refinery.store.query.dnf;
7 7
8import org.junit.jupiter.api.Test; 8import org.junit.jupiter.api.Test;
9import tools.refinery.store.query.term.ParameterDirection;
10import tools.refinery.store.query.term.Variable;
9import tools.refinery.store.query.view.KeyOnlyView; 11import tools.refinery.store.query.view.KeyOnlyView;
10import tools.refinery.store.query.view.SymbolView; 12import tools.refinery.store.query.view.SymbolView;
11import tools.refinery.store.representation.Symbol; 13import tools.refinery.store.representation.Symbol;
@@ -17,7 +19,7 @@ import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo
17 19
18class DnfBuilderVariableUnificationTest { 20class DnfBuilderVariableUnificationTest {
19 private final Symbol<Boolean> friend = new Symbol<>("friend", 2, Boolean.class, false); 21 private final Symbol<Boolean> friend = new Symbol<>("friend", 2, Boolean.class, false);
20 private final Symbol<Boolean> children = new Symbol<>("friend", 2, Boolean.class, false); 22 private final Symbol<Boolean> children = new Symbol<>("children", 2, Boolean.class, false);
21 private final SymbolView<Boolean> friendView = new KeyOnlyView<>(friend); 23 private final SymbolView<Boolean> friendView = new KeyOnlyView<>(friend);
22 private final SymbolView<Boolean> childrenView = new KeyOnlyView<>(children); 24 private final SymbolView<Boolean> childrenView = new KeyOnlyView<>(children);
23 25
@@ -30,12 +32,14 @@ class DnfBuilderVariableUnificationTest {
30 p.isEquivalent(q) 32 p.isEquivalent(q)
31 )); 33 ));
32 }); 34 });
33 var expected = Dnf.of(builder -> {
34 var p = builder.parameter("p");
35 builder.clause(friendView.call(p, p));
36 });
37 35
38 assertThat(actual, structurallyEqualTo(expected)); 36 var expectedP = Variable.of("p");
37 assertThat(actual, structurallyEqualTo(
38 List.of(new SymbolicParameter(expectedP, ParameterDirection.OUT)),
39 List.of(
40 List.of(friendView.call(expectedP, expectedP))
41 )
42 ));
39 } 43 }
40 44
41 @Test 45 @Test
@@ -47,12 +51,14 @@ class DnfBuilderVariableUnificationTest {
47 q.isEquivalent(p) 51 q.isEquivalent(p)
48 )); 52 ));
49 }); 53 });
50 var expected = Dnf.of(builder -> {
51 var p = builder.parameter("p");
52 builder.clause(friendView.call(p, p));
53 });
54 54
55 assertThat(actual, structurallyEqualTo(expected)); 55 var expectedP = Variable.of("p");
56 assertThat(actual, structurallyEqualTo(
57 List.of(new SymbolicParameter(expectedP, ParameterDirection.OUT)),
58 List.of(
59 List.of(friendView.call(expectedP, expectedP))
60 )
61 ));
56 } 62 }
57 63
58 @Test 64 @Test
@@ -61,9 +67,14 @@ class DnfBuilderVariableUnificationTest {
61 friendView.call(p, q), 67 friendView.call(p, q),
62 p.isEquivalent(q) 68 p.isEquivalent(q)
63 ))); 69 )));
64 var expected = Dnf.of(builder -> builder.clause(p -> List.of(friendView.call(p, p))));
65 70
66 assertThat(actual, structurallyEqualTo(expected)); 71 var expectedP = Variable.of("p");
72 assertThat(actual, structurallyEqualTo(
73 List.of(),
74 List.of(
75 List.of(friendView.call(expectedP, expectedP))
76 )
77 ));
67 } 78 }
68 79
69 @Test 80 @Test
@@ -74,12 +85,14 @@ class DnfBuilderVariableUnificationTest {
74 childrenView.call(p, r), 85 childrenView.call(p, r),
75 q.isEquivalent(r) 86 q.isEquivalent(r)
76 ))); 87 )));
77 var expected = Dnf.of(builder -> builder.clause(p -> List.of(
78 friendView.call(p, p),
79 childrenView.call(p, p)
80 )));
81 88
82 assertThat(actual, structurallyEqualTo(expected)); 89 var expectedP = Variable.of("p");
90 assertThat(actual, structurallyEqualTo(
91 List.of(),
92 List.of(
93 List.of(friendView.call(expectedP, expectedP), childrenView.call(expectedP, expectedP))
94 )
95 ));
83 } 96 }
84 97
85 @Test 98 @Test
@@ -90,9 +103,14 @@ class DnfBuilderVariableUnificationTest {
90 friendView.call(p, r), 103 friendView.call(p, r),
91 q.isEquivalent(r) 104 q.isEquivalent(r)
92 ))); 105 )));
93 var expected = Dnf.of(builder -> builder.clause(p -> List.of(friendView.call(p, p))));
94 106
95 assertThat(actual, structurallyEqualTo(expected)); 107 var expectedP = Variable.of("p");
108 assertThat(actual, structurallyEqualTo(
109 List.of(),
110 List.of(
111 List.of(friendView.call(expectedP, expectedP))
112 )
113 ));
96 } 114 }
97 115
98 @Test 116 @Test
@@ -105,16 +123,18 @@ class DnfBuilderVariableUnificationTest {
105 p.isEquivalent(q) 123 p.isEquivalent(q)
106 ); 124 );
107 }); 125 });
108 var expected = Dnf.of(builder -> {
109 var p = builder.parameter("p");
110 var q = builder.parameter("q");
111 builder.clause(
112 q.isEquivalent(p),
113 friendView.call(p, p)
114 );
115 });
116 126
117 assertThat(actual, structurallyEqualTo(expected)); 127 var expectedP = Variable.of("p");
128 var expectedQ = Variable.of("q");
129 assertThat(actual, structurallyEqualTo(
130 List.of(
131 new SymbolicParameter(expectedP, ParameterDirection.OUT),
132 new SymbolicParameter(expectedQ, ParameterDirection.OUT)
133 ),
134 List.of(
135 List.of(friendView.call(expectedP, expectedP), expectedQ.isEquivalent(expectedP))
136 )
137 ));
118 } 138 }
119 139
120 @Test 140 @Test
@@ -130,19 +150,25 @@ class DnfBuilderVariableUnificationTest {
130 r.isEquivalent(q) 150 r.isEquivalent(q)
131 ); 151 );
132 }); 152 });
133 var expected = Dnf.of(builder -> {
134 var p = builder.parameter("p");
135 var q = builder.parameter("q");
136 var r = builder.parameter("r");
137 builder.clause(
138 q.isEquivalent(p),
139 r.isEquivalent(p),
140 friendView.call(p, p),
141 childrenView.call(p, p)
142 );
143 });
144 153
145 assertThat(actual, structurallyEqualTo(expected)); 154 var expectedP = Variable.of("p");
155 var expectedQ = Variable.of("q");
156 var expectedR = Variable.of("r");
157 assertThat(actual, structurallyEqualTo(
158 List.of(
159 new SymbolicParameter(expectedP, ParameterDirection.OUT),
160 new SymbolicParameter(expectedQ, ParameterDirection.OUT),
161 new SymbolicParameter(expectedR, ParameterDirection.OUT)
162 ),
163 List.of(
164 List.of(
165 friendView.call(expectedP, expectedP),
166 expectedQ.isEquivalent(expectedP),
167 expectedR.isEquivalent(expectedP),
168 childrenView.call(expectedP, expectedP)
169 )
170 )
171 ));
146 } 172 }
147 173
148 @Test 174 @Test
@@ -157,17 +183,23 @@ class DnfBuilderVariableUnificationTest {
157 q.isEquivalent(r) 183 q.isEquivalent(r)
158 )); 184 ));
159 }); 185 });
160 var expected = Dnf.of(builder -> {
161 var p = builder.parameter("p");
162 var q = builder.parameter("q");
163 builder.clause(
164 q.isEquivalent(p),
165 friendView.call(p, p),
166 childrenView.call(p, p)
167 );
168 });
169 186
170 assertThat(actual, structurallyEqualTo(expected)); 187
188 var expectedP = Variable.of("p");
189 var expectedQ = Variable.of("q");
190 assertThat(actual, structurallyEqualTo(
191 List.of(
192 new SymbolicParameter(expectedP, ParameterDirection.OUT),
193 new SymbolicParameter(expectedQ, ParameterDirection.OUT)
194 ),
195 List.of(
196 List.of(
197 friendView.call(expectedP, expectedP),
198 expectedQ.isEquivalent(expectedP),
199 childrenView.call(expectedP, expectedP)
200 )
201 )
202 ));
171 } 203 }
172 204
173 @Test 205 @Test
@@ -182,17 +214,22 @@ class DnfBuilderVariableUnificationTest {
182 q.isEquivalent(r) 214 q.isEquivalent(r)
183 )); 215 ));
184 }); 216 });
185 var expected = Dnf.of(builder -> {
186 var p = builder.parameter("p");
187 var q = builder.parameter("q");
188 builder.clause(
189 q.isEquivalent(p),
190 friendView.call(p, p),
191 childrenView.call(p, p)
192 );
193 });
194 217
195 assertThat(actual, structurallyEqualTo(expected)); 218 var expectedP = Variable.of("p");
219 var expectedQ = Variable.of("q");
220 assertThat(actual, structurallyEqualTo(
221 List.of(
222 new SymbolicParameter(expectedP, ParameterDirection.OUT),
223 new SymbolicParameter(expectedQ, ParameterDirection.OUT)
224 ),
225 List.of(
226 List.of(
227 friendView.call(expectedP, expectedP),
228 expectedQ.isEquivalent(expectedP),
229 childrenView.call(expectedP, expectedP)
230 )
231 )
232 ));
196 } 233 }
197 234
198 @Test 235 @Test
@@ -207,17 +244,22 @@ class DnfBuilderVariableUnificationTest {
207 r.isEquivalent(q) 244 r.isEquivalent(q)
208 )); 245 ));
209 }); 246 });
210 var expected = Dnf.of(builder -> {
211 var p = builder.parameter("p");
212 var q = builder.parameter("q");
213 builder.clause(
214 q.isEquivalent(p),
215 friendView.call(p, p),
216 childrenView.call(p, p)
217 );
218 });
219 247
220 assertThat(actual, structurallyEqualTo(expected)); 248 var expectedP = Variable.of("p");
249 var expectedQ = Variable.of("q");
250 assertThat(actual, structurallyEqualTo(
251 List.of(
252 new SymbolicParameter(expectedP, ParameterDirection.OUT),
253 new SymbolicParameter(expectedQ, ParameterDirection.OUT)
254 ),
255 List.of(
256 List.of(
257 friendView.call(expectedP, expectedP),
258 expectedQ.isEquivalent(expectedP),
259 childrenView.call(expectedP, expectedP)
260 )
261 )
262 ));
221 } 263 }
222 264
223 @Test 265 @Test
@@ -232,17 +274,22 @@ class DnfBuilderVariableUnificationTest {
232 r.isEquivalent(q) 274 r.isEquivalent(q)
233 )); 275 ));
234 }); 276 });
235 var expected = Dnf.of(builder -> {
236 var p = builder.parameter("p");
237 var q = builder.parameter("q");
238 builder.clause(
239 q.isEquivalent(p),
240 friendView.call(p, p),
241 childrenView.call(p, p)
242 );
243 });
244 277
245 assertThat(actual, structurallyEqualTo(expected)); 278 var expectedP = Variable.of("p");
279 var expectedQ = Variable.of("q");
280 assertThat(actual, structurallyEqualTo(
281 List.of(
282 new SymbolicParameter(expectedP, ParameterDirection.OUT),
283 new SymbolicParameter(expectedQ, ParameterDirection.OUT)
284 ),
285 List.of(
286 List.of(
287 friendView.call(expectedP, expectedP),
288 expectedQ.isEquivalent(expectedP),
289 childrenView.call(expectedP, expectedP)
290 )
291 )
292 ));
246 } 293 }
247 294
248 @Test 295 @Test
@@ -258,16 +305,21 @@ class DnfBuilderVariableUnificationTest {
258 q.isEquivalent(s) 305 q.isEquivalent(s)
259 )); 306 ));
260 }); 307 });
261 var expected = Dnf.of(builder -> {
262 var p = builder.parameter("p");
263 var q = builder.parameter("q");
264 builder.clause(
265 q.isEquivalent(p),
266 friendView.call(p, p),
267 childrenView.call(p, p)
268 );
269 });
270 308
271 assertThat(actual, structurallyEqualTo(expected)); 309 var expectedP = Variable.of("p");
310 var expectedQ = Variable.of("q");
311 assertThat(actual, structurallyEqualTo(
312 List.of(
313 new SymbolicParameter(expectedP, ParameterDirection.OUT),
314 new SymbolicParameter(expectedQ, ParameterDirection.OUT)
315 ),
316 List.of(
317 List.of(
318 friendView.call(expectedP, expectedP),
319 expectedQ.isEquivalent(expectedP),
320 childrenView.call(expectedP, expectedP)
321 )
322 )
323 ));
272 } 324 }
273} 325}
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToRawTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToRawTest.java
new file mode 100644
index 00000000..07a55ff3
--- /dev/null
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToRawTest.java
@@ -0,0 +1,189 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.tests;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.query.dnf.Dnf;
10import tools.refinery.store.query.dnf.SymbolicParameter;
11import tools.refinery.store.query.term.ParameterDirection;
12import tools.refinery.store.query.term.Variable;
13import tools.refinery.store.query.view.KeyOnlyView;
14import tools.refinery.store.representation.Symbol;
15
16import java.util.List;
17
18import static org.hamcrest.CoreMatchers.containsString;
19import static org.hamcrest.MatcherAssert.assertThat;
20import static org.hamcrest.Matchers.allOf;
21import static org.junit.jupiter.api.Assertions.assertThrows;
22import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo;
23
24class StructurallyEqualToRawTest {
25 @Test
26 void flatEqualsTest() {
27 var p = Variable.of("p");
28 var q = Variable.of("q");
29 var person = new Symbol<>("Person", 1, Boolean.class, false);
30 var personView = new KeyOnlyView<>(person);
31
32 var actual = Dnf.builder("Actual").parameters(p).clause(personView.call(p)).build();
33
34 assertThat(actual, structurallyEqualTo(
35 List.of(new SymbolicParameter(q, ParameterDirection.OUT)),
36 List.of(List.of(personView.call(q)))
37 ));
38 }
39
40 @Test
41 void flatNotEqualsTest() {
42 var p = Variable.of("p");
43 var q = Variable.of("q");
44 var friend = new Symbol<>("friend", 2, Boolean.class, false);
45 var friendView = new KeyOnlyView<>(friend);
46
47 var actual = Dnf.builder("Actual").parameters(p).clause(friendView.call(p, q)).build();
48
49 var assertion = structurallyEqualTo(
50 List.of(new SymbolicParameter(q, ParameterDirection.OUT)),
51 List.of(List.of(friendView.call(q, q)))
52 );
53 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
54 }
55
56 @Test
57 void deepEqualsTest() {
58 var p = Variable.of("p");
59 var q = Variable.of("q");
60 var person = new Symbol<>("Person", 1, Boolean.class, false);
61 var personView = new KeyOnlyView<>(person);
62
63 var actual = Dnf.builder("Actual").parameters(q).clause(
64 Dnf.builder("Actual2").parameters(p).clause(personView.call(p)).build().call(q)
65 ).build();
66
67 assertThat(actual, structurallyEqualTo(
68 List.of(new SymbolicParameter(q, ParameterDirection.OUT)),
69 List.of(
70 List.of(
71 Dnf.builder("Expected2").parameters(p).clause(personView.call(p)).build().call(q)
72 )
73 )
74 ));
75 }
76
77 @Test
78 void deepNotEqualsTest() {
79 var p = Variable.of("p");
80 var q = Variable.of("q");
81 var friend = new Symbol<>("friend", 2, Boolean.class, false);
82 var friendView = new KeyOnlyView<>(friend);
83
84 var actual = Dnf.builder("Actual").parameter(q).clause(
85 Dnf.builder("Actual2").parameters(p).clause(friendView.call(p, q)).build().call(q)
86 ).build();
87
88 var assertion = structurallyEqualTo(
89 List.of(new SymbolicParameter(q, ParameterDirection.OUT)),
90 List.of(
91 List.of(
92 Dnf.builder("Expected2")
93 .parameters(p)
94 .clause(friendView.call(p, p))
95 .build()
96 .call(q)
97 )
98 )
99 );
100 var error = assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
101 assertThat(error.getMessage(), allOf(containsString("Expected2"), containsString("Actual2")));
102 }
103
104 @Test
105 void parameterListLengthMismatchTest() {
106 var p = Variable.of("p");
107 var q = Variable.of("q");
108 var friend = new Symbol<>("friend", 2, Boolean.class, false);
109 var friendView = new KeyOnlyView<>(friend);
110
111 var actual = Dnf.builder("Actual").parameters(p, q).clause(
112 friendView.call(p, q)
113 ).build();
114
115 var assertion = structurallyEqualTo(
116 List.of(new SymbolicParameter(p, ParameterDirection.OUT)),
117 List.of(List.of(friendView.call(p, p)))
118 );
119
120 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
121 }
122
123 @Test
124 void parameterDirectionMismatchTest() {
125 var p = Variable.of("p");
126 var person = new Symbol<>("Person", 1, Boolean.class, false);
127 var personView = new KeyOnlyView<>(person);
128
129 var actual = Dnf.builder("Actual").parameter(p, ParameterDirection.IN).clause(
130 personView.call(p)
131 ).build();
132
133 var assertion = structurallyEqualTo(
134 List.of(new SymbolicParameter(p, ParameterDirection.OUT)),
135 List.of(List.of(personView.call(p)))
136 );
137
138 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
139 }
140
141 @Test
142 void clauseCountMismatchTest() {
143 var p = Variable.of("p");
144 var q = Variable.of("q");
145 var friend = new Symbol<>("friend", 2, Boolean.class, false);
146 var friendView = new KeyOnlyView<>(friend);
147
148 var actual = Dnf.builder("Actual").parameters(p, q).clause(
149 friendView.call(p, q)
150 ).build();
151
152 var assertion = structurallyEqualTo(
153 List.of(
154 new SymbolicParameter(p, ParameterDirection.OUT),
155 new SymbolicParameter(q, ParameterDirection.OUT)
156 ),
157 List.of(
158 List.of(friendView.call(p, q)),
159 List.of(friendView.call(q, p))
160 )
161 );
162
163 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
164 }
165
166 @Test
167 void literalCountMismatchTest() {
168 var p = Variable.of("p");
169 var q = Variable.of("q");
170 var friend = new Symbol<>("friend", 2, Boolean.class, false);
171 var friendView = new KeyOnlyView<>(friend);
172
173 var actual = Dnf.builder("Actual").parameters(p, q).clause(
174 friendView.call(p, q)
175 ).build();
176
177 var assertion = structurallyEqualTo(
178 List.of(
179 new SymbolicParameter(p, ParameterDirection.OUT),
180 new SymbolicParameter(q, ParameterDirection.OUT)
181 ),
182 List.of(
183 List.of(friendView.call(p, q), friendView.call(q, p))
184 )
185 );
186
187 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
188 }
189}
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToTest.java
index a1407288..e2983a3a 100644
--- a/subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToTest.java
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToTest.java
@@ -7,6 +7,7 @@ package tools.refinery.store.query.tests;
7 7
8import org.junit.jupiter.api.Test; 8import org.junit.jupiter.api.Test;
9import tools.refinery.store.query.dnf.Dnf; 9import tools.refinery.store.query.dnf.Dnf;
10import tools.refinery.store.query.term.ParameterDirection;
10import tools.refinery.store.query.term.Variable; 11import tools.refinery.store.query.term.Variable;
11import tools.refinery.store.query.view.KeyOnlyView; 12import tools.refinery.store.query.view.KeyOnlyView;
12import tools.refinery.store.representation.Symbol; 13import tools.refinery.store.representation.Symbol;
@@ -34,11 +35,11 @@ class StructurallyEqualToTest {
34 void flatNotEqualsTest() { 35 void flatNotEqualsTest() {
35 var p = Variable.of("p"); 36 var p = Variable.of("p");
36 var q = Variable.of("q"); 37 var q = Variable.of("q");
37 var person = new Symbol<>("Person", 1, Boolean.class, false); 38 var friend = new Symbol<>("friend", 2, Boolean.class, false);
38 var personView = new KeyOnlyView<>(person); 39 var friendView = new KeyOnlyView<>(friend);
39 40
40 var expected = Dnf.builder("Expected").parameters(q).clause(personView.call(q)).build(); 41 var expected = Dnf.builder("Expected").parameters(q).clause(friendView.call(q, q)).build();
41 var actual = Dnf.builder("Actual").parameters(p).clause(personView.call(q)).build(); 42 var actual = Dnf.builder("Actual").parameters(p).clause(friendView.call(p, q)).build();
42 43
43 var assertion = structurallyEqualTo(expected); 44 var assertion = structurallyEqualTo(expected);
44 assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); 45 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
@@ -65,18 +66,92 @@ class StructurallyEqualToTest {
65 void deepNotEqualsTest() { 66 void deepNotEqualsTest() {
66 var p = Variable.of("p"); 67 var p = Variable.of("p");
67 var q = Variable.of("q"); 68 var q = Variable.of("q");
68 var person = new Symbol<>("Person", 1, Boolean.class, false); 69 var friend = new Symbol<>("friend", 2, Boolean.class, false);
69 var personView = new KeyOnlyView<>(person); 70 var friendView = new KeyOnlyView<>(friend);
70 71
71 var expected = Dnf.builder("Expected").parameters(q).clause( 72 var expected = Dnf.builder("Expected").parameters(q).clause(
72 Dnf.builder("Expected2").parameters(p).clause(personView.call(p)).build().call(q) 73 Dnf.builder("Expected2").parameters(p).clause(friendView.call(p, p)).build().call(q)
73 ).build(); 74 ).build();
74 var actual = Dnf.builder("Actual").parameters(q).clause( 75 var actual = Dnf.builder("Actual").parameter(q).clause(
75 Dnf.builder("Actual2").parameters(p).clause(personView.call(q)).build().call(q) 76 Dnf.builder("Actual2").parameters(p).clause(friendView.call(p, q)).build().call(q)
76 ).build(); 77 ).build();
77 78
78 var assertion = structurallyEqualTo(expected); 79 var assertion = structurallyEqualTo(expected);
79 var error = assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); 80 var error = assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
80 assertThat(error.getMessage(), containsString(" called from Expected/1 ")); 81 assertThat(error.getMessage(), containsString(" called from Expected/1 "));
81 } 82 }
83
84 @Test
85 void parameterListLengthMismatchTest() {
86 var p = Variable.of("p");
87 var q = Variable.of("q");
88 var friend = new Symbol<>("friend", 2, Boolean.class, false);
89 var friendView = new KeyOnlyView<>(friend);
90
91 var expected = Dnf.builder("Expected").parameter(p).clause(
92 friendView.call(p, p)
93 ).build();
94 var actual = Dnf.builder("Actual").parameters(p, q).clause(
95 friendView.call(p, q)
96 ).build();
97
98 var assertion = structurallyEqualTo(expected);
99 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
100 }
101
102 @Test
103 void parameterDirectionMismatchTest() {
104 var p = Variable.of("p");
105 var person = new Symbol<>("Person", 1, Boolean.class, false);
106 var personView = new KeyOnlyView<>(person);
107
108 var expected = Dnf.builder("Expected").parameter(p, ParameterDirection.OUT).clause(
109 personView.call(p)
110 ).build();
111 var actual = Dnf.builder("Actual").parameter(p, ParameterDirection.IN).clause(
112 personView.call(p)
113 ).build();
114
115 var assertion = structurallyEqualTo(expected);
116 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
117 }
118
119 @Test
120 void clauseCountMismatchTest() {
121 var p = Variable.of("p");
122 var q = Variable.of("q");
123 var friend = new Symbol<>("friend", 2, Boolean.class, false);
124 var friendView = new KeyOnlyView<>(friend);
125
126 var expected = Dnf.builder("Expected")
127 .parameters(p, q)
128 .clause(friendView.call(p, q))
129 .clause(friendView.call(q, p))
130 .build();
131 var actual = Dnf.builder("Actual").parameters(p, q).clause(
132 friendView.call(p, q)
133 ).build();
134
135 var assertion = structurallyEqualTo(expected);
136 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
137 }
138
139 @Test
140 void literalCountMismatchTest() {
141 var p = Variable.of("p");
142 var q = Variable.of("q");
143 var friend = new Symbol<>("friend", 2, Boolean.class, false);
144 var friendView = new KeyOnlyView<>(friend);
145
146 var expected = Dnf.builder("Expected").parameters(p, q).clause(
147 friendView.call(p, q),
148 friendView.call(q, p)
149 ).build();
150 var actual = Dnf.builder("Actual").parameters(p, q).clause(
151 friendView.call(p, q)
152 ).build();
153
154 var assertion = structurallyEqualTo(expected);
155 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
156 }
82} 157}
diff --git a/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/MismatchDescribingDnfEqualityChecker.java b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/MismatchDescribingDnfEqualityChecker.java
index a5b7f85a..6a3301b3 100644
--- a/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/MismatchDescribingDnfEqualityChecker.java
+++ b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/MismatchDescribingDnfEqualityChecker.java
@@ -6,27 +6,47 @@
6package tools.refinery.store.query.tests; 6package tools.refinery.store.query.tests;
7 7
8import org.hamcrest.Description; 8import org.hamcrest.Description;
9import tools.refinery.store.query.dnf.Dnf;
10import tools.refinery.store.query.dnf.SymbolicParameter;
9import tools.refinery.store.query.equality.DeepDnfEqualityChecker; 11import tools.refinery.store.query.equality.DeepDnfEqualityChecker;
12import tools.refinery.store.query.literal.Literal;
13
14import java.util.List;
10 15
11class MismatchDescribingDnfEqualityChecker extends DeepDnfEqualityChecker { 16class MismatchDescribingDnfEqualityChecker extends DeepDnfEqualityChecker {
12 private final Description description; 17 private final Description description;
13 private boolean described; 18 private boolean raw;
19 private boolean needsDescription = true;
14 20
15 MismatchDescribingDnfEqualityChecker(Description description) { 21 MismatchDescribingDnfEqualityChecker(Description description) {
16 this.description = description; 22 this.description = description;
17 } 23 }
18 24
19 public boolean isDescribed() { 25 public boolean needsDescription() {
20 return described; 26 return needsDescription;
27 }
28
29 @Override
30 public boolean dnfEqualRaw(List<SymbolicParameter> symbolicParameters, List<? extends List<? extends Literal>> clauses, Dnf other) {
31 try {
32 raw = true;
33 boolean result = super.dnfEqualRaw(symbolicParameters, clauses, other);
34 if (!result && needsDescription) {
35 description.appendText("was ").appendText(other.toDefinitionString());
36 }
37 return false;
38 } finally {
39 raw = false;
40 }
21 } 41 }
22 42
23 @Override 43 @Override
24 protected boolean doCheckEqual(Pair pair) { 44 protected boolean doCheckEqual(Pair pair) {
25 boolean result = super.doCheckEqual(pair); 45 boolean result = super.doCheckEqual(pair);
26 if (!result && !described) { 46 if (!result && needsDescription) {
27 describeMismatch(pair); 47 describeMismatch(pair);
28 // Only describe the first found (innermost) mismatch. 48 // Only describe the first found (innermost) mismatch.
29 described = true; 49 needsDescription = false;
30 } 50 }
31 return result; 51 return result;
32 } 52 }
@@ -34,7 +54,7 @@ class MismatchDescribingDnfEqualityChecker extends DeepDnfEqualityChecker {
34 private void describeMismatch(Pair pair) { 54 private void describeMismatch(Pair pair) {
35 var inProgress = getInProgress(); 55 var inProgress = getInProgress();
36 int size = inProgress.size(); 56 int size = inProgress.size();
37 if (size <= 1) { 57 if (size <= 1 && !raw) {
38 description.appendText("was ").appendText(pair.right().toDefinitionString()); 58 description.appendText("was ").appendText(pair.right().toDefinitionString());
39 return; 59 return;
40 } 60 }
diff --git a/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/QueryMatchers.java b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/QueryMatchers.java
index 8706ef70..cd449a6a 100644
--- a/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/QueryMatchers.java
+++ b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/QueryMatchers.java
@@ -7,13 +7,40 @@ package tools.refinery.store.query.tests;
7 7
8import org.hamcrest.Matcher; 8import org.hamcrest.Matcher;
9import tools.refinery.store.query.dnf.Dnf; 9import tools.refinery.store.query.dnf.Dnf;
10import tools.refinery.store.query.dnf.SymbolicParameter;
11import tools.refinery.store.query.literal.Literal;
12
13import java.util.List;
10 14
11public final class QueryMatchers { 15public final class QueryMatchers {
12 private QueryMatchers() { 16 private QueryMatchers() {
13 throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); 17 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
14 } 18 }
15 19
20 /**
21 * Compare two {@link Dnf} instances up to renaming of variables.
22 *
23 * @param expected The expected {@link Dnf} instance.
24 * @return A Hamcrest matcher for equality up to renaming of variables.
25 */
16 public static Matcher<Dnf> structurallyEqualTo(Dnf expected) { 26 public static Matcher<Dnf> structurallyEqualTo(Dnf expected) {
17 return new StructurallyEqualTo(expected); 27 return new StructurallyEqualTo(expected);
18 } 28 }
29
30 /**
31 * Compare a {@link Dnf} instance to another predicate in DNF form without constructing it.
32 * <p>
33 * This matcher should be used instead of {@link #structurallyEqualTo(Dnf)} when the validation and
34 * pre-processing associated with the {@link Dnf} constructor, i.e., validation of parameter directions,
35 * topological sorting of literals, and the reduction of trivial predicates is not desired. In particular, this
36 * matcher can be used to test for exact order of literal after pre-processing.
37 *
38 * @param expectedSymbolicParameters The expected list of symbolic parameters.
39 * @param expectedLiterals The expected clauses. Each clause is represented by a list of literals.
40 * @return A Hamcrest matcher for equality up to renaming of variables.
41 */
42 public static Matcher<Dnf> structurallyEqualTo(List<SymbolicParameter> expectedSymbolicParameters,
43 List<? extends List<? extends Literal>> expectedLiterals) {
44 return new StructurallyEqualToRaw(expectedSymbolicParameters, expectedLiterals);
45 }
19} 46}
diff --git a/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualTo.java b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualTo.java
index ba51c084..86149141 100644
--- a/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualTo.java
+++ b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualTo.java
@@ -29,7 +29,7 @@ public class StructurallyEqualTo extends TypeSafeMatcher<Dnf> {
29 if (describingChecker.dnfEqual(expected, item)) { 29 if (describingChecker.dnfEqual(expected, item)) {
30 throw new IllegalStateException("Mismatched Dnf was matching on repeated comparison"); 30 throw new IllegalStateException("Mismatched Dnf was matching on repeated comparison");
31 } 31 }
32 if (!describingChecker.isDescribed()) { 32 if (describingChecker.needsDescription()) {
33 super.describeMismatchSafely(item, mismatchDescription); 33 super.describeMismatchSafely(item, mismatchDescription);
34 } 34 }
35 } 35 }
diff --git a/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualToRaw.java b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualToRaw.java
new file mode 100644
index 00000000..2f8c2944
--- /dev/null
+++ b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualToRaw.java
@@ -0,0 +1,51 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.tests;
7
8import org.hamcrest.Description;
9import org.hamcrest.TypeSafeMatcher;
10import tools.refinery.store.query.dnf.Dnf;
11import tools.refinery.store.query.dnf.SymbolicParameter;
12import tools.refinery.store.query.equality.DeepDnfEqualityChecker;
13import tools.refinery.store.query.literal.Literal;
14
15import java.util.List;
16
17public class StructurallyEqualToRaw extends TypeSafeMatcher<Dnf> {
18 private final List<SymbolicParameter> expectedSymbolicParameters;
19 private final List<? extends List<? extends Literal>> expectedClauses;
20
21 public StructurallyEqualToRaw(List<SymbolicParameter> expectedSymbolicParameters,
22 List<? extends List<? extends Literal>> expectedClauses) {
23 this.expectedSymbolicParameters = expectedSymbolicParameters;
24 this.expectedClauses = expectedClauses;
25 }
26
27 @Override
28 protected boolean matchesSafely(Dnf item) {
29 var checker = new DeepDnfEqualityChecker();
30 return checker.dnfEqualRaw(expectedSymbolicParameters, expectedClauses, item);
31 }
32
33 @Override
34 protected void describeMismatchSafely(Dnf item, Description mismatchDescription) {
35 var describingChecker = new MismatchDescribingDnfEqualityChecker(mismatchDescription);
36 if (describingChecker.dnfEqualRaw(expectedSymbolicParameters, expectedClauses, item)) {
37 throw new IllegalStateException("Mismatched Dnf was matching on repeated comparison");
38 }
39 if (describingChecker.needsDescription()) {
40 super.describeMismatchSafely(item, mismatchDescription);
41 }
42 }
43
44 @Override
45 public void describeTo(Description description) {
46 description.appendText("structurally equal to ")
47 .appendValueList("(", ", ", ")", expectedSymbolicParameters)
48 .appendText(" <-> ")
49 .appendValueList("", ", ", ".", expectedClauses);
50 }
51}