Compare commits
7 commits
Author | SHA1 | Date | |
---|---|---|---|
56bc62c333 | |||
a74f30f912 | |||
f890d21c86 | |||
2982d729bf | |||
32a69d734b | |||
8e01e9d6c8 | |||
40fe036ce4 |
15 changed files with 527 additions and 54 deletions
|
@ -1,5 +1,5 @@
|
|||
// See: https://github.com/scalameta/scalafmt/tags for the latest tags.
|
||||
version = 3.8.1
|
||||
version = 3.8.2
|
||||
runner.dialect = scala3
|
||||
maxColumn = 80
|
||||
|
||||
|
|
|
@ -7,14 +7,18 @@ Test data generation library for Scala 3. `gs-datagen` provides _composable_
|
|||
generator definitions that can be reused across tests.
|
||||
|
||||
- [Usage](#usage)
|
||||
- [Dependency](#dependency)
|
||||
- [Imports](#imports)
|
||||
- [Examples](#examples)
|
||||
- [Example: Generate a Random User](#example-generate-a-random-user)
|
||||
- [Example: Require Input](#example-require-input)
|
||||
- [Supported Generators](#supported-generators)
|
||||
- [Donate](#donate)
|
||||
|
||||
## Usage
|
||||
|
||||
### Dependency
|
||||
|
||||
```
|
||||
object Gs {
|
||||
val Datagen: ModuleID =
|
||||
|
@ -28,7 +32,7 @@ The standard way to use `gs-datagen` is to import the entire package, which
|
|||
pulls in `Gen[A]`, `Generated[A]` and `Datagen[A, -I]`:
|
||||
|
||||
```scala
|
||||
import gs.datagen.v0.*
|
||||
import gs.datagen.v0._
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
val scala3: String = "3.4.1"
|
||||
val scala3: String = "3.7.2"
|
||||
|
||||
ThisBuild / scalaVersion := scala3
|
||||
ThisBuild / versionScheme := Some("semver-spec")
|
||||
|
@ -22,7 +22,7 @@ val sharedSettings = Seq(
|
|||
|
||||
lazy val testSettings = Seq(
|
||||
libraryDependencies ++= Seq(
|
||||
"org.scalameta" %% "munit" % "1.0.0-M12" % Test
|
||||
"org.scalameta" %% "munit" % "1.1.0" % Test
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -39,6 +39,6 @@ lazy val core = project
|
|||
.settings(name := s"${gsProjectName.value}-core-v${semVerMajor.value}")
|
||||
.settings(
|
||||
libraryDependencies ++= Seq(
|
||||
"gs" %% "gs-uuid-v0" % "0.2.3"
|
||||
"gs" %% "gs-uuid-v0" % "0.4.1"
|
||||
)
|
||||
)
|
||||
|
|
|
@ -13,6 +13,15 @@ abstract class Datagen[A, -I]:
|
|||
*/
|
||||
def generate(input: I): A
|
||||
|
||||
/** Convert this to a `Gen[A]` using the supplied input.
|
||||
*
|
||||
* @param input
|
||||
* The input that must always be applied.
|
||||
* @return
|
||||
* The new generator that does not require input.
|
||||
*/
|
||||
def toGen(input: I): Gen[A] = (_: Any) => this.generate(input)
|
||||
|
||||
def flatMap[B, I2 <: I](f: A => Datagen[B, I2]): Datagen[B, I2] =
|
||||
new Datagen.Defer[B, I2](input => f(generate(input)).generate(input))
|
||||
|
||||
|
@ -43,6 +52,16 @@ object Datagen:
|
|||
*/
|
||||
def gen(): A = generate(())
|
||||
|
||||
/** Satisfy the target generator (that requires input) using this generator
|
||||
* to provide that input.
|
||||
*
|
||||
* @param target
|
||||
* The target generator.
|
||||
* @return
|
||||
* The new input-satisfied generator.
|
||||
*/
|
||||
def satisfy[B](target: Datagen[B, A]): Gen[B] = target.toGen(gen())
|
||||
|
||||
def flatMap[B](f: A => NoInput[B]): NoInput[B] =
|
||||
new NoInput.Defer[B](() => f(generate(())).generate(()))
|
||||
|
||||
|
|
|
@ -7,11 +7,23 @@ trait Generated[A]:
|
|||
|
||||
object Generated:
|
||||
|
||||
/** Summon an instance of the [[Generated]] type class.
|
||||
*
|
||||
* @param G
|
||||
* The type class instance.
|
||||
* @return
|
||||
* The summoned instance of [[Generated]].
|
||||
*/
|
||||
def apply[A](
|
||||
using
|
||||
G: Generated[A]
|
||||
): Generated[A] = G
|
||||
|
||||
/** Implementation of [[Generated]] based on an instance of [[Gen]].
|
||||
*
|
||||
* @param gen
|
||||
* The underlying [[Gen]] used to produce values.
|
||||
*/
|
||||
final class FromGenerator[A](gen: Gen[A]) extends Generated[A]:
|
||||
override def generate(): A = gen.gen()
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package gs.datagen.v0
|
||||
|
||||
import gs.datagen.v0.generators.Alphabet
|
||||
import gs.datagen.v0.generators.GenBool
|
||||
import gs.datagen.v0.generators.GenFiniteDuration
|
||||
import gs.datagen.v0.generators.GenInteger
|
||||
import gs.datagen.v0.generators.GenList
|
||||
|
@ -9,16 +10,17 @@ import gs.datagen.v0.generators.GenLong
|
|||
import gs.datagen.v0.generators.GenMap
|
||||
import gs.datagen.v0.generators.GenOneOf
|
||||
import gs.datagen.v0.generators.GenOneOfGen
|
||||
import gs.datagen.v0.generators.GenOption
|
||||
import gs.datagen.v0.generators.GenSet
|
||||
import gs.datagen.v0.generators.GenString
|
||||
import gs.datagen.v0.generators.GenUUID
|
||||
import gs.datagen.v0.generators.MinMax
|
||||
import gs.datagen.v0.generators.Size
|
||||
import gs.uuid.v0.UUID
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.util.Random
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import scala.concurrent.duration.DAYS
|
||||
|
@ -88,11 +90,66 @@ object Gen:
|
|||
*/
|
||||
def single[A](value: A): Gen[A] = Datagen.pure(value)
|
||||
|
||||
/** Generator for a list of some [[Size]] based on a generator for the list
|
||||
* elements.
|
||||
/** Generator which produces Boolean values.
|
||||
*
|
||||
* @return
|
||||
* New generator that randomly produces true/false.
|
||||
*/
|
||||
def boolean(): Gen[Boolean] = new GenBool()
|
||||
|
||||
object option:
|
||||
|
||||
/** Generator that will randomly determine whether `Some` or `None` is
|
||||
* returned for the given underlying generator.
|
||||
*
|
||||
* @param generator
|
||||
* The generator that populates the content.
|
||||
* @return
|
||||
* The generator for the optional content.
|
||||
*/
|
||||
def apply[A](generator: Gen[A]): Gen[Option[A]] =
|
||||
GenOption[A](generator)
|
||||
|
||||
/** Generator that will randomly determine whether `Some` or `None` is
|
||||
* returned for the given underlying generator.
|
||||
*
|
||||
* @param generator
|
||||
* The generator that populates the content.
|
||||
* @return
|
||||
* The generator for the optional content.
|
||||
*/
|
||||
def random[A](generator: Gen[A]): Gen[Option[A]] =
|
||||
apply(generator)
|
||||
|
||||
/** Generator that will always return `Some`, using some underlying
|
||||
* generator.
|
||||
*
|
||||
* @param generator
|
||||
* The generator that populates the content.
|
||||
* @return
|
||||
* The generator for the optional content.
|
||||
*/
|
||||
def alwaysSome[A](generator: Gen[A]): GenOption[A] =
|
||||
GenOption.alwaysSome(generator)
|
||||
|
||||
/** Generator that will always return `None`, and therefore will never
|
||||
* actually invoke the underlying generator.
|
||||
*
|
||||
* @param generator
|
||||
* The generator that populates the content.
|
||||
* @return
|
||||
* The generator for the optional content.
|
||||
*/
|
||||
def alwaysNone[A](generator: Gen[A]): GenOption[A] =
|
||||
GenOption.alwaysNone(generator)
|
||||
|
||||
end option
|
||||
|
||||
/** Generator for a list of some [[gs.datagen.v0.generators.Size]] based on a
|
||||
* generator for the list elements.
|
||||
*
|
||||
* @param size
|
||||
* The [[Size]] of the list.
|
||||
* The [[gs.datagen.v0.generators.Size]] of the list.
|
||||
* @param gen
|
||||
* The generator for the list elements.
|
||||
*/
|
||||
|
@ -101,12 +158,41 @@ object Gen:
|
|||
gen: Gen[A]
|
||||
): Gen[List[A]] = new GenList[A](size, gen)
|
||||
|
||||
/** Generator for a set of some [[Size]] based on a generator for the set
|
||||
/** Generator for a list of some fixed size based on a generator for the list
|
||||
* elements.
|
||||
*
|
||||
* @param fixedSize
|
||||
* The [[gs.datagen.v0.generators.Size]] of the list.
|
||||
* @param gen
|
||||
* The generator for the list elements.
|
||||
*/
|
||||
def list[A](
|
||||
fixedSize: Int,
|
||||
gen: Gen[A]
|
||||
): Gen[List[A]] = new GenList[A](Size.Fixed(fixedSize), gen)
|
||||
|
||||
/** Generator for a list of some size based on a generator for the list
|
||||
* elements.
|
||||
*
|
||||
* @param minSize
|
||||
* The minimum size of the list.
|
||||
* @param maxSize
|
||||
* The maximum size of the list.
|
||||
* @param gen
|
||||
* The generator for the list elements.
|
||||
*/
|
||||
def list[A](
|
||||
minSize: Int,
|
||||
maxSize: Int,
|
||||
gen: Gen[A]
|
||||
): Gen[List[A]] = new GenList[A](Size.Between(minSize, maxSize), gen)
|
||||
|
||||
/** Generator for a set of some [[gs.datagen.v0.generators.Size]] based on a
|
||||
* generator for the set elements.
|
||||
*
|
||||
* @param size
|
||||
* The goal [[Size]] of the set. If duplicate elements are generated, the
|
||||
* size will be less then specified.
|
||||
* The goal [[gs.datagen.v0.generators.Size]] of the set. If duplicate
|
||||
* elements are generated, the size will be less then specified.
|
||||
* @param gen
|
||||
* The generator for the list elements.
|
||||
*/
|
||||
|
@ -115,6 +201,36 @@ object Gen:
|
|||
gen: Gen[A]
|
||||
): Gen[Set[A]] = new GenSet[A](size, gen)
|
||||
|
||||
/** Generator for a set of some fixed size based on a generator for the set
|
||||
* elements.
|
||||
*
|
||||
* @param fixedSize
|
||||
* The goal [[gs.datagen.v0.generators.Size]] of the set. If duplicate
|
||||
* elements are generated, the size will be less then specified.
|
||||
* @param gen
|
||||
* The generator for the list elements.
|
||||
*/
|
||||
def set[A](
|
||||
fixedSize: Int,
|
||||
gen: Gen[A]
|
||||
): Gen[Set[A]] = new GenSet[A](Size.Fixed(fixedSize), gen)
|
||||
|
||||
/** Generator for a set of some size based on a generator for the set
|
||||
* elements.
|
||||
*
|
||||
* @param minSize
|
||||
* Minimum size of the generated set.
|
||||
* @param maxSize
|
||||
* Maximum size of the generated set.
|
||||
* @param gen
|
||||
* The generator for the list elements.
|
||||
*/
|
||||
def set[A](
|
||||
minSize: Int,
|
||||
maxSize: Int,
|
||||
gen: Gen[A]
|
||||
): Gen[Set[A]] = new GenSet[A](Size.Between(minSize, maxSize), gen)
|
||||
|
||||
/** Generators which pick a single random element from a collection.
|
||||
*/
|
||||
object oneOf:
|
||||
|
@ -135,7 +251,7 @@ object Gen:
|
|||
* @param cs
|
||||
* The choices.
|
||||
*/
|
||||
def generatedChoices[A: ClassTag](cs: Gen[A]*): Gen[A] =
|
||||
def generatedChoices[A](cs: Gen[A]*): Gen[A] =
|
||||
GenOneOfGen.list(cs.toList)
|
||||
|
||||
/** Generator which randomly selects from a fixed list of choices.
|
||||
|
@ -153,7 +269,7 @@ object Gen:
|
|||
* @param choices
|
||||
* The choices.
|
||||
*/
|
||||
def generatedList[A: ClassTag](
|
||||
def generatedList[A](
|
||||
choices: List[Gen[A]]
|
||||
): Gen[A] =
|
||||
GenOneOfGen.list(choices)
|
||||
|
@ -177,7 +293,7 @@ object Gen:
|
|||
* values.
|
||||
*
|
||||
* @param size
|
||||
* The [[Size]] of the generated map.
|
||||
* The [[gs.datagen.v0.generators.Size]] of the generated map.
|
||||
* @param keyGen
|
||||
* The generator for keys.
|
||||
* @param valueGen
|
||||
|
@ -198,7 +314,7 @@ object Gen:
|
|||
* keys to values.
|
||||
*
|
||||
* @param size
|
||||
* The [[Size]] of the generated map.
|
||||
* The [[gs.datagen.v0.generators.Size]] of the generated map.
|
||||
* @param keyValueGen
|
||||
* The generator for key/value pairs.
|
||||
*/
|
||||
|
@ -217,8 +333,9 @@ object Gen:
|
|||
*/
|
||||
object string:
|
||||
|
||||
/** Generator for a string of the specified [[Size]], where the characters
|
||||
* are restricted by the given alphabet.
|
||||
/** Generator for a string of the specified
|
||||
* [[gs.datagen.v0.generators.Size]], where the characters are restricted
|
||||
* by the given alphabet.
|
||||
*
|
||||
* @param size
|
||||
* The size constraints for the generated string.
|
||||
|
@ -234,8 +351,9 @@ object Gen:
|
|||
size = size
|
||||
)
|
||||
|
||||
/** Generator for a string of the specified [[Size]], where the characters
|
||||
* are restricted to ASCII lowercase letters.
|
||||
/** Generator for a string of the specified
|
||||
* [[gs.datagen.v0.generators.Size]], where the characters are restricted
|
||||
* to ASCII lowercase letters.
|
||||
*
|
||||
* @param size
|
||||
* The size constraints for the generated string.
|
||||
|
@ -248,8 +366,9 @@ object Gen:
|
|||
size = size
|
||||
)
|
||||
|
||||
/** Generator for a string of the specified [[Size]], where the characters
|
||||
* are restricted to ASCII uppercase letters.
|
||||
/** Generator for a string of the specified
|
||||
* [[gs.datagen.v0.generators.Size]], where the characters are restricted
|
||||
* to ASCII uppercase letters.
|
||||
*
|
||||
* @param size
|
||||
* The size constraints for the generated string.
|
||||
|
@ -262,8 +381,9 @@ object Gen:
|
|||
size = size
|
||||
)
|
||||
|
||||
/** Generator for a string of the specified [[Size]], where the characters
|
||||
* are restricted to ASCII letters.
|
||||
/** Generator for a string of the specified
|
||||
* [[gs.datagen.v0.generators.Size]], where the characters are restricted
|
||||
* to ASCII letters.
|
||||
*
|
||||
* @param size
|
||||
* The size constraints for the generated string.
|
||||
|
@ -276,8 +396,9 @@ object Gen:
|
|||
size = size
|
||||
)
|
||||
|
||||
/** Generator for a string of the specified [[Size]], where the characters
|
||||
* are restricted to ASCII letters and numbers.
|
||||
/** Generator for a string of the specified
|
||||
* [[gs.datagen.v0.generators.Size]], where the characters are restricted
|
||||
* to ASCII letters and numbers.
|
||||
*
|
||||
* @param size
|
||||
* The size constraints for the generated string.
|
||||
|
@ -290,8 +411,9 @@ object Gen:
|
|||
size = size
|
||||
)
|
||||
|
||||
/** Generator for a string of the specified [[Size]], where the characters
|
||||
* are restricted to lowercase ASCII letters and numbers.
|
||||
/** Generator for a string of the specified
|
||||
* [[gs.datagen.v0.generators.Size]], where the characters are restricted
|
||||
* to lowercase ASCII letters and numbers.
|
||||
*
|
||||
* @param size
|
||||
* The size constraints for the generated string.
|
||||
|
@ -304,8 +426,9 @@ object Gen:
|
|||
size = size
|
||||
)
|
||||
|
||||
/** Generator for a string of the specified [[Size]], where the characters
|
||||
* are restricted to uppercase ASCII letters and numbers.
|
||||
/** Generator for a string of the specified
|
||||
* [[gs.datagen.v0.generators.Size]], where the characters are restricted
|
||||
* to uppercase ASCII letters and numbers.
|
||||
*
|
||||
* @param size
|
||||
* The size constraints for the generated string.
|
||||
|
@ -320,7 +443,7 @@ object Gen:
|
|||
|
||||
end string
|
||||
|
||||
/** Generators for integers.
|
||||
/** Generators for integers (32-bit integers).
|
||||
*/
|
||||
object integer:
|
||||
|
||||
|
@ -354,7 +477,7 @@ object Gen:
|
|||
|
||||
end integer
|
||||
|
||||
/** Generators for longs.
|
||||
/** Generators for longs (64-bit integers).
|
||||
*/
|
||||
object long:
|
||||
|
||||
|
@ -396,7 +519,7 @@ object Gen:
|
|||
*
|
||||
* ## Example
|
||||
*
|
||||
* The following generator produces a date before today where:
|
||||
* The following generator produces a date before the given date where:
|
||||
*
|
||||
* - The day component is subtracted by 0 to 10.
|
||||
* - The month component is subtracted by 1 to 11.
|
||||
|
@ -437,6 +560,37 @@ object Gen:
|
|||
years = years
|
||||
)
|
||||
|
||||
/** Generate a date before the current date.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* The following generator produces a date before **today** where:
|
||||
*
|
||||
* - The day component is subtracted by 0 to 10.
|
||||
* - The month component is subtracted by 1 to 11.
|
||||
* - The year component is subtracted by 0 to 30.
|
||||
*
|
||||
* The produced date will be, at most, 30 years, 11 months and 10 days
|
||||
* prior to the selected pivot. It will be at least 1 month prior to the
|
||||
* selected pivot.
|
||||
*
|
||||
* {{{
|
||||
* Gen.date.beforeToday(
|
||||
* days = MinMax.nonNegative(0, 10),
|
||||
* months = MinMax.nonNegative(1, 11),
|
||||
* years = MinMax.nonNegative(0, 30)
|
||||
* )
|
||||
* }}}
|
||||
*
|
||||
* @param date
|
||||
* The maximum date.
|
||||
* @param days
|
||||
* The range of days prior to today.
|
||||
* @param months
|
||||
* The range of months prior to today.
|
||||
* @param years
|
||||
* The range of years prior to today.
|
||||
*/
|
||||
def beforeToday(
|
||||
days: MinMax.NonNegative,
|
||||
months: MinMax.NonNegative = MinMax.Zero,
|
||||
|
@ -450,6 +604,38 @@ object Gen:
|
|||
years = years
|
||||
)
|
||||
|
||||
/** Generate a date after the given date.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* The following generator produces a date after the given date where:
|
||||
*
|
||||
* - The day component is added by 0 to 10.
|
||||
* - The month component is added by 1 to 11.
|
||||
* - The year component is added by 0 to 30.
|
||||
*
|
||||
* The produced date will be, at most, 30 years, 11 months and 10 days
|
||||
* after the selected pivot. It will be at least 1 month after the selected
|
||||
* pivot.
|
||||
*
|
||||
* {{{
|
||||
* Gen.date.after(
|
||||
* date = LocalDate.now(),
|
||||
* days = MinMax.nonNegative(0, 10),
|
||||
* months = MinMax.nonNegative(1, 11),
|
||||
* years = MinMax.nonNegative(0, 30)
|
||||
* )
|
||||
* }}}
|
||||
*
|
||||
* @param date
|
||||
* The maximum date.
|
||||
* @param days
|
||||
* The range of days after the given date.
|
||||
* @param months
|
||||
* The range of months after the given date.
|
||||
* @param years
|
||||
* The range of years after the given date.
|
||||
*/
|
||||
def after(
|
||||
date: LocalDate,
|
||||
days: MinMax.NonNegative,
|
||||
|
@ -463,6 +649,38 @@ object Gen:
|
|||
years = years
|
||||
)
|
||||
|
||||
/** Generate a date after the current date.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* The following generator produces a date after the **current** date
|
||||
* where:
|
||||
*
|
||||
* - The day component is added by 0 to 10.
|
||||
* - The month component is added by 1 to 11.
|
||||
* - The year component is added by 0 to 30.
|
||||
*
|
||||
* The produced date will be, at most, 30 years, 11 months and 10 days
|
||||
* after the selected pivot. It will be at least 1 month after the selected
|
||||
* pivot.
|
||||
*
|
||||
* {{{
|
||||
* Gen.date.afterToday(
|
||||
* days = MinMax.nonNegative(0, 10),
|
||||
* months = MinMax.nonNegative(1, 11),
|
||||
* years = MinMax.nonNegative(0, 30)
|
||||
* )
|
||||
* }}}
|
||||
*
|
||||
* @param date
|
||||
* The maximum date.
|
||||
* @param days
|
||||
* The range of days after today.
|
||||
* @param months
|
||||
* The range of months after today.
|
||||
* @param years
|
||||
* The range of years after today.
|
||||
*/
|
||||
def afterToday(
|
||||
days: MinMax.NonNegative,
|
||||
months: MinMax.NonNegative = MinMax.Zero,
|
||||
|
@ -476,26 +694,89 @@ object Gen:
|
|||
years = years
|
||||
)
|
||||
|
||||
/** Generate a date around (centered upon) the given date.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* The following generator produces a date around the given date where:
|
||||
*
|
||||
* - The day component is added or subtracted by 0 to 10.
|
||||
* - The month component is added or subtracted by 1 to 11.
|
||||
* - The year component is added or subtracted by 0 to 30.
|
||||
*
|
||||
* The produced date will be, at most, 30 years, 11 months and 10 days
|
||||
* before or after the selected pivot. It will be at least 1 month before
|
||||
* or after the selected pivot.
|
||||
*
|
||||
* {{{
|
||||
* Gen.date.around(
|
||||
* date = LocalDate.now(),
|
||||
* days = MinMax.nonNegative(0, 10),
|
||||
* months = MinMax.nonNegative(1, 11),
|
||||
* years = MinMax.nonNegative(0, 30)
|
||||
* )
|
||||
* }}}
|
||||
*
|
||||
* @param date
|
||||
* The maximum date.
|
||||
* @param days
|
||||
* The range of days before or after the given date.
|
||||
* @param months
|
||||
* The range of months before or after the given date.
|
||||
* @param years
|
||||
* The range of years before or after the given date.
|
||||
*/
|
||||
def around(
|
||||
date: LocalDate,
|
||||
days: MinMax,
|
||||
months: MinMax = MinMax.Zero,
|
||||
years: MinMax = MinMax.Zero
|
||||
): Gen[LocalDate] =
|
||||
new GenLocalDate.After(
|
||||
new GenLocalDate.Around(
|
||||
pivot = date,
|
||||
days = days,
|
||||
months = months,
|
||||
years = years
|
||||
)
|
||||
|
||||
/** Generate a date around (centered upon) today.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* The following generator produces a date around today where:
|
||||
*
|
||||
* - The day component is added or subtracted by 0 to 10.
|
||||
* - The month component is added or subtracted by 1 to 11.
|
||||
* - The year component is added or subtracted by 0 to 30.
|
||||
*
|
||||
* The produced date will be, at most, 30 years, 11 months and 10 days
|
||||
* before or after today. It will be at least 1 month before or after
|
||||
* today.
|
||||
*
|
||||
* {{{
|
||||
* Gen.date.around(
|
||||
* days = MinMax.nonNegative(0, 10),
|
||||
* months = MinMax.nonNegative(1, 11),
|
||||
* years = MinMax.nonNegative(0, 30)
|
||||
* )
|
||||
* }}}
|
||||
*
|
||||
* @param date
|
||||
* The maximum date.
|
||||
* @param days
|
||||
* The range of days before or after today.
|
||||
* @param months
|
||||
* The range of months before or after today.
|
||||
* @param years
|
||||
* The range of years before or after today.
|
||||
*/
|
||||
def aroundToday(
|
||||
days: MinMax,
|
||||
months: MinMax = MinMax.Zero,
|
||||
years: MinMax = MinMax.Zero,
|
||||
clock: Clock = DefaultClock
|
||||
): Gen[LocalDate] =
|
||||
new GenLocalDate.After(
|
||||
new GenLocalDate.Around(
|
||||
pivot = LocalDate.now(clock),
|
||||
days = days,
|
||||
months = months,
|
||||
|
@ -504,7 +785,7 @@ object Gen:
|
|||
|
||||
end date
|
||||
|
||||
/** Geneators for `java.util.UUID` values.
|
||||
/** Geneators for GS `UUID` values.
|
||||
*/
|
||||
object uuid:
|
||||
|
||||
|
@ -514,12 +795,7 @@ object Gen:
|
|||
|
||||
/** Generator for UUIDs represented as strings.
|
||||
*/
|
||||
def string(): Gen[String] = uuid.random().map(id => id.toString)
|
||||
|
||||
/** Generator for UUIDs represented as strings, without dashes.
|
||||
*/
|
||||
def noDashes(): Gen[String] =
|
||||
uuid.random().map(id => id.toString.replace("-", ""))
|
||||
def string(): Gen[String] = uuid.random().map(id => id.str())
|
||||
|
||||
end uuid
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package gs.datagen.v0.generators
|
||||
|
||||
import gs.datagen.v0.Gen
|
||||
|
||||
final class GenBool() extends Gen[Boolean]:
|
||||
|
||||
override def generate(input: Any): Boolean =
|
||||
Gen.rng().nextBoolean()
|
|
@ -2,11 +2,28 @@ package gs.datagen.v0.generators
|
|||
|
||||
import gs.datagen.v0.Gen
|
||||
import java.time.LocalDate
|
||||
import java.util.Random
|
||||
|
||||
/** Base for generators that produce `java.time.LocalDate` values.
|
||||
*/
|
||||
trait GenLocalDate extends Gen[LocalDate]
|
||||
|
||||
/** Provids implementations for [[GenLocalDate]].
|
||||
*/
|
||||
object GenLocalDate:
|
||||
|
||||
/** Implementation of [[GenLocalDate]] that selects random dates before some
|
||||
* given date.
|
||||
*
|
||||
* @param pivot
|
||||
* The pivot date before which random values are selected.
|
||||
* @param days
|
||||
* The day bound.
|
||||
* @param months
|
||||
* The month bound.
|
||||
* @param years
|
||||
* The year bound.
|
||||
*/
|
||||
final class Before(
|
||||
val pivot: LocalDate,
|
||||
val days: MinMax,
|
||||
|
@ -14,6 +31,8 @@ object GenLocalDate:
|
|||
val years: MinMax
|
||||
) extends GenLocalDate:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def generate(input: Any): LocalDate =
|
||||
val rng = Gen.rng()
|
||||
pivot
|
||||
|
@ -21,6 +40,18 @@ object GenLocalDate:
|
|||
.minusMonths(months.select(rng).toInt)
|
||||
.minusYears(years.select(rng).toInt)
|
||||
|
||||
/** Implementation of [[GenLocalDate]] that selects random dates after some
|
||||
* given date.
|
||||
*
|
||||
* @param pivot
|
||||
* The pivot date after which random values are selected.
|
||||
* @param days
|
||||
* The day bound.
|
||||
* @param months
|
||||
* The month bound.
|
||||
* @param years
|
||||
* The year bound.
|
||||
*/
|
||||
final class After(
|
||||
val pivot: LocalDate,
|
||||
val days: MinMax,
|
||||
|
@ -28,6 +59,8 @@ object GenLocalDate:
|
|||
val years: MinMax
|
||||
) extends GenLocalDate:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def generate(input: Any): LocalDate =
|
||||
val rng = Gen.rng()
|
||||
pivot
|
||||
|
@ -35,4 +68,82 @@ object GenLocalDate:
|
|||
.plusMonths(months.select(rng).toInt)
|
||||
.plusYears(years.select(rng).toInt)
|
||||
|
||||
/** Implementation of [[GenLocalDate]] that selects random dates centered on
|
||||
* some given date.
|
||||
*
|
||||
* @param pivot
|
||||
* The pivot date around which random values are selected.
|
||||
* @param days
|
||||
* The day bound.
|
||||
* @param months
|
||||
* The month bound.
|
||||
* @param years
|
||||
* The year bound.
|
||||
*/
|
||||
final class Around(
|
||||
val pivot: LocalDate,
|
||||
val days: MinMax,
|
||||
val months: MinMax,
|
||||
val years: MinMax
|
||||
) extends GenLocalDate:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def generate(input: Any): LocalDate =
|
||||
val rng = Gen.rng()
|
||||
pivot
|
||||
.plusOrMinusDays(rng, days)
|
||||
.plusOrMinusMonths(rng, months)
|
||||
.plusOrMinusYears(rng, years)
|
||||
|
||||
extension (base: LocalDate)
|
||||
|
||||
/** Select a bounded random number of days before or after the base date.
|
||||
*
|
||||
* @param rng
|
||||
* The random generator.
|
||||
* @param range
|
||||
* The bounded range.
|
||||
* @return
|
||||
* The new date.
|
||||
*/
|
||||
def plusOrMinusDays(
|
||||
rng: Random,
|
||||
range: MinMax
|
||||
): LocalDate =
|
||||
if rng.nextBoolean() then base.plusDays(range.select(rng).toInt)
|
||||
else base.minusDays(range.select(rng).toInt)
|
||||
|
||||
/** Select a bounded random number of months before or after the base date.
|
||||
*
|
||||
* @param rng
|
||||
* The random generator.
|
||||
* @param range
|
||||
* The bounded range.
|
||||
* @return
|
||||
* The new date.
|
||||
*/
|
||||
def plusOrMinusMonths(
|
||||
rng: Random,
|
||||
range: MinMax
|
||||
): LocalDate =
|
||||
if rng.nextBoolean() then base.plusMonths(range.select(rng).toInt)
|
||||
else base.minusMonths(range.select(rng).toInt)
|
||||
|
||||
/** Select a bounded random number of years before or after the base date.
|
||||
*
|
||||
* @param rng
|
||||
* The random generator.
|
||||
* @param range
|
||||
* The bounded range.
|
||||
* @return
|
||||
* The new date.
|
||||
*/
|
||||
def plusOrMinusYears(
|
||||
rng: Random,
|
||||
range: MinMax
|
||||
): LocalDate =
|
||||
if rng.nextBoolean() then base.plusYears(range.select(rng).toInt)
|
||||
else base.minusYears(range.select(rng).toInt)
|
||||
|
||||
end GenLocalDate
|
||||
|
|
|
@ -3,7 +3,7 @@ package gs.datagen.v0.generators
|
|||
import gs.datagen.v0.Gen
|
||||
import scala.reflect.ClassTag
|
||||
|
||||
final class GenOneOf[A: ClassTag] private (
|
||||
final class GenOneOf[A] private (
|
||||
val choices: Array[A]
|
||||
) extends Gen[A]:
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ package gs.datagen.v0.generators
|
|||
import gs.datagen.v0.Gen
|
||||
import scala.reflect.ClassTag
|
||||
|
||||
final class GenOneOfGen[A: ClassTag] private (
|
||||
final class GenOneOfGen[A] private (
|
||||
val choices: Array[Gen[A]]
|
||||
) extends Gen[A]:
|
||||
|
||||
|
@ -12,7 +12,7 @@ final class GenOneOfGen[A: ClassTag] private (
|
|||
|
||||
object GenOneOfGen:
|
||||
|
||||
def choices[A: ClassTag](
|
||||
def choices[A](
|
||||
cs: Gen[A]*
|
||||
): GenOneOfGen[A] =
|
||||
if cs.isEmpty then
|
||||
|
@ -21,7 +21,7 @@ object GenOneOfGen:
|
|||
)
|
||||
else new GenOneOfGen[A](cs.toArray)
|
||||
|
||||
def list[A: ClassTag](
|
||||
def list[A](
|
||||
choices: List[Gen[A]]
|
||||
): GenOneOfGen[A] =
|
||||
if choices.isEmpty then
|
||||
|
@ -30,7 +30,7 @@ object GenOneOfGen:
|
|||
)
|
||||
else new GenOneOfGen[A](choices.toArray)
|
||||
|
||||
def set[A: ClassTag](
|
||||
def set[A](
|
||||
choices: Set[Gen[A]]
|
||||
): GenOneOfGen[A] =
|
||||
if choices.isEmpty then
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
package gs.datagen.v0.generators
|
||||
|
||||
import GenOption.DeterminationType
|
||||
import gs.datagen.v0.Gen
|
||||
|
||||
final class GenOption[A](
|
||||
val determinationType: DeterminationType,
|
||||
val generator: Gen[A]
|
||||
) extends Gen[Option[A]]:
|
||||
|
||||
override def generate(input: Any): Option[A] =
|
||||
determinationType match
|
||||
case DeterminationType.AlwaysSome =>
|
||||
Some(generator.gen())
|
||||
case DeterminationType.AlwaysNone =>
|
||||
None
|
||||
case DeterminationType.Random =>
|
||||
if Gen.rng().nextBoolean() then Some(generator.gen()) else None
|
||||
|
||||
object GenOption:
|
||||
|
||||
def apply[A](generator: Gen[A]): GenOption[A] =
|
||||
new GenOption[A](DeterminationType.Random, generator)
|
||||
|
||||
def random[A](generator: Gen[A]): GenOption[A] = apply[A](generator)
|
||||
|
||||
def alwaysSome[A](generator: Gen[A]): GenOption[A] =
|
||||
new GenOption[A](DeterminationType.AlwaysSome, generator)
|
||||
|
||||
def alwaysNone[A](generator: Gen[A]): GenOption[A] =
|
||||
new GenOption[A](DeterminationType.AlwaysNone, generator)
|
||||
|
||||
enum DeterminationType:
|
||||
case AlwaysSome, AlwaysNone, Random
|
||||
|
||||
object DeterminationType:
|
||||
|
||||
given CanEqual[DeterminationType, DeterminationType] = CanEqual.derived
|
||||
|
||||
end DeterminationType
|
||||
|
||||
end GenOption
|
|
@ -1,9 +1,10 @@
|
|||
package gs.datagen.v0.generators
|
||||
|
||||
import gs.datagen.v0.Gen
|
||||
import java.util.UUID
|
||||
import gs.uuid.v0.UUID
|
||||
|
||||
final class GenUUID extends Gen[UUID]:
|
||||
given UUID.Generator = UUID.Generator.version4
|
||||
|
||||
override def generate(input: Any): UUID =
|
||||
UUID.randomUUID()
|
||||
UUID.generate()
|
||||
|
|
|
@ -14,10 +14,10 @@ class DatagenTests extends munit.FunSuite:
|
|||
val data = "foo"
|
||||
|
||||
val p1: NoInput[String] =
|
||||
Datagen.pure(data).flatMap(x => Datagen.pure(data))
|
||||
Datagen.pure(data).flatMap(_ => Datagen.pure(data))
|
||||
|
||||
val p2: Datagen[String, Any] =
|
||||
Datagen.pure(data).map(x => data)
|
||||
Datagen.pure(data).map(_ => data)
|
||||
|
||||
assert(p1.generate(()) == data)
|
||||
assert(p2.generate(()) == data)
|
||||
|
|
|
@ -1 +1 @@
|
|||
sbt.version=1.9.9
|
||||
sbt.version=1.11.6
|
||||
|
|
|
@ -28,6 +28,6 @@ externalResolvers := Seq(
|
|||
"Garrity Software Releases" at "https://maven.garrity.co/gs"
|
||||
)
|
||||
|
||||
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.11")
|
||||
addSbtPlugin("gs" % "sbt-garrity-software" % "0.3.0")
|
||||
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.3.1")
|
||||
addSbtPlugin("gs" % "sbt-garrity-software" % "0.6.0")
|
||||
addSbtPlugin("gs" % "sbt-gs-semver" % "0.3.0")
|
||||
|
|
Loading…
Add table
Reference in a new issue