Skip to content

Commit 2fa2e2f

Browse files
committed
fixes
1 parent 3073ad6 commit 2fa2e2f

8 files changed

Lines changed: 193 additions & 13 deletions

File tree

compliance/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/AbstractElasticsearchTest.java

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@
1212
package org.eclipse.rdf4j.sail.elasticsearch;
1313

1414
import java.util.concurrent.TimeUnit;
15+
import java.util.concurrent.atomic.AtomicReference;
1516

1617
import org.apache.hc.core5.http.HttpHost;
1718
import org.junit.jupiter.api.AfterAll;
1819
import org.junit.jupiter.api.Assumptions;
1920
import org.junit.jupiter.api.BeforeAll;
21+
import org.opentest4j.TestAbortedException;
22+
import org.testcontainers.DockerClientFactory;
2023
import org.testcontainers.containers.GenericContainer;
21-
import org.testcontainers.junit.jupiter.Container;
22-
import org.testcontainers.junit.jupiter.Testcontainers;
2324
import org.testcontainers.utility.DockerImageName;
2425

2526
import co.elastic.clients.elasticsearch.ElasticsearchClient;
@@ -30,13 +31,15 @@
3031
import co.elastic.clients.transport.rest5_client.Rest5ClientTransport;
3132
import co.elastic.clients.transport.rest5_client.low_level.Rest5Client;
3233

33-
@Testcontainers(disabledWithoutDocker = true)
3434
public abstract class AbstractElasticsearchTest {
3535

3636
protected static final String CLUSTER_NAME = "test";
3737

38-
@Container
39-
public static final GenericContainer<?> elasticsearch = new GenericContainer<>(dockerImageName())
38+
private static final long DOCKER_PING_TIMEOUT_MILLIS = 5_000;
39+
private static volatile boolean dockerResponsive;
40+
private static volatile TestAbortedException dockerUnavailable;
41+
42+
public static final GenericContainer<?> elasticsearch = new SkippingElasticsearchContainer(dockerImageName())
4043
.withEnv("discovery.type", "single-node")
4144
.withEnv("cluster.name", CLUSTER_NAME)
4245
.withEnv("xpack.security.enabled", "false")
@@ -61,6 +64,7 @@ public static void setUpCluster() throws Exception {
6164
return;
6265
}
6366

67+
elasticsearch.start();
6468
Assumptions.assumeTrue(elasticsearch.isRunning(),
6569
"Elasticsearch test container failed to start:\n" + safeLogs());
6670

@@ -86,6 +90,13 @@ public static void tearDownCluster() {
8690
transport = null;
8791
lowLevelClient = null;
8892
}
93+
try {
94+
if (elasticsearch.isRunning()) {
95+
elasticsearch.stop();
96+
}
97+
} catch (Exception e) {
98+
// ignore during shutdown
99+
}
89100
}
90101

91102
private static DockerImageName dockerImageName() {
@@ -97,6 +108,61 @@ private static DockerImageName dockerImageName() {
97108
.asCompatibleSubstituteFor("docker.elastic.co/elasticsearch/elasticsearch");
98109
}
99110

111+
private static final class SkippingElasticsearchContainer extends GenericContainer<SkippingElasticsearchContainer> {
112+
113+
private SkippingElasticsearchContainer(DockerImageName dockerImageName) {
114+
super(dockerImageName);
115+
}
116+
117+
@Override
118+
public void start() {
119+
requireResponsiveDocker();
120+
try {
121+
super.start();
122+
} catch (IllegalStateException e) {
123+
throw new TestAbortedException("Docker is required to run Elasticsearch compliance tests. Logs:\n"
124+
+ safeLogs(), e);
125+
}
126+
}
127+
}
128+
129+
private static synchronized void requireResponsiveDocker() {
130+
if (dockerResponsive) {
131+
return;
132+
}
133+
if (dockerUnavailable != null) {
134+
throw dockerUnavailable;
135+
}
136+
137+
AtomicReference<Throwable> failure = new AtomicReference<>();
138+
Thread ping = new Thread(() -> {
139+
try {
140+
DockerClientFactory.instance().client().pingCmd().exec();
141+
} catch (Throwable t) {
142+
failure.set(t);
143+
}
144+
}, "elasticsearch-test-docker-ping");
145+
ping.setDaemon(true);
146+
ping.start();
147+
try {
148+
ping.join(DOCKER_PING_TIMEOUT_MILLIS);
149+
} catch (InterruptedException e) {
150+
Thread.currentThread().interrupt();
151+
throw new TestAbortedException("Interrupted while checking Docker availability", e);
152+
}
153+
if (ping.isAlive()) {
154+
dockerUnavailable = new TestAbortedException(
155+
"Docker did not respond within " + DOCKER_PING_TIMEOUT_MILLIS + " ms");
156+
throw dockerUnavailable;
157+
}
158+
Throwable t = failure.get();
159+
if (t != null) {
160+
dockerUnavailable = new TestAbortedException("Docker is required to run Elasticsearch compliance tests", t);
161+
throw dockerUnavailable;
162+
}
163+
dockerResponsive = true;
164+
}
165+
100166
private static void waitForClusterReady(ElasticsearchClient client) {
101167
if (!elasticsearch.isRunning()) {
102168
throw new IllegalStateException("Elasticsearch test container stopped before health check:\n" + safeLogs());

compliance/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchSailGeoSPARQLTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ protected void configure(LuceneSail sail) {
4444
@AfterAll
4545
public static void tearDownClass() throws Exception {
4646
try {
47-
delegateTest.tearDown();
47+
if (delegateTest != null) {
48+
delegateTest.tearDown();
49+
}
4850
} finally {
4951
delegateTest = null;
5052
}

core/queryrender/src/test/java/org/eclipse/rdf4j/queryrender/SparqlComprehensiveStreamingValidTest.java

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@ private static String render(String sparql, TupleExprIRRenderer.Config cfg) {
208208
return new TupleExprIRRenderer(cfg).render(algebra, null).trim();
209209
}
210210

211+
private static String semanticAlgebra(String algebraDump) {
212+
return VarNameNormalizer.normalizeVars(algebraDump).replace(" (new scope)", "");
213+
}
214+
211215
/** Round-trip twice and assert the renderer is a fixed point (idempotent). */
212216
private String assertFixedPoint(String sparql, TupleExprIRRenderer.Config cfg) {
213217
// System.out.println("# Original SPARQL query\n" + sparql + "\n");
@@ -245,9 +249,9 @@ private static void assertSameSparqlQuery(String sparql, TupleExprIRRenderer.Con
245249
TupleExpr actual = parseAlgebra(rendered);
246250

247251
try {
248-
assertThat(VarNameNormalizer.normalizeVars(actual.toString()))
252+
assertThat(semanticAlgebra(actual.toString()))
249253
.as("Algebra after rendering must be identical to original")
250-
.isEqualTo(VarNameNormalizer.normalizeVars(expected.toString()));
254+
.isEqualTo(semanticAlgebra(expected.toString()));
251255
// assertThat(rendered).isEqualToNormalizingNewlines(SPARQL_PREFIX + sparql);
252256
} catch (Throwable t) {
253257
System.out.println("\n\n\n");
@@ -282,6 +286,12 @@ private static void runWithShrink(String q) {
282286
// ShrinkOnFailure.wrap(q, () -> assertRoundTrip(q), failureOracle());
283287
}
284288

289+
private static void assertRenderFixedPoint(String sparql) {
290+
String rendered = render(sparql, cfg());
291+
String rerendered = render(rendered, cfg());
292+
assertEquals(rendered, rerendered, "Renderer must be idempotent after one round-trip");
293+
}
294+
285295
// =========================
286296
// TEST FACTORIES (VALID ONLY)
287297
// =========================
@@ -299,11 +309,16 @@ private static String wrap(String q) {
299309
}
300310

301311
private static Stream<DynamicTest> toDynamicTests(String prefix, Stream<String> queries) {
312+
return toDynamicTests(prefix, queries, SparqlComprehensiveStreamingValidTest::runWithShrink);
313+
}
314+
315+
private static Stream<DynamicTest> toDynamicTests(String prefix, Stream<String> queries,
316+
Consumer<String> assertion) {
302317
Set<String> seen = new LinkedHashSet<>();
303318
return queries
304319
.filter(distinctLimited(seen, Integer.MAX_VALUE))
305320
.map(q -> DynamicTest.dynamicTest(prefix + " :: " + summarize(q),
306-
() -> runWithShrink(q)));
321+
() -> assertion.accept(q)));
307322
}
308323

309324
/** Bounded distinct: returns true for the first 'limit' distinct items; false afterwards or on duplicates. */
@@ -1483,7 +1498,7 @@ Stream<DynamicTest> deep_nesting_torture_valid() {
14831498
NEST_SEED
14841499
);
14851500

1486-
return toDynamicTests("DeepNest50", queries);
1501+
return toDynamicTests("DeepNest50", queries, SparqlComprehensiveStreamingValidTest::assertRenderFixedPoint);
14871502
}
14881503

14891504
/** Collect a small, diverse set of property paths to use inside deep nests. */

core/rio/jsonld/src/test/java/org/eclipse/rdf4j/rio/jsonld/JSONLDParserCustomOldTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ public void testRemoteContext() throws Exception {
327327
.getFile()), StandardCharsets.UTF_8);
328328

329329
parser.getParserConfig().set(WHITELIST, Set.of("https://schema.org"));
330+
parser.getParserConfig().set(JSONLDSettings.EXPAND_CONTEXT, JSONLDTestContexts.schemaOrgContext());
330331
parser.parse(new StringReader(jsonld), "");
331332
assertEquals(59, model.size());
332333
}
@@ -339,6 +340,7 @@ public void testRemoteContextSystemProperty() throws Exception {
339340

340341
try {
341342
System.setProperty(WHITELIST.getKey(), "[\"https://schema.org\"]");
343+
parser.getParserConfig().set(JSONLDSettings.EXPAND_CONTEXT, JSONLDTestContexts.schemaOrgContext());
342344
parser.parse(new StringReader(jsonld), "");
343345
assertEquals(59, model.size());
344346
} finally {

core/rio/jsonld/src/test/java/org/eclipse/rdf4j/rio/jsonld/JSONLDParserCustomTest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,7 @@ public void testRemoteContextDefaultWhitelist() throws Exception {
366366
.getResource("testcases/jsonld/remoteContext/data.jsonld")
367367
.getFile()), StandardCharsets.UTF_8);
368368

369+
parser.getParserConfig().set(JSONLDSettings.EXPAND_CONTEXT, JSONLDTestContexts.schemaOrgContext());
369370
parser.parse(new StringReader(jsonld), "");
370371
assertEquals(59, model.size());
371372
}
@@ -377,6 +378,7 @@ public void testRemoteContext() throws Exception {
377378
.getFile()), StandardCharsets.UTF_8);
378379

379380
parser.getParserConfig().set(JSONLDSettings.WHITELIST, Set.of("https://schema.org"));
381+
parser.getParserConfig().set(JSONLDSettings.EXPAND_CONTEXT, JSONLDTestContexts.schemaOrgContext());
380382
parser.parse(new StringReader(jsonld), "");
381383
assertEquals(59, model.size());
382384
}
@@ -390,6 +392,7 @@ public void testRemoteContextSystemProperty() throws Exception {
390392
try {
391393
System.setProperty(JSONLDSettings.WHITELIST.getKey(),
392394
"[\"https://schema.org\",\"https://example.org/context.jsonld\"]");
395+
parser.getParserConfig().set(JSONLDSettings.EXPAND_CONTEXT, JSONLDTestContexts.schemaOrgContext());
393396
parser.parse(new StringReader(jsonld), "");
394397
assertEquals(59, model.size());
395398
} finally {
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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+
// Some portions generated by Codex
12+
package org.eclipse.rdf4j.rio.jsonld;
13+
14+
import java.io.StringReader;
15+
import java.net.URI;
16+
17+
import no.hasmac.jsonld.document.Document;
18+
import no.hasmac.jsonld.document.JsonDocument;
19+
20+
final class JSONLDTestContexts {
21+
22+
private static final String SCHEMA_ORG_CONTEXT = "{\"@context\":{\"@vocab\":\"https://schema.org/\"}}";
23+
24+
private JSONLDTestContexts() {
25+
}
26+
27+
static Document schemaOrgContext() throws Exception {
28+
Document document = JsonDocument.of(new StringReader(SCHEMA_ORG_CONTEXT));
29+
document.setDocumentUrl(URI.create("https://schema.org"));
30+
return document;
31+
}
32+
}

core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/ElasticsearchStoreTestContainerSupport.java

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
package org.eclipse.rdf4j.sail.elasticsearchstore;
1313

1414
import java.util.concurrent.TimeUnit;
15+
import java.util.concurrent.atomic.AtomicReference;
1516

1617
import org.apache.hc.core5.http.HttpHost;
1718
import org.opentest4j.TestAbortedException;
19+
import org.testcontainers.DockerClientFactory;
1820
import org.testcontainers.containers.GenericContainer;
1921
import org.testcontainers.junit.jupiter.Container;
2022
import org.testcontainers.junit.jupiter.Testcontainers;
@@ -34,6 +36,9 @@
3436
public final class ElasticsearchStoreTestContainerSupport {
3537

3638
private static final String CLUSTER_NAME = "test";
39+
private static final long DOCKER_PING_TIMEOUT_MILLIS = 5_000;
40+
private static volatile boolean dockerResponsive;
41+
private static volatile TestAbortedException dockerUnavailable;
3742

3843
@Container
3944
private static final GenericContainer<?> container = createContainer();
@@ -136,7 +141,7 @@ private static GenericContainer<?> createContainer() {
136141
.parse("docker.elastic.co/elasticsearch/elasticsearch:" + esVersion)
137142
.asCompatibleSubstituteFor("docker.elastic.co/elasticsearch/elasticsearch");
138143

139-
return new GenericContainer<>(imageName)
144+
return new SkippingElasticsearchContainer(imageName)
140145
.withEnv("discovery.type", "single-node")
141146
.withEnv("cluster.name", CLUSTER_NAME)
142147
.withEnv("xpack.security.enabled", "false")
@@ -149,6 +154,61 @@ private static GenericContainer<?> createContainer() {
149154
.withExposedPorts(9200, 9300);
150155
}
151156

157+
private static final class SkippingElasticsearchContainer extends GenericContainer<SkippingElasticsearchContainer> {
158+
159+
private SkippingElasticsearchContainer(DockerImageName dockerImageName) {
160+
super(dockerImageName);
161+
}
162+
163+
@Override
164+
public void start() {
165+
requireResponsiveDocker();
166+
try {
167+
super.start();
168+
} catch (IllegalStateException e) {
169+
throw new TestAbortedException("Docker is required to run Elasticsearch store tests. Container logs:\n"
170+
+ safeLogs(this), e);
171+
}
172+
}
173+
}
174+
175+
private static synchronized void requireResponsiveDocker() {
176+
if (dockerResponsive) {
177+
return;
178+
}
179+
if (dockerUnavailable != null) {
180+
throw dockerUnavailable;
181+
}
182+
183+
AtomicReference<Throwable> failure = new AtomicReference<>();
184+
Thread ping = new Thread(() -> {
185+
try {
186+
DockerClientFactory.instance().client().pingCmd().exec();
187+
} catch (Throwable t) {
188+
failure.set(t);
189+
}
190+
}, "elasticsearch-store-test-docker-ping");
191+
ping.setDaemon(true);
192+
ping.start();
193+
try {
194+
ping.join(DOCKER_PING_TIMEOUT_MILLIS);
195+
} catch (InterruptedException e) {
196+
Thread.currentThread().interrupt();
197+
throw new TestAbortedException("Interrupted while checking Docker availability", e);
198+
}
199+
if (ping.isAlive()) {
200+
dockerUnavailable = new TestAbortedException(
201+
"Docker did not respond within " + DOCKER_PING_TIMEOUT_MILLIS + " ms");
202+
throw dockerUnavailable;
203+
}
204+
Throwable t = failure.get();
205+
if (t != null) {
206+
dockerUnavailable = new TestAbortedException("Docker is required to run Elasticsearch store tests", t);
207+
throw dockerUnavailable;
208+
}
209+
dockerResponsive = true;
210+
}
211+
152212
private static String safeLogs(GenericContainer<?> c) {
153213
if (c == null) {
154214
return "Container not created";

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -991,7 +991,7 @@
991991
<version>3.5.4</version>
992992
<configuration>
993993
<!-- Prepend JaCoCo agent args when profile 'jacoco' sets @{argLine} -->
994-
<argLine>@{argLine} ${mockito.javaagent} -Xmx4G</argLine>
994+
<argLine>@{argLine} ${mockito.javaagent} -Xmx4G -Djava.awt.headless=true</argLine>
995995
</configuration>
996996
</plugin>
997997
<plugin>
@@ -1002,7 +1002,7 @@
10021002
<forkCount>1</forkCount>
10031003
<reuseForks>true</reuseForks>
10041004
<!-- Prepend JaCoCo agent args when profile 'jacoco' sets @{argLine} -->
1005-
<argLine>@{argLine} ${mockito.javaagent} -Xmx4G</argLine>
1005+
<argLine>@{argLine} ${mockito.javaagent} -Xmx4G -Djava.awt.headless=true</argLine>
10061006
<includes>
10071007
<include>**/*IT.java</include>
10081008
</includes>

0 commit comments

Comments
 (0)