With this project you can externalize Gen[T] configuration.
When I needed to generate some amount of test data the first think appeared to my mind was to use Gen[T]
from awesome scalacheck project. I have written first version of data generator really quickly. I have deployed then
into right place, started, checked my app started receiving test data. That was cool.
Let me show you the example.
import java.time._
import org.scalacheck._
case class MyEvent(id: String, timeStamp: Instant, message: String)
val myEventGen = for {
  id        <- Gen.identifier
  timeStamp <- Gen.choose(0, Instant.now().toEpochMilli) map { Instant.ofEpochMilli }
  message   <- Gen.alphaNumStr
} yield MyEvent(id, timeStamp, message)The code above is pretty much what I have started with.
But then I realized that I need to change test data generation profile. I needed to replace some Gen.const with,
say, Gen.oneof or Gen.choose. So, the only solution was to change the code and redeploy, restart, etc..
That is what always makes me unhappy.
So I thought, that would be helpful if I could change test data generation profile without real code change. Can we, for example, utilize environment variables to read generator definition from there? In particular, can we do something like this
def genFor[T](name: String): Option[Gen[T]] = {
  sys.props.get(name) flatMap {
    ParseGen(_).fold(
      { err => log.warning(err); None },
      { gen => Some(gen) })
  }
}
    
val myEventGen = for {
  id        <- genFor[String] ("identifier") getOrElse Gen.identifier
  timeStamp <- genFor[Instant]("time-stamp") getOrElse Gen.choose(0, Instant.now().toEpochMilli) map { Instant.ofEpochMilli }
  message   <- genFor[String] ("message")    getOrElse Gen.alphaNumStr
} yield MyEvent(id, timeStamp, message)With this solution the only thing I need is to restart data generation with desired system properties set.
(-Didentifier=alphaNumStr -Dtime-stamp="range: 2018-04-04T00:00Z .. 2018-04-14T23:59Z")
The project is in it's initial phase so do not expect too much form it yet please.
The only pretty strict amount of type supported yet.
- Most of scala numeric types (Byte,Short,Int,Long,Float,Double)
- Charand- String
- Boolean
- Date time types
- java.time.Instant
- java.time.LocalDateTime
- java.time.LocalDate
- java.time.LocalTime
- java.time.OffsetDateTime
- java.time.ZonedDateTime
- java.util.Date
- java.util.Calendar
- java.sql.Date
- java.sql.Time
 
- Duration types
- FiniteDuration
- Duration
 
ParseGen[T](defn: String): Either[String, Gen[T]]
Examples
import com.github.andyglow.scalacheck._
val Right(idGen)        = ParseGen[String]("identifier: 16")
val Right(posLongGen)   = ParseGen[Long]("posNum")
val Right(timestampGen) = ParseGen[java.time.Instant]("range: 2018-01-01T00:00Z .. 2018-12-31T23:59Z")Below is a table showing which definitions can be supported by types.
| Numeric types | Char | String | Boolean | Date types | Duration | |
|---|---|---|---|---|---|---|
| identifier | [x] | |||||
| numChar | [x] | |||||
| numStr | [x] | |||||
| alphaUpperChar | [x] | |||||
| alphaUpperStr | [x] | |||||
| alphaLowerChar | [x] | |||||
| alphaLowerStr | [x] | |||||
| alphaChar | [x] | |||||
| alphaStr | [x] | |||||
| alphaNumChar | [x] | |||||
| alphaNumStr | [x] | |||||
| asciiChar | [x] | |||||
| asciiStr | [x] | |||||
| asciiPrintableChar | [x] | |||||
| asciiPrintableStr | [x] | |||||
| posNum | [x] | |||||
| negNum | [x] | |||||
| const | [x] | [x] | [x] | [x] | [x] | [x] | 
| range | [x] | [x] | [x] | [x] | ||
| oneof | [x] | [x] | [x] | [x] | [x] | [x] | 
String definition can be complicated with size restriction.
<definition>   ::= <term> ":" <restriction>
<restriction>  ::= <strict>
                 | <greater-then>
                 | <less-then>       
<strict>       := DIGIT+
<greater-then> := ">" DIGIT+
<less-then>    := "<" DIGIT+
Examples
identifier: 16
numStr: < 8
asciiStr: > 0
const definition will result in Gen.const with given value if converted to specified type.
<definition>   ::= "const" ":" <value>
<value>        ::= <any>
Examples
const: 5
const: foo bar baz
const: 2018-04-04T00:00Z
range definition will result in Gen.choose with given value if converted to specified type.
<definition>   ::= "range" ":" <value> ".." <value>
<value>        ::= <any>
Examples
range: 5 .. 12 // numeric
range: 2016-04-04T00:00Z .. 2018-04-04T00:00Z // date
range: 5ns .. 8m // duration
oneof definition will result in Gen.choose with given value if converted to specified type.
<definition>   ::= "oneof" ":" <value> ("," <value>)*
<value>        ::= <any>
Examples
oneof: 1, 2, 3, 76 // numeric
oneof: 2016-04-04T00:00Z, 2018-04-04T00:00Z // date
oneof: 5ns, 8m, 32d // duration
libraryDependencies += "com.github.andyglow" %% "scalacheck-gen-configured" % "$latestVersion"compile "com.github.andyglow:scalacheck-gen-configured_${scalaVersion}:$latestVersion"
<dependency>
    <groupId>com.github.andyglow</groupId>
    <artifactId>scalacheck-gen-configured_${scalaVersion}</artifactId>
    <version>$latestVersion</version>
</dependency>- options
-  containers (List,Set,Map, etc)
-  handle strings quoted (especially important when used woth oneof)
- enums
- case classes
-  allow pluggable definition formats. now it's strings only, but could be something else (consider typesafe Config, jsonAST, ...)
-  implement typesafe-config enabled DefnFormat