Skip to content
This repository has been archived by the owner on Aug 24, 2021. It is now read-only.

Commit

Permalink
feat: Add Duration, Instant and LocalTime generators (#181)
Browse files Browse the repository at this point in the history
Thanks to @alvaroReina
  • Loading branch information
alvaroReina authored Aug 4, 2020
1 parent fd12d4d commit 9843a8f
Show file tree
Hide file tree
Showing 2 changed files with 339 additions and 0 deletions.
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)
}
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 })
}
}

0 comments on commit 9843a8f

Please sign in to comment.