Skip to content

Commit 221e957

Browse files
committed
Stabilize unit tests (Local, Static initialization)
- stabilize unit tests that depend on locale (e.g. number formatting in string output) - introduce a JUnit extension that handles Var.Provider setting only for specific tests where required
1 parent 91bbd1e commit 221e957

10 files changed

Lines changed: 191 additions & 2 deletions

File tree

core/query/src/test/java/org/eclipse/rdf4j/query/explanation/GenericPlanNodeTest.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,28 @@
1616
import static org.junit.jupiter.api.Assertions.assertNull;
1717
import static org.junit.jupiter.api.Assertions.assertTrue;
1818

19+
import java.util.Locale;
20+
21+
import org.junit.jupiter.api.AfterEach;
22+
import org.junit.jupiter.api.BeforeEach;
1923
import org.junit.jupiter.api.Test;
2024

2125
class GenericPlanNodeTest {
2226

27+
private Locale defaultLocale;
28+
29+
@BeforeEach
30+
void setUp() {
31+
defaultLocale = Locale.getDefault();
32+
// set EN locale explicitly to avoid different number formatting across environment
33+
Locale.setDefault(Locale.ENGLISH);
34+
}
35+
36+
@AfterEach
37+
void tearDown() {
38+
Locale.setDefault(defaultLocale);
39+
}
40+
2341
@Test
2442
void toStringIncludesPopulatedTelemetryFields() {
2543
GenericPlanNode node = new GenericPlanNode("Join");

core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/impl/QueryCostEstimatesTest.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,35 @@
1212

1313
import static org.assertj.core.api.Assertions.assertThat;
1414

15+
import java.util.Locale;
16+
1517
import org.eclipse.rdf4j.common.exception.RDF4JException;
1618
import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.QueryJoinOptimizer;
1719
import org.eclipse.rdf4j.query.parser.ParsedQuery;
1820
import org.eclipse.rdf4j.query.parser.sparql.SPARQLParser;
21+
import org.junit.jupiter.api.AfterEach;
22+
import org.junit.jupiter.api.BeforeEach;
1923
import org.junit.jupiter.api.Test;
2024

2125
/**
2226
* Tests that cost estimates are printed as part of the plan
2327
*/
2428
public class QueryCostEstimatesTest {
2529

30+
private Locale defaultLocale;
31+
32+
@BeforeEach
33+
void setUp() {
34+
defaultLocale = Locale.getDefault();
35+
// set EN locale explicitly to avoid different number formatting across environment
36+
Locale.setDefault(Locale.ENGLISH);
37+
}
38+
39+
@AfterEach
40+
void tearDown() {
41+
Locale.setDefault(defaultLocale);
42+
}
43+
2644
@Test
2745
public void testBindingSetAssignmentOptimization() throws RDF4JException {
2846
String query = "prefix ex: <ex:>" + "select ?s ?p ?o ?x where {" + " ex:s1 ex:pred ?v. "

core/queryalgebra/model/src/main/java/org/eclipse/rdf4j/query/algebra/Var.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
import org.eclipse.rdf4j.model.Value;
1717

18+
import com.google.common.annotations.VisibleForTesting;
19+
1820
/**
1921
* A variable that can contain a Value.
2022
*
@@ -312,10 +314,33 @@ public boolean isConstant() {
312314
return constant;
313315
}
314316

317+
/**
318+
* Installs a custom {@link Provider} to be used by {@link Var#of} and {@link Var#clone}. Intended for test use
319+
* only. Always call {@link #resetProvider()} in a corresponding teardown.
320+
*/
321+
@VisibleForTesting
322+
/* package */ static void setProvider(Provider provider) {
323+
Holder.PROVIDER = provider;
324+
}
325+
326+
/**
327+
* Resets the {@link Provider} to the one resolved from the system property and {@link ServiceLoader}. Intended for
328+
* test use only to undo a prior {@link #setProvider} call.
329+
*/
330+
@VisibleForTesting
331+
/* package */ static void resetProvider() {
332+
Holder.reset();
333+
}
334+
315335
private static final class Holder {
316336
private static final Provider DEFAULT = Var::new;
317337

318-
static final Provider PROVIDER = initProvider();
338+
// Not final so that Var.setProvider() can replace it during tests
339+
static Provider PROVIDER = initProvider();
340+
341+
private static void reset() {
342+
PROVIDER = initProvider();
343+
}
319344

320345
private static Provider initProvider() {
321346
// 1) Explicit override via system property (FQCN of Var.Provider)

core/queryalgebra/model/src/test/java/org/eclipse/rdf4j/query/algebra/VarProviderCloneHookTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
2424
import org.junit.jupiter.api.Test;
2525

26+
@WithVarProvider(KindAwareVarProvider.class)
2627
class VarProviderCloneHookTest {
2728

2829
private final ValueFactory vf = SimpleValueFactory.getInstance();
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 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.query.algebra;
12+
13+
import java.util.Optional;
14+
15+
import org.junit.jupiter.api.extension.AfterEachCallback;
16+
import org.junit.jupiter.api.extension.BeforeEachCallback;
17+
import org.junit.jupiter.api.extension.ExtensionContext;
18+
19+
/**
20+
* JUnit 5 extension that installs a custom {@link Var.Provider} for the duration of each test and resets it afterwards.
21+
*
22+
* <p>
23+
* Activated via {@link WithVarProvider} on the test class or method. The method-level annotation takes precedence over
24+
* the class-level annotation. The provider is always reset after each test regardless of outcome.
25+
*
26+
* @see WithVarProvider
27+
*/
28+
class VarProviderExtension implements BeforeEachCallback, AfterEachCallback {
29+
30+
@Override
31+
public void beforeEach(ExtensionContext context) throws Exception {
32+
Optional<WithVarProvider> annotation = findAnnotation(context);
33+
if (annotation.isPresent()) {
34+
Var.Provider provider = annotation.get().value().getDeclaredConstructor().newInstance();
35+
Var.setProvider(provider);
36+
}
37+
}
38+
39+
@Override
40+
public void afterEach(ExtensionContext context) {
41+
Var.resetProvider();
42+
}
43+
44+
/**
45+
* Returns the {@link WithVarProvider} annotation, preferring the method level over the class level.
46+
*/
47+
private Optional<WithVarProvider> findAnnotation(ExtensionContext context) {
48+
return context.getTestMethod()
49+
.map(m -> m.getAnnotation(WithVarProvider.class))
50+
.or(() -> context.getTestClass().map(c -> c.getAnnotation(WithVarProvider.class)));
51+
}
52+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 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.query.algebra;
12+
13+
import java.lang.annotation.ElementType;
14+
import java.lang.annotation.Retention;
15+
import java.lang.annotation.RetentionPolicy;
16+
import java.lang.annotation.Target;
17+
18+
import org.junit.jupiter.api.extension.ExtendWith;
19+
20+
/**
21+
* Installs a custom {@link Var.Provider} for the duration of the annotated test class or test method.
22+
*
23+
* <p>
24+
* The specified provider is instantiated via its no-argument constructor, installed before each test, and removed after
25+
* each test via {@link Var#resetProvider()}. When placed on both a class and a method, the method-level annotation
26+
* takes precedence.
27+
*
28+
* <p>
29+
* Usage:
30+
*
31+
* <pre>
32+
* &#64;WithVarProvider(MyVarProvider.class)
33+
* class MyTest {
34+
*
35+
* &#64;Test
36+
* void test() {
37+
* Var v = Var.of("x"); // produced by MyVarProvider
38+
* }
39+
* }
40+
* </pre>
41+
*/
42+
@Target({ ElementType.TYPE, ElementType.METHOD })
43+
@Retention(RetentionPolicy.RUNTIME)
44+
@ExtendWith(VarProviderExtension.class)
45+
public @interface WithVarProvider {
46+
47+
/**
48+
* The {@link Var.Provider} implementation to install. Must have a public no-argument constructor.
49+
*/
50+
Class<? extends Var.Provider> value();
51+
}

core/queryalgebra/model/src/test/java/org/eclipse/rdf4j/query/algebra/helpers/QueryModelTreeToGenericPlanNodeTest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.eclipse.rdf4j.query.algebra.ExtensionElem;
2323
import org.eclipse.rdf4j.query.algebra.Filter;
2424
import org.eclipse.rdf4j.query.algebra.Join;
25+
import org.eclipse.rdf4j.query.algebra.KindAwareVarProvider;
2526
import org.eclipse.rdf4j.query.algebra.Projection;
2627
import org.eclipse.rdf4j.query.algebra.ProjectionElem;
2728
import org.eclipse.rdf4j.query.algebra.ProjectionElemList;
@@ -30,6 +31,7 @@
3031
import org.eclipse.rdf4j.query.algebra.StatementPattern;
3132
import org.eclipse.rdf4j.query.algebra.TupleExpr;
3233
import org.eclipse.rdf4j.query.algebra.Var;
34+
import org.eclipse.rdf4j.query.algebra.WithVarProvider;
3335
import org.eclipse.rdf4j.query.explanation.Explanation;
3436
import org.eclipse.rdf4j.query.explanation.GenericPlanNode;
3537
import org.eclipse.rdf4j.query.explanation.TelemetryMetricNames;
@@ -189,6 +191,7 @@ public void doesNotAnnotateConnectedJoinAsCartesianJoin() {
189191
}
190192

191193
@Test
194+
@WithVarProvider(KindAwareVarProvider.class)
192195
public void skipsCartesianAnnotationForUnsupportedVarSubclass() {
193196
Join join = new Join(
194197
new StatementPattern(Var.of("s"), Var.of("p"), Var.of("o")),

core/queryalgebra/model/src/test/resources/META-INF/services/org.eclipse.rdf4j.query.algebra.Var$Provider

Lines changed: 0 additions & 1 deletion
This file was deleted.

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.lang.reflect.Field;
3232
import java.lang.reflect.Method;
3333
import java.util.LinkedHashSet;
34+
import java.util.Locale;
3435
import java.util.Set;
3536

3637
import org.eclipse.rdf4j.common.iteration.CloseableIteration;
@@ -67,6 +68,8 @@ public class LmdbSailStoreTest {
6768
protected Repository repo;
6869
private File dataDir;
6970

71+
private Locale defaultLocale;
72+
7073
protected final ValueFactory F = SimpleValueFactory.getInstance();
7174

7275
protected final IRI CTX_1 = F.createIRI("urn:one");
@@ -82,6 +85,8 @@ public class LmdbSailStoreTest {
8285

8386
@BeforeEach
8487
public void before(@TempDir File dataDir) {
88+
defaultLocale = Locale.getDefault();
89+
Locale.setDefault(Locale.ENGLISH);
8590
this.dataDir = dataDir;
8691
repo = new SailRepository(new LmdbStore(dataDir, new LmdbStoreConfig("spoc,posc")));
8792
repo.init();
@@ -433,6 +438,7 @@ public void after() {
433438
repo.shutDown();
434439
} finally {
435440
LmdbTestUtil.deleteDir(dataDir);
441+
Locale.setDefault(defaultLocale);
436442
}
437443
}
438444
}

core/sail/memory/src/test/java/org/eclipse/rdf4j/sail/memory/QueryPlanRetrievalTest.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.io.IOException;
2020
import java.io.StringReader;
2121
import java.nio.charset.StandardCharsets;
22+
import java.util.Locale;
2223
import java.util.function.Predicate;
2324

2425
import org.apache.commons.io.IOUtils;
@@ -43,6 +44,8 @@
4344
import org.eclipse.rdf4j.repository.sail.SailRepository;
4445
import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection;
4546
import org.eclipse.rdf4j.rio.RDFFormat;
47+
import org.junit.jupiter.api.AfterEach;
48+
import org.junit.jupiter.api.BeforeEach;
4649
import org.junit.jupiter.api.Disabled;
4750
import org.junit.jupiter.api.Test;
4851

@@ -53,6 +56,19 @@ public class QueryPlanRetrievalTest {
5356

5457
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
5558

59+
private Locale defaultLocale;
60+
61+
@BeforeEach
62+
void setLocale() {
63+
defaultLocale = Locale.getDefault();
64+
Locale.setDefault(Locale.ENGLISH);
65+
}
66+
67+
@AfterEach
68+
void restoreLocale() {
69+
Locale.setDefault(defaultLocale);
70+
}
71+
5672
public static final String MAIN_QUERY = String.join("\n", "",
5773
"{",
5874
" {",

0 commit comments

Comments
 (0)