Browse Source

Add module to support testing of generated code

Add a new unpublished `spring-core-test` module to support testing of
generated code. The module include a `TestCompiler` class which can be
used to dynamically compile generated Java code. It also include an
AssertJ friendly `SourceFile` class which uses qdox to provide targeted
assertions on specific parts of a generated source file.

See gh-28120
pull/28170/head
Phillip Webb 3 years ago committed by Stephane Nicoll
parent
commit
653dc5951d
  1. 1
      build.gradle
  2. 2
      framework-bom/framework-bom.gradle
  3. 1
      settings.gradle
  4. 13
      spring-core-test/spring-core-test.gradle
  5. 32
      spring-core-test/src/main/java/org/springframework/aot/test/generator/compile/CompilationException.java
  6. 169
      spring-core-test/src/main/java/org/springframework/aot/test/generator/compile/Compiled.java
  7. 63
      spring-core-test/src/main/java/org/springframework/aot/test/generator/compile/DynamicClassFileObject.java
  8. 186
      spring-core-test/src/main/java/org/springframework/aot/test/generator/compile/DynamicClassLoader.java
  9. 71
      spring-core-test/src/main/java/org/springframework/aot/test/generator/compile/DynamicJavaFileManager.java
  10. 50
      spring-core-test/src/main/java/org/springframework/aot/test/generator/compile/DynamicJavaFileObject.java
  11. 242
      spring-core-test/src/main/java/org/springframework/aot/test/generator/compile/TestCompiler.java
  12. 105
      spring-core-test/src/main/java/org/springframework/aot/test/generator/file/DynamicFile.java
  13. 54
      spring-core-test/src/main/java/org/springframework/aot/test/generator/file/DynamicFileAssert.java
  14. 114
      spring-core-test/src/main/java/org/springframework/aot/test/generator/file/DynamicFiles.java
  15. 63
      spring-core-test/src/main/java/org/springframework/aot/test/generator/file/MethodAssert.java
  16. 72
      spring-core-test/src/main/java/org/springframework/aot/test/generator/file/ResourceFile.java
  17. 34
      spring-core-test/src/main/java/org/springframework/aot/test/generator/file/ResourceFileAssert.java
  18. 144
      spring-core-test/src/main/java/org/springframework/aot/test/generator/file/ResourceFiles.java
  19. 161
      spring-core-test/src/main/java/org/springframework/aot/test/generator/file/SourceFile.java
  20. 128
      spring-core-test/src/main/java/org/springframework/aot/test/generator/file/SourceFileAssert.java
  21. 144
      spring-core-test/src/main/java/org/springframework/aot/test/generator/file/SourceFiles.java
  22. 39
      spring-core-test/src/main/java/org/springframework/aot/test/generator/file/WritableContent.java
  23. 38
      spring-core-test/src/test/java/org/springframework/aot/test/generator/compile/CompilationExceptionTests.java
  24. 194
      spring-core-test/src/test/java/org/springframework/aot/test/generator/compile/CompiledTests.java
  25. 60
      spring-core-test/src/test/java/org/springframework/aot/test/generator/compile/DynamicClassFileObjectTests.java
  26. 101
      spring-core-test/src/test/java/org/springframework/aot/test/generator/compile/DynamicJavaFileManagerTests.java
  27. 47
      spring-core-test/src/test/java/org/springframework/aot/test/generator/compile/DynamicJavaFileObjectTests.java
  28. 179
      spring-core-test/src/test/java/org/springframework/aot/test/generator/compile/TestCompilerTests.java
  29. 73
      spring-core-test/src/test/java/org/springframework/aot/test/generator/file/MethodAssertTests.java
  30. 52
      spring-core-test/src/test/java/org/springframework/aot/test/generator/file/ResourceFileTests.java
  31. 131
      spring-core-test/src/test/java/org/springframework/aot/test/generator/file/ResourceFilesTests.java
  32. 128
      spring-core-test/src/test/java/org/springframework/aot/test/generator/file/SourceFileAssertTests.java
  33. 138
      spring-core-test/src/test/java/org/springframework/aot/test/generator/file/SourceFileTests.java
  34. 135
      spring-core-test/src/test/java/org/springframework/aot/test/generator/file/SourceFilesTests.java

1
build.gradle

@ -74,6 +74,7 @@ configure(allprojects) { project -> @@ -74,6 +74,7 @@ configure(allprojects) { project ->
dependency "com.google.code.gson:gson:2.8.9"
dependency "com.google.protobuf:protobuf-java-util:3.19.3"
dependency "com.googlecode.protobuf-java-format:protobuf-java-format:1.4"
dependency "com.thoughtworks.qdox:qdox:2.0.1"
dependency("com.thoughtworks.xstream:xstream:1.4.18") {
exclude group: "xpp3", name: "xpp3_min"
exclude group: "xmlpull", name: "xmlpull"

2
framework-bom/framework-bom.gradle

@ -7,7 +7,7 @@ group = "org.springframework" @@ -7,7 +7,7 @@ group = "org.springframework"
dependencies {
constraints {
parent.moduleProjects.sort { "$it.name" }.each {
parent.moduleProjects.findAll{ it.name != 'spring-core-test' }.sort{ "$it.name" }.each {
api it
}
}

1
settings.gradle

@ -18,6 +18,7 @@ include "spring-context" @@ -18,6 +18,7 @@ include "spring-context"
include "spring-context-indexer"
include "spring-context-support"
include "spring-core"
include "spring-core-test"
include "spring-expression"
include "spring-instrument"
include "spring-jcl"

13
spring-core-test/spring-core-test.gradle

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
description = "Spring Core Test"
dependencies {
api(project(":spring-core"))
api("org.assertj:assertj-core")
api("com.thoughtworks.qdox:qdox")
}
tasks.withType(PublishToMavenRepository).configureEach {
it.enabled = false
}

32
spring-core-test/src/main/java/org/springframework/aot/test/generator/compile/CompilationException.java

@ -0,0 +1,32 @@ @@ -0,0 +1,32 @@
/*
* 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.generator.compile;
/**
* Exception thrown when code cannot compile.
*
* @author Phillip Webb
* @since 6.0
*/
@SuppressWarnings("serial")
public class CompilationException extends RuntimeException {
CompilationException(String message) {
super(message);
}
}

169
spring-core-test/src/main/java/org/springframework/aot/test/generator/compile/Compiled.java

@ -0,0 +1,169 @@ @@ -0,0 +1,169 @@
/*
* 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.generator.compile;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.aot.test.generator.file.ResourceFile;
import org.springframework.aot.test.generator.file.ResourceFiles;
import org.springframework.aot.test.generator.file.SourceFile;
import org.springframework.aot.test.generator.file.SourceFiles;
import org.springframework.util.Assert;
/**
* Fully compiled results provided from a {@link TestCompiler}.
*
* @author Phillip Webb
* @since 6.0
*/
public class Compiled {
private final ClassLoader classLoader;
private final SourceFiles sourceFiles;
private final ResourceFiles resourceFiles;
private List<Class<?>> compiledClasses;
Compiled(ClassLoader classLoader, SourceFiles sourceFiles,
ResourceFiles resourceFiles) {
this.classLoader = classLoader;
this.sourceFiles = sourceFiles;
this.resourceFiles = resourceFiles;
}
/**
* Return the classloader containing the compiled content and access to the
* resources.
* @return the classLoader
*/
public ClassLoader getClassLoader() {
return this.classLoader;
}
/**
* Return the single source file that was compiled.
* @return the single source file
* @throws IllegalStateException if the compiler wasn't passed exactly one
* file
*/
public SourceFile getSourceFile() {
return this.sourceFiles.getSingle();
}
/**
* Return all source files that were compiled.
* @return the source files used by the compiler
*/
public SourceFiles getSourceFiles() {
return this.sourceFiles;
}
/**
* Return the single resource file that was used when compiled.
* @return the single resource file
* @throws IllegalStateException if the compiler wasn't passed exactly one
* file
*/
public ResourceFile getResourceFile() {
return this.resourceFiles.getSingle();
}
/**
* Return all resource files that were compiled.
* @return the resource files used by the compiler
*/
public ResourceFiles getResourceFiles() {
return this.resourceFiles;
}
/**
* Return a new instance of a compiled class of the given type. There must
* be only a single instance and it must have a default constructor.
* @param <T> the required type
* @param type the required type
* @return an instance of type created from the compiled classes
* @throws IllegalStateException if no instance can be found or instantiated
*/
public <T> T getInstance(Class<T> type) {
List<Class<?>> matching = getAllCompiledClasses().stream().filter(
candidate -> type.isAssignableFrom(candidate)).toList();
Assert.state(!matching.isEmpty(), () -> "No instance found of type " + type.getName());
Assert.state(matching.size() == 1, () -> "Multiple instances found of type " + type.getName());
return newInstance(matching.get(0));
}
/**
* Return an instance of a compiled class identified by its class name. The
* class must have a default constructor.
* @param <T> the type to return
* @param type the type to return
* @param className the class name to load
* @return an instance of the class
* @throws IllegalStateException if no instance can be found or instantiated
*/
public <T> T getInstance(Class<T> type, String className) {
Class<?> loaded = loadClass(className);
return newInstance(loaded);
}
/**
* Return all compiled classes.
* @return a list of all compiled classes
*/
public List<Class<?>> getAllCompiledClasses() {
List<Class<?>> compiledClasses = this.compiledClasses;
if (compiledClasses == null) {
compiledClasses = new ArrayList<>();
this.sourceFiles.stream().map(this::loadClass).forEach(compiledClasses::add);
this.compiledClasses = Collections.unmodifiableList(compiledClasses);
}
return compiledClasses;
}
@SuppressWarnings("unchecked")
private <T> T newInstance(Class<?> loaded) {
try {
Constructor<?> constructor = loaded.getDeclaredConstructor();
return (T) constructor.newInstance();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
private Class<?> loadClass(SourceFile sourceFile) {
return loadClass(sourceFile.getClassName());
}
private Class<?> loadClass(String className) {
try {
return this.classLoader.loadClass(className);
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(ex);
}
}
}

63
spring-core-test/src/main/java/org/springframework/aot/test/generator/compile/DynamicClassFileObject.java

@ -0,0 +1,63 @@ @@ -0,0 +1,63 @@
/*
* 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.generator.compile;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
/**
* In-memory {@link JavaFileObject} used to hold class bytecode.
*
* @author Phillip Webb
* @since 6.0
*/
class DynamicClassFileObject extends SimpleJavaFileObject {
private volatile byte[] bytes;
DynamicClassFileObject(String className) {
super(URI.create("class:///" + className.replace('.', '/') + ".class"),
Kind.CLASS);
}
@Override
public OutputStream openOutputStream() throws IOException {
return new JavaClassOutputStream();
}
byte[] getBytes() {
return this.bytes;
}
class JavaClassOutputStream extends ByteArrayOutputStream {
@Override
public void close() throws IOException {
DynamicClassFileObject.this.bytes = toByteArray();
}
}
}

186
spring-core-test/src/main/java/org/springframework/aot/test/generator/compile/DynamicClassLoader.java

@ -0,0 +1,186 @@ @@ -0,0 +1,186 @@
/*
* 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.generator.compile;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.net.MalformedURLException;
import java.net.URL;
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.generator.file.ResourceFile;
import org.springframework.aot.test.generator.file.ResourceFiles;
import org.springframework.aot.test.generator.file.SourceFile;
import org.springframework.aot.test.generator.file.SourceFiles;
/**
* {@link ClassLoader} used to expose dynamically generated content.
*
* @author Phillip Webb
* @since 6.0
*/
public class DynamicClassLoader extends ClassLoader {
private static final Logger logger = System.getLogger(
DynamicClassLoader.class.getName());
private final SourceFiles sourceFiles;
private final ResourceFiles resourceFiles;
private final Map<String, DynamicClassFileObject> classFiles;
public DynamicClassLoader(ClassLoader parent, SourceFiles sourceFiles,
ResourceFiles resourceFiles, Map<String, DynamicClassFileObject> classFiles) {
super(parent);
this.sourceFiles = sourceFiles;
this.resourceFiles = resourceFiles;
this.classFiles = classFiles;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
DynamicClassFileObject classFile = this.classFiles.get(name);
if (classFile != null) {
return defineClass(name, classFile);
}
return super.findClass(name);
}
private Class<?> defineClass(String name, DynamicClassFileObject classFile) {
byte[] bytes = classFile.getBytes();
SourceFile sourceFile = this.sourceFiles.get(name);
if (sourceFile != null && sourceFile.getTarget() != null) {
try {
Lookup lookup = MethodHandles.privateLookupIn(sourceFile.getTarget(),
MethodHandles.lookup());
return lookup.defineClass(bytes);
}
catch (IllegalAccessException ex) {
logger.log(Level.WARNING,
"Unable to define class using MethodHandles Lookup, "
+ "only public methods and classes will be accessible");
}
}
return defineClass(name, bytes, 0, bytes.length, null);
}
@Override
protected Enumeration<URL> findResources(String name) throws IOException {
URL resource = findResource(name);
if (resource != null) {
return new SingletonEnumeration<>(resource);
}
return super.findResources(name);
}
@Override
protected URL findResource(String name) {
ResourceFile file = this.resourceFiles.get(name);
if (file != null) {
try {
return new URL(null, "resource:///" + file.getPath(),
new ResourceFileHandler(file));
}
catch (MalformedURLException ex) {
throw new IllegalStateException(ex);
}
}
return super.findResource(name);
}
private static class SingletonEnumeration<E> implements Enumeration<E> {
private E element;
SingletonEnumeration(E element) {
this.element = element;
}
@Override
public boolean hasMoreElements() {
return this.element != null;
}
@Override
public E nextElement() {
E next = this.element;
this.element = null;
return next;
}
}
private static class ResourceFileHandler extends URLStreamHandler {
private final ResourceFile file;
ResourceFileHandler(ResourceFile file) {
this.file = file;
}
@Override
protected URLConnection openConnection(URL url) throws IOException {
return new ResourceFileConnection(url, this.file);
}
}
private static class ResourceFileConnection extends URLConnection {
private final ResourceFile file;
protected ResourceFileConnection(URL url, ResourceFile file) {
super(url);
this.file = file;
}
@Override
public void connect() throws IOException {
}
@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(
this.file.getContent().getBytes(StandardCharsets.UTF_8));
}
}
}

71
spring-core-test/src/main/java/org/springframework/aot/test/generator/compile/DynamicJavaFileManager.java

@ -0,0 +1,71 @@ @@ -0,0 +1,71 @@
/*
* 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.generator.compile;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
/**
* {@link JavaFileManager} to create in-memory {@link DynamicClassFileObject
* ClassFileObjects} when compiling.
*
* @author Phillip Webb
* @since 6.0
*/
class DynamicJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
private final ClassLoader classLoader;
private final Map<String, DynamicClassFileObject> classFiles = Collections.synchronizedMap(
new LinkedHashMap<>());
DynamicJavaFileManager(JavaFileManager fileManager, ClassLoader classLoader) {
super(fileManager);
this.classLoader = classLoader;
}
@Override
public ClassLoader getClassLoader(Location location) {
return this.classLoader;
}
@Override
public JavaFileObject getJavaFileForOutput(Location location, String className,
JavaFileObject.Kind kind, FileObject sibling) throws IOException {
if (kind == JavaFileObject.Kind.CLASS) {
return this.classFiles.computeIfAbsent(className,
DynamicClassFileObject::new);
}
return super.getJavaFileForOutput(location, className, kind, sibling);
}
Map<String, DynamicClassFileObject> getClassFiles() {
return Collections.unmodifiableMap(this.classFiles);
}
}

50
spring-core-test/src/main/java/org/springframework/aot/test/generator/compile/DynamicJavaFileObject.java

@ -0,0 +1,50 @@ @@ -0,0 +1,50 @@
/*
* 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.generator.compile;
import java.io.IOException;
import java.net.URI;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import org.springframework.aot.test.generator.file.SourceFile;
/**
* Adapts a {@link SourceFile} instance to a {@link JavaFileObject}.
*
* @author Phillip Webb
* @since 6.0
*/
class DynamicJavaFileObject extends SimpleJavaFileObject {
private final SourceFile sourceFile;
DynamicJavaFileObject(SourceFile sourceFile) {
super(URI.create(sourceFile.getPath()), Kind.SOURCE);
this.sourceFile = sourceFile;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
return this.sourceFile.getContent();
}
}

242
spring-core-test/src/main/java/org/springframework/aot/test/generator/compile/TestCompiler.java

@ -0,0 +1,242 @@ @@ -0,0 +1,242 @@
/*
* 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.generator.compile;
import java.util.List;
import java.util.Locale;
import java.util.function.Consumer;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticListener;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import org.springframework.aot.test.generator.file.ResourceFile;
import org.springframework.aot.test.generator.file.ResourceFiles;
import org.springframework.aot.test.generator.file.SourceFile;
import org.springframework.aot.test.generator.file.SourceFiles;
import org.springframework.aot.test.generator.file.WritableContent;
/**
* Utility that can be used to dynamically compile and test Java source code.
*
* @author Phillip Webb
* @since 6.0
* @see #forSystem()
*/
public final class TestCompiler {
private final ClassLoader classLoader;
private final JavaCompiler compiler;
private final SourceFiles sourceFiles;
private final ResourceFiles resourceFiles;
private TestCompiler(ClassLoader classLoader, JavaCompiler compiler,
SourceFiles sourceFiles, ResourceFiles resourceFiles) {
this.classLoader = classLoader;
this.compiler = compiler;
this.sourceFiles = sourceFiles;
this.resourceFiles = resourceFiles;
}
/**
* Return a new {@link TestCompiler} backed by the system java compiler.
* @return a new {@link TestCompiler} instance
*/
public static TestCompiler forSystem() {
return forCompiler(ToolProvider.getSystemJavaCompiler());
}
/**
* Return a new {@link TestCompiler} backed by the given
* {@link JavaCompiler}.
* @param javaCompiler the java compiler to use
* @return a new {@link TestCompiler} instance
*/
public static TestCompiler forCompiler(JavaCompiler javaCompiler) {
return new TestCompiler(null, javaCompiler, SourceFiles.none(),
ResourceFiles.none());
}
/**
* Return a new {@link TestCompiler} instance with addition source files.
* @param sourceFiles the additional source files
* @return a new {@link TestCompiler} instance
*/
public TestCompiler withSources(SourceFile... sourceFiles) {
return new TestCompiler(this.classLoader, this.compiler,
this.sourceFiles.and(sourceFiles), this.resourceFiles);
}
/**
* Return a new {@link TestCompiler} instance with addition source files.
* @param sourceFiles the additional source files
* @return a new {@link TestCompiler} instance
*/
public TestCompiler withSources(SourceFiles sourceFiles) {
return new TestCompiler(this.classLoader, this.compiler,
this.sourceFiles.and(sourceFiles), this.resourceFiles);
}
/**
* Return a new {@link TestCompiler} instance with addition resource files.
* @param resourceFiles the additional resource files
* @return a new {@link TestCompiler} instance
*/
public TestCompiler withResources(ResourceFile... resourceFiles) {
return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles,
this.resourceFiles.and(resourceFiles));
}
/**
* Return a new {@link TestCompiler} instance with addition resource files.
* @param resourceFiles the additional resource files
* @return a new {@link TestCompiler} instance
*/
public TestCompiler withResources(ResourceFiles resourceFiles) {
return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles,
this.resourceFiles.and(resourceFiles));
}
/**
* Compile content from this instance along with the additional provided
* content.
* @param content the additional content to compile
* @param compiled a consumed used to further assert the compiled code
* @throws CompilationException if source cannot be compiled
*/
public void compile(WritableContent content, Consumer<Compiled> compiled) {
compile(SourceFile.of(content), compiled);
}
/**
* Compile content from this instance along with the additional provided
* source file.
* @param sourceFile the additional source file to compile
* @param compiled a consumed used to further assert the compiled code
* @throws CompilationException if source cannot be compiled
*/
public void compile(SourceFile sourceFile, Consumer<Compiled> compiled) {
withSources(sourceFile).compile(compiled);
}
/**
* Compile content from this instance along with the additional provided
* source files.
* @param sourceFiles the additional source files to compile
* @param compiled a consumed used to further assert the compiled code
* @throws CompilationException if source cannot be compiled
*/
public void compile(SourceFiles sourceFiles, Consumer<Compiled> compiled) {
withSources(sourceFiles).compile(compiled);
}
/**
* Compile content from this instance along with the additional provided
* source and resource files.
* @param sourceFiles the additional source files to compile
* @param resourceFiles the additional resource files to include
* @param compiled a consumed used to further assert the compiled code
* @throws CompilationException if source cannot be compiled
*/
public void compile(SourceFiles sourceFiles, ResourceFiles resourceFiles,
Consumer<Compiled> compiled) {
withSources(sourceFiles).withResources(resourceFiles).compile(compiled);
}
/**
* Compile content from this instance.
* @param compiled a consumed used to further assert the compiled code
* @throws CompilationException if source cannot be compiled
*/
public void compile(Consumer<Compiled> compiled) throws CompilationException {
DynamicClassLoader dynamicClassLoader = compile();
ClassLoader previousClassLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(dynamicClassLoader);
compiled.accept(new Compiled(dynamicClassLoader, this.sourceFiles,
this.resourceFiles));
}
finally {
Thread.currentThread().setContextClassLoader(previousClassLoader);
}
}
private DynamicClassLoader compile() {
ClassLoader classLoader = (this.classLoader != null) ? this.classLoader
: Thread.currentThread().getContextClassLoader();
List<DynamicJavaFileObject> compilationUnits = this.sourceFiles.stream().map(
DynamicJavaFileObject::new).toList();
StandardJavaFileManager standardFileManager = this.compiler.getStandardFileManager(
null, null, null);
DynamicJavaFileManager fileManager = new DynamicJavaFileManager(
standardFileManager, classLoader);
if (!this.sourceFiles.isEmpty()) {
Errors errors = new Errors();
CompilationTask task = this.compiler.getTask(null, fileManager, errors, null,
null, compilationUnits);
boolean result = task.call();
if (!result || errors.hasReportedErrors()) {
throw new CompilationException("Unable to compile source" + errors);
}
}
return new DynamicClassLoader(this.classLoader, this.sourceFiles,
this.resourceFiles, fileManager.getClassFiles());
}
/**
* {@link DiagnosticListener} used to collect errors.
*/
static class Errors implements DiagnosticListener<JavaFileObject> {
private final StringBuilder message = new StringBuilder();
@Override
public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
this.message.append("\n");
this.message.append(diagnostic.getMessage(Locale.getDefault()));
this.message.append(" ");
this.message.append(diagnostic.getSource().getName());
this.message.append(" ");
this.message.append(
diagnostic.getLineNumber() + ":" + diagnostic.getColumnNumber());
}
}
boolean hasReportedErrors() {
return this.message.length() > 0;
}
@Override
public String toString() {
return this.message.toString();
}
}
}

105
spring-core-test/src/main/java/org/springframework/aot/test/generator/file/DynamicFile.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.generator.file;
import java.io.IOException;
import java.util.Objects;
import org.assertj.core.util.Strings;
/**
* Abstract base class for dynamically generated files.
*
* @author Phillip Webb
* @since 6.0
* @see SourceFile
* @see ResourceFile
*/
public abstract sealed class DynamicFile permits SourceFile,ResourceFile {
private final String path;
private final String content;
protected DynamicFile(String path, String content) {
if (Strings.isNullOrEmpty(content)) {
throw new IllegalArgumentException("'path' must not to be empty");
}
if (Strings.isNullOrEmpty(content)) {
throw new IllegalArgumentException("'content' must not to be empty");
}
this.path = path;
this.content = content;
}
protected static String toString(WritableContent writableContent) {
if (writableContent == null) {
throw new IllegalArgumentException("'writableContent' must not to be empty");
}
try {
StringBuilder stringBuilder = new StringBuilder();
writableContent.writeTo(stringBuilder);
return stringBuilder.toString();
}
catch (IOException ex) {
throw new IllegalStateException("Unable to read content", ex);
}
}
/**
* Return the contents of the file.
* @return the file contents
*/
public String getContent() {
return this.content;
}
/**
* Return the relative path of the file.
* @return the file path
*/
public String getPath() {
return this.path;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
DynamicFile other = (DynamicFile) obj;
return Objects.equals(this.path, other.path)
&& Objects.equals(this.content, other.content);
}
@Override
public int hashCode() {
return Objects.hash(this.path, this.content);
}
@Override
public String toString() {
return this.path;
}
}

54
spring-core-test/src/main/java/org/springframework/aot/test/generator/file/DynamicFileAssert.java

@ -0,0 +1,54 @@ @@ -0,0 +1,54 @@
/*
* 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.generator.file;
import org.assertj.core.api.AbstractAssert;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Assertion methods for {@code DynamicFile} instances.
*
* @author Phillip Webb
* @since 6.0
* @param <A> the assertion type
* @param <F> the file type
*/
public class DynamicFileAssert<A extends DynamicFileAssert<A, F>, F extends DynamicFile>
extends AbstractAssert<A, F> {
DynamicFileAssert(F actual, Class<?> selfType) {
super(actual, selfType);
}
public A contains(CharSequence... values) {
assertThat(this.actual.getContent()).contains(values);
return this.myself;
}
public A isEqualTo(Object expected) {
if (expected instanceof DynamicFile) {
return super.isEqualTo(expected);
}
assertThat(this.actual.getContent()).isEqualTo(
expected != null ? expected.toString() : null);
return this.myself;
}
}

114
spring-core-test/src/main/java/org/springframework/aot/test/generator/file/DynamicFiles.java

@ -0,0 +1,114 @@ @@ -0,0 +1,114 @@
/*
* 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.generator.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 javax.annotation.Nullable;
/**
* Internal class used by {@link SourceFiles} and {@link ResourceFiles} to
* manage {@link DynamicFile} instances.
*
* @author Phillip Webb
* @since 6.0
* @param <F> the {@link DynamicFile} type
*/
final class DynamicFiles<F extends DynamicFile> implements Iterable<F> {
private static final DynamicFiles<?> NONE = new DynamicFiles<>(
Collections.emptyMap());
private final Map<String, F> files;
private DynamicFiles(Map<String, F> files) {
this.files = files;
}
@SuppressWarnings("unchecked")
static <F extends DynamicFile> DynamicFiles<F> none() {
return (DynamicFiles<F>) NONE;
}
DynamicFiles<F> and(F[] files) {
Map<String, F> merged = new LinkedHashMap<>(this.files);
Arrays.stream(files).forEach(file -> merged.put(file.getPath(), file));
return new DynamicFiles<>(Collections.unmodifiableMap(merged));
}
DynamicFiles<F> and(DynamicFiles<F> files) {
Map<String, F> merged = new LinkedHashMap<>(this.files);
merged.putAll(files.files);
return new DynamicFiles<>(Collections.unmodifiableMap(merged));
}
@Override
public Iterator<F> iterator() {
return this.files.values().iterator();
}
Stream<F> stream() {
return this.files.values().stream();
}
boolean isEmpty() {
return this.files.isEmpty();
}
@Nullable
F get(String path) {
return this.files.get(path);
}
F getSingle() {
if (this.files.size() != 1) {
throw new IllegalStateException("No single file available");
}
return this.files.values().iterator().next();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
return this.files.equals(((DynamicFiles<?>) obj).files);
}
@Override
public int hashCode() {
return this.files.hashCode();
}
@Override
public String toString() {
return this.files.toString();
}
}

63
spring-core-test/src/main/java/org/springframework/aot/test/generator/file/MethodAssert.java

@ -0,0 +1,63 @@ @@ -0,0 +1,63 @@
/*
* 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.generator.file;
import java.util.stream.Collectors;
import com.thoughtworks.qdox.model.JavaMethod;
import com.thoughtworks.qdox.model.JavaParameter;
import org.assertj.core.api.AbstractAssert;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Assertion methods for {@code SourceFile} methods.
*
* @author Phillip Webb
* @since 6.0
*/
public class MethodAssert extends AbstractAssert<MethodAssert, JavaMethod> {
MethodAssert(JavaMethod actual) {
super(actual, MethodAssert.class);
as(describe(actual));
}
private String describe(JavaMethod actual) {
return actual.getName() + "("
+ actual.getParameters().stream().map(
this::getFullyQualifiedName).collect(Collectors.joining(", "))
+ ")";
}
private String getFullyQualifiedName(JavaParameter parameter) {
return parameter.getType().getFullyQualifiedName();
}
public void withBody(String expected) {
assertThat(this.actual.getSourceCode()).as(
this.info.description()).isEqualToNormalizingWhitespace(expected);
}
public void withBodyContaining(CharSequence... values) {
assertThat(this.actual.getSourceCode()).as(this.info.description()).contains(
values);
}
}

72
spring-core-test/src/main/java/org/springframework/aot/test/generator/file/ResourceFile.java

@ -0,0 +1,72 @@ @@ -0,0 +1,72 @@
/*
* 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.generator.file;
import org.assertj.core.api.AssertProvider;
/**
* {@link DynamicFile} that holds resource file content and provides
* {@link ResourceFileAssert} support.
*
* @author Phillip Webb
* @since 6.0
*/
public final class ResourceFile extends DynamicFile
implements AssertProvider<ResourceFileAssert> {
private ResourceFile(String path, String content) {
super(path, content);
}
/**
* Factory method to create a new {@link ResourceFile} from the given
* {@link CharSequence}.
* @param path the relative path of the file or {@code null} to have the
* path deduced
* @param charSequence a file containing the source contents
* @return a {@link ResourceFile} instance
*/
public static ResourceFile of(String path, CharSequence charSequence) {
return new ResourceFile(path, charSequence.toString());
}
/**
* Factory method to create a new {@link SourceFile} from the given
* {@link WritableContent}.
* @param path the relative path of the file or {@code null} to have the
* path deduced
* @param writableContent the content to write to the file
* @return a {@link SourceFile} instance
*/
public static ResourceFile of(String path, WritableContent writableContent) {
return new ResourceFile(path, toString(writableContent));
}
/**
* AssertJ {@code assertThat} support.
* @deprecated use {@code assertThat(sourceFile)} rather than calling this
* method directly.
*/
@Override
@Deprecated
public ResourceFileAssert assertThat() {
return new ResourceFileAssert(this);
}
}

34
spring-core-test/src/main/java/org/springframework/aot/test/generator/file/ResourceFileAssert.java

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
/*
* 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.generator.file;
/**
* Assertion methods for {@code ResourceFile} instances.
*
* @author Phillip Webb
* @since 6.0
*/
public class ResourceFileAssert
extends DynamicFileAssert<ResourceFileAssert, ResourceFile> {
ResourceFileAssert(ResourceFile actual) {
super(actual, ResourceFileAssert.class);
}
}

144
spring-core-test/src/main/java/org/springframework/aot/test/generator/file/ResourceFiles.java

@ -0,0 +1,144 @@ @@ -0,0 +1,144 @@
/*
* 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.generator.file;
import java.util.Iterator;
import java.util.stream.Stream;
import javax.annotation.Nullable;
/**
* An immutable collection of {@link ResourceFile} instances.
*
* @author Phillip Webb
* @since 6.0
*/
public final class ResourceFiles implements Iterable<ResourceFile> {
private static final ResourceFiles NONE = new ResourceFiles(DynamicFiles.none());
private final DynamicFiles<ResourceFile> files;
private ResourceFiles(DynamicFiles<ResourceFile> files) {
this.files = files;
}
/**
* Return a {@link DynamicFiles} instance with no items.
* @return the empty instance
*/
public static ResourceFiles none() {
return NONE;
}
/**
* Factory method that can be used to create a {@link ResourceFiles}
* instance containing the specified files.
* @param ResourceFiles the files to include
* @return a {@link ResourceFiles} instance
*/
public static ResourceFiles of(ResourceFile... ResourceFiles) {
return none().and(ResourceFiles);
}
/**
* Return a new {@link ResourceFiles} instance that merges files from
* another array of {@link ResourceFile} instances.
* @param ResourceFiles the instances to merge
* @return a new {@link ResourceFiles} instance containing merged content
*/
public ResourceFiles and(ResourceFile... ResourceFiles) {
return new ResourceFiles(this.files.and(ResourceFiles));
}
/**
* Return a new {@link ResourceFiles} instance that merges files from
* another {@link ResourceFiles} instance.
* @param ResourceFiles the instance to merge
* @return a new {@link ResourceFiles} instance containing merged content
*/
public ResourceFiles and(ResourceFiles ResourceFiles) {
return new ResourceFiles(this.files.and(ResourceFiles.files));
}
@Override
public Iterator<ResourceFile> iterator() {
return this.files.iterator();
}
/**
* Stream the {@link ResourceFile} instances contained in this collection.
* @return a stream of file instances
*/
public Stream<ResourceFile> stream() {
return this.files.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 ResourceFile} with the given
* {@code DynamicFile#getPath() path}.
* @param path the path to find
* @return a {@link ResourceFile} instance or {@code null}
*/
@Nullable
public ResourceFile get(String path) {
return this.files.get(path);
}
/**
* Return the single source file contained in the collection.
* @return the single file
* @throws IllegalStateException if the collection doesn't contain exactly
* one file
*/
public ResourceFile getSingle() throws IllegalStateException {
return this.files.getSingle();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
return this.files.equals(((ResourceFiles) obj).files);
}
@Override
public int hashCode() {
return this.files.hashCode();
}
@Override
public String toString() {
return this.files.toString();
}
}

161
spring-core-test/src/main/java/org/springframework/aot/test/generator/file/SourceFile.java

@ -0,0 +1,161 @@ @@ -0,0 +1,161 @@
/*
* 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.generator.file;
import java.io.StringReader;
import javax.annotation.Nullable;
import com.thoughtworks.qdox.JavaProjectBuilder;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaPackage;
import com.thoughtworks.qdox.model.JavaSource;
import org.assertj.core.api.AssertProvider;
import org.assertj.core.util.Strings;
/**
* {@link DynamicFile} that holds Java source code and provides
* {@link SourceFileAssert} support. Usually created from an AOT generated type,
* for example:
*
* <pre class="code">
* SourceFile.of(generatedFile::writeTo)
* </pre>
*
* @author Phillip Webb
* @since 6.0
*/
public final class SourceFile extends DynamicFile
implements AssertProvider<SourceFileAssert> {
private final JavaSource javaSource;
private SourceFile(String path, String content, JavaSource javaSource) {
super(path, content);
this.javaSource = javaSource;
}
/**
* Factory method to create a new {@link SourceFile} from the given
* {@link CharSequence}.
* @param charSequence a file containing the source contents
* @return a {@link SourceFile} instance
*/
public static SourceFile of(CharSequence charSequence) {
return of(null, appendable -> appendable.append(charSequence));
}
/**
* Factory method to create a new {@link SourceFile} from the given
* {@link CharSequence}.
* @param path the relative path of the file or {@code null} to have the
* path deduced
* @param charSequence a file containing the source contents
* @return a {@link SourceFile} instance
*/
public static SourceFile of(@Nullable String path, CharSequence charSequence) {
return of(path, appendable -> appendable.append(charSequence));
}
/**
* Factory method to create a new {@link SourceFile} from the given
* {@link WritableContent}.
* @param writableContent the content to write to the file
* @return a {@link SourceFile} instance
*/
public static SourceFile of(WritableContent writableContent) {
return of(null, writableContent);
}
/**
* Factory method to create a new {@link SourceFile} from the given
* {@link WritableContent}.
* @param path the relative path of the file or {@code null} to have the
* path deduced
* @param writableContent the content to write to the file
* @return a {@link SourceFile} instance
*/
public static SourceFile of(@Nullable String path, WritableContent writableContent) {
String content = toString(writableContent);
if (Strings.isNullOrEmpty(content)) {
throw new IllegalStateException("WritableContent did not append any content");
}
JavaSource javaSource = parse(content);
if (path == null || path.isEmpty()) {
path = deducePath(javaSource);
}
return new SourceFile(path, content, javaSource);
}
private static JavaSource parse(String content) {
JavaProjectBuilder builder = new JavaProjectBuilder();
try {
JavaSource javaSource = builder.addSource(new StringReader(content));
if (javaSource.getClasses().size() != 1) {
throw new IllegalStateException("Source must define a single class");
}
return javaSource;
}
catch (Exception ex) {
throw new IllegalStateException(
"Unable to parse source file content:\n\n" + content, ex);
}
}
private static String deducePath(JavaSource javaSource) {
JavaPackage javaPackage = javaSource.getPackage();
JavaClass javaClass = javaSource.getClasses().get(0);
String path = javaClass.getName() + ".java";
if (javaPackage != null) {
path = javaPackage.getName().replace('.', '/') + "/" + path;
}
return path;
}
JavaSource getJavaSource() {
return this.javaSource;
}
/**
* Return the target class for this source file or {@code null}. The target
* class can be used if private lookup access is required.
* @return the target class
*/
@Nullable
public Class<?> getTarget() {
return null; // Not yet supported
}
public String getClassName() {
return this.javaSource.getClasses().get(0).getFullyQualifiedName();
}
/**
* AssertJ {@code assertThat} support.
* @deprecated use {@code assertThat(sourceFile)} rather than calling this
* method directly.
*/
@Override
@Deprecated
public SourceFileAssert assertThat() {
return new SourceFileAssert(this);
}
}

128
spring-core-test/src/main/java/org/springframework/aot/test/generator/file/SourceFileAssert.java

@ -0,0 +1,128 @@ @@ -0,0 +1,128 @@
/*
* 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.generator.file;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaMethod;
import com.thoughtworks.qdox.model.JavaParameter;
import com.thoughtworks.qdox.model.JavaType;
import org.assertj.core.error.BasicErrorMessageFactory;
import org.assertj.core.internal.Failures;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Assertion methods for {@code SourceFile} instances.
*
* @author Phillip Webb
* @since 6.0
*/
public class SourceFileAssert extends DynamicFileAssert<SourceFileAssert, SourceFile> {
SourceFileAssert(SourceFile actual) {
super(actual, SourceFileAssert.class);
}
public SourceFileAssert implementsInterface(Class<?> type) {
return implementsInterface((type != null) ? type.getName() : (String) null);
}
public SourceFileAssert implementsInterface(String name) {
JavaClass javaClass = getJavaClass();
assertThat(javaClass.getImplements()).as("implements").map(
JavaType::getFullyQualifiedName).contains(name);
return this;
}
public MethodAssert hasMethodNamed(String name) {
JavaClass javaClass = getJavaClass();
JavaMethod method = null;
for (JavaMethod candidate : javaClass.getMethods()) {
if (candidate.getName().equals(name)) {
if (method != null) {
throw Failures.instance().failure(this.info,
new BasicErrorMessageFactory(String.format(
"%nExpecting actual:%n %s%nto contain unique method:%n %s%n",
this.actual.getContent(), name)));
}
method = candidate;
}
}
if (method == null) {
throw Failures.instance().failure(this.info,
new BasicErrorMessageFactory(String.format(
"%nExpecting actual:%n %s%nto contain method:%n %s%n",
this.actual.getContent(), name)));
}
return new MethodAssert(method);
}
public MethodAssert hasMethod(String name, Class<?>... parameters) {
JavaClass javaClass = getJavaClass();
JavaMethod method = null;
for (JavaMethod candidate : javaClass.getMethods()) {
if (candidate.getName().equals(name)
&& hasParameters(candidate, parameters)) {
if (method != null) {
throw Failures.instance().failure(this.info,
new BasicErrorMessageFactory(String.format(
"%nExpecting actual:%n %s%nto contain unique method:%n %s%n",
this.actual.getContent(), name)));
}
method = candidate;
}
}
if (method == null) {
String methodDescription = getMethodDescription(name, parameters);
throw Failures.instance().failure(this.info,
new BasicErrorMessageFactory(String.format(
"%nExpecting actual:%n %s%nto contain method:%n %s%n",
this.actual.getContent(), methodDescription)));
}
return new MethodAssert(method);
}
private boolean hasParameters(JavaMethod method, Class<?>[] requiredParameters) {
List<JavaParameter> parameters = method.getParameters();
if (parameters.size() != requiredParameters.length) {
return false;
}
for (int i = 0; i < requiredParameters.length; i++) {
if (!requiredParameters[i].getName().equals(
parameters.get(i).getFullyQualifiedName())) {
return false;
}
}
return true;
}
private String getMethodDescription(String name, Class<?>... parameters) {
return name + "(" + Arrays.stream(parameters).map(Class::getName).collect(
Collectors.joining(", ")) + ")";
}
private JavaClass getJavaClass() {
return this.actual.getJavaSource().getClasses().get(0);
}
}

144
spring-core-test/src/main/java/org/springframework/aot/test/generator/file/SourceFiles.java

@ -0,0 +1,144 @@ @@ -0,0 +1,144 @@
/*
* 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.generator.file;
import java.util.Iterator;
import java.util.stream.Stream;
import javax.annotation.Nullable;
/**
* An immutable collection of {@link SourceFile} instances.
*
* @author Phillip Webb
* @since 6.0
*/
public final class SourceFiles implements Iterable<SourceFile> {
private static final SourceFiles NONE = new SourceFiles(DynamicFiles.none());
private final DynamicFiles<SourceFile> files;
private SourceFiles(DynamicFiles<SourceFile> files) {
this.files = files;
}
/**
* Return a {@link SourceFiles} instance with no items.
* @return the empty instance
*/
public static SourceFiles none() {
return NONE;
}
/**
* Factory method that can be used to create a {@link SourceFiles} instance
* containing the specified files.
* @param sourceFiles the files to include
* @return a {@link SourceFiles} instance
*/
public static SourceFiles of(SourceFile... sourceFiles) {
return none().and(sourceFiles);
}
/**
* Return a new {@link SourceFiles} instance that merges files from another
* array of {@link SourceFile} instances.
* @param sourceFiles the instances to merge
* @return a new {@link SourceFiles} instance containing merged content
*/
public SourceFiles and(SourceFile... sourceFiles) {
return new SourceFiles(this.files.and(sourceFiles));
}
/**
* Return a new {@link SourceFiles} instance that merges files from another
* {@link SourceFiles} instance.
* @param sourceFiles the instance to merge
* @return a new {@link SourceFiles} instance containing merged content
*/
public SourceFiles and(SourceFiles sourceFiles) {
return new SourceFiles(this.files.and(sourceFiles.files));
}
@Override
public Iterator<SourceFile> iterator() {
return this.files.iterator();
}
/**
* Stream the {@link SourceFile} instances contained in this collection.
* @return a stream of file instances
*/
public Stream<SourceFile> stream() {
return this.files.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 SourceFile} with the given
* {@code DynamicFile#getPath() path}.
* @param path the path to find
* @return a {@link SourceFile} instance or {@code null}
*/
@Nullable
public SourceFile get(String path) {
return this.files.get(path);
}
/**
* Return the single source file contained in the collection.
* @return the single file
* @throws IllegalStateException if the collection doesn't contain exactly
* one file
*/
public SourceFile getSingle() throws IllegalStateException {
return this.files.getSingle();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
return this.files.equals(((SourceFiles) obj).files);
}
@Override
public int hashCode() {
return this.files.hashCode();
}
@Override
public String toString() {
return this.files.toString();
}
}

39
spring-core-test/src/main/java/org/springframework/aot/test/generator/file/WritableContent.java

@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
/*
* 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.generator.file;
import java.io.IOException;
/**
* Callback interface used to write file content. Designed to align with
* JavaPoet's {@code JavaFile.writeTo} method.
*
* @author Phillip Webb
* @since 6.0
*/
@FunctionalInterface
public interface WritableContent {
/**
* Callback method that should write the content to the given
* {@link Appendable}.
* @param out the {@link Appendable} used to receive the content
* @throws IOException on IO error
*/
void writeTo(Appendable out) throws IOException;
}

38
spring-core-test/src/test/java/org/springframework/aot/test/generator/compile/CompilationExceptionTests.java

@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
/*
* 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.generator.compile;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link CompilationException}.
*
* @author Phillip Webb
* @since 6.0
*/
class CompilationExceptionTests {
@Test
void getMessageReturnsMessage() {
CompilationException exception = new CompilationException("message");
assertThat(exception).hasMessage("message");
}
}

194
spring-core-test/src/test/java/org/springframework/aot/test/generator/compile/CompiledTests.java

@ -0,0 +1,194 @@ @@ -0,0 +1,194 @@
/*
* 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.generator.compile;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.function.Supplier;
import org.junit.jupiter.api.Test;
import org.springframework.aot.test.generator.file.ResourceFile;
import org.springframework.aot.test.generator.file.ResourceFiles;
import org.springframework.aot.test.generator.file.SourceFile;
import org.springframework.aot.test.generator.file.SourceFiles;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link Compiled}.
*
* @author Phillip Webb
* @since 6.0
*/
class CompiledTests {
private static final String HELLO_WORLD = """
package com.example;
public class HelloWorld implements java.util.function.Supplier<String> {
public String get() {
return "Hello World!";
}
}
""";
private static final String HELLO_SPRING = """
package com.example;
public class HelloSpring implements java.util.function.Supplier<String> {
public String get() {
return "Hello Spring!"; // !!
}
}
""";
@Test
void getSourceFileWhenSingleReturnsSourceFile() {
SourceFile sourceFile = SourceFile.of(HELLO_WORLD);
TestCompiler.forSystem().compile(sourceFile,
compiled -> assertThat(compiled.getSourceFile()).isSameAs(sourceFile));
}
@Test
void getSourceFileWhenMultipleThrowsException() {
SourceFiles sourceFiles = SourceFiles.of(SourceFile.of(HELLO_WORLD),
SourceFile.of(HELLO_SPRING));
TestCompiler.forSystem().compile(sourceFiles,
compiled -> assertThatIllegalStateException().isThrownBy(
compiled::getSourceFile));
}
@Test
void getSourceFileWhenNoneThrowsException() {
TestCompiler.forSystem().compile(
compiled -> assertThatIllegalStateException().isThrownBy(
compiled::getSourceFile));
}
@Test
void getSourceFilesReturnsSourceFiles() {
SourceFiles sourceFiles = SourceFiles.of(SourceFile.of(HELLO_WORLD),
SourceFile.of(HELLO_SPRING));
TestCompiler.forSystem().compile(sourceFiles,
compiled -> assertThat(compiled.getSourceFiles()).isEqualTo(sourceFiles));
}
@Test
void getResourceFileWhenSingleReturnsSourceFile() {
ResourceFile resourceFile = ResourceFile.of("META-INF/myfile", "test");
TestCompiler.forSystem().withResources(resourceFile).compile(
compiled -> assertThat(compiled.getResourceFile()).isSameAs(
resourceFile));
}
@Test
void getResourceFileWhenMultipleThrowsException() {
ResourceFiles resourceFiles = ResourceFiles.of(
ResourceFile.of("META-INF/myfile1", "test1"),
ResourceFile.of("META-INF/myfile2", "test2"));
TestCompiler.forSystem().withResources(resourceFiles).compile(
compiled -> assertThatIllegalStateException().isThrownBy(
() -> compiled.getResourceFile()));
}
@Test
void getResourceFileWhenNoneThrowsException() {
TestCompiler.forSystem().compile(
compiled -> assertThatIllegalStateException().isThrownBy(
() -> compiled.getResourceFile()));
}
@Test
void getResourceFilesReturnsResourceFiles() {
ResourceFiles resourceFiles = ResourceFiles.of(
ResourceFile.of("META-INF/myfile1", "test1"),
ResourceFile.of("META-INF/myfile2", "test2"));
TestCompiler.forSystem().withResources(resourceFiles).compile(
compiled -> assertThat(compiled.getResourceFiles()).isEqualTo(
resourceFiles));
}
@Test
void getInstanceWhenNoneMatchesThrowsException() {
TestCompiler.forSystem().compile(SourceFile.of(HELLO_WORLD),
compiled -> assertThatIllegalStateException().isThrownBy(
() -> compiled.getInstance(Callable.class)));
}
@Test
void getInstanceWhenMultipleMatchesThrowsException() {
SourceFiles sourceFiles = SourceFiles.of(SourceFile.of(HELLO_WORLD),
SourceFile.of(HELLO_SPRING));
TestCompiler.forSystem().compile(sourceFiles,
compiled -> assertThatIllegalStateException().isThrownBy(
() -> compiled.getInstance(Supplier.class)));
}
@Test
void getInstanceWhenNoDefaultConstructorThrowsException() {
SourceFile sourceFile = SourceFile.of("""
package com.example;
public class HelloWorld implements java.util.function.Supplier<String> {
public HelloWorld(String name) {
}
public String get() {
return "Hello World!";
}
}
""");
TestCompiler.forSystem().compile(sourceFile,
compiled -> assertThatIllegalStateException().isThrownBy(
() -> compiled.getInstance(Supplier.class)));
}
@Test
void getInstanceReturnsInstance() {
TestCompiler.forSystem().compile(SourceFile.of(HELLO_WORLD),
compiled -> assertThat(compiled.getInstance(Supplier.class)).isNotNull());
}
@Test
void getInstanceByNameReturnsInstance() {
SourceFiles sourceFiles = SourceFiles.of(SourceFile.of(HELLO_WORLD),
SourceFile.of(HELLO_SPRING));
TestCompiler.forSystem().compile(sourceFiles,
compiled -> assertThat(compiled.getInstance(Supplier.class,
"com.example.HelloWorld")).isNotNull());
}
@Test
void getAllCompiledClassesReturnsCompiledClasses() {
SourceFiles sourceFiles = SourceFiles.of(SourceFile.of(HELLO_WORLD),
SourceFile.of(HELLO_SPRING));
TestCompiler.forSystem().compile(sourceFiles, compiled -> {
List<Class<?>> classes = compiled.getAllCompiledClasses();
assertThat(classes.stream().map(Class::getName)).containsExactlyInAnyOrder(
"com.example.HelloWorld", "com.example.HelloSpring");
});
}
}

60
spring-core-test/src/test/java/org/springframework/aot/test/generator/compile/DynamicClassFileObjectTests.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.generator.compile;
import java.io.ByteArrayInputStream;
import java.io.OutputStream;
import javax.tools.JavaFileObject.Kind;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link DynamicClassFileObject}.
*
* @author Phillip Webb
* @since 6.0
*/
class DynamicClassFileObjectTests {
@Test
void getUriReturnsGeneratedUriBasedOnClassName() {
DynamicClassFileObject fileObject = new DynamicClassFileObject(
"com.example.MyClass");
assertThat(fileObject.toUri()).hasToString("class:///com/example/MyClass.class");
}
@Test
void getKindReturnsClass() {
DynamicClassFileObject fileObject = new DynamicClassFileObject(
"com.example.MyClass");
assertThat(fileObject.getKind()).isEqualTo(Kind.CLASS);
}
@Test
void openOutputStreamWritesToBytes() throws Exception {
DynamicClassFileObject fileObject = new DynamicClassFileObject(
"com.example.MyClass");
try(OutputStream outputStream = fileObject.openOutputStream()) {
new ByteArrayInputStream("test".getBytes()).transferTo(outputStream);
}
assertThat(fileObject.getBytes()).isEqualTo("test".getBytes());
}
}

101
spring-core-test/src/test/java/org/springframework/aot/test/generator/compile/DynamicJavaFileManagerTests.java

@ -0,0 +1,101 @@ @@ -0,0 +1,101 @@
/*
* 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.generator.compile;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileManager.Location;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.then;
/**
* Tests for {@link DynamicJavaFileManager}.
*
* @author Phillip Webb
* @since 6.0
*/
class DynamicJavaFileManagerTests {
@Mock
private JavaFileManager parentFileManager;
@Mock
private Location location;
private ClassLoader classLoader;
private DynamicJavaFileManager fileManager;
@BeforeEach
void setup() {
MockitoAnnotations.openMocks(this);
this.classLoader = new ClassLoader() {
};
this.fileManager = new DynamicJavaFileManager(this.parentFileManager,
this.classLoader);
}
@Test
void getClassLoaderReturnsClassLoader() {
assertThat(this.fileManager.getClassLoader(this.location)).isSameAs(
this.classLoader);
}
@Test
void getJavaFileForOutputWhenClassKindReturnsDynamicClassFile() throws Exception {
JavaFileObject fileObject = this.fileManager.getJavaFileForOutput(this.location,
"com.example.MyClass", Kind.CLASS, null);
assertThat(fileObject).isInstanceOf(DynamicClassFileObject.class);
}
@Test
void getJavaFileForOutputWhenClassKindAndAlreadySeenReturnsSameDynamicClassFile()
throws Exception {
JavaFileObject fileObject1 = this.fileManager.getJavaFileForOutput(this.location,
"com.example.MyClass", Kind.CLASS, null);
JavaFileObject fileObject2 = this.fileManager.getJavaFileForOutput(this.location,
"com.example.MyClass", Kind.CLASS, null);
assertThat(fileObject1).isSameAs(fileObject2);
}
@Test
void getJavaFileForOutputWhenNotClassKindDelegatesToParentFileManager()
throws Exception {
this.fileManager.getJavaFileForOutput(this.location, "com.example.MyClass",
Kind.SOURCE, null);
then(this.parentFileManager).should().getJavaFileForOutput(this.location,
"com.example.MyClass", Kind.SOURCE, null);
}
@Test
void getClassFilesReturnsClassFiles() throws Exception {
this.fileManager.getJavaFileForOutput(this.location, "com.example.MyClass1",
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");
}
}

47
spring-core-test/src/test/java/org/springframework/aot/test/generator/compile/DynamicJavaFileObjectTests.java

@ -0,0 +1,47 @@ @@ -0,0 +1,47 @@
/*
* 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.generator.compile;
import org.junit.jupiter.api.Test;
import org.springframework.aot.test.generator.file.SourceFile;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link DynamicJavaFileObject}.
*
* @author Phillip Webb
* @since 6.0
*/
class DynamicJavaFileObjectTests {
private static final String CONTENT = "package com.example; public class Hello {}";
@Test
void getUriReturnsPath() {
DynamicJavaFileObject fileObject = new DynamicJavaFileObject(SourceFile.of(CONTENT));
assertThat(fileObject.toUri()).hasToString("com/example/Hello.java");
}
@Test
void getCharContentReturnsContent() throws Exception {
DynamicJavaFileObject fileObject = new DynamicJavaFileObject(SourceFile.of(CONTENT));
assertThat(fileObject.getCharContent(true)).isEqualTo(CONTENT);
}
}

179
spring-core-test/src/test/java/org/springframework/aot/test/generator/compile/TestCompilerTests.java

@ -0,0 +1,179 @@ @@ -0,0 +1,179 @@
/*
* 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.generator.compile;
import java.util.function.Supplier;
import org.junit.jupiter.api.Test;
import org.springframework.aot.test.generator.file.ResourceFile;
import org.springframework.aot.test.generator.file.ResourceFiles;
import org.springframework.aot.test.generator.file.SourceFile;
import org.springframework.aot.test.generator.file.SourceFiles;
import org.springframework.aot.test.generator.file.WritableContent;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link TestCompiler}.
*
* @author Phillip Webb
*/
class TestCompilerTests {
private static final String HELLO_WORLD = """
package com.example;
import java.util.function.Supplier;
@Deprecated
public class Hello implements Supplier<String> {
public String get() {
return "Hello World!";
}
}
""";
private static final String HELLO_SPRING = """
package com.example;
import java.util.function.Supplier;
public class Hello implements Supplier<String> {
public String get() {
return "Hello Spring!"; // !!
}
}
""";
private static final String HELLO_BAD = """
package com.example;
public class Hello implements Supplier<String> {
public String get() {
return "Missing Import!";
}
}
""";
@Test
@SuppressWarnings("unchecked")
void compileWhenHasDifferentClassesWithSameClassNameCompilesBoth() {
TestCompiler.forSystem().withSources(SourceFile.of(HELLO_WORLD)).compile(
compiled -> {
Supplier<String> supplier = compiled.getInstance(Supplier.class,
"com.example.Hello");
assertThat(supplier.get()).isEqualTo("Hello World!");
});
TestCompiler.forSystem().withSources(SourceFile.of(HELLO_SPRING)).compile(
compiled -> {
Supplier<String> supplier = compiled.getInstance(Supplier.class,
"com.example.Hello");
assertThat(supplier.get()).isEqualTo("Hello Spring!");
});
}
@Test
void compileAndGetSourceFile() {
TestCompiler.forSystem().withSources(SourceFile.of(HELLO_SPRING)).compile(
compiled -> assertThat(compiled.getSourceFile()).hasMethodNamed(
"get").withBodyContaining("// !!"));
}
@Test
void compileWhenSourceHasCompileErrors() {
assertThatExceptionOfType(CompilationException.class).isThrownBy(
() -> TestCompiler.forSystem().withSources(
SourceFile.of(HELLO_BAD)).compile(compiled -> {
}));
}
@Test
void withSourcesArrayAddsSource() {
SourceFile sourceFile = SourceFile.of(HELLO_WORLD);
TestCompiler.forSystem().withSources(sourceFile).compile(
this::assertSuppliesHelloWorld);
}
@Test
void withSourcesAddsSource() {
SourceFiles sourceFiles = SourceFiles.of(SourceFile.of(HELLO_WORLD));
TestCompiler.forSystem().withSources(sourceFiles).compile(
this::assertSuppliesHelloWorld);
}
@Test
void withResourcesArrayAddsResource() {
ResourceFile resourceFile = ResourceFile.of("META-INF/myfile", "test");
TestCompiler.forSystem().withResources(resourceFile).compile(
this::assertHasResource);
}
@Test
void withResourcesAddsResource() {
ResourceFiles resourceFiles = ResourceFiles.of(
ResourceFile.of("META-INF/myfile", "test"));
TestCompiler.forSystem().withResources(resourceFiles).compile(
this::assertHasResource);
}
@Test
void compileWithWritableContent() {
WritableContent content = appendable -> appendable.append(HELLO_WORLD);
TestCompiler.forSystem().compile(content, this::assertSuppliesHelloWorld);
}
@Test
void compileWithSourceFile() {
SourceFile sourceFile = SourceFile.of(HELLO_WORLD);
TestCompiler.forSystem().compile(sourceFile, this::assertSuppliesHelloWorld);
}
@Test
void compileWithSourceFiles() {
SourceFiles sourceFiles = SourceFiles.of(SourceFile.of(HELLO_WORLD));
TestCompiler.forSystem().compile(sourceFiles, this::assertSuppliesHelloWorld);
}
@Test
void compileWithSourceFilesAndResourceFiles() {
SourceFiles sourceFiles = SourceFiles.of(SourceFile.of(HELLO_WORLD));
ResourceFiles resourceFiles = ResourceFiles.of(
ResourceFile.of("META-INF/myfile", "test"));
TestCompiler.forSystem().compile(sourceFiles, resourceFiles, compiled -> {
assertSuppliesHelloWorld(compiled);
assertHasResource(compiled);
});
}
private void assertSuppliesHelloWorld(Compiled compiled) {
assertThat(compiled.getInstance(Supplier.class).get()).isEqualTo("Hello World!");
}
private void assertHasResource(Compiled compiled) {
assertThat(compiled.getClassLoader().getResourceAsStream(
"META-INF/myfile")).hasContent("test");
}
}

73
spring-core-test/src/test/java/org/springframework/aot/test/generator/file/MethodAssertTests.java

@ -0,0 +1,73 @@ @@ -0,0 +1,73 @@
/*
* 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.generator.file;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link MethodAssert}.
*
* @author Phillip Webb
*/
class MethodAssertTests {
private static final String SAMPLE = """
package com.example;
public class Sample {
public void run() {
System.out.println("Hello World!");
}
}
""";
private final SourceFile sourceFile = SourceFile.of(SAMPLE);
@Test
void withBodyWhenMatches() {
assertThat(this.sourceFile).hasMethodNamed("run").withBody("""
System.out.println("Hello World!");""");
}
@Test
void withBodyWhenDoesNotMatchThrowsException() {
assertThatExceptionOfType(AssertionError.class).isThrownBy(
() -> assertThat(this.sourceFile).hasMethodNamed("run").withBody("""
System.out.println("Hello Spring!");""")).withMessageContaining(
"to be equal to");
}
@Test
void withBodyContainingWhenContainsAll() {
assertThat(this.sourceFile).hasMethodNamed("run").withBodyContaining("Hello",
"World!");
}
@Test
void withBodyWhenDoesNotContainOneThrowsException() {
assertThatExceptionOfType(AssertionError.class).isThrownBy(
() -> assertThat(this.sourceFile).hasMethodNamed(
"run").withBodyContaining("Hello",
"Spring!")).withMessageContaining("to contain");
}
}

52
spring-core-test/src/test/java/org/springframework/aot/test/generator/file/ResourceFileTests.java

@ -0,0 +1,52 @@ @@ -0,0 +1,52 @@
/*
* 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.generator.file;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ResourceFile}.
*
* @author Phillip Webb
* @since 6.0
*/
class ResourceFileTests {
@Test
void ofPathAndCharSequenceCreatesResource() {
ResourceFile file = ResourceFile.of("path", "test");
assertThat(file.getPath()).isEqualTo("path");
assertThat(file.getContent()).isEqualTo("test");
}
@Test
void ofPathAndWritableContentCreatesResource() {
ResourceFile file = ResourceFile.of("path", appendable -> appendable.append("test"));
assertThat(file.getPath()).isEqualTo("path");
assertThat(file.getContent()).isEqualTo("test");
}
@Test
@SuppressWarnings("deprecation")
void assertThatReturnsResourceFileAssert() {
ResourceFile file = ResourceFile.of("path", "test");
assertThat(file.assertThat()).isInstanceOf(ResourceFileAssert.class);
}
}

131
spring-core-test/src/test/java/org/springframework/aot/test/generator/file/ResourceFilesTests.java

@ -0,0 +1,131 @@ @@ -0,0 +1,131 @@
/*
* 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.generator.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.assertThatIllegalStateException;
import static org.assertj.core.api.Assertions.assertThatObject;
class ReResourceFilesTests {
private static final ResourceFile RESOURCE_FILE_1 = ResourceFile.of("path1",
"resource1");
private static final ResourceFile RESOURCE_FILE_2 = ResourceFile.of("path2",
"resource2");
@Test
void noneReturnsNone() {
ResourceFiles none = ResourceFiles.none();
assertThat(none).isNotNull();
assertThat(none.isEmpty()).isTrue();
}
@Test
void ofCreatesResourceFiles() {
ResourceFiles resourceFiles = ResourceFiles.of(RESOURCE_FILE_1, RESOURCE_FILE_2);
assertThat(resourceFiles).containsExactly(RESOURCE_FILE_1, RESOURCE_FILE_2);
}
@Test
void andAddsResourceFiles() {
ResourceFiles resourceFiles = ResourceFiles.of(RESOURCE_FILE_1);
ResourceFiles added = resourceFiles.and(RESOURCE_FILE_2);
assertThat(resourceFiles).containsExactly(RESOURCE_FILE_1);
assertThat(added).containsExactly(RESOURCE_FILE_1, RESOURCE_FILE_2);
}
@Test
void andResourceFilesAddsResourceFiles() {
ResourceFiles resourceFiles = ResourceFiles.of(RESOURCE_FILE_1);
ResourceFiles added = resourceFiles.and(ResourceFiles.of(RESOURCE_FILE_2));
assertThat(resourceFiles).containsExactly(RESOURCE_FILE_1);
assertThat(added).containsExactly(RESOURCE_FILE_1, RESOURCE_FILE_2);
}
@Test
void iteratorIteratesResourceFiles() {
ResourceFiles resourceFiles = ResourceFiles.of(RESOURCE_FILE_1, RESOURCE_FILE_2);
Iterator<ResourceFile> iterator = resourceFiles.iterator();
assertThat(iterator.next()).isEqualTo(RESOURCE_FILE_1);
assertThat(iterator.next()).isEqualTo(RESOURCE_FILE_2);
assertThat(iterator.hasNext()).isFalse();
}
@Test
void streamStreamsResourceFiles() {
ResourceFiles resourceFiles = ResourceFiles.of(RESOURCE_FILE_1, RESOURCE_FILE_2);
assertThat(resourceFiles.stream()).containsExactly(RESOURCE_FILE_1,
RESOURCE_FILE_2);
}
@Test
void isEmptyWhenEmptyReturnsTrue() {
ResourceFiles resourceFiles = ResourceFiles.of();
assertThat(resourceFiles.isEmpty()).isTrue();
}
@Test
void isEmptyWhenNotEmptyReturnsFalse() {
ResourceFiles resourceFiles = ResourceFiles.of(RESOURCE_FILE_1);
assertThat(resourceFiles.isEmpty()).isFalse();
}
@Test
void getWhenHasFileReturnsFile() {
ResourceFiles resourceFiles = ResourceFiles.of(RESOURCE_FILE_1);
assertThat(resourceFiles.get("path1")).isNotNull();
}
@Test
void getWhenMissingFileReturnsNull() {
ResourceFiles resourceFiles = ResourceFiles.of(RESOURCE_FILE_2);
assertThatObject(resourceFiles.get("path1")).isNull();
}
@Test
void getSingleWhenHasNoFilesThrowsException() {
assertThatIllegalStateException().isThrownBy(
() -> ResourceFiles.none().getSingle());
}
@Test
void getSingleWhenHasMultipleFilesThrowsException() {
ResourceFiles resourceFiles = ResourceFiles.of(RESOURCE_FILE_1, RESOURCE_FILE_2);
assertThatIllegalStateException().isThrownBy(() -> resourceFiles.getSingle());
}
@Test
void getSingleWhenHasSingleFileReturnsFile() {
ResourceFiles resourceFiles = ResourceFiles.of(RESOURCE_FILE_1);
assertThat(resourceFiles.getSingle()).isEqualTo(RESOURCE_FILE_1);
}
@Test
void equalsAndHashCode() {
ResourceFiles s1 = ResourceFiles.of(RESOURCE_FILE_1, RESOURCE_FILE_2);
ResourceFiles s2 = ResourceFiles.of(RESOURCE_FILE_1, RESOURCE_FILE_2);
ResourceFiles s3 = ResourceFiles.of(RESOURCE_FILE_1);
assertThat(s1.hashCode()).isEqualTo(s2.hashCode());
assertThatObject(s1).isEqualTo(s2).isNotEqualTo(s3);
}
}

128
spring-core-test/src/test/java/org/springframework/aot/test/generator/file/SourceFileAssertTests.java

@ -0,0 +1,128 @@ @@ -0,0 +1,128 @@
/*
* 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.generator.file;
import java.util.concurrent.Callable;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link SourceFileAssert}.
*
* @author Phillip Webb
*/
class SourceFileAssertTests {
private static final String SAMPLE = """
package com.example;
import java.lang.Runnable;
public class Sample implements Runnable {
void run() {
run("Hello World!");
}
void run(String message) {
System.out.println(message);
}
public static void main(String[] args) {
new Sample().run();
}
}
""";
private final SourceFile sourceFile = SourceFile.of(SAMPLE);
@Test
void containsWhenContainsAll() {
assertThat(this.sourceFile).contains("Sample", "main");
}
@Test
void containsWhenMissingOneThrowsException() {
assertThatExceptionOfType(AssertionError.class).isThrownBy(
() -> assertThat(this.sourceFile).contains("Sample",
"missing")).withMessageContaining("to contain");
}
@Test
void isEqualToWhenEqual() {
assertThat(this.sourceFile).isEqualTo(SAMPLE);
}
@Test
void isEqualToWhenNotEqualThrowsException() {
assertThatExceptionOfType(AssertionError.class).isThrownBy(
() -> assertThat(this.sourceFile).isEqualTo("no")).withMessageContaining(
"expected", "but was");
}
@Test
void implementsInterfaceWhenImplementsInterface() {
assertThat(this.sourceFile).implementsInterface(Runnable.class);
}
@Test
void implementsInterfaceWhenDoesNotImplementInterfaceThrowsException() {
assertThatExceptionOfType(AssertionError.class).isThrownBy(
() -> assertThat(this.sourceFile).implementsInterface(
Callable.class)).withMessageContaining("to contain:");
}
@Test
void hasMethodNamedWhenHasName() {
MethodAssert methodAssert = assertThat(this.sourceFile).hasMethodNamed("main");
assertThat(methodAssert).isNotNull();
}
@Test
void hasMethodNameWhenDoesNotHaveMethodThrowsException() {
assertThatExceptionOfType(AssertionError.class).isThrownBy(
() -> assertThat(this.sourceFile).hasMethodNamed(
"missing")).withMessageContaining("to contain method");
}
@Test
void hasMethodNameWhenHasMultipleMethodsWithNameThrowsException() {
assertThatExceptionOfType(AssertionError.class).isThrownBy(
() -> assertThat(this.sourceFile).hasMethodNamed(
"run")).withMessageContaining("to contain unique method");
}
@Test
void hasMethodWhenHasMethod() {
MethodAssert methodAssert = assertThat(this.sourceFile).hasMethod("run",
String.class);
assertThat(methodAssert).isNotNull();
}
@Test
void hasMethodWhenDoesNotHaveMethod() {
assertThatExceptionOfType(AssertionError.class).isThrownBy(
() -> assertThat(this.sourceFile).hasMethod("run",
Integer.class)).withMessageContaining(
"to contain").withMessageContaining(
"run(java.lang.Integer");
}
}

138
spring-core-test/src/test/java/org/springframework/aot/test/generator/file/SourceFileTests.java

@ -0,0 +1,138 @@ @@ -0,0 +1,138 @@
/*
* 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.generator.file;
import java.io.IOException;
import com.thoughtworks.qdox.model.JavaSource;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link SourceFile}.
*
* @author Phillip Webb
*/
class SourceFileTests {
private static final String HELLO_WORLD = """
package com.example.helloworld;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
""";
@Test
void ofWhenContentIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(
() -> SourceFile.of((WritableContent) null)).withMessage(
"'writableContent' must not to be empty");
}
@Test
void ofWhenContentIsEmptyThrowsException() {
assertThatIllegalStateException().isThrownBy(() -> SourceFile.of("")).withMessage(
"WritableContent did not append any content");
}
@Test
void ofWhenSourceDefinesNoClassThrowsException() {
assertThatIllegalStateException().isThrownBy(
() -> SourceFile.of("package com.example;")).withMessageContaining(
"Unable to parse").havingCause().withMessage(
"Source must define a single class");
}
@Test
void ofWhenSourceDefinesMultipleClassesThrowsException() {
assertThatIllegalStateException().isThrownBy(() -> SourceFile.of(
"public class One {}\npublic class Two{}")).withMessageContaining(
"Unable to parse").havingCause().withMessage(
"Source must define a single class");
}
@Test
void ofWhenSourceCannotBeParsedThrowsException() {
assertThatIllegalStateException().isThrownBy(
() -> SourceFile.of("well this is broken {")).withMessageContaining(
"Unable to parse source file content");
}
@Test
void ofWithoutPathDeducesPath() {
SourceFile sourceFile = SourceFile.of(HELLO_WORLD);
assertThat(sourceFile.getPath()).isEqualTo(
"com/example/helloworld/HelloWorld.java");
}
@Test
void ofWithPathUsesPath() {
SourceFile sourceFile = SourceFile.of("com/example/DifferentPath.java",
HELLO_WORLD);
assertThat(sourceFile.getPath()).isEqualTo("com/example/DifferentPath.java");
}
@Test
void getContentReturnsContent() {
SourceFile sourceFile = SourceFile.of(HELLO_WORLD);
assertThat(sourceFile.getContent()).isEqualTo(HELLO_WORLD);
}
@Test
void getJavaSourceReturnsJavaSource() {
SourceFile sourceFile = SourceFile.of(HELLO_WORLD);
assertThat(sourceFile.getJavaSource()).isInstanceOf(JavaSource.class);
}
@Test
@SuppressWarnings("deprecation")
void assertThatReturnsAssert() {
SourceFile sourceFile = SourceFile.of(HELLO_WORLD);
assertThat(sourceFile.assertThat()).isInstanceOf(SourceFileAssert.class);
}
@Test
void createFromJavaPoetStyleApi() {
JavaFile javaFile = new JavaFile(HELLO_WORLD);
SourceFile sourceFile = SourceFile.of(javaFile::writeTo);
assertThat(sourceFile.getContent()).isEqualTo(HELLO_WORLD);
}
/**
* JavaPoet style API with a {@code writeTo} method.
*/
static class JavaFile {
private final String content;
JavaFile(String content) {
this.content = content;
}
void writeTo(Appendable out) throws IOException {
out.append(this.content);
}
}
}

135
spring-core-test/src/test/java/org/springframework/aot/test/generator/file/SourceFilesTests.java

@ -0,0 +1,135 @@ @@ -0,0 +1,135 @@
/*
* 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.generator.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.assertThatIllegalStateException;
import static org.assertj.core.api.Assertions.assertThatObject;
/**
* Tests for {@link SourceFiles}.
*
* @author Phillip Webb
*/
class SourceFilesTests {
private static final SourceFile SOURCE_FILE_1 = SourceFile.of(
"public class Test1 {}");
private static final SourceFile SOURCE_FILE_2 = SourceFile.of(
"public class Test2 {}");
@Test
void noneReturnsNone() {
SourceFiles none = SourceFiles.none();
assertThat(none).isNotNull();
assertThat(none.isEmpty()).isTrue();
}
@Test
void ofCreatesSourceFiles() {
SourceFiles sourceFiles = SourceFiles.of(SOURCE_FILE_1, SOURCE_FILE_2);
assertThat(sourceFiles).containsExactly(SOURCE_FILE_1, SOURCE_FILE_2);
}
@Test
void andAddsSourceFiles() {
SourceFiles sourceFiles = SourceFiles.of(SOURCE_FILE_1);
SourceFiles added = sourceFiles.and(SOURCE_FILE_2);
assertThat(sourceFiles).containsExactly(SOURCE_FILE_1);
assertThat(added).containsExactly(SOURCE_FILE_1, SOURCE_FILE_2);
}
@Test
void andSourceFilesAddsSourceFiles() {
SourceFiles sourceFiles = SourceFiles.of(SOURCE_FILE_1);
SourceFiles added = sourceFiles.and(SourceFiles.of(SOURCE_FILE_2));
assertThat(sourceFiles).containsExactly(SOURCE_FILE_1);
assertThat(added).containsExactly(SOURCE_FILE_1, SOURCE_FILE_2);
}
@Test
void iteratorIteratesSourceFiles() {
SourceFiles sourceFiles = SourceFiles.of(SOURCE_FILE_1, SOURCE_FILE_2);
Iterator<SourceFile> iterator = sourceFiles.iterator();
assertThat(iterator.next()).isEqualTo(SOURCE_FILE_1);
assertThat(iterator.next()).isEqualTo(SOURCE_FILE_2);
assertThat(iterator.hasNext()).isFalse();
}
@Test
void streamStreamsSourceFiles() {
SourceFiles sourceFiles = SourceFiles.of(SOURCE_FILE_1, SOURCE_FILE_2);
assertThat(sourceFiles.stream()).containsExactly(SOURCE_FILE_1, SOURCE_FILE_2);
}
@Test
void isEmptyWhenEmptyReturnsTrue() {
SourceFiles sourceFiles = SourceFiles.of();
assertThat(sourceFiles.isEmpty()).isTrue();
}
@Test
void isEmptyWhenNotEmptyReturnsFalse() {
SourceFiles sourceFiles = SourceFiles.of(SOURCE_FILE_1);
assertThat(sourceFiles.isEmpty()).isFalse();
}
@Test
void getWhenHasFileReturnsFile() {
SourceFiles sourceFiles = SourceFiles.of(SOURCE_FILE_1);
assertThat(sourceFiles.get("Test1.java")).isNotNull();
}
@Test
void getWhenMissingFileReturnsNull() {
SourceFiles sourceFiles = SourceFiles.of(SOURCE_FILE_2);
assertThatObject(sourceFiles.get("Test1.java")).isNull();
}
@Test
void getSingleWhenHasNoFilesThrowsException() {
assertThatIllegalStateException().isThrownBy(
() -> SourceFiles.none().getSingle());
}
@Test
void getSingleWhenHasMultipleFilesThrowsException() {
SourceFiles sourceFiles = SourceFiles.of(SOURCE_FILE_1, SOURCE_FILE_2);
assertThatIllegalStateException().isThrownBy(() -> sourceFiles.getSingle());
}
@Test
void getSingleWhenHasSingleFileReturnsFile() {
SourceFiles sourceFiles = SourceFiles.of(SOURCE_FILE_1);
assertThat(sourceFiles.getSingle()).isEqualTo(SOURCE_FILE_1);
}
@Test
void equalsAndHashCode() {
SourceFiles s1 = SourceFiles.of(SOURCE_FILE_1, SOURCE_FILE_2);
SourceFiles s2 = SourceFiles.of(SOURCE_FILE_1, SOURCE_FILE_2);
SourceFiles s3 = SourceFiles.of(SOURCE_FILE_1);
assertThat(s1.hashCode()).isEqualTo(s2.hashCode());
assertThatObject(s1).isEqualTo(s2).isNotEqualTo(s3);
}
}
Loading…
Cancel
Save