-
Notifications
You must be signed in to change notification settings - Fork 0
Add FloatStrategy to control generated Float value range #87
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
Changes from all commits
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,28 @@ | ||
| package dev.appoutlet.some.config | ||
|
|
||
| /** | ||
| * Strategy for generating float values. | ||
| * | ||
| * @property range The range within which float values will be generated (default 0.0f..1.0f). | ||
| */ | ||
| data class FloatStrategy( | ||
| val range: ClosedFloatingPointRange<Float> = 0.0f..1.0f | ||
| ) : Strategy { | ||
| override val key = FloatStrategy::class | ||
|
|
||
| init { | ||
| require(range.start <= range.endInclusive) { "range.start must be less than or equal to range.endInclusive" } | ||
| } | ||
|
|
||
| /** | ||
| * Convenience constructor for a fixed float value. | ||
| */ | ||
| constructor(fixed: Float) : this(fixed..fixed) | ||
|
|
||
| companion object { | ||
| /** | ||
| * The default float strategy. | ||
| */ | ||
| val default: FloatStrategy get() = FloatStrategy() | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -55,7 +55,7 @@ import kotlin.reflect.KClass | |||||||||||
| * @param propertyFactories Custom property factories keyed by class and property name. | ||||||||||||
| */ | ||||||||||||
| data class SomeConfig( | ||||||||||||
| val strategies: Map<KClass<out Strategy>, Strategy> = emptyMap(), | ||||||||||||
| val strategies: Map<KClass<out Strategy>, Strategy> = defaultStrategies(), | ||||||||||||
|
Comment on lines
57
to
+58
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. By changing the default value of Since all resolvers (including the new
Suggested change
|
||||||||||||
| val seed: Long? = null, | ||||||||||||
| val typeFactories: Map<KClass<*>, FixtureContext.() -> Any?> = emptyMap(), | ||||||||||||
| val propertyFactories: Map<Pair<KClass<*>, String>, FixtureContext.() -> Any?> = emptyMap(), | ||||||||||||
|
|
@@ -116,7 +116,7 @@ data class SomeConfig( | |||||||||||
| IntResolver(random), | ||||||||||||
| LongResolver(random), | ||||||||||||
| DoubleResolver(random), | ||||||||||||
| FloatResolver(random), | ||||||||||||
| FloatResolver(strategyProvider, random), | ||||||||||||
| BooleanResolver(random), | ||||||||||||
| CharResolver(random), | ||||||||||||
| ByteResolver(random), | ||||||||||||
|
|
@@ -194,4 +194,17 @@ data class SomeConfig( | |||||||||||
| * @return [Random] seeded with [seed] if set, or [Random.Default] otherwise. | ||||||||||||
| */ | ||||||||||||
| internal fun buildRandom(): Random = seed?.let { Random(it) } ?: Random.Default | ||||||||||||
|
|
||||||||||||
| companion object { | ||||||||||||
| /** | ||||||||||||
| * Returns the default strategies for fixture generation. | ||||||||||||
| */ | ||||||||||||
| fun defaultStrategies(): Map<KClass<out Strategy>, Strategy> = mapOf( | ||||||||||||
| NullableStrategy::class to NullableStrategy.default, | ||||||||||||
| StringStrategy::class to StringStrategy.default, | ||||||||||||
| CollectionStrategy::class to CollectionStrategy.default, | ||||||||||||
| FloatStrategy::class to FloatStrategy.default, | ||||||||||||
| DefaultValueStrategy::class to DefaultValueStrategy.default, | ||||||||||||
| ) | ||||||||||||
| } | ||||||||||||
|
Comment on lines
+198
to
+209
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. |
||||||||||||
| } | ||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,32 @@ | ||
| package dev.appoutlet.some.resolver | ||
|
|
||
| import dev.appoutlet.some.config.FloatStrategy | ||
| import dev.appoutlet.some.core.ResolverChain | ||
| import dev.appoutlet.some.core.StrategyProvider | ||
| import dev.appoutlet.some.core.TypeResolver | ||
| import dev.appoutlet.some.core.get | ||
| import kotlin.random.Random | ||
| import kotlin.reflect.KType | ||
| import kotlin.reflect.typeOf | ||
|
|
||
| class FloatResolver(val random: Random) : TypeResolver { | ||
| /** | ||
| * Resolves [Float] types using the active [FloatStrategy]. | ||
| * | ||
| * @param strategyProvider Provider of all configured generation strategies. | ||
| * @param random Random source used for generating float values. | ||
| */ | ||
| class FloatResolver( | ||
| strategyProvider: StrategyProvider, | ||
| private val random: Random | ||
| ) : TypeResolver { | ||
| private val floatStrategy = strategyProvider.get<FloatStrategy>() ?: FloatStrategy.default | ||
|
|
||
| override fun canResolve(type: KType): Boolean = type == typeOf<Float>() | ||
|
|
||
| override fun resolve(type: KType, chain: ResolverChain): Any { | ||
| return random.nextFloat() | ||
| val range = floatStrategy.range | ||
| if (range.start == range.endInclusive) return range.start | ||
|
|
||
| return random.nextDouble(range.start.toDouble(), range.endInclusive.toDouble()).toFloat() | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| package dev.appoutlet.some.config | ||
|
|
||
| import kotlin.test.Test | ||
| import kotlin.test.assertEquals | ||
| import kotlin.test.assertFailsWith | ||
|
|
||
| class FloatStrategyTest { | ||
| @Test | ||
| fun `default strategy uses range 0 to 1`() { | ||
| val strategy = FloatStrategy.default | ||
| assertEquals(0.0f..1.0f, strategy.range) | ||
| } | ||
|
|
||
| @Test | ||
| fun `secondary constructor pins value`() { | ||
| val strategy = FloatStrategy(5.0f) | ||
| assertEquals(5.0f..5.0f, strategy.range) | ||
| } | ||
|
|
||
| @Test | ||
| fun `validation rejects inverted range`() { | ||
| assertFailsWith<IllegalArgumentException> { | ||
| FloatStrategy(5.0f..2.0f) | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| fun `zero-width range is allowed`() { | ||
| val strategy = FloatStrategy(2.0f..2.0f) | ||
| assertEquals(2.0f..2.0f, strategy.range) | ||
| } | ||
| } |
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.
It is a good practice to ensure that the range bounds are finite (
Float.isFinite()). If a user passesFloat.NEGATIVE_INFINITYorFloat.POSITIVE_INFINITY, generating a random value inFloatResolverusingRandom.nextDoublewill fail or produce unexpected results (likeNaNorInfinity) because uniform sampling over an infinite range is not supported.