Giter Club home page Giter Club logo

jtheories's People

Contributors

hryuk avatar nirei avatar

Watchers

 avatar  avatar

jtheories's Issues

Control cyclic generator dependencies

Currently two generators can be declared such as:

@Generator
public interface AGenerator {
    default A generate(B b) {
        return new A(b);
    }
}

@Generator
public interface BGenerator {
    default B generate(A a) {
        return new B(a);
    }
}

Creation of such generators will, at best, result in a StackOverflowException.

Precautions should be taken to prevent this situations at compile time and force users to modify such paradoxical declarations.

Some generated collections makes tests fail

	@RepeatedTest(200)
	void productsHavePriceAndName(final List<Product> products) {
		products.forEach(
			product -> {
				Assertions.assertTrue(product.getPrice() > 0);
			}
		);
	}

Error examples:

[ERROR] com.jtheories.examples.ProductTest.productsHavePriceAndName(List)[195]  Time elapsed: 0.001 s  <<< ERROR!
org.junit.jupiter.api.extension.ParameterResolutionException: Failed to resolve parameter [java.util.List<com.jtheories.examples.Product> arg0] in method [void com.jtheories.examples.ProductTest.productsHavePriceAndName(java.util.List<com.jtheories.examples.Product>)]: Method generate() on class com.jtheories.generators.collections.GenericListGenerator threw an exception
Caused by: com.jtheories.core.generator.exceptions.GenerationRuntimeException: Method generate() on class com.jtheories.generators.collections.GenericListGenerator threw an exception
Caused by: java.lang.reflect.InvocationTargetException
Caused by: java.lang.ClassCastException: class com.jtheories.examples.Product cannot be cast to class java.lang.Comparable (com.jtheories.examples.Product is in unnamed module of loader 'app'; java.lang.Comparable is in module java.base of loader 'bootstrap')
[ERROR] com.jtheories.examples.ProductTest.productsHavePriceAndName(List)[173]  Time elapsed: 0 s  <<< ERROR!
org.junit.jupiter.api.extension.ParameterResolutionException: Failed to resolve parameter [java.util.List<com.jtheories.examples.Product> arg0] in method [void com.jtheories.examples.ProductTest.productsHavePriceAndName(java.util.List<com.jtheories.examples.Product>)]: Method generate() on class com.jtheories.generators.collections.GenericListGenerator threw an exception
Caused by: com.jtheories.core.generator.exceptions.GenerationRuntimeException: Method generate() on class com.jtheories.generators.collections.GenericListGenerator threw an exception
Caused by: java.lang.reflect.InvocationTargetException
Caused by: java.lang.IllegalStateException: Queue full

Add support for generating parameterized types

  @Test
  void testParameterized(Collection<Product> products) {
    products.forEach(product -> {
        Assertions.assertEquals(product.getId(), UUID.fromString(product.getId().toString()));
        Assertions.assertTrue(product.getPrice() > 0);
        Assertions.assertNotEquals(product.getPrice() % 10, 0);
    });
  }

Create a JUnit Runner

Create a runner through JUnit 5 extension API that runs every test annotated with @Property multiple times with random generared data that would allow eventually to support shrinking.

Add support for direct type parameters in generators

Currently, when a generator needs to produce generics, implementation details of the library need to be exposed to the user, who needs to receive a Class<T> type parameter (plus constraining annotations) to be able to manually call Generators.gen.

Instead of having the user receive Class<T> class and Class<?> annotations in user-defined generate() methods add support for using type parameters directly:

Hiding implementation details

At present time, our generator implementation for generic classes look something like this:

@Generated("com.jtheories.core.generator.processor.GeneratorProcessor")
public final class GenericThingGenerator<T> implements ThingGenerator, Generator<Thing> {
  public GenericThing generateConstrained(Class type, Class... annotations) {
    return GenericThing.super.generate(type, annotations);
  }
}

Internally, they are called with the type parameter of the generated class. They go on to pass that class to the user's default implementation. The user then uses that received type to manually call Generators or another specific generator like this:

	default List<T> generate(Class<T> type, Class<T>... constrains) {
		var elementGenerator = Generators.gen(type, constrains);
               ...
	}

This is a clear schism in the usual API we are usually trying to provide for our users, in which they just ask for the parameters they need to build their target class, such as this:

default Short generate(SourceOfRandom random) {...}

or this:

	default Product generate(UUID id, String name, @Positive @NotMultipleOf10 Long price) {...}

To solve this problem and hide specific parameterizing types from the user, it'd be better if we did the following instead:

In the user-defined generator the user declares parameters as usual. If they don't know the specific type of a parameter, they introduce a variable-type parameter in their generator function:

    default <A, B> MyParameterizedType<A, B> generate(A a, B b, String string, Long aLong) {
        return MyParameterizedType.builder()
            .setA(a)
            .setB(b)
            .setString(string)
            .setLong(aLong)
            .build();
    }

Internally, when a test or otherwise needs to generate a value of MyParameterizedType<SpecificX, SpecificY>, the type parameters will be resolved and the implementation will be called. The implementation receives those type parameters as input, resolves them to specific values, just the same as any other type, and then calls the user code:

@Generated("com.jtheories.core.generator.processor.GeneratorProcessor")
public final class MyParameterizedType<A, B> implements MyParameterizedGenerator, Generator<MyParameterizedType<A, B>> {
  public <A, B> MyParameterizedType<A, B> generateConstrained(
        Class<A> typeA, Class[] annotationsA,
        Class<B> typeB, Class[] annotationsB,
        Class[] annotationsString, Class[] annotationsLong) {
    var random_A = Generators.gen(typeA, annotationsA);
    var random_B = Generators.gen(typeB, annotationsB);
    var random_string = Generators.gen(String.class, annotationsString);
    var random_long = Generators.gen(Long.class, annotationsLong);
    return MyParameterizedType.super.generate(random_A, random_B, random_string, random_long);
  }
}

In this way, generics are handled just like any other type.

Generator instances are created multiple times

Generator instances are created when the generator is called instead of being created at startup a single time. This, besides performance decay, creates another an issue with SourceOfRandomGenerator, because a new SourceOfRandom with a different seed is created for every other generator, making the tests not reproductibles.

Improve performance

Tests runs are too slow, probably becasue generators are created recursively for every test instead of being created once and being saved to a list of already created generators.

Allow to annotate generated values on JUnit extension

Currently a user can annotate generator method values to constrain the generated value, for example:

   default Product generate(UUID id, String name, @Positive @NotMultipleOf10 Long price) {
    return new Product(id, name, price);
  }

But a user can not annotate test parameters to constrain the generaed value:

  void doSomethingTest(/* DOESN'T WORK*/ @Free Product product) {
    Assertions.assertEquals(product.getId(), UUID.fromString(product.getId().toString()));
    Assertions.assertTrue(product.getPrice() == 0);
  }

Improve support for arbitrary number of parameter values

Currently the library supports the generation of an arbitrary number of parameters via the same mechanism that it uses to support generics: the injection of a variable of type Class<T> that allows the user to manually call the Generators class.

This approach, although effective, is limited to only one arbitrary-amount parameter.

To solve this we could implement a new kind of parameter that allows for arbitrary generation of values, the Supplier.

Suppliers

Suppliers were added in Java 8. They are the functional-interface equivalent of a factory pattern. Since generators are essentially object factories, it is easy to think that suppliers make a great match for wrapping entire generators as parameters.

The proposed approach would look like this on the user side:

default MyStringContainer generate(Supplier<String> stringSupplier) {
    // User can call stringSupplier::get as many times as they want to obtain any number of values.
}

The implementation would do something like this this for every needed Supplier<> parameter.

  public MyStringContainer generateConstrained() {
    var random_string_supplier = () -> Generators.gen(String.class);
    return ListGenerator.super.generate(random_string_supplier);
  }

If we combine this with #26 we could also easily provide support for an arbitrary number of generic parameters.

  public MyStringContainer generateConstrained(Class<T> type_t, Class<?>... annotationsT) {
    var random_string_supplier = () -> Generators.gen(String.class, annotations);
    return ListGenerator.super.generate(random_string_supplier);
  }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.