diff --git a/docs/generators.rst b/docs/generators.rst index fa914ed4..7cb7c00b 100644 --- a/docs/generators.rst +++ b/docs/generators.rst @@ -134,3 +134,17 @@ Java ``Generator.uuids()`` Create a generator for UUID + +Java Time API +------------- +``Generator.instants(min: Instant.MIN, max: Instant.MAX)`` + Generates Instants (with nano seconds), includes the samples: ``Instant.EPOCH`` (1970-01-01T00:00:00.000Z), ``min`` and ``max`` + +``Generator.durations(min, max)`` + Generates Durations (with nano seconds), Includes the samples: ``Duration.ZERO``, ``min`` and ``max`` + +``Generator.localTimes(min: LocalTime.MIN, max: LocalTime.MAX)`` + Generates LocalTimes (with nano seconds), Includes the samples: ``LocalTime.NOON``, ``min`` and ``max`` + +``Generator.localDates(min: LocalDate.MIN, max: LocalDate.MAX)`` + Generates LocalDates, includes the samples: ``LocalDate.EPOCH`` (1970-01-01), ``min`` and ``max`` diff --git a/generator/stdlib/src/jvmMain/kotlin/com/github/jcornaz/kwik/generator/stdlib/DateTime.kt b/generator/stdlib/src/jvmMain/kotlin/com/github/jcornaz/kwik/generator/stdlib/DateTime.kt index 6e0dc8d9..f92cd67d 100644 --- a/generator/stdlib/src/jvmMain/kotlin/com/github/jcornaz/kwik/generator/stdlib/DateTime.kt +++ b/generator/stdlib/src/jvmMain/kotlin/com/github/jcornaz/kwik/generator/stdlib/DateTime.kt @@ -4,6 +4,7 @@ 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.LocalDate import java.time.LocalTime import java.time.temporal.ChronoField import kotlin.random.Random @@ -11,6 +12,8 @@ import kotlin.random.Random 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()) +private val EPOCH: LocalDate = LocalDate.ofEpochDay(0) // can be replaced by LocalDate.EPOCH with Java 9 + /** * Returns a generator of [Instant] between [min] and [max] (inclusive) @@ -19,9 +22,7 @@ fun Generator.Companion.instants( min: Instant = Instant.MIN, max: Instant = Instant.MAX ): Generator { - require(max >= min) { - "Max must be equal or after min but min was $min and max was $max" - } + requireMaxEqualOrHigherThanMin(max, min) val range = min..max @@ -100,9 +101,7 @@ fun Generator.Companion.localTimes( min: LocalTime = LocalTime.MIN, max: LocalTime = LocalTime.MAX ): Generator { - require(max >= min) { - "Max must be equal or after min but min was $min and max was $max" - } + requireMaxEqualOrHigherThanMin(max, min) val range = min..max @@ -116,3 +115,31 @@ fun Generator.Companion.localTimes( LocalTime.ofNanoOfDay(random.nextLong(min.toNanoOfDay(), max.toNanoOfDay())) }.withSamples(samples) } + +/** + * Returns a generator of [LocalDate] between [min] and [max] (inclusive) + */ +fun Generator.Companion.localDates( + min: LocalDate = LocalDate.MIN, + max: LocalDate = LocalDate.MAX +): Generator { + requireMaxEqualOrHigherThanMin(max, min) + + val range = min..max + + val samples = mutableListOf(min, max) + + if (EPOCH in range && EPOCH !in samples) { + samples += EPOCH + } + + return Generator { random: Random -> + LocalDate.ofEpochDay(random.nextLong(min.toEpochDay(), max.toEpochDay())) + }.withSamples(samples) +} + +private fun requireMaxEqualOrHigherThanMin(max: Comparable, min: T) { + require(max >= min) { + "Max must be equal or after min but min was $min and max was $max" + } +} diff --git a/generator/stdlib/src/jvmTest/kotlin/com/github/jcornaz/kwik/generator/stdlib/DateTimeGeneratorTest.kt b/generator/stdlib/src/jvmTest/kotlin/com/github/jcornaz/kwik/generator/stdlib/DateTimeGeneratorTest.kt index e2e75540..ae4f3f1f 100644 --- a/generator/stdlib/src/jvmTest/kotlin/com/github/jcornaz/kwik/generator/stdlib/DateTimeGeneratorTest.kt +++ b/generator/stdlib/src/jvmTest/kotlin/com/github/jcornaz/kwik/generator/stdlib/DateTimeGeneratorTest.kt @@ -5,12 +5,15 @@ 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.LocalDate import java.time.LocalTime import kotlin.test.Test import kotlin.test.assertFailsWith import kotlin.test.assertTrue private const val MAX_NANOSECONDS = 999_999_999L +// can be replaced by LocalDate.EPOCH with Java 9 +private val EPOCH: LocalDate = LocalDate.ofEpochDay(0) class DurationGeneratorTest : AbstractGeneratorTest() { override val generator: Generator = Generator.durations(Duration.ZERO, Duration.ofDays(100)) @@ -220,3 +223,69 @@ class LocalTimeGeneratorTest : AbstractGeneratorTest() { assertTrue(Generator.localTimes(min = min).randomSequence(0).take(50).any { it == min }) } } + + +class LocalDateGeneratorTest : AbstractGeneratorTest() { + + override val generator: Generator = Generator.localDates() + + @Test + fun `fail for invalid range`() { + assertFailsWith { + Generator.localDates(LocalDate.MAX, LocalDate.MIN) + } + } + + @Test + fun `produce inside given range`() { + val min = EPOCH + val max = min.plusWeeks(2) + assertTrue(Generator.localDates(min = min, max = max).randomSequence(0).take(50).all { it >= min && it <= max }) + } + + @Test + fun `do not produce EPOCH if not in range`() { + val min = EPOCH.plusDays(2) + val max = EPOCH.plusWeeks(2) + assertTrue(Generator.localDates(min = min, max = max).randomSequence(0).take(50).none { it == EPOCH }) + } + + @Test + fun `do not produce global max if not in range`() { + val max = LocalDate.MAX.minusDays(1) + assertTrue(Generator.localDates(max = max).randomSequence(0).take(50).none { it == LocalDate.MAX }) + } + + @Test + fun `do not produce global min if not in range`() { + val min = LocalDate.MIN.plusDays(1) + assertTrue(Generator.localDates(min = min).randomSequence(0).take(50).none { it == LocalDate.MIN }) + } + + @Test + fun `generate epoch`() { + assertTrue(Generator.localDates().randomSequence(0).take(50).any { it == EPOCH }) + } + + @Test + fun `generate global max`() { + assertTrue(Generator.localDates().randomSequence(0).take(50).any { it == LocalDate.MAX }) + } + + @Test + fun `generate global min`() { + assertTrue(Generator.localDates().randomSequence(0).take(50).any { it == LocalDate.MIN }) + } + + @Test + fun `generate max`() { + val max = EPOCH.plusDays(1) + assertTrue(Generator.localDates(max = max).randomSequence(0).take(50).any { it == max }) + } + + @Test + fun `generate min`() { + val min = EPOCH.plusDays(1) + assertTrue(Generator.localDates(min = min).randomSequence(0).take(50).any { it == min }) + } +}