From 0bea613d7e650f71cbe9d72e20505746fd11c833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20K=C3=B6lker?= Date: Wed, 30 Dec 2020 15:03:35 +0100 Subject: [PATCH 1/3] Add a few tests of tuple shrinking (and throw in a typo fix) --- .../org/scalacheck/ShrinkSpecification.scala | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/test/scala/org/scalacheck/ShrinkSpecification.scala b/src/test/scala/org/scalacheck/ShrinkSpecification.scala index 97c2a4543..e628e4081 100644 --- a/src/test/scala/org/scalacheck/ShrinkSpecification.scala +++ b/src/test/scala/org/scalacheck/ShrinkSpecification.scala @@ -102,6 +102,40 @@ object ShrinkSpecification extends Properties("Shrink") { shrink(e).forall(_.isRight) } + property("shrink[Unit].isEmpty") = Prop(shrink(()).isEmpty) + + // Tuples shrink even when one component doesn't + property("shrink[(T, Unit)] eqv shrink[T]") = + forAllNoShrink { (u: Unit, i: Int) => + shrink(((), i)) == shrink(i).map(((), _)) + } + + property("shrink[(Unit, T)] eqv shrink[T]") = + forAllNoShrink { (i: Int, u: Unit) => + shrink((i, ())) == shrink(i).map((_, ())) + } + + // Tuple shrinking is associative* for all arities, and can be inductively + // defined for n in terms of 2 and n-1. (* modulo ordering) + def eqvTupleShrinks[T: Ordering](xs: Stream[T], ys: Stream[T]): Boolean = + xs.toList.sorted == ys.toList.sorted + + property("shrink[(T, U, V)] eqv shrink[(T, (U, V))]") = + forAllNoShrink { (b: Byte, c: Char, s: Short) => + eqvTupleShrinks( + shrink((b, c, s)), + shrink((b, (c, s))).map { case (b, (c, s)) => (b, c, s) } + ) + } + + property("shrink[(T, U, V, W)] eqv shrink[((T, U, V), W)]") = + forAllNoShrink { (b: Byte, c: Char, s: Short, i: Int) => + eqvTupleShrinks( + shrink((b, c, s, i)), + shrink(((b, c, s), i)).map { case ((b, c, s), i) => (b, c, s, i) } + ) + } + property("suchThat") = { implicit def shrinkEvenLength[A]: Shrink[List[A]] = Shrink.shrinkContainer[List,A].suchThat(evenLength _) @@ -116,7 +150,7 @@ object ShrinkSpecification extends Properties("Shrink") { def shrinkEvenLength[A]: Shrink[List[A]] = Shrink.shrinkContainer[List,A].suchThat(evenLength _) - property("shrink[List[Int].suchThat") = { + property("shrink[List[Int]].suchThat") = { forAll { (l: List[Int]) => shrink(l)(shrinkEvenLength).forall(evenLength _) } From 97580149a827ae1176988216ad36adfbdfedb482 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20K=C3=B6lker?= Date: Wed, 30 Dec 2020 15:47:04 +0100 Subject: [PATCH 2/3] Add shrinkers for tuples of size 10 through 22 --- project/codegen.scala | 35 +++++++ src/main/scala/org/scalacheck/Shrink.scala | 104 +-------------------- 2 files changed, 36 insertions(+), 103 deletions(-) diff --git a/project/codegen.scala b/project/codegen.scala index 8c5bccf21..4602d2a09 100644 --- a/project/codegen.scala +++ b/project/codegen.scala @@ -50,6 +50,27 @@ object codegen { def fntype(i: Int) = s"(${types(i)}) => Z" + def shrinkTuple(i: Int): String = + s"""| /** Shrink instance of ${i}-tuple */ + | implicit def shrinkTuple${i}[ + | ${shrinkTypes(i)} + | ]: Shrink[(${types(i)})] = + | Shrink { case (${vals(i)}) => + | ${shrinkEachComponentOneByOne(i)} + | }""".stripMargin + + def shrinkTypes(n: Int): String = + (1 to n).map(i => s"T${i}:Shrink").mkString(", ") + + def shrinkEachComponentOneByOne(n: Int): String = + (1 to n).map(shrinkSingleComponent(n, _)).mkString(" append\n ") + + def shrinkSingleComponent(n: Int, i: Int): String = + s"""Shrink.shrink(t${i}).map((${partialShrinkTuple(n, i)}))""" + + def partialShrinkTuple(n: Int, i: Int): String = + (1 to n).map(k => if (k == i) "_" else s"t${k}").mkString(", ") + def arbfn(i: Int) = s""" | /** Arbitrary instance of Function${i} */ | implicit def arbFunction${i}[${types(i)},Z](implicit g: Arbitrary[Z], ${coImplicits(i)}): Arbitrary[${fntype(i)}] = @@ -158,6 +179,20 @@ object codegen { } val genAll: Seq[GeneratedFile] = Seq( + GeneratedFile( + "ShrinkArities.scala", + s"""/** + |Defines implicit [[org.scalacheck.Shrink]] instances for tuples + | + |Auto-generated using project/codegen.scala + |*/ + |package org.scalacheck + | + |private[scalacheck] trait ShrinkArities{ + | + |${2 to 22 map shrinkTuple mkString("\n\n")} + |} + |""".stripMargin), GeneratedFile( "ArbitraryArities.scala", s"""/** diff --git a/src/main/scala/org/scalacheck/Shrink.scala b/src/main/scala/org/scalacheck/Shrink.scala index 63e714e83..3c5382e5f 100644 --- a/src/main/scala/org/scalacheck/Shrink.scala +++ b/src/main/scala/org/scalacheck/Shrink.scala @@ -29,7 +29,7 @@ trait ShrinkLowPriority { implicit def shrinkAny[T]: Shrink[T] = Shrink(_ => Stream.empty) } -object Shrink extends ShrinkLowPriority with ShrinkVersionSpecific { +object Shrink extends ShrinkLowPriority with ShrinkVersionSpecific with ShrinkArities { import Stream.{cons, empty} import scala.collection._ @@ -114,108 +114,6 @@ object Shrink extends ShrinkLowPriority with ShrinkVersionSpecific { case Some(x) => cons(None, for(y <- shrink(x)) yield Some(y)) } - /** Shrink instance of 2-tuple */ - implicit def shrinkTuple2[ - T1:Shrink, T2:Shrink - ]: Shrink[(T1,T2)] = - Shrink { case (t1,t2) => - shrink(t1).map((_,t2)) append - shrink(t2).map((t1,_)) - } - - /** Shrink instance of 3-tuple */ - implicit def shrinkTuple3[ - T1:Shrink, T2:Shrink, T3:Shrink - ]: Shrink[(T1,T2,T3)] = - Shrink { case (t1,t2,t3) => - shrink(t1).map((_, t2, t3)) append - shrink(t2).map((t1, _, t3)) append - shrink(t3).map((t1, t2, _)) - } - - /** Shrink instance of 4-tuple */ - implicit def shrinkTuple4[ - T1:Shrink, T2:Shrink, T3:Shrink, T4:Shrink - ]: Shrink[(T1,T2,T3,T4)] = - Shrink { case (t1,t2,t3,t4) => - shrink(t1).map((_, t2, t3, t4)) append - shrink(t2).map((t1, _, t3, t4)) append - shrink(t3).map((t1, t2, _, t4)) append - shrink(t4).map((t1, t2, t3, _)) - } - - /** Shrink instance of 5-tuple */ - implicit def shrinkTuple5[ - T1:Shrink, T2:Shrink, T3:Shrink, T4:Shrink, T5:Shrink - ]: Shrink[(T1,T2,T3,T4,T5)] = - Shrink { case (t1,t2,t3,t4,t5) => - shrink(t1).map((_, t2, t3, t4, t5)) append - shrink(t2).map((t1, _, t3, t4, t5)) append - shrink(t3).map((t1, t2, _, t4, t5)) append - shrink(t4).map((t1, t2, t3, _, t5)) append - shrink(t5).map((t1, t2, t3, t4, _)) - } - - /** Shrink instance of 6-tuple */ - implicit def shrinkTuple6[ - T1:Shrink, T2:Shrink, T3:Shrink, T4:Shrink, T5:Shrink, T6:Shrink - ]: Shrink[(T1,T2,T3,T4,T5,T6)] = - Shrink { case (t1,t2,t3,t4,t5,t6) => - shrink(t1).map((_, t2, t3, t4, t5, t6)) append - shrink(t2).map((t1, _, t3, t4, t5, t6)) append - shrink(t3).map((t1, t2, _, t4, t5, t6)) append - shrink(t4).map((t1, t2, t3, _, t5, t6)) append - shrink(t5).map((t1, t2, t3, t4, _, t6)) append - shrink(t6).map((t1, t2, t3, t4, t5, _)) - } - - /** Shrink instance of 7-tuple */ - implicit def shrinkTuple7[ - T1:Shrink, T2:Shrink, T3:Shrink, T4:Shrink, T5:Shrink, T6:Shrink, T7:Shrink - ]: Shrink[(T1,T2,T3,T4,T5,T6,T7)] = - Shrink { case (t1,t2,t3,t4,t5,t6,t7) => - shrink(t1).map((_, t2, t3, t4, t5, t6, t7)) append - shrink(t2).map((t1, _, t3, t4, t5, t6, t7)) append - shrink(t3).map((t1, t2, _, t4, t5, t6, t7)) append - shrink(t4).map((t1, t2, t3, _, t5, t6, t7)) append - shrink(t5).map((t1, t2, t3, t4, _, t6, t7)) append - shrink(t6).map((t1, t2, t3, t4, t5, _, t7)) append - shrink(t7).map((t1, t2, t3, t4, t5, t6, _)) - } - - /** Shrink instance of 8-tuple */ - implicit def shrinkTuple8[ - T1:Shrink, T2:Shrink, T3:Shrink, T4:Shrink, T5:Shrink, T6:Shrink, - T7:Shrink, T8:Shrink - ]: Shrink[(T1,T2,T3,T4,T5,T6,T7,T8)] = - Shrink { case (t1,t2,t3,t4,t5,t6,t7,t8) => - shrink(t1).map((_, t2, t3, t4, t5, t6, t7, t8)) append - shrink(t2).map((t1, _, t3, t4, t5, t6, t7, t8)) append - shrink(t3).map((t1, t2, _, t4, t5, t6, t7, t8)) append - shrink(t4).map((t1, t2, t3, _, t5, t6, t7, t8)) append - shrink(t5).map((t1, t2, t3, t4, _, t6, t7, t8)) append - shrink(t6).map((t1, t2, t3, t4, t5, _, t7, t8)) append - shrink(t7).map((t1, t2, t3, t4, t5, t6, _, t8)) append - shrink(t8).map((t1, t2, t3, t4, t5, t6, t7, _)) - } - - /** Shrink instance of 9-tuple */ - implicit def shrinkTuple9[ - T1:Shrink, T2:Shrink, T3:Shrink, T4:Shrink, T5:Shrink, T6:Shrink, - T7:Shrink, T8:Shrink, T9:Shrink - ]: Shrink[(T1,T2,T3,T4,T5,T6,T7,T8,T9)] = - Shrink { case (t1,t2,t3,t4,t5,t6,t7,t8,t9) => - shrink(t1).map((_, t2, t3, t4, t5, t6, t7, t8, t9)) append - shrink(t2).map((t1, _, t3, t4, t5, t6, t7, t8, t9)) append - shrink(t3).map((t1, t2, _, t4, t5, t6, t7, t8, t9)) append - shrink(t4).map((t1, t2, t3, _, t5, t6, t7, t8, t9)) append - shrink(t5).map((t1, t2, t3, t4, _, t6, t7, t8, t9)) append - shrink(t6).map((t1, t2, t3, t4, t5, _, t7, t8, t9)) append - shrink(t7).map((t1, t2, t3, t4, t5, t6, _, t8, t9)) append - shrink(t8).map((t1, t2, t3, t4, t5, t6, t7, _, t9)) append - shrink(t9).map((t1, t2, t3, t4, t5, t6, t7, t8, _)) - } - implicit def shrinkEither[T1:Shrink, T2:Shrink]: Shrink[Either[T1, T2]] = Shrink { x => x.fold(shrink(_).map(Left(_)), shrink(_).map(Right(_))) From 897e9b250c083dfda1d049393456ad1f416da995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20K=C3=B6lker?= Date: Tue, 20 Jul 2021 00:54:51 +0200 Subject: [PATCH 3/3] Placate MiMa about signatures of moved methods --- project/MimaSettings.scala | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/project/MimaSettings.scala b/project/MimaSettings.scala index d8fa47d62..bf581aae8 100644 --- a/project/MimaSettings.scala +++ b/project/MimaSettings.scala @@ -25,6 +25,14 @@ object MimaSettings { ) private def otherProblems = Seq( - ) + "org.scalacheck.Shrink.shrinkTuple2", + "org.scalacheck.Shrink.shrinkTuple3", + "org.scalacheck.Shrink.shrinkTuple4", + "org.scalacheck.Shrink.shrinkTuple5", + "org.scalacheck.Shrink.shrinkTuple6", + "org.scalacheck.Shrink.shrinkTuple7", + "org.scalacheck.Shrink.shrinkTuple8", + "org.scalacheck.Shrink.shrinkTuple9" + ).map(m => ProblemFilters.exclude[IncompatibleSignatureProblem](m)) }