Skip to content

Instantly share code, notes, and snippets.

@jamescway
Last active October 12, 2015 16:50
Show Gist options
  • Save jamescway/0abcec0c7d619d6e8708 to your computer and use it in GitHub Desktop.
Save jamescway/0abcec0c7d619d6e8708 to your computer and use it in GitHub Desktop.
fpinscala-testing
import org.scalatest.FunSpec
import Stream._
import State._s
// import parallelism._
import Par._
import Gen._
import Prop._
import java.util.concurrent.{Executors,ExecutorService}
case class Prop(run: (MaxSize,TestCases,RNG) => Result) {
def &&(p: Prop) = Prop {
(max,n,rng) => run(max,n,rng) match {
case Passed | Proved => p.run(max, n, rng)
case x => x
}
}
def ||(p: Prop) = Prop {
(max,n,rng) => run(max,n,rng) match {
// In case of failure, run the other prop.
case Falsified(msg, _) => p.tag(msg).run(max,n,rng)
case x => x
}
}
/* This is rather simplistic - in the event of failure, we simply prepend
* the given message on a newline in front of the existing message.
*/
def tag(msg: String) = Prop {
(max,n,rng) => run(max,n,rng) match {
case Falsified(e, c) => Falsified(msg + "\n" + e, c)
case x => x
}
}
}
object Prop {
type SuccessCount = Int
type TestCases = Int
type MaxSize = Int
type FailedCase = String
sealed trait Result {
def isFalsified: Boolean
}
case object Passed extends Result {
def isFalsified = false
}
case class Falsified(failure: FailedCase,
successes: SuccessCount) extends Result {
def isFalsified = true
}
case object Proved extends Result {
def isFalsified = false
}
/* Produce an infinite random stream from a `Gen` and a starting `RNG`. */
def randomStream[A](g: Gen[A])(rng: RNG): Stream[A] =
Stream.unfold(rng)(rng => Some(g.sample.run(rng)))
def forAll[A](as: Gen[A])(f: A => Boolean): Prop = Prop {
(n,rng) => randomStream(as)(rng).zip(Stream.from(0)).take(n).map {
case (a, i) => try {
if (f(a)) Passed else Falsified(a.toString, i)
} catch { case e: Exception => Falsified(buildMsg(a, e), i) }
}.find(_.isFalsified).getOrElse(Passed)
}
// String interpolation syntax. A string starting with `s"` can refer to
// a Scala value `v` as `$v` or `${v}` in the string.
// This will be expanded to `v.toString` by the Scala compiler.
def buildMsg[A](s: A, e: Exception): String =
s"test case: $s\n" +
s"generated an exception: ${e.getMessage}\n" +
s"stack trace:\n ${e.getStackTrace.mkString("\n")}"
def apply(f: (TestCases,RNG) => Result): Prop =
Prop { (_,n,rng) => f(n,rng) }
def forAll[A](g: SGen[A])(f: A => Boolean): Prop =
forAll(g(_))(f)
def forAll[A](g: Int => Gen[A])(f: A => Boolean): Prop = Prop {
(max,n,rng) =>
val casesPerSize = (n - 1) / max + 1
val props: Stream[Prop] =
Stream.from(0).take((n min max) + 1).map(i => forAll(g(i))(f))
val prop: Prop =
props.map(p => Prop { (max, n, rng) =>
p.run(max, casesPerSize, rng)
}).toList.reduce(_ && _)
prop.run(max,n,rng)
}
def run(p: Prop,
maxSize: Int = 100,
testCases: Int = 100,
rng: RNG = RNG.Simple(System.currentTimeMillis)): Unit =
p.run(maxSize, testCases, rng) match {
case Falsified(msg, n) =>
println(s"! Falsified after $n passed tests:\n $msg")
case Passed =>
println(s"+ OK, passed $testCases tests.")
case Proved =>
println(s"+ OK, proved property.")
}
val ES: ExecutorService = Executors.newCachedThreadPool
val p1 = Prop.forAll(Gen.unit(Par.unit(1)))(i =>
Par.map(i)(_ + 1)(ES).get == Par.unit(2)(ES).get)
def check(p: => Boolean): Prop = Prop { (_, _, _) =>
if (p) Passed else Falsified("()", 0)
}
val p2 = check {
val p = Par.map(Par.unit(1))(_ + 1)
val p2 = Par.unit(2)
p(ES).get == p2(ES).get
}
def equal[A](p: Par[A], p2: Par[A]): Par[Boolean] =
Par.map2(p,p2)(_ == _)
val p3 = check {
equal (
Par.map(Par.unit(1))(_ + 1),
Par.unit(2)
) (ES) get
}
// val S = weighted(
// choose(1,4).map(Executors.newFixedThreadPool) -> .75,
// unit(Executors.newCachedThreadPool) -> .25) // `a -> b` is syntax sugar for `(a,b)`
// def forAllPar[A](g: Gen[A])(f: A => Par[Boolean]): Prop =
// forAll(S.map2(g)((_,_))) { case (s,a) => f(a)(s).get }
// def checkPar(p: Par[Boolean]): Prop =
// forAllPar(Gen.unit(()))(_ => p)
// def forAllPar2[A](g: Gen[A])(f: A => Par[Boolean]): Prop =
// forAll(S ** g) { case (s,a) => f(a)(s).get }
// def forAllPar3[A](g: Gen[A])(f: A => Par[Boolean]): Prop =
// forAll(S ** g) { case s ** a => f(a)(s).get }
// val pint = Gen.choose(0,10) map (Par.unit(_))
// val p4 =
// forAllPar(pint)(n => equal(Par.map(n)(y => y), n))
// val forkProp = Prop.forAllPar(pint2)(i => equal(Par.fork(i), i)) tag "fork"
}
case class Gen[+A](sample: State[RNG,A]) {
def map[B](f: A => B): Gen[B] =
Gen(sample.map(f))
def map2[B,C](g: Gen[B])(f: (A,B) => C): Gen[C] =
Gen(sample.map2(g.sample)(f))
def flatMap[B](f: A => Gen[B]): Gen[B] =
Gen(sample.flatMap(a => f(a).sample))
/* A method alias for the function we wrote earlier. */
def listOfN(size: Int): Gen[List[A]] =
Gen.listOfN(size, this)
/* A version of `listOfN` that generates the size to use dynamically. */
def listOfN(size: Gen[Int]): Gen[List[A]] =
size flatMap (n => this.listOfN(n))
def listOf: SGen[List[A]] = Gen.listOf(this)
def listOf1: SGen[List[A]] = Gen.listOf1(this)
def unsized = SGen(_ => this)
def **[B](g: Gen[B]): Gen[(A,B)] =
(this map2 g)((_,_))
}
object Gen {
def unit[A](a: => A): Gen[A] =
Gen(State.unit(a))
val boolean: Gen[Boolean] =
Gen(State(RNG.boolean))
def choose(start: Int, stopExclusive: Int): Gen[Int] =
Gen(State(RNG.nonNegativeInt).map(n => start + n % (stopExclusive-start)))
def listOfN[A](n: Int, g: Gen[A]): Gen[List[A]] =
Gen(State.sequence(List.fill(n)(g.sample)))
val uniform: Gen[Double] = Gen(State(RNG.double))
def choose(i: Double, j: Double): Gen[Double] =
Gen(State(RNG.double).map(d => i + d*(j-i)))
/* Basic idea is to add 1 to the result of `choose` if it is of the wrong
* parity, but we require some special handling to deal with the maximum
* integer in the range.
*/
def even(start: Int, stopExclusive: Int): Gen[Int] =
choose(start, if (stopExclusive%2 == 0) stopExclusive - 1 else stopExclusive).
map (n => if (n%2 != 0) n+1 else n)
def odd(start: Int, stopExclusive: Int): Gen[Int] =
choose(start, if (stopExclusive%2 != 0) stopExclusive - 1 else stopExclusive).
map (n => if (n%2 == 0) n+1 else n)
def sameParity(from: Int, to: Int): Gen[(Int,Int)] = for {
i <- choose(from,to)
j <- if (i%2 == 0) even(from,to) else odd(from,to)
} yield (i,j)
def listOfN_1[A](n: Int, g: Gen[A]): Gen[List[A]] =
List.fill(n)(g).foldRight(unit(List[A]()))((a,b) => a.map2(b)(_ :: _))
def union[A](g1: Gen[A], g2: Gen[A]): Gen[A] =
boolean.flatMap(b => if (b) g1 else g2)
def weighted[A](g1: (Gen[A],Double), g2: (Gen[A],Double)): Gen[A] = {
/* The probability we should pull from `g1`. */
val g1Threshold = g1._2.abs / (g1._2.abs + g2._2.abs)
Gen(State(RNG.double).flatMap(d => if (d < g1Threshold) g1._1.sample else g2._1.sample))
}
def listOf[A](g: Gen[A]): SGen[List[A]] =
SGen(n => g.listOfN(n))
/* Not the most efficient implementation, but it's simple.
* This generates ASCII strings.
*/
def stringN(n: Int): Gen[String] =
listOfN(n, choose(0,127)).map(_.map(_.toChar).mkString)
val string: SGen[String] = SGen(stringN)
implicit def unsized[A](g: Gen[A]): SGen[A] = SGen(_ => g)
val smallInt = Gen.choose(-10,10)
val maxProp = forAll(listOf(smallInt)) { l =>
val max = l.max
!l.exists(_ > max) // No value greater than `max` should exist in `l`
}
def listOf1[A](g: Gen[A]): SGen[List[A]] =
SGen(n => g.listOfN(n max 1))
val maxProp1 = forAll(listOf1(smallInt)) { l =>
val max = l.max
!l.exists(_ > max) // No value greater than `max` should exist in `l`
}
// We specify that every sorted list is either empty, has one element,
// or has no two consecutive elements `(a,b)` such that `a` is greater than `b`.
val sortedProp = forAll(listOf(smallInt)) { l =>
val ls = l.sorted
l.isEmpty || ls.tail.isEmpty || !ls.zip(ls.tail).exists { case (a,b) => a > b }
}
object ** {
def unapply[A,B](p: (A,B)) = Some(p)
}
/* A `Gen[Par[Int]]` generated from a list summation that spawns a new parallel
* computation for each element of the input list summed to produce the final
* result. This is not the most compelling example, but it provides at least some
* variation in structure to use for testing.
*
* Note that this has to be a `lazy val` because of the way Scala initializes objects.
* It depends on the `Prop` companion object being created, which references `pint2`.
*/
lazy val pint2: Gen[Par[Int]] = choose(-100,100).listOfN(choose(0,20)).map(l =>
l.foldLeft(Par.unit(0))((p,i) =>
Par.fork { Par.map2(p, Par.unit(i))(_ + _) }))
def genStringIntFn(g: Gen[Int]): Gen[String => Int] =
g map (i => (s => i))
}
case class SGen[+A](g: Int => Gen[A]) {
def apply(n: Int): Gen[A] = g(n)
def map[B](f: A => B): SGen[B] =
SGen(g andThen (_ map f))
def flatMap[B](f: A => Gen[B]): SGen[B] =
SGen(g andThen (_ flatMap f))
def **[B](s2: SGen[B]): SGen[(A,B)] =
SGen(n => apply(n) ** s2(n))
}
class GenSpec extends FunSpec {
val rng1 = RNG.Simple(42)
val rng11 = RNG.Simple(43)
describe("Test case") {
it("does stuff"){
// val (x, rng2) = RNG.nonNegativeInt(rng1)
// val g = new Gen(State(RNG.nonNegativeInt))
val x = Gen.choose(0,100).sample.run(rng1)._1
println(s"x is equal to: $x")
x > -1
}
}
}
set -xe
#deleted the packages identifiers to make this work in the same directory
scalac Par.scala
scalac State.scala
scalac Stream.scala
scalac Gen.scala
scala org.scalatest.run GenSpec
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment