Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* 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.diagnostics;

import org.jspecify.annotations.Nullable;

import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.util.StringUtils;

/**
* Failure analyzer for the Docker environment wasn't found.
*
* @author Dmytro Nosan
*/
class DockerEnvironmentNotFoundFailureAnalyzer extends AbstractFailureAnalyzer<IllegalStateException> {

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 (isDockerEnvironmentNotFoundError(cause)) {
return new FailureAnalysis(DESCRIPTION, ACTION, cause);
}
return null;
}

private boolean isDockerEnvironmentNotFoundError(IllegalStateException cause) {
return StringUtils.hasText(cause.getMessage()) && cause.getMessage().contains(EXPECTED_MESSAGE);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* 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.
*/

/**
* Classes for diagnosing issues with Testcontainers.
*/
@NullMarked
package org.springframework.boot.testcontainers.diagnostics;

import org.jspecify.annotations.NullMarked;
Original file line number Diff line number Diff line change
Expand Up @@ -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.diagnostics.DockerEnvironmentNotFoundFailureAnalyzer
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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.diagnostics;

import org.junit.jupiter.api.Test;

import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.boot.diagnostics.FailureAnalyzer;
import org.springframework.core.io.support.SpringFactoriesLoader;

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();
}

@Test
void shouldBeRegisteredInSpringFactories() {
assertThat(SpringFactoriesLoader.forDefaultResourceLocation(getClass().getClassLoader())
.load(FailureAnalyzer.class, (factoryType, factoryImplementationName, failure) -> {
})).anyMatch(DockerEnvironmentNotFoundFailureAnalyzer.class::isInstance);
}

}