This repository has been archived by the owner on Aug 24, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add Duration, Instant and LocalTime generators (#181)
Thanks to @alvaroReina
- Loading branch information
1 parent
fd12d4d
commit 9843a8f
Showing
2 changed files
with
339 additions
and
0 deletions.
There are no files selected for viewing
117 changes: 117 additions & 0 deletions
117
generator/stdlib/src/jvmMain/kotlin/com/github/jcornaz/kwik/generator/stdlib/DateTime.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
package com.github.jcornaz.kwik.generator.stdlib | ||
|
||
import com.github.jcornaz.kwik.generator.api.Generator | ||
import com.github.jcornaz.kwik.generator.api.withSamples | ||
import java.time.Duration | ||
import java.time.Instant | ||
import java.time.LocalTime | ||
import java.time.temporal.ChronoField | ||
|
||
private const val MAX_NANOSECONDS = 999_999_999 | ||
private val MIN_DURATION = Duration.ofSeconds(Long.MIN_VALUE) | ||
private val MAX_DURATION = Duration.ofSeconds(Long.MAX_VALUE, MAX_NANOSECONDS.toLong()) | ||
|
||
/** | ||
* Returns a generator of [Instant] between [min] and [max] (inclusive) | ||
*/ | ||
fun Generator.Companion.instants( | ||
min: Instant = Instant.MIN, | ||
max: Instant = Instant.MAX | ||
): Generator<Instant> { | ||
require(max >= min) { | ||
"Max must be equal or after min but min was $min and max was $max" | ||
} | ||
|
||
val range = min..max | ||
|
||
val samples = mutableListOf(min, max) | ||
|
||
if (Instant.EPOCH in range && Instant.EPOCH !in samples) { | ||
samples += Instant.EPOCH | ||
} | ||
|
||
return create { random -> | ||
val seconds = | ||
if (min.epochSecond == max.epochSecond) min.epochSecond | ||
else random.nextLong(from = min.epochSecond, until = max.epochSecond) | ||
|
||
val instant = Instant.ofEpochSecond(seconds) | ||
|
||
val minNano = | ||
if (instant.epochSecond == min.epochSecond) min.nano | ||
else 0 | ||
|
||
val maxNano = | ||
if (instant.epochSecond == instant.epochSecond) max.nano | ||
else MAX_NANOSECONDS | ||
|
||
instant.with( | ||
ChronoField.NANO_OF_SECOND, | ||
random.nextLong(from = minNano.toLong(), until = maxNano.toLong() + 1) | ||
) | ||
}.withSamples(samples) | ||
} | ||
|
||
/** | ||
* Returns a generator of [Duration] between [min] and [max] (inclusive) | ||
*/ | ||
fun Generator.Companion.durations( | ||
min: Duration = MIN_DURATION, | ||
max: Duration = MAX_DURATION | ||
): Generator<Duration> { | ||
require(min <= max) { | ||
"Min must be shorter than max but min was $min and max was $max" | ||
} | ||
|
||
val range = min..max | ||
|
||
val samples = mutableListOf(min, max) | ||
|
||
if (Duration.ZERO in range && Duration.ZERO !in samples) { | ||
samples += Duration.ZERO | ||
} | ||
|
||
return create { random -> | ||
val seconds = | ||
if (min.seconds == max.seconds) min.seconds | ||
else random.nextLong(from = min.seconds, until = max.seconds) | ||
|
||
val duration = Duration.ofSeconds(seconds) | ||
|
||
val minNano = | ||
if (duration.seconds == min.seconds) min.nano | ||
else 0 | ||
|
||
val maxNano = | ||
if (duration.seconds == max.seconds) max.nano | ||
else MAX_NANOSECONDS | ||
|
||
duration.withNanos(random.nextInt(from = minNano, until = maxNano + 1)) | ||
}.withSamples(samples) | ||
} | ||
|
||
|
||
/** | ||
* Returns a generator of [LocalTime] between [min] and [max] (inclusive) | ||
*/ | ||
|
||
fun Generator.Companion.localTimes( | ||
min: LocalTime = LocalTime.MIN, | ||
max: LocalTime = LocalTime.MAX | ||
): Generator<LocalTime> { | ||
require(max >= min) { | ||
"Max must be equal or after min but min was $min and max was $max" | ||
} | ||
|
||
val range = min..max | ||
|
||
val samples = mutableListOf(min, max) | ||
|
||
if (LocalTime.NOON in range && LocalTime.NOON !in samples) { | ||
samples += LocalTime.NOON | ||
} | ||
|
||
return create { random -> | ||
LocalTime.ofNanoOfDay(random.nextLong(min.toNanoOfDay(), max.toNanoOfDay())) | ||
}.withSamples(samples) | ||
} |
222 changes: 222 additions & 0 deletions
222
...dlib/src/jvmTest/kotlin/com/github/jcornaz/kwik/generator/stdlib/DateTimeGeneratorTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,222 @@ | ||
package com.github.jcornaz.kwik.generator.stdlib | ||
|
||
import com.github.jcornaz.kwik.generator.api.Generator | ||
import com.github.jcornaz.kwik.generator.api.randomSequence | ||
import com.github.jcornaz.kwik.generator.test.AbstractGeneratorTest | ||
import java.time.Duration | ||
import java.time.Instant | ||
import java.time.LocalTime | ||
import kotlin.test.Test | ||
import kotlin.test.assertFailsWith | ||
import kotlin.test.assertTrue | ||
|
||
private const val MAX_NANOSECONDS = 999_999_999L | ||
|
||
class DurationGeneratorTest : AbstractGeneratorTest() { | ||
override val generator: Generator<Duration> = Generator.durations(Duration.ZERO, Duration.ofDays(100)) | ||
|
||
@Test | ||
fun failForInvalidRange() { | ||
assertFailsWith<IllegalArgumentException> { | ||
Generator.durations(Duration.ofSeconds(1), Duration.ZERO) | ||
} | ||
} | ||
|
||
@Test | ||
fun produceInsideGivenRange() { | ||
val min = Duration.ofMinutes(-1) | ||
val max = Duration.ofMinutes(1) | ||
assertTrue(Generator.durations(min = min, max = max).randomSequence(0).take(50).all { it >= min && it <= max }) | ||
} | ||
|
||
@Test | ||
fun produceInsideGivenNanosecondsRange() { | ||
val min = Duration.ofSeconds(0, 500) | ||
val max = Duration.ofSeconds(0, 1000) | ||
assertTrue(Generator.durations(min = min, max = max).randomSequence(0).take(50).all { it >= min && it <= max }) | ||
} | ||
|
||
@Test | ||
fun doNotProduceZeroIfNotInRange() { | ||
val min = Duration.ofMinutes(1) | ||
assertTrue(Generator.durations(min = min).randomSequence(0).take(50).none { it == Duration.ZERO }) | ||
} | ||
|
||
@Test | ||
fun doNotProduceGlobalMaxIfNotInRange() { | ||
val max = Duration.ZERO | ||
assertTrue(Generator.durations(max = max).randomSequence(0).take(50).none { it == Duration.ofSeconds(Long.MAX_VALUE, MAX_NANOSECONDS) }) | ||
} | ||
|
||
@Test | ||
fun doNotProduceGlobalMinIfNotInRange() { | ||
val min = Duration.ZERO | ||
assertTrue(Generator.durations(min = min).randomSequence(0).take(50).none { it == Duration.ofSeconds(Long.MIN_VALUE) }) | ||
} | ||
|
||
@Test | ||
fun generateZero() { | ||
assertTrue(Generator.durations().randomSequence(0).take(50).any { it == Duration.ZERO }) | ||
} | ||
|
||
@Test | ||
fun generateGlobalMax() { | ||
assertTrue(Generator.durations().randomSequence(0).take(50).any { it == Duration.ofSeconds(Long.MAX_VALUE, MAX_NANOSECONDS) }) | ||
} | ||
|
||
@Test | ||
fun generateGlobalMin() { | ||
assertTrue(Generator.durations().randomSequence(0).take(50).any { it == Duration.ofSeconds(Long.MIN_VALUE) }) | ||
} | ||
|
||
@Test | ||
fun generateMax() { | ||
val max = Duration.ofSeconds(1) | ||
assertTrue(Generator.durations(max = max).randomSequence(0).take(50).any { it == max }) | ||
} | ||
|
||
@Test | ||
fun generateMin() { | ||
val min = Duration.ofSeconds(1) | ||
assertTrue(Generator.durations(min = min).randomSequence(0).take(50).any { it == min }) | ||
} | ||
} | ||
|
||
class InstantGeneratorTest : AbstractGeneratorTest() { | ||
override val generator: Generator<Instant> = Generator.instants(Instant.EPOCH, Instant.now()) | ||
|
||
@Test | ||
fun failForInvalidRange() { | ||
assertFailsWith<IllegalArgumentException> { | ||
Generator.instants(Instant.now(), Instant.EPOCH) | ||
} | ||
} | ||
|
||
@Test | ||
fun produceInsideGivenRange() { | ||
val min = Instant.now() | ||
val max = min.plusSeconds(50) | ||
assertTrue(Generator.instants(min = min, max = max).randomSequence(0).take(50).all { it >= min && it <= max }) | ||
} | ||
|
||
@Test | ||
fun produceInsideGivenNanosecondsRange() { | ||
val min = Instant.EPOCH | ||
val max = min.plusNanos(500) | ||
assertTrue(Generator.instants(min = min, max = max).randomSequence(0).take(50).all { it >= min && it <= max }) | ||
} | ||
|
||
@Test | ||
fun doNotProduceEpochIfNotInRange() { | ||
val min = Instant.now() | ||
val max = min.plusSeconds(50) | ||
assertTrue(Generator.instants(min = min, max = max).randomSequence(0).take(50).none { it == Instant.EPOCH }) | ||
} | ||
|
||
@Test | ||
fun doNotProduceFarPastIfNotInRange() { | ||
val min = Instant.now() | ||
val max = min.plusSeconds(50) | ||
assertTrue(Generator.instants(min = min, max = max).randomSequence(0).take(50).none { it == Instant.MIN }) | ||
} | ||
|
||
@Test | ||
fun doNotProduceFarFutureIfNotInRange() { | ||
val min = Instant.now() | ||
val max = min.plusSeconds(50) | ||
assertTrue(Generator.instants(min = min, max = max).randomSequence(0).take(50).none { it == Instant.MAX }) | ||
} | ||
|
||
@Test | ||
fun generateEpoch() { | ||
assertTrue(Generator.instants().randomSequence(0).take(50).any { it == Instant.EPOCH }) | ||
} | ||
|
||
@Test | ||
fun generateFarPast() { | ||
assertTrue(Generator.instants().randomSequence(0).take(50).any { it == Instant.MIN }) | ||
} | ||
|
||
@Test | ||
fun generateFarFuture() { | ||
assertTrue(Generator.instants().randomSequence(0).take(50).any { it == Instant.MAX }) | ||
} | ||
|
||
@Test | ||
fun generateMax() { | ||
val min = Instant.now() | ||
val max = min.plusSeconds(50) | ||
assertTrue(Generator.instants(min = min, max = max).randomSequence(0).take(50).any { it == max }) | ||
} | ||
|
||
@Test | ||
fun generateMin() { | ||
val min = Instant.now() | ||
val max = min.plusSeconds(50) | ||
assertTrue(Generator.instants(min = min, max = max).randomSequence(0).take(50).any { it == min }) | ||
} | ||
} | ||
|
||
class LocalTimeGeneratorTest : AbstractGeneratorTest() { | ||
override val generator: Generator<LocalTime> = Generator.localTimes() | ||
|
||
@Test | ||
fun failForInvalidRange() { | ||
assertFailsWith<IllegalArgumentException> { | ||
Generator.localTimes(LocalTime.MAX, LocalTime.MIN) | ||
} | ||
} | ||
|
||
@Test | ||
fun produceInsideGivenRange() { | ||
val min = LocalTime.NOON | ||
val max = min.plusHours(2) | ||
assertTrue(Generator.localTimes(min = min, max = max).randomSequence(0).take(50).all { it >= min && it <= max }) | ||
} | ||
|
||
@Test | ||
fun doNotProduceNoonIfNotInRange() { | ||
val min = LocalTime.NOON.plusHours(2) | ||
assertTrue(Generator.localTimes(min = min).randomSequence(0).take(50).none { it == LocalTime.NOON }) | ||
} | ||
|
||
@Test | ||
fun doNotProduceGlobalMaxIfNotInRange() { | ||
val max = LocalTime.MAX.minusHours(1) | ||
assertTrue(Generator.localTimes(max = max).randomSequence(0).take(50).none { it == LocalTime.MAX }) | ||
} | ||
|
||
@Test | ||
fun doNotProduceGlobalMinIfNotInRange() { | ||
val min = LocalTime.MIN.plusHours(1) | ||
assertTrue(Generator.localTimes(min = min).randomSequence(0).take(50).none { it == LocalTime.MIN }) | ||
} | ||
|
||
@Test | ||
fun generateNoon() { | ||
assertTrue(Generator.localTimes().randomSequence(0).take(50).any { it == LocalTime.NOON }) | ||
} | ||
|
||
@Test | ||
fun generateGlobalMax() { | ||
assertTrue(Generator.localTimes().randomSequence(0).take(50).any { it == LocalTime.MAX }) | ||
} | ||
|
||
|
||
@Test | ||
fun generateGlobalMin() { | ||
assertTrue(Generator.localTimes().randomSequence(0).take(50).any { it == LocalTime.MIN }) | ||
} | ||
|
||
@Test | ||
fun generateMax() { | ||
val max = LocalTime.NOON.plusHours(1) | ||
assertTrue(Generator.localTimes(max = max).randomSequence(0).take(50).any { it == max }) | ||
} | ||
|
||
@Test | ||
fun generateMin() { | ||
val min = LocalTime.NOON.plusHours(1) | ||
assertTrue(Generator.localTimes(min = min).randomSequence(0).take(50).any { it == min }) | ||
} | ||
} |