pholser / junit-quickcheck

Property-based testing, JUnit-style
MIT License
958 stars 121 forks source link

ReflectionException exception for a simple Counter class #497

Closed nagkumar closed 1 year ago

nagkumar commented 1 year ago
com.pholser.junit.quickcheck.internal.ReflectionException: com.pholser.junit.quickcheck.internal.ReflectionException: java.lang.InstantiationException: com.tejasoft.utils.Counter
    at app//com.pholser.junit.quickcheck.internal.Reflection.reflectionException(Reflection.java:302)
    at app//com.pholser.junit.quickcheck.internal.Reflection.instantiate(Reflection.java:130)
    at app//com.pholser.junit.quickcheck.internal.ParameterTypeContext.makeGenerator(ParameterTypeContext.java:283)
    at app//com.pholser.junit.quickcheck.internal.ParameterTypeContext.addGenerators(ParameterTypeContext.java:265)
    at app//com.pholser.junit.quickcheck.internal.ParameterTypeContext.annotate(ParameterTypeContext.java:225)
    at app//com.pholser.junit.quickcheck.internal.PropertyParameterContext.annotate(PropertyParameterContext.java:51)
    at app//com.pholser.junit.quickcheck.runner.PropertyStatement.parameterContextFor(PropertyStatement.java:187)
    at app//com.pholser.junit.quickcheck.runner.PropertyStatement.lambda$evaluate$0(PropertyStatement.java:93)
    at java.base@19.0.2/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
    at java.base@19.0.2/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:1006)
    at java.base@19.0.2/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
    at java.base@19.0.2/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
    at java.base@19.0.2/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
    at java.base@19.0.2/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.base@19.0.2/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682)
    at app//com.pholser.junit.quickcheck.runner.PropertyStatement.evaluate(PropertyStatement.java:101)
    at app//org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
    at app//org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
    at app//org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
    at app//org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
    at app//org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
    at app//org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
    at app//org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
    at app//org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
    at app//org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
    at app//org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at app//org.junit.runners.ParentRunner.run(ParentRunner.java:413)
    at app//org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at app//org.junit.runner.JUnitCore.run(JUnitCore.java:115)
    at app//org.junit.vintage.engine.execution.RunnerExecutor.execute(RunnerExecutor.java:42)
    at app//org.junit.vintage.engine.VintageTestEngine.executeAllChildren(VintageTestEngine.java:80)
    at app//org.junit.vintage.engine.VintageTestEngine.execute(VintageTestEngine.java:72)
    at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:147)
    at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:127)
    at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:90)
    at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:55)
    at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:102)
    at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:54)
    at app//org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
    at app//org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
    at app//org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
    at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:110)
    at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:90)
    at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:85)
    at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:62)
    at java.base@19.0.2/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
    at java.base@19.0.2/java.lang.reflect.Method.invoke(Method.java:578)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
    at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
    at jdk.proxy1/jdk.proxy1.$Proxy2.stop(Unknown Source)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker$3.run(TestWorker.java:193)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60)
    at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
    at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:113)
    at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:65)
    at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
    at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
Wrap lines 

Where counter is

package com.tejasoft.utils;

import java.io.Serializable;
import java.util.concurrent.atomic.AtomicInteger;

import static com.tejasoft.utils.GenConsts.EMPTY_STRING;
import static com.tejasoft.utils.GenConsts.ZERO_INT;
import static com.tejasoft.utils.StringHelper.normalize;

public final class Counter implements Serializable
{
    private final AtomicInteger counter = new AtomicInteger();

    private final String name;

    public Counter(final String aName)
    {
    this(aName, ZERO_INT);
    }

    public Counter(final String aName, int aInitialValue)
    {
    name = normalize(aName);
    counter.set(aInitialValue);
    }

    public final String getName()
    {
    return name;
    }

    public final Counter increment()
    {
    counter.getAndIncrement();
    return this;
    }

    public final int getCount()
    {
    return counter.get();
    }

    @Override
    public final String toString()
    {
    int ckt = getCount();
    return (ckt > 0) ? "Total '" + name + "' Count: " + ckt : EMPTY_STRING;
    }
}

and test case is

package com.tejasoft.utils.tests.ju.ju4.ut.propt.jqt;

import com.pholser.junit.quickcheck.From;
import com.pholser.junit.quickcheck.Property;
import com.pholser.junit.quickcheck.generator.Fields;
import com.pholser.junit.quickcheck.runner.JUnitQuickcheck;
import com.tejasoft.utils.Counter;
import org.junit.runner.RunWith;

import static org.junit.Assert.assertEquals;

@RunWith(JUnitQuickcheck.class)
public final class TestJUQCounter
{
    @Property
    public final void incrementing(@From(Fields.class) final Counter aCounter)
    {
    int count = aCounter.getCount();
    assertEquals(count + 1, aCounter.increment().getCount());
    }
}
nagkumar commented 1 year ago

Is AtomicInteger integer supported..

nagkumar commented 1 year ago

Further guess work revealed that it is unable to create a counter class with there is no zero-argument constructor that is public.

    public Counter()
    {
    this("");
    }

I added this to counter method, then test case worked..

Need for public zero argument is a serious limitation of this framework also, its engine is still junit 4 not upgraded to junit 5..

pholser commented 1 year ago

@nagkumar Thanks for this. Without some help and a serious increase in free time, It's unlikely that I'm going to revamp junit-quickcheck for JUnit 5. See https://github.com/pholser/junit-quickcheck/issues/139.

Without a zero-arg constructor on Counter, how would you propose to tell the Fields generator how to instantiate the Counter class? Perhaps you might use @From(Ctor.class) instead of @From(Fields.class).

AtomicInteger and kin from java.util.concurrent are not supported out of the box. To have default generators in junit-quickcheck for them sounds like a reasonable request. In the meantime, you can create generators for these types on your own, and make them available for junit-quickcheck to discover. See https://pholser.github.io/junit-quickcheck/site/1.0/usage/other-types.html for help in doing this.

Would you mind to create a separate issue requesting support for java.util.concurrent.Atomic{Integer,Boolean,Long}?

nagkumar commented 1 year ago

Perhaps you might use @From(Ctor.class) instead of @From(Fields.class).

It ends up with this error message..

X incrementing

  com.pholser.junit.quickcheck.internal.ReflectionException: com.pholser.junit.quickcheck.internal.ReflectionException: class com.tejasoft.utils.Counter needs a single accessible constructor

image

Would you mind to create a separate issue requesting support forjava.util.concurrent.Atomic{Integer,Boolean,Long}?

When I add a default empty constructor in the Counter class even an atomic integer works...

pholser commented 1 year ago

@nagkumar If you use Fields, there needs to be an accessible zero-arg constructor on the class representing the annotated parameter.

If you use Ctor, you need a single accessible constructor.

If you create a custom generator for your class, it can instantiate your class in whatever manner it pleases.

What change(s) to junit-quickcheck are you requesting?

nagkumar commented 1 year ago

What change(s) to junit-quickcheck are you requesting?

If you use Ctor, you need a single accessible constructor.

I have multiple constructors, can I not specify which one to use

pholser commented 1 year ago

What change(s) to junit-quickcheck are you requesting?

If you use Ctor, you need a single accessible constructor.

I have multiple constructors, can I not specify which one to use

No, you cannot. Your best bet is to make a generator for your class, and make junit-quickcheck aware of it. junit-quickcheck comes with a number of generators that you can use as a model.