DevTools should only shut down single, auto-configured DataSource
Previously, there were two problems with DevTools’ DataSource auto-configuration: 1. It did not tolerate a context with multiple DataSources 2. It would attempt to shut down a DataSource that had not been created by DataSourceAutoConfiguration and, therefore, where we could not be sure of its configuration. This commit updates DevToolsDataSourceAutoConfiguration so that it backs off unless the context contains DataSourceProperties and a single DataSource created by DataSourceAutoConfiguration. This ensures that it can safely use DataSourceProperties to get the DataSource’s driver class name and accurately determine if it’s an in-memory or external database. Shutdown is only called for an in-memory database. Closes gh-5540pull/5808/merge
parent
432969e61e
commit
a19eeaf91d
@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright 2012-2016 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.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.test.EnvironmentTestUtils;
|
||||
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.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Base class for tests for {@link DevToolsDataSourceAutoConfiguration}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class AbstractDevToolsDataSourceAutoConfigurationTests {
|
||||
|
||||
@Test
|
||||
public void singleManuallyConfiguredDataSourceIsNotClosed() throws SQLException {
|
||||
ConfigurableApplicationContext context = createContext(
|
||||
DataSourcePropertiesConfiguration.class,
|
||||
SingleDataSourceConfiguration.class);
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
Statement statement = configureDataSourceBehaviour(dataSource);
|
||||
verify(statement, times(0)).execute("SHUTDOWN");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleDataSourcesAreIgnored() throws SQLException {
|
||||
ConfigurableApplicationContext context = createContext(
|
||||
DataSourcePropertiesConfiguration.class,
|
||||
MultipleDataSourcesConfiguration.class);
|
||||
Collection<DataSource> dataSources = context.getBeansOfType(DataSource.class)
|
||||
.values();
|
||||
for (DataSource dataSource : dataSources) {
|
||||
Statement statement = configureDataSourceBehaviour(dataSource);
|
||||
verify(statement, times(0)).execute("SHUTDOWN");
|
||||
}
|
||||
}
|
||||
|
||||
protected final Statement configureDataSourceBehaviour(DataSource dataSource)
|
||||
throws SQLException {
|
||||
Connection connection = mock(Connection.class);
|
||||
Statement statement = mock(Statement.class);
|
||||
doReturn(connection).when(dataSource).getConnection();
|
||||
given(connection.createStatement()).willReturn(statement);
|
||||
return statement;
|
||||
}
|
||||
|
||||
protected final ConfigurableApplicationContext createContext(String driverClassName,
|
||||
Class<?>... classes) {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||
context.register(classes);
|
||||
context.register(DevToolsDataSourceAutoConfiguration.class);
|
||||
if (driverClassName != null) {
|
||||
EnvironmentTestUtils.addEnvironment(context,
|
||||
"spring.datasource.driver-class-name:" + driverClassName);
|
||||
}
|
||||
context.refresh();
|
||||
return context;
|
||||
}
|
||||
|
||||
protected final ConfigurableApplicationContext createContext(Class<?>... classes) {
|
||||
return this.createContext(null, classes);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class SingleDataSourceConfiguration {
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
return mock(DataSource.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class MultipleDataSourcesConfiguration {
|
||||
|
||||
@Bean
|
||||
public DataSource dataSourceOne() {
|
||||
return mock(DataSource.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DataSource dataSourceTwo() {
|
||||
return mock(DataSource.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(DataSourceProperties.class)
|
||||
static class DataSourcePropertiesConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class DataSourceSpyConfiguration {
|
||||
|
||||
@Bean
|
||||
public DataSourceSpyBeanPostProcessor dataSourceSpyBeanPostProcessor() {
|
||||
return new DataSourceSpyBeanPostProcessor();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class DataSourceSpyBeanPostProcessor implements BeanPostProcessor {
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName)
|
||||
throws BeansException {
|
||||
if (bean instanceof DataSource) {
|
||||
bean = spy(bean);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName)
|
||||
throws BeansException {
|
||||
return bean;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,187 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2016 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.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.test.EnvironmentTestUtils;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link DevToolsDataSourceAutoConfiguration}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class DevToolsDataSourceAutoConfigurationTests {
|
||||
|
||||
@Test
|
||||
public void embeddedDatabaseIsNotShutDown() throws SQLException {
|
||||
ConfigurableApplicationContext context = createContextWithDriver("org.h2.Driver",
|
||||
EmbeddedDatabaseConfiguration.class);
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
context.close();
|
||||
verify(dataSource, times(0)).getConnection();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void externalDatabaseIsNotShutDown() throws SQLException {
|
||||
ConfigurableApplicationContext context = createContextWithDriver(
|
||||
"org.postgresql.Driver", DataSourceConfiguration.class);
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
context.close();
|
||||
verify(dataSource, times(0)).getConnection();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nonEmbeddedInMemoryDatabaseConfiguredWithDriverIsShutDown()
|
||||
throws SQLException {
|
||||
ConfigurableApplicationContext context = createContextWithDriver("org.h2.Driver",
|
||||
DataSourceConfiguration.class);
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
Connection connection = mock(Connection.class);
|
||||
given(dataSource.getConnection()).willReturn(connection);
|
||||
Statement statement = mock(Statement.class);
|
||||
given(connection.createStatement()).willReturn(statement);
|
||||
context.close();
|
||||
verify(statement).execute("SHUTDOWN");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nonEmbeddedInMemoryDatabaseConfiguredWithUrlIsShutDown()
|
||||
throws SQLException {
|
||||
ConfigurableApplicationContext context = createContextWithUrl("jdbc:h2:mem:test",
|
||||
DataSourceConfiguration.class);
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
Connection connection = mock(Connection.class);
|
||||
given(dataSource.getConnection()).willReturn(connection);
|
||||
Statement statement = mock(Statement.class);
|
||||
given(connection.createStatement()).willReturn(statement);
|
||||
context.close();
|
||||
verify(statement).execute("SHUTDOWN");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configurationBacksOffWithoutDataSourceProperties() throws SQLException {
|
||||
ConfigurableApplicationContext context = createContext("org.h2.Driver",
|
||||
NoDataSourcePropertiesConfiguration.class);
|
||||
assertThat(
|
||||
context.getBeansOfType(DevToolsDataSourceAutoConfiguration.class).size(),
|
||||
is(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void entityManagerFactoryIsClosedBeforeDatabaseIsShutDown()
|
||||
throws SQLException {
|
||||
ConfigurableApplicationContext context = createContextWithUrl("jdbc:h2:mem:test",
|
||||
DataSourceConfiguration.class, EntityManagerFactoryConfiguration.class);
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
Connection connection = mock(Connection.class);
|
||||
given(dataSource.getConnection()).willReturn(connection);
|
||||
Statement statement = mock(Statement.class);
|
||||
given(connection.createStatement()).willReturn(statement);
|
||||
EntityManagerFactory entityManagerFactory = context
|
||||
.getBean(EntityManagerFactory.class);
|
||||
context.close();
|
||||
InOrder inOrder = inOrder(statement, entityManagerFactory);
|
||||
inOrder.verify(statement).execute("SHUTDOWN");
|
||||
inOrder.verify(entityManagerFactory).close();
|
||||
}
|
||||
|
||||
private ConfigurableApplicationContext createContextWithDriver(String driver,
|
||||
Class<?>... classes) {
|
||||
return createContext("spring.datasource.driver-class-name:" + driver, classes);
|
||||
}
|
||||
|
||||
private ConfigurableApplicationContext createContextWithUrl(String url,
|
||||
Class<?>... classes) {
|
||||
return createContext("spring.datasource.url:" + url, classes);
|
||||
}
|
||||
|
||||
private ConfigurableApplicationContext createContext(String property,
|
||||
Class<?>... classes) {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||
context.register(classes);
|
||||
context.register(DevToolsDataSourceAutoConfiguration.class);
|
||||
EnvironmentTestUtils.addEnvironment(context, property);
|
||||
context.refresh();
|
||||
return context;
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(DataSourceProperties.class)
|
||||
static class EmbeddedDatabaseConfiguration {
|
||||
|
||||
@Bean
|
||||
public EmbeddedDatabase embeddedDatabase() {
|
||||
return mock(EmbeddedDatabase.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(DataSourceProperties.class)
|
||||
static class DataSourceConfiguration {
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
return mock(DataSource.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class NoDataSourcePropertiesConfiguration {
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
return mock(DataSource.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class EntityManagerFactoryConfiguration {
|
||||
|
||||
@Bean
|
||||
public EntityManagerFactory entityManagerFactory() {
|
||||
return mock(EntityManagerFactory.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2012-2016 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.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.devtools.test.ClassPathExclusions;
|
||||
import org.springframework.boot.devtools.test.FilteredClassPathRunner;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link DevToolsDataSourceAutoConfiguration} with an embedded data source.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@RunWith(FilteredClassPathRunner.class)
|
||||
@ClassPathExclusions("tomcat-jdbc-*.jar")
|
||||
public class DevToolsEmbeddedDataSourceAutoConfigurationTests
|
||||
extends AbstractDevToolsDataSourceAutoConfigurationTests {
|
||||
|
||||
@Test
|
||||
public void autoConfiguredDataSourceIsNotShutdown() throws SQLException {
|
||||
ConfigurableApplicationContext context = createContext(
|
||||
DataSourceAutoConfiguration.class, DataSourceSpyConfiguration.class);
|
||||
Statement statement = configureDataSourceBehaviour(
|
||||
context.getBean(DataSource.class));
|
||||
context.close();
|
||||
verify(statement, times(0)).execute("SHUTDOWN");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2012-2016 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.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.devtools.test.FilteredClassPathRunner;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link DevToolsDataSourceAutoConfiguration} with a pooled data source.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@RunWith(FilteredClassPathRunner.class)
|
||||
public class DevToolsPooledDataSourceAutoConfigurationTests
|
||||
extends AbstractDevToolsDataSourceAutoConfigurationTests {
|
||||
|
||||
@Test
|
||||
public void autoConfiguredInMemoryDataSourceIsShutdown() throws SQLException {
|
||||
ConfigurableApplicationContext context = createContext(
|
||||
DataSourceAutoConfiguration.class, DataSourceSpyConfiguration.class);
|
||||
Statement statement = configureDataSourceBehaviour(
|
||||
context.getBean(DataSource.class));
|
||||
context.close();
|
||||
verify(statement).execute("SHUTDOWN");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void autoConfiguredExternalDataSourceIsNotShutdown() throws SQLException {
|
||||
ConfigurableApplicationContext context = createContext("org.postgresql.Driver",
|
||||
DataSourceAutoConfiguration.class, DataSourceSpyConfiguration.class);
|
||||
Statement statement = configureDataSourceBehaviour(
|
||||
context.getBean(DataSource.class));
|
||||
context.close();
|
||||
verify(statement, times(0)).execute("SHUTDOWN");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2012-2016 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.File;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation used in combination with {@link FilteredClassPathRunner} to exclude entries
|
||||
* from the classpath.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface ClassPathExclusions {
|
||||
|
||||
/**
|
||||
* One or more Ant-style patterns that identify entries to be excluded from the class
|
||||
* path. Matching is performed against an entry's {@link File#getName() file name}.
|
||||
* For example, to exclude Hibernate Validator from the classpath,
|
||||
* {@code "hibernate-validator-*.jar"} can be used.
|
||||
* @return the exclusion patterns
|
||||
*/
|
||||
String[] value();
|
||||
|
||||
}
|
@ -0,0 +1,222 @@
|
||||
/*
|
||||
* Copyright 2012-2016 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.File;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import org.junit.runners.BlockJUnit4ClassRunner;
|
||||
import org.junit.runners.model.FrameworkMethod;
|
||||
import org.junit.runners.model.InitializationError;
|
||||
import org.junit.runners.model.TestClass;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A custom {@link BlockJUnit4ClassRunner} that runs tests using a filtered class path.
|
||||
* Entries are excluded from the class path using {@link ClassPathExclusions} on the test
|
||||
* class. A class loader is created with the customized class path and is used both to
|
||||
* load the test class and as the thread context class loader while the test is being run.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class FilteredClassPathRunner extends BlockJUnit4ClassRunner {
|
||||
|
||||
public FilteredClassPathRunner(Class<?> testClass) throws InitializationError {
|
||||
super(testClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TestClass createTestClass(Class<?> testClass) {
|
||||
try {
|
||||
ClassLoader classLoader = createTestClassLoader(testClass);
|
||||
return new FilteredTestClass(classLoader, testClass.getName());
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private URLClassLoader createTestClassLoader(Class<?> testClass) throws Exception {
|
||||
URLClassLoader classLoader = (URLClassLoader) this.getClass().getClassLoader();
|
||||
return new URLClassLoader(filterUrls(extractUrls(classLoader), testClass),
|
||||
classLoader.getParent());
|
||||
}
|
||||
|
||||
private URL[] extractUrls(URLClassLoader classLoader) throws Exception {
|
||||
List<URL> extractedUrls = new ArrayList<URL>();
|
||||
for (URL url : classLoader.getURLs()) {
|
||||
if (isSurefireBooterJar(url)) {
|
||||
extractedUrls.addAll(extractUrlsFromManifestClassPath(url));
|
||||
}
|
||||
else {
|
||||
extractedUrls.add(url);
|
||||
}
|
||||
}
|
||||
return extractedUrls.toArray(new URL[extractedUrls.size()]);
|
||||
}
|
||||
|
||||
private boolean isSurefireBooterJar(URL url) {
|
||||
return url.getPath().contains("surefirebooter");
|
||||
}
|
||||
|
||||
private List<URL> extractUrlsFromManifestClassPath(URL booterJar) throws Exception {
|
||||
List<URL> urls = new ArrayList<URL>();
|
||||
for (String entry : getClassPath(booterJar)) {
|
||||
urls.add(new URL(entry));
|
||||
}
|
||||
return urls;
|
||||
}
|
||||
|
||||
private String[] getClassPath(URL booterJar) throws Exception {
|
||||
JarFile jarFile = new JarFile(new File(booterJar.toURI()));
|
||||
try {
|
||||
return StringUtils.delimitedListToStringArray(jarFile.getManifest()
|
||||
.getMainAttributes().getValue(Attributes.Name.CLASS_PATH), " ");
|
||||
}
|
||||
finally {
|
||||
jarFile.close();
|
||||
}
|
||||
}
|
||||
|
||||
private URL[] filterUrls(URL[] urls, Class<?> testClass) throws Exception {
|
||||
ClassPathEntryFilter filter = new ClassPathEntryFilter(testClass);
|
||||
List<URL> filteredUrls = new ArrayList<URL>();
|
||||
for (URL url : urls) {
|
||||
if (!filter.isExcluded(url)) {
|
||||
filteredUrls.add(url);
|
||||
}
|
||||
}
|
||||
return filteredUrls.toArray(new URL[filteredUrls.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter for class path entries.
|
||||
*/
|
||||
private static final class ClassPathEntryFilter {
|
||||
|
||||
private final List<String> exclusions;
|
||||
|
||||
private final AntPathMatcher matcher = new AntPathMatcher();
|
||||
|
||||
private ClassPathEntryFilter(Class<?> testClass) throws Exception {
|
||||
ClassPathExclusions exclusions = AnnotationUtils.findAnnotation(testClass,
|
||||
ClassPathExclusions.class);
|
||||
this.exclusions = exclusions == null ? Collections.<String>emptyList()
|
||||
: Arrays.asList(exclusions.value());
|
||||
}
|
||||
|
||||
private boolean isExcluded(URL url) throws Exception {
|
||||
if (!"file".equals(url.getProtocol())) {
|
||||
return false;
|
||||
}
|
||||
String name = new File(url.toURI()).getName();
|
||||
for (String exclusion : this.exclusions) {
|
||||
if (this.matcher.match(exclusion, name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filtered version of JUnit's {@link TestClass}.
|
||||
*/
|
||||
private static final class FilteredTestClass extends TestClass {
|
||||
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
FilteredTestClass(ClassLoader classLoader, String testClassName)
|
||||
throws ClassNotFoundException {
|
||||
super(classLoader.loadClass(testClassName));
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FrameworkMethod> getAnnotatedMethods(
|
||||
Class<? extends Annotation> annotationClass) {
|
||||
try {
|
||||
return getAnnotatedMethods(annotationClass.getName());
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<FrameworkMethod> getAnnotatedMethods(String annotationClassName)
|
||||
throws ClassNotFoundException {
|
||||
Class<? extends Annotation> annotationClass = (Class<? extends Annotation>) this.classLoader
|
||||
.loadClass(annotationClassName);
|
||||
List<FrameworkMethod> methods = super.getAnnotatedMethods(annotationClass);
|
||||
return wrapFrameworkMethods(methods);
|
||||
}
|
||||
|
||||
private List<FrameworkMethod> wrapFrameworkMethods(
|
||||
List<FrameworkMethod> methods) {
|
||||
List<FrameworkMethod> wrapped = new ArrayList<FrameworkMethod>(
|
||||
methods.size());
|
||||
for (FrameworkMethod frameworkMethod : methods) {
|
||||
wrapped.add(new FilteredFrameworkMethod(this.classLoader,
|
||||
frameworkMethod.getMethod()));
|
||||
}
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Filtered version of JUnit's {@link FrameworkMethod}.
|
||||
*/
|
||||
private static final class FilteredFrameworkMethod extends FrameworkMethod {
|
||||
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
private FilteredFrameworkMethod(ClassLoader classLoader, Method method) {
|
||||
super(method);
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invokeExplosively(Object target, Object... params)
|
||||
throws Throwable {
|
||||
ClassLoader originalClassLoader = Thread.currentThread()
|
||||
.getContextClassLoader();
|
||||
Thread.currentThread().setContextClassLoader(this.classLoader);
|
||||
try {
|
||||
return super.invokeExplosively(target, params);
|
||||
}
|
||||
finally {
|
||||
Thread.currentThread().setContextClassLoader(originalClassLoader);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue