Ensure containers are started before binding datasource properties
Update `TestcontainersLifecycleBeanPostProcessor` so that containers are now initialized either on the first `postProcessAfterInitialization` call with a frozen configuration or just before a test container property is supplied. Prior to this commit, it was assumed that the first post-process call after the configuration was frozen was suitably early to initialize the containers. This turns out to not be no always the case. Specifically, in the `finishBeanFactoryInitialization` method of `AbstractApplicationContext` we see that `LoadTimeWeaverAware` beans are obtained before the configuration is frozen. One such bean is `DefaultPersistenceUnitManager` which is likely to need datasource properties that will require a started container. To fix the problem, the `TestcontainersPropertySource` now publishes a `BeforeTestcontainersPropertySuppliedEvent` to the ApplicationContext just before any value is supplied. By listening for this event, we can ensure that containers are initialized and started before any dynamic property is read. Fixes gh-38913
This commit is contained in:
parent
f59fa2e3f7
commit
89874d351a
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012-2023 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -20,6 +20,7 @@ import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||||
import org.springframework.boot.testcontainers.properties.TestcontainersPropertySource;
|
import org.springframework.boot.testcontainers.properties.TestcontainersPropertySource;
|
||||||
import org.springframework.core.MethodIntrospector;
|
import org.springframework.core.MethodIntrospector;
|
||||||
import org.springframework.core.annotation.MergedAnnotations;
|
import org.springframework.core.annotation.MergedAnnotations;
|
||||||
|
@ -43,16 +44,17 @@ class DynamicPropertySourceMethodsImporter {
|
||||||
this.environment = environment;
|
this.environment = environment;
|
||||||
}
|
}
|
||||||
|
|
||||||
void registerDynamicPropertySources(Class<?> definitionClass) {
|
void registerDynamicPropertySources(BeanDefinitionRegistry beanDefinitionRegistry, Class<?> definitionClass) {
|
||||||
Set<Method> methods = MethodIntrospector.selectMethods(definitionClass, this::isAnnotated);
|
Set<Method> methods = MethodIntrospector.selectMethods(definitionClass, this::isAnnotated);
|
||||||
if (methods.isEmpty()) {
|
if (methods.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DynamicPropertyRegistry registry = TestcontainersPropertySource.attach(this.environment);
|
DynamicPropertyRegistry dynamicPropertyRegistry = TestcontainersPropertySource.attach(this.environment,
|
||||||
|
beanDefinitionRegistry);
|
||||||
methods.forEach((method) -> {
|
methods.forEach((method) -> {
|
||||||
assertValid(method);
|
assertValid(method);
|
||||||
ReflectionUtils.makeAccessible(method);
|
ReflectionUtils.makeAccessible(method);
|
||||||
ReflectionUtils.invokeMethod(method, null, registry);
|
ReflectionUtils.invokeMethod(method, null, dynamicPropertyRegistry);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012-2023 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -62,7 +62,7 @@ class ImportTestcontainersRegistrar implements ImportBeanDefinitionRegistrar {
|
||||||
for (Class<?> definitionClass : definitionClasses) {
|
for (Class<?> definitionClass : definitionClasses) {
|
||||||
this.containerFieldsImporter.registerBeanDefinitions(registry, definitionClass);
|
this.containerFieldsImporter.registerBeanDefinitions(registry, definitionClass);
|
||||||
if (this.dynamicPropertySourceMethodsImporter != null) {
|
if (this.dynamicPropertySourceMethodsImporter != null) {
|
||||||
this.dynamicPropertySourceMethodsImporter.registerDynamicPropertySources(definitionClass);
|
this.dynamicPropertySourceMethodsImporter.registerDynamicPropertySources(registry, definitionClass);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012-2023 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -48,7 +48,10 @@ public class TestcontainersLifecycleApplicationContextInitializer
|
||||||
ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
|
ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
|
||||||
applicationContext.addBeanFactoryPostProcessor(new TestcontainersLifecycleBeanFactoryPostProcessor());
|
applicationContext.addBeanFactoryPostProcessor(new TestcontainersLifecycleBeanFactoryPostProcessor());
|
||||||
TestcontainersStartup startup = TestcontainersStartup.get(applicationContext.getEnvironment());
|
TestcontainersStartup startup = TestcontainersStartup.get(applicationContext.getEnvironment());
|
||||||
beanFactory.addBeanPostProcessor(new TestcontainersLifecycleBeanPostProcessor(beanFactory, startup));
|
TestcontainersLifecycleBeanPostProcessor beanPostProcessor = new TestcontainersLifecycleBeanPostProcessor(
|
||||||
|
beanFactory, startup);
|
||||||
|
beanFactory.addBeanPostProcessor(beanPostProcessor);
|
||||||
|
applicationContext.addApplicationListener(beanPostProcessor);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012-2023 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -38,6 +38,8 @@ import org.springframework.beans.factory.config.BeanDefinition;
|
||||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||||
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
|
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
|
||||||
|
import org.springframework.boot.testcontainers.properties.BeforeTestcontainersPropertySuppliedEvent;
|
||||||
|
import org.springframework.context.ApplicationListener;
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
import org.springframework.core.log.LogMessage;
|
import org.springframework.core.log.LogMessage;
|
||||||
|
@ -56,7 +58,8 @@ import org.springframework.core.log.LogMessage;
|
||||||
* @see TestcontainersLifecycleApplicationContextInitializer
|
* @see TestcontainersLifecycleApplicationContextInitializer
|
||||||
*/
|
*/
|
||||||
@Order(Ordered.LOWEST_PRECEDENCE)
|
@Order(Ordered.LOWEST_PRECEDENCE)
|
||||||
class TestcontainersLifecycleBeanPostProcessor implements DestructionAwareBeanPostProcessor {
|
class TestcontainersLifecycleBeanPostProcessor
|
||||||
|
implements DestructionAwareBeanPostProcessor, ApplicationListener<BeforeTestcontainersPropertySuppliedEvent> {
|
||||||
|
|
||||||
private static final Log logger = LogFactory.getLog(TestcontainersLifecycleBeanPostProcessor.class);
|
private static final Log logger = LogFactory.getLog(TestcontainersLifecycleBeanPostProcessor.class);
|
||||||
|
|
||||||
|
@ -74,9 +77,14 @@ class TestcontainersLifecycleBeanPostProcessor implements DestructionAwareBeanPo
|
||||||
this.startup = startup;
|
this.startup = startup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onApplicationEvent(BeforeTestcontainersPropertySuppliedEvent event) {
|
||||||
|
initializeContainers();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||||
if (this.beanFactory.isConfigurationFrozen() && this.containersInitialized.compareAndSet(false, true)) {
|
if (this.beanFactory.isConfigurationFrozen()) {
|
||||||
initializeContainers();
|
initializeContainers();
|
||||||
}
|
}
|
||||||
if (bean instanceof Startable startableBean) {
|
if (bean instanceof Startable startableBean) {
|
||||||
|
@ -121,6 +129,7 @@ class TestcontainersLifecycleBeanPostProcessor implements DestructionAwareBeanPo
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeContainers() {
|
private void initializeContainers() {
|
||||||
|
if (this.containersInitialized.compareAndSet(false, true)) {
|
||||||
logger.trace("Initializing containers");
|
logger.trace("Initializing containers");
|
||||||
List<String> beanNames = List.of(this.beanFactory.getBeanNamesForType(ContainerState.class, false, false));
|
List<String> beanNames = List.of(this.beanFactory.getBeanNamesForType(ContainerState.class, false, false));
|
||||||
List<Object> beans = getBeans(beanNames);
|
List<Object> beans = getBeans(beanNames);
|
||||||
|
@ -132,6 +141,7 @@ class TestcontainersLifecycleBeanPostProcessor implements DestructionAwareBeanPo
|
||||||
this.containersInitialized.set(false);
|
this.containersInitialized.set(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private List<Object> getBeans(List<String> beanNames) {
|
private List<Object> getBeans(List<String> beanNames) {
|
||||||
List<Object> beans = new ArrayList<>(beanNames.size());
|
List<Object> beans = new ArrayList<>(beanNames.size());
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2024 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.properties;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.springframework.context.ApplicationEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event published just before the {@link Supplier value supplier} of a
|
||||||
|
* {@link TestcontainersPropertySource} property is called.
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
* @since 3.2.2
|
||||||
|
*/
|
||||||
|
public class BeforeTestcontainersPropertySuppliedEvent extends ApplicationEvent {
|
||||||
|
|
||||||
|
private final String propertyName;
|
||||||
|
|
||||||
|
BeforeTestcontainersPropertySuppliedEvent(TestcontainersPropertySource source, String propertyName) {
|
||||||
|
super(source);
|
||||||
|
this.propertyName = propertyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the name of the property about to be supplied.
|
||||||
|
* @return the propertyName the property name
|
||||||
|
*/
|
||||||
|
public String getPropertyName() {
|
||||||
|
return this.propertyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012-2023 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -19,10 +19,20 @@ package org.springframework.boot.testcontainers.properties;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import org.testcontainers.containers.Container;
|
import org.testcontainers.containers.Container;
|
||||||
|
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||||
|
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||||
|
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||||
|
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||||
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
|
import org.springframework.context.ApplicationEventPublisherAware;
|
||||||
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
import org.springframework.core.env.ConfigurableEnvironment;
|
import org.springframework.core.env.ConfigurableEnvironment;
|
||||||
import org.springframework.core.env.EnumerablePropertySource;
|
import org.springframework.core.env.EnumerablePropertySource;
|
||||||
import org.springframework.core.env.Environment;
|
import org.springframework.core.env.Environment;
|
||||||
|
@ -44,6 +54,8 @@ public class TestcontainersPropertySource extends EnumerablePropertySource<Map<S
|
||||||
|
|
||||||
private final DynamicPropertyRegistry registry;
|
private final DynamicPropertyRegistry registry;
|
||||||
|
|
||||||
|
private final Set<ApplicationEventPublisher> eventPublishers = new CopyOnWriteArraySet<>();
|
||||||
|
|
||||||
TestcontainersPropertySource() {
|
TestcontainersPropertySource() {
|
||||||
this(Collections.synchronizedMap(new LinkedHashMap<>()));
|
this(Collections.synchronizedMap(new LinkedHashMap<>()));
|
||||||
}
|
}
|
||||||
|
@ -57,10 +69,20 @@ public class TestcontainersPropertySource extends EnumerablePropertySource<Map<S
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addEventPublisher(ApplicationEventPublisher eventPublisher) {
|
||||||
|
this.eventPublishers.add(eventPublisher);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getProperty(String name) {
|
public Object getProperty(String name) {
|
||||||
Supplier<Object> valueSupplier = this.source.get(name);
|
Supplier<Object> valueSupplier = this.source.get(name);
|
||||||
return (valueSupplier != null) ? valueSupplier.get() : null;
|
return (valueSupplier != null) ? getProperty(name, valueSupplier) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object getProperty(String name, Supplier<Object> valueSupplier) {
|
||||||
|
BeforeTestcontainersPropertySuppliedEvent event = new BeforeTestcontainersPropertySuppliedEvent(this, name);
|
||||||
|
this.eventPublishers.forEach((eventPublisher) -> eventPublisher.publishEvent(event));
|
||||||
|
return valueSupplier.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -74,20 +96,73 @@ public class TestcontainersPropertySource extends EnumerablePropertySource<Map<S
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DynamicPropertyRegistry attach(Environment environment) {
|
public static DynamicPropertyRegistry attach(Environment environment) {
|
||||||
Assert.state(environment instanceof ConfigurableEnvironment,
|
return attach(environment, null);
|
||||||
"TestcontainersPropertySource can only be attached to a ConfigurableEnvironment");
|
|
||||||
return attach((ConfigurableEnvironment) environment);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DynamicPropertyRegistry attach(ConfigurableEnvironment environment) {
|
static DynamicPropertyRegistry attach(ConfigurableApplicationContext applicationContext) {
|
||||||
|
return attach(applicationContext.getEnvironment(), applicationContext, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DynamicPropertyRegistry attach(Environment environment, BeanDefinitionRegistry registry) {
|
||||||
|
return attach(environment, null, registry);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DynamicPropertyRegistry attach(Environment environment, ApplicationEventPublisher eventPublisher,
|
||||||
|
BeanDefinitionRegistry registry) {
|
||||||
|
Assert.state(environment instanceof ConfigurableEnvironment,
|
||||||
|
"TestcontainersPropertySource can only be attached to a ConfigurableEnvironment");
|
||||||
|
TestcontainersPropertySource propertySource = getOrAdd((ConfigurableEnvironment) environment);
|
||||||
|
if (eventPublisher != null) {
|
||||||
|
propertySource.addEventPublisher(eventPublisher);
|
||||||
|
}
|
||||||
|
else if (registry != null) {
|
||||||
|
registry.registerBeanDefinition(EventPublisherRegistrar.NAME, new RootBeanDefinition(
|
||||||
|
EventPublisherRegistrar.class, () -> new EventPublisherRegistrar(environment)));
|
||||||
|
}
|
||||||
|
return propertySource.registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
static TestcontainersPropertySource getOrAdd(ConfigurableEnvironment environment) {
|
||||||
PropertySource<?> propertySource = environment.getPropertySources().get(NAME);
|
PropertySource<?> propertySource = environment.getPropertySources().get(NAME);
|
||||||
if (propertySource == null) {
|
if (propertySource == null) {
|
||||||
environment.getPropertySources().addFirst(new TestcontainersPropertySource());
|
environment.getPropertySources().addFirst(new TestcontainersPropertySource());
|
||||||
return attach(environment);
|
return getOrAdd(environment);
|
||||||
}
|
}
|
||||||
Assert.state(propertySource instanceof TestcontainersPropertySource,
|
Assert.state(propertySource instanceof TestcontainersPropertySource,
|
||||||
"Incorrect DynamicValuesPropertySource type registered");
|
"Incorrect DynamicValuesPropertySource type registered");
|
||||||
return ((TestcontainersPropertySource) propertySource).registry;
|
return ((TestcontainersPropertySource) propertySource);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link BeanFactoryPostProcessor} to register the {@link ApplicationEventPublisher}
|
||||||
|
* to the {@link TestcontainersPropertySource}. This class is a
|
||||||
|
* {@link BeanFactoryPostProcessor} so that it is initialized as early as possible.
|
||||||
|
*/
|
||||||
|
private static class EventPublisherRegistrar implements BeanFactoryPostProcessor, ApplicationEventPublisherAware {
|
||||||
|
|
||||||
|
static final String NAME = EventPublisherRegistrar.class.getName();
|
||||||
|
|
||||||
|
private final Environment environment;
|
||||||
|
|
||||||
|
private ApplicationEventPublisher eventPublisher;
|
||||||
|
|
||||||
|
EventPublisherRegistrar(Environment environment) {
|
||||||
|
this.environment = environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
|
||||||
|
this.eventPublisher = eventPublisher;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||||
|
if (this.eventPublisher != null) {
|
||||||
|
TestcontainersPropertySource.getOrAdd((ConfigurableEnvironment) this.environment)
|
||||||
|
.addEventPublisher(this.eventPublisher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012-2023 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,9 +16,12 @@
|
||||||
|
|
||||||
package org.springframework.boot.testcontainers.properties;
|
package org.springframework.boot.testcontainers.properties;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.core.env.ConfigurableEnvironment;
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
import org.springframework.test.context.DynamicPropertyRegistry;
|
import org.springframework.test.context.DynamicPropertyRegistry;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,6 +31,8 @@ import org.springframework.test.context.DynamicPropertyRegistry;
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
* @since 3.1.0
|
* @since 3.1.0
|
||||||
*/
|
*/
|
||||||
|
@AutoConfiguration
|
||||||
|
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||||
@ConditionalOnClass(DynamicPropertyRegistry.class)
|
@ConditionalOnClass(DynamicPropertyRegistry.class)
|
||||||
public class TestcontainersPropertySourceAutoConfiguration {
|
public class TestcontainersPropertySourceAutoConfiguration {
|
||||||
|
|
||||||
|
@ -35,8 +40,8 @@ public class TestcontainersPropertySourceAutoConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
DynamicPropertyRegistry dynamicPropertyRegistry(ConfigurableEnvironment environment) {
|
static DynamicPropertyRegistry dynamicPropertyRegistry(ConfigurableApplicationContext applicationContext) {
|
||||||
return TestcontainersPropertySource.attach(environment);
|
return TestcontainersPropertySource.attach(applicationContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2024 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.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.testcontainers.containers.PostgreSQLContainer;
|
||||||
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.boot.test.context.TestConfiguration;
|
||||||
|
import org.springframework.boot.testcontainers.context.ImportTestcontainers;
|
||||||
|
import org.springframework.boot.testcontainers.lifecycle.TestcontainersImportWithPropertiesInjectedIntoLoadTimeWeaverAwareBeanIntegrationTests.Containers;
|
||||||
|
import org.springframework.boot.testsupport.testcontainers.DisabledIfDockerUnavailable;
|
||||||
|
import org.springframework.boot.testsupport.testcontainers.DockerImageNames;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.weaving.LoadTimeWeaverAware;
|
||||||
|
import org.springframework.instrument.classloading.LoadTimeWeaver;
|
||||||
|
import org.springframework.test.annotation.DirtiesContext;
|
||||||
|
import org.springframework.test.context.DynamicPropertyRegistry;
|
||||||
|
import org.springframework.test.context.DynamicPropertySource;
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Phillip Webb
|
||||||
|
*/
|
||||||
|
@ExtendWith(SpringExtension.class)
|
||||||
|
@DirtiesContext
|
||||||
|
@DisabledIfDockerUnavailable
|
||||||
|
@ImportTestcontainers(Containers.class)
|
||||||
|
class TestcontainersImportWithPropertiesInjectedIntoLoadTimeWeaverAwareBeanIntegrationTests {
|
||||||
|
|
||||||
|
// gh-38913
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void starts() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestConfiguration
|
||||||
|
@EnableConfigurationProperties(MockDataSourceProperties.class)
|
||||||
|
static class Config {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
MockEntityManager mockEntityManager(MockDataSourceProperties properties) {
|
||||||
|
return new MockEntityManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class MockEntityManager implements LoadTimeWeaverAware {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLoadTimeWeaver(LoadTimeWeaver loadTimeWeaver) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConfigurationProperties("spring.datasource")
|
||||||
|
public static class MockDataSourceProperties {
|
||||||
|
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
public String getUrl() {
|
||||||
|
return this.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUrl(String url) {
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Containers {
|
||||||
|
|
||||||
|
@Container
|
||||||
|
static PostgreSQLContainer<?> container = new PostgreSQLContainer<>(DockerImageNames.postgresql());
|
||||||
|
|
||||||
|
@DynamicPropertySource
|
||||||
|
static void setConnectionProperties(DynamicPropertyRegistry registry) {
|
||||||
|
registry.add("spring.datasource.url", container::getJdbcUrl);
|
||||||
|
registry.add("spring.datasource.password", container::getPassword);
|
||||||
|
registry.add("spring.datasource.username", container::getUsername);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012-2023 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,6 +16,9 @@
|
||||||
|
|
||||||
package org.springframework.boot.testcontainers.properties;
|
package org.springframework.boot.testcontainers.properties;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||||
|
@ -25,6 +28,7 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||||
import org.springframework.boot.testcontainers.lifecycle.TestcontainersLifecycleApplicationContextInitializer;
|
import org.springframework.boot.testcontainers.lifecycle.TestcontainersLifecycleApplicationContextInitializer;
|
||||||
import org.springframework.boot.testsupport.testcontainers.DisabledIfDockerUnavailable;
|
import org.springframework.boot.testsupport.testcontainers.DisabledIfDockerUnavailable;
|
||||||
import org.springframework.boot.testsupport.testcontainers.RedisContainer;
|
import org.springframework.boot.testsupport.testcontainers.RedisContainer;
|
||||||
|
import org.springframework.context.ApplicationEvent;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
|
@ -46,10 +50,15 @@ class TestcontainersPropertySourceAutoConfigurationTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void containerBeanMethodContributesProperties() {
|
void containerBeanMethodContributesProperties() {
|
||||||
this.contextRunner.withUserConfiguration(ContainerAndPropertiesConfiguration.class).run((context) -> {
|
List<ApplicationEvent> events = new ArrayList<>();
|
||||||
|
this.contextRunner.withUserConfiguration(ContainerAndPropertiesConfiguration.class)
|
||||||
|
.withInitializer((context) -> context.addApplicationListener(events::add))
|
||||||
|
.run((context) -> {
|
||||||
TestBean testBean = context.getBean(TestBean.class);
|
TestBean testBean = context.getBean(TestBean.class);
|
||||||
RedisContainer redisContainer = context.getBean(RedisContainer.class);
|
RedisContainer redisContainer = context.getBean(RedisContainer.class);
|
||||||
assertThat(testBean.getUsingPort()).isEqualTo(redisContainer.getFirstMappedPort());
|
assertThat(testBean.getUsingPort()).isEqualTo(redisContainer.getFirstMappedPort());
|
||||||
|
assertThat(events.stream().filter(BeforeTestcontainersPropertySuppliedEvent.class::isInstance))
|
||||||
|
.hasSize(1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012-2023 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,10 +16,15 @@
|
||||||
|
|
||||||
package org.springframework.boot.testcontainers.properties;
|
package org.springframework.boot.testcontainers.properties;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||||
|
import org.springframework.context.ApplicationEvent;
|
||||||
|
import org.springframework.context.support.GenericApplicationContext;
|
||||||
import org.springframework.core.env.EnumerablePropertySource;
|
import org.springframework.core.env.EnumerablePropertySource;
|
||||||
import org.springframework.core.env.PropertySource;
|
import org.springframework.core.env.PropertySource;
|
||||||
import org.springframework.mock.env.MockEnvironment;
|
import org.springframework.mock.env.MockEnvironment;
|
||||||
|
@ -101,4 +106,20 @@ class TestcontainersPropertySourceTests {
|
||||||
assertThat(p1).isSameAs(p2);
|
assertThat(p1).isSameAs(p2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getPropertyPublishesEvent() {
|
||||||
|
try (GenericApplicationContext applicationContext = new GenericApplicationContext()) {
|
||||||
|
List<ApplicationEvent> events = new ArrayList<>();
|
||||||
|
applicationContext.addApplicationListener(events::add);
|
||||||
|
DynamicPropertyRegistry registry = TestcontainersPropertySource.attach(applicationContext.getEnvironment(),
|
||||||
|
(BeanDefinitionRegistry) applicationContext.getBeanFactory());
|
||||||
|
applicationContext.refresh();
|
||||||
|
registry.add("test", () -> "spring");
|
||||||
|
assertThat(applicationContext.getEnvironment().containsProperty("test")).isTrue();
|
||||||
|
assertThat(events.isEmpty());
|
||||||
|
assertThat(applicationContext.getEnvironment().getProperty("test")).isEqualTo("spring");
|
||||||
|
assertThat(events.stream().filter(BeforeTestcontainersPropertySuppliedEvent.class::isInstance)).hasSize(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue