Skip to content

Commit 8628f73

Browse files
committed
Ensure @AutoConfigureTestDatabase does not replace test databases
Update `@AutoConfigureTestDatabase` support so that by default test databases are not replaced. Fixes gh-35253
1 parent 2e28d26 commit 8628f73

File tree

16 files changed

+654
-22
lines changed

16 files changed

+654
-22
lines changed

buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -168,21 +168,23 @@ private ArchRule allBeanFactoryPostProcessorBeanMethodsShouldBeStaticAndHaveNoPa
168168
.and()
169169
.haveRawReturnType(
170170
Predicates.assignableTo("org.springframework.beans.factory.config.BeanFactoryPostProcessor"))
171-
.should(haveNoParameters())
171+
.should(onlyInjectEnvironment())
172172
.andShould()
173173
.beStatic()
174174
.allowEmptyShould(true);
175175
}
176176

177-
private ArchCondition<JavaMethod> haveNoParameters() {
178-
return new ArchCondition<>("have no parameters") {
177+
private ArchCondition<JavaMethod> onlyInjectEnvironment() {
178+
return new ArchCondition<>("only inject Environment") {
179179

180180
@Override
181181
public void check(JavaMethod item, ConditionEvents events) {
182182
List<JavaParameter> parameters = item.getParameters();
183-
if (!parameters.isEmpty()) {
184-
events
185-
.add(SimpleConditionEvent.violated(item, item.getDescription() + " should have no parameters"));
183+
for (JavaParameter parameter : parameters) {
184+
if (!"org.springframework.core.env.Environment".equals(parameter.getType().getName())) {
185+
events.add(SimpleConditionEvent.violated(item,
186+
item.getDescription() + " should only inject Environment"));
187+
}
186188
}
187189
}
188190

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2012-2024 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.autoconfigure.container;
18+
19+
import org.springframework.beans.factory.config.BeanDefinition;
20+
import org.springframework.core.AttributeAccessor;
21+
22+
/**
23+
* Metadata about a container image that can be added to an {@link AttributeAccessor}.
24+
* Primarily designed to be attached to {@link BeanDefinition BeanDefinitions} created in
25+
* support of Testcontainers or Docker Compose.
26+
*
27+
* @param imageName the contaimer image name or {@code null} if the image name is not yet
28+
* known
29+
* @author Phillip Webb
30+
* @since 3.4.0
31+
*/
32+
public record ContainerImageMetadata(String imageName) {
33+
34+
static final String NAME = ContainerImageMetadata.class.getName();
35+
36+
/**
37+
* Add this container image metadata to the given attributes.
38+
* @param attributes the attributes to add the metadata to
39+
*/
40+
public void addTo(AttributeAccessor attributes) {
41+
if (attributes != null) {
42+
attributes.setAttribute(NAME, this);
43+
}
44+
}
45+
46+
/**
47+
* Return {@code true} if {@link ContainerImageMetadata} has been added to the given
48+
* attributes.
49+
* @param attributes the attributes to check
50+
* @return if metadata is present
51+
*/
52+
public static boolean isPresent(AttributeAccessor attributes) {
53+
return getFrom(attributes) != null;
54+
}
55+
56+
/**
57+
* Return {@link ContainerImageMetadata} from the given attributes or {@code null} if
58+
* no metadata has been added.
59+
* @param attributes the attributes
60+
* @return the metadata or {@code null}
61+
*/
62+
public static ContainerImageMetadata getFrom(AttributeAccessor attributes) {
63+
return (attributes != null) ? (ContainerImageMetadata) attributes.getAttribute(NAME) : null;
64+
}
65+
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright 2012-2024 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+
* Support classes related to auto-configuration involving containers.
19+
*/
20+
package org.springframework.boot.autoconfigure.container;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright 2012-2024 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.autoconfigure.container;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import org.springframework.core.AttributeAccessor;
22+
import org.springframework.core.AttributeAccessorSupport;
23+
24+
import static org.assertj.core.api.Assertions.assertThat;
25+
26+
/**
27+
* Tests for {@link ContainerImageMetadata}.
28+
*
29+
* @author Phillip Webb
30+
*/
31+
class ContainerImageMetadataTests {
32+
33+
private ContainerImageMetadata metadata = new ContainerImageMetadata("test");
34+
35+
private AttributeAccessor attributes = new AttributeAccessorSupport() {
36+
37+
};
38+
39+
@Test
40+
void addToWhenAttributesIsNullDoesNothing() {
41+
this.metadata.addTo(null);
42+
}
43+
44+
@Test
45+
void addToAddsMetadata() {
46+
this.metadata.addTo(this.attributes);
47+
assertThat(this.attributes.getAttribute(ContainerImageMetadata.NAME)).isSameAs(this.metadata);
48+
}
49+
50+
@Test
51+
void isPresentWhenPresentReturnsTrue() {
52+
this.metadata.addTo(this.attributes);
53+
assertThat(ContainerImageMetadata.isPresent(this.attributes)).isTrue();
54+
}
55+
56+
@Test
57+
void isPresentWhenNotPresentReturnsFalse() {
58+
assertThat(ContainerImageMetadata.isPresent(this.attributes)).isFalse();
59+
}
60+
61+
@Test
62+
void isPresentWhenNullAttributesReturnsFalse() {
63+
assertThat(ContainerImageMetadata.isPresent(null)).isFalse();
64+
}
65+
66+
@Test
67+
void getFromWhenPresentReturnsMetadata() {
68+
this.metadata.addTo(this.attributes);
69+
assertThat(ContainerImageMetadata.getFrom(this.attributes)).isSameAs(this.metadata);
70+
}
71+
72+
@Test
73+
void getFromWhenNotPresentReturnsNull() {
74+
assertThat(ContainerImageMetadata.getFrom(this.attributes)).isNull();
75+
}
76+
77+
@Test
78+
void getFromWhenNullAttributesReturnsNull() {
79+
assertThat(ContainerImageMetadata.getFrom(null)).isNull();
80+
}
81+
82+
}

spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/DockerComposeServiceConnectionsApplicationListener.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@
2424

2525
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
2626
import org.springframework.beans.factory.support.RootBeanDefinition;
27+
import org.springframework.boot.autoconfigure.container.ContainerImageMetadata;
2728
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
2829
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactories;
2930
import org.springframework.boot.docker.compose.core.RunningService;
@@ -77,10 +78,13 @@ private void registerConnectionDetails(BeanDefinitionRegistry registry, List<Run
7778
@SuppressWarnings("unchecked")
7879
private <T> void register(BeanDefinitionRegistry registry, RunningService runningService,
7980
Class<?> connectionDetailsType, ConnectionDetails connectionDetails) {
81+
ContainerImageMetadata containerMetadata = new ContainerImageMetadata(runningService.image().toString());
8082
String beanName = getBeanName(runningService, connectionDetailsType);
8183
Class<T> beanType = (Class<T>) connectionDetails.getClass();
8284
Supplier<T> beanSupplier = () -> (T) connectionDetails;
83-
registry.registerBeanDefinition(beanName, new RootBeanDefinition(beanType, beanSupplier));
85+
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType, beanSupplier);
86+
containerMetadata.addTo(beanDefinition);
87+
registry.registerBeanDefinition(beanName, beanDefinition);
8488
}
8589

8690
private String getBeanName(RunningService runningService, Class<?> connectionDetailsType) {

spring-boot-project/spring-boot-test-autoconfigure/build.gradle

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,23 @@ dependencies {
1313
api(project(":spring-boot-project:spring-boot-test"))
1414
api(project(":spring-boot-project:spring-boot-autoconfigure"))
1515

16+
dockerTestImplementation(project(":spring-boot-project:spring-boot-docker-compose"))
1617
dockerTestImplementation(project(":spring-boot-project:spring-boot-testcontainers"))
1718
dockerTestImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support-docker"))
19+
dockerTestImplementation("com.zaxxer:HikariCP")
1820
dockerTestImplementation("io.projectreactor:reactor-test")
1921
dockerTestImplementation("com.redis:testcontainers-redis")
22+
dockerTestImplementation("com.h2database:h2")
2023
dockerTestImplementation("org.assertj:assertj-core")
2124
dockerTestImplementation("org.junit.jupiter:junit-jupiter")
25+
dockerTestImplementation("org.postgresql:postgresql")
2226
dockerTestImplementation("org.testcontainers:cassandra")
2327
dockerTestImplementation("org.testcontainers:couchbase")
2428
dockerTestImplementation("org.testcontainers:elasticsearch")
2529
dockerTestImplementation("org.testcontainers:junit-jupiter")
2630
dockerTestImplementation("org.testcontainers:mongodb")
2731
dockerTestImplementation("org.testcontainers:neo4j")
32+
dockerTestImplementation("org.testcontainers:postgresql")
2833
dockerTestImplementation("org.testcontainers:testcontainers")
2934

3035
dockerTestRuntimeOnly("io.lettuce:lettuce-core")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright 2012-2024 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.test.autoconfigure.jdbc;
18+
19+
import java.io.IOException;
20+
import java.io.UncheckedIOException;
21+
import java.nio.charset.StandardCharsets;
22+
import java.nio.file.Files;
23+
import java.nio.file.Path;
24+
25+
import javax.sql.DataSource;
26+
27+
import com.zaxxer.hikari.HikariDataSource;
28+
import org.junit.jupiter.api.Test;
29+
30+
import org.springframework.beans.factory.annotation.Autowired;
31+
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
32+
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
33+
import org.springframework.boot.test.autoconfigure.OverrideAutoConfiguration;
34+
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabaseDockerComposeIntegrationTests.SetupDockerCompose;
35+
import org.springframework.boot.test.context.SpringBootTest;
36+
import org.springframework.boot.testsupport.container.DisabledIfDockerUnavailable;
37+
import org.springframework.boot.testsupport.container.TestImage;
38+
import org.springframework.context.ApplicationContextInitializer;
39+
import org.springframework.context.ConfigurableApplicationContext;
40+
import org.springframework.context.annotation.Configuration;
41+
import org.springframework.core.io.ClassPathResource;
42+
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
43+
import org.springframework.test.context.ContextConfiguration;
44+
import org.springframework.test.context.support.TestPropertySourceUtils;
45+
46+
import static org.assertj.core.api.Assertions.assertThat;
47+
48+
/**
49+
* Integration tests for {@link AutoConfigureTestDatabase} with Docker Compose.
50+
*
51+
* @author Phillip Webb
52+
*/
53+
@SpringBootTest
54+
@ContextConfiguration(initializers = SetupDockerCompose.class)
55+
@AutoConfigureTestDatabase
56+
@OverrideAutoConfiguration(enabled = false)
57+
@DisabledIfDockerUnavailable
58+
class AutoConfigureTestDatabaseDockerComposeIntegrationTests {
59+
60+
@Autowired
61+
private DataSource dataSource;
62+
63+
@Test
64+
void dataSourceIsNotReplaced() {
65+
assertThat(this.dataSource).isInstanceOf(HikariDataSource.class).isNotInstanceOf(EmbeddedDatabase.class);
66+
}
67+
68+
@Configuration
69+
@ImportAutoConfiguration(DataSourceAutoConfiguration.class)
70+
static class Config {
71+
72+
}
73+
74+
static class SetupDockerCompose implements ApplicationContextInitializer<ConfigurableApplicationContext> {
75+
76+
@Override
77+
public void initialize(ConfigurableApplicationContext applicationContext) {
78+
try {
79+
Path composeFile = Files.createTempFile("", "-postgres-compose");
80+
String composeFileContent = new ClassPathResource("postgres-compose.yaml")
81+
.getContentAsString(StandardCharsets.UTF_8)
82+
.replace("{imageName}", TestImage.POSTGRESQL.toString());
83+
Files.writeString(composeFile, composeFileContent);
84+
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(applicationContext,
85+
"spring.docker.compose.skip.in-tests=false", "spring.docker.compose.stop.command=down",
86+
"spring.docker.compose.file=" + composeFile.toAbsolutePath().toString());
87+
}
88+
catch (IOException ex) {
89+
throw new UncheckedIOException(ex);
90+
}
91+
}
92+
93+
}
94+
95+
}

0 commit comments

Comments
 (0)