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

View File

@ -38,12 +38,12 @@ public class CompilationException extends RuntimeException {
message.append(errors); message.append(errors);
message.append("\n\n"); message.append("\n\n");
for (SourceFile sourceFile : sourceFiles) { 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(sourceFile.getContent());
message.append("\n\n"); message.append("\n\n");
} }
for (ResourceFile resourceFile : resourceFiles) { 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(resourceFile.getContent());
message.append("\n\n"); message.append("\n\n");
} }

View File

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

View File

@ -30,6 +30,7 @@ import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
/** /**
@ -65,12 +66,12 @@ public class DynamicClassLoader extends ClassLoader {
this.dynamicResourceFiles = dynamicResourceFiles; this.dynamicResourceFiles = dynamicResourceFiles;
Class<? extends ClassLoader> parentClass = parent.getClass(); Class<? extends ClassLoader> parentClass = parent.getClass();
if (parentClass.getName().equals(CompileWithForkedClassLoaderClassLoader.class.getName())) { if (parentClass.getName().equals(CompileWithForkedClassLoaderClassLoader.class.getName())) {
Method setClassResourceLookupMethod = ReflectionUtils.findMethod(parentClass, Method setClassResourceLookupMethod = lookupMethod(parentClass,
"setClassResourceLookup", Function.class); "setClassResourceLookup", Function.class);
ReflectionUtils.makeAccessible(setClassResourceLookupMethod); ReflectionUtils.makeAccessible(setClassResourceLookupMethod);
ReflectionUtils.invokeMethod(setClassResourceLookupMethod, ReflectionUtils.invokeMethod(setClassResourceLookupMethod,
getParent(), (Function<String, byte[]>) this::findClassBytes); getParent(), (Function<String, byte[]>) this::findClassBytes);
this.defineClassMethod = ReflectionUtils.findMethod(parentClass, this.defineClassMethod = lookupMethod(parentClass,
"defineDynamicClass", String.class, byte[].class, int.class, int.class); "defineDynamicClass", String.class, byte[].class, int.class, int.class);
ReflectionUtils.makeAccessible(this.defineClassMethod); ReflectionUtils.makeAccessible(this.defineClassMethod);
this.dynamicClassFiles.forEach((name, file) -> defineClass(name, file.getBytes())); this.dynamicClassFiles.forEach((name, file) -> defineClass(name, file.getBytes()));
@ -84,12 +85,13 @@ public class DynamicClassLoader extends ClassLoader {
@Override @Override
protected Class<?> findClass(String name) throws ClassNotFoundException { protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] bytes = findClassBytes(name); byte[] bytes = findClassBytes(name);
if(bytes != null) { if (bytes != null) {
return defineClass(name, bytes); return defineClass(name, bytes);
} }
return super.findClass(name); return super.findClass(name);
} }
@Nullable
private byte[] findClassBytes(String name) { private byte[] findClassBytes(String name) {
ClassFile classFile = this.classFiles.get(name); ClassFile classFile = this.classFiles.get(name);
if (classFile != null) { 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> { 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. * Assertion methods for {@code DynamicFile} instances.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Stephane Nicoll
* @since 6.0 * @since 6.0
* @param <A> the assertion type * @param <A> the assertion type
* @param <F> the file type * @param <F> the file type
@ -38,25 +39,35 @@ public class DynamicFileAssert<A extends DynamicFileAssert<A, F>, F extends Dyna
super(actual, selfType); 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) { public A contains(CharSequence... values) {
assertThat(this.actual.getContent()).contains(values); assertThat(this.actual.getContent()).contains(values);
return this.myself; 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) { public A doesNotContain(CharSequence... values) {
assertThat(this.actual.getContent()).doesNotContain(values); assertThat(this.actual.getContent()).doesNotContain(values);
return this.myself; 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.JavaFileObject;
import javax.tools.SimpleJavaFileObject; import javax.tools.SimpleJavaFileObject;
import org.springframework.lang.Nullable;
/** /**
* In-memory {@link JavaFileObject} used to hold generated resource file contents. * In-memory {@link JavaFileObject} used to hold generated resource file contents.
* *
@ -35,6 +37,7 @@ import javax.tools.SimpleJavaFileObject;
*/ */
class DynamicResourceFileObject extends SimpleJavaFileObject { class DynamicResourceFileObject extends SimpleJavaFileObject {
@Nullable
private volatile byte[] bytes; private volatile byte[] bytes;
@ -54,10 +57,11 @@ class DynamicResourceFileObject extends SimpleJavaFileObject {
@Override @Override
public InputStream openInputStream() throws IOException { public InputStream openInputStream() throws IOException {
if (this.bytes == null) { byte[] content = this.bytes;
if (content == null) {
throw new IOException("No data written"); throw new IOException("No data written");
} }
return new ByteArrayInputStream(this.bytes); return new ByteArrayInputStream(content);
} }
@Override @Override
@ -69,6 +73,7 @@ class DynamicResourceFileObject extends SimpleJavaFileObject {
this.bytes = bytes; this.bytes = bytes;
} }
@Nullable
byte[] getBytes() { byte[] getBytes() {
return this.bytes; return this.bytes;
} }

View File

@ -309,12 +309,12 @@ public final class TestCompiler {
*/ */
public TestCompiler printFiles(PrintStream printStream) { public TestCompiler printFiles(PrintStream printStream) {
for (SourceFile sourceFile : this.sourceFiles) { 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(sourceFile.getContent());
printStream.append("\n\n"); printStream.append("\n\n");
} }
for (ResourceFile resourceFile : this.resourceFiles) { 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(resourceFile.getContent());
printStream.append("\n\n"); printStream.append("\n\n");
} }

View File

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

View File

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