diff --git a/CHANGELOG.md b/CHANGELOG.md index 9efa64cef..8aeb50a7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,8 +15,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- The error message in some edge cases involving complex generics and abstract classes is now improved. ([Issue 983](https://github.com/jqno/equalsverifier/issues/983)) - The line in the error message that shows the version of EqualsVerifier and the JDK, now also indicates whether EqualsVerifier runs on the classpath or the modulepath. +### Deprecated + +- `withResetCaches()` was once needed for use in Quarkus, but caches are now reset automatically on every run. + ## [3.16.1] - 2024-04-03 ### Fixed diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/ConfiguredEqualsVerifier.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/ConfiguredEqualsVerifier.java index f6f5743e3..c9fba2ab4 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/ConfiguredEqualsVerifier.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/ConfiguredEqualsVerifier.java @@ -12,7 +12,6 @@ import nl.jqno.equalsverifier.internal.prefabvalues.FactoryCache; import nl.jqno.equalsverifier.internal.reflection.PackageScanner; import nl.jqno.equalsverifier.internal.util.ListBuilders; -import nl.jqno.equalsverifier.internal.util.ObjenesisWrapper; import nl.jqno.equalsverifier.internal.util.PrefabValuesApi; import nl.jqno.equalsverifier.internal.util.Validations; @@ -104,10 +103,13 @@ public ConfiguredEqualsVerifier withFieldnameToGetterConverter( return this; } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + * @deprecated No longer needed; this happens automatically. + */ + @Deprecated @Override public ConfiguredEqualsVerifier withResetCaches() { - ObjenesisWrapper.reset(); return this; } diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/api/EqualsVerifierApi.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/api/EqualsVerifierApi.java index fbbc5e1e4..fbb3e9491 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/api/EqualsVerifierApi.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/api/EqualsVerifierApi.java @@ -90,6 +90,8 @@ public interface EqualsVerifierApi { * that would normally be equal, to be unequal, because their ClassLoaders don't match. * * @return {@code this}, for easy method chaining. + * @deprecated No longer needed; this happens automatically. */ + @Deprecated EqualsVerifierApi withResetCaches(); } diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/api/MultipleTypeEqualsVerifierApi.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/api/MultipleTypeEqualsVerifierApi.java index b178689e7..ecb285619 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/api/MultipleTypeEqualsVerifierApi.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/api/MultipleTypeEqualsVerifierApi.java @@ -14,7 +14,6 @@ import nl.jqno.equalsverifier.internal.util.ErrorMessage; import nl.jqno.equalsverifier.internal.util.Formatter; import nl.jqno.equalsverifier.internal.util.ListBuilders; -import nl.jqno.equalsverifier.internal.util.ObjenesisWrapper; import nl.jqno.equalsverifier.internal.util.Validations; /** @@ -80,10 +79,13 @@ public MultipleTypeEqualsVerifierApi withFieldnameToGetterConverter( return this; } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + * @deprecated No longer needed; this happens automatically. + */ + @Deprecated @Override public MultipleTypeEqualsVerifierApi withResetCaches() { - ObjenesisWrapper.reset(); return this; } diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/api/SingleTypeEqualsVerifierApi.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/api/SingleTypeEqualsVerifierApi.java index bb64d54c1..4bde026d1 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/api/SingleTypeEqualsVerifierApi.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/api/SingleTypeEqualsVerifierApi.java @@ -328,10 +328,13 @@ public SingleTypeEqualsVerifierApi withLombokCachedHashCode(T example) { return this; } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + * @deprecated No longer needed; this happens automatically. + */ + @Deprecated @Override public SingleTypeEqualsVerifierApi withResetCaches() { - ObjenesisWrapper.reset(); return this; } @@ -402,6 +405,7 @@ private String buildErrorMessage(String description, boolean showUrl) { } private void performVerification() { + ObjenesisWrapper.reset(); if (type.isEnum() || type.isInterface()) { return; } diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/FieldModifier.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/FieldModifier.java index 51ba705c9..8dd428481 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/FieldModifier.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/FieldModifier.java @@ -139,9 +139,13 @@ private void wrappedChange(FieldChanger changer) throws IllegalAccessException { try { changer.change(); } catch (IllegalArgumentException e) { - if (e.getMessage().startsWith("Can not set")) { + String msg = e.getMessage(); + if (msg.startsWith("Can not set") || msg.startsWith("Can not get")) { throw new ReflectionException( - "Reflection error: perhaps a ClassLoader problem?\nTry re-running with #withResetCaches()", + "Reflection error: try adding a prefab value for field " + + field.getName() + + " of type " + + field.getType().getName(), e ); } else { diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/integration/extended_contract/GenericTypesTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/integration/extended_contract/GenericTypesTest.java index 2a28834e5..822567528 100644 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/integration/extended_contract/GenericTypesTest.java +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/integration/extended_contract/GenericTypesTest.java @@ -8,6 +8,8 @@ import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Supplier; import nl.jqno.equalsverifier.EqualsVerifier; +import nl.jqno.equalsverifier.Warning; +import nl.jqno.equalsverifier.internal.testhelpers.ExpectedException; import nl.jqno.equalsverifier.testhelpers.types.Point; import org.junit.jupiter.api.Test; @@ -93,6 +95,32 @@ public void succeed_whenClassHasTypeVariableThatExtendsSomethingThatSupersSometh EqualsVerifier.forClass(TypeVariableExtendsWithSuperContainer.class).verify(); } + @Test + public void failGracefully_whenClassHasASelfReferenceGenericParameter() { + ExpectedException + .when(() -> EqualsVerifier.forClass(SelfReferringGenericType.class).verify()) + .assertFailure() + .assertMessageContains( + "Reflection error", + "try adding a prefab value", + "field wrapped", + "of type " + SelfReferringGenericType.class.getName() + ); + } + + @Test + public void succeed_whenClassHasASelfReferenceGenericParameter_givenPrefabValues() { + EqualsVerifier + .forClass(SelfReferringGenericType.class) + .withPrefabValues( + SelfReferringGenericType.class, + new SelfReferringGenericType<>(1), + new SelfReferringGenericType<>(2) + ) + .suppress(Warning.NONFINAL_FIELDS) + .verify(); + } + static final class JavaGenericTypeContainer { private final Optional optional; @@ -704,4 +732,31 @@ public int hashCode() { return defaultHashCode(this); } } + + public static class SelfReferringGenericType> { + + private T wrapped; + + // Everything below is boilerplate to be able to run the tests; the bit above this comment is what matters + + private final int i; + + public SelfReferringGenericType(int i) { + this.i = i; + } + + @Override + public final boolean equals(Object obj) { + if (!(obj instanceof SelfReferringGenericType)) { + return false; + } + SelfReferringGenericType other = (SelfReferringGenericType) obj; + return i == other.i && Objects.equals(wrapped, other.wrapped); + } + + @Override + public final int hashCode() { + return Objects.hash(i, wrapped); + } + } } diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/integration/operational/WithResetCachesTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/integration/operational/WithResetCachesTest.java index 82b331cfe..8803c0a40 100644 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/integration/operational/WithResetCachesTest.java +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/integration/operational/WithResetCachesTest.java @@ -5,32 +5,15 @@ import nl.jqno.equalsverifier.EqualsVerifier; import nl.jqno.equalsverifier.internal.util.ObjenesisWrapper; import nl.jqno.equalsverifier.testhelpers.packages.correct.A; -import nl.jqno.equalsverifier.testhelpers.packages.correct.B; import org.junit.jupiter.api.Test; import org.objenesis.Objenesis; public class WithResetCachesTest { @Test - public void single() { + public void resetObjenesisCacheOnEachRun() { Objenesis original = ObjenesisWrapper.getObjenesis(); - EqualsVerifier.forClass(A.class).withResetCaches(); - Objenesis reset = ObjenesisWrapper.getObjenesis(); - assertNotEquals(original, reset); - } - - @Test - public void multiple() { - Objenesis original = ObjenesisWrapper.getObjenesis(); - EqualsVerifier.forClasses(A.class, B.class).withResetCaches(); - Objenesis reset = ObjenesisWrapper.getObjenesis(); - assertNotEquals(original, reset); - } - - @Test - public void configured() { - Objenesis original = ObjenesisWrapper.getObjenesis(); - EqualsVerifier.configure().withResetCaches(); + EqualsVerifier.forClass(A.class).verify(); Objenesis reset = ObjenesisWrapper.getObjenesis(); assertNotEquals(original, reset); } diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/FieldModifierTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/FieldModifierTest.java index 1574eafd0..544b73ad3 100644 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/FieldModifierTest.java +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/FieldModifierTest.java @@ -7,11 +7,9 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.lang.reflect.Field; -import nl.jqno.equalsverifier.internal.exceptions.ReflectionException; import nl.jqno.equalsverifier.internal.prefabvalues.FactoryCache; import nl.jqno.equalsverifier.internal.prefabvalues.JavaApiPrefabValues; import nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues; @@ -333,20 +331,6 @@ public void addPrefabArrayValues() { assertEquals(RED_NEW_POINT, foo.points[0]); } - @Test - public void shouldDetectClassloaderIssue() throws Exception { - // We're faking the problem by using two entirely different classes. - // In reality, this problem comes up with the same types, but loaded by different class loaders, - // which makes them effectively different types as well. This was hard to fake in a test. - Object foo = new ObjectContainer(); - Field field = PrimitiveContainer.class.getField("field"); - FieldModifier fm = FieldModifier.of(field, foo); - - ReflectionException e = assertThrows(ReflectionException.class, () -> fm.set(new Object())); - - assertTrue(e.getMessage().contains("perhaps a ClassLoader problem?")); - } - private void setField(Object object, String fieldName, Object value) { getAccessorFor(object, fieldName).set(value); }