Skip to content
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

Closed
wants to merge 32 commits into from

Conversation

astubbs
Copy link

@astubbs astubbs commented Jul 29, 2021

An implementation of #612 Consider auto-generating assertThat and subject factory for custom subjects

Generates the bare bones boiler plate subject for later extension.

Needs several usability

Issue with this, is if you use the pattern of extending the generated classes - the accessor methods return itself, not the class you've extended. Still super useful to bootstrap many model classes. There may be a way around this - need to think about it. Otherwise, Roaster also allows modifying existing classes - so could try experimenting with mutating existing classes, instead of overwriting them - this will preserve the custom assertion methods added to the class after it's first generated.

  • Clean up
  • Merging everything to be in line with the other models (java version, deps etc)
  • Improvements to make it practically accessible.
  • Template the javadoc statements or other large text bits or code, larger segment templating getting hard to read (no parameter names e.g. just %s's)
  • Some basic refactoring.
  • Add options for targets - like entire package contents as opposed to single classes.
  • Add a tag annotation to use for targets.
  • Generate basic usefull assertions for primitives e.g. employee#hasName().
  • Wrap in a maven plugin - otherwise, it can just be run on a beforeSuite step in unit tests, but that's slow to run it every time.
  • Optional Recursive generation of Subjects so you don’t have to register them all

Features (done):

  • Dog foods - uses it’s own bootstrapped Subjects to test itself
  • Single entry point for all Managed subjects
  • Template generation for easy incorporation of custom Subjects in your managed source control
  • Ignoring white space and line endings string Subject
  • Various ways to specify classes under test for generation
  • Shade other module classes like java.* classes (UUID etc)
  • Several standard assertion strategies
  • Chain assertion strategy
  • Detection of existing user managed Subject - doesn't regenerate
  • Support for legacy non bean compliant objects like Kafka's ConsumerRecord

More (done):

  • Optional Recursive generation of Subjects so you don’t have to register them all - going into external modules (i.e. java's UUID, ZonedDateTime etc)
  • Automatic shading of classes who's package is contained in an existing module already (e.g. can't put java.util.UUID's subject into same package java.util)
  • Maven plugin
More (done):
  • Collections, maps etc generic types are read And used in assertions ie assert.map.containsKey(MyKey key)
  • Customisable extension points for base subjects (like list and map) which will apply to all types in generated tree (where generated entry points are used)

Generates the bare bones boiler plate subject for later extension.

Needs several usability improvements to make it practically accessible.
@google-cla
Copy link

google-cla bot commented Jul 29, 2021

Thanks for your pull request. It looks like this may be your first contribution to a Google open source project (if not, look below for help). Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

📝 Please visit https://cla.developers.google.com/ to sign.

Once you've signed (or fixed any issues), please reply here with @googlebot I signed it! and we'll verify it.


What to do if you already signed the CLA

Individual signers
Corporate signers

ℹ️ Googlers: Go here for more info.

@google-cla
Copy link

google-cla bot commented Jul 29, 2021

Thanks for your pull request. It looks like this may be your first contribution to a Google open source project (if not, look below for help). Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

📝 Please visit https://cla.developers.google.com/ to sign.

Once you've signed (or fixed any issues), please reply here with @googlebot I signed it! and we'll verify it.


What to do if you already signed the CLA

Individual signers
Corporate signers

ℹ️ Googlers: Go here for more info.

@google-cla
Copy link

google-cla bot commented Jul 29, 2021

Thanks for your pull request. It looks like this may be your first contribution to a Google open source project (if not, look below for help). Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

📝 Please visit https://cla.developers.google.com/ to sign.

Once you've signed (or fixed any issues), please reply here with @googlebot I signed it! and we'll verify it.


What to do if you already signed the CLA

Individual signers
Corporate signers

ℹ️ Googlers: Go here for more info.

@astubbs
Copy link
Author

astubbs commented Jul 29, 2021

@googlebot I signed it!

@google-cla google-cla bot added cla: yes and removed cla: no labels Jul 29, 2021
@astubbs astubbs marked this pull request as draft July 29, 2021 17:58
…ge 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
…ge 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.
@astubbs
Copy link
Author

astubbs commented Jul 29, 2021

FYI, needs more work of course, but I know have a 3 class system,

  • a parent that is generated and has all the new stuff that'll evolve over a time
  • a generated bare bones class that extents parent which use can copy into source control and add custom stuff to
  • child which extends user's class and manages the access points (not sure this is needed, but it keeps the generated part of the user class smaller

All generated assertion entry points get collected into a ManagedTruth class (sample at the bottom here).

And have started experimenting with some basic test generation. It's all generated except for:

	void hasCustomAdvancedUserWrttenTest(){
		// ...
	}
package io.confluent.parallelconsumer.state;

import com.google.common.truth.BooleanSubject;
import com.google.common.truth.FailureMetadata;
import com.google.common.truth.IntegerSubject;
import com.google.common.truth.Subject;

/**
 * Truth Subject for the {@link WorkContainer}.
 * 
 * Note that this class is generated / managed, and will change over time. So
 * any changes you might make will be overwritten.
 * 
 * @see WorkContainer
 * @see WorkContainerSubject
 * @see WorkContainerChildSubject
 */
public class WorkContainerParentSubject extends Subject {

	protected final WorkContainer actual;

	protected WorkContainerParentSubject(FailureMetadata failureMetadata,
			io.confluent.parallelconsumer.state.WorkContainer actual) {
		super(failureMetadata, actual);
		this.actual = actual;
	}

	public IntegerSubject hasepoch() {
		return check("epoch").that(actual.getEpoch());
	}

	public BooleanSubject hasinFlight() {
		return check("inFlight").that(actual.isNotInFlight());
	}

	public IntegerSubject hasnumberOfFailedAttempts() {
		return check("numberOfFailedAttempts").that(actual.getNumberOfFailedAttempts());
	}
}
package io.confluent.parallelconsumer.state;

import io.confluent.parallelconsumer.state.WorkContainerParentSubject;
import com.google.common.truth.FailureMetadata;

/**
 * Optionally move this class into source control, and add your custom
 * assertions here.
 * 
 * @see WorkContainerParentSubject
 */
public class WorkContainerSubject extends WorkContainerParentSubject {

	protected WorkContainerSubject(FailureMetadata failureMetadata,
			io.confluent.parallelconsumer.state.WorkContainer actual) {
		super(failureMetadata, actual);
	}

	/**
	 * Returns an assertion builder for a {@link WorkContainer} class.
	 */
	public static Factory<WorkContainerSubject, WorkContainer> workContainers() {
		return WorkContainerSubject::new;
	}
	
	void hasCustomAdvancedUserWrttenTest(){
		// ...
	}
}
package io.confluent.parallelconsumer.state;

import io.confluent.parallelconsumer.state.WorkContainerSubject;
import static io.confluent.parallelconsumer.state.WorkContainerSubject.*;
import com.google.common.truth.Truth;

/**
 * Entry point for assertions for @{WorkContainer}. Import the static accessor
 * methods from this class and use them. Combines the generated code from
 * {@WorkContainerParentSubject}and the user code from
 * {@io.confluent.parallelconsumer.state.WorkContainerSubject}.
 * 
 * @see io.confluent.parallelconsumer.state.WorkContainer
 * @see io.confluent.parallelconsumer.state.WorkContainerSubject
 * @see WorkContainerParentSubject
 */
public class WorkContainerChildSubject extends WorkContainerSubject {

	/**
	 * Entry point for {@link WorkContainer} assertions.
	 */
	public static WorkContainerSubject assertThat(io.confluent.parallelconsumer.state.WorkContainer actual) {
		return Truth.assertAbout(workContainers()).that(actual);
	}

	/**
	 * Convenience entry point for {@link WorkContainer} assertions when being mixed
	 * with other "assertThat" assertion libraries.
	 * 
	 * @see #assertThat
	 */
	public static WorkContainerSubject assertTruth(io.confluent.parallelconsumer.state.WorkContainer actual) {
		return assertThat(actual);
	}
}
package io.confluent.parallelconsumer.truth;

import io.confluent.parallelconsumer.state.WorkContainerSubject;
import static io.confluent.parallelconsumer.state.WorkContainerSubject.*;
import com.google.common.truth.Truth;
import io.confluent.parallelconsumer.model.CommitHistorySubject;
import static io.confluent.parallelconsumer.model.CommitHistorySubject.*;
import io.confluent.parallelconsumer.offsets.EncodedOffsetPairSubject;
import static io.confluent.parallelconsumer.offsets.EncodedOffsetPairSubject.*;

/**
 * Single point of access for all managed Subjects.
 */
public class ManagedTruth {

	/**
	 * Entry point for {@link WorkContainer} assertions.
	 */
	public static WorkContainerSubject assertThat(io.confluent.parallelconsumer.state.WorkContainer actual) {
		return Truth.assertAbout(workContainers()).that(actual);
	}

	/**
	 * Convenience entry point for {@link WorkContainer} assertions when being mixed
	 * with other "assertThat" assertion libraries.
	 * 
	 * @see #assertThat
	 */
	public static WorkContainerSubject assertTruth(io.confluent.parallelconsumer.state.WorkContainer actual) {
		return assertThat(actual);
	}

	/**
	 * Entry point for {@link CommitHistory} assertions.
	 */
	public static CommitHistorySubject assertThat(io.confluent.parallelconsumer.model.CommitHistory actual) {
		return Truth.assertAbout(commitHistories()).that(actual);
	}

	/**
	 * Convenience entry point for {@link CommitHistory} assertions when being mixed
	 * with other "assertThat" assertion libraries.
	 * 
	 * @see #assertThat
	 */
	public static CommitHistorySubject assertTruth(io.confluent.parallelconsumer.model.CommitHistory actual) {
		return assertThat(actual);
	}

	/**
	 * Entry point for {@link EncodedOffsetPair} assertions.
	 */
	public static EncodedOffsetPairSubject assertThat(io.confluent.parallelconsumer.offsets.EncodedOffsetPair actual) {
		return Truth.assertAbout(encodedOffsetPairs()).that(actual);
	}

	/**
	 * Convenience entry point for {@link EncodedOffsetPair} assertions when being
	 * mixed with other "assertThat" assertion libraries.
	 * 
	 * @see #assertThat
	 */
	public static EncodedOffsetPairSubject assertTruth(io.confluent.parallelconsumer.offsets.EncodedOffsetPair actual) {
		return assertThat(actual);
	}
}

@astubbs
Copy link
Author

astubbs commented Aug 2, 2021

Ok, it's mostly ready to look at now. I'm at my personal MVP and will start using it in my projects.

@astubbs astubbs changed the title feature: WIP: Subject code generator feature: WIP: Subject code generator, fixes #612 Aug 2, 2021
@astubbs astubbs marked this pull request as ready for review August 3, 2021 18:28
this(packageFromClass.getPackage().getName());
}

public void generateAllFoundInPackagesOf(Class<?>... classes) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename from generate verb (as it doesnt yet), to something like 'add'

import static com.google.common.truth.Truth.assertThat;

@RunWith(JUnit4.class)
public class TruthGeneratorTest {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adopt project naming style


@RunWith(JUnit4.class)
public class TruthGeneratorTest {

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to make sure test suite works as a whole

@cgdecker cgdecker added the P3 not scheduled label Aug 9, 2021
@astubbs astubbs marked this pull request as draft August 12, 2021 09:26
@astubbs
Copy link
Author

astubbs commented Aug 12, 2021

Converted back to draft. Will mark ready again when:
[ ] clean up done
[x] collections customisation entry points done
[ ] project aligned
[ ] when I get a green light that this could actually get merged one day

@astubbs
Copy link
Author

astubbs commented Aug 12, 2021

Hi @cpovirk , is it possible to get CI turned on for this PR? Or am I barking up the wrong tree here.. :)

Markers for factory methods - not used yet
Markers for bae subject extensions - not used yet
@astubbs
Copy link
Author

astubbs commented Aug 13, 2021

@astubbs
Copy link
Author

astubbs commented Oct 19, 2021

heya @cpovirk , or anyone out there - if I could get an indication if there is any interest in merging this? Otherwise, I'll put effort into releasing it myself as a seperate project. But would rather not double up.
Cheers ❤️

@cpovirk
Copy link
Member

cpovirk commented Oct 19, 2021

Sorry, we keep saying that we need to write down the meanings of our priority labels, but we keep not doing so. "P3" == "not scheduled," so I would be happy to see such a thing released separately so that it exists and we can point people to it, rather than having it wait here indefinitely.

@astubbs
Copy link
Author

astubbs commented Oct 20, 2021

Thanks Chris! Ok sweet as - shouldn't be too much work to migrate. Just the publishing part is a PITA :)
I'll clean it up as I've indicated, massage the API a bit more then release it. I'll post back here when it's ready - would still love even the most mildest of reviews then with suggestions or guidance on how you'd like to see it work or evolve.

@astubbs
Copy link
Author

astubbs commented Nov 3, 2021

Ok @cpovirk - initial refactored version is here: https://github.com/astubbs/truth-generator
Lots left to do before publishing to repo1, but the plugin works for my needs atm. Will post back here again when 0.1 is available in repo1. But for now it can be installed locally and used (though that makes it unusable for actual projects, but people can try it out on their own stuff).

@astubbs astubbs closed this Nov 3, 2021
@astubbs
Copy link
Author

astubbs commented Nov 4, 2021

The plugin is now available in a couple of Maven repositories (but not repo1 yet), and the README has been updated with feature list and examples. See the README for details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla: yes P3 not scheduled
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants