Skip to content

Commit c914f64

Browse files
nosansnicoll
authored andcommitted
Add failure analysis for invalid Docker environments
This commit introduces a failure analyzer implementation that provides actionable guidance when Testcontainers fails to detect a running Docker-compatible environment. See spring-projectsgh-47797 Signed-off-by: Dmytro Nosan <dimanosan@gmail.com>
1 parent 024bb72 commit c914f64

File tree

4 files changed

+149
-0
lines changed

4 files changed

+149
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2012-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.testcontainers.diagnostics;
18+
19+
import org.jspecify.annotations.Nullable;
20+
21+
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
22+
import org.springframework.boot.diagnostics.FailureAnalysis;
23+
import org.springframework.util.StringUtils;
24+
25+
/**
26+
* Failure analyzer for the Docker environment wasn't found.
27+
*
28+
* @author Dmytro Nosan
29+
*/
30+
class DockerEnvironmentNotFoundFailureAnalyzer extends AbstractFailureAnalyzer<IllegalStateException> {
31+
32+
private static final String EXPECTED_MESSAGE = "Could not find a valid Docker environment";
33+
34+
private static final String DESCRIPTION = "Could not find a valid Docker environment for Testcontainers.";
35+
36+
private static final String ACTION = """
37+
- Ensure a Docker-compatible container engine is installed and running.
38+
- If running Testcontainers in CI, ensure the runner has access to the daemon, typically by using a mounted socket or a Docker-in-Docker setup.
39+
- Review the Testcontainers documentation for troubleshooting and advanced configuration options.
40+
""";
41+
42+
@Override
43+
protected @Nullable FailureAnalysis analyze(Throwable rootFailure, IllegalStateException cause) {
44+
if (isDockerEnvironmentNotFoundError(cause)) {
45+
return new FailureAnalysis(DESCRIPTION, ACTION, cause);
46+
}
47+
return null;
48+
}
49+
50+
private boolean isDockerEnvironmentNotFoundError(IllegalStateException cause) {
51+
return StringUtils.hasText(cause.getMessage()) && cause.getMessage().contains(EXPECTED_MESSAGE);
52+
}
53+
54+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2012-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* Classes for diagnosing issues with Testcontainers.
19+
*/
20+
@NullMarked
21+
package org.springframework.boot.testcontainers.diagnostics;
22+
23+
import org.jspecify.annotations.NullMarked;

core/spring-boot-testcontainers/src/main/resources/META-INF/spring.factories

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@ org.springframework.boot.testcontainers.lifecycle.TestcontainersLifecycleApplica
66
org.springframework.test.context.ContextCustomizerFactory=\
77
org.springframework.boot.testcontainers.service.connection.ServiceConnectionContextCustomizerFactory
88

9+
# Failure Analyzers
10+
org.springframework.boot.diagnostics.FailureAnalyzer=\
11+
org.springframework.boot.testcontainers.diagnostics.DockerEnvironmentNotFoundFailureAnalyzer
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2012-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.testcontainers.diagnostics;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import org.springframework.boot.diagnostics.FailureAnalysis;
22+
import org.springframework.boot.diagnostics.FailureAnalyzer;
23+
import org.springframework.core.io.support.SpringFactoriesLoader;
24+
25+
import static org.assertj.core.api.Assertions.assertThat;
26+
27+
/**
28+
* Tests for {@link DockerEnvironmentNotFoundFailureAnalyzer}.
29+
*
30+
* @author Dmytro Nosan
31+
*/
32+
class DockerEnvironmentNotFoundFailureAnalyzerTests {
33+
34+
private final DockerEnvironmentNotFoundFailureAnalyzer analyzer = new DockerEnvironmentNotFoundFailureAnalyzer();
35+
36+
@Test
37+
void shouldReturnFailureAnalysisWhenMessageMatches() {
38+
IllegalStateException cause = new IllegalStateException(
39+
"Could not find a valid Docker environment. Please see logs and check configuration");
40+
FailureAnalysis analysis = this.analyzer
41+
.analyze(new RuntimeException("Root", new RuntimeException("Intermediate", cause)));
42+
assertThat(analysis).isNotNull();
43+
assertThat(analysis.getDescription())
44+
.isEqualTo("Could not find a valid Docker environment for Testcontainers.");
45+
assertThat(analysis.getAction())
46+
.contains("Ensure a Docker-compatible container engine is installed and running");
47+
assertThat(analysis.getCause()).isSameAs(cause);
48+
}
49+
50+
@Test
51+
void shouldReturnNullWhenMessageDoesNotMatch() {
52+
FailureAnalysis analysis = this.analyzer.analyze(new IllegalStateException("Some message"));
53+
assertThat(analysis).isNull();
54+
}
55+
56+
@Test
57+
void shouldReturnNullWhenMessageIsNull() {
58+
FailureAnalysis analysis = this.analyzer.analyze(new IllegalStateException());
59+
assertThat(analysis).isNull();
60+
}
61+
62+
@Test
63+
void shouldBeRegisteredInSpringFactories() {
64+
assertThat(SpringFactoriesLoader.forDefaultResourceLocation(getClass().getClassLoader())
65+
.load(FailureAnalyzer.class, (factoryType, factoryImplementationName, failure) -> {
66+
})).anyMatch(DockerEnvironmentNotFoundFailureAnalyzer.class::isInstance);
67+
}
68+
69+
}

0 commit comments

Comments
 (0)