Skip to content

Commit f8d1887

Browse files
committed
add tests
1 parent 295b06f commit f8d1887

18 files changed

Lines changed: 1119 additions & 26 deletions

core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/CardinalityTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public class CardinalityTest {
3838
public void before() throws Exception {
3939
File dataDir = new File(tempFolder, "triplestore");
4040
dataDir.mkdir();
41-
tripleStore = new TripleStore(dataDir, new LmdbStoreConfig("spoc,posc"));
41+
tripleStore = new TripleStore(dataDir, new LmdbStoreConfig("spoc,posc"), null);
4242
}
4343

4444
int count(RecordIterator it) {

core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/DefaultIndexTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public class DefaultIndexTest {
2626

2727
@Test
2828
public void testDefaultIndex(@TempDir File dir) throws Exception {
29-
TripleStore store = new TripleStore(dir, new LmdbStoreConfig());
29+
TripleStore store = new TripleStore(dir, new LmdbStoreConfig(), null);
3030
store.close();
3131
// check that the triple store used the default index
3232
assertEquals("spoc,posc", findIndex(dir));
@@ -36,11 +36,11 @@ public void testDefaultIndex(@TempDir File dir) throws Exception {
3636
@Test
3737
public void testExistingIndex(@TempDir File dir) throws Exception {
3838
// set a non-default index
39-
TripleStore store = new TripleStore(dir, new LmdbStoreConfig("spoc,opsc"));
39+
TripleStore store = new TripleStore(dir, new LmdbStoreConfig("spoc,opsc"), null);
4040
store.close();
4141
String before = findIndex(dir);
4242
// check that the index is preserved with a null value
43-
store = new TripleStore(dir, new LmdbStoreConfig(null));
43+
store = new TripleStore(dir, new LmdbStoreConfig(null), null);
4444
store.close();
4545
assertEquals(before, findIndex(dir));
4646
FileUtil.deleteDir(dir);
Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Eclipse RDF4J contributors.
3+
*
4+
* All rights reserved. This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Distribution License v1.0
6+
* which accompanies this distribution, and is available at
7+
* http://www.eclipse.org/org/documents/edl-v10.php.
8+
*
9+
* SPDX-License-Identifier: BSD-3-Clause
10+
******************************************************************************/
11+
package org.eclipse.rdf4j.sail.lmdb;
12+
13+
import static org.junit.jupiter.api.Assertions.assertFalse;
14+
import static org.junit.jupiter.api.Assertions.assertTrue;
15+
16+
import java.nio.ByteBuffer;
17+
import java.nio.ByteOrder;
18+
import java.util.ArrayList;
19+
import java.util.Arrays;
20+
import java.util.List;
21+
22+
import org.eclipse.rdf4j.sail.lmdb.util.GroupMatcher;
23+
import org.junit.jupiter.api.Test;
24+
25+
class GroupMatcherTest {
26+
27+
private static final int FIELD_COUNT = 4;
28+
private static final int MAX_LENGTH = 9;
29+
30+
private static final ValueVariants[] VALUE_VARIANTS = buildValueVariants();
31+
private static final List<byte[]> ALL_LENGTH_COMBINATIONS = buildAllLengthCombinations();
32+
private static final CandidateStrategy[] CANDIDATE_STRATEGIES = CandidateStrategy.values();
33+
34+
@Test
35+
void coversEveryMatcherMaskAcrossAllLengthCombinations() {
36+
for (int mask = 0; mask < (1 << FIELD_COUNT); mask++) {
37+
final int maskBits = mask;
38+
boolean[] shouldMatch = maskToArray(mask);
39+
for (byte[] valueLengths : ALL_LENGTH_COMBINATIONS) {
40+
final byte[] lengthsRef = valueLengths;
41+
long[] referenceValues = valuesForLengths(valueLengths);
42+
GroupMatcher matcher = new GroupMatcher(encodeBE(referenceValues).duplicate().array(), shouldMatch);
43+
44+
for (CandidateStrategy strategy : CANDIDATE_STRATEGIES) {
45+
final CandidateStrategy strategyRef = strategy;
46+
long[] candidateValues = buildCandidateValues(referenceValues, valueLengths, shouldMatch, strategy);
47+
final long[] candidateCopy = candidateValues;
48+
ByteBuffer matchBuffer = encode(candidateCopy);
49+
50+
assertTrue(matcher.matches(nativeOrder(matchBuffer.duplicate())),
51+
() -> failureMessage("expected match", maskBits, lengthsRef, strategyRef, candidateCopy,
52+
null));
53+
54+
if (hasMatch(shouldMatch)) {
55+
for (int index = 0; index < FIELD_COUNT; index++) {
56+
if (!shouldMatch[index]) {
57+
continue;
58+
}
59+
for (MismatchType mismatchType : MismatchType.values()) {
60+
long[] mismatchValues = createMismatch(candidateCopy, lengthsRef, index, mismatchType);
61+
if (mismatchValues == null) {
62+
continue;
63+
}
64+
final long[] mismatchCopy = mismatchValues;
65+
ByteBuffer mismatchBuffer = encode(mismatchCopy);
66+
assertFalse(matcher.matches(nativeOrder(mismatchBuffer.duplicate())),
67+
() -> failureMessage("expected mismatch",
68+
maskBits, lengthsRef, strategyRef, mismatchCopy, mismatchType));
69+
}
70+
}
71+
}
72+
}
73+
}
74+
}
75+
}
76+
77+
private ByteBuffer nativeOrder(ByteBuffer duplicate) {
78+
duplicate.order(ByteOrder.nativeOrder());
79+
return duplicate;
80+
}
81+
82+
private static long[] valuesForLengths(byte[] lengthIndices) {
83+
long[] values = new long[FIELD_COUNT];
84+
for (int i = 0; i < FIELD_COUNT; i++) {
85+
int lengthIndex = Byte.toUnsignedInt(lengthIndices[i]);
86+
values[i] = VALUE_VARIANTS[lengthIndex].base;
87+
}
88+
return values;
89+
}
90+
91+
private static long[] buildCandidateValues(long[] referenceValues, byte[] valueLengths, boolean[] shouldMatch,
92+
CandidateStrategy strategy) {
93+
long[] candidateValues = new long[FIELD_COUNT];
94+
for (int i = 0; i < FIELD_COUNT; i++) {
95+
if (shouldMatch[i]) {
96+
candidateValues[i] = referenceValues[i];
97+
} else {
98+
int lengthIndex = selectLengthIndex(valueLengths, i, strategy);
99+
candidateValues[i] = VALUE_VARIANTS[lengthIndex].nonMatchingSameLength;
100+
}
101+
}
102+
return candidateValues;
103+
}
104+
105+
private static int selectLengthIndex(byte[] lengths, int position, CandidateStrategy strategy) {
106+
int base = Byte.toUnsignedInt(lengths[position]);
107+
switch (strategy) {
108+
case SAME_LENGTHS:
109+
return base;
110+
case ROTATED_LENGTHS:
111+
return Byte.toUnsignedInt(lengths[(position + 1) % FIELD_COUNT]);
112+
case INCREMENTED_LENGTHS:
113+
return base == MAX_LENGTH ? 1 : base + 1;
114+
default:
115+
throw new IllegalStateException("Unsupported strategy: " + strategy);
116+
}
117+
}
118+
119+
private static ByteBuffer encode(long[] values) {
120+
ByteBuffer buffer = ByteBuffer
121+
.allocate(Varint.calcListLengthUnsigned(values[0], values[1], values[2], values[3]));
122+
buffer.order(ByteOrder.nativeOrder());
123+
for (long value : values) {
124+
Varint.writeUnsigned(buffer, value);
125+
}
126+
buffer.flip();
127+
return buffer;
128+
}
129+
130+
private static ByteBuffer encodeBE(long[] values) {
131+
ByteBuffer buffer = ByteBuffer
132+
.allocate(Varint.calcListLengthUnsigned(values[0], values[1], values[2], values[3]));
133+
buffer.order(ByteOrder.nativeOrder());
134+
for (long value : values) {
135+
Varint.writeUnsigned(buffer, value);
136+
}
137+
buffer.flip();
138+
return buffer;
139+
}
140+
141+
private static boolean[] maskToArray(int mask) {
142+
boolean[] shouldMatch = new boolean[FIELD_COUNT];
143+
for (int i = 0; i < FIELD_COUNT; i++) {
144+
shouldMatch[i] = (mask & (1 << i)) != 0;
145+
}
146+
return shouldMatch;
147+
}
148+
149+
private static boolean hasMatch(boolean[] shouldMatch) {
150+
for (boolean flag : shouldMatch) {
151+
if (flag) {
152+
return true;
153+
}
154+
}
155+
return false;
156+
}
157+
158+
private static int firstMatchedIndex(boolean[] shouldMatch) {
159+
for (int i = 0; i < FIELD_COUNT; i++) {
160+
if (shouldMatch[i]) {
161+
return i;
162+
}
163+
}
164+
return -1;
165+
}
166+
167+
private static List<byte[]> buildAllLengthCombinations() {
168+
List<byte[]> combos = new ArrayList<>((int) Math.pow(MAX_LENGTH, FIELD_COUNT));
169+
buildCombos(combos, new byte[FIELD_COUNT], 0);
170+
return combos;
171+
}
172+
173+
private static void buildCombos(List<byte[]> combos, byte[] current, int index) {
174+
if (index == FIELD_COUNT) {
175+
combos.add(current.clone());
176+
return;
177+
}
178+
for (int len = 1; len <= MAX_LENGTH; len++) {
179+
current[index] = (byte) len;
180+
buildCombos(combos, current, index + 1);
181+
}
182+
}
183+
184+
private static String failureMessage(String expectation, int mask, byte[] valueLengths, CandidateStrategy strategy,
185+
long[] candidateValues, MismatchType mismatchType) {
186+
return expectation + " for mask " + toMask(mask) + ", valueLengths=" + Arrays.toString(toIntArray(valueLengths))
187+
+ ", strategy=" + strategy
188+
+ (mismatchType == null ? "" : ", mismatchType=" + mismatchType)
189+
+ ", candidate=" + Arrays.toString(candidateValues);
190+
}
191+
192+
private static String toMask(int mask) {
193+
return String.format("%4s", Integer.toBinaryString(mask)).replace(' ', '0');
194+
}
195+
196+
private static int[] toIntArray(byte[] values) {
197+
int[] ints = new int[values.length];
198+
for (int i = 0; i < values.length; i++) {
199+
ints[i] = Byte.toUnsignedInt(values[i]);
200+
}
201+
return ints;
202+
}
203+
204+
private static long[] createMismatch(long[] baseCandidate, byte[] valueLengths, int index,
205+
MismatchType mismatchType) {
206+
int lengthIndex = Byte.toUnsignedInt(valueLengths[index]);
207+
ValueVariants variants = VALUE_VARIANTS[lengthIndex];
208+
long replacement;
209+
switch (mismatchType) {
210+
case SAME_FIRST_BYTE:
211+
if (variants.sameFirstVariant == null) {
212+
return null;
213+
}
214+
replacement = variants.sameFirstVariant;
215+
break;
216+
case DIFFERENT_FIRST_BYTE:
217+
replacement = variants.differentFirstVariant;
218+
break;
219+
default:
220+
throw new IllegalStateException("Unsupported mismatch type: " + mismatchType);
221+
}
222+
if (replacement == baseCandidate[index]) {
223+
return null;
224+
}
225+
long[] mismatch = baseCandidate.clone();
226+
mismatch[index] = replacement;
227+
return mismatch;
228+
}
229+
230+
private static ValueVariants[] buildValueVariants() {
231+
ValueVariants[] variants = new ValueVariants[MAX_LENGTH + 1];
232+
variants[1] = new ValueVariants(42L, 99L, null, 99L);
233+
variants[2] = new ValueVariants(241L, 330L, 330L, 600L);
234+
variants[3] = new ValueVariants(50_000L, 60_000L, 60_000L, 70_000L);
235+
variants[4] = new ValueVariants(1_048_576L, 1_048_577L, 1_048_577L, 16_777_216L);
236+
variants[5] = new ValueVariants(16_777_216L, 16_777_217L, 16_777_217L, 4_294_967_296L);
237+
variants[6] = new ValueVariants(4_294_967_296L, 4_294_967_297L, 4_294_967_297L, 1_099_511_627_776L);
238+
variants[7] = new ValueVariants(1_099_511_627_776L, 1_099_511_627_777L, 1_099_511_627_777L,
239+
281_474_976_710_656L);
240+
variants[8] = new ValueVariants(281_474_976_710_656L, 281_474_976_710_657L, 281_474_976_710_657L,
241+
72_057_594_037_927_936L);
242+
variants[9] = new ValueVariants(72_057_594_037_927_936L, 72_057_594_037_927_937L,
243+
72_057_594_037_927_937L, 281_474_976_710_656L);
244+
245+
for (int len = 1; len <= MAX_LENGTH; len++) {
246+
ValueVariants v = variants[len];
247+
if (Varint.calcLengthUnsigned(v.base) != len) {
248+
throw new IllegalStateException("Unexpected length for base value " + v.base + " (len=" + len + ")");
249+
}
250+
if (Varint.calcLengthUnsigned(v.nonMatchingSameLength) != len) {
251+
throw new IllegalStateException(
252+
"Unexpected length for same-length variant " + v.nonMatchingSameLength + " (len=" + len + ")");
253+
}
254+
if (v.sameFirstVariant != null && firstByte(v.sameFirstVariant.longValue()) != firstByte(v.base)) {
255+
throw new IllegalStateException("Expected same-first variant to share header for length " + len);
256+
}
257+
if (firstByte(v.differentFirstVariant) == firstByte(v.base)) {
258+
throw new IllegalStateException("Expected different-first variant to differ for length " + len);
259+
}
260+
}
261+
262+
return variants;
263+
}
264+
265+
private static byte firstByte(long value) {
266+
ByteBuffer buffer = ByteBuffer.allocate(Varint.calcLengthUnsigned(value));
267+
Varint.writeUnsigned(buffer, value);
268+
return buffer.array()[0];
269+
}
270+
271+
private static final class ValueVariants {
272+
final long base;
273+
final long nonMatchingSameLength;
274+
final Long sameFirstVariant;
275+
final long differentFirstVariant;
276+
277+
ValueVariants(long base, long nonMatchingSameLength, Long sameFirstVariant, long differentFirstVariant) {
278+
this.base = base;
279+
this.nonMatchingSameLength = nonMatchingSameLength;
280+
this.sameFirstVariant = sameFirstVariant;
281+
this.differentFirstVariant = differentFirstVariant;
282+
}
283+
}
284+
285+
private enum MismatchType {
286+
SAME_FIRST_BYTE,
287+
DIFFERENT_FIRST_BYTE
288+
}
289+
290+
private enum CandidateStrategy {
291+
SAME_LENGTHS,
292+
ROTATED_LENGTHS,
293+
INCREMENTED_LENGTHS
294+
}
295+
}

0 commit comments

Comments
 (0)