Commit ac2b0093 authored by Madhura Bhave's avatar Madhura Bhave

Disable DevTools' post-processors and auto-config when running tests

Closes gh-5307
parent b164b16c
/*
* 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<String> SKIPPED_STACK_ELEMENTS;
static {
Set<String> 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;
}
}
...@@ -55,7 +55,7 @@ import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; ...@@ -55,7 +55,7 @@ import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
* @since 1.3.3 * @since 1.3.3
*/ */
@AutoConfigureAfter(DataSourceAutoConfiguration.class) @AutoConfigureAfter(DataSourceAutoConfiguration.class)
@Conditional(DevToolsDataSourceCondition.class) @Conditional({ OnEnabledDevtoolsCondition.class, DevToolsDataSourceCondition.class })
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
public class DevToolsDataSourceAutoConfiguration { public class DevToolsDataSourceAutoConfiguration {
......
/*
* 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."));
}
}
...@@ -43,6 +43,7 @@ import org.springframework.boot.devtools.restart.server.HttpRestartServer; ...@@ -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.HttpRestartServerHandler;
import org.springframework.boot.devtools.restart.server.SourceFolderUrlFilter; import org.springframework.boot.devtools.restart.server.SourceFolderUrlFilter;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpRequest;
...@@ -55,6 +56,7 @@ import org.springframework.http.server.ServerHttpRequest; ...@@ -55,6 +56,7 @@ import org.springframework.http.server.ServerHttpRequest;
* @since 1.3.0 * @since 1.3.0
*/ */
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@Conditional(OnEnabledDevtoolsCondition.class)
@ConditionalOnProperty(prefix = "spring.devtools.remote", name = "secret") @ConditionalOnProperty(prefix = "spring.devtools.remote", name = "secret")
@ConditionalOnClass({ Filter.class, ServerHttpRequest.class }) @ConditionalOnClass({ Filter.class, ServerHttpRequest.class })
@EnableConfigurationProperties({ ServerProperties.class, DevToolsProperties.class }) @EnableConfigurationProperties({ ServerProperties.class, DevToolsProperties.class })
......
...@@ -21,6 +21,7 @@ import java.io.IOException; ...@@ -21,6 +21,7 @@ import java.io.IOException;
import java.util.Properties; import java.util.Properties;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.devtools.DevtoolsEnablementDeducer;
import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertiesPropertySource; import org.springframework.core.env.PropertiesPropertySource;
...@@ -43,18 +44,20 @@ public class DevToolsHomePropertiesPostProcessor implements EnvironmentPostProce ...@@ -43,18 +44,20 @@ public class DevToolsHomePropertiesPostProcessor implements EnvironmentPostProce
@Override @Override
public void postProcessEnvironment(ConfigurableEnvironment environment, public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) { SpringApplication application) {
File home = getHomeFolder(); if (DevtoolsEnablementDeducer.shouldEnable(Thread.currentThread())) {
File propertyFile = (home != null) ? new File(home, FILE_NAME) : null; File home = getHomeFolder();
if (propertyFile != null && propertyFile.exists() && propertyFile.isFile()) { File propertyFile = (home != null) ? new File(home, FILE_NAME) : null;
FileSystemResource resource = new FileSystemResource(propertyFile); if (propertyFile != null && propertyFile.exists() && propertyFile.isFile()) {
Properties properties; FileSystemResource resource = new FileSystemResource(propertyFile);
try { Properties properties;
properties = PropertiesLoaderUtils.loadProperties(resource); try {
environment.getPropertySources().addFirst( properties = PropertiesLoaderUtils.loadProperties(resource);
new PropertiesPropertySource("devtools-local", properties)); environment.getPropertySources().addFirst(
} new PropertiesPropertySource("devtools-local", properties));
catch (IOException ex) { }
throw new IllegalStateException("Unable to load " + FILE_NAME, ex); catch (IOException ex) {
throw new IllegalStateException("Unable to load " + FILE_NAME, ex);
}
} }
} }
} }
......
...@@ -23,6 +23,7 @@ import java.util.Map; ...@@ -23,6 +23,7 @@ import java.util.Map;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.devtools.DevtoolsEnablementDeducer;
import org.springframework.boot.devtools.logger.DevToolsLogFactory; import org.springframework.boot.devtools.logger.DevToolsLogFactory;
import org.springframework.boot.devtools.restart.Restarter; import org.springframework.boot.devtools.restart.Restarter;
import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.boot.env.EnvironmentPostProcessor;
...@@ -79,7 +80,8 @@ public class DevToolsPropertyDefaultsPostProcessor implements EnvironmentPostPro ...@@ -79,7 +80,8 @@ public class DevToolsPropertyDefaultsPostProcessor implements EnvironmentPostPro
@Override @Override
public void postProcessEnvironment(ConfigurableEnvironment environment, public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) { SpringApplication application) {
if (isLocalApplication(environment)) { if (DevtoolsEnablementDeducer.shouldEnable(Thread.currentThread())
&& isLocalApplication(environment)) {
if (canAddProperties(environment)) { if (canAddProperties(environment)) {
logger.info("Devtools property defaults active! Set '" + ENABLED logger.info("Devtools property defaults active! Set '" + ENABLED
+ "' to 'false' to disable"); + "' to 'false' to disable");
......
...@@ -17,9 +17,8 @@ ...@@ -17,9 +17,8 @@
package org.springframework.boot.devtools.restart; package org.springframework.boot.devtools.restart;
import java.net.URL; import java.net.URL;
import java.util.Collections;
import java.util.LinkedHashSet; import org.springframework.boot.devtools.DevtoolsEnablementDeducer;
import java.util.Set;
/** /**
* Default {@link RestartInitializer} that only enable initial restart when running a * Default {@link RestartInitializer} that only enable initial restart when running a
...@@ -32,26 +31,13 @@ import java.util.Set; ...@@ -32,26 +31,13 @@ import java.util.Set;
*/ */
public class DefaultRestartInitializer implements RestartInitializer { public class DefaultRestartInitializer implements RestartInitializer {
private static final Set<String> SKIPPED_STACK_ELEMENTS;
static {
Set<String> 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 @Override
public URL[] getInitialUrls(Thread thread) { public URL[] getInitialUrls(Thread thread) {
if (!isMain(thread)) { if (!isMain(thread)) {
return null; return null;
} }
for (StackTraceElement element : thread.getStackTrace()) { if (!DevtoolsEnablementDeducer.shouldEnable(thread)) {
if (isSkippedStackElement(element)) { return null;
return null;
}
} }
return getUrls(thread); return getUrls(thread);
} }
...@@ -67,22 +53,6 @@ public class DefaultRestartInitializer implements RestartInitializer { ...@@ -67,22 +53,6 @@ public class DefaultRestartInitializer implements RestartInitializer {
.getClass().getName().contains("AppClassLoader"); .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. * Return the URLs that should be used with initialization.
* @param thread the source thread * @param thread the source thread
......
...@@ -20,6 +20,9 @@ import java.sql.Connection; ...@@ -20,6 +20,9 @@ import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.Collection; import java.util.Collection;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import javax.sql.DataSource; import javax.sql.DataSource;
...@@ -49,18 +52,18 @@ import static org.mockito.Mockito.verify; ...@@ -49,18 +52,18 @@ import static org.mockito.Mockito.verify;
public abstract class AbstractDevToolsDataSourceAutoConfigurationTests { public abstract class AbstractDevToolsDataSourceAutoConfigurationTests {
@Test @Test
public void singleManuallyConfiguredDataSourceIsNotClosed() throws SQLException { public void singleManuallyConfiguredDataSourceIsNotClosed() throws Exception {
ConfigurableApplicationContext context = createContext( ConfigurableApplicationContext context = getContext(
SingleDataSourceConfiguration.class); () -> createContext(SingleDataSourceConfiguration.class));
DataSource dataSource = context.getBean(DataSource.class); DataSource dataSource = context.getBean(DataSource.class);
Statement statement = configureDataSourceBehavior(dataSource); Statement statement = configureDataSourceBehavior(dataSource);
verify(statement, never()).execute("SHUTDOWN"); verify(statement, never()).execute("SHUTDOWN");
} }
@Test @Test
public void multipleDataSourcesAreIgnored() throws SQLException { public void multipleDataSourcesAreIgnored() throws Exception {
ConfigurableApplicationContext context = createContext( ConfigurableApplicationContext context = getContext(
MultipleDataSourcesConfiguration.class); () -> createContext(MultipleDataSourcesConfiguration.class));
Collection<DataSource> dataSources = context.getBeansOfType(DataSource.class) Collection<DataSource> dataSources = context.getBeansOfType(DataSource.class)
.values(); .values();
for (DataSource dataSource : dataSources) { for (DataSource dataSource : dataSources) {
...@@ -90,6 +93,20 @@ public abstract class AbstractDevToolsDataSourceAutoConfigurationTests { ...@@ -90,6 +93,20 @@ public abstract class AbstractDevToolsDataSourceAutoConfigurationTests {
return statement; return statement;
} }
protected ConfigurableApplicationContext getContext(
Supplier<ConfigurableApplicationContext> supplier) throws Exception {
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<ConfigurableApplicationContext> 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) { protected final ConfigurableApplicationContext createContext(Class<?>... classes) {
return this.createContext(null, classes); return this.createContext(null, classes);
} }
......
...@@ -17,7 +17,6 @@ ...@@ -17,7 +17,6 @@
package org.springframework.boot.devtools.autoconfigure; package org.springframework.boot.devtools.autoconfigure;
import java.io.IOException; import java.io.IOException;
import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import javax.sql.DataSource; import javax.sql.DataSource;
...@@ -58,9 +57,9 @@ public class DevToolsPooledDataSourceAutoConfigurationTests ...@@ -58,9 +57,9 @@ public class DevToolsPooledDataSourceAutoConfigurationTests
} }
@Test @Test
public void autoConfiguredInMemoryDataSourceIsShutdown() throws SQLException { public void autoConfiguredInMemoryDataSourceIsShutdown() throws Exception {
ConfigurableApplicationContext context = createContext( ConfigurableApplicationContext context = getContext(() -> createContext(
DataSourceAutoConfiguration.class, DataSourceSpyConfiguration.class); DataSourceAutoConfiguration.class, DataSourceSpyConfiguration.class));
Statement statement = configureDataSourceBehavior( Statement statement = configureDataSourceBehavior(
context.getBean(DataSource.class)); context.getBean(DataSource.class));
context.close(); context.close();
...@@ -68,9 +67,10 @@ public class DevToolsPooledDataSourceAutoConfigurationTests ...@@ -68,9 +67,10 @@ public class DevToolsPooledDataSourceAutoConfigurationTests
} }
@Test @Test
public void autoConfiguredExternalDataSourceIsNotShutdown() throws SQLException { public void autoConfiguredExternalDataSourceIsNotShutdown() throws Exception {
ConfigurableApplicationContext context = createContext("org.postgresql.Driver", ConfigurableApplicationContext context = getContext(() -> createContext(
DataSourceAutoConfiguration.class, DataSourceSpyConfiguration.class); "org.postgresql.Driver", DataSourceAutoConfiguration.class,
DataSourceSpyConfiguration.class));
Statement statement = configureDataSourceBehavior( Statement statement = configureDataSourceBehavior(
context.getBean(DataSource.class)); context.getBean(DataSource.class));
context.close(); context.close();
...@@ -78,10 +78,10 @@ public class DevToolsPooledDataSourceAutoConfigurationTests ...@@ -78,10 +78,10 @@ public class DevToolsPooledDataSourceAutoConfigurationTests
} }
@Test @Test
public void h2ServerIsNotShutdown() throws SQLException { public void h2ServerIsNotShutdown() throws Exception {
ConfigurableApplicationContext context = createContext("org.h2.Driver", ConfigurableApplicationContext context = getContext(() -> createContext(
"jdbc:h2:hsql://localhost", DataSourceAutoConfiguration.class, "org.h2.Driver", "jdbc:h2:hsql://localhost",
DataSourceSpyConfiguration.class); DataSourceAutoConfiguration.class, DataSourceSpyConfiguration.class));
Statement statement = configureDataSourceBehavior( Statement statement = configureDataSourceBehavior(
context.getBean(DataSource.class)); context.getBean(DataSource.class));
context.close(); context.close();
...@@ -89,10 +89,10 @@ public class DevToolsPooledDataSourceAutoConfigurationTests ...@@ -89,10 +89,10 @@ public class DevToolsPooledDataSourceAutoConfigurationTests
} }
@Test @Test
public void inMemoryH2IsShutdown() throws SQLException { public void inMemoryH2IsShutdown() throws Exception {
ConfigurableApplicationContext context = createContext("org.h2.Driver", ConfigurableApplicationContext context = getContext(() -> createContext(
"jdbc:h2:mem:test", DataSourceAutoConfiguration.class, "org.h2.Driver", "jdbc:h2:mem:test", DataSourceAutoConfiguration.class,
DataSourceSpyConfiguration.class); DataSourceSpyConfiguration.class));
Statement statement = configureDataSourceBehavior( Statement statement = configureDataSourceBehavior(
context.getBean(DataSource.class)); context.getBean(DataSource.class));
context.close(); context.close();
...@@ -100,10 +100,10 @@ public class DevToolsPooledDataSourceAutoConfigurationTests ...@@ -100,10 +100,10 @@ public class DevToolsPooledDataSourceAutoConfigurationTests
} }
@Test @Test
public void hsqlServerIsNotShutdown() throws SQLException { public void hsqlServerIsNotShutdown() throws Exception {
ConfigurableApplicationContext context = createContext("org.hsqldb.jdbcDriver", ConfigurableApplicationContext context = getContext(() -> createContext(
"jdbc:hsqldb:hsql://localhost", DataSourceAutoConfiguration.class, "org.hsqldb.jdbcDriver", "jdbc:hsqldb:hsql://localhost",
DataSourceSpyConfiguration.class); DataSourceAutoConfiguration.class, DataSourceSpyConfiguration.class));
Statement statement = configureDataSourceBehavior( Statement statement = configureDataSourceBehavior(
context.getBean(DataSource.class)); context.getBean(DataSource.class));
context.close(); context.close();
...@@ -111,10 +111,10 @@ public class DevToolsPooledDataSourceAutoConfigurationTests ...@@ -111,10 +111,10 @@ public class DevToolsPooledDataSourceAutoConfigurationTests
} }
@Test @Test
public void inMemoryHsqlIsShutdown() throws SQLException { public void inMemoryHsqlIsShutdown() throws Exception {
ConfigurableApplicationContext context = createContext("org.hsqldb.jdbcDriver", ConfigurableApplicationContext context = getContext(() -> createContext(
"jdbc:hsqldb:mem:test", DataSourceAutoConfiguration.class, "org.hsqldb.jdbcDriver", "jdbc:hsqldb:mem:test",
DataSourceSpyConfiguration.class); DataSourceAutoConfiguration.class, DataSourceSpyConfiguration.class));
Statement statement = configureDataSourceBehavior( Statement statement = configureDataSourceBehavior(
context.getBean(DataSource.class)); context.getBean(DataSource.class));
context.close(); context.close();
...@@ -122,10 +122,10 @@ public class DevToolsPooledDataSourceAutoConfigurationTests ...@@ -122,10 +122,10 @@ public class DevToolsPooledDataSourceAutoConfigurationTests
} }
@Test @Test
public void derbyClientIsNotShutdown() throws SQLException { public void derbyClientIsNotShutdown() throws Exception {
ConfigurableApplicationContext context = createContext( ConfigurableApplicationContext context = getContext(() -> createContext(
"org.apache.derby.jdbc.ClientDriver", "jdbc:derby://localhost", "org.apache.derby.jdbc.ClientDriver", "jdbc:derby://localhost",
DataSourceAutoConfiguration.class, DataSourceSpyConfiguration.class); DataSourceAutoConfiguration.class, DataSourceSpyConfiguration.class));
Statement statement = configureDataSourceBehavior( Statement statement = configureDataSourceBehavior(
context.getBean(DataSource.class)); context.getBean(DataSource.class));
context.close(); context.close();
...@@ -133,13 +133,14 @@ public class DevToolsPooledDataSourceAutoConfigurationTests ...@@ -133,13 +133,14 @@ public class DevToolsPooledDataSourceAutoConfigurationTests
} }
@Test @Test
public void inMemoryDerbyIsShutdown() throws SQLException { public void inMemoryDerbyIsShutdown() throws Exception {
ConfigurableApplicationContext context = createContext( ConfigurableApplicationContext configurableApplicationContext = getContext(
"org.apache.derby.jdbc.EmbeddedDriver", "jdbc:derby:memory:test", () -> createContext("org.apache.derby.jdbc.EmbeddedDriver",
DataSourceAutoConfiguration.class, DataSourceSpyConfiguration.class); "jdbc:derby:memory:test", DataSourceAutoConfiguration.class,
DataSourceSpyConfiguration.class));
Statement statement = configureDataSourceBehavior( Statement statement = configureDataSourceBehavior(
context.getBean(DataSource.class)); configurableApplicationContext.getBean(DataSource.class));
context.close(); configurableApplicationContext.close();
verify(statement, times(1)).execute("SHUTDOWN"); verify(statement, times(1)).execute("SHUTDOWN");
} }
......
...@@ -21,6 +21,9 @@ import java.time.Duration; ...@@ -21,6 +21,9 @@ import java.time.Duration;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; 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.Container;
import org.apache.catalina.core.StandardWrapper; import org.apache.catalina.core.StandardWrapper;
...@@ -84,28 +87,29 @@ public class LocalDevToolsAutoConfigurationTests { ...@@ -84,28 +87,29 @@ public class LocalDevToolsAutoConfigurationTests {
} }
@Test @Test
public void thymeleafCacheIsFalse() { public void thymeleafCacheIsFalse() throws Exception {
this.context = initializeAndRun(Config.class); this.context = getContext(() -> initializeAndRun(Config.class));
SpringResourceTemplateResolver resolver = this.context SpringResourceTemplateResolver resolver = this.context
.getBean(SpringResourceTemplateResolver.class); .getBean(SpringResourceTemplateResolver.class);
assertThat(resolver.isCacheable()).isFalse(); assertThat(resolver.isCacheable()).isFalse();
} }
@Test @Test
public void defaultPropertyCanBeOverriddenFromCommandLine() { public void defaultPropertyCanBeOverriddenFromCommandLine() throws Exception {
this.context = initializeAndRun(Config.class, "--spring.thymeleaf.cache=true"); this.context = getContext(
() -> initializeAndRun(Config.class, "--spring.thymeleaf.cache=true"));
SpringResourceTemplateResolver resolver = this.context SpringResourceTemplateResolver resolver = this.context
.getBean(SpringResourceTemplateResolver.class); .getBean(SpringResourceTemplateResolver.class);
assertThat(resolver.isCacheable()).isTrue(); assertThat(resolver.isCacheable()).isTrue();
} }
@Test @Test
public void defaultPropertyCanBeOverriddenFromUserHomeProperties() { public void defaultPropertyCanBeOverriddenFromUserHomeProperties() throws Exception {
String userHome = System.getProperty("user.home"); String userHome = System.getProperty("user.home");
System.setProperty("user.home", System.setProperty("user.home",
new File("src/test/resources/user-home").getAbsolutePath()); new File("src/test/resources/user-home").getAbsolutePath());
try { try {
this.context = initializeAndRun(Config.class); this.context = getContext(() -> initializeAndRun(Config.class));
SpringResourceTemplateResolver resolver = this.context SpringResourceTemplateResolver resolver = this.context
.getBean(SpringResourceTemplateResolver.class); .getBean(SpringResourceTemplateResolver.class);
assertThat(resolver.isCacheable()).isTrue(); assertThat(resolver.isCacheable()).isTrue();
...@@ -116,22 +120,22 @@ public class LocalDevToolsAutoConfigurationTests { ...@@ -116,22 +120,22 @@ public class LocalDevToolsAutoConfigurationTests {
} }
@Test @Test
public void resourceCachePeriodIsZero() { public void resourceCachePeriodIsZero() throws Exception {
this.context = initializeAndRun(WebResourcesConfig.class); this.context = getContext(() -> initializeAndRun(WebResourcesConfig.class));
ResourceProperties properties = this.context.getBean(ResourceProperties.class); ResourceProperties properties = this.context.getBean(ResourceProperties.class);
assertThat(properties.getCache().getPeriod()).isEqualTo(Duration.ZERO); assertThat(properties.getCache().getPeriod()).isEqualTo(Duration.ZERO);
} }
@Test @Test
public void liveReloadServer() { public void liveReloadServer() throws Exception {
this.context = initializeAndRun(Config.class); this.context = getContext(() -> initializeAndRun(Config.class));
LiveReloadServer server = this.context.getBean(LiveReloadServer.class); LiveReloadServer server = this.context.getBean(LiveReloadServer.class);
assertThat(server.isStarted()).isTrue(); assertThat(server.isStarted()).isTrue();
} }
@Test @Test
public void liveReloadTriggeredOnContextRefresh() { public void liveReloadTriggeredOnContextRefresh() throws Exception {
this.context = initializeAndRun(ConfigWithMockLiveReload.class); this.context = getContext(() -> initializeAndRun(ConfigWithMockLiveReload.class));
LiveReloadServer server = this.context.getBean(LiveReloadServer.class); LiveReloadServer server = this.context.getBean(LiveReloadServer.class);
reset(server); reset(server);
this.context.publishEvent(new ContextRefreshedEvent(this.context)); this.context.publishEvent(new ContextRefreshedEvent(this.context));
...@@ -139,8 +143,8 @@ public class LocalDevToolsAutoConfigurationTests { ...@@ -139,8 +143,8 @@ public class LocalDevToolsAutoConfigurationTests {
} }
@Test @Test
public void liveReloadTriggeredOnClassPathChangeWithoutRestart() { public void liveReloadTriggeredOnClassPathChangeWithoutRestart() throws Exception {
this.context = initializeAndRun(ConfigWithMockLiveReload.class); this.context = getContext(() -> initializeAndRun(ConfigWithMockLiveReload.class));
LiveReloadServer server = this.context.getBean(LiveReloadServer.class); LiveReloadServer server = this.context.getBean(LiveReloadServer.class);
reset(server); reset(server);
ClassPathChangedEvent event = new ClassPathChangedEvent(this.context, ClassPathChangedEvent event = new ClassPathChangedEvent(this.context,
...@@ -150,8 +154,8 @@ public class LocalDevToolsAutoConfigurationTests { ...@@ -150,8 +154,8 @@ public class LocalDevToolsAutoConfigurationTests {
} }
@Test @Test
public void liveReloadNotTriggeredOnClassPathChangeWithRestart() { public void liveReloadNotTriggeredOnClassPathChangeWithRestart() throws Exception {
this.context = initializeAndRun(ConfigWithMockLiveReload.class); this.context = getContext(() -> initializeAndRun(ConfigWithMockLiveReload.class));
LiveReloadServer server = this.context.getBean(LiveReloadServer.class); LiveReloadServer server = this.context.getBean(LiveReloadServer.class);
reset(server); reset(server);
ClassPathChangedEvent event = new ClassPathChangedEvent(this.context, ClassPathChangedEvent event = new ClassPathChangedEvent(this.context,
...@@ -161,17 +165,17 @@ public class LocalDevToolsAutoConfigurationTests { ...@@ -161,17 +165,17 @@ public class LocalDevToolsAutoConfigurationTests {
} }
@Test @Test
public void liveReloadDisabled() { public void liveReloadDisabled() throws Exception {
Map<String, Object> properties = new HashMap<>(); Map<String, Object> properties = new HashMap<>();
properties.put("spring.devtools.livereload.enabled", false); properties.put("spring.devtools.livereload.enabled", false);
this.context = initializeAndRun(Config.class, properties); this.context = getContext(() -> initializeAndRun(Config.class, properties));
assertThatExceptionOfType(NoSuchBeanDefinitionException.class) assertThatExceptionOfType(NoSuchBeanDefinitionException.class)
.isThrownBy(() -> this.context.getBean(OptionalLiveReloadServer.class)); .isThrownBy(() -> this.context.getBean(OptionalLiveReloadServer.class));
} }
@Test @Test
public void restartTriggeredOnClassPathChangeWithRestart() { public void restartTriggeredOnClassPathChangeWithRestart() throws Exception {
this.context = initializeAndRun(Config.class); this.context = getContext(() -> initializeAndRun(Config.class));
ClassPathChangedEvent event = new ClassPathChangedEvent(this.context, ClassPathChangedEvent event = new ClassPathChangedEvent(this.context,
Collections.emptySet(), true); Collections.emptySet(), true);
this.context.publishEvent(event); this.context.publishEvent(event);
...@@ -179,8 +183,8 @@ public class LocalDevToolsAutoConfigurationTests { ...@@ -179,8 +183,8 @@ public class LocalDevToolsAutoConfigurationTests {
} }
@Test @Test
public void restartNotTriggeredOnClassPathChangeWithRestart() { public void restartNotTriggeredOnClassPathChangeWithRestart() throws Exception {
this.context = initializeAndRun(Config.class); this.context = getContext(() -> initializeAndRun(Config.class));
ClassPathChangedEvent event = new ClassPathChangedEvent(this.context, ClassPathChangedEvent event = new ClassPathChangedEvent(this.context,
Collections.emptySet(), false); Collections.emptySet(), false);
this.context.publishEvent(event); this.context.publishEvent(event);
...@@ -188,27 +192,27 @@ public class LocalDevToolsAutoConfigurationTests { ...@@ -188,27 +192,27 @@ public class LocalDevToolsAutoConfigurationTests {
} }
@Test @Test
public void restartWatchingClassPath() { public void restartWatchingClassPath() throws Exception {
this.context = initializeAndRun(Config.class); this.context = getContext(() -> initializeAndRun(Config.class));
ClassPathFileSystemWatcher watcher = this.context ClassPathFileSystemWatcher watcher = this.context
.getBean(ClassPathFileSystemWatcher.class); .getBean(ClassPathFileSystemWatcher.class);
assertThat(watcher).isNotNull(); assertThat(watcher).isNotNull();
} }
@Test @Test
public void restartDisabled() { public void restartDisabled() throws Exception {
Map<String, Object> properties = new HashMap<>(); Map<String, Object> properties = new HashMap<>();
properties.put("spring.devtools.restart.enabled", false); properties.put("spring.devtools.restart.enabled", false);
this.context = initializeAndRun(Config.class, properties); this.context = getContext(() -> initializeAndRun(Config.class, properties));
assertThatExceptionOfType(NoSuchBeanDefinitionException.class) assertThatExceptionOfType(NoSuchBeanDefinitionException.class)
.isThrownBy(() -> this.context.getBean(ClassPathFileSystemWatcher.class)); .isThrownBy(() -> this.context.getBean(ClassPathFileSystemWatcher.class));
} }
@Test @Test
public void restartWithTriggerFile() { public void restartWithTriggerFile() throws Exception {
Map<String, Object> properties = new HashMap<>(); Map<String, Object> properties = new HashMap<>();
properties.put("spring.devtools.restart.trigger-file", "somefile.txt"); 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 ClassPathFileSystemWatcher classPathWatcher = this.context
.getBean(ClassPathFileSystemWatcher.class); .getBean(ClassPathFileSystemWatcher.class);
Object watcher = ReflectionTestUtils.getField(classPathWatcher, Object watcher = ReflectionTestUtils.getField(classPathWatcher,
...@@ -218,11 +222,11 @@ public class LocalDevToolsAutoConfigurationTests { ...@@ -218,11 +222,11 @@ public class LocalDevToolsAutoConfigurationTests {
} }
@Test @Test
public void watchingAdditionalPaths() { public void watchingAdditionalPaths() throws Exception {
Map<String, Object> properties = new HashMap<>(); Map<String, Object> properties = new HashMap<>();
properties.put("spring.devtools.restart.additional-paths", properties.put("spring.devtools.restart.additional-paths",
"src/main/java,src/test/java"); "src/main/java,src/test/java");
this.context = initializeAndRun(Config.class, properties); this.context = getContext(() -> initializeAndRun(Config.class, properties));
ClassPathFileSystemWatcher classPathWatcher = this.context ClassPathFileSystemWatcher classPathWatcher = this.context
.getBean(ClassPathFileSystemWatcher.class); .getBean(ClassPathFileSystemWatcher.class);
Object watcher = ReflectionTestUtils.getField(classPathWatcher, Object watcher = ReflectionTestUtils.getField(classPathWatcher,
...@@ -236,8 +240,8 @@ public class LocalDevToolsAutoConfigurationTests { ...@@ -236,8 +240,8 @@ public class LocalDevToolsAutoConfigurationTests {
} }
@Test @Test
public void devToolsSwitchesJspServletToDevelopmentMode() { public void devToolsSwitchesJspServletToDevelopmentMode() throws Exception {
this.context = initializeAndRun(Config.class); this.context = getContext(() -> initializeAndRun(Config.class));
TomcatWebServer tomcatContainer = (TomcatWebServer) ((ServletWebServerApplicationContext) this.context) TomcatWebServer tomcatContainer = (TomcatWebServer) ((ServletWebServerApplicationContext) this.context)
.getWebServer(); .getWebServer();
Container context = tomcatContainer.getTomcat().getHost().findChildren()[0]; Container context = tomcatContainer.getTomcat().getHost().findChildren()[0];
...@@ -247,6 +251,20 @@ public class LocalDevToolsAutoConfigurationTests { ...@@ -247,6 +251,20 @@ public class LocalDevToolsAutoConfigurationTests {
assertThat(options.getDevelopment()).isTrue(); assertThat(options.getDevelopment()).isTrue();
} }
private ConfigurableApplicationContext getContext(
Supplier<ConfigurableApplicationContext> supplier) throws Exception {
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<ConfigurableApplicationContext> 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, private ConfigurableApplicationContext initializeAndRun(Class<?> config,
String... args) { String... args) {
return initializeAndRun(config, Collections.emptyMap(), args); return initializeAndRun(config, Collections.emptyMap(), args);
......
/*
* 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";
}
}
}
...@@ -16,6 +16,10 @@ ...@@ -16,6 +16,10 @@
package org.springframework.boot.devtools.autoconfigure; 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.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
...@@ -81,15 +85,16 @@ public class RemoteDevToolsAutoConfigurationTests { ...@@ -81,15 +85,16 @@ public class RemoteDevToolsAutoConfigurationTests {
} }
@Test @Test
public void disabledIfRemoteSecretIsMissing() { public void disabledIfRemoteSecretIsMissing() throws Exception {
loadContext("a:b"); this.context = getContext(() -> loadContext("a:b"));
assertThatExceptionOfType(NoSuchBeanDefinitionException.class) assertThatExceptionOfType(NoSuchBeanDefinitionException.class)
.isThrownBy(() -> this.context.getBean(DispatcherFilter.class)); .isThrownBy(() -> this.context.getBean(DispatcherFilter.class));
} }
@Test @Test
public void ignoresUnmappedUrl() throws Exception { 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); DispatcherFilter filter = this.context.getBean(DispatcherFilter.class);
this.request.setRequestURI("/restart"); this.request.setRequestURI("/restart");
this.request.addHeader(DEFAULT_SECRET_HEADER_NAME, "supersecret"); this.request.addHeader(DEFAULT_SECRET_HEADER_NAME, "supersecret");
...@@ -99,7 +104,8 @@ public class RemoteDevToolsAutoConfigurationTests { ...@@ -99,7 +104,8 @@ public class RemoteDevToolsAutoConfigurationTests {
@Test @Test
public void ignoresIfMissingSecretFromRequest() throws Exception { 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); DispatcherFilter filter = this.context.getBean(DispatcherFilter.class);
this.request.setRequestURI(DEFAULT_CONTEXT_PATH + "/restart"); this.request.setRequestURI(DEFAULT_CONTEXT_PATH + "/restart");
filter.doFilter(this.request, this.response, this.chain); filter.doFilter(this.request, this.response, this.chain);
...@@ -108,7 +114,8 @@ public class RemoteDevToolsAutoConfigurationTests { ...@@ -108,7 +114,8 @@ public class RemoteDevToolsAutoConfigurationTests {
@Test @Test
public void ignoresInvalidSecretInRequest() throws Exception { 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); DispatcherFilter filter = this.context.getBean(DispatcherFilter.class);
this.request.setRequestURI(DEFAULT_CONTEXT_PATH + "/restart"); this.request.setRequestURI(DEFAULT_CONTEXT_PATH + "/restart");
this.request.addHeader(DEFAULT_SECRET_HEADER_NAME, "invalid"); this.request.addHeader(DEFAULT_SECRET_HEADER_NAME, "invalid");
...@@ -118,7 +125,8 @@ public class RemoteDevToolsAutoConfigurationTests { ...@@ -118,7 +125,8 @@ public class RemoteDevToolsAutoConfigurationTests {
@Test @Test
public void invokeRestartWithDefaultSetup() throws Exception { 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); DispatcherFilter filter = this.context.getBean(DispatcherFilter.class);
this.request.setRequestURI(DEFAULT_CONTEXT_PATH + "/restart"); this.request.setRequestURI(DEFAULT_CONTEXT_PATH + "/restart");
this.request.addHeader(DEFAULT_SECRET_HEADER_NAME, "supersecret"); this.request.addHeader(DEFAULT_SECRET_HEADER_NAME, "supersecret");
...@@ -128,8 +136,9 @@ public class RemoteDevToolsAutoConfigurationTests { ...@@ -128,8 +136,9 @@ public class RemoteDevToolsAutoConfigurationTests {
@Test @Test
public void invokeRestartWithCustomServerContextPath() throws Exception { public void invokeRestartWithCustomServerContextPath() throws Exception {
loadContext("spring.devtools.remote.secret:supersecret", this.context = getContext(
"server.servlet.context-path:/test"); () -> loadContext("spring.devtools.remote.secret:supersecret",
"server.servlet.context-path:/test"));
DispatcherFilter filter = this.context.getBean(DispatcherFilter.class); DispatcherFilter filter = this.context.getBean(DispatcherFilter.class);
this.request.setRequestURI("/test" + DEFAULT_CONTEXT_PATH + "/restart"); this.request.setRequestURI("/test" + DEFAULT_CONTEXT_PATH + "/restart");
this.request.addHeader(DEFAULT_SECRET_HEADER_NAME, "supersecret"); this.request.addHeader(DEFAULT_SECRET_HEADER_NAME, "supersecret");
...@@ -138,16 +147,18 @@ public class RemoteDevToolsAutoConfigurationTests { ...@@ -138,16 +147,18 @@ public class RemoteDevToolsAutoConfigurationTests {
} }
@Test @Test
public void disableRestart() { public void disableRestart() throws Exception {
loadContext("spring.devtools.remote.secret:supersecret", this.context = getContext(
"spring.devtools.remote.restart.enabled:false"); () -> loadContext("spring.devtools.remote.secret:supersecret",
"spring.devtools.remote.restart.enabled:false"));
assertThatExceptionOfType(NoSuchBeanDefinitionException.class) assertThatExceptionOfType(NoSuchBeanDefinitionException.class)
.isThrownBy(() -> this.context.getBean("remoteRestartHandlerMapper")); .isThrownBy(() -> this.context.getBean("remoteRestartHandlerMapper"));
} }
@Test @Test
public void devToolsHealthReturns200() throws Exception { 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); DispatcherFilter filter = this.context.getBean(DispatcherFilter.class);
this.request.setRequestURI(DEFAULT_CONTEXT_PATH); this.request.setRequestURI(DEFAULT_CONTEXT_PATH);
this.request.addHeader(DEFAULT_SECRET_HEADER_NAME, "supersecret"); this.request.addHeader(DEFAULT_SECRET_HEADER_NAME, "supersecret");
...@@ -158,8 +169,9 @@ public class RemoteDevToolsAutoConfigurationTests { ...@@ -158,8 +169,9 @@ public class RemoteDevToolsAutoConfigurationTests {
@Test @Test
public void devToolsHealthWithCustomServerContextPathReturns200() throws Exception { public void devToolsHealthWithCustomServerContextPathReturns200() throws Exception {
loadContext("spring.devtools.remote.secret:supersecret", this.context = getContext(
"server.servlet.context-path:/test"); () -> loadContext("spring.devtools.remote.secret:supersecret",
"server.servlet.context-path:/test"));
DispatcherFilter filter = this.context.getBean(DispatcherFilter.class); DispatcherFilter filter = this.context.getBean(DispatcherFilter.class);
this.request.setRequestURI("/test" + DEFAULT_CONTEXT_PATH); this.request.setRequestURI("/test" + DEFAULT_CONTEXT_PATH);
this.request.addHeader(DEFAULT_SECRET_HEADER_NAME, "supersecret"); this.request.addHeader(DEFAULT_SECRET_HEADER_NAME, "supersecret");
...@@ -168,17 +180,34 @@ public class RemoteDevToolsAutoConfigurationTests { ...@@ -168,17 +180,34 @@ public class RemoteDevToolsAutoConfigurationTests {
assertThat(this.response.getStatus()).isEqualTo(200); assertThat(this.response.getStatus()).isEqualTo(200);
} }
private AnnotationConfigServletWebApplicationContext getContext(
Supplier<AnnotationConfigServletWebApplicationContext> supplier)
throws Exception {
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<AnnotationConfigServletWebApplicationContext> 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) { private void assertRestartInvoked(boolean value) {
assertThat(this.context.getBean(MockHttpRestartServer.class).invoked) assertThat(this.context.getBean(MockHttpRestartServer.class).invoked)
.isEqualTo(value); .isEqualTo(value);
} }
private void loadContext(String... properties) { private AnnotationConfigServletWebApplicationContext loadContext(
this.context = new AnnotationConfigServletWebApplicationContext(); String... properties) {
this.context.setServletContext(new MockServletContext()); AnnotationConfigServletWebApplicationContext context = new AnnotationConfigServletWebApplicationContext();
this.context.register(Config.class, PropertyPlaceholderAutoConfiguration.class); context.setServletContext(new MockServletContext());
TestPropertyValues.of(properties).applyTo(this.context); context.register(Config.class, PropertyPlaceholderAutoConfiguration.class);
this.context.refresh(); TestPropertyValues.of(properties).applyTo(context);
context.refresh();
return context;
} }
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
......
...@@ -18,6 +18,9 @@ package org.springframework.boot.devtools.env; ...@@ -18,6 +18,9 @@ package org.springframework.boot.devtools.env;
import java.net.URL; import java.net.URL;
import java.util.Collections; 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.After;
import org.junit.Before; import org.junit.Before;
...@@ -61,37 +64,40 @@ public class DevToolPropertiesIntegrationTests { ...@@ -61,37 +64,40 @@ public class DevToolPropertiesIntegrationTests {
} }
@Test @Test
public void classPropertyConditionIsAffectedByDevToolProperties() { public void classPropertyConditionIsAffectedByDevToolProperties() throws Exception {
SpringApplication application = new SpringApplication( SpringApplication application = new SpringApplication(
ClassConditionConfiguration.class); ClassConditionConfiguration.class);
application.setWebApplicationType(WebApplicationType.NONE); application.setWebApplicationType(WebApplicationType.NONE);
this.context = application.run(); this.context = getContext(application::run);
this.context.getBean(ClassConditionConfiguration.class); this.context.getBean(ClassConditionConfiguration.class);
} }
@Test @Test
public void beanMethodPropertyConditionIsAffectedByDevToolProperties() { public void beanMethodPropertyConditionIsAffectedByDevToolProperties()
throws Exception {
SpringApplication application = new SpringApplication( SpringApplication application = new SpringApplication(
BeanConditionConfiguration.class); BeanConditionConfiguration.class);
application.setWebApplicationType(WebApplicationType.NONE); application.setWebApplicationType(WebApplicationType.NONE);
this.context = application.run(); this.context = getContext(application::run);
this.context.getBean(MyBean.class); this.context.getBean(MyBean.class);
} }
@Test @Test
public void postProcessWhenRestarterDisabledAndRemoteSecretNotSetShouldNotAddPropertySource() { public void postProcessWhenRestarterDisabledAndRemoteSecretNotSetShouldNotAddPropertySource()
throws Exception {
Restarter.clearInstance(); Restarter.clearInstance();
Restarter.disable(); Restarter.disable();
SpringApplication application = new SpringApplication( SpringApplication application = new SpringApplication(
BeanConditionConfiguration.class); BeanConditionConfiguration.class);
application.setWebApplicationType(WebApplicationType.NONE); application.setWebApplicationType(WebApplicationType.NONE);
this.context = application.run(); this.context = getContext(application::run);
assertThatExceptionOfType(NoSuchBeanDefinitionException.class) assertThatExceptionOfType(NoSuchBeanDefinitionException.class)
.isThrownBy(() -> this.context.getBean(MyBean.class)); .isThrownBy(() -> this.context.getBean(MyBean.class));
} }
@Test @Test
public void postProcessWhenRestarterDisabledAndRemoteSecretSetShouldAddPropertySource() { public void postProcessWhenRestarterDisabledAndRemoteSecretSetShouldAddPropertySource()
throws Exception {
Restarter.clearInstance(); Restarter.clearInstance();
Restarter.disable(); Restarter.disable();
SpringApplication application = new SpringApplication( SpringApplication application = new SpringApplication(
...@@ -99,21 +105,35 @@ public class DevToolPropertiesIntegrationTests { ...@@ -99,21 +105,35 @@ public class DevToolPropertiesIntegrationTests {
application.setWebApplicationType(WebApplicationType.NONE); application.setWebApplicationType(WebApplicationType.NONE);
application.setDefaultProperties( application.setDefaultProperties(
Collections.singletonMap("spring.devtools.remote.secret", "donttell")); Collections.singletonMap("spring.devtools.remote.secret", "donttell"));
this.context = application.run(); this.context = getContext(application::run);
this.context.getBean(MyBean.class); this.context.getBean(MyBean.class);
} }
@Test @Test
public void postProcessEnablesIncludeStackTraceProperty() { public void postProcessEnablesIncludeStackTraceProperty() throws Exception {
SpringApplication application = new SpringApplication(TestConfiguration.class); SpringApplication application = new SpringApplication(TestConfiguration.class);
application.setWebApplicationType(WebApplicationType.NONE); application.setWebApplicationType(WebApplicationType.NONE);
this.context = application.run(); this.context = getContext(application::run);
ConfigurableEnvironment environment = this.context.getEnvironment(); ConfigurableEnvironment environment = this.context.getEnvironment();
String property = environment.getProperty("server.error.include-stacktrace"); String property = environment.getProperty("server.error.include-stacktrace");
assertThat(property) assertThat(property)
.isEqualTo(ErrorProperties.IncludeStacktrace.ALWAYS.toString()); .isEqualTo(ErrorProperties.IncludeStacktrace.ALWAYS.toString());
} }
protected ConfigurableApplicationContext getContext(
Supplier<ConfigurableApplicationContext> supplier) throws Exception {
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<ConfigurableApplicationContext> 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) @Configuration(proxyBeanMethods = false)
static class TestConfiguration { static class TestConfiguration {
......
...@@ -21,6 +21,7 @@ import java.io.FileOutputStream; ...@@ -21,6 +21,7 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
...@@ -60,18 +61,28 @@ public class DevToolsHomePropertiesPostProcessorTests { ...@@ -60,18 +61,28 @@ public class DevToolsHomePropertiesPostProcessorTests {
out.close(); out.close();
ConfigurableEnvironment environment = new MockEnvironment(); ConfigurableEnvironment environment = new MockEnvironment();
MockDevToolHomePropertiesPostProcessor postProcessor = new MockDevToolHomePropertiesPostProcessor(); MockDevToolHomePropertiesPostProcessor postProcessor = new MockDevToolHomePropertiesPostProcessor();
postProcessor.postProcessEnvironment(environment, null); runPostProcessor(() -> postProcessor.postProcessEnvironment(environment, null));
assertThat(environment.getProperty("abc")).isEqualTo("def"); assertThat(environment.getProperty("abc")).isEqualTo("def");
} }
@Test @Test
public void ignoresMissingHomeProperties() { public void ignoresMissingHomeProperties() throws Exception {
ConfigurableEnvironment environment = new MockEnvironment(); ConfigurableEnvironment environment = new MockEnvironment();
MockDevToolHomePropertiesPostProcessor postProcessor = new MockDevToolHomePropertiesPostProcessor(); MockDevToolHomePropertiesPostProcessor postProcessor = new MockDevToolHomePropertiesPostProcessor();
postProcessor.postProcessEnvironment(environment, null); runPostProcessor(() -> postProcessor.postProcessEnvironment(environment, null));
assertThat(environment.getProperty("abc")).isNull(); 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 private class MockDevToolHomePropertiesPostProcessor
extends DevToolsHomePropertiesPostProcessor { extends DevToolsHomePropertiesPostProcessor {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment