diff --git a/api/src/main/java/net/jqwik/api/Arbitrary.java b/api/src/main/java/net/jqwik/api/Arbitrary.java index 2baafceab..f82c5000f 100644 --- a/api/src/main/java/net/jqwik/api/Arbitrary.java +++ b/api/src/main/java/net/jqwik/api/Arbitrary.java @@ -1,5 +1,6 @@ package net.jqwik.api; +import java.io.*; import java.util.*; import java.util.function.*; import java.util.stream.*; diff --git a/engine/src/main/java/net/jqwik/engine/properties/arbitraries/DefaultTypeArbitrary.java b/engine/src/main/java/net/jqwik/engine/properties/arbitraries/DefaultTypeArbitrary.java index 31c93e529..1854c85ab 100644 --- a/engine/src/main/java/net/jqwik/engine/properties/arbitraries/DefaultTypeArbitrary.java +++ b/engine/src/main/java/net/jqwik/engine/properties/arbitraries/DefaultTypeArbitrary.java @@ -4,6 +4,8 @@ import java.util.*; import java.util.function.*; +import net.jqwik.api.support.*; + import org.jspecify.annotations.*; import org.junit.platform.commons.support.*; @@ -36,6 +38,25 @@ protected Arbitrary arbitrary() { return traverseArbitrary; } + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + DefaultTypeArbitrary that = (DefaultTypeArbitrary) o; + return defaultsSet == that.defaultsSet && + Objects.equals(targetType, that.targetType) && + Objects.equals(explicitCreators, that.explicitCreators) && + Objects.equals(constructorFilters, that.constructorFilters) && + Objects.equals(factoryMethodFilters, that.factoryMethodFilters); + // TODO: compare traverseArbitrary.enableRecursion as well. + // NOTE: we can't call Objects.equals(traverseArbitrary, ..) since it results in StackOverflowError due to + // traverseArbitrary -> traverser -> DefaultTypeArbitrary cycle since TypeTraverser is an inner class so it brings the loop + } + + @Override + public int hashCode() { + return HashCodeSupport.hash(targetType, explicitCreators, constructorFilters, factoryMethodFilters, defaultsSet); + } + private DefaultTypeArbitrary cloneWithoutDefaultsSet() { DefaultTypeArbitrary clone = typedClone(); if (clone.defaultsSet) { diff --git a/kotlin/src/main/kotlin/net/jqwik/kotlin/api/AnyForSubtypeOfDsl.kt b/kotlin/src/main/kotlin/net/jqwik/kotlin/api/AnyForSubtypeOfDsl.kt index 34ca145bf..f87a7d91a 100644 --- a/kotlin/src/main/kotlin/net/jqwik/kotlin/api/AnyForSubtypeOfDsl.kt +++ b/kotlin/src/main/kotlin/net/jqwik/kotlin/api/AnyForSubtypeOfDsl.kt @@ -37,4 +37,17 @@ class SubtypeScope { val targetType: KClass, val arbitraryFactory: () -> Arbitrary ) + + override fun hashCode(): Int { + return customProviders.hashCode() + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as SubtypeScope<*> + + return customProviders == other.customProviders + } } \ No newline at end of file diff --git a/kotlin/src/main/kotlin/net/jqwik/kotlin/api/JqwikGlobals.kt b/kotlin/src/main/kotlin/net/jqwik/kotlin/api/JqwikGlobals.kt index 892b8da6d..48b5717a1 100644 --- a/kotlin/src/main/kotlin/net/jqwik/kotlin/api/JqwikGlobals.kt +++ b/kotlin/src/main/kotlin/net/jqwik/kotlin/api/JqwikGlobals.kt @@ -117,10 +117,19 @@ inline fun anyForType(): TypeArbitrary @API(status = API.Status.EXPERIMENTAL, since = "1.8.4") inline fun anyForSubtypeOf( enableArbitraryRecursion: Boolean = false, - crossinline subtypeScope: SubtypeScope.() -> Unit = {} -): Arbitrary where T : Any { + noinline subtypeScope: SubtypeScope.() -> Unit = {} +): Arbitrary where T : Any { + return anyForTypes(T::class.allSealedSubclasses, enableArbitraryRecursion, subtypeScope) +} + +@API(status = API.Status.EXPERIMENTAL, since = "1.9.3") +fun anyForTypes( + types: List>, + enableArbitraryRecursion: Boolean = false, + subtypeScope: SubtypeScope.() -> Unit = {}, +): Arbitrary where T : Any { val scope = SubtypeScope().apply(subtypeScope) - return Arbitraries.of(T::class.allSealedSubclasses).flatMap { + return Arbitraries.of(types).flatMap { scope.getProviderFor(it) ?: Arbitraries.forType(it.java).run { if (enableArbitraryRecursion) { @@ -129,7 +138,7 @@ inline fun anyForSubtypeOf( this } } - }.map { obj -> obj } + } } /** diff --git a/kotlin/src/test/kotlin/net/jqwik/kotlin/AnyForSubtypeOfTests.kt b/kotlin/src/test/kotlin/net/jqwik/kotlin/AnyForSubtypeOfTests.kt index 75c503d3a..f57b7541a 100644 --- a/kotlin/src/test/kotlin/net/jqwik/kotlin/AnyForSubtypeOfTests.kt +++ b/kotlin/src/test/kotlin/net/jqwik/kotlin/AnyForSubtypeOfTests.kt @@ -10,7 +10,8 @@ import java.util.* class AnyForSubtypeOfTests { sealed interface Interface - class Implementation(val value: String) : Interface + // Make generator Arbitrary cacheable by ensuring Implementation has equals/hashCode + data class Implementation(val value: String) : Interface @Example fun `anyForSubtypeOf() returns type arbitrary for any implementations of given sealed interface`(@ForAll random: Random) {