-
Notifications
You must be signed in to change notification settings - Fork 260
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feature: WIP: Subject code generator, fixes #612 #894
Changes from 21 commits
383add2
1a6c0be
1199e02
1d0ceb9
13451cb
a2904f8
1d2012c
31b9906
12648cb
b014e0b
073603e
58161fb
ee0b396
b600a34
b11c935
8ef4230
487bafc
9cf239c
420f4c6
352493e
c51e46e
a28a2dd
145fcbc
f760439
a3d898c
4ec12b3
1a96b1f
efb8118
b4e627c
8ed2d40
058691f
d5461b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/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"> | ||
<parent> | ||
<groupId>com.google.truth.extensions</groupId> | ||
<artifactId>truth-extensions-parent</artifactId> | ||
<version>HEAD-SNAPSHOT</version> | ||
</parent> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<name>Truth Extension for generating Subjects</name> | ||
<artifactId>truth-generator-extension</artifactId> | ||
|
||
<properties> | ||
<version.roaster>2.23.0.Final</version.roaster> | ||
</properties> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>org.jboss.forge.roaster</groupId> | ||
<artifactId>roaster-api</artifactId> | ||
<version>${version.roaster}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.jboss.forge.roaster</groupId> | ||
<artifactId>roaster-jdt</artifactId> | ||
<version>${version.roaster}</version> | ||
<scope>compile</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.google.truth</groupId> | ||
<artifactId>truth</artifactId> | ||
<scope>compile</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.google.truth</groupId> | ||
<artifactId>truth</artifactId> | ||
<type>test-jar</type> | ||
<version>HEAD-SNAPSHOT</version> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.checkerframework</groupId> | ||
<artifactId>checker-compat-qual</artifactId> | ||
<version>2.5.5</version> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't need? |
||
</dependency> | ||
<dependency> | ||
<groupId>org.atteo</groupId> | ||
<artifactId>evo-inflector</artifactId> | ||
<version>1.3</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.reflections</groupId> | ||
<artifactId>reflections</artifactId> | ||
<version>0.9.12</version> | ||
</dependency> | ||
<!-- reflections optional dependency --> | ||
<dependency> | ||
<groupId>org.dom4j</groupId> | ||
<artifactId>dom4j</artifactId> | ||
<version>2.1.1</version> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't need? |
||
</dependency> | ||
<dependency> | ||
<groupId>org.apache.commons</groupId> | ||
<artifactId>commons-lang3</artifactId> | ||
<version>3.12.0</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.google.guava</groupId> | ||
<artifactId>guava</artifactId> | ||
<version>30.1.1-android</version> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Outsource version |
||
</dependency> | ||
<dependency> | ||
<groupId>com.google.flogger</groupId> | ||
<artifactId>flogger</artifactId> | ||
<version>0.6</version> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Scope |
||
</dependency> | ||
<dependency> | ||
<groupId>com.google.flogger</groupId> | ||
<artifactId>flogger-log4j2-backend</artifactId> | ||
<version>0.6</version> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Scope |
||
</dependency> | ||
<dependency> | ||
<groupId>com.google.flogger</groupId> | ||
<artifactId>flogger-system-backend</artifactId> | ||
<version>0.6</version> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Scope |
||
</dependency> | ||
<dependency> | ||
<groupId>org.projectlombok</groupId> | ||
<artifactId>lombok</artifactId> | ||
<version>1.18.20</version> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replace with auto value? |
||
</dependency> | ||
<dependency> | ||
<groupId>uk.co.jemos.podam</groupId> | ||
<artifactId>podam</artifactId> | ||
<version>7.2.7.RELEASE</version> | ||
<scope>compile</scope> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test scope |
||
</dependency> | ||
</dependencies> | ||
|
||
<build> | ||
<plugins> | ||
<plugin> | ||
<artifactId>maven-compiler-plugin</artifactId> | ||
<configuration> | ||
<source>1.8</source> | ||
<target>1.8</target> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be 1.7? |
||
</configuration> | ||
</plugin> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-compiler-plugin</artifactId> | ||
<configuration> | ||
<source>13</source> | ||
<target>13</target> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Outsource? Should be 7/8? |
||
</configuration> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
</project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
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.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. | ||
*/ | ||
@Getter | ||
public class SourceClassSets { | ||
|
||
private final String packageForOverall; | ||
private final Set<Class<?>[]> simplePackageOfClasses = new HashSet<>(); | ||
private final Set<Class<?>> simpleClasses = new HashSet<>(); | ||
private final Set<PackageAndClasses> packageAndClasses = new HashSet<>(); | ||
private final Set<Class<?>> legacyBeans = new HashSet<>(); | ||
private final Set<PackageAndClasses> legacyPackageAndClasses = new HashSet<>(); | ||
|
||
|
||
/** | ||
* @param packageForOverall the package to put the overall access points | ||
*/ | ||
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()); | ||
} | ||
|
||
public void generateAllFoundInPackagesOf(Class<?>... classes) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. rename from generate verb (as it doesnt yet), to something like 'add' |
||
simplePackageOfClasses.add(classes); | ||
} | ||
|
||
/** | ||
* Useful for generating Java module Subjects and put them in our package. | ||
* <p> | ||
* 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)); | ||
} | ||
|
||
public void generateFrom(Class<?>... classes) { | ||
this.simpleClasses.addAll(Arrays.stream(classes).collect(Collectors.toSet())); | ||
} | ||
|
||
public void generateFrom(Set<Class<?>> classes) { | ||
this.simpleClasses.addAll(classes); | ||
} | ||
|
||
/** | ||
* Shades the given source classes into the base package, suffixed with the source package | ||
*/ | ||
public void generateFromShaded(Class<?>... classes) { | ||
Set<PackageAndClasses> packageAndClassesStream = mapToPackageSets(classes); | ||
this.packageAndClasses.addAll(packageAndClassesStream); | ||
} | ||
|
||
private Set<PackageAndClasses> mapToPackageSets(Class<?>[] classes) { | ||
ImmutableListMultimap<Package, Class<?>> grouped = Multimaps.index(Arrays.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), | ||
classSet); | ||
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<PackageAndClasses> packageAndClassesStream = mapToPackageSets(clazzes); | ||
this.legacyPackageAndClasses.addAll(packageAndClassesStream); | ||
} | ||
|
||
@Value | ||
public static class PackageAndClasses { | ||
String targetPackageName; | ||
Class<?>[] classes; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
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.ThreeSystem; | ||
|
||
import java.util.Map; | ||
import java.util.Set; | ||
|
||
/** | ||
* | ||
*/ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Todo |
||
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. | ||
* <p> | ||
* Not implemented yet. | ||
*/ | ||
String maintain(Class source, Class userAndGeneratedMix); | ||
|
||
/** | ||
* todo | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Todo |
||
*/ | ||
<T> String combinedSystem(Class<T> source); | ||
|
||
/** | ||
* todo | ||
*/ | ||
void combinedSystem(String... modelPackages); | ||
|
||
/** | ||
* @param modelPackages | ||
*/ | ||
void generate(String... modelPackages); | ||
|
||
/** | ||
* @param classes | ||
*/ | ||
void generateFromPackagesOf(Class<?>... classes); | ||
|
||
/** | ||
* @param ss | ||
*/ | ||
void combinedSystem(SourceClassSets ss); | ||
|
||
/** | ||
* 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. | ||
* | ||
* <p> | ||
* There are many different ways to add, check out the different methods in {@link SourceClassSets}. | ||
* | ||
* @see SourceClassSets | ||
*/ | ||
Map<Class<?>, ThreeSystem> generate(SourceClassSets ss); | ||
|
||
/** | ||
* @param classes | ||
* @return | ||
*/ | ||
Map<Class<?>, ThreeSystem> generate(Set<Class<?>> classes); | ||
|
||
void generate(Class<?>... classes); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package com.google.common.truth.extension.generator; | ||
|
||
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. | ||
* <p> | ||
* <p> | ||
* 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(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
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; | ||
|
||
/** | ||
* @see IgnoringWhiteSpaceComparison | ||
*/ | ||
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<MyStringSubject, String> 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+$", ""); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't remove empty lines - test this |
||
} | ||
|
||
/** | ||
* make line endings consistent | ||
*/ | ||
private String normaliseEndingsEndings(String raw) { | ||
return raw.replaceAll("\\r\\n?", "\n"); | ||
} | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove