Merge branch '1.5.x'

This commit is contained in:
Andy Wilkinson 2016-11-18 16:41:22 +00:00
commit 9273b1789b
23 changed files with 509 additions and 69 deletions

View File

@ -22,7 +22,7 @@ import org.junit.runner.RunWith;
import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.boot.testutil.ClassPathExclusions;
import org.springframework.boot.testutil.FilteredClassPathRunner;
import org.springframework.boot.testutil.ModifiedClassPathRunner;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
@ -33,7 +33,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Stephane Nicoll
*/
@RunWith(FilteredClassPathRunner.class)
@RunWith(ModifiedClassPathRunner.class)
@ClassPathExclusions("spring-security-*.jar")
public class ManagementServerPropertiesAutoConfigurationNoSecurityTests {

View File

@ -31,7 +31,7 @@ import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
import org.springframework.boot.testutil.ClassPathExclusions;
import org.springframework.boot.testutil.FilteredClassPathRunner;
import org.springframework.boot.testutil.ModifiedClassPathRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mock.web.MockServletContext;
@ -49,7 +49,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
*
* @author Andy Wilkinson
*/
@RunWith(FilteredClassPathRunner.class)
@RunWith(ModifiedClassPathRunner.class)
@ClassPathExclusions("spring-security-*.jar")
public class NoSpringSecurityHealthMvcEndpointIntegrationTests {

View File

@ -22,7 +22,7 @@ import org.junit.runner.RunWith;
import org.springframework.boot.autoconfigure.condition.OnBeanCondition.BeanTypeDeductionException;
import org.springframework.boot.testutil.ClassPathExclusions;
import org.springframework.boot.testutil.FilteredClassPathRunner;
import org.springframework.boot.testutil.ModifiedClassPathRunner;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -38,7 +38,7 @@ import static org.junit.Assert.fail;
*
* @author Andy Wilkinson
*/
@RunWith(FilteredClassPathRunner.class)
@RunWith(ModifiedClassPathRunner.class)
@ClassPathExclusions("jackson-core-*.jar")
public class OnBeanConditionTypeDeductionFailureTests {

View File

@ -23,7 +23,7 @@ import org.springframework.beans.factory.BeanCreationException;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.boot.testutil.ClassPathExclusions;
import org.springframework.boot.testutil.FilteredClassPathRunner;
import org.springframework.boot.testutil.ModifiedClassPathRunner;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
@ -34,7 +34,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Andy Wilkinson
*/
@RunWith(FilteredClassPathRunner.class)
@RunWith(ModifiedClassPathRunner.class)
@ClassPathExclusions({ "h2-*.jar", "hsqldb-*.jar" })
public class DataSourceBeanCreationFailureAnalyzerTests {

View File

@ -26,7 +26,7 @@ import org.junit.runner.RunWith;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.testutil.ClassPathExclusions;
import org.springframework.boot.testutil.FilteredClassPathRunner;
import org.springframework.boot.testutil.ModifiedClassPathRunner;
import org.springframework.context.ConfigurableApplicationContext;
import static org.mockito.Mockito.times;
@ -37,7 +37,7 @@ import static org.mockito.Mockito.verify;
*
* @author Andy Wilkinson
*/
@RunWith(FilteredClassPathRunner.class)
@RunWith(ModifiedClassPathRunner.class)
@ClassPathExclusions("tomcat-jdbc-*.jar")
public class DevToolsEmbeddedDataSourceAutoConfigurationTests
extends AbstractDevToolsDataSourceAutoConfigurationTests {

View File

@ -22,7 +22,7 @@ import org.junit.runner.RunWith;
import org.springframework.boot.test.rule.OutputCapture;
import org.springframework.boot.testutil.ClassPathExclusions;
import org.springframework.boot.testutil.FilteredClassPathRunner;
import org.springframework.boot.testutil.ModifiedClassPathRunner;
import static org.assertj.core.api.Assertions.assertThat;
@ -31,7 +31,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Andy Wilkinson
*/
@RunWith(FilteredClassPathRunner.class)
@RunWith(ModifiedClassPathRunner.class)
@ClassPathExclusions("spring-web-*.jar")
public class SpringApplicationBuilderExampleTests {

View File

@ -112,6 +112,12 @@
<optional>true</optional>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-runtime</artifactId>

View File

@ -20,7 +20,6 @@ import java.util.List;
import org.mockito.MockSettings;
import org.mockito.Mockito;
import org.mockito.internal.util.MockUtil;
import org.mockito.listeners.InvocationListener;
import org.mockito.listeners.MethodInvocationReport;
import org.mockito.mock.MockCreationSettings;
@ -105,9 +104,8 @@ public enum MockReset {
static MockReset get(Object mock) {
MockReset reset = MockReset.NONE;
if (ClassUtils.isPresent("org.mockito.internal.util.MockUtil", null)) {
MockUtil mockUtil = new MockUtil();
if (mockUtil.isMock(mock)) {
MockCreationSettings settings = mockUtil.getMockSettings(mock);
if (Mockito.mockingDetails(mock).isMock()) {
MockCreationSettings settings = SpringBootMockUtil.getMockSettings(mock);
List listeners = settings.getInvocationListeners();
for (Object listener : listeners) {
if (listener instanceof ResetInvocationListener) {

View File

@ -16,19 +16,14 @@
package org.springframework.boot.test.mock.mockito;
import java.lang.reflect.Field;
import java.util.List;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.Interceptor;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.mockito.internal.InternalMockHandler;
import org.mockito.internal.matchers.LocalizedMatcher;
import org.mockito.internal.progress.ArgumentMatcherStorage;
import org.mockito.internal.progress.MockingProgress;
import org.mockito.internal.stubbing.InvocationContainer;
import org.mockito.internal.util.MockUtil;
import org.mockito.internal.verification.MockAwareVerificationMode;
import org.mockito.verification.VerificationMode;
@ -38,7 +33,6 @@ import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.util.AopTestUtils;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
/**
* AOP {@link Interceptor} that attempts to make AOP proxy beans work with Mockito. Works
@ -58,7 +52,7 @@ class MockitoAopProxyTargetInterceptor implements MethodInterceptor {
MockitoAopProxyTargetInterceptor(Object source, Object target) throws Exception {
this.source = source;
this.target = target;
this.verification = new Verification(target);
this.verification = new Verification();
}
@Override
@ -94,21 +88,10 @@ class MockitoAopProxyTargetInterceptor implements MethodInterceptor {
private final Object monitor = new Object();
private final MockingProgress progress;
Verification(Object target) {
MockUtil mockUtil = new MockUtil();
InternalMockHandler<?> handler = mockUtil.getMockHandler(target);
InvocationContainer container = handler.getInvocationContainer();
Field field = ReflectionUtils.findField(container.getClass(),
"mockingProgress");
ReflectionUtils.makeAccessible(field);
this.progress = (MockingProgress) ReflectionUtils.getField(field, container);
}
public boolean isVerifying() {
synchronized (this.monitor) {
VerificationMode mode = this.progress.pullVerificationMode();
VerificationMode mode = SpringBootMockUtil.mockingProgress()
.pullVerificationMode();
if (mode != null) {
resetVerificationStarted(mode);
return true;
@ -119,7 +102,8 @@ class MockitoAopProxyTargetInterceptor implements MethodInterceptor {
public void replaceVerifyMock(Object source, Object target) {
synchronized (this.monitor) {
VerificationMode mode = this.progress.pullVerificationMode();
VerificationMode mode = SpringBootMockUtil.mockingProgress()
.pullVerificationMode();
if (mode != null) {
if (mode instanceof MockAwareVerificationMode) {
MockAwareVerificationMode mockAwareMode = (MockAwareVerificationMode) mode;
@ -133,12 +117,11 @@ class MockitoAopProxyTargetInterceptor implements MethodInterceptor {
}
private void resetVerificationStarted(VerificationMode mode) {
ArgumentMatcherStorage storage = this.progress.getArgumentMatcherStorage();
ArgumentMatcherStorage storage = SpringBootMockUtil.mockingProgress()
.getArgumentMatcherStorage();
List<LocalizedMatcher> matchers = storage.pullLocalizedMatchers();
this.progress.verificationStarted(mode);
for (LocalizedMatcher matcher : matchers) {
storage.reportMatcher(matcher);
}
SpringBootMockUtil.mockingProgress().verificationStarted(mode);
SpringBootMockUtil.reportMatchers(storage, matchers);
}
}

View File

@ -0,0 +1,138 @@
/*
* 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.test.mock.mockito;
import java.lang.reflect.Method;
import java.util.List;
import org.mockito.ArgumentMatcher;
import org.mockito.internal.matchers.LocalizedMatcher;
import org.mockito.internal.progress.ArgumentMatcherStorage;
import org.mockito.internal.progress.MockingProgress;
import org.mockito.internal.progress.ThreadSafeMockingProgress;
import org.mockito.internal.util.MockUtil;
import org.mockito.mock.MockCreationSettings;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/**
* A facade for Mockito's {@link MockUtil} that hides API differences between Mockito 1
* and 2.
*
* @author Andy Wilkinson
*/
class SpringBootMockUtil {
private static final MockUtilAdapter adapter;
static {
if (ClassUtils.isPresent("org.mockito.quality.MockitoHint",
SpringBootMockUtil.class.getClassLoader())) {
adapter = new Mockito2MockUtilAdapter();
}
else {
adapter = new Mockito1MockUtilAdapter();
}
}
static MockCreationSettings<?> getMockSettings(Object mock) {
return adapter.getMockSettings(mock);
}
static MockingProgress mockingProgress() {
return adapter.mockingProgress();
}
static void reportMatchers(ArgumentMatcherStorage storage,
List<LocalizedMatcher> matchers) {
adapter.reportMatchers(storage, matchers);
}
private interface MockUtilAdapter {
MockCreationSettings<?> getMockSettings(Object mock);
MockingProgress mockingProgress();
void reportMatchers(ArgumentMatcherStorage storage,
List<LocalizedMatcher> matchers);
}
private static class Mockito1MockUtilAdapter implements MockUtilAdapter {
private static final MockingProgress mockingProgress = new ThreadSafeMockingProgress();
@Override
public MockCreationSettings<?> getMockSettings(Object mock) {
return new MockUtil().getMockSettings(mock);
}
@Override
public MockingProgress mockingProgress() {
return mockingProgress;
}
@Override
public void reportMatchers(ArgumentMatcherStorage storage,
List<LocalizedMatcher> matchers) {
for (LocalizedMatcher matcher : matchers) {
storage.reportMatcher(matcher);
}
}
}
private static class Mockito2MockUtilAdapter implements MockUtilAdapter {
private final Method getMockSettingsMethod = ReflectionUtils
.findMethod(MockUtil.class, "getMockSettings", Object.class);
private final Method mockingProgressMethod = ReflectionUtils
.findMethod(ThreadSafeMockingProgress.class, "mockingProgress");
private final Method reportMatcherMethod = ReflectionUtils.findMethod(
ArgumentMatcherStorage.class, "reportMatcher", ArgumentMatcher.class);
private final Method getMatcherMethod = ReflectionUtils
.findMethod(LocalizedMatcher.class, "getMatcher");
@Override
public MockCreationSettings<?> getMockSettings(Object mock) {
return (MockCreationSettings<?>) ReflectionUtils
.invokeMethod(this.getMockSettingsMethod, null, mock);
}
@Override
public MockingProgress mockingProgress() {
return (MockingProgress) ReflectionUtils
.invokeMethod(this.mockingProgressMethod, null);
}
@Override
public void reportMatchers(ArgumentMatcherStorage storage,
List<LocalizedMatcher> matchers) {
for (LocalizedMatcher matcher : matchers) {
ReflectionUtils.invokeMethod(this.reportMatcherMethod, storage,
ReflectionUtils.invokeMethod(this.getMatcherMethod, matcher));
}
}
}
}

View File

@ -18,7 +18,6 @@ package org.springframework.boot.test.mock.mockito;
import org.mockito.MockSettings;
import org.mockito.Mockito;
import org.mockito.internal.util.MockUtil;
import org.springframework.core.ResolvableType;
import org.springframework.core.style.ToStringCreator;
@ -33,8 +32,6 @@ import org.springframework.util.StringUtils;
*/
class SpyDefinition extends Definition {
private MockUtil mockUtil = new MockUtil();
private static final int MULTIPLIER = 31;
private final ResolvableType typeToSpy;
@ -87,7 +84,7 @@ class SpyDefinition extends Definition {
public <T> T createSpy(String name, Object instance) {
Assert.notNull(instance, "Instance must not be null");
Assert.isInstanceOf(this.typeToSpy.resolve(), instance);
if (this.mockUtil.isSpy(instance)) {
if (Mockito.mockingDetails(instance).isSpy()) {
return (T) instance;
}
MockSettings settings = MockReset.withSettings(getReset());

View File

@ -20,7 +20,6 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.Answers;
import org.mockito.internal.util.MockUtil;
import org.mockito.mock.MockCreationSettings;
import org.springframework.boot.test.mock.mockito.example.ExampleExtraInterface;
@ -86,7 +85,7 @@ public class MockDefinitionTests {
new Class<?>[] { ExampleExtraInterface.class },
Answers.RETURNS_SMART_NULLS, true, MockReset.BEFORE, null);
ExampleService mock = definition.createMock();
MockCreationSettings<?> settings = new MockUtil().getMockSettings(mock);
MockCreationSettings<?> settings = SpringBootMockUtil.getMockSettings(mock);
assertThat(mock).isInstanceOf(ExampleService.class);
assertThat(mock).isInstanceOf(ExampleExtraInterface.class);
assertThat(settings.getMockName().toString()).isEqualTo("name");

View File

@ -0,0 +1,54 @@
/*
* 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.test.mock.mockito;
import org.junit.Test;
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.RunWith;
import org.springframework.boot.testutil.ClassPathOverrides;
import org.springframework.boot.testutil.ModifiedClassPathRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for compatibility with Mockito 2.1
*
* @author Andy Wilkinson
*/
@RunWith(ModifiedClassPathRunner.class)
@ClassPathOverrides("org.mockito:mockito-core:2.1.0")
public class Mockito21Tests {
@Test
public void resetMocksTestExecutionListenerTestsWithMockito2() {
runTests(ResetMocksTestExecutionListenerTests.class);
}
@Test
public void spyBeanWithAopProxyTestsWithMockito2() {
runTests(SpyBeanWithAopProxyTests.class);
}
private void runTests(Class<?> testClass) {
Result result = new JUnitCore().run(testClass);
assertThat(result.getFailureCount()).isEqualTo(0);
assertThat(result.getRunCount()).isGreaterThan(0);
}
}

View File

@ -0,0 +1,54 @@
/*
* 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.test.mock.mockito;
import org.junit.Test;
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.RunWith;
import org.springframework.boot.testutil.ClassPathOverrides;
import org.springframework.boot.testutil.ModifiedClassPathRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for compatibility with Mockito 2.2
*
* @author Andy Wilkinson
*/
@RunWith(ModifiedClassPathRunner.class)
@ClassPathOverrides("org.mockito:mockito-core:2.2.0")
public class Mockito22Tests {
@Test
public void resetMocksTestExecutionListenerTestsWithMockito2() {
runTests(ResetMocksTestExecutionListenerTests.class);
}
@Test
public void spyBeanWithAopProxyTestsWithMockito2() {
runTests(SpyBeanWithAopProxyTests.class);
}
private void runTests(Class<?> testClass) {
Result result = new JUnitCore().run(testClass);
assertThat(result.getFailureCount()).isEqualTo(0);
assertThat(result.getRunCount()).isGreaterThan(0);
}
}

View File

@ -19,7 +19,7 @@ package org.springframework.boot.test.mock.mockito;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.internal.util.MockUtil;
import org.mockito.Mockito;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Qualifier;
@ -80,7 +80,8 @@ public class MockitoPostProcessorTests {
context.registerBeanDefinition("beanToBeMocked", factoryBeanDefinition);
context.register(MockedFactoryBean.class);
context.refresh();
assertThat(new MockUtil().isMock(context.getBean("beanToBeMocked"))).isTrue();
assertThat(Mockito.mockingDetails(context.getBean("beanToBeMocked")).isMock())
.isTrue();
}
@Configuration

View File

@ -20,7 +20,6 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.Answers;
import org.mockito.internal.util.MockUtil;
import org.mockito.mock.MockCreationSettings;
import org.springframework.boot.test.mock.mockito.example.ExampleService;
@ -79,7 +78,7 @@ public class SpyDefinitionTests {
SpyDefinition definition = new SpyDefinition("name", REAL_SERVICE_TYPE,
MockReset.BEFORE, true, null);
RealExampleService spy = definition.createSpy(new RealExampleService("hello"));
MockCreationSettings<?> settings = new MockUtil().getMockSettings(spy);
MockCreationSettings<?> settings = SpringBootMockUtil.getMockSettings(spy);
assertThat(spy).isInstanceOf(ExampleService.class);
assertThat(settings.getMockName().toString()).isEqualTo("name");
assertThat(settings.getDefaultAnswer())

View File

@ -290,6 +290,64 @@
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpasyncclient</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-aether-provider</artifactId>
<exclusions>
<exclusion>
<artifactId>org.eclipse.sisu.plexus</artifactId>
<groupId>org.eclipse.sisu</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-settings-builder</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-component-api</artifactId>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.eclipse.aether</groupId>
<artifactId>aether-api</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.aether</groupId>
<artifactId>aether-connector-basic</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.aether</groupId>
<artifactId>aether-impl</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.aether</groupId>
<artifactId>aether-spi</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.aether</groupId>
<artifactId>aether-transport-file</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.aether</groupId>
<artifactId>aether-transport-http</artifactId>
<exclusions>
<exclusion>
<artifactId>jcl-over-slf4j</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.eclipse.aether</groupId>
<artifactId>aether-util</artifactId>
</dependency>
<dependency>
<groupId>org.firebirdsql.jdbc</groupId>

View File

@ -23,7 +23,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.boot.testutil.ClassPathExclusions;
import org.springframework.boot.testutil.FilteredClassPathRunner;
import org.springframework.boot.testutil.ModifiedClassPathRunner;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
@ -34,7 +34,7 @@ import static org.junit.Assert.fail;
*
* @author Andy Wilkinson
*/
@RunWith(FilteredClassPathRunner.class)
@RunWith(ModifiedClassPathRunner.class)
@ClassPathExclusions("hibernate-validator-*.jar")
public class ValidationExceptionFailureAnalyzerTests {

View File

@ -23,7 +23,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation used in combination with {@link FilteredClassPathRunner} to exclude entries
* Annotation used in combination with {@link ModifiedClassPathRunner} to exclude entries
* from the classpath.
*
* @author Andy Wilkinson

View File

@ -0,0 +1,42 @@
/*
* 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.testutil;
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 ModifiedClassPathRunner} to override entries
* on the classpath.
*
* @author Andy Wilkinson
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ClassPathOverrides {
/**
* One or more sets of Maven coordinates ({@code groupId:artifactId:version}) to be
* added to the classpath. The additions will take precedence over any existing
* classes on the classpath.
* @return the coordinates
*/
String[] value();
}

View File

@ -28,6 +28,22 @@ import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.collection.CollectRequest;
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.impl.DefaultServiceLocator;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.resolution.DependencyRequest;
import org.eclipse.aether.resolution.DependencyResult;
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
import org.eclipse.aether.spi.connector.transport.TransporterFactory;
import org.eclipse.aether.transport.http.HttpTransporterFactory;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
@ -38,16 +54,17 @@ 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.
* A custom {@link BlockJUnit4ClassRunner} that runs tests using a modified class path.
* Entries are excluded from the class path using {@link ClassPathExclusions} and
* overridden using {@link ClassPathOverrides} 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 class ModifiedClassPathRunner extends BlockJUnit4ClassRunner {
public FilteredClassPathRunner(Class<?> testClass) throws InitializationError {
public ModifiedClassPathRunner(Class<?> testClass) throws InitializationError {
super(testClass);
}
@ -64,7 +81,7 @@ public class FilteredClassPathRunner extends BlockJUnit4ClassRunner {
private URLClassLoader createTestClassLoader(Class<?> testClass) throws Exception {
URLClassLoader classLoader = (URLClassLoader) this.getClass().getClassLoader();
return new FilteredClassLoader(filterUrls(extractUrls(classLoader), testClass),
return new FilteredClassLoader(processUrls(extractUrls(classLoader), testClass),
classLoader.getParent(), classLoader);
}
@ -104,15 +121,61 @@ public class FilteredClassPathRunner extends BlockJUnit4ClassRunner {
}
}
private URL[] filterUrls(URL[] urls, Class<?> testClass) throws Exception {
private URL[] processUrls(URL[] urls, Class<?> testClass) throws Exception {
ClassPathEntryFilter filter = new ClassPathEntryFilter(testClass);
List<URL> filteredUrls = new ArrayList<URL>();
List<URL> processedUrls = new ArrayList<URL>();
processedUrls.addAll(getAdditionalUrls(testClass));
for (URL url : urls) {
if (!filter.isExcluded(url)) {
filteredUrls.add(url);
processedUrls.add(url);
}
}
return filteredUrls.toArray(new URL[filteredUrls.size()]);
return processedUrls.toArray(new URL[processedUrls.size()]);
}
private List<URL> getAdditionalUrls(Class<?> testClass) throws Exception {
ClassPathOverrides overrides = AnnotationUtils.findAnnotation(testClass,
ClassPathOverrides.class);
if (overrides == null) {
return Collections.emptyList();
}
return resolveCoordinates(overrides.value());
}
private List<URL> resolveCoordinates(String[] coordinates) throws Exception {
DefaultServiceLocator serviceLocator = MavenRepositorySystemUtils
.newServiceLocator();
serviceLocator.addService(RepositoryConnectorFactory.class,
BasicRepositoryConnectorFactory.class);
serviceLocator.addService(TransporterFactory.class, HttpTransporterFactory.class);
RepositorySystem repositorySystem = serviceLocator
.getService(RepositorySystem.class);
DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
LocalRepository localRepository = new LocalRepository(
System.getProperty("user.home") + "/.m2/repository");
session.setLocalRepositoryManager(
repositorySystem.newLocalRepositoryManager(session, localRepository));
CollectRequest collectRequest = new CollectRequest(null,
Arrays.asList(new RemoteRepository.Builder("central", "default",
"http://central.maven.org/maven2").build()));
collectRequest.setDependencies(createDependencies(coordinates));
DependencyRequest dependencyRequest = new DependencyRequest(collectRequest, null);
DependencyResult result = repositorySystem.resolveDependencies(session,
dependencyRequest);
List<URL> resolvedArtifacts = new ArrayList<URL>();
for (ArtifactResult artifact : result.getArtifactResults()) {
resolvedArtifacts.add(artifact.getArtifact().getFile().toURI().toURL());
}
return resolvedArtifacts;
}
private List<Dependency> createDependencies(String[] allCoordinates) {
List<Dependency> dependencies = new ArrayList<Dependency>();
for (String coordinates : allCoordinates) {
dependencies.add(new Dependency(new DefaultArtifact(coordinates), null));
}
return dependencies;
}
/**

View File

@ -22,13 +22,13 @@ import org.junit.runner.RunWith;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link FilteredClassPathRunner}
* Tests for {@link ModifiedClassPathRunner} excluding entries from the class path.
*
* @author Andy Wilkinson
*/
@RunWith(FilteredClassPathRunner.class)
@RunWith(ModifiedClassPathRunner.class)
@ClassPathExclusions("hibernate-validator-*.jar")
public class FilteredClassPathRunnerTests {
public class ModifiedClassPathRunnerExclusionsTests {
private static final String EXCLUDED_RESOURCE = "META-INF/services/"
+ "javax.validation.spi.ValidationProvider";

View File

@ -0,0 +1,48 @@
/*
* 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.testutil;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.ApplicationContext;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ModifiedClassPathRunner} overriding entries on the class path.
*
* @author Andy Wilkinson
*/
@RunWith(ModifiedClassPathRunner.class)
@ClassPathOverrides("org.springframework:spring-context:4.1.0.RELEASE")
public class ModifiedClassPathRunnerOverridesTests {
@Test
public void classesAreLoadedFromOverride() {
assertThat(ApplicationContext.class.getProtectionDomain().getCodeSource()
.getLocation().toString()).endsWith("spring-context-4.1.0.RELEASE.jar");
}
@Test
public void classesAreLoadedFromTransitiveDependencyOfOverride() {
assertThat(StringUtils.class.getProtectionDomain().getCodeSource().getLocation()
.toString()).endsWith("spring-core-4.1.0.RELEASE.jar");
}
}