Skip to content

Commit

Permalink
Improve performance of arbitraries
Browse files Browse the repository at this point in the history
All generators are guaranteed to generate values with a 100% success
rate. The testGen function checks that. In case of exhausted case, an
exception is thrown.

Some generators had to be written specifically for that purpose.

Most of string generators reuse the collection generators.
  • Loading branch information
jprudent committed Jan 31, 2024
1 parent 6776639 commit e75a77f
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 9 deletions.
17 changes: 17 additions & 0 deletions scalacheck/src/io/github/iltotore/iron/scalacheck/collection.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,23 @@ import scala.compiletime.constValue

object collection:

inline given empty[A, CC[_]](using arbElem: Arbitrary[A], evb: Buildable[A, CC[A]], evt: CC[A] => Iterable[A]): Arbitrary[CC[A] :| Empty] = exactLength[A, CC, 0].asInstanceOf

inline given exactLength[A, CC[_], V <: Int](using arbElem: Arbitrary[A], evb: Buildable[A, CC[A]], evt: CC[A] => Iterable[A]): Arbitrary[CC[A] :| FixedLength[V]] =
Arbitrary(Gen.infiniteLazyList(arbElem.arbitrary).flatMap(ll => evb.fromIterable(ll.take(constValue[V])))).asInstanceOf

inline given minLength[A, CC[_], V <: Int](using arbElem: Arbitrary[A], evb: Buildable[A,CC[A]], evt: CC[A] => Iterable[A]): Arbitrary[CC[A] :| MinLength[V]] =
Arbitrary(
for
prefix <- Gen.infiniteLazyList(arbElem.arbitrary)
postfix <- Gen.listOf(arbElem.arbitrary)
yield
evb.fromIterable(prefix.take(constValue[V]) ++ postfix)
).asInstanceOf

inline given maxLength[A, CC[_], V <: Int](using arbElem: Arbitrary[A], evb: Buildable[A,CC[A]], evt: CC[A] => Iterable[A]): Arbitrary[CC[A] :| MaxLength[V]] =
Arbitrary(Gen.containerOf(arbElem.arbitrary).flatMap(cc => Gen.const(evt(cc).take(constValue[V])))).asInstanceOf[Arbitrary[CC[A] :| MaxLength[V]]]

inline given length[A, CC[_], C](using arbLength: Arbitrary[Int :| C], arbElem: Arbitrary[A], evb: Buildable[A,CC[A]], evt: CC[A] => Iterable[A]): Arbitrary[CC[A] :| Length[C]] =
Arbitrary(arbLength.arbitrary.flatMap(n => Gen.containerOfN(n, arbElem.arbitrary))).asInstanceOf

Expand Down
22 changes: 18 additions & 4 deletions scalacheck/src/io/github/iltotore/iron/scalacheck/string.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.github.iltotore.iron.scalacheck

import io.github.iltotore.iron.*
import io.github.iltotore.iron.constraint.collection.Empty
import io.github.iltotore.iron.constraint.collection.*
import io.github.iltotore.iron.constraint.string.*
import org.scalacheck.{Arbitrary, Gen}
import org.scalacheck.Gen.Choose
Expand All @@ -10,9 +10,23 @@ import scala.compiletime.constValue

object string:
inline given notEmptyString: Arbitrary[String :| Not[Empty]] = collection.notEmptyCollection[String, Char]

inline given startWith[V <: String]: Arbitrary[String :| StartWith[V]] =
Arbitrary(Gen.asciiStr.map(constValue[V] + _)).asInstanceOf

inline given endWith[V <: String]: Arbitrary[String :| EndWith[V]] =
Arbitrary(Gen.asciiStr.map(_ + constValue[V])).asInstanceOf
Arbitrary(Gen.asciiStr.map(_ + constValue[V])).asInstanceOf
inline given minLength[V <: Int](using listArb: Arbitrary[List[Char] :| MinLength[V]]) : Arbitrary[String :| MinLength[V]] = reuseCollection
inline given maxLength[V <: Int](using listArb: Arbitrary[List[Char] :| MaxLength[V]]): Arbitrary[String :| MaxLength[V]] = reuseCollection
inline given exactLength[V <: Int](using listArb: Arbitrary[List[Char] :| FixedLength[V]]): Arbitrary[String :| FixedLength[V]] = reuseCollection
inline given emptyLength(using listArb: Arbitrary[List[Char] :| Empty]): Arbitrary[String :| Empty] = reuseCollection
inline given forAll[V](using listArb: Arbitrary[List[Char] :| ForAll[V]]): Arbitrary[String :| ForAll[V]] = reuseCollection
inline given init[V](using listArb: Arbitrary[List[Char] :| Init[V]]): Arbitrary[String :| Init[V]] = reuseCollection
inline given tail[V](using listArb: Arbitrary[List[Char] :| Tail[V]]): Arbitrary[String :| Tail[V]] = reuseCollection
inline given head[V](using listArb: Arbitrary[List[Char] :| Head[V]]): Arbitrary[String :| Head[V]] = reuseCollection
inline given last[V](using listArb: Arbitrary[List[Char] :| Last[V]]): Arbitrary[String :| Last[V]] = reuseCollection
inline given contain[V <: String](using stringArb: Arbitrary[String]): Arbitrary[String :| Contain[V]] =
Arbitrary(for {
prefix <- stringArb.arbitrary
suffix <- stringArb.arbitrary
} yield prefix + constValue[V] + suffix).asInstanceOf
private inline def reuseCollection[C1, C2](using arb: Arbitrary[List[Char] :| C1]): Arbitrary[String :| C2] =
Arbitrary(arb.arbitrary.map(_.mkString(""))).asInstanceOf
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ object CharSuite extends TestSuite:
test("whitespace") - testGen[Char, Whitespace]

test("lowercase") - testGen[Char, LowerCase]

test("uppercase") - testGen[Char, UpperCase]

test("digit") - testGen[Char, Digit]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ object CollectionSuite extends TestSuite:
test("seq") - testGen[Seq[Boolean], MaxLength[5]]
test("string") - testGen[String, MaxLength[5]]
}
test("exactLength") {
test("seq") - testGen[Seq[Boolean], FixedLength[5]]
test("string") - testGen[String, FixedLength[5]]
}
test("empty") {
test("seq") - testGen[Seq[Boolean], Empty]
test("string") - testGen[String, Empty]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.github.iltotore.iron.scalacheck
import io.github.iltotore.iron.*
import io.github.iltotore.iron.constraint.numeric.*
import io.github.iltotore.iron.scalacheck.numeric.given
import io.github.iltotore.iron.scalacheck.any.strictEqual
import org.scalacheck.*
import utest.*

Expand Down Expand Up @@ -38,6 +39,13 @@ object NumericSuite extends TestSuite:
test("double") - testGen[Double, LessEqual[5d]]
}

test("strictEqual") {
test("int") - testGen[Int, StrictEqual[5]]
test("long") - testGen[Long, StrictEqual[5L]]
test("float") - testGen[Float, StrictEqual[5f]]
test("double") - testGen[Double, StrictEqual[5d]]
}

test("interval") {
test("open") {
test("int") - testGen[Int, Interval.Open[0, 5]]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
package io.github.iltotore.iron.scalacheck

import io.github.iltotore.iron.{:|, Constraint}
import org.scalacheck.Test.Parameters
import org.scalacheck.{Arbitrary, Prop, Test}
import utest.*

inline def testGen[A, C](using inline arb: Arbitrary[A :| C], inline constraint: Constraint[A, C]): Unit =

def getTestValues(args: List[Prop.Arg[Any]]): List[TestValue] =
args.zipWithIndex.map((arg, i) => TestValue(if arg.label.isBlank then s"value$i" else arg.label, "T", arg.arg))

Test.check(Prop.forAll(arb.arbitrary)(constraint.test(_)))(p => p).status match
case Test.Passed | Test.Proved(_) =>
val result = Test.check(Prop.forAll(arb.arbitrary)(constraint.test(_)))(p => p)
result.status match
case Test.Passed | Test.Proved(_) => assert(result.discarded == 0)
case Test.Failed(args, _) =>
throw AssertionError(s"Some constrained values failed for ${constraint.message}", getTestValues(args))
case Test.Exhausted => new java.lang.AssertionError("Exhausted")
case Test.Exhausted => throw new java.lang.AssertionError("Exhausted")
case Test.PropException(args, e, _) =>
throw AssertionError(s"An error occurred for ${constraint.message}", getTestValues(args), e)

0 comments on commit e75a77f

Please sign in to comment.