From 82700e7e8a64967244782f650198ff4214325c5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20K=C3=B6lker?= Date: Sat, 29 Feb 2020 20:46:52 +0100 Subject: [PATCH 1/2] Improve shrinking of Command sequences and allow customization There are three improvements here. Together they accomplish more than the sum of their parts. - Instead of shrinking the State with `shrinkWithOrig` and combining the result with the shrunk sequential and parallel commands, just shrink each `Actions` field independently. That is, include in the `shrink` output some Actions where only the State has been shrunk; previously State-shrinking would only occur together with shrinking of at least one command sequence. - Instead of throwing out `Actions` objects that do not satisfy the `actionsPrecond` predicate, throw out individual Command objects to make the `Actions` object conform to `actionsPrecond`. This allows the state and commands to be shrunk jointly in a sensible fashion. - Allow the user to customize command shrinking, at three levels: * `shrinkCommand` shrinks individual commands. * `shrinkSequentialCommands` shrinks the sequential part. * `shrinkParallelCommands` shrink the parallel command sequences. If the user only defines `shrinkCommand`, the other two will be defined using `implicitly` for `List`. --- .../org/scalacheck/commands/Commands.scala | 54 +++++++++++++++++-- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/src/main/scala/org/scalacheck/commands/Commands.scala b/src/main/scala/org/scalacheck/commands/Commands.scala index 1327de7ff..b45c3ed01 100644 --- a/src/main/scala/org/scalacheck/commands/Commands.scala +++ b/src/main/scala/org/scalacheck/commands/Commands.scala @@ -262,6 +262,24 @@ trait Commands { * [[State]]. By default no shrinking is done for [[State]]. */ def shrinkState: Shrink[State] = implicitly + /** Override this to provide a custom Shrinker for your internal + * [[Command]]. By default no shrinking is done for [[Command]]. */ + def shrinkCommand: Shrink[Command] = implicitly + + /** Override this to provide a custom Shrinker of sequential [[Command]]s. + * By default, the implicit List shrinker is used with [[shrinkCommand]]. */ + def shrinkSequentialCommands: Shrink[List[Command]] = { + implicit val commandShrinker = shrinkCommand + implicitly + } + + /** Override this to provide a custom Shrinker of parallel [[Command]]s. + * By default, the implict List shrinker is used with [[shrinkCommand]]. */ + def shrinkParallelCommands: Shrink[List[List[Command]]] = { + implicit val commandShrinker = shrinkCommand + implicitly + } + // Private methods // private type Commands = List[Command] @@ -270,12 +288,38 @@ trait Commands { ) private implicit val shrinkActions: Shrink[Actions] = Shrink[Actions] { as => - val shrinkedCmds: Stream[Actions] = - Shrink.shrink(as.seqCmds).map(cs => as.copy(seqCmds = cs)) append - Shrink.shrink(as.parCmds).map(cs => as.copy(parCmds = cs)) + ( + Shrink.shrink(as.s )(shrinkState ).map(s2 => as.copy(s = s2)) append + Shrink.shrink(as.seqCmds)(shrinkSequentialCommands).map(cs => as.copy(seqCmds = cs)) append + Shrink.shrink(as.parCmds)(shrinkParallelCommands ).map(cs => as.copy(parCmds = cs)) + ).map(actions => removeInvalidCommands(actions.s, actions.seqCmds, actions.parCmds)) + } + + private def removeInvalidCommands(state: State, seqCmds: Commands, parCmds: List[Commands]): Actions = { + + def filterCommandSequence(s: State, cmds: Commands): Commands = cmds match { + case c :: cs => + if (c.preCondition(s)) + c :: filterCommandSequence(c.nextState(s), cs) + else + filterCommandSequence(s, cs) + case Nil => Nil + } + + val filteredSequentialCommands = filterCommandSequence(state, seqCmds) + val stateAfterSequentialCommands = filteredSequentialCommands.foldLeft(state) { + case (st, cmd) => cmd.nextState(st) + } + + val filteredParallelCommands = parCmds.map(cmds => { + filterCommandSequence(stateAfterSequentialCommands, cmds) + }).filter(_.nonEmpty) - Shrink.shrinkWithOrig[State](as.s)(shrinkState) flatMap { state => - shrinkedCmds.map(_.copy(s = state)) + filteredParallelCommands match { + case List(singleThreadedContinuation) => + Actions(state, filteredSequentialCommands ++ singleThreadedContinuation, Nil) + case _ => + Actions(state, filteredSequentialCommands, filteredParallelCommands) } } From 742a32fcb56d07cc339aa7e72e387671165d7588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20K=C3=B6lker?= Date: Wed, 4 Mar 2020 08:11:55 +0100 Subject: [PATCH 2/2] Add new methods to MiMa's newMethods, supressing a warning --- project/MimaSettings.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/project/MimaSettings.scala b/project/MimaSettings.scala index 1d0764d53..bfc57c93f 100644 --- a/project/MimaSettings.scala +++ b/project/MimaSettings.scala @@ -16,6 +16,9 @@ object MimaSettings { ) private def newMethods = Seq( + "org.scalacheck.commands.Commands.shrinkCommand", + "org.scalacheck.commands.Commands.shrinkSequentialCommands", + "org.scalacheck.commands.Commands.shrinkParallelCommands", ) private def removedPrivateMethods = Seq(