diff options
Diffstat (limited to 'src/partest-extras/scala/org/scalacheck/Commands.scala')
-rw-r--r-- | src/partest-extras/scala/org/scalacheck/Commands.scala | 146 |
1 files changed, 146 insertions, 0 deletions
diff --git a/src/partest-extras/scala/org/scalacheck/Commands.scala b/src/partest-extras/scala/org/scalacheck/Commands.scala new file mode 100644 index 0000000000..5ff3a397e5 --- /dev/null +++ b/src/partest-extras/scala/org/scalacheck/Commands.scala @@ -0,0 +1,146 @@ +/*-------------------------------------------------------------------------*\ +** ScalaCheck ** +** Copyright (c) 2007-2014 Rickard Nilsson. All rights reserved. ** +** http://www.scalacheck.org ** +** ** +** This software is released under the terms of the Revised BSD License. ** +** There is NO WARRANTY. See the file LICENSE for the full text. ** +\*------------------------------------------------------------------------ */ + +package org.scalacheck + +/** See User Guide for usage examples */ +@deprecated("Will be replaced with a new implementation in 1.12.0", "1.11.4") +trait Commands extends Prop { + + /** The abstract state data type. This type must be immutable. + * The state type that encodes the abstract state. The abstract state + * should model all the features we need from the real state, the system + * under test. We should leave out all details that aren't needed for + * specifying our pre- and postconditions. The state type must be called + * State and be immutable. */ + type State <: AnyRef + + class Binding(private val key: State) { + def get: Any = bindings.find(_._1 eq key) match { + case None => sys.error("No value bound") + case Some(x) => x._2 + } + } + + /** Abstract commands are defined as subtypes of the traits Command or SetCommand. + * Each command must have a run method and a method that returns the new abstract + * state, as it should look after the command has been run. + * A command can also define a precondition that states how the current + * abstract state must look if the command should be allowed to run. + * Finally, we can also define a postcondition which verifies that the + * system under test is in a correct state after the command exectution. */ + trait Command { + + /** Used internally. */ + protected[Commands] def run_(s: State) = run(s) + + def run(s: State): Any + def nextState(s: State): State + + /** Returns all preconditions merged into a single function */ + def preCondition: (State => Boolean) = + s => preConditions.toList.forall(_.apply(s)) + + /** A precondition is a function that + * takes the current abstract state as parameter and returns a boolean + * that says if the precondition is fulfilled or not. You can add several + * conditions to the precondition list */ + val preConditions = new collection.mutable.ListBuffer[State => Boolean] + + /** Returns all postconditions merged into a single function */ + def postCondition: (State,State,Any) => Prop = + (s0,s1,r) => Prop.all(postConditions.map(_.apply(s0,s1,r)): _*) + + /** A postcondition is a function that + * takes three parameters, s0, s1 and r. s0 is the abstract state before + * the command was run, s1 is the abstract state after the command was + * run, and r is the result from the command's run + * method. The postcondition function should return a Boolean (or + * a Prop instance) that says if the condition holds or not. You can add several + * conditions to the postConditions list. */ + val postConditions = new collection.mutable.ListBuffer[(State,State,Any) => Prop] + } + + /** A command that binds its result for later use */ + trait SetCommand extends Command { + /** Used internally. */ + protected[Commands] final override def run_(s: State) = { + val r = run(s) + bindings += ((s,r)) + r + } + + final def nextState(s: State) = nextState(s, new Binding(s)) + def nextState(s: State, b: Binding): State + } + + private case class Cmds(cs: List[Command], ss: List[State]) { + override def toString = cs.map(_.toString).mkString(", ") + } + + private val bindings = new scala.collection.mutable.ListBuffer[(State,Any)] + + private def initState() = { + bindings.clear() + initialState() + } + + private def genCmds: Gen[Cmds] = { + def sizedCmds(s: State, sz: Int): Gen[Cmds] = { + if(sz <= 0) Gen.const(Cmds(Nil, Nil)) else for { + c <- genCommand(s) suchThat (_.preCondition(s)) + Cmds(cs,ss) <- sizedCmds(c.nextState(s), sz-1) + } yield Cmds(c::cs, s::ss) + } + + Gen.sized(sz => sizedCmds(initialState(), sz)) + } + + private def validCmds(s: State, cs: List[Command]): Option[Cmds] = + cs match { + case Nil => Some(Cmds(Nil, s::Nil)) + case c::_ if !c.preCondition(s) => None + case c::cmds => for { + Cmds(_, ss) <- validCmds(c.nextState(s), cmds) + } yield Cmds(cs, s::ss) + } + + private def runCommands(cmds: Cmds): Prop = Prop.all { + cmds.cs.indices.map { i => + val (c,s) = (cmds.cs(i), cmds.ss(i)) + c.postCondition(s,c.nextState(s),c.run_(s)) + } : _* + } + + private def commandsProp: Prop = { + def shrinkCmds(cmds: Cmds) = + Shrink.shrink(cmds.cs)(Shrink.shrinkContainer).flatMap { cs => + validCmds(initialState(), cs).toList + } + + Prop.forAllShrink(genCmds label "COMMANDS", shrinkCmds)(runCommands _) + } + + def apply(p: Gen.Parameters) = commandsProp(p) + + /** initialState should reset the system under test to a well defined + * initial state, and return the abstract version of that state. */ + def initialState(): State + + /** The command generator. Given an abstract state, the generator + * should return a command that is allowed to run in that state. Note that + * it is still neccessary to define preconditions on the commands if there + * are any. The generator is just giving a hint of which commands that are + * suitable for a given state, the preconditions will still be checked before + * a command runs. Sometimes you maybe want to adjust the distribution of + * your command generator according to the state, or do other calculations + * based on the state. */ + def genCommand(s: State): Gen[Command] + +} |