diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/ForkedClassPath.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/ForkedClassPath.java new file mode 100644 index 000000000000..4b2f8f85eeb5 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/ForkedClassPath.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testsupport.classpath; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Annotation used to fork the classpath. This can be helpful were neither + * {@link ClassPathExclusions} or {@link ClassPathOverrides} are needed, but just a copy + * of the classpath. + * + * @author Christoph Dreis + * @since 2.4.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +@ExtendWith(ModifiedClassPathExtension.class) +public @interface ForkedClassPath { + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/ModifiedClassPathClassLoader.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/ModifiedClassPathClassLoader.java index d78d16a80f25..e85e59a45d3b 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/ModifiedClassPathClassLoader.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/ModifiedClassPathClassLoader.java @@ -87,7 +87,14 @@ static ModifiedClassPathClassLoader get(Class testClass) { private static ModifiedClassPathClassLoader compute(Class testClass) { ClassLoader classLoader = testClass.getClassLoader(); - return new ModifiedClassPathClassLoader(processUrls(extractUrls(classLoader), testClass), + MergedAnnotations annotations = MergedAnnotations.from(testClass, + MergedAnnotations.SearchStrategy.TYPE_HIERARCHY); + if (annotations.isPresent(ForkedClassPath.class) && (annotations.isPresent(ClassPathOverrides.class) + || annotations.isPresent(ClassPathExclusions.class))) { + throw new IllegalStateException("@ForkedClassPath is redundant in combination with either " + + "@ClassPathOverrides or @ClassPathExclusions"); + } + return new ModifiedClassPathClassLoader(processUrls(extractUrls(classLoader), annotations), classLoader.getParent(), classLoader); } @@ -168,9 +175,7 @@ private static Attributes getManifestMainAttributesFromUrl(URL url) throws Excep } } - private static URL[] processUrls(URL[] urls, Class testClass) { - MergedAnnotations annotations = MergedAnnotations.from(testClass, - MergedAnnotations.SearchStrategy.TYPE_HIERARCHY); + private static URL[] processUrls(URL[] urls, MergedAnnotations annotations) { ClassPathEntryFilter filter = new ClassPathEntryFilter(annotations.get(ClassPathExclusions.class)); List additionalUrls = getAdditionalUrls(annotations.get(ClassPathOverrides.class)); List processedUrls = new ArrayList<>(additionalUrls); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/ModifiedClassPathExtension.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/ModifiedClassPathExtension.java index a5c4bc165939..4e704ed71b5d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/ModifiedClassPathExtension.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/ModifiedClassPathExtension.java @@ -35,14 +35,14 @@ /** * A custom {@link Extension} that runs tests using a modified class path. Entries are * excluded from the class path using {@link ClassPathExclusions @ClassPathExclusions} and - * overridden using {@link ClassPathOverrides @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. + * overridden using {@link ClassPathOverrides @ClassPathOverrides} on the test class. For + * an unchanged copy of the class path {@link ForkedClassPath @ForkedClassPath} can be + * used. 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 Christoph Dreis - * @since 2.4.0 */ -public class ModifiedClassPathExtension implements InvocationInterceptor { +class ModifiedClassPathExtension implements InvocationInterceptor { @Override public void interceptBeforeAllMethod(Invocation invocation, diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/ModifiedClassPathExtensionForkTests.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/ModifiedClassPathExtensionForkTests.java new file mode 100644 index 000000000000..fed8ad860a66 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/ModifiedClassPathExtensionForkTests.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testsupport.classpath; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ForkedClassPath @ForkedClassPath}. + * + * @author Christoph Dreis + */ +@ForkedClassPath +class ModifiedClassPathExtensionForkTests { + + @Test + void modifiedClassLoaderIsUsed() { + ClassLoader classLoader = getClass().getClassLoader(); + assertThat(classLoader.getClass().getName()).isEqualTo(ModifiedClassPathClassLoader.class.getName()); + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/IgnoringXmlBeanDefinitionLoaderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/IgnoringXmlBeanDefinitionLoaderTests.java index a74a5475b2e2..471397779221 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/IgnoringXmlBeanDefinitionLoaderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/IgnoringXmlBeanDefinitionLoaderTests.java @@ -19,15 +19,14 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.BeanDefinitionStoreException; -import org.springframework.boot.testsupport.classpath.ModifiedClassPathExtension; +import org.springframework.boot.testsupport.classpath.ForkedClassPath; import org.springframework.context.support.StaticApplicationContext; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -@ExtendWith(ModifiedClassPathExtension.class) +@ForkedClassPath class IgnoringXmlBeanDefinitionLoaderTests { @BeforeAll