This commit is contained in:
Stephane Nicoll 2022-09-20 13:03:03 +02:00
parent 321092ce6f
commit 2f84096af1
9 changed files with 61 additions and 32 deletions

View File

@ -17,6 +17,7 @@
package org.springframework.core.test.io.support;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
@ -58,7 +59,8 @@ public class MockSpringFactoriesLoader extends SpringFactoriesLoader {
this(classLoader, new LinkedHashMap<>());
}
protected MockSpringFactoriesLoader(ClassLoader classLoader, Map<String, List<String>> factories) {
protected MockSpringFactoriesLoader(@Nullable ClassLoader classLoader,
Map<String, List<String>> factories) {
super(classLoader, factories);
this.factories = factories;
}
@ -66,8 +68,8 @@ public class MockSpringFactoriesLoader extends SpringFactoriesLoader {
@Override
@SuppressWarnings("unchecked")
protected <T> T instantiateFactory(String implementationName, Class<T> type, ArgumentResolver argumentResolver,
FailureHandler failureHandler) {
protected <T> T instantiateFactory(String implementationName, Class<T> type,
@Nullable ArgumentResolver argumentResolver, FailureHandler failureHandler) {
if (implementationName.startsWith("!")) {
Object implementation = this.implementations.get(implementationName);
if (implementation != null) {
@ -83,7 +85,6 @@ public class MockSpringFactoriesLoader extends SpringFactoriesLoader {
* @param factoryImplementations the implementation classes
*/
@SafeVarargs
@SuppressWarnings("unchecked")
public final <T> void add(Class<T> factoryType, Class<? extends T>... factoryImplementations) {
for (Class<? extends T> factoryImplementation : factoryImplementations) {
add(factoryType.getName(), factoryImplementation.getName());
@ -96,10 +97,9 @@ public class MockSpringFactoriesLoader extends SpringFactoriesLoader {
* @param factoryImplementations the implementation class names
*/
public void add(String factoryType, String... factoryImplementations) {
List<String> implementations = this.factories.computeIfAbsent(factoryType, key -> new ArrayList<>());
for (String factoryImplementation : factoryImplementations) {
implementations.add(factoryImplementation);
}
List<String> implementations = this.factories.computeIfAbsent(
factoryType, key -> new ArrayList<>());
Collections.addAll(implementations, factoryImplementations);
}
/**

View File

@ -38,12 +38,12 @@ public class CompilationException extends RuntimeException {
message.append(errors);
message.append("\n\n");
for (SourceFile sourceFile : sourceFiles) {
message.append("---- source: " + sourceFile.getPath() + "\n\n");
message.append("---- source: ").append(sourceFile.getPath()).append("\n\n");
message.append(sourceFile.getContent());
message.append("\n\n");
}
for (ResourceFile resourceFile : resourceFiles) {
message.append("---- resource: " + resourceFile.getPath() + "\n\n");
message.append("---- resource: ").append(resourceFile.getPath()).append("\n\n");
message.append(resourceFile.getContent());
message.append("\n\n");
}

View File

@ -26,6 +26,8 @@ import java.net.URI;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import org.springframework.lang.Nullable;
/**
* In-memory {@link JavaFileObject} used to hold class bytecode.
*
@ -36,6 +38,7 @@ class DynamicClassFileObject extends SimpleJavaFileObject {
private final String className;
@Nullable
private volatile byte[] bytes;
@ -57,10 +60,11 @@ class DynamicClassFileObject extends SimpleJavaFileObject {
@Override
public InputStream openInputStream() throws IOException {
if (this.bytes == null) {
byte[] content = this.bytes;
if (content == null) {
throw new IOException("No data written");
}
return new ByteArrayInputStream(this.bytes);
return new ByteArrayInputStream(content);
}
@Override
@ -76,6 +80,7 @@ class DynamicClassFileObject extends SimpleJavaFileObject {
return this.className;
}
@Nullable
byte[] getBytes() {
return this.bytes;
}

View File

@ -30,6 +30,7 @@ import java.util.function.Function;
import java.util.function.Supplier;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
/**
@ -65,12 +66,12 @@ public class DynamicClassLoader extends ClassLoader {
this.dynamicResourceFiles = dynamicResourceFiles;
Class<? extends ClassLoader> parentClass = parent.getClass();
if (parentClass.getName().equals(CompileWithForkedClassLoaderClassLoader.class.getName())) {
Method setClassResourceLookupMethod = ReflectionUtils.findMethod(parentClass,
Method setClassResourceLookupMethod = lookupMethod(parentClass,
"setClassResourceLookup", Function.class);
ReflectionUtils.makeAccessible(setClassResourceLookupMethod);
ReflectionUtils.invokeMethod(setClassResourceLookupMethod,
getParent(), (Function<String, byte[]>) this::findClassBytes);
this.defineClassMethod = ReflectionUtils.findMethod(parentClass,
this.defineClassMethod = lookupMethod(parentClass,
"defineDynamicClass", String.class, byte[].class, int.class, int.class);
ReflectionUtils.makeAccessible(this.defineClassMethod);
this.dynamicClassFiles.forEach((name, file) -> defineClass(name, file.getBytes()));
@ -84,12 +85,13 @@ public class DynamicClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] bytes = findClassBytes(name);
if(bytes != null) {
if (bytes != null) {
return defineClass(name, bytes);
}
return super.findClass(name);
}
@Nullable
private byte[] findClassBytes(String name) {
ClassFile classFile = this.classFiles.get(name);
if (classFile != null) {
@ -141,6 +143,12 @@ public class DynamicClassLoader extends ClassLoader {
}
}
private static Method lookupMethod(Class<?> target, String name, Class<?>... parameterTypes) {
Method method = ReflectionUtils.findMethod(target, name, parameterTypes);
Assert.notNull(method, "Expected method '" + name + "' on '" + target.getName());
return method;
}
private static class SingletonEnumeration<E> implements Enumeration<E> {

View File

@ -26,6 +26,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* Assertion methods for {@code DynamicFile} instances.
*
* @author Phillip Webb
* @author Stephane Nicoll
* @since 6.0
* @param <A> the assertion type
* @param <F> the file type
@ -38,25 +39,35 @@ public class DynamicFileAssert<A extends DynamicFileAssert<A, F>, F extends Dyna
super(actual, selfType);
}
/**
* Verify that the actual content is equal to the given one.
* @param content the expected content of the file
* @return {@code this}, to facilitate method chaining
*/
public A hasContent(@Nullable CharSequence content) {
assertThat(this.actual.getContent()).isEqualTo(
content != null ? content.toString() : null);
return this.myself;
}
/**
* Verify that the actual content contains all the given values.
* @param values the values to look for
* @return {@code this}, to facilitate method chaining
*/
public A contains(CharSequence... values) {
assertThat(this.actual.getContent()).contains(values);
return this.myself;
}
/**
* Verify that the actual content does not contain any of the given values.
* @param values the values to look for
* @return {@code this}, to facilitate method chaining
*/
public A doesNotContain(CharSequence... values) {
assertThat(this.actual.getContent()).doesNotContain(values);
return this.myself;
}
@Override
public A isEqualTo(@Nullable Object expected) {
if (expected instanceof DynamicFile) {
return super.isEqualTo(expected);
}
assertThat(this.actual.getContent()).isEqualTo(
expected != null ? expected.toString() : null);
return this.myself;
}
}

View File

@ -26,6 +26,8 @@ import java.net.URI;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import org.springframework.lang.Nullable;
/**
* In-memory {@link JavaFileObject} used to hold generated resource file contents.
*
@ -35,6 +37,7 @@ import javax.tools.SimpleJavaFileObject;
*/
class DynamicResourceFileObject extends SimpleJavaFileObject {
@Nullable
private volatile byte[] bytes;
@ -54,10 +57,11 @@ class DynamicResourceFileObject extends SimpleJavaFileObject {
@Override
public InputStream openInputStream() throws IOException {
if (this.bytes == null) {
byte[] content = this.bytes;
if (content == null) {
throw new IOException("No data written");
}
return new ByteArrayInputStream(this.bytes);
return new ByteArrayInputStream(content);
}
@Override
@ -69,6 +73,7 @@ class DynamicResourceFileObject extends SimpleJavaFileObject {
this.bytes = bytes;
}
@Nullable
byte[] getBytes() {
return this.bytes;
}

View File

@ -309,12 +309,12 @@ public final class TestCompiler {
*/
public TestCompiler printFiles(PrintStream printStream) {
for (SourceFile sourceFile : this.sourceFiles) {
printStream.append("---- source: " + sourceFile.getPath() + "\n\n");
printStream.append("---- source: ").append(sourceFile.getPath()).append("\n\n");
printStream.append(sourceFile.getContent());
printStream.append("\n\n");
}
for (ResourceFile resourceFile : this.resourceFiles) {
printStream.append("---- resource: " + resourceFile.getPath() + "\n\n");
printStream.append("---- resource: ").append(resourceFile.getPath()).append("\n\n");
printStream.append(resourceFile.getContent());
printStream.append("\n\n");
}

View File

@ -66,7 +66,7 @@ class MockSpringFactoriesLoaderTests {
assertThat(factories.get(1)).isInstanceOf(TestFactoryTwo.class);
}
static interface TestFactoryType {
interface TestFactoryType {
}

View File

@ -65,13 +65,13 @@ class SourceFileAssertTests {
@Test
void isEqualToWhenEqual() {
assertThat(this.sourceFile).isEqualTo(SAMPLE);
assertThat(this.sourceFile).hasContent(SAMPLE);
}
@Test
void isEqualToWhenNotEqualThrowsException() {
assertThatExceptionOfType(AssertionError.class).isThrownBy(
() -> assertThat(this.sourceFile).isEqualTo("no")).withMessageContaining(
() -> assertThat(this.sourceFile).hasContent("no")).withMessageContaining(
"expected", "but was");
}