Simplify SourceFileAssert assertion methods

Remove assertion methods that turned out not to be needed when
testing Spring Framework's AOT generated code.

Closes gh-28556
This commit is contained in:
Phillip Webb 2022-06-02 15:07:39 -07:00
parent 5f4bcf197c
commit 74caa9213a
8 changed files with 30 additions and 333 deletions

View File

@ -1,63 +0,0 @@
/*
* 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);
}
}

View File

@ -23,14 +23,15 @@ import java.nio.charset.StandardCharsets;
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;
import org.springframework.core.io.InputStreamSource;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;
/**
* {@link DynamicFile} that holds Java source code and provides
@ -48,12 +49,12 @@ public final class SourceFile extends DynamicFile
implements AssertProvider<SourceFileAssert> {
private final JavaSource javaSource;
private final String className;
private SourceFile(String path, String content, JavaSource javaSource) {
private SourceFile(String path, String content, String className) {
super(path, content);
this.javaSource = javaSource;
this.className = className;
}
@ -126,24 +127,31 @@ public final class SourceFile extends DynamicFile
*/
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");
Assert.state(StringUtils.hasLength(content), "WritableContent did not append any content");
String className = getClassName(content);
if (!StringUtils.hasLength(path)) {
path = ClassUtils.convertClassNameToResourcePath(className) + ".java";
}
JavaSource javaSource = parse(content);
if (path == null || path.isEmpty()) {
path = deducePath(javaSource);
}
return new SourceFile(path, content, javaSource);
return new SourceFile(path, content, className);
}
private static JavaSource parse(String content) {
/**
* Return the fully-qualified class name.
* @return the fully qualified class name
*/
public String getClassName() {
return this.className;
}
private static String getClassName(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;
Assert.state(javaSource.getClasses().size() == 1, "Source must define a single class");
JavaClass javaClass = javaSource.getClasses().get(0);
return (javaSource.getPackage() != null)
? javaSource.getPackageName() + "." + javaClass.getName()
: javaClass.getName();
}
catch (Exception ex) {
throw new IllegalStateException(
@ -151,28 +159,6 @@ public final class SourceFile extends DynamicFile
}
}
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 class name of the source file.
* @return the class name
*/
public String getClassName() {
return this.javaSource.getClasses().get(0).getFullyQualifiedName();
}
/**
* AssertJ {@code assertThat} support.
* @deprecated use {@code assertThat(sourceFile)} rather than calling this
@ -184,4 +170,6 @@ public final class SourceFile extends DynamicFile
return new SourceFileAssert(this);
}
}

View File

@ -16,21 +16,6 @@
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 org.springframework.lang.Nullable;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Assertion methods for {@code SourceFile} instances.
*
@ -44,87 +29,4 @@ public class SourceFileAssert extends DynamicFileAssert<SourceFileAssert, Source
super(actual, SourceFileAssert.class);
}
public SourceFileAssert implementsInterface(@Nullable Class<?> type) {
return implementsInterface((type != null ? type.getName() : null));
}
public SourceFileAssert implementsInterface(@Nullable 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);
}
}

View File

@ -22,6 +22,7 @@ import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
/**
* An immutable collection of {@link SourceFile} instances.
@ -155,7 +156,7 @@ public final class SourceFiles implements Iterable<SourceFile> {
*/
public SourceFile getSingleFromPackage(String packageName) {
return this.files.getSingle(candidate -> Objects.equals(packageName,
candidate.getJavaSource().getPackageName()));
ClassUtils.getPackageName(candidate.getClassName())));
}
@Override

View File

@ -100,8 +100,7 @@ class TestCompilerTests {
@Test
void compileAndGetSourceFile() {
TestCompiler.forSystem().withSources(SourceFile.of(HELLO_SPRING)).compile(
compiled -> assertThat(compiled.getSourceFile()).hasMethodNamed(
"get").withBodyContaining("// !!"));
compiled -> assertThat(compiled.getSourceFile()).contains("// !!"));
}
@Test

View File

@ -1,73 +0,0 @@
/*
* 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");
}
}

View File

@ -16,8 +16,6 @@
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;
@ -77,52 +75,4 @@ class SourceFileAssertTests {
"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");
}
}

View File

@ -18,7 +18,6 @@ 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;
@ -90,12 +89,6 @@ class SourceFileTests {
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() {