-
-
Notifications
You must be signed in to change notification settings - Fork 64
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
Add anyForType() dedicated to sealed classes and interfaces in Kotlin module: anyForSubtypeOf() #555
Add anyForType() dedicated to sealed classes and interfaces in Kotlin module: anyForSubtypeOf() #555
Changes from all commits
5085c3d
de21029
4c0da39
4955a77
6d8eb8f
5c8edc9
e599a36
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package net.jqwik.kotlin.api | ||
|
||
import net.jqwik.api.Arbitrary | ||
import kotlin.reflect.KClass | ||
|
||
/** | ||
* All sealed subclasses, recursively. | ||
*/ | ||
val <T : Any> KClass<T>.allSealedSubclasses: List<KClass<out T>> | ||
get() = sealedSubclasses.flatMap { | ||
if (it.isSealed) { | ||
it.allSealedSubclasses | ||
} else { | ||
listOf(it) | ||
} | ||
} | ||
|
||
class SubtypeScope<T> { | ||
val customProviders = mutableListOf<CustomProvider<T>>() | ||
|
||
/** | ||
* Registers a custom provider for subtype [U], instead of default one created by [anyForSubtypeOf]. | ||
*/ | ||
inline fun <reified U> provide(noinline customProvider: () -> Arbitrary<U>) where U : T { | ||
customProviders.add(CustomProvider(U::class as KClass<Any>, customProvider) as CustomProvider<T>) | ||
} | ||
|
||
/** | ||
* @return custom provider registered with [provide], or null. | ||
*/ | ||
fun getProviderFor(targetType: KClass<*>) = | ||
customProviders.firstOrNull { it.targetType == targetType }?.arbitraryFactory?.invoke() | ||
|
||
class CustomProvider<T>( | ||
val targetType: KClass<Any>, | ||
val arbitraryFactory: () -> Arbitrary<T> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -106,6 +106,37 @@ inline fun <reified T> anyForType(): TypeArbitrary<T> | |
return Arbitraries.forType(T::class.java) | ||
} | ||
|
||
|
||
/** | ||
* Creates [Arbitrary] with subtypes of a sealed class or interface [T]. | ||
* If a subtype is a sealed class or interface, its subtypes are used to create [Arbitrary]. This is done recursively. | ||
* [TypeArbitrary] are created by default under the hood, but this can be customized, for each subtype, with [SubtypeScope.provide] . | ||
* ```kotlin | ||
* anyForSubtypeOf<MyInterface> { | ||
* provide<MyImplementation1> { customArbitrary1() } | ||
* provide<MyImplementation2> { customArbitrary2() } | ||
* } | ||
* ``` | ||
* @param enableArbitraryRecursion is applied to all created [TypeArbitrary]. | ||
*/ | ||
@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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be possible to have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would imply that custom arbitrary (registered with |
||
val scope = SubtypeScope<T>().apply(subtypeScope) | ||
return Arbitraries.of(T::class.allSealedSubclasses).flatMap { | ||
scope.getProviderFor(it) | ||
?: Arbitraries.forType(it.java as Class<T>).run { | ||
if (enableArbitraryRecursion) { | ||
enableRecursion() | ||
} else { | ||
this | ||
} | ||
} | ||
}.map { obj -> obj as T } | ||
} | ||
|
||
/** | ||
* Function to create arbitrary that generates one of the provided values with a given frequency. | ||
* | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add api annotation:
@API(status = API.Status.EXPERIMENTAL, since = "1.8.4")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done