Skip to content

Commit

Permalink
Cleans up the sealed types recursion fix
Browse files Browse the repository at this point in the history
  • Loading branch information
jqno committed Feb 23, 2024
1 parent f5a42d2 commit 548fdef
Show file tree
Hide file tree
Showing 11 changed files with 132 additions and 62 deletions.
Original file line number Diff line number Diff line change
@@ -1,27 +1,22 @@
package nl.jqno.equalsverifier.internal.reflection;

import static nl.jqno.equalsverifier.internal.prefabvalues.factories.Factories.values;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.*;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import nl.jqno.equalsverifier.internal.exceptions.EqualsVerifierInternalBugException;
import nl.jqno.equalsverifier.internal.prefabvalues.FactoryCache;
import nl.jqno.equalsverifier.internal.prefabvalues.JavaApiPrefabValues;
import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues;
import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag;
import nl.jqno.equalsverifier.internal.prefabvalues.*;
import nl.jqno.equalsverifier.internal.testhelpers.ExpectedException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

public class RecordObjectAccessorScramblingTest {

private static final LinkedHashSet<TypeTag> EMPTY_TYPE_STACK = new LinkedHashSet<>();
private FactoryCache factoryCache;
private PrefabValues prefabValues;

Expand Down Expand Up @@ -137,7 +132,7 @@ private Object fieldValue(ObjectAccessor<?> accessor, String fieldName)
}

private ObjectAccessor<Object> doScramble(Object object) {
return create(object).scramble(prefabValues, TypeTag.NULL);
return create(object).scramble(prefabValues, TypeTag.NULL, EMPTY_TYPE_STACK);
}

record Point(int x, int y) {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.LinkedHashSet;
import java.util.Objects;
import nl.jqno.equalsverifier.internal.exceptions.ReflectionException;
import nl.jqno.equalsverifier.internal.prefabvalues.JavaApiPrefabValues;
Expand All @@ -17,6 +18,7 @@

public class RecordObjectAccessorTest {

private static final LinkedHashSet<TypeTag> EMPTY_TYPE_STACK = new LinkedHashSet<>();
private Object recordInstance;

@BeforeEach
Expand Down Expand Up @@ -72,7 +74,7 @@ public void fail_whenConstructorThrowsOnSomethingElse() {

PrefabValues pv = new PrefabValues(JavaApiPrefabValues.build());
ExpectedException
.when(() -> accessorFor(instance).scramble(pv, TypeTag.NULL))
.when(() -> accessorFor(instance).scramble(pv, TypeTag.NULL, EMPTY_TYPE_STACK))
.assertThrows(ReflectionException.class)
.assertMessageContains("Record:", "failed to run constructor", "prefab values");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,62 +2,77 @@

import java.util.Objects;
import nl.jqno.equalsverifier.EqualsVerifier;
import nl.jqno.equalsverifier.Warning;
import nl.jqno.equalsverifier.internal.testhelpers.ExpectedException;
import org.junit.jupiter.api.Test;

class SealedTypesRecursionTest {

@Test
public void testEV() {
EqualsVerifier
.forClass(A.class)
.suppress(Warning.STRICT_INHERITANCE, Warning.NONFINAL_FIELDS)
.verify();
// A container with a field of a sealed interface.
// The sealed interface permits only 1 type, which refers back to the container.
ExpectedException
.when(() -> EqualsVerifier.forClass(SealedContainer.class).verify())
.assertFailure()
.assertMessageContains(
"Recursive datastructure",
"Add prefab values for one of the following types",
"SealedContainer",
"SealedInterface"
);
}

class A {
static final class SealedContainer {

public I sealedClassField;
public final SealedInterface sealed;

public SealedContainer(SealedInterface sealed) {
this.sealed = sealed;
}

@Override
public int hashCode() {
return Objects.hash(sealedClassField);
return Objects.hash(sealed);
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof A)) {
if (!(obj instanceof SealedContainer)) {
return false;
}
A other = (A) obj;
return Objects.equals(sealedClassField, other.sealedClassField);
SealedContainer other = (SealedContainer) obj;
return Objects.equals(sealed, other.sealed);
}
}

public sealed interface I permits E {}
sealed interface SealedInterface permits OnlyPermittedImplementation {}

public final class E implements I {
static final class OnlyPermittedImplementation implements SealedInterface {

public A referenceToA;
public final SealedContainer container;

public OnlyPermittedImplementation(SealedContainer container) {
this.container = container;
}

@Override
public int hashCode() {
return Objects.hash(referenceToA);
return Objects.hash(container);
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof E)) {
if (!(obj instanceof OnlyPermittedImplementation)) {
return false;
}
E other = (E) obj;
return Objects.equals(referenceToA, other.referenceToA);
OnlyPermittedImplementation other = (OnlyPermittedImplementation) obj;
return Objects.equals(container, other.container);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ public <T> Tuple<T> giveTuple(TypeTag tag) {
return giveTuple(tag, new LinkedHashSet<>());
}

/**
* Returns a tuple of two different prefabricated values of the specified type.
*
* @param <T> The returned tuple will have this generic type.
* @param tag A description of the desired type, including generic parameters.
* @param typeStack Keeps track of recursion in the type.
* @return A tuple of two different values of the given type.
*/
public <T> Tuple<T> giveTuple(TypeTag tag, LinkedHashSet<TypeTag> typeStack) {
realizeCacheFor(tag, typeStack);
return cache.getTuple(tag);
Expand All @@ -99,6 +107,16 @@ public <T> T giveOther(TypeTag tag, T value) {
return giveOther(tag, value, new LinkedHashSet<>());
}

/**
* Returns a prefabricated value of the specified type, that is different from the specified
* value.
*
* @param <T> The type of the value.
* @param tag A description of the desired type, including generic parameters.
* @param value A value that is different from the value that will be returned.
* @param typeStack Keeps track of recursion in the type.
* @return A value that is different from {@code value}.
*/
public <T> T giveOther(TypeTag tag, T value, LinkedHashSet<TypeTag> typeStack) {
Class<T> type = tag.getType();
if (
Expand Down Expand Up @@ -131,10 +149,6 @@ private boolean arraysAreDeeplyEqual(Object x, Object y) {
return Arrays.deepEquals(new Object[] { x }, new Object[] { y });
}

private LinkedHashSet<TypeTag> emptyStack() {
return new LinkedHashSet<>();
}

/**
* Makes sure that values for the specified type are present in the cache, but doesn't return
* them.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ private Tuple<T> giveInstances(
) {
ClassAccessor<T> accessor = ClassAccessor.of(tag.getType(), prefabValues);
T red = accessor.getRedObject(tag, typeStack);
T blue = accessor.getBlueObject(tag);
T redCopy = accessor.getRedObject(tag);
T blue = accessor.getBlueObject(tag, typeStack);
T redCopy = accessor.getRedObject(tag, typeStack);
return new Tuple<>(red, blue, redCopy);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,15 @@ public T getRedObject(TypeTag enclosingType) {
return getRedAccessor(enclosingType).get();
}

/**
* Returns an instance of T that is not equal to the instance of T returned by {@link
* #getBlueObject(TypeTag)}.
*
* @param enclosingType Describes the type that contains this object as a field, to determine
* any generic parameters it may contain.
* @param typeStack Keeps track of recursion in the type.
* @return An instance of T.
*/
public T getRedObject(TypeTag enclosingType, LinkedHashSet<TypeTag> typeStack) {
return getRedAccessor(enclosingType, typeStack).get();
}
Expand All @@ -201,6 +210,14 @@ public ObjectAccessor<T> getRedAccessor(TypeTag enclosingType) {
return getRedAccessor(enclosingType, new LinkedHashSet<>());
}

/**
* Returns an {@link ObjectAccessor} for {@link #getRedObject(TypeTag)}.
*
* @param enclosingType Describes the type that contains this object as a field, to determine
* any generic parameters it may contain.
* @param typeStack Keeps track of recursion in the type.
* @return An {@link ObjectAccessor} for {@link #getRedObject(TypeTag)}.
*/
public ObjectAccessor<T> getRedAccessor(
TypeTag enclosingType,
LinkedHashSet<TypeTag> typeStack
Expand All @@ -220,6 +237,19 @@ public T getBlueObject(TypeTag enclosingType) {
return getBlueAccessor(enclosingType).get();
}

/**
* Returns an instance of T that is not equal to the instance of T returned by {@link
* #getRedObject(TypeTag)}.
*
* @param enclosingType Describes the type that contains this object as a field, to determine
* any generic parameters it may contain.
* @param typeStack Keeps track of recursion in the type.
* @return An instance of T.
*/
public T getBlueObject(TypeTag enclosingType, LinkedHashSet<TypeTag> typeStack) {
return getBlueAccessor(enclosingType, typeStack).get();
}

/**
* Returns an {@link ObjectAccessor} for {@link #getBlueObject(TypeTag)}.
*
Expand All @@ -228,9 +258,24 @@ public T getBlueObject(TypeTag enclosingType) {
* @return An {@link ObjectAccessor} for {@link #getBlueObject(TypeTag)}.
*/
public ObjectAccessor<T> getBlueAccessor(TypeTag enclosingType) {
return getBlueAccessor(enclosingType, new LinkedHashSet<>());
}

/**
* Returns an {@link ObjectAccessor} for {@link #getBlueObject(TypeTag)}.
*
* @param enclosingType Describes the type that contains this object as a field, to determine
* any generic parameters it may contain.
* @param typeStack Keeps track of recursion in the type.
* @return An {@link ObjectAccessor} for {@link #getBlueObject(TypeTag)}.
*/
public ObjectAccessor<T> getBlueAccessor(
TypeTag enclosingType,
LinkedHashSet<TypeTag> typeStack
) {
return buildObjectAccessor()
.scramble(prefabValues, enclosingType)
.scramble(prefabValues, enclosingType);
.scramble(prefabValues, enclosingType, typeStack)
.scramble(prefabValues, enclosingType, typeStack);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import java.lang.reflect.Field;
import java.util.LinkedHashSet;

import nl.jqno.equalsverifier.internal.exceptions.ReflectionException;
import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues;
import nl.jqno.equalsverifier.internal.prefabvalues.TypeTag;
Expand Down Expand Up @@ -97,7 +96,24 @@ public void changeField(PrefabValues prefabValues, TypeTag enclosingType) {
changeField(prefabValues, enclosingType, new LinkedHashSet<>());
}

public void changeField(PrefabValues prefabValues, TypeTag enclosingType, LinkedHashSet<TypeTag> typeStack) {
/**
* Changes the field's value to something else. The new value will never be null. Other than
* that, the precise value is undefined.
*
* <p>Ignores static fields and fields that can't be modified reflectively.
*
* @param prefabValues If the field is of a type contained within prefabValues, the new value
* will be taken from it.
* @param enclosingType A tag for the type that contains the field. Needed to determine a
* generic type, if it has one..
* @param typeStack Keeps track of recursion in the type.
* @throws ReflectionException If the operation fails.
*/
public void changeField(
PrefabValues prefabValues,
TypeTag enclosingType,
LinkedHashSet<TypeTag> typeStack
) {
FieldChanger fm = () -> {
TypeTag tag = TypeTag.of(field, enclosingType);
Object newValue = prefabValues.giveOther(tag, field.get(object), typeStack);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,6 @@ private <S> S copyInto(S copy) {
return copy;
}

/** {@inheritDoc} */
@Override
public ObjectAccessor<T> scramble(PrefabValues prefabValues, TypeTag enclosingType) {
return scramble(prefabValues, enclosingType, new LinkedHashSet<>());
}

/** {@inheritDoc} */
@Override
public ObjectAccessor<T> scramble(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,9 @@ public T getField(Field field) {
* @param prefabValues Prefabricated values to take values from.
* @param enclosingType Describes the type that contains this object as a field, to determine
* any generic parameters it may contain.
* @param typeStack Keeps track of recursion in the type.
* @return An accessor to the scrambled object.
*/
public abstract ObjectAccessor<T> scramble(PrefabValues prefabValues, TypeTag enclosingType);

public abstract ObjectAccessor<T> scramble(
PrefabValues prefabValues,
TypeTag enclosingType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,6 @@ public T copyIntoAnonymousSubclass() {
);
}

/** {@inheritDoc} */
@Override
public ObjectAccessor<T> scramble(PrefabValues prefabValues, TypeTag enclosingType) {
return scramble(prefabValues, enclosingType, new LinkedHashSet<>());
}

/** {@inheritDoc} */
@Override
public ObjectAccessor<T> scramble(
Expand Down
Loading

0 comments on commit 548fdef

Please sign in to comment.