Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/src/main/java/net/jqwik/api/Arbitrary.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package net.jqwik.api;

import java.io.*;
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.*;

Expand Down Expand Up @@ -36,6 +38,25 @@ protected Arbitrary<T> 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<T> cloneWithoutDefaultsSet() {
DefaultTypeArbitrary<T> clone = typedClone();
if (clone.defaultsSet) {
Expand Down
13 changes: 13 additions & 0 deletions kotlin/src/main/kotlin/net/jqwik/kotlin/api/AnyForSubtypeOfDsl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,17 @@ class SubtypeScope<T: Any> {
val targetType: KClass<out T>,
val arbitraryFactory: () -> Arbitrary<out T>
)

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
}
}
17 changes: 13 additions & 4 deletions kotlin/src/main/kotlin/net/jqwik/kotlin/api/JqwikGlobals.kt
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,19 @@ inline fun <reified T> anyForType(): TypeArbitrary<T>
@API(status = API.Status.EXPERIMENTAL, since = "1.8.4")
inline fun <reified T> anyForSubtypeOf(
enableArbitraryRecursion: Boolean = false,
crossinline subtypeScope: SubtypeScope<T>.() -> Unit = {}
): Arbitrary<T> where T : Any {
noinline subtypeScope: SubtypeScope<T>.() -> Unit = {}
): Arbitrary<out T> where T : Any {
return anyForTypes(T::class.allSealedSubclasses, enableArbitraryRecursion, subtypeScope)
}

@API(status = API.Status.EXPERIMENTAL, since = "1.9.3")
fun <T> anyForTypes(
types: List<KClass<out T>>,
enableArbitraryRecursion: Boolean = false,
subtypeScope: SubtypeScope<T>.() -> Unit = {},
): Arbitrary<out T> where T : Any {
val scope = SubtypeScope<T>().apply(subtypeScope)
return Arbitraries.of(T::class.allSealedSubclasses).flatMap {
return Arbitraries.of(types).flatMap {
scope.getProviderFor(it)
?: Arbitraries.forType(it.java).run {
if (enableArbitraryRecursion) {
Expand All @@ -129,7 +138,7 @@ inline fun <reified T> anyForSubtypeOf(
this
}
}
}.map { obj -> obj }
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import java.util.*
class AnyForSubtypeOfTests {

sealed interface Interface
class Implementation(val value: String) : Interface
// Make generator Arbitrary<Implementation> 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) {
Expand Down
Loading