Rename spring-boot-developer-tools -> devtools
Fixes gh-3099
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.devtools.RemoteUrlPropertyExtractor;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link RemoteUrlPropertyExtractor}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class RemoteUrlPropertyExtractorTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void missingUrl() throws Exception {
|
||||
this.thrown.expect(IllegalStateException.class);
|
||||
this.thrown.expectMessage("No remote URL specified");
|
||||
doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void malformedUrl() throws Exception {
|
||||
this.thrown.expect(IllegalStateException.class);
|
||||
this.thrown.expectMessage("Malformed URL '::://wibble'");
|
||||
doTest("::://wibble");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleUrls() throws Exception {
|
||||
this.thrown.expect(IllegalStateException.class);
|
||||
this.thrown.expectMessage("Multiple URLs specified");
|
||||
doTest("http://localhost:8080", "http://localhost:9090");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validUrl() throws Exception {
|
||||
ApplicationContext context = doTest("http://localhost:8080");
|
||||
assertThat(context.getEnvironment().getProperty("remoteUrl"),
|
||||
equalTo("http://localhost:8080"));
|
||||
}
|
||||
|
||||
private ApplicationContext doTest(String... args) {
|
||||
SpringApplication application = new SpringApplication(Config.class);
|
||||
application.setWebEnvironment(false);
|
||||
application.addListeners(new RemoteUrlPropertyExtractor());
|
||||
return application.run(args);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration;
|
||||
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.MockRestartInitializer;
|
||||
import org.springframework.boot.devtools.restart.MockRestarter;
|
||||
import org.springframework.boot.devtools.restart.Restarter;
|
||||
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.util.SocketUtils;
|
||||
import org.thymeleaf.templateresolver.TemplateResolver;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
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
|
||||
*/
|
||||
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);
|
||||
TemplateResolver resolver = this.context.getBean(TemplateResolver.class);
|
||||
resolver.initialize();
|
||||
assertThat(resolver.isCacheable(), equalTo(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void liveReloadServer() throws Exception {
|
||||
this.context = initializeAndRun(Config.class);
|
||||
LiveReloadServer server = this.context.getBean(LiveReloadServer.class);
|
||||
assertThat(server.isStarted(), equalTo(true));
|
||||
}
|
||||
|
||||
@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 liveReloadTriggerdOnClassPathChangeWithoutRestart() 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 liveReloadNotTriggerdOnClassPathChangeWithRestart() 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<String, Object>();
|
||||
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 restartTriggerdOnClassPathChangeWithRestart() 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();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void restartNotTriggerdOnClassPathChangeWithRestart() 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, notNullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void restartDisabled() throws Exception {
|
||||
Map<String, Object> properties = new HashMap<String, Object>();
|
||||
properties.put("spring.devtools.restart.enabled", false);
|
||||
this.context = initializeAndRun(Config.class, properties);
|
||||
this.thrown.expect(NoSuchBeanDefinitionException.class);
|
||||
this.context.getBean(ClassPathFileSystemWatcher.class);
|
||||
}
|
||||
|
||||
private ConfigurableApplicationContext initializeAndRun(Class<?> config) {
|
||||
return initializeAndRun(config, Collections.<String, Object> emptyMap());
|
||||
}
|
||||
|
||||
private ConfigurableApplicationContext initializeAndRun(Class<?> config,
|
||||
Map<String, Object> properties) {
|
||||
Restarter.initialize(new String[0], false, new MockRestartInitializer(), false);
|
||||
SpringApplication application = new SpringApplication(config);
|
||||
application.setDefaultProperties(getDefaultProperties(properties));
|
||||
application.setWebEnvironment(false);
|
||||
ConfigurableApplicationContext context = application.run();
|
||||
return context;
|
||||
}
|
||||
|
||||
private Map<String, Object> getDefaultProperties(
|
||||
Map<String, Object> specifiedProperties) {
|
||||
Map<String, Object> properties = new HashMap<String, Object>();
|
||||
properties.put("spring.thymeleaf.check-template-location", false);
|
||||
properties.put("spring.devtools.livereload.port", this.liveReloadPort);
|
||||
properties.putAll(specifiedProperties);
|
||||
return properties;
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Import({ LocalDevToolsAutoConfiguration.class,
|
||||
ThymeleafAutoConfiguration.class })
|
||||
public static class Config {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Import({ LocalDevToolsAutoConfiguration.class,
|
||||
ThymeleafAutoConfiguration.class })
|
||||
public static class ConfigWithMockLiveReload {
|
||||
|
||||
@Bean
|
||||
public LiveReloadServer liveReloadServer() {
|
||||
return mock(LiveReloadServer.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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 org.junit.Test;
|
||||
import org.springframework.boot.devtools.autoconfigure.OptionalLiveReloadServer;
|
||||
import org.springframework.boot.devtools.livereload.LiveReloadServer;
|
||||
|
||||
import static org.mockito.BDDMockito.willThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link OptionalLiveReloadServer}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class OptionalLiveReloadServerTests {
|
||||
|
||||
@Test
|
||||
public void nullServer() throws Exception {
|
||||
OptionalLiveReloadServer server = new OptionalLiveReloadServer(null);
|
||||
server.startServer();
|
||||
server.triggerReload();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serverWontStart() throws Exception {
|
||||
LiveReloadServer delegate = mock(LiveReloadServer.class);
|
||||
OptionalLiveReloadServer server = new OptionalLiveReloadServer(delegate);
|
||||
willThrow(new RuntimeException("Error")).given(delegate).start();
|
||||
server.startServer();
|
||||
server.triggerReload();
|
||||
verify(delegate, never()).triggerReload();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.IOException;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration;
|
||||
import org.springframework.boot.devtools.remote.server.DispatcherFilter;
|
||||
import org.springframework.boot.devtools.restart.MockRestarter;
|
||||
import org.springframework.boot.devtools.restart.server.HttpRestartServer;
|
||||
import org.springframework.boot.devtools.restart.server.SourceFolderUrlFilter;
|
||||
import org.springframework.boot.devtools.tunnel.server.HttpTunnelServer;
|
||||
import org.springframework.boot.devtools.tunnel.server.RemoteDebugPortProvider;
|
||||
import org.springframework.boot.devtools.tunnel.server.SocketTargetServerConnection;
|
||||
import org.springframework.boot.devtools.tunnel.server.TargetServerConnection;
|
||||
import org.springframework.boot.test.EnvironmentTestUtils;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.mock.web.MockFilterChain;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.mock.web.MockServletContext;
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link RemoteDevToolsAutoConfiguration}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class RemoteDevToolsAutoConfigurationTests {
|
||||
|
||||
private static final String DEFAULT_CONTEXT_PATH = RemoteDevToolsProperties.DEFAULT_CONTEXT_PATH;
|
||||
|
||||
private static final String DEFAULT_SECRET_HEADER_NAME = RemoteDevToolsProperties.DEFAULT_SECRET_HEADER_NAME;
|
||||
|
||||
@Rule
|
||||
public MockRestarter mockRestarter = new MockRestarter();
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private AnnotationConfigWebApplicationContext context;
|
||||
|
||||
private MockHttpServletRequest request;
|
||||
|
||||
private MockHttpServletResponse response;
|
||||
|
||||
private MockFilterChain chain;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.request = new MockHttpServletRequest();
|
||||
this.response = new MockHttpServletResponse();
|
||||
this.chain = new MockFilterChain();
|
||||
}
|
||||
|
||||
@After
|
||||
public void close() {
|
||||
if (this.context != null) {
|
||||
this.context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disabledIfRemoteSecretIsMissing() throws Exception {
|
||||
loadContext("a:b");
|
||||
this.thrown.expect(NoSuchBeanDefinitionException.class);
|
||||
this.context.getBean(DispatcherFilter.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ignoresUnmappedUrl() throws Exception {
|
||||
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");
|
||||
filter.doFilter(this.request, this.response, this.chain);
|
||||
assertRestartInvoked(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ignoresIfMissingSecretFromRequest() throws Exception {
|
||||
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);
|
||||
assertRestartInvoked(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ignoresInvalidSecretInRequest() throws Exception {
|
||||
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");
|
||||
filter.doFilter(this.request, this.response, this.chain);
|
||||
assertRestartInvoked(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeRestartWithDefaultSetup() throws Exception {
|
||||
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");
|
||||
filter.doFilter(this.request, this.response, this.chain);
|
||||
assertRestartInvoked(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disableRestart() throws Exception {
|
||||
loadContext("spring.devtools.remote.secret:supersecret",
|
||||
"spring.devtools.remote.restart.enabled:false");
|
||||
this.thrown.expect(NoSuchBeanDefinitionException.class);
|
||||
this.context.getBean("remoteRestartHanderMapper");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeTunnelWithDefaultSetup() throws Exception {
|
||||
loadContext("spring.devtools.remote.secret:supersecret");
|
||||
DispatcherFilter filter = this.context.getBean(DispatcherFilter.class);
|
||||
this.request.setRequestURI(DEFAULT_CONTEXT_PATH + "/debug");
|
||||
this.request.addHeader(DEFAULT_SECRET_HEADER_NAME, "supersecret");
|
||||
filter.doFilter(this.request, this.response, this.chain);
|
||||
assertTunnelInvoked(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeTunnelWithCustomHeaderName() throws Exception {
|
||||
loadContext("spring.devtools.remote.secret:supersecret",
|
||||
"spring.devtools.remote.secretHeaderName:customheader");
|
||||
DispatcherFilter filter = this.context.getBean(DispatcherFilter.class);
|
||||
this.request.setRequestURI(DEFAULT_CONTEXT_PATH + "/debug");
|
||||
this.request.addHeader("customheader", "supersecret");
|
||||
filter.doFilter(this.request, this.response, this.chain);
|
||||
assertTunnelInvoked(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disableRemoteDebug() throws Exception {
|
||||
loadContext("spring.devtools.remote.secret:supersecret",
|
||||
"spring.devtools.remote.debug.enabled:false");
|
||||
this.thrown.expect(NoSuchBeanDefinitionException.class);
|
||||
this.context.getBean("remoteDebugHanderMapper");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void devToolsHealthReturns200() throws Exception {
|
||||
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");
|
||||
this.response.setStatus(500);
|
||||
filter.doFilter(this.request, this.response, this.chain);
|
||||
assertThat(this.response.getStatus(), equalTo(200));
|
||||
}
|
||||
|
||||
private void assertTunnelInvoked(boolean value) {
|
||||
assertThat(this.context.getBean(MockHttpTunnelServer.class).invoked,
|
||||
equalTo(value));
|
||||
}
|
||||
|
||||
private void assertRestartInvoked(boolean value) {
|
||||
assertThat(this.context.getBean(MockHttpRestartServer.class).invoked,
|
||||
equalTo(value));
|
||||
}
|
||||
|
||||
private void loadContext(String... properties) {
|
||||
this.context = new AnnotationConfigWebApplicationContext();
|
||||
this.context.setServletContext(new MockServletContext());
|
||||
this.context.register(Config.class, ServerPropertiesAutoConfiguration.class,
|
||||
PropertyPlaceholderAutoConfiguration.class);
|
||||
EnvironmentTestUtils.addEnvironment(this.context, properties);
|
||||
this.context.refresh();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Import(RemoteDevToolsAutoConfiguration.class)
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public HttpTunnelServer remoteDebugHttpTunnelServer() {
|
||||
return new MockHttpTunnelServer(new SocketTargetServerConnection(
|
||||
new RemoteDebugPortProvider()));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public HttpRestartServer remoteRestartHttpRestartServer() {
|
||||
SourceFolderUrlFilter sourceFolderUrlFilter = mock(SourceFolderUrlFilter.class);
|
||||
return new MockHttpRestartServer(sourceFolderUrlFilter);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock {@link HttpTunnelServer} implementation.
|
||||
*/
|
||||
static class MockHttpTunnelServer extends HttpTunnelServer {
|
||||
|
||||
private boolean invoked;
|
||||
|
||||
public MockHttpTunnelServer(TargetServerConnection serverConnection) {
|
||||
super(serverConnection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(ServerHttpRequest request, ServerHttpResponse response)
|
||||
throws IOException {
|
||||
this.invoked = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock {@link HttpRestartServer} implementation.
|
||||
*/
|
||||
static class MockHttpRestartServer extends HttpRestartServer {
|
||||
|
||||
private boolean invoked;
|
||||
|
||||
public MockHttpRestartServer(SourceFolderUrlFilter sourceFolderUrlFilter) {
|
||||
super(sourceFolderUrlFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(ServerHttpRequest request, ServerHttpResponse response)
|
||||
throws IOException {
|
||||
this.invoked = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.classpath;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.boot.devtools.classpath.ClassPathChangedEvent;
|
||||
import org.springframework.boot.devtools.filewatch.ChangedFiles;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.sameInstance;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ClassPathChangedEvent}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class ClassPathChangedEventTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private Object source = new Object();
|
||||
|
||||
@Test
|
||||
public void changeSetMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("ChangeSet must not be null");
|
||||
new ClassPathChangedEvent(this.source, null, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getChangeSet() throws Exception {
|
||||
Set<ChangedFiles> changeSet = new LinkedHashSet<ChangedFiles>();
|
||||
ClassPathChangedEvent event = new ClassPathChangedEvent(this.source, changeSet,
|
||||
false);
|
||||
assertThat(event.getChangeSet(), sameInstance(changeSet));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRestartRequired() throws Exception {
|
||||
Set<ChangedFiles> changeSet = new LinkedHashSet<ChangedFiles>();
|
||||
ClassPathChangedEvent event;
|
||||
event = new ClassPathChangedEvent(this.source, changeSet, false);
|
||||
assertThat(event.isRestartRequired(), equalTo(false));
|
||||
event = new ClassPathChangedEvent(this.source, changeSet, true);
|
||||
assertThat(event.isRestartRequired(), equalTo(true));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.classpath;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.springframework.boot.devtools.classpath.ClassPathChangedEvent;
|
||||
import org.springframework.boot.devtools.classpath.ClassPathFileChangeListener;
|
||||
import org.springframework.boot.devtools.classpath.ClassPathRestartStrategy;
|
||||
import org.springframework.boot.devtools.filewatch.ChangedFile;
|
||||
import org.springframework.boot.devtools.filewatch.ChangedFiles;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link ClassPathFileChangeListener}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class ClassPathFileChangeListenerTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<ApplicationEvent> eventCaptor;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void eventPublisherMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("EventPublisher must not be null");
|
||||
new ClassPathFileChangeListener(null, mock(ClassPathRestartStrategy.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void restartStrategyMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("RestartStrategy must not be null");
|
||||
new ClassPathFileChangeListener(mock(ApplicationEventPublisher.class), null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendsEventWithoutRestart() throws Exception {
|
||||
testSendsEvent(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendsEventWithRestart() throws Exception {
|
||||
testSendsEvent(true);
|
||||
}
|
||||
|
||||
private void testSendsEvent(boolean restart) {
|
||||
ApplicationEventPublisher eventPublisher = mock(ApplicationEventPublisher.class);
|
||||
ClassPathRestartStrategy restartStrategy = mock(ClassPathRestartStrategy.class);
|
||||
ClassPathFileChangeListener listener = new ClassPathFileChangeListener(
|
||||
eventPublisher, restartStrategy);
|
||||
File folder = new File("s1");
|
||||
File file = new File("f1");
|
||||
ChangedFile file1 = new ChangedFile(folder, file, ChangedFile.Type.ADD);
|
||||
ChangedFile file2 = new ChangedFile(folder, file, ChangedFile.Type.ADD);
|
||||
Set<ChangedFile> files = new LinkedHashSet<ChangedFile>();
|
||||
files.add(file1);
|
||||
files.add(file2);
|
||||
ChangedFiles changedFiles = new ChangedFiles(new File("source"), files);
|
||||
Set<ChangedFiles> changeSet = Collections.singleton(changedFiles);
|
||||
if (restart) {
|
||||
given(restartStrategy.isRestartRequired(file2)).willReturn(true);
|
||||
}
|
||||
listener.onChange(changeSet);
|
||||
verify(eventPublisher).publishEvent(this.eventCaptor.capture());
|
||||
ClassPathChangedEvent actualEvent = (ClassPathChangedEvent) this.eventCaptor
|
||||
.getValue();
|
||||
assertThat(actualEvent.getChangeSet(), equalTo(changeSet));
|
||||
assertThat(actualEvent.isRestartRequired(), equalTo(restart));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.classpath;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.devtools.classpath.ClassPathChangedEvent;
|
||||
import org.springframework.boot.devtools.classpath.ClassPathFileSystemWatcher;
|
||||
import org.springframework.boot.devtools.classpath.ClassPathRestartStrategy;
|
||||
import org.springframework.boot.devtools.filewatch.ChangedFile;
|
||||
import org.springframework.boot.devtools.filewatch.FileSystemWatcher;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ClassPathFileSystemWatcher}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class ClassPathFileSystemWatcherTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temp = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void urlsMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Urls must not be null");
|
||||
URL[] urls = null;
|
||||
new ClassPathFileSystemWatcher(urls);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configuredWithRestartStrategy() throws Exception {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||
Map<String, Object> properties = new HashMap<String, Object>();
|
||||
File folder = this.temp.newFolder();
|
||||
List<URL> urls = new ArrayList<URL>();
|
||||
urls.add(new URL("http://spring.io"));
|
||||
urls.add(folder.toURI().toURL());
|
||||
properties.put("urls", urls);
|
||||
MapPropertySource propertySource = new MapPropertySource("test", properties);
|
||||
context.getEnvironment().getPropertySources().addLast(propertySource);
|
||||
context.register(Config.class);
|
||||
context.refresh();
|
||||
Thread.sleep(100);
|
||||
File classFile = new File(folder, "Example.class");
|
||||
FileCopyUtils.copy("file".getBytes(), classFile);
|
||||
Thread.sleep(1100);
|
||||
List<ClassPathChangedEvent> events = context.getBean(Listener.class).getEvents();
|
||||
assertThat(events.size(), equalTo(1));
|
||||
assertThat(events.get(0).getChangeSet().iterator().next().getFiles().iterator()
|
||||
.next().getFile(), equalTo(classFile));
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class Config {
|
||||
|
||||
@Autowired
|
||||
public Environment environemnt;
|
||||
|
||||
@Bean
|
||||
public ClassPathFileSystemWatcher watcher() {
|
||||
FileSystemWatcher watcher = new FileSystemWatcher(false, 100, 10);
|
||||
URL[] urls = this.environemnt.getProperty("urls", URL[].class);
|
||||
return new ClassPathFileSystemWatcher(watcher, restartStrategy(), urls);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ClassPathRestartStrategy restartStrategy() {
|
||||
return new ClassPathRestartStrategy() {
|
||||
|
||||
@Override
|
||||
public boolean isRestartRequired(ChangedFile file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Listener listener() {
|
||||
return new Listener();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Listener implements ApplicationListener<ClassPathChangedEvent> {
|
||||
|
||||
private List<ClassPathChangedEvent> events = new ArrayList<ClassPathChangedEvent>();
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ClassPathChangedEvent event) {
|
||||
this.events.add(event);
|
||||
}
|
||||
|
||||
public List<ClassPathChangedEvent> getEvents() {
|
||||
return this.events;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.classpath;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.devtools.classpath.ClassPathRestartStrategy;
|
||||
import org.springframework.boot.devtools.classpath.PatternClassPathRestartStrategy;
|
||||
import org.springframework.boot.devtools.filewatch.ChangedFile;
|
||||
import org.springframework.boot.devtools.filewatch.ChangedFile.Type;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link PatternClassPathRestartStrategy}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class PatternClassPathRestartStrategyTests {
|
||||
|
||||
@Test
|
||||
public void nullPattern() throws Exception {
|
||||
ClassPathRestartStrategy strategy = createStrategy(null);
|
||||
assertRestartRequired(strategy, "a/b.txt", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyPattern() throws Exception {
|
||||
ClassPathRestartStrategy strategy = createStrategy("");
|
||||
assertRestartRequired(strategy, "a/b.txt", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void singlePattern() throws Exception {
|
||||
ClassPathRestartStrategy strategy = createStrategy("static/**");
|
||||
assertRestartRequired(strategy, "static/file.txt", false);
|
||||
assertRestartRequired(strategy, "static/folder/file.txt", false);
|
||||
assertRestartRequired(strategy, "public/file.txt", true);
|
||||
assertRestartRequired(strategy, "public/folder/file.txt", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multiplePatterns() throws Exception {
|
||||
ClassPathRestartStrategy strategy = createStrategy("static/**,public/**");
|
||||
assertRestartRequired(strategy, "static/file.txt", false);
|
||||
assertRestartRequired(strategy, "static/folder/file.txt", false);
|
||||
assertRestartRequired(strategy, "public/file.txt", false);
|
||||
assertRestartRequired(strategy, "public/folder/file.txt", false);
|
||||
assertRestartRequired(strategy, "src/file.txt", true);
|
||||
assertRestartRequired(strategy, "src/folder/file.txt", true);
|
||||
}
|
||||
|
||||
private ClassPathRestartStrategy createStrategy(String pattern) {
|
||||
return new PatternClassPathRestartStrategy(pattern);
|
||||
}
|
||||
|
||||
private void assertRestartRequired(ClassPathRestartStrategy strategy,
|
||||
String relativeName, boolean expected) {
|
||||
assertThat(strategy.isRestartRequired(mockFile(relativeName)), equalTo(expected));
|
||||
}
|
||||
|
||||
private ChangedFile mockFile(String relativeName) {
|
||||
return new ChangedFile(new File("."), new File("./" + relativeName), Type.ADD);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.filewatch;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.boot.devtools.filewatch.ChangedFile;
|
||||
import org.springframework.boot.devtools.filewatch.ChangedFile.Type;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ChangedFile}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class ChangedFileTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temp = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void sourceFolderMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("SourceFolder must not be null");
|
||||
new ChangedFile(null, this.temp.newFile(), Type.ADD);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fileMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("File must not be null");
|
||||
new ChangedFile(this.temp.newFolder(), null, Type.ADD);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void typeMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Type must not be null");
|
||||
new ChangedFile(this.temp.newFile(), this.temp.newFolder(), null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getFile() throws Exception {
|
||||
File file = this.temp.newFile();
|
||||
ChangedFile changedFile = new ChangedFile(this.temp.newFolder(), file, Type.ADD);
|
||||
assertThat(changedFile.getFile(), equalTo(file));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getType() throws Exception {
|
||||
ChangedFile changedFile = new ChangedFile(this.temp.newFolder(),
|
||||
this.temp.newFile(), Type.DELETE);
|
||||
assertThat(changedFile.getType(), equalTo(Type.DELETE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRelativeName() throws Exception {
|
||||
File folder = this.temp.newFolder();
|
||||
File subFolder = new File(folder, "A");
|
||||
File file = new File(subFolder, "B.txt");
|
||||
ChangedFile changedFile = new ChangedFile(folder, file, Type.ADD);
|
||||
assertThat(changedFile.getRelativeName(), equalTo("A/B.txt"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.filewatch;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.boot.devtools.filewatch.FileSnapshot;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link FileSnapshot}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class FileSnapshotTests {
|
||||
|
||||
private static final long TWO_MINS = TimeUnit.MINUTES.toMillis(2);
|
||||
|
||||
private static final long MODIFIED = new Date().getTime()
|
||||
- TimeUnit.DAYS.toMillis(10);
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void fileMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("File must not be null");
|
||||
new FileSnapshot(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fileMustNotBeAFolder() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("File must not be a folder");
|
||||
new FileSnapshot(this.temporaryFolder.newFolder());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsIfTheSame() throws Exception {
|
||||
File file = createNewFile("abc", MODIFIED);
|
||||
File fileCopy = new File(file, "x").getParentFile();
|
||||
FileSnapshot snapshot1 = new FileSnapshot(file);
|
||||
FileSnapshot snapshot2 = new FileSnapshot(fileCopy);
|
||||
assertThat(snapshot1, equalTo(snapshot2));
|
||||
assertThat(snapshot1.hashCode(), equalTo(snapshot2.hashCode()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notEqualsIfDeleted() throws Exception {
|
||||
File file = createNewFile("abc", MODIFIED);
|
||||
FileSnapshot snapshot1 = new FileSnapshot(file);
|
||||
file.delete();
|
||||
assertThat(snapshot1, not(equalTo(new FileSnapshot(file))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notEqualsIfLengthChanges() throws Exception {
|
||||
File file = createNewFile("abc", MODIFIED);
|
||||
FileSnapshot snapshot1 = new FileSnapshot(file);
|
||||
setupFile(file, "abcd", MODIFIED);
|
||||
assertThat(snapshot1, not(equalTo(new FileSnapshot(file))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notEqualsIfLastModifiedChanges() throws Exception {
|
||||
File file = createNewFile("abc", MODIFIED);
|
||||
FileSnapshot snapshot1 = new FileSnapshot(file);
|
||||
setupFile(file, "abc", MODIFIED + TWO_MINS);
|
||||
assertThat(snapshot1, not(equalTo(new FileSnapshot(file))));
|
||||
}
|
||||
|
||||
private File createNewFile(String content, long lastModified) throws IOException {
|
||||
File file = this.temporaryFolder.newFile();
|
||||
setupFile(file, content, lastModified);
|
||||
return file;
|
||||
}
|
||||
|
||||
private void setupFile(File file, String content, long lastModified)
|
||||
throws IOException {
|
||||
FileCopyUtils.copy(content.getBytes(), file);
|
||||
file.setLastModified(lastModified);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.filewatch;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.boot.devtools.filewatch.ChangedFile;
|
||||
import org.springframework.boot.devtools.filewatch.ChangedFiles;
|
||||
import org.springframework.boot.devtools.filewatch.FileChangeListener;
|
||||
import org.springframework.boot.devtools.filewatch.FileSystemWatcher;
|
||||
import org.springframework.boot.devtools.filewatch.ChangedFile.Type;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link FileSystemWatcher}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class FileSystemWatcherTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private FileSystemWatcher watcher;
|
||||
|
||||
private List<Set<ChangedFiles>> changes = new ArrayList<Set<ChangedFiles>>();
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temp = new TemporaryFolder();
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
setupWatcher(20, 10);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listenerMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("FileChangeListener must not be null");
|
||||
this.watcher.addListener(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotAddListenerToStartedListener() throws Exception {
|
||||
this.thrown.expect(IllegalStateException.class);
|
||||
this.thrown.expectMessage("FileSystemWatcher already started");
|
||||
this.watcher.start();
|
||||
this.watcher.addListener(mock(FileChangeListener.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sourceFolderMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Folder must not be null");
|
||||
this.watcher.addSourceFolder(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotAddSourceFolderToStartedListener() throws Exception {
|
||||
this.thrown.expect(IllegalStateException.class);
|
||||
this.thrown.expectMessage("FileSystemWatcher already started");
|
||||
this.watcher.start();
|
||||
this.watcher.addSourceFolder(this.temp.newFolder());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addFile() throws Exception {
|
||||
File folder = startWithNewFolder();
|
||||
File file = touch(new File(folder, "test.txt"));
|
||||
this.watcher.stopAfter(1);
|
||||
ChangedFiles changedFiles = getSingleChangedFiles();
|
||||
ChangedFile expected = new ChangedFile(folder, file, Type.ADD);
|
||||
assertThat(changedFiles.getFiles(), contains(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addNestedFile() throws Exception {
|
||||
File folder = startWithNewFolder();
|
||||
File file = touch(new File(new File(folder, "sub"), "text.txt"));
|
||||
this.watcher.stopAfter(1);
|
||||
ChangedFiles changedFiles = getSingleChangedFiles();
|
||||
ChangedFile expected = new ChangedFile(folder, file, Type.ADD);
|
||||
assertThat(changedFiles.getFiles(), contains(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void waitsForIdleTime() throws Exception {
|
||||
this.changes.clear();
|
||||
setupWatcher(100, 0);
|
||||
File folder = startWithNewFolder();
|
||||
touch(new File(folder, "test1.txt"));
|
||||
Thread.sleep(200);
|
||||
touch(new File(folder, "test2.txt"));
|
||||
this.watcher.stopAfter(1);
|
||||
assertThat(this.changes.size(), equalTo(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void waitsForQuietTime() throws Exception {
|
||||
setupWatcher(300, 200);
|
||||
File folder = startWithNewFolder();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
touch(new File(folder, i + "test.txt"));
|
||||
Thread.sleep(100);
|
||||
}
|
||||
this.watcher.stopAfter(1);
|
||||
ChangedFiles changedFiles = getSingleChangedFiles();
|
||||
assertThat(changedFiles.getFiles().size(), equalTo(10));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withExistingFiles() throws Exception {
|
||||
File folder = this.temp.newFolder();
|
||||
touch(new File(folder, "test.txt"));
|
||||
this.watcher.addSourceFolder(folder);
|
||||
this.watcher.start();
|
||||
File file = touch(new File(folder, "test2.txt"));
|
||||
this.watcher.stopAfter(1);
|
||||
ChangedFiles changedFiles = getSingleChangedFiles();
|
||||
ChangedFile expected = new ChangedFile(folder, file, Type.ADD);
|
||||
assertThat(changedFiles.getFiles(), contains(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleSources() throws Exception {
|
||||
File folder1 = this.temp.newFolder();
|
||||
File folder2 = this.temp.newFolder();
|
||||
this.watcher.addSourceFolder(folder1);
|
||||
this.watcher.addSourceFolder(folder2);
|
||||
this.watcher.start();
|
||||
File file1 = touch(new File(folder1, "test.txt"));
|
||||
File file2 = touch(new File(folder2, "test.txt"));
|
||||
this.watcher.stopAfter(1);
|
||||
Set<ChangedFiles> change = getSingleOnChange();
|
||||
assertThat(change.size(), equalTo(2));
|
||||
for (ChangedFiles changedFiles : change) {
|
||||
if (changedFiles.getSourceFolder().equals(folder1)) {
|
||||
ChangedFile file = new ChangedFile(folder1, file1, Type.ADD);
|
||||
assertEquals(new HashSet<ChangedFile>(Arrays.asList(file)),
|
||||
changedFiles.getFiles());
|
||||
}
|
||||
else {
|
||||
ChangedFile file = new ChangedFile(folder2, file2, Type.ADD);
|
||||
assertEquals(new HashSet<ChangedFile>(Arrays.asList(file)),
|
||||
changedFiles.getFiles());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleListeners() throws Exception {
|
||||
File folder = this.temp.newFolder();
|
||||
final Set<ChangedFiles> listener2Changes = new LinkedHashSet<ChangedFiles>();
|
||||
this.watcher.addSourceFolder(folder);
|
||||
this.watcher.addListener(new FileChangeListener() {
|
||||
@Override
|
||||
public void onChange(Set<ChangedFiles> changeSet) {
|
||||
listener2Changes.addAll(changeSet);
|
||||
}
|
||||
});
|
||||
this.watcher.start();
|
||||
File file = touch(new File(folder, "test.txt"));
|
||||
this.watcher.stopAfter(1);
|
||||
ChangedFiles changedFiles = getSingleChangedFiles();
|
||||
ChangedFile expected = new ChangedFile(folder, file, Type.ADD);
|
||||
assertThat(changedFiles.getFiles(), contains(expected));
|
||||
assertEquals(this.changes.get(0), listener2Changes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void modifyDeleteAndAdd() throws Exception {
|
||||
File folder = this.temp.newFolder();
|
||||
File modify = touch(new File(folder, "modify.txt"));
|
||||
File delete = touch(new File(folder, "delete.txt"));
|
||||
this.watcher.addSourceFolder(folder);
|
||||
this.watcher.start();
|
||||
FileCopyUtils.copy("abc".getBytes(), modify);
|
||||
delete.delete();
|
||||
File add = touch(new File(folder, "add.txt"));
|
||||
this.watcher.stopAfter(1);
|
||||
ChangedFiles changedFiles = getSingleChangedFiles();
|
||||
Set<ChangedFile> actual = changedFiles.getFiles();
|
||||
Set<ChangedFile> expected = new HashSet<ChangedFile>();
|
||||
expected.add(new ChangedFile(folder, modify, Type.MODIFY));
|
||||
expected.add(new ChangedFile(folder, delete, Type.DELETE));
|
||||
expected.add(new ChangedFile(folder, add, Type.ADD));
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
private void setupWatcher(long idleTime, long quietTime) {
|
||||
this.watcher = new FileSystemWatcher(false, idleTime, quietTime);
|
||||
this.watcher.addListener(new FileChangeListener() {
|
||||
@Override
|
||||
public void onChange(Set<ChangedFiles> changeSet) {
|
||||
FileSystemWatcherTests.this.changes.add(changeSet);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private File startWithNewFolder() throws IOException {
|
||||
File folder = this.temp.newFolder();
|
||||
this.watcher.addSourceFolder(folder);
|
||||
this.watcher.start();
|
||||
return folder;
|
||||
}
|
||||
|
||||
private ChangedFiles getSingleChangedFiles() {
|
||||
Set<ChangedFiles> singleChange = getSingleOnChange();
|
||||
assertThat(singleChange.size(), equalTo(1));
|
||||
return singleChange.iterator().next();
|
||||
}
|
||||
|
||||
private Set<ChangedFiles> getSingleOnChange() {
|
||||
assertThat(this.changes.size(), equalTo(1));
|
||||
return this.changes.get(0);
|
||||
}
|
||||
|
||||
private File touch(File file) throws FileNotFoundException, IOException {
|
||||
file.getParentFile().mkdirs();
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(file);
|
||||
fileOutputStream.close();
|
||||
return file;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.filewatch;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.boot.devtools.filewatch.ChangedFile;
|
||||
import org.springframework.boot.devtools.filewatch.ChangedFiles;
|
||||
import org.springframework.boot.devtools.filewatch.FolderSnapshot;
|
||||
import org.springframework.boot.devtools.filewatch.ChangedFile.Type;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link FolderSnapshot}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class FolderSnapshotTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
private File folder;
|
||||
|
||||
private FolderSnapshot initialSnapshot;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
this.folder = createTestFolderStructure();
|
||||
this.initialSnapshot = new FolderSnapshot(this.folder);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void folderMustNotBeNull() {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Folder must not be null");
|
||||
new FolderSnapshot(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void folderMustNotBeFile() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Folder must not be a file");
|
||||
new FolderSnapshot(this.temporaryFolder.newFile());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsWhenNothingHasChanged() throws Exception {
|
||||
FolderSnapshot updatedSnapshot = new FolderSnapshot(this.folder);
|
||||
assertThat(this.initialSnapshot, equalTo(updatedSnapshot));
|
||||
assertThat(this.initialSnapshot.hashCode(), equalTo(updatedSnapshot.hashCode()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notEqualsWhenAFileIsAdded() throws Exception {
|
||||
new File(new File(this.folder, "folder1"), "newfile").createNewFile();
|
||||
FolderSnapshot updatedSnapshot = new FolderSnapshot(this.folder);
|
||||
assertThat(this.initialSnapshot, not(equalTo(updatedSnapshot)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notEqualsWhenAFileIsDeleted() throws Exception {
|
||||
new File(new File(this.folder, "folder1"), "file1").delete();
|
||||
FolderSnapshot updatedSnapshot = new FolderSnapshot(this.folder);
|
||||
assertThat(this.initialSnapshot, not(equalTo(updatedSnapshot)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notEqualsWhenAFileIsModified() throws Exception {
|
||||
File file1 = new File(new File(this.folder, "folder1"), "file1");
|
||||
FileCopyUtils.copy("updatedcontent".getBytes(), file1);
|
||||
FolderSnapshot updatedSnapshot = new FolderSnapshot(this.folder);
|
||||
assertThat(this.initialSnapshot, not(equalTo(updatedSnapshot)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getChangedFilesSnapshotMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Snapshot must not be null");
|
||||
this.initialSnapshot.getChangedFiles(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getChangedFilesSnapshotMustBeTheSameSourceFolder() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Snapshot source folder must be '" + this.folder + "'");
|
||||
this.initialSnapshot.getChangedFiles(new FolderSnapshot(
|
||||
createTestFolderStructure()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getChangedFilesWhenNothingHasChanged() throws Exception {
|
||||
FolderSnapshot updatedSnapshot = new FolderSnapshot(this.folder);
|
||||
this.initialSnapshot.getChangedFiles(updatedSnapshot);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getChangedFilesWhenAFileIsAddedAndDeletedAndChanged() throws Exception {
|
||||
File folder1 = new File(this.folder, "folder1");
|
||||
File file1 = new File(folder1, "file1");
|
||||
File file2 = new File(folder1, "file2");
|
||||
File newFile = new File(folder1, "newfile");
|
||||
FileCopyUtils.copy("updatedcontent".getBytes(), file1);
|
||||
file2.delete();
|
||||
newFile.createNewFile();
|
||||
FolderSnapshot updatedSnapshot = new FolderSnapshot(this.folder);
|
||||
ChangedFiles changedFiles = this.initialSnapshot.getChangedFiles(updatedSnapshot);
|
||||
assertThat(changedFiles.getSourceFolder(), equalTo(this.folder));
|
||||
assertThat(getChangedFile(changedFiles, file1).getType(), equalTo(Type.MODIFY));
|
||||
assertThat(getChangedFile(changedFiles, file2).getType(), equalTo(Type.DELETE));
|
||||
assertThat(getChangedFile(changedFiles, newFile).getType(), equalTo(Type.ADD));
|
||||
}
|
||||
|
||||
private ChangedFile getChangedFile(ChangedFiles changedFiles, File file) {
|
||||
for (ChangedFile changedFile : changedFiles) {
|
||||
if (changedFile.getFile().equals(file)) {
|
||||
return changedFile;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private File createTestFolderStructure() throws IOException {
|
||||
File root = this.temporaryFolder.newFolder();
|
||||
File folder1 = new File(root, "folder1");
|
||||
folder1.mkdirs();
|
||||
FileCopyUtils.copy("abc".getBytes(), new File(folder1, "file1"));
|
||||
FileCopyUtils.copy("abc".getBytes(), new File(folder1, "file2"));
|
||||
return root;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.integrationtest;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
|
||||
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
|
||||
import org.springframework.boot.devtools.remote.server.AccessManager;
|
||||
import org.springframework.boot.devtools.remote.server.Dispatcher;
|
||||
import org.springframework.boot.devtools.remote.server.DispatcherFilter;
|
||||
import org.springframework.boot.devtools.remote.server.HandlerMapper;
|
||||
import org.springframework.boot.devtools.remote.server.UrlHandlerMapper;
|
||||
import org.springframework.boot.devtools.tunnel.client.HttpTunnelConnection;
|
||||
import org.springframework.boot.devtools.tunnel.client.TunnelClient;
|
||||
import org.springframework.boot.devtools.tunnel.client.TunnelConnection;
|
||||
import org.springframework.boot.devtools.tunnel.server.HttpTunnelServer;
|
||||
import org.springframework.boot.devtools.tunnel.server.HttpTunnelServerHandler;
|
||||
import org.springframework.boot.devtools.tunnel.server.PortProvider;
|
||||
import org.springframework.boot.devtools.tunnel.server.SocketTargetServerConnection;
|
||||
import org.springframework.boot.devtools.tunnel.server.StaticPortProvider;
|
||||
import org.springframework.boot.devtools.tunnel.server.TargetServerConnection;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.boot.test.TestRestTemplate;
|
||||
import org.springframework.boot.test.WebIntegrationTest;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.util.SocketUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Simple integration tests for HTTP tunneling.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = HttpTunnelIntegrationTest.Config.class)
|
||||
@WebIntegrationTest
|
||||
public class HttpTunnelIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private Config config;
|
||||
|
||||
@Test
|
||||
public void httpServerDirect() throws Exception {
|
||||
String url = "http://localhost:" + this.config.httpServerPort + "/hello";
|
||||
ResponseEntity<String> entity = new TestRestTemplate().getForEntity(url,
|
||||
String.class);
|
||||
assertEquals(HttpStatus.OK, entity.getStatusCode());
|
||||
assertEquals("Hello World", entity.getBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void viaTunnel() throws Exception {
|
||||
String url = "http://localhost:" + this.config.clientPort + "/hello";
|
||||
ResponseEntity<String> entity = new TestRestTemplate().getForEntity(url,
|
||||
String.class);
|
||||
assertEquals(HttpStatus.OK, entity.getStatusCode());
|
||||
assertEquals("Hello World", entity.getBody());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
static class Config {
|
||||
|
||||
private int clientPort = SocketUtils.findAvailableTcpPort();
|
||||
|
||||
private int httpServerPort = SocketUtils.findAvailableTcpPort();
|
||||
|
||||
@Bean
|
||||
public EmbeddedServletContainerFactory container() {
|
||||
return new TomcatEmbeddedServletContainerFactory(this.httpServerPort);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DispatcherFilter filter() {
|
||||
PortProvider port = new StaticPortProvider(this.httpServerPort);
|
||||
TargetServerConnection connection = new SocketTargetServerConnection(port);
|
||||
HttpTunnelServer server = new HttpTunnelServer(connection);
|
||||
HandlerMapper mapper = new UrlHandlerMapper("/httptunnel",
|
||||
new HttpTunnelServerHandler(server));
|
||||
Collection<HandlerMapper> mappers = Collections.singleton(mapper);
|
||||
Dispatcher dispatcher = new Dispatcher(AccessManager.PERMIT_ALL, mappers);
|
||||
return new DispatcherFilter(dispatcher);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TunnelClient tunnelClient() {
|
||||
String url = "http://localhost:" + this.httpServerPort + "/httptunnel";
|
||||
TunnelConnection connection = new HttpTunnelConnection(url,
|
||||
new SimpleClientHttpRequestFactory());
|
||||
return new TunnelClient(this.clientPort, connection);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DispatcherServlet dispatcherServlet() {
|
||||
return new DispatcherServlet();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MyController myController() {
|
||||
return new MyController();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class MyController {
|
||||
|
||||
@RequestMapping("/hello")
|
||||
public String hello() {
|
||||
return "Hello World";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.livereload;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.devtools.livereload.Base64Encoder;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link Base64Encoder}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class Base64EncoderTests {
|
||||
|
||||
private static final String TEXT = "Man is distinguished, not only by his reason, "
|
||||
+ "but by this singular passion from other animals, which is a lust of the "
|
||||
+ "mind, that by a perseverance of delight in the continued and indefatigable "
|
||||
+ "generation of knowledge, exceeds the short vehemence of any carnal pleasure.";
|
||||
|
||||
private static final String ENCODED = "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5I"
|
||||
+ "GhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbm"
|
||||
+ "ltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmF"
|
||||
+ "uY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVy"
|
||||
+ "YXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55I"
|
||||
+ "GNhcm5hbCBwbGVhc3VyZS4=";
|
||||
|
||||
@Test
|
||||
public void encodeText() {
|
||||
assertThat(Base64Encoder.encode(TEXT), equalTo(ENCODED));
|
||||
assertThat(Base64Encoder.encode("pleasure."), equalTo("cGxlYXN1cmUu"));
|
||||
assertThat(Base64Encoder.encode("leasure."), equalTo("bGVhc3VyZS4="));
|
||||
assertThat(Base64Encoder.encode("easure."), equalTo("ZWFzdXJlLg=="));
|
||||
assertThat(Base64Encoder.encode("asure."), equalTo("YXN1cmUu"));
|
||||
assertThat(Base64Encoder.encode("sure."), equalTo("c3VyZS4="));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.livereload;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.boot.devtools.livereload.ConnectionInputStream;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ConnectionInputStream}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
public class ConnectionInputStreamTests {
|
||||
|
||||
private static final byte[] NO_BYTES = {};
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void readHeader() throws Exception {
|
||||
String header = "";
|
||||
for (int i = 0; i < 100; i++) {
|
||||
header += "x-something-" + i
|
||||
+ ": xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
|
||||
}
|
||||
String data = header + "\r\n\r\n" + "content\r\n";
|
||||
ConnectionInputStream inputStream = new ConnectionInputStream(
|
||||
new ByteArrayInputStream(data.getBytes()));
|
||||
assertThat(inputStream.readHeader(), equalTo(header));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readFully() throws Exception {
|
||||
byte[] bytes = "the data that we want to read fully".getBytes();
|
||||
LimitedInputStream source = new LimitedInputStream(
|
||||
new ByteArrayInputStream(bytes), 2);
|
||||
ConnectionInputStream inputStream = new ConnectionInputStream(source);
|
||||
byte[] buffer = new byte[bytes.length];
|
||||
inputStream.readFully(buffer, 0, buffer.length);
|
||||
assertThat(buffer, equalTo(bytes));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkedRead() throws Exception {
|
||||
ConnectionInputStream inputStream = new ConnectionInputStream(
|
||||
new ByteArrayInputStream(NO_BYTES));
|
||||
this.thrown.expect(IOException.class);
|
||||
this.thrown.expectMessage("End of stream");
|
||||
inputStream.checkedRead();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkedReadArray() throws Exception {
|
||||
ConnectionInputStream inputStream = new ConnectionInputStream(
|
||||
new ByteArrayInputStream(NO_BYTES));
|
||||
this.thrown.expect(IOException.class);
|
||||
this.thrown.expectMessage("End of stream");
|
||||
byte[] buffer = new byte[100];
|
||||
inputStream.checkedRead(buffer, 0, buffer.length);
|
||||
}
|
||||
|
||||
private static class LimitedInputStream extends FilterInputStream {
|
||||
|
||||
private final int max;
|
||||
|
||||
protected LimitedInputStream(InputStream in, int max) {
|
||||
super(in);
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
return super.read(b, off, Math.min(len, this.max));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.livereload;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.devtools.livereload.ConnectionOutputStream;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link ConnectionOutputStream}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
public class ConnectionOutputStreamTests {
|
||||
|
||||
@Test
|
||||
public void write() throws Exception {
|
||||
OutputStream out = mock(OutputStream.class);
|
||||
ConnectionOutputStream outputStream = new ConnectionOutputStream(out);
|
||||
byte[] b = new byte[100];
|
||||
outputStream.write(b, 1, 2);
|
||||
verify(out).write(b, 1, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeHttp() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ConnectionOutputStream outputStream = new ConnectionOutputStream(out);
|
||||
outputStream.writeHttp(new ByteArrayInputStream("hi".getBytes()), "x-type");
|
||||
String expected = "";
|
||||
expected += "HTTP/1.1 200 OK\r\n";
|
||||
expected += "Content-Type: x-type\r\n";
|
||||
expected += "Content-Length: 2\r\n";
|
||||
expected += "Connection: close\r\n\r\n";
|
||||
expected += "hi";
|
||||
assertThat(out.toString(), equalTo(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeHeaders() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ConnectionOutputStream outputStream = new ConnectionOutputStream(out);
|
||||
outputStream.writeHeaders("A: a", "B: b");
|
||||
outputStream.flush();
|
||||
String expected = "";
|
||||
expected += "A: a\r\n";
|
||||
expected += "B: b\r\n\r\n";
|
||||
assertThat(out.toString(), equalTo(expected));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.livereload;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.boot.devtools.livereload.ConnectionInputStream;
|
||||
import org.springframework.boot.devtools.livereload.Frame;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link Frame}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class FrameTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void payloadMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Payload must not be null");
|
||||
new Frame((String) null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void typeMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Type must not be null");
|
||||
new Frame((Frame.Type) null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void textPayload() throws Exception {
|
||||
Frame frame = new Frame("abc");
|
||||
assertThat(frame.getType(), equalTo(Frame.Type.TEXT));
|
||||
assertThat(frame.getPayload(), equalTo("abc".getBytes()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void typedPayload() throws Exception {
|
||||
Frame frame = new Frame(Frame.Type.CLOSE);
|
||||
assertThat(frame.getType(), equalTo(Frame.Type.CLOSE));
|
||||
assertThat(frame.getPayload(), equalTo(new byte[] {}));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeSmallPayload() throws Exception {
|
||||
String payload = createString(1);
|
||||
Frame frame = new Frame(payload);
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
frame.write(bos);
|
||||
assertThat(bos.toByteArray(), equalTo(new byte[] { (byte) 0x81, 0x01, 0x41 }));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeLargePayload() throws Exception {
|
||||
String payload = createString(126);
|
||||
Frame frame = new Frame(payload);
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
frame.write(bos);
|
||||
byte[] bytes = bos.toByteArray();
|
||||
assertThat(bytes.length, equalTo(130));
|
||||
assertThat(bytes[0], equalTo((byte) 0x81));
|
||||
assertThat(bytes[1], equalTo((byte) 0x7E));
|
||||
assertThat(bytes[2], equalTo((byte) 0x00));
|
||||
assertThat(bytes[3], equalTo((byte) 126));
|
||||
assertThat(bytes[4], equalTo((byte) 0x41));
|
||||
assertThat(bytes[5], equalTo((byte) 0x41));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readFragmentedNotSupported() throws Exception {
|
||||
byte[] bytes = new byte[] { 0x0F };
|
||||
this.thrown.expect(IllegalStateException.class);
|
||||
this.thrown.expectMessage("Fragmented frames are not supported");
|
||||
Frame.read(newConnectionInputStream(bytes));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readLargeFramesNotSupported() throws Exception {
|
||||
byte[] bytes = new byte[] { (byte) 0x80, (byte) 0xFF };
|
||||
this.thrown.expect(IllegalStateException.class);
|
||||
this.thrown.expectMessage("Large frames are not supported");
|
||||
Frame.read(newConnectionInputStream(bytes));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readSmallTextFrame() throws Exception {
|
||||
byte[] bytes = new byte[] { (byte) 0x81, (byte) 0x02, 0x41, 0x41 };
|
||||
Frame frame = Frame.read(newConnectionInputStream(bytes));
|
||||
assertThat(frame.getType(), equalTo(Frame.Type.TEXT));
|
||||
assertThat(frame.getPayload(), equalTo(new byte[] { 0x41, 0x41 }));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readMaskedTextFrame() throws Exception {
|
||||
byte[] bytes = new byte[] { (byte) 0x81, (byte) 0x82, 0x0F, 0x0F, 0x0F, 0x0F,
|
||||
0x4E, 0x4E };
|
||||
Frame frame = Frame.read(newConnectionInputStream(bytes));
|
||||
assertThat(frame.getType(), equalTo(Frame.Type.TEXT));
|
||||
assertThat(frame.getPayload(), equalTo(new byte[] { 0x41, 0x41 }));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readLargeTextFrame() throws Exception {
|
||||
byte[] bytes = new byte[134];
|
||||
Arrays.fill(bytes, (byte) 0x4E);
|
||||
bytes[0] = (byte) 0x81;
|
||||
bytes[1] = (byte) 0xFE;
|
||||
bytes[2] = 0x00;
|
||||
bytes[3] = 126;
|
||||
bytes[4] = 0x0F;
|
||||
bytes[5] = 0x0F;
|
||||
bytes[6] = 0x0F;
|
||||
bytes[7] = 0x0F;
|
||||
Frame frame = Frame.read(newConnectionInputStream(bytes));
|
||||
assertThat(frame.getType(), equalTo(Frame.Type.TEXT));
|
||||
assertThat(frame.getPayload(), equalTo(createString(126).getBytes()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readContinuation() throws Exception {
|
||||
byte[] bytes = new byte[] { (byte) 0x80, (byte) 0x00 };
|
||||
Frame frame = Frame.read(newConnectionInputStream(bytes));
|
||||
assertThat(frame.getType(), equalTo(Frame.Type.CONTINUATION));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readBinary() throws Exception {
|
||||
byte[] bytes = new byte[] { (byte) 0x82, (byte) 0x00 };
|
||||
Frame frame = Frame.read(newConnectionInputStream(bytes));
|
||||
assertThat(frame.getType(), equalTo(Frame.Type.BINARY));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readClose() throws Exception {
|
||||
byte[] bytes = new byte[] { (byte) 0x88, (byte) 0x00 };
|
||||
Frame frame = Frame.read(newConnectionInputStream(bytes));
|
||||
assertThat(frame.getType(), equalTo(Frame.Type.CLOSE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readPing() throws Exception {
|
||||
byte[] bytes = new byte[] { (byte) 0x89, (byte) 0x00 };
|
||||
Frame frame = Frame.read(newConnectionInputStream(bytes));
|
||||
assertThat(frame.getType(), equalTo(Frame.Type.PING));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readPong() throws Exception {
|
||||
byte[] bytes = new byte[] { (byte) 0x8A, (byte) 0x00 };
|
||||
Frame frame = Frame.read(newConnectionInputStream(bytes));
|
||||
assertThat(frame.getType(), equalTo(Frame.Type.PONG));
|
||||
}
|
||||
|
||||
private ConnectionInputStream newConnectionInputStream(byte[] bytes) {
|
||||
return new ConnectionInputStream(new ByteArrayInputStream(bytes));
|
||||
}
|
||||
|
||||
private String createString(int length) {
|
||||
char[] chars = new char[length];
|
||||
Arrays.fill(chars, 'A');
|
||||
return new String(chars);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.livereload;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketListener;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
|
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.eclipse.jetty.websocket.common.events.JettyListenerEventDriver;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.devtools.livereload.Connection;
|
||||
import org.springframework.boot.devtools.livereload.ConnectionClosedException;
|
||||
import org.springframework.boot.devtools.livereload.LiveReloadServer;
|
||||
import org.springframework.util.SocketUtils;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link LiveReloadServer}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class LiveReloadServerTests {
|
||||
|
||||
private static final String HANDSHAKE = "{command: 'hello', "
|
||||
+ "protocols: ['http://livereload.com/protocols/official-7']}";
|
||||
|
||||
private static final ByteBuffer NO_DATA = ByteBuffer.allocate(0);
|
||||
|
||||
private int port = SocketUtils.findAvailableTcpPort();
|
||||
|
||||
private MonitoredLiveReloadServer server;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
this.server = new MonitoredLiveReloadServer(this.port);
|
||||
this.server.start();
|
||||
}
|
||||
|
||||
@After
|
||||
public void teardown() throws Exception {
|
||||
this.server.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void servesLivereloadJs() throws Exception {
|
||||
RestTemplate template = new RestTemplate();
|
||||
URI uri = new URI("http://localhost:" + this.port + "/livereload.js");
|
||||
String script = template.getForObject(uri, String.class);
|
||||
assertThat(script, containsString("livereload.com/protocols/official-7"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void triggerReload() throws Exception {
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
try {
|
||||
Socket socket = openSocket(client, new Socket());
|
||||
this.server.triggerReload();
|
||||
Thread.sleep(500);
|
||||
this.server.stop();
|
||||
assertThat(socket.getMessages(0),
|
||||
containsString("http://livereload.com/protocols/official-7"));
|
||||
assertThat(socket.getMessages(1), containsString("command\":\"reload\""));
|
||||
}
|
||||
finally {
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pingPong() throws Exception {
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
try {
|
||||
Socket socket = new Socket();
|
||||
Driver driver = openSocket(client, new Driver(socket));
|
||||
socket.getRemote().sendPing(NO_DATA);
|
||||
Thread.sleep(200);
|
||||
this.server.stop();
|
||||
assertThat(driver.getPongCount(), equalTo(1));
|
||||
}
|
||||
finally {
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clientClose() throws Exception {
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
try {
|
||||
Socket socket = openSocket(client, new Socket());
|
||||
socket.getSession().close();
|
||||
}
|
||||
finally {
|
||||
client.stop();
|
||||
}
|
||||
assertThat(this.server.getClosedExceptions().size(), greaterThan(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serverClose() throws Exception {
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
try {
|
||||
Socket socket = openSocket(client, new Socket());
|
||||
Thread.sleep(200);
|
||||
this.server.stop();
|
||||
Thread.sleep(200);
|
||||
assertThat(socket.getCloseStatus(), equalTo(1006));
|
||||
}
|
||||
finally {
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private <T> T openSocket(WebSocketClient client, T socket) throws Exception,
|
||||
URISyntaxException, InterruptedException, ExecutionException, IOException {
|
||||
client.start();
|
||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||
URI uri = new URI("ws://localhost:" + this.port + "/livereload");
|
||||
Session session = client.connect(socket, uri, request).get();
|
||||
session.getRemote().sendString(HANDSHAKE);
|
||||
Thread.sleep(200);
|
||||
return socket;
|
||||
}
|
||||
|
||||
private static class Driver extends JettyListenerEventDriver {
|
||||
|
||||
private int pongCount;
|
||||
|
||||
public Driver(WebSocketListener listener) {
|
||||
super(WebSocketPolicy.newClientPolicy(), listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPong(ByteBuffer buffer) {
|
||||
super.onPong(buffer);
|
||||
this.pongCount++;
|
||||
}
|
||||
|
||||
public int getPongCount() {
|
||||
return this.pongCount;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class Socket extends WebSocketAdapter {
|
||||
|
||||
private List<String> messages = new ArrayList<String>();
|
||||
|
||||
private Integer closeStatus;
|
||||
|
||||
@Override
|
||||
public void onWebSocketText(String message) {
|
||||
this.messages.add(message);
|
||||
}
|
||||
|
||||
public String getMessages(int index) {
|
||||
return this.messages.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketClose(int statusCode, String reason) {
|
||||
this.closeStatus = statusCode;
|
||||
}
|
||||
|
||||
public Integer getCloseStatus() {
|
||||
return this.closeStatus;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Useful main method for manual testing against a real browser.
|
||||
* @param args main args
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void main(String[] args) throws IOException {
|
||||
LiveReloadServer server = new LiveReloadServer();
|
||||
server.start();
|
||||
while (true) {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
server.triggerReload();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link LiveReloadServer} with additional monitoring.
|
||||
*/
|
||||
private static class MonitoredLiveReloadServer extends LiveReloadServer {
|
||||
|
||||
private List<ConnectionClosedException> closedExceptions = new ArrayList<ConnectionClosedException>();
|
||||
|
||||
public MonitoredLiveReloadServer(int port) {
|
||||
super(port);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Connection createConnection(java.net.Socket socket,
|
||||
InputStream inputStream, OutputStream outputStream) throws IOException {
|
||||
return new MonitoredConnection(socket, inputStream, outputStream);
|
||||
}
|
||||
|
||||
public List<ConnectionClosedException> getClosedExceptions() {
|
||||
return this.closedExceptions;
|
||||
}
|
||||
|
||||
private class MonitoredConnection extends Connection {
|
||||
|
||||
public MonitoredConnection(java.net.Socket socket, InputStream inputStream,
|
||||
OutputStream outputStream) throws IOException {
|
||||
super(socket, inputStream, outputStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
try {
|
||||
super.run();
|
||||
}
|
||||
catch (ConnectionClosedException ex) {
|
||||
MonitoredLiveReloadServer.this.closedExceptions.add(ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.remote.client;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.boot.devtools.classpath.ClassPathChangedEvent;
|
||||
import org.springframework.boot.devtools.filewatch.ChangedFile;
|
||||
import org.springframework.boot.devtools.filewatch.ChangedFiles;
|
||||
import org.springframework.boot.devtools.filewatch.ChangedFile.Type;
|
||||
import org.springframework.boot.devtools.remote.client.ClassPathChangeUploader;
|
||||
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile;
|
||||
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFiles;
|
||||
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile.Kind;
|
||||
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFiles.SourceFolder;
|
||||
import org.springframework.boot.devtools.test.MockClientHttpRequestFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.mock.http.client.MockClientHttpRequest;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ClassPathChangeUploader}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class ClassPathChangeUploaderTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temp = new TemporaryFolder();
|
||||
|
||||
private MockClientHttpRequestFactory requestFactory;
|
||||
|
||||
private ClassPathChangeUploader uploader;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.requestFactory = new MockClientHttpRequestFactory();
|
||||
this.uploader = new ClassPathChangeUploader("http://localhost/upload",
|
||||
this.requestFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void urlMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("URL must not be empty");
|
||||
new ClassPathChangeUploader(null, this.requestFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void urlMustNotBeEmpty() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("URL must not be empty");
|
||||
new ClassPathChangeUploader("", this.requestFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestFactoryMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("RequestFactory must not be null");
|
||||
new ClassPathChangeUploader("http://localhost:8080", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void urlMustNotBeMalformed() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Malformed URL 'htttttp:///ttest'");
|
||||
new ClassPathChangeUploader("htttttp:///ttest", this.requestFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendsClassLoaderFiles() throws Exception {
|
||||
File sourceFolder = this.temp.newFolder();
|
||||
Set<ChangedFile> files = new LinkedHashSet<ChangedFile>();
|
||||
File file1 = createFile(sourceFolder, "File1");
|
||||
File file2 = createFile(sourceFolder, "File2");
|
||||
File file3 = createFile(sourceFolder, "File3");
|
||||
files.add(new ChangedFile(sourceFolder, file1, Type.ADD));
|
||||
files.add(new ChangedFile(sourceFolder, file2, Type.MODIFY));
|
||||
files.add(new ChangedFile(sourceFolder, file3, Type.DELETE));
|
||||
Set<ChangedFiles> changeSet = new LinkedHashSet<ChangedFiles>();
|
||||
changeSet.add(new ChangedFiles(sourceFolder, files));
|
||||
ClassPathChangedEvent event = new ClassPathChangedEvent(this, changeSet, false);
|
||||
this.requestFactory.willRespond(HttpStatus.OK);
|
||||
this.uploader.onApplicationEvent(event);
|
||||
MockClientHttpRequest request = this.requestFactory.getExecutedRequests().get(0);
|
||||
ClassLoaderFiles classLoaderFiles = deserialize(request.getBodyAsBytes());
|
||||
Collection<SourceFolder> sourceFolders = classLoaderFiles.getSourceFolders();
|
||||
assertThat(sourceFolders.size(), equalTo(1));
|
||||
SourceFolder classSourceFolder = sourceFolders.iterator().next();
|
||||
assertThat(classSourceFolder.getName(), equalTo(sourceFolder.getAbsolutePath()));
|
||||
Iterator<ClassLoaderFile> classFiles = classSourceFolder.getFiles().iterator();
|
||||
assertClassFile(classFiles.next(), "File1", ClassLoaderFile.Kind.ADDED);
|
||||
assertClassFile(classFiles.next(), "File2", ClassLoaderFile.Kind.MODIFIED);
|
||||
assertClassFile(classFiles.next(), null, ClassLoaderFile.Kind.DELETED);
|
||||
assertThat(classFiles.hasNext(), equalTo(false));
|
||||
}
|
||||
|
||||
private void assertClassFile(ClassLoaderFile file, String content, Kind kind) {
|
||||
assertThat(file.getContents(),
|
||||
equalTo(content == null ? null : content.getBytes()));
|
||||
assertThat(file.getKind(), equalTo(kind));
|
||||
}
|
||||
|
||||
private File createFile(File sourceFolder, String name) throws IOException {
|
||||
File file = new File(sourceFolder, name);
|
||||
FileCopyUtils.copy(name.getBytes(), file);
|
||||
return file;
|
||||
}
|
||||
|
||||
private ClassLoaderFiles deserialize(byte[] bytes) throws IOException,
|
||||
ClassNotFoundException {
|
||||
ObjectInputStream objectInputStream = new ObjectInputStream(
|
||||
new ByteArrayInputStream(bytes));
|
||||
return (ClassLoaderFiles) objectInputStream.readObject();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.remote.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.springframework.boot.devtools.autoconfigure.OptionalLiveReloadServer;
|
||||
import org.springframework.boot.devtools.remote.client.DelayedLiveReloadTrigger;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.client.ClientHttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link DelayedLiveReloadTrigger}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class DelayedLiveReloadTriggerTests {
|
||||
|
||||
private static final String URL = "http://localhost:8080";
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Mock
|
||||
private OptionalLiveReloadServer liveReloadServer;
|
||||
|
||||
@Mock
|
||||
private ClientHttpRequestFactory requestFactory;
|
||||
|
||||
@Mock
|
||||
private ClientHttpRequest errorRequest;
|
||||
|
||||
@Mock
|
||||
private ClientHttpRequest okRequest;
|
||||
|
||||
@Mock
|
||||
private ClientHttpResponse errorResponse;
|
||||
|
||||
@Mock
|
||||
private ClientHttpResponse okResponse;
|
||||
|
||||
private DelayedLiveReloadTrigger trigger;
|
||||
|
||||
@Before
|
||||
public void setup() throws IOException {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
given(this.errorRequest.execute()).willReturn(this.errorResponse);
|
||||
given(this.okRequest.execute()).willReturn(this.okResponse);
|
||||
given(this.errorResponse.getStatusCode()).willReturn(
|
||||
HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
given(this.okResponse.getStatusCode()).willReturn(HttpStatus.OK);
|
||||
this.trigger = new DelayedLiveReloadTrigger(this.liveReloadServer,
|
||||
this.requestFactory, URL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void liveReloadServerMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("LiveReloadServer must not be null");
|
||||
new DelayedLiveReloadTrigger(null, this.requestFactory, URL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestFactoryMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("RequestFactory must not be null");
|
||||
new DelayedLiveReloadTrigger(this.liveReloadServer, null, URL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void urlMostNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("URL must not be empty");
|
||||
new DelayedLiveReloadTrigger(this.liveReloadServer, this.requestFactory, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void urlMustNotBeEmpty() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("URL must not be empty");
|
||||
new DelayedLiveReloadTrigger(this.liveReloadServer, this.requestFactory, "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void triggerReloadOnStatus() throws Exception {
|
||||
given(this.requestFactory.createRequest(new URI(URL), HttpMethod.GET)).willThrow(
|
||||
new IOException()).willReturn(this.errorRequest, this.okRequest);
|
||||
long startTime = System.currentTimeMillis();
|
||||
this.trigger.setTimings(10, 200, 30000);
|
||||
this.trigger.run();
|
||||
assertThat(System.currentTimeMillis() - startTime, greaterThan(300L));
|
||||
verify(this.liveReloadServer).triggerReload();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void timeout() throws Exception {
|
||||
given(this.requestFactory.createRequest(new URI(URL), HttpMethod.GET)).willThrow(
|
||||
new IOException());
|
||||
this.trigger.setTimings(10, 0, 10);
|
||||
this.trigger.run();
|
||||
verify(this.liveReloadServer, never()).triggerReload();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.remote.client;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.boot.devtools.remote.client.HttpHeaderInterceptor;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestExecution;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
|
||||
/**
|
||||
* Tests for {@link HttpHeaderInterceptor}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class HttpHeaderInterceptorTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private String name;
|
||||
|
||||
private String value;
|
||||
|
||||
private HttpHeaderInterceptor interceptor;
|
||||
|
||||
private HttpRequest request;
|
||||
|
||||
private byte[] body;
|
||||
|
||||
@Mock
|
||||
private ClientHttpRequestExecution execution;
|
||||
|
||||
@Mock
|
||||
private ClientHttpResponse response;
|
||||
|
||||
private MockHttpServletRequest httpRequest;
|
||||
|
||||
@Before
|
||||
public void setup() throws IOException {
|
||||
this.body = new byte[] {};
|
||||
this.httpRequest = new MockHttpServletRequest();
|
||||
this.request = new ServletServerHttpRequest(this.httpRequest);
|
||||
this.name = "X-AUTH-TOKEN";
|
||||
this.value = "secret";
|
||||
given(this.execution.execute(this.request, this.body)).willReturn(this.response);
|
||||
this.interceptor = new HttpHeaderInterceptor(this.name, this.value);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorNullHeaderName() {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Name must not be empty");
|
||||
new HttpHeaderInterceptor(null, this.value);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorEmptyHeaderName() {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Name must not be empty");
|
||||
new HttpHeaderInterceptor("", this.value);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorNullHeaderValue() {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Value must not be empty");
|
||||
new HttpHeaderInterceptor(this.name, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorEmptyHeaderValue() {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Value must not be empty");
|
||||
new HttpHeaderInterceptor(this.name, "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void intercept() throws IOException {
|
||||
ClientHttpResponse result = this.interceptor.intercept(this.request, this.body,
|
||||
this.execution);
|
||||
assertThat(this.request.getHeaders().getFirst(this.name), equalTo(this.value));
|
||||
assertThat(result, equalTo(this.response));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.remote.client;
|
||||
|
||||
import java.net.ServerSocket;
|
||||
|
||||
import javax.net.ServerSocketFactory;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
|
||||
import org.springframework.boot.test.EnvironmentTestUtils;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
import org.springframework.util.SocketUtils;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link LocalDebugPortAvailableCondition}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class LocalDebugPortAvailableConditionTests {
|
||||
|
||||
private int port = SocketUtils.findAvailableTcpPort();
|
||||
|
||||
private LocalDebugPortAvailableCondition condition = new LocalDebugPortAvailableCondition();
|
||||
|
||||
@Test
|
||||
public void portAvailable() throws Exception {
|
||||
ConditionOutcome outcome = getOutcome();
|
||||
assertThat(outcome.isMatch(), equalTo(true));
|
||||
assertThat(outcome.getMessage(), equalTo("Local debug port availble"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void portInUse() throws Exception {
|
||||
final ServerSocket serverSocket = ServerSocketFactory.getDefault()
|
||||
.createServerSocket(this.port);
|
||||
ConditionOutcome outcome = getOutcome();
|
||||
serverSocket.close();
|
||||
assertThat(outcome.isMatch(), equalTo(false));
|
||||
assertThat(outcome.getMessage(), equalTo("Local debug port unavailble"));
|
||||
}
|
||||
|
||||
private ConditionOutcome getOutcome() {
|
||||
MockEnvironment environment = new MockEnvironment();
|
||||
EnvironmentTestUtils.addEnvironment(environment,
|
||||
"spring.devtools.remote.debug.local-port:" + this.port);
|
||||
ConditionContext context = mock(ConditionContext.class);
|
||||
given(context.getEnvironment()).willReturn(environment);
|
||||
ConditionOutcome outcome = this.condition.getMatchOutcome(context, null);
|
||||
return outcome;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.remote.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext;
|
||||
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
|
||||
import org.springframework.boot.devtools.autoconfigure.OptionalLiveReloadServer;
|
||||
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.remote.client.RemoteClientConfiguration.LiveReloadConfiguration;
|
||||
import org.springframework.boot.devtools.remote.server.Dispatcher;
|
||||
import org.springframework.boot.devtools.remote.server.DispatcherFilter;
|
||||
import org.springframework.boot.devtools.restart.MockRestarter;
|
||||
import org.springframework.boot.devtools.restart.RestartScopeInitializer;
|
||||
import org.springframework.boot.devtools.tunnel.client.TunnelClient;
|
||||
import org.springframework.boot.test.EnvironmentTestUtils;
|
||||
import org.springframework.boot.test.OutputCapture;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.util.SocketUtils;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link RemoteClientConfiguration}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class RemoteClientConfigurationTests {
|
||||
|
||||
@Rule
|
||||
public MockRestarter restarter = new MockRestarter();
|
||||
|
||||
@Rule
|
||||
public OutputCapture output = new OutputCapture();
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private AnnotationConfigEmbeddedWebApplicationContext context;
|
||||
|
||||
private static int remotePort = SocketUtils.findAvailableTcpPort();
|
||||
|
||||
@After
|
||||
public void cleanup() {
|
||||
if (this.context != null) {
|
||||
this.context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void warnIfDebugAndRestartDisabled() throws Exception {
|
||||
configure("spring.devtools.remote.debug.enabled:false",
|
||||
"spring.devtools.remote.restart.enabled:false");
|
||||
assertThat(this.output.toString(),
|
||||
containsString("Remote restart and debug are both disabled"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void warnIfNotHttps() throws Exception {
|
||||
configure("http://localhost", true);
|
||||
assertThat(this.output.toString(), containsString("is insecure"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesntWarnIfUsingHttps() throws Exception {
|
||||
configure("https://localhost", true);
|
||||
assertThat(this.output.toString(), not(containsString("is insecure")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failIfNoSecret() throws Exception {
|
||||
this.thrown.expect(BeanCreationException.class);
|
||||
this.thrown.expectMessage("required to secure your connection");
|
||||
configure("http://localhost", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void liveReloadOnClassPathChanged() throws Exception {
|
||||
configure();
|
||||
Set<ChangedFiles> changeSet = new HashSet<ChangedFiles>();
|
||||
ClassPathChangedEvent event = new ClassPathChangedEvent(this, changeSet, false);
|
||||
this.context.publishEvent(event);
|
||||
LiveReloadConfiguration configuration = this.context
|
||||
.getBean(LiveReloadConfiguration.class);
|
||||
configuration.getExecutor().shutdown();
|
||||
configuration.getExecutor().awaitTermination(2, TimeUnit.SECONDS);
|
||||
LiveReloadServer server = this.context.getBean(LiveReloadServer.class);
|
||||
verify(server).triggerReload();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void liveReloadDisabled() throws Exception {
|
||||
configure("spring.devtools.livereload.enabled:false");
|
||||
this.thrown.expect(NoSuchBeanDefinitionException.class);
|
||||
this.context.getBean(OptionalLiveReloadServer.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void remoteRestartDisabled() throws Exception {
|
||||
configure("spring.devtools.remote.restart.enabled:false");
|
||||
this.thrown.expect(NoSuchBeanDefinitionException.class);
|
||||
this.context.getBean(ClassPathFileSystemWatcher.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void remoteDebugDisabled() throws Exception {
|
||||
configure("spring.devtools.remote.debug.enabled:false");
|
||||
this.thrown.expect(NoSuchBeanDefinitionException.class);
|
||||
this.context.getBean(TunnelClient.class);
|
||||
}
|
||||
|
||||
private void configure(String... pairs) {
|
||||
configure("http://localhost", true, pairs);
|
||||
}
|
||||
|
||||
private void configure(String remoteUrl, boolean setSecret, String... pairs) {
|
||||
this.context = new AnnotationConfigEmbeddedWebApplicationContext();
|
||||
new RestartScopeInitializer().initialize(this.context);
|
||||
this.context.register(Config.class, RemoteClientConfiguration.class);
|
||||
String remoteUrlProperty = "remoteUrl:" + remoteUrl + ":"
|
||||
+ RemoteClientConfigurationTests.remotePort;
|
||||
EnvironmentTestUtils.addEnvironment(this.context, remoteUrlProperty);
|
||||
EnvironmentTestUtils.addEnvironment(this.context, pairs);
|
||||
if (setSecret) {
|
||||
EnvironmentTestUtils.addEnvironment(this.context,
|
||||
"spring.devtools.remote.secret:secret");
|
||||
}
|
||||
this.context.refresh();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public TomcatEmbeddedServletContainerFactory tomcat() {
|
||||
return new TomcatEmbeddedServletContainerFactory(remotePort);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LiveReloadServer liveReloadServer() {
|
||||
return mock(LiveReloadServer.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DispatcherFilter dispatcherFilter() throws IOException {
|
||||
return new DispatcherFilter(dispatcher());
|
||||
}
|
||||
|
||||
public Dispatcher dispatcher() throws IOException {
|
||||
Dispatcher dispatcher = mock(Dispatcher.class);
|
||||
ServerHttpRequest anyRequest = (ServerHttpRequest) any();
|
||||
ServerHttpResponse anyResponse = (ServerHttpResponse) any();
|
||||
given(dispatcher.handle(anyRequest, anyResponse)).willReturn(true);
|
||||
return dispatcher;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.remote.server;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.springframework.boot.devtools.remote.server.Dispatcher;
|
||||
import org.springframework.boot.devtools.remote.server.DispatcherFilter;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.http.server.ServletServerHttpResponse;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.BDDMockito.willReturn;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
|
||||
/**
|
||||
* Tests for {@link DispatcherFilter}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class DispatcherFilterTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Mock
|
||||
private Dispatcher dispatcher;
|
||||
|
||||
@Mock
|
||||
private FilterChain chain;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<ServerHttpResponse> serverResponseCaptor;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<ServerHttpRequest> serverRequestCaptor;
|
||||
|
||||
private DispatcherFilter filter;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
this.filter = new DispatcherFilter(this.dispatcher);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dispatcherMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Dispatcher must not be null");
|
||||
new DispatcherFilter(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ignoresNotServletRequests() throws Exception {
|
||||
ServletRequest request = mock(ServletRequest.class);
|
||||
ServletResponse response = mock(ServletResponse.class);
|
||||
this.filter.doFilter(request, response, this.chain);
|
||||
verifyZeroInteractions(this.dispatcher);
|
||||
verify(this.chain).doFilter(request, response);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ignoredByDispatcher() throws Exception {
|
||||
HttpServletRequest request = new MockHttpServletRequest("GET", "/hello");
|
||||
HttpServletResponse response = new MockHttpServletResponse();
|
||||
this.filter.doFilter(request, response, this.chain);
|
||||
verify(this.chain).doFilter(request, response);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handledByDispatcher() throws Exception {
|
||||
HttpServletRequest request = new MockHttpServletRequest("GET", "/hello");
|
||||
HttpServletResponse response = new MockHttpServletResponse();
|
||||
willReturn(true).given(this.dispatcher).handle(any(ServerHttpRequest.class),
|
||||
any(ServerHttpResponse.class));
|
||||
this.filter.doFilter(request, response, this.chain);
|
||||
verifyZeroInteractions(this.chain);
|
||||
verify(this.dispatcher).handle(this.serverRequestCaptor.capture(),
|
||||
this.serverResponseCaptor.capture());
|
||||
ServerHttpRequest dispatcherRequest = this.serverRequestCaptor.getValue();
|
||||
ServletServerHttpRequest actualRequest = (ServletServerHttpRequest) dispatcherRequest;
|
||||
ServerHttpResponse dispatcherResponse = this.serverResponseCaptor.getValue();
|
||||
ServletServerHttpResponse actualResponse = (ServletServerHttpResponse) dispatcherResponse;
|
||||
assertThat(actualRequest.getServletRequest(), equalTo(request));
|
||||
assertThat(actualResponse.getServletResponse(), equalTo(response));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.remote.server;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.springframework.boot.devtools.remote.server.AccessManager;
|
||||
import org.springframework.boot.devtools.remote.server.Dispatcher;
|
||||
import org.springframework.boot.devtools.remote.server.Handler;
|
||||
import org.springframework.boot.devtools.remote.server.HandlerMapper;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.http.server.ServletServerHttpResponse;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
import static org.mockito.Mockito.withSettings;
|
||||
|
||||
/**
|
||||
* Tests for {@link Dispatcher}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class DispatcherTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Mock
|
||||
private AccessManager accessManager;
|
||||
|
||||
private MockHttpServletRequest request;
|
||||
|
||||
private MockHttpServletResponse response;
|
||||
|
||||
private ServerHttpRequest serverRequest;
|
||||
|
||||
private ServerHttpResponse serverResponse;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
this.request = new MockHttpServletRequest();
|
||||
this.response = new MockHttpServletResponse();
|
||||
this.serverRequest = new ServletServerHttpRequest(this.request);
|
||||
this.serverResponse = new ServletServerHttpResponse(this.response);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void accessManagerMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("AccessManager must not be null");
|
||||
new Dispatcher(null, Collections.<HandlerMapper> emptyList());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mappersMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Mappers must not be null");
|
||||
new Dispatcher(this.accessManager, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void accessManagerVetoRequest() throws Exception {
|
||||
given(this.accessManager.isAllowed(any(ServerHttpRequest.class))).willReturn(
|
||||
false);
|
||||
HandlerMapper mapper = mock(HandlerMapper.class);
|
||||
Handler handler = mock(Handler.class);
|
||||
given(mapper.getHandler(any(ServerHttpRequest.class))).willReturn(handler);
|
||||
Dispatcher dispatcher = new Dispatcher(this.accessManager,
|
||||
Collections.singleton(mapper));
|
||||
dispatcher.handle(this.serverRequest, this.serverResponse);
|
||||
verifyZeroInteractions(handler);
|
||||
assertThat(this.response.getStatus(), equalTo(403));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void accessManagerAllowRequest() throws Exception {
|
||||
given(this.accessManager.isAllowed(any(ServerHttpRequest.class)))
|
||||
.willReturn(true);
|
||||
HandlerMapper mapper = mock(HandlerMapper.class);
|
||||
Handler handler = mock(Handler.class);
|
||||
given(mapper.getHandler(any(ServerHttpRequest.class))).willReturn(handler);
|
||||
Dispatcher dispatcher = new Dispatcher(this.accessManager,
|
||||
Collections.singleton(mapper));
|
||||
dispatcher.handle(this.serverRequest, this.serverResponse);
|
||||
verify(handler).handle(this.serverRequest, this.serverResponse);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ordersMappers() throws Exception {
|
||||
HandlerMapper mapper1 = mock(HandlerMapper.class,
|
||||
withSettings().extraInterfaces(Ordered.class));
|
||||
HandlerMapper mapper2 = mock(HandlerMapper.class,
|
||||
withSettings().extraInterfaces(Ordered.class));
|
||||
given(((Ordered) mapper1).getOrder()).willReturn(1);
|
||||
given(((Ordered) mapper2).getOrder()).willReturn(2);
|
||||
List<HandlerMapper> mappers = Arrays.asList(mapper2, mapper1);
|
||||
Dispatcher dispatcher = new Dispatcher(AccessManager.PERMIT_ALL, mappers);
|
||||
dispatcher.handle(this.serverRequest, this.serverResponse);
|
||||
InOrder inOrder = inOrder(mapper1, mapper2);
|
||||
inOrder.verify(mapper1).getHandler(this.serverRequest);
|
||||
inOrder.verify(mapper2).getHandler(this.serverRequest);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.remote.server;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.boot.devtools.remote.server.HttpHeaderAccessManager;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link HttpHeaderAccessManager}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class HttpHeaderAccessManagerTests {
|
||||
|
||||
private static final String HEADER = "X-AUTH_TOKEN";
|
||||
|
||||
private static final String SECRET = "password";
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private MockHttpServletRequest request;
|
||||
|
||||
private ServerHttpRequest serverRequest;
|
||||
|
||||
private HttpHeaderAccessManager manager;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.request = new MockHttpServletRequest("GET", "/");
|
||||
this.serverRequest = new ServletServerHttpRequest(this.request);
|
||||
this.manager = new HttpHeaderAccessManager(HEADER, SECRET);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headerNameMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("HeaderName must not be empty");
|
||||
new HttpHeaderAccessManager(null, SECRET);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headerNameMustNotBeEmpty() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("HeaderName must not be empty");
|
||||
new HttpHeaderAccessManager("", SECRET);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void expectedSecretMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("ExpectedSecret must not be empty");
|
||||
new HttpHeaderAccessManager(HEADER, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void expectedSecretMustNotBeEmpty() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("ExpectedSecret must not be empty");
|
||||
new HttpHeaderAccessManager(HEADER, "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allowsMatching() throws Exception {
|
||||
this.request.addHeader(HEADER, SECRET);
|
||||
assertThat(this.manager.isAllowed(this.serverRequest), equalTo(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disallowsWrongSecret() throws Exception {
|
||||
this.request.addHeader(HEADER, "wrong");
|
||||
assertThat(this.manager.isAllowed(this.serverRequest), equalTo(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disallowsNoSecret() throws Exception {
|
||||
assertThat(this.manager.isAllowed(this.serverRequest), equalTo(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disallowsWrongHeader() throws Exception {
|
||||
this.request.addHeader("X-WRONG", SECRET);
|
||||
assertThat(this.manager.isAllowed(this.serverRequest), equalTo(false));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.remote.server;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.boot.devtools.remote.server.HttpStatusHandler;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.http.server.ServletServerHttpResponse;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link HttpStatusHandler}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class HttpStatusHandlerTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private MockHttpServletRequest servletRequest;
|
||||
|
||||
private MockHttpServletResponse servletResponse;
|
||||
|
||||
private ServerHttpResponse response;
|
||||
|
||||
private ServerHttpRequest request;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.servletRequest = new MockHttpServletRequest();
|
||||
this.servletResponse = new MockHttpServletResponse();
|
||||
this.request = new ServletServerHttpRequest(this.servletRequest);
|
||||
this.response = new ServletServerHttpResponse(this.servletResponse);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void statusMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Status must not be null");
|
||||
new HttpStatusHandler(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void respondsOk() throws Exception {
|
||||
HttpStatusHandler handler = new HttpStatusHandler();
|
||||
handler.handle(this.request, this.response);
|
||||
assertThat(this.servletResponse.getStatus(), equalTo(200));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void respondsWithStatus() throws Exception {
|
||||
HttpStatusHandler handler = new HttpStatusHandler(HttpStatus.I_AM_A_TEAPOT);
|
||||
handler.handle(this.request, this.response);
|
||||
assertThat(this.servletResponse.getStatus(), equalTo(418));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.remote.server;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.boot.devtools.remote.server.Handler;
|
||||
import org.springframework.boot.devtools.remote.server.UrlHandlerMapper;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link UrlHandlerMapper}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class UrlHandlerMapperTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private Handler handler = mock(Handler.class);
|
||||
|
||||
@Test
|
||||
public void requestUriMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("URL must not be empty");
|
||||
new UrlHandlerMapper(null, this.handler);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestUriMustNotBeEmpty() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("URL must not be empty");
|
||||
new UrlHandlerMapper("", this.handler);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestUrlMustStartWithSlash() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("URL must start with '/'");
|
||||
new UrlHandlerMapper("tunnel", this.handler);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handlesMatchedUrl() throws Exception {
|
||||
UrlHandlerMapper mapper = new UrlHandlerMapper("/tunnel", this.handler);
|
||||
HttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/tunnel");
|
||||
ServerHttpRequest request = new ServletServerHttpRequest(servletRequest);
|
||||
assertThat(mapper.getHandler(request), equalTo(this.handler));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ignoresDifferentUrl() throws Exception {
|
||||
UrlHandlerMapper mapper = new UrlHandlerMapper("/tunnel", this.handler);
|
||||
HttpServletRequest servletRequest = new MockHttpServletRequest("GET",
|
||||
"/tunnel/other");
|
||||
ServerHttpRequest request = new ServletServerHttpRequest(servletRequest);
|
||||
assertThat(mapper.getHandler(request), nullValue());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.restart;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.boot.devtools.restart.ChangeableUrls;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ChangeableUrls}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class ChangeableUrlsTests {
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void folderUrl() throws Exception {
|
||||
URL url = makeUrl("myproject");
|
||||
assertThat(ChangeableUrls.fromUrls(url).size(), equalTo(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fileUrl() throws Exception {
|
||||
URL url = this.temporaryFolder.newFile().toURI().toURL();
|
||||
assertThat(ChangeableUrls.fromUrls(url).size(), equalTo(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void httpUrl() throws Exception {
|
||||
URL url = new URL("http://spring.io");
|
||||
assertThat(ChangeableUrls.fromUrls(url).size(), equalTo(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void skipsUrls() throws Exception {
|
||||
ChangeableUrls urls = ChangeableUrls
|
||||
.fromUrls(makeUrl("spring-boot"), makeUrl("spring-boot-autoconfigure"),
|
||||
makeUrl("spring-boot-actuator"), makeUrl("spring-boot-starter"),
|
||||
makeUrl("spring-boot-starter-some-thing"));
|
||||
assertThat(urls.size(), equalTo(0));
|
||||
}
|
||||
|
||||
private URL makeUrl(String name) throws IOException {
|
||||
File file = this.temporaryFolder.newFolder();
|
||||
file = new File(file, name);
|
||||
file = new File(file, "target");
|
||||
file = new File(file, "classes");
|
||||
file.mkdirs();
|
||||
return file.toURI().toURL();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.restart;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.devtools.restart.DefaultRestartInitializer;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link DefaultRestartInitializer}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class DefaultRestartInitializerTests {
|
||||
|
||||
@Test
|
||||
public void nullForTests() throws Exception {
|
||||
MockRestartInitializer initializer = new MockRestartInitializer(true);
|
||||
assertThat(initializer.getInitialUrls(Thread.currentThread()), nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validMainThread() throws Exception {
|
||||
MockRestartInitializer initializer = new MockRestartInitializer(false);
|
||||
ClassLoader classLoader = new MockAppClassLoader(getClass().getClassLoader());
|
||||
Thread thread = new Thread();
|
||||
thread.setName("main");
|
||||
thread.setContextClassLoader(classLoader);
|
||||
assertThat(initializer.isMain(thread), equalTo(true));
|
||||
assertThat(initializer.getInitialUrls(thread), not(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void threadNotNamedMain() throws Exception {
|
||||
MockRestartInitializer initializer = new MockRestartInitializer(false);
|
||||
ClassLoader classLoader = new MockAppClassLoader(getClass().getClassLoader());
|
||||
Thread thread = new Thread();
|
||||
thread.setName("buscuit");
|
||||
thread.setContextClassLoader(classLoader);
|
||||
assertThat(initializer.isMain(thread), equalTo(false));
|
||||
assertThat(initializer.getInitialUrls(thread), nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void threadNotUsingAppClassLoader() throws Exception {
|
||||
MockRestartInitializer initializer = new MockRestartInitializer(false);
|
||||
ClassLoader classLoader = new MockLauncherClassLoader(getClass().getClassLoader());
|
||||
Thread thread = new Thread();
|
||||
thread.setName("main");
|
||||
thread.setContextClassLoader(classLoader);
|
||||
assertThat(initializer.isMain(thread), equalTo(false));
|
||||
assertThat(initializer.getInitialUrls(thread), nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void skipsDueToJUnitStacks() throws Exception {
|
||||
testSkipStack("org.junit.runners.Something", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void skipsDueToSpringTest() throws Exception {
|
||||
testSkipStack("org.springframework.boot.test.Something", true);
|
||||
}
|
||||
|
||||
private void testSkipStack(String className, boolean expected) {
|
||||
MockRestartInitializer initializer = new MockRestartInitializer(true);
|
||||
StackTraceElement element = new StackTraceElement(className, "someMethod",
|
||||
"someFile", 123);
|
||||
assertThat(initializer.isSkippedStackElement(element), equalTo(expected));
|
||||
}
|
||||
|
||||
private static class MockAppClassLoader extends ClassLoader {
|
||||
|
||||
public MockAppClassLoader(ClassLoader parent) {
|
||||
super(parent);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class MockLauncherClassLoader extends ClassLoader {
|
||||
|
||||
public MockLauncherClassLoader(ClassLoader parent) {
|
||||
super(parent);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class MockRestartInitializer extends DefaultRestartInitializer {
|
||||
|
||||
private final boolean considerStackElements;
|
||||
|
||||
public MockRestartInitializer(boolean considerStackElements) {
|
||||
this.considerStackElements = considerStackElements;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isSkippedStackElement(StackTraceElement element) {
|
||||
if (!this.considerStackElements) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected URL[] getUrls(Thread thread) {
|
||||
return new URL[0];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.restart;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.boot.devtools.restart.MainMethod;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link MainMethod}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class MainMethodTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private static ThreadLocal<MainMethod> mainMethod = new ThreadLocal<MainMethod>();
|
||||
|
||||
private Method actualMain;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
this.actualMain = Valid.class.getMethod("main", String[].class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void threadMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Thread must not be null");
|
||||
new MainMethod(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validMainMethod() throws Exception {
|
||||
MainMethod method = new TestThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Valid.main();
|
||||
}
|
||||
}).test();
|
||||
assertThat(method.getMethod(), equalTo(this.actualMain));
|
||||
assertThat(method.getDeclaringClassName(), equalTo(this.actualMain
|
||||
.getDeclaringClass().getName()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingArgsMainMethod() throws Exception {
|
||||
this.thrown.expect(IllegalStateException.class);
|
||||
this.thrown.expectMessage("Unable to find main method");
|
||||
new TestThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
MissingArgs.main();
|
||||
}
|
||||
}).test();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nonStatic() throws Exception {
|
||||
this.thrown.expect(IllegalStateException.class);
|
||||
this.thrown.expectMessage("Unable to find main method");
|
||||
new TestThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
new NonStaticMain().main();
|
||||
}
|
||||
}).test();
|
||||
}
|
||||
|
||||
private static class TestThread extends Thread {
|
||||
|
||||
private final Runnable runnable;
|
||||
|
||||
private Exception exception;
|
||||
|
||||
private MainMethod mainMethod;
|
||||
|
||||
public TestThread(Runnable runnable) {
|
||||
this.runnable = runnable;
|
||||
}
|
||||
|
||||
public MainMethod test() throws InterruptedException {
|
||||
start();
|
||||
join();
|
||||
if (this.exception != null) {
|
||||
ReflectionUtils.rethrowRuntimeException(this.exception);
|
||||
}
|
||||
return this.mainMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
this.runnable.run();
|
||||
this.mainMethod = MainMethodTests.mainMethod.get();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
this.exception = ex;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Valid {
|
||||
|
||||
public static void main(String... args) {
|
||||
someOtherMethod();
|
||||
}
|
||||
|
||||
private static void someOtherMethod() {
|
||||
mainMethod.set(new MainMethod());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class MissingArgs {
|
||||
|
||||
public static void main() {
|
||||
mainMethod.set(new MainMethod());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class NonStaticMain {
|
||||
|
||||
public void main(String... args) {
|
||||
mainMethod.set(new MainMethod());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.restart;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
import org.springframework.boot.devtools.restart.RestartInitializer;
|
||||
|
||||
/**
|
||||
* Simple mock {@link RestartInitializer} that returns an empty array of URLs.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class MockRestartInitializer implements RestartInitializer {
|
||||
|
||||
@Override
|
||||
public URL[] getInitialUrls(Thread thread) {
|
||||
return new URL[] {};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.restart;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
import org.junit.rules.TestRule;
|
||||
import org.junit.runner.Description;
|
||||
import org.junit.runners.model.Statement;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
import org.springframework.boot.devtools.restart.Restarter;
|
||||
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Mocked version of {@link Restarter}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class MockRestarter implements TestRule {
|
||||
|
||||
private Map<String, Object> attributes = new HashMap<String, Object>();
|
||||
|
||||
private Restarter mock = mock(Restarter.class);
|
||||
|
||||
@Override
|
||||
public Statement apply(final Statement base, Description description) {
|
||||
return new Statement() {
|
||||
|
||||
@Override
|
||||
public void evaluate() throws Throwable {
|
||||
setup();
|
||||
base.evaluate();
|
||||
cleanup();
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private void setup() {
|
||||
Restarter.setInstance(this.mock);
|
||||
given(this.mock.getInitialUrls()).willReturn(new URL[] {});
|
||||
given(this.mock.getOrAddAttribute(anyString(), (ObjectFactory) any()))
|
||||
.willAnswer(new Answer<Object>() {
|
||||
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocation) throws Throwable {
|
||||
String name = (String) invocation.getArguments()[0];
|
||||
ObjectFactory factory = (ObjectFactory) invocation.getArguments()[1];
|
||||
Object attribute = MockRestarter.this.attributes.get(name);
|
||||
if (attribute == null) {
|
||||
attribute = factory.getObject();
|
||||
MockRestarter.this.attributes.put(name, attribute);
|
||||
}
|
||||
return attribute;
|
||||
}
|
||||
|
||||
});
|
||||
given(this.mock.getThreadFactory()).willReturn(new ThreadFactory() {
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
return new Thread(r);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private void cleanup() {
|
||||
this.attributes.clear();
|
||||
Restarter.clearInstance();
|
||||
}
|
||||
|
||||
public Restarter getMock() {
|
||||
return this.mock;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.restart;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.devtools.restart.ConditionalOnInitializedRestarter;
|
||||
import org.springframework.boot.devtools.restart.OnInitializedRestarterCondition;
|
||||
import org.springframework.boot.devtools.restart.RestartInitializer;
|
||||
import org.springframework.boot.devtools.restart.Restarter;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link OnInitializedRestarterCondition}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class OnInitializedRestarterConditionTests {
|
||||
|
||||
private static Object wait = new Object();
|
||||
|
||||
@Before
|
||||
@After
|
||||
public void cleanup() {
|
||||
Restarter.clearInstance();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noInstance() throws Exception {
|
||||
Restarter.clearInstance();
|
||||
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
Config.class);
|
||||
assertThat(context.containsBean("bean"), equalTo(false));
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noInitialization() throws Exception {
|
||||
Restarter.initialize(new String[0], false, RestartInitializer.NONE);
|
||||
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
Config.class);
|
||||
assertThat(context.containsBean("bean"), equalTo(false));
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initialized() throws Exception {
|
||||
Thread thread = new Thread() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
TestInitialized.main();
|
||||
};
|
||||
|
||||
};
|
||||
thread.start();
|
||||
synchronized (wait) {
|
||||
wait.wait();
|
||||
}
|
||||
}
|
||||
|
||||
public static class TestInitialized {
|
||||
|
||||
public static void main(String... args) {
|
||||
RestartInitializer initializer = mock(RestartInitializer.class);
|
||||
given(initializer.getInitialUrls((Thread) any())).willReturn(new URL[0]);
|
||||
Restarter.initialize(new String[0], false, initializer);
|
||||
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
Config.class);
|
||||
assertThat(context.containsBean("bean"), equalTo(true));
|
||||
context.close();
|
||||
synchronized (wait) {
|
||||
wait.notify();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class Config {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnInitializedRestarter
|
||||
public String bean() {
|
||||
return "bean";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.restart;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.context.event.ApplicationFailedEvent;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.boot.context.event.ApplicationStartedEvent;
|
||||
import org.springframework.boot.devtools.restart.RestartApplicationListener;
|
||||
import org.springframework.boot.devtools.restart.Restarter;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link RestartApplicationListener}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class RestartApplicationListenerTests {
|
||||
|
||||
@Before
|
||||
@After
|
||||
public void cleanup() {
|
||||
Restarter.clearInstance();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isHighestPriority() throws Exception {
|
||||
assertThat(new RestartApplicationListener().getOrder(),
|
||||
equalTo(Ordered.HIGHEST_PRECEDENCE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initializeWithReady() throws Exception {
|
||||
testInitialize(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initializeWithFail() throws Exception {
|
||||
testInitialize(true);
|
||||
}
|
||||
|
||||
private void testInitialize(boolean failed) {
|
||||
Restarter.clearInstance();
|
||||
RestartApplicationListener listener = new RestartApplicationListener();
|
||||
SpringApplication application = new SpringApplication();
|
||||
ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class);
|
||||
String[] args = new String[] { "a", "b", "c" };
|
||||
listener.onApplicationEvent(new ApplicationStartedEvent(application, args));
|
||||
assertThat(Restarter.getInstance(), not(nullValue()));
|
||||
assertThat(Restarter.getInstance().isFinished(), equalTo(false));
|
||||
assertThat(ReflectionTestUtils.getField(Restarter.getInstance(), "args"),
|
||||
equalTo((Object) args));
|
||||
if (failed) {
|
||||
listener.onApplicationEvent(new ApplicationFailedEvent(application, args,
|
||||
context, new RuntimeException()));
|
||||
}
|
||||
else {
|
||||
listener.onApplicationEvent(new ApplicationReadyEvent(application, args,
|
||||
context));
|
||||
}
|
||||
assertThat(Restarter.getInstance().isFinished(), equalTo(true));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.restart;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.devtools.restart.RestartScope;
|
||||
import org.springframework.boot.devtools.restart.RestartScopeInitializer;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link RestartScopeInitializer}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class RestartScopeInitializerTests {
|
||||
|
||||
private static AtomicInteger createCount;
|
||||
|
||||
private static AtomicInteger refreshCount;
|
||||
|
||||
@Test
|
||||
public void restartScope() throws Exception {
|
||||
createCount = new AtomicInteger();
|
||||
refreshCount = new AtomicInteger();
|
||||
ConfigurableApplicationContext context = runApplication();
|
||||
context.close();
|
||||
context = runApplication();
|
||||
context.close();
|
||||
assertThat(createCount.get(), equalTo(1));
|
||||
assertThat(refreshCount.get(), equalTo(2));
|
||||
}
|
||||
|
||||
private ConfigurableApplicationContext runApplication() {
|
||||
SpringApplication application = new SpringApplication(Config.class);
|
||||
application.setWebEnvironment(false);
|
||||
return application.run();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class Config {
|
||||
|
||||
@Bean
|
||||
@RestartScope
|
||||
public ScopeTestBean scopeTestBean() {
|
||||
return new ScopeTestBean();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ScopeTestBean implements
|
||||
ApplicationListener<ContextRefreshedEvent> {
|
||||
|
||||
public ScopeTestBean() {
|
||||
createCount.incrementAndGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ContextRefreshedEvent event) {
|
||||
refreshCount.incrementAndGet();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.restart;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
import org.springframework.boot.devtools.restart.RestartInitializer;
|
||||
import org.springframework.boot.devtools.restart.Restarter;
|
||||
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile;
|
||||
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFiles;
|
||||
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile.Kind;
|
||||
import org.springframework.boot.test.OutputCapture;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
|
||||
/**
|
||||
* Tests for {@link Restarter}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class RestarterTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Rule
|
||||
public OutputCapture out = new OutputCapture();
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
Restarter.setInstance(new TestableRestarter());
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanup() {
|
||||
Restarter.clearInstance();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cantGetInstanceBeforeInitialize() throws Exception {
|
||||
Restarter.clearInstance();
|
||||
this.thrown.expect(IllegalStateException.class);
|
||||
this.thrown.expectMessage("Restarter has not been initialized");
|
||||
Restarter.getInstance();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRestart() throws Exception {
|
||||
Restarter.clearInstance();
|
||||
Thread thread = new Thread() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
SampleApplication.main();
|
||||
};
|
||||
|
||||
};
|
||||
thread.start();
|
||||
Thread.sleep(2600);
|
||||
String output = this.out.toString();
|
||||
assertThat(StringUtils.countOccurrencesOf(output, "Tick 0"), greaterThan(1));
|
||||
assertThat(StringUtils.countOccurrencesOf(output, "Tick 1"), greaterThan(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("rawtypes")
|
||||
public void getOrAddAttributeWithNewAttribute() throws Exception {
|
||||
ObjectFactory objectFactory = mock(ObjectFactory.class);
|
||||
given(objectFactory.getObject()).willReturn("abc");
|
||||
Object attribute = Restarter.getInstance().getOrAddAttribute("x", objectFactory);
|
||||
assertThat(attribute, equalTo((Object) "abc"));
|
||||
}
|
||||
|
||||
public void addUrlsMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Urls must not be null");
|
||||
Restarter.getInstance().addUrls(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addUrls() throws Exception {
|
||||
URL url = new URL("file:/proj/module-a.jar!/");
|
||||
Collection<URL> urls = Collections.singleton(url);
|
||||
Restarter restarter = Restarter.getInstance();
|
||||
restarter.addUrls(urls);
|
||||
restarter.restart();
|
||||
ClassLoader classLoader = ((TestableRestarter) restarter)
|
||||
.getRelaunchClassLoader();
|
||||
assertThat(((URLClassLoader) classLoader).getURLs()[0], equalTo(url));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addClassLoaderFilesMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("ClassLoaderFiles must not be null");
|
||||
Restarter.getInstance().addClassLoaderFiles(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addClassLoaderFiles() throws Exception {
|
||||
ClassLoaderFiles classLoaderFiles = new ClassLoaderFiles();
|
||||
classLoaderFiles.addFile("f", new ClassLoaderFile(Kind.ADDED, "abc".getBytes()));
|
||||
Restarter restarter = Restarter.getInstance();
|
||||
restarter.addClassLoaderFiles(classLoaderFiles);
|
||||
restarter.restart();
|
||||
ClassLoader classLoader = ((TestableRestarter) restarter)
|
||||
.getRelaunchClassLoader();
|
||||
assertThat(FileCopyUtils.copyToByteArray(classLoader.getResourceAsStream("f")),
|
||||
equalTo("abc".getBytes()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("rawtypes")
|
||||
public void getOrAddAttributeWithExistingAttribute() throws Exception {
|
||||
Restarter.getInstance().getOrAddAttribute("x", new ObjectFactory<String>() {
|
||||
@Override
|
||||
public String getObject() throws BeansException {
|
||||
return "abc";
|
||||
}
|
||||
});
|
||||
ObjectFactory objectFactory = mock(ObjectFactory.class);
|
||||
Object attribute = Restarter.getInstance().getOrAddAttribute("x", objectFactory);
|
||||
assertThat(attribute, equalTo((Object) "abc"));
|
||||
verifyZeroInteractions(objectFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getThreadFactory() throws Exception {
|
||||
final ClassLoader parentLoader = Thread.currentThread().getContextClassLoader();
|
||||
final ClassLoader contextClassLoader = new URLClassLoader(new URL[0]);
|
||||
Thread thread = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
Runnable runnable = mock(Runnable.class);
|
||||
Thread regular = new Thread();
|
||||
ThreadFactory factory = Restarter.getInstance().getThreadFactory();
|
||||
Thread viaFactory = factory.newThread(runnable);
|
||||
// Regular threads will inherit the current thread
|
||||
assertThat(regular.getContextClassLoader(), equalTo(contextClassLoader));
|
||||
// Factory threads should should inherit from the initial thread
|
||||
assertThat(viaFactory.getContextClassLoader(), equalTo(parentLoader));
|
||||
};
|
||||
};
|
||||
thread.setContextClassLoader(contextClassLoader);
|
||||
thread.start();
|
||||
thread.join();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getInitialUrls() throws Exception {
|
||||
Restarter.clearInstance();
|
||||
RestartInitializer initializer = mock(RestartInitializer.class);
|
||||
URL[] urls = new URL[] { new URL("file:/proj/module-a.jar!/") };
|
||||
given(initializer.getInitialUrls(any(Thread.class))).willReturn(urls);
|
||||
Restarter.initialize(new String[0], false, initializer, false);
|
||||
assertThat(Restarter.getInstance().getInitialUrls(), equalTo(urls));
|
||||
}
|
||||
|
||||
@Component
|
||||
@EnableScheduling
|
||||
public static class SampleApplication {
|
||||
|
||||
private int count = 0;
|
||||
|
||||
private static volatile boolean quit = false;
|
||||
|
||||
@Scheduled(fixedDelay = 200)
|
||||
public void tickBean() {
|
||||
System.out.println("Tick " + this.count++ + " " + Thread.currentThread());
|
||||
}
|
||||
|
||||
@Scheduled(initialDelay = 500, fixedDelay = 500)
|
||||
public void restart() {
|
||||
System.out.println("Restart " + Thread.currentThread());
|
||||
if (!SampleApplication.quit) {
|
||||
Restarter.getInstance().restart();
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String... args) {
|
||||
Restarter.initialize(args, false, new MockRestartInitializer());
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
SampleApplication.class);
|
||||
context.registerShutdownHook();
|
||||
System.out.println("Sleep " + Thread.currentThread());
|
||||
sleep();
|
||||
quit = true;
|
||||
context.close();
|
||||
}
|
||||
|
||||
private static void sleep() {
|
||||
try {
|
||||
Thread.sleep(1200);
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class TestableRestarter extends Restarter {
|
||||
|
||||
private ClassLoader relaunchClassLoader;
|
||||
|
||||
public TestableRestarter() {
|
||||
this(Thread.currentThread(), new String[] {}, false,
|
||||
new MockRestartInitializer());
|
||||
}
|
||||
|
||||
protected TestableRestarter(Thread thread, String[] args,
|
||||
boolean forceReferenceCleanup, RestartInitializer initializer) {
|
||||
super(thread, args, forceReferenceCleanup, initializer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restart() {
|
||||
try {
|
||||
stop();
|
||||
start();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void relaunch(ClassLoader classLoader) throws Exception {
|
||||
this.relaunchClassLoader = classLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stop() throws Exception {
|
||||
}
|
||||
|
||||
public ClassLoader getRelaunchClassLoader() {
|
||||
return this.relaunchClassLoader;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.restart;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.devtools.restart.SilentExitExceptionHandler;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* Tests for {@link SilentExitExceptionHandler}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class SilentExitExceptionHandlerTests {
|
||||
|
||||
@Test
|
||||
public void setupAndExit() throws Exception {
|
||||
TestThread testThread = new TestThread() {
|
||||
@Override
|
||||
public void run() {
|
||||
SilentExitExceptionHandler.exitCurrentThread();
|
||||
fail("Didn't exit");
|
||||
}
|
||||
};
|
||||
SilentExitExceptionHandler.setup(testThread);
|
||||
testThread.startAndJoin();
|
||||
assertThat(testThread.getThrown(), nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesntInterferWithOtherExceptions() throws Exception {
|
||||
TestThread testThread = new TestThread() {
|
||||
@Override
|
||||
public void run() {
|
||||
throw new IllegalStateException("Expected");
|
||||
}
|
||||
};
|
||||
SilentExitExceptionHandler.setup(testThread);
|
||||
testThread.startAndJoin();
|
||||
assertThat(testThread.getThrown().getMessage(), equalTo("Expected"));
|
||||
}
|
||||
|
||||
private static abstract class TestThread extends Thread {
|
||||
|
||||
private Throwable thrown;
|
||||
|
||||
public TestThread() {
|
||||
setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
|
||||
@Override
|
||||
public void uncaughtException(Thread t, Throwable e) {
|
||||
TestThread.this.thrown = e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Throwable getThrown() {
|
||||
return this.thrown;
|
||||
}
|
||||
|
||||
public void startAndJoin() throws InterruptedException {
|
||||
start();
|
||||
join();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.restart.classloader;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile;
|
||||
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile.Kind;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ClassLoaderFile}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class ClassLoaderFileTests {
|
||||
|
||||
public static final byte[] BYTES = "ABC".getBytes();
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void kindMustNotBeNull() {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Kind must not be null");
|
||||
new ClassLoaderFile(null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addedContentsMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Contents must not be null");
|
||||
new ClassLoaderFile(Kind.ADDED, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void modifiedContentsMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Contents must not be null");
|
||||
new ClassLoaderFile(Kind.MODIFIED, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deletedContentsMustBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Contents must be null");
|
||||
new ClassLoaderFile(Kind.DELETED, new byte[10]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void added() throws Exception {
|
||||
ClassLoaderFile file = new ClassLoaderFile(Kind.ADDED, BYTES);
|
||||
assertThat(file.getKind(), equalTo(ClassLoaderFile.Kind.ADDED));
|
||||
assertThat(file.getContents(), equalTo(BYTES));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void modified() throws Exception {
|
||||
ClassLoaderFile file = new ClassLoaderFile(Kind.MODIFIED, BYTES);
|
||||
assertThat(file.getKind(), equalTo(ClassLoaderFile.Kind.MODIFIED));
|
||||
assertThat(file.getContents(), equalTo(BYTES));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleted() throws Exception {
|
||||
ClassLoaderFile file = new ClassLoaderFile(Kind.DELETED, null);
|
||||
assertThat(file.getKind(), equalTo(ClassLoaderFile.Kind.DELETED));
|
||||
assertThat(file.getContents(), nullValue());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.restart.classloader;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile;
|
||||
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFiles;
|
||||
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile.Kind;
|
||||
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFiles.SourceFolder;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link ClassLoaderFiles}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class ClassLoaderFilesTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private ClassLoaderFiles files = new ClassLoaderFiles();
|
||||
|
||||
@Test
|
||||
public void addFileNameMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Name must not be null");
|
||||
this.files.addFile(null, mock(ClassLoaderFile.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addFileFileMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("File must not be null");
|
||||
this.files.addFile("test", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getFileWithNullName() throws Exception {
|
||||
assertThat(this.files.getFile(null), nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addAndGet() throws Exception {
|
||||
ClassLoaderFile file = new ClassLoaderFile(Kind.ADDED, new byte[10]);
|
||||
this.files.addFile("myfile", file);
|
||||
assertThat(this.files.getFile("myfile"), equalTo(file));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getMissing() throws Exception {
|
||||
assertThat(this.files.getFile("missing"), nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addTwice() throws Exception {
|
||||
ClassLoaderFile file1 = new ClassLoaderFile(Kind.ADDED, new byte[10]);
|
||||
ClassLoaderFile file2 = new ClassLoaderFile(Kind.MODIFIED, new byte[10]);
|
||||
this.files.addFile("myfile", file1);
|
||||
this.files.addFile("myfile", file2);
|
||||
assertThat(this.files.getFile("myfile"), equalTo(file2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addTwiceInDifferentSourceFolders() throws Exception {
|
||||
ClassLoaderFile file1 = new ClassLoaderFile(Kind.ADDED, new byte[10]);
|
||||
ClassLoaderFile file2 = new ClassLoaderFile(Kind.MODIFIED, new byte[10]);
|
||||
this.files.addFile("a", "myfile", file1);
|
||||
this.files.addFile("b", "myfile", file2);
|
||||
assertThat(this.files.getFile("myfile"), equalTo(file2));
|
||||
assertThat(this.files.getOrCreateSourceFolder("a").getFiles().size(), equalTo(0));
|
||||
assertThat(this.files.getOrCreateSourceFolder("b").getFiles().size(), equalTo(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSourceFolders() throws Exception {
|
||||
ClassLoaderFile file1 = new ClassLoaderFile(Kind.ADDED, new byte[10]);
|
||||
ClassLoaderFile file2 = new ClassLoaderFile(Kind.MODIFIED, new byte[10]);
|
||||
ClassLoaderFile file3 = new ClassLoaderFile(Kind.MODIFIED, new byte[10]);
|
||||
ClassLoaderFile file4 = new ClassLoaderFile(Kind.MODIFIED, new byte[10]);
|
||||
this.files.addFile("a", "myfile1", file1);
|
||||
this.files.addFile("a", "myfile2", file2);
|
||||
this.files.addFile("b", "myfile3", file3);
|
||||
this.files.addFile("b", "myfile4", file4);
|
||||
Iterator<SourceFolder> sourceFolders = this.files.getSourceFolders().iterator();
|
||||
SourceFolder sourceFolder1 = sourceFolders.next();
|
||||
SourceFolder sourceFolder2 = sourceFolders.next();
|
||||
assertThat(sourceFolders.hasNext(), equalTo(false));
|
||||
assertThat(sourceFolder1.getName(), equalTo("a"));
|
||||
assertThat(sourceFolder2.getName(), equalTo("b"));
|
||||
assertThat(new ArrayList<ClassLoaderFile>(sourceFolder1.getFiles()),
|
||||
equalTo(Arrays.asList(file1, file2)));
|
||||
assertThat(new ArrayList<ClassLoaderFile>(sourceFolder2.getFiles()),
|
||||
equalTo(Arrays.asList(file3, file4)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serialzie() throws Exception {
|
||||
ClassLoaderFile file = new ClassLoaderFile(Kind.ADDED, new byte[10]);
|
||||
this.files.addFile("myfile", file);
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
ObjectOutputStream oos = new ObjectOutputStream(bos);
|
||||
oos.writeObject(this.files);
|
||||
oos.close();
|
||||
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(
|
||||
bos.toByteArray()));
|
||||
ClassLoaderFiles readObject = (ClassLoaderFiles) ois.readObject();
|
||||
assertThat(readObject.getFile("myfile"), notNullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addAll() throws Exception {
|
||||
ClassLoaderFile file1 = new ClassLoaderFile(Kind.ADDED, new byte[10]);
|
||||
this.files.addFile("a", "myfile1", file1);
|
||||
ClassLoaderFiles toAdd = new ClassLoaderFiles();
|
||||
ClassLoaderFile file2 = new ClassLoaderFile(Kind.MODIFIED, new byte[10]);
|
||||
ClassLoaderFile file3 = new ClassLoaderFile(Kind.MODIFIED, new byte[10]);
|
||||
toAdd.addFile("a", "myfile2", file2);
|
||||
toAdd.addFile("b", "myfile3", file3);
|
||||
this.files.addAll(toAdd);
|
||||
Iterator<SourceFolder> sourceFolders = this.files.getSourceFolders().iterator();
|
||||
SourceFolder sourceFolder1 = sourceFolders.next();
|
||||
SourceFolder sourceFolder2 = sourceFolders.next();
|
||||
assertThat(sourceFolders.hasNext(), equalTo(false));
|
||||
assertThat(sourceFolder1.getName(), equalTo("a"));
|
||||
assertThat(sourceFolder2.getName(), equalTo("b"));
|
||||
assertThat(new ArrayList<ClassLoaderFile>(sourceFolder1.getFiles()),
|
||||
equalTo(Arrays.asList(file1, file2)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSize() throws Exception {
|
||||
this.files.addFile("s1", "n1", mock(ClassLoaderFile.class));
|
||||
this.files.addFile("s1", "n2", mock(ClassLoaderFile.class));
|
||||
this.files.addFile("s2", "n3", mock(ClassLoaderFile.class));
|
||||
this.files.addFile("s2", "n1", mock(ClassLoaderFile.class));
|
||||
assertThat(this.files.size(), equalTo(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void classLoaderFilesMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("ClassLoaderFiles must not be null");
|
||||
new ClassLoaderFiles(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructFromExistingSet() throws Exception {
|
||||
this.files.addFile("s1", "n1", mock(ClassLoaderFile.class));
|
||||
this.files.addFile("s1", "n2", mock(ClassLoaderFile.class));
|
||||
ClassLoaderFiles copy = new ClassLoaderFiles(this.files);
|
||||
this.files.addFile("s2", "n3", mock(ClassLoaderFile.class));
|
||||
assertThat(this.files.size(), equalTo(3));
|
||||
assertThat(copy.size(), equalTo(2));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.restart.classloader;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile;
|
||||
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFiles;
|
||||
import org.springframework.boot.devtools.restart.classloader.RestartClassLoader;
|
||||
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile.Kind;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link RestartClassLoader}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
public class RestartClassLoaderTests {
|
||||
|
||||
private static final String PACKAGE = RestartClassLoaderTests.class.getPackage()
|
||||
.getName();
|
||||
|
||||
private static final String PACKAGE_PATH = PACKAGE.replace(".", "/");
|
||||
|
||||
private static final Charset UTF_8 = Charset.forName("UTF-8");
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temp = new TemporaryFolder();
|
||||
|
||||
private File sampleJarFile;
|
||||
|
||||
private URLClassLoader parentClassLoader;
|
||||
|
||||
private ClassLoaderFiles updatedFiles;
|
||||
|
||||
private RestartClassLoader reloadClassLoader;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
this.sampleJarFile = createSampleJarFile();
|
||||
URL url = this.sampleJarFile.toURI().toURL();
|
||||
ClassLoader classLoader = getClass().getClassLoader();
|
||||
URL[] urls = new URL[] { url };
|
||||
this.parentClassLoader = new URLClassLoader(urls, classLoader);
|
||||
this.updatedFiles = new ClassLoaderFiles();
|
||||
this.reloadClassLoader = new RestartClassLoader(this.parentClassLoader, urls,
|
||||
this.updatedFiles);
|
||||
}
|
||||
|
||||
private File createSampleJarFile() throws IOException {
|
||||
File file = this.temp.newFile("sample.jar");
|
||||
JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(file));
|
||||
jarOutputStream.putNextEntry(new ZipEntry(PACKAGE_PATH + "/Sample.class"));
|
||||
StreamUtils.copy(getClass().getResourceAsStream("Sample.class"), jarOutputStream);
|
||||
jarOutputStream.closeEntry();
|
||||
jarOutputStream.putNextEntry(new ZipEntry(PACKAGE_PATH + "/Sample.txt"));
|
||||
StreamUtils.copy("fromchild", UTF_8, jarOutputStream);
|
||||
jarOutputStream.closeEntry();
|
||||
jarOutputStream.close();
|
||||
return file;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parentMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Parent must not be null");
|
||||
new RestartClassLoader(null, new URL[] {});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updatedFilesMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("UpdatedFiles must not be null");
|
||||
new RestartClassLoader(this.parentClassLoader, new URL[] {}, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getResourceFromReloadableUrl() throws Exception {
|
||||
String content = readString(this.reloadClassLoader
|
||||
.getResourceAsStream(PACKAGE_PATH + "/Sample.txt"));
|
||||
assertThat(content, startsWith("fromchild"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getResourceFromParent() throws Exception {
|
||||
String content = readString(this.reloadClassLoader
|
||||
.getResourceAsStream(PACKAGE_PATH + "/Parent.txt"));
|
||||
assertThat(content, startsWith("fromparent"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getResourcesFiltersDuplicates() throws Exception {
|
||||
List<URL> resources = toList(this.reloadClassLoader.getResources(PACKAGE_PATH
|
||||
+ "/Sample.txt"));
|
||||
assertThat(resources.size(), equalTo(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadClassFromReloadableUrl() throws Exception {
|
||||
Class<?> loaded = this.reloadClassLoader.loadClass(PACKAGE + ".Sample");
|
||||
assertThat(loaded.getClassLoader(), equalTo((ClassLoader) this.reloadClassLoader));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadClassFromParent() throws Exception {
|
||||
Class<?> loaded = this.reloadClassLoader.loadClass(PACKAGE + ".SampleParent");
|
||||
assertThat(loaded.getClassLoader(), equalTo(getClass().getClassLoader()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDeletedResource() throws Exception {
|
||||
String name = PACKAGE_PATH + "/Sample.txt";
|
||||
this.updatedFiles.addFile(name, new ClassLoaderFile(Kind.DELETED, null));
|
||||
assertThat(this.reloadClassLoader.getResource(name), equalTo(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDeletedResourceAsStream() throws Exception {
|
||||
String name = PACKAGE_PATH + "/Sample.txt";
|
||||
this.updatedFiles.addFile(name, new ClassLoaderFile(Kind.DELETED, null));
|
||||
assertThat(this.reloadClassLoader.getResourceAsStream(name), equalTo(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getUpdatedResource() throws Exception {
|
||||
String name = PACKAGE_PATH + "/Sample.txt";
|
||||
byte[] bytes = "abc".getBytes();
|
||||
this.updatedFiles.addFile(name, new ClassLoaderFile(Kind.MODIFIED, bytes));
|
||||
URL resource = this.reloadClassLoader.getResource(name);
|
||||
assertThat(FileCopyUtils.copyToByteArray(resource.openStream()), equalTo(bytes));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getResourcesWithDeleted() throws Exception {
|
||||
String name = PACKAGE_PATH + "/Sample.txt";
|
||||
this.updatedFiles.addFile(name, new ClassLoaderFile(Kind.DELETED, null));
|
||||
List<URL> resources = toList(this.reloadClassLoader.getResources(name));
|
||||
assertThat(resources.size(), equalTo(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getResourcesWithUpdated() throws Exception {
|
||||
String name = PACKAGE_PATH + "/Sample.txt";
|
||||
byte[] bytes = "abc".getBytes();
|
||||
this.updatedFiles.addFile(name, new ClassLoaderFile(Kind.MODIFIED, bytes));
|
||||
List<URL> resources = toList(this.reloadClassLoader.getResources(name));
|
||||
assertThat(FileCopyUtils.copyToByteArray(resources.get(0).openStream()),
|
||||
equalTo(bytes));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDeletedClass() throws Exception {
|
||||
String name = PACKAGE_PATH + "/Sample.class";
|
||||
this.updatedFiles.addFile(name, new ClassLoaderFile(Kind.DELETED, null));
|
||||
this.thrown.expect(ClassNotFoundException.class);
|
||||
this.reloadClassLoader.loadClass(PACKAGE + ".Sample");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getUpdatedClass() throws Exception {
|
||||
String name = PACKAGE_PATH + "/Sample.class";
|
||||
this.updatedFiles.addFile(name, new ClassLoaderFile(Kind.MODIFIED, new byte[10]));
|
||||
this.thrown.expect(ClassFormatError.class);
|
||||
this.reloadClassLoader.loadClass(PACKAGE + ".Sample");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAddedClass() throws Exception {
|
||||
String name = PACKAGE_PATH + "/SampleParent.class";
|
||||
byte[] bytes = FileCopyUtils.copyToByteArray(getClass().getResourceAsStream(
|
||||
"SampleParent.class"));
|
||||
this.updatedFiles.addFile(name, new ClassLoaderFile(Kind.ADDED, bytes));
|
||||
Class<?> loaded = this.reloadClassLoader.loadClass(PACKAGE + ".SampleParent");
|
||||
assertThat(loaded.getClassLoader(), equalTo((ClassLoader) this.reloadClassLoader));
|
||||
}
|
||||
|
||||
private String readString(InputStream in) throws IOException {
|
||||
return new String(FileCopyUtils.copyToByteArray(in));
|
||||
}
|
||||
|
||||
private <T> List<T> toList(Enumeration<T> enumeration) {
|
||||
List<T> list = new ArrayList<T>();
|
||||
if (enumeration != null) {
|
||||
while (enumeration.hasMoreElements()) {
|
||||
list.add(enumeration.nextElement());
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.restart.classloader;
|
||||
|
||||
/**
|
||||
* A sample class used to test reloading.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class Sample {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.restart.classloader;
|
||||
|
||||
/**
|
||||
* A sample class used to test reloading.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class SampleParent {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.restart.server;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.devtools.restart.server.DefaultSourceFolderUrlFilter;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link DefaultSourceFolderUrlFilter}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class DefaultSourceFolderUrlFilterTests {
|
||||
|
||||
private static final String SOURCE_ROOT = "/Users/me/code/some-root/";
|
||||
|
||||
private static final List<String> COMMON_POSTFIXES;
|
||||
static {
|
||||
List<String> postfixes = new ArrayList<String>();
|
||||
postfixes.add(".jar");
|
||||
postfixes.add("-1.3.0.jar");
|
||||
postfixes.add("-1.3.0-SNAPSHOT.jar");
|
||||
postfixes.add("-1.3.0.BUILD-SNAPSHOT.jar");
|
||||
postfixes.add("-1.3.0.M1.jar");
|
||||
postfixes.add("-1.3.0.RC1.jar");
|
||||
postfixes.add("-1.3.0.RELEASE.jar");
|
||||
postfixes.add("-1.3.0.Final.jar");
|
||||
postfixes.add("-1.3.0.GA.jar");
|
||||
postfixes.add("-1.3.0.0.0.0.jar");
|
||||
COMMON_POSTFIXES = Collections.unmodifiableList(postfixes);
|
||||
}
|
||||
|
||||
private DefaultSourceFolderUrlFilter filter = new DefaultSourceFolderUrlFilter();
|
||||
|
||||
@Test
|
||||
public void mavenSourceFolder() throws Exception {
|
||||
doTest("my-module/target/classes/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void gradleEclipseSourceFolder() throws Exception {
|
||||
doTest("my-module/bin/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unusualSourceFolder() throws Exception {
|
||||
doTest("my-module/something/quite/quite/mad/");
|
||||
}
|
||||
|
||||
private void doTest(String sourcePostfix) throws MalformedURLException {
|
||||
doTest(sourcePostfix, "my-module", true);
|
||||
doTest(sourcePostfix, "my-module-other", false);
|
||||
doTest(sourcePostfix, "my-module-other-again", false);
|
||||
doTest(sourcePostfix, "my-module.other", false);
|
||||
}
|
||||
|
||||
private void doTest(String sourcePostfix, String moduleRoot, boolean expected)
|
||||
throws MalformedURLException {
|
||||
String sourceFolder = SOURCE_ROOT + sourcePostfix;
|
||||
for (String postfix : COMMON_POSTFIXES) {
|
||||
for (URL url : getUrls(moduleRoot + postfix)) {
|
||||
boolean match = this.filter.isMatch(sourceFolder, url);
|
||||
assertThat(url + " against " + sourceFolder, match, equalTo(expected));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<URL> getUrls(String name) throws MalformedURLException {
|
||||
List<URL> urls = new ArrayList<URL>();
|
||||
urls.add(new URL("file:/some/path/" + name));
|
||||
urls.add(new URL("file:/some/path/" + name + "!/"));
|
||||
for (String postfix : COMMON_POSTFIXES) {
|
||||
urls.add(new URL("jar:file:/some/path/lib-module" + postfix + "!/lib/" + name));
|
||||
urls.add(new URL("jar:file:/some/path/lib-module" + postfix + "!/lib/" + name
|
||||
+ "!/"));
|
||||
}
|
||||
return urls;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.restart.server;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.boot.devtools.restart.server.HttpRestartServer;
|
||||
import org.springframework.boot.devtools.restart.server.HttpRestartServerHandler;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link HttpRestartServerHandler}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class HttpRestartServerHandlerTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void serverMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Server must not be null");
|
||||
new HttpRestartServerHandler(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleDelegatesToServer() throws Exception {
|
||||
HttpRestartServer server = mock(HttpRestartServer.class);
|
||||
HttpRestartServerHandler handler = new HttpRestartServerHandler(server);
|
||||
ServerHttpRequest request = mock(ServerHttpRequest.class);
|
||||
ServerHttpResponse response = mock(ServerHttpResponse.class);
|
||||
handler.handle(request, response);
|
||||
verify(server).handle(request, response);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.restart.server;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectOutputStream;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile;
|
||||
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFiles;
|
||||
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile.Kind;
|
||||
import org.springframework.boot.devtools.restart.server.HttpRestartServer;
|
||||
import org.springframework.boot.devtools.restart.server.RestartServer;
|
||||
import org.springframework.boot.devtools.restart.server.SourceFolderUrlFilter;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.http.server.ServletServerHttpResponse;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
|
||||
/**
|
||||
* Tests for {@link HttpRestartServer}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class HttpRestartServerTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Mock
|
||||
private RestartServer delegate;
|
||||
|
||||
private HttpRestartServer server;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<ClassLoaderFiles> filesCaptor;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
this.server = new HttpRestartServer(this.delegate);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sourceFolderUrlFilterMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("SourceFolderUrlFilter must not be null");
|
||||
new HttpRestartServer((SourceFolderUrlFilter) null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void restartServerMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("RestartServer must not be null");
|
||||
new HttpRestartServer((RestartServer) null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendClassLoaderFiles() throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
ClassLoaderFiles files = new ClassLoaderFiles();
|
||||
files.addFile("name", new ClassLoaderFile(Kind.ADDED, new byte[0]));
|
||||
byte[] bytes = serialize(files);
|
||||
request.setContent(bytes);
|
||||
this.server.handle(new ServletServerHttpRequest(request),
|
||||
new ServletServerHttpResponse(response));
|
||||
verify(this.delegate).updateAndRestart(this.filesCaptor.capture());
|
||||
assertThat(this.filesCaptor.getValue().getFile("name"), notNullValue());
|
||||
assertThat(response.getStatus(), equalTo(200));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendNoContent() throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
this.server.handle(new ServletServerHttpRequest(request),
|
||||
new ServletServerHttpResponse(response));
|
||||
verifyZeroInteractions(this.delegate);
|
||||
assertThat(response.getStatus(), equalTo(500));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendBadData() throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
request.setContent(new byte[] { 0, 0, 0 });
|
||||
this.server.handle(new ServletServerHttpRequest(request),
|
||||
new ServletServerHttpResponse(response));
|
||||
verifyZeroInteractions(this.delegate);
|
||||
assertThat(response.getStatus(), equalTo(500));
|
||||
}
|
||||
|
||||
private byte[] serialize(Object object) throws IOException {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
ObjectOutputStream oos = new ObjectOutputStream(bos);
|
||||
oos.writeObject(object);
|
||||
oos.close();
|
||||
return bos.toByteArray();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.restart.server;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile;
|
||||
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFiles;
|
||||
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile.Kind;
|
||||
import org.springframework.boot.devtools.restart.server.DefaultSourceFolderUrlFilter;
|
||||
import org.springframework.boot.devtools.restart.server.RestartServer;
|
||||
import org.springframework.boot.devtools.restart.server.SourceFolderUrlFilter;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link RestartServer}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class RestartServerTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temp = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void sourceFolderUrlFilterMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("SourceFolderUrlFilter must not be null");
|
||||
new RestartServer((SourceFolderUrlFilter) null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateAndRestart() throws Exception {
|
||||
URL url1 = new URL("file:/proj/module-a.jar!/");
|
||||
URL url2 = new URL("file:/proj/module-b.jar!/");
|
||||
URL url3 = new URL("file:/proj/module-c.jar!/");
|
||||
URL url4 = new URL("file:/proj/module-d.jar!/");
|
||||
URLClassLoader classLoaderA = new URLClassLoader(new URL[] { url1, url2 });
|
||||
URLClassLoader classLoaderB = new URLClassLoader(new URL[] { url3, url4 },
|
||||
classLoaderA);
|
||||
SourceFolderUrlFilter filter = new DefaultSourceFolderUrlFilter();
|
||||
MockRestartServer server = new MockRestartServer(filter, classLoaderB);
|
||||
ClassLoaderFiles files = new ClassLoaderFiles();
|
||||
ClassLoaderFile fileA = new ClassLoaderFile(Kind.ADDED, new byte[0]);
|
||||
ClassLoaderFile fileB = new ClassLoaderFile(Kind.ADDED, new byte[0]);
|
||||
files.addFile("my/module-a", "ClassA.class", fileA);
|
||||
files.addFile("my/module-c", "ClassB.class", fileB);
|
||||
server.updateAndRestart(files);
|
||||
Set<URL> expectedUrls = new LinkedHashSet<URL>(Arrays.asList(url1, url3));
|
||||
assertThat(server.restartUrls, equalTo(expectedUrls));
|
||||
assertThat(server.restartFiles, equalTo(files));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateSetsJarLastModified() throws Exception {
|
||||
long startTime = System.currentTimeMillis();
|
||||
File folder = this.temp.newFolder();
|
||||
File jarFile = new File(folder, "module-a.jar");
|
||||
new FileOutputStream(jarFile).close();
|
||||
jarFile.setLastModified(0);
|
||||
URL url = jarFile.toURI().toURL();
|
||||
URLClassLoader classLoader = new URLClassLoader(new URL[] { url });
|
||||
SourceFolderUrlFilter filter = new DefaultSourceFolderUrlFilter();
|
||||
MockRestartServer server = new MockRestartServer(filter, classLoader);
|
||||
ClassLoaderFiles files = new ClassLoaderFiles();
|
||||
ClassLoaderFile fileA = new ClassLoaderFile(Kind.ADDED, new byte[0]);
|
||||
files.addFile("my/module-a", "ClassA.class", fileA);
|
||||
server.updateAndRestart(files);
|
||||
assertThat(jarFile.lastModified(), greaterThan(startTime - 1000));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateReplacesLocalFilesWhenPossible() throws Exception {
|
||||
// This is critical for Cloud Foundry support where the application is
|
||||
// run exploded and resources can be found from the servlet root (outside of the
|
||||
// classloader)
|
||||
File folder = this.temp.newFolder();
|
||||
File classFile = new File(folder, "ClassA.class");
|
||||
FileCopyUtils.copy("abc".getBytes(), classFile);
|
||||
URL url = folder.toURI().toURL();
|
||||
URLClassLoader classLoader = new URLClassLoader(new URL[] { url });
|
||||
SourceFolderUrlFilter filter = new DefaultSourceFolderUrlFilter();
|
||||
MockRestartServer server = new MockRestartServer(filter, classLoader);
|
||||
ClassLoaderFiles files = new ClassLoaderFiles();
|
||||
ClassLoaderFile fileA = new ClassLoaderFile(Kind.ADDED, "def".getBytes());
|
||||
files.addFile("my/module-a", "ClassA.class", fileA);
|
||||
server.updateAndRestart(files);
|
||||
assertThat(FileCopyUtils.copyToByteArray(classFile), equalTo("def".getBytes()));
|
||||
}
|
||||
|
||||
private static class MockRestartServer extends RestartServer {
|
||||
|
||||
public MockRestartServer(SourceFolderUrlFilter sourceFolderUrlFilter,
|
||||
ClassLoader classLoader) {
|
||||
super(sourceFolderUrlFilter, classLoader);
|
||||
}
|
||||
|
||||
private Set<URL> restartUrls;
|
||||
|
||||
private ClassLoaderFiles restartFiles;
|
||||
|
||||
@Override
|
||||
protected void restart(Set<URL> urls, ClassLoaderFiles files) {
|
||||
this.restartUrls = urls;
|
||||
this.restartFiles = files;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.client.ClientHttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.mock.http.client.MockClientHttpRequest;
|
||||
import org.springframework.mock.http.client.MockClientHttpResponse;
|
||||
|
||||
/**
|
||||
* Mock {@link ClientHttpRequestFactory}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class MockClientHttpRequestFactory implements ClientHttpRequestFactory {
|
||||
|
||||
private AtomicLong seq = new AtomicLong();
|
||||
|
||||
private Deque<Response> responses = new ArrayDeque<Response>();
|
||||
|
||||
private List<MockClientHttpRequest> executedRequests = new ArrayList<MockClientHttpRequest>();
|
||||
|
||||
@Override
|
||||
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod)
|
||||
throws IOException {
|
||||
return new MockRequest(uri, httpMethod);
|
||||
}
|
||||
|
||||
public void willRespond(HttpStatus... response) {
|
||||
for (HttpStatus status : response) {
|
||||
this.responses.add(new Response(0, null, status));
|
||||
}
|
||||
}
|
||||
|
||||
public void willRespond(String... response) {
|
||||
for (String payload : response) {
|
||||
this.responses.add(new Response(0, payload.getBytes(), HttpStatus.OK));
|
||||
}
|
||||
}
|
||||
|
||||
public void willRespondAfterDelay(int delay, HttpStatus status) {
|
||||
this.responses.add(new Response(delay, null, status));
|
||||
}
|
||||
|
||||
public List<MockClientHttpRequest> getExecutedRequests() {
|
||||
return this.executedRequests;
|
||||
}
|
||||
|
||||
private class MockRequest extends MockClientHttpRequest {
|
||||
|
||||
public MockRequest(URI uri, HttpMethod httpMethod) {
|
||||
super(httpMethod, uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClientHttpResponse executeInternal() throws IOException {
|
||||
MockClientHttpRequestFactory.this.executedRequests.add(this);
|
||||
Response response = MockClientHttpRequestFactory.this.responses.pollFirst();
|
||||
if (response == null) {
|
||||
response = new Response(0, null, HttpStatus.GONE);
|
||||
}
|
||||
return response.asHttpResponse(MockClientHttpRequestFactory.this.seq);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class Response {
|
||||
|
||||
private final int delay;
|
||||
|
||||
private final byte[] payload;
|
||||
|
||||
private final HttpStatus status;
|
||||
|
||||
public Response(int delay, byte[] payload, HttpStatus status) {
|
||||
this.delay = delay;
|
||||
this.payload = payload;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public ClientHttpResponse asHttpResponse(AtomicLong seq) {
|
||||
MockClientHttpResponse httpResponse = new MockClientHttpResponse(
|
||||
this.payload, this.status);
|
||||
waitForDelay();
|
||||
if (this.payload != null) {
|
||||
httpResponse.getHeaders().setContentLength(this.payload.length);
|
||||
httpResponse.getHeaders().setContentType(
|
||||
MediaType.APPLICATION_OCTET_STREAM);
|
||||
httpResponse.getHeaders().add("x-seq",
|
||||
Long.toString(seq.incrementAndGet()));
|
||||
}
|
||||
return httpResponse;
|
||||
}
|
||||
|
||||
private void waitForDelay() {
|
||||
if (this.delay > 0) {
|
||||
try {
|
||||
Thread.sleep(this.delay);
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.tunnel.client;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.springframework.boot.devtools.test.MockClientHttpRequestFactory;
|
||||
import org.springframework.boot.devtools.tunnel.client.HttpTunnelConnection;
|
||||
import org.springframework.boot.devtools.tunnel.client.HttpTunnelConnection.TunnelChannel;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.util.SocketUtils;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link HttpTunnelConnection}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class HttpTunnelConnectionTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private int port = SocketUtils.findAvailableTcpPort();
|
||||
|
||||
private String url;
|
||||
|
||||
private ByteArrayOutputStream incommingData;
|
||||
|
||||
private WritableByteChannel incomingChannel;
|
||||
|
||||
@Mock
|
||||
private Closeable closeable;
|
||||
|
||||
private MockClientHttpRequestFactory requestFactory = new MockClientHttpRequestFactory();
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
this.url = "http://localhost:" + this.port;
|
||||
this.incommingData = new ByteArrayOutputStream();
|
||||
this.incomingChannel = Channels.newChannel(this.incommingData);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void urlMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("URL must not be empty");
|
||||
new HttpTunnelConnection(null, this.requestFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void urlMustNotBeEmpty() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("URL must not be empty");
|
||||
new HttpTunnelConnection("", this.requestFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void urlMustNotBeMalformed() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Malformed URL 'htttttp:///ttest'");
|
||||
new HttpTunnelConnection("htttttp:///ttest", this.requestFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestFactoryMustNotBeNull() {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("RequestFactory must not be null");
|
||||
new HttpTunnelConnection(this.url, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void closeTunnelChangesIsOpen() throws Exception {
|
||||
this.requestFactory.willRespondAfterDelay(1000, HttpStatus.GONE);
|
||||
WritableByteChannel channel = openTunnel(false);
|
||||
assertThat(channel.isOpen(), equalTo(true));
|
||||
channel.close();
|
||||
assertThat(channel.isOpen(), equalTo(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void closeTunnelCallsCloseableOnce() throws Exception {
|
||||
this.requestFactory.willRespondAfterDelay(1000, HttpStatus.GONE);
|
||||
WritableByteChannel channel = openTunnel(false);
|
||||
verify(this.closeable, never()).close();
|
||||
channel.close();
|
||||
channel.close();
|
||||
verify(this.closeable, times(1)).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void typicalTraffic() throws Exception {
|
||||
this.requestFactory.willRespond("hi", "=2", "=3");
|
||||
TunnelChannel channel = openTunnel(true);
|
||||
write(channel, "hello");
|
||||
write(channel, "1+1");
|
||||
write(channel, "1+2");
|
||||
assertThat(this.incommingData.toString(), equalTo("hi=2=3"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void trafficWithLongPollTimeouts() throws Exception {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
this.requestFactory.willRespond(HttpStatus.NO_CONTENT);
|
||||
}
|
||||
this.requestFactory.willRespond("hi");
|
||||
TunnelChannel channel = openTunnel(true);
|
||||
write(channel, "hello");
|
||||
assertThat(this.incommingData.toString(), equalTo("hi"));
|
||||
assertThat(this.requestFactory.getExecutedRequests().size(), greaterThan(10));
|
||||
}
|
||||
|
||||
private void write(TunnelChannel channel, String string) throws IOException {
|
||||
channel.write(ByteBuffer.wrap(string.getBytes()));
|
||||
}
|
||||
|
||||
private TunnelChannel openTunnel(boolean singleThreaded) throws Exception {
|
||||
HttpTunnelConnection connection = new HttpTunnelConnection(this.url,
|
||||
this.requestFactory,
|
||||
(singleThreaded ? new CurrentThreadExecutor() : null));
|
||||
return connection.open(this.incomingChannel, this.closeable);
|
||||
}
|
||||
|
||||
private static class CurrentThreadExecutor implements Executor {
|
||||
|
||||
@Override
|
||||
public void execute(Runnable command) {
|
||||
command.run();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.tunnel.client;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.boot.devtools.tunnel.client.TunnelClient;
|
||||
import org.springframework.boot.devtools.tunnel.client.TunnelClientListener;
|
||||
import org.springframework.boot.devtools.tunnel.client.TunnelConnection;
|
||||
import org.springframework.util.SocketUtils;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link TunnelClient}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class TunnelClientTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private int listenPort = SocketUtils.findAvailableTcpPort();
|
||||
|
||||
private MockTunnelConnection tunnelConnection = new MockTunnelConnection();
|
||||
|
||||
@Test
|
||||
public void listenPortMustBePositive() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("ListenPort must be positive");
|
||||
new TunnelClient(0, this.tunnelConnection);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tunnelConnectionMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("TunnelConnection must not be null");
|
||||
new TunnelClient(1, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void typicalTraffic() throws Exception {
|
||||
TunnelClient client = new TunnelClient(this.listenPort, this.tunnelConnection);
|
||||
client.start();
|
||||
SocketChannel channel = SocketChannel
|
||||
.open(new InetSocketAddress(this.listenPort));
|
||||
channel.write(ByteBuffer.wrap("hello".getBytes()));
|
||||
ByteBuffer buffer = ByteBuffer.allocate(5);
|
||||
channel.read(buffer);
|
||||
channel.close();
|
||||
this.tunnelConnection.verifyWritten("hello");
|
||||
assertThat(new String(buffer.array()), equalTo("olleh"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void socketChannelClosedTriggersTunnelClose() throws Exception {
|
||||
TunnelClient client = new TunnelClient(this.listenPort, this.tunnelConnection);
|
||||
client.start();
|
||||
SocketChannel channel = SocketChannel
|
||||
.open(new InetSocketAddress(this.listenPort));
|
||||
Thread.sleep(200);
|
||||
channel.close();
|
||||
client.getServerThread().stopAcceptingConnections();
|
||||
client.getServerThread().join(2000);
|
||||
assertThat(this.tunnelConnection.getOpenedTimes(), equalTo(1));
|
||||
assertThat(this.tunnelConnection.isOpen(), equalTo(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stopTriggersTunnelClose() throws Exception {
|
||||
TunnelClient client = new TunnelClient(this.listenPort, this.tunnelConnection);
|
||||
client.start();
|
||||
SocketChannel channel = SocketChannel
|
||||
.open(new InetSocketAddress(this.listenPort));
|
||||
Thread.sleep(200);
|
||||
client.stop();
|
||||
assertThat(this.tunnelConnection.getOpenedTimes(), equalTo(1));
|
||||
assertThat(this.tunnelConnection.isOpen(), equalTo(false));
|
||||
assertThat(channel.read(ByteBuffer.allocate(1)), equalTo(-1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addListener() throws Exception {
|
||||
TunnelClient client = new TunnelClient(this.listenPort, this.tunnelConnection);
|
||||
TunnelClientListener listener = mock(TunnelClientListener.class);
|
||||
client.addListener(listener);
|
||||
client.start();
|
||||
SocketChannel channel = SocketChannel
|
||||
.open(new InetSocketAddress(this.listenPort));
|
||||
Thread.sleep(200);
|
||||
channel.close();
|
||||
client.getServerThread().stopAcceptingConnections();
|
||||
client.getServerThread().join(2000);
|
||||
verify(listener).onOpen(any(SocketChannel.class));
|
||||
verify(listener).onClose(any(SocketChannel.class));
|
||||
}
|
||||
|
||||
private static class MockTunnelConnection implements TunnelConnection {
|
||||
|
||||
private final ByteArrayOutputStream written = new ByteArrayOutputStream();
|
||||
|
||||
private boolean open;
|
||||
|
||||
private int openedTimes;
|
||||
|
||||
@Override
|
||||
public WritableByteChannel open(WritableByteChannel incomingChannel,
|
||||
Closeable closeable) throws Exception {
|
||||
this.openedTimes++;
|
||||
this.open = true;
|
||||
return new TunnelChannel(incomingChannel, closeable);
|
||||
}
|
||||
|
||||
public void verifyWritten(String expected) {
|
||||
verifyWritten(expected.getBytes());
|
||||
}
|
||||
|
||||
public void verifyWritten(byte[] expected) {
|
||||
synchronized (this.written) {
|
||||
assertThat(this.written.toByteArray(), equalTo(expected));
|
||||
this.written.reset();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isOpen() {
|
||||
return this.open;
|
||||
}
|
||||
|
||||
public int getOpenedTimes() {
|
||||
return this.openedTimes;
|
||||
}
|
||||
|
||||
private class TunnelChannel implements WritableByteChannel {
|
||||
|
||||
private final WritableByteChannel incomingChannel;
|
||||
|
||||
private final Closeable closeable;
|
||||
|
||||
public TunnelChannel(WritableByteChannel incomingChannel, Closeable closeable) {
|
||||
this.incomingChannel = incomingChannel;
|
||||
this.closeable = closeable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return MockTunnelConnection.this.open;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
MockTunnelConnection.this.open = false;
|
||||
this.closeable.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int write(ByteBuffer src) throws IOException {
|
||||
int remaining = src.remaining();
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
Channels.newChannel(stream).write(src);
|
||||
byte[] bytes = stream.toByteArray();
|
||||
synchronized (MockTunnelConnection.this.written) {
|
||||
MockTunnelConnection.this.written.write(bytes);
|
||||
}
|
||||
byte[] reversed = new byte[bytes.length];
|
||||
for (int i = 0; i < reversed.length; i++) {
|
||||
reversed[i] = bytes[bytes.length - 1 - i];
|
||||
}
|
||||
this.incomingChannel.write(ByteBuffer.wrap(reversed));
|
||||
return remaining;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.tunnel.payload;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.boot.devtools.tunnel.payload.HttpTunnelPayload;
|
||||
import org.springframework.boot.devtools.tunnel.payload.HttpTunnelPayloadForwarder;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link HttpTunnelPayloadForwarder}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class HttpTunnelPayloadForwarderTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void targetChannelMustNoBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("TargetChannel must not be null");
|
||||
new HttpTunnelPayloadForwarder(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forwardInSequence() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
WritableByteChannel channel = Channels.newChannel(out);
|
||||
HttpTunnelPayloadForwarder forwarder = new HttpTunnelPayloadForwarder(channel);
|
||||
forwarder.forward(payload(1, "he"));
|
||||
forwarder.forward(payload(2, "ll"));
|
||||
forwarder.forward(payload(3, "o"));
|
||||
assertThat(out.toByteArray(), equalTo("hello".getBytes()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forwardOutOfSequence() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
WritableByteChannel channel = Channels.newChannel(out);
|
||||
HttpTunnelPayloadForwarder forwarder = new HttpTunnelPayloadForwarder(channel);
|
||||
forwarder.forward(payload(3, "o"));
|
||||
forwarder.forward(payload(2, "ll"));
|
||||
forwarder.forward(payload(1, "he"));
|
||||
assertThat(out.toByteArray(), equalTo("hello".getBytes()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void overflow() throws Exception {
|
||||
WritableByteChannel channel = Channels.newChannel(new ByteArrayOutputStream());
|
||||
HttpTunnelPayloadForwarder forwarder = new HttpTunnelPayloadForwarder(channel);
|
||||
this.thrown.expect(IllegalStateException.class);
|
||||
this.thrown.expectMessage("Too many messages queued");
|
||||
for (int i = 2; i < 130; i++) {
|
||||
forwarder.forward(payload(i, "data" + i));
|
||||
}
|
||||
}
|
||||
|
||||
private HttpTunnelPayload payload(long sequence, String data) {
|
||||
return new HttpTunnelPayload(sequence, ByteBuffer.wrap(data.getBytes()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.tunnel.payload;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.boot.devtools.tunnel.payload.HttpTunnelPayload;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.http.server.ServletServerHttpResponse;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link HttpTunnelPayload}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class HttpTunnelPayloadTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void sequenceMustBePositive() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Sequence must be positive");
|
||||
new HttpTunnelPayload(0, ByteBuffer.allocate(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dataMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Data must not be null");
|
||||
new HttpTunnelPayload(1, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSequence() throws Exception {
|
||||
HttpTunnelPayload payload = new HttpTunnelPayload(1, ByteBuffer.allocate(1));
|
||||
assertThat(payload.getSequence(), equalTo(1L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getData() throws Exception {
|
||||
ByteBuffer data = ByteBuffer.wrap("hello".getBytes());
|
||||
HttpTunnelPayload payload = new HttpTunnelPayload(1, data);
|
||||
assertThat(getData(payload), equalTo(data.array()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assignTo() throws Exception {
|
||||
ByteBuffer data = ByteBuffer.wrap("hello".getBytes());
|
||||
HttpTunnelPayload payload = new HttpTunnelPayload(2, data);
|
||||
MockHttpServletResponse servletResponse = new MockHttpServletResponse();
|
||||
HttpOutputMessage response = new ServletServerHttpResponse(servletResponse);
|
||||
payload.assignTo(response);
|
||||
assertThat(servletResponse.getHeader("x-seq"), equalTo("2"));
|
||||
assertThat(servletResponse.getContentAsString(), equalTo("hello"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getNoData() throws Exception {
|
||||
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
|
||||
HttpInputMessage request = new ServletServerHttpRequest(servletRequest);
|
||||
HttpTunnelPayload payload = HttpTunnelPayload.get(request);
|
||||
assertThat(payload, nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWithMissingHeader() throws Exception {
|
||||
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
|
||||
servletRequest.setContent("hello".getBytes());
|
||||
HttpInputMessage request = new ServletServerHttpRequest(servletRequest);
|
||||
this.thrown.expect(IllegalStateException.class);
|
||||
this.thrown.expectMessage("Missing sequence header");
|
||||
HttpTunnelPayload.get(request);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWithData() throws Exception {
|
||||
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
|
||||
servletRequest.setContent("hello".getBytes());
|
||||
servletRequest.addHeader("x-seq", 123);
|
||||
HttpInputMessage request = new ServletServerHttpRequest(servletRequest);
|
||||
HttpTunnelPayload payload = HttpTunnelPayload.get(request);
|
||||
assertThat(payload.getSequence(), equalTo(123L));
|
||||
assertThat(getData(payload), equalTo("hello".getBytes()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPayloadData() throws Exception {
|
||||
ReadableByteChannel channel = Channels.newChannel(new ByteArrayInputStream(
|
||||
"hello".getBytes()));
|
||||
ByteBuffer payloadData = HttpTunnelPayload.getPayloadData(channel);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
WritableByteChannel writeChannel = Channels.newChannel(out);
|
||||
while (payloadData.hasRemaining()) {
|
||||
writeChannel.write(payloadData);
|
||||
}
|
||||
assertThat(out.toByteArray(), equalTo("hello".getBytes()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPayloadDataWithTimeout() throws Exception {
|
||||
ReadableByteChannel channel = mock(ReadableByteChannel.class);
|
||||
given(channel.read(any(ByteBuffer.class)))
|
||||
.willThrow(new SocketTimeoutException());
|
||||
ByteBuffer payload = HttpTunnelPayload.getPayloadData(channel);
|
||||
assertThat(payload, nullValue());
|
||||
}
|
||||
|
||||
private byte[] getData(HttpTunnelPayload payload) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
WritableByteChannel channel = Channels.newChannel(out);
|
||||
payload.writeTo(channel);
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.tunnel.server;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.boot.devtools.tunnel.server.HttpTunnelServer;
|
||||
import org.springframework.boot.devtools.tunnel.server.HttpTunnelServerHandler;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link HttpTunnelServerHandler}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class HttpTunnelServerHandlerTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void serverMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Server must not be null");
|
||||
new HttpTunnelServerHandler(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleDelegatesToServer() throws Exception {
|
||||
HttpTunnelServer server = mock(HttpTunnelServer.class);
|
||||
HttpTunnelServerHandler handler = new HttpTunnelServerHandler(server);
|
||||
ServerHttpRequest request = mock(ServerHttpRequest.class);
|
||||
ServerHttpResponse response = mock(ServerHttpResponse.class);
|
||||
handler.handle(request, response);
|
||||
verify(server).handle(request, response);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,482 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.tunnel.server;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ByteChannel;
|
||||
import java.nio.channels.Channels;
|
||||
import java.util.concurrent.BlockingDeque;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.springframework.boot.devtools.tunnel.payload.HttpTunnelPayload;
|
||||
import org.springframework.boot.devtools.tunnel.server.HttpTunnelServer;
|
||||
import org.springframework.boot.devtools.tunnel.server.TargetServerConnection;
|
||||
import org.springframework.boot.devtools.tunnel.server.HttpTunnelServer.HttpConnection;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.server.ServerHttpAsyncRequestControl;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.http.server.ServletServerHttpResponse;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link HttpTunnelServer}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class HttpTunnelServerTests {
|
||||
|
||||
private static final int DEFAULT_LONG_POLL_TIMEOUT = 10000;
|
||||
|
||||
private static final byte[] NO_DATA = {};
|
||||
|
||||
private static final String SEQ_HEADER = "x-seq";
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private HttpTunnelServer server;
|
||||
|
||||
@Mock
|
||||
private TargetServerConnection serverConnection;
|
||||
|
||||
private MockHttpServletRequest servletRequest;
|
||||
|
||||
private MockHttpServletResponse servletResponse;
|
||||
|
||||
private ServerHttpRequest request;
|
||||
|
||||
private ServerHttpResponse response;
|
||||
|
||||
private MockServerChannel serverChannel;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
this.server = new HttpTunnelServer(this.serverConnection);
|
||||
given(this.serverConnection.open(anyInt())).willAnswer(new Answer<ByteChannel>() {
|
||||
@Override
|
||||
public ByteChannel answer(InvocationOnMock invocation) throws Throwable {
|
||||
MockServerChannel channel = HttpTunnelServerTests.this.serverChannel;
|
||||
channel.setTimeout((Integer) invocation.getArguments()[0]);
|
||||
return channel;
|
||||
}
|
||||
});
|
||||
this.servletRequest = new MockHttpServletRequest();
|
||||
this.servletRequest.setAsyncSupported(true);
|
||||
this.servletResponse = new MockHttpServletResponse();
|
||||
this.request = new ServletServerHttpRequest(this.servletRequest);
|
||||
this.response = new ServletServerHttpResponse(this.servletResponse);
|
||||
this.serverChannel = new MockServerChannel();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serverConnectionIsRequired() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("ServerConnection must not be null");
|
||||
new HttpTunnelServer(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serverConnectedOnFirstRequest() throws Exception {
|
||||
verify(this.serverConnection, never()).open(anyInt());
|
||||
this.server.handle(this.request, this.response);
|
||||
verify(this.serverConnection, times(1)).open(DEFAULT_LONG_POLL_TIMEOUT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void longPollTimeout() throws Exception {
|
||||
this.server.setLongPollTimeout(800);
|
||||
this.server.handle(this.request, this.response);
|
||||
verify(this.serverConnection, times(1)).open(800);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void longPollTimeoutMustBePositiveValue() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("LongPollTimeout must be a positive value");
|
||||
this.server.setLongPollTimeout(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initialRequestIsSentToServer() throws Exception {
|
||||
this.servletRequest.addHeader(SEQ_HEADER, "1");
|
||||
this.servletRequest.setContent("hello".getBytes());
|
||||
this.server.handle(this.request, this.response);
|
||||
this.serverChannel.disconnect();
|
||||
this.server.getServerThread().join();
|
||||
this.serverChannel.verifyReceived("hello");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void intialRequestIsUsedForFirstServerResponse() throws Exception {
|
||||
this.servletRequest.addHeader(SEQ_HEADER, "1");
|
||||
this.servletRequest.setContent("hello".getBytes());
|
||||
this.server.handle(this.request, this.response);
|
||||
System.out.println("sending");
|
||||
this.serverChannel.send("hello");
|
||||
this.serverChannel.disconnect();
|
||||
this.server.getServerThread().join();
|
||||
assertThat(this.servletResponse.getContentAsString(), equalTo("hello"));
|
||||
this.serverChannel.verifyReceived("hello");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initialRequestHasNoPayload() throws Exception {
|
||||
this.server.handle(this.request, this.response);
|
||||
this.serverChannel.disconnect();
|
||||
this.server.getServerThread().join();
|
||||
this.serverChannel.verifyReceived(NO_DATA);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void typicalReqestResponseTraffic() throws Exception {
|
||||
MockHttpConnection h1 = new MockHttpConnection();
|
||||
this.server.handle(h1);
|
||||
MockHttpConnection h2 = new MockHttpConnection("hello server", 1);
|
||||
this.server.handle(h2);
|
||||
this.serverChannel.verifyReceived("hello server");
|
||||
this.serverChannel.send("hello client");
|
||||
h1.verifyReceived("hello client", 1);
|
||||
MockHttpConnection h3 = new MockHttpConnection("1+1", 2);
|
||||
this.server.handle(h3);
|
||||
this.serverChannel.send("=2");
|
||||
h2.verifyReceived("=2", 2);
|
||||
MockHttpConnection h4 = new MockHttpConnection("1+2", 3);
|
||||
this.server.handle(h4);
|
||||
this.serverChannel.send("=3");
|
||||
h3.verifyReceived("=3", 3);
|
||||
this.serverChannel.disconnect();
|
||||
this.server.getServerThread().join();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clientIsAwareOfServerClose() throws Exception {
|
||||
MockHttpConnection h1 = new MockHttpConnection("1", 1);
|
||||
this.server.handle(h1);
|
||||
this.serverChannel.disconnect();
|
||||
this.server.getServerThread().join();
|
||||
assertThat(h1.getServletResponse().getStatus(), equalTo(410));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clientCanCloseServer() throws Exception {
|
||||
MockHttpConnection h1 = new MockHttpConnection();
|
||||
this.server.handle(h1);
|
||||
MockHttpConnection h2 = new MockHttpConnection("DISCONNECT", 1);
|
||||
h2.getServletRequest().addHeader("Content-Type", "application/x-disconnect");
|
||||
this.server.handle(h2);
|
||||
this.server.getServerThread().join();
|
||||
assertThat(h1.getServletResponse().getStatus(), equalTo(410));
|
||||
assertThat(this.serverChannel.isOpen(), equalTo(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void neverMoreThanTwoHttpConnections() throws Exception {
|
||||
MockHttpConnection h1 = new MockHttpConnection();
|
||||
this.server.handle(h1);
|
||||
MockHttpConnection h2 = new MockHttpConnection("1", 2);
|
||||
this.server.handle(h2);
|
||||
MockHttpConnection h3 = new MockHttpConnection("2", 3);
|
||||
this.server.handle(h3);
|
||||
h1.waitForResponse();
|
||||
assertThat(h1.getServletResponse().getStatus(), equalTo(429));
|
||||
this.serverChannel.disconnect();
|
||||
this.server.getServerThread().join();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestRecievedOutOfOrder() throws Exception {
|
||||
MockHttpConnection h1 = new MockHttpConnection();
|
||||
MockHttpConnection h2 = new MockHttpConnection("1+2", 1);
|
||||
MockHttpConnection h3 = new MockHttpConnection("+3", 2);
|
||||
this.server.handle(h1);
|
||||
this.server.handle(h3);
|
||||
this.server.handle(h2);
|
||||
this.serverChannel.verifyReceived("1+2+3");
|
||||
this.serverChannel.disconnect();
|
||||
this.server.getServerThread().join();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void httpConnectionsAreClosedAfterLongPollTimeout() throws Exception {
|
||||
this.server.setDisconnectTimeout(1000);
|
||||
this.server.setLongPollTimeout(100);
|
||||
MockHttpConnection h1 = new MockHttpConnection();
|
||||
this.server.handle(h1);
|
||||
MockHttpConnection h2 = new MockHttpConnection();
|
||||
this.server.handle(h2);
|
||||
Thread.sleep(400);
|
||||
this.serverChannel.disconnect();
|
||||
this.server.getServerThread().join();
|
||||
assertThat(h1.getServletResponse().getStatus(), equalTo(204));
|
||||
assertThat(h2.getServletResponse().getStatus(), equalTo(204));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disconnectTimeout() throws Exception {
|
||||
this.server.setDisconnectTimeout(100);
|
||||
this.server.setLongPollTimeout(100);
|
||||
MockHttpConnection h1 = new MockHttpConnection();
|
||||
this.server.handle(h1);
|
||||
this.serverChannel.send("hello");
|
||||
this.server.getServerThread().join();
|
||||
assertThat(this.serverChannel.isOpen(), equalTo(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disconnectTimeoutMustBePositive() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("DisconnectTimeout must be a positive value");
|
||||
this.server.setDisconnectTimeout(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void httpConnectionRespondWithPayload() throws Exception {
|
||||
HttpConnection connection = new HttpConnection(this.request, this.response);
|
||||
connection.waitForResponse();
|
||||
connection.respond(new HttpTunnelPayload(1, ByteBuffer.wrap("hello".getBytes())));
|
||||
assertThat(this.servletResponse.getStatus(), equalTo(200));
|
||||
assertThat(this.servletResponse.getContentAsString(), equalTo("hello"));
|
||||
assertThat(this.servletResponse.getHeader(SEQ_HEADER), equalTo("1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void httpConnectionRespondWithStatus() throws Exception {
|
||||
HttpConnection connection = new HttpConnection(this.request, this.response);
|
||||
connection.waitForResponse();
|
||||
connection.respond(HttpStatus.I_AM_A_TEAPOT);
|
||||
assertThat(this.servletResponse.getStatus(), equalTo(418));
|
||||
assertThat(this.servletResponse.getContentLength(), equalTo(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void httpConnectionAsync() throws Exception {
|
||||
ServerHttpAsyncRequestControl async = mock(ServerHttpAsyncRequestControl.class);
|
||||
ServerHttpRequest request = mock(ServerHttpRequest.class);
|
||||
given(request.getAsyncRequestControl(this.response)).willReturn(async);
|
||||
HttpConnection connection = new HttpConnection(request, this.response);
|
||||
connection.waitForResponse();
|
||||
verify(async).start();
|
||||
connection.respond(HttpStatus.NO_CONTENT);
|
||||
verify(async).complete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void httpConnectionNonAsync() throws Exception {
|
||||
testHttpConnectionNonAsync(0);
|
||||
testHttpConnectionNonAsync(100);
|
||||
}
|
||||
|
||||
private void testHttpConnectionNonAsync(long sleepBeforeResponse) throws IOException,
|
||||
InterruptedException {
|
||||
ServerHttpRequest request = mock(ServerHttpRequest.class);
|
||||
given(request.getAsyncRequestControl(this.response)).willThrow(
|
||||
new IllegalArgumentException());
|
||||
final HttpConnection connection = new HttpConnection(request, this.response);
|
||||
final AtomicBoolean responded = new AtomicBoolean();
|
||||
Thread connectionThread = new Thread() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
connection.waitForResponse();
|
||||
responded.set(true);
|
||||
}
|
||||
|
||||
};
|
||||
connectionThread.start();
|
||||
assertThat(responded.get(), equalTo(false));
|
||||
Thread.sleep(sleepBeforeResponse);
|
||||
connection.respond(HttpStatus.NO_CONTENT);
|
||||
connectionThread.join();
|
||||
assertThat(responded.get(), equalTo(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void httpConnectionRunning() throws Exception {
|
||||
HttpConnection connection = new HttpConnection(this.request, this.response);
|
||||
assertThat(connection.isOlderThan(100), equalTo(false));
|
||||
Thread.sleep(200);
|
||||
assertThat(connection.isOlderThan(100), equalTo(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock {@link ByteChannel} used to simulate the server connection.
|
||||
*/
|
||||
private static class MockServerChannel implements ByteChannel {
|
||||
|
||||
private static final ByteBuffer DISCONNECT = ByteBuffer.wrap(NO_DATA);
|
||||
|
||||
private int timeout;
|
||||
|
||||
private BlockingDeque<ByteBuffer> outgoing = new LinkedBlockingDeque<ByteBuffer>();
|
||||
|
||||
private ByteArrayOutputStream written = new ByteArrayOutputStream();
|
||||
|
||||
private AtomicBoolean open = new AtomicBoolean(true);
|
||||
|
||||
public void setTimeout(int timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
public void send(String content) {
|
||||
send(content.getBytes());
|
||||
}
|
||||
|
||||
public void send(byte[] bytes) {
|
||||
this.outgoing.addLast(ByteBuffer.wrap(bytes));
|
||||
}
|
||||
|
||||
public void disconnect() {
|
||||
this.outgoing.addLast(DISCONNECT);
|
||||
}
|
||||
|
||||
public void verifyReceived(String expected) {
|
||||
verifyReceived(expected.getBytes());
|
||||
}
|
||||
|
||||
public void verifyReceived(byte[] expected) {
|
||||
synchronized (this.written) {
|
||||
assertThat(this.written.toByteArray(), equalTo(expected));
|
||||
this.written.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(ByteBuffer dst) throws IOException {
|
||||
try {
|
||||
ByteBuffer bytes = this.outgoing.pollFirst(this.timeout,
|
||||
TimeUnit.MILLISECONDS);
|
||||
if (bytes == null) {
|
||||
throw new SocketTimeoutException();
|
||||
}
|
||||
if (bytes == DISCONNECT) {
|
||||
this.open.set(false);
|
||||
return -1;
|
||||
}
|
||||
int initialRemaining = dst.remaining();
|
||||
bytes.limit(Math.min(bytes.limit(), initialRemaining));
|
||||
dst.put(bytes);
|
||||
bytes.limit(bytes.capacity());
|
||||
return initialRemaining - dst.remaining();
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int write(ByteBuffer src) throws IOException {
|
||||
int remaining = src.remaining();
|
||||
synchronized (this.written) {
|
||||
Channels.newChannel(this.written).write(src);
|
||||
}
|
||||
return remaining;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return this.open.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
this.open.set(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock {@link HttpConnection}.
|
||||
*/
|
||||
private static class MockHttpConnection extends HttpConnection {
|
||||
|
||||
public MockHttpConnection() {
|
||||
super(new ServletServerHttpRequest(new MockHttpServletRequest()),
|
||||
new ServletServerHttpResponse(new MockHttpServletResponse()));
|
||||
}
|
||||
|
||||
public MockHttpConnection(String content, int seq) {
|
||||
this();
|
||||
MockHttpServletRequest request = getServletRequest();
|
||||
request.setContent(content.getBytes());
|
||||
request.addHeader(SEQ_HEADER, String.valueOf(seq));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ServerHttpAsyncRequestControl startAsync() {
|
||||
getServletRequest().setAsyncSupported(true);
|
||||
return super.startAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void complete() {
|
||||
super.complete();
|
||||
getServletResponse().setCommitted(true);
|
||||
}
|
||||
|
||||
public MockHttpServletRequest getServletRequest() {
|
||||
return (MockHttpServletRequest) ((ServletServerHttpRequest) getRequest())
|
||||
.getServletRequest();
|
||||
}
|
||||
|
||||
public MockHttpServletResponse getServletResponse() {
|
||||
return (MockHttpServletResponse) ((ServletServerHttpResponse) getResponse())
|
||||
.getServletResponse();
|
||||
}
|
||||
|
||||
public void verifyReceived(String expectedContent, int expectedSeq)
|
||||
throws Exception {
|
||||
waitForServletResponse();
|
||||
MockHttpServletResponse resp = getServletResponse();
|
||||
assertThat(resp.getContentAsString(), equalTo(expectedContent));
|
||||
assertThat(resp.getHeader(SEQ_HEADER), equalTo(String.valueOf(expectedSeq)));
|
||||
}
|
||||
|
||||
public void waitForServletResponse() throws InterruptedException {
|
||||
while (!getServletResponse().isCommitted()) {
|
||||
Thread.sleep(10);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.tunnel.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ByteChannel;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.devtools.tunnel.server.SocketTargetServerConnection;
|
||||
import org.springframework.boot.devtools.tunnel.server.StaticPortProvider;
|
||||
import org.springframework.util.SocketUtils;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
||||
import static org.hamcrest.Matchers.lessThan;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* Tests for {@link SocketTargetServerConnection}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class SocketTargetServerConnectionTests {
|
||||
|
||||
private static final int DEFAULT_TIMEOUT = 1000;
|
||||
|
||||
private int port;
|
||||
|
||||
private MockServer server;
|
||||
|
||||
private SocketTargetServerConnection connection;
|
||||
|
||||
@Before
|
||||
public void setup() throws IOException {
|
||||
this.port = SocketUtils.findAvailableTcpPort();
|
||||
this.server = new MockServer(this.port);
|
||||
StaticPortProvider portProvider = new StaticPortProvider(this.port);
|
||||
this.connection = new SocketTargetServerConnection(portProvider);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readData() throws Exception {
|
||||
this.server.willSend("hello".getBytes());
|
||||
this.server.start();
|
||||
ByteChannel channel = this.connection.open(DEFAULT_TIMEOUT);
|
||||
ByteBuffer buffer = ByteBuffer.allocate(5);
|
||||
channel.read(buffer);
|
||||
assertThat(buffer.array(), equalTo("hello".getBytes()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeData() throws Exception {
|
||||
this.server.expect("hello".getBytes());
|
||||
this.server.start();
|
||||
ByteChannel channel = this.connection.open(DEFAULT_TIMEOUT);
|
||||
ByteBuffer buffer = ByteBuffer.wrap("hello".getBytes());
|
||||
channel.write(buffer);
|
||||
this.server.closeAndVerify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void timeout() throws Exception {
|
||||
this.server.delay(1000);
|
||||
this.server.start();
|
||||
ByteChannel channel = this.connection.open(10);
|
||||
long startTime = System.currentTimeMillis();
|
||||
try {
|
||||
channel.read(ByteBuffer.allocate(5));
|
||||
fail("No socket timeout thrown");
|
||||
}
|
||||
catch (SocketTimeoutException ex) {
|
||||
// Expected
|
||||
long runTime = System.currentTimeMillis() - startTime;
|
||||
assertThat(runTime, greaterThanOrEqualTo(10L));
|
||||
assertThat(runTime, lessThan(10000L));
|
||||
}
|
||||
}
|
||||
|
||||
private static class MockServer {
|
||||
|
||||
private ServerSocketChannel serverSocket;
|
||||
|
||||
private byte[] send;
|
||||
|
||||
private byte[] expect;
|
||||
|
||||
private int delay;
|
||||
|
||||
private ByteBuffer actualRead;
|
||||
|
||||
private ServerThread thread;
|
||||
|
||||
public MockServer(int port) throws IOException {
|
||||
this.serverSocket = ServerSocketChannel.open();
|
||||
this.serverSocket.bind(new InetSocketAddress(port));
|
||||
}
|
||||
|
||||
public void delay(int delay) {
|
||||
this.delay = delay;
|
||||
}
|
||||
|
||||
public void willSend(byte[] send) {
|
||||
this.send = send;
|
||||
}
|
||||
|
||||
public void expect(byte[] expect) {
|
||||
this.expect = expect;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
this.thread = new ServerThread();
|
||||
this.thread.start();
|
||||
}
|
||||
|
||||
public void closeAndVerify() throws InterruptedException {
|
||||
close();
|
||||
assertThat(this.actualRead.array(), equalTo(this.expect));
|
||||
}
|
||||
|
||||
public void close() throws InterruptedException {
|
||||
while (this.thread.isAlive()) {
|
||||
Thread.sleep(10);
|
||||
}
|
||||
}
|
||||
|
||||
private class ServerThread extends Thread {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
SocketChannel channel = MockServer.this.serverSocket.accept();
|
||||
Thread.sleep(MockServer.this.delay);
|
||||
if (MockServer.this.send != null) {
|
||||
ByteBuffer buffer = ByteBuffer.wrap(MockServer.this.send);
|
||||
while (buffer.hasRemaining()) {
|
||||
channel.write(buffer);
|
||||
}
|
||||
}
|
||||
if (MockServer.this.expect != null) {
|
||||
ByteBuffer buffer = ByteBuffer
|
||||
.allocate(MockServer.this.expect.length);
|
||||
while (buffer.hasRemaining()) {
|
||||
channel.read(buffer);
|
||||
}
|
||||
MockServer.this.actualRead = buffer;
|
||||
}
|
||||
channel.close();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
fail();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.tunnel.server;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.boot.devtools.tunnel.server.StaticPortProvider;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link StaticPortProvider}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class StaticPortProviderTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void portMustBePostive() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Port must be positive");
|
||||
new StaticPortProvider(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPort() throws Exception {
|
||||
StaticPortProvider provider = new StaticPortProvider(123);
|
||||
assertThat(provider.getPort(), equalTo(123));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
fromparent
|
||||
Reference in New Issue
Block a user