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>
This commit is contained in:
Stephane Nicoll
2022-09-15 15:32:50 +02:00
parent aa5d7398d5
commit e59da2de1e
11 changed files with 611 additions and 31 deletions

View File

@@ -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;
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;
*/
class DynamicJavaFileManagerTests {
private static final byte[] DUMMY_BYTECODE = new byte[] { 'a' };
@Mock
private JavaFileManager parentFileManager;
@@ -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 {
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);
}
}

View File

@@ -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 {
.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!");

View File

@@ -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"));
}
}

View File

@@ -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);
}
}