diff --git a/core/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/DockerEnvironmentNotFoundFailureAnalyzer.java b/core/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/DockerEnvironmentNotFoundFailureAnalyzer.java new file mode 100644 index 00000000000..0b096d70484 --- /dev/null +++ b/core/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/DockerEnvironmentNotFoundFailureAnalyzer.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.lifecycle; + +import org.jspecify.annotations.Nullable; + +import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; +import org.springframework.boot.diagnostics.FailureAnalysis; +import org.springframework.util.StringUtils; + +/** + * A {@code FailureAnalyzer} that performs analysis of failures caused by Testcontainers + * not finding a valid Docker environment. + * + * @author Dmytro Nosan + * @author Stephane Nicoll + */ +class DockerEnvironmentNotFoundFailureAnalyzer extends AbstractFailureAnalyzer { + + private static final String EXPECTED_MESSAGE = "Could not find a valid Docker environment"; + + private static final String DESCRIPTION = "Could not find a valid Docker environment for Testcontainers."; + + private static final String ACTION = """ + - Ensure a Docker-compatible container engine is installed and running. + - 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. + - Review the Testcontainers documentation for troubleshooting and advanced configuration options. + """; + + @Override + protected @Nullable FailureAnalysis analyze(Throwable rootFailure, IllegalStateException cause) { + if (StringUtils.hasText(cause.getMessage()) && cause.getMessage().contains(EXPECTED_MESSAGE)) { + return new FailureAnalysis(DESCRIPTION, ACTION, cause); + } + return null; + } + +} diff --git a/core/spring-boot-testcontainers/src/main/resources/META-INF/spring.factories b/core/spring-boot-testcontainers/src/main/resources/META-INF/spring.factories index 965d32bf8b6..c816883c85f 100644 --- a/core/spring-boot-testcontainers/src/main/resources/META-INF/spring.factories +++ b/core/spring-boot-testcontainers/src/main/resources/META-INF/spring.factories @@ -6,3 +6,6 @@ org.springframework.boot.testcontainers.lifecycle.TestcontainersLifecycleApplica org.springframework.test.context.ContextCustomizerFactory=\ org.springframework.boot.testcontainers.service.connection.ServiceConnectionContextCustomizerFactory +# Failure Analyzers +org.springframework.boot.diagnostics.FailureAnalyzer=\ +org.springframework.boot.testcontainers.lifecycle.DockerEnvironmentNotFoundFailureAnalyzer diff --git a/core/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/lifecycle/DockerEnvironmentNotFoundFailureAnalyzerTests.java b/core/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/lifecycle/DockerEnvironmentNotFoundFailureAnalyzerTests.java new file mode 100644 index 00000000000..2a777e73815 --- /dev/null +++ b/core/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/lifecycle/DockerEnvironmentNotFoundFailureAnalyzerTests.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.lifecycle; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.diagnostics.FailureAnalysis; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link DockerEnvironmentNotFoundFailureAnalyzer}. + * + * @author Dmytro Nosan + */ +class DockerEnvironmentNotFoundFailureAnalyzerTests { + + private final DockerEnvironmentNotFoundFailureAnalyzer analyzer = new DockerEnvironmentNotFoundFailureAnalyzer(); + + @Test + void shouldReturnFailureAnalysisWhenMessageMatches() { + IllegalStateException cause = new IllegalStateException( + "Could not find a valid Docker environment. Please see logs and check configuration"); + FailureAnalysis analysis = this.analyzer + .analyze(new RuntimeException("Root", new RuntimeException("Intermediate", cause))); + assertThat(analysis).isNotNull(); + assertThat(analysis.getDescription()) + .isEqualTo("Could not find a valid Docker environment for Testcontainers."); + assertThat(analysis.getAction()) + .contains("Ensure a Docker-compatible container engine is installed and running"); + assertThat(analysis.getCause()).isSameAs(cause); + } + + @Test + void shouldReturnNullWhenMessageDoesNotMatch() { + FailureAnalysis analysis = this.analyzer.analyze(new IllegalStateException("Some message")); + assertThat(analysis).isNull(); + } + + @Test + void shouldReturnNullWhenMessageIsNull() { + FailureAnalysis analysis = this.analyzer.analyze(new IllegalStateException()); + assertThat(analysis).isNull(); + } + +}