From 1f2b9b34e5352e69d51caf13d1bd425be3e0d4e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fede=20Fern=C3=A1ndez?= Date: Mon, 4 Jul 2016 16:48:35 +0200 Subject: [PATCH 1/6] Adds scalacheck-shapeless dependency --- build.sbt | 1 + 1 file changed, 1 insertion(+) diff --git a/build.sbt b/build.sbt index 1ed478a..0649433 100755 --- a/build.sbt +++ b/build.sbt @@ -15,6 +15,7 @@ lazy val scalacheck = (project in file(".")) "org.scala-exercises" %% "exercise-compiler" % version.value, "org.scala-exercises" %% "definitions" % version.value, "org.scalacheck" %% "scalacheck" % "1.12.5", + "com.github.alexarchambault" %% "scalacheck-shapeless_1.12" % "0.3.1", compilerPlugin("org.spire-math" %% "kind-projector" % "0.7.1") ) ) From 2e5569fe862fc4d7cfcdee81d7f1fdc0c20b43cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fede=20Fern=C3=A1ndez?= Date: Mon, 4 Jul 2016 16:49:01 +0200 Subject: [PATCH 2/6] Includes the properties section --- .../scalachecklib/PropertiesHelpers.scala | 17 ++ .../scalachecklib/PropertiesSection.scala | 160 ++++++++++++++++++ .../scalachecklib/ScalacheckLibrary.scala | 16 ++ 3 files changed, 193 insertions(+) create mode 100644 src/main/scala/scalachecklib/PropertiesHelpers.scala create mode 100644 src/main/scala/scalachecklib/PropertiesSection.scala create mode 100644 src/main/scala/scalachecklib/ScalacheckLibrary.scala diff --git a/src/main/scala/scalachecklib/PropertiesHelpers.scala b/src/main/scala/scalachecklib/PropertiesHelpers.scala new file mode 100644 index 0000000..b852149 --- /dev/null +++ b/src/main/scala/scalachecklib/PropertiesHelpers.scala @@ -0,0 +1,17 @@ +package scalachecklib + +import org.scalacheck._ + +object PropertiesHelpers { + + class ZeroSpecification(value: Int) extends Properties("Zero") { + + import Prop.{BooleanOperators, forAll} + + property("sum") = forAll { n: Int => (n != 0) ==> (n + value == n) } + + property("prod") = forAll { n: Int => (n != 0) ==> (n * value == 0) } + + } + +} diff --git a/src/main/scala/scalachecklib/PropertiesSection.scala b/src/main/scala/scalachecklib/PropertiesSection.scala new file mode 100644 index 0000000..99637f7 --- /dev/null +++ b/src/main/scala/scalachecklib/PropertiesSection.scala @@ -0,0 +1,160 @@ +package scalachecklib + +import org.scalatest._ +import org.scalatest.prop.Checkers +import PropertiesHelpers._ + +/** A ''property'' is the testable unit in ScalaCheck, and is represented by the `org.scalacheck.Prop` class. + * There are several ways to create properties in ScalaCheck, one of them is to use the `org.scalacheck.Prop.forAll` + * method like in the example above. That method creates universally quantified properties directly, but it is also + * possible to create new properties by combining other properties, or to use any of the specialised + * methods in the `org.scalacheck.Prop` object. + * + * @param name properties + */ +object PropertiesSection extends Checkers with Matchers with exercise.Section { + + /** ==Universally quantified properties== + * + * As mentioned before, `org.scalacheck.Prop.forAll` creates universally quantified properties. + * `forAll` takes a function as parameter, and creates a property out of it that can be tested with the check method. + * The function should return `Boolean` or another property, and can take parameters of any types, as long as there + * exist implicit `Arbitrary` instances for the types. By default, ScalaCheck has instances for common types like + * `Int`, `String`, `List`, etc, but it is also possible to define your own `Arbitrary` instances. + * + * For example, for every `String` generated: + */ + def universallyQuantifiedPropertiesString(res0: Boolean) = { + + import org.scalacheck.Prop.forAll + + check { + forAll { (s1: String, s2: String) => (s1 + s2).endsWith(s2) == res0 } + } + + } + + /** When you run `check` on the properties, ScalaCheck generates random instances of the function parameters and + * evaluates the results, reporting any failing cases. + * + * You can also give `forAll` a specific data generator. In the following example `smallInteger` defines a generator + * that generates integers between `0` and `100`, inclusively. + * + * This way of using the `forAll` method is good to use when you want to control the data generation by specifying + * exactly which generator that should be used, and not rely on a default generator for the given type. + */ + def universallyQuantifiedPropertiesGen(res0: Boolean) = { + + import org.scalacheck.Gen + import org.scalacheck.Prop.forAll + + val smallInteger = Gen.choose(0,100) + + check { + forAll(smallInteger) { n => (n >= 0 && n <= 100) == res0 } + } + + } + + /** ==Conditional properties== + * + * Sometimes, a specification takes the form of an implication. In ScalaCheck, you can use the implication + * operator `==>` to filter the generated values. + * + * If the implication operator is given a condition that is hard or impossible to fulfill, ScalaCheck might + * not find enough passing test cases to state that the property holds. In the following trivial example, + * all cases where n is non-zero will be thrown away: + * + * {{{ + * scala> import org.scalacheck.Prop.{forAll, BooleanOperators} + * scala> val propTrivial = forAll { n: Int => + * | (n == 0) ==> (n == 0) + * | } + * + * scala> propTrivial.check + * ! Gave up after only 4 passed tests. 500 tests were discarded. + * }}} + * + * It is possible to tell ScalaCheck to try harder when it generates test cases, but generally you should + * try to refactor your property specification instead of generating more test cases, if you get this scenario. + * + * Using implications, we realise that a property might not just pass or fail, it could also be undecided if + * the implication condition doesn't get fulfilled. + * + * In this example, ScalaCheck will only care for the cases when n is not negative. We also filter out large numbers, + * since we don't want to generate huge lists. + */ + def conditionalProperties(res0: Int) = { + + import org.scalacheck.Prop.{BooleanOperators, forAll} + + check { + forAll { n: Int => + (n % 2 == 0) ==> { n % 2 == res0 } + } + } + + } + + /** ==Combining Properties== + * + * A third way of creating properties, is to combine existing properties into new ones. + * + * {{{ + * val p1 = forAll(...) + * val p2 = forAll(...) + * val p3 = p1 && p2 + * val p4 = p1 || p2 + * val p5 = p1 == p2 + * val p6 = all(p1, p2) // same as p1 && p2 + * val p7 = atLeastOne(p1, p2) // same as p1 || p2 + * }}} + * + * Here, `p3` will hold if and only if both `p1` and `p2` hold, `p4` will hold if either `p1` or `p2` holds, + * and `p5` will hold if `p1` holds exactly when `p2` holds and vice versa. + */ + def combiningProperties(res0: Boolean, res1: Boolean) = { + + import org.scalacheck.Gen + import org.scalacheck.Prop.forAll + + val oneInteger = Gen.choose(0,50) + val anotherInteger = Gen.choose(51,100) + + check { + forAll(oneInteger) { n => (n > 50) == res0 } && forAll(anotherInteger) { n => (n > 50) == res1 } + } + + } + + /** ==Grouping Properties== + * + * Often you want to specify several related properties, perhaps for all methods in a class. + * ScalaCheck provides a simple way of doing this, through the `Properties` trait. + * Look at the following specifications that only works for zero: + * + * {{{ + * import org.scalacheck._ + * + * class ZeroSpecification(value: Int) extends org.scalacheck.Properties("Zero") { + * + * import org.scalacheck.Prop.{forAll, BooleanOperators} + * + * property("sum") = forAll { n: Int => (n != 0) ==> (n + value == n) } + * + * property("prod") = forAll { n: Int => (n != 0) ==> (n * value == 0) } + * + * } + * }}} + * + * You can use the check method of the `Properties` class to check all specified properties, + * just like for simple `Prop` instances. In fact, `Properties` is a subtype of `Prop`, + * so you can use it just as if it was a single property. + * That single property holds if and only if all of the contained properties hold. + */ + def groupingProperties(res0: Int) = { + + check(new ZeroSpecification(res0)) + + } +} \ No newline at end of file diff --git a/src/main/scala/scalachecklib/ScalacheckLibrary.scala b/src/main/scala/scalachecklib/ScalacheckLibrary.scala new file mode 100644 index 0000000..cdc4b3b --- /dev/null +++ b/src/main/scala/scalachecklib/ScalacheckLibrary.scala @@ -0,0 +1,16 @@ +package scalachecklib + +/** ScalaCheck is a tool for testing Scala and Java programs, based on property specifications and automatic test data generation. + * + * @param name scalacheck + */ +object ScalacheckLibrary extends exercise.Library { + override def owner = "scala-exercises" + override def repository = "exercises-scalacheck" + + override def color = Some("#5B5988") + + override def sections = List( + PropertiesSection + ) +} From f83a5e1f0a6cfee577583a84cf090f14557e3255 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fede=20Fern=C3=A1ndez?= Date: Mon, 4 Jul 2016 16:49:15 +0200 Subject: [PATCH 3/6] Adds the tests for the properties section --- .../scalachecklib/PropertiesSpec.scala | 63 +++++++++++++++++++ .../scala/exercises/scalachecklib/Test.scala | 54 ++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 src/test/scala/exercises/scalachecklib/PropertiesSpec.scala create mode 100755 src/test/scala/exercises/scalachecklib/Test.scala diff --git a/src/test/scala/exercises/scalachecklib/PropertiesSpec.scala b/src/test/scala/exercises/scalachecklib/PropertiesSpec.scala new file mode 100644 index 0000000..0997e79 --- /dev/null +++ b/src/test/scala/exercises/scalachecklib/PropertiesSpec.scala @@ -0,0 +1,63 @@ +package exercises.scalachecklib + +import org.scalacheck.Shapeless._ +import org.scalatest.Spec +import org.scalatest.prop.Checkers +import shapeless.HNil + +import scalachecklib.PropertiesSection + + +class PropertiesSpec extends Spec with Checkers { + + def `always ends with the second string` = { + + check( + Test.testSuccess( + PropertiesSection.universallyQuantifiedPropertiesString _, + true :: HNil + ) + ) + } + + def `all numbers are generated between the desired interval` = { + + check( + Test.testSuccess( + PropertiesSection.universallyQuantifiedPropertiesGen _, + true :: HNil + ) + ) + } + + def `all generated numbers are even` = { + + check( + Test.testSuccess( + PropertiesSection.conditionalProperties _, + 0 :: HNil + ) + ) + } + + def `only the second condition is true` = { + + check( + Test.testSuccess( + PropertiesSection.combiningProperties _, + false :: true :: HNil + ) + ) + } + + def `the zero specification only works for 0` = { + + check( + Test.testSuccess( + PropertiesSection.groupingProperties _, + 0 :: HNil + ) + ) + } + +} diff --git a/src/test/scala/exercises/scalachecklib/Test.scala b/src/test/scala/exercises/scalachecklib/Test.scala new file mode 100755 index 0000000..204c95f --- /dev/null +++ b/src/test/scala/exercises/scalachecklib/Test.scala @@ -0,0 +1,54 @@ +package exercises.scalachecklib + +import cats.data.Xor + +import shapeless._ +import shapeless.ops.function._ + +import org.scalacheck.{Prop, Arbitrary } +import org.scalacheck.Gen +import org.scalacheck.Shapeless._ +import org.scalatest.exceptions._ + +import Prop.forAll + +object Test { + + def testSuccess[F, R, L <: HList](method: F, answer: L)( + implicit + A: Arbitrary[L], + fntop: FnToProduct.Aux[F, L ⇒ R] + ): Prop = { + val rightGen = genRightAnswer(answer) + val rightProp = forAll(rightGen)({ p ⇒ + + val result = Xor.catchOnly[GeneratorDrivenPropertyCheckFailedException]({ fntop(method)(p) }) + result match { + case Xor.Left(exc) ⇒ exc.cause match { + case Some(originalException) ⇒ throw originalException + case _ ⇒ false + } + case _ ⇒ true + } + }) + + val wrongGen = genWrongAnswer(answer) + val wrongProp = forAll(wrongGen)({ p ⇒ + Xor.catchNonFatal({ fntop(method)(p) }).isLeft + }) + + Prop.all(rightProp, wrongProp) + } + + def genRightAnswer[L <: HList](answer: L): Gen[L] = { + Gen.const(answer) + } + + def genWrongAnswer[L <: HList](l: L)( + implicit + A: Arbitrary[L] + ): Gen[L] = { + A.arbitrary.suchThat(_ != l) + } +} + From 93b8d667d125d49f00a72d0b63e39a11d076a8f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fede=20Fern=C3=A1ndez?= Date: Mon, 4 Jul 2016 17:05:17 +0200 Subject: [PATCH 4/6] Improves the documentation and examples --- .../scalachecklib/PropertiesHelpers.scala | 6 ++- .../scalachecklib/PropertiesSection.scala | 41 ++++++++++++------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/main/scala/scalachecklib/PropertiesHelpers.scala b/src/main/scala/scalachecklib/PropertiesHelpers.scala index b852149..bf35906 100644 --- a/src/main/scala/scalachecklib/PropertiesHelpers.scala +++ b/src/main/scala/scalachecklib/PropertiesHelpers.scala @@ -8,9 +8,11 @@ object PropertiesHelpers { import Prop.{BooleanOperators, forAll} - property("sum") = forAll { n: Int => (n != 0) ==> (n + value == n) } + property("addition property") = forAll { n: Int => (n != 0) ==> (n + value == n) } - property("prod") = forAll { n: Int => (n != 0) ==> (n * value == 0) } + property("additive inverse property") = forAll { n: Int => (n != 0) ==> (n + (-n) == value) } + + property("multiplication property") = forAll { n: Int => (n != 0) ==> (n * value == 0) } } diff --git a/src/main/scala/scalachecklib/PropertiesSection.scala b/src/main/scala/scalachecklib/PropertiesSection.scala index 99637f7..4e6c8b7 100644 --- a/src/main/scala/scalachecklib/PropertiesSection.scala +++ b/src/main/scala/scalachecklib/PropertiesSection.scala @@ -6,7 +6,13 @@ import PropertiesHelpers._ /** A ''property'' is the testable unit in ScalaCheck, and is represented by the `org.scalacheck.Prop` class. * There are several ways to create properties in ScalaCheck, one of them is to use the `org.scalacheck.Prop.forAll` - * method like in the example above. That method creates universally quantified properties directly, but it is also + * method like in the example below. + * + * {{{ + * val propSqrt = forAll { (n: Int) => scala.math.sqrt(n*n) == n } + * }}} + * + * That method creates universally quantified properties directly, but it is also * possible to create new properties by combining other properties, or to use any of the specialised * methods in the `org.scalacheck.Prop` object. * @@ -17,12 +23,16 @@ object PropertiesSection extends Checkers with Matchers with exercise.Section { /** ==Universally quantified properties== * * As mentioned before, `org.scalacheck.Prop.forAll` creates universally quantified properties. - * `forAll` takes a function as parameter, and creates a property out of it that can be tested with the check method. - * The function should return `Boolean` or another property, and can take parameters of any types, as long as there - * exist implicit `Arbitrary` instances for the types. By default, ScalaCheck has instances for common types like - * `Int`, `String`, `List`, etc, but it is also possible to define your own `Arbitrary` instances. + * `forAll` takes a function as parameter, and creates a property out of it that can be tested with the `check` + * method or with Scalatest, like in these examples. + * * - * For example, for every `String` generated: + * The function passed to `forAll` should return `Boolean` or another property, and can take parameters of any types, + * as long as there exist implicit `Arbitrary` instances for the types. + * By default, ScalaCheck has instances for common types like `Int`, `String`, `List`, etc, but it is also possible + * to define your own `Arbitrary` instances. + * + * For example: */ def universallyQuantifiedPropertiesString(res0: Boolean) = { @@ -63,7 +73,7 @@ object PropertiesSection extends Checkers with Matchers with exercise.Section { * * If the implication operator is given a condition that is hard or impossible to fulfill, ScalaCheck might * not find enough passing test cases to state that the property holds. In the following trivial example, - * all cases where n is non-zero will be thrown away: + * all cases where `n` is non-zero will be thrown away: * * {{{ * scala> import org.scalacheck.Prop.{forAll, BooleanOperators} @@ -81,8 +91,7 @@ object PropertiesSection extends Checkers with Matchers with exercise.Section { * Using implications, we realise that a property might not just pass or fail, it could also be undecided if * the implication condition doesn't get fulfilled. * - * In this example, ScalaCheck will only care for the cases when n is not negative. We also filter out large numbers, - * since we don't want to generate huge lists. + * In this example, ScalaCheck will only care for the cases when `n` is an even number. */ def conditionalProperties(res0: Int) = { @@ -118,11 +127,10 @@ object PropertiesSection extends Checkers with Matchers with exercise.Section { import org.scalacheck.Gen import org.scalacheck.Prop.forAll - val oneInteger = Gen.choose(0,50) - val anotherInteger = Gen.choose(51,100) + val smallInteger = Gen.choose(0,100) check { - forAll(oneInteger) { n => (n > 50) == res0 } && forAll(anotherInteger) { n => (n > 50) == res1 } + forAll(smallInteger) { n => (n > 100) == res0 } && forAll(smallInteger) { n => (n >= 0) == res1 } } } @@ -131,7 +139,7 @@ object PropertiesSection extends Checkers with Matchers with exercise.Section { * * Often you want to specify several related properties, perhaps for all methods in a class. * ScalaCheck provides a simple way of doing this, through the `Properties` trait. - * Look at the following specifications that only works for zero: + * Look at the following specifications that define some properties for zero: * * {{{ * import org.scalacheck._ @@ -140,9 +148,11 @@ object PropertiesSection extends Checkers with Matchers with exercise.Section { * * import org.scalacheck.Prop.{forAll, BooleanOperators} * - * property("sum") = forAll { n: Int => (n != 0) ==> (n + value == n) } + * property("addition property") = forAll { n: Int => (n != 0) ==> (n + value == n) } + * + * property("additive inverse property") = forAll { n: Int => (n != 0) ==> (n + (-n) == value) } * - * property("prod") = forAll { n: Int => (n != 0) ==> (n * value == 0) } + * property("multiplication property") = forAll { n: Int => (n != 0) ==> (n * value == 0) } * * } * }}} @@ -150,6 +160,7 @@ object PropertiesSection extends Checkers with Matchers with exercise.Section { * You can use the check method of the `Properties` class to check all specified properties, * just like for simple `Prop` instances. In fact, `Properties` is a subtype of `Prop`, * so you can use it just as if it was a single property. + * * That single property holds if and only if all of the contained properties hold. */ def groupingProperties(res0: Int) = { From 762206712b0062001d569a69536bb695b27dabc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fede=20Fern=C3=A1ndez?= Date: Mon, 4 Jul 2016 17:07:12 +0200 Subject: [PATCH 5/6] Minor improvements on code doc --- src/main/scala/scalachecklib/PropertiesSection.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/scala/scalachecklib/PropertiesSection.scala b/src/main/scala/scalachecklib/PropertiesSection.scala index 4e6c8b7..fa7e0c0 100644 --- a/src/main/scala/scalachecklib/PropertiesSection.scala +++ b/src/main/scala/scalachecklib/PropertiesSection.scala @@ -144,9 +144,9 @@ object PropertiesSection extends Checkers with Matchers with exercise.Section { * {{{ * import org.scalacheck._ * - * class ZeroSpecification(value: Int) extends org.scalacheck.Properties("Zero") { + * class ZeroSpecification(value: Int) extends Properties("Zero") { * - * import org.scalacheck.Prop.{forAll, BooleanOperators} + * import org.scalacheck.Prop.{BooleanOperators, forAll} * * property("addition property") = forAll { n: Int => (n != 0) ==> (n + value == n) } * @@ -160,7 +160,7 @@ object PropertiesSection extends Checkers with Matchers with exercise.Section { * You can use the check method of the `Properties` class to check all specified properties, * just like for simple `Prop` instances. In fact, `Properties` is a subtype of `Prop`, * so you can use it just as if it was a single property. - * + * * That single property holds if and only if all of the contained properties hold. */ def groupingProperties(res0: Int) = { From 992206878dfe5f85ced75270f1f82d566799a09c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fede=20Fern=C3=A1ndez?= Date: Mon, 4 Jul 2016 17:13:59 +0200 Subject: [PATCH 6/6] Unifies the methods indentations --- .../scala/scalachecklib/PropertiesSection.scala | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/scala/scalachecklib/PropertiesSection.scala b/src/main/scala/scalachecklib/PropertiesSection.scala index fa7e0c0..505f9da 100644 --- a/src/main/scala/scalachecklib/PropertiesSection.scala +++ b/src/main/scala/scalachecklib/PropertiesSection.scala @@ -39,7 +39,9 @@ object PropertiesSection extends Checkers with Matchers with exercise.Section { import org.scalacheck.Prop.forAll check { - forAll { (s1: String, s2: String) => (s1 + s2).endsWith(s2) == res0 } + forAll { (s1: String, s2: String) => + (s1 + s2).endsWith(s2) == res0 + } } } @@ -61,7 +63,9 @@ object PropertiesSection extends Checkers with Matchers with exercise.Section { val smallInteger = Gen.choose(0,100) check { - forAll(smallInteger) { n => (n >= 0 && n <= 100) == res0 } + forAll(smallInteger) { n => + (n >= 0 && n <= 100) == res0 + } } } @@ -99,7 +103,7 @@ object PropertiesSection extends Checkers with Matchers with exercise.Section { check { forAll { n: Int => - (n % 2 == 0) ==> { n % 2 == res0 } + (n % 2 == 0) ==> (n % 2 == res0) } } @@ -130,7 +134,8 @@ object PropertiesSection extends Checkers with Matchers with exercise.Section { val smallInteger = Gen.choose(0,100) check { - forAll(smallInteger) { n => (n > 100) == res0 } && forAll(smallInteger) { n => (n >= 0) == res1 } + forAll(smallInteger) { n => (n > 100) == res0 } && + forAll(smallInteger) { n => (n >= 0) == res1 } } }