From 383add20c93c4b71ef985c049a92d2c2a5253caf Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Thu, 29 Jul 2021 14:22:11 +0100 Subject: [PATCH 01/32] feature: WIP: Subject code generator Generates the bare bones boiler plate subject for later extension. Needs several usability improvements to make it practically accessible. --- extensions/generator/pom.xml | 65 +++++++++ .../extension/generator/TruthGenerator.java | 133 ++++++++++++++++++ .../common/truth/TruthGeneratorTest.java | 35 +++++ .../expected-EmployeeSubject.java.txt | 35 +++++ extensions/pom.xml | 1 + 5 files changed, 269 insertions(+) create mode 100644 extensions/generator/pom.xml create mode 100644 extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java create mode 100644 extensions/generator/src/test/java/com/google/common/truth/TruthGeneratorTest.java create mode 100644 extensions/generator/src/test/resources/expected-EmployeeSubject.java.txt diff --git a/extensions/generator/pom.xml b/extensions/generator/pom.xml new file mode 100644 index 000000000..06b99b4b0 --- /dev/null +++ b/extensions/generator/pom.xml @@ -0,0 +1,65 @@ + + + + com.google.truth.extensions + truth-extensions-parent + HEAD-SNAPSHOT + + 4.0.0 + + Truth Extension for generating Subjects + truth-generator-extension + + + 2.23.1-SNAPSHOT + + + + + org.jboss.forge.roaster + roaster-api + ${version.roaster} + + + org.jboss.forge.roaster + roaster-jdt + ${version.roaster} + runtime + + + com.google.truth + truth + + + com.google.truth + truth + test + test-jar + HEAD-SNAPSHOT + test + + + org.checkerframework + checker-qual + + + org.atteo + evo-inflector + 1.3 + + + + + + + maven-compiler-plugin + + 1.8 + 1.8 + + + + + diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java new file mode 100644 index 000000000..cbb7ee506 --- /dev/null +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java @@ -0,0 +1,133 @@ +package com.google.common.truth.extension.generator; + +import com.google.common.base.Joiner; +import com.google.common.truth.FailureMetadata; +import com.google.common.truth.Subject; +import com.google.common.truth.Truth; +import org.atteo.evo.inflector.English; +import org.jboss.forge.roaster.Roaster; +import org.jboss.forge.roaster.model.source.JavaClassSource; +import org.jboss.forge.roaster.model.source.JavaDocSource; +import org.jboss.forge.roaster.model.source.MethodSource; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.PrintWriter; +import java.nio.file.Paths; + +public class TruthGenerator { + + public String generate(final Class source) throws FileNotFoundException { + final JavaClassSource javaClass = Roaster.create(JavaClassSource.class); + + String packageName = source.getPackage().getName(); + String sourceName = source.getSimpleName(); + String subjectClassName = sourceName + "Subject"; + + + javaClass.setPackage(packageName).setName(subjectClassName); + + // extend + javaClass.extendSuperType(Subject.class); + + // class javadc + JavaDocSource classDocs = javaClass.getJavaDoc(); + classDocs.setText("Truth Subject for the {@link " + sourceName + "} - extend this class, with your own custom " + + "assertions.\n\n" + + "Note that the generated class will change over time, so your edits will be overwritten. " + + "Or, you can copy the generated code into your project."); + classDocs.addTagValue("@see", sourceName); + + // requires java 9 + // annotate generated + // @javax.annotation.Generated(value="") + // only in @since 1.9, so can't add it programmatically + // AnnotationSource generated = javaClass.addAnnotation(Generated.class); + // Can't add it without the value param, see https://github.com/forge/roaster/issues/201 + // AnnotationSource generated = javaClass.addAnnotation("javax.annotation.processing.Generated"); + // generated.addAnnotationValue("truth-generator"); https://github.com/forge/roaster/issues/201 + + // constructor + MethodSource constructor = javaClass.addMethod() + .setConstructor(true) + .setProtected(); + constructor.addParameter(FailureMetadata.class, "failureMetadata"); + constructor.addParameter(source, "actual"); + constructor.setBody("super(failureMetadata, actual);"); + + + // factory accessor + String returnType = getTypeWithGenerics(Subject.Factory.class, javaClass.getName(), sourceName); + MethodSource factory = javaClass.addMethod() + .setName(getFactoryName(source)) + .setPublic() + .setStatic(true) + // todo replace with something other than the string method - I suppose it's not possible to do generics type safely + .setReturnType(returnType) + .setBody("return " + javaClass.getName() + "::new;"); + JavaDocSource> factoryDocs = factory.getJavaDoc(); + factoryDocs.setText("Returns an assertion builder for a {@link " + sourceName + "} class."); + + + // entry point + MethodSource assertThat = javaClass.addMethod() + .setName("assertThat") + .setPublic() + .setStatic(true) + .setReturnType(javaClass.getEnclosingType()); + assertThat.addParameter(source, "actual"); + // return assertAbout(things()).that(actual); + // add explicit static reference for now - see below + String entryPointBody = "return Truth.assertAbout(" + factory.getName() + "()).that(actual);"; + assertThat.setBody(entryPointBody); + javaClass.addImport(Truth.class); + assertThat.getJavaDoc().setText("Entry point for {@link " + sourceName + "} assertions."); + + + // todo add static import for Truth.assertAbout somehow? +// Import anImport = javaClass.addImport(Truth.class); +// javaClass.addImport(anImport.setStatic(true)); +// javaClass.addImport(new Im) + + // output + String classSource = javaClass.toString(); + try (PrintWriter out = new PrintWriter(getFileName(javaClass))) { + out.println(classSource); + } + + return classSource; + } + + private String getTypeWithGenerics(final Class factoryClass, String... classes) { + String genericsList = Joiner.on(", ").skipNulls().join(classes); + final String generics = new StringBuilder("<>").insert(1, genericsList).toString(); + return factoryClass.getSimpleName() + generics; + } + + private String getFileName(JavaClassSource javaClass) { + String directoryName = getDirectoryName(javaClass); + File dir = new File(directoryName); + if (!dir.exists()) { + boolean mkdir = dir.mkdirs(); + } + return directoryName + javaClass.getName() + ".java"; + } + + private String getDirectoryName(JavaClassSource javaClass) { + String parent = Paths.get("").toAbsolutePath().toString(); + String packageName = javaClass.getPackage(); + String packageNameDir = packageName.replace(".", "/"); + return parent + "/target/generated-test-sources/" + packageNameDir + "/"; + } + + private String getFactoryName(final Class source) { + String simpleName = source.getSimpleName(); + String plural = English.plural(simpleName); + String normal = toLowerCaseFirstLetter(plural); + return normal; + } + + private String toLowerCaseFirstLetter(final String plural) { + return plural.substring(0, 1).toLowerCase() + plural.substring(1); + } +} diff --git a/extensions/generator/src/test/java/com/google/common/truth/TruthGeneratorTest.java b/extensions/generator/src/test/java/com/google/common/truth/TruthGeneratorTest.java new file mode 100644 index 000000000..6ab50993d --- /dev/null +++ b/extensions/generator/src/test/java/com/google/common/truth/TruthGeneratorTest.java @@ -0,0 +1,35 @@ +package com.google.common.truth; + +import com.google.common.io.Resources; +import com.google.common.truth.extension.Employee; +import com.google.common.truth.extension.generator.TruthGenerator; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.IOException; +import java.nio.charset.Charset; + +import static com.google.common.truth.Truth.assertThat; + +@RunWith(JUnit4.class) +public class TruthGeneratorTest { + + @Test + public void poc() throws IOException { + TruthGenerator truthGenerator = new TruthGenerator(); + String generated = truthGenerator.generate(Employee.class); + + String expectedFileName = "expected-EmployeeSubject.java.txt"; + String expecting = Resources.toString(Resources.getResource(expectedFileName), Charset.defaultCharset()); + + + assertThat(trim(generated)).isEqualTo(trim(expecting)); + } + + private String trim(String in) { + return in.replaceAll("(?m)^(\\s)*|(\\s)*$", ""); + + } + +} diff --git a/extensions/generator/src/test/resources/expected-EmployeeSubject.java.txt b/extensions/generator/src/test/resources/expected-EmployeeSubject.java.txt new file mode 100644 index 000000000..38e69c787 --- /dev/null +++ b/extensions/generator/src/test/resources/expected-EmployeeSubject.java.txt @@ -0,0 +1,35 @@ +package com.google.common.truth.extension; + +import com.google.common.truth.Subject; +import com.google.common.truth.FailureMetadata; +import com.google.common.truth.Truth; + +/** + * Truth Subject for the {@link Employee} - extend this class, with your own + * custom assertions. + * + * Note that the generated class will change over time, so your edits will be + * overwritten. Or, you can copy the generated code into your project. + * + * @see Employee + */ +public class EmployeeSubject extends Subject { + + protected EmployeeSubject(FailureMetadata failureMetadata, com.google.common.truth.extension.Employee actual) { + super(failureMetadata, actual); + } + + /** + * Returns an assertion builder for a {@link Employee} class. + */ + public static Factory employees() { + return EmployeeSubject::new; + } + + /** + * Entry point for {@link Employee} assertions. + */ + public static EmployeeSubject assertThat(com.google.common.truth.extension.Employee actual) { + return Truth.assertAbout(employees()).that(actual); + } +} diff --git a/extensions/pom.xml b/extensions/pom.xml index a3af92922..58bde545b 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -22,5 +22,6 @@ re2j liteproto proto + generator From 1a6c0be78fe8ca6c66e907259d0ba1b8f7dee160 Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Thu, 29 Jul 2021 15:53:16 +0100 Subject: [PATCH 02/32] Include test jar build and use released roaster --- core/pom.xml | 5 +++++ extensions/generator/pom.xml | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index f600f1c76..992b1c5e2 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -124,6 +124,11 @@ + + + test-jar + + diff --git a/extensions/generator/pom.xml b/extensions/generator/pom.xml index 06b99b4b0..afcf2283c 100644 --- a/extensions/generator/pom.xml +++ b/extensions/generator/pom.xml @@ -13,7 +13,7 @@ truth-generator-extension - 2.23.1-SNAPSHOT + 2.23.0.Final @@ -35,7 +35,6 @@ com.google.truth truth - test test-jar HEAD-SNAPSHOT test From 1199e02e8bfb440d5f93953ab407190fb8807e98 Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Thu, 29 Jul 2021 16:02:47 +0100 Subject: [PATCH 03/32] add mising actual field --- .../truth/extension/generator/TruthGenerator.java | 10 +++++++++- .../test/resources/expected-EmployeeSubject.java.txt | 3 +++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java index cbb7ee506..9d4f0d4bc 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java @@ -47,13 +47,21 @@ public String generate(final Class source) throws FileNotFoundException { // AnnotationSource generated = javaClass.addAnnotation("javax.annotation.processing.Generated"); // generated.addAnnotationValue("truth-generator"); https://github.com/forge/roaster/issues/201 + // actual field + javaClass.addField() + .setPrivate() + .setType(source) + .setName("actual") + .setFinal(true); + // constructor MethodSource constructor = javaClass.addMethod() .setConstructor(true) .setProtected(); constructor.addParameter(FailureMetadata.class, "failureMetadata"); constructor.addParameter(source, "actual"); - constructor.setBody("super(failureMetadata, actual);"); + constructor.setBody("super(failureMetadata, actual);\n" + + "this.actual = actual;"); // factory accessor diff --git a/extensions/generator/src/test/resources/expected-EmployeeSubject.java.txt b/extensions/generator/src/test/resources/expected-EmployeeSubject.java.txt index 38e69c787..d17a65c2b 100644 --- a/extensions/generator/src/test/resources/expected-EmployeeSubject.java.txt +++ b/extensions/generator/src/test/resources/expected-EmployeeSubject.java.txt @@ -15,8 +15,11 @@ import com.google.common.truth.Truth; */ public class EmployeeSubject extends Subject { + private final Employee actual; + protected EmployeeSubject(FailureMetadata failureMetadata, com.google.common.truth.extension.Employee actual) { super(failureMetadata, actual); + this.actual = actual; } /** From 1d0ceb9dac3715a28a33c9ebfe342ef415102538 Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Thu, 29 Jul 2021 16:25:23 +0100 Subject: [PATCH 04/32] fix actual access and add convenience assertTruth method --- .../truth/extension/generator/TruthGenerator.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java index 9d4f0d4bc..a641253a2 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java @@ -49,7 +49,7 @@ public String generate(final Class source) throws FileNotFoundException { // actual field javaClass.addField() - .setPrivate() + .setProtected() .setType(source) .setName("actual") .setFinal(true); @@ -76,7 +76,6 @@ public String generate(final Class source) throws FileNotFoundException { JavaDocSource> factoryDocs = factory.getJavaDoc(); factoryDocs.setText("Returns an assertion builder for a {@link " + sourceName + "} class."); - // entry point MethodSource assertThat = javaClass.addMethod() .setName("assertThat") @@ -91,6 +90,18 @@ public String generate(final Class source) throws FileNotFoundException { javaClass.addImport(Truth.class); assertThat.getJavaDoc().setText("Entry point for {@link " + sourceName + "} assertions."); + // convenience entry point when being mixed with other "assertThat" assertion libraries + MethodSource assertTruth = javaClass.addMethod() + .setName("assertTruth") + .setPublic() + .setStatic(true) + .setReturnType(javaClass.getEnclosingType()); + assertTruth.addParameter(source, "actual"); + assertTruth.setBody("return " + assertThat.getName() + "(actual);"); + assertTruth.getJavaDoc().setText("Convenience entry point for {@link " + sourceName + "} assertions when being " + + "mixed with other \"assertThat\" assertion libraries.") + .addTagValue("@see", "#assertThat"); + // todo add static import for Truth.assertAbout somehow? // Import anImport = javaClass.addImport(Truth.class); From 13451cbbdf4c0d742283d53cdf597ea33f5cdea4 Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Thu, 29 Jul 2021 20:27:47 +0100 Subject: [PATCH 05/32] feature: START-WIP: squash up: boiler plate only, three layer and merge systems Choose which style to use - Boiler plate dump - generates everything we have in one file and overwrites everything on subsequent runs - Three layer keeps user code as separate as possible from generated code - Merge system attempts to merge user and generated code --- .../extension/generator/TruthGenerator.java | 521 +++++++++++++++--- .../common/truth/TruthGeneratorTest.java | 3 +- 2 files changed, 444 insertions(+), 80 deletions(-) diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java index a641253a2..dcba37e8b 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java @@ -6,6 +6,8 @@ import com.google.common.truth.Truth; import org.atteo.evo.inflector.English; import org.jboss.forge.roaster.Roaster; +import org.jboss.forge.roaster.model.Method; +import org.jboss.forge.roaster.model.source.Import; import org.jboss.forge.roaster.model.source.JavaClassSource; import org.jboss.forge.roaster.model.source.JavaDocSource; import org.jboss.forge.roaster.model.source.MethodSource; @@ -14,30 +16,264 @@ import java.io.FileNotFoundException; import java.io.PrintWriter; import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; public class TruthGenerator { - public String generate(final Class source) throws FileNotFoundException { - final JavaClassSource javaClass = Roaster.create(JavaClassSource.class); + private final String basePackage; + + public TruthGenerator(String basePackage) { + this.basePackage = basePackage; + } + + private final List managedSubjects = new ArrayList<>(); + private final List children = new ArrayList<>(); + + /** + * Takes a user maintained source file, and adds boiler plate that is missing. If aggressively skips parst if it + * thinks the user has overridden something. + */ + public String maintain(Class source, Class userAndGeneratedMix) { + throw new IllegalStateException(); + } + + /** + * Uses an optional three layer system to manage the Subjects. + *
    + *
  1. The top layer extends Subject and stores the actual and factory. + *
  2. The second layer is the user's code - they extend the first layer, and add their custom assertion methods there. + *
  3. The third layer extends the user's class, and stores the generated entry points, so that users's get's access + * to all three layers, by only importing the bottom layer. + *
+ *

+ * For any source class that doesn't have a user created middle class, an empty one will be generated, that the user + * can copy into their source control. If it's used, a helpful message will be written to the console, prompting the + * user to do so. These messages can be globally disabled. + *

+ * This way there's no complexity with mixing generated and user written code, but at the cost of 2 extra classes + * per source. While still allowing the user to leverage the full code generation system but maintaining their own extensions + * with clear separation from the code generation. + */ + public void threeLayerSystem(Class source, Class usersMiddleClass) throws FileNotFoundException { + // make parent - boiler plate access + ParentClass parent = createParent(source); + + String factoryMethodName = getFactoryName(source); + + // make child - client code entry point + createChild(parent, usersMiddleClass.getName(), source, factoryMethodName); + } + + /** + * Create the place holder middle class, for optional copying into source code + * + * @see #threeLayerSystem(Class, Class) + */ + public void threeLayerSystem(Class source) throws FileNotFoundException { + ParentClass parent = createParent(source); + + MiddleClass middle = createMiddlePlaceHolder(parent.generated, source); + + createChild(parent, middle.generated.getQualifiedName(), source, middle.factoryMethod.getName()); + } + + private MiddleClass createMiddlePlaceHolder(JavaClassSource parent, Class source) throws FileNotFoundException { + JavaClassSource middle = Roaster.create(JavaClassSource.class); + middle.setName(getSubjectName(source.getSimpleName())); + middle.setPackage(parent.getPackage()); + middle.extendSuperType(parent); + JavaDocSource jd = middle.getJavaDoc(); + jd.setText("Optionally move this class into source control, and add your custom assertions here."); + jd.addTagValue("@see", parent.getName()); + + addConstructor(source, middle, false); + + MethodSource factory = addFactoryAccesor(source, middle, source.getSimpleName()); + + writeToDisk(middle); + return new MiddleClass(middle, factory); + } + + class AClass { + final JavaClassSource generated; + + AClass(final JavaClassSource generated) { + this.generated = generated; + } + } + + class ParentClass extends AClass { + ParentClass(JavaClassSource generated) { + super(generated); + } + } + + class MiddleClass extends AClass { + final MethodSource factoryMethod; + + MiddleClass(JavaClassSource generated, MethodSource factoryMethod) { + super(generated); + this.factoryMethod = factoryMethod; + } + } + + /** + * Having collected together all the access points, creates one large class filled with access points to all of + * them. + *

+ * The overall access will throw an error if any middle classes don't correctly extend their parent. + */ + public void createOverallAccessPoints() throws FileNotFoundException { + JavaClassSource overallAccess = Roaster.create(JavaClassSource.class); + overallAccess.setName("ManagedTruth"); + overallAccess.getJavaDoc() + .setText("Single point of access for all managed Subjects."); + overallAccess.setPublic() + .setPackage(getManagedClassesBasePackage()); + + // brute force + for (JavaClassSource j : children) { + List> methods = j.getMethods(); + for (Method m : methods) { + overallAccess.addMethod(m); + } + // this seems like overkill, but at least in the child style case, there's very few imports - even + // none extra at all (aside from wild card vs specific methods). + List imports = j.getImports(); + for (Import i : imports) { + overallAccess.addImport(i); + } + } +// +// for (ManagedClassSet managed : managedSubjects) { +// addAssertThat(managed.sourceClass, managed.generatedClass, null, null); +// addAssertTruth(managed.sourceClass, managed.generatedClass, null); +// +// addAccessPoints(managed.sourceClass, ); +// } + + writeToDisk(overallAccess); + } + + private String getManagedClassesBasePackage() { + return basePackage; + } + + private ParentClass createParent(Class source) throws FileNotFoundException { + JavaClassSource parent = Roaster.create(JavaClassSource.class); + String sourceName = source.getSimpleName(); + String parentName = getSubjectName(sourceName + "Parent"); + parent.setName(parentName); + + addPackageSuperAndAnnotation(parent, source); + + addClassJavaDoc(parent, sourceName); + + addActualField(source, parent); + + addConstructor(source, parent, true); + + writeToDisk(parent); + + return new ParentClass(parent); + } + + private void addPackageSuperAndAnnotation(final JavaClassSource javaClass, final Class source) { + addPackageSuperAndAnnotation(javaClass, source.getPackage().getName(), getSubjectName(source.getSimpleName())); + } + + private JavaClassSource createChild(ParentClass parent, + String usersMiddleClassName, + Class source, + String factoryMethodName) throws FileNotFoundException { + // todo if middle doesn't extend parent, warn + + JavaClassSource child = Roaster.create(JavaClassSource.class); + child.setName(getSubjectName(source.getSimpleName() + "Child")); + child.setPackage(parent.generated.getPackage()); + JavaDocSource javaDoc = child.getJavaDoc(); + javaDoc.setText("Entry point for assertions for @{" + source.getSimpleName() + "}. Import the static accessor methods from this class and use them.\n" + + "Combines the generated code from {@" + parent.generated.getName() + "}and the user code from {@" + usersMiddleClassName + "}."); + javaDoc.addTagValue("@see", source.getName()); + javaDoc.addTagValue("@see", usersMiddleClassName); + javaDoc.addTagValue("@see", parent.generated.getName()); + + addAccessPoints(source, child, factoryMethodName, usersMiddleClassName); + + writeToDisk(child); + children.add(child); + return child; + } + + private void registerManagedClass(Class sourceClass, JavaClassSource gengeratedClass) { + managedSubjects.add(new ManagedClassSet(sourceClass, gengeratedClass)); + } + + class ManagedClassSet { + final Class sourceClass; + final JavaClassSource generatedClass; + + ManagedClassSet(final Class sourceClass, final JavaClassSource generatedClass) { + this.sourceClass = sourceClass; + this.generatedClass = generatedClass; + } + } + + public String generate(Class source) throws FileNotFoundException { + JavaClassSource javaClass = Roaster.create(JavaClassSource.class); + + JavaClassSource handWrittenExampleCode = Roaster.parse(JavaClassSource.class, handWritten); + + registerManagedClass(source, handWrittenExampleCode); + + javaClass = handWrittenExampleCode; String packageName = source.getPackage().getName(); String sourceName = source.getSimpleName(); - String subjectClassName = sourceName + "Subject"; + String subjectClassName = getSubjectName(sourceName); + + + addPackageSuperAndAnnotation(javaClass, packageName, subjectClassName); + + addClassJavaDoc(javaClass, sourceName); + + addActualField(source, javaClass); + + addConstructor(source, javaClass, true); + + MethodSource factory = addFactoryAccesor(source, javaClass, sourceName); + + addAccessPoints(source, javaClass, factory.getName(), javaClass.getQualifiedName()); + + // todo add static import for Truth.assertAbout somehow? +// Import anImport = javaClass.addImport(Truth.class); +// javaClass.addImport(anImport.setStatic(true)); +// javaClass.addImport(new Im) + + String classSource = writeToDisk(javaClass); + return classSource; + } + + private String getSubjectName(final String sourceName) { + return sourceName + "Subject"; + } - javaClass.setPackage(packageName).setName(subjectClassName); + private void addAccessPoints(Class source, JavaClassSource javaClass, + String factoryMethod, + String factoryContainerQualifiedName) { + MethodSource assertThat = addAssertThat(source, javaClass, factoryMethod, factoryContainerQualifiedName); + + addAssertTruth(source, javaClass, assertThat); + } + + private void addPackageSuperAndAnnotation(JavaClassSource javaClass, String packageName, String subjectClassName) { + javaClass.setPackage(packageName); // extend javaClass.extendSuperType(Subject.class); - // class javadc - JavaDocSource classDocs = javaClass.getJavaDoc(); - classDocs.setText("Truth Subject for the {@link " + sourceName + "} - extend this class, with your own custom " + - "assertions.\n\n" + - "Note that the generated class will change over time, so your edits will be overwritten. " + - "Or, you can copy the generated code into your project."); - classDocs.addTagValue("@see", sourceName); - // requires java 9 // annotate generated // @javax.annotation.Generated(value="") @@ -46,80 +282,132 @@ public String generate(final Class source) throws FileNotFoundException { // Can't add it without the value param, see https://github.com/forge/roaster/issues/201 // AnnotationSource generated = javaClass.addAnnotation("javax.annotation.processing.Generated"); // generated.addAnnotationValue("truth-generator"); https://github.com/forge/roaster/issues/201 + } - // actual field - javaClass.addField() - .setProtected() - .setType(source) - .setName("actual") - .setFinal(true); - - // constructor - MethodSource constructor = javaClass.addMethod() - .setConstructor(true) - .setProtected(); - constructor.addParameter(FailureMetadata.class, "failureMetadata"); - constructor.addParameter(source, "actual"); - constructor.setBody("super(failureMetadata, actual);\n" + - "this.actual = actual;"); - - - // factory accessor - String returnType = getTypeWithGenerics(Subject.Factory.class, javaClass.getName(), sourceName); - MethodSource factory = javaClass.addMethod() - .setName(getFactoryName(source)) - .setPublic() - .setStatic(true) - // todo replace with something other than the string method - I suppose it's not possible to do generics type safely - .setReturnType(returnType) - .setBody("return " + javaClass.getName() + "::new;"); - JavaDocSource> factoryDocs = factory.getJavaDoc(); - factoryDocs.setText("Returns an assertion builder for a {@link " + sourceName + "} class."); - - // entry point - MethodSource assertThat = javaClass.addMethod() - .setName("assertThat") - .setPublic() - .setStatic(true) - .setReturnType(javaClass.getEnclosingType()); - assertThat.addParameter(source, "actual"); - // return assertAbout(things()).that(actual); - // add explicit static reference for now - see below - String entryPointBody = "return Truth.assertAbout(" + factory.getName() + "()).that(actual);"; - assertThat.setBody(entryPointBody); - javaClass.addImport(Truth.class); - assertThat.getJavaDoc().setText("Entry point for {@link " + sourceName + "} assertions."); - - // convenience entry point when being mixed with other "assertThat" assertion libraries - MethodSource assertTruth = javaClass.addMethod() - .setName("assertTruth") - .setPublic() - .setStatic(true) - .setReturnType(javaClass.getEnclosingType()); - assertTruth.addParameter(source, "actual"); - assertTruth.setBody("return " + assertThat.getName() + "(actual);"); - assertTruth.getJavaDoc().setText("Convenience entry point for {@link " + sourceName + "} assertions when being " + - "mixed with other \"assertThat\" assertion libraries.") - .addTagValue("@see", "#assertThat"); - - - // todo add static import for Truth.assertAbout somehow? -// Import anImport = javaClass.addImport(Truth.class); -// javaClass.addImport(anImport.setStatic(true)); -// javaClass.addImport(new Im) + private void addClassJavaDoc(JavaClassSource javaClass, String sourceName) { + // class javadc + JavaDocSource classDocs = javaClass.getJavaDoc(); + if (classDocs.getFullText().isEmpty()) { + classDocs.setText("Truth Subject for the {@link " + sourceName + "}." + + "\n\n" + + "Note that this class is generated / managed, and will change over time. So any changes you might " + + "make will be overwritten."); + classDocs.addTagValue("@see", sourceName); + classDocs.addTagValue("@see", getSubjectName(sourceName)); + classDocs.addTagValue("@see", getSubjectName(sourceName + "Child")); + } + } - // output + private String writeToDisk(JavaClassSource javaClass) throws FileNotFoundException { String classSource = javaClass.toString(); try (PrintWriter out = new PrintWriter(getFileName(javaClass))) { out.println(classSource); } - return classSource; } - private String getTypeWithGenerics(final Class factoryClass, String... classes) { + private void addAssertTruth(Class source, JavaClassSource javaClass, MethodSource assertThat) { + String name = "assertTruth"; + if (!containsMethodCalled(javaClass, name)) { + // convenience entry point when being mixed with other "assertThat" assertion libraries + MethodSource assertTruth = javaClass.addMethod() + .setName(name) + .setPublic() + .setStatic(true) + .setReturnType(assertThat.getReturnType()); + assertTruth.addParameter(source, "actual"); + assertTruth.setBody("return " + assertThat.getName() + "(actual);"); + assertTruth.getJavaDoc().setText("Convenience entry point for {@link " + source.getSimpleName() + "} assertions when being " + + "mixed with other \"assertThat\" assertion libraries.") + .addTagValue("@see", "#assertThat"); + } + } + + private MethodSource addAssertThat(Class source, + JavaClassSource javaClass, + String factoryMethodName, + String factoryContainerQualifiedName) { + String methodName = "assertThat"; + if (containsMethodCalled(javaClass, methodName)) { + return getMethodCalled(javaClass, methodName); + } else { + // entry point + MethodSource assertThat = javaClass.addMethod() + .setName(methodName) + .setPublic() + .setStatic(true) + .setReturnType(factoryContainerQualifiedName); + assertThat.addParameter(source, "actual"); + // return assertAbout(things()).that(actual); + // add explicit static reference for now - see below + javaClass.addImport(factoryContainerQualifiedName + ".*") + .setStatic(true); + String entryPointBody = "return Truth.assertAbout(" + factoryMethodName + "()).that(actual);"; + assertThat.setBody(entryPointBody); + javaClass.addImport(Truth.class); + assertThat.getJavaDoc().setText("Entry point for {@link " + source.getSimpleName() + "} assertions."); + return assertThat; + } + } + + private MethodSource getMethodCalled(JavaClassSource javaClass, String methodName) { + return javaClass.getMethods().stream().filter(x -> x.getName().equals(methodName)).findFirst().get(); + } + + private MethodSource addFactoryAccesor(Class source, JavaClassSource javaClass, String sourceName) { + String factoryName = getFactoryName(source); + if (containsMethodCalled(javaClass, factoryName)) { + return getMethodCalled(javaClass, factoryName); + } else { + // factory accessor + String returnType = getTypeWithGenerics(Subject.Factory.class, javaClass.getName(), sourceName); + MethodSource factory = javaClass.addMethod() + .setName(factoryName) + .setPublic() + .setStatic(true) + // todo replace with something other than the string method - I suppose it's not possible to do generics type safely + .setReturnType(returnType) + .setBody("return " + javaClass.getName() + "::new;"); + JavaDocSource> factoryDocs = factory.getJavaDoc(); + factoryDocs.setText("Returns an assertion builder for a {@link " + sourceName + "} class."); + return factory; + } + } + + private boolean containsMethodCalled(JavaClassSource javaClass, String factoryName) { + return javaClass.getMethods().stream().anyMatch(x -> x.getName().equals(factoryName)); + } + + private void addConstructor(Class source, JavaClassSource javaClass, boolean setActual) { + if (!javaClass.getMethods().stream().anyMatch(x -> x.isConstructor())) { + // constructor + MethodSource constructor = javaClass.addMethod() + .setConstructor(true) + .setProtected(); + constructor.addParameter(FailureMetadata.class, "failureMetadata"); + constructor.addParameter(source, "actual"); + StringBuilder sb = new StringBuilder("super(failureMetadata, actual);\n"); + if (setActual) + sb.append("this.actual = actual;"); + constructor.setBody(sb.toString()); + } + } + + private void addActualField(Class source, JavaClassSource javaClass) { + String fieldName = "actual"; + if (javaClass.getField(fieldName) == null) { + // actual field + javaClass.addField() + .setProtected() + .setType(source) + .setName(fieldName) + .setFinal(true); + } + } + + private String getTypeWithGenerics(Class factoryClass, String... classes) { String genericsList = Joiner.on(", ").skipNulls().join(classes); - final String generics = new StringBuilder("<>").insert(1, genericsList).toString(); + String generics = new StringBuilder("<>").insert(1, genericsList).toString(); return factoryClass.getSimpleName() + generics; } @@ -139,14 +427,91 @@ private String getDirectoryName(JavaClassSource javaClass) { return parent + "/target/generated-test-sources/" + packageNameDir + "/"; } - private String getFactoryName(final Class source) { + private String getFactoryName(Class source) { String simpleName = source.getSimpleName(); String plural = English.plural(simpleName); String normal = toLowerCaseFirstLetter(plural); return normal; } - private String toLowerCaseFirstLetter(final String plural) { + private String toLowerCaseFirstLetter(String plural) { return plural.substring(0, 1).toLowerCase() + plural.substring(1); } + + String handWritten = "package io.confluent.csid.utils;\n" + + "\n" + + "import com.google.common.truth.Subject;\n" + + "import com.google.common.truth.FailureMetadata;\n" + + "import com.google.common.truth.Truth;\n" + + "import io.confluent.parallelconsumer.model.CommitHistory;\n" + + "import io.confluent.parallelconsumer.truth.CommitHistorySubject;\n" + + "import org.apache.kafka.clients.consumer.OffsetAndMetadata;\n" + + "import org.apache.kafka.common.TopicPartition;\n" + + "\n" + + "import java.util.List;\n" + + "import java.util.Map;\n" + + "import java.util.concurrent.CopyOnWriteArrayList;\n" + + "import java.util.stream.Collectors;\n" + + "\n" + + "import static io.confluent.parallelconsumer.truth.CommitHistorySubject.commitHistories;\n" + + "\n" + +// "/**\n" + +// " * Truth Subject for the {@link LongPollingMockConsumer} - extend this class,\n" + +// " * with your own custom assertions.\n" + +// " * \n" + +// " * Note that the generated class will change over time, so your edits will be\n" + +// " * overwritten. Or, you can copy the generated code into your project.\n" + +// " * \n" + +// " * @see LongPollingMockConsumer\n" + + "/** my own docs\n" + + " */\n" + + "public class LongPollingMockConsumerSubject extends Subject {\n" + + "\n" + +// "\tprotected LongPollingMockConsumer actual;\n" + +// "\n" + +// "\tprotected LongPollingMockConsumerSubject(FailureMetadata failureMetadata,\n" + +// "\t\t\tio.confluent.csid.utils.LongPollingMockConsumer actual) {\n" + +// "\t\tsuper(failureMetadata, actual);\n" + +// "\t\tthis.actual = actual;\n" + +// "\t}\n" + +// "\n" + +// "\t/**\n" + +// "\t * Returns an assertion builder for a {@link LongPollingMockConsumer} class.\n" + +// "\t */\n" + +// "\tpublic static Factory longPollingMockConsumers() {\n" + +// "\t\treturn LongPollingMockConsumerSubject::new;\n" + +// "\t}\n" + +// "\n" + +// "\t/**\n" + +// "\t * Entry point for {@link LongPollingMockConsumer} assertions.\n" + +// "\t */\n" + +// "\tpublic static LongPollingMockConsumerSubject assertThat(io.confluent.csid.utils.LongPollingMockConsumer actual) {\n" + +// "\t\treturn Truth.assertAbout(longPollingMockConsumers()).that(actual);\n" + +// "\t}\n" + +// "\n" + +// "\t/**\n" + +// "\t * Convenience entry point for {@link LongPollingMockConsumer} assertions when\n" + +// "\t * being mixed with other \"assertThat\" assertion libraries.\n" + +// "\t * \n" + +// "\t * @see #assertThat\n" + +// "\t */\n" + +// "\tpublic static LongPollingMockConsumerSubject assertTruth(io.confluent.csid.utils.LongPollingMockConsumer actual) {\n" + +// "\t\treturn assertThat(actual);\n" + +// "\t}\n" + + "\n" + + "\tpublic CommitHistorySubject hasCommittedToPartition(TopicPartition tp) {\n" + + "\t\tisNotNull();\n" + + "\t\tCopyOnWriteArrayList> commits = actual.getCommitHistoryInt();\n" + + "\t\tList collect = commits.stream()\n" + + "\t\t\t\t.filter(x -> x.containsKey(tp))\n" + + "\t\t\t\t.map(x -> x.get(tp))\n" + + "\t\t\t\t.collect(Collectors.toList());\n" + + "\t\tCommitHistory commitHistory = new CommitHistory(collect);\n" + + "\t\treturn check(\"hasCommittedToPartition(%s)\", tp).about(commitHistories()).that(commitHistory);\n" + + "\t}\n" + + "\n" + + "void somethingElse(){}\n" + + "}\n"; + + } diff --git a/extensions/generator/src/test/java/com/google/common/truth/TruthGeneratorTest.java b/extensions/generator/src/test/java/com/google/common/truth/TruthGeneratorTest.java index 6ab50993d..7c3a48d54 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/TruthGeneratorTest.java +++ b/extensions/generator/src/test/java/com/google/common/truth/TruthGeneratorTest.java @@ -17,13 +17,12 @@ public class TruthGeneratorTest { @Test public void poc() throws IOException { - TruthGenerator truthGenerator = new TruthGenerator(); + TruthGenerator truthGenerator = new TruthGenerator(getClass().getPackage().getName()); String generated = truthGenerator.generate(Employee.class); String expectedFileName = "expected-EmployeeSubject.java.txt"; String expecting = Resources.toString(Resources.getResource(expectedFileName), Charset.defaultCharset()); - assertThat(trim(generated)).isEqualTo(trim(expecting)); } From a2904f8dd04b9d30b5ed04e4436ac3de86db11aa Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Thu, 29 Jul 2021 20:45:04 +0100 Subject: [PATCH 06/32] feature: START-WIP: squash up: boiler plate only, three layer and merge systems Choose which style to use - Boiler plate dump - generates everything we have in one file and overwrites everything on subsequent runs - Three layer keeps user code as separate as possible from generated code - Merge system attempts to merge user and generated code For all the above options, also generates a class as a single point for all the managed Subjects. --- .../truth/extension/generator/TruthGenerator.java | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java index dcba37e8b..71c7a9aab 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java @@ -145,15 +145,7 @@ public void createOverallAccessPoints() throws FileNotFoundException { overallAccess.addImport(i); } } -// -// for (ManagedClassSet managed : managedSubjects) { -// addAssertThat(managed.sourceClass, managed.generatedClass, null, null); -// addAssertTruth(managed.sourceClass, managed.generatedClass, null); -// -// addAccessPoints(managed.sourceClass, ); -// } - - writeToDisk(overallAccess); + } private String getManagedClassesBasePackage() { From 1d2012c2137960ea3486bbf10e983dd2027dc7cf Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Thu, 29 Jul 2021 21:53:22 +0100 Subject: [PATCH 07/32] WIP: test generators --- extensions/generator/pom.xml | 10 +++ .../extension/generator/TestCreator.java | 62 +++++++++++++++++++ .../extension/generator/TruthGenerator.java | 8 ++- .../truth/extension/generator/MyEmployee.java | 26 ++++++++ .../extension/generator/TestCreatorTest.java | 19 ++++++ .../generator}/TruthGeneratorTest.java | 12 +++- 6 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 extensions/generator/src/main/java/com/google/common/truth/extension/generator/TestCreator.java create mode 100644 extensions/generator/src/test/java/com/google/common/truth/extension/generator/MyEmployee.java create mode 100644 extensions/generator/src/test/java/com/google/common/truth/extension/generator/TestCreatorTest.java rename extensions/generator/src/test/java/com/google/common/truth/{ => extension/generator}/TruthGeneratorTest.java (70%) diff --git a/extensions/generator/pom.xml b/extensions/generator/pom.xml index afcf2283c..ce3f84fa1 100644 --- a/extensions/generator/pom.xml +++ b/extensions/generator/pom.xml @@ -48,6 +48,16 @@ evo-inflector 1.3 + + org.reflections + reflections + 0.9.12 + + + org.apache.commons + commons-lang3 + 3.12.0 + diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TestCreator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TestCreator.java new file mode 100644 index 000000000..19acc22c9 --- /dev/null +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TestCreator.java @@ -0,0 +1,62 @@ +package com.google.common.truth.extension.generator; + +import org.apache.commons.lang3.ClassUtils; +import org.jboss.forge.roaster.model.source.JavaClassSource; +import org.jboss.forge.roaster.model.source.MethodSource; +import org.reflections.ReflectionUtils; + +import java.lang.reflect.Field; +import java.util.Set; + +import static org.apache.commons.lang3.StringUtils.capitalize; + +public class TestCreator { + + public void addTests(JavaClassSource parent, Class classUnderTest) { + Set allFields = ReflectionUtils.getAllFields(classUnderTest, x -> true); +// Set allFields = ReflectionUtils.getAllMethods(classUnderTest, x -> x.); +// + + for (Field f : allFields) { + Class type = f.getType(); + if (type.isPrimitive()) { + addPrimitiveTest(f, parent, classUnderTest); + } else { + addCallToDelegateSubject(f, parent, classUnderTest); + } + } + + System.out.println(allFields.toString()); + } + + private void addPrimitiveTest(Field f, JavaClassSource parent, Class classUnderTest) { + Class type = f.getType(); + Class aClass1 = ClassUtils.primitiveToWrapper(type); + + if (aClass1.getSimpleName().contains("boolean")) + return; + + MethodSource has = parent.addMethod() + .setName("has" + f.getName()) + .setPublic(); + + + has.setBody("return check(\"" + f.getName() + "\").that(actual.get" + capitalize(f.getName()) + "());"); + + String retrnTypeName = aClass1.getSimpleName() + "Subject"; +// Class aClass = null; +// try { +// aClass = ClassUtils.getClass(Truth.class.getClassLoader(), retrnTypeName); +// } catch (ClassNotFoundException e) { +// e.printStackTrace(); +// } + has.setReturnType(retrnTypeName); + parent.addImport(retrnTypeName); + + // check("atLeastOffset()").that(actual.highestCommit()).isAtLeast(needleCommit); + } + + private void addCallToDelegateSubject(Field f, JavaClassSource parent, Class classUnderTest) { + + } +} diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java index 71c7a9aab..b8cff62b0 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java @@ -55,7 +55,7 @@ public String maintain(Class source, Class userAndGeneratedMix) { * per source. While still allowing the user to leverage the full code generation system but maintaining their own extensions * with clear separation from the code generation. */ - public void threeLayerSystem(Class source, Class usersMiddleClass) throws FileNotFoundException { + public void threeLayerSystem(Class source, Class usersMiddleClass) throws FileNotFoundException { // make parent - boiler plate access ParentClass parent = createParent(source); @@ -73,6 +73,7 @@ public void threeLayerSystem(Class source, Class usersMiddleClass) thr public void threeLayerSystem(Class source) throws FileNotFoundException { ParentClass parent = createParent(source); + // todo try to see if class already exists first, user may already have a written one and not know MiddleClass middle = createMiddlePlaceHolder(parent.generated, source); createChild(parent, middle.generated.getQualifiedName(), source, middle.factoryMethod.getName()); @@ -146,6 +147,7 @@ public void createOverallAccessPoints() throws FileNotFoundException { } } + writeToDisk(overallAccess); } private String getManagedClassesBasePackage() { @@ -166,8 +168,10 @@ private ParentClass createParent(Class source) throws FileNotFoundExcepti addConstructor(source, parent, true); - writeToDisk(parent); + TestCreator testCreator = new TestCreator(); + testCreator.addTests(parent, source); + writeToDisk(parent); return new ParentClass(parent); } diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/MyEmployee.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/MyEmployee.java new file mode 100644 index 000000000..10ad805e7 --- /dev/null +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/MyEmployee.java @@ -0,0 +1,26 @@ +package com.google.common.truth.extension.generator; + +public class MyEmployee { + private long birthYear; + private String name; + + public MyEmployee(final String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public long getBirthYear() { + return birthYear; + } + + public void setBirthYear(final long birthYear) { + this.birthYear = birthYear; + } +} diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TestCreatorTest.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TestCreatorTest.java new file mode 100644 index 000000000..59679a018 --- /dev/null +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TestCreatorTest.java @@ -0,0 +1,19 @@ +package com.google.common.truth.extension.generator; + +import org.jboss.forge.roaster.Roaster; +import org.jboss.forge.roaster.model.source.JavaClassSource; +import org.junit.Test; + +import static com.google.common.truth.Truth.assertThat; + +public class TestCreatorTest { + + @Test + public void poc(){ + JavaClassSource generated = Roaster.create(JavaClassSource.class); + TestCreator testCreator = new TestCreator(); + testCreator.addTests(generated, MyEmployee.class); + + assertThat(generated.toString()).isEqualTo(""); + } +} diff --git a/extensions/generator/src/test/java/com/google/common/truth/TruthGeneratorTest.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java similarity index 70% rename from extensions/generator/src/test/java/com/google/common/truth/TruthGeneratorTest.java rename to extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java index 7c3a48d54..c1f6ff4ae 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/TruthGeneratorTest.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java @@ -1,12 +1,12 @@ -package com.google.common.truth; +package com.google.common.truth.extension.generator; import com.google.common.io.Resources; import com.google.common.truth.extension.Employee; -import com.google.common.truth.extension.generator.TruthGenerator; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.io.FileNotFoundException; import java.io.IOException; import java.nio.charset.Charset; @@ -28,6 +28,14 @@ public void poc() throws IOException { private String trim(String in) { return in.replaceAll("(?m)^(\\s)*|(\\s)*$", ""); + } + + @Test + public void employee() throws FileNotFoundException { + TruthGenerator truthGenerator = new TruthGenerator(getClass().getPackage().getName()); + String generate = truthGenerator.generate(MyEmployee.class); + + assertThat(trim("")).isEqualTo(trim(generate)); } From 31b99065446a270083814e374a89fb96318f1ce2 Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Fri, 30 Jul 2021 01:34:53 +0100 Subject: [PATCH 08/32] WIP: test generators iterate --- extensions/generator/pom.xml | 30 ++++ .../extension/generator/TestCreator.java | 62 -------- .../extension/generator/TestGenerator.java | 139 ++++++++++++++++++ .../extension/generator/TruthGenerator.java | 124 ++++------------ .../truth/extension/generator/IdCard.java | 39 +++++ .../truth/extension/generator/MyEmployee.java | 76 +++++++++- .../truth/extension/generator/SlipUp.java | 29 ++++ .../extension/generator/TestCreatorTest.java | 7 +- 8 files changed, 346 insertions(+), 160 deletions(-) delete mode 100644 extensions/generator/src/main/java/com/google/common/truth/extension/generator/TestCreator.java create mode 100644 extensions/generator/src/main/java/com/google/common/truth/extension/generator/TestGenerator.java create mode 100644 extensions/generator/src/test/java/com/google/common/truth/extension/generator/IdCard.java create mode 100644 extensions/generator/src/test/java/com/google/common/truth/extension/generator/SlipUp.java diff --git a/extensions/generator/pom.xml b/extensions/generator/pom.xml index ce3f84fa1..87e364ad1 100644 --- a/extensions/generator/pom.xml +++ b/extensions/generator/pom.xml @@ -31,6 +31,8 @@ com.google.truth truth + jar + compile com.google.truth @@ -53,11 +55,31 @@ reflections 0.9.12 + + + org.dom4j + dom4j + 2.1.1 + org.apache.commons commons-lang3 3.12.0 + + com.google.truth + truth + test + + + com.google.guava + guava + 30.1.1-jre + + + com.google.truth + truth + @@ -69,6 +91,14 @@ 1.8 + + org.apache.maven.plugins + maven-compiler-plugin + + 13 + 13 + + diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TestCreator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TestCreator.java deleted file mode 100644 index 19acc22c9..000000000 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TestCreator.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.google.common.truth.extension.generator; - -import org.apache.commons.lang3.ClassUtils; -import org.jboss.forge.roaster.model.source.JavaClassSource; -import org.jboss.forge.roaster.model.source.MethodSource; -import org.reflections.ReflectionUtils; - -import java.lang.reflect.Field; -import java.util.Set; - -import static org.apache.commons.lang3.StringUtils.capitalize; - -public class TestCreator { - - public void addTests(JavaClassSource parent, Class classUnderTest) { - Set allFields = ReflectionUtils.getAllFields(classUnderTest, x -> true); -// Set allFields = ReflectionUtils.getAllMethods(classUnderTest, x -> x.); -// - - for (Field f : allFields) { - Class type = f.getType(); - if (type.isPrimitive()) { - addPrimitiveTest(f, parent, classUnderTest); - } else { - addCallToDelegateSubject(f, parent, classUnderTest); - } - } - - System.out.println(allFields.toString()); - } - - private void addPrimitiveTest(Field f, JavaClassSource parent, Class classUnderTest) { - Class type = f.getType(); - Class aClass1 = ClassUtils.primitiveToWrapper(type); - - if (aClass1.getSimpleName().contains("boolean")) - return; - - MethodSource has = parent.addMethod() - .setName("has" + f.getName()) - .setPublic(); - - - has.setBody("return check(\"" + f.getName() + "\").that(actual.get" + capitalize(f.getName()) + "());"); - - String retrnTypeName = aClass1.getSimpleName() + "Subject"; -// Class aClass = null; -// try { -// aClass = ClassUtils.getClass(Truth.class.getClassLoader(), retrnTypeName); -// } catch (ClassNotFoundException e) { -// e.printStackTrace(); -// } - has.setReturnType(retrnTypeName); - parent.addImport(retrnTypeName); - - // check("atLeastOffset()").that(actual.highestCommit()).isAtLeast(needleCommit); - } - - private void addCallToDelegateSubject(Field f, JavaClassSource parent, Class classUnderTest) { - - } -} diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TestGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TestGenerator.java new file mode 100644 index 000000000..a337da79c --- /dev/null +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TestGenerator.java @@ -0,0 +1,139 @@ +package com.google.common.truth.extension.generator; + +import com.google.common.collect.Lists; +import com.google.common.truth.Subject; +import org.apache.commons.lang3.ClassUtils; +import org.jboss.forge.roaster.model.source.JavaClassSource; +import org.jboss.forge.roaster.model.source.MethodSource; +import org.reflections.ReflectionUtils; +import org.reflections.Reflections; + +import java.io.FileNotFoundException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; + +import static org.reflections.ReflectionUtils.*; + +public class TestGenerator { + + private final Map subjects; + + public TestGenerator() { + Reflections reflections = new Reflections("com.google.common.truth", "io.confluent"); + Set> subTypes = reflections.getSubTypesOf(Subject.class); + + Map maps = new HashMap<>(); + subTypes.stream().forEach(x -> maps.put(x.getSimpleName(), x)); + this.subjects = maps; + } + + + public void addTests(JavaClassSource parent, Class classUnderTest) { + Set allFields = ReflectionUtils.getAllFields(classUnderTest, x -> true); + + Set getters = ReflectionUtils.getAllMethods(classUnderTest, + withModifier(Modifier.PUBLIC), withPrefix("get"), withParametersCount(0)); + + Set issers = ReflectionUtils.getAllMethods(classUnderTest, + withModifier(Modifier.PUBLIC), withPrefix("is"), withParametersCount(0)); + + getters.addAll(issers); + + // + for (Method method : getters) { + addPrimitiveTest(method, parent, classUnderTest); + } + + try { + TruthGenerator.writeToDisk(parent); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + + private void addPrimitiveTest(Method f, JavaClassSource parent, Class classUnderTest) { + Class type = f.getReturnType(); + Class aClass1 = ClassUtils.primitiveToWrapper(type); + + Optional subjectForType = getSubjectForType(type); + + // no subject to chain + // todo needs two passes - one to generate the custom classes, then one to use them in other classes + // should generate all base classes first, then run the test creator pass afterwards + if (subjectForType.isEmpty()) { + System.out.println("Cant find subject for " + type); + // todo log + return; + } + + Class subjectClass = subjectForType.get(); + + // todo add versions with and with the get + String prefix = (type.getSimpleName().contains("boolean")) ? "is" : ""; + MethodSource has = parent.addMethod() + .setName(prefix + f.getName()) + .setPublic(); + + StringBuilder body = new StringBuilder("isNotNull();\n"); + String check = "return check(\"" + f.getName() + "\")"; + body.append(check); + + // todo use qualified names + // todo add support truth8 extensions - optional etc + // todo try generatin classes for DateTime pakages, like Instant and Duration + // todo this is of course too aggresive + List specials = Lists.newArrayList("String", "BigDecimal", "Iterable", "Optional", "List"); + + boolean isCoveredByNonPrimitiveStandardSubjects = specials.contains(type.getSimpleName()); + boolean notPrimitive = !type.isPrimitive(); + if (notPrimitive && !isCoveredByNonPrimitiveStandardSubjects) { + // need to get the Subject instance using about + // return check("hasCommittedToPartition(%s)", tp).about(commitHistories()).that(commitHistory); + String aboutName; + Set factoryPotentials = getMethods(subjectClass, x -> + !x.getName().startsWith("assert") + ); + if (factoryPotentials.isEmpty()) { + aboutName = TruthGenerator.getFactoryName(type); // take a guess + } else { + Method method = factoryPotentials.stream().findFirst().get(); + aboutName = method.getName(); + } + body.append(String.format(".about(%s())", aboutName)); + } + +// String methodPrefix = (type.getSimpleName().contains("boolean")) ? "is" : "get"; +// body.append(".that(actual." + methodPrefix + capitalize(f.getName()) + "());"); + body.append(".that(actual." + f.getName() + "());"); + + has.setBody(body.toString()); + + has.setReturnType(subjectClass); + parent.addImport(subjectClass); + } + + private Optional getSubjectForType(final Class type) { + String name; + if (type.isPrimitive()) { + Class wrapped = ClassUtils.primitiveToWrapper(type); + name = wrapped.getSimpleName(); + } else { + name = type.getSimpleName(); + } + Optional subject = getSubjectFromString(name); + + if (subject.isEmpty()) { + if (Iterable.class.isAssignableFrom(type)) { + subject = getSubjectForType(Iterable.class); + } + } + return subject; + } + + private Optional getSubjectFromString(final String name) { + return Optional.ofNullable(this.subjects.get(name + "Subject")); + } + +} diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java index b8cff62b0..8dd4256df 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java @@ -68,15 +68,32 @@ public void threeLayerSystem(Class source, Class usersMiddleClass) throws /** * Create the place holder middle class, for optional copying into source code * + * @return * @see #threeLayerSystem(Class, Class) */ - public void threeLayerSystem(Class source) throws FileNotFoundException { + public ThreeSystem threeLayerSystem(Class source) throws FileNotFoundException { ParentClass parent = createParent(source); // todo try to see if class already exists first, user may already have a written one and not know MiddleClass middle = createMiddlePlaceHolder(parent.generated, source); - createChild(parent, middle.generated.getQualifiedName(), source, middle.factoryMethod.getName()); + JavaClassSource child = createChild(parent, middle.generated.getQualifiedName(), source, middle.factoryMethod.getName()); + + return new ThreeSystem(source, parent, middle, child); + } + + public class ThreeSystem { + public ThreeSystem(final Class classUnderTest, final ParentClass parent, final MiddleClass middle, final JavaClassSource child) { + this.classUnderTest = classUnderTest; + this.parent = parent; + this.middle = middle; + this.child = child; + } + + public final Class classUnderTest; + public final ParentClass parent; + public final MiddleClass middle; + public final JavaClassSource child; } private MiddleClass createMiddlePlaceHolder(JavaClassSource parent, Class source) throws FileNotFoundException { @@ -96,21 +113,21 @@ private MiddleClass createMiddlePlaceHolder(JavaClassSource parent, Class source return new MiddleClass(middle, factory); } - class AClass { - final JavaClassSource generated; + public static class AClass { + public final JavaClassSource generated; AClass(final JavaClassSource generated) { this.generated = generated; } } - class ParentClass extends AClass { + public static class ParentClass extends AClass { ParentClass(JavaClassSource generated) { super(generated); } } - class MiddleClass extends AClass { + public static class MiddleClass extends AClass { final MethodSource factoryMethod; MiddleClass(JavaClassSource generated, MethodSource factoryMethod) { @@ -168,9 +185,6 @@ private ParentClass createParent(Class source) throws FileNotFoundExcepti addConstructor(source, parent, true); - TestCreator testCreator = new TestCreator(); - testCreator.addTests(parent, source); - writeToDisk(parent); return new ParentClass(parent); } @@ -219,11 +233,11 @@ class ManagedClassSet { public String generate(Class source) throws FileNotFoundException { JavaClassSource javaClass = Roaster.create(JavaClassSource.class); - JavaClassSource handWrittenExampleCode = Roaster.parse(JavaClassSource.class, handWritten); +// JavaClassSource handWrittenExampleCode = Roaster.parse(JavaClassSource.class, handWritten); - registerManagedClass(source, handWrittenExampleCode); +// registerManagedClass(source, handWrittenExampleCode); - javaClass = handWrittenExampleCode; +// javaClass = handWrittenExampleCode; String packageName = source.getPackage().getName(); String sourceName = source.getSimpleName(); @@ -294,7 +308,7 @@ private void addClassJavaDoc(JavaClassSource javaClass, String sourceName) { } } - private String writeToDisk(JavaClassSource javaClass) throws FileNotFoundException { + public static String writeToDisk(JavaClassSource javaClass) throws FileNotFoundException { String classSource = javaClass.toString(); try (PrintWriter out = new PrintWriter(getFileName(javaClass))) { out.println(classSource); @@ -407,7 +421,7 @@ private String getTypeWithGenerics(Class factoryClass, String... classes) { return factoryClass.getSimpleName() + generics; } - private String getFileName(JavaClassSource javaClass) { + private static String getFileName(JavaClassSource javaClass) { String directoryName = getDirectoryName(javaClass); File dir = new File(directoryName); if (!dir.exists()) { @@ -416,98 +430,22 @@ private String getFileName(JavaClassSource javaClass) { return directoryName + javaClass.getName() + ".java"; } - private String getDirectoryName(JavaClassSource javaClass) { + private static String getDirectoryName(JavaClassSource javaClass) { String parent = Paths.get("").toAbsolutePath().toString(); String packageName = javaClass.getPackage(); String packageNameDir = packageName.replace(".", "/"); return parent + "/target/generated-test-sources/" + packageNameDir + "/"; } - private String getFactoryName(Class source) { + public static String getFactoryName(Class source) { String simpleName = source.getSimpleName(); String plural = English.plural(simpleName); String normal = toLowerCaseFirstLetter(plural); return normal; } - private String toLowerCaseFirstLetter(String plural) { + private static String toLowerCaseFirstLetter(String plural) { return plural.substring(0, 1).toLowerCase() + plural.substring(1); } - String handWritten = "package io.confluent.csid.utils;\n" + - "\n" + - "import com.google.common.truth.Subject;\n" + - "import com.google.common.truth.FailureMetadata;\n" + - "import com.google.common.truth.Truth;\n" + - "import io.confluent.parallelconsumer.model.CommitHistory;\n" + - "import io.confluent.parallelconsumer.truth.CommitHistorySubject;\n" + - "import org.apache.kafka.clients.consumer.OffsetAndMetadata;\n" + - "import org.apache.kafka.common.TopicPartition;\n" + - "\n" + - "import java.util.List;\n" + - "import java.util.Map;\n" + - "import java.util.concurrent.CopyOnWriteArrayList;\n" + - "import java.util.stream.Collectors;\n" + - "\n" + - "import static io.confluent.parallelconsumer.truth.CommitHistorySubject.commitHistories;\n" + - "\n" + -// "/**\n" + -// " * Truth Subject for the {@link LongPollingMockConsumer} - extend this class,\n" + -// " * with your own custom assertions.\n" + -// " * \n" + -// " * Note that the generated class will change over time, so your edits will be\n" + -// " * overwritten. Or, you can copy the generated code into your project.\n" + -// " * \n" + -// " * @see LongPollingMockConsumer\n" + - "/** my own docs\n" + - " */\n" + - "public class LongPollingMockConsumerSubject extends Subject {\n" + - "\n" + -// "\tprotected LongPollingMockConsumer actual;\n" + -// "\n" + -// "\tprotected LongPollingMockConsumerSubject(FailureMetadata failureMetadata,\n" + -// "\t\t\tio.confluent.csid.utils.LongPollingMockConsumer actual) {\n" + -// "\t\tsuper(failureMetadata, actual);\n" + -// "\t\tthis.actual = actual;\n" + -// "\t}\n" + -// "\n" + -// "\t/**\n" + -// "\t * Returns an assertion builder for a {@link LongPollingMockConsumer} class.\n" + -// "\t */\n" + -// "\tpublic static Factory longPollingMockConsumers() {\n" + -// "\t\treturn LongPollingMockConsumerSubject::new;\n" + -// "\t}\n" + -// "\n" + -// "\t/**\n" + -// "\t * Entry point for {@link LongPollingMockConsumer} assertions.\n" + -// "\t */\n" + -// "\tpublic static LongPollingMockConsumerSubject assertThat(io.confluent.csid.utils.LongPollingMockConsumer actual) {\n" + -// "\t\treturn Truth.assertAbout(longPollingMockConsumers()).that(actual);\n" + -// "\t}\n" + -// "\n" + -// "\t/**\n" + -// "\t * Convenience entry point for {@link LongPollingMockConsumer} assertions when\n" + -// "\t * being mixed with other \"assertThat\" assertion libraries.\n" + -// "\t * \n" + -// "\t * @see #assertThat\n" + -// "\t */\n" + -// "\tpublic static LongPollingMockConsumerSubject assertTruth(io.confluent.csid.utils.LongPollingMockConsumer actual) {\n" + -// "\t\treturn assertThat(actual);\n" + -// "\t}\n" + - "\n" + - "\tpublic CommitHistorySubject hasCommittedToPartition(TopicPartition tp) {\n" + - "\t\tisNotNull();\n" + - "\t\tCopyOnWriteArrayList> commits = actual.getCommitHistoryInt();\n" + - "\t\tList collect = commits.stream()\n" + - "\t\t\t\t.filter(x -> x.containsKey(tp))\n" + - "\t\t\t\t.map(x -> x.get(tp))\n" + - "\t\t\t\t.collect(Collectors.toList());\n" + - "\t\tCommitHistory commitHistory = new CommitHistory(collect);\n" + - "\t\treturn check(\"hasCommittedToPartition(%s)\", tp).about(commitHistories()).that(commitHistory);\n" + - "\t}\n" + - "\n" + - "void somethingElse(){}\n" + - "}\n"; - - } diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/IdCard.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/IdCard.java new file mode 100644 index 000000000..7d2e4d6c2 --- /dev/null +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/IdCard.java @@ -0,0 +1,39 @@ +package com.google.common.truth.extension.generator; + +import java.util.UUID; + +public class IdCard { + private UUID id = UUID.randomUUID(); + private String name; + private int epoch; + + public IdCard(final UUID id, final String name, final int epoch) { + this.id = id; + this.name = name; + this.epoch = epoch; + } + + public UUID getId() { + return id; + } + + public void setId(final UUID id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public int getEpoch() { + return epoch; + } + + public void setEpoch(final int epoch) { + this.epoch = epoch; + } +} diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/MyEmployee.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/MyEmployee.java index 10ad805e7..90f534f0a 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/MyEmployee.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/MyEmployee.java @@ -1,9 +1,81 @@ package com.google.common.truth.extension.generator; +import java.util.List; + public class MyEmployee { - private long birthYear; + private long birthSeconds; + private boolean employed; + private double weighting; + private int birthYear; + private char id; private String name; + public long getBirthSeconds() { + return birthSeconds; + } + + public void setBirthSeconds(final long birthSeconds) { + this.birthSeconds = birthSeconds; + } + + public boolean isEmployed() { + return employed; + } + + public void setEmployed(final boolean employed) { + this.employed = employed; + } + + public double getWeighting() { + return weighting; + } + + public void setWeighting(final double weighting) { + this.weighting = weighting; + } + + public void setBirthYear(final int birthYear) { + this.birthYear = birthYear; + } + + public char getId() { + return id; + } + + public void setId(final char id) { + this.id = id; + } + + public MyEmployee getBoss() { + return boss; + } + + public void setBoss(final MyEmployee boss) { + this.boss = boss; + } + + public IdCard getCard() { + return card; + } + + public void setCard(final IdCard card) { + this.card = card; + } + + public List getSlipUpList() { + return slipUpList; + } + + public void setSlipUpList(final List slipUpList) { + this.slipUpList = slipUpList; + } + + private MyEmployee boss; + + private IdCard card; + + private List slipUpList; + public MyEmployee(final String name) { this.name = name; } @@ -21,6 +93,6 @@ public long getBirthYear() { } public void setBirthYear(final long birthYear) { - this.birthYear = birthYear; + this.birthYear = (int) birthYear; } } diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/SlipUp.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/SlipUp.java new file mode 100644 index 000000000..1d78da7cb --- /dev/null +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/SlipUp.java @@ -0,0 +1,29 @@ +package com.google.common.truth.extension.generator; + +import java.time.ZonedDateTime; + +public class SlipUp { + private String desc; + private ZonedDateTime occurance; + + public SlipUp(final String desc, final ZonedDateTime occurance) { + this.desc = desc; + this.occurance = occurance; + } + + public String getDesc() { + return desc; + } + + public void setDesc(final String desc) { + this.desc = desc; + } + + public ZonedDateTime getOccurance() { + return occurance; + } + + public void setOccurance(final ZonedDateTime occurance) { + this.occurance = occurance; + } +} diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TestCreatorTest.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TestCreatorTest.java index 59679a018..671d82208 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TestCreatorTest.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TestCreatorTest.java @@ -1,5 +1,6 @@ package com.google.common.truth.extension.generator; +import com.google.common.truth.Truth; import org.jboss.forge.roaster.Roaster; import org.jboss.forge.roaster.model.source.JavaClassSource; import org.junit.Test; @@ -11,9 +12,9 @@ public class TestCreatorTest { @Test public void poc(){ JavaClassSource generated = Roaster.create(JavaClassSource.class); - TestCreator testCreator = new TestCreator(); - testCreator.addTests(generated, MyEmployee.class); + TestGenerator testGenerator = new TestGenerator(); + testGenerator.addTests(generated, MyEmployee.class); - assertThat(generated.toString()).isEqualTo(""); + Truth.assertThat(generated.toString()).isEqualTo(""); } } From 12648cb3c861b2122cd6aad9eceb307a69a1d4ce Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Fri, 30 Jul 2021 11:12:53 +0100 Subject: [PATCH 09/32] WIP: test generators iterate - generates valid code --- .../extension/generator/TestGenerator.java | 48 +++++++++++++------ 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TestGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TestGenerator.java index a337da79c..cf4d49e8d 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TestGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TestGenerator.java @@ -3,6 +3,7 @@ import com.google.common.collect.Lists; import com.google.common.truth.Subject; import org.apache.commons.lang3.ClassUtils; +import org.jboss.forge.roaster.model.source.Import; import org.jboss.forge.roaster.model.source.JavaClassSource; import org.jboss.forge.roaster.model.source.MethodSource; import org.reflections.ReflectionUtils; @@ -13,7 +14,9 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; +import java.util.stream.Stream; +import static java.lang.String.format; import static org.reflections.ReflectionUtils.*; public class TestGenerator { @@ -53,7 +56,7 @@ public void addTests(JavaClassSource parent, Class classUnderTest) { } } - private void addPrimitiveTest(Method f, JavaClassSource parent, Class classUnderTest) { + private void addPrimitiveTest(Method f, JavaClassSource generated, Class classUnderTest) { Class type = f.getReturnType(); Class aClass1 = ClassUtils.primitiveToWrapper(type); @@ -72,7 +75,7 @@ private void addPrimitiveTest(Method f, JavaClassSource parent, Class classUn // todo add versions with and with the get String prefix = (type.getSimpleName().contains("boolean")) ? "is" : ""; - MethodSource has = parent.addMethod() + MethodSource has = generated.addMethod() .setName(prefix + f.getName()) .setPublic(); @@ -84,7 +87,7 @@ private void addPrimitiveTest(Method f, JavaClassSource parent, Class classUn // todo add support truth8 extensions - optional etc // todo try generatin classes for DateTime pakages, like Instant and Duration // todo this is of course too aggresive - List specials = Lists.newArrayList("String", "BigDecimal", "Iterable", "Optional", "List"); + List specials = Lists.newArrayList("String", "BigDecimal", "Iterable", "List"); boolean isCoveredByNonPrimitiveStandardSubjects = specials.contains(type.getSimpleName()); boolean notPrimitive = !type.isPrimitive(); @@ -92,26 +95,41 @@ private void addPrimitiveTest(Method f, JavaClassSource parent, Class classUn // need to get the Subject instance using about // return check("hasCommittedToPartition(%s)", tp).about(commitHistories()).that(commitHistory); String aboutName; - Set factoryPotentials = getMethods(subjectClass, x -> - !x.getName().startsWith("assert") - ); - if (factoryPotentials.isEmpty()) { - aboutName = TruthGenerator.getFactoryName(type); // take a guess - } else { - Method method = factoryPotentials.stream().findFirst().get(); - aboutName = method.getName(); - } - body.append(String.format(".about(%s())", aboutName)); +// Set factoryPotentials = getMethods(subjectClass, x -> +// !x.getName().startsWith("assert") // the factory method won't be the assert methods +// && !x.getName().startsWith("lambda") // the factory method won't be the assert methods +// ); +// if (factoryPotentials.isEmpty()) { + aboutName = TruthGenerator.getFactoryName(type); // take a guess +// } else { +// Method method = factoryPotentials.stream().findFirst().get(); +// aboutName = method.getName(); +// } + body.append(format(".about(%s())", aboutName)); + + // import + Optional factoryContainer = this.subjects.values().parallelStream() + .filter(classes -> Arrays.stream(classes.getMethods()) + .anyMatch(methods -> methods.getName().equals(aboutName))) + .findFirst(); + if (factoryContainer.isPresent()) { + Class container = factoryContainer.get(); + Import anImport = generated.addImport(container); + String name = container.getCanonicalName() + "." + aboutName; + anImport.setName(name) // todo better way to do static method import? + .setStatic(true); + } else + System.err.println(format("Can't find container for method %s", aboutName)); } // String methodPrefix = (type.getSimpleName().contains("boolean")) ? "is" : "get"; // body.append(".that(actual." + methodPrefix + capitalize(f.getName()) + "());"); - body.append(".that(actual." + f.getName() + "());"); + body.append(format(".that(actual.%s());", f.getName())); has.setBody(body.toString()); has.setReturnType(subjectClass); - parent.addImport(subjectClass); + generated.addImport(subjectClass); } private Optional getSubjectForType(final Class type) { From b014e0b6e79a329064ea8e0ccd92aba908f37583 Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Fri, 30 Jul 2021 19:01:47 +0100 Subject: [PATCH 10/32] WIP: test generators iterate - generates valid code on big project - enums, arrays, etc etc --- extensions/generator/pom.xml | 23 +- .../extension/generator/TestGenerator.java | 375 +++++++++++---- .../extension/generator/TruthGenerator.java | 451 ------------------ .../truth/extension/generator/SlipUp.java | 29 -- .../extension/generator/TestCreatorTest.java | 17 +- .../generator/TruthGeneratorTest.java | 98 +++- .../truth/extension/generator/TypeTests.java | 25 + .../generator/{ => model}/IdCard.java | 11 +- .../generator/{ => model}/MyEmployee.java | 50 +- .../extension/generator/model/Project.java | 29 ++ 10 files changed, 475 insertions(+), 633 deletions(-) delete mode 100644 extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java delete mode 100644 extensions/generator/src/test/java/com/google/common/truth/extension/generator/SlipUp.java create mode 100644 extensions/generator/src/test/java/com/google/common/truth/extension/generator/TypeTests.java rename extensions/generator/src/test/java/com/google/common/truth/extension/generator/{ => model}/IdCard.java (71%) rename extensions/generator/src/test/java/com/google/common/truth/extension/generator/{ => model}/MyEmployee.java (64%) create mode 100644 extensions/generator/src/test/java/com/google/common/truth/extension/generator/model/Project.java diff --git a/extensions/generator/pom.xml b/extensions/generator/pom.xml index 87e364ad1..283204d8e 100644 --- a/extensions/generator/pom.xml +++ b/extensions/generator/pom.xml @@ -26,12 +26,11 @@ org.jboss.forge.roaster roaster-jdt ${version.roaster} - runtime + compile com.google.truth truth - jar compile @@ -66,19 +65,25 @@ commons-lang3 3.12.0 - - com.google.truth - truth - test - com.google.guava guava 30.1.1-jre - com.google.truth - truth + com.google.flogger + flogger + 0.6 + + + com.google.flogger + flogger-log4j2-backend + 0.6 + + + com.google.flogger + flogger-system-backend + 0.6 diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TestGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TestGenerator.java index cf4d49e8d..0f04de3cd 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TestGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TestGenerator.java @@ -1,7 +1,11 @@ package com.google.common.truth.extension.generator; -import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.common.flogger.FluentLogger; +import com.google.common.truth.ObjectArraySubject; import com.google.common.truth.Subject; +import com.google.common.truth.extension.generator.internal.TruthGenerator; +import com.google.common.truth.extension.generator.internal.model.ThreeSystem; import org.apache.commons.lang3.ClassUtils; import org.jboss.forge.roaster.model.source.Import; import org.jboss.forge.roaster.model.source.JavaClassSource; @@ -9,149 +13,316 @@ import org.reflections.ReflectionUtils; import org.reflections.Reflections; -import java.io.FileNotFoundException; -import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; -import java.util.stream.Stream; +import java.util.stream.Collectors; import static java.lang.String.format; +import static java.util.Optional.empty; +import static java.util.Optional.ofNullable; +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; import static org.reflections.ReflectionUtils.*; +/** + * @author Antony Stubbs + */ public class TestGenerator { - private final Map subjects; + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - public TestGenerator() { - Reflections reflections = new Reflections("com.google.common.truth", "io.confluent"); - Set> subTypes = reflections.getSubTypesOf(Subject.class); + private final Map compiledSubjects; + private final Map generatedSubjects; - Map maps = new HashMap<>(); - subTypes.stream().forEach(x -> maps.put(x.getSimpleName(), x)); - this.subjects = maps; + public TestGenerator(final Set allTypes) { + this.generatedSubjects = allTypes.stream().collect(Collectors.toMap(x -> x.classUnderTest.getSimpleName(), x -> x)); + + Reflections reflections = new Reflections("com.google.common.truth", "io.confluent"); + Set> subTypes = reflections.getSubTypesOf(Subject.class); + + Map maps = new HashMap<>(); + subTypes.stream().forEach(x -> maps.put(x.getSimpleName(), x)); + this.compiledSubjects = maps; + } + + + public void addTests(JavaClassSource parent, Class classUnderTest) { + Collection getters = getMethods(classUnderTest); + + // + for (Method method : getters) { + addPrimitiveTest(method, parent, classUnderTest); } + } + private Collection getMethods(final Class classUnderTest) { + Set getters = ReflectionUtils.getAllMethods(classUnderTest, + withModifier(Modifier.PUBLIC), withPrefix("get"), withParametersCount(0)); - public void addTests(JavaClassSource parent, Class classUnderTest) { - Set allFields = ReflectionUtils.getAllFields(classUnderTest, x -> true); + Set issers = ReflectionUtils.getAllMethods(classUnderTest, + withModifier(Modifier.PUBLIC), withPrefix("is"), withParametersCount(0)); - Set getters = ReflectionUtils.getAllMethods(classUnderTest, - withModifier(Modifier.PUBLIC), withPrefix("get"), withParametersCount(0)); + getters.addAll(issers);; - Set issers = ReflectionUtils.getAllMethods(classUnderTest, - withModifier(Modifier.PUBLIC), withPrefix("is"), withParametersCount(0)); + return removeOverridden(getters); + } - getters.addAll(issers); + private Collection removeOverridden(final Set getters) { + Map result = new HashMap<>(); + for (Method getter : getters) { + String sig = getSignature(getter); + if (result.containsKey(sig)) { + Method existing = result.get(sig); + Class existingDeclaringClass = existing.getDeclaringClass(); + Class newDeclaringClass = getter.getDeclaringClass(); - // - for (Method method : getters) { - addPrimitiveTest(method, parent, classUnderTest); + if (existingDeclaringClass.isAssignableFrom(newDeclaringClass)) { + // replace + result.put(sig, getter); + } else { + // skip } + } else { + result.put(sig, getter); + } + } + return result.values(); + } - try { - TruthGenerator.writeToDisk(parent); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } + private String getSignature(final Method getter) { + return getter.getName() + Arrays.stream(getter.getParameterTypes()).map(Class::getName).collect(Collectors.toList()); + } + + /** + * In priority order - most specific first + */ + private final HashSet> nativeTypes = Sets.newHashSet( + Map.class, + Set.class, + List.class, + Iterable.class, + Number.class, + String.class, + Comparable.class, + Class.class // Enum#getDeclaringClass + ); + + private void addPrimitiveTest(Method method, JavaClassSource generated, Class classUnderTest) { + Class returnType = method.getReturnType(); + + boolean isCoveredByNonPrimitiveStandardSubjects = isTypeCoveredUnderStandardSubjects(returnType); + + Optional subjectForType = getSubjectForType(returnType); + + // no subject to chain + // todo needs two passes - one to generate the custom classes, then one to use them in other classes + // should generate all base classes first, then run the test creator pass afterwards + if (subjectForType.isEmpty() && !isCoveredByNonPrimitiveStandardSubjects) { + logger.at(WARNING).log("Cant find subject for " + returnType); + // todo log + return; } - private void addPrimitiveTest(Method f, JavaClassSource generated, Class classUnderTest) { - Class type = f.getReturnType(); - Class aClass1 = ClassUtils.primitiveToWrapper(type); + ClassOrGenerated subjectClass = subjectForType.get(); - Optional subjectForType = getSubjectForType(type); + // todo add versions with and with the get + //String prefix = (returnType.getSimpleName().contains("boolean")) ? "" : ""; + MethodSource has = generated.addMethod() +// .setName(prefix + method.getName()) + .setName(method.getName()) + .setPublic(); - // no subject to chain - // todo needs two passes - one to generate the custom classes, then one to use them in other classes - // should generate all base classes first, then run the test creator pass afterwards - if (subjectForType.isEmpty()) { - System.out.println("Cant find subject for " + type); - // todo log - return; - } + StringBuilder body = new StringBuilder("isNotNull();\n"); + String check = "return check(\"" + method.getName() + "\")"; + body.append(check); - Class subjectClass = subjectForType.get(); - - // todo add versions with and with the get - String prefix = (type.getSimpleName().contains("boolean")) ? "is" : ""; - MethodSource has = generated.addMethod() - .setName(prefix + f.getName()) - .setPublic(); - - StringBuilder body = new StringBuilder("isNotNull();\n"); - String check = "return check(\"" + f.getName() + "\")"; - body.append(check); - - // todo use qualified names - // todo add support truth8 extensions - optional etc - // todo try generatin classes for DateTime pakages, like Instant and Duration - // todo this is of course too aggresive - List specials = Lists.newArrayList("String", "BigDecimal", "Iterable", "List"); - - boolean isCoveredByNonPrimitiveStandardSubjects = specials.contains(type.getSimpleName()); - boolean notPrimitive = !type.isPrimitive(); - if (notPrimitive && !isCoveredByNonPrimitiveStandardSubjects) { - // need to get the Subject instance using about - // return check("hasCommittedToPartition(%s)", tp).about(commitHistories()).that(commitHistory); - String aboutName; + + boolean notPrimitive = !returnType.isPrimitive(); + boolean needsAboutCall = notPrimitive && !isCoveredByNonPrimitiveStandardSubjects; + if (needsAboutCall || subjectClass.isGenerated()) { + // need to get the Subject instance using about + // return check("hasCommittedToPartition(%s)", tp).about(commitHistories()).that(commitHistory); + String aboutName; // Set factoryPotentials = getMethods(subjectClass, x -> // !x.getName().startsWith("assert") // the factory method won't be the assert methods // && !x.getName().startsWith("lambda") // the factory method won't be the assert methods // ); // if (factoryPotentials.isEmpty()) { - aboutName = TruthGenerator.getFactoryName(type); // take a guess + aboutName = TruthGenerator.getFactoryName(returnType); // take a guess // } else { // Method method = factoryPotentials.stream().findFirst().get(); // aboutName = method.getName(); // } - body.append(format(".about(%s())", aboutName)); - - // import - Optional factoryContainer = this.subjects.values().parallelStream() - .filter(classes -> Arrays.stream(classes.getMethods()) - .anyMatch(methods -> methods.getName().equals(aboutName))) - .findFirst(); - if (factoryContainer.isPresent()) { - Class container = factoryContainer.get(); - Import anImport = generated.addImport(container); - String name = container.getCanonicalName() + "." + aboutName; - anImport.setName(name) // todo better way to do static method import? - .setStatic(true); - } else - System.err.println(format("Can't find container for method %s", aboutName)); - } + body.append(format(".about(%s())", aboutName)); -// String methodPrefix = (type.getSimpleName().contains("boolean")) ? "is" : "get"; -// body.append(".that(actual." + methodPrefix + capitalize(f.getName()) + "());"); - body.append(format(".that(actual.%s());", f.getName())); + // import + String factoryContainer = subjectClass.getFactoryContainerName(); +// Optional factoryContainerOld = this.compiledSubjects.values().parallelStream() +// .filter(classes -> Arrays.stream(classes.getMethods()) +// .anyMatch(methods -> methods.getName().equals(aboutName))) +// .findFirst(); +// if (factoryContainer.isPresent()) { +// Class container = factoryContainer.get(); + Import anImport = generated.addImport(factoryContainer); + String name = factoryContainer + "." + aboutName; + anImport.setName(name) // todo better way to do static method import? + .setStatic(true); +// } else +// System.err.println(format("Can't find container for method %s", aboutName)); - has.setBody(body.toString()); - has.setReturnType(subjectClass); - generated.addImport(subjectClass); } - private Optional getSubjectForType(final Class type) { - String name; - if (type.isPrimitive()) { - Class wrapped = ClassUtils.primitiveToWrapper(type); - name = wrapped.getSimpleName(); - } else { - name = type.getSimpleName(); - } - Optional subject = getSubjectFromString(name); +// String methodPrefix = (returnType.getSimpleName().contains("boolean")) ? "is" : "get"; +// body.append(".that(actual." + methodPrefix + capitalize(method.getName()) + "());"); + body.append(format(".that(actual.%s());", method.getName())); - if (subject.isEmpty()) { - if (Iterable.class.isAssignableFrom(type)) { - subject = getSubjectForType(Iterable.class); - } - } - return subject; + has.setBody(body.toString()); + + has.setReturnType(subjectClass.getSubjectSimpleName()); + + generated.addImport(subjectClass.getSubjectQualifiedName()); + } + + private boolean isTypeCoveredUnderStandardSubjects(final Class returnType) { + // todo should only do this, if we can't find a more specific subect for the returnType + // todo should check if class is assignable from the super subjects, instead of checking names + // todo use qualified names + // todo add support truth8 extensions - optional etc + // todo try generatin classes for DateTime pakages, like Instant and Duration + // todo this is of course too aggressive + +// boolean isCoveredByNonPrimitiveStandardSubjects = specials.contains(returnType.getSimpleName()); + boolean isCoveredByNonPrimitiveStandardSubjects = nativeTypes.stream().anyMatch(x->x.isAssignableFrom(returnType)); + + // todo is it an array of objects? + boolean array = returnType.isArray(); + Class[] classes = returnType.getClasses(); + String typeName = returnType.getTypeName(); + Class componentType = returnType.getComponentType(); + + return isCoveredByNonPrimitiveStandardSubjects || array; + } + + private Optional getSubjectForType(final Class type) { + String name; + if (type.isPrimitive()) { + Class wrapped = ClassUtils.primitiveToWrapper(type); + name = wrapped.getSimpleName(); + } else { + name = type.getSimpleName(); + } + Optional subject = getSubjectFromString(name); + + if (subject.isEmpty()) { + if (Iterable.class.isAssignableFrom(type)) { + subject = getSubjectForType(Iterable.class); + } } - private Optional getSubjectFromString(final String name) { - return Optional.ofNullable(this.subjects.get(name + "Subject")); + // fall back to native subjects + if(subject.isEmpty()){ + subject = ClassOrGenerated.ofClass(getNativeSubjectForType(type)); + if(subject.isPresent()) + logger.at(INFO).log("Falling back to native interface subject %s for type %s", subject.get().clazz, type); } + return subject; + } + + private Optional> getNativeSubjectForType(final Class type) { + Optional> first = nativeTypes.stream().filter(x -> x.isAssignableFrom(type)).findFirst(); + if(first.isPresent()){ + Class aClass = first.get(); + Class compiledSubjectForTypeName = getCompiledSubjectForTypeName(aClass.getSimpleName()); + return ofNullable(compiledSubjectForTypeName); + } + return empty(); + } + + private Optional getSubjectFromString(final String name) { + if (name.endsWith("[]")) + return Optional.of(new ClassOrGenerated(ObjectArraySubject.class, null)); + + Optional subjectFromGenerated = getSubjectFromGenerated(name);// takes precedence + if(subjectFromGenerated.isPresent()) { + return Optional.of(new ClassOrGenerated(null, subjectFromGenerated.get())); +// String returnName = subjectFromGenerated.get().middle.factoryMethod.getReturnType().getName(); +// Class aClass = Class.forName(returnName); +// return Optional.of(aClass); + } + + Class aClass = getCompiledSubjectForTypeName(name); + if (aClass != null) + return Optional.of(new ClassOrGenerated(aClass, null)); + + return empty(); + } + + private Class getCompiledSubjectForTypeName(final String name) { + Class aClass = this.compiledSubjects.get(name + "Subject"); + return aClass; + } + + private Optional getSubjectFromGenerated(final String name) { + return Optional.ofNullable(this.generatedSubjects.get(name)); + } + + public void addTests(final Set allTypes) { + for (ThreeSystem c : allTypes) { + addTests(c.parent.generated, c.classUnderTest); + } + + // only serialise results, when all have finished - useful for debugging + for (ThreeSystem c : allTypes) { + TruthGenerator.writeToDisk(c.parent.generated); + } + } + + static class ClassOrGenerated { + final Class clazz; + final ThreeSystem generated; + + ClassOrGenerated(final Class clazz, final ThreeSystem generated) { + this.clazz = clazz; + this.generated = generated; + } + + static Optional ofClass(Optional> clazz) { + if (clazz.isPresent()) + return Optional.of(new ClassOrGenerated(clazz.get(), null)); + else return empty(); + } + + String getSubjectSimpleName() { + if (clazz == null) + return this.generated.middle.generated.getName(); + else + return clazz.getSimpleName(); + } + + String getSubjectQualifiedName() { + if (clazz == null) + return this.generated.middle.generated.getQualifiedName(); + else + return clazz.getCanonicalName(); + } + + @Override + public String toString() { + return "ClassOrGenerated{" + + "clazz=" + clazz + + ", generated=" + generated + + '}'; + } + + public boolean isGenerated() { + return generated!=null; + } + + public String getFactoryContainerName() { + return (isGenerated()) ? generated.middle.generated.getCanonicalName() : clazz.getCanonicalName(); + } + } } diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java deleted file mode 100644 index 8dd4256df..000000000 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGenerator.java +++ /dev/null @@ -1,451 +0,0 @@ -package com.google.common.truth.extension.generator; - -import com.google.common.base.Joiner; -import com.google.common.truth.FailureMetadata; -import com.google.common.truth.Subject; -import com.google.common.truth.Truth; -import org.atteo.evo.inflector.English; -import org.jboss.forge.roaster.Roaster; -import org.jboss.forge.roaster.model.Method; -import org.jboss.forge.roaster.model.source.Import; -import org.jboss.forge.roaster.model.source.JavaClassSource; -import org.jboss.forge.roaster.model.source.JavaDocSource; -import org.jboss.forge.roaster.model.source.MethodSource; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.PrintWriter; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; - -public class TruthGenerator { - - private final String basePackage; - - public TruthGenerator(String basePackage) { - this.basePackage = basePackage; - } - - private final List managedSubjects = new ArrayList<>(); - private final List children = new ArrayList<>(); - - /** - * Takes a user maintained source file, and adds boiler plate that is missing. If aggressively skips parst if it - * thinks the user has overridden something. - */ - public String maintain(Class source, Class userAndGeneratedMix) { - throw new IllegalStateException(); - } - - /** - * Uses an optional three layer system to manage the Subjects. - *

    - *
  1. The top layer extends Subject and stores the actual and factory. - *
  2. The second layer is the user's code - they extend the first layer, and add their custom assertion methods there. - *
  3. The third layer extends the user's class, and stores the generated entry points, so that users's get's access - * to all three layers, by only importing the bottom layer. - *
- *

- * For any source class that doesn't have a user created middle class, an empty one will be generated, that the user - * can copy into their source control. If it's used, a helpful message will be written to the console, prompting the - * user to do so. These messages can be globally disabled. - *

- * This way there's no complexity with mixing generated and user written code, but at the cost of 2 extra classes - * per source. While still allowing the user to leverage the full code generation system but maintaining their own extensions - * with clear separation from the code generation. - */ - public void threeLayerSystem(Class source, Class usersMiddleClass) throws FileNotFoundException { - // make parent - boiler plate access - ParentClass parent = createParent(source); - - String factoryMethodName = getFactoryName(source); - - // make child - client code entry point - createChild(parent, usersMiddleClass.getName(), source, factoryMethodName); - } - - /** - * Create the place holder middle class, for optional copying into source code - * - * @return - * @see #threeLayerSystem(Class, Class) - */ - public ThreeSystem threeLayerSystem(Class source) throws FileNotFoundException { - ParentClass parent = createParent(source); - - // todo try to see if class already exists first, user may already have a written one and not know - MiddleClass middle = createMiddlePlaceHolder(parent.generated, source); - - JavaClassSource child = createChild(parent, middle.generated.getQualifiedName(), source, middle.factoryMethod.getName()); - - return new ThreeSystem(source, parent, middle, child); - } - - public class ThreeSystem { - public ThreeSystem(final Class classUnderTest, final ParentClass parent, final MiddleClass middle, final JavaClassSource child) { - this.classUnderTest = classUnderTest; - this.parent = parent; - this.middle = middle; - this.child = child; - } - - public final Class classUnderTest; - public final ParentClass parent; - public final MiddleClass middle; - public final JavaClassSource child; - } - - private MiddleClass createMiddlePlaceHolder(JavaClassSource parent, Class source) throws FileNotFoundException { - JavaClassSource middle = Roaster.create(JavaClassSource.class); - middle.setName(getSubjectName(source.getSimpleName())); - middle.setPackage(parent.getPackage()); - middle.extendSuperType(parent); - JavaDocSource jd = middle.getJavaDoc(); - jd.setText("Optionally move this class into source control, and add your custom assertions here."); - jd.addTagValue("@see", parent.getName()); - - addConstructor(source, middle, false); - - MethodSource factory = addFactoryAccesor(source, middle, source.getSimpleName()); - - writeToDisk(middle); - return new MiddleClass(middle, factory); - } - - public static class AClass { - public final JavaClassSource generated; - - AClass(final JavaClassSource generated) { - this.generated = generated; - } - } - - public static class ParentClass extends AClass { - ParentClass(JavaClassSource generated) { - super(generated); - } - } - - public static class MiddleClass extends AClass { - final MethodSource factoryMethod; - - MiddleClass(JavaClassSource generated, MethodSource factoryMethod) { - super(generated); - this.factoryMethod = factoryMethod; - } - } - - /** - * Having collected together all the access points, creates one large class filled with access points to all of - * them. - *

- * The overall access will throw an error if any middle classes don't correctly extend their parent. - */ - public void createOverallAccessPoints() throws FileNotFoundException { - JavaClassSource overallAccess = Roaster.create(JavaClassSource.class); - overallAccess.setName("ManagedTruth"); - overallAccess.getJavaDoc() - .setText("Single point of access for all managed Subjects."); - overallAccess.setPublic() - .setPackage(getManagedClassesBasePackage()); - - // brute force - for (JavaClassSource j : children) { - List> methods = j.getMethods(); - for (Method m : methods) { - overallAccess.addMethod(m); - } - // this seems like overkill, but at least in the child style case, there's very few imports - even - // none extra at all (aside from wild card vs specific methods). - List imports = j.getImports(); - for (Import i : imports) { - overallAccess.addImport(i); - } - } - - writeToDisk(overallAccess); - } - - private String getManagedClassesBasePackage() { - return basePackage; - } - - private ParentClass createParent(Class source) throws FileNotFoundException { - JavaClassSource parent = Roaster.create(JavaClassSource.class); - String sourceName = source.getSimpleName(); - String parentName = getSubjectName(sourceName + "Parent"); - parent.setName(parentName); - - addPackageSuperAndAnnotation(parent, source); - - addClassJavaDoc(parent, sourceName); - - addActualField(source, parent); - - addConstructor(source, parent, true); - - writeToDisk(parent); - return new ParentClass(parent); - } - - private void addPackageSuperAndAnnotation(final JavaClassSource javaClass, final Class source) { - addPackageSuperAndAnnotation(javaClass, source.getPackage().getName(), getSubjectName(source.getSimpleName())); - } - - private JavaClassSource createChild(ParentClass parent, - String usersMiddleClassName, - Class source, - String factoryMethodName) throws FileNotFoundException { - // todo if middle doesn't extend parent, warn - - JavaClassSource child = Roaster.create(JavaClassSource.class); - child.setName(getSubjectName(source.getSimpleName() + "Child")); - child.setPackage(parent.generated.getPackage()); - JavaDocSource javaDoc = child.getJavaDoc(); - javaDoc.setText("Entry point for assertions for @{" + source.getSimpleName() + "}. Import the static accessor methods from this class and use them.\n" + - "Combines the generated code from {@" + parent.generated.getName() + "}and the user code from {@" + usersMiddleClassName + "}."); - javaDoc.addTagValue("@see", source.getName()); - javaDoc.addTagValue("@see", usersMiddleClassName); - javaDoc.addTagValue("@see", parent.generated.getName()); - - addAccessPoints(source, child, factoryMethodName, usersMiddleClassName); - - writeToDisk(child); - children.add(child); - return child; - } - - private void registerManagedClass(Class sourceClass, JavaClassSource gengeratedClass) { - managedSubjects.add(new ManagedClassSet(sourceClass, gengeratedClass)); - } - - class ManagedClassSet { - final Class sourceClass; - final JavaClassSource generatedClass; - - ManagedClassSet(final Class sourceClass, final JavaClassSource generatedClass) { - this.sourceClass = sourceClass; - this.generatedClass = generatedClass; - } - } - - public String generate(Class source) throws FileNotFoundException { - JavaClassSource javaClass = Roaster.create(JavaClassSource.class); - -// JavaClassSource handWrittenExampleCode = Roaster.parse(JavaClassSource.class, handWritten); - -// registerManagedClass(source, handWrittenExampleCode); - -// javaClass = handWrittenExampleCode; - - String packageName = source.getPackage().getName(); - String sourceName = source.getSimpleName(); - String subjectClassName = getSubjectName(sourceName); - - - addPackageSuperAndAnnotation(javaClass, packageName, subjectClassName); - - addClassJavaDoc(javaClass, sourceName); - - addActualField(source, javaClass); - - addConstructor(source, javaClass, true); - - MethodSource factory = addFactoryAccesor(source, javaClass, sourceName); - - addAccessPoints(source, javaClass, factory.getName(), javaClass.getQualifiedName()); - - // todo add static import for Truth.assertAbout somehow? -// Import anImport = javaClass.addImport(Truth.class); -// javaClass.addImport(anImport.setStatic(true)); -// javaClass.addImport(new Im) - - String classSource = writeToDisk(javaClass); - - return classSource; - } - - private String getSubjectName(final String sourceName) { - return sourceName + "Subject"; - } - - private void addAccessPoints(Class source, JavaClassSource javaClass, - String factoryMethod, - String factoryContainerQualifiedName) { - MethodSource assertThat = addAssertThat(source, javaClass, factoryMethod, factoryContainerQualifiedName); - - addAssertTruth(source, javaClass, assertThat); - } - - private void addPackageSuperAndAnnotation(JavaClassSource javaClass, String packageName, String subjectClassName) { - javaClass.setPackage(packageName); - - // extend - javaClass.extendSuperType(Subject.class); - - // requires java 9 - // annotate generated - // @javax.annotation.Generated(value="") - // only in @since 1.9, so can't add it programmatically - // AnnotationSource generated = javaClass.addAnnotation(Generated.class); - // Can't add it without the value param, see https://github.com/forge/roaster/issues/201 - // AnnotationSource generated = javaClass.addAnnotation("javax.annotation.processing.Generated"); - // generated.addAnnotationValue("truth-generator"); https://github.com/forge/roaster/issues/201 - } - - private void addClassJavaDoc(JavaClassSource javaClass, String sourceName) { - // class javadc - JavaDocSource classDocs = javaClass.getJavaDoc(); - if (classDocs.getFullText().isEmpty()) { - classDocs.setText("Truth Subject for the {@link " + sourceName + "}." + - "\n\n" + - "Note that this class is generated / managed, and will change over time. So any changes you might " + - "make will be overwritten."); - classDocs.addTagValue("@see", sourceName); - classDocs.addTagValue("@see", getSubjectName(sourceName)); - classDocs.addTagValue("@see", getSubjectName(sourceName + "Child")); - } - } - - public static String writeToDisk(JavaClassSource javaClass) throws FileNotFoundException { - String classSource = javaClass.toString(); - try (PrintWriter out = new PrintWriter(getFileName(javaClass))) { - out.println(classSource); - } - return classSource; - } - - private void addAssertTruth(Class source, JavaClassSource javaClass, MethodSource assertThat) { - String name = "assertTruth"; - if (!containsMethodCalled(javaClass, name)) { - // convenience entry point when being mixed with other "assertThat" assertion libraries - MethodSource assertTruth = javaClass.addMethod() - .setName(name) - .setPublic() - .setStatic(true) - .setReturnType(assertThat.getReturnType()); - assertTruth.addParameter(source, "actual"); - assertTruth.setBody("return " + assertThat.getName() + "(actual);"); - assertTruth.getJavaDoc().setText("Convenience entry point for {@link " + source.getSimpleName() + "} assertions when being " + - "mixed with other \"assertThat\" assertion libraries.") - .addTagValue("@see", "#assertThat"); - } - } - - private MethodSource addAssertThat(Class source, - JavaClassSource javaClass, - String factoryMethodName, - String factoryContainerQualifiedName) { - String methodName = "assertThat"; - if (containsMethodCalled(javaClass, methodName)) { - return getMethodCalled(javaClass, methodName); - } else { - // entry point - MethodSource assertThat = javaClass.addMethod() - .setName(methodName) - .setPublic() - .setStatic(true) - .setReturnType(factoryContainerQualifiedName); - assertThat.addParameter(source, "actual"); - // return assertAbout(things()).that(actual); - // add explicit static reference for now - see below - javaClass.addImport(factoryContainerQualifiedName + ".*") - .setStatic(true); - String entryPointBody = "return Truth.assertAbout(" + factoryMethodName + "()).that(actual);"; - assertThat.setBody(entryPointBody); - javaClass.addImport(Truth.class); - assertThat.getJavaDoc().setText("Entry point for {@link " + source.getSimpleName() + "} assertions."); - return assertThat; - } - } - - private MethodSource getMethodCalled(JavaClassSource javaClass, String methodName) { - return javaClass.getMethods().stream().filter(x -> x.getName().equals(methodName)).findFirst().get(); - } - - private MethodSource addFactoryAccesor(Class source, JavaClassSource javaClass, String sourceName) { - String factoryName = getFactoryName(source); - if (containsMethodCalled(javaClass, factoryName)) { - return getMethodCalled(javaClass, factoryName); - } else { - // factory accessor - String returnType = getTypeWithGenerics(Subject.Factory.class, javaClass.getName(), sourceName); - MethodSource factory = javaClass.addMethod() - .setName(factoryName) - .setPublic() - .setStatic(true) - // todo replace with something other than the string method - I suppose it's not possible to do generics type safely - .setReturnType(returnType) - .setBody("return " + javaClass.getName() + "::new;"); - JavaDocSource> factoryDocs = factory.getJavaDoc(); - factoryDocs.setText("Returns an assertion builder for a {@link " + sourceName + "} class."); - return factory; - } - } - - private boolean containsMethodCalled(JavaClassSource javaClass, String factoryName) { - return javaClass.getMethods().stream().anyMatch(x -> x.getName().equals(factoryName)); - } - - private void addConstructor(Class source, JavaClassSource javaClass, boolean setActual) { - if (!javaClass.getMethods().stream().anyMatch(x -> x.isConstructor())) { - // constructor - MethodSource constructor = javaClass.addMethod() - .setConstructor(true) - .setProtected(); - constructor.addParameter(FailureMetadata.class, "failureMetadata"); - constructor.addParameter(source, "actual"); - StringBuilder sb = new StringBuilder("super(failureMetadata, actual);\n"); - if (setActual) - sb.append("this.actual = actual;"); - constructor.setBody(sb.toString()); - } - } - - private void addActualField(Class source, JavaClassSource javaClass) { - String fieldName = "actual"; - if (javaClass.getField(fieldName) == null) { - // actual field - javaClass.addField() - .setProtected() - .setType(source) - .setName(fieldName) - .setFinal(true); - } - } - - private String getTypeWithGenerics(Class factoryClass, String... classes) { - String genericsList = Joiner.on(", ").skipNulls().join(classes); - String generics = new StringBuilder("<>").insert(1, genericsList).toString(); - return factoryClass.getSimpleName() + generics; - } - - private static String getFileName(JavaClassSource javaClass) { - String directoryName = getDirectoryName(javaClass); - File dir = new File(directoryName); - if (!dir.exists()) { - boolean mkdir = dir.mkdirs(); - } - return directoryName + javaClass.getName() + ".java"; - } - - private static String getDirectoryName(JavaClassSource javaClass) { - String parent = Paths.get("").toAbsolutePath().toString(); - String packageName = javaClass.getPackage(); - String packageNameDir = packageName.replace(".", "/"); - return parent + "/target/generated-test-sources/" + packageNameDir + "/"; - } - - public static String getFactoryName(Class source) { - String simpleName = source.getSimpleName(); - String plural = English.plural(simpleName); - String normal = toLowerCaseFirstLetter(plural); - return normal; - } - - private static String toLowerCaseFirstLetter(String plural) { - return plural.substring(0, 1).toLowerCase() + plural.substring(1); - } - -} diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/SlipUp.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/SlipUp.java deleted file mode 100644 index 1d78da7cb..000000000 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/SlipUp.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.google.common.truth.extension.generator; - -import java.time.ZonedDateTime; - -public class SlipUp { - private String desc; - private ZonedDateTime occurance; - - public SlipUp(final String desc, final ZonedDateTime occurance) { - this.desc = desc; - this.occurance = occurance; - } - - public String getDesc() { - return desc; - } - - public void setDesc(final String desc) { - this.desc = desc; - } - - public ZonedDateTime getOccurance() { - return occurance; - } - - public void setOccurance(final ZonedDateTime occurance) { - this.occurance = occurance; - } -} diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TestCreatorTest.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TestCreatorTest.java index 671d82208..a85b066f2 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TestCreatorTest.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TestCreatorTest.java @@ -1,20 +1,23 @@ package com.google.common.truth.extension.generator; import com.google.common.truth.Truth; +import com.google.common.truth.extension.generator.model.MyEmployee; import org.jboss.forge.roaster.Roaster; import org.jboss.forge.roaster.model.source.JavaClassSource; import org.junit.Test; +import java.util.Set; + import static com.google.common.truth.Truth.assertThat; public class TestCreatorTest { - @Test - public void poc(){ - JavaClassSource generated = Roaster.create(JavaClassSource.class); - TestGenerator testGenerator = new TestGenerator(); - testGenerator.addTests(generated, MyEmployee.class); + @Test + public void poc(){ + JavaClassSource generated = Roaster.create(JavaClassSource.class); + TestGenerator testGenerator = new TestGenerator(Set.of()); + testGenerator.addTests(generated, MyEmployee.class); - Truth.assertThat(generated.toString()).isEqualTo(""); - } + Truth.assertThat(generated.toString()).isEqualTo(""); + } } diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java index c1f6ff4ae..b1e5e4ea6 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java @@ -2,6 +2,11 @@ import com.google.common.io.Resources; import com.google.common.truth.extension.Employee; +import com.google.common.truth.extension.generator.internal.TruthGenerator; +import com.google.common.truth.extension.generator.internal.model.ThreeSystem; +import com.google.common.truth.extension.generator.model.IdCard; +import com.google.common.truth.extension.generator.model.MyEmployee; +import com.google.common.truth.extension.generator.model.Project; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -9,34 +14,93 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; import static com.google.common.truth.Truth.assertThat; @RunWith(JUnit4.class) public class TruthGeneratorTest { - @Test - public void poc() throws IOException { - TruthGenerator truthGenerator = new TruthGenerator(getClass().getPackage().getName()); - String generated = truthGenerator.generate(Employee.class); + @Test + public void poc() throws IOException { + TruthGeneratorAPI truthGeneratorAPI = new TruthGenerator(Employee.class.getPackageName()); + String generated = truthGeneratorAPI.generate(Employee.class); - String expectedFileName = "expected-EmployeeSubject.java.txt"; - String expecting = Resources.toString(Resources.getResource(expectedFileName), Charset.defaultCharset()); + String expectedFileName = "expected-EmployeeSubject.java.txt"; + String expecting = Resources.toString(Resources.getResource(expectedFileName), Charset.defaultCharset()); - assertThat(trim(generated)).isEqualTo(trim(expecting)); - } + assertThat(trim(generated)).isEqualTo(trim(expecting)); + } - private String trim(String in) { - return in.replaceAll("(?m)^(\\s)*|(\\s)*$", ""); - } + private String trim(String in) { + return in.replaceAll("(?m)^(\\s)*|(\\s)*$", ""); + } - @Test - public void employee() throws FileNotFoundException { - TruthGenerator truthGenerator = new TruthGenerator(getClass().getPackage().getName()); - String generate = truthGenerator.generate(MyEmployee.class); + @Test + public void employee() throws FileNotFoundException { + TruthGeneratorAPI truthGeneratorAPI = new TruthGenerator(MyEmployee.class.getPackageName()); + String generate = truthGeneratorAPI.generate(MyEmployee.class); - assertThat(trim("")).isEqualTo(trim(generate)); + assertThat(trim("")).isEqualTo(trim(generate)); + } - } + @Test + void generate_code() throws FileNotFoundException { + // todo need to be able to set base package for all generated classes, kind of like shade, so you cah generate test for classes in other restricted modules + TruthGeneratorAPI truthGenerator = new TruthGenerator(MyEmployee.class.getPackageName()); + + List classes = new ArrayList<>(); + classes.add(MyEmployee.class); + classes.add(IdCard.class); + classes.add(Project.class); + + // package exists in other module error - needs package target support +// classes.add(ZonedDateTime.class); +// classes.add(UUID.class); + + List threeSystemStream = classes.stream().map(x -> truthGenerator.threeLayerSystem(x).get()).collect(Collectors.toList()); + + TestGenerator tg = new TestGenerator(Set.of()); + threeSystemStream.forEach(x -> { + tg.addTests(x.parent.generated, x.classUnderTest); + }); + truthGenerator.createOverallAccessPoints(); + + +// Assertions.assertThat(wc). + +// truthGenerator.generate(PartitionState.class); + +// truthGenerator.maintain(LongPollingMockConsumer.class, LongPollingMockConsumerSubject.class); + + } + + @Test + public void try_out_assertions() { + + // all asserts should be available +// ManagedTruth.assertThat(new CommitHistory(List.of())); +// ManagedTruth.assertTruth(Range.range(4)); +// +// WorkContainer wc = new WorkContainer(4, null, ""); +// +// assertTruth(wc).getEpoch().isAtLeast(4); +// Truth.assertThat(wc.getEpoch()).isAtLeast(4); +// +// MyEmployee hi = new MyEmployee("Zeynep"); +// hi.setBoss(new MyEmployee("Lilan")); +// +// assertTruth(hi).getBirthYear().isAtLeast(200); +// assertThat(hi.getBirthYear()).isAtLeast(200); +// +// assertThat(hi.getBoss().getName()).contains("Tony"); +// assertTruth(hi).getBoss().getName().contains("Tony"); +// assertTruth(hi).getCard().getEpoch().isAtLeast(20); +// assertTruth(hi).getSlipUpList().hasSize(3); +// MyEmployeeSubject myEmployeeSubject = ManagedTruth.assertTruth(hi); + } } diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TypeTests.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TypeTests.java new file mode 100644 index 000000000..5cf1e1a4e --- /dev/null +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TypeTests.java @@ -0,0 +1,25 @@ +package com.google.common.truth.extension.generator; + +import org.junit.Test; + +public class TypeTests { + @Test + public void arrays(){ + int[] ints = new int[]{0,1}; + String[] strings = new String[]{}; + EnumTest[] enums = new EnumTest[]{}; + + Class aClass = ints.getClass(); + Class componentType = aClass.getComponentType(); + + Class componentType1 = strings.getClass().getComponentType(); + + Class enumscomp = enums.getClass().getComponentType(); + + Class enumsco2mp = enums.getClass().getComponentType(); + } + + enum EnumTest{ + ONE, TWO + } +} diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/IdCard.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/model/IdCard.java similarity index 71% rename from extensions/generator/src/test/java/com/google/common/truth/extension/generator/IdCard.java rename to extensions/generator/src/test/java/com/google/common/truth/extension/generator/model/IdCard.java index 7d2e4d6c2..f492844e1 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/IdCard.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/model/IdCard.java @@ -1,4 +1,4 @@ -package com.google.common.truth.extension.generator; +package com.google.common.truth.extension.generator.model; import java.util.UUID; @@ -7,6 +7,15 @@ public class IdCard { private String name; private int epoch; + @Override + public String toString() { + return "IdCard{" + + "id=" + id + + ", name='" + name + '\'' + + ", epoch=" + epoch + + '}'; + } + public IdCard(final UUID id, final String name, final int epoch) { this.id = id; this.name = name; diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/MyEmployee.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/model/MyEmployee.java similarity index 64% rename from extensions/generator/src/test/java/com/google/common/truth/extension/generator/MyEmployee.java rename to extensions/generator/src/test/java/com/google/common/truth/extension/generator/model/MyEmployee.java index 90f534f0a..11e73e82d 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/MyEmployee.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/model/MyEmployee.java @@ -1,4 +1,4 @@ -package com.google.common.truth.extension.generator; +package com.google.common.truth.extension.generator.model; import java.util.List; @@ -10,6 +10,31 @@ public class MyEmployee { private char id; private String name; + //todo private enum + //todo optional + //todo ZonedDateTime + + private MyEmployee boss; + + private IdCard card; + + private List projectList; + + @Override + public String toString() { + return "MyEmployee{" + + "birthSeconds=" + birthSeconds + + ", employed=" + employed + + ", weighting=" + weighting + + ", birthYear=" + birthYear + + ", id=" + id + + ", name='" + name + '\'' + + ", boss=" + boss + + ", card=" + card + + ", slipUpList=" + projectList + + '}'; + } + public long getBirthSeconds() { return birthSeconds; } @@ -34,9 +59,6 @@ public void setWeighting(final double weighting) { this.weighting = weighting; } - public void setBirthYear(final int birthYear) { - this.birthYear = birthYear; - } public char getId() { return id; @@ -62,20 +84,14 @@ public void setCard(final IdCard card) { this.card = card; } - public List getSlipUpList() { - return slipUpList; + public List getSlipUpList() { + return projectList; } - public void setSlipUpList(final List slipUpList) { - this.slipUpList = slipUpList; + public void setSlipUpList(final List slipUpList) { + this.projectList = slipUpList; } - private MyEmployee boss; - - private IdCard card; - - private List slipUpList; - public MyEmployee(final String name) { this.name = name; } @@ -88,11 +104,11 @@ public void setName(final String name) { this.name = name; } - public long getBirthYear() { + public int getBirthYear() { return birthYear; } - public void setBirthYear(final long birthYear) { - this.birthYear = (int) birthYear; + public void setBirthYear(final int birthYear) { + this.birthYear = birthYear; } } diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/model/Project.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/model/Project.java new file mode 100644 index 000000000..1a91ed4c2 --- /dev/null +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/model/Project.java @@ -0,0 +1,29 @@ +package com.google.common.truth.extension.generator.model; + +import java.time.ZonedDateTime; + +public class Project { + private String desc; + private ZonedDateTime start; + + public Project(final String desc, final ZonedDateTime start) { + this.desc = desc; + this.start = start; + } + + public String getDesc() { + return desc; + } + + public void setDesc(final String desc) { + this.desc = desc; + } + + public ZonedDateTime getStart() { + return start; + } + + public void setStart(final ZonedDateTime start) { + this.start = start; + } +} From 073603ee511b86d7fef5e96b0864928b131bff4b Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Sat, 31 Jul 2021 14:15:08 +0100 Subject: [PATCH 11/32] compiles --- .../generator/TruthGeneratorAPI.java | 63 +++ .../generator/internal/OverallEntryPoint.java | 53 +++ .../generator/internal/SkeletonGenerator.java | 360 ++++++++++++++++++ .../internal/SkeletonGeneratorAPI.java | 47 +++ .../SubjectMethodGenerator.java} | 13 +- .../generator/internal/TruthGenerator.java | 140 +++++++ .../extension/generator/internal/Utils.java | 73 ++++ .../generator/internal/model/AClass.java | 11 + .../internal/model/ManagedClassSet.java | 13 + .../generator/internal/model/MiddleClass.java | 19 + .../generator/internal/model/ParentClass.java | 9 + .../internal/model/SourceClassSets.java | 61 +++ .../generator/internal/model/ThreeSystem.java | 23 ++ .../SubjectMethodGeneratorTests.java | 25 ++ .../extension/generator/TestCreatorTest.java | 23 -- .../generator/TruthGeneratorTest.java | 52 +-- .../extension/generator/model/MyEmployee.java | 114 ------ .../{model => testModel}/IdCard.java | 2 +- .../generator/testModel/MyEmployee.java | 104 +++++ .../extension/generator/testModel/Person.java | 39 ++ .../{model => testModel}/Project.java | 2 +- 21 files changed, 1078 insertions(+), 168 deletions(-) create mode 100644 extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGeneratorAPI.java create mode 100644 extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/OverallEntryPoint.java create mode 100644 extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java create mode 100644 extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGeneratorAPI.java rename extensions/generator/src/main/java/com/google/common/truth/extension/generator/{TestGenerator.java => internal/SubjectMethodGenerator.java} (96%) create mode 100644 extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java create mode 100644 extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/Utils.java create mode 100644 extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/AClass.java create mode 100644 extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ManagedClassSet.java create mode 100644 extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/MiddleClass.java create mode 100644 extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ParentClass.java create mode 100644 extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/SourceClassSets.java create mode 100644 extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ThreeSystem.java create mode 100644 extensions/generator/src/test/java/com/google/common/truth/extension/generator/SubjectMethodGeneratorTests.java delete mode 100644 extensions/generator/src/test/java/com/google/common/truth/extension/generator/TestCreatorTest.java delete mode 100644 extensions/generator/src/test/java/com/google/common/truth/extension/generator/model/MyEmployee.java rename extensions/generator/src/test/java/com/google/common/truth/extension/generator/{model => testModel}/IdCard.java (93%) create mode 100644 extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/MyEmployee.java create mode 100644 extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/Person.java rename extensions/generator/src/test/java/com/google/common/truth/extension/generator/{model => testModel}/Project.java (89%) diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGeneratorAPI.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGeneratorAPI.java new file mode 100644 index 000000000..159f31344 --- /dev/null +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGeneratorAPI.java @@ -0,0 +1,63 @@ +package com.google.common.truth.extension.generator; + +import com.google.common.truth.extension.generator.internal.TruthGenerator; +import com.google.common.truth.extension.generator.internal.model.SourceClassSets; +import com.google.common.truth.extension.generator.internal.model.ThreeSystem; + +import java.io.FileNotFoundException; +import java.util.List; +import java.util.Optional; + +/** + * + */ +public interface TruthGeneratorAPI { + + static TruthGeneratorAPI create() { + return new TruthGenerator(); + } + + /** + * Takes a user maintained source file, and adds boiler plate and Subject methods that are missing. If aggressively + * skips parts if it thinks the user has overridden something. + *

+ * Not implemented yet. + */ + String maintain(Class source, Class userAndGeneratedMix); + + /** + * todo + */ + String combinedSystem(Class source); + + /** + * todo + */ + void combinedSystem(String... modelPackages); + + /** + * @param modelPackages + */ + void generate(String... modelPackages); + + /** + * @param classes + */ + void generateFromPackagesOf(Class... classes); + + /** + * @param ss + */ + void combinedSystem(SourceClassSets ss); + + /** + * @param ss + */ + void generate(SourceClassSets ss); + + /** + * + * @param classes + */ + void generate(List classes); +} diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/OverallEntryPoint.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/OverallEntryPoint.java new file mode 100644 index 000000000..1599536e7 --- /dev/null +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/OverallEntryPoint.java @@ -0,0 +1,53 @@ +package com.google.common.truth.extension.generator.internal; + +import com.google.common.truth.extension.generator.internal.model.ThreeSystem; +import org.jboss.forge.roaster.Roaster; +import org.jboss.forge.roaster.model.Method; +import org.jboss.forge.roaster.model.source.Import; +import org.jboss.forge.roaster.model.source.JavaClassSource; +import org.jboss.forge.roaster.model.source.MethodSource; + +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.List; + +import static com.google.common.truth.extension.generator.internal.Utils.writeToDisk; + +public class OverallEntryPoint { + + private final List children = new ArrayList<>(); + + /** + * Having collected together all the access points, creates one large class filled with access points to all of them. + *

+ * The overall access will throw an error if any middle classes don't correctly extend their parent. + */ + public void createOverallAccessPoints(String packageName) { + JavaClassSource overallAccess = Roaster.create(JavaClassSource.class); + overallAccess.setName("ManagedTruth"); + overallAccess.getJavaDoc() + .setText("Single point of access for all managed Subjects."); + overallAccess.setPublic() + .setPackage(packageName); + + // brute force + for (JavaClassSource j : children) { + List> methods = j.getMethods(); + for (Method m : methods) { + overallAccess.addMethod(m); + } + // this seems like overkill, but at least in the child style case, there's very few imports - even + // none extra at all (aside from wild card vs specific methods). + List imports = j.getImports(); + for (Import i : imports) { + overallAccess.addImport(i); + } + } + + writeToDisk(overallAccess); + } + + public void add(ThreeSystem ts) { + this.children.add(ts.child); + } +} diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java new file mode 100644 index 000000000..dd2f68bf3 --- /dev/null +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java @@ -0,0 +1,360 @@ +package com.google.common.truth.extension.generator.internal; + +import com.google.common.base.Joiner; +import com.google.common.truth.FailureMetadata; +import com.google.common.truth.Subject; +import com.google.common.truth.Truth; +import com.google.common.truth.extension.generator.internal.model.*; +import org.jboss.forge.roaster.Roaster; +import org.jboss.forge.roaster.model.source.*; +import org.reflections.ReflectionUtils; + +import com.google.common.flogger.FluentLogger; + +import javax.annotation.processing.Generated; +import java.io.FileNotFoundException; +import java.util.*; +import java.util.logging.Level; + +import static com.google.common.truth.extension.generator.internal.Utils.getFactoryName; +import static com.google.common.truth.extension.generator.internal.Utils.writeToDisk; +import static java.lang.String.format; +import static java.util.Optional.empty; + +/** + * @author Antony Stubbs + */ +public class SkeletonGenerator implements SkeletonGeneratorAPI { + + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + + @Override + public String maintain(Class source, Class userAndGeneratedMix) { + throw new IllegalStateException("Not implemented yet"); + } + + @Override + public Optional threeLayerSystem(Class source, Class usersMiddleClass) throws FileNotFoundException { + if (checkSource(source)) + return empty(); + + // make parent - boiler plate access + ParentClass parent = createParent(source); + + String factoryMethodName = getFactoryName(source); + + // make child - client code entry point + JavaClassSource child = createChild(parent, usersMiddleClass.getName(), source, factoryMethodName); + + MiddleClass middleClass = new MiddleClass(null, null, usersMiddleClass); + + return Optional.of(new ThreeSystem(source, parent, middleClass, child)); + } + + @Override + public Optional threeLayerSystem(Class source, Optional targetPackageName) { + if (checkSource(source)) + return empty(); + + ParentClass parent = createParent(source); + + // todo try to see if class already exists first, user may already have a written one and not know + MiddleClass middle = createMiddlePlaceHolder(parent.generated, source); + + JavaClassSource child = createChild(parent, middle.generated.getQualifiedName(), source, middle.factoryMethod.getName()); + + return Optional.of(new ThreeSystem(source, parent, middle, child)); + } + + private boolean checkSource(final Class source) { + if (source.isAnonymousClass()) { + logger.at(Level.FINE).log("Skipping anonymous class %s", source); + return true; + } + + String simpleName = source.getSimpleName(); + if (simpleName.contains("Builder")) { + logger.at(Level.FINE).log("Skipping builder class %s", source); + return true; + } + + if (isTestClass(source)) { + logger.at(Level.FINE).log("Skipping a test class %s", source); + return true; + } + + return false; + } + + /** + * If any method is annotated with something with Test in it, then assume it's a test class + */ + private boolean isTestClass(final Class source) { + boolean hasTestAnnotatedMethod = !ReflectionUtils.getMethods(source, + x -> Arrays.stream(x.getAnnotations()) + .anyMatch(y -> y.annotationType() + .getSimpleName().contains("Test"))).isEmpty(); + boolean nameEndsInTest = source.getSimpleName().endsWith("Test"); + return hasTestAnnotatedMethod || nameEndsInTest; + } + + private MiddleClass createMiddlePlaceHolder(JavaClassSource parent, Class source) { + JavaClassSource middle = Roaster.create(JavaClassSource.class); + middle.setName(getSubjectName(source.getSimpleName())); + middle.setPackage(parent.getPackage()); + middle.extendSuperType(parent); + JavaDocSource jd = middle.getJavaDoc(); + jd.setText("Optionally move this class into source control, and add your custom assertions here.\n\n" + + "

If the system detects this class already exists, it won't attempt to generate a new one. Note that " + + "if the base skeleton of this class ever changes, you won't automatically get it updated."); + jd.addTagValue("@see", parent.getName()); + + addConstructor(source, middle, false); + + MethodSource factory = addFactoryAccesor(source, middle, source.getSimpleName()); + + addGeneratedMarker(middle); + + writeToDisk(middle); + return new MiddleClass(middle, factory, null); + } + + private ParentClass createParent(Class source) { + JavaClassSource parent = Roaster.create(JavaClassSource.class); + String sourceName = source.getSimpleName(); + String parentName = getSubjectName(sourceName + "Parent"); + parent.setName(parentName); + + addPackageSuperAndAnnotation(parent, source); + + addClassJavaDoc(parent, sourceName); + + addActualField(source, parent); + + addConstructor(source, parent, true); + + writeToDisk(parent); + return new ParentClass(parent); + } + + private void addPackageSuperAndAnnotation(final JavaClassSource javaClass, final Class source) { + addPackageSuperAndAnnotation(javaClass, source.getPackage().getName(), getSubjectName(source.getSimpleName())); + } + + private JavaClassSource createChild(ParentClass parent, + String usersMiddleClassName, + Class source, + String factoryMethodName) { + // todo if middle doesn't extend parent, warn + + JavaClassSource child = Roaster.create(JavaClassSource.class); + child.setName(getSubjectName(source.getSimpleName() + "Child")); + child.setPackage(parent.generated.getPackage()); + JavaDocSource javaDoc = child.getJavaDoc(); + javaDoc.setText("Entry point for assertions for @{" + source.getSimpleName() + "}. Import the static accessor methods from this class and use them.\n" + + "Combines the generated code from {@" + parent.generated.getName() + "}and the user code from {@" + usersMiddleClassName + "}."); + javaDoc.addTagValue("@see", source.getName()); + javaDoc.addTagValue("@see", usersMiddleClassName); + javaDoc.addTagValue("@see", parent.generated.getName()); + + addAccessPoints(source, child, factoryMethodName, usersMiddleClassName); + + addGeneratedMarker(child); + + writeToDisk(child); + return child; + } + +// private void registerManagedClass(Class sourceClass, JavaClassSource gengeratedClass) { +// managedSubjects.add(new ManagedClassSet(sourceClass, gengeratedClass)); +// } + + @Override + public String combinedSystem(Class source) { + JavaClassSource javaClass = Roaster.create(JavaClassSource.class); + +// JavaClassSource handWrittenExampleCode = Roaster.parse(JavaClassSource.class, handWritten); + +// registerManagedClass(source, handWrittenExampleCode); + +// javaClass = handWrittenExampleCode; + + String packageName = source.getPackage().getName(); + String sourceName = source.getSimpleName(); + String subjectClassName = getSubjectName(sourceName); + + + addPackageSuperAndAnnotation(javaClass, packageName, subjectClassName); + + addClassJavaDoc(javaClass, sourceName); + + addActualField(source, javaClass); + + addConstructor(source, javaClass, true); + + MethodSource factory = addFactoryAccesor(source, javaClass, sourceName); + + addAccessPoints(source, javaClass, factory.getName(), javaClass.getQualifiedName()); + + // todo add static import for Truth.assertAbout somehow? +// Import anImport = javaClass.addImport(Truth.class); +// javaClass.addImport(anImport.setStatic(true)); +// javaClass.addImport(new Im) + + String classSource = writeToDisk(javaClass); + + return classSource; + } + + private String getSubjectName(final String sourceName) { + return sourceName + "Subject"; + } + + private void addAccessPoints(Class source, JavaClassSource javaClass, + String factoryMethod, + String factoryContainerQualifiedName) { + MethodSource assertThat = addAssertThat(source, javaClass, factoryMethod, factoryContainerQualifiedName); + + addAssertTruth(source, javaClass, assertThat); + } + + private void addPackageSuperAndAnnotation(JavaClassSource javaClass, String packageName, String subjectClassName) { + javaClass.setPackage(packageName); + + // extend + javaClass.extendSuperType(Subject.class); + + addGeneratedMarker(javaClass); + } + + private void addGeneratedMarker(final JavaClassSource javaClass) { + // requires java 9 + // annotate generated + // @javax.annotation.Generated(value="") + // only in @since 1.9, so can't add it programmatically + AnnotationSource generated = javaClass.addAnnotation(Generated.class); + generated.setStringValue("truth-generator"); + // Can't add it without the value param, see https://github.com/forge/roaster/issues/201 + // AnnotationSource generated = javaClass.addAnnotation("javax.annotation.processing.Generated"); + } + + private void addClassJavaDoc(JavaClassSource javaClass, String sourceName) { + // class javadc + JavaDocSource classDocs = javaClass.getJavaDoc(); + if (classDocs.getFullText().isEmpty()) { + classDocs.setText("Truth Subject for the {@link " + sourceName + "}." + + "\n\n" + + "Note that this class is generated / managed, and will change over time. So any changes you might " + + "make will be overwritten."); + classDocs.addTagValue("@see", sourceName); + classDocs.addTagValue("@see", getSubjectName(sourceName)); + classDocs.addTagValue("@see", getSubjectName(sourceName + "Child")); + } + } + + private void addAssertTruth(Class source, JavaClassSource javaClass, MethodSource assertThat) { + String name = "assertTruth"; + if (!containsMethodCalled(javaClass, name)) { + // convenience entry point when being mixed with other "assertThat" assertion libraries + MethodSource assertTruth = javaClass.addMethod() + .setName(name) + .setPublic() + .setStatic(true) + .setReturnType(assertThat.getReturnType()); + assertTruth.addParameter(source, "actual"); + assertTruth.setBody("return " + assertThat.getName() + "(actual);"); + assertTruth.getJavaDoc().setText("Convenience entry point for {@link " + source.getSimpleName() + "} assertions when being " + + "mixed with other \"assertThat\" assertion libraries.") + .addTagValue("@see", "#assertThat"); + } + } + + private MethodSource addAssertThat(Class source, + JavaClassSource javaClass, + String factoryMethodName, + String factoryContainerQualifiedName) { + String methodName = "assertThat"; + if (containsMethodCalled(javaClass, methodName)) { + return getMethodCalled(javaClass, methodName); + } else { + // entry point + MethodSource assertThat = javaClass.addMethod() + .setName(methodName) + .setPublic() + .setStatic(true) + .setReturnType(factoryContainerQualifiedName); + assertThat.addParameter(source, "actual"); + // return assertAbout(things()).that(actual); + // add explicit static reference for now - see below + javaClass.addImport(factoryContainerQualifiedName + ".*") + .setStatic(true); + String entryPointBody = "return Truth.assertAbout(" + factoryMethodName + "()).that(actual);"; + assertThat.setBody(entryPointBody); + javaClass.addImport(Truth.class); + assertThat.getJavaDoc().setText("Entry point for {@link " + source.getSimpleName() + "} assertions."); + return assertThat; + } + } + + private MethodSource getMethodCalled(JavaClassSource javaClass, String methodName) { + return javaClass.getMethods().stream().filter(x -> x.getName().equals(methodName)).findFirst().get(); + } + + private MethodSource addFactoryAccesor(Class source, JavaClassSource javaClass, String sourceName) { + String factoryName = getFactoryName(source); + if (containsMethodCalled(javaClass, factoryName)) { + return getMethodCalled(javaClass, factoryName); + } else { + // factory accessor + String returnType = getTypeWithGenerics(Subject.Factory.class, javaClass.getName(), sourceName); + MethodSource factory = javaClass.addMethod() + .setName(factoryName) + .setPublic() + .setStatic(true) + // todo replace with something other than the string method - I suppose it's not possible to do generics type safely + .setReturnType(returnType) + .setBody("return " + javaClass.getName() + "::new;"); + JavaDocSource> factoryDocs = factory.getJavaDoc(); + factoryDocs.setText("Returns an assertion builder for a {@link " + sourceName + "} class."); + return factory; + } + } + + private boolean containsMethodCalled(JavaClassSource javaClass, String factoryName) { + return javaClass.getMethods().stream().anyMatch(x -> x.getName().equals(factoryName)); + } + + private void addConstructor(Class source, JavaClassSource javaClass, boolean setActual) { + if (!javaClass.getMethods().stream().anyMatch(x -> x.isConstructor())) { + // constructor + MethodSource constructor = javaClass.addMethod() + .setConstructor(true) + .setProtected(); + constructor.addParameter(FailureMetadata.class, "failureMetadata"); + constructor.addParameter(source, "actual"); + StringBuilder sb = new StringBuilder("super(failureMetadata, actual);\n"); + if (setActual) + sb.append("this.actual = actual;"); + constructor.setBody(sb.toString()); + } + } + + private void addActualField(Class source, JavaClassSource javaClass) { + String fieldName = "actual"; + if (javaClass.getField(fieldName) == null) { + // actual field + javaClass.addField() + .setProtected() + .setType(source) + .setName(fieldName) + .setFinal(true); + } + } + + private String getTypeWithGenerics(Class factoryClass, String... classes) { + String genericsList = Joiner.on(", ").skipNulls().join(classes); + String generics = new StringBuilder("<>").insert(1, genericsList).toString(); + return factoryClass.getSimpleName() + generics; + } + +} diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGeneratorAPI.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGeneratorAPI.java new file mode 100644 index 000000000..3f84e7d41 --- /dev/null +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGeneratorAPI.java @@ -0,0 +1,47 @@ +package com.google.common.truth.extension.generator.internal; + +import com.google.common.truth.extension.generator.internal.model.ThreeSystem; + +import java.io.FileNotFoundException; +import java.util.Optional; + +public interface SkeletonGeneratorAPI { + + /** + * @see com.google.common.truth.extension.generator.TruthGeneratorAPI#maintain(Class, Class) + */ + String maintain(Class source, Class userAndGeneratedMix); + + /** + * Uses an optional three layer system to manage the Subjects. + *

    + *
  1. The top layer extends Subject and stores the actual and factory. + *
  2. The second layer is the user's code - they extend the first layer, and add their custom assertion methods there. + *
  3. The third layer extends the user's class, and stores the generated entry points, so that users's get's access + * to all three layers, by only importing the bottom layer. + *
+ *

+ * For any source class that doesn't have a user created middle class, an empty one will be generated, that the user + * can copy into their source control. If it's used, a helpful message will be written to the console, prompting the + * user to do so. These messages can be globally disabled. + *

+ * This way there's no complexity with mixing generated and user written code, but at the cost of 2 extra classes + * per source. While still allowing the user to leverage the full code generation system but maintaining their own extensions + * with clear separation from the code generation. + * @return + */ + Optional threeLayerSystem(Class source, Class usersMiddleClass) throws FileNotFoundException; + + /** + * Create the place holder middle class, for optional copying into source code + * + * @return null if for some reason the class isn't supported + * @see #threeLayerSystem(Class, Class) + */ + Optional threeLayerSystem(Class source, Optional targetPackageName); + + /** + * @see com.google.common.truth.extension.generator.TruthGeneratorAPI#combinedSystem(Class) + */ + String combinedSystem(Class source); +} diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TestGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java similarity index 96% rename from extensions/generator/src/main/java/com/google/common/truth/extension/generator/TestGenerator.java rename to extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java index 0f04de3cd..dafe7be76 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TestGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java @@ -1,10 +1,10 @@ -package com.google.common.truth.extension.generator; +package com.google.common.truth.extension.generator.internal; import com.google.common.collect.Sets; import com.google.common.flogger.FluentLogger; import com.google.common.truth.ObjectArraySubject; import com.google.common.truth.Subject; -import com.google.common.truth.extension.generator.internal.TruthGenerator; +import com.google.common.truth.extension.generator.internal.SkeletonGenerator; import com.google.common.truth.extension.generator.internal.model.ThreeSystem; import org.apache.commons.lang3.ClassUtils; import org.jboss.forge.roaster.model.source.Import; @@ -28,14 +28,15 @@ /** * @author Antony Stubbs */ -public class TestGenerator { +// todo needs refactoring into different strategies, interface +public class SubjectMethodGenerator { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); private final Map compiledSubjects; private final Map generatedSubjects; - public TestGenerator(final Set allTypes) { + public SubjectMethodGenerator(final Set allTypes) { this.generatedSubjects = allTypes.stream().collect(Collectors.toMap(x -> x.classUnderTest.getSimpleName(), x -> x)); Reflections reflections = new Reflections("com.google.common.truth", "io.confluent"); @@ -149,7 +150,7 @@ private void addPrimitiveTest(Method method, JavaClassSource generated, Class // && !x.getName().startsWith("lambda") // the factory method won't be the assert methods // ); // if (factoryPotentials.isEmpty()) { - aboutName = TruthGenerator.getFactoryName(returnType); // take a guess + aboutName = Utils.getFactoryName(returnType); // take a guess // } else { // Method method = factoryPotentials.stream().findFirst().get(); // aboutName = method.getName(); @@ -276,7 +277,7 @@ public void addTests(final Set allTypes) { // only serialise results, when all have finished - useful for debugging for (ThreeSystem c : allTypes) { - TruthGenerator.writeToDisk(c.parent.generated); + Utils.writeToDisk(c.parent.generated); } } diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java new file mode 100644 index 000000000..c8dac4f50 --- /dev/null +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java @@ -0,0 +1,140 @@ +package com.google.common.truth.extension.generator.internal; + +import com.google.common.collect.Sets; +import com.google.common.truth.Subject; +import com.google.common.truth.extension.generator.TruthGeneratorAPI; +import com.google.common.truth.extension.generator.internal.model.SourceClassSets; +import com.google.common.truth.extension.generator.internal.model.ThreeSystem; +import org.reflections.Reflections; +import org.reflections.scanners.SubTypesScanner; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author Antony Stubbs + */ +public class TruthGenerator implements TruthGeneratorAPI { + + @Override + public void generate(String... modelPackages) { + Utils.requireNotEmpty(modelPackages); + + OverallEntryPoint overallEntryPoint = new OverallEntryPoint(); + Set subjectsSystems = generateSkeletonsFromPackages(modelPackages, overallEntryPoint); + + // + addTests(subjectsSystems); + + // just take the first for now + String[] packageNameForOverall = modelPackages; + overallEntryPoint.createOverallAccessPoints(packageNameForOverall[0]); + } + + private Set generateSkeletonsFromPackages(final String[] modelPackages, OverallEntryPoint overallEntryPoint) { + Set> allTypes = collectSourceClasses(modelPackages); + return generateSkeletons(allTypes, Optional.empty(), overallEntryPoint); + } + + private Set generateSkeletons(Set> classes, Optional targetPackageName, + OverallEntryPoint overallEntryPoint) { + SkeletonGenerator skeletonGenerator = new SkeletonGenerator(); + + Set subjectsSystems = new HashSet<>(); + for (Class c : classes) { + Optional threeSystem = skeletonGenerator.threeLayerSystem(c, targetPackageName); + if (threeSystem.isPresent()) { + ThreeSystem ts = threeSystem.get(); + subjectsSystems.add(ts); + overallEntryPoint.add(ts); + } + } + return subjectsSystems; + } + + private Set> collectSourceClasses(final String[] modelPackages) { + // for all classes in package + SubTypesScanner subTypesScanner = new SubTypesScanner(false); + + Reflections reflections = new Reflections(modelPackages, subTypesScanner); + reflections.expandSuperTypes(); // get things that extend something that extend object + + // https://github.com/ronmamo/reflections/issues/126 + Set> subTypesOfEnums = reflections.getSubTypesOf(Enum.class); + + Set> allTypes = reflections.getSubTypesOf(Object.class) + // remove Subject classes from previous runs + .stream().filter(x -> !Subject.class.isAssignableFrom(x)) + .collect(Collectors.toSet()); + allTypes.addAll(subTypesOfEnums); + return allTypes; + } + + private void addTests(final Set allTypes) { + SubjectMethodGenerator tg = new SubjectMethodGenerator(allTypes); + tg.addTests(allTypes); + } + + @Override + public void generateFromPackagesOf(Class... classes) { + generate(getPackageStrings(classes)); + } + + @Override + public void combinedSystem(final SourceClassSets ss) { + + } + + private String[] getPackageStrings(final Class[] classes) { + return Arrays.stream(classes).map(x -> x.getPackage().getName()).collect(Collectors.toList()).toArray(new String[0]); + } + + @Override + public void generate(SourceClassSets ss) { + Set packages = ss.getSimplePackageOfClasses().stream().map( + this::getPackageStrings + ).collect(Collectors.toSet()); + + OverallEntryPoint overallEntryPoint = new OverallEntryPoint(); + + // skeletons generation is independent and should be able to be done in parallel + Set skeletons = packages.parallelStream().flatMap( + x -> generateSkeletonsFromPackages(x, overallEntryPoint).stream() + ).collect(Collectors.toSet()); + + Set packageAndClasses = ss.getPackageAndClasses(); + Set setStream = packageAndClasses.stream().flatMap( + x -> { + Set> collect = Arrays.stream(x.getClasses()).collect(Collectors.toSet()); + return generateSkeletons(collect, Optional.of(x.getTargetPackageName()), overallEntryPoint).stream(); + } + ).collect(Collectors.toSet()); + + // + addTests(Sets.union(skeletons, setStream)); + + // create overall entry point + overallEntryPoint.createOverallAccessPoints(ss.getPackageForOverall()); + } + + @Override + public void generate(final List classes) { + Utils.requireNotEmpty(classes); + generate(new SourceClassSets(classes.get(0).getPackageName())); + } + + @Override + public String maintain(final Class source, final Class userAndGeneratedMix) { + throw new IllegalStateException("Not implemented yet"); + } + + @Override + public String combinedSystem(final Class source) { + throw new IllegalStateException("Not implemented yet"); + } + + @Override + public void combinedSystem(final String... modelPackages) { + throw new IllegalStateException("Not implemented yet"); + } +} diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/Utils.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/Utils.java new file mode 100644 index 000000000..08cf5cfd2 --- /dev/null +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/Utils.java @@ -0,0 +1,73 @@ +package com.google.common.truth.extension.generator.internal; + +import org.atteo.evo.inflector.English; +import org.jboss.forge.roaster.model.source.JavaClassSource; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.PrintWriter; +import java.nio.file.Paths; +import java.util.List; + +import static java.lang.String.format; + +public class Utils { + + public static String writeToDisk(JavaClassSource javaClass) { + String classSource = javaClass.toString(); + String fileName = getFileName(javaClass); + try (PrintWriter out = new PrintWriter(fileName)) { + out.println(classSource); + } catch (FileNotFoundException e) { + throw new IllegalStateException(format("Cannot write to file %s", fileName)); + } + return classSource; + } + + private static String getFileName(JavaClassSource javaClass) { + String directoryName = getDirectoryName(javaClass); + File dir = new File(directoryName); + if (!dir.exists()) { + boolean mkdir = dir.mkdirs(); + } + return directoryName + javaClass.getName() + ".java"; + } + + private static String getDirectoryName(JavaClassSource javaClass) { + String parent = Paths.get("").toAbsolutePath().toString(); + String packageName = javaClass.getPackage(); + String packageNameSuffix = ".truth"; + + List ids = List.of("Parent", "Child"); + boolean isChildOrParent = ids.stream().anyMatch(x -> javaClass.getName().contains(x)); + + String baseDirSuffix = (isChildOrParent) ? "truth-assertions-managed" : "truth-assertions-templates"; + + String packageNameDir = packageName.replace(".", "/"); + + return format("%s/target/generated-test-sources/%s/%s/", parent, baseDirSuffix, packageNameDir); + } + + public static String getFactoryName(Class source) { + String simpleName = source.getSimpleName(); + String plural = English.plural(simpleName); + String normal = toLowerCaseFirstLetter(plural); + return normal; + } + + private static String toLowerCaseFirstLetter(String plural) { + return plural.substring(0, 1).toLowerCase() + plural.substring(1); + } + + public static void requireNotEmpty(final List classes) { + if (classes.isEmpty()) { + throw new IllegalArgumentException("No classes to generate from"); + } + } + + public static void requireNotEmpty(final String[] modelPackages) { + if (modelPackages.length == 0) { + throw new IllegalArgumentException("No packages to generate from"); + } + } +} diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/AClass.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/AClass.java new file mode 100644 index 000000000..62f4a124f --- /dev/null +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/AClass.java @@ -0,0 +1,11 @@ +package com.google.common.truth.extension.generator.internal.model; + +import org.jboss.forge.roaster.model.source.JavaClassSource; + +public class AClass { + public final JavaClassSource generated; + + AClass(final JavaClassSource generated) { + this.generated = generated; + } +} diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ManagedClassSet.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ManagedClassSet.java new file mode 100644 index 000000000..90a7437c6 --- /dev/null +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ManagedClassSet.java @@ -0,0 +1,13 @@ +package com.google.common.truth.extension.generator.internal.model; + +import org.jboss.forge.roaster.model.source.JavaClassSource; + +public class ManagedClassSet { + final Class sourceClass; + final JavaClassSource generatedClass; + + public ManagedClassSet(final Class sourceClass, final JavaClassSource generatedClass) { + this.sourceClass = sourceClass; + this.generatedClass = generatedClass; + } +} diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/MiddleClass.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/MiddleClass.java new file mode 100644 index 000000000..955eb7464 --- /dev/null +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/MiddleClass.java @@ -0,0 +1,19 @@ +package com.google.common.truth.extension.generator.internal.model; + +import org.jboss.forge.roaster.model.source.JavaClassSource; +import org.jboss.forge.roaster.model.source.MethodSource; + +public class MiddleClass extends AClass { + public final MethodSource factoryMethod; + public final Class usersMiddleClass; + + public MiddleClass(JavaClassSource generated, MethodSource factoryMethod, final Class usersMiddleClass) { + super(generated); + this.factoryMethod = factoryMethod; + this.usersMiddleClass = usersMiddleClass; + } + + public String getName() { + return (usersMiddleClass==null)?super.generated.getName():usersMiddleClass.getName(); + } +} diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ParentClass.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ParentClass.java new file mode 100644 index 000000000..c11bc9177 --- /dev/null +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ParentClass.java @@ -0,0 +1,9 @@ +package com.google.common.truth.extension.generator.internal.model; + +import org.jboss.forge.roaster.model.source.JavaClassSource; + +public class ParentClass extends AClass { + public ParentClass(JavaClassSource generated) { + super(generated); + } +} diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/SourceClassSets.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/SourceClassSets.java new file mode 100644 index 000000000..c8d53c8be --- /dev/null +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/SourceClassSets.java @@ -0,0 +1,61 @@ +package com.google.common.truth.extension.generator.internal.model; + +import java.util.HashSet; +import java.util.Set; + +public class SourceClassSets { + + private final String packageForOverall; + private final Set[]> simplePackageOfClasses = new HashSet<>(); + private final Set packageAndClasses = new HashSet<>(); + + public SourceClassSets(final String packageForOverall) { + this.packageForOverall = packageForOverall; + } + + public void generateFromPackagesOf(Class... classes) { + simplePackageOfClasses.add(classes); + } + + /** + * Useful for generating Java module Subjects and put them in our package. + * + * I.e. for UUID.class you can't create a Subject in the same package as it (not allowed) + * + * @param targetPackageName + * @param classes + */ + public void generateFrom(String targetPackageName, Class... classes) { + packageAndClasses.add(new PackageAndClasses(targetPackageName, classes)); + } + + public class PackageAndClasses { + final String targetPackageName; + + final Class[] classes; + private PackageAndClasses(final String targetPackageName, final Class[] classes) { + this.targetPackageName = targetPackageName; + this.classes = classes; + } + + public String getTargetPackageName() { + return targetPackageName; + } + + public Class[] getClasses() { + return classes; + } + } + + public Set[]> getSimplePackageOfClasses() { + return simplePackageOfClasses; + } + + public Set getPackageAndClasses() { + return packageAndClasses; + } + + public String getPackageForOverall() { + return packageForOverall; + } +} diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ThreeSystem.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ThreeSystem.java new file mode 100644 index 000000000..a0b6ad665 --- /dev/null +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ThreeSystem.java @@ -0,0 +1,23 @@ +package com.google.common.truth.extension.generator.internal.model; + +import org.jboss.forge.roaster.model.source.JavaClassSource; + +public class ThreeSystem { + @Override + public String toString() { + return "ThreeSystem{" + + "classUnderTest=" + classUnderTest + '}'; + } + + public final Class classUnderTest; + public final ParentClass parent; + public final MiddleClass middle; + public final JavaClassSource child; + + public ThreeSystem(final Class classUnderTest, final ParentClass parent, final MiddleClass middle, final JavaClassSource child) { + this.classUnderTest = classUnderTest; + this.parent = parent; + this.middle = middle; + this.child = child; + } +} diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/SubjectMethodGeneratorTests.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/SubjectMethodGeneratorTests.java new file mode 100644 index 000000000..b45c1e057 --- /dev/null +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/SubjectMethodGeneratorTests.java @@ -0,0 +1,25 @@ +package com.google.common.truth.extension.generator; + +import com.google.common.truth.Truth; +import com.google.common.truth.extension.generator.internal.SubjectMethodGenerator; +import com.google.common.truth.extension.generator.testModel.MyEmployee; +import org.jboss.forge.roaster.Roaster; +import org.jboss.forge.roaster.model.source.JavaClassSource; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.Set; + +@RunWith(JUnit4.class) +public class SubjectMethodGeneratorTests { + + @Test + public void poc(){ + JavaClassSource generated = Roaster.create(JavaClassSource.class); + SubjectMethodGenerator subjectMethodGenerator = new SubjectMethodGenerator(Set.of()); + subjectMethodGenerator.addTests(generated, MyEmployee.class); + + Truth.assertThat(generated.toString()).isEqualTo(""); + } +} diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TestCreatorTest.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TestCreatorTest.java deleted file mode 100644 index a85b066f2..000000000 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TestCreatorTest.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.google.common.truth.extension.generator; - -import com.google.common.truth.Truth; -import com.google.common.truth.extension.generator.model.MyEmployee; -import org.jboss.forge.roaster.Roaster; -import org.jboss.forge.roaster.model.source.JavaClassSource; -import org.junit.Test; - -import java.util.Set; - -import static com.google.common.truth.Truth.assertThat; - -public class TestCreatorTest { - - @Test - public void poc(){ - JavaClassSource generated = Roaster.create(JavaClassSource.class); - TestGenerator testGenerator = new TestGenerator(Set.of()); - testGenerator.addTests(generated, MyEmployee.class); - - Truth.assertThat(generated.toString()).isEqualTo(""); - } -} diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java index b1e5e4ea6..74095657d 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java @@ -2,22 +2,20 @@ import com.google.common.io.Resources; import com.google.common.truth.extension.Employee; -import com.google.common.truth.extension.generator.internal.TruthGenerator; -import com.google.common.truth.extension.generator.internal.model.ThreeSystem; -import com.google.common.truth.extension.generator.model.IdCard; -import com.google.common.truth.extension.generator.model.MyEmployee; -import com.google.common.truth.extension.generator.model.Project; +import com.google.common.truth.extension.generator.internal.model.SourceClassSets; +import com.google.common.truth.extension.generator.testModel.IdCard; +import com.google.common.truth.extension.generator.testModel.MyEmployee; +import com.google.common.truth.extension.generator.testModel.Project; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import java.io.FileNotFoundException; import java.io.IOException; import java.nio.charset.Charset; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; +import java.util.UUID; import static com.google.common.truth.Truth.assertThat; @@ -26,8 +24,8 @@ public class TruthGeneratorTest { @Test public void poc() throws IOException { - TruthGeneratorAPI truthGeneratorAPI = new TruthGenerator(Employee.class.getPackageName()); - String generated = truthGeneratorAPI.generate(Employee.class); + TruthGeneratorAPI truthGeneratorAPI = TruthGeneratorAPI.create(); + String generated = truthGeneratorAPI.combinedSystem(Employee.class); String expectedFileName = "expected-EmployeeSubject.java.txt"; String expecting = Resources.toString(Resources.getResource(expectedFileName), Charset.defaultCharset()); @@ -40,17 +38,17 @@ private String trim(String in) { } @Test - public void employee() throws FileNotFoundException { - TruthGeneratorAPI truthGeneratorAPI = new TruthGenerator(MyEmployee.class.getPackageName()); - String generate = truthGeneratorAPI.generate(MyEmployee.class); + public void combined() { + TruthGeneratorAPI truthGeneratorAPI = TruthGeneratorAPI.create(); + String generate = truthGeneratorAPI.combinedSystem(MyEmployee.class); assertThat(trim("")).isEqualTo(trim(generate)); } @Test - void generate_code() throws FileNotFoundException { + void generate_code() { // todo need to be able to set base package for all generated classes, kind of like shade, so you cah generate test for classes in other restricted modules - TruthGeneratorAPI truthGenerator = new TruthGenerator(MyEmployee.class.getPackageName()); + TruthGeneratorAPI truthGenerator = TruthGeneratorAPI.create(); List classes = new ArrayList<>(); classes.add(MyEmployee.class); @@ -61,14 +59,7 @@ void generate_code() throws FileNotFoundException { // classes.add(ZonedDateTime.class); // classes.add(UUID.class); - List threeSystemStream = classes.stream().map(x -> truthGenerator.threeLayerSystem(x).get()).collect(Collectors.toList()); - - TestGenerator tg = new TestGenerator(Set.of()); - threeSystemStream.forEach(x -> { - tg.addTests(x.parent.generated, x.classUnderTest); - }); - truthGenerator.createOverallAccessPoints(); - + truthGenerator.generate(classes); // Assertions.assertThat(wc). @@ -79,6 +70,21 @@ void generate_code() throws FileNotFoundException { } @Test + public void package_java_mix() { + TruthGeneratorAPI tg = TruthGeneratorAPI.create(); + + String targetPackageName = this.getClass().getPackage().getName(); + SourceClassSets ss = new SourceClassSets(targetPackageName); + + ss.generateFromPackagesOf(IdCard.class); + + // generate java Subjects and put them in our package + ss.generateFrom(targetPackageName, UUID.class, ZonedDateTime.class); + + tg.generate(ss); + } + + @Test public void try_out_assertions() { // all asserts should be available diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/model/MyEmployee.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/model/MyEmployee.java deleted file mode 100644 index 11e73e82d..000000000 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/model/MyEmployee.java +++ /dev/null @@ -1,114 +0,0 @@ -package com.google.common.truth.extension.generator.model; - -import java.util.List; - -public class MyEmployee { - private long birthSeconds; - private boolean employed; - private double weighting; - private int birthYear; - private char id; - private String name; - - //todo private enum - //todo optional - //todo ZonedDateTime - - private MyEmployee boss; - - private IdCard card; - - private List projectList; - - @Override - public String toString() { - return "MyEmployee{" + - "birthSeconds=" + birthSeconds + - ", employed=" + employed + - ", weighting=" + weighting + - ", birthYear=" + birthYear + - ", id=" + id + - ", name='" + name + '\'' + - ", boss=" + boss + - ", card=" + card + - ", slipUpList=" + projectList + - '}'; - } - - public long getBirthSeconds() { - return birthSeconds; - } - - public void setBirthSeconds(final long birthSeconds) { - this.birthSeconds = birthSeconds; - } - - public boolean isEmployed() { - return employed; - } - - public void setEmployed(final boolean employed) { - this.employed = employed; - } - - public double getWeighting() { - return weighting; - } - - public void setWeighting(final double weighting) { - this.weighting = weighting; - } - - - public char getId() { - return id; - } - - public void setId(final char id) { - this.id = id; - } - - public MyEmployee getBoss() { - return boss; - } - - public void setBoss(final MyEmployee boss) { - this.boss = boss; - } - - public IdCard getCard() { - return card; - } - - public void setCard(final IdCard card) { - this.card = card; - } - - public List getSlipUpList() { - return projectList; - } - - public void setSlipUpList(final List slipUpList) { - this.projectList = slipUpList; - } - - public MyEmployee(final String name) { - this.name = name; - } - - public String getName() { - return name; - } - - public void setName(final String name) { - this.name = name; - } - - public int getBirthYear() { - return birthYear; - } - - public void setBirthYear(final int birthYear) { - this.birthYear = birthYear; - } -} diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/model/IdCard.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/IdCard.java similarity index 93% rename from extensions/generator/src/test/java/com/google/common/truth/extension/generator/model/IdCard.java rename to extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/IdCard.java index f492844e1..698d04a1f 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/model/IdCard.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/IdCard.java @@ -1,4 +1,4 @@ -package com.google.common.truth.extension.generator.model; +package com.google.common.truth.extension.generator.testModel; import java.util.UUID; diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/MyEmployee.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/MyEmployee.java new file mode 100644 index 000000000..e56e2fe3b --- /dev/null +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/MyEmployee.java @@ -0,0 +1,104 @@ +package com.google.common.truth.extension.generator.testModel; + +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public class MyEmployee extends Person { + private UUID id; + + private ZonedDateTime anniversary; + + private MyEmployee boss; + + private IdCard card; + + private List projectList; + + State employed; + + enum State { + EMPLOLYED, PREVIOUSLY_EMPLOYED, NEVER_EMPLOYED; + } + + /** + * Overriding support in Subject + */ + @Override + public String getName() { + return super.getName() + " ID: " +this.getId(); + } + + public char getId() { + return id; + } + + public void setId(final char id) { + this.id = id; + } + + public MyEmployee getBoss() { + return boss; + } + + public void setBoss(final MyEmployee boss) { + this.boss = boss; + } + + public IdCard getCard() { + return card; + } + + public void setCard(final IdCard card) { + this.card = card; + } + + public List getSlipUpList() { + return projectList; + } + + public void setSlipUpList(final List slipUpList) { + this.projectList = slipUpList; + } + + public MyEmployee(final String name) { + super(name); + } + + public List getProjectList() { + return this.projectList; + } + + public void setProjectList(final List projectList) { + this.projectList = projectList; + } + + public void setWeighting(final Optional weighting) { + this.weighting = weighting; + } + + private Optional weighting; + + public ZonedDateTime getAnniversary() { + return this.anniversary; + } + + public void setAnniversary(final ZonedDateTime anniversary) { + this.anniversary = anniversary; + } + + + public State getEmployed() { + return this.employed; + } + + public void setEmployed(final State employed) { + this.employed = employed; + } + + public Optional getWeighting() { + return this.weighting; + } + +} diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/Person.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/Person.java new file mode 100644 index 000000000..02206e131 --- /dev/null +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/Person.java @@ -0,0 +1,39 @@ +package com.google.common.truth.extension.generator.testModel; + +import java.time.ZonedDateTime; + +public class Person { + private long birthSeconds; + private String name; + private int birthYear; + private ZonedDateTime brithday; + + public long getBirthSeconds() { + return this.birthSeconds; + } + + public void setBirthSeconds(final long birthSeconds) { + this.birthSeconds = birthSeconds; + } + + public String getName() { + return this.name; + } + + public void setName(final String name) { + this.name = name; + } + + public int getBirthYear() { + return this.birthYear; + } + + public void setBirthYear(final int birthYear) { + this.birthYear = birthYear; + } + + public Person(final String name) { + this.name = name; + } + +} diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/model/Project.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/Project.java similarity index 89% rename from extensions/generator/src/test/java/com/google/common/truth/extension/generator/model/Project.java rename to extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/Project.java index 1a91ed4c2..21d8119f5 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/model/Project.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/Project.java @@ -1,4 +1,4 @@ -package com.google.common.truth.extension.generator.model; +package com.google.common.truth.extension.generator.testModel; import java.time.ZonedDateTime; From 58161fbb51bf4f2cb12725c9e130a345859c8dd1 Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Sat, 31 Jul 2021 15:45:01 +0100 Subject: [PATCH 12/32] iterate, custom package targets (UUID, ZonedDateTime) --- .../generator/TruthGeneratorAPI.java | 10 +- .../generator/internal/SkeletonGenerator.java | 514 +++++++++--------- .../internal/SkeletonGeneratorAPI.java | 2 +- .../generator/internal/SourceChecking.java | 72 +++ .../generator/internal/TruthGenerator.java | 234 ++++---- .../extension/generator/internal/Utils.java | 27 +- .../internal/model/SourceClassSets.java | 95 ++-- .../extension/generator/TestModelUtils.java | 22 + .../generator/TruthGeneratorTest.java | 41 +- .../extension/generator/testModel/IdCard.java | 2 +- .../generator/testModel/MyEmployee.java | 46 +- .../extension/generator/testModel/Person.java | 11 +- 12 files changed, 609 insertions(+), 467 deletions(-) create mode 100644 extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SourceChecking.java create mode 100644 extensions/generator/src/test/java/com/google/common/truth/extension/generator/TestModelUtils.java diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGeneratorAPI.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGeneratorAPI.java index 159f31344..7581c70e5 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGeneratorAPI.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGeneratorAPI.java @@ -4,9 +4,8 @@ import com.google.common.truth.extension.generator.internal.model.SourceClassSets; import com.google.common.truth.extension.generator.internal.model.ThreeSystem; -import java.io.FileNotFoundException; import java.util.List; -import java.util.Optional; +import java.util.Set; /** * @@ -52,12 +51,15 @@ static TruthGeneratorAPI create() { /** * @param ss + * @return */ - void generate(SourceClassSets ss); + Set generate(SourceClassSets ss); /** * * @param classes + * @return */ - void generate(List classes); + Set generate(Set> classes); + } diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java index dd2f68bf3..407e8da5a 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java @@ -7,14 +7,12 @@ import com.google.common.truth.extension.generator.internal.model.*; import org.jboss.forge.roaster.Roaster; import org.jboss.forge.roaster.model.source.*; -import org.reflections.ReflectionUtils; import com.google.common.flogger.FluentLogger; import javax.annotation.processing.Generated; import java.io.FileNotFoundException; import java.util.*; -import java.util.logging.Level; import static com.google.common.truth.extension.generator.internal.Utils.getFactoryName; import static com.google.common.truth.extension.generator.internal.Utils.writeToDisk; @@ -26,152 +24,126 @@ */ public class SkeletonGenerator implements SkeletonGeneratorAPI { - private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - @Override - public String maintain(Class source, Class userAndGeneratedMix) { - throw new IllegalStateException("Not implemented yet"); - } + private final Optional targetPackageName; - @Override - public Optional threeLayerSystem(Class source, Class usersMiddleClass) throws FileNotFoundException { - if (checkSource(source)) - return empty(); + public SkeletonGenerator(final Optional targetPackageName) { + this.targetPackageName = targetPackageName; + } - // make parent - boiler plate access - ParentClass parent = createParent(source); + @Override + public String maintain(Class source, Class userAndGeneratedMix) { + throw new IllegalStateException("Not implemented yet"); + } - String factoryMethodName = getFactoryName(source); + @Override + public Optional threeLayerSystem(Class source, Class usersMiddleClass) throws FileNotFoundException { + if (SourceChecking.checkSource(source, empty())) + return empty(); - // make child - client code entry point - JavaClassSource child = createChild(parent, usersMiddleClass.getName(), source, factoryMethodName); + // make parent - boiler plate access + ParentClass parent = createParent(source); - MiddleClass middleClass = new MiddleClass(null, null, usersMiddleClass); + String factoryMethodName = getFactoryName(source); - return Optional.of(new ThreeSystem(source, parent, middleClass, child)); - } + // make child - client code entry point + JavaClassSource child = createChild(parent, usersMiddleClass.getName(), source, factoryMethodName); - @Override - public Optional threeLayerSystem(Class source, Optional targetPackageName) { - if (checkSource(source)) - return empty(); + MiddleClass middleClass = new MiddleClass(null, null, usersMiddleClass); - ParentClass parent = createParent(source); + return Optional.of(new ThreeSystem(source, parent, middleClass, child)); + } - // todo try to see if class already exists first, user may already have a written one and not know - MiddleClass middle = createMiddlePlaceHolder(parent.generated, source); + @Override + public Optional threeLayerSystem(Class source) { + if (SourceChecking.checkSource(source, targetPackageName)) + return empty(); - JavaClassSource child = createChild(parent, middle.generated.getQualifiedName(), source, middle.factoryMethod.getName()); + ParentClass parent = createParent(source); - return Optional.of(new ThreeSystem(source, parent, middle, child)); - } + // todo try to see if class already exists first, user may already have a written one and not know + MiddleClass middle = createMiddlePlaceHolder(parent.generated, source); - private boolean checkSource(final Class source) { - if (source.isAnonymousClass()) { - logger.at(Level.FINE).log("Skipping anonymous class %s", source); - return true; - } + JavaClassSource child = createChild(parent, middle.generated.getQualifiedName(), source, middle.factoryMethod.getName()); - String simpleName = source.getSimpleName(); - if (simpleName.contains("Builder")) { - logger.at(Level.FINE).log("Skipping builder class %s", source); - return true; - } + return Optional.of(new ThreeSystem(source, parent, middle, child)); + } - if (isTestClass(source)) { - logger.at(Level.FINE).log("Skipping a test class %s", source); - return true; - } + private MiddleClass createMiddlePlaceHolder(JavaClassSource parent, Class source) { + JavaClassSource middle = Roaster.create(JavaClassSource.class); + middle.setName(getSubjectName(source.getSimpleName())); + middle.setPackage(parent.getPackage()); + middle.extendSuperType(parent); + JavaDocSource jd = middle.getJavaDoc(); + jd.setText("Optionally move this class into source control, and add your custom assertions here.\n\n" + + "

If the system detects this class already exists, it won't attempt to generate a new one. Note that " + + "if the base skeleton of this class ever changes, you won't automatically get it updated."); + jd.addTagValue("@see", parent.getName()); - return false; - } + addConstructor(source, middle, false); - /** - * If any method is annotated with something with Test in it, then assume it's a test class - */ - private boolean isTestClass(final Class source) { - boolean hasTestAnnotatedMethod = !ReflectionUtils.getMethods(source, - x -> Arrays.stream(x.getAnnotations()) - .anyMatch(y -> y.annotationType() - .getSimpleName().contains("Test"))).isEmpty(); - boolean nameEndsInTest = source.getSimpleName().endsWith("Test"); - return hasTestAnnotatedMethod || nameEndsInTest; - } + MethodSource factory = addFactoryAccesor(source, middle, source.getSimpleName()); - private MiddleClass createMiddlePlaceHolder(JavaClassSource parent, Class source) { - JavaClassSource middle = Roaster.create(JavaClassSource.class); - middle.setName(getSubjectName(source.getSimpleName())); - middle.setPackage(parent.getPackage()); - middle.extendSuperType(parent); - JavaDocSource jd = middle.getJavaDoc(); - jd.setText("Optionally move this class into source control, and add your custom assertions here.\n\n" + - "

If the system detects this class already exists, it won't attempt to generate a new one. Note that " + - "if the base skeleton of this class ever changes, you won't automatically get it updated."); - jd.addTagValue("@see", parent.getName()); + addGeneratedMarker(middle); - addConstructor(source, middle, false); - - MethodSource factory = addFactoryAccesor(source, middle, source.getSimpleName()); - - addGeneratedMarker(middle); - - writeToDisk(middle); - return new MiddleClass(middle, factory, null); - } + writeToDisk(middle, targetPackageName); + return new MiddleClass(middle, factory, null); + } - private ParentClass createParent(Class source) { - JavaClassSource parent = Roaster.create(JavaClassSource.class); - String sourceName = source.getSimpleName(); - String parentName = getSubjectName(sourceName + "Parent"); - parent.setName(parentName); + private ParentClass createParent(Class source) { + JavaClassSource parent = Roaster.create(JavaClassSource.class); + String sourceName = source.getSimpleName(); + String parentName = getSubjectName(sourceName + "Parent"); + parent.setName(parentName); - addPackageSuperAndAnnotation(parent, source); + addPackageSuperAndAnnotation(parent, source); - addClassJavaDoc(parent, sourceName); + addClassJavaDoc(parent, sourceName); - addActualField(source, parent); + addActualField(source, parent); - addConstructor(source, parent, true); + addConstructor(source, parent, true); - writeToDisk(parent); - return new ParentClass(parent); - } + writeToDisk(parent, targetPackageName); + return new ParentClass(parent); + } - private void addPackageSuperAndAnnotation(final JavaClassSource javaClass, final Class source) { - addPackageSuperAndAnnotation(javaClass, source.getPackage().getName(), getSubjectName(source.getSimpleName())); - } + private void addPackageSuperAndAnnotation(final JavaClassSource javaClass, final Class source) { + addPackageSuperAndAnnotation(javaClass, source.getPackage().getName(), getSubjectName(source.getSimpleName())); + } - private JavaClassSource createChild(ParentClass parent, - String usersMiddleClassName, - Class source, - String factoryMethodName) { - // todo if middle doesn't extend parent, warn + private JavaClassSource createChild(ParentClass parent, + String usersMiddleClassName, + Class source, + String factoryMethodName) { + // todo if middle doesn't extend parent, warn - JavaClassSource child = Roaster.create(JavaClassSource.class); - child.setName(getSubjectName(source.getSimpleName() + "Child")); - child.setPackage(parent.generated.getPackage()); - JavaDocSource javaDoc = child.getJavaDoc(); - javaDoc.setText("Entry point for assertions for @{" + source.getSimpleName() + "}. Import the static accessor methods from this class and use them.\n" + - "Combines the generated code from {@" + parent.generated.getName() + "}and the user code from {@" + usersMiddleClassName + "}."); - javaDoc.addTagValue("@see", source.getName()); - javaDoc.addTagValue("@see", usersMiddleClassName); - javaDoc.addTagValue("@see", parent.generated.getName()); + JavaClassSource child = Roaster.create(JavaClassSource.class); + child.setName(getSubjectName(source.getSimpleName() + "Child")); + child.setPackage(parent.generated.getPackage()); + JavaDocSource javaDoc = child.getJavaDoc(); + javaDoc.setText("Entry point for assertions for @{" + source.getSimpleName() + "}. Import the static accessor methods from this class and use them.\n" + + "Combines the generated code from {@" + parent.generated.getName() + "}and the user code from {@" + usersMiddleClassName + "}."); + javaDoc.addTagValue("@see", source.getName()); + javaDoc.addTagValue("@see", usersMiddleClassName); + javaDoc.addTagValue("@see", parent.generated.getName()); - addAccessPoints(source, child, factoryMethodName, usersMiddleClassName); + addAccessPoints(source, child, factoryMethodName, usersMiddleClassName); - addGeneratedMarker(child); + addGeneratedMarker(child); - writeToDisk(child); - return child; - } + writeToDisk(child, targetPackageName); + return child; + } // private void registerManagedClass(Class sourceClass, JavaClassSource gengeratedClass) { // managedSubjects.add(new ManagedClassSet(sourceClass, gengeratedClass)); // } - @Override - public String combinedSystem(Class source) { - JavaClassSource javaClass = Roaster.create(JavaClassSource.class); + @Override + public String combinedSystem(Class source) { + JavaClassSource javaClass = Roaster.create(JavaClassSource.class); // JavaClassSource handWrittenExampleCode = Roaster.parse(JavaClassSource.class, handWritten); @@ -179,182 +151,184 @@ public String combinedSystem(Class source) { // javaClass = handWrittenExampleCode; - String packageName = source.getPackage().getName(); - String sourceName = source.getSimpleName(); - String subjectClassName = getSubjectName(sourceName); + String packageName = source.getPackage().getName(); + String sourceName = source.getSimpleName(); + String subjectClassName = getSubjectName(sourceName); - addPackageSuperAndAnnotation(javaClass, packageName, subjectClassName); + addPackageSuperAndAnnotation(javaClass, packageName, subjectClassName); - addClassJavaDoc(javaClass, sourceName); + addClassJavaDoc(javaClass, sourceName); - addActualField(source, javaClass); + addActualField(source, javaClass); - addConstructor(source, javaClass, true); + addConstructor(source, javaClass, true); - MethodSource factory = addFactoryAccesor(source, javaClass, sourceName); + MethodSource factory = addFactoryAccesor(source, javaClass, sourceName); - addAccessPoints(source, javaClass, factory.getName(), javaClass.getQualifiedName()); + addAccessPoints(source, javaClass, factory.getName(), javaClass.getQualifiedName()); - // todo add static import for Truth.assertAbout somehow? + // todo add static import for Truth.assertAbout somehow? // Import anImport = javaClass.addImport(Truth.class); // javaClass.addImport(anImport.setStatic(true)); // javaClass.addImport(new Im) - String classSource = writeToDisk(javaClass); - - return classSource; - } - - private String getSubjectName(final String sourceName) { - return sourceName + "Subject"; - } - - private void addAccessPoints(Class source, JavaClassSource javaClass, - String factoryMethod, - String factoryContainerQualifiedName) { - MethodSource assertThat = addAssertThat(source, javaClass, factoryMethod, factoryContainerQualifiedName); - - addAssertTruth(source, javaClass, assertThat); + String classSource = writeToDisk(javaClass, targetPackageName); + + return classSource; + } + + private String getSubjectName(final String sourceName) { + return sourceName + "Subject"; + } + + private void addAccessPoints(Class source, JavaClassSource javaClass, + String factoryMethod, + String factoryContainerQualifiedName) { + MethodSource assertThat = addAssertThat(source, javaClass, factoryMethod, factoryContainerQualifiedName); + + addAssertTruth(source, javaClass, assertThat); + } + + private void addPackageSuperAndAnnotation(JavaClassSource javaClass, String packageName, String subjectClassName) { + String packageNameToUse = targetPackageName.isPresent() ? targetPackageName.get() : packageName; + javaClass.setPackage(packageNameToUse); + + // extend + javaClass.extendSuperType(Subject.class); + + // + addGeneratedMarker(javaClass); + } + + private void addGeneratedMarker(final JavaClassSource javaClass) { + // requires java 9 + // annotate generated + // @javax.annotation.Generated(value="") + // only in @since 1.9, so can't add it programmatically + AnnotationSource generated = javaClass.addAnnotation(Generated.class); + generated.setStringValue("truth-generator"); + // Can't add it without the value param, see https://github.com/forge/roaster/issues/201 + // AnnotationSource generated = javaClass.addAnnotation("javax.annotation.processing.Generated"); + } + + private void addClassJavaDoc(JavaClassSource javaClass, String sourceName) { + // class javadc + JavaDocSource classDocs = javaClass.getJavaDoc(); + if (classDocs.getFullText().isEmpty()) { + classDocs.setText("Truth Subject for the {@link " + sourceName + "}." + + "\n\n" + + "Note that this class is generated / managed, and will change over time. So any changes you might " + + "make will be overwritten."); + classDocs.addTagValue("@see", sourceName); + classDocs.addTagValue("@see", getSubjectName(sourceName)); + classDocs.addTagValue("@see", getSubjectName(sourceName + "Child")); } - - private void addPackageSuperAndAnnotation(JavaClassSource javaClass, String packageName, String subjectClassName) { - javaClass.setPackage(packageName); - - // extend - javaClass.extendSuperType(Subject.class); - - addGeneratedMarker(javaClass); - } - - private void addGeneratedMarker(final JavaClassSource javaClass) { - // requires java 9 - // annotate generated - // @javax.annotation.Generated(value="") - // only in @since 1.9, so can't add it programmatically - AnnotationSource generated = javaClass.addAnnotation(Generated.class); - generated.setStringValue("truth-generator"); - // Can't add it without the value param, see https://github.com/forge/roaster/issues/201 - // AnnotationSource generated = javaClass.addAnnotation("javax.annotation.processing.Generated"); - } - - private void addClassJavaDoc(JavaClassSource javaClass, String sourceName) { - // class javadc - JavaDocSource classDocs = javaClass.getJavaDoc(); - if (classDocs.getFullText().isEmpty()) { - classDocs.setText("Truth Subject for the {@link " + sourceName + "}." + - "\n\n" + - "Note that this class is generated / managed, and will change over time. So any changes you might " + - "make will be overwritten."); - classDocs.addTagValue("@see", sourceName); - classDocs.addTagValue("@see", getSubjectName(sourceName)); - classDocs.addTagValue("@see", getSubjectName(sourceName + "Child")); - } - } - - private void addAssertTruth(Class source, JavaClassSource javaClass, MethodSource assertThat) { - String name = "assertTruth"; - if (!containsMethodCalled(javaClass, name)) { - // convenience entry point when being mixed with other "assertThat" assertion libraries - MethodSource assertTruth = javaClass.addMethod() - .setName(name) - .setPublic() - .setStatic(true) - .setReturnType(assertThat.getReturnType()); - assertTruth.addParameter(source, "actual"); - assertTruth.setBody("return " + assertThat.getName() + "(actual);"); - assertTruth.getJavaDoc().setText("Convenience entry point for {@link " + source.getSimpleName() + "} assertions when being " + - "mixed with other \"assertThat\" assertion libraries.") - .addTagValue("@see", "#assertThat"); - } + } + + private void addAssertTruth(Class source, JavaClassSource javaClass, MethodSource assertThat) { + String name = "assertTruth"; + if (!containsMethodCalled(javaClass, name)) { + // convenience entry point when being mixed with other "assertThat" assertion libraries + MethodSource assertTruth = javaClass.addMethod() + .setName(name) + .setPublic() + .setStatic(true) + .setReturnType(assertThat.getReturnType()); + assertTruth.addParameter(source, "actual"); + assertTruth.setBody("return " + assertThat.getName() + "(actual);"); + assertTruth.getJavaDoc().setText("Convenience entry point for {@link " + source.getSimpleName() + "} assertions when being " + + "mixed with other \"assertThat\" assertion libraries.") + .addTagValue("@see", "#assertThat"); } - - private MethodSource addAssertThat(Class source, - JavaClassSource javaClass, - String factoryMethodName, - String factoryContainerQualifiedName) { - String methodName = "assertThat"; - if (containsMethodCalled(javaClass, methodName)) { - return getMethodCalled(javaClass, methodName); - } else { - // entry point - MethodSource assertThat = javaClass.addMethod() - .setName(methodName) - .setPublic() - .setStatic(true) - .setReturnType(factoryContainerQualifiedName); - assertThat.addParameter(source, "actual"); - // return assertAbout(things()).that(actual); - // add explicit static reference for now - see below - javaClass.addImport(factoryContainerQualifiedName + ".*") - .setStatic(true); - String entryPointBody = "return Truth.assertAbout(" + factoryMethodName + "()).that(actual);"; - assertThat.setBody(entryPointBody); - javaClass.addImport(Truth.class); - assertThat.getJavaDoc().setText("Entry point for {@link " + source.getSimpleName() + "} assertions."); - return assertThat; - } + } + + private MethodSource addAssertThat(Class source, + JavaClassSource javaClass, + String factoryMethodName, + String factoryContainerQualifiedName) { + String methodName = "assertThat"; + if (containsMethodCalled(javaClass, methodName)) { + return getMethodCalled(javaClass, methodName); + } else { + // entry point + MethodSource assertThat = javaClass.addMethod() + .setName(methodName) + .setPublic() + .setStatic(true) + .setReturnType(factoryContainerQualifiedName); + assertThat.addParameter(source, "actual"); + // return assertAbout(things()).that(actual); + // add explicit static reference for now - see below + javaClass.addImport(factoryContainerQualifiedName + ".*") + .setStatic(true); + String entryPointBody = "return Truth.assertAbout(" + factoryMethodName + "()).that(actual);"; + assertThat.setBody(entryPointBody); + javaClass.addImport(Truth.class); + assertThat.getJavaDoc().setText("Entry point for {@link " + source.getSimpleName() + "} assertions."); + return assertThat; } - - private MethodSource getMethodCalled(JavaClassSource javaClass, String methodName) { - return javaClass.getMethods().stream().filter(x -> x.getName().equals(methodName)).findFirst().get(); + } + + private MethodSource getMethodCalled(JavaClassSource javaClass, String methodName) { + return javaClass.getMethods().stream().filter(x -> x.getName().equals(methodName)).findFirst().get(); + } + + private MethodSource addFactoryAccesor(Class source, JavaClassSource javaClass, String sourceName) { + String factoryName = getFactoryName(source); + if (containsMethodCalled(javaClass, factoryName)) { + return getMethodCalled(javaClass, factoryName); + } else { + // factory accessor + String returnType = getTypeWithGenerics(Subject.Factory.class, javaClass.getName(), sourceName); + MethodSource factory = javaClass.addMethod() + .setName(factoryName) + .setPublic() + .setStatic(true) + // todo replace with something other than the string method - I suppose it's not possible to do generics type safely + .setReturnType(returnType) + .setBody("return " + javaClass.getName() + "::new;"); + JavaDocSource> factoryDocs = factory.getJavaDoc(); + factoryDocs.setText("Returns an assertion builder for a {@link " + sourceName + "} class."); + return factory; } - - private MethodSource addFactoryAccesor(Class source, JavaClassSource javaClass, String sourceName) { - String factoryName = getFactoryName(source); - if (containsMethodCalled(javaClass, factoryName)) { - return getMethodCalled(javaClass, factoryName); - } else { - // factory accessor - String returnType = getTypeWithGenerics(Subject.Factory.class, javaClass.getName(), sourceName); - MethodSource factory = javaClass.addMethod() - .setName(factoryName) - .setPublic() - .setStatic(true) - // todo replace with something other than the string method - I suppose it's not possible to do generics type safely - .setReturnType(returnType) - .setBody("return " + javaClass.getName() + "::new;"); - JavaDocSource> factoryDocs = factory.getJavaDoc(); - factoryDocs.setText("Returns an assertion builder for a {@link " + sourceName + "} class."); - return factory; - } + } + + private boolean containsMethodCalled(JavaClassSource javaClass, String factoryName) { + return javaClass.getMethods().stream().anyMatch(x -> x.getName().equals(factoryName)); + } + + private void addConstructor(Class source, JavaClassSource javaClass, boolean setActual) { + if (!javaClass.getMethods().stream().anyMatch(x -> x.isConstructor())) { + // constructor + MethodSource constructor = javaClass.addMethod() + .setConstructor(true) + .setProtected(); + constructor.addParameter(FailureMetadata.class, "failureMetadata"); + constructor.addParameter(source, "actual"); + StringBuilder sb = new StringBuilder("super(failureMetadata, actual);\n"); + if (setActual) + sb.append("this.actual = actual;"); + constructor.setBody(sb.toString()); } - - private boolean containsMethodCalled(JavaClassSource javaClass, String factoryName) { - return javaClass.getMethods().stream().anyMatch(x -> x.getName().equals(factoryName)); + } + + private void addActualField(Class source, JavaClassSource javaClass) { + String fieldName = "actual"; + if (javaClass.getField(fieldName) == null) { + // actual field + javaClass.addField() + .setProtected() + .setType(source) + .setName(fieldName) + .setFinal(true); } + } - private void addConstructor(Class source, JavaClassSource javaClass, boolean setActual) { - if (!javaClass.getMethods().stream().anyMatch(x -> x.isConstructor())) { - // constructor - MethodSource constructor = javaClass.addMethod() - .setConstructor(true) - .setProtected(); - constructor.addParameter(FailureMetadata.class, "failureMetadata"); - constructor.addParameter(source, "actual"); - StringBuilder sb = new StringBuilder("super(failureMetadata, actual);\n"); - if (setActual) - sb.append("this.actual = actual;"); - constructor.setBody(sb.toString()); - } - } - - private void addActualField(Class source, JavaClassSource javaClass) { - String fieldName = "actual"; - if (javaClass.getField(fieldName) == null) { - // actual field - javaClass.addField() - .setProtected() - .setType(source) - .setName(fieldName) - .setFinal(true); - } - } - - private String getTypeWithGenerics(Class factoryClass, String... classes) { - String genericsList = Joiner.on(", ").skipNulls().join(classes); - String generics = new StringBuilder("<>").insert(1, genericsList).toString(); - return factoryClass.getSimpleName() + generics; - } + private String getTypeWithGenerics(Class factoryClass, String... classes) { + String genericsList = Joiner.on(", ").skipNulls().join(classes); + String generics = new StringBuilder("<>").insert(1, genericsList).toString(); + return factoryClass.getSimpleName() + generics; + } } diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGeneratorAPI.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGeneratorAPI.java index 3f84e7d41..73f04b113 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGeneratorAPI.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGeneratorAPI.java @@ -38,7 +38,7 @@ public interface SkeletonGeneratorAPI { * @return null if for some reason the class isn't supported * @see #threeLayerSystem(Class, Class) */ - Optional threeLayerSystem(Class source, Optional targetPackageName); + Optional threeLayerSystem(Class source); /** * @see com.google.common.truth.extension.generator.TruthGeneratorAPI#combinedSystem(Class) diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SourceChecking.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SourceChecking.java new file mode 100644 index 000000000..0a3e99be1 --- /dev/null +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SourceChecking.java @@ -0,0 +1,72 @@ +package com.google.common.truth.extension.generator.internal; + +import com.google.common.flogger.FluentLogger; +import org.apache.commons.lang3.StringUtils; +import org.reflections.ReflectionUtils; + +import java.util.Arrays; +import java.util.Optional; +import java.util.logging.Level; + +public class SourceChecking { + + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + + static boolean checkSource(Class source, Optional targetPackage) { + if (isAnonymous(source)) + return true; + + if (isBuilder(source)) + return true; + + if (isTestClass(source)) + return true; + + if (isJavaPackage(source, targetPackage)) + return true; + + return false; + } + + + private static boolean isJavaPackage(Class source, Optional targetPackage) { + boolean isBlank = targetPackage.isEmpty() || StringUtils.isBlank(targetPackage.get()); + if (source.getPackage().getName().startsWith("java.") && isBlank) + throw new IllegalArgumentException("Cannot construct Subject's for external modules without changing their " + + "destination package. See SourceClassSets#generateFrom(String, Class...)"); + return false; + } + + /** + * If any method is annotated with something with Test in it, then assume it's a test class + */ + private static boolean isTestClass(Class source) { + boolean hasTestAnnotatedMethod = !ReflectionUtils.getMethods(source, + x -> Arrays.stream(x.getAnnotations()) + .anyMatch(y -> y.annotationType() + .getSimpleName().contains("Test"))).isEmpty(); + boolean nameEndsInTest = source.getSimpleName().endsWith("Test"); + boolean isIndeed = hasTestAnnotatedMethod || nameEndsInTest; + if (isIndeed) { + logger.at(Level.FINE).log("Skipping a test class %s", source); + } + return isIndeed; + } + + private static boolean isBuilder(Class source) { + String simpleName = source.getSimpleName(); + if (simpleName.contains("Builder")) { + logger.at(Level.FINE).log("Skipping builder class %s", source); + return true; + } + return false; + } + + private static boolean isAnonymous(Class source) { + if (source.isAnonymousClass()) { + logger.at(Level.FINE).log("Skipping anonymous class %s", source); + return true; + } + return false; + } +} diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java index c8dac4f50..a23bdbfa7 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java @@ -1,9 +1,11 @@ package com.google.common.truth.extension.generator.internal; import com.google.common.collect.Sets; +import com.google.common.collect.Sets.SetView; import com.google.common.truth.Subject; import com.google.common.truth.extension.generator.TruthGeneratorAPI; import com.google.common.truth.extension.generator.internal.model.SourceClassSets; +import com.google.common.truth.extension.generator.internal.model.SourceClassSets.PackageAndClasses; import com.google.common.truth.extension.generator.internal.model.ThreeSystem; import org.reflections.Reflections; import org.reflections.scanners.SubTypesScanner; @@ -11,130 +13,142 @@ import java.util.*; import java.util.stream.Collectors; +import static com.google.common.collect.Sets.union; + /** * @author Antony Stubbs */ public class TruthGenerator implements TruthGeneratorAPI { - @Override - public void generate(String... modelPackages) { - Utils.requireNotEmpty(modelPackages); - - OverallEntryPoint overallEntryPoint = new OverallEntryPoint(); - Set subjectsSystems = generateSkeletonsFromPackages(modelPackages, overallEntryPoint); - - // - addTests(subjectsSystems); - - // just take the first for now - String[] packageNameForOverall = modelPackages; - overallEntryPoint.createOverallAccessPoints(packageNameForOverall[0]); + @Override + public void generate(String... modelPackages) { + Utils.requireNotEmpty(modelPackages); + + OverallEntryPoint overallEntryPoint = new OverallEntryPoint(); + Set subjectsSystems = generateSkeletonsFromPackages(modelPackages, overallEntryPoint); + + // + addTests(subjectsSystems); + + // just take the first for now + String[] packageNameForOverall = modelPackages; + overallEntryPoint.createOverallAccessPoints(packageNameForOverall[0]); + } + + private Set generateSkeletonsFromPackages(final String[] modelPackages, OverallEntryPoint overallEntryPoint) { + Set> allTypes = collectSourceClasses(modelPackages); + return generateSkeletons(allTypes, Optional.empty(), overallEntryPoint); + } + + private Set generateSkeletons(Set> classes, Optional targetPackageName, + OverallEntryPoint overallEntryPoint) { + SkeletonGenerator skeletonGenerator = new SkeletonGenerator(targetPackageName); + + Set subjectsSystems = new HashSet<>(); + for (Class c : classes) { + Optional threeSystem = skeletonGenerator.threeLayerSystem(c); + if (threeSystem.isPresent()) { + ThreeSystem ts = threeSystem.get(); + subjectsSystems.add(ts); + overallEntryPoint.add(ts); + } } - - private Set generateSkeletonsFromPackages(final String[] modelPackages, OverallEntryPoint overallEntryPoint) { - Set> allTypes = collectSourceClasses(modelPackages); - return generateSkeletons(allTypes, Optional.empty(), overallEntryPoint); - } - - private Set generateSkeletons(Set> classes, Optional targetPackageName, - OverallEntryPoint overallEntryPoint) { - SkeletonGenerator skeletonGenerator = new SkeletonGenerator(); - - Set subjectsSystems = new HashSet<>(); - for (Class c : classes) { - Optional threeSystem = skeletonGenerator.threeLayerSystem(c, targetPackageName); - if (threeSystem.isPresent()) { - ThreeSystem ts = threeSystem.get(); - subjectsSystems.add(ts); - overallEntryPoint.add(ts); + return subjectsSystems; + } + + private Set> collectSourceClasses(final String[] modelPackages) { + // for all classes in package + SubTypesScanner subTypesScanner = new SubTypesScanner(false); + + Reflections reflections = new Reflections(modelPackages, subTypesScanner); + reflections.expandSuperTypes(); // get things that extend something that extend object + + // https://github.com/ronmamo/reflections/issues/126 + Set> subTypesOfEnums = reflections.getSubTypesOf(Enum.class); + + Set> allTypes = reflections.getSubTypesOf(Object.class) + // remove Subject classes from previous runs + .stream().filter(x -> !Subject.class.isAssignableFrom(x)) + .collect(Collectors.toSet()); + allTypes.addAll(subTypesOfEnums); + return allTypes; + } + + private void addTests(final Set allTypes) { + SubjectMethodGenerator tg = new SubjectMethodGenerator(allTypes); + tg.addTests(allTypes); + } + + @Override + public void generateFromPackagesOf(Class... classes) { + generate(getPackageStrings(classes)); + } + + @Override + public void combinedSystem(final SourceClassSets ss) { + + } + + private String[] getPackageStrings(final Class[] classes) { + return Arrays.stream(classes).map(x -> x.getPackage().getName()).collect(Collectors.toList()).toArray(new String[0]); + } + + @Override + public Set generate(SourceClassSets ss) { + Set packages = ss.getSimplePackageOfClasses().stream().map( + this::getPackageStrings + ).collect(Collectors.toSet()); + + OverallEntryPoint overallEntryPoint = new OverallEntryPoint(); + + // skeletons generation is independent and should be able to be done in parallel + Set skeletons = packages.parallelStream().flatMap( + x -> generateSkeletonsFromPackages(x, overallEntryPoint).stream() + ).collect(Collectors.toSet()); + + // custom packge destination + Set packageAndClasses = ss.getPackageAndClasses(); + Set setStream = packageAndClasses.stream().flatMap( + x -> { + Set> collect = Arrays.stream(x.getClasses()).collect(Collectors.toSet()); + return generateSkeletons(collect, Optional.of(x.getTargetPackageName()), overallEntryPoint).stream(); } - } - return subjectsSystems; - } - - private Set> collectSourceClasses(final String[] modelPackages) { - // for all classes in package - SubTypesScanner subTypesScanner = new SubTypesScanner(false); - - Reflections reflections = new Reflections(modelPackages, subTypesScanner); - reflections.expandSuperTypes(); // get things that extend something that extend object - - // https://github.com/ronmamo/reflections/issues/126 - Set> subTypesOfEnums = reflections.getSubTypesOf(Enum.class); - - Set> allTypes = reflections.getSubTypesOf(Object.class) - // remove Subject classes from previous runs - .stream().filter(x -> !Subject.class.isAssignableFrom(x)) - .collect(Collectors.toSet()); - allTypes.addAll(subTypesOfEnums); - return allTypes; - } - - private void addTests(final Set allTypes) { - SubjectMethodGenerator tg = new SubjectMethodGenerator(allTypes); - tg.addTests(allTypes); - } - - @Override - public void generateFromPackagesOf(Class... classes) { - generate(getPackageStrings(classes)); - } - - @Override - public void combinedSystem(final SourceClassSets ss) { - - } - - private String[] getPackageStrings(final Class[] classes) { - return Arrays.stream(classes).map(x -> x.getPackage().getName()).collect(Collectors.toList()).toArray(new String[0]); - } + ).collect(Collectors.toSet()); - @Override - public void generate(SourceClassSets ss) { - Set packages = ss.getSimplePackageOfClasses().stream().map( - this::getPackageStrings - ).collect(Collectors.toSet()); + // straight up classes + Set simpleClasses = generateSkeletons(ss.getSimpleClasses(), Optional.empty(), overallEntryPoint); - OverallEntryPoint overallEntryPoint = new OverallEntryPoint(); - // skeletons generation is independent and should be able to be done in parallel - Set skeletons = packages.parallelStream().flatMap( - x -> generateSkeletonsFromPackages(x, overallEntryPoint).stream() - ).collect(Collectors.toSet()); + // + SetView union = union(union(skeletons, setStream), simpleClasses); + addTests(union); - Set packageAndClasses = ss.getPackageAndClasses(); - Set setStream = packageAndClasses.stream().flatMap( - x -> { - Set> collect = Arrays.stream(x.getClasses()).collect(Collectors.toSet()); - return generateSkeletons(collect, Optional.of(x.getTargetPackageName()), overallEntryPoint).stream(); - } - ).collect(Collectors.toSet()); + // create overall entry point + overallEntryPoint.createOverallAccessPoints(ss.getPackageForOverall()); - // - addTests(Sets.union(skeletons, setStream)); + return union; + } - // create overall entry point - overallEntryPoint.createOverallAccessPoints(ss.getPackageForOverall()); - } + @Override + public Set generate(Set> classes) { + Utils.requireNotEmpty(classes); + SourceClassSets ss = new SourceClassSets(classes.stream().findFirst().get().getPackageName()); + ss.generateFrom(classes); + return generate(ss); + } - @Override - public void generate(final List classes) { - Utils.requireNotEmpty(classes); - generate(new SourceClassSets(classes.get(0).getPackageName())); - } + @Override + public String maintain(final Class source, final Class userAndGeneratedMix) { + throw new IllegalStateException("Not implemented yet"); + } - @Override - public String maintain(final Class source, final Class userAndGeneratedMix) { - throw new IllegalStateException("Not implemented yet"); - } + @Override + public String combinedSystem(final Class source) { + throw new IllegalStateException("Not implemented yet"); + } - @Override - public String combinedSystem(final Class source) { - throw new IllegalStateException("Not implemented yet"); - } - - @Override - public void combinedSystem(final String... modelPackages) { - throw new IllegalStateException("Not implemented yet"); - } + @Override + public void combinedSystem(final String... modelPackages) { + throw new IllegalStateException("Not implemented yet"); + } } diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/Utils.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/Utils.java index 08cf5cfd2..4e14b2ba8 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/Utils.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/Utils.java @@ -8,14 +8,20 @@ import java.io.PrintWriter; import java.nio.file.Paths; import java.util.List; +import java.util.Optional; +import java.util.Set; import static java.lang.String.format; public class Utils { public static String writeToDisk(JavaClassSource javaClass) { + return writeToDisk(javaClass, Optional.empty()); + } + + public static String writeToDisk(JavaClassSource javaClass, Optional targetPackageName) { String classSource = javaClass.toString(); - String fileName = getFileName(javaClass); + String fileName = getFileName(javaClass, targetPackageName); try (PrintWriter out = new PrintWriter(fileName)) { out.println(classSource); } catch (FileNotFoundException e) { @@ -24,8 +30,8 @@ public static String writeToDisk(JavaClassSource javaClass) { return classSource; } - private static String getFileName(JavaClassSource javaClass) { - String directoryName = getDirectoryName(javaClass); + private static String getFileName(JavaClassSource javaClass, Optional targetPackageName) { + String directoryName = getDirectoryName(javaClass, targetPackageName); File dir = new File(directoryName); if (!dir.exists()) { boolean mkdir = dir.mkdirs(); @@ -33,9 +39,12 @@ private static String getFileName(JavaClassSource javaClass) { return directoryName + javaClass.getName() + ".java"; } - private static String getDirectoryName(JavaClassSource javaClass) { + private static String getDirectoryName(JavaClassSource javaClass, Optional targetPackageName) { String parent = Paths.get("").toAbsolutePath().toString(); - String packageName = javaClass.getPackage(); + String packageName = targetPackageName.isEmpty() ? javaClass.getPackage() : targetPackageName.get(); + + // don't use, as won't be able to access package level access methods if we live in a different package + // user can still use a custom target package if they like String packageNameSuffix = ".truth"; List ids = List.of("Parent", "Child"); @@ -59,7 +68,7 @@ private static String toLowerCaseFirstLetter(String plural) { return plural.substring(0, 1).toLowerCase() + plural.substring(1); } - public static void requireNotEmpty(final List classes) { + public static void requireNotEmpty(final List> classes) { if (classes.isEmpty()) { throw new IllegalArgumentException("No classes to generate from"); } @@ -70,4 +79,10 @@ public static void requireNotEmpty(final String[] modelPackages) { throw new IllegalArgumentException("No packages to generate from"); } } + + public static void requireNotEmpty(final Set> classes) { + if (classes.isEmpty()) { + throw new IllegalArgumentException("No classes to generate from"); + } + } } diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/SourceClassSets.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/SourceClassSets.java index c8d53c8be..1f57882c5 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/SourceClassSets.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/SourceClassSets.java @@ -1,61 +1,76 @@ package com.google.common.truth.extension.generator.internal.model; import java.util.HashSet; +import java.util.List; import java.util.Set; public class SourceClassSets { - private final String packageForOverall; - private final Set[]> simplePackageOfClasses = new HashSet<>(); - private final Set packageAndClasses = new HashSet<>(); + private final String packageForOverall; + private Set[]> simplePackageOfClasses = new HashSet<>(); + private Set> simpleClasses = new HashSet<>(); + private Set packageAndClasses = new HashSet<>(); - public SourceClassSets(final String packageForOverall) { - this.packageForOverall = packageForOverall; - } + /** + * @param packageForOverall the package to put the overall access points + */ + public SourceClassSets(final String packageForOverall) { + this.packageForOverall = packageForOverall; + } - public void generateFromPackagesOf(Class... classes) { - simplePackageOfClasses.add(classes); - } + public void generateAllFoundInPackagesOf(Class... classes) { + simplePackageOfClasses.add(classes); + } - /** - * Useful for generating Java module Subjects and put them in our package. - * - * I.e. for UUID.class you can't create a Subject in the same package as it (not allowed) - * - * @param targetPackageName - * @param classes - */ - public void generateFrom(String targetPackageName, Class... classes) { - packageAndClasses.add(new PackageAndClasses(targetPackageName, classes)); - } + /** + * Useful for generating Java module Subjects and put them in our package. + *

+ * I.e. for UUID.class you can't create a Subject in the same package as it (not allowed) + * + * @param targetPackageName + * @param classes + */ + public void generateFrom(String targetPackageName, Class... classes) { + packageAndClasses.add(new PackageAndClasses(targetPackageName, classes)); + } - public class PackageAndClasses { - final String targetPackageName; + public void generateFrom(Set> classes) { + this.simpleClasses = classes; + } - final Class[] classes; - private PackageAndClasses(final String targetPackageName, final Class[] classes) { - this.targetPackageName = targetPackageName; - this.classes = classes; - } + public class PackageAndClasses { + final String targetPackageName; - public String getTargetPackageName() { - return targetPackageName; - } + final Class[] classes; - public Class[] getClasses() { - return classes; - } + private PackageAndClasses(final String targetPackageName, final Class[] classes) { + this.targetPackageName = targetPackageName; + this.classes = classes; } - public Set[]> getSimplePackageOfClasses() { - return simplePackageOfClasses; + public String getTargetPackageName() { + return targetPackageName; } - public Set getPackageAndClasses() { - return packageAndClasses; + public Class[] getClasses() { + return classes; } + } + + public Set[]> getSimplePackageOfClasses() { + return simplePackageOfClasses; + } + + public Set getPackageAndClasses() { + return packageAndClasses; + } + + public String getPackageForOverall() { + return packageForOverall; + } + + public Set> getSimpleClasses() { + return this.simpleClasses; + } - public String getPackageForOverall() { - return packageForOverall; - } } diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TestModelUtils.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TestModelUtils.java new file mode 100644 index 000000000..384804da8 --- /dev/null +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TestModelUtils.java @@ -0,0 +1,22 @@ +package com.google.common.truth.extension.generator; + +import com.google.common.truth.extension.generator.testModel.IdCard; +import com.google.common.truth.extension.generator.testModel.MyEmployee; + +import java.time.ZonedDateTime; + +public class TestModelUtils { + + static MyEmployee createEmployee() { + MyEmployee employee = new MyEmployee("Zeynep"); + employee.setBirthday(ZonedDateTime.now().withYear(1983)); + employee.setBoss(new MyEmployee("Lilan")); + employee.setCard(createCard()); + return employee; + } + + private static IdCard createCard() { + return new IdCard("special-card-x", 4); + } + +} diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java index 74095657d..b8c079677 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java @@ -3,9 +3,8 @@ import com.google.common.io.Resources; import com.google.common.truth.extension.Employee; import com.google.common.truth.extension.generator.internal.model.SourceClassSets; -import com.google.common.truth.extension.generator.testModel.IdCard; -import com.google.common.truth.extension.generator.testModel.MyEmployee; -import com.google.common.truth.extension.generator.testModel.Project; +import com.google.common.truth.extension.generator.internal.model.ThreeSystem; +import com.google.common.truth.extension.generator.testModel.*; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -13,9 +12,8 @@ import java.io.IOException; import java.nio.charset.Charset; import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; +import java.util.*; +import java.util.stream.Collectors; import static com.google.common.truth.Truth.assertThat; @@ -46,26 +44,33 @@ public void combined() { } @Test - void generate_code() { + public void generate_code() { // todo need to be able to set base package for all generated classes, kind of like shade, so you cah generate test for classes in other restricted modules TruthGeneratorAPI truthGenerator = TruthGeneratorAPI.create(); - List classes = new ArrayList<>(); + Set> classes = new HashSet<>(); classes.add(MyEmployee.class); classes.add(IdCard.class); classes.add(Project.class); + String basePackageName = getClass().getPackage().getName(); + SourceClassSets ss = new SourceClassSets(basePackageName); + ss.generateFrom(classes); + // package exists in other module error - needs package target support -// classes.add(ZonedDateTime.class); -// classes.add(UUID.class); + ss.generateFrom(basePackageName, ZonedDateTime.class, UUID.class); - truthGenerator.generate(classes); + Set generated = truthGenerator.generate(ss); -// Assertions.assertThat(wc). + assertThat(generated.size()).isAtLeast(classes.size()); + Set> generatedSourceClasses = generated.stream().map(x -> x.classUnderTest).collect(Collectors.toSet()); + assertThat(generatedSourceClasses).containsAtLeast(UUID.class, ZonedDateTime.class); -// truthGenerator.generate(PartitionState.class); + MyEmployee employee = TestModelUtils.createEmployee(); + ManagedTruth.assertThat(employee).getCard().getEpoch().isAtLeast(0); +// ManagedTruth.assertThat(employee).getBoss().getName().isEqualTo("Tony"); -// truthGenerator.maintain(LongPollingMockConsumer.class, LongPollingMockConsumerSubject.class); + ManagedTruth.assertThat(employee).getBirthday().getYear().isLessThan(1920); } @@ -76,15 +81,17 @@ public void package_java_mix() { String targetPackageName = this.getClass().getPackage().getName(); SourceClassSets ss = new SourceClassSets(targetPackageName); - ss.generateFromPackagesOf(IdCard.class); + ss.generateAllFoundInPackagesOf(IdCard.class); // generate java Subjects and put them in our package ss.generateFrom(targetPackageName, UUID.class, ZonedDateTime.class); - tg.generate(ss); + Set generated = tg.generate(ss); + assertThat(generated.size()).isAtLeast(ss.getPackageAndClasses().size()); + } - @Test + @Test public void try_out_assertions() { // all asserts should be available diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/IdCard.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/IdCard.java index 698d04a1f..62a7a231b 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/IdCard.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/IdCard.java @@ -16,7 +16,7 @@ public String toString() { '}'; } - public IdCard(final UUID id, final String name, final int epoch) { + public IdCard(final String name, final int epoch) { this.id = id; this.name = name; this.epoch = epoch; diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/MyEmployee.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/MyEmployee.java index e56e2fe3b..ff261d540 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/MyEmployee.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/MyEmployee.java @@ -1,22 +1,28 @@ package com.google.common.truth.extension.generator.testModel; import java.time.ZonedDateTime; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; public class MyEmployee extends Person { - private UUID id; + private UUID id = UUID.randomUUID(); - private ZonedDateTime anniversary; + private ZonedDateTime anniversary = ZonedDateTime.now(); private MyEmployee boss; private IdCard card; - private List projectList; + private List projectList = new ArrayList<>(); + + State employed = State.NEVER_EMPLOYED; + + public MyEmployee(final String name) { + super(name); + } - State employed; enum State { EMPLOLYED, PREVIOUSLY_EMPLOYED, NEVER_EMPLOYED; @@ -27,15 +33,7 @@ enum State { */ @Override public String getName() { - return super.getName() + " ID: " +this.getId(); - } - - public char getId() { - return id; - } - - public void setId(final char id) { - this.id = id; + return super.getName() + " ID: " + this.getId(); } public MyEmployee getBoss() { @@ -62,10 +60,6 @@ public void setSlipUpList(final List slipUpList) { this.projectList = slipUpList; } - public MyEmployee(final String name) { - super(name); - } - public List getProjectList() { return this.projectList; } @@ -101,4 +95,22 @@ public Optional getWeighting() { return this.weighting; } + public UUID getId() { + return id; + } + + public void setId(final UUID id) { + this.id = id; + } + + @Override + public String toString() { + return "MyEmployee{" + +// "id=" + id + + ", name=" + getName() + + ", card=" + card + + ", employed=" + employed + + '}'; + } + } diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/Person.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/Person.java index 02206e131..7aaf45d59 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/Person.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/Person.java @@ -6,7 +6,8 @@ public class Person { private long birthSeconds; private String name; private int birthYear; - private ZonedDateTime brithday; + + private ZonedDateTime birthday; public long getBirthSeconds() { return this.birthSeconds; @@ -36,4 +37,12 @@ public Person(final String name) { this.name = name; } + public ZonedDateTime getBirthday() { + return this.birthday; + } + + public void setBirthday(final ZonedDateTime birthday) { + this.birthday = birthday; + } + } From ee0b39665206529973963e913e2faadd016c5c59 Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Mon, 2 Aug 2021 10:23:48 +0100 Subject: [PATCH 13/32] squash-up - switching machines --- .../internal/SubjectMethodGenerator.java | 117 ++++++++---------- .../generator/TruthGeneratorTest.java | 3 +- 2 files changed, 53 insertions(+), 67 deletions(-) diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java index dafe7be76..3a44938c8 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java @@ -4,9 +4,9 @@ import com.google.common.flogger.FluentLogger; import com.google.common.truth.ObjectArraySubject; import com.google.common.truth.Subject; -import com.google.common.truth.extension.generator.internal.SkeletonGenerator; import com.google.common.truth.extension.generator.internal.model.ThreeSystem; import org.apache.commons.lang3.ClassUtils; +import org.apache.commons.lang3.StringUtils; import org.jboss.forge.roaster.model.source.Import; import org.jboss.forge.roaster.model.source.JavaClassSource; import org.jboss.forge.roaster.model.source.MethodSource; @@ -15,6 +15,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.math.BigDecimal; import java.util.*; import java.util.stream.Collectors; @@ -53,7 +54,7 @@ public void addTests(JavaClassSource parent, Class classUnderTest) { // for (Method method : getters) { - addPrimitiveTest(method, parent, classUnderTest); + addFieldAccessors(method, parent, classUnderTest); } } @@ -64,7 +65,7 @@ private Collection getMethods(final Class classUnderTest) { Set issers = ReflectionUtils.getAllMethods(classUnderTest, withModifier(Modifier.PUBLIC), withPrefix("is"), withParametersCount(0)); - getters.addAll(issers);; + getters.addAll(issers); return removeOverridden(getters); } @@ -104,14 +105,20 @@ private String getSignature(final Method getter) { List.class, Iterable.class, Number.class, + Throwable.class, + BigDecimal.class, String.class, Comparable.class, Class.class // Enum#getDeclaringClass - ); + ); - private void addPrimitiveTest(Method method, JavaClassSource generated, Class classUnderTest) { + private void addFieldAccessors(Method method, JavaClassSource generated, Class classUnderTest) { Class returnType = method.getReturnType(); + if(returnType.isAssignableFrom(Boolean.class)){ + addBooleanMethod(); + } + boolean isCoveredByNonPrimitiveStandardSubjects = isTypeCoveredUnderStandardSubjects(returnType); Optional subjectForType = getSubjectForType(returnType); @@ -128,51 +135,28 @@ private void addPrimitiveTest(Method method, JavaClassSource generated, Class ClassOrGenerated subjectClass = subjectForType.get(); // todo add versions with and with the get - //String prefix = (returnType.getSimpleName().contains("boolean")) ? "" : ""; MethodSource has = generated.addMethod() -// .setName(prefix + method.getName()) - .setName(method.getName()) + .setName(createNameForSubjectMethod(method)) .setPublic(); StringBuilder body = new StringBuilder("isNotNull();\n"); - String check = "return check(\"" + method.getName() + "\")"; + String check = format("return check(\"%s\")", createNameForSubjectMethod(method)); body.append(check); - boolean notPrimitive = !returnType.isPrimitive(); boolean needsAboutCall = notPrimitive && !isCoveredByNonPrimitiveStandardSubjects; + if (needsAboutCall || subjectClass.isGenerated()) { - // need to get the Subject instance using about - // return check("hasCommittedToPartition(%s)", tp).about(commitHistories()).that(commitHistory); String aboutName; -// Set factoryPotentials = getMethods(subjectClass, x -> -// !x.getName().startsWith("assert") // the factory method won't be the assert methods -// && !x.getName().startsWith("lambda") // the factory method won't be the assert methods -// ); -// if (factoryPotentials.isEmpty()) { aboutName = Utils.getFactoryName(returnType); // take a guess -// } else { -// Method method = factoryPotentials.stream().findFirst().get(); -// aboutName = method.getName(); -// } body.append(format(".about(%s())", aboutName)); // import String factoryContainer = subjectClass.getFactoryContainerName(); -// Optional factoryContainerOld = this.compiledSubjects.values().parallelStream() -// .filter(classes -> Arrays.stream(classes.getMethods()) -// .anyMatch(methods -> methods.getName().equals(aboutName))) -// .findFirst(); -// if (factoryContainer.isPresent()) { -// Class container = factoryContainer.get(); - Import anImport = generated.addImport(factoryContainer); - String name = factoryContainer + "." + aboutName; - anImport.setName(name) // todo better way to do static method import? - .setStatic(true); -// } else -// System.err.println(format("Can't find container for method %s", aboutName)); - - + Import anImport = generated.addImport(factoryContainer); + String name = factoryContainer + "." + aboutName; + anImport.setName(name) // todo better way to do static method import? + .setStatic(true); } // String methodPrefix = (returnType.getSimpleName().contains("boolean")) ? "is" : "get"; @@ -186,6 +170,12 @@ private void addPrimitiveTest(Method method, JavaClassSource generated, Class generated.addImport(subjectClass.getSubjectQualifiedName()); } + private String createNameForSubjectMethod(final Method method) { + String name = method.getName(); + name = StringUtils.removeStart("get", name); + return "has" + name; + } + private boolean isTypeCoveredUnderStandardSubjects(final Class returnType) { // todo should only do this, if we can't find a more specific subect for the returnType // todo should check if class is assignable from the super subjects, instead of checking names @@ -195,7 +185,7 @@ private boolean isTypeCoveredUnderStandardSubjects(final Class returnType) { // todo this is of course too aggressive // boolean isCoveredByNonPrimitiveStandardSubjects = specials.contains(returnType.getSimpleName()); - boolean isCoveredByNonPrimitiveStandardSubjects = nativeTypes.stream().anyMatch(x->x.isAssignableFrom(returnType)); + boolean isCoveredByNonPrimitiveStandardSubjects = nativeTypes.stream().anyMatch(x -> x.isAssignableFrom(returnType)); // todo is it an array of objects? boolean array = returnType.isArray(); @@ -223,9 +213,9 @@ private Optional getSubjectForType(final Class type) { } // fall back to native subjects - if(subject.isEmpty()){ + if (subject.isEmpty()) { subject = ClassOrGenerated.ofClass(getNativeSubjectForType(type)); - if(subject.isPresent()) + if (subject.isPresent()) logger.at(INFO).log("Falling back to native interface subject %s for type %s", subject.get().clazz, type); } @@ -234,10 +224,10 @@ private Optional getSubjectForType(final Class type) { private Optional> getNativeSubjectForType(final Class type) { Optional> first = nativeTypes.stream().filter(x -> x.isAssignableFrom(type)).findFirst(); - if(first.isPresent()){ - Class aClass = first.get(); - Class compiledSubjectForTypeName = getCompiledSubjectForTypeName(aClass.getSimpleName()); - return ofNullable(compiledSubjectForTypeName); + if (first.isPresent()) { + Class aClass = first.get(); + Class compiledSubjectForTypeName = getCompiledSubjectForTypeName(aClass.getSimpleName()); + return ofNullable(compiledSubjectForTypeName); } return empty(); } @@ -247,26 +237,23 @@ private Optional getSubjectFromString(final String name) { return Optional.of(new ClassOrGenerated(ObjectArraySubject.class, null)); Optional subjectFromGenerated = getSubjectFromGenerated(name);// takes precedence - if(subjectFromGenerated.isPresent()) { - return Optional.of(new ClassOrGenerated(null, subjectFromGenerated.get())); -// String returnName = subjectFromGenerated.get().middle.factoryMethod.getReturnType().getName(); -// Class aClass = Class.forName(returnName); -// return Optional.of(aClass); - } + if (subjectFromGenerated.isPresent()) { + return Optional.of(new ClassOrGenerated(null, subjectFromGenerated.get())); + } - Class aClass = getCompiledSubjectForTypeName(name); - if (aClass != null) + Class aClass = getCompiledSubjectForTypeName(name); + if (aClass != null) return Optional.of(new ClassOrGenerated(aClass, null)); return empty(); } - private Class getCompiledSubjectForTypeName(final String name) { - Class aClass = this.compiledSubjects.get(name + "Subject"); - return aClass; - } + private Class getCompiledSubjectForTypeName(final String name) { + Class aClass = this.compiledSubjects.get(name + "Subject"); + return aClass; + } - private Optional getSubjectFromGenerated(final String name) { + private Optional getSubjectFromGenerated(final String name) { return Optional.ofNullable(this.generatedSubjects.get(name)); } @@ -282,8 +269,8 @@ public void addTests(final Set allTypes) { } static class ClassOrGenerated { - final Class clazz; - final ThreeSystem generated; + final Class clazz; + final ThreeSystem generated; ClassOrGenerated(final Class clazz, final ThreeSystem generated) { this.clazz = clazz; @@ -310,16 +297,16 @@ String getSubjectQualifiedName() { return clazz.getCanonicalName(); } - @Override - public String toString() { - return "ClassOrGenerated{" + - "clazz=" + clazz + - ", generated=" + generated + - '}'; - } + @Override + public String toString() { + return "ClassOrGenerated{" + + "clazz=" + clazz + + ", generated=" + generated + + '}'; + } public boolean isGenerated() { - return generated!=null; + return generated != null; } public String getFactoryContainerName() { diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java index b8c079677..6450b5809 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java @@ -70,8 +70,7 @@ public void generate_code() { ManagedTruth.assertThat(employee).getCard().getEpoch().isAtLeast(0); // ManagedTruth.assertThat(employee).getBoss().getName().isEqualTo("Tony"); - ManagedTruth.assertThat(employee).getBirthday().getYear().isLessThan(1920); - +// ManagedTruth.assertThat(employee).getBirthday().getYear().isLessThan(1920); } @Test From b600a34c111f60a095794d028ce36bad886ae833 Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Mon, 2 Aug 2021 12:42:16 +0100 Subject: [PATCH 14/32] Switch to Lombok for now (AutoValue later?), boolean strategy --- extensions/generator/pom.xml | 16 ++- .../{internal/model => }/SourceClassSets.java | 44 ++---- .../generator/TruthGeneratorAPI.java | 2 - .../internal/SubjectMethodGenerator.java | 129 ++++++++++++++++-- .../generator/internal/TruthGenerator.java | 5 +- .../generator/internal/model/AClass.java | 9 +- .../internal/model/ManagedClassSet.java | 11 +- .../generator/internal/model/MiddleClass.java | 2 + .../generator/internal/model/ParentClass.java | 2 + .../generator/internal/model/ThreeSystem.java | 17 +-- .../extension/generator/TestModelUtils.java | 15 +- .../generator/TruthGeneratorTest.java | 3 +- .../extension/generator/testModel/IdCard.java | 48 +------ .../generator/testModel/MyEmployee.java | 118 ++++++---------- .../extension/generator/testModel/Person.java | 53 ++----- .../generator/testModel/Project.java | 28 +--- 16 files changed, 242 insertions(+), 260 deletions(-) rename extensions/generator/src/main/java/com/google/common/truth/extension/generator/{internal/model => }/SourceClassSets.java (59%) diff --git a/extensions/generator/pom.xml b/extensions/generator/pom.xml index 283204d8e..dbe87ef44 100644 --- a/extensions/generator/pom.xml +++ b/extensions/generator/pom.xml @@ -42,7 +42,8 @@ org.checkerframework - checker-qual + checker-compat-qual + 2.5.5 org.atteo @@ -68,7 +69,7 @@ com.google.guava guava - 30.1.1-jre + 30.1.1-android com.google.flogger @@ -85,6 +86,17 @@ flogger-system-backend 0.6 + + org.projectlombok + lombok + 1.18.20 + + + uk.co.jemos.podam + podam + 7.2.7.RELEASE + compile + diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/SourceClassSets.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/SourceClassSets.java similarity index 59% rename from extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/SourceClassSets.java rename to extensions/generator/src/main/java/com/google/common/truth/extension/generator/SourceClassSets.java index 1f57882c5..19c9cf776 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/SourceClassSets.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/SourceClassSets.java @@ -1,9 +1,16 @@ -package com.google.common.truth.extension.generator.internal.model; +package com.google.common.truth.extension.generator; + +import lombok.Getter; +import lombok.Value; import java.util.HashSet; import java.util.List; import java.util.Set; +/** + * Use this class to prepare the set of source classes to generate for, and settings for different types of sources. + */ +@Getter public class SourceClassSets { private final String packageForOverall; @@ -38,39 +45,10 @@ public void generateFrom(Set> classes) { this.simpleClasses = classes; } + @Value public class PackageAndClasses { - final String targetPackageName; - - final Class[] classes; - - private PackageAndClasses(final String targetPackageName, final Class[] classes) { - this.targetPackageName = targetPackageName; - this.classes = classes; - } - - public String getTargetPackageName() { - return targetPackageName; - } - - public Class[] getClasses() { - return classes; - } - } - - public Set[]> getSimplePackageOfClasses() { - return simplePackageOfClasses; - } - - public Set getPackageAndClasses() { - return packageAndClasses; - } - - public String getPackageForOverall() { - return packageForOverall; - } - - public Set> getSimpleClasses() { - return this.simpleClasses; + String targetPackageName; + Class[] classes; } } diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGeneratorAPI.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGeneratorAPI.java index 7581c70e5..503bb722e 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGeneratorAPI.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGeneratorAPI.java @@ -1,10 +1,8 @@ package com.google.common.truth.extension.generator; import com.google.common.truth.extension.generator.internal.TruthGenerator; -import com.google.common.truth.extension.generator.internal.model.SourceClassSets; import com.google.common.truth.extension.generator.internal.model.ThreeSystem; -import java.util.List; import java.util.Set; /** diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java index 3a44938c8..e4644ab3a 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java @@ -2,6 +2,7 @@ import com.google.common.collect.Sets; import com.google.common.flogger.FluentLogger; +import com.google.common.truth.Fact; import com.google.common.truth.ObjectArraySubject; import com.google.common.truth.Subject; import com.google.common.truth.extension.generator.internal.model.ThreeSystem; @@ -14,16 +15,18 @@ import org.reflections.Reflections; import java.lang.reflect.Method; -import java.lang.reflect.Modifier; import java.math.BigDecimal; import java.util.*; import java.util.stream.Collectors; import static java.lang.String.format; +import static java.lang.reflect.Modifier.PRIVATE; import static java.util.Optional.empty; import static java.util.Optional.ofNullable; +import static java.util.function.Predicate.not; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; +import static org.apache.commons.lang3.StringUtils.removeStart; import static org.reflections.ReflectionUtils.*; /** @@ -60,10 +63,10 @@ public void addTests(JavaClassSource parent, Class classUnderTest) { private Collection getMethods(final Class classUnderTest) { Set getters = ReflectionUtils.getAllMethods(classUnderTest, - withModifier(Modifier.PUBLIC), withPrefix("get"), withParametersCount(0)); + not(withModifier(PRIVATE)), withPrefix("get"), withParametersCount(0)); Set issers = ReflectionUtils.getAllMethods(classUnderTest, - withModifier(Modifier.PUBLIC), withPrefix("is"), withParametersCount(0)); + not(withModifier(PRIVATE)), withPrefix("is"), withParametersCount(0)); getters.addAll(issers); @@ -113,12 +116,105 @@ private String getSignature(final Method getter) { ); private void addFieldAccessors(Method method, JavaClassSource generated, Class classUnderTest) { - Class returnType = method.getReturnType(); + Class returnType = getWrappedReturnType(method); - if(returnType.isAssignableFrom(Boolean.class)){ - addBooleanMethod(); + if(Boolean.class.isAssignableFrom(returnType)){ + addBooleanStrategy(method, generated, classUnderTest); } + if(Collection.class.isAssignableFrom(returnType)){ + addHasElementStrategy(method, generated, classUnderTest); + } + + if(Optional.class.isAssignableFrom(returnType)){ + addOptionalStrategy(method, generated, classUnderTest); + } + + if(Map.class.isAssignableFrom(returnType)){ + addMapStrategy(method, generated, classUnderTest); + } + + if(Enum.class.isAssignableFrom(returnType)){ + addEnumStrategy(method, generated, classUnderTest); + } + + addEqualityStrategy(method, generated, classUnderTest); + + addChainStrategy(method, generated, returnType); + } + + private Class getWrappedReturnType(Method method) { + Class wrapped = ClassUtils.primitiveToWrapper(method.getReturnType()); + return wrapped; + } + + private void addEqualityStrategy(Method method, JavaClassSource generated, Class classUnderTest) { + + } + + private void addEnumStrategy(Method method, JavaClassSource generated, Class classUnderTest) { + + } + + private void addMapStrategy(Method method, JavaClassSource generated, Class classUnderTest) { + } + + private void addOptionalStrategy(Method method, JavaClassSource generated, Class classUnderTest) { + } + + private void addHasElementStrategy(Method method, JavaClassSource generated, Class classUnderTest) { + + } + + /** + * public void isCeo() { + * if (!actual.isCeo()) { + * failWithActual(simpleFact("expected to be CEO")); + * } + * } + */ + private void addBooleanStrategy(Method method, JavaClassSource generated, Class classUnderTest) { + addPositiveBoolean(method, generated); + addNegativeBoolean(method, generated); + + generated.addImport(Fact.class) + .setStatic(true) + .setName(Fact.class.getCanonicalName() + ".simpleFact"); + } + + private void addPositiveBoolean(Method method, JavaClassSource generated) { + String body = ""+ + " if (actual.%s()) {\n" + + " failWithActual(simpleFact(\"expected NOT to be %s\"));\n" + + " }\n"; + String noun = StringUtils.remove(method.getName(), "is"); + body = format(body, method.getName(), noun); + + String negativeMethodName = removeStart(method.getName(), "is"); + negativeMethodName = "isNot" + negativeMethodName; + generated.addMethod() + .setName(negativeMethodName) + .setReturnTypeVoid() + .setBody(body) + .setPublic(); + } + + private void addNegativeBoolean(Method method, JavaClassSource generated) { + String body = ""+ + " if (!actual.%s()) {\n" + + " failWithActual(simpleFact(\"expected to be %s\"));\n" + + " }\n"; + String noun = StringUtils.remove(method.getName(), "is"); + body = format(body, method.getName(), noun); + + generated.addMethod() + .setName(method.getName()) + .setReturnTypeVoid() + .setBody(body) + .setPublic(); + } + + private void addChainStrategy(Method method, JavaClassSource generated, Class returnType) { boolean isCoveredByNonPrimitiveStandardSubjects = isTypeCoveredUnderStandardSubjects(returnType); Optional subjectForType = getSubjectForType(returnType); @@ -135,12 +231,13 @@ private void addFieldAccessors(Method method, JavaClassSource generated, Class has = generated.addMethod() - .setName(createNameForSubjectMethod(method)) + .setName(nameForChainMethod) .setPublic(); StringBuilder body = new StringBuilder("isNotNull();\n"); - String check = format("return check(\"%s\")", createNameForSubjectMethod(method)); + String check = format("return check(\"%s\")", method.getName()); body.append(check); boolean notPrimitive = !returnType.isPrimitive(); @@ -170,10 +267,18 @@ private void addFieldAccessors(Method method, JavaClassSource generated, Class returnType) { @@ -198,6 +303,8 @@ private boolean isTypeCoveredUnderStandardSubjects(final Class returnType) { private Optional getSubjectForType(final Class type) { String name; + + // if primitive, wrap and get wrapped Subject if (type.isPrimitive()) { Class wrapped = ClassUtils.primitiveToWrapper(type); name = wrapped.getSimpleName(); diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java index a23bdbfa7..f3c328527 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java @@ -1,11 +1,10 @@ package com.google.common.truth.extension.generator.internal; -import com.google.common.collect.Sets; import com.google.common.collect.Sets.SetView; import com.google.common.truth.Subject; import com.google.common.truth.extension.generator.TruthGeneratorAPI; -import com.google.common.truth.extension.generator.internal.model.SourceClassSets; -import com.google.common.truth.extension.generator.internal.model.SourceClassSets.PackageAndClasses; +import com.google.common.truth.extension.generator.SourceClassSets; +import com.google.common.truth.extension.generator.SourceClassSets.PackageAndClasses; import com.google.common.truth.extension.generator.internal.model.ThreeSystem; import org.reflections.Reflections; import org.reflections.scanners.SubTypesScanner; diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/AClass.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/AClass.java index 62f4a124f..92a53b124 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/AClass.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/AClass.java @@ -1,11 +1,12 @@ package com.google.common.truth.extension.generator.internal.model; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Value; import org.jboss.forge.roaster.model.source.JavaClassSource; +@Getter +@RequiredArgsConstructor public class AClass { public final JavaClassSource generated; - - AClass(final JavaClassSource generated) { - this.generated = generated; - } } diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ManagedClassSet.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ManagedClassSet.java index 90a7437c6..a5ae5bb22 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ManagedClassSet.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ManagedClassSet.java @@ -1,13 +1,10 @@ package com.google.common.truth.extension.generator.internal.model; +import lombok.Value; import org.jboss.forge.roaster.model.source.JavaClassSource; +@Value public class ManagedClassSet { - final Class sourceClass; - final JavaClassSource generatedClass; - - public ManagedClassSet(final Class sourceClass, final JavaClassSource generatedClass) { - this.sourceClass = sourceClass; - this.generatedClass = generatedClass; - } + Class sourceClass; + JavaClassSource generatedClass; } diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/MiddleClass.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/MiddleClass.java index 955eb7464..556d20abe 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/MiddleClass.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/MiddleClass.java @@ -1,8 +1,10 @@ package com.google.common.truth.extension.generator.internal.model; +import lombok.Value; import org.jboss.forge.roaster.model.source.JavaClassSource; import org.jboss.forge.roaster.model.source.MethodSource; +@Value public class MiddleClass extends AClass { public final MethodSource factoryMethod; public final Class usersMiddleClass; diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ParentClass.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ParentClass.java index c11bc9177..868298b5c 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ParentClass.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ParentClass.java @@ -1,7 +1,9 @@ package com.google.common.truth.extension.generator.internal.model; +import lombok.Value; import org.jboss.forge.roaster.model.source.JavaClassSource; +@Value public class ParentClass extends AClass { public ParentClass(JavaClassSource generated) { super(generated); diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ThreeSystem.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ThreeSystem.java index a0b6ad665..a4e54b49f 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ThreeSystem.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ThreeSystem.java @@ -1,7 +1,9 @@ package com.google.common.truth.extension.generator.internal.model; +import lombok.Value; import org.jboss.forge.roaster.model.source.JavaClassSource; +@Value public class ThreeSystem { @Override public String toString() { @@ -9,15 +11,8 @@ public String toString() { "classUnderTest=" + classUnderTest + '}'; } - public final Class classUnderTest; - public final ParentClass parent; - public final MiddleClass middle; - public final JavaClassSource child; - - public ThreeSystem(final Class classUnderTest, final ParentClass parent, final MiddleClass middle, final JavaClassSource child) { - this.classUnderTest = classUnderTest; - this.parent = parent; - this.middle = middle; - this.child = child; - } + public Class classUnderTest; + public ParentClass parent; + public MiddleClass middle; + public JavaClassSource child; } diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TestModelUtils.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TestModelUtils.java index 384804da8..3617cc8eb 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TestModelUtils.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TestModelUtils.java @@ -2,17 +2,22 @@ import com.google.common.truth.extension.generator.testModel.IdCard; import com.google.common.truth.extension.generator.testModel.MyEmployee; +import uk.co.jemos.podam.api.PodamFactory; +import uk.co.jemos.podam.api.PodamFactoryImpl; import java.time.ZonedDateTime; public class TestModelUtils { static MyEmployee createEmployee() { - MyEmployee employee = new MyEmployee("Zeynep"); - employee.setBirthday(ZonedDateTime.now().withYear(1983)); - employee.setBoss(new MyEmployee("Lilan")); - employee.setCard(createCard()); - return employee; + PodamFactory factory = new PodamFactoryImpl(); + MyEmployee.MyEmployeeBuilder employee = factory.manufacturePojo(MyEmployee.class).toBuilder(); + employee.anniversary(ZonedDateTime.now().withYear(1983)); + MyEmployee boss = factory.manufacturePojo(MyEmployee.class).toBuilder().name("Lilan").build(); + employee = employee + .boss(boss) + .card(createCard()); + return employee.build(); } private static IdCard createCard() { diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java index 6450b5809..4eb7fa177 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java @@ -2,7 +2,6 @@ import com.google.common.io.Resources; import com.google.common.truth.extension.Employee; -import com.google.common.truth.extension.generator.internal.model.SourceClassSets; import com.google.common.truth.extension.generator.internal.model.ThreeSystem; import com.google.common.truth.extension.generator.testModel.*; import org.junit.Test; @@ -67,7 +66,7 @@ public void generate_code() { assertThat(generatedSourceClasses).containsAtLeast(UUID.class, ZonedDateTime.class); MyEmployee employee = TestModelUtils.createEmployee(); - ManagedTruth.assertThat(employee).getCard().getEpoch().isAtLeast(0); +// ManagedTruth.assertThat(employee).getCard().getEpoch().isAtLeast(0); // ManagedTruth.assertThat(employee).getBoss().getName().isEqualTo("Tony"); // ManagedTruth.assertThat(employee).getBirthday().getYear().isLessThan(1920); diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/IdCard.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/IdCard.java index 62a7a231b..7fd0e4050 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/IdCard.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/IdCard.java @@ -1,48 +1,12 @@ package com.google.common.truth.extension.generator.testModel; +import lombok.Value; + import java.util.UUID; +@Value public class IdCard { - private UUID id = UUID.randomUUID(); - private String name; - private int epoch; - - @Override - public String toString() { - return "IdCard{" + - "id=" + id + - ", name='" + name + '\'' + - ", epoch=" + epoch + - '}'; - } - - public IdCard(final String name, final int epoch) { - this.id = id; - this.name = name; - this.epoch = epoch; - } - - public UUID getId() { - return id; - } - - public void setId(final UUID id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(final String name) { - this.name = name; - } - - public int getEpoch() { - return epoch; - } - - public void setEpoch(final int epoch) { - this.epoch = epoch; - } + UUID id = UUID.randomUUID(); + String name; + int epoch; } diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/MyEmployee.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/MyEmployee.java index ff261d540..30898615c 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/MyEmployee.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/MyEmployee.java @@ -1,107 +1,73 @@ package com.google.common.truth.extension.generator.testModel; +import lombok.*; +import lombok.experimental.SuperBuilder; + +import javax.annotation.Nonnull; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; +import static com.google.common.truth.extension.generator.testModel.MyEmployee.State.EMPLOLYED; + +//@AllArgsConstructor +@SuperBuilder(toBuilder = true) +//@Data +@Getter +@Setter(AccessLevel.PRIVATE) +//@With public class MyEmployee extends Person { - private UUID id = UUID.randomUUID(); - private ZonedDateTime anniversary = ZonedDateTime.now(); + UUID id = UUID.randomUUID(); - private MyEmployee boss; +// @With + ZonedDateTime anniversary = ZonedDateTime.now(); - private IdCard card; +// @With + MyEmployee boss = null; - private List projectList = new ArrayList<>(); + IdCard card = null; - State employed = State.NEVER_EMPLOYED; + List projectList = new ArrayList<>(); - public MyEmployee(final String name) { - super(name); - } + State employmentState = State.NEVER_EMPLOYED; + Optional weighting = Optional.empty(); + + public MyEmployee(@Nonnull String name, long someLongAspect, @Nonnull ZonedDateTime birthday) { + super(name, someLongAspect, birthday); + } enum State { EMPLOLYED, PREVIOUSLY_EMPLOYED, NEVER_EMPLOYED; } /** - * Overriding support in Subject + * Package private test + * @return */ - @Override - public String getName() { - return super.getName() + " ID: " + this.getId(); - } - - public MyEmployee getBoss() { - return boss; - } - - public void setBoss(final MyEmployee boss) { - this.boss = boss; - } - - public IdCard getCard() { - return card; - } - - public void setCard(final IdCard card) { - this.card = card; - } - - public List getSlipUpList() { - return projectList; - } - - public void setSlipUpList(final List slipUpList) { - this.projectList = slipUpList; - } - - public List getProjectList() { - return this.projectList; + boolean isEmployed(){ + return this.employmentState == EMPLOLYED; } - public void setProjectList(final List projectList) { - this.projectList = projectList; - } - - public void setWeighting(final Optional weighting) { - this.weighting = weighting; - } - - private Optional weighting; - - public ZonedDateTime getAnniversary() { - return this.anniversary; - } - - public void setAnniversary(final ZonedDateTime anniversary) { - this.anniversary = anniversary; - } - - - public State getEmployed() { - return this.employed; - } - - public void setEmployed(final State employed) { - this.employed = employed; - } - - public Optional getWeighting() { - return this.weighting; + /** + * Primitive vs Wrapper test + * @return + */ + Boolean isEmployedWrapped(){ + return this.employmentState == EMPLOLYED; } - public UUID getId() { - return id; + /** + * Overriding support in Subject + */ + @Override + public String getName() { + return super.getName() + " ID: " + this.getId(); } - public void setId(final UUID id) { - this.id = id; - } @Override public String toString() { @@ -109,7 +75,7 @@ public String toString() { // "id=" + id + ", name=" + getName() + ", card=" + card + - ", employed=" + employed + + ", employed=" + employmentState + '}'; } diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/Person.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/Person.java index 7aaf45d59..29d64d8d2 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/Person.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/Person.java @@ -1,48 +1,23 @@ package com.google.common.truth.extension.generator.testModel; +import lombok.*; +import lombok.experimental.SuperBuilder; + import java.time.ZonedDateTime; +@Getter +//@With +@SuperBuilder(toBuilder = true) +//@AllArgsConstructor +//@NoArgsConstructor +@RequiredArgsConstructor public class Person { - private long birthSeconds; - private String name; - private int birthYear; - - private ZonedDateTime birthday; - - public long getBirthSeconds() { - return this.birthSeconds; - } - - public void setBirthSeconds(final long birthSeconds) { - this.birthSeconds = birthSeconds; - } - - public String getName() { - return this.name; - } - - public void setName(final String name) { - this.name = name; - } - - public int getBirthYear() { - return this.birthYear; - } - - public void setBirthYear(final int birthYear) { - this.birthYear = birthYear; - } - - public Person(final String name) { - this.name = name; - } - - public ZonedDateTime getBirthday() { - return this.birthday; - } + protected final String name; + protected final long someLongAspect; + protected final ZonedDateTime birthday; - public void setBirthday(final ZonedDateTime birthday) { - this.birthday = birthday; + public int getBirthYear(){ + return birthday.getYear(); } } diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/Project.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/Project.java index 21d8119f5..c25e47158 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/Project.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/Project.java @@ -1,29 +1,11 @@ package com.google.common.truth.extension.generator.testModel; +import lombok.Value; + import java.time.ZonedDateTime; +@Value public class Project { - private String desc; - private ZonedDateTime start; - - public Project(final String desc, final ZonedDateTime start) { - this.desc = desc; - this.start = start; - } - - public String getDesc() { - return desc; - } - - public void setDesc(final String desc) { - this.desc = desc; - } - - public ZonedDateTime getStart() { - return start; - } - - public void setStart(final ZonedDateTime start) { - this.start = start; - } + String desc; + ZonedDateTime start; } From b11c935977d3f17429637785e436ec6193928427 Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Mon, 2 Aug 2021 16:38:15 +0100 Subject: [PATCH 15/32] Fix child extend middle, add auto shading, fix static chains, update source tests --- .../extension/generator/SourceClassSets.java | 34 +- .../generator/TruthGeneratorAPI.java | 16 +- .../generator/internal/MyStringSubject.java | 60 +++ .../generator/internal/OverallEntryPoint.java | 68 +-- .../generator/internal/SkeletonGenerator.java | 21 +- .../internal/SubjectMethodGenerator.java | 35 +- .../generator/internal/TruthGenerator.java | 24 +- .../generator/ManagedTruthChicken.java | 464 ++++++++++++++++++ .../generator/TruthGeneratorTest.java | 66 ++- .../internal/model/ThreeSystemSubject.java | 42 ++ .../model/source/JavaClassSourceSubject.java | 37 ++ .../generator/testModel/MyEmployee.java | 6 +- .../expected-EmployeeSubject.java.txt | 38 -- .../expected/MyEmployeeChildSubject.java.txt | 50 ++ .../expected/MyEmployeeParentSubject.java.txt | 129 +++++ .../expected/MyEmployeeSubject.java.txt | 32 ++ 16 files changed, 984 insertions(+), 138 deletions(-) create mode 100644 extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/MyStringSubject.java create mode 100644 extensions/generator/src/test/java/com/google/common/truth/extension/generator/ManagedTruthChicken.java create mode 100644 extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/model/ThreeSystemSubject.java create mode 100644 extensions/generator/src/test/java/com/google/common/truth/extension/generator/shaded/org/jboss/forge/roaster/model/source/JavaClassSourceSubject.java delete mode 100644 extensions/generator/src/test/resources/expected-EmployeeSubject.java.txt create mode 100644 extensions/generator/src/test/resources/expected/MyEmployeeChildSubject.java.txt create mode 100644 extensions/generator/src/test/resources/expected/MyEmployeeParentSubject.java.txt create mode 100644 extensions/generator/src/test/resources/expected/MyEmployeeSubject.java.txt diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/SourceClassSets.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/SourceClassSets.java index 19c9cf776..9a9d7c64f 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/SourceClassSets.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/SourceClassSets.java @@ -1,11 +1,14 @@ package com.google.common.truth.extension.generator; +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.Multimaps; import lombok.Getter; import lombok.Value; +import java.util.Arrays; import java.util.HashSet; -import java.util.List; import java.util.Set; +import java.util.stream.Collectors; /** * Use this class to prepare the set of source classes to generate for, and settings for different types of sources. @@ -14,9 +17,9 @@ public class SourceClassSets { private final String packageForOverall; - private Set[]> simplePackageOfClasses = new HashSet<>(); + private final Set[]> simplePackageOfClasses = new HashSet<>(); private Set> simpleClasses = new HashSet<>(); - private Set packageAndClasses = new HashSet<>(); + private final Set packageAndClasses = new HashSet<>(); /** * @param packageForOverall the package to put the overall access points @@ -32,10 +35,7 @@ public void generateAllFoundInPackagesOf(Class... classes) { /** * Useful for generating Java module Subjects and put them in our package. *

- * I.e. for UUID.class you can't create a Subject in the same package as it (not allowed) - * - * @param targetPackageName - * @param classes + * I.e. for UUID.class you can't create a Subject in the same package as it (not allowed). */ public void generateFrom(String targetPackageName, Class... classes) { packageAndClasses.add(new PackageAndClasses(targetPackageName, classes)); @@ -45,8 +45,26 @@ public void generateFrom(Set> classes) { this.simpleClasses = classes; } + /** + * Shades the given source classes into the base package, suffixed with the source package + */ + public void generateFromShaded(Class... classes) { + ImmutableListMultimap> grouped = Multimaps.index(Arrays.asList(classes), Class::getPackage); + + grouped.keySet().forEach(x -> { + Class[] classSet = grouped.get(x).toArray(new Class[0]); + PackageAndClasses newSet = new PackageAndClasses(getTargetPackageName(x), + classSet); + packageAndClasses.add(newSet); + }); + } + + private String getTargetPackageName(Package p) { + return this.packageForOverall + ".shaded." + p.getName(); + } + @Value - public class PackageAndClasses { + public static class PackageAndClasses { String targetPackageName; Class[] classes; } diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGeneratorAPI.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGeneratorAPI.java index 503bb722e..c502adf9c 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGeneratorAPI.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGeneratorAPI.java @@ -3,6 +3,7 @@ import com.google.common.truth.extension.generator.internal.TruthGenerator; import com.google.common.truth.extension.generator.internal.model.ThreeSystem; +import java.util.Map; import java.util.Set; /** @@ -48,16 +49,21 @@ static TruthGeneratorAPI create() { void combinedSystem(SourceClassSets ss); /** - * @param ss - * @return + * Use this entry point to generate for a large and differing set of source classes - which will also generate a + * single point of entry for all of them. + * + *

+ * There are many different ways to add, check out the different methods in {@link SourceClassSets}. + * + * @see SourceClassSets */ - Set generate(SourceClassSets ss); + Map, ThreeSystem> generate(SourceClassSets ss); /** - * * @param classes * @return */ - Set generate(Set> classes); + Map, ThreeSystem> generate(Set> classes); + void generate(Class... classes); } diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/MyStringSubject.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/MyStringSubject.java new file mode 100644 index 000000000..1cd5dff4b --- /dev/null +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/MyStringSubject.java @@ -0,0 +1,60 @@ +package com.google.common.truth.extension.generator.internal; + +import com.google.common.truth.FailureMetadata; +import com.google.common.truth.StringSubject; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.jboss.forge.roaster.model.source.JavaClassSource; + +public class MyStringSubject extends StringSubject { + + String actual; + + protected MyStringSubject(FailureMetadata failureMetadata, String actual) { + super(failureMetadata, actual); + this.actual = actual; + } + + /** + * Returns an assertion builder for a {@link JavaClassSource} class. + */ + public static Factory myStrings() { + return MyStringSubject::new; + } + + public IgnoringWhiteSpaceComparison ignoringWhiteSpace() { + return new IgnoringWhiteSpaceComparison(); + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public class IgnoringWhiteSpaceComparison { + + public void equalTo(String expected) { + String expectedNormal = normalise(expected); + String actualNormal = normalise(actual); + + check("").that(actualNormal).isEqualTo(expectedNormal); + } + + private String normalise(String raw) { + String normal = normaliseEndingsEndings(raw); + normal = normaliseWhiteSpaceAtEndings(normal); + return normal; + } + + /** + * lazy remove trailing whitespace on lines + */ + private String normaliseWhiteSpaceAtEndings(String raw) { + return raw.replaceAll("(?m)\\s+$", ""); + } + + /** + * make line endings consistent + */ + private String normaliseEndingsEndings(String raw) { + return raw.replaceAll("\\r\\n?", "\n"); + } + } + +} diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/OverallEntryPoint.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/OverallEntryPoint.java index 1599536e7..dd9d59af2 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/OverallEntryPoint.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/OverallEntryPoint.java @@ -7,7 +7,6 @@ import org.jboss.forge.roaster.model.source.JavaClassSource; import org.jboss.forge.roaster.model.source.MethodSource; -import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.List; @@ -15,39 +14,40 @@ public class OverallEntryPoint { - private final List children = new ArrayList<>(); - - /** - * Having collected together all the access points, creates one large class filled with access points to all of them. - *

- * The overall access will throw an error if any middle classes don't correctly extend their parent. - */ - public void createOverallAccessPoints(String packageName) { - JavaClassSource overallAccess = Roaster.create(JavaClassSource.class); - overallAccess.setName("ManagedTruth"); - overallAccess.getJavaDoc() - .setText("Single point of access for all managed Subjects."); - overallAccess.setPublic() - .setPackage(packageName); - - // brute force - for (JavaClassSource j : children) { - List> methods = j.getMethods(); - for (Method m : methods) { - overallAccess.addMethod(m); - } - // this seems like overkill, but at least in the child style case, there's very few imports - even - // none extra at all (aside from wild card vs specific methods). - List imports = j.getImports(); - for (Import i : imports) { - overallAccess.addImport(i); - } - } - - writeToDisk(overallAccess); + private final List children = new ArrayList<>(); + + /** + * Having collected together all the access points, creates one large class filled with access points to all of them. + *

+ * The overall access will throw an error if any middle classes don't correctly extend their parent. + */ + public void createOverallAccessPoints(String packageName) { + JavaClassSource overallAccess = Roaster.create(JavaClassSource.class); + overallAccess.setName("ManagedTruth"); + overallAccess.getJavaDoc() + .setText("Single point of access for all managed Subjects."); + overallAccess.setPublic() + .setPackage(packageName); + + // brute force + for (JavaClassSource child : children) { + List> methods = child.getMethods(); + for (Method m : methods) { + if (!m.isConstructor()) + overallAccess.addMethod(m); + } + // this seems like overkill, but at least in the child style case, there's very few imports - even + // none extra at all (aside from wild card vs specific methods). + List imports = child.getImports(); + for (Import i : imports) { + overallAccess.addImport(i); + } } - public void add(ThreeSystem ts) { - this.children.add(ts.child); - } + writeToDisk(overallAccess); + } + + public void add(ThreeSystem ts) { + this.children.add(ts.child); + } } diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java index 407e8da5a..adc37eba3 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java @@ -27,6 +27,8 @@ public class SkeletonGenerator implements SkeletonGeneratorAPI { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); private final Optional targetPackageName; + private MiddleClass middle; + private ParentClass parent; public SkeletonGenerator(final Optional targetPackageName) { this.targetPackageName = targetPackageName; @@ -61,9 +63,11 @@ public Optional threeLayerSystem(Class source) { return empty(); ParentClass parent = createParent(source); + this.parent = parent; // todo try to see if class already exists first, user may already have a written one and not know MiddleClass middle = createMiddlePlaceHolder(parent.generated, source); + this.middle = middle; JavaClassSource child = createChild(parent, middle.generated.getQualifiedName(), source, middle.factoryMethod.getName()); @@ -109,13 +113,13 @@ private ParentClass createParent(Class source) { return new ParentClass(parent); } - private void addPackageSuperAndAnnotation(final JavaClassSource javaClass, final Class source) { + private void addPackageSuperAndAnnotation(final JavaClassSource javaClass, final Class source) { addPackageSuperAndAnnotation(javaClass, source.getPackage().getName(), getSubjectName(source.getSimpleName())); } - private JavaClassSource createChild(ParentClass parent, + private JavaClassSource createChild(ParentClass parent, String usersMiddleClassName, - Class source, + Class source, String factoryMethodName) { // todo if middle doesn't extend parent, warn @@ -129,6 +133,13 @@ private JavaClassSource createChild(ParentClass parent, javaDoc.addTagValue("@see", usersMiddleClassName); javaDoc.addTagValue("@see", parent.generated.getName()); + child.extendSuperType(this.middle.generated); + + MethodSource constructor = addConstructor(source, child, false); + constructor.getJavaDoc().setText("This constructor should not be used, instead see the parent's.") + .addTagValue("@see", usersMiddleClassName); + constructor.setPrivate(); + addAccessPoints(source, child, factoryMethodName, usersMiddleClassName); addGeneratedMarker(child); @@ -298,7 +309,7 @@ private boolean containsMethodCalled(JavaClassSource javaClass, String factoryNa return javaClass.getMethods().stream().anyMatch(x -> x.getName().equals(factoryName)); } - private void addConstructor(Class source, JavaClassSource javaClass, boolean setActual) { + private MethodSource addConstructor(Class source, JavaClassSource javaClass, boolean setActual) { if (!javaClass.getMethods().stream().anyMatch(x -> x.isConstructor())) { // constructor MethodSource constructor = javaClass.addMethod() @@ -310,7 +321,9 @@ private void addConstructor(Class source, JavaClassSource javaClass, bool if (setActual) sb.append("this.actual = actual;"); constructor.setBody(sb.toString()); + return constructor; } + return null; } private void addActualField(Class source, JavaClassSource javaClass) { diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java index e4644ab3a..334f24902 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java @@ -21,6 +21,7 @@ import static java.lang.String.format; import static java.lang.reflect.Modifier.PRIVATE; +import static java.lang.reflect.Modifier.STATIC; import static java.util.Optional.empty; import static java.util.Optional.ofNullable; import static java.util.function.Predicate.not; @@ -118,23 +119,23 @@ private String getSignature(final Method getter) { private void addFieldAccessors(Method method, JavaClassSource generated, Class classUnderTest) { Class returnType = getWrappedReturnType(method); - if(Boolean.class.isAssignableFrom(returnType)){ + if (Boolean.class.isAssignableFrom(returnType)) { addBooleanStrategy(method, generated, classUnderTest); } - if(Collection.class.isAssignableFrom(returnType)){ + if (Collection.class.isAssignableFrom(returnType)) { addHasElementStrategy(method, generated, classUnderTest); } - if(Optional.class.isAssignableFrom(returnType)){ + if (Optional.class.isAssignableFrom(returnType)) { addOptionalStrategy(method, generated, classUnderTest); } - if(Map.class.isAssignableFrom(returnType)){ + if (Map.class.isAssignableFrom(returnType)) { addMapStrategy(method, generated, classUnderTest); } - if(Enum.class.isAssignableFrom(returnType)){ + if (Enum.class.isAssignableFrom(returnType)) { addEnumStrategy(method, generated, classUnderTest); } @@ -167,11 +168,7 @@ private void addHasElementStrategy(Method method, JavaClassSource generated, Cla } /** - * public void isCeo() { - * if (!actual.isCeo()) { - * failWithActual(simpleFact("expected to be CEO")); - * } - * } + * public void isCeo() { if (!actual.isCeo()) { failWithActual(simpleFact("expected to be CEO")); } } */ private void addBooleanStrategy(Method method, JavaClassSource generated, Class classUnderTest) { addPositiveBoolean(method, generated); @@ -183,7 +180,7 @@ private void addBooleanStrategy(Method method, JavaClassSource generated, Class< } private void addPositiveBoolean(Method method, JavaClassSource generated) { - String body = ""+ + String body = "" + " if (actual.%s()) {\n" + " failWithActual(simpleFact(\"expected NOT to be %s\"));\n" + " }\n"; @@ -200,7 +197,7 @@ private void addPositiveBoolean(Method method, JavaClassSource generated) { } private void addNegativeBoolean(Method method, JavaClassSource generated) { - String body = ""+ + String body = "" + " if (!actual.%s()) {\n" + " failWithActual(simpleFact(\"expected to be %s\"));\n" + " }\n"; @@ -256,9 +253,11 @@ private void addChainStrategy(Method method, JavaClassSource generated, Class .setStatic(true); } -// String methodPrefix = (returnType.getSimpleName().contains("boolean")) ? "is" : "get"; -// body.append(".that(actual." + methodPrefix + capitalize(method.getName()) + "());"); - body.append(format(".that(actual.%s());", method.getName())); + if (methodIsStatic(method)) { + body.append(format(".that(%s.%s());", method.getDeclaringClass().getSimpleName(), method.getName())); + } else { + body.append(format(".that(actual.%s());", method.getName())); + } has.setBody(body.toString()); @@ -267,6 +266,10 @@ private void addChainStrategy(Method method, JavaClassSource generated, Class generated.addImport(subjectClass.getSubjectQualifiedName()); } + private boolean methodIsStatic(Method method) { + return withModifier(STATIC).test(method); + } + /** * Attempt to swap get for has, but only if it starts with get - otherwise leave it alone. */ @@ -375,7 +378,7 @@ public void addTests(final Set allTypes) { } } - static class ClassOrGenerated { + public static class ClassOrGenerated { final Class clazz; final ThreeSystem generated; diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java index f3c328527..10ff2ad9a 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java @@ -1,15 +1,17 @@ package com.google.common.truth.extension.generator.internal; import com.google.common.collect.Sets.SetView; +import com.google.common.flogger.FluentLogger; import com.google.common.truth.Subject; -import com.google.common.truth.extension.generator.TruthGeneratorAPI; import com.google.common.truth.extension.generator.SourceClassSets; import com.google.common.truth.extension.generator.SourceClassSets.PackageAndClasses; +import com.google.common.truth.extension.generator.TruthGeneratorAPI; import com.google.common.truth.extension.generator.internal.model.ThreeSystem; import org.reflections.Reflections; import org.reflections.scanners.SubTypesScanner; import java.util.*; +import java.util.logging.Level; import java.util.stream.Collectors; import static com.google.common.collect.Sets.union; @@ -19,6 +21,8 @@ */ public class TruthGenerator implements TruthGeneratorAPI { + private static final FluentLogger log = FluentLogger.forEnclosingClass(); + @Override public void generate(String... modelPackages) { Utils.requireNotEmpty(modelPackages); @@ -41,10 +45,14 @@ private Set generateSkeletonsFromPackages(final String[] modelPacka private Set generateSkeletons(Set> classes, Optional targetPackageName, OverallEntryPoint overallEntryPoint) { - SkeletonGenerator skeletonGenerator = new SkeletonGenerator(targetPackageName); + int sizeBeforeFilter = classes.size(); + // filter existing subjects from inbound set + classes = classes.stream().filter(x -> !Subject.class.isAssignableFrom(x)).collect(Collectors.toSet()); + log.at(Level.WARNING).log("Removed %s Subjects from inbound", classes.size() - sizeBeforeFilter); Set subjectsSystems = new HashSet<>(); for (Class c : classes) { + SkeletonGenerator skeletonGenerator = new SkeletonGenerator(targetPackageName); Optional threeSystem = skeletonGenerator.threeLayerSystem(c); if (threeSystem.isPresent()) { ThreeSystem ts = threeSystem.get(); @@ -93,7 +101,7 @@ private String[] getPackageStrings(final Class[] classes) { } @Override - public Set generate(SourceClassSets ss) { + public Map, ThreeSystem> generate(SourceClassSets ss) { Set packages = ss.getSimplePackageOfClasses().stream().map( this::getPackageStrings ).collect(Collectors.toSet()); @@ -117,7 +125,6 @@ public Set generate(SourceClassSets ss) { // straight up classes Set simpleClasses = generateSkeletons(ss.getSimpleClasses(), Optional.empty(), overallEntryPoint); - // SetView union = union(union(skeletons, setStream), simpleClasses); addTests(union); @@ -125,17 +132,22 @@ public Set generate(SourceClassSets ss) { // create overall entry point overallEntryPoint.createOverallAccessPoints(ss.getPackageForOverall()); - return union; + return union.stream().collect(Collectors.toMap(ThreeSystem::getClassUnderTest, x -> x)); } @Override - public Set generate(Set> classes) { + public Map, ThreeSystem> generate(Set> classes) { Utils.requireNotEmpty(classes); SourceClassSets ss = new SourceClassSets(classes.stream().findFirst().get().getPackageName()); ss.generateFrom(classes); return generate(ss); } + @Override + public void generate(Class... classes) { + generate(Arrays.stream(classes).collect(Collectors.toSet())); + } + @Override public String maintain(final Class source, final Class userAndGeneratedMix) { throw new IllegalStateException("Not implemented yet"); diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/ManagedTruthChicken.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/ManagedTruthChicken.java new file mode 100644 index 000000000..e7c1d4865 --- /dev/null +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/ManagedTruthChicken.java @@ -0,0 +1,464 @@ +package com.google.common.truth.extension.generator; + +import com.google.common.truth.Truth; +import com.google.common.truth.extension.generator.SourceClassSets.PackageAndClasses; +import com.google.common.truth.extension.generator.internal.*; +import com.google.common.truth.extension.generator.internal.SubjectMethodGenerator.ClassOrGenerated; +import com.google.common.truth.extension.generator.internal.model.*; +import com.google.common.truth.extension.generator.shaded.org.jboss.forge.roaster.model.source.JavaClassSourceSubject; +import com.google.common.truth.extension.generator.testModel.*; +import com.google.common.truth.extension.generator.testModel.MyEmployee.State; +import org.jboss.forge.roaster.model.source.JavaClassSource; + +import static com.google.common.truth.extension.generator.PackageAndClassesSubject.packageAndClasseses; +import static com.google.common.truth.extension.generator.SourceClassSetsSubject.sourceClassSetses; +import static com.google.common.truth.extension.generator.TestModelUtilsSubject.testModelUtilses; +import static com.google.common.truth.extension.generator.TruthGeneratorAPISubject.truthGeneratorAPIs; +import static com.google.common.truth.extension.generator.internal.ClassOrGeneratedSubject.classOrGenerateds; +import static com.google.common.truth.extension.generator.internal.OverallEntryPointSubject.overallEntryPoints; +import static com.google.common.truth.extension.generator.internal.SkeletonGeneratorAPISubject.skeletonGeneratorAPIs; +import static com.google.common.truth.extension.generator.internal.SkeletonGeneratorSubject.skeletonGenerators; +import static com.google.common.truth.extension.generator.internal.SourceCheckingSubject.sourceCheckings; +import static com.google.common.truth.extension.generator.internal.SubjectMethodGeneratorSubject.subjectMethodGenerators; +import static com.google.common.truth.extension.generator.internal.TruthGeneratorSubject.truthGenerators; +import static com.google.common.truth.extension.generator.internal.UtilsSubject.utilses; +import static com.google.common.truth.extension.generator.internal.model.AClassSubject.aClasses; +import static com.google.common.truth.extension.generator.internal.model.ManagedClassSetSubject.managedClassSets; +import static com.google.common.truth.extension.generator.internal.model.MiddleClassSubject.middleClasses; +import static com.google.common.truth.extension.generator.internal.model.ParentClassSubject.parentClasses; +import static com.google.common.truth.extension.generator.internal.model.ThreeSystemSubject.threeSystems; +import static com.google.common.truth.extension.generator.shaded.org.jboss.forge.roaster.model.source.JavaClassSourceSubject.javaClassSources; +import static com.google.common.truth.extension.generator.testModel.IdCardSubject.idCards; +import static com.google.common.truth.extension.generator.testModel.MyEmployeeSubject.myEmployees; +import static com.google.common.truth.extension.generator.testModel.PersonSubject.persons; +import static com.google.common.truth.extension.generator.testModel.ProjectSubject.projects; +import static com.google.common.truth.extension.generator.testModel.StateSubject.states; + +/** + * Single point of access for all managed Subjects. + */ +public class ManagedTruthChicken { + + /** + * Entry point for {@link SourceClassSets} assertions. + */ + public static SourceClassSetsSubject assertThat( + com.google.common.truth.extension.generator.SourceClassSets actual) { + return Truth.assertAbout(sourceClassSetses()).that(actual); + } + + /** + * Convenience entry point for {@link SourceClassSets} assertions when being + * mixed with other "assertThat" assertion libraries. + * + * @see #assertThat + */ + public static SourceClassSetsSubject assertTruth( + com.google.common.truth.extension.generator.SourceClassSets actual) { + return assertThat(actual); + } + + /** + * Entry point for {@link ManagedClassSet} assertions. + */ + public static ManagedClassSetSubject assertThat( + com.google.common.truth.extension.generator.internal.model.ManagedClassSet actual) { + return Truth.assertAbout(managedClassSets()).that(actual); + } + + /** + * Convenience entry point for {@link ManagedClassSet} assertions when being + * mixed with other "assertThat" assertion libraries. + * + * @see #assertThat + */ + public static ManagedClassSetSubject assertTruth( + com.google.common.truth.extension.generator.internal.model.ManagedClassSet actual) { + return assertThat(actual); + } + + /** + * Entry point for {@link SkeletonGenerator} assertions. + */ + public static SkeletonGeneratorSubject assertThat( + com.google.common.truth.extension.generator.internal.SkeletonGenerator actual) { + return Truth.assertAbout(skeletonGenerators()).that(actual); + } + + /** + * Convenience entry point for {@link SkeletonGenerator} assertions when being + * mixed with other "assertThat" assertion libraries. + * + * @see #assertThat + */ + public static SkeletonGeneratorSubject assertTruth( + com.google.common.truth.extension.generator.internal.SkeletonGenerator actual) { + return assertThat(actual); + } + + /** + * Entry point for {@link SubjectMethodGenerator} assertions. + */ + public static SubjectMethodGeneratorSubject assertThat( + com.google.common.truth.extension.generator.internal.SubjectMethodGenerator actual) { + return Truth.assertAbout(subjectMethodGenerators()).that(actual); + } + + /** + * Convenience entry point for {@link SubjectMethodGenerator} assertions when + * being mixed with other "assertThat" assertion libraries. + * + * @see #assertThat + */ + public static SubjectMethodGeneratorSubject assertTruth( + com.google.common.truth.extension.generator.internal.SubjectMethodGenerator actual) { + return assertThat(actual); + } + + /** + * Entry point for {@link ClassOrGenerated} assertions. + */ + public static ClassOrGeneratedSubject assertThat( + com.google.common.truth.extension.generator.internal.SubjectMethodGenerator.ClassOrGenerated actual) { + return Truth.assertAbout(classOrGenerateds()).that(actual); + } + + /** + * Convenience entry point for {@link ClassOrGenerated} assertions when being + * mixed with other "assertThat" assertion libraries. + * + * @see #assertThat + */ + public static ClassOrGeneratedSubject assertTruth( + com.google.common.truth.extension.generator.internal.SubjectMethodGenerator.ClassOrGenerated actual) { + return assertThat(actual); + } + + /** + * Entry point for {@link PackageAndClasses} assertions. + */ + public static PackageAndClassesSubject assertThat( + com.google.common.truth.extension.generator.SourceClassSets.PackageAndClasses actual) { + return Truth.assertAbout(packageAndClasseses()).that(actual); + } + + /** + * Convenience entry point for {@link PackageAndClasses} assertions when being + * mixed with other "assertThat" assertion libraries. + * + * @see #assertThat + */ + public static PackageAndClassesSubject assertTruth( + com.google.common.truth.extension.generator.SourceClassSets.PackageAndClasses actual) { + return assertThat(actual); + } + + /** + * Entry point for {@link ThreeSystem} assertions. + */ + public static ThreeSystemSubject assertThat( + com.google.common.truth.extension.generator.internal.model.ThreeSystem actual) { + return Truth.assertAbout(threeSystems()).that(actual); + } + + /** + * Convenience entry point for {@link ThreeSystem} assertions when being mixed + * with other "assertThat" assertion libraries. + * + * @see #assertThat + */ + public static ThreeSystemSubject assertTruth( + com.google.common.truth.extension.generator.internal.model.ThreeSystem actual) { + return assertThat(actual); + } + + /** + * Entry point for {@link IdCard} assertions. + */ + public static IdCardSubject assertThat(com.google.common.truth.extension.generator.testModel.IdCard actual) { + return Truth.assertAbout(idCards()).that(actual); + } + + /** + * Convenience entry point for {@link IdCard} assertions when being mixed with + * other "assertThat" assertion libraries. + * + * @see #assertThat + */ + public static IdCardSubject assertTruth(com.google.common.truth.extension.generator.testModel.IdCard actual) { + return assertThat(actual); + } + + /** + * Entry point for {@link State} assertions. + */ + public static StateSubject assertThat( + com.google.common.truth.extension.generator.testModel.MyEmployee.State actual) { + return Truth.assertAbout(states()).that(actual); + } + + /** + * Convenience entry point for {@link State} assertions when being mixed with + * other "assertThat" assertion libraries. + * + * @see #assertThat + */ + public static StateSubject assertTruth( + com.google.common.truth.extension.generator.testModel.MyEmployee.State actual) { + return assertThat(actual); + } + + /** + * Entry point for {@link SkeletonGeneratorAPI} assertions. + */ + public static SkeletonGeneratorAPISubject assertThat( + com.google.common.truth.extension.generator.internal.SkeletonGeneratorAPI actual) { + return Truth.assertAbout(skeletonGeneratorAPIs()).that(actual); + } + + /** + * Convenience entry point for {@link SkeletonGeneratorAPI} assertions when + * being mixed with other "assertThat" assertion libraries. + * + * @see #assertThat + */ + public static SkeletonGeneratorAPISubject assertTruth( + com.google.common.truth.extension.generator.internal.SkeletonGeneratorAPI actual) { + return assertThat(actual); + } + + /** + * Entry point for {@link Person} assertions. + */ + public static PersonSubject assertThat(com.google.common.truth.extension.generator.testModel.Person actual) { + return Truth.assertAbout(persons()).that(actual); + } + + /** + * Convenience entry point for {@link Person} assertions when being mixed with + * other "assertThat" assertion libraries. + * + * @see #assertThat + */ + public static PersonSubject assertTruth(com.google.common.truth.extension.generator.testModel.Person actual) { + return assertThat(actual); + } + + /** + * Entry point for {@link MiddleClass} assertions. + */ + public static MiddleClassSubject assertThat( + com.google.common.truth.extension.generator.internal.model.MiddleClass actual) { + return Truth.assertAbout(middleClasses()).that(actual); + } + + /** + * Convenience entry point for {@link MiddleClass} assertions when being mixed + * with other "assertThat" assertion libraries. + * + * @see #assertThat + */ + public static MiddleClassSubject assertTruth( + com.google.common.truth.extension.generator.internal.model.MiddleClass actual) { + return assertThat(actual); + } + + /** + * Entry point for {@link Utils} assertions. + */ + public static UtilsSubject assertThat(com.google.common.truth.extension.generator.internal.Utils actual) { + return Truth.assertAbout(utilses()).that(actual); + } + + /** + * Convenience entry point for {@link Utils} assertions when being mixed with + * other "assertThat" assertion libraries. + * + * @see #assertThat + */ + public static UtilsSubject assertTruth(com.google.common.truth.extension.generator.internal.Utils actual) { + return assertThat(actual); + } + + /** + * Entry point for {@link SourceChecking} assertions. + */ + public static SourceCheckingSubject assertThat( + com.google.common.truth.extension.generator.internal.SourceChecking actual) { + return Truth.assertAbout(sourceCheckings()).that(actual); + } + + /** + * Convenience entry point for {@link SourceChecking} assertions when being + * mixed with other "assertThat" assertion libraries. + * + * @see #assertThat + */ + public static SourceCheckingSubject assertTruth( + com.google.common.truth.extension.generator.internal.SourceChecking actual) { + return assertThat(actual); + } + + /** + * Entry point for {@link AClass} assertions. + */ + public static AClassSubject assertThat(com.google.common.truth.extension.generator.internal.model.AClass actual) { + return Truth.assertAbout(aClasses()).that(actual); + } + + /** + * Convenience entry point for {@link AClass} assertions when being mixed with + * other "assertThat" assertion libraries. + * + * @see #assertThat + */ + public static AClassSubject assertTruth(com.google.common.truth.extension.generator.internal.model.AClass actual) { + return assertThat(actual); + } + + /** + * Entry point for {@link MyEmployee} assertions. + */ + public static MyEmployeeSubject assertThat( + com.google.common.truth.extension.generator.testModel.MyEmployee actual) { + return Truth.assertAbout(myEmployees()).that(actual); + } + + /** + * Convenience entry point for {@link MyEmployee} assertions when being mixed + * with other "assertThat" assertion libraries. + * + * @see #assertThat + */ + public static MyEmployeeSubject assertTruth( + com.google.common.truth.extension.generator.testModel.MyEmployee actual) { + return assertThat(actual); + } + + /** + * Entry point for {@link Project} assertions. + */ + public static ProjectSubject assertThat(com.google.common.truth.extension.generator.testModel.Project actual) { + return Truth.assertAbout(projects()).that(actual); + } + + /** + * Convenience entry point for {@link Project} assertions when being mixed with + * other "assertThat" assertion libraries. + * + * @see #assertThat + */ + public static ProjectSubject assertTruth(com.google.common.truth.extension.generator.testModel.Project actual) { + return assertThat(actual); + } + + /** + * Entry point for {@link TestModelUtils} assertions. + */ + public static TestModelUtilsSubject assertThat(com.google.common.truth.extension.generator.TestModelUtils actual) { + return Truth.assertAbout(testModelUtilses()).that(actual); + } + + /** + * Convenience entry point for {@link TestModelUtils} assertions when being + * mixed with other "assertThat" assertion libraries. + * + * @see #assertThat + */ + public static TestModelUtilsSubject assertTruth(com.google.common.truth.extension.generator.TestModelUtils actual) { + return assertThat(actual); + } + + /** + * Entry point for {@link TruthGenerator} assertions. + */ + public static TruthGeneratorSubject assertThat( + com.google.common.truth.extension.generator.internal.TruthGenerator actual) { + return Truth.assertAbout(truthGenerators()).that(actual); + } + + /** + * Convenience entry point for {@link TruthGenerator} assertions when being + * mixed with other "assertThat" assertion libraries. + * + * @see #assertThat + */ + public static TruthGeneratorSubject assertTruth( + com.google.common.truth.extension.generator.internal.TruthGenerator actual) { + return assertThat(actual); + } + + /** + * Entry point for {@link ParentClass} assertions. + */ + public static ParentClassSubject assertThat( + com.google.common.truth.extension.generator.internal.model.ParentClass actual) { + return Truth.assertAbout(parentClasses()).that(actual); + } + + /** + * Convenience entry point for {@link ParentClass} assertions when being mixed + * with other "assertThat" assertion libraries. + * + * @see #assertThat + */ + public static ParentClassSubject assertTruth( + com.google.common.truth.extension.generator.internal.model.ParentClass actual) { + return assertThat(actual); + } + + /** + * Entry point for {@link TruthGeneratorAPI} assertions. + */ + public static TruthGeneratorAPISubject assertThat( + com.google.common.truth.extension.generator.TruthGeneratorAPI actual) { + return Truth.assertAbout(truthGeneratorAPIs()).that(actual); + } + + /** + * Convenience entry point for {@link TruthGeneratorAPI} assertions when being + * mixed with other "assertThat" assertion libraries. + * + * @see #assertThat + */ + public static TruthGeneratorAPISubject assertTruth( + com.google.common.truth.extension.generator.TruthGeneratorAPI actual) { + return assertThat(actual); + } + + /** + * Entry point for {@link OverallEntryPoint} assertions. + */ + public static OverallEntryPointSubject assertThat( + com.google.common.truth.extension.generator.internal.OverallEntryPoint actual) { + return Truth.assertAbout(overallEntryPoints()).that(actual); + } + + /** + * Convenience entry point for {@link OverallEntryPoint} assertions when being + * mixed with other "assertThat" assertion libraries. + * + * @see #assertThat + */ + public static OverallEntryPointSubject assertTruth( + com.google.common.truth.extension.generator.internal.OverallEntryPoint actual) { + return assertThat(actual); + } + + /** + * Entry point for {@link JavaClassSource} assertions. + */ + public static JavaClassSourceSubject assertThat(org.jboss.forge.roaster.model.source.JavaClassSource actual) { + return Truth.assertAbout(javaClassSources()).that(actual); + } + + /** + * Convenience entry point for {@link JavaClassSource} assertions when being + * mixed with other "assertThat" assertion libraries. + * + * @see #assertThat + */ + public static JavaClassSourceSubject assertTruth(org.jboss.forge.roaster.model.source.JavaClassSource actual) { + return assertThat(actual); + } +} diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java index 4eb7fa177..3e7f492a7 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java @@ -1,16 +1,18 @@ package com.google.common.truth.extension.generator; import com.google.common.io.Resources; -import com.google.common.truth.extension.Employee; import com.google.common.truth.extension.generator.internal.model.ThreeSystem; import com.google.common.truth.extension.generator.testModel.*; +import org.jboss.forge.roaster.model.source.JavaClassSource; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.IOException; import java.nio.charset.Charset; +import java.time.ZoneId; import java.time.ZonedDateTime; +import java.time.chrono.Chronology; import java.util.*; import java.util.stream.Collectors; @@ -19,15 +21,8 @@ @RunWith(JUnit4.class) public class TruthGeneratorTest { - @Test - public void poc() throws IOException { - TruthGeneratorAPI truthGeneratorAPI = TruthGeneratorAPI.create(); - String generated = truthGeneratorAPI.combinedSystem(Employee.class); - - String expectedFileName = "expected-EmployeeSubject.java.txt"; - String expecting = Resources.toString(Resources.getResource(expectedFileName), Charset.defaultCharset()); - - assertThat(trim(generated)).isEqualTo(trim(expecting)); + private String loadFileToString(String expectedFileName) throws IOException { + return Resources.toString(Resources.getResource(expectedFileName), Charset.defaultCharset()); } private String trim(String in) { @@ -43,7 +38,7 @@ public void combined() { } @Test - public void generate_code() { + public void generate_code() throws IOException { // todo need to be able to set base package for all generated classes, kind of like shade, so you cah generate test for classes in other restricted modules TruthGeneratorAPI truthGenerator = TruthGeneratorAPI.create(); @@ -54,22 +49,47 @@ public void generate_code() { String basePackageName = getClass().getPackage().getName(); SourceClassSets ss = new SourceClassSets(basePackageName); - ss.generateFrom(classes); + ss.generateAllFoundInPackagesOf(MyEmployee.class); // package exists in other module error - needs package target support - ss.generateFrom(basePackageName, ZonedDateTime.class, UUID.class); + ss.generateFrom(basePackageName, UUID.class); + ss.generateFromShaded(ZoneId.class, ZonedDateTime.class, Chronology.class); - Set generated = truthGenerator.generate(ss); + Map, ThreeSystem> generated = truthGenerator.generate(ss); assertThat(generated.size()).isAtLeast(classes.size()); - Set> generatedSourceClasses = generated.stream().map(x -> x.classUnderTest).collect(Collectors.toSet()); - assertThat(generatedSourceClasses).containsAtLeast(UUID.class, ZonedDateTime.class); + Set> generatedSourceClasses = generated.values().stream().map(x -> x.classUnderTest).collect(Collectors.toSet()); + assertThat(generatedSourceClasses).containsAtLeast(UUID.class, ZonedDateTime.class, MyEmployee.State.class); + + String expectedMyEmployeeParent = loadFileToString("expected/MyEmployeeParentSubject.java.txt"); + String expectedMyEmployeeChild = loadFileToString("expected/MyEmployeeChildSubject.java.txt"); + String expectedMyEmployeeMiddle = loadFileToString("expected/MyEmployeeSubject.java.txt"); + + + ThreeSystem threeSystemGenerated = generated.get(MyEmployee.class); + + ManagedTruthChicken.assertThat(threeSystemGenerated) + .hasParent().hasGenerated().hasSourceText() + .ignoringWhiteSpace().equalTo(expectedMyEmployeeParent); // sanity full chain - MyEmployee employee = TestModelUtils.createEmployee(); -// ManagedTruth.assertThat(employee).getCard().getEpoch().isAtLeast(0); -// ManagedTruth.assertThat(employee).getBoss().getName().isEqualTo("Tony"); + ManagedTruthChicken.assertThat(threeSystemGenerated).hasParentSource(expectedMyEmployeeParent); + + ManagedTruthChicken.assertThat(threeSystemGenerated).hasMiddleSource(expectedMyEmployeeMiddle); + + ManagedTruthChicken.assertThat(threeSystemGenerated).hasChildSource(expectedMyEmployeeChild); -// ManagedTruth.assertThat(employee).getBirthday().getYear().isLessThan(1920); + } + + /** + * Chicken, or the egg? + */ + @Test + public void boostrapProjectSubjects(){ + TruthGeneratorAPI tg = TruthGeneratorAPI.create(); + SourceClassSets ss = new SourceClassSets(getClass().getPackage().getName()); + ss.generateFromShaded(JavaClassSource.class); + ss.generateAllFoundInPackagesOf(getClass()); + tg.generate(ss); } @Test @@ -82,11 +102,11 @@ public void package_java_mix() { ss.generateAllFoundInPackagesOf(IdCard.class); // generate java Subjects and put them in our package - ss.generateFrom(targetPackageName, UUID.class, ZonedDateTime.class); + ss.generateFrom(targetPackageName, UUID.class); + ss.generateFromShaded(ZoneId.class, ZonedDateTime.class, Chronology.class); - Set generated = tg.generate(ss); + Map, ThreeSystem> generated = tg.generate(ss); assertThat(generated.size()).isAtLeast(ss.getPackageAndClasses().size()); - } @Test diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/model/ThreeSystemSubject.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/model/ThreeSystemSubject.java new file mode 100644 index 000000000..e35f44ded --- /dev/null +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/model/ThreeSystemSubject.java @@ -0,0 +1,42 @@ +package com.google.common.truth.extension.generator.internal.model; + +import com.google.common.truth.FailureMetadata; + +import javax.annotation.processing.Generated; + +/** + * Optionally move this class into source control, and add your custom assertions here. + * + *

+ * If the system detects this class already exists, it won't attempt to generate a new one. Note that if the base + * skeleton of this class ever changes, you won't automatically get it updated. + * + * @see ThreeSystemParentSubject + */ +@Generated("truth-generator") +public class ThreeSystemSubject extends ThreeSystemParentSubject { + + protected ThreeSystemSubject(FailureMetadata failureMetadata, + com.google.common.truth.extension.generator.internal.model.ThreeSystem actual) { + super(failureMetadata, actual); + } + + /** + * Returns an assertion builder for a {@link ThreeSystem} class. + */ + public static Factory threeSystems() { + return ThreeSystemSubject::new; + } + + public void hasParentSource(String expected) { + hasParent().hasGenerated().hasSourceText().ignoringWhiteSpace().equalTo(expected); + } + + public void hasMiddleSource(String expected) { + hasMiddle().hasGenerated().hasSourceText().ignoringWhiteSpace().equalTo(expected); + } + + public void hasChildSource(String expected) { + hasChild().hasSourceText().ignoringWhiteSpace().equalTo(expected); + } +} diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/shaded/org/jboss/forge/roaster/model/source/JavaClassSourceSubject.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/shaded/org/jboss/forge/roaster/model/source/JavaClassSourceSubject.java new file mode 100644 index 000000000..3ba0e515f --- /dev/null +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/shaded/org/jboss/forge/roaster/model/source/JavaClassSourceSubject.java @@ -0,0 +1,37 @@ +package com.google.common.truth.extension.generator.shaded.org.jboss.forge.roaster.model.source; + +import com.google.common.truth.extension.generator.internal.MyStringSubject; +import com.google.common.truth.FailureMetadata; +import org.jboss.forge.roaster.model.source.JavaClassSource; +import javax.annotation.processing.Generated; + +/** + * Optionally move this class into source control, and add your custom + * assertions here. + * + *

+ * If the system detects this class already exists, it won't attempt to generate + * a new one. Note that if the base skeleton of this class ever changes, you + * won't automatically get it updated. + * + * @see JavaClassSourceParentSubject + */ +@Generated("truth-generator") +public class JavaClassSourceSubject extends JavaClassSourceParentSubject { + + protected JavaClassSourceSubject(FailureMetadata failureMetadata, JavaClassSource actual) { + super(failureMetadata, actual); + } + + /** + * Returns an assertion builder for a {@link JavaClassSource} class. + */ + public static Factory javaClassSources() { + return JavaClassSourceSubject::new; + } + + public MyStringSubject hasSourceText() { + return check("toString").about(MyStringSubject.myStrings()).that(actual.toString()); + } + +} diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/MyEmployee.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/MyEmployee.java index 30898615c..6fcaeb1e1 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/MyEmployee.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/MyEmployee.java @@ -40,13 +40,12 @@ public MyEmployee(@Nonnull String name, long someLongAspect, @Nonnull ZonedDateT super(name, someLongAspect, birthday); } - enum State { + public enum State { EMPLOLYED, PREVIOUSLY_EMPLOYED, NEVER_EMPLOYED; } /** - * Package private test - * @return + * Package-private test */ boolean isEmployed(){ return this.employmentState == EMPLOLYED; @@ -54,7 +53,6 @@ boolean isEmployed(){ /** * Primitive vs Wrapper test - * @return */ Boolean isEmployedWrapped(){ return this.employmentState == EMPLOLYED; diff --git a/extensions/generator/src/test/resources/expected-EmployeeSubject.java.txt b/extensions/generator/src/test/resources/expected-EmployeeSubject.java.txt deleted file mode 100644 index d17a65c2b..000000000 --- a/extensions/generator/src/test/resources/expected-EmployeeSubject.java.txt +++ /dev/null @@ -1,38 +0,0 @@ -package com.google.common.truth.extension; - -import com.google.common.truth.Subject; -import com.google.common.truth.FailureMetadata; -import com.google.common.truth.Truth; - -/** - * Truth Subject for the {@link Employee} - extend this class, with your own - * custom assertions. - * - * Note that the generated class will change over time, so your edits will be - * overwritten. Or, you can copy the generated code into your project. - * - * @see Employee - */ -public class EmployeeSubject extends Subject { - - private final Employee actual; - - protected EmployeeSubject(FailureMetadata failureMetadata, com.google.common.truth.extension.Employee actual) { - super(failureMetadata, actual); - this.actual = actual; - } - - /** - * Returns an assertion builder for a {@link Employee} class. - */ - public static Factory employees() { - return EmployeeSubject::new; - } - - /** - * Entry point for {@link Employee} assertions. - */ - public static EmployeeSubject assertThat(com.google.common.truth.extension.Employee actual) { - return Truth.assertAbout(employees()).that(actual); - } -} diff --git a/extensions/generator/src/test/resources/expected/MyEmployeeChildSubject.java.txt b/extensions/generator/src/test/resources/expected/MyEmployeeChildSubject.java.txt new file mode 100644 index 000000000..12e95e96f --- /dev/null +++ b/extensions/generator/src/test/resources/expected/MyEmployeeChildSubject.java.txt @@ -0,0 +1,50 @@ +package com.google.common.truth.extension.generator.testModel; + +import com.google.common.truth.extension.generator.testModel.MyEmployeeSubject; +import com.google.common.truth.FailureMetadata; +import static com.google.common.truth.extension.generator.testModel.MyEmployeeSubject.*; +import com.google.common.truth.Truth; +import javax.annotation.processing.Generated; + +/** + * Entry point for assertions for @{MyEmployee}. Import the static accessor + * methods from this class and use them. Combines the generated code from + * {@MyEmployeeParentSubject}and the user code from + * {@com.google.common.truth.extension.generator.testModel.MyEmployeeSubject}. + * + * @see com.google.common.truth.extension.generator.testModel.MyEmployee + * @see com.google.common.truth.extension.generator.testModel.MyEmployeeSubject + * @see MyEmployeeParentSubject + */ +@Generated("truth-generator") +public class MyEmployeeChildSubject extends MyEmployeeSubject { + + /** + * This constructor should not be used, instead see the parent's. + * + * @see com.google.common.truth.extension.generator.testModel.MyEmployeeSubject + */ + private MyEmployeeChildSubject(FailureMetadata failureMetadata, + com.google.common.truth.extension.generator.testModel.MyEmployee actual) { + super(failureMetadata, actual); + } + + /** + * Entry point for {@link MyEmployee} assertions. + */ + public static MyEmployeeSubject assertThat( + com.google.common.truth.extension.generator.testModel.MyEmployee actual) { + return Truth.assertAbout(myEmployees()).that(actual); + } + + /** + * Convenience entry point for {@link MyEmployee} assertions when being mixed + * with other "assertThat" assertion libraries. + * + * @see #assertThat + */ + public static MyEmployeeSubject assertTruth( + com.google.common.truth.extension.generator.testModel.MyEmployee actual) { + return assertThat(actual); + } +} diff --git a/extensions/generator/src/test/resources/expected/MyEmployeeParentSubject.java.txt b/extensions/generator/src/test/resources/expected/MyEmployeeParentSubject.java.txt new file mode 100644 index 000000000..285369c1a --- /dev/null +++ b/extensions/generator/src/test/resources/expected/MyEmployeeParentSubject.java.txt @@ -0,0 +1,129 @@ +package com.google.common.truth.extension.generator.testModel; + +import com.google.common.truth.Subject; +import javax.annotation.processing.Generated; +import com.google.common.truth.FailureMetadata; +import com.google.common.truth.IntegerSubject; +import static com.google.common.truth.extension.generator.testModel.MyEmployeeSubject.myEmployees; +import com.google.common.truth.extension.generator.testModel.MyEmployeeSubject; +import static com.google.common.truth.extension.generator.testModel.StateSubject.states; +import com.google.common.truth.extension.generator.testModel.StateSubject; +import static com.google.common.truth.extension.generator.shaded.java.time.ZonedDateTimeSubject.zonedDateTimes; +import static com.google.common.truth.extension.generator.shaded.java.time.ZonedDateTimeSubject.zonedDateTimes; +import static com.google.common.truth.extension.generator.UUIDSubject.uUIDs; +import com.google.common.truth.extension.generator.UUIDSubject; +import com.google.common.truth.StringSubject; +import com.google.common.truth.LongSubject; +import com.google.common.truth.IterableSubject; +import static com.google.common.truth.Fact.simpleFact; +import com.google.common.truth.BooleanSubject; +import com.google.common.truth.extension.generator.shaded.java.time.ZonedDateTimeSubject; +import static com.google.common.truth.extension.generator.testModel.IdCardSubject.idCards; +import com.google.common.truth.extension.generator.testModel.IdCardSubject; +import static com.google.common.truth.Fact.simpleFact; + +/** + * Truth Subject for the {@link MyEmployee}. + * + * Note that this class is generated / managed, and will change over time. So + * any changes you might make will be overwritten. + * + * @see MyEmployee + * @see MyEmployeeSubject + * @see MyEmployeeChildSubject + */ +@Generated("truth-generator") +public class MyEmployeeParentSubject extends Subject { + + protected final MyEmployee actual; + + protected MyEmployeeParentSubject(FailureMetadata failureMetadata, + com.google.common.truth.extension.generator.testModel.MyEmployee actual) { + super(failureMetadata, actual); + this.actual = actual; + } + + public IntegerSubject hasBirthYear() { + isNotNull(); + return check("getBirthYear").that(actual.getBirthYear()); + } + + public MyEmployeeSubject hasBoss() { + isNotNull(); + return check("getBoss").about(myEmployees()).that(actual.getBoss()); + } + + public StateSubject hasEmploymentState() { + isNotNull(); + return check("getEmploymentState").about(states()).that(actual.getEmploymentState()); + } + + public ZonedDateTimeSubject hasAnniversary() { + isNotNull(); + return check("getAnniversary").about(zonedDateTimes()).that(actual.getAnniversary()); + } + + public UUIDSubject hasId() { + isNotNull(); + return check("getId").about(uUIDs()).that(actual.getId()); + } + + public StringSubject hasName() { + isNotNull(); + return check("getName").that(actual.getName()); + } + + public LongSubject hasSomeLongAspect() { + isNotNull(); + return check("getSomeLongAspect").that(actual.getSomeLongAspect()); + } + + public IterableSubject hasProjectList() { + isNotNull(); + return check("getProjectList").that(actual.getProjectList()); + } + + public void isNotEmployed() { + if (actual.isEmployed()) { + failWithActual(simpleFact("expected NOT to be Employed")); + } + } + + public void isEmployed() { + if (!actual.isEmployed()) { + failWithActual(simpleFact("expected to be Employed")); + } + } + + public BooleanSubject hasEmployed() { + isNotNull(); + return check("isEmployed").that(actual.isEmployed()); + } + + public ZonedDateTimeSubject hasBirthday() { + isNotNull(); + return check("getBirthday").about(zonedDateTimes()).that(actual.getBirthday()); + } + + public IdCardSubject hasCard() { + isNotNull(); + return check("getCard").about(idCards()).that(actual.getCard()); + } + + public void isNotEmployedWrapped() { + if (actual.isEmployedWrapped()) { + failWithActual(simpleFact("expected NOT to be EmployedWrapped")); + } + } + + public void isEmployedWrapped() { + if (!actual.isEmployedWrapped()) { + failWithActual(simpleFact("expected to be EmployedWrapped")); + } + } + + public BooleanSubject hasEmployedWrapped() { + isNotNull(); + return check("isEmployedWrapped").that(actual.isEmployedWrapped()); + } +} \ No newline at end of file diff --git a/extensions/generator/src/test/resources/expected/MyEmployeeSubject.java.txt b/extensions/generator/src/test/resources/expected/MyEmployeeSubject.java.txt new file mode 100644 index 000000000..157fb7bdb --- /dev/null +++ b/extensions/generator/src/test/resources/expected/MyEmployeeSubject.java.txt @@ -0,0 +1,32 @@ +package com.google.common.truth.extension.generator.testModel; + +import com.google.common.truth.extension.generator.testModel.MyEmployeeParentSubject; +import com.google.common.truth.FailureMetadata; +import javax.annotation.processing.Generated; + +/** + * Optionally move this class into source control, and add your custom + * assertions here. + * + *

+ * If the system detects this class already exists, it won't attempt to generate + * a new one. Note that if the base skeleton of this class ever changes, you + * won't automatically get it updated. + * + * @see MyEmployeeParentSubject + */ +@Generated("truth-generator") +public class MyEmployeeSubject extends MyEmployeeParentSubject { + + protected MyEmployeeSubject(FailureMetadata failureMetadata, + com.google.common.truth.extension.generator.testModel.MyEmployee actual) { + super(failureMetadata, actual); + } + + /** + * Returns an assertion builder for a {@link MyEmployee} class. + */ + public static Factory myEmployees() { + return MyEmployeeSubject::new; + } +} From 8ef4230dbe100cc76983e8bc0ccf9d5a6d008ec9 Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Mon, 2 Aug 2021 18:07:54 +0100 Subject: [PATCH 16/32] Don't make another middle if it already exists, move chickens into source control under new package --- .../generator/internal/MyStringSubject.java | 3 + .../generator/internal/SkeletonGenerator.java | 73 ++- .../internal/SubjectMethodGenerator.java | 10 +- .../generator/internal/TruthGenerator.java | 15 +- .../extension/generator/internal/Utils.java | 1 + .../generator/internal/model/AClass.java | 5 +- .../generator/internal/model/MiddleClass.java | 41 +- .../generator/ManagedTruthChicken.java | 464 ------------------ .../generator/TruthGeneratorTest.java | 10 +- .../MiddleClassParentSubject.java | 56 +++ .../MiddleClassSubject.java | 33 ++ .../ParentClassParentSubject.java | 39 ++ .../ParentClassSubject.java | 33 ++ .../ThreeSystemChildSubject.java | 49 ++ .../ThreeSystemParentSubject.java | 57 +++ .../ThreeSystemSubject.java | 5 +- .../JavaClassSourceParentSubject.java | 371 ++++++++++++++ .../JavaClassSourceSubject.java | 17 +- 18 files changed, 762 insertions(+), 520 deletions(-) delete mode 100644 extensions/generator/src/test/java/com/google/common/truth/extension/generator/ManagedTruthChicken.java create mode 100644 extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/MiddleClassParentSubject.java create mode 100644 extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/MiddleClassSubject.java create mode 100644 extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ParentClassParentSubject.java create mode 100644 extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ParentClassSubject.java create mode 100644 extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ThreeSystemChildSubject.java create mode 100644 extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ThreeSystemParentSubject.java rename extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/{model => modelSubjectChickens}/ThreeSystemSubject.java (85%) create mode 100644 extensions/generator/src/test/java/com/google/common/truth/extension/generator/shaded/org/jboss/forge/roaster/model/sourceChickens/JavaClassSourceParentSubject.java rename extensions/generator/src/test/java/com/google/common/truth/extension/generator/shaded/org/jboss/forge/roaster/model/{source => sourceChickens}/JavaClassSourceSubject.java (68%) diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/MyStringSubject.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/MyStringSubject.java index 1cd5dff4b..9c5d67389 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/MyStringSubject.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/MyStringSubject.java @@ -6,6 +6,9 @@ import lombok.NoArgsConstructor; import org.jboss.forge.roaster.model.source.JavaClassSource; +/** + * @see IgnoringWhiteSpaceComparison + */ public class MyStringSubject extends StringSubject { String actual; diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java index adc37eba3..41821333c 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java @@ -1,23 +1,28 @@ package com.google.common.truth.extension.generator.internal; import com.google.common.base.Joiner; +import com.google.common.flogger.FluentLogger; import com.google.common.truth.FailureMetadata; import com.google.common.truth.Subject; import com.google.common.truth.Truth; -import com.google.common.truth.extension.generator.internal.model.*; +import com.google.common.truth.extension.generator.internal.model.MiddleClass; +import com.google.common.truth.extension.generator.internal.model.ParentClass; +import com.google.common.truth.extension.generator.internal.model.ThreeSystem; import org.jboss.forge.roaster.Roaster; -import org.jboss.forge.roaster.model.source.*; - -import com.google.common.flogger.FluentLogger; +import org.jboss.forge.roaster.model.source.AnnotationSource; +import org.jboss.forge.roaster.model.source.JavaClassSource; +import org.jboss.forge.roaster.model.source.JavaDocSource; +import org.jboss.forge.roaster.model.source.MethodSource; +import org.reflections.Reflections; import javax.annotation.processing.Generated; import java.io.FileNotFoundException; -import java.util.*; +import java.util.Optional; import static com.google.common.truth.extension.generator.internal.Utils.getFactoryName; import static com.google.common.truth.extension.generator.internal.Utils.writeToDisk; -import static java.lang.String.format; import static java.util.Optional.empty; +import static java.util.Optional.of; /** * @author Antony Stubbs @@ -27,11 +32,14 @@ public class SkeletonGenerator implements SkeletonGeneratorAPI { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); private final Optional targetPackageName; + private final Reflections reflections; private MiddleClass middle; private ParentClass parent; public SkeletonGenerator(final Optional targetPackageName) { this.targetPackageName = targetPackageName; + this.reflections = new Reflections(this.targetPackageName); + } @Override @@ -52,9 +60,9 @@ public Optional threeLayerSystem(Class source, Class usersMiddleCl // make child - client code entry point JavaClassSource child = createChild(parent, usersMiddleClass.getName(), source, factoryMethodName); - MiddleClass middleClass = new MiddleClass(null, null, usersMiddleClass); + MiddleClass middleClass = MiddleClass.of(usersMiddleClass); - return Optional.of(new ThreeSystem(source, parent, middleClass, child)); + return of(new ThreeSystem(source, parent, middleClass, child)); } @Override @@ -65,18 +73,28 @@ public Optional threeLayerSystem(Class source) { ParentClass parent = createParent(source); this.parent = parent; - // todo try to see if class already exists first, user may already have a written one and not know - MiddleClass middle = createMiddlePlaceHolder(parent.generated, source); + MiddleClass middle = createMiddlePlaceHolder(parent.getGenerated(), source); + boolean threeSystemParentSubect = source.getName().contains("ThreeSystem"); this.middle = middle; - JavaClassSource child = createChild(parent, middle.generated.getQualifiedName(), source, middle.factoryMethod.getName()); + String factoryName = Utils.getFactoryName(source); + JavaClassSource child = createChild(parent, middle.getSimpleName(), source, factoryName); - return Optional.of(new ThreeSystem(source, parent, middle, child)); + return of(new ThreeSystem(source, parent, middle, child)); } private MiddleClass createMiddlePlaceHolder(JavaClassSource parent, Class source) { + String middleClassName = getSubjectName(source.getSimpleName()); + + // todo try to see if class already exists first, user may already have a written one and not know + Optional> compiledMiddleClass = middleExists(parent, middleClassName); + if (compiledMiddleClass.isPresent()) { + logger.atInfo().log("Skipping middle class Template creation as class already exists: %s", middleClassName); + return MiddleClass.of(compiledMiddleClass.get()); + } + JavaClassSource middle = Roaster.create(JavaClassSource.class); - middle.setName(getSubjectName(source.getSimpleName())); + middle.setName(middleClassName); middle.setPackage(parent.getPackage()); middle.extendSuperType(parent); JavaDocSource jd = middle.getJavaDoc(); @@ -92,7 +110,18 @@ private MiddleClass createMiddlePlaceHolder(JavaClassSource parent, Class source addGeneratedMarker(middle); writeToDisk(middle, targetPackageName); - return new MiddleClass(middle, factory, null); + return MiddleClass.of(middle, factory); + } + + private Optional> middleExists(JavaClassSource parent, String middleClassName) { + try { + // load from annotated classes instead using Reflections? + String fullName = parent.getPackage() + "." + middleClassName; + Class aClass = Class.forName(fullName); + return of(aClass); + } catch (ClassNotFoundException e) { + return empty(); + } } private ParentClass createParent(Class source) { @@ -118,29 +147,29 @@ private void addPackageSuperAndAnnotation(final JavaClassSource javaClass, final } private JavaClassSource createChild(ParentClass parent, - String usersMiddleClassName, - Class source, - String factoryMethodName) { + String usersMiddleClassName, + Class source, + String factoryMethodName) { // todo if middle doesn't extend parent, warn JavaClassSource child = Roaster.create(JavaClassSource.class); child.setName(getSubjectName(source.getSimpleName() + "Child")); - child.setPackage(parent.generated.getPackage()); + child.setPackage(parent.getGenerated().getPackage()); JavaDocSource javaDoc = child.getJavaDoc(); javaDoc.setText("Entry point for assertions for @{" + source.getSimpleName() + "}. Import the static accessor methods from this class and use them.\n" + - "Combines the generated code from {@" + parent.generated.getName() + "}and the user code from {@" + usersMiddleClassName + "}."); + "Combines the generated code from {@" + parent.getGenerated().getName() + "}and the user code from {@" + usersMiddleClassName + "}."); javaDoc.addTagValue("@see", source.getName()); javaDoc.addTagValue("@see", usersMiddleClassName); - javaDoc.addTagValue("@see", parent.generated.getName()); + javaDoc.addTagValue("@see", parent.getGenerated().getName()); - child.extendSuperType(this.middle.generated); + middle.makeChildExtend(child); MethodSource constructor = addConstructor(source, child, false); constructor.getJavaDoc().setText("This constructor should not be used, instead see the parent's.") .addTagValue("@see", usersMiddleClassName); constructor.setPrivate(); - addAccessPoints(source, child, factoryMethodName, usersMiddleClassName); + addAccessPoints(source, child, factoryMethodName, middle.getCanonicalName()); addGeneratedMarker(child); diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java index 334f24902..2964151da 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java @@ -369,12 +369,12 @@ private Optional getSubjectFromGenerated(final String name) { public void addTests(final Set allTypes) { for (ThreeSystem c : allTypes) { - addTests(c.parent.generated, c.classUnderTest); + addTests(c.parent.getGenerated(), c.classUnderTest); } // only serialise results, when all have finished - useful for debugging for (ThreeSystem c : allTypes) { - Utils.writeToDisk(c.parent.generated); + Utils.writeToDisk(c.parent.getGenerated()); } } @@ -395,14 +395,14 @@ static Optional ofClass(Optional> clazz) { String getSubjectSimpleName() { if (clazz == null) - return this.generated.middle.generated.getName(); + return this.generated.middle.getSimpleName(); else return clazz.getSimpleName(); } String getSubjectQualifiedName() { if (clazz == null) - return this.generated.middle.generated.getQualifiedName(); + return this.generated.middle.getCanonicalName(); else return clazz.getCanonicalName(); } @@ -420,7 +420,7 @@ public boolean isGenerated() { } public String getFactoryContainerName() { - return (isGenerated()) ? generated.middle.generated.getCanonicalName() : clazz.getCanonicalName(); + return (isGenerated()) ? generated.middle.getCanonicalName() : clazz.getCanonicalName(); } } } diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java index 10ff2ad9a..c358e8a4e 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java @@ -46,14 +46,12 @@ private Set generateSkeletonsFromPackages(final String[] modelPacka private Set generateSkeletons(Set> classes, Optional targetPackageName, OverallEntryPoint overallEntryPoint) { int sizeBeforeFilter = classes.size(); - // filter existing subjects from inbound set - classes = classes.stream().filter(x -> !Subject.class.isAssignableFrom(x)).collect(Collectors.toSet()); - log.at(Level.WARNING).log("Removed %s Subjects from inbound", classes.size() - sizeBeforeFilter); + classes = filterSubjects(classes, sizeBeforeFilter); Set subjectsSystems = new HashSet<>(); - for (Class c : classes) { + for (Class clazz : classes) { SkeletonGenerator skeletonGenerator = new SkeletonGenerator(targetPackageName); - Optional threeSystem = skeletonGenerator.threeLayerSystem(c); + Optional threeSystem = skeletonGenerator.threeLayerSystem(clazz); if (threeSystem.isPresent()) { ThreeSystem ts = threeSystem.get(); subjectsSystems.add(ts); @@ -63,6 +61,13 @@ private Set generateSkeletons(Set> classes, Optional> filterSubjects(Set> classes, int sizeBeforeFilter) { + // filter existing subjects from inbound set + classes = classes.stream().filter(x -> !Subject.class.isAssignableFrom(x)).collect(Collectors.toSet()); + log.at(Level.WARNING).log("Removed %s Subjects from inbound", classes.size() - sizeBeforeFilter); + return classes; + } + private Set> collectSourceClasses(final String[] modelPackages) { // for all classes in package SubTypesScanner subTypesScanner = new SubTypesScanner(false); diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/Utils.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/Utils.java index 4e14b2ba8..524856610 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/Utils.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/Utils.java @@ -47,6 +47,7 @@ private static String getDirectoryName(JavaClassSource javaClass, Optional ids = List.of("Parent", "Child"); boolean isChildOrParent = ids.stream().anyMatch(x -> javaClass.getName().contains(x)); diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/AClass.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/AClass.java index 92a53b124..13e2e690d 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/AClass.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/AClass.java @@ -1,12 +1,15 @@ package com.google.common.truth.extension.generator.internal.model; +import lombok.AllArgsConstructor; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Value; +import lombok.experimental.SuperBuilder; import org.jboss.forge.roaster.model.source.JavaClassSource; @Getter @RequiredArgsConstructor +@SuperBuilder public class AClass { - public final JavaClassSource generated; + protected final JavaClassSource generated; } diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/MiddleClass.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/MiddleClass.java index 556d20abe..43a3de57c 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/MiddleClass.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/MiddleClass.java @@ -1,21 +1,42 @@ package com.google.common.truth.extension.generator.internal.model; +import lombok.EqualsAndHashCode; import lombok.Value; +import lombok.experimental.SuperBuilder; import org.jboss.forge.roaster.model.source.JavaClassSource; import org.jboss.forge.roaster.model.source.MethodSource; +@EqualsAndHashCode(callSuper = true) @Value +@SuperBuilder public class MiddleClass extends AClass { - public final MethodSource factoryMethod; - public final Class usersMiddleClass; + MethodSource factoryMethod; + Class usersMiddleClass; - public MiddleClass(JavaClassSource generated, MethodSource factoryMethod, final Class usersMiddleClass) { - super(generated); - this.factoryMethod = factoryMethod; - this.usersMiddleClass = usersMiddleClass; - } + public static MiddleClass of(Class aClass) { + return MiddleClass.builder().usersMiddleClass(aClass).build(); + } - public String getName() { - return (usersMiddleClass==null)?super.generated.getName():usersMiddleClass.getName(); - } + public static MiddleClass of(JavaClassSource middle, MethodSource factory) { + return MiddleClass.builder().generated(middle).factoryMethod(factory).build(); + } + + public String getSimpleName() { + return (usersMiddleClass == null) + ? super.generated.getName() + : usersMiddleClass.getName(); + } + + public void makeChildExtend(JavaClassSource child) { + if (usersMiddleClass == null) + child.extendSuperType(generated); + else + child.extendSuperType(usersMiddleClass); + } + + public String getCanonicalName() { + return (usersMiddleClass == null) + ? super.generated.getCanonicalName() + : usersMiddleClass.getCanonicalName(); + } } diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/ManagedTruthChicken.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/ManagedTruthChicken.java deleted file mode 100644 index e7c1d4865..000000000 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/ManagedTruthChicken.java +++ /dev/null @@ -1,464 +0,0 @@ -package com.google.common.truth.extension.generator; - -import com.google.common.truth.Truth; -import com.google.common.truth.extension.generator.SourceClassSets.PackageAndClasses; -import com.google.common.truth.extension.generator.internal.*; -import com.google.common.truth.extension.generator.internal.SubjectMethodGenerator.ClassOrGenerated; -import com.google.common.truth.extension.generator.internal.model.*; -import com.google.common.truth.extension.generator.shaded.org.jboss.forge.roaster.model.source.JavaClassSourceSubject; -import com.google.common.truth.extension.generator.testModel.*; -import com.google.common.truth.extension.generator.testModel.MyEmployee.State; -import org.jboss.forge.roaster.model.source.JavaClassSource; - -import static com.google.common.truth.extension.generator.PackageAndClassesSubject.packageAndClasseses; -import static com.google.common.truth.extension.generator.SourceClassSetsSubject.sourceClassSetses; -import static com.google.common.truth.extension.generator.TestModelUtilsSubject.testModelUtilses; -import static com.google.common.truth.extension.generator.TruthGeneratorAPISubject.truthGeneratorAPIs; -import static com.google.common.truth.extension.generator.internal.ClassOrGeneratedSubject.classOrGenerateds; -import static com.google.common.truth.extension.generator.internal.OverallEntryPointSubject.overallEntryPoints; -import static com.google.common.truth.extension.generator.internal.SkeletonGeneratorAPISubject.skeletonGeneratorAPIs; -import static com.google.common.truth.extension.generator.internal.SkeletonGeneratorSubject.skeletonGenerators; -import static com.google.common.truth.extension.generator.internal.SourceCheckingSubject.sourceCheckings; -import static com.google.common.truth.extension.generator.internal.SubjectMethodGeneratorSubject.subjectMethodGenerators; -import static com.google.common.truth.extension.generator.internal.TruthGeneratorSubject.truthGenerators; -import static com.google.common.truth.extension.generator.internal.UtilsSubject.utilses; -import static com.google.common.truth.extension.generator.internal.model.AClassSubject.aClasses; -import static com.google.common.truth.extension.generator.internal.model.ManagedClassSetSubject.managedClassSets; -import static com.google.common.truth.extension.generator.internal.model.MiddleClassSubject.middleClasses; -import static com.google.common.truth.extension.generator.internal.model.ParentClassSubject.parentClasses; -import static com.google.common.truth.extension.generator.internal.model.ThreeSystemSubject.threeSystems; -import static com.google.common.truth.extension.generator.shaded.org.jboss.forge.roaster.model.source.JavaClassSourceSubject.javaClassSources; -import static com.google.common.truth.extension.generator.testModel.IdCardSubject.idCards; -import static com.google.common.truth.extension.generator.testModel.MyEmployeeSubject.myEmployees; -import static com.google.common.truth.extension.generator.testModel.PersonSubject.persons; -import static com.google.common.truth.extension.generator.testModel.ProjectSubject.projects; -import static com.google.common.truth.extension.generator.testModel.StateSubject.states; - -/** - * Single point of access for all managed Subjects. - */ -public class ManagedTruthChicken { - - /** - * Entry point for {@link SourceClassSets} assertions. - */ - public static SourceClassSetsSubject assertThat( - com.google.common.truth.extension.generator.SourceClassSets actual) { - return Truth.assertAbout(sourceClassSetses()).that(actual); - } - - /** - * Convenience entry point for {@link SourceClassSets} assertions when being - * mixed with other "assertThat" assertion libraries. - * - * @see #assertThat - */ - public static SourceClassSetsSubject assertTruth( - com.google.common.truth.extension.generator.SourceClassSets actual) { - return assertThat(actual); - } - - /** - * Entry point for {@link ManagedClassSet} assertions. - */ - public static ManagedClassSetSubject assertThat( - com.google.common.truth.extension.generator.internal.model.ManagedClassSet actual) { - return Truth.assertAbout(managedClassSets()).that(actual); - } - - /** - * Convenience entry point for {@link ManagedClassSet} assertions when being - * mixed with other "assertThat" assertion libraries. - * - * @see #assertThat - */ - public static ManagedClassSetSubject assertTruth( - com.google.common.truth.extension.generator.internal.model.ManagedClassSet actual) { - return assertThat(actual); - } - - /** - * Entry point for {@link SkeletonGenerator} assertions. - */ - public static SkeletonGeneratorSubject assertThat( - com.google.common.truth.extension.generator.internal.SkeletonGenerator actual) { - return Truth.assertAbout(skeletonGenerators()).that(actual); - } - - /** - * Convenience entry point for {@link SkeletonGenerator} assertions when being - * mixed with other "assertThat" assertion libraries. - * - * @see #assertThat - */ - public static SkeletonGeneratorSubject assertTruth( - com.google.common.truth.extension.generator.internal.SkeletonGenerator actual) { - return assertThat(actual); - } - - /** - * Entry point for {@link SubjectMethodGenerator} assertions. - */ - public static SubjectMethodGeneratorSubject assertThat( - com.google.common.truth.extension.generator.internal.SubjectMethodGenerator actual) { - return Truth.assertAbout(subjectMethodGenerators()).that(actual); - } - - /** - * Convenience entry point for {@link SubjectMethodGenerator} assertions when - * being mixed with other "assertThat" assertion libraries. - * - * @see #assertThat - */ - public static SubjectMethodGeneratorSubject assertTruth( - com.google.common.truth.extension.generator.internal.SubjectMethodGenerator actual) { - return assertThat(actual); - } - - /** - * Entry point for {@link ClassOrGenerated} assertions. - */ - public static ClassOrGeneratedSubject assertThat( - com.google.common.truth.extension.generator.internal.SubjectMethodGenerator.ClassOrGenerated actual) { - return Truth.assertAbout(classOrGenerateds()).that(actual); - } - - /** - * Convenience entry point for {@link ClassOrGenerated} assertions when being - * mixed with other "assertThat" assertion libraries. - * - * @see #assertThat - */ - public static ClassOrGeneratedSubject assertTruth( - com.google.common.truth.extension.generator.internal.SubjectMethodGenerator.ClassOrGenerated actual) { - return assertThat(actual); - } - - /** - * Entry point for {@link PackageAndClasses} assertions. - */ - public static PackageAndClassesSubject assertThat( - com.google.common.truth.extension.generator.SourceClassSets.PackageAndClasses actual) { - return Truth.assertAbout(packageAndClasseses()).that(actual); - } - - /** - * Convenience entry point for {@link PackageAndClasses} assertions when being - * mixed with other "assertThat" assertion libraries. - * - * @see #assertThat - */ - public static PackageAndClassesSubject assertTruth( - com.google.common.truth.extension.generator.SourceClassSets.PackageAndClasses actual) { - return assertThat(actual); - } - - /** - * Entry point for {@link ThreeSystem} assertions. - */ - public static ThreeSystemSubject assertThat( - com.google.common.truth.extension.generator.internal.model.ThreeSystem actual) { - return Truth.assertAbout(threeSystems()).that(actual); - } - - /** - * Convenience entry point for {@link ThreeSystem} assertions when being mixed - * with other "assertThat" assertion libraries. - * - * @see #assertThat - */ - public static ThreeSystemSubject assertTruth( - com.google.common.truth.extension.generator.internal.model.ThreeSystem actual) { - return assertThat(actual); - } - - /** - * Entry point for {@link IdCard} assertions. - */ - public static IdCardSubject assertThat(com.google.common.truth.extension.generator.testModel.IdCard actual) { - return Truth.assertAbout(idCards()).that(actual); - } - - /** - * Convenience entry point for {@link IdCard} assertions when being mixed with - * other "assertThat" assertion libraries. - * - * @see #assertThat - */ - public static IdCardSubject assertTruth(com.google.common.truth.extension.generator.testModel.IdCard actual) { - return assertThat(actual); - } - - /** - * Entry point for {@link State} assertions. - */ - public static StateSubject assertThat( - com.google.common.truth.extension.generator.testModel.MyEmployee.State actual) { - return Truth.assertAbout(states()).that(actual); - } - - /** - * Convenience entry point for {@link State} assertions when being mixed with - * other "assertThat" assertion libraries. - * - * @see #assertThat - */ - public static StateSubject assertTruth( - com.google.common.truth.extension.generator.testModel.MyEmployee.State actual) { - return assertThat(actual); - } - - /** - * Entry point for {@link SkeletonGeneratorAPI} assertions. - */ - public static SkeletonGeneratorAPISubject assertThat( - com.google.common.truth.extension.generator.internal.SkeletonGeneratorAPI actual) { - return Truth.assertAbout(skeletonGeneratorAPIs()).that(actual); - } - - /** - * Convenience entry point for {@link SkeletonGeneratorAPI} assertions when - * being mixed with other "assertThat" assertion libraries. - * - * @see #assertThat - */ - public static SkeletonGeneratorAPISubject assertTruth( - com.google.common.truth.extension.generator.internal.SkeletonGeneratorAPI actual) { - return assertThat(actual); - } - - /** - * Entry point for {@link Person} assertions. - */ - public static PersonSubject assertThat(com.google.common.truth.extension.generator.testModel.Person actual) { - return Truth.assertAbout(persons()).that(actual); - } - - /** - * Convenience entry point for {@link Person} assertions when being mixed with - * other "assertThat" assertion libraries. - * - * @see #assertThat - */ - public static PersonSubject assertTruth(com.google.common.truth.extension.generator.testModel.Person actual) { - return assertThat(actual); - } - - /** - * Entry point for {@link MiddleClass} assertions. - */ - public static MiddleClassSubject assertThat( - com.google.common.truth.extension.generator.internal.model.MiddleClass actual) { - return Truth.assertAbout(middleClasses()).that(actual); - } - - /** - * Convenience entry point for {@link MiddleClass} assertions when being mixed - * with other "assertThat" assertion libraries. - * - * @see #assertThat - */ - public static MiddleClassSubject assertTruth( - com.google.common.truth.extension.generator.internal.model.MiddleClass actual) { - return assertThat(actual); - } - - /** - * Entry point for {@link Utils} assertions. - */ - public static UtilsSubject assertThat(com.google.common.truth.extension.generator.internal.Utils actual) { - return Truth.assertAbout(utilses()).that(actual); - } - - /** - * Convenience entry point for {@link Utils} assertions when being mixed with - * other "assertThat" assertion libraries. - * - * @see #assertThat - */ - public static UtilsSubject assertTruth(com.google.common.truth.extension.generator.internal.Utils actual) { - return assertThat(actual); - } - - /** - * Entry point for {@link SourceChecking} assertions. - */ - public static SourceCheckingSubject assertThat( - com.google.common.truth.extension.generator.internal.SourceChecking actual) { - return Truth.assertAbout(sourceCheckings()).that(actual); - } - - /** - * Convenience entry point for {@link SourceChecking} assertions when being - * mixed with other "assertThat" assertion libraries. - * - * @see #assertThat - */ - public static SourceCheckingSubject assertTruth( - com.google.common.truth.extension.generator.internal.SourceChecking actual) { - return assertThat(actual); - } - - /** - * Entry point for {@link AClass} assertions. - */ - public static AClassSubject assertThat(com.google.common.truth.extension.generator.internal.model.AClass actual) { - return Truth.assertAbout(aClasses()).that(actual); - } - - /** - * Convenience entry point for {@link AClass} assertions when being mixed with - * other "assertThat" assertion libraries. - * - * @see #assertThat - */ - public static AClassSubject assertTruth(com.google.common.truth.extension.generator.internal.model.AClass actual) { - return assertThat(actual); - } - - /** - * Entry point for {@link MyEmployee} assertions. - */ - public static MyEmployeeSubject assertThat( - com.google.common.truth.extension.generator.testModel.MyEmployee actual) { - return Truth.assertAbout(myEmployees()).that(actual); - } - - /** - * Convenience entry point for {@link MyEmployee} assertions when being mixed - * with other "assertThat" assertion libraries. - * - * @see #assertThat - */ - public static MyEmployeeSubject assertTruth( - com.google.common.truth.extension.generator.testModel.MyEmployee actual) { - return assertThat(actual); - } - - /** - * Entry point for {@link Project} assertions. - */ - public static ProjectSubject assertThat(com.google.common.truth.extension.generator.testModel.Project actual) { - return Truth.assertAbout(projects()).that(actual); - } - - /** - * Convenience entry point for {@link Project} assertions when being mixed with - * other "assertThat" assertion libraries. - * - * @see #assertThat - */ - public static ProjectSubject assertTruth(com.google.common.truth.extension.generator.testModel.Project actual) { - return assertThat(actual); - } - - /** - * Entry point for {@link TestModelUtils} assertions. - */ - public static TestModelUtilsSubject assertThat(com.google.common.truth.extension.generator.TestModelUtils actual) { - return Truth.assertAbout(testModelUtilses()).that(actual); - } - - /** - * Convenience entry point for {@link TestModelUtils} assertions when being - * mixed with other "assertThat" assertion libraries. - * - * @see #assertThat - */ - public static TestModelUtilsSubject assertTruth(com.google.common.truth.extension.generator.TestModelUtils actual) { - return assertThat(actual); - } - - /** - * Entry point for {@link TruthGenerator} assertions. - */ - public static TruthGeneratorSubject assertThat( - com.google.common.truth.extension.generator.internal.TruthGenerator actual) { - return Truth.assertAbout(truthGenerators()).that(actual); - } - - /** - * Convenience entry point for {@link TruthGenerator} assertions when being - * mixed with other "assertThat" assertion libraries. - * - * @see #assertThat - */ - public static TruthGeneratorSubject assertTruth( - com.google.common.truth.extension.generator.internal.TruthGenerator actual) { - return assertThat(actual); - } - - /** - * Entry point for {@link ParentClass} assertions. - */ - public static ParentClassSubject assertThat( - com.google.common.truth.extension.generator.internal.model.ParentClass actual) { - return Truth.assertAbout(parentClasses()).that(actual); - } - - /** - * Convenience entry point for {@link ParentClass} assertions when being mixed - * with other "assertThat" assertion libraries. - * - * @see #assertThat - */ - public static ParentClassSubject assertTruth( - com.google.common.truth.extension.generator.internal.model.ParentClass actual) { - return assertThat(actual); - } - - /** - * Entry point for {@link TruthGeneratorAPI} assertions. - */ - public static TruthGeneratorAPISubject assertThat( - com.google.common.truth.extension.generator.TruthGeneratorAPI actual) { - return Truth.assertAbout(truthGeneratorAPIs()).that(actual); - } - - /** - * Convenience entry point for {@link TruthGeneratorAPI} assertions when being - * mixed with other "assertThat" assertion libraries. - * - * @see #assertThat - */ - public static TruthGeneratorAPISubject assertTruth( - com.google.common.truth.extension.generator.TruthGeneratorAPI actual) { - return assertThat(actual); - } - - /** - * Entry point for {@link OverallEntryPoint} assertions. - */ - public static OverallEntryPointSubject assertThat( - com.google.common.truth.extension.generator.internal.OverallEntryPoint actual) { - return Truth.assertAbout(overallEntryPoints()).that(actual); - } - - /** - * Convenience entry point for {@link OverallEntryPoint} assertions when being - * mixed with other "assertThat" assertion libraries. - * - * @see #assertThat - */ - public static OverallEntryPointSubject assertTruth( - com.google.common.truth.extension.generator.internal.OverallEntryPoint actual) { - return assertThat(actual); - } - - /** - * Entry point for {@link JavaClassSource} assertions. - */ - public static JavaClassSourceSubject assertThat(org.jboss.forge.roaster.model.source.JavaClassSource actual) { - return Truth.assertAbout(javaClassSources()).that(actual); - } - - /** - * Convenience entry point for {@link JavaClassSource} assertions when being - * mixed with other "assertThat" assertion libraries. - * - * @see #assertThat - */ - public static JavaClassSourceSubject assertTruth(org.jboss.forge.roaster.model.source.JavaClassSource actual) { - return assertThat(actual); - } -} diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java index 3e7f492a7..3443925c1 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java @@ -2,6 +2,8 @@ import com.google.common.io.Resources; import com.google.common.truth.extension.generator.internal.model.ThreeSystem; +import com.google.common.truth.extension.generator.internal.modelSubjectChickens.ThreeSystemChildSubject; +import com.google.common.truth.extension.generator.internal.modelSubjectChickens.ThreeSystemSubject; import com.google.common.truth.extension.generator.testModel.*; import org.jboss.forge.roaster.model.source.JavaClassSource; import org.junit.Test; @@ -68,15 +70,15 @@ public void generate_code() throws IOException { ThreeSystem threeSystemGenerated = generated.get(MyEmployee.class); - ManagedTruthChicken.assertThat(threeSystemGenerated) + ThreeSystemChildSubject.assertThat(threeSystemGenerated) .hasParent().hasGenerated().hasSourceText() .ignoringWhiteSpace().equalTo(expectedMyEmployeeParent); // sanity full chain - ManagedTruthChicken.assertThat(threeSystemGenerated).hasParentSource(expectedMyEmployeeParent); + ThreeSystemChildSubject.assertThat(threeSystemGenerated).hasParentSource(expectedMyEmployeeParent); - ManagedTruthChicken.assertThat(threeSystemGenerated).hasMiddleSource(expectedMyEmployeeMiddle); + ThreeSystemChildSubject.assertThat(threeSystemGenerated).hasMiddleSource(expectedMyEmployeeMiddle); - ManagedTruthChicken.assertThat(threeSystemGenerated).hasChildSource(expectedMyEmployeeChild); + ThreeSystemChildSubject.assertThat(threeSystemGenerated).hasChildSource(expectedMyEmployeeChild); } diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/MiddleClassParentSubject.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/MiddleClassParentSubject.java new file mode 100644 index 000000000..8f8fa76da --- /dev/null +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/MiddleClassParentSubject.java @@ -0,0 +1,56 @@ +package com.google.common.truth.extension.generator.internal.modelSubjectChickens; + +import com.google.common.truth.ClassSubject; +import com.google.common.truth.FailureMetadata; +import com.google.common.truth.StringSubject; +import com.google.common.truth.Subject; +import com.google.common.truth.extension.generator.internal.model.MiddleClass; +import com.google.common.truth.extension.generator.shaded.org.jboss.forge.roaster.model.sourceChickens.JavaClassSourceSubject; + +import javax.annotation.processing.Generated; + +import static com.google.common.truth.extension.generator.shaded.org.jboss.forge.roaster.model.sourceChickens.JavaClassSourceSubject.javaClassSources; + +// in VCS as we're still in the chicken phase of what comes first - stable maven plugin to generate this for the build before we can remove + +/** + * Truth Subject for the {@link MiddleClass}. + *

+ * Note that this class is generated / managed, and will change over time. So any changes you might make will be + * overwritten. + * + * @see MiddleClass + * @see MiddleClassSubject + * @see MiddleClassChildSubject + */ +@Generated("truth-generator") +public class MiddleClassParentSubject extends Subject { + + protected final MiddleClass actual; + + protected MiddleClassParentSubject(FailureMetadata failureMetadata, + MiddleClass actual) { + super(failureMetadata, actual); + this.actual = actual; + } + + public StringSubject hasCanonicalName() { + isNotNull(); + return check("getCanonicalName").that(actual.getCanonicalName()); + } + + public ClassSubject hasUsersMiddleClass() { + isNotNull(); + return check("getUsersMiddleClass").that(actual.getUsersMiddleClass()); + } + + public JavaClassSourceSubject hasGenerated() { + isNotNull(); + return check("getGenerated").about(javaClassSources()).that(actual.getGenerated()); + } + + public StringSubject hasSimpleName() { + isNotNull(); + return check("getSimpleName").that(actual.getSimpleName()); + } +} diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/MiddleClassSubject.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/MiddleClassSubject.java new file mode 100644 index 000000000..4ced82acc --- /dev/null +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/MiddleClassSubject.java @@ -0,0 +1,33 @@ +package com.google.common.truth.extension.generator.internal.modelSubjectChickens; + +import com.google.common.truth.FailureMetadata; +import com.google.common.truth.extension.generator.internal.model.MiddleClass; + +import javax.annotation.processing.Generated; + +// in VCS as we're still in the chicken phase of what comes first - stable maven plugin to generate this for the build before we can remove + +/** + * Optionally move this class into source control, and add your custom assertions here. + * + *

+ * If the system detects this class already exists, it won't attempt to generate a new one. Note that if the base + * skeleton of this class ever changes, you won't automatically get it updated. + * + * @see MiddleClassParentSubject + */ +@Generated("truth-generator") +public class MiddleClassSubject extends MiddleClassParentSubject { + + protected MiddleClassSubject(FailureMetadata failureMetadata, + MiddleClass actual) { + super(failureMetadata, actual); + } + + /** + * Returns an assertion builder for a {@link MiddleClass} class. + */ + public static Factory middleClasses() { + return MiddleClassSubject::new; + } +} diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ParentClassParentSubject.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ParentClassParentSubject.java new file mode 100644 index 000000000..34a57020e --- /dev/null +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ParentClassParentSubject.java @@ -0,0 +1,39 @@ +package com.google.common.truth.extension.generator.internal.modelSubjectChickens; + +import com.google.common.truth.FailureMetadata; +import com.google.common.truth.Subject; +import com.google.common.truth.extension.generator.internal.model.ParentClass; +import com.google.common.truth.extension.generator.shaded.org.jboss.forge.roaster.model.sourceChickens.JavaClassSourceSubject; + +import javax.annotation.processing.Generated; + +import static com.google.common.truth.extension.generator.shaded.org.jboss.forge.roaster.model.sourceChickens.JavaClassSourceSubject.javaClassSources; + +// in VCS as we're still in the chicken phase of what comes first - stable maven plugin to generate this for the build before we can remove + +/** + * Truth Subject for the {@link ParentClass}. + *

+ * Note that this class is generated / managed, and will change over time. So any changes you might make will be + * overwritten. + * + * @see ParentClass + * @see ParentClassSubject + * @see ParentClassChildSubject + */ +@Generated("truth-generator") +public class ParentClassParentSubject extends Subject { + + protected final ParentClass actual; + + protected ParentClassParentSubject(FailureMetadata failureMetadata, + ParentClass actual) { + super(failureMetadata, actual); + this.actual = actual; + } + + public JavaClassSourceSubject hasGenerated() { + isNotNull(); + return check("getGenerated").about(javaClassSources()).that(actual.getGenerated()); + } +} diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ParentClassSubject.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ParentClassSubject.java new file mode 100644 index 000000000..92249ac05 --- /dev/null +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ParentClassSubject.java @@ -0,0 +1,33 @@ +package com.google.common.truth.extension.generator.internal.modelSubjectChickens; + +import com.google.common.truth.FailureMetadata; +import com.google.common.truth.extension.generator.internal.model.ParentClass; + +import javax.annotation.processing.Generated; + +/** + * Optionally move this class into source control, and add your custom + * assertions here. + * + *

+ * If the system detects this class already exists, it won't attempt to generate + * a new one. Note that if the base skeleton of this class ever changes, you + * won't automatically get it updated. + * + * @see ParentClassParentSubject + */ +@Generated("truth-generator") +public class ParentClassSubject extends ParentClassParentSubject { + + protected ParentClassSubject(FailureMetadata failureMetadata, + ParentClass actual) { + super(failureMetadata, actual); + } + + /** + * Returns an assertion builder for a {@link ParentClass} class. + */ + public static Factory parentClasses() { + return ParentClassSubject::new; + } +} diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ThreeSystemChildSubject.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ThreeSystemChildSubject.java new file mode 100644 index 000000000..688ecc1b9 --- /dev/null +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ThreeSystemChildSubject.java @@ -0,0 +1,49 @@ +package com.google.common.truth.extension.generator.internal.modelSubjectChickens; + +import com.google.common.truth.FailureMetadata; +import com.google.common.truth.Truth; +import com.google.common.truth.extension.generator.internal.model.ThreeSystem; + +import javax.annotation.processing.Generated; + +/** + * Entry point for assertions for @{ThreeSystem}. Import the static accessor + * methods from this class and use them. Combines the generated code from + * {@ThreeSystemParentSubject}and the user code from {@ThreeSystemSubject}. + * + * @see ThreeSystem + * @see ThreeSystemSubject + * @see ThreeSystemParentSubject + */ +@Generated("truth-generator") +public class ThreeSystemChildSubject extends ThreeSystemSubject { + + /** + * This constructor should not be used, instead see the parent's. + * + * @see ThreeSystemSubject + */ + private ThreeSystemChildSubject(FailureMetadata failureMetadata, + ThreeSystem actual) { + super(failureMetadata, actual); + } + + /** + * Entry point for {@link ThreeSystem} assertions. + */ + public static ThreeSystemSubject assertThat( + ThreeSystem actual) { + return Truth.assertAbout(threeSystems()).that(actual); + } + + /** + * Convenience entry point for {@link ThreeSystem} assertions when being mixed + * with other "assertThat" assertion libraries. + * + * @see #assertThat + */ + public static ThreeSystemSubject assertTruth( + ThreeSystem actual) { + return assertThat(actual); + } +} diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ThreeSystemParentSubject.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ThreeSystemParentSubject.java new file mode 100644 index 000000000..aa9f3b663 --- /dev/null +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ThreeSystemParentSubject.java @@ -0,0 +1,57 @@ +package com.google.common.truth.extension.generator.internal.modelSubjectChickens; + +import com.google.common.truth.ClassSubject; +import com.google.common.truth.FailureMetadata; +import com.google.common.truth.Subject; +import com.google.common.truth.extension.generator.internal.model.ThreeSystem; +import com.google.common.truth.extension.generator.shaded.org.jboss.forge.roaster.model.sourceChickens.JavaClassSourceSubject; + +import javax.annotation.processing.Generated; + +import static com.google.common.truth.extension.generator.internal.modelSubjectChickens.MiddleClassSubject.middleClasses; +import static com.google.common.truth.extension.generator.internal.modelSubjectChickens.ParentClassSubject.parentClasses; +import static com.google.common.truth.extension.generator.shaded.org.jboss.forge.roaster.model.sourceChickens.JavaClassSourceSubject.javaClassSources; + +// in VCS as we're still in the chicken phase of what comes first - stable maven plugin to generate this for the build before we can remove + +/** + * Truth Subject for the {@link ThreeSystem}. + *

+ * Note that this class is generated / managed, and will change over time. So any changes you might make will be + * overwritten. + * + * @see ThreeSystem + * @see ThreeSystemSubject + * @see ThreeSystemChildSubject + */ +@Generated("truth-generator") +public class ThreeSystemParentSubject extends Subject { + + protected final ThreeSystem actual; + + protected ThreeSystemParentSubject(FailureMetadata failureMetadata, + com.google.common.truth.extension.generator.internal.model.ThreeSystem actual) { + super(failureMetadata, actual); + this.actual = actual; + } + + public JavaClassSourceSubject hasChild() { + isNotNull(); + return check("getChild").about(javaClassSources()).that(actual.getChild()); + } + + public ParentClassSubject hasParent() { + isNotNull(); + return check("getParent").about(parentClasses()).that(actual.getParent()); + } + + public MiddleClassSubject hasMiddle() { + isNotNull(); + return check("getMiddle").about(middleClasses()).that(actual.getMiddle()); + } + + public ClassSubject hasClassUnderTest() { + isNotNull(); + return check("getClassUnderTest").that(actual.getClassUnderTest()); + } +} diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/model/ThreeSystemSubject.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ThreeSystemSubject.java similarity index 85% rename from extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/model/ThreeSystemSubject.java rename to extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ThreeSystemSubject.java index e35f44ded..294dd34e1 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/model/ThreeSystemSubject.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ThreeSystemSubject.java @@ -1,9 +1,12 @@ -package com.google.common.truth.extension.generator.internal.model; +package com.google.common.truth.extension.generator.internal.modelSubjectChickens; import com.google.common.truth.FailureMetadata; +import com.google.common.truth.extension.generator.internal.model.ThreeSystem; import javax.annotation.processing.Generated; +// in VCS as we're still in the chicken phase of what comes first - stable maven plugin to generate this for the build before we can remove + /** * Optionally move this class into source control, and add your custom assertions here. * diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/shaded/org/jboss/forge/roaster/model/sourceChickens/JavaClassSourceParentSubject.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/shaded/org/jboss/forge/roaster/model/sourceChickens/JavaClassSourceParentSubject.java new file mode 100644 index 000000000..a1307afbb --- /dev/null +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/shaded/org/jboss/forge/roaster/model/sourceChickens/JavaClassSourceParentSubject.java @@ -0,0 +1,371 @@ +package com.google.common.truth.extension.generator.shaded.org.jboss.forge.roaster.model.sourceChickens; + +import com.google.common.truth.*; +//import com.google.common.truth.extension.generator.shaded.org.jboss.forge.roaster.model.source.JavaClassSourceChildSubject; +import org.jboss.forge.roaster.model.source.JavaClassSource; + +import javax.annotation.processing.Generated; + +import static com.google.common.truth.Fact.simpleFact; + +// in VCS as we're still in the chicken phase of what comes first - stable maven plugin to generate this for the build before we can remove + +/** + * Truth Subject for the {@link JavaClassSource}. + *

+ * Note that this class is generated / managed, and will change over time. So any changes you might make will be + * overwritten. + * + * @see JavaClassSource + * @see JavaClassSourceSubject + * @see JavaClassSourceChildSubject + */ +@Generated("truth-generator") +public class JavaClassSourceParentSubject extends Subject { + + protected final JavaClassSource actual; + + protected JavaClassSourceParentSubject(FailureMetadata failureMetadata, + JavaClassSource actual) { + super(failureMetadata, actual); + this.actual = actual; + } + + public StringSubject hasCanonicalName() { + isNotNull(); + return check("getCanonicalName").that(actual.getCanonicalName()); + } + + public void isNotClass() { + if (actual.isClass()) { + failWithActual(simpleFact("expected NOT to be Class")); + } + } + + public void isClass() { + if (!actual.isClass()) { + failWithActual(simpleFact("expected to be Class")); + } + } + + public BooleanSubject hasClass() { + isNotNull(); + return check("isClass").that(actual.isClass()); + } + + public void isNotProtected() { + if (actual.isProtected()) { + failWithActual(simpleFact("expected NOT to be Protected")); + } + } + + public void isProtected() { + if (!actual.isProtected()) { + failWithActual(simpleFact("expected to be Protected")); + } + } + + public BooleanSubject hasProtected() { + isNotNull(); + return check("isProtected").that(actual.isProtected()); + } + + public void isNotPublic() { + if (actual.isPublic()) { + failWithActual(simpleFact("expected NOT to be Public")); + } + } + + public void isPublic() { + if (!actual.isPublic()) { + failWithActual(simpleFact("expected to be Public")); + } + } + + public BooleanSubject hasPublic() { + isNotNull(); + return check("isPublic").that(actual.isPublic()); + } + + public StringSubject hasQualifiedName() { + isNotNull(); + return check("getQualifiedName").that(actual.getQualifiedName()); + } + + public IterableSubject hasAnnotations() { + isNotNull(); + return check("getAnnotations").that(actual.getAnnotations()); + } + + public IntegerSubject hasEndPosition() { + isNotNull(); + return check("getEndPosition").that(actual.getEndPosition()); + } + + public void isNotPrivate() { + if (actual.isPrivate()) { + failWithActual(simpleFact("expected NOT to be Private")); + } + } + + public void isPrivate() { + if (!actual.isPrivate()) { + failWithActual(simpleFact("expected to be Private")); + } + } + + public BooleanSubject hasPrivate() { + isNotNull(); + return check("isPrivate").that(actual.isPrivate()); + } + + public IterableSubject hasSyntaxErrors() { + isNotNull(); + return check("getSyntaxErrors").that(actual.getSyntaxErrors()); + } + + public void isNotStatic() { + if (actual.isStatic()) { + failWithActual(simpleFact("expected NOT to be Static")); + } + } + + public void isStatic() { + if (!actual.isStatic()) { + failWithActual(simpleFact("expected to be Static")); + } + } + + public BooleanSubject hasStatic() { + isNotNull(); + return check("isStatic").that(actual.isStatic()); + } + + public void isNotInterface() { + if (actual.isInterface()) { + failWithActual(simpleFact("expected NOT to be Interface")); + } + } + + public void isInterface() { + if (!actual.isInterface()) { + failWithActual(simpleFact("expected to be Interface")); + } + } + + public BooleanSubject hasInterface() { + isNotNull(); + return check("isInterface").that(actual.isInterface()); + } + + public IterableSubject hasProperties() { + isNotNull(); + return check("getProperties").that(actual.getProperties()); + } + + public void isNotFinal() { + if (actual.isFinal()) { + failWithActual(simpleFact("expected NOT to be Final")); + } + } + + public void isFinal() { + if (!actual.isFinal()) { + failWithActual(simpleFact("expected to be Final")); + } + } + + public BooleanSubject hasFinal() { + isNotNull(); + return check("isFinal").that(actual.isFinal()); + } + + public IntegerSubject hasLineNumber() { + isNotNull(); + return check("getLineNumber").that(actual.getLineNumber()); + } + + public void isNotLocalClass() { + if (actual.isLocalClass()) { + failWithActual(simpleFact("expected NOT to be LocalClass")); + } + } + + public void isLocalClass() { + if (!actual.isLocalClass()) { + failWithActual(simpleFact("expected to be LocalClass")); + } + } + + public BooleanSubject hasLocalClass() { + isNotNull(); + return check("isLocalClass").that(actual.isLocalClass()); + } + + public IntegerSubject hasStartPosition() { + isNotNull(); + return check("getStartPosition").that(actual.getStartPosition()); + } + + public IterableSubject hasMethods() { + isNotNull(); + return check("getMethods").that(actual.getMethods()); + } + + public StringSubject hasPackage() { + isNotNull(); + return check("getPackage").that(actual.getPackage()); + } + + public StringSubject hasSuperType() { + isNotNull(); + return check("getSuperType").that(actual.getSuperType()); + } + + public IterableSubject hasImports() { + isNotNull(); + return check("getImports").that(actual.getImports()); + } + + public void isNotEnum() { + if (actual.isEnum()) { + failWithActual(simpleFact("expected NOT to be Enum")); + } + } + + public void isEnum() { + if (!actual.isEnum()) { + failWithActual(simpleFact("expected to be Enum")); + } + } + + public BooleanSubject hasEnum() { + isNotNull(); + return check("isEnum").that(actual.isEnum()); + } + + public IterableSubject hasTypeVariables() { + isNotNull(); + return check("getTypeVariables").that(actual.getTypeVariables()); + } + + public void isNotRecord() { + if (actual.isRecord()) { + failWithActual(simpleFact("expected NOT to be Record")); + } + } + + public void isRecord() { + if (!actual.isRecord()) { + failWithActual(simpleFact("expected to be Record")); + } + } + + public BooleanSubject hasRecord() { + isNotNull(); + return check("isRecord").that(actual.isRecord()); + } + + public IterableSubject hasFields() { + isNotNull(); + return check("getFields").that(actual.getFields()); + } + + public IterableSubject hasMembers() { + isNotNull(); + return check("getMembers").that(actual.getMembers()); + } + + public IntegerSubject hasColumnNumber() { + isNotNull(); + return check("getColumnNumber").that(actual.getColumnNumber()); + } + + public IterableSubject hasNestedTypes() { + isNotNull(); + return check("getNestedTypes").that(actual.getNestedTypes()); + } + + public StringSubject hasName() { + isNotNull(); + return check("getName").that(actual.getName()); + } + + public ComparableSubject hasVisibility() { + isNotNull(); + return check("getVisibility").that(actual.getVisibility()); + } + + public void isNotDefaultPackage() { + if (actual.isDefaultPackage()) { + failWithActual(simpleFact("expected NOT to be DefaultPackage")); + } + } + + public void isDefaultPackage() { + if (!actual.isDefaultPackage()) { + failWithActual(simpleFact("expected to be DefaultPackage")); + } + } + + public BooleanSubject hasDefaultPackage() { + isNotNull(); + return check("isDefaultPackage").that(actual.isDefaultPackage()); + } + + public void isNotAbstract() { + if (actual.isAbstract()) { + failWithActual(simpleFact("expected NOT to be Abstract")); + } + } + + public void isAbstract() { + if (!actual.isAbstract()) { + failWithActual(simpleFact("expected to be Abstract")); + } + } + + public BooleanSubject hasAbstract() { + isNotNull(); + return check("isAbstract").that(actual.isAbstract()); + } + + public IterableSubject hasInterfaces() { + isNotNull(); + return check("getInterfaces").that(actual.getInterfaces()); + } + + public void isNotAnnotation() { + if (actual.isAnnotation()) { + failWithActual(simpleFact("expected NOT to be Annotation")); + } + } + + public void isAnnotation() { + if (!actual.isAnnotation()) { + failWithActual(simpleFact("expected to be Annotation")); + } + } + + public BooleanSubject hasAnnotation() { + isNotNull(); + return check("isAnnotation").that(actual.isAnnotation()); + } + + public void isNotPackagePrivate() { + if (actual.isPackagePrivate()) { + failWithActual(simpleFact("expected NOT to be PackagePrivate")); + } + } + + public void isPackagePrivate() { + if (!actual.isPackagePrivate()) { + failWithActual(simpleFact("expected to be PackagePrivate")); + } + } + + public BooleanSubject hasPackagePrivate() { + isNotNull(); + return check("isPackagePrivate").that(actual.isPackagePrivate()); + } +} diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/shaded/org/jboss/forge/roaster/model/source/JavaClassSourceSubject.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/shaded/org/jboss/forge/roaster/model/sourceChickens/JavaClassSourceSubject.java similarity index 68% rename from extensions/generator/src/test/java/com/google/common/truth/extension/generator/shaded/org/jboss/forge/roaster/model/source/JavaClassSourceSubject.java rename to extensions/generator/src/test/java/com/google/common/truth/extension/generator/shaded/org/jboss/forge/roaster/model/sourceChickens/JavaClassSourceSubject.java index 3ba0e515f..f4a8cfc7d 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/shaded/org/jboss/forge/roaster/model/source/JavaClassSourceSubject.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/shaded/org/jboss/forge/roaster/model/sourceChickens/JavaClassSourceSubject.java @@ -1,20 +1,21 @@ -package com.google.common.truth.extension.generator.shaded.org.jboss.forge.roaster.model.source; +package com.google.common.truth.extension.generator.shaded.org.jboss.forge.roaster.model.sourceChickens; -import com.google.common.truth.extension.generator.internal.MyStringSubject; import com.google.common.truth.FailureMetadata; +import com.google.common.truth.extension.generator.internal.MyStringSubject; import org.jboss.forge.roaster.model.source.JavaClassSource; + import javax.annotation.processing.Generated; +// in VCS as we're still in the chicken phase of what comes first - stable maven plugin to generate this for the build before we can remove + /** - * Optionally move this class into source control, and add your custom - * assertions here. + * Optionally move this class into source control, and add your custom assertions here. * *

- * If the system detects this class already exists, it won't attempt to generate - * a new one. Note that if the base skeleton of this class ever changes, you - * won't automatically get it updated. + * If the system detects this class already exists, it won't attempt to generate a new one. Note that if the base + * skeleton of this class ever changes, you won't automatically get it updated. * - * @see JavaClassSourceParentSubject + * @see com.google.common.truth.extension.generator.shaded.org.jboss.forge.roaster.model.sourceChickens.JavaClassSourceParentSubject */ @Generated("truth-generator") public class JavaClassSourceSubject extends JavaClassSourceParentSubject { From 487bafcdbdf79e9e20773c52c91b6abca1b2973e Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Mon, 2 Aug 2021 19:54:55 +0100 Subject: [PATCH 17/32] Create and tag with a user managed annotation marker --- .../generator/internal/SkeletonGenerator.java | 18 +++++++-------- .../generator/internal/TruthGenerator.java | 2 +- .../extension/generator/internal/Utils.java | 3 ++- .../internal/model/UserManagedTruth.java | 23 +++++++++++++++++++ .../MiddleClassSubject.java | 2 ++ .../ParentClassSubject.java | 2 ++ .../ThreeSystemSubject.java | 2 ++ .../JavaClassSourceSubject.java | 2 ++ 8 files changed, 42 insertions(+), 12 deletions(-) create mode 100644 extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/UserManagedTruth.java diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java index 41821333c..bd37b7636 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java @@ -8,12 +8,12 @@ import com.google.common.truth.extension.generator.internal.model.MiddleClass; import com.google.common.truth.extension.generator.internal.model.ParentClass; import com.google.common.truth.extension.generator.internal.model.ThreeSystem; +import com.google.common.truth.extension.generator.internal.model.UserManagedTruth; import org.jboss.forge.roaster.Roaster; import org.jboss.forge.roaster.model.source.AnnotationSource; import org.jboss.forge.roaster.model.source.JavaClassSource; import org.jboss.forge.roaster.model.source.JavaDocSource; import org.jboss.forge.roaster.model.source.MethodSource; -import org.reflections.Reflections; import javax.annotation.processing.Generated; import java.io.FileNotFoundException; @@ -32,14 +32,11 @@ public class SkeletonGenerator implements SkeletonGeneratorAPI { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); private final Optional targetPackageName; - private final Reflections reflections; private MiddleClass middle; private ParentClass parent; public SkeletonGenerator(final Optional targetPackageName) { this.targetPackageName = targetPackageName; - this.reflections = new Reflections(this.targetPackageName); - } @Override @@ -73,8 +70,7 @@ public Optional threeLayerSystem(Class source) { ParentClass parent = createParent(source); this.parent = parent; - MiddleClass middle = createMiddlePlaceHolder(parent.getGenerated(), source); - boolean threeSystemParentSubect = source.getName().contains("ThreeSystem"); + MiddleClass middle = createMiddleUserTemplate(parent.getGenerated(), source); this.middle = middle; String factoryName = Utils.getFactoryName(source); @@ -83,11 +79,10 @@ public Optional threeLayerSystem(Class source) { return of(new ThreeSystem(source, parent, middle, child)); } - private MiddleClass createMiddlePlaceHolder(JavaClassSource parent, Class source) { + private MiddleClass createMiddleUserTemplate(JavaClassSource parent, Class source) { String middleClassName = getSubjectName(source.getSimpleName()); - // todo try to see if class already exists first, user may already have a written one and not know - Optional> compiledMiddleClass = middleExists(parent, middleClassName); + Optional> compiledMiddleClass = middleExists(parent, middleClassName, source); if (compiledMiddleClass.isPresent()) { logger.atInfo().log("Skipping middle class Template creation as class already exists: %s", middleClassName); return MiddleClass.of(compiledMiddleClass.get()); @@ -101,10 +96,13 @@ private MiddleClass createMiddlePlaceHolder(JavaClassSource parent, Class source jd.setText("Optionally move this class into source control, and add your custom assertions here.\n\n" + "

If the system detects this class already exists, it won't attempt to generate a new one. Note that " + "if the base skeleton of this class ever changes, you won't automatically get it updated."); + jd.addTagValue("@see", source.getSimpleName()); jd.addTagValue("@see", parent.getName()); addConstructor(source, middle, false); + middle.addAnnotation(UserManagedTruth.class).setClassValue("clazz", source); + MethodSource factory = addFactoryAccesor(source, middle, source.getSimpleName()); addGeneratedMarker(middle); @@ -113,7 +111,7 @@ private MiddleClass createMiddlePlaceHolder(JavaClassSource parent, Class source return MiddleClass.of(middle, factory); } - private Optional> middleExists(JavaClassSource parent, String middleClassName) { + private Optional> middleExists(JavaClassSource parent, String middleClassName, Class source) { try { // load from annotated classes instead using Reflections? String fullName = parent.getPackage() + "." + middleClassName; diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java index c358e8a4e..f0bba7683 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java @@ -64,7 +64,7 @@ private Set generateSkeletons(Set> classes, Optional> filterSubjects(Set> classes, int sizeBeforeFilter) { // filter existing subjects from inbound set classes = classes.stream().filter(x -> !Subject.class.isAssignableFrom(x)).collect(Collectors.toSet()); - log.at(Level.WARNING).log("Removed %s Subjects from inbound", classes.size() - sizeBeforeFilter); + log.at(Level.FINE).log("Removed %s Subjects from inbound", classes.size() - sizeBeforeFilter); return classes; } diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/Utils.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/Utils.java index 524856610..7b645e2a9 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/Utils.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/Utils.java @@ -1,5 +1,6 @@ package com.google.common.truth.extension.generator.internal; +import com.google.common.truth.extension.generator.internal.model.UserManagedTruth; import org.atteo.evo.inflector.English; import org.jboss.forge.roaster.model.source.JavaClassSource; @@ -49,7 +50,7 @@ private static String getDirectoryName(JavaClassSource javaClass, Optional ids = List.of("Parent", "Child"); - boolean isChildOrParent = ids.stream().anyMatch(x -> javaClass.getName().contains(x)); + boolean isChildOrParent = javaClass.getAnnotation(UserManagedTruth.class) == null; String baseDirSuffix = (isChildOrParent) ? "truth-assertions-managed" : "truth-assertions-templates"; diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/UserManagedTruth.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/UserManagedTruth.java new file mode 100644 index 000000000..21f5428d8 --- /dev/null +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/UserManagedTruth.java @@ -0,0 +1,23 @@ +package com.google.common.truth.extension.generator.internal.model; + +import com.google.common.truth.Subject; +import com.google.common.truth.extension.generator.internal.SkeletonGeneratorAPI; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +/** + * Maker for the {@link SkeletonGeneratorAPI#threeLayerSystem)} which instructs the system that this class is the user + * managed middle class. + *

+ *

+ * Useful for detecting with it already exists, instead of relaying on class name matching. And good for discovering the + * class under test. + */ +@Target({ElementType.TYPE}) +public @interface UserManagedTruth { + /** + * The class that this is a {@link Subject} for. + */ + Class clazz(); +} diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/MiddleClassSubject.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/MiddleClassSubject.java index 4ced82acc..9054c2773 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/MiddleClassSubject.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/MiddleClassSubject.java @@ -2,6 +2,7 @@ import com.google.common.truth.FailureMetadata; import com.google.common.truth.extension.generator.internal.model.MiddleClass; +import com.google.common.truth.extension.generator.internal.model.UserManagedTruth; import javax.annotation.processing.Generated; @@ -16,6 +17,7 @@ * * @see MiddleClassParentSubject */ +@UserManagedTruth(clazz = MiddleClass.class) @Generated("truth-generator") public class MiddleClassSubject extends MiddleClassParentSubject { diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ParentClassSubject.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ParentClassSubject.java index 92249ac05..069b8d1a2 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ParentClassSubject.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ParentClassSubject.java @@ -2,6 +2,7 @@ import com.google.common.truth.FailureMetadata; import com.google.common.truth.extension.generator.internal.model.ParentClass; +import com.google.common.truth.extension.generator.internal.model.UserManagedTruth; import javax.annotation.processing.Generated; @@ -16,6 +17,7 @@ * * @see ParentClassParentSubject */ +@UserManagedTruth(clazz = ParentClass.class) @Generated("truth-generator") public class ParentClassSubject extends ParentClassParentSubject { diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ThreeSystemSubject.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ThreeSystemSubject.java index 294dd34e1..1cdd9ed13 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ThreeSystemSubject.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ThreeSystemSubject.java @@ -2,6 +2,7 @@ import com.google.common.truth.FailureMetadata; import com.google.common.truth.extension.generator.internal.model.ThreeSystem; +import com.google.common.truth.extension.generator.internal.model.UserManagedTruth; import javax.annotation.processing.Generated; @@ -16,6 +17,7 @@ * * @see ThreeSystemParentSubject */ +@UserManagedTruth(clazz = ThreeSystem.class) @Generated("truth-generator") public class ThreeSystemSubject extends ThreeSystemParentSubject { diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/shaded/org/jboss/forge/roaster/model/sourceChickens/JavaClassSourceSubject.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/shaded/org/jboss/forge/roaster/model/sourceChickens/JavaClassSourceSubject.java index f4a8cfc7d..95cd1532a 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/shaded/org/jboss/forge/roaster/model/sourceChickens/JavaClassSourceSubject.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/shaded/org/jboss/forge/roaster/model/sourceChickens/JavaClassSourceSubject.java @@ -2,6 +2,7 @@ import com.google.common.truth.FailureMetadata; import com.google.common.truth.extension.generator.internal.MyStringSubject; +import com.google.common.truth.extension.generator.internal.model.UserManagedTruth; import org.jboss.forge.roaster.model.source.JavaClassSource; import javax.annotation.processing.Generated; @@ -17,6 +18,7 @@ * * @see com.google.common.truth.extension.generator.shaded.org.jboss.forge.roaster.model.sourceChickens.JavaClassSourceParentSubject */ +@UserManagedTruth(clazz = JavaClassSource.class) @Generated("truth-generator") public class JavaClassSourceSubject extends JavaClassSourceParentSubject { From 9cf239c6225e651b4b33fe99d629528e58a7fa96 Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Mon, 2 Aug 2021 21:12:48 +0100 Subject: [PATCH 18/32] Add test strategies for optional, map, collection --- .../model => }/UserManagedTruth.java | 2 +- .../generator/internal/SkeletonGenerator.java | 2 +- .../internal/SubjectMethodGenerator.java | 162 +++++++++---- .../extension/generator/internal/Utils.java | 2 +- .../generator/TruthGeneratorTest.java | 3 + .../MiddleClassSubject.java | 2 +- .../ParentClassSubject.java | 2 +- .../ThreeSystemSubject.java | 2 +- .../JavaClassSourceSubject.java | 2 +- .../generator/testModel/MyEmployee.java | 7 +- .../expected/MyEmployeeParentSubject.java.txt | 218 ++++++++++++++++-- .../expected/MyEmployeeSubject.java.txt | 4 + 12 files changed, 343 insertions(+), 65 deletions(-) rename extensions/generator/src/main/java/com/google/common/truth/extension/generator/{internal/model => }/UserManagedTruth.java (90%) diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/UserManagedTruth.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/UserManagedTruth.java similarity index 90% rename from extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/UserManagedTruth.java rename to extensions/generator/src/main/java/com/google/common/truth/extension/generator/UserManagedTruth.java index 21f5428d8..8063101f7 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/UserManagedTruth.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/UserManagedTruth.java @@ -1,4 +1,4 @@ -package com.google.common.truth.extension.generator.internal.model; +package com.google.common.truth.extension.generator; import com.google.common.truth.Subject; import com.google.common.truth.extension.generator.internal.SkeletonGeneratorAPI; diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java index bd37b7636..b8601e837 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java @@ -5,10 +5,10 @@ import com.google.common.truth.FailureMetadata; import com.google.common.truth.Subject; import com.google.common.truth.Truth; +import com.google.common.truth.extension.generator.UserManagedTruth; import com.google.common.truth.extension.generator.internal.model.MiddleClass; import com.google.common.truth.extension.generator.internal.model.ParentClass; import com.google.common.truth.extension.generator.internal.model.ThreeSystem; -import com.google.common.truth.extension.generator.internal.model.UserManagedTruth; import org.jboss.forge.roaster.Roaster; import org.jboss.forge.roaster.model.source.AnnotationSource; import org.jboss.forge.roaster.model.source.JavaClassSource; diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java index 2964151da..9003cd370 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java @@ -27,6 +27,7 @@ import static java.util.function.Predicate.not; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; +import static org.apache.commons.lang3.StringUtils.capitalize; import static org.apache.commons.lang3.StringUtils.removeStart; import static org.reflections.ReflectionUtils.*; @@ -119,29 +120,34 @@ private String getSignature(final Method getter) { private void addFieldAccessors(Method method, JavaClassSource generated, Class classUnderTest) { Class returnType = getWrappedReturnType(method); + // todo skip static methods for now - just need to make template a bit more advanced + if(methodIsStatic(method)) + return; + if (Boolean.class.isAssignableFrom(returnType)) { addBooleanStrategy(method, generated, classUnderTest); - } + } else { - if (Collection.class.isAssignableFrom(returnType)) { - addHasElementStrategy(method, generated, classUnderTest); - } + if (Collection.class.isAssignableFrom(returnType)) { + addHasElementStrategy(method, generated, classUnderTest); + } - if (Optional.class.isAssignableFrom(returnType)) { - addOptionalStrategy(method, generated, classUnderTest); - } + if (Optional.class.isAssignableFrom(returnType)) { + addOptionalStrategy(method, generated, classUnderTest); + } - if (Map.class.isAssignableFrom(returnType)) { - addMapStrategy(method, generated, classUnderTest); - } + if (Map.class.isAssignableFrom(returnType)) { + addMapStrategy(method, generated, classUnderTest); + } - if (Enum.class.isAssignableFrom(returnType)) { - addEnumStrategy(method, generated, classUnderTest); + addEqualityStrategy(method, generated, classUnderTest); } - addEqualityStrategy(method, generated, classUnderTest); - addChainStrategy(method, generated, returnType); + + generated.addImport(Fact.class) + .setStatic(true) + .setName(Fact.class.getCanonicalName() + ".*"); } private Class getWrappedReturnType(Method method) { @@ -150,62 +156,136 @@ private Class getWrappedReturnType(Method method) { } private void addEqualityStrategy(Method method, JavaClassSource generated, Class classUnderTest) { - + equalityStrategyGeneric(method, generated, false); + equalityStrategyGeneric(method, generated, true); } - private void addEnumStrategy(Method method, JavaClassSource generated, Class classUnderTest) { + private void equalityStrategyGeneric(Method method, JavaClassSource generated, boolean positive) { + boolean primitive = method.getReturnType().isPrimitive(); + String equality = primitive ? " == expected" : ".equals(expected)"; + + String body = "" + + " if (%s(actual.%s()%s)) {\n" + + " failWithActual(fact(\"expected %s %sto be equal to\", expected));\n" + + " }\n"; + + String testPrefix = positive ? "" : "!"; + String say = positive ? "" : "NOT "; + String fieldName = removeStart(method.getName(), "get"); + body = format(body, testPrefix, method.getName(), equality, fieldName, say); + + String methodName = "has" + fieldName + capitalize(say.toLowerCase()).trim() + "EqualTo"; + MethodSource newMethod = generated.addMethod(); + newMethod.setName(methodName) + .setReturnTypeVoid() + .setBody(body) + .setPublic(); + newMethod.addParameter(method.getReturnType(), "expected"); } private void addMapStrategy(Method method, JavaClassSource generated, Class classUnderTest) { + addMapStrategyGeneric(method, generated, false); + addMapStrategyGeneric(method, generated, true); + } + + private void addMapStrategyGeneric(Method method, JavaClassSource generated, boolean positive) { + String testPrefix = positive ? "" : "!"; + + String body = "" + + " if (%sactual.%s().containsKey(expected)) {\n" + + " failWithActual(fact(\"expected %s %sto have key\", expected));\n" + + " }\n"; + + String say = positive ? "" : "NOT "; + String fieldName = removeStart(method.getName(), "get"); + body = format(body, testPrefix, method.getName(), fieldName, say); + + String methodName = "has" + fieldName + capitalize(say.toLowerCase()).trim() + "WithKey"; + MethodSource newMethod = generated.addMethod(); + newMethod + .setName(methodName) + .setReturnTypeVoid() + .setBody(body) + .setPublic(); + newMethod.addParameter(Object.class, "expected"); } private void addOptionalStrategy(Method method, JavaClassSource generated, Class classUnderTest) { + addOptionalStrategyGeneric(method, generated, false); + addOptionalStrategyGeneric(method, generated, true); } - private void addHasElementStrategy(Method method, JavaClassSource generated, Class classUnderTest) { + private void addOptionalStrategyGeneric(Method method, JavaClassSource generated, boolean positive) { + String testPrefix = positive ? "" : "!"; + String body = "" + + " if (%sactual.%s().isPresent()) {\n" + + " failWithActual(simpleFact(\"expected %s %sto be present\"));\n" + + " }\n"; - } + String say = positive ? "" : "NOT "; + String fieldName = removeStart(method.getName(), "get"); + body = format(body, testPrefix, method.getName(), fieldName, say); - /** - * public void isCeo() { if (!actual.isCeo()) { failWithActual(simpleFact("expected to be CEO")); } } - */ - private void addBooleanStrategy(Method method, JavaClassSource generated, Class classUnderTest) { - addPositiveBoolean(method, generated); - addNegativeBoolean(method, generated); + String methodName = "has" + fieldName + capitalize(say.toLowerCase()).trim() + "Present"; + MethodSource newMethod = generated.addMethod(); + newMethod + .setName(methodName) + .setReturnTypeVoid() + .setBody(body) + .setPublic(); + newMethod.addParameter(method.getReturnType(), "expected"); + } - generated.addImport(Fact.class) - .setStatic(true) - .setName(Fact.class.getCanonicalName() + ".simpleFact"); + private void addHasElementStrategy(Method method, JavaClassSource generated, Class classUnderTest) { + addHasElementStrategyGeneric(method, generated, false); + addHasElementStrategyGeneric(method, generated, true); } - private void addPositiveBoolean(Method method, JavaClassSource generated) { + private void addHasElementStrategyGeneric(Method method, JavaClassSource generated, boolean positive) { String body = "" + - " if (actual.%s()) {\n" + - " failWithActual(simpleFact(\"expected NOT to be %s\"));\n" + + " if (%sactual.%s().contains(expected)) {\n" + + " failWithActual(fact(\"expected %s %sto have element\", expected));\n" + " }\n"; - String noun = StringUtils.remove(method.getName(), "is"); - body = format(body, method.getName(), noun); + String testPrefix = positive ? "" : "!"; - String negativeMethodName = removeStart(method.getName(), "is"); - negativeMethodName = "isNot" + negativeMethodName; - generated.addMethod() - .setName(negativeMethodName) + String fieldName = removeStart(method.getName(), "get"); + + String say = positive ? "" : "NOT "; + body = format(body, testPrefix, method.getName(), fieldName, say); + + String methodName = "has" + fieldName + capitalize(say.toLowerCase()).trim() + "WithElement"; + MethodSource newMethod = generated.addMethod(); + newMethod + .setName(methodName) .setReturnTypeVoid() .setBody(body) .setPublic(); + newMethod.addParameter(Object.class, "expected"); + } + + private void addBooleanStrategy(Method method, JavaClassSource generated, Class classUnderTest) { + addBooleanGeneric(method, generated, true); + addBooleanGeneric(method, generated, false); } - private void addNegativeBoolean(Method method, JavaClassSource generated) { + private void addBooleanGeneric(Method method, JavaClassSource generated, boolean positive) { + String testPrefix = positive ? "" : "!"; + String say = positive ? "" : "NOT "; + String body = "" + - " if (!actual.%s()) {\n" + - " failWithActual(simpleFact(\"expected to be %s\"));\n" + + " if (%sactual.%s()) {\n" + + " failWithActual(simpleFact(\"expected %sto be %s\"));\n" + " }\n"; + String noun = StringUtils.remove(method.getName(), "is"); - body = format(body, method.getName(), noun); + body = format(body, testPrefix, method.getName(), say, noun); + + String methodName = removeStart(method.getName(), "is"); + methodName = "is" + capitalize(say.toLowerCase()).trim() + methodName; generated.addMethod() - .setName(method.getName()) + .setName(methodName) .setReturnTypeVoid() .setBody(body) .setPublic(); diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/Utils.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/Utils.java index 7b645e2a9..1b784fae0 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/Utils.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/Utils.java @@ -1,6 +1,6 @@ package com.google.common.truth.extension.generator.internal; -import com.google.common.truth.extension.generator.internal.model.UserManagedTruth; +import com.google.common.truth.extension.generator.UserManagedTruth; import org.atteo.evo.inflector.English; import org.jboss.forge.roaster.model.source.JavaClassSource; diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java index 3443925c1..7db3320d7 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java @@ -134,6 +134,9 @@ public void try_out_assertions() { // assertTruth(hi).getCard().getEpoch().isAtLeast(20); // assertTruth(hi).getSlipUpList().hasSize(3); // MyEmployeeSubject myEmployeeSubject = ManagedTruth.assertTruth(hi); + +// MyEmployeeChildSubject.assertThat(TestModelUtils.createEmployee()).hasProjectMapWithKey("key"); + } } diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/MiddleClassSubject.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/MiddleClassSubject.java index 9054c2773..7de903749 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/MiddleClassSubject.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/MiddleClassSubject.java @@ -2,7 +2,7 @@ import com.google.common.truth.FailureMetadata; import com.google.common.truth.extension.generator.internal.model.MiddleClass; -import com.google.common.truth.extension.generator.internal.model.UserManagedTruth; +import com.google.common.truth.extension.generator.UserManagedTruth; import javax.annotation.processing.Generated; diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ParentClassSubject.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ParentClassSubject.java index 069b8d1a2..1c1864137 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ParentClassSubject.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ParentClassSubject.java @@ -2,7 +2,7 @@ import com.google.common.truth.FailureMetadata; import com.google.common.truth.extension.generator.internal.model.ParentClass; -import com.google.common.truth.extension.generator.internal.model.UserManagedTruth; +import com.google.common.truth.extension.generator.UserManagedTruth; import javax.annotation.processing.Generated; diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ThreeSystemSubject.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ThreeSystemSubject.java index 1cdd9ed13..5bb06dd37 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ThreeSystemSubject.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ThreeSystemSubject.java @@ -2,7 +2,7 @@ import com.google.common.truth.FailureMetadata; import com.google.common.truth.extension.generator.internal.model.ThreeSystem; -import com.google.common.truth.extension.generator.internal.model.UserManagedTruth; +import com.google.common.truth.extension.generator.UserManagedTruth; import javax.annotation.processing.Generated; diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/shaded/org/jboss/forge/roaster/model/sourceChickens/JavaClassSourceSubject.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/shaded/org/jboss/forge/roaster/model/sourceChickens/JavaClassSourceSubject.java index 95cd1532a..7433a5f1b 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/shaded/org/jboss/forge/roaster/model/sourceChickens/JavaClassSourceSubject.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/shaded/org/jboss/forge/roaster/model/sourceChickens/JavaClassSourceSubject.java @@ -2,7 +2,7 @@ import com.google.common.truth.FailureMetadata; import com.google.common.truth.extension.generator.internal.MyStringSubject; -import com.google.common.truth.extension.generator.internal.model.UserManagedTruth; +import com.google.common.truth.extension.generator.UserManagedTruth; import org.jboss.forge.roaster.model.source.JavaClassSource; import javax.annotation.processing.Generated; diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/MyEmployee.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/MyEmployee.java index 6fcaeb1e1..b898b6d42 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/MyEmployee.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/MyEmployee.java @@ -5,10 +5,7 @@ import javax.annotation.Nonnull; import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.UUID; +import java.util.*; import static com.google.common.truth.extension.generator.testModel.MyEmployee.State.EMPLOLYED; @@ -36,6 +33,8 @@ public class MyEmployee extends Person { Optional weighting = Optional.empty(); + Map projectMap; + public MyEmployee(@Nonnull String name, long someLongAspect, @Nonnull ZonedDateTime birthday) { super(name, someLongAspect, birthday); } diff --git a/extensions/generator/src/test/resources/expected/MyEmployeeParentSubject.java.txt b/extensions/generator/src/test/resources/expected/MyEmployeeParentSubject.java.txt index 285369c1a..81d24f170 100644 --- a/extensions/generator/src/test/resources/expected/MyEmployeeParentSubject.java.txt +++ b/extensions/generator/src/test/resources/expected/MyEmployeeParentSubject.java.txt @@ -4,30 +4,36 @@ import com.google.common.truth.Subject; import javax.annotation.processing.Generated; import com.google.common.truth.FailureMetadata; import com.google.common.truth.IntegerSubject; +import static com.google.common.truth.Fact.*; import static com.google.common.truth.extension.generator.testModel.MyEmployeeSubject.myEmployees; import com.google.common.truth.extension.generator.testModel.MyEmployeeSubject; +import com.google.common.truth.extension.generator.testModel.MyEmployee.State; import static com.google.common.truth.extension.generator.testModel.StateSubject.states; import com.google.common.truth.extension.generator.testModel.StateSubject; +import java.util.Optional; +import java.time.ZonedDateTime; import static com.google.common.truth.extension.generator.shaded.java.time.ZonedDateTimeSubject.zonedDateTimes; import static com.google.common.truth.extension.generator.shaded.java.time.ZonedDateTimeSubject.zonedDateTimes; +import java.util.UUID; import static com.google.common.truth.extension.generator.UUIDSubject.uUIDs; import com.google.common.truth.extension.generator.UUIDSubject; import com.google.common.truth.StringSubject; import com.google.common.truth.LongSubject; +import java.util.List; import com.google.common.truth.IterableSubject; -import static com.google.common.truth.Fact.simpleFact; +import java.util.Map; +import com.google.common.truth.MapSubject; import com.google.common.truth.BooleanSubject; import com.google.common.truth.extension.generator.shaded.java.time.ZonedDateTimeSubject; import static com.google.common.truth.extension.generator.testModel.IdCardSubject.idCards; import com.google.common.truth.extension.generator.testModel.IdCardSubject; -import static com.google.common.truth.Fact.simpleFact; /** * Truth Subject for the {@link MyEmployee}. - * + * * Note that this class is generated / managed, and will change over time. So * any changes you might make will be overwritten. - * + * * @see MyEmployee * @see MyEmployeeSubject * @see MyEmployeeChildSubject @@ -43,82 +49,268 @@ public class MyEmployeeParentSubject extends Subject { this.actual = actual; } + public void hasBirthYearNotEqualTo(int expected) { + if (!(actual.getBirthYear() == expected)) { + failWithActual(fact("expected BirthYear NOT to be equal to", expected)); + } + } + + public void hasBirthYearEqualTo(int expected) { + if ((actual.getBirthYear() == expected)) { + failWithActual(fact("expected BirthYear to be equal to", expected)); + } + } + public IntegerSubject hasBirthYear() { isNotNull(); return check("getBirthYear").that(actual.getBirthYear()); } + public void hasBossNotEqualTo(com.google.common.truth.extension.generator.testModel.MyEmployee expected) { + if (!(actual.getBoss().equals(expected))) { + failWithActual(fact("expected Boss NOT to be equal to", expected)); + } + } + + public void hasBossEqualTo(com.google.common.truth.extension.generator.testModel.MyEmployee expected) { + if ((actual.getBoss().equals(expected))) { + failWithActual(fact("expected Boss to be equal to", expected)); + } + } + public MyEmployeeSubject hasBoss() { isNotNull(); return check("getBoss").about(myEmployees()).that(actual.getBoss()); } + public void hasEmploymentStateNotEqualTo(State expected) { + if (!(actual.getEmploymentState().equals(expected))) { + failWithActual(fact("expected EmploymentState NOT to be equal to", expected)); + } + } + + public void hasEmploymentStateEqualTo( + com.google.common.truth.extension.generator.testModel.MyEmployee.State expected) { + if ((actual.getEmploymentState().equals(expected))) { + failWithActual(fact("expected EmploymentState to be equal to", expected)); + } + } + public StateSubject hasEmploymentState() { isNotNull(); return check("getEmploymentState").about(states()).that(actual.getEmploymentState()); } + public void hasWeightingNotPresent(Optional expected) { + if (!actual.getWeighting().isPresent()) { + failWithActual(simpleFact("expected Weighting NOT to be present")); + } + } + + public void hasWeightingPresent(java.util.Optional expected) { + if (actual.getWeighting().isPresent()) { + failWithActual(simpleFact("expected Weighting to be present")); + } + } + + public void hasWeightingNotEqualTo(java.util.Optional expected) { + if (!(actual.getWeighting().equals(expected))) { + failWithActual(fact("expected Weighting NOT to be equal to", expected)); + } + } + + public void hasWeightingEqualTo(java.util.Optional expected) { + if ((actual.getWeighting().equals(expected))) { + failWithActual(fact("expected Weighting to be equal to", expected)); + } + } + + public void hasAnniversaryNotEqualTo(ZonedDateTime expected) { + if (!(actual.getAnniversary().equals(expected))) { + failWithActual(fact("expected Anniversary NOT to be equal to", expected)); + } + } + + public void hasAnniversaryEqualTo(java.time.ZonedDateTime expected) { + if ((actual.getAnniversary().equals(expected))) { + failWithActual(fact("expected Anniversary to be equal to", expected)); + } + } + public ZonedDateTimeSubject hasAnniversary() { isNotNull(); return check("getAnniversary").about(zonedDateTimes()).that(actual.getAnniversary()); } + public void hasIdNotEqualTo(UUID expected) { + if (!(actual.getId().equals(expected))) { + failWithActual(fact("expected Id NOT to be equal to", expected)); + } + } + + public void hasIdEqualTo(java.util.UUID expected) { + if ((actual.getId().equals(expected))) { + failWithActual(fact("expected Id to be equal to", expected)); + } + } + public UUIDSubject hasId() { isNotNull(); return check("getId").about(uUIDs()).that(actual.getId()); } + public void hasNameNotEqualTo(java.lang.String expected) { + if (!(actual.getName().equals(expected))) { + failWithActual(fact("expected Name NOT to be equal to", expected)); + } + } + + public void hasNameEqualTo(java.lang.String expected) { + if ((actual.getName().equals(expected))) { + failWithActual(fact("expected Name to be equal to", expected)); + } + } + public StringSubject hasName() { isNotNull(); return check("getName").that(actual.getName()); } + public void hasSomeLongAspectNotEqualTo(long expected) { + if (!(actual.getSomeLongAspect() == expected)) { + failWithActual(fact("expected SomeLongAspect NOT to be equal to", expected)); + } + } + + public void hasSomeLongAspectEqualTo(long expected) { + if ((actual.getSomeLongAspect() == expected)) { + failWithActual(fact("expected SomeLongAspect to be equal to", expected)); + } + } + public LongSubject hasSomeLongAspect() { isNotNull(); return check("getSomeLongAspect").that(actual.getSomeLongAspect()); } + public void hasProjectListNotWithElement(java.lang.Object expected) { + if (!actual.getProjectList().contains(expected)) { + failWithActual(fact("expected ProjectList NOT to have element", expected)); + } + } + + public void hasProjectListWithElement(java.lang.Object expected) { + if (actual.getProjectList().contains(expected)) { + failWithActual(fact("expected ProjectList to have element", expected)); + } + } + + public void hasProjectListNotEqualTo(List expected) { + if (!(actual.getProjectList().equals(expected))) { + failWithActual(fact("expected ProjectList NOT to be equal to", expected)); + } + } + + public void hasProjectListEqualTo(java.util.List expected) { + if ((actual.getProjectList().equals(expected))) { + failWithActual(fact("expected ProjectList to be equal to", expected)); + } + } + public IterableSubject hasProjectList() { isNotNull(); return check("getProjectList").that(actual.getProjectList()); } - public void isNotEmployed() { - if (actual.isEmployed()) { - failWithActual(simpleFact("expected NOT to be Employed")); + public void hasProjectMapNotWithKey(java.lang.Object expected) { + if (!actual.getProjectMap().containsKey(expected)) { + failWithActual(fact("expected ProjectMap NOT to have key", expected)); } } + public void hasProjectMapWithKey(java.lang.Object expected) { + if (actual.getProjectMap().containsKey(expected)) { + failWithActual(fact("expected ProjectMap to have key", expected)); + } + } + + public void hasProjectMapNotEqualTo(Map expected) { + if (!(actual.getProjectMap().equals(expected))) { + failWithActual(fact("expected ProjectMap NOT to be equal to", expected)); + } + } + + public void hasProjectMapEqualTo(java.util.Map expected) { + if ((actual.getProjectMap().equals(expected))) { + failWithActual(fact("expected ProjectMap to be equal to", expected)); + } + } + + public MapSubject hasProjectMap() { + isNotNull(); + return check("getProjectMap").that(actual.getProjectMap()); + } + public void isEmployed() { - if (!actual.isEmployed()) { + if (actual.isEmployed()) { failWithActual(simpleFact("expected to be Employed")); } } + public void isNotEmployed() { + if (!actual.isEmployed()) { + failWithActual(simpleFact("expected NOT to be Employed")); + } + } + public BooleanSubject hasEmployed() { isNotNull(); return check("isEmployed").that(actual.isEmployed()); } + public void hasBirthdayNotEqualTo(java.time.ZonedDateTime expected) { + if (!(actual.getBirthday().equals(expected))) { + failWithActual(fact("expected Birthday NOT to be equal to", expected)); + } + } + + public void hasBirthdayEqualTo(java.time.ZonedDateTime expected) { + if ((actual.getBirthday().equals(expected))) { + failWithActual(fact("expected Birthday to be equal to", expected)); + } + } + public ZonedDateTimeSubject hasBirthday() { isNotNull(); return check("getBirthday").about(zonedDateTimes()).that(actual.getBirthday()); } + public void hasCardNotEqualTo(com.google.common.truth.extension.generator.testModel.IdCard expected) { + if (!(actual.getCard().equals(expected))) { + failWithActual(fact("expected Card NOT to be equal to", expected)); + } + } + + public void hasCardEqualTo(com.google.common.truth.extension.generator.testModel.IdCard expected) { + if ((actual.getCard().equals(expected))) { + failWithActual(fact("expected Card to be equal to", expected)); + } + } + public IdCardSubject hasCard() { isNotNull(); return check("getCard").about(idCards()).that(actual.getCard()); } - public void isNotEmployedWrapped() { + public void isEmployedWrapped() { if (actual.isEmployedWrapped()) { - failWithActual(simpleFact("expected NOT to be EmployedWrapped")); + failWithActual(simpleFact("expected to be EmployedWrapped")); } } - public void isEmployedWrapped() { + public void isNotEmployedWrapped() { if (!actual.isEmployedWrapped()) { - failWithActual(simpleFact("expected to be EmployedWrapped")); + failWithActual(simpleFact("expected NOT to be EmployedWrapped")); } } @@ -126,4 +318,4 @@ public class MyEmployeeParentSubject extends Subject { isNotNull(); return check("isEmployedWrapped").that(actual.isEmployedWrapped()); } -} \ No newline at end of file +} diff --git a/extensions/generator/src/test/resources/expected/MyEmployeeSubject.java.txt b/extensions/generator/src/test/resources/expected/MyEmployeeSubject.java.txt index 157fb7bdb..6b1c589cc 100644 --- a/extensions/generator/src/test/resources/expected/MyEmployeeSubject.java.txt +++ b/extensions/generator/src/test/resources/expected/MyEmployeeSubject.java.txt @@ -2,6 +2,8 @@ package com.google.common.truth.extension.generator.testModel; import com.google.common.truth.extension.generator.testModel.MyEmployeeParentSubject; import com.google.common.truth.FailureMetadata; +import com.google.common.truth.extension.generator.UserManagedTruth; +import com.google.common.truth.extension.generator.testModel.MyEmployee; import javax.annotation.processing.Generated; /** @@ -13,8 +15,10 @@ import javax.annotation.processing.Generated; * a new one. Note that if the base skeleton of this class ever changes, you * won't automatically get it updated. * + * @see MyEmployee * @see MyEmployeeParentSubject */ +@UserManagedTruth(clazz = MyEmployee.class) @Generated("truth-generator") public class MyEmployeeSubject extends MyEmployeeParentSubject { From 420f4c6985c03c14c046880345737537e6b0ba92 Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Tue, 3 Aug 2021 18:08:44 +0100 Subject: [PATCH 19/32] Don't do protected methods, add simple javadoc, fix multiple calls to generateFrom --- .../extension/generator/SourceClassSets.java | 8 ++++-- .../internal/SubjectMethodGenerator.java | 28 +++++++++++++------ 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/SourceClassSets.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/SourceClassSets.java index 9a9d7c64f..066048b08 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/SourceClassSets.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/SourceClassSets.java @@ -18,7 +18,7 @@ public class SourceClassSets { private final String packageForOverall; private final Set[]> simplePackageOfClasses = new HashSet<>(); - private Set> simpleClasses = new HashSet<>(); + private final Set> simpleClasses = new HashSet<>(); private final Set packageAndClasses = new HashSet<>(); /** @@ -41,8 +41,12 @@ public void generateFrom(String targetPackageName, Class... classes) { packageAndClasses.add(new PackageAndClasses(targetPackageName, classes)); } + public void generateFrom(Class... classes) { + this.simpleClasses.addAll(Arrays.stream(classes).collect(Collectors.toSet())); + } + public void generateFrom(Set> classes) { - this.simpleClasses = classes; + this.simpleClasses.addAll(classes); } /** diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java index 9003cd370..3df71e44e 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java @@ -20,8 +20,7 @@ import java.util.stream.Collectors; import static java.lang.String.format; -import static java.lang.reflect.Modifier.PRIVATE; -import static java.lang.reflect.Modifier.STATIC; +import static java.lang.reflect.Modifier.*; import static java.util.Optional.empty; import static java.util.Optional.ofNullable; import static java.util.function.Predicate.not; @@ -39,7 +38,7 @@ public class SubjectMethodGenerator { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - private final Map compiledSubjects; + private final Map> compiledSubjects; private final Map generatedSubjects; public SubjectMethodGenerator(final Set allTypes) { @@ -48,8 +47,8 @@ public SubjectMethodGenerator(final Set allTypes) { Reflections reflections = new Reflections("com.google.common.truth", "io.confluent"); Set> subTypes = reflections.getSubTypesOf(Subject.class); - Map maps = new HashMap<>(); - subTypes.stream().forEach(x -> maps.put(x.getSimpleName(), x)); + Map> maps = new HashMap<>(); + subTypes.forEach(x -> maps.put(x.getSimpleName(), x)); this.compiledSubjects = maps; } @@ -65,10 +64,10 @@ public void addTests(JavaClassSource parent, Class classUnderTest) { private Collection getMethods(final Class classUnderTest) { Set getters = ReflectionUtils.getAllMethods(classUnderTest, - not(withModifier(PRIVATE)), withPrefix("get"), withParametersCount(0)); + not(withModifier(PRIVATE)), not(withModifier(PROTECTED)), withPrefix("get"), withParametersCount(0)); Set issers = ReflectionUtils.getAllMethods(classUnderTest, - not(withModifier(PRIVATE)), withPrefix("is"), withParametersCount(0)); + not(withModifier(PRIVATE)), not(withModifier(PROTECTED)), withPrefix("is"), withParametersCount(0)); getters.addAll(issers); @@ -182,6 +181,8 @@ private void equalityStrategyGeneric(Method method, JavaClassSource generated, b .setPublic(); newMethod.addParameter(method.getReturnType(), "expected"); + newMethod.getJavaDoc().setText("Simple check for equality for all fields."); + } private void addMapStrategy(Method method, JavaClassSource generated, Class classUnderTest) { @@ -209,6 +210,8 @@ private void addMapStrategyGeneric(Method method, JavaClassSource generated, boo .setBody(body) .setPublic(); newMethod.addParameter(Object.class, "expected"); + + newMethod.getJavaDoc().setText("Check Maps for containing a given key."); } private void addOptionalStrategy(Method method, JavaClassSource generated, Class classUnderTest) { @@ -235,6 +238,8 @@ private void addOptionalStrategyGeneric(Method method, JavaClassSource generated .setBody(body) .setPublic(); newMethod.addParameter(method.getReturnType(), "expected"); + + newMethod.getJavaDoc().setText("Checks Optional fields for presence."); } private void addHasElementStrategy(Method method, JavaClassSource generated, Class classUnderTest) { @@ -262,6 +267,8 @@ private void addHasElementStrategyGeneric(Method method, JavaClassSource generat .setBody(body) .setPublic(); newMethod.addParameter(Object.class, "expected"); + + newMethod.getJavaDoc().setText("Checks if the element is or is not contained in the collection."); } private void addBooleanStrategy(Method method, JavaClassSource generated, Class classUnderTest) { @@ -284,11 +291,14 @@ private void addBooleanGeneric(Method method, JavaClassSource generated, boolean String methodName = removeStart(method.getName(), "is"); methodName = "is" + capitalize(say.toLowerCase()).trim() + methodName; - generated.addMethod() + MethodSource booleanMethod = generated.addMethod(); + booleanMethod .setName(methodName) .setReturnTypeVoid() .setBody(body) .setPublic(); + + booleanMethod.getJavaDoc().setText("Simple is or is not expectation for boolean fields."); } private void addChainStrategy(Method method, JavaClassSource generated, Class returnType) { @@ -344,6 +354,8 @@ private void addChainStrategy(Method method, JavaClassSource generated, Class has.setReturnType(subjectClass.getSubjectSimpleName()); generated.addImport(subjectClass.getSubjectQualifiedName()); + + has.getJavaDoc().setText("Returns the Subject for the given field type, so you can chain on other assertions."); } private boolean methodIsStatic(Method method) { From 352493e3fe59891570c78540c29e5a18377368ca Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Tue, 3 Aug 2021 18:11:41 +0100 Subject: [PATCH 20/32] base package shortcut --- .../common/truth/extension/generator/SourceClassSets.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/SourceClassSets.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/SourceClassSets.java index 066048b08..7fb97b8ef 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/SourceClassSets.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/SourceClassSets.java @@ -28,6 +28,10 @@ public SourceClassSets(final String packageForOverall) { this.packageForOverall = packageForOverall; } + public SourceClassSets(Class packageFromClass) { + this(packageFromClass.getPackage().getName()); + } + public void generateAllFoundInPackagesOf(Class... classes) { simplePackageOfClasses.add(classes); } From c51e46e36bfeceb25f44a582b24c3fe7af87aaec Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Mon, 2 Aug 2021 21:25:19 +0100 Subject: [PATCH 21/32] feature: legacy mode for non bean compatible classes --- extensions/generator/pom.xml | 2 +- .../extension/generator/SourceClassSets.java | 32 ++++++++++- .../generator/internal/SkeletonGenerator.java | 4 ++ .../internal/SubjectMethodGenerator.java | 55 ++++++++++++------- .../generator/internal/TruthGenerator.java | 26 ++++++++- .../generator/internal/model/ThreeSystem.java | 27 ++++++--- .../SubjectMethodGeneratorTests.java | 8 ++- .../generator/TruthGeneratorTest.java | 18 ++++++ .../generator/testModel/NonBeanLegacy.java | 18 ++++++ 9 files changed, 156 insertions(+), 34 deletions(-) create mode 100644 extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/NonBeanLegacy.java diff --git a/extensions/generator/pom.xml b/extensions/generator/pom.xml index dbe87ef44..1b5128899 100644 --- a/extensions/generator/pom.xml +++ b/extensions/generator/pom.xml @@ -55,7 +55,7 @@ reflections 0.9.12 - + org.dom4j dom4j diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/SourceClassSets.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/SourceClassSets.java index 7fb97b8ef..0dc969e41 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/SourceClassSets.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/SourceClassSets.java @@ -20,6 +20,9 @@ public class SourceClassSets { private final Set[]> simplePackageOfClasses = new HashSet<>(); private final Set> simpleClasses = new HashSet<>(); private final Set packageAndClasses = new HashSet<>(); + private final Set> legacyBeans = new HashSet<>(); + private final Set legacyPackageAndClasses = new HashSet<>(); + /** * @param packageForOverall the package to put the overall access points @@ -28,6 +31,13 @@ public SourceClassSets(final String packageForOverall) { this.packageForOverall = packageForOverall; } + /** + * Use the package of the parameter as the base package; + */ + public SourceClassSets(Object packageFromObject) { + this(packageFromObject.getClass().getPackage().getName()); + } + public SourceClassSets(Class packageFromClass) { this(packageFromClass.getPackage().getName()); } @@ -57,20 +67,36 @@ public void generateFrom(Set> classes) { * Shades the given source classes into the base package, suffixed with the source package */ public void generateFromShaded(Class... classes) { + Set packageAndClassesStream = mapToPackageSets(classes); + this.packageAndClasses.addAll(packageAndClassesStream); + } + + private Set mapToPackageSets(Class[] classes) { ImmutableListMultimap> grouped = Multimaps.index(Arrays.asList(classes), Class::getPackage); - grouped.keySet().forEach(x -> { + return grouped.keySet().stream().map(x -> { Class[] classSet = grouped.get(x).toArray(new Class[0]); PackageAndClasses newSet = new PackageAndClasses(getTargetPackageName(x), classSet); - packageAndClasses.add(newSet); - }); + return newSet; + }).collect(Collectors.toSet()); } private String getTargetPackageName(Package p) { return this.packageForOverall + ".shaded." + p.getName(); } + public void generateFromNonBean(Class... nonBeanLegacyClass) { + for (Class beanLegacyClass : nonBeanLegacyClass) { + legacyBeans.add(beanLegacyClass); + } + } + + public void generateFromShadedNonBean(Class... clazzes) { + Set packageAndClassesStream = mapToPackageSets(clazzes); + this.legacyPackageAndClasses.addAll(packageAndClassesStream); + } + @Value public static class PackageAndClasses { String targetPackageName; diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java index b8601e837..ad7f1d841 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java @@ -9,6 +9,7 @@ import com.google.common.truth.extension.generator.internal.model.MiddleClass; import com.google.common.truth.extension.generator.internal.model.ParentClass; import com.google.common.truth.extension.generator.internal.model.ThreeSystem; +import lombok.Setter; import org.jboss.forge.roaster.Roaster; import org.jboss.forge.roaster.model.source.AnnotationSource; import org.jboss.forge.roaster.model.source.JavaClassSource; @@ -35,6 +36,9 @@ public class SkeletonGenerator implements SkeletonGeneratorAPI { private MiddleClass middle; private ParentClass parent; + @Setter + private boolean legacyMode = false; + public SkeletonGenerator(final Optional targetPackageName) { this.targetPackageName = targetPackageName; } diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java index 3df71e44e..0d1b3cb1c 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java @@ -17,6 +17,7 @@ import java.lang.reflect.Method; import java.math.BigDecimal; import java.util.*; +import java.util.function.Predicate; import java.util.stream.Collectors; import static java.lang.String.format; @@ -40,6 +41,7 @@ public class SubjectMethodGenerator { private final Map> compiledSubjects; private final Map generatedSubjects; + private ThreeSystem context; public SubjectMethodGenerator(final Set allTypes) { this.generatedSubjects = allTypes.stream().collect(Collectors.toMap(x -> x.classUnderTest.getSimpleName(), x -> x)); @@ -52,26 +54,38 @@ public SubjectMethodGenerator(final Set allTypes) { this.compiledSubjects = maps; } - - public void addTests(JavaClassSource parent, Class classUnderTest) { - Collection getters = getMethods(classUnderTest); + public void addTests(ThreeSystem system) { + Collection getters = getMethods(system); // for (Method method : getters) { - addFieldAccessors(method, parent, classUnderTest); + this.context = system; + addFieldAccessors(method, system.getParent().getGenerated(), system.getClassUnderTest()); } } - private Collection getMethods(final Class classUnderTest) { - Set getters = ReflectionUtils.getAllMethods(classUnderTest, - not(withModifier(PRIVATE)), not(withModifier(PROTECTED)), withPrefix("get"), withParametersCount(0)); + private Collection getMethods(ThreeSystem system) { + Class classUnderTest = system.getClassUnderTest(); + boolean legacyMode = system.isLegacyMode(); + + Set union = new HashSet<>(); + Set getters = getMethods(classUnderTest, withPrefix("get")); + Set issers = getMethods(classUnderTest, withPrefix("is")); - Set issers = ReflectionUtils.getAllMethods(classUnderTest, - not(withModifier(PRIVATE)), not(withModifier(PROTECTED)), withPrefix("is"), withParametersCount(0)); + // also get all other methods, regardless of their prefix + Predicate expectSetters = not(withPrefix("set")); + Set legacy = (legacyMode) ? getMethods(classUnderTest, expectSetters) : Set.of(); - getters.addAll(issers); + union.addAll(getters); + union.addAll(issers); + union.addAll(legacy); + + return removeOverridden(union); + } - return removeOverridden(getters); + private Set getMethods(Class classUnderTest, Predicate prefix) { + return ReflectionUtils.getAllMethods(classUnderTest, + not(withModifier(PRIVATE)), not(withModifier(PROTECTED)), prefix, withParametersCount(0)); } private Collection removeOverridden(final Set getters) { @@ -120,7 +134,7 @@ private void addFieldAccessors(Method method, JavaClassSource generated, Class returnType = getWrappedReturnType(method); // todo skip static methods for now - just need to make template a bit more advanced - if(methodIsStatic(method)) + if (methodIsStatic(method)) return; if (Boolean.class.isAssignableFrom(returnType)) { @@ -173,7 +187,7 @@ private void equalityStrategyGeneric(Method method, JavaClassSource generated, b String fieldName = removeStart(method.getName(), "get"); body = format(body, testPrefix, method.getName(), equality, fieldName, say); - String methodName = "has" + fieldName + capitalize(say.toLowerCase()).trim() + "EqualTo"; + String methodName = "has" + capitalize(fieldName) + capitalize(say.toLowerCase()).trim() + "EqualTo"; MethodSource newMethod = generated.addMethod(); newMethod.setName(methodName) .setReturnTypeVoid() @@ -202,7 +216,7 @@ private void addMapStrategyGeneric(Method method, JavaClassSource generated, boo String fieldName = removeStart(method.getName(), "get"); body = format(body, testPrefix, method.getName(), fieldName, say); - String methodName = "has" + fieldName + capitalize(say.toLowerCase()).trim() + "WithKey"; + String methodName = "has" + capitalize(fieldName) + capitalize(say.toLowerCase()).trim() + "WithKey"; MethodSource newMethod = generated.addMethod(); newMethod .setName(methodName) @@ -230,7 +244,7 @@ private void addOptionalStrategyGeneric(Method method, JavaClassSource generated String fieldName = removeStart(method.getName(), "get"); body = format(body, testPrefix, method.getName(), fieldName, say); - String methodName = "has" + fieldName + capitalize(say.toLowerCase()).trim() + "Present"; + String methodName = "has" + capitalize(fieldName) + capitalize(say.toLowerCase()).trim() + "Present"; MethodSource newMethod = generated.addMethod(); newMethod .setName(methodName) @@ -259,7 +273,7 @@ private void addHasElementStrategyGeneric(Method method, JavaClassSource generat String say = positive ? "" : "NOT "; body = format(body, testPrefix, method.getName(), fieldName, say); - String methodName = "has" + fieldName + capitalize(say.toLowerCase()).trim() + "WithElement"; + String methodName = "has" + capitalize(fieldName) + capitalize(say.toLowerCase()).trim() + "WithElement"; MethodSource newMethod = generated.addMethod(); newMethod .setName(methodName) @@ -317,7 +331,6 @@ private void addChainStrategy(Method method, JavaClassSource generated, Class ClassOrGenerated subjectClass = subjectForType.get(); - // todo add versions with and with the get String nameForChainMethod = createNameForChainMethod(method); MethodSource has = generated.addMethod() .setName(nameForChainMethod) @@ -367,6 +380,10 @@ private boolean methodIsStatic(Method method) { */ private String createNameForChainMethod(final Method method) { String name = method.getName(); + + if (context.isLegacyMode()) + return "has" + capitalize(name); + if (name.startsWith("get")) { name = removeStart(name, "get"); return "has" + name; @@ -460,8 +477,8 @@ private Optional getSubjectFromGenerated(final String name) { } public void addTests(final Set allTypes) { - for (ThreeSystem c : allTypes) { - addTests(c.parent.getGenerated(), c.classUnderTest); + for (ThreeSystem system : allTypes) { + addTests(system); } // only serialise results, when all have finished - useful for debugging diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java index f0bba7683..663bd6f47 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java @@ -118,7 +118,7 @@ public Map, ThreeSystem> generate(SourceClassSets ss) { x -> generateSkeletonsFromPackages(x, overallEntryPoint).stream() ).collect(Collectors.toSet()); - // custom packge destination + // custom package destination Set packageAndClasses = ss.getPackageAndClasses(); Set setStream = packageAndClasses.stream().flatMap( x -> { @@ -130,8 +130,30 @@ public Map, ThreeSystem> generate(SourceClassSets ss) { // straight up classes Set simpleClasses = generateSkeletons(ss.getSimpleClasses(), Optional.empty(), overallEntryPoint); + // legacy classes + Set legacyClasses = generateSkeletons(ss.getLegacyBeans(), Optional.empty(), overallEntryPoint); + legacyClasses.forEach(x -> x.setLegacyMode(true)); + + // legacy classes with custom package destination + Set legacyPackageAndClasses = ss.getLegacyPackageAndClasses(); + Set legacyPackageSet = legacyPackageAndClasses.stream().flatMap( + x -> { + Set> collect = Arrays.stream(x.getClasses()).collect(Collectors.toSet()); + return generateSkeletons(collect, Optional.of(x.getTargetPackageName()), overallEntryPoint).stream(); + } + ).collect(Collectors.toSet()); + legacyPackageSet.forEach(x -> x.setLegacyMode(true)); + + + // add tests + Set union = new HashSet<>(); + union.addAll(skeletons); + union.addAll(setStream); + union.addAll(simpleClasses); + union.addAll(legacyClasses); + union.addAll(legacyPackageSet); + // - SetView union = union(union(skeletons, setStream), simpleClasses); addTests(union); // create overall entry point diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ThreeSystem.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ThreeSystem.java index a4e54b49f..a4807cac4 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ThreeSystem.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ThreeSystem.java @@ -1,18 +1,31 @@ package com.google.common.truth.extension.generator.internal.model; -import lombok.Value; +import lombok.*; +import org.checkerframework.common.util.report.qual.ReportWrite; import org.jboss.forge.roaster.model.source.JavaClassSource; -@Value +@Getter public class ThreeSystem { - @Override - public String toString() { - return "ThreeSystem{" + - "classUnderTest=" + classUnderTest + '}'; - } + + @Setter + boolean legacyMode = false; public Class classUnderTest; + public ParentClass parent; public MiddleClass middle; public JavaClassSource child; + + public ThreeSystem(Class classUnderTest, ParentClass parent, MiddleClass middle, JavaClassSource child) { + this.classUnderTest = classUnderTest; + this.parent = parent; + this.middle = middle; + this.child = child; + } + + @Override + public String toString() { + return "ThreeSystem{" + + "classUnderTest=" + classUnderTest + '}'; + } } diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/SubjectMethodGeneratorTests.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/SubjectMethodGeneratorTests.java index b45c1e057..65dc067e2 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/SubjectMethodGeneratorTests.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/SubjectMethodGeneratorTests.java @@ -2,12 +2,16 @@ import com.google.common.truth.Truth; import com.google.common.truth.extension.generator.internal.SubjectMethodGenerator; +import com.google.common.truth.extension.generator.internal.model.ParentClass; +import com.google.common.truth.extension.generator.internal.model.ThreeSystem; import com.google.common.truth.extension.generator.testModel.MyEmployee; import org.jboss.forge.roaster.Roaster; import org.jboss.forge.roaster.model.source.JavaClassSource; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import uk.co.jemos.podam.api.PodamFactory; +import uk.co.jemos.podam.api.PodamFactoryImpl; import java.util.Set; @@ -18,8 +22,8 @@ public class SubjectMethodGeneratorTests { public void poc(){ JavaClassSource generated = Roaster.create(JavaClassSource.class); SubjectMethodGenerator subjectMethodGenerator = new SubjectMethodGenerator(Set.of()); - subjectMethodGenerator.addTests(generated, MyEmployee.class); - + ThreeSystem threeSystem = new ThreeSystem(MyEmployee.class, new ParentClass(generated), null, null); + subjectMethodGenerator.addTests(threeSystem); Truth.assertThat(generated.toString()).isEqualTo(""); } } diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java index 7db3320d7..e221e2ccd 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java @@ -9,6 +9,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import uk.co.jemos.podam.api.PodamFactoryImpl; import java.io.IOException; import java.nio.charset.Charset; @@ -23,6 +24,8 @@ @RunWith(JUnit4.class) public class TruthGeneratorTest { + public static final PodamFactoryImpl PODAM_FACTORY = new PodamFactoryImpl(); + private String loadFileToString(String expectedFileName) throws IOException { return Resources.toString(Resources.getResource(expectedFileName), Charset.defaultCharset()); } @@ -139,4 +142,19 @@ public void try_out_assertions() { } + @Test + public void test_legacy_mode(){ + TruthGeneratorAPI tg = TruthGeneratorAPI.create(); + SourceClassSets ss = new SourceClassSets(this); + ss.generateFromNonBean(NonBeanLegacy.class); + tg.generate(ss); + + NonBeanLegacy nonBeanLegacy = createInstance(); + NonBeanLegacyChildSubject.assertThat(nonBeanLegacy).hasAge().isNotNull(); + } + + private NonBeanLegacy createInstance() { + return PODAM_FACTORY.manufacturePojo(NonBeanLegacy.class); + } + } diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/NonBeanLegacy.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/NonBeanLegacy.java new file mode 100644 index 000000000..70039bc8a --- /dev/null +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/NonBeanLegacy.java @@ -0,0 +1,18 @@ +package com.google.common.truth.extension.generator.testModel; + + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class NonBeanLegacy { + final int age; + final String name; + + public int age() { + return age; + } + + public String name() { + return name; + } +} From a28a2dda5f02fdced3ded54713150e3d84a38196 Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Tue, 3 Aug 2021 20:57:23 +0100 Subject: [PATCH 22/32] WIP-START: maven plugin --- .../generator/TruthGeneratorTest.java | 3 +- extensions/plugin-maven/pom.xml | 176 ++++++++++++++++++ extensions/plugin-maven/src/it/settings.xml | 37 ++++ .../plugin-maven/src/it/simple-it/pom.xml | 34 ++++ .../src/it/simple-it/verify.groovy | 3 + .../generator/plugin/GreetingMojo.java | 18 ++ .../com/google/truth/extensions/MyMojo.java | 53 ++++++ .../google/truth/extensions/MyMojoTest.java | 60 ++++++ .../test/resources/project-to-test/pom.xml | 24 +++ extensions/pom.xml | 1 + 10 files changed, 407 insertions(+), 2 deletions(-) create mode 100644 extensions/plugin-maven/pom.xml create mode 100644 extensions/plugin-maven/src/it/settings.xml create mode 100644 extensions/plugin-maven/src/it/simple-it/pom.xml create mode 100644 extensions/plugin-maven/src/it/simple-it/verify.groovy create mode 100644 extensions/plugin-maven/src/main/java/com/google/common/truth/extension/generator/plugin/GreetingMojo.java create mode 100644 extensions/plugin-maven/src/main/java/com/google/truth/extensions/MyMojo.java create mode 100644 extensions/plugin-maven/src/test/java/com/google/truth/extensions/MyMojoTest.java create mode 100644 extensions/plugin-maven/src/test/resources/project-to-test/pom.xml diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java index e221e2ccd..06929d355 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java @@ -3,7 +3,6 @@ import com.google.common.io.Resources; import com.google.common.truth.extension.generator.internal.model.ThreeSystem; import com.google.common.truth.extension.generator.internal.modelSubjectChickens.ThreeSystemChildSubject; -import com.google.common.truth.extension.generator.internal.modelSubjectChickens.ThreeSystemSubject; import com.google.common.truth.extension.generator.testModel.*; import org.jboss.forge.roaster.model.source.JavaClassSource; import org.junit.Test; @@ -150,7 +149,7 @@ public void test_legacy_mode(){ tg.generate(ss); NonBeanLegacy nonBeanLegacy = createInstance(); - NonBeanLegacyChildSubject.assertThat(nonBeanLegacy).hasAge().isNotNull(); +// NonBeanLegacyChildSubject.assertThat(nonBeanLegacy).hasAge().isNotNull(); } private NonBeanLegacy createInstance() { diff --git a/extensions/plugin-maven/pom.xml b/extensions/plugin-maven/pom.xml new file mode 100644 index 000000000..d43d52b6b --- /dev/null +++ b/extensions/plugin-maven/pom.xml @@ -0,0 +1,176 @@ + + + + com.google.truth.extensions + truth-extensions-parent + HEAD-SNAPSHOT + + 4.0.0 + + + truth-generator-maven-plugin + Truth Generator Maven PLugin + + + + 8 + 8 + 3.3.9 + UTF-8 + + + + + org.apache.maven + maven-plugin-api + ${maven.version} + provided + + + org.apache.maven + maven-core + ${maven.version} + provided + + + org.apache.maven + maven-artifact + ${maven.version} + provided + + + org.apache.maven + maven-compat + ${maven.version} + test + + + org.apache.maven.plugin-tools + maven-plugin-annotations + 3.6.0 + provided + + + junit + junit + 4.12 + test + + + org.apache.maven.plugin-testing + maven-plugin-testing-harness + 3.3.0 + test + + + + + + + + maven-clean-plugin + 3.1.0 + + + + maven-resources-plugin + 3.0.2 + + + maven-compiler-plugin + 3.8.0 + + + maven-plugin-plugin + 3.6.0 + + + maven-surefire-plugin + 2.22.1 + + + maven-jar-plugin + 3.0.2 + + + maven-install-plugin + 2.5.2 + + + maven-deploy-plugin + 2.8.2 + + + maven-invoker-plugin + 3.1.0 + + + + + + org.apache.maven.plugins + maven-plugin-plugin + 3.6.0 + + + true + + + + mojo-descriptor + + descriptor + + + + help-goal + + helpmojo + + + + + + + + + + run-its + + + + org.apache.maven.plugins + maven-invoker-plugin + 3.1.0 + + true + ${project.build.directory}/it + + */pom.xml + + verify + ${project.build.directory}/local-repo + src/it/settings.xml + + clean + test-compile + + + + + integration-test + + install + integration-test + verify + + + + + + + + + diff --git a/extensions/plugin-maven/src/it/settings.xml b/extensions/plugin-maven/src/it/settings.xml new file mode 100644 index 000000000..000e72667 --- /dev/null +++ b/extensions/plugin-maven/src/it/settings.xml @@ -0,0 +1,37 @@ + + + + + + + it-repo + + true + + + + local.central + @localRepositoryUrl@ + + true + + + true + + + + + + local.central + @localRepositoryUrl@ + + true + + + true + + + + + + diff --git a/extensions/plugin-maven/src/it/simple-it/pom.xml b/extensions/plugin-maven/src/it/simple-it/pom.xml new file mode 100644 index 000000000..be81f7bb2 --- /dev/null +++ b/extensions/plugin-maven/src/it/simple-it/pom.xml @@ -0,0 +1,34 @@ + + + 4.0.0 + + sample.plugin.it + simple-it + 1.0-SNAPSHOT + + A simple IT verifying the basic use case. + + + UTF-8 + + + + + + @project.groupId@ + @project.artifactId@ + @project.version@ + + + touch + validate + + touch + + + + + + + diff --git a/extensions/plugin-maven/src/it/simple-it/verify.groovy b/extensions/plugin-maven/src/it/simple-it/verify.groovy new file mode 100644 index 000000000..7b307c785 --- /dev/null +++ b/extensions/plugin-maven/src/it/simple-it/verify.groovy @@ -0,0 +1,3 @@ +File touchFile = new File( basedir, "target/touch.txt" ); + +assert touchFile.isFile() diff --git a/extensions/plugin-maven/src/main/java/com/google/common/truth/extension/generator/plugin/GreetingMojo.java b/extensions/plugin-maven/src/main/java/com/google/common/truth/extension/generator/plugin/GreetingMojo.java new file mode 100644 index 000000000..4ade20b19 --- /dev/null +++ b/extensions/plugin-maven/src/main/java/com/google/common/truth/extension/generator/plugin/GreetingMojo.java @@ -0,0 +1,18 @@ +package com.google.common.truth.extension.generator.plugin; + +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.Mojo; + +/** + * Says "Hi" to the user. + * + */ +@Mojo( name = "sayhi") +public class GreetingMojo extends AbstractMojo +{ + public void execute() throws MojoExecutionException + { + getLog().info( "Hello, world." ); + } +} diff --git a/extensions/plugin-maven/src/main/java/com/google/truth/extensions/MyMojo.java b/extensions/plugin-maven/src/main/java/com/google/truth/extensions/MyMojo.java new file mode 100644 index 000000000..9a39a5e76 --- /dev/null +++ b/extensions/plugin-maven/src/main/java/com/google/truth/extensions/MyMojo.java @@ -0,0 +1,53 @@ +package com.google.truth.extensions; + + +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + +/** + * Goal which touches a timestamp file. + */ +@Mojo(name = "touch", defaultPhase = LifecyclePhase.PROCESS_SOURCES) +public class MyMojo extends AbstractMojo { + + /** + * Location of the file. + */ + @Parameter(defaultValue = "${project.build.directory}", property = "outputDir", required = true) + private File outputDirectory; + + public void execute() + throws MojoExecutionException { + File f = outputDirectory; + + if (!f.exists()) { + f.mkdirs(); + } + + File touch = new File(f, "touch.txt"); + + FileWriter w = null; + try { + w = new FileWriter(touch); + + w.write("touch.txt"); + } catch (IOException e) { + throw new MojoExecutionException("Error creating file " + touch, e); + } finally { + if (w != null) { + try { + w.close(); + } catch (IOException e) { + // ignore + } + } + } + } +} diff --git a/extensions/plugin-maven/src/test/java/com/google/truth/extensions/MyMojoTest.java b/extensions/plugin-maven/src/test/java/com/google/truth/extensions/MyMojoTest.java new file mode 100644 index 000000000..c04c3e264 --- /dev/null +++ b/extensions/plugin-maven/src/test/java/com/google/truth/extensions/MyMojoTest.java @@ -0,0 +1,60 @@ +package com.google.truth.extensions; + + +import org.apache.maven.plugin.testing.MojoRule; +import org.apache.maven.plugin.testing.WithoutMojo; +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class MyMojoTest { + + @Rule + public MojoRule rule = new MojoRule() { + @Override + protected void before() throws Throwable { + } + + @Override + protected void after() { + } + }; + + /** + * @throws Exception if any + */ + @Test + public void testSomething() + throws Exception { + File pom = new File("target/test-classes/project-to-test/"); + assertNotNull(pom); + assertTrue(pom.exists()); + + MyMojo myMojo = (MyMojo) rule.lookupConfiguredMojo(pom, "touch"); + assertNotNull(myMojo); + myMojo.execute(); + + File outputDirectory = (File) rule.getVariableValueFromObject(myMojo, "outputDirectory"); + assertNotNull(outputDirectory); + assertTrue(outputDirectory.exists()); + + File touch = new File(outputDirectory, "touch.txt"); + assertTrue(touch.exists()); + + } + + /** + * Do not need the MojoRule. + */ + @WithoutMojo + @Test + public void testSomethingWhichDoesNotNeedTheMojoAndProbablyShouldBeExtractedIntoANewClassOfItsOwn() { + assertTrue(true); + } + +} + diff --git a/extensions/plugin-maven/src/test/resources/project-to-test/pom.xml b/extensions/plugin-maven/src/test/resources/project-to-test/pom.xml new file mode 100644 index 000000000..8135a5439 --- /dev/null +++ b/extensions/plugin-maven/src/test/resources/project-to-test/pom.xml @@ -0,0 +1,24 @@ + + + 4.0.0 + + sample.plugin + hello-maven-plugin + HEAD-SNAPSHOT + jar + Test MyMojo + + + + + maven-my-plugin + + + target/test-harness/project-to-test + + + + + diff --git a/extensions/pom.xml b/extensions/pom.xml index 58bde545b..feb55a230 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -23,5 +23,6 @@ liteproto proto generator + plugin-maven From 145fcbcfc066d7d7cf5d186793f5234937f2d4ed Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Thu, 12 Aug 2021 14:07:09 +0100 Subject: [PATCH 23/32] feature: Working plugin, recursive generation, auto shading --- extensions/generator-assertions-tests/pom.xml | 69 +++ .../generator/GeneratedAssertionTests.java | 89 ++++ extensions/generator/pom.xml | 16 + .../generator/GeneratorException.java | 7 + .../extension/generator/SourceClassSets.java | 59 ++- .../generator/TruthGeneratorAPI.java | 4 +- .../generator/internal/OverallEntryPoint.java | 21 +- .../generator/internal/RecursiveChecker.java | 91 ++++ .../generator/internal/SkeletonGenerator.java | 12 +- .../generator/internal/SourceChecking.java | 22 +- .../internal/SubjectMethodGenerator.java | 223 ++++++--- .../generator/internal/TruthGenerator.java | 57 ++- .../generator/internal/model/ThreeSystem.java | 53 ++- .../generator/TruthGeneratorTest.java | 89 ++-- extensions/plugin-maven/pom.xml | 59 ++- .../plugin-maven/src/it/simple-it/pom.xml | 9 +- .../src/it/simple-it/verify.groovy | 3 +- .../generator/plugin/GeneratorMojo.java | 176 +++++++ .../generator/plugin/GreetingMojo.java | 18 - .../com/google/truth/extensions/MyMojo.java | 53 --- .../truth/extensions/GeneratorMojoTest.java | 81 ++++ .../google/truth/extensions/MyMojoTest.java | 60 --- .../com/google/truth/extensions/Subjects.java | 22 + .../plugin/GeneratorMojoChildSubject.java | 48 ++ .../plugin/GeneratorMojoParentSubject.java | 371 +++++++++++++++ .../plugin/GeneratorMojoSubject.java | 35 ++ .../generator/plugin/ManagedTruth.java | 51 ++ .../shaded/java/io/FileChildSubject.java | 47 ++ .../shaded/java/io/FileParentSubject.java | 441 ++++++++++++++++++ .../shaded/java/io/FileSubject.java | 48 ++ .../test/resources/project-to-test/pom.xml | 52 ++- extensions/pom.xml | 3 +- 32 files changed, 2088 insertions(+), 301 deletions(-) create mode 100644 extensions/generator-assertions-tests/pom.xml create mode 100644 extensions/generator-assertions-tests/src/test/java/com/google/common/truth/extension/generator/GeneratedAssertionTests.java create mode 100644 extensions/generator/src/main/java/com/google/common/truth/extension/generator/GeneratorException.java create mode 100644 extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/RecursiveChecker.java create mode 100644 extensions/plugin-maven/src/main/java/com/google/common/truth/extension/generator/plugin/GeneratorMojo.java delete mode 100644 extensions/plugin-maven/src/main/java/com/google/common/truth/extension/generator/plugin/GreetingMojo.java delete mode 100644 extensions/plugin-maven/src/main/java/com/google/truth/extensions/MyMojo.java create mode 100644 extensions/plugin-maven/src/test/java/com/google/truth/extensions/GeneratorMojoTest.java delete mode 100644 extensions/plugin-maven/src/test/java/com/google/truth/extensions/MyMojoTest.java create mode 100644 extensions/plugin-maven/src/test/java/com/google/truth/extensions/Subjects.java create mode 100644 extensions/plugin-maven/src/test/java/com/google/truth/extensions/generator/plugin/GeneratorMojoChildSubject.java create mode 100644 extensions/plugin-maven/src/test/java/com/google/truth/extensions/generator/plugin/GeneratorMojoParentSubject.java create mode 100644 extensions/plugin-maven/src/test/java/com/google/truth/extensions/generator/plugin/GeneratorMojoSubject.java create mode 100644 extensions/plugin-maven/src/test/java/com/google/truth/extensions/generator/plugin/ManagedTruth.java create mode 100644 extensions/plugin-maven/src/test/java/com/google/truth/extensions/shaded/java/io/FileChildSubject.java create mode 100644 extensions/plugin-maven/src/test/java/com/google/truth/extensions/shaded/java/io/FileParentSubject.java create mode 100644 extensions/plugin-maven/src/test/java/com/google/truth/extensions/shaded/java/io/FileSubject.java diff --git a/extensions/generator-assertions-tests/pom.xml b/extensions/generator-assertions-tests/pom.xml new file mode 100644 index 000000000..1aa0936c0 --- /dev/null +++ b/extensions/generator-assertions-tests/pom.xml @@ -0,0 +1,69 @@ + + + 4.0.0 + + + com.google.truth.extensions + truth-extensions-parent + HEAD-SNAPSHOT + + + generator-assertions-tests + HEAD-SNAPSHOT + + + + + + + + + + + + + + + + + + + + + + + + + com.google.truth + truth + compile + + + com.google.truth.extensions + truth-java8-extension + compile + ${project.version} + + + com.google.truth.extensions + truth-generator-extension + test-jar + ${project.version} + test + + + org.projectlombok + lombok + 1.18.20 + + + uk.co.jemos.podam + podam + 7.2.7.RELEASE + compile + + + + \ No newline at end of file diff --git a/extensions/generator-assertions-tests/src/test/java/com/google/common/truth/extension/generator/GeneratedAssertionTests.java b/extensions/generator-assertions-tests/src/test/java/com/google/common/truth/extension/generator/GeneratedAssertionTests.java new file mode 100644 index 000000000..5b76c3f09 --- /dev/null +++ b/extensions/generator-assertions-tests/src/test/java/com/google/common/truth/extension/generator/GeneratedAssertionTests.java @@ -0,0 +1,89 @@ +package com.google.common.truth.extension.generator; + +import com.google.common.truth.Truth; +import com.google.common.truth.extension.generator.testModel.*; +import org.apache.commons.lang3.builder.ToStringExclude; +import org.junit.Test; +import uk.co.jemos.podam.api.PodamFactoryImpl; + +import java.time.LocalDate; +import java.time.ZonedDateTime; + +import static com.google.common.truth.extension.generator.ManagedTruth.assertTruth; +import static com.google.common.truth.extension.generator.testModel.MyEmployeeChildSubject.assertTruth; + +/** + * @see TruthGeneratorTest#generate_code + */ +public class GeneratedAssertionTests { + + public static final PodamFactoryImpl PODAM_FACTORY = new PodamFactoryImpl(); + + @Test + public void try_out_assertions() { + // all asserts should be available + MyEmployee hi = createInstance(MyEmployee.class); + hi = hi.toBuilder() + .name("Zeynep") + .boss(createInstance(MyEmployee.class)) + .build(); + + assertTruth(hi).hasBirthYear().isAtLeast(200); + Truth.assertThat(hi.getBirthYear()).isAtLeast(200); + + Truth.assertThat(hi.getBoss().getName()).contains("Tony"); + assertTruth(hi).hasBoss().hasName().contains("Tony"); +// assertTruth(hi).hasCard().hasEpoch().isAtLeast(20); + assertTruth(hi).hasProjectList().hasSize(3); + MyEmployeeSubject myEmployeeSubject = assertTruth(hi); + + MyEmployeeChildSubject.assertThat(TestModelUtils.createEmployee()).hasProjectMapWithKey("key"); + } + + /** + * @see TruthGeneratorTest#test_legacy_mode + */ + @Test + public void test_legacy_mode() { + NonBeanLegacy nonBeanLegacy = createInstance(NonBeanLegacy.class); +// NonBeanLegacySubject nonBeanLegacySubject = NonBeanLegacyChildSubject.assertThat(nonBeanLegacy); +// nonBeanLegacySubject.hasAge().isNotNull(); +// nonBeanLegacySubject.hasName().isEqualTo("lilan"); + } + + private T createInstance(Class clazz) { + return PODAM_FACTORY.manufacturePojo(clazz); + } + + @Test + public void recursive() { + MyEmployee emp = createInstance(MyEmployee.class); + MyEmployeeSubject es = ManagedTruth.assertThat(emp); + +// ZonedDateTime anniversary = emp.getAnniversary(); +// anniversary.toLocalDate(). +// anniversary. + } + + @Test + public void as_type_chain_transformers() { + MyEmployee emp = createInstance(MyEmployee.class); + MyEmployeeSubject es = ManagedTruth.assertThat(emp); + + LocalDate localDate = emp.getAnniversary().toLocalDate(); + + es.hasAnniversary().toLocalDate().hasEra(); + es.hasAnniversary().toLocalDateTime().toString(); + } + + @Test + public void enums(){ + MyEmployee emp = createInstance(MyEmployee.class); + MyEmployeeSubject es = ManagedTruth.assertThat(emp); + + es.hasAnniversary() + es.hasEmploymentState(); + } + + +} \ No newline at end of file diff --git a/extensions/generator/pom.xml b/extensions/generator/pom.xml index 1b5128899..fae40cc6b 100644 --- a/extensions/generator/pom.xml +++ b/extensions/generator/pom.xml @@ -33,6 +33,12 @@ truth compile + + com.google.truth.extensions + truth-java8-extension + compile + ${project.version} + com.google.truth truth @@ -116,6 +122,16 @@ 13 + + maven-jar-plugin + + + + test-jar + + + + diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/GeneratorException.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/GeneratorException.java new file mode 100644 index 000000000..dd7d62fce --- /dev/null +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/GeneratorException.java @@ -0,0 +1,7 @@ +package com.google.common.truth.extension.generator; + +public class GeneratorException extends RuntimeException { + public GeneratorException(final String s, final ClassNotFoundException e) { + super(s, e); + } +} diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/SourceClassSets.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/SourceClassSets.java index 0dc969e41..bc3f48371 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/SourceClassSets.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/SourceClassSets.java @@ -10,6 +10,9 @@ import java.util.Set; import java.util.stream.Collectors; +import static java.util.Arrays.asList; +import static java.util.Arrays.stream; + /** * Use this class to prepare the set of source classes to generate for, and settings for different types of sources. */ @@ -22,12 +25,13 @@ public class SourceClassSets { private final Set packageAndClasses = new HashSet<>(); private final Set> legacyBeans = new HashSet<>(); private final Set legacyPackageAndClasses = new HashSet<>(); + private Set> classSetCache; /** * @param packageForOverall the package to put the overall access points */ - public SourceClassSets(final String packageForOverall) { + public SourceClassSets(String packageForOverall) { this.packageForOverall = packageForOverall; } @@ -56,7 +60,7 @@ public void generateFrom(String targetPackageName, Class... classes) { } public void generateFrom(Class... classes) { - this.simpleClasses.addAll(Arrays.stream(classes).collect(Collectors.toSet())); + this.simpleClasses.addAll(stream(classes).collect(Collectors.toSet())); } public void generateFrom(Set> classes) { @@ -72,7 +76,7 @@ public void generateFromShaded(Class... classes) { } private Set mapToPackageSets(Class[] classes) { - ImmutableListMultimap> grouped = Multimaps.index(Arrays.asList(classes), Class::getPackage); + ImmutableListMultimap> grouped = Multimaps.index(asList(classes), Class::getPackage); return grouped.keySet().stream().map(x -> { Class[] classSet = grouped.get(x).toArray(new Class[0]); @@ -97,6 +101,55 @@ public void generateFromShadedNonBean(Class... clazzes) { this.legacyPackageAndClasses.addAll(packageAndClassesStream); } + public void generateFrom(ClassLoader loader, String... classes) { + Class[] as = stream(classes).map(x -> { + try { + return loader.loadClass(x); + } catch (ClassNotFoundException e) { + throw new GeneratorException("Cannot find class asked to generate from: " + x, e); + } + }).collect(Collectors.toList()).toArray(new Class[0]); + generateFrom(as); + } + + // todo shouldn't be public? + public Set> getAllClasses() { + Set> union = new HashSet<>(); + union.addAll(getSimpleClasses()); + union.addAll(getLegacyBeans()); + + Set> collect = getPackageAndClasses().stream().flatMap(x -> + stream(x.classes) + ).collect(Collectors.toSet()); + union.addAll(collect); + + union.addAll(getLegacyPackageAndClasses().stream().flatMap(x -> stream(x.classes)).collect(Collectors.toSet())); + + union.addAll(getSimplePackageOfClasses().stream().flatMap(x -> stream(x)).collect(Collectors.toSet())); + + // todo need more elegant solution than this + this.classSetCache = union; + return union; + } + + // todo shouldn't be public? + public void addIfMissing(final Set> clazzes) { + clazzes.forEach(x -> { + if (!classSetCache.contains(x)) + generateFrom(x); + }); + } + + // todo shouldn't be public? + public boolean isClassIncluded(final Class clazz) { + return classSetCache.contains(clazz); + } + + public boolean isLegacyClass(final Class theClass) { + return getLegacyBeans().contains(theClass) + || getLegacyPackageAndClasses().stream().anyMatch(x-> asList(x.classes).contains(theClass)); + } + @Value public static class PackageAndClasses { String targetPackageName; diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGeneratorAPI.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGeneratorAPI.java index c502adf9c..2e53a7bf3 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGeneratorAPI.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGeneratorAPI.java @@ -11,7 +11,7 @@ */ public interface TruthGeneratorAPI { - static TruthGeneratorAPI create() { + static TruthGenerator create() { return new TruthGenerator(); } @@ -65,5 +65,5 @@ static TruthGeneratorAPI create() { */ Map, ThreeSystem> generate(Set> classes); - void generate(Class... classes); + Map, ThreeSystem> generate(Class... classes); } diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/OverallEntryPoint.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/OverallEntryPoint.java index dd9d59af2..4bd3e48b8 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/OverallEntryPoint.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/OverallEntryPoint.java @@ -1,6 +1,9 @@ package com.google.common.truth.extension.generator.internal; +import com.google.common.flogger.FluentLogger; import com.google.common.truth.extension.generator.internal.model.ThreeSystem; +import lombok.Getter; +import lombok.RequiredArgsConstructor; import org.jboss.forge.roaster.Roaster; import org.jboss.forge.roaster.model.Method; import org.jboss.forge.roaster.model.source.Import; @@ -9,19 +12,27 @@ import java.util.ArrayList; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; import static com.google.common.truth.extension.generator.internal.Utils.writeToDisk; +@RequiredArgsConstructor public class OverallEntryPoint { + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + private final List children = new ArrayList<>(); + @Getter + private final String packageName; + /** * Having collected together all the access points, creates one large class filled with access points to all of them. *

* The overall access will throw an error if any middle classes don't correctly extend their parent. */ - public void createOverallAccessPoints(String packageName) { + public void createOverallAccessPoints() { JavaClassSource overallAccess = Roaster.create(JavaClassSource.class); overallAccess.setName("ManagedTruth"); overallAccess.getJavaDoc() @@ -40,7 +51,13 @@ public void createOverallAccessPoints(String packageName) { // none extra at all (aside from wild card vs specific methods). List imports = child.getImports(); for (Import i : imports) { - overallAccess.addImport(i); + // roaster just throws up a NPE when this happens + Set simpleNames = overallAccess.getImports().stream().map(Import::getSimpleName).filter(x -> !x.equals("*")).collect(Collectors.toSet()); + if (simpleNames.contains(i.getSimpleName())) { + logger.atWarning().log("Collision of imports - cannot add duplicated import %s", i); + } else { + overallAccess.addImport(i); + } } } diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/RecursiveChecker.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/RecursiveChecker.java new file mode 100644 index 000000000..087058e39 --- /dev/null +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/RecursiveChecker.java @@ -0,0 +1,91 @@ +package com.google.common.truth.extension.generator.internal; + +import com.google.common.flogger.FluentLogger; +import com.google.common.truth.extension.generator.SourceClassSets; +import org.apache.commons.lang3.ClassUtils; +import org.apache.commons.lang3.StringUtils; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.apache.commons.lang3.StringUtils.startsWithAny; + +public class RecursiveChecker { + + private static final FluentLogger log = FluentLogger.forEnclosingClass(); + + private HashSet> seen = new HashSet<>(); + + private String[] allowablePrexis = new String[]{"is", "get", "to"}; + + public void addReferencedIncluded(final SourceClassSets ss) { + Set> referencedNotIncluded = findReferencedNotIncluded(ss); + ss.addIfMissing(referencedNotIncluded); + } + + public Set> findReferencedNotIncluded(SourceClassSets ss) { + Set> union = ss.getAllClasses(); + + final Set> visited = new HashSet<>(); + + // for all the classes + union.stream().flatMap(x -> + // for all the methods + recursive(x, ss, visited) + ) + .collect(Collectors.toSet()); + + return seen; + } + + private Stream> recursive(Class theClass, SourceClassSets ss, Set> visited) { + if (visited.contains(theClass)) + return Stream.of(theClass); // terminal + else + visited.add(theClass); + + // gather al the types, and add myself to the list (in case none of my methods return me) + // not sure if there's a way to do this while remaining in a stream (you can't add elements to a stream, except from it's source) + Method[] methods = theClass.getMethods(); + List> methodReturnTypes = Arrays.stream(methods) + // only no param methods (getters, issers) + .filter(y -> y.getParameterCount() == 0) + // don't filter method names on legacy classes + .filter(m -> ss.isLegacyClass(theClass) || startsWithAny(m.getName(), allowablePrexis)) + // for all the return types + .map((Method method) -> ClassUtils.primitiveToWrapper(method.getReturnType())) + .collect(Collectors.toList()); + + // + methodReturnTypes.add(theClass); + + // transform / filter + List> filtered = methodReturnTypes.stream() + .map(y -> { + if (y.isArray()) { + return y.getComponentType(); + } else { + return y; + } + }) + .filter(cls -> !seen.contains(cls)) + .filter(cls -> !Void.TYPE.isAssignableFrom(cls) && !cls.isPrimitive() && !cls.equals(Object.class)) + .filter(type -> !SubjectMethodGenerator.getNativeTypes().contains(type) && !ss.isClassIncluded(type)) + .collect(Collectors.toList()); + + if (!filtered.isEmpty()) { + log.atInfo().log("Adding return types from %s : %s", theClass, + filtered.stream().map(Class::getSimpleName).collect(Collectors.toList())); + } + + seen.addAll(filtered); + + return filtered.stream().flatMap(type -> recursive(type, ss, visited)); + } + +} diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java index ad7f1d841..3cd588e83 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java @@ -31,16 +31,19 @@ public class SkeletonGenerator implements SkeletonGeneratorAPI { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + private static final String BACKUP_PACKAGE = "com.google.common.truth.extension.generator"; + private final OverallEntryPoint overallEntryPoint; - private final Optional targetPackageName; + private Optional targetPackageName; private MiddleClass middle; private ParentClass parent; @Setter private boolean legacyMode = false; - public SkeletonGenerator(final Optional targetPackageName) { + public SkeletonGenerator(Optional targetPackageName, OverallEntryPoint overallEntryPoint) { this.targetPackageName = targetPackageName; + this.overallEntryPoint = overallEntryPoint; } @Override @@ -71,6 +74,11 @@ public Optional threeLayerSystem(Class source) { if (SourceChecking.checkSource(source, targetPackageName)) return empty(); + // todo make sure this doesn';'t override explicit shading settings + if (SourceChecking.needsShading(source)) { + targetPackageName = of(this.overallEntryPoint.getPackageName() + ".autoShaded." + source.getPackage().getName()); + } + ParentClass parent = createParent(source); this.parent = parent; diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SourceChecking.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SourceChecking.java index 0a3e99be1..265d59664 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SourceChecking.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SourceChecking.java @@ -4,8 +4,11 @@ import org.apache.commons.lang3.StringUtils; import org.reflections.ReflectionUtils; +import java.lang.module.Configuration; +import java.lang.module.ModuleDescriptor; import java.util.Arrays; import java.util.Optional; +import java.util.Set; import java.util.logging.Level; public class SourceChecking { @@ -22,15 +25,32 @@ static boolean checkSource(Class source, Optional targetPackage) { if (isTestClass(source)) return true; - if (isJavaPackage(source, targetPackage)) + if (SubjectMethodGenerator.getNativeTypes().contains(source)) return true; return false; } + public static boolean needsShading(Class source) { + // todo needs to be more sophisticated and compare modules + + Module sourceModule = source.getModule(); + Module myModule = SourceChecking.class.getModule(); + Set packages = sourceModule.getPackages(); + ModuleDescriptor descriptor = sourceModule.getDescriptor(); + ModuleLayer layer = sourceModule.getLayer(); + + Package aPackage = source.getPackage(); + + Class componentType = source.getComponentType(); + boolean array = source.isArray(); + + return source.getPackage().getName().startsWith("java."); + } private static boolean isJavaPackage(Class source, Optional targetPackage) { boolean isBlank = targetPackage.isEmpty() || StringUtils.isBlank(targetPackage.get()); + if (source.getPackage().getName().startsWith("java.") && isBlank) throw new IllegalArgumentException("Cannot construct Subject's for external modules without changing their " + "destination package. See SourceClassSets#generateFrom(String, Class...)"); diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java index 0d1b3cb1c..7e09edca9 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java @@ -6,6 +6,7 @@ import com.google.common.truth.ObjectArraySubject; import com.google.common.truth.Subject; import com.google.common.truth.extension.generator.internal.model.ThreeSystem; +import lombok.Getter; import org.apache.commons.lang3.ClassUtils; import org.apache.commons.lang3.StringUtils; import org.jboss.forge.roaster.model.source.Import; @@ -14,21 +15,24 @@ import org.reflections.ReflectionUtils; import org.reflections.Reflections; +import javax.swing.text.html.Option; +import java.lang.reflect.Member; import java.lang.reflect.Method; import java.math.BigDecimal; import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; +import java.util.stream.Stream; import static java.lang.String.format; import static java.lang.reflect.Modifier.*; -import static java.util.Optional.empty; -import static java.util.Optional.ofNullable; +import static java.util.Optional.*; import static java.util.function.Predicate.not; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; -import static org.apache.commons.lang3.StringUtils.capitalize; -import static org.apache.commons.lang3.StringUtils.removeStart; +import static org.apache.commons.lang3.ClassUtils.primitiveToWrapper; +import static org.apache.commons.lang3.ClassUtils.primitivesToWrappers; +import static org.apache.commons.lang3.StringUtils.*; import static org.reflections.ReflectionUtils.*; /** @@ -44,7 +48,7 @@ public class SubjectMethodGenerator { private ThreeSystem context; public SubjectMethodGenerator(final Set allTypes) { - this.generatedSubjects = allTypes.stream().collect(Collectors.toMap(x -> x.classUnderTest.getSimpleName(), x -> x)); + this.generatedSubjects = allTypes.stream().collect(Collectors.toMap(x -> x.classUnderTest.getName(), x -> x)); Reflections reflections = new Reflections("com.google.common.truth", "io.confluent"); Set> subTypes = reflections.getSubTypesOf(Subject.class); @@ -55,15 +59,39 @@ public SubjectMethodGenerator(final Set allTypes) { } public void addTests(ThreeSystem system) { - Collection getters = getMethods(system); + this.context = system; // - for (Method method : getters) { - this.context = system; - addFieldAccessors(method, system.getParent().getGenerated(), system.getClassUnderTest()); + Class classUnderTest = system.getClassUnderTest(); + JavaClassSource generated = system.getParent().getGenerated(); + + // + { + Collection getters = getMethods(system); + for (Method method : getters) { + addFieldAccessors(method, generated, classUnderTest); + } + } + + // + { + Collection toers = getMethods(classUnderTest, input -> { + if (input == null) return false; + String name = input.getName(); + // exclude lombok builder methods + return startsWith("to", name) && !endsWith("Builder", name); + }); + toers = removeOverridden(toers); + for (Method method : toers) { + addChainStrategy(method, generated, method.getReturnType()); + } } } + private Predicate withSuffix(String suffix) { + return input -> input != null && input.getName().startsWith(suffix); + } + private Collection getMethods(ThreeSystem system) { Class classUnderTest = system.getClassUnderTest(); boolean legacyMode = system.isLegacyMode(); @@ -84,11 +112,16 @@ private Collection getMethods(ThreeSystem system) { } private Set getMethods(Class classUnderTest, Predicate prefix) { + // if shaded, can't access package private methods + boolean isShaded = context.isShaded(); + Predicate skip = (ignore) -> true; + Predicate shadedPredicate = (isShaded) ? withModifier(PUBLIC) : skip; + return ReflectionUtils.getAllMethods(classUnderTest, - not(withModifier(PRIVATE)), not(withModifier(PROTECTED)), prefix, withParametersCount(0)); + not(withModifier(PRIVATE)), not(withModifier(PROTECTED)), shadedPredicate, prefix, withParametersCount(0)); } - private Collection removeOverridden(final Set getters) { + private Collection removeOverridden(final Collection getters) { Map result = new HashMap<>(); for (Method getter : getters) { String sig = getSignature(getter); @@ -117,18 +150,38 @@ private String getSignature(final Method getter) { /** * In priority order - most specific first */ - private final HashSet> nativeTypes = Sets.newHashSet( - Map.class, - Set.class, - List.class, - Iterable.class, - Number.class, - Throwable.class, - BigDecimal.class, - String.class, - Comparable.class, - Class.class // Enum#getDeclaringClass - ); + @Getter + private static final HashSet> nativeTypes = new LinkedHashSet(); + + @Getter + private static final HashSet> nativeTypesTruth8 = new LinkedHashSet(); + + static { + // + Class[] classes = { + Optional.class, + Map.class, + Iterable.class, + List.class, + Set.class, + Number.class, + Throwable.class, + BigDecimal.class, + String.class, + Comparable.class, + Class.class, // Enum#getDeclaringClass + Double.class, + Long.class, + Integer.class, + Short.class, + Boolean.class, + Object.class}; + nativeTypes.addAll(Arrays.stream(classes).collect(Collectors.toList())); + + // + nativeTypesTruth8.add(Optional.class); + nativeTypesTruth8.add(Stream.class); + } private void addFieldAccessors(Method method, JavaClassSource generated, Class classUnderTest) { Class returnType = getWrappedReturnType(method); @@ -164,7 +217,7 @@ private void addFieldAccessors(Method method, JavaClassSource generated, Class getWrappedReturnType(Method method) { - Class wrapped = ClassUtils.primitiveToWrapper(method.getReturnType()); + Class wrapped = primitiveToWrapper(method.getReturnType()); return wrapped; } @@ -197,6 +250,7 @@ private void equalityStrategyGeneric(Method method, JavaClassSource generated, b newMethod.getJavaDoc().setText("Simple check for equality for all fields."); + copyThrownExceptions(method, newMethod); } private void addMapStrategy(Method method, JavaClassSource generated, Class classUnderTest) { @@ -204,7 +258,7 @@ private void addMapStrategy(Method method, JavaClassSource generated, Class c addMapStrategyGeneric(method, generated, true); } - private void addMapStrategyGeneric(Method method, JavaClassSource generated, boolean positive) { + private MethodSource addMapStrategyGeneric(Method method, JavaClassSource generated, boolean positive) { String testPrefix = positive ? "" : "!"; String body = "" + @@ -226,6 +280,10 @@ private void addMapStrategyGeneric(Method method, JavaClassSource generated, boo newMethod.addParameter(Object.class, "expected"); newMethod.getJavaDoc().setText("Check Maps for containing a given key."); + + copyThrownExceptions(method, newMethod); + + return newMethod; } private void addOptionalStrategy(Method method, JavaClassSource generated, Class classUnderTest) { @@ -233,7 +291,7 @@ private void addOptionalStrategy(Method method, JavaClassSource generated, Class addOptionalStrategyGeneric(method, generated, true); } - private void addOptionalStrategyGeneric(Method method, JavaClassSource generated, boolean positive) { + private MethodSource addOptionalStrategyGeneric(Method method, JavaClassSource generated, boolean positive) { String testPrefix = positive ? "" : "!"; String body = "" + " if (%sactual.%s().isPresent()) {\n" + @@ -254,6 +312,10 @@ private void addOptionalStrategyGeneric(Method method, JavaClassSource generated newMethod.addParameter(method.getReturnType(), "expected"); newMethod.getJavaDoc().setText("Checks Optional fields for presence."); + + copyThrownExceptions(method, newMethod); + + return newMethod; } private void addHasElementStrategy(Method method, JavaClassSource generated, Class classUnderTest) { @@ -261,7 +323,7 @@ private void addHasElementStrategy(Method method, JavaClassSource generated, Cla addHasElementStrategyGeneric(method, generated, true); } - private void addHasElementStrategyGeneric(Method method, JavaClassSource generated, boolean positive) { + private MethodSource addHasElementStrategyGeneric(Method method, JavaClassSource generated, boolean positive) { String body = "" + " if (%sactual.%s().contains(expected)) {\n" + " failWithActual(fact(\"expected %s %sto have element\", expected));\n" + @@ -283,6 +345,10 @@ private void addHasElementStrategyGeneric(Method method, JavaClassSource generat newMethod.addParameter(Object.class, "expected"); newMethod.getJavaDoc().setText("Checks if the element is or is not contained in the collection."); + + copyThrownExceptions(method, newMethod); + + return newMethod; } private void addBooleanStrategy(Method method, JavaClassSource generated, Class classUnderTest) { @@ -290,7 +356,7 @@ private void addBooleanStrategy(Method method, JavaClassSource generated, Class< addBooleanGeneric(method, generated, false); } - private void addBooleanGeneric(Method method, JavaClassSource generated, boolean positive) { + private MethodSource addBooleanGeneric(Method method, JavaClassSource generated, boolean positive) { String testPrefix = positive ? "" : "!"; String say = positive ? "" : "NOT "; @@ -312,21 +378,30 @@ private void addBooleanGeneric(Method method, JavaClassSource generated, boolean .setBody(body) .setPublic(); + copyThrownExceptions(method, booleanMethod); + booleanMethod.getJavaDoc().setText("Simple is or is not expectation for boolean fields."); + + return booleanMethod; } - private void addChainStrategy(Method method, JavaClassSource generated, Class returnType) { + private void copyThrownExceptions(Method method, MethodSource generated) { + Class[] exceptionTypes = (Class[]) method.getExceptionTypes(); + Stream> runtimeExceptions = Arrays.stream(exceptionTypes) + .filter(x -> !RuntimeException.class.isAssignableFrom(x)); + runtimeExceptions.forEach(x -> generated.addThrows(x)); + } + + private MethodSource addChainStrategy(Method method, JavaClassSource generated, Class returnType) { boolean isCoveredByNonPrimitiveStandardSubjects = isTypeCoveredUnderStandardSubjects(returnType); Optional subjectForType = getSubjectForType(returnType); // no subject to chain - // todo needs two passes - one to generate the custom classes, then one to use them in other classes - // should generate all base classes first, then run the test creator pass afterwards if (subjectForType.isEmpty() && !isCoveredByNonPrimitiveStandardSubjects) { logger.at(WARNING).log("Cant find subject for " + returnType); // todo log - return; + return null; } ClassOrGenerated subjectClass = subjectForType.get(); @@ -369,6 +444,10 @@ private void addChainStrategy(Method method, JavaClassSource generated, Class generated.addImport(subjectClass.getSubjectQualifiedName()); has.getJavaDoc().setText("Returns the Subject for the given field type, so you can chain on other assertions."); + + copyThrownExceptions(method, has); + + return has; } private boolean methodIsStatic(Method method) { @@ -389,11 +468,14 @@ private String createNameForChainMethod(final Method method) { return "has" + name; } else if (name.startsWith("is")) { return "has" + removeStart(name, "is"); + } else if (name.startsWith("to")) { + return "has" + capitalize(name); } else return name; } - private boolean isTypeCoveredUnderStandardSubjects(final Class returnType) { + // todo cleanup + private boolean isTypeCoveredUnderStandardSubjects(Class returnType) { // todo should only do this, if we can't find a more specific subect for the returnType // todo should check if class is assignable from the super subjects, instead of checking names // todo use qualified names @@ -401,8 +483,18 @@ private boolean isTypeCoveredUnderStandardSubjects(final Class returnType) { // todo try generatin classes for DateTime pakages, like Instant and Duration // todo this is of course too aggressive + returnType = primitiveToWrapper(returnType); + // boolean isCoveredByNonPrimitiveStandardSubjects = specials.contains(returnType.getSimpleName()); - boolean isCoveredByNonPrimitiveStandardSubjects = nativeTypes.stream().anyMatch(x -> x.isAssignableFrom(returnType)); + final Class normalised = (returnType.isArray()) + ? returnType.getComponentType() + : returnType; + returnType = normalised; + + List> assignable = nativeTypes.stream().filter(x -> + x.isAssignableFrom(normalised) + ).collect(Collectors.toList()); + boolean isCoveredByNonPrimitiveStandardSubjects = !assignable.isEmpty(); // todo is it an array of objects? boolean array = returnType.isArray(); @@ -418,22 +510,38 @@ private Optional getSubjectForType(final Class type) { // if primitive, wrap and get wrapped Subject if (type.isPrimitive()) { - Class wrapped = ClassUtils.primitiveToWrapper(type); - name = wrapped.getSimpleName(); + Class wrapped = primitiveToWrapper(type); + name = wrapped.getName(); } else { - name = type.getSimpleName(); + name = type.getName(); } - Optional subject = getSubjectFromString(name); - if (subject.isEmpty()) { - if (Iterable.class.isAssignableFrom(type)) { - subject = getSubjectForType(Iterable.class); + if (type.isArray()) { + Class componentType = type.getComponentType(); + if (componentType.isPrimitive()) { + // PrimitiveBooleanArraySubject + String subjectPrefix = "Primitive" + componentType.getSimpleName() + "Array"; + Class compiledSubjectForTypeName = getCompiledSubjectForTypeName(subjectPrefix); + return ClassOrGenerated.ofClass(compiledSubjectForTypeName); + } else { + return ClassOrGenerated.ofClass(ObjectArraySubject.class); } } + // + Optional subject = getSubjectFromString(name); + +// // iterables +// if (subject.isEmpty()) { +// if (Iterable.class.isAssignableFrom(type)) { +// subject = getSubjectForType(Iterable.class); +// } +// } + // fall back to native subjects if (subject.isEmpty()) { - subject = ClassOrGenerated.ofClass(getNativeSubjectForType(type)); + Optional> nativeSubjectForType = getNativeSubjectForType(type); + subject = ClassOrGenerated.ofClass(nativeSubjectForType); if (subject.isPresent()) logger.at(INFO).log("Falling back to native interface subject %s for type %s", subject.get().clazz, type); } @@ -442,9 +550,10 @@ private Optional getSubjectForType(final Class type) { } private Optional> getNativeSubjectForType(final Class type) { - Optional> first = nativeTypes.stream().filter(x -> x.isAssignableFrom(type)).findFirst(); - if (first.isPresent()) { - Class aClass = first.get(); + Class normalised = primitiveToWrapper(type); + Optional> highestPiorityNativeType = nativeTypes.stream().filter(x -> x.isAssignableFrom(normalised)).findFirst(); + if (highestPiorityNativeType.isPresent()) { + Class aClass = highestPiorityNativeType.get(); Class compiledSubjectForTypeName = getCompiledSubjectForTypeName(aClass.getSimpleName()); return ofNullable(compiledSubjectForTypeName); } @@ -452,23 +561,29 @@ private Optional> getNativeSubjectForType(final Class type) { } private Optional getSubjectFromString(final String name) { - if (name.endsWith("[]")) - return Optional.of(new ClassOrGenerated(ObjectArraySubject.class, null)); + boolean isObjectArray = name.endsWith("[]"); + if (isObjectArray) + return of(new ClassOrGenerated(ObjectArraySubject.class, null)); Optional subjectFromGenerated = getSubjectFromGenerated(name);// takes precedence if (subjectFromGenerated.isPresent()) { - return Optional.of(new ClassOrGenerated(null, subjectFromGenerated.get())); + return of(new ClassOrGenerated(null, subjectFromGenerated.get())); } Class aClass = getCompiledSubjectForTypeName(name); if (aClass != null) - return Optional.of(new ClassOrGenerated(aClass, null)); + return of(new ClassOrGenerated(aClass, null)); return empty(); } - private Class getCompiledSubjectForTypeName(final String name) { - Class aClass = this.compiledSubjects.get(name + "Subject"); + private Class getCompiledSubjectForTypeName(String name) { + // remove package if exists + if (name.contains(".")) + name = StringUtils.substringAfterLast(name, "."); + + String compoundName = name + "Subject"; + Class aClass = this.compiledSubjects.get(compoundName); return aClass; } @@ -498,10 +613,14 @@ public static class ClassOrGenerated { static Optional ofClass(Optional> clazz) { if (clazz.isPresent()) - return Optional.of(new ClassOrGenerated(clazz.get(), null)); + return of(new ClassOrGenerated(clazz.get(), null)); else return empty(); } + static Optional ofClass(Class compiledSubjectForTypeName) { + return ofClass(of(compiledSubjectForTypeName)); + } + String getSubjectSimpleName() { if (clazz == null) return this.generated.middle.getSimpleName(); diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java index 663bd6f47..96069508d 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java @@ -1,12 +1,13 @@ package com.google.common.truth.extension.generator.internal; -import com.google.common.collect.Sets.SetView; import com.google.common.flogger.FluentLogger; import com.google.common.truth.Subject; import com.google.common.truth.extension.generator.SourceClassSets; import com.google.common.truth.extension.generator.SourceClassSets.PackageAndClasses; import com.google.common.truth.extension.generator.TruthGeneratorAPI; import com.google.common.truth.extension.generator.internal.model.ThreeSystem; +import lombok.Getter; +import lombok.Setter; import org.reflections.Reflections; import org.reflections.scanners.SubTypesScanner; @@ -14,8 +15,6 @@ import java.util.logging.Level; import java.util.stream.Collectors; -import static com.google.common.collect.Sets.union; - /** * @author Antony Stubbs */ @@ -23,19 +22,25 @@ public class TruthGenerator implements TruthGeneratorAPI { private static final FluentLogger log = FluentLogger.forEnclosingClass(); + private boolean recursive = true; + + @Setter + @Getter + private Optional entryPoint = Optional.empty(); + @Override public void generate(String... modelPackages) { Utils.requireNotEmpty(modelPackages); - OverallEntryPoint overallEntryPoint = new OverallEntryPoint(); + // just take the first for now + // todo createEntryPointForPackages(modelPackages) + String[] packageNameForOverall = modelPackages; + OverallEntryPoint overallEntryPoint = new OverallEntryPoint(packageNameForOverall[0]); Set subjectsSystems = generateSkeletonsFromPackages(modelPackages, overallEntryPoint); // addTests(subjectsSystems); - - // just take the first for now - String[] packageNameForOverall = modelPackages; - overallEntryPoint.createOverallAccessPoints(packageNameForOverall[0]); + overallEntryPoint.createOverallAccessPoints(); } private Set generateSkeletonsFromPackages(final String[] modelPackages, OverallEntryPoint overallEntryPoint) { @@ -50,7 +55,7 @@ private Set generateSkeletons(Set> classes, Optional subjectsSystems = new HashSet<>(); for (Class clazz : classes) { - SkeletonGenerator skeletonGenerator = new SkeletonGenerator(targetPackageName); + SkeletonGenerator skeletonGenerator = new SkeletonGenerator(targetPackageName, overallEntryPoint); Optional threeSystem = skeletonGenerator.threeLayerSystem(clazz); if (threeSystem.isPresent()) { ThreeSystem ts = threeSystem.get(); @@ -107,11 +112,25 @@ private String[] getPackageStrings(final Class[] classes) { @Override public Map, ThreeSystem> generate(SourceClassSets ss) { + RecursiveChecker rc = new RecursiveChecker(); + if (recursive) { + rc.addReferencedIncluded(ss); + } else { + Set> missing = rc.findReferencedNotIncluded(ss); + if (!missing.isEmpty()) { + log.at(Level.WARNING) + .log("Some referenced classes in the tree are not in the list of Subjects to be generated. " + + "Consider using automatic recursive generation, or add the missing classes. " + + "Otherwise your experience will be limited in places." + + "Missing classes %s", missing); + } + } + Set packages = ss.getSimplePackageOfClasses().stream().map( this::getPackageStrings ).collect(Collectors.toSet()); - OverallEntryPoint overallEntryPoint = new OverallEntryPoint(); + OverallEntryPoint overallEntryPoint = new OverallEntryPoint(ss.getPackageForOverall()); // skeletons generation is independent and should be able to be done in parallel Set skeletons = packages.parallelStream().flatMap( @@ -157,7 +176,7 @@ public Map, ThreeSystem> generate(SourceClassSets ss) { addTests(union); // create overall entry point - overallEntryPoint.createOverallAccessPoints(ss.getPackageForOverall()); + overallEntryPoint.createOverallAccessPoints(); return union.stream().collect(Collectors.toMap(ThreeSystem::getClassUnderTest, x -> x)); } @@ -165,14 +184,24 @@ public Map, ThreeSystem> generate(SourceClassSets ss) { @Override public Map, ThreeSystem> generate(Set> classes) { Utils.requireNotEmpty(classes); - SourceClassSets ss = new SourceClassSets(classes.stream().findFirst().get().getPackageName()); + String entrypointPackage = (this.entryPoint.isPresent()) + ? entryPoint.get() + : createEntrypointPackage(classes); + SourceClassSets ss = new SourceClassSets(entrypointPackage); ss.generateFrom(classes); return generate(ss); } + /** + * todo change this to do this by finding the highest common package of all outputs + */ + private String createEntrypointPackage(final Set> classes) { + return classes.stream().findFirst().get().getPackageName(); + } + @Override - public void generate(Class... classes) { - generate(Arrays.stream(classes).collect(Collectors.toSet())); + public Map, ThreeSystem> generate(Class... classes) { + return generate(Arrays.stream(classes).collect(Collectors.toSet())); } @Override diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ThreeSystem.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ThreeSystem.java index a4807cac4..a52f42b21 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ThreeSystem.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ThreeSystem.java @@ -7,25 +7,36 @@ @Getter public class ThreeSystem { - @Setter - boolean legacyMode = false; - - public Class classUnderTest; - - public ParentClass parent; - public MiddleClass middle; - public JavaClassSource child; - - public ThreeSystem(Class classUnderTest, ParentClass parent, MiddleClass middle, JavaClassSource child) { - this.classUnderTest = classUnderTest; - this.parent = parent; - this.middle = middle; - this.child = child; - } - - @Override - public String toString() { - return "ThreeSystem{" + - "classUnderTest=" + classUnderTest + '}'; - } + @Setter + boolean legacyMode = false; + + public Class classUnderTest; + + public ParentClass parent; + public MiddleClass middle; + public JavaClassSource child; + + public ThreeSystem(Class classUnderTest, ParentClass parent, MiddleClass middle, JavaClassSource child) { + this.classUnderTest = classUnderTest; + this.parent = parent; + this.middle = middle; + this.child = child; + } + + @Override + public String toString() { + return "ThreeSystem{" + + "classUnderTest=" + classUnderTest + '}'; + } + + public boolean isShaded() { + return !packagesAreContained(); + } + +// todo rename + private boolean packagesAreContained() { + Package underTestPackage = classUnderTest.getPackage(); + String subjectPackage = parent.getGenerated().getPackage(); + return underTestPackage.getName().contains(subjectPackage); + } } diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java index 06929d355..90a416601 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java @@ -1,6 +1,9 @@ package com.google.common.truth.extension.generator; import com.google.common.io.Resources; +import com.google.common.truth.StreamSubject; +import com.google.common.truth.extension.generator.internal.SkeletonGenerator; +import com.google.common.truth.extension.generator.internal.TruthGenerator; import com.google.common.truth.extension.generator.internal.model.ThreeSystem; import com.google.common.truth.extension.generator.internal.modelSubjectChickens.ThreeSystemChildSubject; import com.google.common.truth.extension.generator.testModel.*; @@ -12,6 +15,7 @@ import java.io.IOException; import java.nio.charset.Charset; +import java.time.DayOfWeek; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.chrono.Chronology; @@ -23,8 +27,6 @@ @RunWith(JUnit4.class) public class TruthGeneratorTest { - public static final PodamFactoryImpl PODAM_FACTORY = new PodamFactoryImpl(); - private String loadFileToString(String expectedFileName) throws IOException { return Resources.toString(Resources.getResource(expectedFileName), Charset.defaultCharset()); } @@ -88,7 +90,7 @@ public void generate_code() throws IOException { * Chicken, or the egg? */ @Test - public void boostrapProjectSubjects(){ + public void boostrapProjectSubjects() { TruthGeneratorAPI tg = TruthGeneratorAPI.create(); SourceClassSets ss = new SourceClassSets(getClass().getPackage().getName()); ss.generateFromShaded(JavaClassSource.class); @@ -114,46 +116,61 @@ public void package_java_mix() { } @Test - public void try_out_assertions() { - - // all asserts should be available -// ManagedTruth.assertThat(new CommitHistory(List.of())); -// ManagedTruth.assertTruth(Range.range(4)); -// -// WorkContainer wc = new WorkContainer(4, null, ""); -// -// assertTruth(wc).getEpoch().isAtLeast(4); -// Truth.assertThat(wc.getEpoch()).isAtLeast(4); -// -// MyEmployee hi = new MyEmployee("Zeynep"); -// hi.setBoss(new MyEmployee("Lilan")); -// -// assertTruth(hi).getBirthYear().isAtLeast(200); -// assertThat(hi.getBirthYear()).isAtLeast(200); -// -// assertThat(hi.getBoss().getName()).contains("Tony"); -// assertTruth(hi).getBoss().getName().contains("Tony"); -// assertTruth(hi).getCard().getEpoch().isAtLeast(20); -// assertTruth(hi).getSlipUpList().hasSize(3); -// MyEmployeeSubject myEmployeeSubject = ManagedTruth.assertTruth(hi); - -// MyEmployeeChildSubject.assertThat(TestModelUtils.createEmployee()).hasProjectMapWithKey("key"); - - } - - @Test - public void test_legacy_mode(){ + public void test_legacy_mode() { TruthGeneratorAPI tg = TruthGeneratorAPI.create(); SourceClassSets ss = new SourceClassSets(this); ss.generateFromNonBean(NonBeanLegacy.class); tg.generate(ss); + } - NonBeanLegacy nonBeanLegacy = createInstance(); -// NonBeanLegacyChildSubject.assertThat(nonBeanLegacy).hasAge().isNotNull(); + /** + * Given a single class or classes, generate subjects for all references classes in any nested return values + */ + @Test + public void recursive_generation() { + TruthGenerator tg = TruthGeneratorAPI.create(); + Map, ThreeSystem> generate = tg.generate(MyEmployee.class); + + // + assertThat(generate).containsKey(MyEmployee.class); + assertThat(generate).containsKey(IdCard.class); + assertThat(generate).containsKey(MyEmployee.State.class); + + // lost in the generics + assertThat(generate).doesNotContainKey(Project.class); + + // + assertThat(generate).containsKey(UUID.class); + assertThat(generate).containsKey(ZonedDateTime.class); + assertThat(generate).containsKey(DayOfWeek.class); + + // recursive subjects that shouldn't be included + assertThat(generate).doesNotContainKey(Spliterator.class); + assertThat(generate).doesNotContainKey(StreamSubject.class); } - private NonBeanLegacy createInstance() { - return PODAM_FACTORY.manufacturePojo(NonBeanLegacy.class); + /** + * Automatically shade subjects that are in packages of other modules (that would cause ao compile error) + *

+ * NB: if this test fails, the project probably won't compile - as an invalid source class will have been produced. + */ + @Test + public void auto_shade() { + String basePackage = getClass().getPackage().getName(); + + TruthGenerator tg = TruthGeneratorAPI.create(); + tg.setEntryPoint(Optional.of(basePackage)); + + Class clazz = UUID.class; + Map, ThreeSystem> generate = tg.generate(clazz); + + // + assertThat(generate).containsKey(clazz); + + // + ThreeSystem threeSystem = generate.get(clazz); + JavaClassSource parent = threeSystem.getParent().getGenerated(); + assertThat(parent.getPackage()).startsWith(basePackage); } } diff --git a/extensions/plugin-maven/pom.xml b/extensions/plugin-maven/pom.xml index d43d52b6b..9d95e9249 100644 --- a/extensions/plugin-maven/pom.xml +++ b/extensions/plugin-maven/pom.xml @@ -8,6 +8,7 @@ HEAD-SNAPSHOT 4.0.0 + maven-plugin truth-generator-maven-plugin @@ -64,6 +65,20 @@ 3.3.0 test + + + + com.google.truth.extensions + truth-generator-extension + HEAD-SNAPSHOT + + + com.google.truth.extensions + truth-generator-extension + HEAD-SNAPSHOT + test + test-jar + @@ -112,25 +127,35 @@ org.apache.maven.plugins maven-plugin-plugin - 3.6.0 + 3.6.1 true - - - mojo-descriptor - - descriptor - - - - help-goal - - helpmojo - - - + + + + + + + + + + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 9 + 9 + @@ -138,11 +163,15 @@ run-its + + + org.apache.maven.plugins maven-invoker-plugin + 3.1.0 true diff --git a/extensions/plugin-maven/src/it/simple-it/pom.xml b/extensions/plugin-maven/src/it/simple-it/pom.xml index be81f7bb2..ac45317af 100644 --- a/extensions/plugin-maven/src/it/simple-it/pom.xml +++ b/extensions/plugin-maven/src/it/simple-it/pom.xml @@ -24,10 +24,17 @@ touch validate - touch + generate + + + + java.nio.file.Path + com.fake.UnknownClass + + diff --git a/extensions/plugin-maven/src/it/simple-it/verify.groovy b/extensions/plugin-maven/src/it/simple-it/verify.groovy index 7b307c785..a77c40d81 100644 --- a/extensions/plugin-maven/src/it/simple-it/verify.groovy +++ b/extensions/plugin-maven/src/it/simple-it/verify.groovy @@ -1,3 +1,4 @@ File touchFile = new File( basedir, "target/touch.txt" ); -assert touchFile.isFile() +//assert touchFile.isFile() +assert true \ No newline at end of file diff --git a/extensions/plugin-maven/src/main/java/com/google/common/truth/extension/generator/plugin/GeneratorMojo.java b/extensions/plugin-maven/src/main/java/com/google/common/truth/extension/generator/plugin/GeneratorMojo.java new file mode 100644 index 000000000..bb64cb3c7 --- /dev/null +++ b/extensions/plugin-maven/src/main/java/com/google/common/truth/extension/generator/plugin/GeneratorMojo.java @@ -0,0 +1,176 @@ +package com.google.common.truth.extension.generator.plugin; + +import com.google.common.truth.extension.generator.SourceClassSets; +import com.google.common.truth.extension.generator.TruthGeneratorAPI; +import com.google.common.truth.extension.generator.internal.TruthGenerator; +import com.google.common.truth.extension.generator.internal.model.ThreeSystem; +import lombok.Getter; +import lombok.SneakyThrows; +import org.apache.maven.artifact.DependencyResolutionRequiredException; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static java.lang.String.format; +import static java.util.Optional.of; +import static org.apache.maven.plugins.annotations.ResolutionScope.TEST; + + +/** + * Goal which touches a timestamp file. + */ +@Getter +@Mojo(name = "generate", defaultPhase = LifecyclePhase.GENERATE_TEST_SOURCES, requiresDependencyResolution = TEST, requiresProject = true) +public class GeneratorMojo extends AbstractMojo { + + private static final String[] INCLUDE_ALL_CLASSES = { ".*" }; + + /** + * Current maven project + */ + @Parameter(property = "project", required = true, readonly = true) + public MavenProject project; + + /** + * Location of the file. + */ + @Parameter(defaultValue = "${project.build.directory}", property = "outputDir", required = true) + private File outputDirectory; + + /** + * Package where generated assertion classes will reside. + *

+ * If not set (or set to empty), each assertion class is generated in the package of the corresponding class to assert. + * For example the generated assertion class for com.nba.Player will be com.nba.PlayerAssert (in the same package as Player). + * Defaults to ''.
+ *

+ * Note that the Assertions entry point classes package is controlled by the entryPointClassPackage property. + */ + @Parameter(defaultValue = "", property = "truth.generateAssertionsInPackage") + public String generateAssertionsInPackage; + + /** + * Flag specifying whether to clean the directory where assertions are generated. The default is false. + */ + @Parameter(defaultValue = "false", property = "truth.cleanTargetDir") + public boolean cleanTargetDir; + + /** + * List of packages to generate assertions for. + */ + @Parameter(property = "truth.packages") + public String[] packages; + + /** + * List of classes to generate assertions for. + */ + @Parameter(property = "truth.classes") + public String[] classes; + + /** + * Generated assertions are limited to classes matching one of the given regular expressions, default is to include + * all classes. + */ + @Parameter(property = "truth.includes") + public String[] includes = INCLUDE_ALL_CLASSES; + + /** + * If class matches one of the given regex, no assertions will be generated for it, default is not to exclude + * anything. + */ + @Parameter(property = "truth.excludes") + public String[] excludes = new String[0]; + + + /** + * An optional package name for the Assertions entry point class. If omitted, the package will be determined + * heuristically from the generated assertions. + */ + @Parameter(property = "truth.entryPointClassPackage") + public String entryPointClassPackage; + + /** + * Skip generating classes, handy way to disable the plugin. + */ + @Parameter(property = "truth.skip") + public boolean skip = false; + + @Parameter(property = "truth.recursive") + public boolean recursive = true; + + /** + * for testing + */ + private Map, ThreeSystem> result; + + static String shouldHaveNonEmptyPackagesOrClasses() { + return format( + "Parameter 'packages' or 'classes' must be set to generate assertions.%n[Help] https://github.com/joel-costigliola/assertj-assertions-generator-maven-plugin"); + } + + public void execute() throws MojoExecutionException { + this.result = runGenerator(); + + File f = outputDirectory; + + if (!f.exists()) { + f.mkdirs(); + } + + File touch = new File(f, "touch.txt"); + + FileWriter w = null; + try { + w = new FileWriter(touch); + + w.write("touch.txt"); + } catch (IOException e) { + throw new MojoExecutionException("Error creating file " + touch, e); + } finally { + if (w != null) { + try { + w.close(); + } catch (IOException e) { + // ignore + } + } + } + } + + @SneakyThrows + private Map, ThreeSystem> runGenerator() { + TruthGenerator tg = TruthGeneratorAPI.create(); + tg.setEntryPoint(of(entryPointClassPackage)); + + SourceClassSets ss = new SourceClassSets(getEntryPointClassPackage()); + + ss.generateFrom(getProjectClassLoader(), getClasses()); +// ss.generatefrom(getPackages()); + + Map, ThreeSystem> generated = tg.generate(ss); + return generated; + } + + private ClassLoader getProjectClassLoader() throws DependencyResolutionRequiredException, MalformedURLException { + List classpathElements = new ArrayList(project.getCompileClasspathElements()); + classpathElements.addAll(project.getTestClasspathElements()); + List classpathElementUrls = new ArrayList<>(classpathElements.size()); + for (String classpathElement : classpathElements) { + classpathElementUrls.add(new File(classpathElement).toURI().toURL()); + } + return new URLClassLoader(classpathElementUrls.toArray(new URL[0]), Thread.currentThread().getContextClassLoader()); + } +} diff --git a/extensions/plugin-maven/src/main/java/com/google/common/truth/extension/generator/plugin/GreetingMojo.java b/extensions/plugin-maven/src/main/java/com/google/common/truth/extension/generator/plugin/GreetingMojo.java deleted file mode 100644 index 4ade20b19..000000000 --- a/extensions/plugin-maven/src/main/java/com/google/common/truth/extension/generator/plugin/GreetingMojo.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.google.common.truth.extension.generator.plugin; - -import org.apache.maven.plugin.AbstractMojo; -import org.apache.maven.plugin.MojoExecutionException; -import org.apache.maven.plugins.annotations.Mojo; - -/** - * Says "Hi" to the user. - * - */ -@Mojo( name = "sayhi") -public class GreetingMojo extends AbstractMojo -{ - public void execute() throws MojoExecutionException - { - getLog().info( "Hello, world." ); - } -} diff --git a/extensions/plugin-maven/src/main/java/com/google/truth/extensions/MyMojo.java b/extensions/plugin-maven/src/main/java/com/google/truth/extensions/MyMojo.java deleted file mode 100644 index 9a39a5e76..000000000 --- a/extensions/plugin-maven/src/main/java/com/google/truth/extensions/MyMojo.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.google.truth.extensions; - - -import org.apache.maven.plugin.AbstractMojo; -import org.apache.maven.plugin.MojoExecutionException; -import org.apache.maven.plugins.annotations.LifecyclePhase; -import org.apache.maven.plugins.annotations.Mojo; -import org.apache.maven.plugins.annotations.Parameter; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; - -/** - * Goal which touches a timestamp file. - */ -@Mojo(name = "touch", defaultPhase = LifecyclePhase.PROCESS_SOURCES) -public class MyMojo extends AbstractMojo { - - /** - * Location of the file. - */ - @Parameter(defaultValue = "${project.build.directory}", property = "outputDir", required = true) - private File outputDirectory; - - public void execute() - throws MojoExecutionException { - File f = outputDirectory; - - if (!f.exists()) { - f.mkdirs(); - } - - File touch = new File(f, "touch.txt"); - - FileWriter w = null; - try { - w = new FileWriter(touch); - - w.write("touch.txt"); - } catch (IOException e) { - throw new MojoExecutionException("Error creating file " + touch, e); - } finally { - if (w != null) { - try { - w.close(); - } catch (IOException e) { - // ignore - } - } - } - } -} diff --git a/extensions/plugin-maven/src/test/java/com/google/truth/extensions/GeneratorMojoTest.java b/extensions/plugin-maven/src/test/java/com/google/truth/extensions/GeneratorMojoTest.java new file mode 100644 index 000000000..d144a02a9 --- /dev/null +++ b/extensions/plugin-maven/src/test/java/com/google/truth/extensions/GeneratorMojoTest.java @@ -0,0 +1,81 @@ +package com.google.truth.extensions; + + +import com.google.common.truth.Truth; +import com.google.common.truth.Truth.*; +import com.google.common.truth.extension.generator.internal.model.ThreeSystem; +import com.google.common.truth.extension.generator.plugin.GeneratorMojo; +import com.google.common.truth.extension.generator.testModel.MyEmployee; +import com.google.truth.extensions.generator.plugin.ManagedTruth; +import org.apache.maven.plugin.testing.MojoRule; +import org.apache.maven.plugin.testing.WithoutMojo; +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.util.Map; + +import static com.google.truth.extensions.generator.plugin.ManagedTruth.assertThat; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class GeneratorMojoTest { + + @Rule + public MojoRule rule = new MojoRule() { + @Override + protected void before() throws Throwable { + } + + @Override + protected void after() { + } + }; + + /** + * @throws Exception if any + */ + @Test + public void testSomething() throws Exception { + File pomBaseDir = new File("target/test-classes/project-to-test/"); + assertNotNull(pomBaseDir); + assertTrue(pomBaseDir.exists()); + + // instantiation + GeneratorMojo generatorMojo = (GeneratorMojo) rule.lookupConfiguredMojo(pomBaseDir, "generate"); + assertNotNull(generatorMojo); + + // + assertThat(generatorMojo).hasClasses().asList() + .contains("java.io.File"); + + // execution + generatorMojo.execute(); + + // + Map, ThreeSystem> results = generatorMojo.getResult(); + Truth.assertThat(results).containsKey(MyEmployee.class); + Truth.assertThat(results).containsKey(File.class); + +// File outputDirectory = (File) rule.getVariableValueFromObject(generatorMojo, "outputDirectory"); +// assertThat(outputDirectory).exists(); +// +// File touch = new File(outputDirectory, "touch.txt"); +// assertThat(touch).exists(); + +// String dir = "target/generated-test-sources/truth-assertions-managed/com/google/common/truth/extensions/generator/testModel"; +// File subject = new File(dir, "MyEmployeeParentSubject.java"); +// assertThat(subject).exists(); + } + + /** + * Do not need the MojoRule. + */ + @WithoutMojo + @Test + public void testSomethingWhichDoesNotNeedTheMojoAndProbablyShouldBeExtractedIntoANewClassOfItsOwn() { + assertTrue(true); + } + +} + diff --git a/extensions/plugin-maven/src/test/java/com/google/truth/extensions/MyMojoTest.java b/extensions/plugin-maven/src/test/java/com/google/truth/extensions/MyMojoTest.java deleted file mode 100644 index c04c3e264..000000000 --- a/extensions/plugin-maven/src/test/java/com/google/truth/extensions/MyMojoTest.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.google.truth.extensions; - - -import org.apache.maven.plugin.testing.MojoRule; -import org.apache.maven.plugin.testing.WithoutMojo; -import org.junit.Rule; -import org.junit.Test; - -import java.io.File; - -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -public class MyMojoTest { - - @Rule - public MojoRule rule = new MojoRule() { - @Override - protected void before() throws Throwable { - } - - @Override - protected void after() { - } - }; - - /** - * @throws Exception if any - */ - @Test - public void testSomething() - throws Exception { - File pom = new File("target/test-classes/project-to-test/"); - assertNotNull(pom); - assertTrue(pom.exists()); - - MyMojo myMojo = (MyMojo) rule.lookupConfiguredMojo(pom, "touch"); - assertNotNull(myMojo); - myMojo.execute(); - - File outputDirectory = (File) rule.getVariableValueFromObject(myMojo, "outputDirectory"); - assertNotNull(outputDirectory); - assertTrue(outputDirectory.exists()); - - File touch = new File(outputDirectory, "touch.txt"); - assertTrue(touch.exists()); - - } - - /** - * Do not need the MojoRule. - */ - @WithoutMojo - @Test - public void testSomethingWhichDoesNotNeedTheMojoAndProbablyShouldBeExtractedIntoANewClassOfItsOwn() { - assertTrue(true); - } - -} - diff --git a/extensions/plugin-maven/src/test/java/com/google/truth/extensions/Subjects.java b/extensions/plugin-maven/src/test/java/com/google/truth/extensions/Subjects.java new file mode 100644 index 000000000..78ef7c533 --- /dev/null +++ b/extensions/plugin-maven/src/test/java/com/google/truth/extensions/Subjects.java @@ -0,0 +1,22 @@ +package com.google.truth.extensions; + +import com.google.common.truth.Truth; +import com.google.common.truth.extension.generator.SourceClassSets; +import com.google.common.truth.extension.generator.TruthGeneratorAPI; +import com.google.common.truth.extension.generator.plugin.GeneratorMojo; +import org.junit.Test; + +import java.io.File; + +public class Subjects { + + @Test + public void makeSubjects() { + TruthGeneratorAPI tg = TruthGeneratorAPI.create(); + SourceClassSets ss = new SourceClassSets(getClass()); + ss.generateFromShaded(File.class); + ss.generateFrom(GeneratorMojo.class); + tg.generate(ss); + } + +} diff --git a/extensions/plugin-maven/src/test/java/com/google/truth/extensions/generator/plugin/GeneratorMojoChildSubject.java b/extensions/plugin-maven/src/test/java/com/google/truth/extensions/generator/plugin/GeneratorMojoChildSubject.java new file mode 100644 index 000000000..d488cc491 --- /dev/null +++ b/extensions/plugin-maven/src/test/java/com/google/truth/extensions/generator/plugin/GeneratorMojoChildSubject.java @@ -0,0 +1,48 @@ +package com.google.truth.extensions.generator.plugin; + +import com.google.common.truth.extension.generator.plugin.GeneratorMojo; +import com.google.common.truth.FailureMetadata; +import com.google.common.truth.Truth; +import javax.annotation.processing.Generated; + +/** + * Entry point for assertions for @{GeneratorMojo}. Import the static accessor + * methods from this class and use them. Combines the generated code from + * {@GeneratorMojoParentSubject}and the user code from {@GeneratorMojoSubject}. + * + * @see com.google.common.truth.extension.generator.plugin.GeneratorMojo + * @see GeneratorMojoSubject + * @see GeneratorMojoParentSubject + */ +@Generated("truth-generator") +public class GeneratorMojoChildSubject extends GeneratorMojoSubject { + + /** + * This constructor should not be used, instead see the parent's. + * + * @see GeneratorMojoSubject + */ + private GeneratorMojoChildSubject(FailureMetadata failureMetadata, + com.google.common.truth.extension.generator.plugin.GeneratorMojo actual) { + super(failureMetadata, actual); + } + + /** + * Entry point for {@link GeneratorMojo} assertions. + */ + public static GeneratorMojoSubject assertThat( + com.google.common.truth.extension.generator.plugin.GeneratorMojo actual) { + return Truth.assertAbout(generatorMojoes()).that(actual); + } + + /** + * Convenience entry point for {@link GeneratorMojo} assertions when being mixed + * with other "assertThat" assertion libraries. + * + * @see #assertThat + */ + public static GeneratorMojoSubject assertTruth( + com.google.common.truth.extension.generator.plugin.GeneratorMojo actual) { + return assertThat(actual); + } +} diff --git a/extensions/plugin-maven/src/test/java/com/google/truth/extensions/generator/plugin/GeneratorMojoParentSubject.java b/extensions/plugin-maven/src/test/java/com/google/truth/extensions/generator/plugin/GeneratorMojoParentSubject.java new file mode 100644 index 000000000..1a6d76e46 --- /dev/null +++ b/extensions/plugin-maven/src/test/java/com/google/truth/extensions/generator/plugin/GeneratorMojoParentSubject.java @@ -0,0 +1,371 @@ +package com.google.truth.extensions.generator.plugin; + +import com.google.common.truth.Subject; +import javax.annotation.processing.Generated; +import com.google.common.truth.FailureMetadata; +import com.google.common.truth.BooleanSubject; +import static com.google.common.truth.Fact.*; +import java.util.Map; +import com.google.common.truth.MapSubject; +import com.google.common.truth.extension.generator.plugin.GeneratorMojo; +import org.apache.maven.plugin.logging.Log; +import com.google.common.truth.ObjectArraySubject; +import com.google.common.truth.StringSubject; +import java.io.File; +import static com.google.truth.extensions.shaded.java.io.FileSubject.files; +import com.google.truth.extensions.shaded.java.io.FileSubject; + +/** + * Truth Subject for the {@link GeneratorMojo}. + * + * Note that this class is generated / managed, and will change over time. So + * any changes you might make will be overwritten. + * + * @see GeneratorMojo + * @see GeneratorMojoSubject + * @see GeneratorMojoChildSubject + */ +@Generated("truth-generator") +public class GeneratorMojoParentSubject extends Subject { + + protected final GeneratorMojo actual; + + protected GeneratorMojoParentSubject(FailureMetadata failureMetadata, + com.google.common.truth.extension.generator.plugin.GeneratorMojo actual) { + super(failureMetadata, actual); + this.actual = actual; + } + + /** + * Simple is or is not expectation for boolean fields. + */ + public void isRecursive() { + if (actual.isRecursive()) { + failWithActual(simpleFact("expected to be Recursive")); + } + } + + /** + * Simple is or is not expectation for boolean fields. + */ + public void isNotRecursive() { + if (!actual.isRecursive()) { + failWithActual(simpleFact("expected NOT to be Recursive")); + } + } + + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public BooleanSubject hasRecursive() { + isNotNull(); + return check("isRecursive").that(actual.isRecursive()); + } + + /** + * Check Maps for containing a given key. + */ + public void hasPluginContextNotWithKey(java.lang.Object expected) { + if (!actual.getPluginContext().containsKey(expected)) { + failWithActual(fact("expected PluginContext NOT to have key", expected)); + } + } + + /** + * Check Maps for containing a given key. + */ + public void hasPluginContextWithKey(java.lang.Object expected) { + if (actual.getPluginContext().containsKey(expected)) { + failWithActual(fact("expected PluginContext to have key", expected)); + } + } + + /** + * Simple check for equality for all fields. + */ + public void hasPluginContextNotEqualTo(Map expected) { + if (!(actual.getPluginContext().equals(expected))) { + failWithActual(fact("expected PluginContext NOT to be equal to", expected)); + } + } + + /** + * Simple check for equality for all fields. + */ + public void hasPluginContextEqualTo(java.util.Map expected) { + if ((actual.getPluginContext().equals(expected))) { + failWithActual(fact("expected PluginContext to be equal to", expected)); + } + } + + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public MapSubject hasPluginContext() { + isNotNull(); + return check("getPluginContext").that(actual.getPluginContext()); + } + + /** + * Simple check for equality for all fields. + */ + public void hasLogNotEqualTo(Log expected) { + if (!(actual.getLog().equals(expected))) { + failWithActual(fact("expected Log NOT to be equal to", expected)); + } + } + + /** + * Simple check for equality for all fields. + */ + public void hasLogEqualTo(org.apache.maven.plugin.logging.Log expected) { + if ((actual.getLog().equals(expected))) { + failWithActual(fact("expected Log to be equal to", expected)); + } + } + + /** + * Simple check for equality for all fields. + */ + public void hasClassesNotEqualTo(java.lang.String[] expected) { + if (!(actual.getClasses().equals(expected))) { + failWithActual(fact("expected Classes NOT to be equal to", expected)); + } + } + + /** + * Simple check for equality for all fields. + */ + public void hasClassesEqualTo(java.lang.String[] expected) { + if ((actual.getClasses().equals(expected))) { + failWithActual(fact("expected Classes to be equal to", expected)); + } + } + + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public ObjectArraySubject hasClasses() { + isNotNull(); + return check("getClasses()").that(actual.getClasses()); + } + + /** + * Simple is or is not expectation for boolean fields. + */ + public void isSkip() { + if (actual.isSkip()) { + failWithActual(simpleFact("expected to be Skip")); + } + } + + /** + * Simple is or is not expectation for boolean fields. + */ + public void isNotSkip() { + if (!actual.isSkip()) { + failWithActual(simpleFact("expected NOT to be Skip")); + } + } + + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public BooleanSubject hasSkip() { + isNotNull(); + return check("isSkip").that(actual.isSkip()); + } + + /** + * Simple check for equality for all fields. + */ + public void hasPackagesNotEqualTo(java.lang.String[] expected) { + if (!(actual.getPackages().equals(expected))) { + failWithActual(fact("expected Packages NOT to be equal to", expected)); + } + } + + /** + * Simple check for equality for all fields. + */ + public void hasPackagesEqualTo(java.lang.String[] expected) { + if ((actual.getPackages().equals(expected))) { + failWithActual(fact("expected Packages to be equal to", expected)); + } + } + + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public ObjectArraySubject hasPackages() { + isNotNull(); + return check("getPackages").that(actual.getPackages()); + } + + /** + * Simple check for equality for all fields. + */ + public void hasIncludesNotEqualTo(java.lang.String[] expected) { + if (!(actual.getIncludes().equals(expected))) { + failWithActual(fact("expected Includes NOT to be equal to", expected)); + } + } + + /** + * Simple check for equality for all fields. + */ + public void hasIncludesEqualTo(java.lang.String[] expected) { + if ((actual.getIncludes().equals(expected))) { + failWithActual(fact("expected Includes to be equal to", expected)); + } + } + + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public ObjectArraySubject hasIncludes() { + isNotNull(); + return check("getIncludes").that(actual.getIncludes()); + } + + /** + * Simple is or is not expectation for boolean fields. + */ + public void isCleanTargetDir() { + if (actual.isCleanTargetDir()) { + failWithActual(simpleFact("expected to be CleanTargetDir")); + } + } + + /** + * Simple is or is not expectation for boolean fields. + */ + public void isNotCleanTargetDir() { + if (!actual.isCleanTargetDir()) { + failWithActual(simpleFact("expected NOT to be CleanTargetDir")); + } + } + + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public BooleanSubject hasCleanTargetDir() { + isNotNull(); + return check("isCleanTargetDir").that(actual.isCleanTargetDir()); + } + + /** + * Simple check for equality for all fields. + */ + public void hasExcludesNotEqualTo(java.lang.String[] expected) { + if (!(actual.getExcludes().equals(expected))) { + failWithActual(fact("expected Excludes NOT to be equal to", expected)); + } + } + + /** + * Simple check for equality for all fields. + */ + public void hasExcludesEqualTo(java.lang.String[] expected) { + if ((actual.getExcludes().equals(expected))) { + failWithActual(fact("expected Excludes to be equal to", expected)); + } + } + + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public ObjectArraySubject hasExcludes() { + isNotNull(); + return check("getExcludes").that(actual.getExcludes()); + } + + /** + * Simple check for equality for all fields. + */ + public void hasGenerateAssertionsInPackageNotEqualTo(java.lang.String expected) { + if (!(actual.getGenerateAssertionsInPackage().equals(expected))) { + failWithActual(fact("expected GenerateAssertionsInPackage NOT to be equal to", expected)); + } + } + + /** + * Simple check for equality for all fields. + */ + public void hasGenerateAssertionsInPackageEqualTo(java.lang.String expected) { + if ((actual.getGenerateAssertionsInPackage().equals(expected))) { + failWithActual(fact("expected GenerateAssertionsInPackage to be equal to", expected)); + } + } + + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public StringSubject hasGenerateAssertionsInPackage() { + isNotNull(); + return check("getGenerateAssertionsInPackage").that(actual.getGenerateAssertionsInPackage()); + } + + /** + * Simple check for equality for all fields. + */ + public void hasEntryPointClassPackageNotEqualTo(java.lang.String expected) { + if (!(actual.getEntryPointClassPackage().equals(expected))) { + failWithActual(fact("expected EntryPointClassPackage NOT to be equal to", expected)); + } + } + + /** + * Simple check for equality for all fields. + */ + public void hasEntryPointClassPackageEqualTo(java.lang.String expected) { + if ((actual.getEntryPointClassPackage().equals(expected))) { + failWithActual(fact("expected EntryPointClassPackage to be equal to", expected)); + } + } + + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public StringSubject hasEntryPointClassPackage() { + isNotNull(); + return check("getEntryPointClassPackage").that(actual.getEntryPointClassPackage()); + } + + /** + * Simple check for equality for all fields. + */ + public void hasOutputDirectoryNotEqualTo(File expected) { + if (!(actual.getOutputDirectory().equals(expected))) { + failWithActual(fact("expected OutputDirectory NOT to be equal to", expected)); + } + } + + /** + * Simple check for equality for all fields. + */ + public void hasOutputDirectoryEqualTo(java.io.File expected) { + if ((actual.getOutputDirectory().equals(expected))) { + failWithActual(fact("expected OutputDirectory to be equal to", expected)); + } + } + + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public FileSubject hasOutputDirectory() { + isNotNull(); + return check("getOutputDirectory").about(files()).that(actual.getOutputDirectory()); + } +} diff --git a/extensions/plugin-maven/src/test/java/com/google/truth/extensions/generator/plugin/GeneratorMojoSubject.java b/extensions/plugin-maven/src/test/java/com/google/truth/extensions/generator/plugin/GeneratorMojoSubject.java new file mode 100644 index 000000000..419842ba2 --- /dev/null +++ b/extensions/plugin-maven/src/test/java/com/google/truth/extensions/generator/plugin/GeneratorMojoSubject.java @@ -0,0 +1,35 @@ +package com.google.truth.extensions.generator.plugin; + +import com.google.common.truth.FailureMetadata; +import com.google.common.truth.extension.generator.UserManagedTruth; +import com.google.common.truth.extension.generator.plugin.GeneratorMojo; +import javax.annotation.processing.Generated; + +/** + * Optionally move this class into source control, and add your custom + * assertions here. + * + *

+ * If the system detects this class already exists, it won't attempt to generate + * a new one. Note that if the base skeleton of this class ever changes, you + * won't automatically get it updated. + * + * @see GeneratorMojo + * @see GeneratorMojoParentSubject + */ +@UserManagedTruth(clazz = GeneratorMojo.class) +@Generated("truth-generator") +public class GeneratorMojoSubject extends GeneratorMojoParentSubject { + + protected GeneratorMojoSubject(FailureMetadata failureMetadata, + com.google.common.truth.extension.generator.plugin.GeneratorMojo actual) { + super(failureMetadata, actual); + } + + /** + * Returns an assertion builder for a {@link GeneratorMojo} class. + */ + public static Factory generatorMojoes() { + return GeneratorMojoSubject::new; + } +} diff --git a/extensions/plugin-maven/src/test/java/com/google/truth/extensions/generator/plugin/ManagedTruth.java b/extensions/plugin-maven/src/test/java/com/google/truth/extensions/generator/plugin/ManagedTruth.java new file mode 100644 index 000000000..75a9441bb --- /dev/null +++ b/extensions/plugin-maven/src/test/java/com/google/truth/extensions/generator/plugin/ManagedTruth.java @@ -0,0 +1,51 @@ +package com.google.truth.extensions.generator.plugin; + +import com.google.truth.extensions.shaded.java.io.FileSubject; + +import java.io.File; +import static com.google.truth.extensions.shaded.java.io.FileSubject.*; +import com.google.common.truth.Truth; +import com.google.truth.extensions.generator.plugin.GeneratorMojoSubject; +import static com.google.truth.extensions.generator.plugin.GeneratorMojoSubject.*; + +/** + * Single point of access for all managed Subjects. + */ +public class ManagedTruth { + + /** + * Entry point for {@link File} assertions. + */ + public static FileSubject assertThat(java.io.File actual) { + return Truth.assertAbout(files()).that(actual); + } + + /** + * Convenience entry point for {@link File} assertions when being mixed with + * other "assertThat" assertion libraries. + * + * @see #assertThat + */ + public static FileSubject assertTruth(java.io.File actual) { + return assertThat(actual); + } + + /** + * Entry point for {@link GeneratorMojo} assertions. + */ + public static GeneratorMojoSubject assertThat( + com.google.common.truth.extension.generator.plugin.GeneratorMojo actual) { + return Truth.assertAbout(generatorMojoes()).that(actual); + } + + /** + * Convenience entry point for {@link GeneratorMojo} assertions when being mixed + * with other "assertThat" assertion libraries. + * + * @see #assertThat + */ + public static GeneratorMojoSubject assertTruth( + com.google.common.truth.extension.generator.plugin.GeneratorMojo actual) { + return assertThat(actual); + } +} diff --git a/extensions/plugin-maven/src/test/java/com/google/truth/extensions/shaded/java/io/FileChildSubject.java b/extensions/plugin-maven/src/test/java/com/google/truth/extensions/shaded/java/io/FileChildSubject.java new file mode 100644 index 000000000..897f8aed7 --- /dev/null +++ b/extensions/plugin-maven/src/test/java/com/google/truth/extensions/shaded/java/io/FileChildSubject.java @@ -0,0 +1,47 @@ +package com.google.truth.extensions.shaded.java.io; + +import com.google.truth.extensions.shaded.java.io.FileSubject; +import com.google.common.truth.FailureMetadata; +import java.io.File; + +import com.google.common.truth.Truth; +import javax.annotation.processing.Generated; + +/** + * Entry point for assertions for @{File}. Import the static accessor methods + * from this class and use them. Combines the generated code from + * {@FileParentSubject}and the user code from {@FileSubject}. + * + * @see java.io.File + * @see FileSubject + * @see FileParentSubject + */ +@Generated("truth-generator") +public class FileChildSubject extends FileSubject { + + /** + * This constructor should not be used, instead see the parent's. + * + * @see FileSubject + */ + private FileChildSubject(FailureMetadata failureMetadata, File actual) { + super(failureMetadata, actual); + } + + /** + * Entry point for {@link File} assertions. + */ + public static FileSubject assertThat(java.io.File actual) { + return Truth.assertAbout(files()).that(actual); + } + + /** + * Convenience entry point for {@link File} assertions when being mixed with + * other "assertThat" assertion libraries. + * + * @see #assertThat + */ + public static FileSubject assertTruth(java.io.File actual) { + return assertThat(actual); + } +} diff --git a/extensions/plugin-maven/src/test/java/com/google/truth/extensions/shaded/java/io/FileParentSubject.java b/extensions/plugin-maven/src/test/java/com/google/truth/extensions/shaded/java/io/FileParentSubject.java new file mode 100644 index 000000000..3311bc61b --- /dev/null +++ b/extensions/plugin-maven/src/test/java/com/google/truth/extensions/shaded/java/io/FileParentSubject.java @@ -0,0 +1,441 @@ +package com.google.truth.extensions.shaded.java.io; + +import com.google.common.truth.Subject; +import javax.annotation.processing.Generated; +import java.io.File; +import com.google.common.truth.FailureMetadata; +import com.google.common.truth.LongSubject; +import static com.google.common.truth.Fact.*; +import com.google.common.truth.BooleanSubject; +import com.google.common.truth.StringSubject; +import static com.google.truth.extensions.shaded.java.io.FileSubject.files; + +import java.io.IOException; + +import com.google.truth.extensions.shaded.java.io.FileSubject; + +/** + * Truth Subject for the {@link File}. + * + * Note that this class is generated / managed, and will change over time. So + * any changes you might make will be overwritten. + * + * @see File + * @see FileSubject + * @see FileChildSubject + */ +@Generated("truth-generator") +public class FileParentSubject extends Subject { + + protected final File actual; + + protected FileParentSubject(FailureMetadata failureMetadata, java.io.File actual) { + super(failureMetadata, actual); + this.actual = actual; + } + + /** + * Simple check for equality for all fields. + */ + public void hasTotalSpaceNotEqualTo(long expected) { + if (!(actual.getTotalSpace() == expected)) { + failWithActual(fact("expected TotalSpace NOT to be equal to", expected)); + } + } + + /** + * Simple check for equality for all fields. + */ + public void hasTotalSpaceEqualTo(long expected) { + if ((actual.getTotalSpace() == expected)) { + failWithActual(fact("expected TotalSpace to be equal to", expected)); + } + } + + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public LongSubject hasTotalSpace() { + isNotNull(); + return check("getTotalSpace").that(actual.getTotalSpace()); + } + + /** + * Simple is or is not expectation for boolean fields. + */ + public void isHidden() { + if (actual.isHidden()) { + failWithActual(simpleFact("expected to be Hidden")); + } + } + + /** + * Simple is or is not expectation for boolean fields. + */ + public void isNotHidden() { + if (!actual.isHidden()) { + failWithActual(simpleFact("expected NOT to be Hidden")); + } + } + + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public BooleanSubject hasHidden() { + isNotNull(); + return check("isHidden").that(actual.isHidden()); + } + + /** + * Simple is or is not expectation for boolean fields. + */ + public void isDirectory() { + if (actual.isDirectory()) { + failWithActual(simpleFact("expected to be Directory")); + } + } + + /** + * Simple is or is not expectation for boolean fields. + */ + public void isNotDirectory() { + if (!actual.isDirectory()) { + failWithActual(simpleFact("expected NOT to be Directory")); + } + } + + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public BooleanSubject hasDirectory() { + isNotNull(); + return check("isDirectory").that(actual.isDirectory()); + } + + /** + * Simple check for equality for all fields. + */ + public void hasAbsolutePathNotEqualTo(java.lang.String expected) { + if (!(actual.getAbsolutePath().equals(expected))) { + failWithActual(fact("expected AbsolutePath NOT to be equal to", expected)); + } + } + + /** + * Simple check for equality for all fields. + */ + public void hasAbsolutePathEqualTo(java.lang.String expected) { + if ((actual.getAbsolutePath().equals(expected))) { + failWithActual(fact("expected AbsolutePath to be equal to", expected)); + } + } + + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public StringSubject hasAbsolutePath() { + isNotNull(); + return check("getAbsolutePath").that(actual.getAbsolutePath()); + } + + /** + * Simple check for equality for all fields. + */ + public void hasAbsoluteFileNotEqualTo(java.io.File expected) { + if (!(actual.getAbsoluteFile().equals(expected))) { + failWithActual(fact("expected AbsoluteFile NOT to be equal to", expected)); + } + } + + /** + * Simple check for equality for all fields. + */ + public void hasAbsoluteFileEqualTo(java.io.File expected) { + if ((actual.getAbsoluteFile().equals(expected))) { + failWithActual(fact("expected AbsoluteFile to be equal to", expected)); + } + } + + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public FileSubject hasAbsoluteFile() { + isNotNull(); + return check("getAbsoluteFile").about(files()).that(actual.getAbsoluteFile()); + } + + /** + * Simple check for equality for all fields. + */ + public void hasParentNotEqualTo(java.lang.String expected) { + if (!(actual.getParent().equals(expected))) { + failWithActual(fact("expected Parent NOT to be equal to", expected)); + } + } + + /** + * Simple check for equality for all fields. + */ + public void hasParentEqualTo(java.lang.String expected) { + if ((actual.getParent().equals(expected))) { + failWithActual(fact("expected Parent to be equal to", expected)); + } + } + + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public StringSubject hasParent() { + isNotNull(); + return check("getParent").that(actual.getParent()); + } + + /** + * Simple check for equality for all fields. + */ + public void hasCanonicalFileNotEqualTo(java.io.File expected) throws IOException { + if (!(actual.getCanonicalFile().equals(expected))) { + failWithActual(fact("expected CanonicalFile NOT to be equal to", expected)); + } + } + + /** + * Simple check for equality for all fields. + */ + public void hasCanonicalFileEqualTo(java.io.File expected) throws IOException { + if ((actual.getCanonicalFile().equals(expected))) { + failWithActual(fact("expected CanonicalFile to be equal to", expected)); + } + } + + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public FileSubject hasCanonicalFile() throws IOException { + isNotNull(); + return check("getCanonicalFile").about(files()).that(actual.getCanonicalFile()); + } + + /** + * Simple check for equality for all fields. + */ + public void hasUsableSpaceNotEqualTo(long expected) { + if (!(actual.getUsableSpace() == expected)) { + failWithActual(fact("expected UsableSpace NOT to be equal to", expected)); + } + } + + /** + * Simple check for equality for all fields. + */ + public void hasUsableSpaceEqualTo(long expected) { + if ((actual.getUsableSpace() == expected)) { + failWithActual(fact("expected UsableSpace to be equal to", expected)); + } + } + + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public LongSubject hasUsableSpace() { + isNotNull(); + return check("getUsableSpace").that(actual.getUsableSpace()); + } + + /** + * Simple check for equality for all fields. + */ + public void hasFreeSpaceNotEqualTo(long expected) { + if (!(actual.getFreeSpace() == expected)) { + failWithActual(fact("expected FreeSpace NOT to be equal to", expected)); + } + } + + /** + * Simple check for equality for all fields. + */ + public void hasFreeSpaceEqualTo(long expected) { + if ((actual.getFreeSpace() == expected)) { + failWithActual(fact("expected FreeSpace to be equal to", expected)); + } + } + + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public LongSubject hasFreeSpace() { + isNotNull(); + return check("getFreeSpace").that(actual.getFreeSpace()); + } + + /** + * Simple check for equality for all fields. + */ + public void hasCanonicalPathNotEqualTo(java.lang.String expected) throws IOException { + if (!(actual.getCanonicalPath().equals(expected))) { + failWithActual(fact("expected CanonicalPath NOT to be equal to", expected)); + } + } + + /** + * Simple check for equality for all fields. + */ + public void hasCanonicalPathEqualTo(java.lang.String expected) throws IOException { + if ((actual.getCanonicalPath().equals(expected))) { + failWithActual(fact("expected CanonicalPath to be equal to", expected)); + } + } + + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public StringSubject hasCanonicalPath() throws IOException { + isNotNull(); + return check("getCanonicalPath").that(actual.getCanonicalPath()); + } + + /** + * Simple check for equality for all fields. + */ + public void hasNameNotEqualTo(java.lang.String expected) { + if (!(actual.getName().equals(expected))) { + failWithActual(fact("expected Name NOT to be equal to", expected)); + } + } + + /** + * Simple check for equality for all fields. + */ + public void hasNameEqualTo(java.lang.String expected) { + if ((actual.getName().equals(expected))) { + failWithActual(fact("expected Name to be equal to", expected)); + } + } + + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public StringSubject hasName() { + isNotNull(); + return check("getName").that(actual.getName()); + } + + /** + * Simple check for equality for all fields. + */ + public void hasParentFileNotEqualTo(java.io.File expected) { + if (!(actual.getParentFile().equals(expected))) { + failWithActual(fact("expected ParentFile NOT to be equal to", expected)); + } + } + + /** + * Simple check for equality for all fields. + */ + public void hasParentFileEqualTo(java.io.File expected) { + if ((actual.getParentFile().equals(expected))) { + failWithActual(fact("expected ParentFile to be equal to", expected)); + } + } + + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public FileSubject hasParentFile() { + isNotNull(); + return check("getParentFile").about(files()).that(actual.getParentFile()); + } + + /** + * Simple is or is not expectation for boolean fields. + */ + public void isFile() { + if (actual.isFile()) { + failWithActual(simpleFact("expected to be File")); + } + } + + /** + * Simple is or is not expectation for boolean fields. + */ + public void isNotFile() { + if (!actual.isFile()) { + failWithActual(simpleFact("expected NOT to be File")); + } + } + + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public BooleanSubject hasFile() { + isNotNull(); + return check("isFile").that(actual.isFile()); + } + + /** + * Simple is or is not expectation for boolean fields. + */ + public void isAbsolute() { + if (actual.isAbsolute()) { + failWithActual(simpleFact("expected to be Absolute")); + } + } + + /** + * Simple is or is not expectation for boolean fields. + */ + public void isNotAbsolute() { + if (!actual.isAbsolute()) { + failWithActual(simpleFact("expected NOT to be Absolute")); + } + } + + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public BooleanSubject hasAbsolute() { + isNotNull(); + return check("isAbsolute").that(actual.isAbsolute()); + } + + /** + * Simple check for equality for all fields. + */ + public void hasPathNotEqualTo(java.lang.String expected) { + if (!(actual.getPath().equals(expected))) { + failWithActual(fact("expected Path NOT to be equal to", expected)); + } + } + + /** + * Simple check for equality for all fields. + */ + public void hasPathEqualTo(java.lang.String expected) { + if ((actual.getPath().equals(expected))) { + failWithActual(fact("expected Path to be equal to", expected)); + } + } + + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public StringSubject hasPath() { + isNotNull(); + return check("getPath").that(actual.getPath()); + } +} diff --git a/extensions/plugin-maven/src/test/java/com/google/truth/extensions/shaded/java/io/FileSubject.java b/extensions/plugin-maven/src/test/java/com/google/truth/extensions/shaded/java/io/FileSubject.java new file mode 100644 index 000000000..7ed6bfa6e --- /dev/null +++ b/extensions/plugin-maven/src/test/java/com/google/truth/extensions/shaded/java/io/FileSubject.java @@ -0,0 +1,48 @@ +package com.google.truth.extensions.shaded.java.io; + +import com.google.common.truth.FailureMetadata; +import java.io.File; +import java.nio.file.Files; + +import com.google.common.truth.extension.generator.UserManagedTruth; +import org.apache.commons.io.FileUtils; + +import javax.annotation.processing.Generated; + +/** + * Optionally move this class into source control, and add your custom + * assertions here. + * + *

+ * If the system detects this class already exists, it won't attempt to generate + * a new one. Note that if the base skeleton of this class ever changes, you + * won't automatically get it updated. + * + * @see File + * @see FileParentSubject + */ +@UserManagedTruth(clazz = File.class) +@Generated("truth-generator") +public class FileSubject extends FileParentSubject { + + protected FileSubject(FailureMetadata failureMetadata, File actual) { + super(failureMetadata, actual); + } + + /** + * Returns an assertion builder for a {@link File} class. + */ + public static Factory files() { + return FileSubject::new; + } + + public void exists() { + boolean exists = actual.exists(); +// if(!exists){ +// String absolutePath = actual.getAbsolutePath(); +// Files.walkFileTree() +// } + check("exists()").that(exists).isTrue(); + } + +} diff --git a/extensions/plugin-maven/src/test/resources/project-to-test/pom.xml b/extensions/plugin-maven/src/test/resources/project-to-test/pom.xml index 8135a5439..f0e764aa3 100644 --- a/extensions/plugin-maven/src/test/resources/project-to-test/pom.xml +++ b/extensions/plugin-maven/src/test/resources/project-to-test/pom.xml @@ -1,24 +1,38 @@ - 4.0.0 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 - sample.plugin - hello-maven-plugin - HEAD-SNAPSHOT - jar - Test MyMojo + sample.plugin + hello-maven-plugin + HEAD-SNAPSHOT + jar + Test MyMojo - - - - maven-my-plugin - - - target/test-harness/project-to-test - - - - + + + + truth-generator-maven-plugin + com.google.truth.extensions + + + + java.io.File + com.google.common.truth.extension.generator.testModel.MyEmployee + + com.google.truth.extensions.tests.projectUnderTest + + target/test-harness/project-to-test + + + + + + + + + + + diff --git a/extensions/pom.xml b/extensions/pom.xml index feb55a230..9990a7b5d 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -14,7 +14,7 @@ pom Truth Extensions (Parent) - Parent metdata for a collection of Truth extensions, Subjects, utilities for + Parent metadata for a collection of Truth extensions, Subjects, utilities for the Truth assertion framework. @@ -24,5 +24,6 @@ proto generator plugin-maven + generator-assertions-tests From f7604390bcda5bb7f03674af70106d56e01bfaba Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Thu, 12 Aug 2021 16:23:50 +0100 Subject: [PATCH 24/32] fix optional chaining --- extensions/generator/pom.xml | 1 + .../extension/generator/SourceClassSets.java | 85 ++++++-- .../generator/internal/ClassUtils.java | 30 +++ .../internal/SubjectMethodGenerator.java | 63 +++--- .../generator/internal/TruthGenerator.java | 78 ++++---- .../generator/TruthGeneratorTest.java | 16 +- .../JavaClassSourceSubject.java | 3 +- .../expected/MyEmployeeChildSubject.java.txt | 7 +- .../expected/MyEmployeeParentSubject.java.txt | 181 +++++++++++++++++- 9 files changed, 345 insertions(+), 119 deletions(-) create mode 100644 extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/ClassUtils.java diff --git a/extensions/generator/pom.xml b/extensions/generator/pom.xml index fae40cc6b..67989a236 100644 --- a/extensions/generator/pom.xml +++ b/extensions/generator/pom.xml @@ -34,6 +34,7 @@ compile + com.google.truth.extensions truth-java8-extension compile diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/SourceClassSets.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/SourceClassSets.java index bc3f48371..a996b5696 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/SourceClassSets.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/SourceClassSets.java @@ -2,16 +2,17 @@ import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.Multimaps; +import com.google.common.truth.extension.generator.internal.ClassUtils; import lombok.Getter; import lombok.Value; -import java.util.Arrays; import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; import static java.util.Arrays.asList; import static java.util.Arrays.stream; +import static java.util.stream.Collectors.toSet; /** * Use this class to prepare the set of source classes to generate for, and settings for different types of sources. @@ -20,13 +21,37 @@ public class SourceClassSets { private final String packageForOverall; - private final Set[]> simplePackageOfClasses = new HashSet<>(); + + /** + * + */ + //todo rename + private final Set simplePackages = new HashSet<>(); + + /** + * + */ private final Set> simpleClasses = new HashSet<>(); - private final Set packageAndClasses = new HashSet<>(); + + /** + * + */ + private final Set targetPackageAndClasses = new HashSet<>(); + + /** + * + */ private final Set> legacyBeans = new HashSet<>(); - private final Set legacyPackageAndClasses = new HashSet<>(); - private Set> classSetCache; + /** + * + */ + private final Set legacyTargetPackageAndClasses = new HashSet<>(); + + /** + * + */ + private Set> classSetCache; /** * @param packageForOverall the package to put the overall access points @@ -42,12 +67,25 @@ public SourceClassSets(Object packageFromObject) { this(packageFromObject.getClass().getPackage().getName()); } + /** + * Use the package of this class base package; + */ public SourceClassSets(Class packageFromClass) { this(packageFromClass.getPackage().getName()); } public void generateAllFoundInPackagesOf(Class... classes) { - simplePackageOfClasses.add(classes); + Set collect = stream(classes).map(x -> x.getPackage().getName()).collect(toSet()); + simplePackages.addAll(collect); + } + + public void generateAllFoundInPackages(Package... packages) { + Set collect = stream(packages).map(Package::getName).collect(toSet()); + simplePackages.addAll(collect); + } + + public void generateAllFoundInPackages(String... packageNames) { + simplePackages.addAll(stream(packageNames).collect(toSet())); } /** @@ -56,11 +94,11 @@ public void generateAllFoundInPackagesOf(Class... classes) { * I.e. for UUID.class you can't create a Subject in the same package as it (not allowed). */ public void generateFrom(String targetPackageName, Class... classes) { - packageAndClasses.add(new PackageAndClasses(targetPackageName, classes)); + targetPackageAndClasses.add(new TargetPackageAndClasses(targetPackageName, classes)); } public void generateFrom(Class... classes) { - this.simpleClasses.addAll(stream(classes).collect(Collectors.toSet())); + this.simpleClasses.addAll(stream(classes).collect(toSet())); } public void generateFrom(Set> classes) { @@ -71,19 +109,19 @@ public void generateFrom(Set> classes) { * Shades the given source classes into the base package, suffixed with the source package */ public void generateFromShaded(Class... classes) { - Set packageAndClassesStream = mapToPackageSets(classes); - this.packageAndClasses.addAll(packageAndClassesStream); + Set targetPackageAndClassesStream = mapToPackageSets(classes); + this.targetPackageAndClasses.addAll(targetPackageAndClassesStream); } - private Set mapToPackageSets(Class[] classes) { + private Set mapToPackageSets(Class[] classes) { ImmutableListMultimap> grouped = Multimaps.index(asList(classes), Class::getPackage); return grouped.keySet().stream().map(x -> { Class[] classSet = grouped.get(x).toArray(new Class[0]); - PackageAndClasses newSet = new PackageAndClasses(getTargetPackageName(x), + TargetPackageAndClasses newSet = new TargetPackageAndClasses(getTargetPackageName(x), classSet); return newSet; - }).collect(Collectors.toSet()); + }).collect(toSet()); } private String getTargetPackageName(Package p) { @@ -97,8 +135,8 @@ public void generateFromNonBean(Class... nonBeanLegacyClass) { } public void generateFromShadedNonBean(Class... clazzes) { - Set packageAndClassesStream = mapToPackageSets(clazzes); - this.legacyPackageAndClasses.addAll(packageAndClassesStream); + Set targetPackageAndClassesStream = mapToPackageSets(clazzes); + this.legacyTargetPackageAndClasses.addAll(targetPackageAndClassesStream); } public void generateFrom(ClassLoader loader, String... classes) { @@ -118,14 +156,15 @@ public Set> getAllClasses() { union.addAll(getSimpleClasses()); union.addAll(getLegacyBeans()); - Set> collect = getPackageAndClasses().stream().flatMap(x -> + Set> collect = getTargetPackageAndClasses().stream().flatMap(x -> stream(x.classes) - ).collect(Collectors.toSet()); + ).collect(toSet()); union.addAll(collect); - union.addAll(getLegacyPackageAndClasses().stream().flatMap(x -> stream(x.classes)).collect(Collectors.toSet())); + union.addAll(getLegacyTargetPackageAndClasses().stream().flatMap(x -> stream(x.classes)).collect(toSet())); - union.addAll(getSimplePackageOfClasses().stream().flatMap(x -> stream(x)).collect(Collectors.toSet())); + union.addAll(getSimplePackages().stream().flatMap( + x -> ClassUtils.collectSourceClasses(x).stream()).collect(toSet())); // todo need more elegant solution than this this.classSetCache = union; @@ -134,6 +173,7 @@ public Set> getAllClasses() { // todo shouldn't be public? public void addIfMissing(final Set> clazzes) { + getAllClasses(); // update class set cache clazzes.forEach(x -> { if (!classSetCache.contains(x)) generateFrom(x); @@ -147,11 +187,14 @@ public boolean isClassIncluded(final Class clazz) { public boolean isLegacyClass(final Class theClass) { return getLegacyBeans().contains(theClass) - || getLegacyPackageAndClasses().stream().anyMatch(x-> asList(x.classes).contains(theClass)); + || getLegacyTargetPackageAndClasses().stream().anyMatch(x -> asList(x.classes).contains(theClass)); } + /** + * Container for classes and the target package they're to be produced into + */ @Value - public static class PackageAndClasses { + public static class TargetPackageAndClasses { String targetPackageName; Class[] classes; } diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/ClassUtils.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/ClassUtils.java new file mode 100644 index 000000000..79373a7f4 --- /dev/null +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/ClassUtils.java @@ -0,0 +1,30 @@ +package com.google.common.truth.extension.generator.internal; + +import com.google.common.truth.Subject; +import org.reflections.Reflections; +import org.reflections.scanners.SubTypesScanner; + +import java.util.Set; +import java.util.stream.Collectors; + +public class ClassUtils { + + public static Set> collectSourceClasses(String... modelPackages) { + // for all classes in package + SubTypesScanner subTypesScanner = new SubTypesScanner(false); + + Reflections reflections = new Reflections(modelPackages, subTypesScanner); + reflections.expandSuperTypes(); // get things that extend something that extend object + + // https://github.com/ronmamo/reflections/issues/126 + Set> subTypesOfEnums = reflections.getSubTypesOf(Enum.class); + + Set> allTypes = reflections.getSubTypesOf(Object.class) + // remove Subject classes from previous runs + .stream().filter(x -> !Subject.class.isAssignableFrom(x)) + .collect(Collectors.toSet()); + allTypes.addAll(subTypesOfEnums); + return allTypes; + } + +} diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java index 7e09edca9..0d8a315fb 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java @@ -1,13 +1,9 @@ package com.google.common.truth.extension.generator.internal; -import com.google.common.collect.Sets; import com.google.common.flogger.FluentLogger; -import com.google.common.truth.Fact; -import com.google.common.truth.ObjectArraySubject; -import com.google.common.truth.Subject; +import com.google.common.truth.*; import com.google.common.truth.extension.generator.internal.model.ThreeSystem; import lombok.Getter; -import org.apache.commons.lang3.ClassUtils; import org.apache.commons.lang3.StringUtils; import org.jboss.forge.roaster.model.source.Import; import org.jboss.forge.roaster.model.source.JavaClassSource; @@ -15,7 +11,6 @@ import org.reflections.ReflectionUtils; import org.reflections.Reflections; -import javax.swing.text.html.Option; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.math.BigDecimal; @@ -31,7 +26,6 @@ import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; import static org.apache.commons.lang3.ClassUtils.primitiveToWrapper; -import static org.apache.commons.lang3.ClassUtils.primitivesToWrappers; import static org.apache.commons.lang3.StringUtils.*; import static org.reflections.ReflectionUtils.*; @@ -43,7 +37,7 @@ public class SubjectMethodGenerator { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - private final Map> compiledSubjects; + private final Map> classPathSubjectTypes = new HashMap<>(); private final Map generatedSubjects; private ThreeSystem context; @@ -51,11 +45,9 @@ public SubjectMethodGenerator(final Set allTypes) { this.generatedSubjects = allTypes.stream().collect(Collectors.toMap(x -> x.classUnderTest.getName(), x -> x)); Reflections reflections = new Reflections("com.google.common.truth", "io.confluent"); - Set> subTypes = reflections.getSubTypesOf(Subject.class); + Set> subjectTypes = reflections.getSubTypesOf(Subject.class); - Map> maps = new HashMap<>(); - subTypes.forEach(x -> maps.put(x.getSimpleName(), x)); - this.compiledSubjects = maps; + subjectTypes.forEach(x -> classPathSubjectTypes.put(x.getSimpleName(), x)); } public void addTests(ThreeSystem system) { @@ -148,7 +140,8 @@ private String getSignature(final Method getter) { } /** - * In priority order - most specific first + * In priority order - most specific first. Types that are native to {@link Truth} - i.e. you can call {@link + * Truth#assertThat}(...) with it. Note that this does not include {@link Truth8} types. */ @Getter private static final HashSet> nativeTypes = new LinkedHashSet(); @@ -159,7 +152,6 @@ private String getSignature(final Method getter) { static { // Class[] classes = { - Optional.class, Map.class, Iterable.class, List.class, @@ -174,8 +166,8 @@ private String getSignature(final Method getter) { Long.class, Integer.class, Short.class, - Boolean.class, - Object.class}; + Boolean.class + }; nativeTypes.addAll(Arrays.stream(classes).collect(Collectors.toList())); // @@ -309,7 +301,6 @@ private MethodSource addOptionalStrategyGeneric(Method method, .setReturnTypeVoid() .setBody(body) .setPublic(); - newMethod.addParameter(method.getReturnType(), "expected"); newMethod.getJavaDoc().setText("Checks Optional fields for presence."); @@ -516,6 +507,7 @@ private Optional getSubjectForType(final Class type) { name = type.getName(); } + // arrays if (type.isArray()) { Class componentType = type.getComponentType(); if (componentType.isPrimitive()) { @@ -529,18 +521,11 @@ private Optional getSubjectForType(final Class type) { } // - Optional subject = getSubjectFromString(name); - -// // iterables -// if (subject.isEmpty()) { -// if (Iterable.class.isAssignableFrom(type)) { -// subject = getSubjectForType(Iterable.class); -// } -// } + Optional subject = getGeneratedOrCompiledSubjectFromString(name); - // fall back to native subjects + // Can't find any generated ones or compiled ones - fall back to native subjects if (subject.isEmpty()) { - Optional> nativeSubjectForType = getNativeSubjectForType(type); + Optional> nativeSubjectForType = getClosestTruthNativeSubjectForType(type); subject = ClassOrGenerated.ofClass(nativeSubjectForType); if (subject.isPresent()) logger.at(INFO).log("Falling back to native interface subject %s for type %s", subject.get().clazz, type); @@ -549,18 +534,18 @@ private Optional getSubjectForType(final Class type) { return subject; } - private Optional> getNativeSubjectForType(final Class type) { + private Optional> getClosestTruthNativeSubjectForType(final Class type) { Class normalised = primitiveToWrapper(type); - Optional> highestPiorityNativeType = nativeTypes.stream().filter(x -> x.isAssignableFrom(normalised)).findFirst(); - if (highestPiorityNativeType.isPresent()) { - Class aClass = highestPiorityNativeType.get(); + Optional> highestPriorityNativeType = nativeTypes.stream().filter(x -> x.isAssignableFrom(normalised)).findFirst(); + if (highestPriorityNativeType.isPresent()) { + Class aClass = highestPriorityNativeType.get(); Class compiledSubjectForTypeName = getCompiledSubjectForTypeName(aClass.getSimpleName()); return ofNullable(compiledSubjectForTypeName); } return empty(); } - private Optional getSubjectFromString(final String name) { + private Optional getGeneratedOrCompiledSubjectFromString(final String name) { boolean isObjectArray = name.endsWith("[]"); if (isObjectArray) return of(new ClassOrGenerated(ObjectArraySubject.class, null)); @@ -583,7 +568,7 @@ private Class getCompiledSubjectForTypeName(String name) { name = StringUtils.substringAfterLast(name, "."); String compoundName = name + "Subject"; - Class aClass = this.compiledSubjects.get(compoundName); + Class aClass = this.classPathSubjectTypes.get(compoundName); return aClass; } @@ -602,8 +587,20 @@ public void addTests(final Set allTypes) { } } + /** + * An `Either` type that represents a {@link Subject} for a type being either an existing Compiled class, or a new + * Generated class. + */ public static class ClassOrGenerated { + + /** + * an existing Subject class on the class path + */ final Class clazz; + + /** + * a new generated Subject + */ final ThreeSystem generated; ClassOrGenerated(final Class clazz, final ThreeSystem generated) { diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java index 96069508d..a89b6ed16 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java @@ -3,18 +3,23 @@ import com.google.common.flogger.FluentLogger; import com.google.common.truth.Subject; import com.google.common.truth.extension.generator.SourceClassSets; -import com.google.common.truth.extension.generator.SourceClassSets.PackageAndClasses; +import com.google.common.truth.extension.generator.SourceClassSets.TargetPackageAndClasses; import com.google.common.truth.extension.generator.TruthGeneratorAPI; import com.google.common.truth.extension.generator.internal.model.ThreeSystem; import lombok.Getter; import lombok.Setter; -import org.reflections.Reflections; -import org.reflections.scanners.SubTypesScanner; -import java.util.*; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.logging.Level; import java.util.stream.Collectors; +import static java.util.Arrays.stream; +import static java.util.Set.of; +import static java.util.stream.Collectors.toSet; + /** * @author Antony Stubbs */ @@ -36,15 +41,15 @@ public void generate(String... modelPackages) { // todo createEntryPointForPackages(modelPackages) String[] packageNameForOverall = modelPackages; OverallEntryPoint overallEntryPoint = new OverallEntryPoint(packageNameForOverall[0]); - Set subjectsSystems = generateSkeletonsFromPackages(modelPackages, overallEntryPoint); + Set subjectsSystems = generateSkeletonsFromPackages(stream(modelPackages).collect(toSet()), overallEntryPoint); // addTests(subjectsSystems); overallEntryPoint.createOverallAccessPoints(); } - private Set generateSkeletonsFromPackages(final String[] modelPackages, OverallEntryPoint overallEntryPoint) { - Set> allTypes = collectSourceClasses(modelPackages); + private Set generateSkeletonsFromPackages(Set modelPackages, OverallEntryPoint overallEntryPoint) { + Set> allTypes = ClassUtils.collectSourceClasses(modelPackages.toArray(new String[0])); return generateSkeletons(allTypes, Optional.empty(), overallEntryPoint); } @@ -68,29 +73,11 @@ private Set generateSkeletons(Set> classes, Optional> filterSubjects(Set> classes, int sizeBeforeFilter) { // filter existing subjects from inbound set - classes = classes.stream().filter(x -> !Subject.class.isAssignableFrom(x)).collect(Collectors.toSet()); + classes = classes.stream().filter(x -> !Subject.class.isAssignableFrom(x)).collect(toSet()); log.at(Level.FINE).log("Removed %s Subjects from inbound", classes.size() - sizeBeforeFilter); return classes; } - private Set> collectSourceClasses(final String[] modelPackages) { - // for all classes in package - SubTypesScanner subTypesScanner = new SubTypesScanner(false); - - Reflections reflections = new Reflections(modelPackages, subTypesScanner); - reflections.expandSuperTypes(); // get things that extend something that extend object - - // https://github.com/ronmamo/reflections/issues/126 - Set> subTypesOfEnums = reflections.getSubTypesOf(Enum.class); - - Set> allTypes = reflections.getSubTypesOf(Object.class) - // remove Subject classes from previous runs - .stream().filter(x -> !Subject.class.isAssignableFrom(x)) - .collect(Collectors.toSet()); - allTypes.addAll(subTypesOfEnums); - return allTypes; - } - private void addTests(final Set allTypes) { SubjectMethodGenerator tg = new SubjectMethodGenerator(allTypes); tg.addTests(allTypes); @@ -98,16 +85,16 @@ private void addTests(final Set allTypes) { @Override public void generateFromPackagesOf(Class... classes) { - generate(getPackageStrings(classes)); + Optional> first = stream(classes).findFirst(); + if (first.isEmpty()) throw new IllegalArgumentException("Must provide at least one Class"); + SourceClassSets ss = new SourceClassSets(first.get().getPackage().getName()); + ss.generateAllFoundInPackagesOf(classes); + generate(ss); } @Override public void combinedSystem(final SourceClassSets ss) { - - } - - private String[] getPackageStrings(final Class[] classes) { - return Arrays.stream(classes).map(x -> x.getPackage().getName()).collect(Collectors.toList()).toArray(new String[0]); + throw new IllegalStateException(); // todo - remove? } @Override @@ -126,25 +113,24 @@ public Map, ThreeSystem> generate(SourceClassSets ss) { } } - Set packages = ss.getSimplePackageOfClasses().stream().map( - this::getPackageStrings - ).collect(Collectors.toSet()); + // from packages + Set packages = ss.getSimplePackages(); OverallEntryPoint overallEntryPoint = new OverallEntryPoint(ss.getPackageForOverall()); // skeletons generation is independent and should be able to be done in parallel Set skeletons = packages.parallelStream().flatMap( - x -> generateSkeletonsFromPackages(x, overallEntryPoint).stream() - ).collect(Collectors.toSet()); + aPackage -> generateSkeletonsFromPackages(of(aPackage), overallEntryPoint).stream() + ).collect(toSet()); // custom package destination - Set packageAndClasses = ss.getPackageAndClasses(); - Set setStream = packageAndClasses.stream().flatMap( + Set targetPackageAndClasses = ss.getTargetPackageAndClasses(); + Set setStream = targetPackageAndClasses.stream().flatMap( x -> { - Set> collect = Arrays.stream(x.getClasses()).collect(Collectors.toSet()); + Set> collect = stream(x.getClasses()).collect(toSet()); return generateSkeletons(collect, Optional.of(x.getTargetPackageName()), overallEntryPoint).stream(); } - ).collect(Collectors.toSet()); + ).collect(toSet()); // straight up classes Set simpleClasses = generateSkeletons(ss.getSimpleClasses(), Optional.empty(), overallEntryPoint); @@ -154,13 +140,13 @@ public Map, ThreeSystem> generate(SourceClassSets ss) { legacyClasses.forEach(x -> x.setLegacyMode(true)); // legacy classes with custom package destination - Set legacyPackageAndClasses = ss.getLegacyPackageAndClasses(); - Set legacyPackageSet = legacyPackageAndClasses.stream().flatMap( + Set legacyTargetPackageAndClasses = ss.getLegacyTargetPackageAndClasses(); + Set legacyPackageSet = legacyTargetPackageAndClasses.stream().flatMap( x -> { - Set> collect = Arrays.stream(x.getClasses()).collect(Collectors.toSet()); + Set> collect = stream(x.getClasses()).collect(toSet()); return generateSkeletons(collect, Optional.of(x.getTargetPackageName()), overallEntryPoint).stream(); } - ).collect(Collectors.toSet()); + ).collect(toSet()); legacyPackageSet.forEach(x -> x.setLegacyMode(true)); @@ -201,7 +187,7 @@ private String createEntrypointPackage(final Set> classes) { @Override public Map, ThreeSystem> generate(Class... classes) { - return generate(Arrays.stream(classes).collect(Collectors.toSet())); + return generate(stream(classes).collect(toSet())); } @Override diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java index 90a416601..8223263de 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java @@ -1,17 +1,17 @@ package com.google.common.truth.extension.generator; import com.google.common.io.Resources; -import com.google.common.truth.StreamSubject; -import com.google.common.truth.extension.generator.internal.SkeletonGenerator; import com.google.common.truth.extension.generator.internal.TruthGenerator; import com.google.common.truth.extension.generator.internal.model.ThreeSystem; import com.google.common.truth.extension.generator.internal.modelSubjectChickens.ThreeSystemChildSubject; -import com.google.common.truth.extension.generator.testModel.*; +import com.google.common.truth.extension.generator.testModel.IdCard; +import com.google.common.truth.extension.generator.testModel.MyEmployee; +import com.google.common.truth.extension.generator.testModel.NonBeanLegacy; +import com.google.common.truth.extension.generator.testModel.Project; import org.jboss.forge.roaster.model.source.JavaClassSource; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import uk.co.jemos.podam.api.PodamFactoryImpl; import java.io.IOException; import java.nio.charset.Charset; @@ -21,6 +21,7 @@ import java.time.chrono.Chronology; import java.util.*; import java.util.stream.Collectors; +import java.util.stream.Stream; import static com.google.common.truth.Truth.assertThat; @@ -43,6 +44,9 @@ public void combined() { assertThat(trim("")).isEqualTo(trim(generate)); } + /** + * Base test that compares with expected generated code for test model + */ @Test public void generate_code() throws IOException { // todo need to be able to set base package for all generated classes, kind of like shade, so you cah generate test for classes in other restricted modules @@ -112,7 +116,7 @@ public void package_java_mix() { ss.generateFromShaded(ZoneId.class, ZonedDateTime.class, Chronology.class); Map, ThreeSystem> generated = tg.generate(ss); - assertThat(generated.size()).isAtLeast(ss.getPackageAndClasses().size()); + assertThat(generated.size()).isAtLeast(ss.getTargetPackageAndClasses().size()); } @Test @@ -146,7 +150,7 @@ public void recursive_generation() { // recursive subjects that shouldn't be included assertThat(generate).doesNotContainKey(Spliterator.class); - assertThat(generate).doesNotContainKey(StreamSubject.class); + assertThat(generate).doesNotContainKey(Stream.class); } /** diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/shaded/org/jboss/forge/roaster/model/sourceChickens/JavaClassSourceSubject.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/shaded/org/jboss/forge/roaster/model/sourceChickens/JavaClassSourceSubject.java index 7433a5f1b..b194f5639 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/shaded/org/jboss/forge/roaster/model/sourceChickens/JavaClassSourceSubject.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/shaded/org/jboss/forge/roaster/model/sourceChickens/JavaClassSourceSubject.java @@ -1,8 +1,8 @@ package com.google.common.truth.extension.generator.shaded.org.jboss.forge.roaster.model.sourceChickens; import com.google.common.truth.FailureMetadata; -import com.google.common.truth.extension.generator.internal.MyStringSubject; import com.google.common.truth.extension.generator.UserManagedTruth; +import com.google.common.truth.extension.generator.internal.MyStringSubject; import org.jboss.forge.roaster.model.source.JavaClassSource; import javax.annotation.processing.Generated; @@ -34,6 +34,7 @@ public static Factory javaClassSources( } public MyStringSubject hasSourceText() { + isNotNull(); return check("toString").about(MyStringSubject.myStrings()).that(actual.toString()); } diff --git a/extensions/generator/src/test/resources/expected/MyEmployeeChildSubject.java.txt b/extensions/generator/src/test/resources/expected/MyEmployeeChildSubject.java.txt index 12e95e96f..0eed5c790 100644 --- a/extensions/generator/src/test/resources/expected/MyEmployeeChildSubject.java.txt +++ b/extensions/generator/src/test/resources/expected/MyEmployeeChildSubject.java.txt @@ -9,11 +9,10 @@ import javax.annotation.processing.Generated; /** * Entry point for assertions for @{MyEmployee}. Import the static accessor * methods from this class and use them. Combines the generated code from - * {@MyEmployeeParentSubject}and the user code from - * {@com.google.common.truth.extension.generator.testModel.MyEmployeeSubject}. + * {@MyEmployeeParentSubject}and the user code from {@MyEmployeeSubject}. * * @see com.google.common.truth.extension.generator.testModel.MyEmployee - * @see com.google.common.truth.extension.generator.testModel.MyEmployeeSubject + * @see MyEmployeeSubject * @see MyEmployeeParentSubject */ @Generated("truth-generator") @@ -22,7 +21,7 @@ public class MyEmployeeChildSubject extends MyEmployeeSubject { /** * This constructor should not be used, instead see the parent's. * - * @see com.google.common.truth.extension.generator.testModel.MyEmployeeSubject + * @see MyEmployeeSubject */ private MyEmployeeChildSubject(FailureMetadata failureMetadata, com.google.common.truth.extension.generator.testModel.MyEmployee actual) { diff --git a/extensions/generator/src/test/resources/expected/MyEmployeeParentSubject.java.txt b/extensions/generator/src/test/resources/expected/MyEmployeeParentSubject.java.txt index 81d24f170..a29f59476 100644 --- a/extensions/generator/src/test/resources/expected/MyEmployeeParentSubject.java.txt +++ b/extensions/generator/src/test/resources/expected/MyEmployeeParentSubject.java.txt @@ -11,12 +11,14 @@ import com.google.common.truth.extension.generator.testModel.MyEmployee.State; import static com.google.common.truth.extension.generator.testModel.StateSubject.states; import com.google.common.truth.extension.generator.testModel.StateSubject; import java.util.Optional; +import static com.google.common.truth.extension.generator.autoShaded.java.util.OptionalSubject.optionals; +import com.google.common.truth.extension.generator.autoShaded.java.util.OptionalSubject; import java.time.ZonedDateTime; -import static com.google.common.truth.extension.generator.shaded.java.time.ZonedDateTimeSubject.zonedDateTimes; -import static com.google.common.truth.extension.generator.shaded.java.time.ZonedDateTimeSubject.zonedDateTimes; +import static com.google.common.truth.extension.generator.autoShaded.java.time.ZonedDateTimeSubject.zonedDateTimes; +import static com.google.common.truth.extension.generator.autoShaded.java.time.ZonedDateTimeSubject.zonedDateTimes; import java.util.UUID; -import static com.google.common.truth.extension.generator.UUIDSubject.uUIDs; -import com.google.common.truth.extension.generator.UUIDSubject; +import static com.google.common.truth.extension.generator.autoShaded.java.util.UUIDSubject.uUIDs; +import com.google.common.truth.extension.generator.autoShaded.java.util.UUIDSubject; import com.google.common.truth.StringSubject; import com.google.common.truth.LongSubject; import java.util.List; @@ -24,7 +26,7 @@ import com.google.common.truth.IterableSubject; import java.util.Map; import com.google.common.truth.MapSubject; import com.google.common.truth.BooleanSubject; -import com.google.common.truth.extension.generator.shaded.java.time.ZonedDateTimeSubject; +import com.google.common.truth.extension.generator.autoShaded.java.time.ZonedDateTimeSubject; import static com.google.common.truth.extension.generator.testModel.IdCardSubject.idCards; import com.google.common.truth.extension.generator.testModel.IdCardSubject; @@ -49,46 +51,72 @@ public class MyEmployeeParentSubject extends Subject { this.actual = actual; } + /** + * Simple check for equality for all fields. + */ public void hasBirthYearNotEqualTo(int expected) { if (!(actual.getBirthYear() == expected)) { failWithActual(fact("expected BirthYear NOT to be equal to", expected)); } } + /** + * Simple check for equality for all fields. + */ public void hasBirthYearEqualTo(int expected) { if ((actual.getBirthYear() == expected)) { failWithActual(fact("expected BirthYear to be equal to", expected)); } } + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ public IntegerSubject hasBirthYear() { isNotNull(); return check("getBirthYear").that(actual.getBirthYear()); } + /** + * Simple check for equality for all fields. + */ public void hasBossNotEqualTo(com.google.common.truth.extension.generator.testModel.MyEmployee expected) { if (!(actual.getBoss().equals(expected))) { failWithActual(fact("expected Boss NOT to be equal to", expected)); } } + /** + * Simple check for equality for all fields. + */ public void hasBossEqualTo(com.google.common.truth.extension.generator.testModel.MyEmployee expected) { if ((actual.getBoss().equals(expected))) { failWithActual(fact("expected Boss to be equal to", expected)); } } + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ public MyEmployeeSubject hasBoss() { isNotNull(); return check("getBoss").about(myEmployees()).that(actual.getBoss()); } + /** + * Simple check for equality for all fields. + */ public void hasEmploymentStateNotEqualTo(State expected) { if (!(actual.getEmploymentState().equals(expected))) { failWithActual(fact("expected EmploymentState NOT to be equal to", expected)); } } + /** + * Simple check for equality for all fields. + */ public void hasEmploymentStateEqualTo( com.google.common.truth.extension.generator.testModel.MyEmployee.State expected) { if ((actual.getEmploymentState().equals(expected))) { @@ -96,224 +124,361 @@ public class MyEmployeeParentSubject extends Subject { } } + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ public StateSubject hasEmploymentState() { isNotNull(); return check("getEmploymentState").about(states()).that(actual.getEmploymentState()); } - public void hasWeightingNotPresent(Optional expected) { + /** + * Checks Optional fields for presence. + */ + public void hasWeightingNotPresent() { if (!actual.getWeighting().isPresent()) { failWithActual(simpleFact("expected Weighting NOT to be present")); } } - public void hasWeightingPresent(java.util.Optional expected) { + /** + * Checks Optional fields for presence. + */ + public void hasWeightingPresent() { if (actual.getWeighting().isPresent()) { failWithActual(simpleFact("expected Weighting to be present")); } } - public void hasWeightingNotEqualTo(java.util.Optional expected) { + /** + * Simple check for equality for all fields. + */ + public void hasWeightingNotEqualTo(Optional expected) { if (!(actual.getWeighting().equals(expected))) { failWithActual(fact("expected Weighting NOT to be equal to", expected)); } } + /** + * Simple check for equality for all fields. + */ public void hasWeightingEqualTo(java.util.Optional expected) { if ((actual.getWeighting().equals(expected))) { failWithActual(fact("expected Weighting to be equal to", expected)); } } + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public OptionalSubject hasWeighting() { + isNotNull(); + return check("getWeighting").about(optionals()).that(actual.getWeighting()); + } + + /** + * Simple check for equality for all fields. + */ public void hasAnniversaryNotEqualTo(ZonedDateTime expected) { if (!(actual.getAnniversary().equals(expected))) { failWithActual(fact("expected Anniversary NOT to be equal to", expected)); } } + /** + * Simple check for equality for all fields. + */ public void hasAnniversaryEqualTo(java.time.ZonedDateTime expected) { if ((actual.getAnniversary().equals(expected))) { failWithActual(fact("expected Anniversary to be equal to", expected)); } } + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ public ZonedDateTimeSubject hasAnniversary() { isNotNull(); return check("getAnniversary").about(zonedDateTimes()).that(actual.getAnniversary()); } + /** + * Simple check for equality for all fields. + */ public void hasIdNotEqualTo(UUID expected) { if (!(actual.getId().equals(expected))) { failWithActual(fact("expected Id NOT to be equal to", expected)); } } + /** + * Simple check for equality for all fields. + */ public void hasIdEqualTo(java.util.UUID expected) { if ((actual.getId().equals(expected))) { failWithActual(fact("expected Id to be equal to", expected)); } } + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ public UUIDSubject hasId() { isNotNull(); return check("getId").about(uUIDs()).that(actual.getId()); } + /** + * Simple check for equality for all fields. + */ public void hasNameNotEqualTo(java.lang.String expected) { if (!(actual.getName().equals(expected))) { failWithActual(fact("expected Name NOT to be equal to", expected)); } } + /** + * Simple check for equality for all fields. + */ public void hasNameEqualTo(java.lang.String expected) { if ((actual.getName().equals(expected))) { failWithActual(fact("expected Name to be equal to", expected)); } } + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ public StringSubject hasName() { isNotNull(); return check("getName").that(actual.getName()); } + /** + * Simple check for equality for all fields. + */ public void hasSomeLongAspectNotEqualTo(long expected) { if (!(actual.getSomeLongAspect() == expected)) { failWithActual(fact("expected SomeLongAspect NOT to be equal to", expected)); } } + /** + * Simple check for equality for all fields. + */ public void hasSomeLongAspectEqualTo(long expected) { if ((actual.getSomeLongAspect() == expected)) { failWithActual(fact("expected SomeLongAspect to be equal to", expected)); } } + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ public LongSubject hasSomeLongAspect() { isNotNull(); return check("getSomeLongAspect").that(actual.getSomeLongAspect()); } + /** + * Checks if the element is or is not contained in the collection. + */ public void hasProjectListNotWithElement(java.lang.Object expected) { if (!actual.getProjectList().contains(expected)) { failWithActual(fact("expected ProjectList NOT to have element", expected)); } } + /** + * Checks if the element is or is not contained in the collection. + */ public void hasProjectListWithElement(java.lang.Object expected) { if (actual.getProjectList().contains(expected)) { failWithActual(fact("expected ProjectList to have element", expected)); } } + /** + * Simple check for equality for all fields. + */ public void hasProjectListNotEqualTo(List expected) { if (!(actual.getProjectList().equals(expected))) { failWithActual(fact("expected ProjectList NOT to be equal to", expected)); } } + /** + * Simple check for equality for all fields. + */ public void hasProjectListEqualTo(java.util.List expected) { if ((actual.getProjectList().equals(expected))) { failWithActual(fact("expected ProjectList to be equal to", expected)); } } + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ public IterableSubject hasProjectList() { isNotNull(); return check("getProjectList").that(actual.getProjectList()); } + /** + * Check Maps for containing a given key. + */ public void hasProjectMapNotWithKey(java.lang.Object expected) { if (!actual.getProjectMap().containsKey(expected)) { failWithActual(fact("expected ProjectMap NOT to have key", expected)); } } + /** + * Check Maps for containing a given key. + */ public void hasProjectMapWithKey(java.lang.Object expected) { if (actual.getProjectMap().containsKey(expected)) { failWithActual(fact("expected ProjectMap to have key", expected)); } } + /** + * Simple check for equality for all fields. + */ public void hasProjectMapNotEqualTo(Map expected) { if (!(actual.getProjectMap().equals(expected))) { failWithActual(fact("expected ProjectMap NOT to be equal to", expected)); } } + /** + * Simple check for equality for all fields. + */ public void hasProjectMapEqualTo(java.util.Map expected) { if ((actual.getProjectMap().equals(expected))) { failWithActual(fact("expected ProjectMap to be equal to", expected)); } } + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ public MapSubject hasProjectMap() { isNotNull(); return check("getProjectMap").that(actual.getProjectMap()); } + /** + * Simple is or is not expectation for boolean fields. + */ public void isEmployed() { if (actual.isEmployed()) { failWithActual(simpleFact("expected to be Employed")); } } + /** + * Simple is or is not expectation for boolean fields. + */ public void isNotEmployed() { if (!actual.isEmployed()) { failWithActual(simpleFact("expected NOT to be Employed")); } } + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ public BooleanSubject hasEmployed() { isNotNull(); return check("isEmployed").that(actual.isEmployed()); } + /** + * Simple check for equality for all fields. + */ public void hasBirthdayNotEqualTo(java.time.ZonedDateTime expected) { if (!(actual.getBirthday().equals(expected))) { failWithActual(fact("expected Birthday NOT to be equal to", expected)); } } + /** + * Simple check for equality for all fields. + */ public void hasBirthdayEqualTo(java.time.ZonedDateTime expected) { if ((actual.getBirthday().equals(expected))) { failWithActual(fact("expected Birthday to be equal to", expected)); } } + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ public ZonedDateTimeSubject hasBirthday() { isNotNull(); return check("getBirthday").about(zonedDateTimes()).that(actual.getBirthday()); } + /** + * Simple check for equality for all fields. + */ public void hasCardNotEqualTo(com.google.common.truth.extension.generator.testModel.IdCard expected) { if (!(actual.getCard().equals(expected))) { failWithActual(fact("expected Card NOT to be equal to", expected)); } } + /** + * Simple check for equality for all fields. + */ public void hasCardEqualTo(com.google.common.truth.extension.generator.testModel.IdCard expected) { if ((actual.getCard().equals(expected))) { failWithActual(fact("expected Card to be equal to", expected)); } } + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ public IdCardSubject hasCard() { isNotNull(); return check("getCard").about(idCards()).that(actual.getCard()); } + /** + * Simple is or is not expectation for boolean fields. + */ public void isEmployedWrapped() { if (actual.isEmployedWrapped()) { failWithActual(simpleFact("expected to be EmployedWrapped")); } } + /** + * Simple is or is not expectation for boolean fields. + */ public void isNotEmployedWrapped() { if (!actual.isEmployedWrapped()) { failWithActual(simpleFact("expected NOT to be EmployedWrapped")); } } + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ public BooleanSubject hasEmployedWrapped() { isNotNull(); return check("isEmployedWrapped").that(actual.isEmployedWrapped()); From a3d898c6d92a476d89163c1683588b0b3a254597 Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Thu, 12 Aug 2021 17:33:11 +0100 Subject: [PATCH 25/32] Update and stabilise base code generation test, fix toers --- .../generator/GeneratedAssertionTests.java | 8 +- .../generator/internal/SkeletonGenerator.java | 9 + .../internal/SubjectMethodGenerator.java | 16 +- .../generator/internal/model/ThreeSystem.java | 6 +- .../SubjectMethodGeneratorTests.java | 29 -- .../truth/extension/generator/TypeTests.java | 11 +- .../{ => internal}/TruthGeneratorTest.java | 84 ++++-- .../generator/testModel/MyEmployee.java | 46 +-- .../expected/MyEmployeeParentSubject.java.txt | 283 ++++++++++-------- 9 files changed, 282 insertions(+), 210 deletions(-) delete mode 100644 extensions/generator/src/test/java/com/google/common/truth/extension/generator/SubjectMethodGeneratorTests.java rename extensions/generator/src/test/java/com/google/common/truth/extension/generator/{ => internal}/TruthGeneratorTest.java (63%) diff --git a/extensions/generator-assertions-tests/src/test/java/com/google/common/truth/extension/generator/GeneratedAssertionTests.java b/extensions/generator-assertions-tests/src/test/java/com/google/common/truth/extension/generator/GeneratedAssertionTests.java index 5b76c3f09..0c3c4a3f0 100644 --- a/extensions/generator-assertions-tests/src/test/java/com/google/common/truth/extension/generator/GeneratedAssertionTests.java +++ b/extensions/generator-assertions-tests/src/test/java/com/google/common/truth/extension/generator/GeneratedAssertionTests.java @@ -2,14 +2,11 @@ import com.google.common.truth.Truth; import com.google.common.truth.extension.generator.testModel.*; -import org.apache.commons.lang3.builder.ToStringExclude; import org.junit.Test; import uk.co.jemos.podam.api.PodamFactoryImpl; import java.time.LocalDate; -import java.time.ZonedDateTime; -import static com.google.common.truth.extension.generator.ManagedTruth.assertTruth; import static com.google.common.truth.extension.generator.testModel.MyEmployeeChildSubject.assertTruth; /** @@ -81,9 +78,10 @@ public void enums(){ MyEmployee emp = createInstance(MyEmployee.class); MyEmployeeSubject es = ManagedTruth.assertThat(emp); - es.hasAnniversary() + es.hasAnniversary(); es.hasEmploymentState(); + Truth.assertThat(true).isFalse(); } -} \ No newline at end of file +} diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java index 3cd588e83..6f626e82e 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SkeletonGenerator.java @@ -32,6 +32,12 @@ public class SkeletonGenerator implements SkeletonGeneratorAPI { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); private static final String BACKUP_PACKAGE = "com.google.common.truth.extension.generator"; + + /** + * For testing. Used to force generating of middle class, even if it's detected. + */ + public static boolean forceMiddleGenerate; + private final OverallEntryPoint overallEntryPoint; private Optional targetPackageName; @@ -124,6 +130,9 @@ private MiddleClass createMiddleUserTemplate(JavaClassSource parent, Class sourc } private Optional> middleExists(JavaClassSource parent, String middleClassName, Class source) { + if (forceMiddleGenerate) + return empty(); + try { // load from annotated classes instead using Reflections? String fullName = parent.getPackage() + "." + middleClassName; diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java index 0d8a315fb..1177dc380 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java @@ -59,7 +59,10 @@ public void addTests(ThreeSystem system) { // { - Collection getters = getMethods(system); + Collection getters = getMethods(system) + // output a consistent ordering - alphabetical my method name + .stream().sorted((o1, o2) -> Comparator.comparing(Method::getName).compare(o1, o2)) + .collect(Collectors.toList()); for (Method method : getters) { addFieldAccessors(method, generated, classUnderTest); } @@ -71,7 +74,7 @@ public void addTests(ThreeSystem system) { if (input == null) return false; String name = input.getName(); // exclude lombok builder methods - return startsWith("to", name) && !endsWith("Builder", name); + return startsWith(name, "to") && !endsWith(name, "Builder"); }); toers = removeOverridden(toers); for (Method method : toers) { @@ -403,7 +406,7 @@ private MethodSource addChainStrategy(Method method, JavaClassS .setPublic(); StringBuilder body = new StringBuilder("isNotNull();\n"); - String check = format("return check(\"%s\")", method.getName()); + String check = format("return check(\"%s()\")", method.getName()); body.append(check); boolean notPrimitive = !returnType.isPrimitive(); @@ -466,21 +469,18 @@ private String createNameForChainMethod(final Method method) { } // todo cleanup - private boolean isTypeCoveredUnderStandardSubjects(Class returnType) { + private boolean isTypeCoveredUnderStandardSubjects(final Class returnType) { // todo should only do this, if we can't find a more specific subect for the returnType // todo should check if class is assignable from the super subjects, instead of checking names // todo use qualified names // todo add support truth8 extensions - optional etc - // todo try generatin classes for DateTime pakages, like Instant and Duration + // todo try generating classes for DateTime packages, like Instant and Duration // todo this is of course too aggressive - returnType = primitiveToWrapper(returnType); - // boolean isCoveredByNonPrimitiveStandardSubjects = specials.contains(returnType.getSimpleName()); final Class normalised = (returnType.isArray()) ? returnType.getComponentType() : returnType; - returnType = normalised; List> assignable = nativeTypes.stream().filter(x -> x.isAssignableFrom(normalised) diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ThreeSystem.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ThreeSystem.java index a52f42b21..f2c08dc3c 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ThreeSystem.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/model/ThreeSystem.java @@ -1,7 +1,7 @@ package com.google.common.truth.extension.generator.internal.model; -import lombok.*; -import org.checkerframework.common.util.report.qual.ReportWrite; +import lombok.Getter; +import lombok.Setter; import org.jboss.forge.roaster.model.source.JavaClassSource; @Getter @@ -33,7 +33,7 @@ public boolean isShaded() { return !packagesAreContained(); } -// todo rename + // todo rename private boolean packagesAreContained() { Package underTestPackage = classUnderTest.getPackage(); String subjectPackage = parent.getGenerated().getPackage(); diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/SubjectMethodGeneratorTests.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/SubjectMethodGeneratorTests.java deleted file mode 100644 index 65dc067e2..000000000 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/SubjectMethodGeneratorTests.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.google.common.truth.extension.generator; - -import com.google.common.truth.Truth; -import com.google.common.truth.extension.generator.internal.SubjectMethodGenerator; -import com.google.common.truth.extension.generator.internal.model.ParentClass; -import com.google.common.truth.extension.generator.internal.model.ThreeSystem; -import com.google.common.truth.extension.generator.testModel.MyEmployee; -import org.jboss.forge.roaster.Roaster; -import org.jboss.forge.roaster.model.source.JavaClassSource; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; -import uk.co.jemos.podam.api.PodamFactory; -import uk.co.jemos.podam.api.PodamFactoryImpl; - -import java.util.Set; - -@RunWith(JUnit4.class) -public class SubjectMethodGeneratorTests { - - @Test - public void poc(){ - JavaClassSource generated = Roaster.create(JavaClassSource.class); - SubjectMethodGenerator subjectMethodGenerator = new SubjectMethodGenerator(Set.of()); - ThreeSystem threeSystem = new ThreeSystem(MyEmployee.class, new ParentClass(generated), null, null); - subjectMethodGenerator.addTests(threeSystem); - Truth.assertThat(generated.toString()).isEqualTo(""); - } -} diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TypeTests.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TypeTests.java index 5cf1e1a4e..a101da2b6 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TypeTests.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TypeTests.java @@ -3,19 +3,20 @@ import org.junit.Test; public class TypeTests { + + /** + * Sanity tests for how arrays are handled in reflection + */ @Test - public void arrays(){ - int[] ints = new int[]{0,1}; + public void arrays() { + int[] ints = new int[]{0, 1}; String[] strings = new String[]{}; EnumTest[] enums = new EnumTest[]{}; Class aClass = ints.getClass(); Class componentType = aClass.getComponentType(); - Class componentType1 = strings.getClass().getComponentType(); - Class enumscomp = enums.getClass().getComponentType(); - Class enumsco2mp = enums.getClass().getComponentType(); } diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/TruthGeneratorTest.java similarity index 63% rename from extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java rename to extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/TruthGeneratorTest.java index 8223263de..19d0de34a 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/TruthGeneratorTest.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/TruthGeneratorTest.java @@ -1,14 +1,20 @@ -package com.google.common.truth.extension.generator; +package com.google.common.truth.extension.generator.internal; import com.google.common.io.Resources; -import com.google.common.truth.extension.generator.internal.TruthGenerator; +import com.google.common.truth.Correspondence; +import com.google.common.truth.ObjectArraySubject; +import com.google.common.truth.extension.generator.SourceClassSets; +import com.google.common.truth.extension.generator.TruthGeneratorAPI; import com.google.common.truth.extension.generator.internal.model.ThreeSystem; import com.google.common.truth.extension.generator.internal.modelSubjectChickens.ThreeSystemChildSubject; import com.google.common.truth.extension.generator.testModel.IdCard; import com.google.common.truth.extension.generator.testModel.MyEmployee; import com.google.common.truth.extension.generator.testModel.NonBeanLegacy; import com.google.common.truth.extension.generator.testModel.Project; +import org.jboss.forge.roaster.model.Method; +import org.jboss.forge.roaster.model.Type; import org.jboss.forge.roaster.model.source.JavaClassSource; +import org.jboss.forge.roaster.model.source.MethodSource; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -23,7 +29,10 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static com.google.common.truth.Correspondence.transforming; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.extension.generator.internal.modelSubjectChickens.ThreeSystemChildSubject.assertThat; +import static java.util.Optional.of; @RunWith(JUnit4.class) public class TruthGeneratorTest { @@ -36,29 +45,25 @@ private String trim(String in) { return in.replaceAll("(?m)^(\\s)*|(\\s)*$", ""); } - @Test - public void combined() { - TruthGeneratorAPI truthGeneratorAPI = TruthGeneratorAPI.create(); - String generate = truthGeneratorAPI.combinedSystem(MyEmployee.class); - - assertThat(trim("")).isEqualTo(trim(generate)); - } - /** * Base test that compares with expected generated code for test model */ @Test public void generate_code() throws IOException { // todo need to be able to set base package for all generated classes, kind of like shade, so you cah generate test for classes in other restricted modules - TruthGeneratorAPI truthGenerator = TruthGeneratorAPI.create(); + TruthGenerator truthGenerator = TruthGeneratorAPI.create(); Set> classes = new HashSet<>(); classes.add(MyEmployee.class); classes.add(IdCard.class); classes.add(Project.class); + String basePackageName = getClass().getPackage().getName(); SourceClassSets ss = new SourceClassSets(basePackageName); + + // + SkeletonGenerator.forceMiddleGenerate = true; ss.generateAllFoundInPackagesOf(MyEmployee.class); // package exists in other module error - needs package target support @@ -78,26 +83,26 @@ public void generate_code() throws IOException { ThreeSystem threeSystemGenerated = generated.get(MyEmployee.class); - ThreeSystemChildSubject.assertThat(threeSystemGenerated) + assertThat(threeSystemGenerated) .hasParent().hasGenerated().hasSourceText() .ignoringWhiteSpace().equalTo(expectedMyEmployeeParent); // sanity full chain - ThreeSystemChildSubject.assertThat(threeSystemGenerated).hasParentSource(expectedMyEmployeeParent); + assertThat(threeSystemGenerated).hasParentSource(expectedMyEmployeeParent); - ThreeSystemChildSubject.assertThat(threeSystemGenerated).hasMiddleSource(expectedMyEmployeeMiddle); + assertThat(threeSystemGenerated).hasMiddleSource(expectedMyEmployeeMiddle); - ThreeSystemChildSubject.assertThat(threeSystemGenerated).hasChildSource(expectedMyEmployeeChild); + assertThat(threeSystemGenerated).hasChildSource(expectedMyEmployeeChild); } /** - * Chicken, or the egg? + * Chicken, or the egg? Create Subjects that we are currently saving and using in tests */ @Test public void boostrapProjectSubjects() { - TruthGeneratorAPI tg = TruthGeneratorAPI.create(); + TruthGenerator tg = TruthGeneratorAPI.create(); SourceClassSets ss = new SourceClassSets(getClass().getPackage().getName()); - ss.generateFromShaded(JavaClassSource.class); + ss.generateFromShaded(JavaClassSource.class, Method.class); ss.generateAllFoundInPackagesOf(getClass()); tg.generate(ss); } @@ -119,12 +124,21 @@ public void package_java_mix() { assertThat(generated.size()).isAtLeast(ss.getTargetPackageAndClasses().size()); } + Correspondence methodHasName = transforming(MethodSource::getName, "has name of"); + @Test public void test_legacy_mode() { TruthGeneratorAPI tg = TruthGeneratorAPI.create(); SourceClassSets ss = new SourceClassSets(this); ss.generateFromNonBean(NonBeanLegacy.class); - tg.generate(ss); + Map, ThreeSystem> generated = tg.generate(ss); + + assertThat(generated).containsKey(NonBeanLegacy.class); + ThreeSystem actual = generated.get(NonBeanLegacy.class); + assertThat(actual) + .hasParent().hasGenerated().hasMethods() + .comparingElementsUsing(methodHasName) + .containsAtLeast("hasName", "hasAge"); } /** @@ -163,7 +177,7 @@ public void auto_shade() { String basePackage = getClass().getPackage().getName(); TruthGenerator tg = TruthGeneratorAPI.create(); - tg.setEntryPoint(Optional.of(basePackage)); + tg.setEntryPoint(of(basePackage)); Class clazz = UUID.class; Map, ThreeSystem> generate = tg.generate(clazz); @@ -177,4 +191,34 @@ public void auto_shade() { assertThat(parent.getPackage()).startsWith(basePackage); } + /** + * Simple check for generating chains for `toSomething` methods + */ + @Test + public void toers() { + TruthGenerator tg = TruthGeneratorAPI.create(); + Map, ThreeSystem> generate = tg.generate(MyEmployee.class); + ThreeSystem threeSystem = generate.get(MyEmployee.class); + assertThat(threeSystem).hasParent().hasGenerated().hasMethods().comparingElementsUsing(methodHasName) + .contains("hasToPlainPerson"); + assertThat(threeSystem.getParent().getGenerated().getMethod("hasToPlainPerson").getReturnType().getName()) + .isEqualTo("PersonSubject"); + } + + /** + * Some source which return different types of arrays have been tricky, get some coverage here + */ + @Test + public void toArrays() { + // would like to use generated truth subjects here, but don't want to have to copy in too many things, until the plugin is boot-strapable + TruthGenerator tg = TruthGeneratorAPI.create(); + Map, ThreeSystem> generate = tg.generate(MyEmployee.class); + ThreeSystem threeSystem = generate.get(MyEmployee.class); + ThreeSystemChildSubject.assertThat(threeSystem).hasParent().hasGenerated().hasMethods().comparingElementsUsing(methodHasName) + .containsAtLeast("hasToProjectObjectArray", "hasToStateArray"); + Type hasToProjectArray = threeSystem.getParent().getGenerated() + .getMethod("hasToStateArray").getReturnType(); + assertThat(hasToProjectArray.getSimpleName()).isEqualTo(ObjectArraySubject.class.getSimpleName()); + } + } diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/MyEmployee.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/MyEmployee.java index b898b6d42..41138989e 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/MyEmployee.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/MyEmployee.java @@ -1,6 +1,8 @@ package com.google.common.truth.extension.generator.testModel; -import lombok.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; import lombok.experimental.SuperBuilder; import javax.annotation.Nonnull; @@ -9,31 +11,26 @@ import static com.google.common.truth.extension.generator.testModel.MyEmployee.State.EMPLOLYED; -//@AllArgsConstructor @SuperBuilder(toBuilder = true) -//@Data @Getter @Setter(AccessLevel.PRIVATE) -//@With public class MyEmployee extends Person { - UUID id = UUID.randomUUID(); + private UUID id = UUID.randomUUID(); -// @With - ZonedDateTime anniversary = ZonedDateTime.now(); + private ZonedDateTime anniversary = ZonedDateTime.now(); -// @With - MyEmployee boss = null; + private MyEmployee boss = null; - IdCard card = null; + private IdCard card = null; - List projectList = new ArrayList<>(); + private List projectList = new ArrayList<>(); - State employmentState = State.NEVER_EMPLOYED; + private State employmentState = State.NEVER_EMPLOYED; - Optional weighting = Optional.empty(); + private Optional weighting = Optional.empty(); - Map projectMap; + private Map projectMap = new HashMap<>(); public MyEmployee(@Nonnull String name, long someLongAspect, @Nonnull ZonedDateTime birthday) { super(name, someLongAspect, birthday); @@ -43,17 +40,32 @@ public enum State { EMPLOLYED, PREVIOUSLY_EMPLOYED, NEVER_EMPLOYED; } + public Person toPlainPerson() { + return Person.builder().birthday(birthday).name(name).someLongAspect(someLongAspect).build(); + } + + /** + * I know - the model doesn't need to make sense :) + */ + public State[] toStateArray() { + return new State[0]; + } + + public Object[] toProjectObjectArray() { + return projectList.toArray(); + } + /** * Package-private test */ - boolean isEmployed(){ + boolean isEmployed() { return this.employmentState == EMPLOLYED; } /** * Primitive vs Wrapper test */ - Boolean isEmployedWrapped(){ + Boolean isEmployedWrapped() { return this.employmentState == EMPLOLYED; } @@ -65,11 +77,9 @@ public String getName() { return super.getName() + " ID: " + this.getId(); } - @Override public String toString() { return "MyEmployee{" + -// "id=" + id + ", name=" + getName() + ", card=" + card + ", employed=" + employmentState + diff --git a/extensions/generator/src/test/resources/expected/MyEmployeeParentSubject.java.txt b/extensions/generator/src/test/resources/expected/MyEmployeeParentSubject.java.txt index a29f59476..f7bd13840 100644 --- a/extensions/generator/src/test/resources/expected/MyEmployeeParentSubject.java.txt +++ b/extensions/generator/src/test/resources/expected/MyEmployeeParentSubject.java.txt @@ -3,32 +3,35 @@ package com.google.common.truth.extension.generator.testModel; import com.google.common.truth.Subject; import javax.annotation.processing.Generated; import com.google.common.truth.FailureMetadata; -import com.google.common.truth.IntegerSubject; +import java.time.ZonedDateTime; +import static com.google.common.truth.extension.generator.internal.autoShaded.java.time.ZonedDateTimeSubject.zonedDateTimes; +import static com.google.common.truth.extension.generator.internal.autoShaded.java.time.ZonedDateTimeSubject.zonedDateTimes; import static com.google.common.truth.Fact.*; +import com.google.common.truth.IntegerSubject; +import com.google.common.truth.extension.generator.internal.autoShaded.java.time.ZonedDateTimeSubject; import static com.google.common.truth.extension.generator.testModel.MyEmployeeSubject.myEmployees; import com.google.common.truth.extension.generator.testModel.MyEmployeeSubject; +import static com.google.common.truth.extension.generator.testModel.IdCardSubject.idCards; +import com.google.common.truth.extension.generator.testModel.IdCardSubject; import com.google.common.truth.extension.generator.testModel.MyEmployee.State; import static com.google.common.truth.extension.generator.testModel.StateSubject.states; import com.google.common.truth.extension.generator.testModel.StateSubject; -import java.util.Optional; -import static com.google.common.truth.extension.generator.autoShaded.java.util.OptionalSubject.optionals; -import com.google.common.truth.extension.generator.autoShaded.java.util.OptionalSubject; -import java.time.ZonedDateTime; -import static com.google.common.truth.extension.generator.autoShaded.java.time.ZonedDateTimeSubject.zonedDateTimes; -import static com.google.common.truth.extension.generator.autoShaded.java.time.ZonedDateTimeSubject.zonedDateTimes; import java.util.UUID; -import static com.google.common.truth.extension.generator.autoShaded.java.util.UUIDSubject.uUIDs; -import com.google.common.truth.extension.generator.autoShaded.java.util.UUIDSubject; +import static com.google.common.truth.extension.generator.internal.autoShaded.java.util.UUIDSubject.uUIDs; +import com.google.common.truth.extension.generator.internal.autoShaded.java.util.UUIDSubject; import com.google.common.truth.StringSubject; -import com.google.common.truth.LongSubject; import java.util.List; import com.google.common.truth.IterableSubject; import java.util.Map; import com.google.common.truth.MapSubject; +import com.google.common.truth.LongSubject; +import java.util.Optional; +import static com.google.common.truth.extension.generator.internal.autoShaded.java.util.OptionalSubject.optionals; +import com.google.common.truth.extension.generator.internal.autoShaded.java.util.OptionalSubject; import com.google.common.truth.BooleanSubject; -import com.google.common.truth.extension.generator.autoShaded.java.time.ZonedDateTimeSubject; -import static com.google.common.truth.extension.generator.testModel.IdCardSubject.idCards; -import com.google.common.truth.extension.generator.testModel.IdCardSubject; +import com.google.common.truth.ObjectArraySubject; +import static com.google.common.truth.extension.generator.testModel.PersonSubject.persons; +import com.google.common.truth.extension.generator.testModel.PersonSubject; /** * Truth Subject for the {@link MyEmployee}. @@ -54,18 +57,18 @@ public class MyEmployeeParentSubject extends Subject { /** * Simple check for equality for all fields. */ - public void hasBirthYearNotEqualTo(int expected) { - if (!(actual.getBirthYear() == expected)) { - failWithActual(fact("expected BirthYear NOT to be equal to", expected)); + public void hasAnniversaryNotEqualTo(ZonedDateTime expected) { + if (!(actual.getAnniversary().equals(expected))) { + failWithActual(fact("expected Anniversary NOT to be equal to", expected)); } } /** * Simple check for equality for all fields. */ - public void hasBirthYearEqualTo(int expected) { - if ((actual.getBirthYear() == expected)) { - failWithActual(fact("expected BirthYear to be equal to", expected)); + public void hasAnniversaryEqualTo(java.time.ZonedDateTime expected) { + if ((actual.getAnniversary().equals(expected))) { + failWithActual(fact("expected Anniversary to be equal to", expected)); } } @@ -73,26 +76,26 @@ public class MyEmployeeParentSubject extends Subject { * Returns the Subject for the given field type, so you can chain on other * assertions. */ - public IntegerSubject hasBirthYear() { + public ZonedDateTimeSubject hasAnniversary() { isNotNull(); - return check("getBirthYear").that(actual.getBirthYear()); + return check("getAnniversary()").about(zonedDateTimes()).that(actual.getAnniversary()); } /** * Simple check for equality for all fields. */ - public void hasBossNotEqualTo(com.google.common.truth.extension.generator.testModel.MyEmployee expected) { - if (!(actual.getBoss().equals(expected))) { - failWithActual(fact("expected Boss NOT to be equal to", expected)); + public void hasBirthYearNotEqualTo(int expected) { + if (!(actual.getBirthYear() == expected)) { + failWithActual(fact("expected BirthYear NOT to be equal to", expected)); } } /** * Simple check for equality for all fields. */ - public void hasBossEqualTo(com.google.common.truth.extension.generator.testModel.MyEmployee expected) { - if ((actual.getBoss().equals(expected))) { - failWithActual(fact("expected Boss to be equal to", expected)); + public void hasBirthYearEqualTo(int expected) { + if ((actual.getBirthYear() == expected)) { + failWithActual(fact("expected BirthYear to be equal to", expected)); } } @@ -100,27 +103,26 @@ public class MyEmployeeParentSubject extends Subject { * Returns the Subject for the given field type, so you can chain on other * assertions. */ - public MyEmployeeSubject hasBoss() { + public IntegerSubject hasBirthYear() { isNotNull(); - return check("getBoss").about(myEmployees()).that(actual.getBoss()); + return check("getBirthYear()").that(actual.getBirthYear()); } /** * Simple check for equality for all fields. */ - public void hasEmploymentStateNotEqualTo(State expected) { - if (!(actual.getEmploymentState().equals(expected))) { - failWithActual(fact("expected EmploymentState NOT to be equal to", expected)); + public void hasBirthdayNotEqualTo(java.time.ZonedDateTime expected) { + if (!(actual.getBirthday().equals(expected))) { + failWithActual(fact("expected Birthday NOT to be equal to", expected)); } } /** * Simple check for equality for all fields. */ - public void hasEmploymentStateEqualTo( - com.google.common.truth.extension.generator.testModel.MyEmployee.State expected) { - if ((actual.getEmploymentState().equals(expected))) { - failWithActual(fact("expected EmploymentState to be equal to", expected)); + public void hasBirthdayEqualTo(java.time.ZonedDateTime expected) { + if ((actual.getBirthday().equals(expected))) { + failWithActual(fact("expected Birthday to be equal to", expected)); } } @@ -128,44 +130,53 @@ public class MyEmployeeParentSubject extends Subject { * Returns the Subject for the given field type, so you can chain on other * assertions. */ - public StateSubject hasEmploymentState() { + public ZonedDateTimeSubject hasBirthday() { isNotNull(); - return check("getEmploymentState").about(states()).that(actual.getEmploymentState()); + return check("getBirthday()").about(zonedDateTimes()).that(actual.getBirthday()); } /** - * Checks Optional fields for presence. + * Simple check for equality for all fields. */ - public void hasWeightingNotPresent() { - if (!actual.getWeighting().isPresent()) { - failWithActual(simpleFact("expected Weighting NOT to be present")); + public void hasBossNotEqualTo(com.google.common.truth.extension.generator.testModel.MyEmployee expected) { + if (!(actual.getBoss().equals(expected))) { + failWithActual(fact("expected Boss NOT to be equal to", expected)); } } /** - * Checks Optional fields for presence. + * Simple check for equality for all fields. */ - public void hasWeightingPresent() { - if (actual.getWeighting().isPresent()) { - failWithActual(simpleFact("expected Weighting to be present")); + public void hasBossEqualTo(com.google.common.truth.extension.generator.testModel.MyEmployee expected) { + if ((actual.getBoss().equals(expected))) { + failWithActual(fact("expected Boss to be equal to", expected)); } } + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public MyEmployeeSubject hasBoss() { + isNotNull(); + return check("getBoss()").about(myEmployees()).that(actual.getBoss()); + } + /** * Simple check for equality for all fields. */ - public void hasWeightingNotEqualTo(Optional expected) { - if (!(actual.getWeighting().equals(expected))) { - failWithActual(fact("expected Weighting NOT to be equal to", expected)); + public void hasCardNotEqualTo(com.google.common.truth.extension.generator.testModel.IdCard expected) { + if (!(actual.getCard().equals(expected))) { + failWithActual(fact("expected Card NOT to be equal to", expected)); } } /** * Simple check for equality for all fields. */ - public void hasWeightingEqualTo(java.util.Optional expected) { - if ((actual.getWeighting().equals(expected))) { - failWithActual(fact("expected Weighting to be equal to", expected)); + public void hasCardEqualTo(com.google.common.truth.extension.generator.testModel.IdCard expected) { + if ((actual.getCard().equals(expected))) { + failWithActual(fact("expected Card to be equal to", expected)); } } @@ -173,26 +184,27 @@ public class MyEmployeeParentSubject extends Subject { * Returns the Subject for the given field type, so you can chain on other * assertions. */ - public OptionalSubject hasWeighting() { + public IdCardSubject hasCard() { isNotNull(); - return check("getWeighting").about(optionals()).that(actual.getWeighting()); + return check("getCard()").about(idCards()).that(actual.getCard()); } /** * Simple check for equality for all fields. */ - public void hasAnniversaryNotEqualTo(ZonedDateTime expected) { - if (!(actual.getAnniversary().equals(expected))) { - failWithActual(fact("expected Anniversary NOT to be equal to", expected)); + public void hasEmploymentStateNotEqualTo(State expected) { + if (!(actual.getEmploymentState().equals(expected))) { + failWithActual(fact("expected EmploymentState NOT to be equal to", expected)); } } /** * Simple check for equality for all fields. */ - public void hasAnniversaryEqualTo(java.time.ZonedDateTime expected) { - if ((actual.getAnniversary().equals(expected))) { - failWithActual(fact("expected Anniversary to be equal to", expected)); + public void hasEmploymentStateEqualTo( + com.google.common.truth.extension.generator.testModel.MyEmployee.State expected) { + if ((actual.getEmploymentState().equals(expected))) { + failWithActual(fact("expected EmploymentState to be equal to", expected)); } } @@ -200,9 +212,9 @@ public class MyEmployeeParentSubject extends Subject { * Returns the Subject for the given field type, so you can chain on other * assertions. */ - public ZonedDateTimeSubject hasAnniversary() { + public StateSubject hasEmploymentState() { isNotNull(); - return check("getAnniversary").about(zonedDateTimes()).that(actual.getAnniversary()); + return check("getEmploymentState()").about(states()).that(actual.getEmploymentState()); } /** @@ -229,7 +241,7 @@ public class MyEmployeeParentSubject extends Subject { */ public UUIDSubject hasId() { isNotNull(); - return check("getId").about(uUIDs()).that(actual.getId()); + return check("getId()").about(uUIDs()).that(actual.getId()); } /** @@ -256,34 +268,7 @@ public class MyEmployeeParentSubject extends Subject { */ public StringSubject hasName() { isNotNull(); - return check("getName").that(actual.getName()); - } - - /** - * Simple check for equality for all fields. - */ - public void hasSomeLongAspectNotEqualTo(long expected) { - if (!(actual.getSomeLongAspect() == expected)) { - failWithActual(fact("expected SomeLongAspect NOT to be equal to", expected)); - } - } - - /** - * Simple check for equality for all fields. - */ - public void hasSomeLongAspectEqualTo(long expected) { - if ((actual.getSomeLongAspect() == expected)) { - failWithActual(fact("expected SomeLongAspect to be equal to", expected)); - } - } - - /** - * Returns the Subject for the given field type, so you can chain on other - * assertions. - */ - public LongSubject hasSomeLongAspect() { - isNotNull(); - return check("getSomeLongAspect").that(actual.getSomeLongAspect()); + return check("getName()").that(actual.getName()); } /** @@ -328,7 +313,7 @@ public class MyEmployeeParentSubject extends Subject { */ public IterableSubject hasProjectList() { isNotNull(); - return check("getProjectList").that(actual.getProjectList()); + return check("getProjectList()").that(actual.getProjectList()); } /** @@ -373,24 +358,24 @@ public class MyEmployeeParentSubject extends Subject { */ public MapSubject hasProjectMap() { isNotNull(); - return check("getProjectMap").that(actual.getProjectMap()); + return check("getProjectMap()").that(actual.getProjectMap()); } /** - * Simple is or is not expectation for boolean fields. + * Simple check for equality for all fields. */ - public void isEmployed() { - if (actual.isEmployed()) { - failWithActual(simpleFact("expected to be Employed")); + public void hasSomeLongAspectNotEqualTo(long expected) { + if (!(actual.getSomeLongAspect() == expected)) { + failWithActual(fact("expected SomeLongAspect NOT to be equal to", expected)); } } /** - * Simple is or is not expectation for boolean fields. + * Simple check for equality for all fields. */ - public void isNotEmployed() { - if (!actual.isEmployed()) { - failWithActual(simpleFact("expected NOT to be Employed")); + public void hasSomeLongAspectEqualTo(long expected) { + if ((actual.getSomeLongAspect() == expected)) { + failWithActual(fact("expected SomeLongAspect to be equal to", expected)); } } @@ -398,26 +383,44 @@ public class MyEmployeeParentSubject extends Subject { * Returns the Subject for the given field type, so you can chain on other * assertions. */ - public BooleanSubject hasEmployed() { + public LongSubject hasSomeLongAspect() { isNotNull(); - return check("isEmployed").that(actual.isEmployed()); + return check("getSomeLongAspect()").that(actual.getSomeLongAspect()); + } + + /** + * Checks Optional fields for presence. + */ + public void hasWeightingNotPresent() { + if (!actual.getWeighting().isPresent()) { + failWithActual(simpleFact("expected Weighting NOT to be present")); + } + } + + /** + * Checks Optional fields for presence. + */ + public void hasWeightingPresent() { + if (actual.getWeighting().isPresent()) { + failWithActual(simpleFact("expected Weighting to be present")); + } } /** * Simple check for equality for all fields. */ - public void hasBirthdayNotEqualTo(java.time.ZonedDateTime expected) { - if (!(actual.getBirthday().equals(expected))) { - failWithActual(fact("expected Birthday NOT to be equal to", expected)); + public void hasWeightingNotEqualTo(Optional expected) { + if (!(actual.getWeighting().equals(expected))) { + failWithActual(fact("expected Weighting NOT to be equal to", expected)); } } /** * Simple check for equality for all fields. */ - public void hasBirthdayEqualTo(java.time.ZonedDateTime expected) { - if ((actual.getBirthday().equals(expected))) { - failWithActual(fact("expected Birthday to be equal to", expected)); + public void hasWeightingEqualTo(java.util.Optional expected) { + if ((actual.getWeighting().equals(expected))) { + failWithActual(fact("expected Weighting to be equal to", expected)); } } @@ -425,26 +428,26 @@ public class MyEmployeeParentSubject extends Subject { * Returns the Subject for the given field type, so you can chain on other * assertions. */ - public ZonedDateTimeSubject hasBirthday() { + public OptionalSubject hasWeighting() { isNotNull(); - return check("getBirthday").about(zonedDateTimes()).that(actual.getBirthday()); + return check("getWeighting()").about(optionals()).that(actual.getWeighting()); } /** - * Simple check for equality for all fields. + * Simple is or is not expectation for boolean fields. */ - public void hasCardNotEqualTo(com.google.common.truth.extension.generator.testModel.IdCard expected) { - if (!(actual.getCard().equals(expected))) { - failWithActual(fact("expected Card NOT to be equal to", expected)); + public void isEmployed() { + if (actual.isEmployed()) { + failWithActual(simpleFact("expected to be Employed")); } } /** - * Simple check for equality for all fields. + * Simple is or is not expectation for boolean fields. */ - public void hasCardEqualTo(com.google.common.truth.extension.generator.testModel.IdCard expected) { - if ((actual.getCard().equals(expected))) { - failWithActual(fact("expected Card to be equal to", expected)); + public void isNotEmployed() { + if (!actual.isEmployed()) { + failWithActual(simpleFact("expected NOT to be Employed")); } } @@ -452,9 +455,9 @@ public class MyEmployeeParentSubject extends Subject { * Returns the Subject for the given field type, so you can chain on other * assertions. */ - public IdCardSubject hasCard() { + public BooleanSubject hasEmployed() { isNotNull(); - return check("getCard").about(idCards()).that(actual.getCard()); + return check("isEmployed()").that(actual.isEmployed()); } /** @@ -481,6 +484,42 @@ public class MyEmployeeParentSubject extends Subject { */ public BooleanSubject hasEmployedWrapped() { isNotNull(); - return check("isEmployedWrapped").that(actual.isEmployedWrapped()); + return check("isEmployedWrapped()").that(actual.isEmployedWrapped()); + } + + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public ObjectArraySubject hasToStateArray() { + isNotNull(); + return check("toStateArray()").that(actual.toStateArray()); + } + + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public StringSubject hasToString() { + isNotNull(); + return check("toString()").that(actual.toString()); + } + + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public ObjectArraySubject hasToProjectObjectArray() { + isNotNull(); + return check("toProjectObjectArray()").that(actual.toProjectObjectArray()); + } + + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public PersonSubject hasToPlainPerson() { + isNotNull(); + return check("toPlainPerson()").about(persons()).that(actual.toPlainPerson()); } } From 4ec12b3215afcc336e1ff76f02c8f13a331d9d4b Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Thu, 12 Aug 2021 19:01:09 +0100 Subject: [PATCH 26/32] Updating tests --- .../generator/GeneratedAssertionTests.java | 44 +++++++++---------- .../internal/TruthGeneratorTest.java | 2 +- .../NonBeanLegacy.java | 4 +- 3 files changed, 26 insertions(+), 24 deletions(-) rename extensions/generator/src/test/java/com/google/common/truth/extension/generator/{testModel => testModelLegacy}/NonBeanLegacy.java (65%) diff --git a/extensions/generator-assertions-tests/src/test/java/com/google/common/truth/extension/generator/GeneratedAssertionTests.java b/extensions/generator-assertions-tests/src/test/java/com/google/common/truth/extension/generator/GeneratedAssertionTests.java index 0c3c4a3f0..49261ad82 100644 --- a/extensions/generator-assertions-tests/src/test/java/com/google/common/truth/extension/generator/GeneratedAssertionTests.java +++ b/extensions/generator-assertions-tests/src/test/java/com/google/common/truth/extension/generator/GeneratedAssertionTests.java @@ -1,14 +1,21 @@ package com.google.common.truth.extension.generator; import com.google.common.truth.Truth; -import com.google.common.truth.extension.generator.testModel.*; +import com.google.common.truth.extension.generator.internal.TruthGeneratorTest; +import com.google.common.truth.extension.generator.testModel.ManagedTruth; +import com.google.common.truth.extension.generator.testModel.MyEmployee; +import com.google.common.truth.extension.generator.testModel.MyEmployeeChildSubject; +import com.google.common.truth.extension.generator.testModel.MyEmployeeSubject; +import com.google.common.truth.extension.generator.testModelLegacy.NonBeanLegacy; +import com.google.common.truth.extension.generator.testModelLegacy.NonBeanLegacyChildSubject; +import com.google.common.truth.extension.generator.testModelLegacy.NonBeanLegacySubject; import org.junit.Test; import uk.co.jemos.podam.api.PodamFactoryImpl; -import java.time.LocalDate; - +import static com.google.common.truth.extension.generator.testModel.MyEmployee.State.NEVER_EMPLOYED; import static com.google.common.truth.extension.generator.testModel.MyEmployeeChildSubject.assertTruth; + /** * @see TruthGeneratorTest#generate_code */ @@ -22,7 +29,7 @@ public void try_out_assertions() { MyEmployee hi = createInstance(MyEmployee.class); hi = hi.toBuilder() .name("Zeynep") - .boss(createInstance(MyEmployee.class)) + .boss(createInstance(MyEmployee.class).toBuilder().name("Tony").build()) .build(); assertTruth(hi).hasBirthYear().isAtLeast(200); @@ -31,7 +38,7 @@ public void try_out_assertions() { Truth.assertThat(hi.getBoss().getName()).contains("Tony"); assertTruth(hi).hasBoss().hasName().contains("Tony"); // assertTruth(hi).hasCard().hasEpoch().isAtLeast(20); - assertTruth(hi).hasProjectList().hasSize(3); + assertTruth(hi).hasProjectList().hasSize(5); MyEmployeeSubject myEmployeeSubject = assertTruth(hi); MyEmployeeChildSubject.assertThat(TestModelUtils.createEmployee()).hasProjectMapWithKey("key"); @@ -42,10 +49,10 @@ public void try_out_assertions() { */ @Test public void test_legacy_mode() { - NonBeanLegacy nonBeanLegacy = createInstance(NonBeanLegacy.class); -// NonBeanLegacySubject nonBeanLegacySubject = NonBeanLegacyChildSubject.assertThat(nonBeanLegacy); -// nonBeanLegacySubject.hasAge().isNotNull(); -// nonBeanLegacySubject.hasName().isEqualTo("lilan"); + NonBeanLegacy nonBeanLegacy = createInstance(NonBeanLegacy.class).toBuilder().name("lilan").build(); + NonBeanLegacySubject nonBeanLegacySubject = NonBeanLegacyChildSubject.assertThat(nonBeanLegacy); + nonBeanLegacySubject.hasAge().isNotNull(); + nonBeanLegacySubject.hasName().isEqualTo("lilan"); } private T createInstance(Class clazz) { @@ -57,9 +64,8 @@ public void recursive() { MyEmployee emp = createInstance(MyEmployee.class); MyEmployeeSubject es = ManagedTruth.assertThat(emp); -// ZonedDateTime anniversary = emp.getAnniversary(); -// anniversary.toLocalDate(). -// anniversary. + es.hasAnniversary().hasToLocalDate().hasEra().hasValue().isNotNull(); + es.hasAnniversary().hasToLocalDate().hasChronology().hasId().isNotNull(); } @Test @@ -67,21 +73,15 @@ public void as_type_chain_transformers() { MyEmployee emp = createInstance(MyEmployee.class); MyEmployeeSubject es = ManagedTruth.assertThat(emp); - LocalDate localDate = emp.getAnniversary().toLocalDate(); - - es.hasAnniversary().toLocalDate().hasEra(); - es.hasAnniversary().toLocalDateTime().toString(); + es.hasAnniversary().hasToLocalDate().hasEra(); + es.hasAnniversary().hasToLocalDateTime().hasToLocalDate().hasEra().isNotNull(); } @Test public void enums(){ - MyEmployee emp = createInstance(MyEmployee.class); + MyEmployee emp = createInstance(MyEmployee.class).toBuilder().employmentState(NEVER_EMPLOYED).build(); MyEmployeeSubject es = ManagedTruth.assertThat(emp); - - es.hasAnniversary(); - es.hasEmploymentState(); - Truth.assertThat(true).isFalse(); + es.hasEmploymentState().isEqualTo(NEVER_EMPLOYED); } - } diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/TruthGeneratorTest.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/TruthGeneratorTest.java index 19d0de34a..e1fe214a3 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/TruthGeneratorTest.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/TruthGeneratorTest.java @@ -9,8 +9,8 @@ import com.google.common.truth.extension.generator.internal.modelSubjectChickens.ThreeSystemChildSubject; import com.google.common.truth.extension.generator.testModel.IdCard; import com.google.common.truth.extension.generator.testModel.MyEmployee; -import com.google.common.truth.extension.generator.testModel.NonBeanLegacy; import com.google.common.truth.extension.generator.testModel.Project; +import com.google.common.truth.extension.generator.testModelLegacy.NonBeanLegacy; import org.jboss.forge.roaster.model.Method; import org.jboss.forge.roaster.model.Type; import org.jboss.forge.roaster.model.source.JavaClassSource; diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/NonBeanLegacy.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModelLegacy/NonBeanLegacy.java similarity index 65% rename from extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/NonBeanLegacy.java rename to extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModelLegacy/NonBeanLegacy.java index 70039bc8a..9c6c160dd 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/NonBeanLegacy.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModelLegacy/NonBeanLegacy.java @@ -1,8 +1,10 @@ -package com.google.common.truth.extension.generator.testModel; +package com.google.common.truth.extension.generator.testModelLegacy; +import lombok.Builder; import lombok.RequiredArgsConstructor; +@Builder(toBuilder = true) @RequiredArgsConstructor public class NonBeanLegacy { final int age; From 1a96b1f6af043f60932c030ac360c07752e43a8a Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Fri, 13 Aug 2021 10:32:27 +0100 Subject: [PATCH 27/32] feature: Use generic types to have strong assertions for map and iterable --- .../generator/internal/ClassUtils.java | 14 +++++- .../internal/SubjectMethodGenerator.java | 35 +++++++++++--- .../generator/internal/TruthGenerator.java | 5 ++ .../internal/TruthGeneratorTest.java | 46 +++++++++++++++++++ .../generator/plugin/GeneratorMojo.java | 2 +- 5 files changed, 94 insertions(+), 8 deletions(-) diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/ClassUtils.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/ClassUtils.java index 79373a7f4..d2c7ae6ae 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/ClassUtils.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/ClassUtils.java @@ -3,7 +3,10 @@ import com.google.common.truth.Subject; import org.reflections.Reflections; import org.reflections.scanners.SubTypesScanner; +import org.reflections.util.ConfigurationBuilder; +import org.reflections.util.FilterBuilder; +import java.lang.reflect.Type; import java.util.Set; import java.util.stream.Collectors; @@ -13,7 +16,13 @@ public static Set> collectSourceClasses(String... modelPackages) { // for all classes in package SubTypesScanner subTypesScanner = new SubTypesScanner(false); - Reflections reflections = new Reflections(modelPackages, subTypesScanner); + ConfigurationBuilder build = new ConfigurationBuilder() + .forPackages(modelPackages) + .filterInputsBy(new FilterBuilder().includePackage(modelPackages)) + .setScanners(subTypesScanner) + .setExpandSuperTypes(true); + + Reflections reflections = new Reflections(build); reflections.expandSuperTypes(); // get things that extend something that extend object // https://github.com/ronmamo/reflections/issues/126 @@ -27,4 +36,7 @@ public static Set> collectSourceClasses(String... modelPackages) { return allTypes; } + public static String maybeGetSimpleName(Type elementType) { + return (elementType instanceof Class) ? ((Class) elementType).getSimpleName() : elementType.getTypeName(); + } } diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java index 1177dc380..8f4e382d8 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java @@ -13,12 +13,15 @@ import java.lang.reflect.Member; import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.math.BigDecimal; import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; +import static com.google.common.truth.extension.generator.internal.ClassUtils.maybeGetSimpleName; import static java.lang.String.format; import static java.lang.reflect.Modifier.*; import static java.util.Optional.*; @@ -272,15 +275,33 @@ private MethodSource addMapStrategyGeneric(Method method, JavaC .setReturnTypeVoid() .setBody(body) .setPublic(); - newMethod.addParameter(Object.class, "expected"); - newMethod.getJavaDoc().setText("Check Maps for containing a given key."); + // parameter + Type keyType = getReturnTypeFirstGenericParam(method); + newMethod.addParameter(keyType.getTypeName(), "expected"); + + // + newMethod.getJavaDoc().setText(format("Check Maps for containing a given {@link %s} key.", maybeGetSimpleName(keyType))); copyThrownExceptions(method, newMethod); return newMethod; } + private Type getReturnTypeFirstGenericParam(Method method) { + Class keyType = Object.class; // default fall back + Type genericReturnType = method.getGenericReturnType(); + if (genericReturnType instanceof ParameterizedType) { + ParameterizedType parameterizedReturnType = (ParameterizedType) genericReturnType; + Type[] actualTypeArguments = parameterizedReturnType.getActualTypeArguments(); + if (actualTypeArguments.length > 0) { // must have at least 1 + Type key = actualTypeArguments[0]; + return key; + } + } + return keyType; + } + private void addOptionalStrategy(Method method, JavaClassSource generated, Class classUnderTest) { addOptionalStrategyGeneric(method, generated, false); addOptionalStrategyGeneric(method, generated, true); @@ -336,9 +357,12 @@ private MethodSource addHasElementStrategyGeneric(Method method .setReturnTypeVoid() .setBody(body) .setPublic(); - newMethod.addParameter(Object.class, "expected"); - newMethod.getJavaDoc().setText("Checks if the element is or is not contained in the collection."); + + Type elementType = getReturnTypeFirstGenericParam(method); + newMethod.addParameter(elementType.getTypeName(), "expected"); + + newMethod.getJavaDoc().setText(format("Checks if a {@link %s} element is, or is not contained in the collection.", maybeGetSimpleName(elementType))); copyThrownExceptions(method, newMethod); @@ -383,7 +407,7 @@ private void copyThrownExceptions(Method method, MethodSource g Class[] exceptionTypes = (Class[]) method.getExceptionTypes(); Stream> runtimeExceptions = Arrays.stream(exceptionTypes) .filter(x -> !RuntimeException.class.isAssignableFrom(x)); - runtimeExceptions.forEach(x -> generated.addThrows(x)); + runtimeExceptions.forEach(generated::addThrows); } private MethodSource addChainStrategy(Method method, JavaClassSource generated, Class returnType) { @@ -394,7 +418,6 @@ private MethodSource addChainStrategy(Method method, JavaClassS // no subject to chain if (subjectForType.isEmpty() && !isCoveredByNonPrimitiveStandardSubjects) { logger.at(WARNING).log("Cant find subject for " + returnType); - // todo log return null; } diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java index a89b6ed16..c5455e720 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java @@ -27,6 +27,11 @@ public class TruthGenerator implements TruthGeneratorAPI { private static final FluentLogger log = FluentLogger.forEnclosingClass(); + /** + * Marks whether to try to find all referenced types from the source types, to generate Subjects for all of them, and + * use them all in the Subject tree. + */ + @Setter private boolean recursive = true; @Setter diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/TruthGeneratorTest.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/TruthGeneratorTest.java index e1fe214a3..68e32a5a9 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/TruthGeneratorTest.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/TruthGeneratorTest.java @@ -15,6 +15,7 @@ import org.jboss.forge.roaster.model.Type; import org.jboss.forge.roaster.model.source.JavaClassSource; import org.jboss.forge.roaster.model.source.MethodSource; +import org.jboss.forge.roaster.model.source.ParameterSource; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -29,10 +30,12 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static com.google.common.truth.Correspondence.from; import static com.google.common.truth.Correspondence.transforming; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.extension.generator.internal.modelSubjectChickens.ThreeSystemChildSubject.assertThat; import static java.util.Optional.of; +import static java.util.stream.Collectors.toList; @RunWith(JUnit4.class) public class TruthGeneratorTest { @@ -221,4 +224,47 @@ public void toArrays() { assertThat(hasToProjectArray.getSimpleName()).isEqualTo(ObjectArraySubject.class.getSimpleName()); } + /** + * Test if we can get generics in some situations + */ + @Test + public void get_generics() { + TruthGenerator tg = TruthGeneratorAPI.create(); + tg.setRecursive(false); // speed + + Map, ThreeSystem> generate = tg.generate(MyEmployee.class); + ThreeSystem threeSystem = generate.get(MyEmployee.class); + JavaClassSource generated = threeSystem.getParent().getGenerated(); + + + Correspondence, Class> classNames = from( + (actual, expected) -> actual.getType().getName().equals(expected.getName()), + "has class name matching class name of"); + + // map contains strong key, value + { + String name = "hasProjectMapWithKey"; + List> method = generated.getMethods().stream().filter(x -> x.getName().equals(name)).collect(toList()); + assertThat(method).hasSize(1); + MethodSource hasKey = method.get(0); + List> parameters = hasKey.getParameters(); + assertThat(parameters).comparingElementsUsing(classNames).containsExactly(String.class); + } + + // list contains strong element + { + String name = "hasProjectListWithElement"; + List> method = generated.getMethods().stream().filter(x -> x.getName().equals(name)).collect(toList()); + assertThat(method).hasSize(1); + MethodSource hasKey = method.get(0); + List> parameters = hasKey.getParameters(); + assertThat(parameters).comparingElementsUsing(classNames).containsExactly(Project.class); + } + } + + @Test + public void standard_extensions() { + assertThat(true).isFalse(); + } + } diff --git a/extensions/plugin-maven/src/main/java/com/google/common/truth/extension/generator/plugin/GeneratorMojo.java b/extensions/plugin-maven/src/main/java/com/google/common/truth/extension/generator/plugin/GeneratorMojo.java index bb64cb3c7..0e5fa667d 100644 --- a/extensions/plugin-maven/src/main/java/com/google/common/truth/extension/generator/plugin/GeneratorMojo.java +++ b/extensions/plugin-maven/src/main/java/com/google/common/truth/extension/generator/plugin/GeneratorMojo.java @@ -158,7 +158,7 @@ private Map, ThreeSystem> runGenerator() { SourceClassSets ss = new SourceClassSets(getEntryPointClassPackage()); ss.generateFrom(getProjectClassLoader(), getClasses()); -// ss.generatefrom(getPackages()); + ss.generateAllFoundInPackages(getPackages()); Map, ThreeSystem> generated = tg.generate(ss); return generated; From efb81180d7e28c7c00276aa243f0e576cb1af13d Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Fri, 13 Aug 2021 10:34:42 +0100 Subject: [PATCH 28/32] remove mojo params not yet implemented --- .../generator/plugin/GeneratorMojo.java | 54 ++++++++++--------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/extensions/plugin-maven/src/main/java/com/google/common/truth/extension/generator/plugin/GeneratorMojo.java b/extensions/plugin-maven/src/main/java/com/google/common/truth/extension/generator/plugin/GeneratorMojo.java index 0e5fa667d..22f26abaa 100644 --- a/extensions/plugin-maven/src/main/java/com/google/common/truth/extension/generator/plugin/GeneratorMojo.java +++ b/extensions/plugin-maven/src/main/java/com/google/common/truth/extension/generator/plugin/GeneratorMojo.java @@ -62,11 +62,12 @@ public class GeneratorMojo extends AbstractMojo { @Parameter(defaultValue = "", property = "truth.generateAssertionsInPackage") public String generateAssertionsInPackage; - /** - * Flag specifying whether to clean the directory where assertions are generated. The default is false. - */ - @Parameter(defaultValue = "false", property = "truth.cleanTargetDir") - public boolean cleanTargetDir; + // todo? +// /** +// * Flag specifying whether to clean the directory where assertions are generated. The default is false. +// */ +// @Parameter(defaultValue = "false", property = "truth.cleanTargetDir") +// public boolean cleanTargetDir; /** * List of packages to generate assertions for. @@ -80,19 +81,21 @@ public class GeneratorMojo extends AbstractMojo { @Parameter(property = "truth.classes") public String[] classes; - /** - * Generated assertions are limited to classes matching one of the given regular expressions, default is to include - * all classes. - */ - @Parameter(property = "truth.includes") - public String[] includes = INCLUDE_ALL_CLASSES; + // todo +// /** +// * Generated assertions are limited to classes matching one of the given regular expressions, default is to include +// * all classes. +// */ +// @Parameter(property = "truth.includes") +// public String[] includes = INCLUDE_ALL_CLASSES; - /** - * If class matches one of the given regex, no assertions will be generated for it, default is not to exclude - * anything. - */ - @Parameter(property = "truth.excludes") - public String[] excludes = new String[0]; + // todo +// /** +// * If class matches one of the given regex, no assertions will be generated for it, default is not to exclude +// * anything. +// */ +// @Parameter(property = "truth.excludes") +// public String[] excludes = new String[0]; /** @@ -102,14 +105,17 @@ public class GeneratorMojo extends AbstractMojo { @Parameter(property = "truth.entryPointClassPackage") public String entryPointClassPackage; - /** - * Skip generating classes, handy way to disable the plugin. - */ - @Parameter(property = "truth.skip") - public boolean skip = false; + // todo +// /** +// * Skip generating classes, handy way to disable the plugin. +// */ +// @Parameter(property = "truth.skip") +// public boolean skip = false; + - @Parameter(property = "truth.recursive") - public boolean recursive = true; + // todo +// @Parameter(property = "truth.recursive") +// public boolean recursive = true; /** * for testing From b4e627c31d0a19f1459c10e1cc830f41d8702a7d Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Fri, 13 Aug 2021 10:43:10 +0100 Subject: [PATCH 29/32] update tests --- .../expected/MyEmployeeParentSubject.java.txt | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/extensions/generator/src/test/resources/expected/MyEmployeeParentSubject.java.txt b/extensions/generator/src/test/resources/expected/MyEmployeeParentSubject.java.txt index f7bd13840..eb91d5275 100644 --- a/extensions/generator/src/test/resources/expected/MyEmployeeParentSubject.java.txt +++ b/extensions/generator/src/test/resources/expected/MyEmployeeParentSubject.java.txt @@ -272,18 +272,20 @@ public class MyEmployeeParentSubject extends Subject { } /** - * Checks if the element is or is not contained in the collection. + * Checks if a {@link Project} element is, or is not contained in the + * collection. */ - public void hasProjectListNotWithElement(java.lang.Object expected) { + public void hasProjectListNotWithElement(com.google.common.truth.extension.generator.testModel.Project expected) { if (!actual.getProjectList().contains(expected)) { failWithActual(fact("expected ProjectList NOT to have element", expected)); } } /** - * Checks if the element is or is not contained in the collection. + * Checks if a {@link Project} element is, or is not contained in the + * collection. */ - public void hasProjectListWithElement(java.lang.Object expected) { + public void hasProjectListWithElement(com.google.common.truth.extension.generator.testModel.Project expected) { if (actual.getProjectList().contains(expected)) { failWithActual(fact("expected ProjectList to have element", expected)); } @@ -317,18 +319,18 @@ public class MyEmployeeParentSubject extends Subject { } /** - * Check Maps for containing a given key. + * Check Maps for containing a given {@link String} key. */ - public void hasProjectMapNotWithKey(java.lang.Object expected) { + public void hasProjectMapNotWithKey(java.lang.String expected) { if (!actual.getProjectMap().containsKey(expected)) { failWithActual(fact("expected ProjectMap NOT to have key", expected)); } } /** - * Check Maps for containing a given key. + * Check Maps for containing a given {@link String} key. */ - public void hasProjectMapWithKey(java.lang.Object expected) { + public void hasProjectMapWithKey(java.lang.String expected) { if (actual.getProjectMap().containsKey(expected)) { failWithActual(fact("expected ProjectMap to have key", expected)); } From 8ed2d40d0b1ae7b55e229ec0197f2133b2cf148c Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Fri, 13 Aug 2021 11:19:12 +0100 Subject: [PATCH 30/32] update tests --- .../generator/GeneratedAssertionTests.java | 6 +-- .../generator/internal/ClassUtils.java | 47 +++++++++++++++++++ .../internal/SubjectMethodGenerator.java | 20 ++------ .../internal/TruthGeneratorTest.java | 4 +- .../legacy}/NonBeanLegacy.java | 2 +- 5 files changed, 56 insertions(+), 23 deletions(-) rename extensions/generator/src/test/java/com/google/common/truth/extension/generator/{testModelLegacy => testing/legacy}/NonBeanLegacy.java (79%) diff --git a/extensions/generator-assertions-tests/src/test/java/com/google/common/truth/extension/generator/GeneratedAssertionTests.java b/extensions/generator-assertions-tests/src/test/java/com/google/common/truth/extension/generator/GeneratedAssertionTests.java index 49261ad82..da0b066b0 100644 --- a/extensions/generator-assertions-tests/src/test/java/com/google/common/truth/extension/generator/GeneratedAssertionTests.java +++ b/extensions/generator-assertions-tests/src/test/java/com/google/common/truth/extension/generator/GeneratedAssertionTests.java @@ -6,9 +6,9 @@ import com.google.common.truth.extension.generator.testModel.MyEmployee; import com.google.common.truth.extension.generator.testModel.MyEmployeeChildSubject; import com.google.common.truth.extension.generator.testModel.MyEmployeeSubject; -import com.google.common.truth.extension.generator.testModelLegacy.NonBeanLegacy; -import com.google.common.truth.extension.generator.testModelLegacy.NonBeanLegacyChildSubject; -import com.google.common.truth.extension.generator.testModelLegacy.NonBeanLegacySubject; +import com.google.common.truth.extension.generator.testing.legacy.NonBeanLegacy; +import com.google.common.truth.extension.generator.testing.legacy.NonBeanLegacyChildSubject; +import com.google.common.truth.extension.generator.testing.legacy.NonBeanLegacySubject; import org.junit.Test; import uk.co.jemos.podam.api.PodamFactoryImpl; diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/ClassUtils.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/ClassUtils.java index d2c7ae6ae..13782e93c 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/ClassUtils.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/ClassUtils.java @@ -6,7 +6,10 @@ import org.reflections.util.ConfigurationBuilder; import org.reflections.util.FilterBuilder; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.lang.reflect.WildcardType; import java.util.Set; import java.util.stream.Collectors; @@ -39,4 +42,48 @@ public static Set> collectSourceClasses(String... modelPackages) { public static String maybeGetSimpleName(Type elementType) { return (elementType instanceof Class) ? ((Class) elementType).getSimpleName() : elementType.getTypeName(); } + + static Type getStrippedReturnTypeFirstGenericParam(Method method) { + Type genericReturnType = method.getGenericReturnType(); + return getStrippedReturnTypeFirstGenericParam(genericReturnType); + } + + private static Type getStrippedReturnTypeFirstGenericParam(Type genericReturnType) { + Class keyType = Object.class; // default fall back + if (genericReturnType instanceof ParameterizedType) { + ParameterizedType parameterizedReturnType = (ParameterizedType) genericReturnType; + Type[] actualTypeArguments = parameterizedReturnType.getActualTypeArguments(); + if (actualTypeArguments.length > 0) { // must have at least 1 + Type key = actualTypeArguments[0]; + return getStrippedReturnType(key); + } + } else if (genericReturnType instanceof Class) { + return genericReturnType; // terminal + } + return keyType; + } + + private static Type getStrippedReturnType(Type key) { + if (key instanceof ParameterizedType) { + // strip type arguments + // could potentially add this as a type parameter to the method instead? + ParameterizedType parameterizedKey = (ParameterizedType) key; + Type rawType = parameterizedKey.getRawType(); + Type recursive = getStrippedReturnTypeFirstGenericParam(rawType); + return recursive; + } else if (key instanceof WildcardType) { + // strip type arguments + // could potentially add this as a type parameter to the method instead? + WildcardType wildcardKey = (WildcardType) key; + Type[] upperBounds = wildcardKey.getUpperBounds(); + if (upperBounds.length > 0) { + Type upperBound = upperBounds[0]; + Type recursive = getStrippedReturnType(upperBound); + return recursive; + } + } + // else + return key; + } + } diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java index 8f4e382d8..15931e15e 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java @@ -13,7 +13,6 @@ import java.lang.reflect.Member; import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.math.BigDecimal; import java.util.*; @@ -21,6 +20,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static com.google.common.truth.extension.generator.internal.ClassUtils.getStrippedReturnTypeFirstGenericParam; import static com.google.common.truth.extension.generator.internal.ClassUtils.maybeGetSimpleName; import static java.lang.String.format; import static java.lang.reflect.Modifier.*; @@ -277,7 +277,7 @@ private MethodSource addMapStrategyGeneric(Method method, JavaC .setPublic(); // parameter - Type keyType = getReturnTypeFirstGenericParam(method); + Type keyType = getStrippedReturnTypeFirstGenericParam(method); newMethod.addParameter(keyType.getTypeName(), "expected"); // @@ -288,20 +288,6 @@ private MethodSource addMapStrategyGeneric(Method method, JavaC return newMethod; } - private Type getReturnTypeFirstGenericParam(Method method) { - Class keyType = Object.class; // default fall back - Type genericReturnType = method.getGenericReturnType(); - if (genericReturnType instanceof ParameterizedType) { - ParameterizedType parameterizedReturnType = (ParameterizedType) genericReturnType; - Type[] actualTypeArguments = parameterizedReturnType.getActualTypeArguments(); - if (actualTypeArguments.length > 0) { // must have at least 1 - Type key = actualTypeArguments[0]; - return key; - } - } - return keyType; - } - private void addOptionalStrategy(Method method, JavaClassSource generated, Class classUnderTest) { addOptionalStrategyGeneric(method, generated, false); addOptionalStrategyGeneric(method, generated, true); @@ -359,7 +345,7 @@ private MethodSource addHasElementStrategyGeneric(Method method .setPublic(); - Type elementType = getReturnTypeFirstGenericParam(method); + Type elementType = getStrippedReturnTypeFirstGenericParam(method); newMethod.addParameter(elementType.getTypeName(), "expected"); newMethod.getJavaDoc().setText(format("Checks if a {@link %s} element is, or is not contained in the collection.", maybeGetSimpleName(elementType))); diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/TruthGeneratorTest.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/TruthGeneratorTest.java index 68e32a5a9..595dbb33b 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/TruthGeneratorTest.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/TruthGeneratorTest.java @@ -10,7 +10,7 @@ import com.google.common.truth.extension.generator.testModel.IdCard; import com.google.common.truth.extension.generator.testModel.MyEmployee; import com.google.common.truth.extension.generator.testModel.Project; -import com.google.common.truth.extension.generator.testModelLegacy.NonBeanLegacy; +import com.google.common.truth.extension.generator.testing.legacy.NonBeanLegacy; import org.jboss.forge.roaster.model.Method; import org.jboss.forge.roaster.model.Type; import org.jboss.forge.roaster.model.source.JavaClassSource; @@ -132,7 +132,7 @@ public void package_java_mix() { @Test public void test_legacy_mode() { TruthGeneratorAPI tg = TruthGeneratorAPI.create(); - SourceClassSets ss = new SourceClassSets(this); + SourceClassSets ss = new SourceClassSets(this.getClass().getPackage().getName() + ".legacy"); ss.generateFromNonBean(NonBeanLegacy.class); Map, ThreeSystem> generated = tg.generate(ss); diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModelLegacy/NonBeanLegacy.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testing/legacy/NonBeanLegacy.java similarity index 79% rename from extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModelLegacy/NonBeanLegacy.java rename to extensions/generator/src/test/java/com/google/common/truth/extension/generator/testing/legacy/NonBeanLegacy.java index 9c6c160dd..ef584db98 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModelLegacy/NonBeanLegacy.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testing/legacy/NonBeanLegacy.java @@ -1,4 +1,4 @@ -package com.google.common.truth.extension.generator.testModelLegacy; +package com.google.common.truth.extension.generator.testing.legacy; import lombok.Builder; From 058691f67cd0d9aa94db02bdb18bbcce406d32bc Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Fri, 13 Aug 2021 12:53:07 +0100 Subject: [PATCH 31/32] feature: base subject extension points (e.g. MyStringSubject) Markers for factory methods - not used yet Markers for bae subject extensions - not used yet --- extensions/generator-assertions-tests/pom.xml | 35 +++++++++++++- .../generator/GeneratedAssertionTests.java | 11 ++--- .../extension/generator/InstanceUtils.java | 7 +++ .../generator/NativeSubjectExtensions.java | 31 ++++++++++++ .../generator/BaseSubjectExtension.java | 15 ++++++ .../generator/SubjectFactoryMethod.java | 16 +++++++ .../generator/TruthGeneratorAPI.java | 11 +++++ .../generator/internal/MyStringSubject.java | 12 ++--- .../internal/SubjectMethodGenerator.java | 17 +++++-- .../generator/internal/TruthGenerator.java | 22 ++++++--- .../generator/internal/MyMapSubject.java | 29 ++++++++++++ .../internal/TruthGeneratorTest.java | 46 +++++++++++++++--- .../ThreeSystemSubject.java | 8 ++-- .../JavaClassSourceSubject.java | 2 +- .../generator/testModel/MyEmployee.java | 2 + .../extension/generator/testModel/Person.java | 6 +-- .../expected/MyEmployeeParentSubject.java.txt | 47 +++++++++++++++---- 17 files changed, 271 insertions(+), 46 deletions(-) create mode 100644 extensions/generator-assertions-tests/src/test/java/com/google/common/truth/extension/generator/InstanceUtils.java create mode 100644 extensions/generator-assertions-tests/src/test/java/com/google/common/truth/extension/generator/NativeSubjectExtensions.java create mode 100644 extensions/generator/src/main/java/com/google/common/truth/extension/generator/BaseSubjectExtension.java create mode 100644 extensions/generator/src/main/java/com/google/common/truth/extension/generator/SubjectFactoryMethod.java create mode 100644 extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/MyMapSubject.java diff --git a/extensions/generator-assertions-tests/pom.xml b/extensions/generator-assertions-tests/pom.xml index 1aa0936c0..b6ac6db3b 100644 --- a/extensions/generator-assertions-tests/pom.xml +++ b/extensions/generator-assertions-tests/pom.xml @@ -28,6 +28,26 @@ + + + + maven-compiler-plugin + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-compiler-plugin + + 13 + 13 + + + + + @@ -46,6 +66,13 @@ compile ${project.version} + + com.google.truth.extensions + truth-generator-extension + jar + ${project.version} + test + com.google.truth.extensions truth-generator-extension @@ -64,6 +91,12 @@ 7.2.7.RELEASE compile + + org.assertj + assertj-core + 3.18.0 + test + - \ No newline at end of file + diff --git a/extensions/generator-assertions-tests/src/test/java/com/google/common/truth/extension/generator/GeneratedAssertionTests.java b/extensions/generator-assertions-tests/src/test/java/com/google/common/truth/extension/generator/GeneratedAssertionTests.java index da0b066b0..6ac2fa8c2 100644 --- a/extensions/generator-assertions-tests/src/test/java/com/google/common/truth/extension/generator/GeneratedAssertionTests.java +++ b/extensions/generator-assertions-tests/src/test/java/com/google/common/truth/extension/generator/GeneratedAssertionTests.java @@ -2,21 +2,24 @@ import com.google.common.truth.Truth; import com.google.common.truth.extension.generator.internal.TruthGeneratorTest; +import com.google.common.truth.extension.generator.internal.legacy.NonBeanLegacyChildSubject; +import com.google.common.truth.extension.generator.internal.legacy.NonBeanLegacySubject; import com.google.common.truth.extension.generator.testModel.ManagedTruth; import com.google.common.truth.extension.generator.testModel.MyEmployee; import com.google.common.truth.extension.generator.testModel.MyEmployeeChildSubject; import com.google.common.truth.extension.generator.testModel.MyEmployeeSubject; import com.google.common.truth.extension.generator.testing.legacy.NonBeanLegacy; -import com.google.common.truth.extension.generator.testing.legacy.NonBeanLegacyChildSubject; -import com.google.common.truth.extension.generator.testing.legacy.NonBeanLegacySubject; import org.junit.Test; import uk.co.jemos.podam.api.PodamFactoryImpl; +import static com.google.common.truth.extension.generator.InstanceUtils.createInstance; import static com.google.common.truth.extension.generator.testModel.MyEmployee.State.NEVER_EMPLOYED; import static com.google.common.truth.extension.generator.testModel.MyEmployeeChildSubject.assertTruth; /** + * Uses output from packages completed tests run from the generator module. + * * @see TruthGeneratorTest#generate_code */ public class GeneratedAssertionTests { @@ -55,10 +58,6 @@ public void test_legacy_mode() { nonBeanLegacySubject.hasName().isEqualTo("lilan"); } - private T createInstance(Class clazz) { - return PODAM_FACTORY.manufacturePojo(clazz); - } - @Test public void recursive() { MyEmployee emp = createInstance(MyEmployee.class); diff --git a/extensions/generator-assertions-tests/src/test/java/com/google/common/truth/extension/generator/InstanceUtils.java b/extensions/generator-assertions-tests/src/test/java/com/google/common/truth/extension/generator/InstanceUtils.java new file mode 100644 index 000000000..6c9098eb9 --- /dev/null +++ b/extensions/generator-assertions-tests/src/test/java/com/google/common/truth/extension/generator/InstanceUtils.java @@ -0,0 +1,7 @@ +package com.google.common.truth.extension.generator; + +public class InstanceUtils { + static T createInstance(Class clazz) { + return GeneratedAssertionTests.PODAM_FACTORY.manufacturePojo(clazz); + } +} diff --git a/extensions/generator-assertions-tests/src/test/java/com/google/common/truth/extension/generator/NativeSubjectExtensions.java b/extensions/generator-assertions-tests/src/test/java/com/google/common/truth/extension/generator/NativeSubjectExtensions.java new file mode 100644 index 000000000..f4bf2cf8a --- /dev/null +++ b/extensions/generator-assertions-tests/src/test/java/com/google/common/truth/extension/generator/NativeSubjectExtensions.java @@ -0,0 +1,31 @@ +package com.google.common.truth.extension.generator; + +import com.google.common.truth.extension.generator.internal.extensions.ManagedTruth; +import com.google.common.truth.extension.generator.internal.extensions.MyEmployeeSubject; +import com.google.common.truth.extension.generator.testModel.MyEmployee; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static com.google.common.truth.extension.generator.InstanceUtils.createInstance; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class NativeSubjectExtensions { + + @Test + public void my_string() { + String nameWithSpace = "tony "; + MyEmployee emp = createInstance(MyEmployee.class).toBuilder().workNickName(nameWithSpace).build(); + MyEmployeeSubject es = ManagedTruth.assertThat(emp); + + // my strings + es.hasWorkNickName().ignoringTrailingWhiteSpace().equalTo("tony"); + + // my maps + assertThatThrownBy(() -> es.hasProjectMap().containsKeys("key1", "key2")).isInstanceOf(AssertionError.class); + List keys = new ArrayList<>(emp.getProjectMap().keySet()); + es.hasProjectMap().containsKeys(keys.get(0), keys.get(1)); + } + +} diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/BaseSubjectExtension.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/BaseSubjectExtension.java new file mode 100644 index 000000000..aaf2240da --- /dev/null +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/BaseSubjectExtension.java @@ -0,0 +1,15 @@ +package com.google.common.truth.extension.generator; + +import com.google.common.truth.Subject; +import com.google.common.truth.extension.generator.internal.MyStringSubject; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +/** + * Marks for the machines, extensions to base Truth {@link Subject}s - for example, {@link MyStringSubject}. + */ +// todo scan for these instead of registering manually +@Target({ElementType.TYPE}) +public @interface BaseSubjectExtension { +} diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/SubjectFactoryMethod.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/SubjectFactoryMethod.java new file mode 100644 index 000000000..d0f6e6276 --- /dev/null +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/SubjectFactoryMethod.java @@ -0,0 +1,16 @@ +package com.google.common.truth.extension.generator; + +import com.google.common.truth.Subject; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +/** + * Marks for the machines, the method used to create the {@link Subject}s. + *

+ * Useful so that we don't need to rely completely on String patterns, and so that Subject extension points can have non + * colliding names - as the method must be static. + */ +@Target({ElementType.METHOD}) +public @interface SubjectFactoryMethod { +} diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGeneratorAPI.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGeneratorAPI.java index 2e53a7bf3..0ce701855 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGeneratorAPI.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/TruthGeneratorAPI.java @@ -1,5 +1,6 @@ package com.google.common.truth.extension.generator; +import com.google.common.truth.Subject; import com.google.common.truth.extension.generator.internal.TruthGenerator; import com.google.common.truth.extension.generator.internal.model.ThreeSystem; @@ -66,4 +67,14 @@ static TruthGenerator create() { Map, ThreeSystem> generate(Set> classes); Map, ThreeSystem> generate(Class... classes); + + /** + * Manually register extensions to base Subject types - i.e. extend StringSubject with your own features. These will + * get dynamically inserted into the generated Subject tree when used. + * + * @param targetType the class under test - e.g. String + * @param myMapSubjectClass the Subject class which extends the base Subject + */ + void registerStandardSubjectExtension(Class targetType, Class myMapSubjectClass); + } diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/MyStringSubject.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/MyStringSubject.java index 9c5d67389..c04668915 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/MyStringSubject.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/MyStringSubject.java @@ -2,13 +2,15 @@ import com.google.common.truth.FailureMetadata; import com.google.common.truth.StringSubject; +import com.google.common.truth.extension.generator.BaseSubjectExtension; +import com.google.common.truth.extension.generator.SubjectFactoryMethod; import lombok.AccessLevel; import lombok.NoArgsConstructor; -import org.jboss.forge.roaster.model.source.JavaClassSource; /** * @see IgnoringWhiteSpaceComparison */ +@BaseSubjectExtension public class MyStringSubject extends StringSubject { String actual; @@ -18,14 +20,12 @@ protected MyStringSubject(FailureMetadata failureMetadata, String actual) { this.actual = actual; } - /** - * Returns an assertion builder for a {@link JavaClassSource} class. - */ - public static Factory myStrings() { + @SubjectFactoryMethod + public static Factory strings() { return MyStringSubject::new; } - public IgnoringWhiteSpaceComparison ignoringWhiteSpace() { + public IgnoringWhiteSpaceComparison ignoringTrailingWhiteSpace() { return new IgnoringWhiteSpaceComparison(); } diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java index 15931e15e..351929d96 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java @@ -42,10 +42,12 @@ public class SubjectMethodGenerator { private final Map> classPathSubjectTypes = new HashMap<>(); private final Map generatedSubjects; + private final Map, Class> subjectExtensions; private ThreeSystem context; - public SubjectMethodGenerator(final Set allTypes) { + public SubjectMethodGenerator(Set allTypes, Map, Class> subjectExtensions) { this.generatedSubjects = allTypes.stream().collect(Collectors.toMap(x -> x.classUnderTest.getName(), x -> x)); + this.subjectExtensions = subjectExtensions; Reflections reflections = new Reflections("com.google.common.truth", "io.confluent"); Set> subjectTypes = reflections.getSubTypesOf(Subject.class); @@ -418,8 +420,9 @@ private MethodSource addChainStrategy(Method method, JavaClassS String check = format("return check(\"%s()\")", method.getName()); body.append(check); + boolean isAnExtendedSubject = this.subjectExtensions.containsValue(subjectClass.clazz); boolean notPrimitive = !returnType.isPrimitive(); - boolean needsAboutCall = notPrimitive && !isCoveredByNonPrimitiveStandardSubjects; + boolean needsAboutCall = notPrimitive && !isCoveredByNonPrimitiveStandardSubjects || isAnExtendedSubject; if (needsAboutCall || subjectClass.isGenerated()) { String aboutName; @@ -529,10 +532,15 @@ private Optional getSubjectForType(final Class type) { } } + // extensions take priority + Class extension = this.subjectExtensions.get(type); + if (extension != null) + return ClassOrGenerated.ofClass(extension); + // Optional subject = getGeneratedOrCompiledSubjectFromString(name); - // Can't find any generated ones or compiled ones - fall back to native subjects + // Can't find any generated ones or compiled ones - fall back to native subjects that are assignable (e.g. comparable or iterable) if (subject.isEmpty()) { Optional> nativeSubjectForType = getClosestTruthNativeSubjectForType(type); subject = ClassOrGenerated.ofClass(nativeSubjectForType); @@ -545,6 +553,8 @@ private Optional getSubjectForType(final Class type) { private Optional> getClosestTruthNativeSubjectForType(final Class type) { Class normalised = primitiveToWrapper(type); + + // native Optional> highestPriorityNativeType = nativeTypes.stream().filter(x -> x.isAssignableFrom(normalised)).findFirst(); if (highestPriorityNativeType.isPresent()) { Class aClass = highestPriorityNativeType.get(); @@ -564,6 +574,7 @@ private Optional getGeneratedOrCompiledSubjectFromString(final return of(new ClassOrGenerated(null, subjectFromGenerated.get())); } + // any matching compiled subject Class aClass = getCompiledSubjectForTypeName(name); if (aClass != null) return of(new ClassOrGenerated(aClass, null)); diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java index c5455e720..5d97d3b4c 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/TruthGenerator.java @@ -9,10 +9,7 @@ import lombok.Getter; import lombok.Setter; -import java.util.HashSet; -import java.util.Map; -import java.util.Optional; -import java.util.Set; +import java.util.*; import java.util.logging.Level; import java.util.stream.Collectors; @@ -38,6 +35,11 @@ public class TruthGenerator implements TruthGeneratorAPI { @Getter private Optional entryPoint = Optional.empty(); + /** + * Base Truth subject extensions to inject into Subject tree + */ + private final Map, Class> subjectExtensions = new HashMap<>(); + @Override public void generate(String... modelPackages) { Utils.requireNotEmpty(modelPackages); @@ -84,7 +86,7 @@ private Set> filterSubjects(Set> classes, int sizeBeforeFilter } private void addTests(final Set allTypes) { - SubjectMethodGenerator tg = new SubjectMethodGenerator(allTypes); + SubjectMethodGenerator tg = new SubjectMethodGenerator(allTypes, subjectExtensions); tg.addTests(allTypes); } @@ -138,10 +140,11 @@ public Map, ThreeSystem> generate(SourceClassSets ss) { ).collect(toSet()); // straight up classes - Set simpleClasses = generateSkeletons(ss.getSimpleClasses(), Optional.empty(), overallEntryPoint); + Optional packageForOverall = Optional.ofNullable(ss.getPackageForOverall()); + Set simpleClasses = generateSkeletons(ss.getSimpleClasses(), packageForOverall, overallEntryPoint); // legacy classes - Set legacyClasses = generateSkeletons(ss.getLegacyBeans(), Optional.empty(), overallEntryPoint); + Set legacyClasses = generateSkeletons(ss.getLegacyBeans(), packageForOverall, overallEntryPoint); legacyClasses.forEach(x -> x.setLegacyMode(true)); // legacy classes with custom package destination @@ -195,6 +198,11 @@ public Map, ThreeSystem> generate(Class... classes) { return generate(stream(classes).collect(toSet())); } + @Override + public void registerStandardSubjectExtension(Class targetType, Class myMapSubjectClass) { + this.subjectExtensions.put(targetType, myMapSubjectClass); + } + @Override public String maintain(final Class source, final Class userAndGeneratedMix) { throw new IllegalStateException("Not implemented yet"); diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/MyMapSubject.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/MyMapSubject.java new file mode 100644 index 000000000..4e56a3d56 --- /dev/null +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/MyMapSubject.java @@ -0,0 +1,29 @@ +package com.google.common.truth.extension.generator.internal; + +import com.google.common.truth.FailureMetadata; +import com.google.common.truth.MapSubject; +import com.google.common.truth.extension.generator.BaseSubjectExtension; +import com.google.common.truth.extension.generator.SubjectFactoryMethod; + +import java.util.Map; + +@BaseSubjectExtension +public class MyMapSubject extends MapSubject { + + private Map actual; + + protected MyMapSubject(FailureMetadata failureMetadata, Map actual) { + super(failureMetadata, actual); + this.actual = actual; + } + + @SubjectFactoryMethod + public static Factory maps() { + return MyMapSubject::new; + } + + public void containsKeys(Object... keys) { + isNotNull(); + check("keySet()").that(actual.keySet()).containsAtLeastElementsIn(keys); + } +} diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/TruthGeneratorTest.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/TruthGeneratorTest.java index 595dbb33b..e6a814c22 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/TruthGeneratorTest.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/TruthGeneratorTest.java @@ -44,10 +44,6 @@ private String loadFileToString(String expectedFileName) throws IOException { return Resources.toString(Resources.getResource(expectedFileName), Charset.defaultCharset()); } - private String trim(String in) { - return in.replaceAll("(?m)^(\\s)*|(\\s)*$", ""); - } - /** * Base test that compares with expected generated code for test model */ @@ -56,6 +52,11 @@ public void generate_code() throws IOException { // todo need to be able to set base package for all generated classes, kind of like shade, so you cah generate test for classes in other restricted modules TruthGenerator truthGenerator = TruthGeneratorAPI.create(); + // + truthGenerator.registerStandardSubjectExtension(String.class, MyStringSubject.class); + truthGenerator.registerStandardSubjectExtension(Map.class, MyMapSubject.class); + + // Set> classes = new HashSet<>(); classes.add(MyEmployee.class); classes.add(IdCard.class); @@ -88,7 +89,7 @@ public void generate_code() throws IOException { assertThat(threeSystemGenerated) .hasParent().hasGenerated().hasSourceText() - .ignoringWhiteSpace().equalTo(expectedMyEmployeeParent); // sanity full chain + .ignoringTrailingWhiteSpace().equalTo(expectedMyEmployeeParent); // sanity full chain assertThat(threeSystemGenerated).hasParentSource(expectedMyEmployeeParent); @@ -264,7 +265,40 @@ public void get_generics() { @Test public void standard_extensions() { - assertThat(true).isFalse(); + TruthGenerator tg = TruthGeneratorAPI.create(); + TruthGeneratorAPI tgApi = tg; + tg.setRecursive(false); // speed + tg.setEntryPoint(of(this.getClass().getPackage().getName() + ".extensions")); + + // register handlers + // todo do this with annotation scanning instead + tgApi.registerStandardSubjectExtension(String.class, MyStringSubject.class); + tgApi.registerStandardSubjectExtension(Map.class, MyMapSubject.class); + + // + Map, ThreeSystem> generate = tg.generate(MyEmployee.class); + ThreeSystem threeSystem = generate.get(MyEmployee.class); + JavaClassSource generated = threeSystem.getParent().getGenerated(); + + // custom string + { + String name = "hasName"; + List> method = generated.getMethods().stream().filter(x -> x.getName().equals(name)).collect(toList()); + assertThat(method).hasSize(1); + MethodSource hasKey = method.get(0); + Type returnType = hasKey.getReturnType(); + assertThat(returnType.getName()).isEqualTo(MyStringSubject.class.getSimpleName()); + } + + // custom map + { + String name = "hasProjectMap"; + List> method = generated.getMethods().stream().filter(x -> x.getName().equals(name)).collect(toList()); + assertThat(method).hasSize(1); + MethodSource hasKey = method.get(0); + Type returnType = hasKey.getReturnType(); + assertThat(returnType.getName()).isEqualTo(MyMapSubject.class.getSimpleName()); + } } } diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ThreeSystemSubject.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ThreeSystemSubject.java index 5bb06dd37..7b4792366 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ThreeSystemSubject.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/internal/modelSubjectChickens/ThreeSystemSubject.java @@ -1,8 +1,8 @@ package com.google.common.truth.extension.generator.internal.modelSubjectChickens; import com.google.common.truth.FailureMetadata; -import com.google.common.truth.extension.generator.internal.model.ThreeSystem; import com.google.common.truth.extension.generator.UserManagedTruth; +import com.google.common.truth.extension.generator.internal.model.ThreeSystem; import javax.annotation.processing.Generated; @@ -34,14 +34,14 @@ public static Factory threeSystems() { } public void hasParentSource(String expected) { - hasParent().hasGenerated().hasSourceText().ignoringWhiteSpace().equalTo(expected); + hasParent().hasGenerated().hasSourceText().ignoringTrailingWhiteSpace().equalTo(expected); } public void hasMiddleSource(String expected) { - hasMiddle().hasGenerated().hasSourceText().ignoringWhiteSpace().equalTo(expected); + hasMiddle().hasGenerated().hasSourceText().ignoringTrailingWhiteSpace().equalTo(expected); } public void hasChildSource(String expected) { - hasChild().hasSourceText().ignoringWhiteSpace().equalTo(expected); + hasChild().hasSourceText().ignoringTrailingWhiteSpace().equalTo(expected); } } diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/shaded/org/jboss/forge/roaster/model/sourceChickens/JavaClassSourceSubject.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/shaded/org/jboss/forge/roaster/model/sourceChickens/JavaClassSourceSubject.java index b194f5639..b99b59839 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/shaded/org/jboss/forge/roaster/model/sourceChickens/JavaClassSourceSubject.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/shaded/org/jboss/forge/roaster/model/sourceChickens/JavaClassSourceSubject.java @@ -35,7 +35,7 @@ public static Factory javaClassSources( public MyStringSubject hasSourceText() { isNotNull(); - return check("toString").about(MyStringSubject.myStrings()).that(actual.toString()); + return check("toString").about(MyStringSubject.strings()).that(actual.toString()); } } diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/MyEmployee.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/MyEmployee.java index 41138989e..dc776629b 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/MyEmployee.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/MyEmployee.java @@ -22,6 +22,8 @@ public class MyEmployee extends Person { private MyEmployee boss = null; + private String workNickName; + private IdCard card = null; private List projectList = new ArrayList<>(); diff --git a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/Person.java b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/Person.java index 29d64d8d2..3e2ce438f 100644 --- a/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/Person.java +++ b/extensions/generator/src/test/java/com/google/common/truth/extension/generator/testModel/Person.java @@ -1,15 +1,13 @@ package com.google.common.truth.extension.generator.testModel; -import lombok.*; +import lombok.Getter; +import lombok.RequiredArgsConstructor; import lombok.experimental.SuperBuilder; import java.time.ZonedDateTime; @Getter -//@With @SuperBuilder(toBuilder = true) -//@AllArgsConstructor -//@NoArgsConstructor @RequiredArgsConstructor public class Person { protected final String name; diff --git a/extensions/generator/src/test/resources/expected/MyEmployeeParentSubject.java.txt b/extensions/generator/src/test/resources/expected/MyEmployeeParentSubject.java.txt index eb91d5275..43a5a9ab1 100644 --- a/extensions/generator/src/test/resources/expected/MyEmployeeParentSubject.java.txt +++ b/extensions/generator/src/test/resources/expected/MyEmployeeParentSubject.java.txt @@ -19,17 +19,21 @@ import com.google.common.truth.extension.generator.testModel.StateSubject; import java.util.UUID; import static com.google.common.truth.extension.generator.internal.autoShaded.java.util.UUIDSubject.uUIDs; import com.google.common.truth.extension.generator.internal.autoShaded.java.util.UUIDSubject; -import com.google.common.truth.StringSubject; +import static com.google.common.truth.extension.generator.internal.MyStringSubject.strings; +import static com.google.common.truth.extension.generator.internal.MyStringSubject.strings; import java.util.List; import com.google.common.truth.IterableSubject; import java.util.Map; -import com.google.common.truth.MapSubject; +import static com.google.common.truth.extension.generator.internal.MyMapSubject.maps; +import com.google.common.truth.extension.generator.internal.MyMapSubject; import com.google.common.truth.LongSubject; import java.util.Optional; import static com.google.common.truth.extension.generator.internal.autoShaded.java.util.OptionalSubject.optionals; import com.google.common.truth.extension.generator.internal.autoShaded.java.util.OptionalSubject; +import static com.google.common.truth.extension.generator.internal.MyStringSubject.strings; import com.google.common.truth.BooleanSubject; import com.google.common.truth.ObjectArraySubject; +import com.google.common.truth.extension.generator.internal.MyStringSubject; import static com.google.common.truth.extension.generator.testModel.PersonSubject.persons; import com.google.common.truth.extension.generator.testModel.PersonSubject; @@ -266,9 +270,9 @@ public class MyEmployeeParentSubject extends Subject { * Returns the Subject for the given field type, so you can chain on other * assertions. */ - public StringSubject hasName() { + public MyStringSubject hasName() { isNotNull(); - return check("getName()").that(actual.getName()); + return check("getName()").about(strings()).that(actual.getName()); } /** @@ -358,9 +362,9 @@ public class MyEmployeeParentSubject extends Subject { * Returns the Subject for the given field type, so you can chain on other * assertions. */ - public MapSubject hasProjectMap() { + public MyMapSubject hasProjectMap() { isNotNull(); - return check("getProjectMap()").that(actual.getProjectMap()); + return check("getProjectMap()").about(maps()).that(actual.getProjectMap()); } /** @@ -435,6 +439,33 @@ public class MyEmployeeParentSubject extends Subject { return check("getWeighting()").about(optionals()).that(actual.getWeighting()); } + /** + * Simple check for equality for all fields. + */ + public void hasWorkNickNameNotEqualTo(java.lang.String expected) { + if (!(actual.getWorkNickName().equals(expected))) { + failWithActual(fact("expected WorkNickName NOT to be equal to", expected)); + } + } + + /** + * Simple check for equality for all fields. + */ + public void hasWorkNickNameEqualTo(java.lang.String expected) { + if ((actual.getWorkNickName().equals(expected))) { + failWithActual(fact("expected WorkNickName to be equal to", expected)); + } + } + + /** + * Returns the Subject for the given field type, so you can chain on other + * assertions. + */ + public MyStringSubject hasWorkNickName() { + isNotNull(); + return check("getWorkNickName()").about(strings()).that(actual.getWorkNickName()); + } + /** * Simple is or is not expectation for boolean fields. */ @@ -502,9 +533,9 @@ public class MyEmployeeParentSubject extends Subject { * Returns the Subject for the given field type, so you can chain on other * assertions. */ - public StringSubject hasToString() { + public MyStringSubject hasToString() { isNotNull(); - return check("toString()").that(actual.toString()); + return check("toString()").about(strings()).that(actual.toString()); } /** From d5461b386a64c3fcd7bd0c449e09ca356e8ed0e4 Mon Sep 17 00:00:00 2001 From: Antony Stubbs Date: Fri, 13 Aug 2021 15:55:59 +0100 Subject: [PATCH 32/32] Fix naming collisions with legacy mode, fail gracefully and log with boolean name collisions --- .../internal/SubjectMethodGenerator.java | 46 ++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java index 351929d96..da2999dad 100644 --- a/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java +++ b/extensions/generator/src/main/java/com/google/common/truth/extension/generator/internal/SubjectMethodGenerator.java @@ -101,8 +101,9 @@ private Collection getMethods(ThreeSystem system) { Set issers = getMethods(classUnderTest, withPrefix("is")); // also get all other methods, regardless of their prefix - Predicate expectSetters = not(withPrefix("set")); - Set legacy = (legacyMode) ? getMethods(classUnderTest, expectSetters) : Set.of(); + Predicate exceptSetters = not(withPrefix("set")); + Predicate exceptToers = not(withPrefix("to")); + Set legacy = (legacyMode) ? getMethods(classUnderTest, exceptSetters, exceptToers) : Set.of(); union.addAll(getters); union.addAll(issers); @@ -111,14 +112,20 @@ private Collection getMethods(ThreeSystem system) { return removeOverridden(union); } - private Set getMethods(Class classUnderTest, Predicate prefix) { + private Set getMethods(Class classUnderTest, Predicate... prefix) { // if shaded, can't access package private methods boolean isShaded = context.isShaded(); - Predicate skip = (ignore) -> true; - Predicate shadedPredicate = (isShaded) ? withModifier(PUBLIC) : skip; + Predicate skip = (ignore) -> true; + Predicate shadedPredicate = (isShaded) ? withModifier(PUBLIC) : skip; - return ReflectionUtils.getAllMethods(classUnderTest, - not(withModifier(PRIVATE)), not(withModifier(PROTECTED)), shadedPredicate, prefix, withParametersCount(0)); + List> predicatesCollect = Arrays.stream(prefix).collect(Collectors.toList()); + predicatesCollect.add(shadedPredicate); + predicatesCollect.add(not(withModifier(PRIVATE))); + predicatesCollect.add(not(withModifier(PROTECTED))); + predicatesCollect.add(withParametersCount(0)); + + Predicate[] predicates = predicatesCollect.toArray(new Predicate[0]); + return ReflectionUtils.getAllMethods(classUnderTest, predicates); } private Collection removeOverridden(final Collection getters) { @@ -377,18 +384,24 @@ private MethodSource addBooleanGeneric(Method method, JavaClass String methodName = removeStart(method.getName(), "is"); methodName = "is" + capitalize(say.toLowerCase()).trim() + methodName; - MethodSource booleanMethod = generated.addMethod(); - booleanMethod - .setName(methodName) - .setReturnTypeVoid() - .setBody(body) - .setPublic(); - copyThrownExceptions(method, booleanMethod); + if (generated.getMethod(methodName) == null) { + MethodSource booleanMethod = generated.addMethod(); + booleanMethod + .setName(methodName) + .setReturnTypeVoid() + .setBody(body) + .setPublic(); - booleanMethod.getJavaDoc().setText("Simple is or is not expectation for boolean fields."); + copyThrownExceptions(method, booleanMethod); - return booleanMethod; + booleanMethod.getJavaDoc().setText("Simple is or is not expectation for boolean fields."); + + return booleanMethod; + } else { + logger.atWarning().log("Method name collision, skipping adding boolean generic for %s", methodName); + return null; + } } private void copyThrownExceptions(Method method, MethodSource generated) { @@ -522,6 +535,7 @@ private Optional getSubjectForType(final Class type) { // arrays if (type.isArray()) { Class componentType = type.getComponentType(); + componentType = primitiveToWrapper(componentType); if (componentType.isPrimitive()) { // PrimitiveBooleanArraySubject String subjectPrefix = "Primitive" + componentType.getSimpleName() + "Array";