From ac2b0093c7ee5a6cd04fd29e14595855ef2c1f9e Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Mon, 15 Apr 2019 15:40:38 -0700 Subject: [PATCH] Disable DevTools' post-processors and auto-config when running tests Closes gh-5307 --- .../devtools/DevtoolsEnablementDeducer.java | 68 +++++++++++++++ .../DevToolsDataSourceAutoConfiguration.java | 2 +- .../OnEnabledDevtoolsCondition.java | 46 +++++++++++ .../RemoteDevToolsAutoConfiguration.java | 2 + .../DevToolsHomePropertiesPostProcessor.java | 27 +++--- ...DevToolsPropertyDefaultsPostProcessor.java | 4 +- .../restart/DefaultRestartInitializer.java | 38 +-------- ...ToolsDataSourceAutoConfigurationTests.java | 29 +++++-- ...ooledDataSourceAutoConfigurationTests.java | 65 +++++++-------- .../LocalDevToolsAutoConfigurationTests.java | 82 +++++++++++-------- .../OnEnabledDevtoolsConditionTests.java | 73 +++++++++++++++++ .../RemoteDevToolsAutoConfigurationTests.java | 69 +++++++++++----- .../DevToolPropertiesIntegrationTests.java | 40 ++++++--- ...ToolsHomePropertiesPostProcessorTests.java | 17 +++- 14 files changed, 411 insertions(+), 151 deletions(-) create mode 100644 spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/DevtoolsEnablementDeducer.java create mode 100644 spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/OnEnabledDevtoolsCondition.java create mode 100644 spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/OnEnabledDevtoolsConditionTests.java diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/DevtoolsEnablementDeducer.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/DevtoolsEnablementDeducer.java new file mode 100644 index 00000000000..85702fa1c6e --- /dev/null +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/DevtoolsEnablementDeducer.java @@ -0,0 +1,68 @@ +/* + * Copyright 2012-2019 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.devtools; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * Utility to deduce if Devtools should be enabled in the current context. + * + * @author Madhura Bhave + */ +public final class DevtoolsEnablementDeducer { + + private static final Set SKIPPED_STACK_ELEMENTS; + + static { + Set skipped = new LinkedHashSet<>(); + skipped.add("org.junit.runners."); + skipped.add("org.junit.platform."); + skipped.add("org.springframework.boot.test."); + skipped.add("cucumber.runtime."); + SKIPPED_STACK_ELEMENTS = Collections.unmodifiableSet(skipped); + } + + private DevtoolsEnablementDeducer() { + } + + /** + * Checks if a specific {@link StackTraceElement} in the current thread's stacktrace + * should cause devtools to be disabled. + * @param thread the current thread + * @return {@code true} if devtools should be enabled skipped + */ + public static boolean shouldEnable(Thread thread) { + for (StackTraceElement element : thread.getStackTrace()) { + if (isSkippedStackElement(element)) { + return false; + } + } + return true; + } + + private static boolean isSkippedStackElement(StackTraceElement element) { + for (String skipped : SKIPPED_STACK_ELEMENTS) { + if (element.getClassName().startsWith(skipped)) { + return true; + } + } + return false; + } + +} diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsDataSourceAutoConfiguration.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsDataSourceAutoConfiguration.java index 9ee38e2c91f..e224606eed2 100644 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsDataSourceAutoConfiguration.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsDataSourceAutoConfiguration.java @@ -55,7 +55,7 @@ import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; * @since 1.3.3 */ @AutoConfigureAfter(DataSourceAutoConfiguration.class) -@Conditional(DevToolsDataSourceCondition.class) +@Conditional({ OnEnabledDevtoolsCondition.class, DevToolsDataSourceCondition.class }) @Configuration(proxyBeanMethods = false) public class DevToolsDataSourceAutoConfiguration { diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/OnEnabledDevtoolsCondition.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/OnEnabledDevtoolsCondition.java new file mode 100644 index 00000000000..29dba33ff42 --- /dev/null +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/OnEnabledDevtoolsCondition.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2019 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.devtools.autoconfigure; + +import org.springframework.boot.autoconfigure.condition.ConditionMessage; +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.boot.devtools.DevtoolsEnablementDeducer; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * A condition that checks if DevTools should be enabled. + * + * @author Madhura Bhave + */ +public class OnEnabledDevtoolsCondition extends SpringBootCondition { + + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, + AnnotatedTypeMetadata metadata) { + ConditionMessage.Builder message = ConditionMessage.forCondition("Devtools"); + boolean shouldEnable = DevtoolsEnablementDeducer + .shouldEnable(Thread.currentThread()); + if (!shouldEnable) { + return ConditionOutcome.noMatch( + message.because("devtools is disabled for current context.")); + } + return ConditionOutcome.match(message.because("devtools enabled.")); + } + +} diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/RemoteDevToolsAutoConfiguration.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/RemoteDevToolsAutoConfiguration.java index 4bccdb379d6..e67fb413129 100644 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/RemoteDevToolsAutoConfiguration.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/RemoteDevToolsAutoConfiguration.java @@ -43,6 +43,7 @@ import org.springframework.boot.devtools.restart.server.HttpRestartServer; import org.springframework.boot.devtools.restart.server.HttpRestartServerHandler; import org.springframework.boot.devtools.restart.server.SourceFolderUrlFilter; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.http.server.ServerHttpRequest; @@ -55,6 +56,7 @@ import org.springframework.http.server.ServerHttpRequest; * @since 1.3.0 */ @Configuration(proxyBeanMethods = false) +@Conditional(OnEnabledDevtoolsCondition.class) @ConditionalOnProperty(prefix = "spring.devtools.remote", name = "secret") @ConditionalOnClass({ Filter.class, ServerHttpRequest.class }) @EnableConfigurationProperties({ ServerProperties.class, DevToolsProperties.class }) diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/env/DevToolsHomePropertiesPostProcessor.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/env/DevToolsHomePropertiesPostProcessor.java index 314121f31a6..aa248beb407 100644 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/env/DevToolsHomePropertiesPostProcessor.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/env/DevToolsHomePropertiesPostProcessor.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.util.Properties; import org.springframework.boot.SpringApplication; +import org.springframework.boot.devtools.DevtoolsEnablementDeducer; import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.PropertiesPropertySource; @@ -43,18 +44,20 @@ public class DevToolsHomePropertiesPostProcessor implements EnvironmentPostProce @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { - File home = getHomeFolder(); - File propertyFile = (home != null) ? new File(home, FILE_NAME) : null; - if (propertyFile != null && propertyFile.exists() && propertyFile.isFile()) { - FileSystemResource resource = new FileSystemResource(propertyFile); - Properties properties; - try { - properties = PropertiesLoaderUtils.loadProperties(resource); - environment.getPropertySources().addFirst( - new PropertiesPropertySource("devtools-local", properties)); - } - catch (IOException ex) { - throw new IllegalStateException("Unable to load " + FILE_NAME, ex); + if (DevtoolsEnablementDeducer.shouldEnable(Thread.currentThread())) { + File home = getHomeFolder(); + File propertyFile = (home != null) ? new File(home, FILE_NAME) : null; + if (propertyFile != null && propertyFile.exists() && propertyFile.isFile()) { + FileSystemResource resource = new FileSystemResource(propertyFile); + Properties properties; + try { + properties = PropertiesLoaderUtils.loadProperties(resource); + environment.getPropertySources().addFirst( + new PropertiesPropertySource("devtools-local", properties)); + } + catch (IOException ex) { + throw new IllegalStateException("Unable to load " + FILE_NAME, ex); + } } } } diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/env/DevToolsPropertyDefaultsPostProcessor.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/env/DevToolsPropertyDefaultsPostProcessor.java index 87e7f4e3371..253ac6e5c8f 100755 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/env/DevToolsPropertyDefaultsPostProcessor.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/env/DevToolsPropertyDefaultsPostProcessor.java @@ -23,6 +23,7 @@ import java.util.Map; import org.apache.commons.logging.Log; import org.springframework.boot.SpringApplication; +import org.springframework.boot.devtools.DevtoolsEnablementDeducer; import org.springframework.boot.devtools.logger.DevToolsLogFactory; import org.springframework.boot.devtools.restart.Restarter; import org.springframework.boot.env.EnvironmentPostProcessor; @@ -79,7 +80,8 @@ public class DevToolsPropertyDefaultsPostProcessor implements EnvironmentPostPro @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { - if (isLocalApplication(environment)) { + if (DevtoolsEnablementDeducer.shouldEnable(Thread.currentThread()) + && isLocalApplication(environment)) { if (canAddProperties(environment)) { logger.info("Devtools property defaults active! Set '" + ENABLED + "' to 'false' to disable"); diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/DefaultRestartInitializer.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/DefaultRestartInitializer.java index 354b5887370..0d241228d21 100644 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/DefaultRestartInitializer.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/DefaultRestartInitializer.java @@ -17,9 +17,8 @@ package org.springframework.boot.devtools.restart; import java.net.URL; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Set; + +import org.springframework.boot.devtools.DevtoolsEnablementDeducer; /** * Default {@link RestartInitializer} that only enable initial restart when running a @@ -32,26 +31,13 @@ import java.util.Set; */ public class DefaultRestartInitializer implements RestartInitializer { - private static final Set SKIPPED_STACK_ELEMENTS; - - static { - Set skipped = new LinkedHashSet<>(); - skipped.add("org.junit.runners."); - skipped.add("org.junit.platform."); - skipped.add("org.springframework.boot.test."); - skipped.add("cucumber.runtime."); - SKIPPED_STACK_ELEMENTS = Collections.unmodifiableSet(skipped); - } - @Override public URL[] getInitialUrls(Thread thread) { if (!isMain(thread)) { return null; } - for (StackTraceElement element : thread.getStackTrace()) { - if (isSkippedStackElement(element)) { - return null; - } + if (!DevtoolsEnablementDeducer.shouldEnable(thread)) { + return null; } return getUrls(thread); } @@ -67,22 +53,6 @@ public class DefaultRestartInitializer implements RestartInitializer { .getClass().getName().contains("AppClassLoader"); } - /** - * Checks if a specific {@link StackTraceElement} should cause the initializer to be - * skipped. - * @param element the stack element to check - * @return {@code true} if the stack element means that the initializer should be - * skipped - */ - private boolean isSkippedStackElement(StackTraceElement element) { - for (String skipped : SKIPPED_STACK_ELEMENTS) { - if (element.getClassName().startsWith(skipped)) { - return true; - } - } - return false; - } - /** * Return the URLs that should be used with initialization. * @param thread the source thread diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/AbstractDevToolsDataSourceAutoConfigurationTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/AbstractDevToolsDataSourceAutoConfigurationTests.java index 27324110f13..6fc1309b4ff 100644 --- a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/AbstractDevToolsDataSourceAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/AbstractDevToolsDataSourceAutoConfigurationTests.java @@ -20,6 +20,9 @@ import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.Collection; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; import javax.sql.DataSource; @@ -49,18 +52,18 @@ import static org.mockito.Mockito.verify; public abstract class AbstractDevToolsDataSourceAutoConfigurationTests { @Test - public void singleManuallyConfiguredDataSourceIsNotClosed() throws SQLException { - ConfigurableApplicationContext context = createContext( - SingleDataSourceConfiguration.class); + public void singleManuallyConfiguredDataSourceIsNotClosed() throws Exception { + ConfigurableApplicationContext context = getContext( + () -> createContext(SingleDataSourceConfiguration.class)); DataSource dataSource = context.getBean(DataSource.class); Statement statement = configureDataSourceBehavior(dataSource); verify(statement, never()).execute("SHUTDOWN"); } @Test - public void multipleDataSourcesAreIgnored() throws SQLException { - ConfigurableApplicationContext context = createContext( - MultipleDataSourcesConfiguration.class); + public void multipleDataSourcesAreIgnored() throws Exception { + ConfigurableApplicationContext context = getContext( + () -> createContext(MultipleDataSourcesConfiguration.class)); Collection dataSources = context.getBeansOfType(DataSource.class) .values(); for (DataSource dataSource : dataSources) { @@ -90,6 +93,20 @@ public abstract class AbstractDevToolsDataSourceAutoConfigurationTests { return statement; } + protected ConfigurableApplicationContext getContext( + Supplier supplier) throws Exception { + CountDownLatch latch = new CountDownLatch(1); + AtomicReference atomicReference = new AtomicReference<>(); + Thread thread = new Thread(() -> { + ConfigurableApplicationContext context = supplier.get(); + latch.countDown(); + atomicReference.getAndSet(context); + }); + thread.start(); + thread.join(); + return atomicReference.get(); + } + protected final ConfigurableApplicationContext createContext(Class... classes) { return this.createContext(null, classes); } diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/DevToolsPooledDataSourceAutoConfigurationTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/DevToolsPooledDataSourceAutoConfigurationTests.java index bedfb3fae3f..bacdfde185e 100644 --- a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/DevToolsPooledDataSourceAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/DevToolsPooledDataSourceAutoConfigurationTests.java @@ -17,7 +17,6 @@ package org.springframework.boot.devtools.autoconfigure; import java.io.IOException; -import java.sql.SQLException; import java.sql.Statement; import javax.sql.DataSource; @@ -58,9 +57,9 @@ public class DevToolsPooledDataSourceAutoConfigurationTests } @Test - public void autoConfiguredInMemoryDataSourceIsShutdown() throws SQLException { - ConfigurableApplicationContext context = createContext( - DataSourceAutoConfiguration.class, DataSourceSpyConfiguration.class); + public void autoConfiguredInMemoryDataSourceIsShutdown() throws Exception { + ConfigurableApplicationContext context = getContext(() -> createContext( + DataSourceAutoConfiguration.class, DataSourceSpyConfiguration.class)); Statement statement = configureDataSourceBehavior( context.getBean(DataSource.class)); context.close(); @@ -68,9 +67,10 @@ public class DevToolsPooledDataSourceAutoConfigurationTests } @Test - public void autoConfiguredExternalDataSourceIsNotShutdown() throws SQLException { - ConfigurableApplicationContext context = createContext("org.postgresql.Driver", - DataSourceAutoConfiguration.class, DataSourceSpyConfiguration.class); + public void autoConfiguredExternalDataSourceIsNotShutdown() throws Exception { + ConfigurableApplicationContext context = getContext(() -> createContext( + "org.postgresql.Driver", DataSourceAutoConfiguration.class, + DataSourceSpyConfiguration.class)); Statement statement = configureDataSourceBehavior( context.getBean(DataSource.class)); context.close(); @@ -78,10 +78,10 @@ public class DevToolsPooledDataSourceAutoConfigurationTests } @Test - public void h2ServerIsNotShutdown() throws SQLException { - ConfigurableApplicationContext context = createContext("org.h2.Driver", - "jdbc:h2:hsql://localhost", DataSourceAutoConfiguration.class, - DataSourceSpyConfiguration.class); + public void h2ServerIsNotShutdown() throws Exception { + ConfigurableApplicationContext context = getContext(() -> createContext( + "org.h2.Driver", "jdbc:h2:hsql://localhost", + DataSourceAutoConfiguration.class, DataSourceSpyConfiguration.class)); Statement statement = configureDataSourceBehavior( context.getBean(DataSource.class)); context.close(); @@ -89,10 +89,10 @@ public class DevToolsPooledDataSourceAutoConfigurationTests } @Test - public void inMemoryH2IsShutdown() throws SQLException { - ConfigurableApplicationContext context = createContext("org.h2.Driver", - "jdbc:h2:mem:test", DataSourceAutoConfiguration.class, - DataSourceSpyConfiguration.class); + public void inMemoryH2IsShutdown() throws Exception { + ConfigurableApplicationContext context = getContext(() -> createContext( + "org.h2.Driver", "jdbc:h2:mem:test", DataSourceAutoConfiguration.class, + DataSourceSpyConfiguration.class)); Statement statement = configureDataSourceBehavior( context.getBean(DataSource.class)); context.close(); @@ -100,10 +100,10 @@ public class DevToolsPooledDataSourceAutoConfigurationTests } @Test - public void hsqlServerIsNotShutdown() throws SQLException { - ConfigurableApplicationContext context = createContext("org.hsqldb.jdbcDriver", - "jdbc:hsqldb:hsql://localhost", DataSourceAutoConfiguration.class, - DataSourceSpyConfiguration.class); + public void hsqlServerIsNotShutdown() throws Exception { + ConfigurableApplicationContext context = getContext(() -> createContext( + "org.hsqldb.jdbcDriver", "jdbc:hsqldb:hsql://localhost", + DataSourceAutoConfiguration.class, DataSourceSpyConfiguration.class)); Statement statement = configureDataSourceBehavior( context.getBean(DataSource.class)); context.close(); @@ -111,10 +111,10 @@ public class DevToolsPooledDataSourceAutoConfigurationTests } @Test - public void inMemoryHsqlIsShutdown() throws SQLException { - ConfigurableApplicationContext context = createContext("org.hsqldb.jdbcDriver", - "jdbc:hsqldb:mem:test", DataSourceAutoConfiguration.class, - DataSourceSpyConfiguration.class); + public void inMemoryHsqlIsShutdown() throws Exception { + ConfigurableApplicationContext context = getContext(() -> createContext( + "org.hsqldb.jdbcDriver", "jdbc:hsqldb:mem:test", + DataSourceAutoConfiguration.class, DataSourceSpyConfiguration.class)); Statement statement = configureDataSourceBehavior( context.getBean(DataSource.class)); context.close(); @@ -122,10 +122,10 @@ public class DevToolsPooledDataSourceAutoConfigurationTests } @Test - public void derbyClientIsNotShutdown() throws SQLException { - ConfigurableApplicationContext context = createContext( + public void derbyClientIsNotShutdown() throws Exception { + ConfigurableApplicationContext context = getContext(() -> createContext( "org.apache.derby.jdbc.ClientDriver", "jdbc:derby://localhost", - DataSourceAutoConfiguration.class, DataSourceSpyConfiguration.class); + DataSourceAutoConfiguration.class, DataSourceSpyConfiguration.class)); Statement statement = configureDataSourceBehavior( context.getBean(DataSource.class)); context.close(); @@ -133,13 +133,14 @@ public class DevToolsPooledDataSourceAutoConfigurationTests } @Test - public void inMemoryDerbyIsShutdown() throws SQLException { - ConfigurableApplicationContext context = createContext( - "org.apache.derby.jdbc.EmbeddedDriver", "jdbc:derby:memory:test", - DataSourceAutoConfiguration.class, DataSourceSpyConfiguration.class); + public void inMemoryDerbyIsShutdown() throws Exception { + ConfigurableApplicationContext configurableApplicationContext = getContext( + () -> createContext("org.apache.derby.jdbc.EmbeddedDriver", + "jdbc:derby:memory:test", DataSourceAutoConfiguration.class, + DataSourceSpyConfiguration.class)); Statement statement = configureDataSourceBehavior( - context.getBean(DataSource.class)); - context.close(); + configurableApplicationContext.getBean(DataSource.class)); + configurableApplicationContext.close(); verify(statement, times(1)).execute("SHUTDOWN"); } diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/LocalDevToolsAutoConfigurationTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/LocalDevToolsAutoConfigurationTests.java index d478df00c47..5546f816f19 100644 --- a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/LocalDevToolsAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/LocalDevToolsAutoConfigurationTests.java @@ -21,6 +21,9 @@ import java.time.Duration; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; import org.apache.catalina.Container; import org.apache.catalina.core.StandardWrapper; @@ -84,28 +87,29 @@ public class LocalDevToolsAutoConfigurationTests { } @Test - public void thymeleafCacheIsFalse() { - this.context = initializeAndRun(Config.class); + public void thymeleafCacheIsFalse() throws Exception { + this.context = getContext(() -> initializeAndRun(Config.class)); SpringResourceTemplateResolver resolver = this.context .getBean(SpringResourceTemplateResolver.class); assertThat(resolver.isCacheable()).isFalse(); } @Test - public void defaultPropertyCanBeOverriddenFromCommandLine() { - this.context = initializeAndRun(Config.class, "--spring.thymeleaf.cache=true"); + public void defaultPropertyCanBeOverriddenFromCommandLine() throws Exception { + this.context = getContext( + () -> initializeAndRun(Config.class, "--spring.thymeleaf.cache=true")); SpringResourceTemplateResolver resolver = this.context .getBean(SpringResourceTemplateResolver.class); assertThat(resolver.isCacheable()).isTrue(); } @Test - public void defaultPropertyCanBeOverriddenFromUserHomeProperties() { + public void defaultPropertyCanBeOverriddenFromUserHomeProperties() throws Exception { String userHome = System.getProperty("user.home"); System.setProperty("user.home", new File("src/test/resources/user-home").getAbsolutePath()); try { - this.context = initializeAndRun(Config.class); + this.context = getContext(() -> initializeAndRun(Config.class)); SpringResourceTemplateResolver resolver = this.context .getBean(SpringResourceTemplateResolver.class); assertThat(resolver.isCacheable()).isTrue(); @@ -116,22 +120,22 @@ public class LocalDevToolsAutoConfigurationTests { } @Test - public void resourceCachePeriodIsZero() { - this.context = initializeAndRun(WebResourcesConfig.class); + public void resourceCachePeriodIsZero() throws Exception { + this.context = getContext(() -> initializeAndRun(WebResourcesConfig.class)); ResourceProperties properties = this.context.getBean(ResourceProperties.class); assertThat(properties.getCache().getPeriod()).isEqualTo(Duration.ZERO); } @Test - public void liveReloadServer() { - this.context = initializeAndRun(Config.class); + public void liveReloadServer() throws Exception { + this.context = getContext(() -> initializeAndRun(Config.class)); LiveReloadServer server = this.context.getBean(LiveReloadServer.class); assertThat(server.isStarted()).isTrue(); } @Test - public void liveReloadTriggeredOnContextRefresh() { - this.context = initializeAndRun(ConfigWithMockLiveReload.class); + public void liveReloadTriggeredOnContextRefresh() throws Exception { + this.context = getContext(() -> initializeAndRun(ConfigWithMockLiveReload.class)); LiveReloadServer server = this.context.getBean(LiveReloadServer.class); reset(server); this.context.publishEvent(new ContextRefreshedEvent(this.context)); @@ -139,8 +143,8 @@ public class LocalDevToolsAutoConfigurationTests { } @Test - public void liveReloadTriggeredOnClassPathChangeWithoutRestart() { - this.context = initializeAndRun(ConfigWithMockLiveReload.class); + public void liveReloadTriggeredOnClassPathChangeWithoutRestart() throws Exception { + this.context = getContext(() -> initializeAndRun(ConfigWithMockLiveReload.class)); LiveReloadServer server = this.context.getBean(LiveReloadServer.class); reset(server); ClassPathChangedEvent event = new ClassPathChangedEvent(this.context, @@ -150,8 +154,8 @@ public class LocalDevToolsAutoConfigurationTests { } @Test - public void liveReloadNotTriggeredOnClassPathChangeWithRestart() { - this.context = initializeAndRun(ConfigWithMockLiveReload.class); + public void liveReloadNotTriggeredOnClassPathChangeWithRestart() throws Exception { + this.context = getContext(() -> initializeAndRun(ConfigWithMockLiveReload.class)); LiveReloadServer server = this.context.getBean(LiveReloadServer.class); reset(server); ClassPathChangedEvent event = new ClassPathChangedEvent(this.context, @@ -161,17 +165,17 @@ public class LocalDevToolsAutoConfigurationTests { } @Test - public void liveReloadDisabled() { + public void liveReloadDisabled() throws Exception { Map properties = new HashMap<>(); properties.put("spring.devtools.livereload.enabled", false); - this.context = initializeAndRun(Config.class, properties); + this.context = getContext(() -> initializeAndRun(Config.class, properties)); assertThatExceptionOfType(NoSuchBeanDefinitionException.class) .isThrownBy(() -> this.context.getBean(OptionalLiveReloadServer.class)); } @Test - public void restartTriggeredOnClassPathChangeWithRestart() { - this.context = initializeAndRun(Config.class); + public void restartTriggeredOnClassPathChangeWithRestart() throws Exception { + this.context = getContext(() -> initializeAndRun(Config.class)); ClassPathChangedEvent event = new ClassPathChangedEvent(this.context, Collections.emptySet(), true); this.context.publishEvent(event); @@ -179,8 +183,8 @@ public class LocalDevToolsAutoConfigurationTests { } @Test - public void restartNotTriggeredOnClassPathChangeWithRestart() { - this.context = initializeAndRun(Config.class); + public void restartNotTriggeredOnClassPathChangeWithRestart() throws Exception { + this.context = getContext(() -> initializeAndRun(Config.class)); ClassPathChangedEvent event = new ClassPathChangedEvent(this.context, Collections.emptySet(), false); this.context.publishEvent(event); @@ -188,27 +192,27 @@ public class LocalDevToolsAutoConfigurationTests { } @Test - public void restartWatchingClassPath() { - this.context = initializeAndRun(Config.class); + public void restartWatchingClassPath() throws Exception { + this.context = getContext(() -> initializeAndRun(Config.class)); ClassPathFileSystemWatcher watcher = this.context .getBean(ClassPathFileSystemWatcher.class); assertThat(watcher).isNotNull(); } @Test - public void restartDisabled() { + public void restartDisabled() throws Exception { Map properties = new HashMap<>(); properties.put("spring.devtools.restart.enabled", false); - this.context = initializeAndRun(Config.class, properties); + this.context = getContext(() -> initializeAndRun(Config.class, properties)); assertThatExceptionOfType(NoSuchBeanDefinitionException.class) .isThrownBy(() -> this.context.getBean(ClassPathFileSystemWatcher.class)); } @Test - public void restartWithTriggerFile() { + public void restartWithTriggerFile() throws Exception { Map properties = new HashMap<>(); properties.put("spring.devtools.restart.trigger-file", "somefile.txt"); - this.context = initializeAndRun(Config.class, properties); + this.context = getContext(() -> initializeAndRun(Config.class, properties)); ClassPathFileSystemWatcher classPathWatcher = this.context .getBean(ClassPathFileSystemWatcher.class); Object watcher = ReflectionTestUtils.getField(classPathWatcher, @@ -218,11 +222,11 @@ public class LocalDevToolsAutoConfigurationTests { } @Test - public void watchingAdditionalPaths() { + public void watchingAdditionalPaths() throws Exception { Map properties = new HashMap<>(); properties.put("spring.devtools.restart.additional-paths", "src/main/java,src/test/java"); - this.context = initializeAndRun(Config.class, properties); + this.context = getContext(() -> initializeAndRun(Config.class, properties)); ClassPathFileSystemWatcher classPathWatcher = this.context .getBean(ClassPathFileSystemWatcher.class); Object watcher = ReflectionTestUtils.getField(classPathWatcher, @@ -236,8 +240,8 @@ public class LocalDevToolsAutoConfigurationTests { } @Test - public void devToolsSwitchesJspServletToDevelopmentMode() { - this.context = initializeAndRun(Config.class); + public void devToolsSwitchesJspServletToDevelopmentMode() throws Exception { + this.context = getContext(() -> initializeAndRun(Config.class)); TomcatWebServer tomcatContainer = (TomcatWebServer) ((ServletWebServerApplicationContext) this.context) .getWebServer(); Container context = tomcatContainer.getTomcat().getHost().findChildren()[0]; @@ -247,6 +251,20 @@ public class LocalDevToolsAutoConfigurationTests { assertThat(options.getDevelopment()).isTrue(); } + private ConfigurableApplicationContext getContext( + Supplier supplier) throws Exception { + CountDownLatch latch = new CountDownLatch(1); + AtomicReference atomicReference = new AtomicReference<>(); + Thread thread = new Thread(() -> { + ConfigurableApplicationContext context = supplier.get(); + latch.countDown(); + atomicReference.getAndSet(context); + }); + thread.start(); + thread.join(); + return atomicReference.get(); + } + private ConfigurableApplicationContext initializeAndRun(Class config, String... args) { return initializeAndRun(config, Collections.emptyMap(), args); diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/OnEnabledDevtoolsConditionTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/OnEnabledDevtoolsConditionTests.java new file mode 100644 index 00000000000..1b4055a6d40 --- /dev/null +++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/OnEnabledDevtoolsConditionTests.java @@ -0,0 +1,73 @@ +/* + * Copyright 2012-2019 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.devtools.autoconfigure; + +import org.junit.Before; +import org.junit.Test; + +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link OnEnabledDevtoolsCondition}. + * + * @author Madhura Bhave + */ +public class OnEnabledDevtoolsConditionTests { + + private AnnotationConfigApplicationContext context; + + @Before + public void setup() { + this.context = new AnnotationConfigApplicationContext(); + this.context.register(TestConfiguration.class); + } + + @Test + public void outcomeWhenDevtoolsShouldBeEnabledIsTrueShouldMatch() throws Exception { + Thread thread = new Thread(() -> { + OnEnabledDevtoolsConditionTests.this.context.refresh(); + assertThat(OnEnabledDevtoolsConditionTests.this.context.containsBean("test")) + .isTrue(); + }); + thread.start(); + thread.join(); + } + + @Test + public void outcomeWhenDevtoolsShouldBeEnabledIsFalseShouldNotMatch() { + OnEnabledDevtoolsConditionTests.this.context.refresh(); + assertThat(OnEnabledDevtoolsConditionTests.this.context.containsBean("test")) + .isFalse(); + } + + @Configuration(proxyBeanMethods = false) + static class TestConfiguration { + + @Bean + @Conditional(OnEnabledDevtoolsCondition.class) + public String test() { + return "hello"; + } + + } + +} diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/RemoteDevToolsAutoConfigurationTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/RemoteDevToolsAutoConfigurationTests.java index 6af1c492aa8..4f82ae09961 100644 --- a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/RemoteDevToolsAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/RemoteDevToolsAutoConfigurationTests.java @@ -16,6 +16,10 @@ package org.springframework.boot.devtools.autoconfigure; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -81,15 +85,16 @@ public class RemoteDevToolsAutoConfigurationTests { } @Test - public void disabledIfRemoteSecretIsMissing() { - loadContext("a:b"); + public void disabledIfRemoteSecretIsMissing() throws Exception { + this.context = getContext(() -> loadContext("a:b")); assertThatExceptionOfType(NoSuchBeanDefinitionException.class) .isThrownBy(() -> this.context.getBean(DispatcherFilter.class)); } @Test public void ignoresUnmappedUrl() throws Exception { - loadContext("spring.devtools.remote.secret:supersecret"); + this.context = getContext( + () -> loadContext("spring.devtools.remote.secret:supersecret")); DispatcherFilter filter = this.context.getBean(DispatcherFilter.class); this.request.setRequestURI("/restart"); this.request.addHeader(DEFAULT_SECRET_HEADER_NAME, "supersecret"); @@ -99,7 +104,8 @@ public class RemoteDevToolsAutoConfigurationTests { @Test public void ignoresIfMissingSecretFromRequest() throws Exception { - loadContext("spring.devtools.remote.secret:supersecret"); + this.context = getContext( + () -> loadContext("spring.devtools.remote.secret:supersecret")); DispatcherFilter filter = this.context.getBean(DispatcherFilter.class); this.request.setRequestURI(DEFAULT_CONTEXT_PATH + "/restart"); filter.doFilter(this.request, this.response, this.chain); @@ -108,7 +114,8 @@ public class RemoteDevToolsAutoConfigurationTests { @Test public void ignoresInvalidSecretInRequest() throws Exception { - loadContext("spring.devtools.remote.secret:supersecret"); + this.context = getContext( + () -> loadContext("spring.devtools.remote.secret:supersecret")); DispatcherFilter filter = this.context.getBean(DispatcherFilter.class); this.request.setRequestURI(DEFAULT_CONTEXT_PATH + "/restart"); this.request.addHeader(DEFAULT_SECRET_HEADER_NAME, "invalid"); @@ -118,7 +125,8 @@ public class RemoteDevToolsAutoConfigurationTests { @Test public void invokeRestartWithDefaultSetup() throws Exception { - loadContext("spring.devtools.remote.secret:supersecret"); + this.context = getContext( + () -> loadContext("spring.devtools.remote.secret:supersecret")); DispatcherFilter filter = this.context.getBean(DispatcherFilter.class); this.request.setRequestURI(DEFAULT_CONTEXT_PATH + "/restart"); this.request.addHeader(DEFAULT_SECRET_HEADER_NAME, "supersecret"); @@ -128,8 +136,9 @@ public class RemoteDevToolsAutoConfigurationTests { @Test public void invokeRestartWithCustomServerContextPath() throws Exception { - loadContext("spring.devtools.remote.secret:supersecret", - "server.servlet.context-path:/test"); + this.context = getContext( + () -> loadContext("spring.devtools.remote.secret:supersecret", + "server.servlet.context-path:/test")); DispatcherFilter filter = this.context.getBean(DispatcherFilter.class); this.request.setRequestURI("/test" + DEFAULT_CONTEXT_PATH + "/restart"); this.request.addHeader(DEFAULT_SECRET_HEADER_NAME, "supersecret"); @@ -138,16 +147,18 @@ public class RemoteDevToolsAutoConfigurationTests { } @Test - public void disableRestart() { - loadContext("spring.devtools.remote.secret:supersecret", - "spring.devtools.remote.restart.enabled:false"); + public void disableRestart() throws Exception { + this.context = getContext( + () -> loadContext("spring.devtools.remote.secret:supersecret", + "spring.devtools.remote.restart.enabled:false")); assertThatExceptionOfType(NoSuchBeanDefinitionException.class) .isThrownBy(() -> this.context.getBean("remoteRestartHandlerMapper")); } @Test public void devToolsHealthReturns200() throws Exception { - loadContext("spring.devtools.remote.secret:supersecret"); + this.context = getContext( + () -> loadContext("spring.devtools.remote.secret:supersecret")); DispatcherFilter filter = this.context.getBean(DispatcherFilter.class); this.request.setRequestURI(DEFAULT_CONTEXT_PATH); this.request.addHeader(DEFAULT_SECRET_HEADER_NAME, "supersecret"); @@ -158,8 +169,9 @@ public class RemoteDevToolsAutoConfigurationTests { @Test public void devToolsHealthWithCustomServerContextPathReturns200() throws Exception { - loadContext("spring.devtools.remote.secret:supersecret", - "server.servlet.context-path:/test"); + this.context = getContext( + () -> loadContext("spring.devtools.remote.secret:supersecret", + "server.servlet.context-path:/test")); DispatcherFilter filter = this.context.getBean(DispatcherFilter.class); this.request.setRequestURI("/test" + DEFAULT_CONTEXT_PATH); this.request.addHeader(DEFAULT_SECRET_HEADER_NAME, "supersecret"); @@ -168,17 +180,34 @@ public class RemoteDevToolsAutoConfigurationTests { assertThat(this.response.getStatus()).isEqualTo(200); } + private AnnotationConfigServletWebApplicationContext getContext( + Supplier supplier) + throws Exception { + CountDownLatch latch = new CountDownLatch(1); + AtomicReference atomicReference = new AtomicReference<>(); + Thread thread = new Thread(() -> { + AnnotationConfigServletWebApplicationContext context = supplier.get(); + latch.countDown(); + atomicReference.getAndSet(context); + }); + thread.start(); + thread.join(); + return atomicReference.get(); + } + private void assertRestartInvoked(boolean value) { assertThat(this.context.getBean(MockHttpRestartServer.class).invoked) .isEqualTo(value); } - private void loadContext(String... properties) { - this.context = new AnnotationConfigServletWebApplicationContext(); - this.context.setServletContext(new MockServletContext()); - this.context.register(Config.class, PropertyPlaceholderAutoConfiguration.class); - TestPropertyValues.of(properties).applyTo(this.context); - this.context.refresh(); + private AnnotationConfigServletWebApplicationContext loadContext( + String... properties) { + AnnotationConfigServletWebApplicationContext context = new AnnotationConfigServletWebApplicationContext(); + context.setServletContext(new MockServletContext()); + context.register(Config.class, PropertyPlaceholderAutoConfiguration.class); + TestPropertyValues.of(properties).applyTo(context); + context.refresh(); + return context; } @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/env/DevToolPropertiesIntegrationTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/env/DevToolPropertiesIntegrationTests.java index c7ccd34a2be..0c73845a3dc 100644 --- a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/env/DevToolPropertiesIntegrationTests.java +++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/env/DevToolPropertiesIntegrationTests.java @@ -18,6 +18,9 @@ package org.springframework.boot.devtools.env; import java.net.URL; import java.util.Collections; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; import org.junit.After; import org.junit.Before; @@ -61,37 +64,40 @@ public class DevToolPropertiesIntegrationTests { } @Test - public void classPropertyConditionIsAffectedByDevToolProperties() { + public void classPropertyConditionIsAffectedByDevToolProperties() throws Exception { SpringApplication application = new SpringApplication( ClassConditionConfiguration.class); application.setWebApplicationType(WebApplicationType.NONE); - this.context = application.run(); + this.context = getContext(application::run); this.context.getBean(ClassConditionConfiguration.class); } @Test - public void beanMethodPropertyConditionIsAffectedByDevToolProperties() { + public void beanMethodPropertyConditionIsAffectedByDevToolProperties() + throws Exception { SpringApplication application = new SpringApplication( BeanConditionConfiguration.class); application.setWebApplicationType(WebApplicationType.NONE); - this.context = application.run(); + this.context = getContext(application::run); this.context.getBean(MyBean.class); } @Test - public void postProcessWhenRestarterDisabledAndRemoteSecretNotSetShouldNotAddPropertySource() { + public void postProcessWhenRestarterDisabledAndRemoteSecretNotSetShouldNotAddPropertySource() + throws Exception { Restarter.clearInstance(); Restarter.disable(); SpringApplication application = new SpringApplication( BeanConditionConfiguration.class); application.setWebApplicationType(WebApplicationType.NONE); - this.context = application.run(); + this.context = getContext(application::run); assertThatExceptionOfType(NoSuchBeanDefinitionException.class) .isThrownBy(() -> this.context.getBean(MyBean.class)); } @Test - public void postProcessWhenRestarterDisabledAndRemoteSecretSetShouldAddPropertySource() { + public void postProcessWhenRestarterDisabledAndRemoteSecretSetShouldAddPropertySource() + throws Exception { Restarter.clearInstance(); Restarter.disable(); SpringApplication application = new SpringApplication( @@ -99,21 +105,35 @@ public class DevToolPropertiesIntegrationTests { application.setWebApplicationType(WebApplicationType.NONE); application.setDefaultProperties( Collections.singletonMap("spring.devtools.remote.secret", "donttell")); - this.context = application.run(); + this.context = getContext(application::run); this.context.getBean(MyBean.class); } @Test - public void postProcessEnablesIncludeStackTraceProperty() { + public void postProcessEnablesIncludeStackTraceProperty() throws Exception { SpringApplication application = new SpringApplication(TestConfiguration.class); application.setWebApplicationType(WebApplicationType.NONE); - this.context = application.run(); + this.context = getContext(application::run); ConfigurableEnvironment environment = this.context.getEnvironment(); String property = environment.getProperty("server.error.include-stacktrace"); assertThat(property) .isEqualTo(ErrorProperties.IncludeStacktrace.ALWAYS.toString()); } + protected ConfigurableApplicationContext getContext( + Supplier supplier) throws Exception { + CountDownLatch latch = new CountDownLatch(1); + AtomicReference atomicReference = new AtomicReference<>(); + Thread thread = new Thread(() -> { + ConfigurableApplicationContext context = supplier.get(); + latch.countDown(); + atomicReference.getAndSet(context); + }); + thread.start(); + thread.join(); + return atomicReference.get(); + } + @Configuration(proxyBeanMethods = false) static class TestConfiguration { diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/env/DevToolsHomePropertiesPostProcessorTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/env/DevToolsHomePropertiesPostProcessorTests.java index a977f6d35e4..655806d4d65 100644 --- a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/env/DevToolsHomePropertiesPostProcessorTests.java +++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/env/DevToolsHomePropertiesPostProcessorTests.java @@ -21,6 +21,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.Properties; +import java.util.concurrent.CountDownLatch; import org.junit.Before; import org.junit.Rule; @@ -60,18 +61,28 @@ public class DevToolsHomePropertiesPostProcessorTests { out.close(); ConfigurableEnvironment environment = new MockEnvironment(); MockDevToolHomePropertiesPostProcessor postProcessor = new MockDevToolHomePropertiesPostProcessor(); - postProcessor.postProcessEnvironment(environment, null); + runPostProcessor(() -> postProcessor.postProcessEnvironment(environment, null)); assertThat(environment.getProperty("abc")).isEqualTo("def"); } @Test - public void ignoresMissingHomeProperties() { + public void ignoresMissingHomeProperties() throws Exception { ConfigurableEnvironment environment = new MockEnvironment(); MockDevToolHomePropertiesPostProcessor postProcessor = new MockDevToolHomePropertiesPostProcessor(); - postProcessor.postProcessEnvironment(environment, null); + runPostProcessor(() -> postProcessor.postProcessEnvironment(environment, null)); assertThat(environment.getProperty("abc")).isNull(); } + protected void runPostProcessor(Runnable runnable) throws Exception { + CountDownLatch latch = new CountDownLatch(1); + Thread thread = new Thread(() -> { + runnable.run(); + latch.countDown(); + }); + thread.start(); + thread.join(); + } + private class MockDevToolHomePropertiesPostProcessor extends DevToolsHomePropertiesPostProcessor {