Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shrinking command data #728

Open
AndersBensen opened this issue Dec 6, 2020 · 1 comment
Open

Shrinking command data #728

AndersBensen opened this issue Dec 6, 2020 · 1 comment

Comments

@AndersBensen
Copy link

I am trying to shrink the data inside a command but i cannot find any documentation on how to. In the code below i have a counter i am testing. I have introduced a bug in the increment method of the counter, where the counter does not get incremented if it has a value of 10.

package LocalCounter

import org.scalacheck.commands.Commands
import org.scalacheck.{Gen, Prop, Properties, Shrink}
import scala.util.{Success, Try}

case class Counter() {
  private var n = 1
  def increment(incrementAmount: Int) = {
    if (n!=10) {
      n += incrementAmount
    }
  }
  def get(): Int = n
}

object CounterCommands extends Commands {
  type State = Int
  type Sut = Counter

  def canCreateNewSut(newState: State, initSuts: Traversable[State],
                      runningSuts: Traversable[Sut]): Boolean = true
  def newSut(state: State): Sut = new Counter
  def destroySut(sut: Sut): Unit = ()
  def initialPreCondition(state: State): Boolean = true
  def genInitialState: Gen[State] = Gen.const(1)
  def genCommand(state: State): Gen[Command] = Gen.oneOf(Increment(Gen.chooseNum(1, 40).sample.get), Get)

  case class Increment(incrementAmount: Int) extends UnitCommand {
    def run(counter: Sut) = counter.increment(incrementAmount)
    def nextState(state: State): State = {state+incrementAmount}
    def preCondition(state: State): Boolean = true
    def postCondition(state: State, success: Boolean) = success
  }

  case object Get extends Command {
    type Result = Int
    def run(counter: Sut): Result = counter.get()
    def nextState(state: State): State = state
    def preCondition(state: State): Boolean = true
    def postCondition(state: State, result: Try[Int]): Prop = result == Success(state)
  }

  implicit val shrinkCommand: Shrink[Command] = Shrink({
      case Increment(amt) => Shrink.shrink(amt).map(Increment(_))
      case Get => Stream.empty
  })
}

object CounterCommandsTest extends Properties("CounterCommands") {
  CounterCommands.property().check()
}

Running the code gave the output:

! Falsified after 4 passed tests.
> Labels of failing property:
initialstate = 1
seqcmds = (Increment(9); Increment(40); Get => 10)
> ARG_0: Actions(1,List(Increment(9), Increment(40), Get),List())
> ARG_0_ORIGINAL: Actions(1,List(Increment(9), Increment(34), Increment(40)
  , Get),List())

Incrementing by 40 is not the minimal value needed to find the bug.

I asked the same question on stackoverflow:
https://stackoverflow.com/questions/64915650/scalacheck-shrinking-command-data-in-stateful-testing
where it was suggested to use the following shrinker:

implicit val shrinkCommand: Shrink[Command] = Shrink({
  case Increment(amt) => shrink(amt).map(Increment(_))
  case Get => Stream.empty
}

I tried using the shrinker and putting it inside of the CounterCommands object but with no luck, the command data was still not shrunk.
Is it even possible to do this in ScalaCheck? And if yes, how do i do it?

I am using Scala version 2.13.3 and ScalaCheck version 1.14.1.

@jonaskoelker
Copy link
Contributor

I noticed the same shortcoming. Unless I have seriously misunderstood something, it is not possible with the current version of ScalaCheck. That's why I implemented that feature: #632. It has not been merged yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants