|
| 1 | +package org.scalacheck.commands |
| 2 | + |
| 3 | +import org.scalacheck.Prop.forAll |
| 4 | +import org.scalacheck.rng.Seed |
| 5 | +import org.scalacheck.{Arbitrary, Gen, Prop, Properties, Shrink} |
| 6 | +import scala.reflect.ClassTag |
| 7 | +import scala.util.{Success, Try} |
| 8 | + |
| 9 | +object CommandsShrinkSpecification extends Properties("Commands Shrinking") { |
| 10 | + |
| 11 | + property("Shrunk command sequences always satisfy preconditions") = { |
| 12 | + forAll(Arbitrary.arbitrary[Long]) { |
| 13 | + case seedValue => |
| 14 | + val seed = Seed(seedValue) |
| 15 | + val parameters = Gen.Parameters.default.withInitialSeed(seed) |
| 16 | + val result = BoundedQueueSpec.property(1)(parameters) |
| 17 | + |
| 18 | + // If the property fails shrinking will kick in. If shrinking does |
| 19 | + // not guarantee that preconditions are satisfied and the shrunk |
| 20 | + // command sequence contains the Dequeue command, we will invoke |
| 21 | + // Nil.tail which will raise UnsupportedOperationException. |
| 22 | + result.status match { |
| 23 | + case Prop.Exception(e: UnsupportedOperationException) => false |
| 24 | + case _ => true |
| 25 | + } |
| 26 | + } |
| 27 | + } |
| 28 | + |
| 29 | + class BoundedQueue[Element: ClassTag](val capacity: Int) { |
| 30 | + require(capacity >= 0) |
| 31 | + |
| 32 | + var buffer = new Array[Element](capacity + 1) |
| 33 | + var reads = 0 |
| 34 | + var writes = 0 |
| 35 | + |
| 36 | + def size: Int = { |
| 37 | + (writes + buffer.length - reads) % buffer.length |
| 38 | + } |
| 39 | + |
| 40 | + def enqueue(element: Element): Unit = { |
| 41 | + require(size < capacity) |
| 42 | + buffer(writes) = element |
| 43 | + writes += 1 |
| 44 | + // writes %= buffer.length // deliberately broken to provoke failure |
| 45 | + } |
| 46 | + |
| 47 | + def dequeue(): Element = { |
| 48 | + require(size > 0) |
| 49 | + val index = reads |
| 50 | + reads += 1 |
| 51 | + reads %= buffer.length |
| 52 | + buffer(index) |
| 53 | + } |
| 54 | + } |
| 55 | + |
| 56 | + object BoundedQueueSpec extends Commands { |
| 57 | + type Element = Byte |
| 58 | + |
| 59 | + type Sut = BoundedQueue[Element] |
| 60 | + |
| 61 | + case class State(capacity: Int, elements: Seq[Element]) |
| 62 | + |
| 63 | + case object Capacity extends Command { |
| 64 | + type Result = Int |
| 65 | + def run(sut: Sut): Result = sut.capacity |
| 66 | + def nextState(state: State): State = state |
| 67 | + def preCondition(state: State): Boolean = true |
| 68 | + def postCondition(state: State, result: Try[Result]): Prop = |
| 69 | + result == Success(state.capacity) |
| 70 | + } |
| 71 | + |
| 72 | + case object Size extends Command { |
| 73 | + type Result = Int |
| 74 | + def run(sut: Sut): Result = sut.size |
| 75 | + def nextState(state: State): State = state |
| 76 | + def preCondition(state: State): Boolean = true |
| 77 | + def postCondition(state: State, result: Try[Result]): Prop = |
| 78 | + result == Success(state.elements.length) |
| 79 | + } |
| 80 | + |
| 81 | + case class Enqueue(element: Element) extends UnitCommand { |
| 82 | + def run(sut: Sut): Unit = |
| 83 | + sut.enqueue(element) |
| 84 | + def nextState(state: State): State = |
| 85 | + State(state.capacity, state.elements ++ List(element)) |
| 86 | + def preCondition(state: State): Boolean = |
| 87 | + state.elements.length < state.capacity |
| 88 | + def postCondition(state: State, success: Boolean): Prop = |
| 89 | + success |
| 90 | + } |
| 91 | + |
| 92 | + case object Dequeue extends Command { |
| 93 | + type Result = Element |
| 94 | + def run(sut: Sut): Result = |
| 95 | + sut.dequeue() |
| 96 | + def nextState(state: State): State = |
| 97 | + State(state.capacity, state.elements.tail) |
| 98 | + def preCondition(state: State): Boolean = |
| 99 | + state.elements.nonEmpty |
| 100 | + def postCondition(state: State, result: Try[Result]): Prop = |
| 101 | + result == Success(state.elements.head) |
| 102 | + } |
| 103 | + |
| 104 | + def genInitialState: Gen[State] = |
| 105 | + Gen.choose(10, 20).map(State(_, Nil)) |
| 106 | + |
| 107 | + def genCommand(state: State): Gen[Command] = |
| 108 | + Gen.oneOf( |
| 109 | + Gen.const(Capacity), |
| 110 | + Gen.const(Size), |
| 111 | + Gen.const(Dequeue), |
| 112 | + Arbitrary.arbitrary[Element].map(Enqueue(_)) |
| 113 | + ).retryUntil(_.preCondition(state), 100) |
| 114 | + |
| 115 | + def newSut(state: State): Sut = |
| 116 | + new BoundedQueue(state.capacity) |
| 117 | + |
| 118 | + def initialPreCondition(state: State): Boolean = |
| 119 | + state.capacity > 0 && state.elements.isEmpty |
| 120 | + |
| 121 | + override def shrinkState: Shrink[State] = |
| 122 | + Shrink.xmap[(Int, Seq[Element]), State]( |
| 123 | + { case (capacity, elems) => State(capacity, elems take capacity) }, |
| 124 | + { case State(capacity, elems) => (capacity, elems) } |
| 125 | + ).suchThat(_.capacity >= 0) |
| 126 | + |
| 127 | + def canCreateNewSut( |
| 128 | + newState: State, |
| 129 | + initSuts: Traversable[State], |
| 130 | + runningSuts: Traversable[Sut] |
| 131 | + ): Boolean = |
| 132 | + true |
| 133 | + |
| 134 | + def destroySut(sut: Sut): Unit = |
| 135 | + () |
| 136 | + } |
| 137 | +} |
0 commit comments