Browse Source

Allow test compilation to reference existing generated classes

Previously, the generated classes from an InMemoryGeneratedFiles were
not taken into account and if generated code refers to any of them,
compilation failed.

This commit introduces a ClasFile abstraction, similar to ResourceFile
for resources that represents an existing generated class.

Closes gh-29141

Co-authored-by: Andy Wilkinson <wilkinsona@vmware.com>
pull/29162/head
Stephane Nicoll 2 years ago
parent
commit
e59da2de1e
  1. 16
      spring-core-test/src/main/java/org/springframework/aot/test/generate/compile/DynamicClassLoader.java
  2. 72
      spring-core-test/src/main/java/org/springframework/aot/test/generate/compile/DynamicJavaFileManager.java
  3. 48
      spring-core-test/src/main/java/org/springframework/aot/test/generate/compile/TestCompiler.java
  4. 105
      spring-core-test/src/main/java/org/springframework/aot/test/generate/file/ClassFile.java
  5. 139
      spring-core-test/src/main/java/org/springframework/aot/test/generate/file/ClassFiles.java
  6. 44
      spring-core-test/src/test/java/org/springframework/aot/test/generate/compile/DynamicJavaFileManagerTests.java
  7. 42
      spring-core-test/src/test/java/org/springframework/aot/test/generate/compile/TestCompilerTests.java
  8. 60
      spring-core-test/src/test/java/org/springframework/aot/test/generate/file/ClassFileTests.java
  9. 116
      spring-core-test/src/test/java/org/springframework/aot/test/generate/file/ClassFilesTests.java
  10. BIN
      spring-core-test/src/test/resources/com.example.Messages
  11. BIN
      spring-core-test/src/test/resources/com.example.subpackage.Messages

16
spring-core-test/src/main/java/org/springframework/aot/test/generate/compile/DynamicClassLoader.java

@ -26,8 +26,9 @@ import java.net.URLConnection; @@ -26,8 +26,9 @@ import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.Map;
import org.springframework.aot.test.generate.file.ClassFile;
import org.springframework.aot.test.generate.file.ClassFiles;
import org.springframework.aot.test.generate.file.ResourceFile;
import org.springframework.aot.test.generate.file.ResourceFiles;
import org.springframework.lang.Nullable;
@ -44,14 +45,14 @@ public class DynamicClassLoader extends ClassLoader { @@ -44,14 +45,14 @@ public class DynamicClassLoader extends ClassLoader {
private final ResourceFiles resourceFiles;
private final Map<String, DynamicClassFileObject> classFiles;
private final ClassFiles classFiles;
@Nullable
private final Method defineClassMethod;
public DynamicClassLoader(ClassLoader parent, ResourceFiles resourceFiles,
Map<String, DynamicClassFileObject> classFiles) {
ClassFiles classFiles) {
super(parent);
this.resourceFiles = resourceFiles;
@ -77,15 +78,16 @@ public class DynamicClassLoader extends ClassLoader { @@ -77,15 +78,16 @@ public class DynamicClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
DynamicClassFileObject classFile = this.classFiles.get(name);
ClassFile classFile = this.classFiles.get(name);
if (classFile != null) {
return defineClass(name, classFile);
return defineClass(classFile);
}
return super.findClass(name);
}
private Class<?> defineClass(String name, DynamicClassFileObject classFile) {
byte[] bytes = classFile.getBytes();
private Class<?> defineClass(ClassFile classFile) {
String name = classFile.getName();
byte[] bytes = classFile.getContent();
if (this.defineClassMethod != null) {
return (Class<?>) ReflectionUtils.invokeMethod(this.defineClassMethod,
getParent(), name, bytes, 0, bytes.length);

72
spring-core-test/src/main/java/org/springframework/aot/test/generate/compile/DynamicJavaFileManager.java

@ -16,35 +16,51 @@ @@ -16,35 +16,51 @@
package org.springframework.aot.test.generate.compile;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.SimpleJavaFileObject;
import org.springframework.aot.test.generate.file.ClassFile;
import org.springframework.aot.test.generate.file.ClassFiles;
import org.springframework.util.ClassUtils;
/**
* {@link JavaFileManager} to create in-memory {@link DynamicClassFileObject
* ClassFileObjects} when compiling.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @since 6.0
*/
class DynamicJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
private final ClassFiles existingClasses;
private final ClassLoader classLoader;
private final Map<String, DynamicClassFileObject> classFiles = Collections.synchronizedMap(
private final Map<String, DynamicClassFileObject> compiledClasses = Collections.synchronizedMap(
new LinkedHashMap<>());
DynamicJavaFileManager(JavaFileManager fileManager, ClassLoader classLoader) {
DynamicJavaFileManager(JavaFileManager fileManager, ClassLoader classLoader,
ClassFiles existingClasses) {
super(fileManager);
this.classLoader = classLoader;
this.existingClasses = existingClasses;
}
@ -57,14 +73,60 @@ class DynamicJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> @@ -57,14 +73,60 @@ class DynamicJavaFileManager extends ForwardingJavaFileManager<JavaFileManager>
public JavaFileObject getJavaFileForOutput(Location location, String className,
JavaFileObject.Kind kind, FileObject sibling) throws IOException {
if (kind == JavaFileObject.Kind.CLASS) {
return this.classFiles.computeIfAbsent(className,
return this.compiledClasses.computeIfAbsent(className,
DynamicClassFileObject::new);
}
return super.getJavaFileForOutput(location, className, kind, sibling);
}
Map<String, DynamicClassFileObject> getClassFiles() {
return Collections.unmodifiableMap(this.classFiles);
@Override
public Iterable<JavaFileObject> list(Location location, String packageName,
Set<Kind> kinds, boolean recurse) throws IOException {
List<JavaFileObject> result = new ArrayList<>();
if (kinds.contains(Kind.CLASS)) {
for (ClassFile existingClass : this.existingClasses) {
String existingPackageName = ClassUtils.getPackageName(existingClass.getName());
if (existingPackageName.equals(packageName) || (recurse && existingPackageName.startsWith(packageName + "."))) {
result.add(new ClassFileJavaFileObject(existingClass));
}
}
}
Iterable<JavaFileObject> listed = super.list(location, packageName, kinds, recurse);
listed.forEach(result::add);
return result;
}
@Override
public String inferBinaryName(Location location, JavaFileObject file) {
if (file instanceof ClassFileJavaFileObject classFile) {
return classFile.getClassName();
}
return super.inferBinaryName(location, file);
}
ClassFiles getClassFiles() {
return this.existingClasses.and(this.compiledClasses.entrySet().stream().map(entry ->
ClassFile.of(entry.getKey(), entry.getValue().getBytes())).toList());
}
private static final class ClassFileJavaFileObject extends SimpleJavaFileObject {
private final ClassFile classFile;
private ClassFileJavaFileObject(ClassFile classFile) {
super(URI.create("class:///" + classFile.getName().replace('.', '/') + ".class"), Kind.CLASS);
this.classFile = classFile;
}
public String getClassName() {
return this.classFile.getName();
}
@Override
public InputStream openInputStream() {
return new ByteArrayInputStream(this.classFile.getContent());
}
}
}

48
spring-core-test/src/main/java/org/springframework/aot/test/generate/compile/TestCompiler.java

@ -35,6 +35,8 @@ import javax.tools.ToolProvider; @@ -35,6 +35,8 @@ import javax.tools.ToolProvider;
import org.springframework.aot.generate.GeneratedFiles.Kind;
import org.springframework.aot.generate.InMemoryGeneratedFiles;
import org.springframework.aot.test.generate.file.ClassFile;
import org.springframework.aot.test.generate.file.ClassFiles;
import org.springframework.aot.test.generate.file.ResourceFile;
import org.springframework.aot.test.generate.file.ResourceFiles;
import org.springframework.aot.test.generate.file.SourceFile;
@ -61,17 +63,20 @@ public final class TestCompiler { @@ -61,17 +63,20 @@ public final class TestCompiler {
private final ResourceFiles resourceFiles;
private final ClassFiles classFiles;
private final List<Processor> processors;
private TestCompiler(@Nullable ClassLoader classLoader, JavaCompiler compiler,
SourceFiles sourceFiles, ResourceFiles resourceFiles,
SourceFiles sourceFiles, ResourceFiles resourceFiles, ClassFiles classFiles,
List<Processor> processors) {
this.classLoader = classLoader;
this.compiler = compiler;
this.sourceFiles = sourceFiles;
this.resourceFiles = resourceFiles;
this.classFiles = classFiles;
this.processors = processors;
}
@ -91,12 +96,12 @@ public final class TestCompiler { @@ -91,12 +96,12 @@ public final class TestCompiler {
*/
public static TestCompiler forCompiler(JavaCompiler javaCompiler) {
return new TestCompiler(null, javaCompiler, SourceFiles.none(),
ResourceFiles.none(), Collections.emptyList());
ResourceFiles.none(), ClassFiles.none(), Collections.emptyList());
}
/**
* Create a new {@code TestCompiler} instance with additional generated
* source and resource files.
* source, resource, and class files.
* @param generatedFiles the generated files to add
* @return a new {@code TestCompiler} instance
*/
@ -107,7 +112,11 @@ public final class TestCompiler { @@ -107,7 +112,11 @@ public final class TestCompiler {
List<ResourceFile> resourceFiles = new ArrayList<>();
generatedFiles.getGeneratedFiles(Kind.RESOURCE).forEach(
(path, inputStreamSource) -> resourceFiles.add(ResourceFile.of(path, inputStreamSource)));
return withSources(sourceFiles).withResources(resourceFiles);
List<ClassFile> classFiles = new ArrayList<>();
generatedFiles.getGeneratedFiles(Kind.CLASS).forEach(
(path, inputStreamSource) -> classFiles.add(ClassFile.of(
ClassFile.toClassName(path), inputStreamSource)));
return withSources(sourceFiles).withResources(resourceFiles).withClasses(classFiles);
}
/**
@ -117,7 +126,8 @@ public final class TestCompiler { @@ -117,7 +126,8 @@ public final class TestCompiler {
*/
public TestCompiler withSources(SourceFile... sourceFiles) {
return new TestCompiler(this.classLoader, this.compiler,
this.sourceFiles.and(sourceFiles), this.resourceFiles, this.processors);
this.sourceFiles.and(sourceFiles), this.resourceFiles,
this.classFiles, this.processors);
}
/**
@ -127,7 +137,8 @@ public final class TestCompiler { @@ -127,7 +137,8 @@ public final class TestCompiler {
*/
public TestCompiler withSources(Iterable<SourceFile> sourceFiles) {
return new TestCompiler(this.classLoader, this.compiler,
this.sourceFiles.and(sourceFiles), this.resourceFiles, this.processors);
this.sourceFiles.and(sourceFiles), this.resourceFiles,
this.classFiles, this.processors);
}
/**
@ -137,7 +148,8 @@ public final class TestCompiler { @@ -137,7 +148,8 @@ public final class TestCompiler {
*/
public TestCompiler withSources(SourceFiles sourceFiles) {
return new TestCompiler(this.classLoader, this.compiler,
this.sourceFiles.and(sourceFiles), this.resourceFiles, this.processors);
this.sourceFiles.and(sourceFiles), this.resourceFiles,
this.classFiles, this.processors);
}
/**
@ -147,7 +159,7 @@ public final class TestCompiler { @@ -147,7 +159,7 @@ public final class TestCompiler {
*/
public TestCompiler withResources(ResourceFile... resourceFiles) {
return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles,
this.resourceFiles.and(resourceFiles), this.processors);
this.resourceFiles.and(resourceFiles), this.classFiles, this.processors);
}
/**
@ -157,7 +169,7 @@ public final class TestCompiler { @@ -157,7 +169,7 @@ public final class TestCompiler {
*/
public TestCompiler withResources(Iterable<ResourceFile> resourceFiles) {
return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles,
this.resourceFiles.and(resourceFiles), this.processors);
this.resourceFiles.and(resourceFiles), this.classFiles, this.processors);
}
/**
@ -167,7 +179,17 @@ public final class TestCompiler { @@ -167,7 +179,17 @@ public final class TestCompiler {
*/
public TestCompiler withResources(ResourceFiles resourceFiles) {
return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles,
this.resourceFiles.and(resourceFiles), this.processors);
this.resourceFiles.and(resourceFiles), this.classFiles, this.processors);
}
/**
* Create a new {@code TestCompiler} instance with additional classes.
* @param classFiles the additional classes
* @return a new {@code TestCompiler} instance
*/
public TestCompiler withClasses(Iterable<ClassFile> classFiles) {
return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles,
this.resourceFiles, this.classFiles.and(classFiles), this.processors);
}
/**
@ -179,7 +201,7 @@ public final class TestCompiler { @@ -179,7 +201,7 @@ public final class TestCompiler {
List<Processor> mergedProcessors = new ArrayList<>(this.processors);
mergedProcessors.addAll(Arrays.asList(processors));
return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles,
this.resourceFiles, mergedProcessors);
this.resourceFiles, this.classFiles, mergedProcessors);
}
/**
@ -191,7 +213,7 @@ public final class TestCompiler { @@ -191,7 +213,7 @@ public final class TestCompiler {
List<Processor> mergedProcessors = new ArrayList<>(this.processors);
processors.forEach(mergedProcessors::add);
return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles,
this.resourceFiles, mergedProcessors);
this.resourceFiles, this.classFiles, mergedProcessors);
}
/**
@ -269,7 +291,7 @@ public final class TestCompiler { @@ -269,7 +291,7 @@ public final class TestCompiler {
StandardJavaFileManager standardFileManager = this.compiler.getStandardFileManager(
null, null, null);
DynamicJavaFileManager fileManager = new DynamicJavaFileManager(
standardFileManager, classLoaderToUse);
standardFileManager, classLoaderToUse, this.classFiles);
if (!this.sourceFiles.isEmpty()) {
Errors errors = new Errors();
CompilationTask task = this.compiler.getTask(null, fileManager, errors, null,

105
spring-core-test/src/main/java/org/springframework/aot/test/generate/file/ClassFile.java

@ -0,0 +1,105 @@ @@ -0,0 +1,105 @@
/*
* Copyright 2002-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.aot.test.generate.file;
import java.io.IOException;
import org.springframework.core.io.InputStreamSource;
import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils;
/**
* In memory representation of a Java class.
*
* @author Stephane Nicoll
* @since 6.0
*/
public final class ClassFile {
private static final String CLASS_SUFFIX = ".class";
private final String name;
private final byte[] content;
private ClassFile(String name, byte[] content) {
this.name = name;
this.content = content;
}
/**
* Return the fully qualified name of the class.
* @return the class name
*/
public String getName() {
return this.name;
}
/**
* Return the bytecode content.
* @return the class content
*/
public byte[] getContent() {
return this.content;
}
/**
* Factory method to create a new {@link ClassFile} from the given
* {@code content}.
* @param name the fully qualified name of the class
* @param content the bytecode of the class
* @return a {@link ClassFile} instance
*/
public static ClassFile of(String name, byte[] content) {
return new ClassFile(name, content);
}
/**
* Factory method to create a new {@link ClassFile} from the given
* {@link InputStreamSource}.
* @param name the fully qualified name of the class
* @param inputStreamSource the bytecode of the class
* @return a {@link ClassFile} instance
*/
public static ClassFile of(String name, InputStreamSource inputStreamSource) {
return of(name, toBytes(inputStreamSource));
}
/**
* Return the name of a class based on its relative path.
* @param path the path of the class
* @return the class name
*/
public static String toClassName(String path) {
Assert.hasText(path, "'path' must not be empty");
if (!path.endsWith(CLASS_SUFFIX)) {
throw new IllegalArgumentException("Path '" + path + "' must end with '.class'");
}
String name = path.replace('/', '.');
return name.substring(0, name.length() - CLASS_SUFFIX.length());
}
private static byte[] toBytes(InputStreamSource inputStreamSource) {
try {
return FileCopyUtils.copyToByteArray(inputStreamSource.getInputStream());
}
catch (IOException ex) {
throw new IllegalStateException("Unable to read content", ex);
}
}
}

139
spring-core-test/src/main/java/org/springframework/aot/test/generate/file/ClassFiles.java

@ -0,0 +1,139 @@ @@ -0,0 +1,139 @@
/*
* Copyright 2002-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.aot.test.generate.file;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Stream;
import org.springframework.lang.Nullable;
/**
* An immutable collection of {@link ClassFile} instances.
*
* @author Stephane Nicoll
* @since 6.0
*/
public final class ClassFiles implements Iterable<ClassFile> {
private static final ClassFiles NONE = new ClassFiles(Collections.emptyMap());
private final Map<String, ClassFile> files;
private ClassFiles(Map<String, ClassFile> files) {
this.files = files;
}
/**
* Return a {@link ClassFiles} instance with no items.
* @return the empty instance
*/
public static ClassFiles none() {
return NONE;
}
/**
* Factory method that can be used to create a {@link ClassFiles}
* instance containing the specified classes.
* @param ClassFiles the classes to include
* @return a {@link ClassFiles} instance
*/
public static ClassFiles of(ClassFile... ClassFiles) {
return none().and(ClassFiles);
}
/**
* Return a new {@link ClassFiles} instance that merges classes from
* another array of {@link ClassFile} instances.
* @param classFiles the instances to merge
* @return a new {@link ClassFiles} instance containing merged content
*/
public ClassFiles and(ClassFile... classFiles) {
Map<String, ClassFile> merged = new LinkedHashMap<>(this.files);
Arrays.stream(classFiles).forEach(file -> merged.put(file.getName(), file));
return new ClassFiles(Collections.unmodifiableMap(merged));
}
/**
* Return a new {@link ClassFiles} instance that merges classes from another
* iterable of {@link ClassFiles} instances.
* @param classFiles the instances to merge
* @return a new {@link ClassFiles} instance containing merged content
*/
public ClassFiles and(Iterable<ClassFile> classFiles) {
Map<String, ClassFile> merged = new LinkedHashMap<>(this.files);
classFiles.forEach(file -> merged.put(file.getName(), file));
return new ClassFiles(Collections.unmodifiableMap(merged));
}
@Override
public Iterator<ClassFile> iterator() {
return this.files.values().iterator();
}
/**
* Stream the {@link ClassFile} instances contained in this collection.
* @return a stream of classes
*/
public Stream<ClassFile> stream() {
return this.files.values().stream();
}
/**
* Returns {@code true} if this collection is empty.
* @return if this collection is empty
*/
public boolean isEmpty() {
return this.files.isEmpty();
}
/**
* Get the {@link ClassFile} with the given class name.
* @param name the fully qualified name to find
* @return a {@link ClassFile} instance or {@code null}
*/
@Nullable
public ClassFile get(String name) {
return this.files.get(name);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
return this.files.equals(((ClassFiles) obj).files);
}
@Override
public int hashCode() {
return this.files.hashCode();
}
@Override
public String toString() {
return this.files.toString();
}
}

44
spring-core-test/src/test/java/org/springframework/aot/test/generate/compile/DynamicJavaFileManagerTests.java

@ -16,6 +16,9 @@ @@ -16,6 +16,9 @@
package org.springframework.aot.test.generate.compile;
import java.io.IOException;
import java.util.EnumSet;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileManager.Location;
import javax.tools.JavaFileObject;
@ -26,6 +29,9 @@ import org.junit.jupiter.api.Test; @@ -26,6 +29,9 @@ import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.aot.test.generate.file.ClassFile;
import org.springframework.aot.test.generate.file.ClassFiles;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.then;
@ -36,6 +42,8 @@ import static org.mockito.BDDMockito.then; @@ -36,6 +42,8 @@ import static org.mockito.BDDMockito.then;
*/
class DynamicJavaFileManagerTests {
private static final byte[] DUMMY_BYTECODE = new byte[] { 'a' };
@Mock
private JavaFileManager parentFileManager;
@ -49,10 +57,10 @@ class DynamicJavaFileManagerTests { @@ -49,10 +57,10 @@ class DynamicJavaFileManagerTests {
@BeforeEach
void setup() {
MockitoAnnotations.openMocks(this);
this.classLoader = new ClassLoader() {
};
this.fileManager = new DynamicJavaFileManager(this.parentFileManager,
this.classLoader);
this.classLoader = new ClassLoader() {};
this.fileManager = new DynamicJavaFileManager(this.parentFileManager, this.classLoader,
ClassFiles.of(ClassFile.of("com.example.one.ClassOne", DUMMY_BYTECODE),
ClassFile.of("com.example.two.ClassTwo", DUMMY_BYTECODE)));
}
@Test
@ -93,8 +101,32 @@ class DynamicJavaFileManagerTests { @@ -93,8 +101,32 @@ class DynamicJavaFileManagerTests {
Kind.CLASS, null);
this.fileManager.getJavaFileForOutput(this.location, "com.example.MyClass2",
Kind.CLASS, null);
assertThat(this.fileManager.getClassFiles()).containsKeys("com.example.MyClass1",
"com.example.MyClass2");
assertThat(this.fileManager.getClassFiles().stream().map(ClassFile::getName))
.contains("com.example.MyClass1", "com.example.MyClass2");
}
@Test
void listWithoutRecurseReturnsClassesInRequestedPackage() throws IOException {
Iterable<JavaFileObject> listed = this.fileManager.list(
this.location, "com.example.one", EnumSet.allOf(Kind.class), false);
assertThat(listed).hasSize(1);
assertThat(listed).extracting(JavaFileObject::getName).containsExactly("/com/example/one/ClassOne.class");
}
@Test
void listWithRecurseReturnsClassesInRequestedPackageAndSubpackages() throws IOException {
Iterable<JavaFileObject> listed = this.fileManager.list(
this.location, "com.example", EnumSet.allOf(Kind.class), true);
assertThat(listed).hasSize(2);
assertThat(listed).extracting(JavaFileObject::getName)
.containsExactly("/com/example/one/ClassOne.class", "/com/example/two/ClassTwo.class");
}
@Test
void listWithoutClassKindDoesNotReturnClasses() throws IOException {
Iterable<JavaFileObject> listed = this.fileManager.list(
this.location, "com.example", EnumSet.of(Kind.SOURCE), true);
assertThat(listed).hasSize(0);
}
}

42
spring-core-test/src/test/java/org/springframework/aot/test/generate/compile/TestCompilerTests.java

@ -30,11 +30,13 @@ import javax.lang.model.element.TypeElement; @@ -30,11 +30,13 @@ import javax.lang.model.element.TypeElement;
import com.example.PublicInterface;
import org.junit.jupiter.api.Test;
import org.springframework.aot.test.generate.file.ClassFile;
import org.springframework.aot.test.generate.file.ResourceFile;
import org.springframework.aot.test.generate.file.ResourceFiles;
import org.springframework.aot.test.generate.file.SourceFile;
import org.springframework.aot.test.generate.file.SourceFiles;
import org.springframework.aot.test.generate.file.WritableContent;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.ClassUtils;
import static org.assertj.core.api.Assertions.assertThat;
@ -241,6 +243,46 @@ class TestCompilerTests { @@ -241,6 +243,46 @@ class TestCompilerTests {
.withMessageContaining(ClassUtils.getShortName(CompileWithTargetClassAccess.class));
}
@Test
void compiledCodeCanReferenceAdditionalClassInSamePackage() {
SourceFiles sourceFiles = SourceFiles.of(SourceFile.of("""
package com.example;
public class Test implements PublicInterface {
public String perform() {
return Messages.HELLO;
}
}
"""));
ClassFile messagesClass = ClassFile.of("com.example.Messages",
new ClassPathResource("com.example.Messages"));
TestCompiler.forSystem().withClasses(List.of(messagesClass)).compile(sourceFiles, compiled ->
assertThat(compiled.getInstance(PublicInterface.class, "com.example.Test").perform())
.isEqualTo("Hello"));
}
@Test
void compiledCodeCanReferenceAdditionalClassInDifferentPackage() {
SourceFiles sourceFiles = SourceFiles.of(SourceFile.of("""
package com.example;
import com.example.subpackage.Messages;
public class Test implements PublicInterface {
public String perform() {
return Messages.HELLO;
}
}
"""));
ClassFile messagesClass = ClassFile.of("com.example.subpackage.Messages",
new ClassPathResource("com.example.subpackage.Messages"));
TestCompiler.forSystem().withClasses(List.of(messagesClass)).compile(sourceFiles, compiled -> assertThat(
compiled.getInstance(PublicInterface.class, "com.example.Test").perform()).isEqualTo("Hello from subpackage"));
}
private void assertSuppliesHelloWorld(Compiled compiled) {
assertThat(compiled.getInstance(Supplier.class).get()).isEqualTo("Hello World!");

60
spring-core-test/src/test/java/org/springframework/aot/test/generate/file/ClassFileTests.java

@ -0,0 +1,60 @@ @@ -0,0 +1,60 @@
/*
* Copyright 2002-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.aot.test.generate.file;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ByteArrayResource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link ClassFile}.
*
* @author Stephane Nicoll
*/
class ClassFileTests {
private final static byte[] TEST_CONTENT = new byte[]{'a'};
@Test
void ofNameAndByteArrayCreatesClass() {
ClassFile classFile = ClassFile.of("com.example.Test", TEST_CONTENT);
assertThat(classFile.getName()).isEqualTo("com.example.Test");
assertThat(classFile.getContent()).isEqualTo(TEST_CONTENT);
}
@Test
void ofNameAndInputStreamResourceCreatesClass() {
ClassFile classFile = ClassFile.of("com.example.Test",
new ByteArrayResource(TEST_CONTENT));
assertThat(classFile.getName()).isEqualTo("com.example.Test");
assertThat(classFile.getContent()).isEqualTo(TEST_CONTENT);
}
@Test
void toClassNameWithPathToClassFile() {
assertThat(ClassFile.toClassName("com/example/Test.class")).isEqualTo("com.example.Test");
}
@Test
void toClassNameWithPathToTextFile() {
assertThatIllegalArgumentException().isThrownBy(() -> ClassFile.toClassName("com/example/Test.txt"));
}
}

116
spring-core-test/src/test/java/org/springframework/aot/test/generate/file/ClassFilesTests.java

@ -0,0 +1,116 @@ @@ -0,0 +1,116 @@
/*
* Copyright 2002-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.aot.test.generate.file;
import java.util.Iterator;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatObject;
/**
* Tests for {@link ClassFiles}.
*
* @author Stephane Nicoll
*/
class ClassFilesTests {
private static final ClassFile CLASS_FILE_1 = ClassFile.of(
"com.example.Test1", new byte[] { 'a' });
private static final ClassFile CLASS_FILE_2 = ClassFile.of(
"com.example.Test2", new byte[] { 'b' });
@Test
void noneReturnsNone() {
ClassFiles none = ClassFiles.none();
assertThat(none).isNotNull();
assertThat(none.isEmpty()).isTrue();
}
@Test
void ofCreatesClassFiles() {
ClassFiles classFiles = ClassFiles.of(CLASS_FILE_1, CLASS_FILE_2);
assertThat(classFiles).containsExactly(CLASS_FILE_1, CLASS_FILE_2);
}
@Test
void andAddsClassFiles() {
ClassFiles classFiles = ClassFiles.of(CLASS_FILE_1);
ClassFiles added = classFiles.and(CLASS_FILE_2);
assertThat(classFiles).containsExactly(CLASS_FILE_1);
assertThat(added).containsExactly(CLASS_FILE_1, CLASS_FILE_2);
}
@Test
void andClassFilesAddsClassFiles() {
ClassFiles classFiles = ClassFiles.of(CLASS_FILE_1);
ClassFiles added = classFiles.and(ClassFiles.of(CLASS_FILE_2));
assertThat(classFiles).containsExactly(CLASS_FILE_1);
assertThat(added).containsExactly(CLASS_FILE_1, CLASS_FILE_2);
}
@Test
void iteratorIteratesClassFiles() {
ClassFiles classFiles = ClassFiles.of(CLASS_FILE_1, CLASS_FILE_2);
Iterator<ClassFile> iterator = classFiles.iterator();
assertThat(iterator.next()).isEqualTo(CLASS_FILE_1);
assertThat(iterator.next()).isEqualTo(CLASS_FILE_2);
assertThat(iterator.hasNext()).isFalse();
}
@Test
void streamStreamsClassFiles() {
ClassFiles classFiles = ClassFiles.of(CLASS_FILE_1, CLASS_FILE_2);
assertThat(classFiles.stream()).containsExactly(CLASS_FILE_1, CLASS_FILE_2);
}
@Test
void isEmptyWhenEmptyReturnsTrue() {
ClassFiles classFiles = ClassFiles.of();
assertThat(classFiles.isEmpty()).isTrue();
}
@Test
void isEmptyWhenNotEmptyReturnsFalse() {
ClassFiles classFiles = ClassFiles.of(CLASS_FILE_1);
assertThat(classFiles.isEmpty()).isFalse();
}
@Test
void getWhenHasFileReturnsFile() {
ClassFiles classFiles = ClassFiles.of(CLASS_FILE_1);
assertThat(classFiles.get("com.example.Test1")).isNotNull();
}
@Test
void getWhenMissingFileReturnsNull() {
ClassFiles classFiles = ClassFiles.of(CLASS_FILE_2);
assertThatObject(classFiles.get("com.example.another.Test2")).isNull();
}
@Test
void equalsAndHashCode() {
ClassFiles s1 = ClassFiles.of(CLASS_FILE_1, CLASS_FILE_2);
ClassFiles s2 = ClassFiles.of(CLASS_FILE_1, CLASS_FILE_2);
ClassFiles s3 = ClassFiles.of(CLASS_FILE_1);
assertThat(s1.hashCode()).isEqualTo(s2.hashCode());
assertThatObject(s1).isEqualTo(s2).isNotEqualTo(s3);
}
}

BIN
spring-core-test/src/test/resources/com.example.Messages

Binary file not shown.

BIN
spring-core-test/src/test/resources/com.example.subpackage.Messages

Binary file not shown.
Loading…
Cancel
Save