Removed ModifiedClasspathRunner and used the ClasspathExtensions from the Spring boot test support (#1181)
This commit is contained in:
@@ -167,11 +167,6 @@
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-test-support</artifactId>
|
||||
|
||||
@@ -16,20 +16,17 @@
|
||||
|
||||
package org.springframework.cloud.client.discovery;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.WebApplicationType;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.cloud.test.ClassPathExclusions;
|
||||
import org.springframework.cloud.test.ModifiedClassPathRunner;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
import static org.assertj.core.api.BDDAssertions.then;
|
||||
|
||||
@RunWith(ModifiedClassPathRunner.class)
|
||||
@ClassPathExclusions({ "spring-boot-actuator-autoconfigure-*" })
|
||||
public class ManagementServerPortUtilsTests {
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.WebApplicationType;
|
||||
|
||||
@@ -18,10 +18,7 @@ package org.springframework.cloud.client.loadbalancer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.cloud.test.ClassPathExclusions;
|
||||
import org.springframework.cloud.test.ModifiedClassPathRunner;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@@ -30,7 +27,6 @@ import static org.assertj.core.api.BDDAssertions.then;
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@RunWith(ModifiedClassPathRunner.class)
|
||||
@ClassPathExclusions({ "spring-retry-*.jar", "spring-boot-starter-aop-*.jar" })
|
||||
public class LoadBalancerAutoConfigurationTests extends AbstractLoadBalancerAutoConfigurationTests {
|
||||
|
||||
|
||||
@@ -16,14 +16,12 @@
|
||||
|
||||
package org.springframework.cloud.client.serviceregistry;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.boot.WebApplicationType;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.cloud.test.ClassPathExclusions;
|
||||
import org.springframework.cloud.test.ModifiedClassPathRunner;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@@ -32,7 +30,6 @@ import static org.assertj.core.api.Assertions.fail;
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@RunWith(ModifiedClassPathRunner.class)
|
||||
@ClassPathExclusions({ "spring-boot-actuator-*.jar", "spring-boot-starter-actuator-*.jar" })
|
||||
public class ServiceRegistryAutoConfigurationTests {
|
||||
|
||||
|
||||
@@ -72,11 +72,6 @@
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-test-support</artifactId>
|
||||
|
||||
@@ -16,15 +16,13 @@
|
||||
|
||||
package org.springframework.cloud.autoconfigure;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.WebApplicationType;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.cloud.endpoint.event.RefreshEventListener;
|
||||
import org.springframework.cloud.test.ClassPathExclusions;
|
||||
import org.springframework.cloud.test.ModifiedClassPathRunner;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@@ -33,7 +31,6 @@ import static org.assertj.core.api.BDDAssertions.then;
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@RunWith(ModifiedClassPathRunner.class)
|
||||
@ClassPathExclusions({ "spring-boot-actuator-*.jar", "spring-boot-starter-actuator-*.jar" })
|
||||
public class RefreshAutoConfigurationClassPathTests {
|
||||
|
||||
|
||||
@@ -16,16 +16,15 @@
|
||||
|
||||
package org.springframework.cloud.autoconfigure;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.boot.WebApplicationType;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.test.system.OutputCaptureRule;
|
||||
import org.springframework.boot.test.system.CapturedOutput;
|
||||
import org.springframework.boot.test.system.OutputCaptureExtension;
|
||||
import org.springframework.cloud.test.ClassPathExclusions;
|
||||
import org.springframework.cloud.test.ModifiedClassPathRunner;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@@ -34,21 +33,18 @@ import static org.assertj.core.api.BDDAssertions.then;
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@RunWith(ModifiedClassPathRunner.class)
|
||||
@ClassPathExclusions({ "spring-boot-actuator-autoconfigure-*.jar", "spring-boot-starter-actuator-*.jar" })
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
public class RefreshAutoConfigurationMoreClassPathTests {
|
||||
|
||||
@Rule
|
||||
public OutputCaptureRule outputCapture = new OutputCaptureRule();
|
||||
|
||||
private static ConfigurableApplicationContext getApplicationContext(Class<?> configuration, String... properties) {
|
||||
return new SpringApplicationBuilder(configuration).web(WebApplicationType.NONE).properties(properties).run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unknownClassProtected() {
|
||||
public void unknownClassProtected(CapturedOutput outputCapture) {
|
||||
try (ConfigurableApplicationContext context = getApplicationContext(Config.class, "debug=true")) {
|
||||
String output = this.outputCapture.toString();
|
||||
String output = outputCapture.toString();
|
||||
then(output)
|
||||
.doesNotContain("Failed to introspect annotations on "
|
||||
+ "[class org.springframework.cloud.autoconfigure.RefreshEndpointAutoConfiguration")
|
||||
|
||||
@@ -18,15 +18,13 @@ package org.springframework.cloud.bootstrap.encrypt;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.WebApplicationType;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.cloud.test.ClassPathExclusions;
|
||||
import org.springframework.cloud.test.ModifiedClassPathRunner;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
import static org.assertj.core.api.BDDAssertions.then;
|
||||
@@ -34,20 +32,19 @@ import static org.assertj.core.api.BDDAssertions.then;
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
@RunWith(ModifiedClassPathRunner.class)
|
||||
@ClassPathExclusions({ "spring-security-rsa*.jar" })
|
||||
public class RsaDisabledTests {
|
||||
|
||||
private ConfigurableApplicationContext context;
|
||||
|
||||
@Before
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE)
|
||||
.sources(EncryptionBootstrapConfiguration.class).web(WebApplicationType.NONE)
|
||||
.properties("encrypt.key:mykey", "encrypt.rsa.strong:true", "encrypt.rsa.salt:foobar").run();
|
||||
}
|
||||
|
||||
@After
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
if (this.context != null) {
|
||||
this.context.close();
|
||||
|
||||
@@ -138,10 +138,5 @@
|
||||
<artifactId>awaitility</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -17,37 +17,28 @@
|
||||
package org.springframework.cloud.loadbalancer.security;
|
||||
|
||||
import org.apache.catalina.webresources.TomcatURLStreamHandlerFactory;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.cloud.test.ClassPathExclusions;
|
||||
import org.springframework.cloud.test.ModifiedClassPathRunner;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@RunWith(ModifiedClassPathRunner.class)
|
||||
@ClassPathExclusions("spring-retry-*.jar")
|
||||
public class OAuth2LoadBalancerClientAutoConfigurationTests {
|
||||
|
||||
private ConfigurableApplicationContext context;
|
||||
|
||||
@Rule
|
||||
public ExpectedException expected = ExpectedException.none();
|
||||
|
||||
@Before
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
// FIXME: why do I need to do this? (fails in maven build without it.
|
||||
// https://stackoverflow.com/questions/28911560/tomcat-8-embedded-error-org-apache-catalina-core-containerbase-a-child-con
|
||||
@@ -55,7 +46,7 @@ public class OAuth2LoadBalancerClientAutoConfigurationTests {
|
||||
TomcatURLStreamHandlerFactory.disable();
|
||||
}
|
||||
|
||||
@After
|
||||
@AfterEach
|
||||
public void close() {
|
||||
if (this.context != null) {
|
||||
this.context.close();
|
||||
@@ -63,7 +54,7 @@ public class OAuth2LoadBalancerClientAutoConfigurationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
@Disabled
|
||||
public void userInfoNotLoadBalanced() {
|
||||
this.context = new SpringApplicationBuilder(ClientConfiguration.class).properties("spring.config.name=test",
|
||||
"server.port=0", "security.oauth2.resource.userInfoUri:https://example.com").run();
|
||||
@@ -73,7 +64,7 @@ public class OAuth2LoadBalancerClientAutoConfigurationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
@Disabled
|
||||
public void userInfoLoadBalancedNoRetry() {
|
||||
this.context = new SpringApplicationBuilder(ClientConfiguration.class).properties("spring.config.name=test",
|
||||
"server.port=0", "security.oauth2.resource.userInfoUri:https://nosuchservice",
|
||||
|
||||
@@ -27,6 +27,10 @@
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.platform</groupId>
|
||||
<artifactId>junit-platform-launcher</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
|
||||
@@ -65,6 +65,18 @@
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.platform</groupId>
|
||||
<artifactId>junit-platform-engine</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.platform</groupId>
|
||||
<artifactId>junit-platform-launcher</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate.validator</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
|
||||
@@ -21,6 +21,8 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
/**
|
||||
* Taken from Spring Boot test utils. https://github.com/spring-projects/spring-boot/blob/
|
||||
* 1.4.x/spring-boot/src/test/java/org/springframework/boot/testutil/ClassPathExclusions.java
|
||||
@@ -28,6 +30,7 @@ import java.lang.annotation.Target;
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@ExtendWith(ModifiedClassPathExtension.class)
|
||||
public @interface ClassPathExclusions {
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,14 +21,17 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
/**
|
||||
* Annotation used in combination with {@link ModifiedClassPathRunner} to override entries
|
||||
* Annotation used in combination with {@link ModifiedClassPathExtension} to override entries
|
||||
* on the classpath.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@ExtendWith(ModifiedClassPathExtension.class)
|
||||
public @interface ClassPathOverrides {
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,310 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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.cloud.test;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
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.springframework.core.annotation.MergedAnnotation;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Custom {@link URLClassLoader} that modifies the class path.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Christoph Dreis
|
||||
* @author Siva Krishna Battu
|
||||
* @see <a href=
|
||||
* "https://github.com/spring-projects/spring-boot/blob/main/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/ModifiedClassPathClassLoader.java">ModifiedClassPathClassLoader.java</a>
|
||||
*/
|
||||
final class ModifiedClassPathClassLoader extends URLClassLoader {
|
||||
|
||||
private static final Map<List<AnnotatedElement>, ModifiedClassPathClassLoader> cache = new ConcurrentReferenceHashMap<>();
|
||||
|
||||
private static final Pattern INTELLIJ_CLASSPATH_JAR_PATTERN = Pattern.compile(".*classpath(\\d+)?\\.jar");
|
||||
|
||||
private static final int MAX_RESOLUTION_ATTEMPTS = 5;
|
||||
|
||||
private final ClassLoader junitLoader;
|
||||
|
||||
ModifiedClassPathClassLoader(URL[] urls, ClassLoader parent, ClassLoader junitLoader) {
|
||||
super(urls, parent);
|
||||
this.junitLoader = junitLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> loadClass(String name) throws ClassNotFoundException {
|
||||
if (name.startsWith("org.junit") || name.startsWith("org.hamcrest")
|
||||
|| name.startsWith("io.netty.internal.tcnative")) {
|
||||
return Class.forName(name, false, this.junitLoader);
|
||||
}
|
||||
return super.loadClass(name);
|
||||
}
|
||||
|
||||
static ModifiedClassPathClassLoader get(Class<?> testClass, Method testMethod, List<Object> arguments) {
|
||||
Set<AnnotatedElement> candidates = new LinkedHashSet<>();
|
||||
candidates.add(testClass);
|
||||
candidates.add(testMethod);
|
||||
candidates.addAll(getAnnotatedElements(arguments.toArray()));
|
||||
List<AnnotatedElement> annotatedElements = candidates.stream()
|
||||
.filter(ModifiedClassPathClassLoader::hasAnnotation).collect(Collectors.toList());
|
||||
if (annotatedElements.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return cache.computeIfAbsent(annotatedElements, (key) -> compute(testClass.getClassLoader(), key));
|
||||
}
|
||||
|
||||
private static Collection<AnnotatedElement> getAnnotatedElements(Object[] array) {
|
||||
Set<AnnotatedElement> result = new LinkedHashSet<>();
|
||||
for (Object item : array) {
|
||||
if (item instanceof AnnotatedElement) {
|
||||
result.add((AnnotatedElement) item);
|
||||
}
|
||||
else if (ObjectUtils.isArray(item)) {
|
||||
result.addAll(getAnnotatedElements(ObjectUtils.toObjectArray(item)));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static boolean hasAnnotation(AnnotatedElement element) {
|
||||
MergedAnnotations annotations = MergedAnnotations.from(element,
|
||||
MergedAnnotations.SearchStrategy.TYPE_HIERARCHY);
|
||||
return annotations.isPresent(ClassPathOverrides.class) || annotations.isPresent(ClassPathExclusions.class);
|
||||
}
|
||||
|
||||
private static ModifiedClassPathClassLoader compute(ClassLoader classLoader,
|
||||
List<AnnotatedElement> annotatedClasses) {
|
||||
List<MergedAnnotations> annotations = annotatedClasses.stream()
|
||||
.map((source) -> MergedAnnotations.from(source, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY))
|
||||
.toList();
|
||||
return new ModifiedClassPathClassLoader(processUrls(extractUrls(classLoader), annotations),
|
||||
classLoader.getParent(), classLoader);
|
||||
}
|
||||
|
||||
private static URL[] extractUrls(ClassLoader classLoader) {
|
||||
List<URL> extractedUrls = new ArrayList<>();
|
||||
doExtractUrls(classLoader).forEach((URL url) -> {
|
||||
if (isManifestOnlyJar(url)) {
|
||||
extractedUrls.addAll(extractUrlsFromManifestClassPath(url));
|
||||
}
|
||||
else {
|
||||
extractedUrls.add(url);
|
||||
}
|
||||
});
|
||||
return extractedUrls.toArray(new URL[0]);
|
||||
}
|
||||
|
||||
private static Stream<URL> doExtractUrls(ClassLoader classLoader) {
|
||||
if (classLoader instanceof URLClassLoader urlClassLoader) {
|
||||
return Stream.of(urlClassLoader.getURLs());
|
||||
}
|
||||
return Stream.of(ManagementFactory.getRuntimeMXBean().getClassPath().split(File.pathSeparator))
|
||||
.map(ModifiedClassPathClassLoader::toURL);
|
||||
}
|
||||
|
||||
private static URL toURL(String entry) {
|
||||
try {
|
||||
return new File(entry).toURI().toURL();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalArgumentException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isManifestOnlyJar(URL url) {
|
||||
return isShortenedIntelliJJar(url);
|
||||
}
|
||||
|
||||
private static boolean isShortenedIntelliJJar(URL url) {
|
||||
String urlPath = url.getPath();
|
||||
boolean isCandidate = INTELLIJ_CLASSPATH_JAR_PATTERN.matcher(urlPath).matches();
|
||||
if (isCandidate) {
|
||||
try {
|
||||
Attributes attributes = getManifestMainAttributesFromUrl(url);
|
||||
String createdBy = attributes.getValue("Created-By");
|
||||
return createdBy != null && createdBy.contains("IntelliJ");
|
||||
}
|
||||
catch (Exception ex) {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static List<URL> extractUrlsFromManifestClassPath(URL booterJar) {
|
||||
List<URL> urls = new ArrayList<>();
|
||||
try {
|
||||
for (String entry : getClassPath(booterJar)) {
|
||||
urls.add(new URL(entry));
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
return urls;
|
||||
}
|
||||
|
||||
private static String[] getClassPath(URL booterJar) throws Exception {
|
||||
Attributes attributes = getManifestMainAttributesFromUrl(booterJar);
|
||||
return StringUtils.delimitedListToStringArray(attributes.getValue(Attributes.Name.CLASS_PATH), " ");
|
||||
}
|
||||
|
||||
private static Attributes getManifestMainAttributesFromUrl(URL url) throws Exception {
|
||||
try (JarFile jarFile = new JarFile(new File(url.toURI()))) {
|
||||
return jarFile.getManifest().getMainAttributes();
|
||||
}
|
||||
}
|
||||
|
||||
private static URL[] processUrls(URL[] urls, List<MergedAnnotations> annotations) {
|
||||
ClassPathEntryFilter filter = new ClassPathEntryFilter(annotations);
|
||||
List<URL> additionalUrls = getAdditionalUrls(annotations);
|
||||
List<URL> processedUrls = new ArrayList<>(additionalUrls);
|
||||
for (URL url : urls) {
|
||||
if (!filter.isExcluded(url)) {
|
||||
processedUrls.add(url);
|
||||
}
|
||||
}
|
||||
return processedUrls.toArray(new URL[0]);
|
||||
}
|
||||
|
||||
private static List<URL> getAdditionalUrls(List<MergedAnnotations> annotations) {
|
||||
Set<URL> urls = new LinkedHashSet<>();
|
||||
for (MergedAnnotations candidate : annotations) {
|
||||
MergedAnnotation<ClassPathOverrides> annotation = candidate.get(ClassPathOverrides.class);
|
||||
if (annotation.isPresent()) {
|
||||
urls.addAll(resolveCoordinates(annotation.getStringArray(MergedAnnotation.VALUE)));
|
||||
}
|
||||
}
|
||||
return urls.stream().toList();
|
||||
}
|
||||
|
||||
private static List<URL> resolveCoordinates(String[] coordinates) {
|
||||
Exception latestFailure = null;
|
||||
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");
|
||||
RemoteRepository remoteRepository = new RemoteRepository.Builder("central", "default",
|
||||
"https://repo.maven.apache.org/maven2").build();
|
||||
session.setLocalRepositoryManager(repositorySystem.newLocalRepositoryManager(session, localRepository));
|
||||
for (int i = 0; i < MAX_RESOLUTION_ATTEMPTS; i++) {
|
||||
CollectRequest collectRequest = new CollectRequest(null, Arrays.asList(remoteRepository));
|
||||
collectRequest.setDependencies(createDependencies(coordinates));
|
||||
DependencyRequest dependencyRequest = new DependencyRequest(collectRequest, null);
|
||||
try {
|
||||
DependencyResult result = repositorySystem.resolveDependencies(session, dependencyRequest);
|
||||
List<URL> resolvedArtifacts = new ArrayList<>();
|
||||
for (ArtifactResult artifact : result.getArtifactResults()) {
|
||||
resolvedArtifacts.add(artifact.getArtifact().getFile().toURI().toURL());
|
||||
}
|
||||
return resolvedArtifacts;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
latestFailure = ex;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Resolution failed after " + MAX_RESOLUTION_ATTEMPTS + " attempts",
|
||||
latestFailure);
|
||||
}
|
||||
|
||||
private static List<Dependency> createDependencies(String[] allCoordinates) {
|
||||
List<Dependency> dependencies = new ArrayList<>();
|
||||
for (String coordinate : allCoordinates) {
|
||||
dependencies.add(new Dependency(new DefaultArtifact(coordinate), null));
|
||||
}
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter for class path entries.
|
||||
*/
|
||||
private static final class ClassPathEntryFilter {
|
||||
|
||||
private final List<String> exclusions;
|
||||
|
||||
private final AntPathMatcher matcher = new AntPathMatcher();
|
||||
|
||||
private ClassPathEntryFilter(List<MergedAnnotations> annotations) {
|
||||
Set<String> exclusions = new LinkedHashSet<>();
|
||||
for (MergedAnnotations candidate : annotations) {
|
||||
MergedAnnotation<ClassPathExclusions> annotation = candidate.get(ClassPathExclusions.class);
|
||||
if (annotation.isPresent()) {
|
||||
exclusions.addAll(Arrays.asList(annotation.getStringArray(MergedAnnotation.VALUE)));
|
||||
}
|
||||
}
|
||||
this.exclusions = exclusions.stream().toList();
|
||||
}
|
||||
|
||||
private boolean isExcluded(URL url) {
|
||||
if ("file".equals(url.getProtocol())) {
|
||||
try {
|
||||
String name = new File(url.toURI()).getName();
|
||||
for (String exclusion : this.exclusions) {
|
||||
if (this.matcher.match(exclusion, name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (URISyntaxException ex) {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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.cloud.test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URLClassLoader;
|
||||
|
||||
import org.junit.jupiter.api.extension.Extension;
|
||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||
import org.junit.jupiter.api.extension.InvocationInterceptor;
|
||||
import org.junit.jupiter.api.extension.ReflectiveInvocationContext;
|
||||
import org.junit.platform.engine.discovery.DiscoverySelectors;
|
||||
import org.junit.platform.launcher.Launcher;
|
||||
import org.junit.platform.launcher.LauncherDiscoveryRequest;
|
||||
import org.junit.platform.launcher.TestPlan;
|
||||
import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder;
|
||||
import org.junit.platform.launcher.core.LauncherFactory;
|
||||
import org.junit.platform.launcher.listeners.SummaryGeneratingListener;
|
||||
import org.junit.platform.launcher.listeners.TestExecutionSummary;
|
||||
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @author Christoph Dreis
|
||||
* @author Siva Krishna Battu
|
||||
* @see <a href=
|
||||
* "https://github.com/spring-projects/spring-boot/blob/main/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/ModifiedClassPathExtension.java">ModifiedClassPathExtension.java</a>
|
||||
*/
|
||||
class ModifiedClassPathExtension implements InvocationInterceptor {
|
||||
|
||||
@Override
|
||||
public void interceptBeforeAllMethod(Invocation<Void> invocation,
|
||||
ReflectiveInvocationContext<Method> invocationContext, ExtensionContext extensionContext) throws Throwable {
|
||||
intercept(invocation, extensionContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interceptBeforeEachMethod(Invocation<Void> invocation,
|
||||
ReflectiveInvocationContext<Method> invocationContext, ExtensionContext extensionContext) throws Throwable {
|
||||
intercept(invocation, extensionContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interceptAfterEachMethod(Invocation<Void> invocation,
|
||||
ReflectiveInvocationContext<Method> invocationContext, ExtensionContext extensionContext) throws Throwable {
|
||||
intercept(invocation, extensionContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interceptAfterAllMethod(Invocation<Void> invocation,
|
||||
ReflectiveInvocationContext<Method> invocationContext, ExtensionContext extensionContext) throws Throwable {
|
||||
intercept(invocation, extensionContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interceptTestMethod(Invocation<Void> invocation, ReflectiveInvocationContext<Method> invocationContext,
|
||||
ExtensionContext extensionContext) throws Throwable {
|
||||
interceptMethod(invocation, invocationContext, extensionContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interceptTestTemplateMethod(Invocation<Void> invocation,
|
||||
ReflectiveInvocationContext<Method> invocationContext, ExtensionContext extensionContext) throws Throwable {
|
||||
interceptMethod(invocation, invocationContext, extensionContext);
|
||||
}
|
||||
|
||||
private void interceptMethod(Invocation<Void> invocation, ReflectiveInvocationContext<Method> invocationContext,
|
||||
ExtensionContext extensionContext) throws Throwable {
|
||||
if (isModifiedClassPathClassLoader(extensionContext)) {
|
||||
invocation.proceed();
|
||||
return;
|
||||
}
|
||||
Class<?> testClass = extensionContext.getRequiredTestClass();
|
||||
Method testMethod = invocationContext.getExecutable();
|
||||
URLClassLoader modifiedClassLoader = ModifiedClassPathClassLoader.get(testClass, testMethod,
|
||||
invocationContext.getArguments());
|
||||
if (modifiedClassLoader == null) {
|
||||
invocation.proceed();
|
||||
return;
|
||||
}
|
||||
invocation.skip();
|
||||
ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
|
||||
Thread.currentThread().setContextClassLoader(modifiedClassLoader);
|
||||
try {
|
||||
runTest(extensionContext.getUniqueId());
|
||||
}
|
||||
finally {
|
||||
Thread.currentThread().setContextClassLoader(originalClassLoader);
|
||||
}
|
||||
}
|
||||
|
||||
private void runTest(String testId) throws Throwable {
|
||||
LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request()
|
||||
.selectors(DiscoverySelectors.selectUniqueId(testId)).build();
|
||||
Launcher launcher = LauncherFactory.create();
|
||||
TestPlan testPlan = launcher.discover(request);
|
||||
SummaryGeneratingListener listener = new SummaryGeneratingListener();
|
||||
launcher.registerTestExecutionListeners(listener);
|
||||
launcher.execute(testPlan);
|
||||
TestExecutionSummary summary = listener.getSummary();
|
||||
if (!CollectionUtils.isEmpty(summary.getFailures())) {
|
||||
throw summary.getFailures().get(0).getException();
|
||||
}
|
||||
}
|
||||
|
||||
private void intercept(Invocation<Void> invocation, ExtensionContext extensionContext) throws Throwable {
|
||||
if (isModifiedClassPathClassLoader(extensionContext)) {
|
||||
invocation.proceed();
|
||||
return;
|
||||
}
|
||||
invocation.skip();
|
||||
}
|
||||
|
||||
private boolean isModifiedClassPathClassLoader(ExtensionContext extensionContext) {
|
||||
Class<?> testClass = extensionContext.getRequiredTestClass();
|
||||
ClassLoader classLoader = testClass.getClassLoader();
|
||||
return classLoader.getClass().getName().equals(ModifiedClassPathClassLoader.class.getName());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -59,6 +59,7 @@ import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
* A custom {@link BlockJUnit4ClassRunner} that runs tests using a modified class path.
|
||||
* Entries are excluded from the class path using
|
||||
* {@link ClassPathExclusions @ClassPathExclusions} and overridden using
|
||||
@@ -67,7 +68,10 @@ import org.springframework.util.StringUtils;
|
||||
* the thread context class loader while the test is being run.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @deprecated Replaced by {@link ModifiedClassPathExtension}
|
||||
*/
|
||||
|
||||
@Deprecated
|
||||
public class ModifiedClassPathRunner extends BlockJUnit4ClassRunner {
|
||||
|
||||
private static final Pattern INTELLIJ_CLASSPATH_JAR_PATTERN = Pattern.compile(".*classpath(\\d+)?\\.jar");
|
||||
|
||||
@@ -17,18 +17,16 @@
|
||||
package org.springframework.cloud.test;
|
||||
|
||||
import org.hamcrest.Matcher;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.CoreMatchers.isA;
|
||||
|
||||
/**
|
||||
* Tests for {@link ModifiedClassPathRunner} excluding entries from the class path.
|
||||
* Tests for {@link ModifiedClassPathExtension} excluding entries from the class path.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@RunWith(ModifiedClassPathRunner.class)
|
||||
@ClassPathExclusions("hibernate-validator-*.jar")
|
||||
public class ModifiedClassPathRunnerExclusionsTests {
|
||||
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
|
||||
package org.springframework.cloud.test;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -25,11 +24,10 @@ import org.springframework.util.StringUtils;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ModifiedClassPathRunner} overriding entries on the class path.
|
||||
* Tests for {@link ModifiedClassPathExtension} overriding entries on the class path.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@RunWith(ModifiedClassPathRunner.class)
|
||||
@ClassPathOverrides("org.springframework:spring-context:4.1.0.RELEASE")
|
||||
public class ModifiedClassPathRunnerOverridesTests {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user