From 2c0fd186bcc7274c9daba57d76ebf80d9c786e74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fede=20Fern=C3=A1ndez?= Date: Fri, 2 Dec 2016 17:45:45 +0100 Subject: [PATCH 1/2] Upgrades scalacheck and scalatest libraries --- build.sbt | 10 ++-- .../scalachecklib/PropertiesSection.scala | 4 +- .../scala/scalachecklib/ArbitrarySpec.scala | 11 ++-- .../scala/scalachecklib/GeneratorsSpec.scala | 19 ++++--- .../scala/scalachecklib/PropertiesSpec.scala | 16 +++--- src/test/scala/scalachecklib/Test.scala | 53 +++++++++++++++++++ 6 files changed, 81 insertions(+), 32 deletions(-) create mode 100644 src/test/scala/scalachecklib/Test.scala diff --git a/build.sbt b/build.sbt index 24c1f9d..49f6537 100755 --- a/build.sbt +++ b/build.sbt @@ -11,11 +11,11 @@ lazy val scalacheck = (project in file(".")) Resolver.sonatypeRepo("releases") ), libraryDependencies ++= Seq( - "org.scalatest" %% "scalatest" % "2.2.4", - "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", + "org.scalatest" %% "scalatest" % "3.0.1" exclude("org.scalacheck", "scalacheck"), + "org.scala-exercises" %% "exercise-compiler" % version.value excludeAll ExclusionRule("com.github.alexarchambault"), + "org.scala-exercises" %% "definitions" % version.value excludeAll ExclusionRule("com.github.alexarchambault"), + "com.fortysevendeg" %% "scalacheck-datetime" % "0.2.0", + "com.github.alexarchambault" %% "scalacheck-shapeless_1.13" % "1.1.3", compilerPlugin("org.spire-math" %% "kind-projector" % "0.9.0") ) ) diff --git a/src/main/scala/scalachecklib/PropertiesSection.scala b/src/main/scala/scalachecklib/PropertiesSection.scala index d0606e0..72efb1b 100644 --- a/src/main/scala/scalachecklib/PropertiesSection.scala +++ b/src/main/scala/scalachecklib/PropertiesSection.scala @@ -185,7 +185,7 @@ object PropertiesSection extends Checkers with Matchers with org.scalaexercises. * */ def groupingProperties(res0: Int, res1: Int, res2: Int) = { - import org.scalacheck.Properties + import org.scalacheck.{Prop, Properties} class ZeroSpecification extends Properties("Zero") { @@ -199,6 +199,6 @@ object PropertiesSection extends Checkers with Matchers with org.scalaexercises. } - check(new ZeroSpecification) + check(Prop.all(new ZeroSpecification().properties.map(_._2): _*)) } } \ No newline at end of file diff --git a/src/test/scala/scalachecklib/ArbitrarySpec.scala b/src/test/scala/scalachecklib/ArbitrarySpec.scala index 54d863e..063bc55 100644 --- a/src/test/scala/scalachecklib/ArbitrarySpec.scala +++ b/src/test/scala/scalachecklib/ArbitrarySpec.scala @@ -1,14 +1,13 @@ package scalachecklib import org.scalacheck.Shapeless._ -import org.scalaexercises.Test -import org.scalatest.Spec +import org.scalatest.FunSuite import org.scalatest.prop.Checkers import shapeless.HNil -class ArbitrarySpec extends Spec with Checkers { +class ArbitrarySpec extends FunSuite with Checkers { - def `implicit arbitrary char` = { + test("implicit arbitrary char") { check( Test.testSuccess( @@ -18,7 +17,7 @@ class ArbitrarySpec extends Spec with Checkers { ) } - def `implicit arbitrary case class` = { + test("implicit arbitrary case class") { check( Test.testSuccess( @@ -28,7 +27,7 @@ class ArbitrarySpec extends Spec with Checkers { ) } - def `arbitrary on gen` = { + test("arbitrary on gen") { check( Test.testSuccess( diff --git a/src/test/scala/scalachecklib/GeneratorsSpec.scala b/src/test/scala/scalachecklib/GeneratorsSpec.scala index 69de8c3..5871bad 100644 --- a/src/test/scala/scalachecklib/GeneratorsSpec.scala +++ b/src/test/scala/scalachecklib/GeneratorsSpec.scala @@ -1,14 +1,13 @@ package scalachecklib import org.scalacheck.Shapeless._ -import org.scalaexercises.Test -import org.scalatest.Spec +import org.scalatest.FunSuite import org.scalatest.prop.Checkers import shapeless.HNil -class GeneratorsSpec extends Spec with Checkers { +class GeneratorsSpec extends FunSuite with Checkers { - def `for-comprehension generator` = { + test("for-comprehension generator") { check( Test.testSuccess( @@ -19,7 +18,7 @@ class GeneratorsSpec extends Spec with Checkers { } - def `oneOf method` = { + test("oneOf method") { check( Test.testSuccess( @@ -30,7 +29,7 @@ class GeneratorsSpec extends Spec with Checkers { } - def `alphaChar, posNum and listOfN` = { + test("alphaChar, posNum and listOfN") { check( Test.testSuccess( @@ -41,7 +40,7 @@ class GeneratorsSpec extends Spec with Checkers { } - def `suchThat condition` = { + test("suchThat condition") { check( Test.testSuccess( @@ -52,7 +51,7 @@ class GeneratorsSpec extends Spec with Checkers { } - def `case class generator` = { + test("case class generator") { check( Test.testSuccess( @@ -63,7 +62,7 @@ class GeneratorsSpec extends Spec with Checkers { } - def `sized generator` = { + test("sized generator") { check( Test.testSuccess( @@ -74,7 +73,7 @@ class GeneratorsSpec extends Spec with Checkers { } - def `list container` = { + test("list container") { check( Test.testSuccess( diff --git a/src/test/scala/scalachecklib/PropertiesSpec.scala b/src/test/scala/scalachecklib/PropertiesSpec.scala index 9abc594..9dbe8b8 100644 --- a/src/test/scala/scalachecklib/PropertiesSpec.scala +++ b/src/test/scala/scalachecklib/PropertiesSpec.scala @@ -1,15 +1,13 @@ package scalachecklib import org.scalacheck.Shapeless._ -import org.scalaexercises.Test -import org.scalatest.Spec +import org.scalatest.FunSuite import org.scalatest.prop.Checkers import shapeless.HNil +class PropertiesSpec extends FunSuite with Checkers { -class PropertiesSpec extends Spec with Checkers { - - def `always ends with the second string` = { + test("always ends with the second string") { check( Test.testSuccess( @@ -19,7 +17,7 @@ class PropertiesSpec extends Spec with Checkers { ) } - def `all numbers are generated between the desired interval` = { + test("all numbers are generated between the desired interval") { check( Test.testSuccess( @@ -29,7 +27,7 @@ class PropertiesSpec extends Spec with Checkers { ) } - def `all generated numbers are even` = { + test("all generated numbers are even") { check( Test.testSuccess( @@ -39,7 +37,7 @@ class PropertiesSpec extends Spec with Checkers { ) } - def `only the second condition is true` = { + test("only the second condition is true") { check( Test.testSuccess( @@ -49,7 +47,7 @@ class PropertiesSpec extends Spec with Checkers { ) } - def `the zero specification only works for 0` = { + test("the zero specification only works for 0") { check( Test.testSuccess( diff --git a/src/test/scala/scalachecklib/Test.scala b/src/test/scala/scalachecklib/Test.scala new file mode 100644 index 0000000..b176537 --- /dev/null +++ b/src/test/scala/scalachecklib/Test.scala @@ -0,0 +1,53 @@ +package scalachecklib + +import shapeless._ +import shapeless.ops.function._ + +import cats.implicits._ +import org.scalacheck.{ Prop, Arbitrary } +import org.scalacheck.Gen +import Prop.forAll + +import org.scalatest.exceptions._ + +import org.scalacheck.Shapeless._ + +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 = Either.catchOnly[GeneratorDrivenPropertyCheckFailedException]({ fntop(method)(p) }) + result match { + case Left(exc) ⇒ exc.cause match { + case Some(originalException) ⇒ throw originalException + case _ ⇒ false + } + case _ ⇒ true + } + }) + + val wrongGen = genWrongAnswer(answer) + val wrongProp = forAll(wrongGen)({ p ⇒ + Either.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 f7022a79dede3476232f04751dfe3800cdc2b053 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fede=20Fern=C3=A1ndez?= Date: Sun, 4 Dec 2016 15:34:18 +0100 Subject: [PATCH 2/2] Finishes scalacheck-datetime section and adds the tests --- .../scalachecklib/ArbitrarySection.scala | 2 +- .../ScalacheckDatetimeSection.scala | 153 ++++++++++++++++++ .../scalachecklib/ScalacheckLibrary.scala | 3 +- .../ScalacheckDatetimeSpec.scala | 50 ++++++ 4 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 src/main/scala/scalachecklib/ScalacheckDatetimeSection.scala create mode 100644 src/test/scala/scalachecklib/ScalacheckDatetimeSpec.scala diff --git a/src/main/scala/scalachecklib/ArbitrarySection.scala b/src/main/scala/scalachecklib/ArbitrarySection.scala index d43249a..c8d8ed2 100644 --- a/src/main/scala/scalachecklib/ArbitrarySection.scala +++ b/src/main/scala/scalachecklib/ArbitrarySection.scala @@ -3,7 +3,7 @@ package scalachecklib import org.scalatest.Matchers import org.scalatest.prop.Checkers -/** ==The `arbitrary` Generator +/** ==The `arbitrary` Generator== * * There is a special generator, `org.scalacheck.Arbitrary.arbitrary`, which generates arbitrary values of any * supported type. diff --git a/src/main/scala/scalachecklib/ScalacheckDatetimeSection.scala b/src/main/scala/scalachecklib/ScalacheckDatetimeSection.scala new file mode 100644 index 0000000..68b5057 --- /dev/null +++ b/src/main/scala/scalachecklib/ScalacheckDatetimeSection.scala @@ -0,0 +1,153 @@ +package scalachecklib + +import org.scalatest.Matchers +import org.scalatest.prop.Checkers + +/** scalacheck-datetime is a library for helping use datetime libraries with ScalaCheck + * + * The motivation behind this library is to provide a simple, easy way to provide generated date and time instances + * that are useful to your own domain. + * + * For SBT, you can add the dependency to your project’s build file: + * + * {{{ + * resolvers += Resolver.sonatypeRepo("releases") + * + * "com.fortysevendeg" %% "scalacheck-datetime" % "0.2.0" % "test" + * }}} + * + * Please, visit the [[https://47deg.github.io/scalacheck-datetime homepage]] for more information + * + * @param name scalacheck-datetime + */ +object ScalacheckDatetimeSection extends Checkers with Matchers with org.scalaexercises.definitions.Section { + + /** ==Usage== + * + * To arbitrarily generate dates and times, you need to have the `Arbitrary` in scope for your date/time class. + * Assuming Joda Time: + */ + def usage(res0: Boolean) = { + + import com.fortysevendeg.scalacheck.datetime.joda.ArbitraryJoda._ + import org.joda.time.DateTime + import org.scalacheck.Prop.forAll + + check { + forAll { dt: DateTime => + (dt.getDayOfMonth >= 1 && dt.getDayOfMonth <= 31) == res0 + } + } + } + + /** ==A note on imports== + * + * For all of the examples given in this document, you can substitute `jdk8` for `joda` and vice-versa, + * depending on which library you would like to generate instances for. + * + * ==Implementation== + * + * The infrastructure behind the generation of date/time instances for any given date/time library, + * which may take ranges into account, is done using a fairly simple typeclass, which has the type signature + * `ScalaCheckDateTimeInfra[D, R]`. That is to say, as long as there is an implicit `ScalaCheckDateTimeInfra` + * instance in scope for a given date/time type `D` (such as Joda’s `DateTime`) and a range type `R` + * (such as Joda’s `Period`), then the code will compile and be able to provide generated date/time instances. + * + * As stated, currently there are two instances, `ScalaCheckDateTimeInfra[DateTime, Period]` for Joda Time and + * `ScalaCheckDateTimeInfra[ZonedDateTime, Duration]` for Java SE 8’s Date and Time. + * + * ==Granularity== + * + * If you wish to restrict the precision of the generated instances, this library refers to that as granularity. + * + * You can constrain the granularity to: + * + * + * + * When a value is constrained, the time fields are set to zero, and the rest to the first day of the month, + * or day of the year. For example, if you constrain a field to be years, the generated instance will be midnight + * exactly, on the first day of January. + * + * To constrain a generated type, you simply need to provide an import for the typeclass for your date/time and + * range, and also an import for the granularity. As an example, this time using Java SE 8's `java.time` package: + */ + def granularity(res0: Int, res1: Int, res2: Int, res3: Int, res4: Int) = { + + import java.time._ + import com.fortysevendeg.scalacheck.datetime.jdk8.ArbitraryJdk8._ + import com.fortysevendeg.scalacheck.datetime.jdk8.granularity.years + import org.scalacheck.Prop.forAll + + check { + forAll { zdt: ZonedDateTime => + zdt.getMonth == Month.JANUARY + (zdt.getDayOfMonth == res0) && + (zdt.getHour == res1) && + (zdt.getMinute == res2) && + (zdt.getSecond == res3) && + (zdt.getNano == res4) + } + } + + } + + /** ==Creating Ranges== + * + * You can generate date/time instances only within a certain range, using the `genDateTimeWithinRange` in the + * `GenDateTime` class. The function takes two parameters, the date/time instances as a base from which to generate + * new date/time instances, and a range for the generated instances. + * + * If the range is positive, it will be in the future from the base date/time, negative in the past. + * + * Showing this usage with Joda Time: + */ + def ranges(res0: Int) = { + + import org.joda.time._ + import com.fortysevendeg.scalacheck.datetime.instances.joda._ + import com.fortysevendeg.scalacheck.datetime.GenDateTime.genDateTimeWithinRange + import org.scalacheck.Prop.forAll + + val from = new DateTime(2016, 1, 1, 0, 0) + val range = Period.years(1) + + check { + forAll(genDateTimeWithinRange(from, range)) { dt => + dt.getYear == res0 + } + } + } + + /** ==Using Granularity and Ranges Together== + * + * As you would expect, it is possible to use the granularity and range concepts together. + * This example should not show anything surprising by now: + */ + def granularityAndRanges(res0: Int, res1: Int, res2: Int, res3: Int, res4: Int) = { + + import org.joda.time._ + import com.fortysevendeg.scalacheck.datetime.instances.joda._ + import com.fortysevendeg.scalacheck.datetime.GenDateTime.genDateTimeWithinRange + import com.fortysevendeg.scalacheck.datetime.joda.granularity.days + import org.scalacheck.Prop.forAll + + val from = new DateTime(2016, 1, 1, 0, 0) + val range = Period.years(1) + + check { + forAll(genDateTimeWithinRange(from, range)) { dt => + (dt.getYear == res0) && + (dt.getHourOfDay == res1) && + (dt.getMinuteOfHour == res2) && + (dt.getSecondOfMinute == res3) && + (dt.getMillisOfSecond == res4) + } + } + } +} diff --git a/src/main/scala/scalachecklib/ScalacheckLibrary.scala b/src/main/scala/scalachecklib/ScalacheckLibrary.scala index 2e6e4e9..1cd6bc9 100644 --- a/src/main/scala/scalachecklib/ScalacheckLibrary.scala +++ b/src/main/scala/scalachecklib/ScalacheckLibrary.scala @@ -12,7 +12,8 @@ object ScalacheckLibrary extends org.scalaexercises.definitions.Library { override def sections = List( PropertiesSection, - GeneratorsSection + GeneratorsSection, + ScalacheckDatetimeSection ) override def logoPath = "scalacheck" diff --git a/src/test/scala/scalachecklib/ScalacheckDatetimeSpec.scala b/src/test/scala/scalachecklib/ScalacheckDatetimeSpec.scala new file mode 100644 index 0000000..3410944 --- /dev/null +++ b/src/test/scala/scalachecklib/ScalacheckDatetimeSpec.scala @@ -0,0 +1,50 @@ +package scalachecklib + +import org.scalacheck.Shapeless._ +import org.scalatest.FunSuite +import org.scalatest.prop.Checkers +import shapeless.HNil + +class ScalacheckDatetimeSpec extends FunSuite with Checkers { + + test("simple usage") { + + check( + Test.testSuccess( + ScalacheckDatetimeSection.usage _, + true :: HNil + ) + ) + } + + test("granularity") { + + check( + Test.testSuccess( + ScalacheckDatetimeSection.granularity _, + 1 :: 0 :: 0 :: 0 :: 0 :: HNil + ) + ) + } + + test("ranges") { + + check( + Test.testSuccess( + ScalacheckDatetimeSection.ranges _, + 2016 :: HNil + ) + ) + } + + test("granularity and ranges together") { + + check( + Test.testSuccess( + ScalacheckDatetimeSection.granularityAndRanges _, + 2016 :: 0 :: 0 :: 0 :: 0 :: HNil + ) + ) + } + +}