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

[Bug] withPrefabValuesForField() does not resolve "Recursive datastructure" #1014

Closed
Vankog opened this issue Nov 5, 2024 · 2 comments
Closed

Comments

@Vankog
Copy link
Contributor

Vankog commented Nov 5, 2024

Describe the bug

Resolving a "Recursive datastructure" via withPrefabValuesForField() does not work.

Steps to reproduce

  1. Build a POJO with a field holding a List of itselves.
  2. Run EqualsVerifier
    • complains about "Recursive datastructure" as expected ☑️
  3. Provide Prefab values via withPrefabValues(List.class, red, blue)
    • recursion is fixed as expected ☑️
  4. Provide Prefab values via withPrefabValuesForField("listField", red, blue)
    • still complains about "Recursive datastructure" ❌

Error message and version number

EqualsVerifier 3.17.2
JDK 8 Temurin

java.lang.AssertionError: EqualsVerifier found a problem in class com.foobar.DummyClass.
-> Recursive datastructure.
Add prefab values for one of the following types: List<DummyClass>, DummyClass.

For more information, go to: https://www.jqno.nl/equalsverifier/errormessages
(EqualsVerifier 3.17.2, JDK 1.8.0_322 running on classpath, on Windows 10)
	at nl.jqno.equalsverifier.api.SingleTypeEqualsVerifierApi.verify(SingleTypeEqualsVerifierApi.java:382)
	at com.foobar.DummyClassTest.testEqualsContractWithPrefabForField(DummyClassTest.java:38)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at java.util.ArrayList.forEach(ArrayList.java:1259)
	at java.util.ArrayList.forEach(ArrayList.java:1259)
Caused by: nl.jqno.equalsverifier.internal.exceptions.RecursionException
	at nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider.createTuple(VintageValueProvider.java:174)
	at nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider.realizeCacheFor(VintageValueProvider.java:157)
	at nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.FallbackFactory.traverseFields(FallbackFactory.java:95)
	at nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.FallbackFactory.createValues(FallbackFactory.java:46)
	at nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider.createTuple(VintageValueProvider.java:189)
	at nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider.realizeCacheFor(VintageValueProvider.java:157)
	at nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.AbstractGenericFactory.determineAndCacheActualTypeTag(AbstractGenericFactory.java:56)
	at nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.AbstractGenericFactory.determineAndCacheActualTypeTag(AbstractGenericFactory.java:42)
	at nl.jqno.equalsverifier.internal.reflection.vintage.prefabvalues.factories.SimpleGenericFactory.createValues(SimpleGenericFactory.java:36)
	at nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider.createTuple(VintageValueProvider.java:185)
	at nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider.realizeCacheFor(VintageValueProvider.java:157)
	at nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider.giveTuple(VintageValueProvider.java:168)
	at nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider.giveTuple(VintageValueProvider.java:163)
	at nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider.lambda$provide$0(VintageValueProvider.java:53)
	at nl.jqno.equalsverifier.internal.util.Rethrow.rethrow(Rethrow.java:35)
	at nl.jqno.equalsverifier.internal.util.Rethrow.rethrow(Rethrow.java:53)
	at nl.jqno.equalsverifier.internal.reflection.instantiation.VintageValueProvider.provide(VintageValueProvider.java:53)
	at nl.jqno.equalsverifier.internal.reflection.instantiation.ChainedValueProvider.lambda$provide$0(ChainedValueProvider.java:52)
	at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
	at java.util.ArrayList$ArrayListSpliterator.tryAdvance(ArrayList.java:1361)
	at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126)
	at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:499)
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:486)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
	at java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:152)
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:531)
	at nl.jqno.equalsverifier.internal.reflection.instantiation.ChainedValueProvider.provide(ChainedValueProvider.java:54)
	at nl.jqno.equalsverifier.internal.reflection.instantiation.SubjectCreator.valuesFor(SubjectCreator.java:242)
	at nl.jqno.equalsverifier.internal.reflection.instantiation.SubjectCreator.determineValues(SubjectCreator.java:213)
	at nl.jqno.equalsverifier.internal.reflection.instantiation.SubjectCreator.createInstance(SubjectCreator.java:202)
	at nl.jqno.equalsverifier.internal.reflection.instantiation.SubjectCreator.plain(SubjectCreator.java:58)
	at nl.jqno.equalsverifier.internal.checkers.AbstractDelegationChecker.check(AbstractDelegationChecker.java:40)
	at nl.jqno.equalsverifier.api.SingleTypeEqualsVerifierApi.verifyWithoutExamples(SingleTypeEqualsVerifierApi.java:493)
	at nl.jqno.equalsverifier.api.SingleTypeEqualsVerifierApi.performVerification(SingleTypeEqualsVerifierApi.java:458)
	at nl.jqno.equalsverifier.api.SingleTypeEqualsVerifierApi.verify(SingleTypeEqualsVerifierApi.java:380)
	... 4 more

Code: EqualsVerifier invocation

package com.foobar;

import java.util.Collections;
import java.util.List;

import org.junit.jupiter.api.Test;

import nl.jqno.equalsverifier.EqualsVerifier;
import nl.jqno.equalsverifier.Warning;

class DummyClassTest {

    @Test
    void testEqualsContractWithClassicPrefab() {

        DummyClass subItem1 = new DummyClass();
        subItem1.id = "1";
        DummyClass subItem2 = new DummyClass();
        subItem2.id = "2";

        EqualsVerifier.forClass(DummyClass.class)
                        .withPrefabValues(List.class, Collections.singletonList(subItem1), Collections.singletonList(subItem2))
                        .suppress(Warning.NONFINAL_FIELDS, Warning.STRICT_INHERITANCE)
                        .verify();
    }

    @Test
    void testEqualsContractWithPrefabForField() {

        DummyClass subItem1 = new DummyClass();
        subItem1.id = "1";
        DummyClass subItem2 = new DummyClass();
        subItem2.id = "2";

        EqualsVerifier.forClass(DummyClass.class)
                        .withPrefabValuesForField("subItems", Collections.singletonList(subItem1), Collections.singletonList(subItem2))
                        .suppress(Warning.NONFINAL_FIELDS, Warning.STRICT_INHERITANCE)
                        .verify();
    }
}

Code: class under test

package com.foobar;

import java.util.List;
import java.util.Objects;

public class DummyClass {

    String id;
    List<DummyClass> subItems;

    @Override
    public int hashCode() {
        return Objects.hash(id, subItems);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof DummyClass)) {
            return false;
        }
        DummyClass other = (DummyClass)obj;
        return Objects.equals(id, other.id) && Objects.equals(subItems, other.subItems);
    }
}

Additional context

Follow-up to #1012.

This time I don't feel like I can provide a fix for that easily. ;-)

@jqno
Copy link
Owner

jqno commented Nov 5, 2024

This is a hard problem that I haven't fully solved yet. I have a branch where I'm working on a fix but it's a slow process. I'll leave this ticket open for reference until I've found a solution.

@jqno
Copy link
Owner

jqno commented Nov 7, 2024

I found out what the problem was, and have released a fix: Verduin 3.17.3.

It turns out the lookup occurred based on the actual type of the prefab values, not on the declared type of the field...

@jqno jqno closed this as completed Nov 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants