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

Generic field type issue (composition) #441

Open
seregamorph opened this issue Nov 9, 2020 · 7 comments
Open

Generic field type issue (composition) #441

seregamorph opened this issue Nov 9, 2020 · 7 comments
Labels
Milestone

Comments

@seregamorph
Copy link
Contributor

seregamorph commented Nov 9, 2020

In addition to #440 there is one more issue related to generic type support.
Consider the test:

import static org.assertj.core.api.Assertions.assertThat;

import java.io.Serializable;
import org.junit.jupiter.api.Test;

public class Generic2Test {

    @Test
    void genericComposedShouldBeCorrectlyPopulated() {
        // given
        EasyRandom easyRandom = new EasyRandom();

        // when
        CompositeResource composite = easyRandom.nextObject(CompositeResource.class);

        // then
        assertThat(composite.longResource.getId())
                .isInstanceOf(Long.class);
    }

    static abstract class IdResource<K extends Serializable, T extends IdResource<K, ?>> {

        private K id;

        @SuppressWarnings("unchecked")
        public T setId(K id) {
            this.id = id;
            return (T) this;
        }

        public K getId() {
            return id;
        }
    }

    static class LongResource extends IdResource<Long, LongResource> {
    }

    static class CompositeResource {
        private LongResource longResource;
    }
}

Instead of generating correct beans it fails with NPE:

org.jeasy.random.ObjectCreationException: Unable to create a random instance of type class org.jeasy.random.Generic2Test$CompositeResource

	at org.jeasy.random.EasyRandom.doPopulateBean(EasyRandom.java:172)
	at org.jeasy.random.EasyRandom.nextObject(EasyRandom.java:100)
	at org.jeasy.random.Generic2Test.genericInheritedShouldBeCorrectlyPopulated(Generic2Test.java:16)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:688)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
	at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:210)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:206)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:131)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:65)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:75)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:71)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
Caused by: org.jeasy.random.ObjectCreationException: Unable to create type: org.jeasy.random.Generic2Test$LongResource for field: longResource of class: org.jeasy.random.Generic2Test$CompositeResource
	at org.jeasy.random.FieldPopulator.populateField(FieldPopulator.java:98)
	at org.jeasy.random.EasyRandom.populateField(EasyRandom.java:209)
	at org.jeasy.random.EasyRandom.populateFields(EasyRandom.java:198)
	at org.jeasy.random.EasyRandom.doPopulateBean(EasyRandom.java:165)
	... 67 more
Caused by: org.jeasy.random.ObjectCreationException: Unable to create a random instance of type class org.jeasy.random.Generic2Test$LongResource
	at org.jeasy.random.EasyRandom.doPopulateBean(EasyRandom.java:172)
	at org.jeasy.random.FieldPopulator.generateRandomValue(FieldPopulator.java:160)
	at org.jeasy.random.FieldPopulator.populateField(FieldPopulator.java:93)
	... 70 more
Caused by: java.lang.NullPointerException
	at org.jeasy.random.FieldPopulator.getParametrizedType(FieldPopulator.java:170)
	at org.jeasy.random.FieldPopulator.getRandomizer(FieldPopulator.java:123)
	at org.jeasy.random.FieldPopulator.populateField(FieldPopulator.java:79)
	at org.jeasy.random.EasyRandom.populateField(EasyRandom.java:209)
	at org.jeasy.random.EasyRandom.populateFields(EasyRandom.java:198)
	at org.jeasy.random.EasyRandom.doPopulateBean(EasyRandom.java:165)
	... 72 more
@fmbenhassine
Copy link
Member

Every new feature comes with new bugs 😄 ER should clearly not fail with a NPE. Thank you for reporting this.

@fmbenhassine fmbenhassine added this to the 5.0.1 milestone Nov 15, 2020
@reitzmichnicht
Copy link
Contributor

I have spottet the same and added a simple test here:
#450
But did not found a solution yet

@seregamorph
Copy link
Contributor Author

@reitzmichnicht you can find a raw solutions here:
master...seregamorph:generic-base-class-classmate - uses com.fasterxml.classmate (jackson also has this dependency) to resolve generic type
Alternative impl:
master...seregamorph:generic-base-class-spring-core-no-spring - it is based on the spring-core code, the spring code copied and cleaned up (all unused lines), alternatively spring-core dependency can be used.

These branches were rejected in this project, but you still can find a solution here.

@fmbenhassine
Copy link
Member

@reitzmichnicht I'm adding your failing test here since #450 was closed.

@Test
void testGenericFieldRandomization() {
    // given
    class Base<T> {
        T t;
    }
    class Concrete {
        Base<String> f;
    }

    // when
    Concrete concrete = easyRandom.nextObject(Concrete.class);

    // then
    assertThat(concrete.f).isInstanceOf(Base.class);
}

This fails with the same stacktrace as above.

@fmbenhassine
Copy link
Member

@seregamorph

These branches were rejected in this project, but you still can find a solution here.

Thank you for pointing out these solutions. I just wanted to give a bit of context here about the reasons of rejecting those options.

master...seregamorph:generic-base-class-classmate - uses com.fasterxml.classmate (jackson also has this dependency) to resolve generic type

My concern with those changes is that they are based on #426, which was rejected for the reasons explained in details here: #426 (comment). While your changes are different from those in #426, the new GenericType is what concerning me the most, because java-classmate already defines its own GenericType. That said, I'm not against adding java-classmate as a dependency if it makes our lives easier regarding generic types introspection, and I'm open for contributions to fix this issue in a way that is not based on #426 and which remains relatively easy in order to control complexity.

master...seregamorph:generic-base-class-spring-core-no-spring - it is based on the spring-core code, the spring code copied and cleaned up (all unused lines), alternatively spring-core dependency can be used.

The reasons why this has been rejected were detailed here: #425 (comment).

@mjureczko
Copy link
Contributor

I submitted a fix for this issue: #466.

There are a number of challenges around this issue (e.g. issue #440) that my fix does not address and the project is in maintenance mode. Thus, @beans can I ask you to look at it ahead and give me a shout if it has a chance for being accepted and if so, when it can be released?

dvgaba referenced this issue in dvgaba/easy-random Aug 3, 2022
#441 test suggested in the issue and a fix
@struberg
Copy link

struberg commented Mar 5, 2024

Did hit the same issue today while upgrading a project.
Afaiu the problem is that the RandomizationContext is constructed with the CompositeResource. And from there all the parameterized information is taken. But this is completely wrong if a field/member has nothing to do with the parameterization hierarchy of that class but has it's own one like with LongResource.

The ParameterizedType of LongResource is defined as IdResource<Long, LongResource> and is completely independent of whatever generic information is in CompositeResource or if it doesn't have any at all, isn't?

I locally have a working hack (needs further polishing) where I create a fresh independent 'sub-RandomizationContext' in case I hit a field which has nothing to do with the generic hierarchy of the current class. Not quite sure though if this is the right way to approach it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants