diff --git a/bench/src/main/scala/org/scalacheck/bench/GenBench.scala b/bench/src/main/scala/org/scalacheck/bench/GenBench.scala index f5eb4d428..e281c0642 100644 --- a/bench/src/main/scala/org/scalacheck/bench/GenBench.scala +++ b/bench/src/main/scala/org/scalacheck/bench/GenBench.scala @@ -3,6 +3,7 @@ package org.scalacheck.bench import java.util.concurrent.TimeUnit import org.openjdk.jmh.annotations._ +import org.scalacheck.Arbitrary.arbitrary import org.scalacheck.Gen import org.scalacheck.rng.Seed @@ -110,6 +111,10 @@ class GenBench { def asciiPrintableStr(): List[String] = seeds.map(s => Gen.asciiPrintableStr.pureApply(params, s)) + @Benchmark + def arbitraryString(): List[String] = + seeds.map(s => arbitrary[String].pureApply(params, s)) + private val gens = Vector.fill(10)(genInt) @Benchmark @@ -146,4 +151,12 @@ class GenBench { val gf = Gen.frequency(1 -> g, 2 -> g, 3 -> g, 4 -> g, 5 -> g) gf.pureApply(params, s) } + + private def fg = Gen.choose(1, 100).filter(_ >= 3) + + @Benchmark + def testFilter(): List[List[Int]] = + seeds.map { s => + Gen.listOfN(100, fg).pureApply(params, s) + } } diff --git a/project/codegen.scala b/project/codegen.scala index d2fde682f..7ba39cfaf 100644 --- a/project/codegen.scala +++ b/project/codegen.scala @@ -84,20 +84,13 @@ object codegen { def zip(i: Int) = { val gens = flatMappedGenerators(i, idents("t",i) zip idents("g",i)) - - def sieveCopy = idents("g",i) zip idents("t",i) map { case (g,t) => s"$g.sieveCopy($t)" } mkString " && " s""" | /** Combines the given generators into one generator that produces a | * tuple of their generated values. */ | def zip[${types(i)}]( | ${wrappedArgs("Gen",i)} - | ): Gen[(${types(i)})] = { - | val g = $gens - | g.suchThat { - | case (${vals(i)}) => - | ${sieveCopy} - | } - | } + | ): Gen[(${types(i)})] = + | $gens |""".stripMargin } diff --git a/src/main/scala/org/scalacheck/Arbitrary.scala b/src/main/scala/org/scalacheck/Arbitrary.scala index e2a1ee152..36473706a 100644 --- a/src/main/scala/org/scalacheck/Arbitrary.scala +++ b/src/main/scala/org/scalacheck/Arbitrary.scala @@ -80,13 +80,6 @@ private[scalacheck] sealed trait ArbitraryLowPriority { /**** Arbitrary instances for each AnyVal ****/ - /** Arbitrary AnyVal */ - implicit lazy val arbAnyVal: Arbitrary[AnyVal] = Arbitrary(oneOf( - arbitrary[Unit], arbitrary[Boolean], arbitrary[Char], arbitrary[Byte], - arbitrary[Short], arbitrary[Int], arbitrary[Long], arbitrary[Float], - arbitrary[Double] - )) - /** Arbitrary instance of Boolean */ implicit lazy val arbBool: Arbitrary[Boolean] = Arbitrary(oneOf(true, false)) @@ -120,37 +113,33 @@ private[scalacheck] sealed trait ArbitraryLowPriority { ) /** Arbitrary instance of Char */ - implicit lazy val arbChar: Arbitrary[Char] = Arbitrary { - // exclude 0xFFFE due to this bug: https://bit.ly/2pTpAu8 - // also exclude 0xFFFF as it is not unicode: http://bit.ly/2cVBrzK - val validRangesInclusive = List[(Char, Char)]( - (0x0000, 0xD7FF), - (0xE000, 0xFFFD) - ) - - Gen.frequency((validRangesInclusive.map { - case (first, last) => (last + 1 - first, Gen.choose[Char](first, last)) - }: List[(Int, Gen[Char])]): _*) - } + implicit lazy val arbChar: Arbitrary[Char] = + Arbitrary(Gen.unicodeChar) /** Arbitrary instance of Byte */ - implicit lazy val arbByte: Arbitrary[Byte] = Arbitrary( - Gen.chooseNum(Byte.MinValue, Byte.MaxValue) - ) + implicit lazy val arbByte: Arbitrary[Byte] = + Arbitrary(Gen.chooseNum(Byte.MinValue, Byte.MaxValue)) /** Arbitrary instance of Short */ - implicit lazy val arbShort: Arbitrary[Short] = Arbitrary( - Gen.chooseNum(Short.MinValue, Short.MaxValue) - ) + implicit lazy val arbShort: Arbitrary[Short] = + Arbitrary(Gen.chooseNum(Short.MinValue, Short.MaxValue)) /** Absolutely, totally, 100% arbitrarily chosen Unit. */ - implicit lazy val arbUnit: Arbitrary[Unit] = Arbitrary(const(())) + implicit lazy val arbUnit: Arbitrary[Unit] = + Arbitrary(Gen.const(())) + + /** Arbitrary AnyVal */ + implicit lazy val arbAnyVal: Arbitrary[AnyVal] = Arbitrary(oneOf( + arbitrary[Unit], arbitrary[Boolean], arbitrary[Char], arbitrary[Byte], + arbitrary[Short], arbitrary[Int], arbitrary[Long], arbitrary[Float], + arbitrary[Double] + )) /**** Arbitrary instances of other common types ****/ /** Arbitrary instance of String */ implicit lazy val arbString: Arbitrary[String] = - Arbitrary(arbitrary[List[Char]] map (_.mkString)) + Arbitrary(Gen.unicodeStr) /** Arbitrary instance of Date */ implicit lazy val arbDate: Arbitrary[java.util.Date] = diff --git a/src/main/scala/org/scalacheck/Gen.scala b/src/main/scala/org/scalacheck/Gen.scala index 17e322d4b..31ba3491b 100644 --- a/src/main/scala/org/scalacheck/Gen.scala +++ b/src/main/scala/org/scalacheck/Gen.scala @@ -33,21 +33,21 @@ sealed abstract class Gen[+T] extends Serializable { self => /** Just an alias */ private type P = Gen.Parameters - /** Should be a copy of R.sieve. Used internally in Gen when some generators - * with suchThat-clause are created (when R is not available). This method - * actually breaks covariance, but since this method will only ever be - * called with a value of exactly type T, it is OK. */ + // This is no long used but preserved here for binary compatibility. private[scalacheck] def sieveCopy(x: Any): Boolean = true + // If you implement new Gen[_] directly (instead of using + // combinators), make sure to use p.initialSeed or p.useInitialSeed + // in the implementation, instead of using seed directly. private[scalacheck] def doApply(p: P, seed: Seed): R[T] //// Public interface //// /** A class supporting filtered operations. */ final class WithFilter(p: T => Boolean) { - def map[U](f: T => U): Gen[U] = Gen.this.suchThat(p).map(f) - def flatMap[U](f: T => Gen[U]): Gen[U] = Gen.this.suchThat(p).flatMap(f) - def withFilter(q: T => Boolean): WithFilter = Gen.this.withFilter(x => p(x) && q(x)) + def map[U](f: T => U): Gen[U] = self.suchThat(p).map(f) + def flatMap[U](f: T => Gen[U]): Gen[U] = self.suchThat(p).flatMap(f) + def withFilter(q: T => Boolean): WithFilter = self.withFilter(x => p(x) && q(x)) } /** Evaluate this generator with the given parameters */ @@ -104,16 +104,14 @@ sealed abstract class Gen[+T] extends Serializable { self => * the generator fails (returns None). Also, make sure that the provided * test property is side-effect free, e.g. it should not use external vars. * This method is identical to [Gen.filter]. */ - def suchThat(f: T => Boolean): Gen[T] = new Gen[T] { - def doApply(p: P, seed: Seed) = - p.useInitialSeed(seed) { (p0, s0) => - val res = Gen.this.doApply(p0, s0) - res.copy(s = { x:T => res.sieve(x) && f(x) }) - } - override def sieveCopy(x: Any) = - try Gen.this.sieveCopy(x) && f(x.asInstanceOf[T]) - catch { case _: java.lang.ClassCastException => false } - } + def suchThat(f: T => Boolean): Gen[T] = + new Gen[T] { + def doApply(p: P, seed: Seed): Gen.R[T] = + p.useInitialSeed(seed) { (p0, s0) => + val r = self.doApply(p0, s0) + r.copy(r = r.retrieve.filter(f)) + } + } case class RetryUntilException(n: Int) extends RuntimeException(s"retryUntil failed after $n attempts") @@ -156,7 +154,7 @@ sealed abstract class Gen[+T] extends Serializable { self => /** Returns a new property that holds if and only if both this * and the given generator generates the same result, or both * generators generate no result. */ - def ==[U](g: Gen[U]) = Prop { prms => + def ==[U](g: Gen[U]): Prop = Prop { prms => // test equality using a random seed val seed = Seed.random() val lhs = doApply(prms, seed).retrieve @@ -164,9 +162,10 @@ sealed abstract class Gen[+T] extends Serializable { self => if (lhs == rhs) Prop.proved(prms) else Prop.falsified(prms) } - def !=[U](g: Gen[U]) = Prop.forAll(this)(r => Prop.forAll(g)(_ != r)) + def !=[U](g: Gen[U]): Prop = + Prop.forAll(this)(r => Prop.forAll(g)(_ != r)) - def !==[U](g: Gen[U]) = Prop { prms => + def !==[U](g: Gen[U]): Prop = Prop { prms => // test inequality using a random seed val seed = Seed.random() val lhs = doApply(prms, seed).retrieve @@ -178,23 +177,22 @@ sealed abstract class Gen[+T] extends Serializable { self => def label(l: String): Gen[T] = new Gen[T] { def doApply(p: P, seed: Seed) = p.useInitialSeed(seed) { (p0, s0) => - val r = Gen.this.doApply(p0, s0) + val r = self.doApply(p0, s0) r.copy(l = r.labels + l) } - override def sieveCopy(x: Any) = Gen.this.sieveCopy(x) } /** Put a label on the generator to make test reports clearer */ - def :|(l: String) = label(l) + def :|(l: String): Gen[T] = label(l) /** Put a label on the generator to make test reports clearer */ - def |:(l: String) = label(l) + def |:(l: String): Gen[T] = label(l) /** Put a label on the generator to make test reports clearer */ - def :|(l: Symbol) = label(l.name) + def :|(l: Symbol): Gen[T] = label(l.name) /** Put a label on the generator to make test reports clearer */ - def |:(l: Symbol) = label(l.name) + def |:(l: Symbol): Gen[T] = label(l.name) /** Perform some RNG perturbation before generating */ def withPerturb(f: Seed => Seed): Gen[T] = @@ -214,35 +212,36 @@ object Gen extends GenArities with GenVersionSpecific { private[scalacheck] trait R[+T] { def labels: Set[String] = Set() - def sieve[U >: T]: U => Boolean = _ => true + // sieve is no longer used but preserved for binary compatibility + final def sieve[U >: T]: U => Boolean = (_: U) => true protected def result: Option[T] def seed: Seed - def retrieve: Option[T] = result.filter(sieve) + def retrieve: Option[T] = result def copy[U >: T]( l: Set[String] = this.labels, + // s is no longer used but preserved for binary compatibility s: U => Boolean = this.sieve, r: Option[U] = this.result, sd: Seed = this.seed ): R[U] = new R[U] { override val labels = l - override def sieve[V >: U] = { (x: Any) => - try s(x.asInstanceOf[U]) - catch { case _: java.lang.ClassCastException => false } - } val seed = sd val result = r } - def map[U](f: T => U): R[U] = r(retrieve.map(f), seed).copy(l = labels) + def map[U](f: T => U): R[U] = + r(retrieve.map(f), seed).copy(l = labels) - def flatMap[U](f: T => R[U]): R[U] = retrieve match { - case None => r(None, seed).copy(l = labels) - case Some(t) => - val r = f(t) - r.copy(l = labels ++ r.labels, sd = r.seed) - } + def flatMap[U](f: T => R[U]): R[U] = + retrieve match { + case None => + r(None, seed).copy(l = labels) + case Some(t) => + val r = f(t) + r.copy(l = labels | r.labels, sd = r.seed) + } } private[scalacheck] def r[T](r: Option[T], sd: Seed): R[T] = new R[T] { @@ -473,7 +472,6 @@ object Gen extends GenArities with GenVersionSpecific { private[scalacheck] def failed[T](seed0: Seed): R[T] = new R[T] { val result: Option[T] = None - override def sieve[U >: T]: U => Boolean = _ => false val seed = seed0 } @@ -485,7 +483,7 @@ object Gen extends GenArities with GenVersionSpecific { /** Sequences generators. If any of the given generators fails, the * resulting generator will also fail. */ - def sequence[C,T](gs: Traversable[Gen[T]])(implicit b: Buildable[T,C]): Gen[C] = { + def sequence[C, T](gs: Traversable[Gen[T]])(implicit b: Buildable[T, C]): Gen[C] = { val g = gen { (p, seed) => gs.foldLeft(r(Some(Vector.empty[T]), seed)) { case (rs,g) => @@ -577,7 +575,7 @@ object Gen extends GenArities with GenVersionSpecific { /** Picks a random generator from a list */ def oneOf[T](g0: Gen[T], g1: Gen[T], gn: Gen[T]*): Gen[T] = { val gs = g0 +: g1 +: gn - choose(0,gs.size-1).flatMap(gs(_)).suchThat(x => gs.exists(_.sieveCopy(x))) + choose(0, gs.size - 1).flatMap(i => gs(i)) } /** Makes a generator result optional. Either `Some(T)` or `None` will be provided. */ @@ -598,16 +596,14 @@ object Gen extends GenArities with GenVersionSpecific { if (filtered.isEmpty) { throw new IllegalArgumentException("no items with positive weights") } else { - var total = 0L + var total = 0L val builder = TreeMap.newBuilder[Long, Gen[T]] filtered.foreach { case (weight, value) => total += weight builder += ((total, value)) } val tree = builder.result - choose(1L, total).flatMap(r => tree.rangeFrom(r).head._2).suchThat { x => - gs.exists(_._2.sieveCopy(x)) - } + choose(1L, total).flatMap(r => tree.rangeFrom(r).head._2) } } @@ -631,11 +627,31 @@ object Gen extends GenArities with GenVersionSpecific { * complete container generator will also fail. */ def buildableOfN[C,T](n: Int, g: Gen[T])(implicit evb: Buildable[T,C], evt: C => Traversable[T] - ): Gen[C] = - sequence[C,T](Traversable.fill(n)(g)) suchThat { c => - // TODO: Can we guarantee c.size == n (See issue #89)? - evt(c).forall(g.sieveCopy) + ): Gen[C] = { + require(n >= 0, s"invalid size given: $n") + new Gen[C] { + def doApply(p: P, seed0: Seed): R[C] = { + var seed: Seed = p.initialSeed.getOrElse(seed0) + val bldr = evb.builder + val allowedFailures = Gen.collectionRetries(n) + var failures = 0 + var count = 0 + while (count < n) { + val res = g.doApply(p, seed) + res.retrieve match { + case Some(t) => + bldr += t + count += 1 + case None => + failures += 1 + if (failures >= allowedFailures) return r(None, res.seed) + } + seed = res.seed + } + r(Some(bldr.result), seed) + } } + } /** Generates a container of any Traversable type for which there exists an * implicit [[org.scalacheck.util.Buildable]] instance. The elements in the @@ -644,9 +660,8 @@ object Gen extends GenArities with GenVersionSpecific { def buildableOf[C,T](g: Gen[T])(implicit evb: Buildable[T,C], evt: C => Traversable[T] ): Gen[C] = - sized(s => choose(0, s max 0).flatMap(buildableOfN[C,T](_,g))) suchThat { c => - if (c == null) g.sieveCopy(null) else evt(c).forall(g.sieveCopy) - } + sized(s => choose(0, Integer.max(s, 0))) + .flatMap(n => buildableOfN(n, g)(evb, evt)) /** Generates a non-empty container of any Traversable type for which there * exists an implicit [[org.scalacheck.util.Buildable]] instance. The @@ -656,80 +671,91 @@ object Gen extends GenArities with GenVersionSpecific { def nonEmptyBuildableOf[C,T](g: Gen[T])(implicit evb: Buildable[T,C], evt: C => Traversable[T] ): Gen[C] = - sized(s => choose(1, s max 1).flatMap(buildableOfN[C,T](_,g))) suchThat(c => evt(c).size > 0) + buildableOf(g)(evb, evt).suchThat(c => evt(c).size > 0) /** A convenience method for calling `buildableOfN[C[T],T](n,g)`. */ def containerOfN[C[_],T](n: Int, g: Gen[T])(implicit evb: Buildable[T,C[T]], evt: C[T] => Traversable[T] - ): Gen[C[T]] = buildableOfN[C[T],T](n,g) + ): Gen[C[T]] = buildableOfN[C[T], T](n, g)(evb, evt) /** A convenience method for calling `buildableOf[C[T],T](g)`. */ def containerOf[C[_],T](g: Gen[T])(implicit evb: Buildable[T,C[T]], evt: C[T] => Traversable[T] - ): Gen[C[T]] = buildableOf[C[T],T](g) + ): Gen[C[T]] = buildableOf[C[T], T](g)(evb, evt) /** A convenience method for calling `nonEmptyBuildableOf[C[T],T](g)`. */ def nonEmptyContainerOf[C[_],T](g: Gen[T])(implicit evb: Buildable[T,C[T]], evt: C[T] => Traversable[T] - ): Gen[C[T]] = nonEmptyBuildableOf[C[T],T](g) + ): Gen[C[T]] = nonEmptyBuildableOf[C[T], T](g)(evb, evt) /** Generates a list of random length. The maximum length depends on the * size parameter. This method is equal to calling * `containerOf[List,T](g)`. */ - def listOf[T](g: => Gen[T]) = buildableOf[List[T],T](g) + def listOf[T](g: => Gen[T]) = buildableOf[List[T], T](g) /** Generates a non-empty list of random length. The maximum length depends * on the size parameter. This method is equal to calling * `nonEmptyContainerOf[List,T](g)`. */ - def nonEmptyListOf[T](g: => Gen[T]) = nonEmptyBuildableOf[List[T],T](g) + def nonEmptyListOf[T](g: => Gen[T]) = nonEmptyBuildableOf[List[T], T](g) /** Generates a list with at most the given number of elements. This method * is equal to calling `containerOfN[List,T](n,g)`. */ - def listOfN[T](n: Int, g: Gen[T]) = buildableOfN[List[T],T](n,g) + def listOfN[T](n: Int, g: Gen[T]) = buildableOfN[List[T], T](n, g) /** Generates a map of random length. The maximum length depends on the * size parameter. This method is equal to calling * containerOf[Map,(T,U)](g). */ - def mapOf[T,U](g: => Gen[(T,U)]) = buildableOf[Map[T,U],(T,U)](g) + def mapOf[T, U](g: => Gen[(T, U)]) = buildableOf[Map[T, U], (T, U)](g) /** Generates a non-empty map of random length. The maximum length depends * on the size parameter. This method is equal to calling * nonEmptyContainerOf[Map,(T,U)](g). */ - def nonEmptyMap[T,U](g: => Gen[(T,U)]) = nonEmptyBuildableOf[Map[T,U],(T,U)](g) + def nonEmptyMap[T,U](g: => Gen[(T,U)]) = nonEmptyBuildableOf[Map[T, U],(T, U)](g) /** Generates a map with at most the given number of elements. This method * is equal to calling containerOfN[Map,(T,U)](n,g). */ - def mapOfN[T,U](n: Int, g: Gen[(T,U)]) = buildableOfN[Map[T,U],(T,U)](n,g) + def mapOfN[T,U](n: Int, g: Gen[(T, U)]) = buildableOfN[Map[T, U],(T, U)](n, g) - /** Generates an infinite stream. */ + /** + * Generates an infinite stream. + * + * Failures in the underlying generator may terminate the stream. + * Otherwise it will continue forever. + */ def infiniteStream[T](g: => Gen[T]): Gen[Stream[T]] = { - def unfold[A, S](z: S)(f: S => Option[(A, S)]): Stream[A] = f(z) match { - case Some((h, s)) => h #:: unfold(s)(f) - case None => Stream.empty - } - gen { (p, seed0) => - new R[Stream[T]] { - val result: Option[Stream[T]] = Some(unfold(seed0)(s => Some(g.pureApply(p, s) -> s.next))) - val seed: Seed = seed0.next + val attemptsPerItem = 10 + def unfold(p: P, seed: Seed, attemptsLeft: Int): Stream[T] = + if (attemptsLeft <= 0) { + Stream.empty + } else { + val r = g.doPureApply(p, seed) + r.retrieve match { + case Some(t) => t #:: unfold(p, r.seed, attemptsPerItem) + case None => unfold(p, r.seed, attemptsLeft - 1) + } } + gen { (p, seed0) => + val stream = unfold(p, seed0, attemptsPerItem) + r(Some(stream), seed0.slide) } } /** A generator that picks a random number of elements from a list */ - def someOf[T](l: Iterable[T]) = choose(0,l.size).flatMap(pick(_,l)) + def someOf[T](l: Iterable[T]): Gen[collection.Seq[T]] = + choose(0, l.size).flatMap(pick(_,l)) /** A generator that picks a random number of elements from a list */ - def someOf[T](g1: Gen[T], g2: Gen[T], gs: Gen[T]*) = + def someOf[T](g1: Gen[T], g2: Gen[T], gs: Gen[T]*): Gen[collection.Seq[T]] = choose(0, gs.length+2).flatMap(pick(_, g1, g2, gs: _*)) /** A generator that picks at least one element from a list */ - def atLeastOne[T](l: Iterable[T]) = { + def atLeastOne[T](l: Iterable[T]): Gen[collection.Seq[T]] = { require(l.size > 0, "There has to be at least one option to choose from") choose(1,l.size).flatMap(pick(_,l)) } /** A generator that picks at least one element from a list */ - def atLeastOne[T](g1: Gen[T], g2: Gen[T], gs: Gen[T]*) = + def atLeastOne[T](g1: Gen[T], g2: Gen[T], gs: Gen[T]*): Gen[collection.Seq[T]] = choose(1, gs.length+2).flatMap(pick(_, g1, g2, gs: _*)) /** A generator that randomly picks a given number of elements from a list @@ -751,7 +777,7 @@ object Gen extends GenArities with GenVersionSpecific { buf += t } else { val (x, s) = seed.long - val i = (x & 0x7fffffff).toInt % count + val i = (x & Long.MaxValue % count).toInt if (i < n) buf(i) = t seed = s } @@ -764,12 +790,8 @@ object Gen extends GenArities with GenVersionSpecific { * * The elements are not guaranteed to be permuted in random order. */ - def pick[T](n: Int, g1: Gen[T], g2: Gen[T], gn: Gen[T]*): Gen[Seq[T]] = { - val gs = g1 +: g2 +: gn - pick(n, 0 until gs.size).flatMap(idxs => - sequence[List[T],T](idxs.toList.map(gs(_))) - ).suchThat(_.forall(x => gs.exists(_.sieveCopy(x)))) - } + def pick[T](n: Int, g1: Gen[T], g2: Gen[T], gn: Gen[T]*): Gen[Seq[T]] = + sequence[Seq[T], T](g1 +: g2 +: gn) /** Takes a function and returns a generator that generates arbitrary * results of that function by feeding it with arbitrarily generated input @@ -784,78 +806,139 @@ object Gen extends GenArities with GenVersionSpecific { //// Character Generators //// + private def charSample(cs: Array[Char]): Gen[Char] = + new Gen[Char] { + def doApply(p: P, seed0: Seed): Gen.R[Char] = { + val seed1 = p.initialSeed.getOrElse(seed0) + val (x, seed2) = seed1.long + val i = ((x & Long.MaxValue) % cs.length).toInt + r(Some(cs(i)), seed2) + } + } + /** Generates a numerical character */ - def numChar: Gen[Char] = choose(48.toChar, 57.toChar) + val numChar: Gen[Char] = + charSample(('0' to '9').toArray) /** Generates an upper-case alpha character */ - def alphaUpperChar: Gen[Char] = choose(65.toChar, 90.toChar) + val alphaUpperChar: Gen[Char] = + charSample(('A' to 'Z').toArray) /** Generates a lower-case alpha character */ - def alphaLowerChar: Gen[Char] = choose(97.toChar, 122.toChar) + val alphaLowerChar: Gen[Char] = + charSample(('a' to 'z').toArray) /** Generates an alpha character */ - def alphaChar = frequency((1,alphaUpperChar), (9,alphaLowerChar)) + val alphaChar: Gen[Char] = + charSample((('A' to 'Z') ++ ('a' to 'z')).toArray) /** Generates an alphanumerical character */ - def alphaNumChar = frequency((1,numChar), (9,alphaChar)) + val alphaNumChar: Gen[Char] = + charSample((('0' to '9') ++ ('A' to 'Z') ++ ('a' to 'z')).toArray) /** Generates a ASCII character, with extra weighting for printable characters */ - def asciiChar: Gen[Char] = chooseNum(0, 127, 32 to 126:_*).map(_.toChar) + val asciiChar: Gen[Char] = + choose(0, 127).map(_.toChar) /** Generates a ASCII printable character */ - def asciiPrintableChar: Gen[Char] = choose(32.toChar, 126.toChar) + val asciiPrintableChar: Gen[Char] = + choose(32.toChar, 126.toChar) /** Generates a character that can represent a valid hexadecimal digit. This * includes both upper and lower case values. */ - def hexChar: Gen[Char] = - Gen.oneOf( - Gen.oneOf("0123456789abcdef".toSeq), - Gen.oneOf("0123456789ABCDEF".toSeq) - ) + val hexChar: Gen[Char] = + charSample("0123456789abcdef0123456789ABCDEF".toArray) + + // valid ranges are [0x0000, 0xD7FF] and [0xE000, 0xFFFD]. + // + // ((0xFFFD + 1) - 0xE000) + ((0xD7FF + 1) - 0x0000) + val unicodeChar: Gen[Char] = + choose(0, 63486).map { i => + if (i <= 0xD7FF) i.toChar + else (i + 2048).toChar + } //// String Generators //// + private def mkString(n: Int, sb: StringBuilder, gc: Gen[Char], p: P, seed0: Seed): R[String] = { + var seed: Seed = seed0 + val allowedFailures = Gen.collectionRetries(n) + var failures = 0 + var count = 0 + while (count < n) { + val res = gc.doApply(p, seed) + res.retrieve match { + case Some(c) => + sb += c + count += 1 + case None => + failures += 1 + if (failures >= allowedFailures) return r(None, res.seed) + } + seed = res.seed + } + r(Some(sb.toString), seed) + } + + def stringOfN(n: Int, gc: Gen[Char]): Gen[String] = + gen { (p, seed) => + mkString(n, new StringBuilder, gc, p, seed) + } + + def stringOf(gc: Gen[Char]): Gen[String] = + gen { (p, seed0) => + val (n, seed1) = Gen.mkSize(p, seed0) + mkString(n, new StringBuilder, gc, p, seed1) + } + /** Generates a string that starts with a lower-case alpha character, * and only contains alphanumerical characters */ - def identifier: Gen[String] = (for { - c <- alphaLowerChar - cs <- listOf(alphaNumChar) - } yield (c::cs).mkString) + val identifier: Gen[String] = + gen { (p, seed0) => + val (n, seed1) = Gen.mkSize(p, seed0) + val sb = new StringBuilder + val res1 = alphaLowerChar.doApply(p, seed1) + sb += res1.retrieve.get + mkString(n - 1, sb, alphaNumChar, p, res1.seed) + } /** Generates a string of digits */ - def numStr: Gen[String] = - listOf(numChar).map(_.mkString) + val numStr: Gen[String] = + stringOf(numChar) /** Generates a string of upper-case alpha characters */ - def alphaUpperStr: Gen[String] = - listOf(alphaUpperChar).map(_.mkString) + val alphaUpperStr: Gen[String] = + stringOf(alphaUpperChar) /** Generates a string of lower-case alpha characters */ - def alphaLowerStr: Gen[String] = - listOf(alphaLowerChar).map(_.mkString) + val alphaLowerStr: Gen[String] = + stringOf(alphaLowerChar) /** Generates a string of alpha characters */ - def alphaStr: Gen[String] = - listOf(alphaChar).map(_.mkString) + val alphaStr: Gen[String] = + stringOf(alphaChar) /** Generates a string of alphanumerical characters */ - def alphaNumStr: Gen[String] = - listOf(alphaNumChar).map(_.mkString) + val alphaNumStr: Gen[String] = + stringOf(alphaNumChar) /** Generates a string of ASCII characters, with extra weighting for printable characters */ - def asciiStr: Gen[String] = - listOf(asciiChar).map(_.mkString) + val asciiStr: Gen[String] = + stringOf(asciiChar) /** Generates a string of ASCII printable characters */ - def asciiPrintableStr: Gen[String] = - listOf(asciiPrintableChar).map(_.mkString) + val asciiPrintableStr: Gen[String] = + stringOf(asciiPrintableChar) /** Generates a string that can represent a valid hexadecimal digit. This * includes both upper and lower case values. */ - def hexStr: Gen[String] = - listOf(hexChar).map(_.mkString) + val hexStr: Gen[String] = + stringOf(hexChar) + + val unicodeStr: Gen[String] = + stringOf(unicodeChar) //// Number Generators //// @@ -993,4 +1076,15 @@ object Gen extends GenArities with GenVersionSpecific { 1 -> const(Duration.Undefined), 1 -> const(Duration.Zero), 6 -> finiteDuration) + + // used to compute a uniformly-distributed size + private def mkSize(p: Gen.Parameters, seed0: Seed): (Int, Seed) = { + val maxSize = Integer.max(p.size + 1, 1) + val (x, seed1) = seed0.long + (((x & Long.MaxValue) % maxSize).toInt, seed1) + } + + // used to calculate how many per-item retries we should allow. + private def collectionRetries(n: Int): Int = + Integer.max(10, n / 10) } diff --git a/src/main/scala/org/scalacheck/Prop.scala b/src/main/scala/org/scalacheck/Prop.scala index 2a5a77c5f..45e4281ae 100644 --- a/src/main/scala/org/scalacheck/Prop.scala +++ b/src/main/scala/org/scalacheck/Prop.scala @@ -779,7 +779,7 @@ object Prop { } def shrinker(x: T, r: Result, shrinks: Int, orig: T): Result = { - val xs = shrink(x).filter(gr.sieve) + val xs = shrink(x) val res = r.addArg(Arg(labels,x,shrinks,orig,pp(x),pp(orig))) if(xs.isEmpty) res else getFirstFailure(xs) match { case Right((x2,r2)) => res diff --git a/src/main/scala/org/scalacheck/util/Buildable.scala b/src/main/scala/org/scalacheck/util/Buildable.scala index f35b554f6..27a38aae6 100644 --- a/src/main/scala/org/scalacheck/util/Buildable.scala +++ b/src/main/scala/org/scalacheck/util/Buildable.scala @@ -22,33 +22,14 @@ trait Buildable[T,C] extends Serializable { object Buildable extends BuildableVersionSpecific { import java.util.ArrayList - implicit def buildableArrayList[T]: Buildable[T, ArrayList[T]] = new Buildable[T,ArrayList[T]] { - def builder = new ArrayListBuilder[T] - } -} - -/* -object Buildable2 { - - implicit def buildableMutableMap[T,U] = new Buildable2[T,U,mutable.Map] { - def builder = mutable.Map.newBuilder - } - - implicit def buildableImmutableMap[T,U] = new Buildable2[T,U,immutable.Map] { - def builder = immutable.Map.newBuilder - } - - implicit def buildableMap[T,U] = new Buildable2[T,U,Map] { - def builder = Map.newBuilder - } - - implicit def buildableImmutableSortedMap[T: Ordering, U] = new Buildable2[T,U,immutable.SortedMap] { - def builder = immutable.SortedMap.newBuilder - } - - implicit def buildableSortedMap[T: Ordering, U] = new Buildable2[T,U,SortedMap] { - def builder = SortedMap.newBuilder - } - + implicit def buildableArrayList[T]: Buildable[T, ArrayList[T]] = + new Buildable[T, ArrayList[T]] { + def builder = new ArrayListBuilder[T] + } + + implicit def buildableSeq[T]: Buildable[T, Seq[T]] = + new Buildable[T, Seq[T]] { + def builder: mutable.Builder[T, Seq[T]] = + Seq.newBuilder[T] + } } -*/