Skip to content

Commit

Permalink
feat: Improve performance of arbitraries (#207)
Browse files Browse the repository at this point in the history
  • Loading branch information
jprudent authored Feb 5, 2024
1 parent 725f269 commit 8eca597
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 8eca597

Please sign in to comment.