/*
* Copyright 2012-2017 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
*
* http://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 java.io.File;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.catalina.Container;
import org.apache.catalina.core.StandardWrapper;
import org.apache.jasper.EmbeddedServletOptions;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
import org.springframework.boot.devtools.classpath.ClassPathChangedEvent;
import org.springframework.boot.devtools.classpath.ClassPathFileSystemWatcher;
import org.springframework.boot.devtools.filewatch.ChangedFiles;
import org.springframework.boot.devtools.livereload.LiveReloadServer;
import org.springframework.boot.devtools.restart.FailureHandler;
import org.springframework.boot.devtools.restart.MockRestartInitializer;
import org.springframework.boot.devtools.restart.MockRestarter;
import org.springframework.boot.devtools.restart.Restarter;
import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.SocketUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link LocalDevToolsAutoConfiguration}.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @author Vladimir Tsanev
*/
public class LocalDevToolsAutoConfigurationTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Rule
public MockRestarter mockRestarter = new MockRestarter();
private int liveReloadPort = SocketUtils.findAvailableTcpPort();
private ConfigurableApplicationContext context;
@After
public void cleanup() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void thymeleafCacheIsFalse() throws Exception {
this.context = initializeAndRun(Config.class);
SpringResourceTemplateResolver resolver = this.context
.getBean(SpringResourceTemplateResolver.class);
assertThat(resolver.isCacheable()).isFalse();
}
@Test
public void defaultPropertyCanBeOverriddenFromCommandLine() throws Exception {
this.context = initializeAndRun(Config.class, "--spring.thymeleaf.cache=true");
SpringResourceTemplateResolver resolver = this.context
.getBean(SpringResourceTemplateResolver.class);
assertThat(resolver.isCacheable()).isTrue();
}
@Test
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);
SpringResourceTemplateResolver resolver = this.context
.getBean(SpringResourceTemplateResolver.class);
assertThat(resolver.isCacheable()).isTrue();
}
finally {
System.setProperty("user.home", userHome);
}
}
@Test
public void resourceCachePeriodIsZero() throws Exception {
this.context = initializeAndRun(WebResourcesConfig.class);
ResourceProperties properties = this.context.getBean(ResourceProperties.class);
assertThat(properties.getCachePeriod()).isEqualTo(0);
}
@Test
public void liveReloadServer() throws Exception {
this.context = initializeAndRun(Config.class);
LiveReloadServer server = this.context.getBean(LiveReloadServer.class);
assertThat(server.isStarted()).isTrue();
}
@Test
public void liveReloadTriggeredOnContextRefresh() throws Exception {
this.context = initializeAndRun(ConfigWithMockLiveReload.class);
LiveReloadServer server = this.context.getBean(LiveReloadServer.class);
reset(server);
this.context.publishEvent(new ContextRefreshedEvent(this.context));
verify(server).triggerReload();
}
@Test
public void liveReloadTriggeredOnClassPathChangeWithoutRestart() throws Exception {
this.context = initializeAndRun(ConfigWithMockLiveReload.class);
LiveReloadServer server = this.context.getBean(LiveReloadServer.class);
reset(server);
ClassPathChangedEvent event = new ClassPathChangedEvent(this.context,
Collections.<ChangedFiles>emptySet(), false);
this.context.publishEvent(event);
verify(server).triggerReload();
}
@Test
public void liveReloadNotTriggeredOnClassPathChangeWithRestart() throws Exception {
this.context = initializeAndRun(ConfigWithMockLiveReload.class);
LiveReloadServer server = this.context.getBean(LiveReloadServer.class);
reset(server);
ClassPathChangedEvent event = new ClassPathChangedEvent(this.context,
Collections.<ChangedFiles>emptySet(), true);
this.context.publishEvent(event);
verify(server, never()).triggerReload();
}
@Test
public void liveReloadDisabled() throws Exception {
Map<String, Object> properties = new HashMap<>();
properties.put("spring.devtools.livereload.enabled", false);
this.context = initializeAndRun(Config.class, properties);
this.thrown.expect(NoSuchBeanDefinitionException.class);
this.context.getBean(OptionalLiveReloadServer.class);
}
@Test
public void restartTriggeredOnClassPathChangeWithRestart() throws Exception {
this.context = initializeAndRun(Config.class);
ClassPathChangedEvent event = new ClassPathChangedEvent(this.context,
Collections.<ChangedFiles>emptySet(), true);
this.context.publishEvent(event);
verify(this.mockRestarter.getMock()).restart(any(FailureHandler.class));
}
@Test
public void restartNotTriggeredOnClassPathChangeWithRestart() throws Exception {
this.context = initializeAndRun(Config.class);
ClassPathChangedEvent event = new ClassPathChangedEvent(this.context,
Collections.<ChangedFiles>emptySet(), false);
this.context.publishEvent(event);
verify(this.mockRestarter.getMock(), never()).restart();
}
@Test
public void restartWatchingClassPath() throws Exception {
this.context = initializeAndRun(Config.class);
ClassPathFileSystemWatcher watcher = this.context
.getBean(ClassPathFileSystemWatcher.class);
assertThat(watcher).isNotNull();
}
@Test
public void restartDisabled() throws Exception {
Map<String, Object> properties = new HashMap<>();
properties.put("spring.devtools.restart.enabled", false);
this.context = initializeAndRun(Config.class, properties);
this.thrown.expect(NoSuchBeanDefinitionException.class);
this.context.getBean(ClassPathFileSystemWatcher.class);
}
@Test
public void restartWithTriggerFile() throws Exception {
Map<String, Object> properties = new HashMap<>();
properties.put("spring.devtools.restart.trigger-file", "somefile.txt");
this.context = initializeAndRun(Config.class, properties);
ClassPathFileSystemWatcher classPathWatcher = this.context
.getBean(ClassPathFileSystemWatcher.class);
Object watcher = ReflectionTestUtils.getField(classPathWatcher,
"fileSystemWatcher");
Object filter = ReflectionTestUtils.getField(watcher, "triggerFilter");
assertThat(filter).isInstanceOf(TriggerFileFilter.class);
}
@Test
public void watchingAdditionalPaths() throws Exception {
Map<String, Object> properties = new HashMap<>();
properties.put("spring.devtools.restart.additional-paths",
"src/main/java,src/test/java");
this.context = initializeAndRun(Config.class, properties);
ClassPathFileSystemWatcher classPathWatcher = this.context
.getBean(ClassPathFileSystemWatcher.class);
Object watcher = ReflectionTestUtils.getField(classPathWatcher,
"fileSystemWatcher");
@SuppressWarnings("unchecked")
Map<File, Object> folders = (Map<File, Object>) ReflectionTestUtils
.getField(watcher, "folders");
assertThat(folders).hasSize(2)
.containsKey(new File("src/main/java").getAbsoluteFile())
.containsKey(new File("src/test/java").getAbsoluteFile());
}
@Test
public void devToolsSwitchesJspServletToDevelopmentMode() {
this.context = initializeAndRun(Config.class);
TomcatWebServer tomcatContainer = (TomcatWebServer) ((ServletWebServerApplicationContext) this.context)
.getWebServer();
Container context = tomcatContainer.getTomcat().getHost().findChildren()[0];
StandardWrapper jspServletWrapper = (StandardWrapper) context.findChild("jsp");
EmbeddedServletOptions options = (EmbeddedServletOptions) ReflectionTestUtils
.getField(jspServletWrapper.getServlet(), "options");
assertThat(options.getDevelopment()).isEqualTo(true);
}
private ConfigurableApplicationContext initializeAndRun(Class<?> config,
String... args) {
return initializeAndRun(config, Collections.<String, Object>emptyMap(), args);
}
private ConfigurableApplicationContext initializeAndRun(Class<?> config,
Map<String, Object> properties, String... args) {
Restarter.initialize(new String[0], false, new MockRestartInitializer(), false);
SpringApplication application = new SpringApplication(config);
application.setDefaultProperties(getDefaultProperties(properties));
ConfigurableApplicationContext context = application.run(args);
return context;
}
private Map<String, Object> getDefaultProperties(
Map<String, Object> specifiedProperties) {
Map<String, Object> properties = new HashMap<>();
properties.put("spring.thymeleaf.check-template-location", false);
properties.put("spring.devtools.livereload.port", this.liveReloadPort);
properties.put("server.port", 0);
properties.putAll(specifiedProperties);
return properties;
}
@Configuration
@Import({ ServletWebServerFactoryAutoConfiguration.class,
LocalDevToolsAutoConfiguration.class, ThymeleafAutoConfiguration.class })
public static class Config {
}
@Configuration
@Import({ ServletWebServerFactoryAutoConfiguration.class,
LocalDevToolsAutoConfiguration.class, ThymeleafAutoConfiguration.class })
public static class ConfigWithMockLiveReload {
@Bean
public LiveReloadServer liveReloadServer() {
return mock(LiveReloadServer.class);
}
}
@Configuration
@Import({ ServletWebServerFactoryAutoConfiguration.class,
LocalDevToolsAutoConfiguration.class, ResourceProperties.class })
public static class WebResourcesConfig {
}
@Configuration
public static class SessionRedisTemplateConfig {
@Bean
public RedisTemplate<Object, Object> sessionRedisTemplate() {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(mock(RedisConnectionFactory.class));
return redisTemplate;
}
}
}