Browse Source
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-28120pull/28170/head
34 changed files with 3163 additions and 1 deletions
@ -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 |
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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)); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
@ -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(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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; |
||||||
|
|
||||||
|
} |
||||||
@ -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"); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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"); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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()); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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"); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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"); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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"); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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"); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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…
Reference in new issue