diff --git a/build.sbt b/build.sbt index ffa08b3f..a424645d 100644 --- a/build.sbt +++ b/build.sbt @@ -11,6 +11,7 @@ val V = new { val `kind-projector` = "0.11.0" val kittens = "2.1.0" val `log-effect-fs2` = "0.12.2" + val munit = "0.7.7" val `parallel-collections` = "0.2.0" val refined = "0.9.14" val scalacheck = "1.14.3" @@ -44,6 +45,8 @@ val `refined-scalacheck` = Def.setting("eu.timepit" %%% "refined-scalacheck" % val scalacheck = Def.setting("org.scalacheck" %%% "scalacheck" % V.scalacheck % Test) val scalatest = Def.setting("org.scalatest" %%% "scalatest" % V.scalatest % Test) val `scalatest-plus` = Def.setting("org.scalatestplus" %%% "scalacheck-1-14" % V.`scalatest-plus` % Test) +val munit = Def.setting("org.scalameta" %%% "munit" % V.munit % Test) +val `munit-scalacheck` = Def.setting("org.scalameta" %%% "munit-scalacheck" % V.munit % Test) val `scala-parallel-collections` = Def.setting { CrossVersion.partialVersion(scalaVersion.value) match { @@ -67,8 +70,8 @@ val coreDeps = Def.Initialize.join { shapeless, `refined-scalacheck`, scalacheck, - scalatest, - `scalatest-plus` + munit, + `munit-scalacheck` ) } @@ -239,6 +242,8 @@ lazy val publishSettings = Seq( ) lazy val testSettings = Seq( + testFrameworks += new TestFramework("munit.Framework"), + scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule)), Test / testOptions += Tests.Argument(TestFrameworks.ScalaTest, "-oDF") ) diff --git a/core/.js/src/test/scala/laserdisc/ScalaCheckSettings.scala b/core/.js/src/test/scala/laserdisc/ScalaCheckSettings.scala index 40f6cafd..edb2cc8d 100644 --- a/core/.js/src/test/scala/laserdisc/ScalaCheckSettings.scala +++ b/core/.js/src/test/scala/laserdisc/ScalaCheckSettings.scala @@ -1,12 +1,10 @@ package laserdisc -import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks +import munit.ScalaCheckSuite -private[laserdisc] trait ScalaCheckSettings extends ScalaCheckPropertyChecks { - override implicit val generatorDrivenConfig: PropertyCheckConfiguration = - PropertyCheckConfiguration( - minSuccessful = 10, - maxDiscardedFactor = 10.0, - workers = 16 - ) +private[laserdisc] trait ScalaCheckSettings extends ScalaCheckSuite { + override val scalaCheckTestParameters = + super.scalaCheckTestParameters + .withMinSuccessfulTests(10) + .withWorkers(8) } diff --git a/core/.jvm/src/test/scala/laserdisc/ScalaCheckSettings.scala b/core/.jvm/src/test/scala/laserdisc/ScalaCheckSettings.scala index 97d91d7f..bb1c37b5 100644 --- a/core/.jvm/src/test/scala/laserdisc/ScalaCheckSettings.scala +++ b/core/.jvm/src/test/scala/laserdisc/ScalaCheckSettings.scala @@ -1,14 +1,14 @@ package laserdisc -import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks +import munit.ScalaCheckSuite -private[laserdisc] trait ScalaCheckSettings extends ScalaCheckPropertyChecks { - override implicit val generatorDrivenConfig: PropertyCheckConfiguration = - PropertyCheckConfiguration( - minSuccessful = 200, - maxDiscardedFactor = 10.0, - minSize = 0, - sizeRange = 100, - workers = 16 - ) +private[laserdisc] trait ScalaCheckSettings extends ScalaCheckSuite { + override val scalaCheckTestParameters = + super.scalaCheckTestParameters + .withMinSuccessfulTests(200) + .withMaxDiscardRatio(20) + .disableLegacyShrinking + .withWorkers(32) + .withMinSize(0) + .withMaxSize(150) } diff --git a/core/src/test/boilerplate/BListExtPSpec.scala.template b/core/src/test/boilerplate/BListExtPSpec.scala.template index ba41ba13..cd4c06ba 100644 --- a/core/src/test/boilerplate/BListExtPSpec.scala.template +++ b/core/src/test/boilerplate/BListExtPSpec.scala.template @@ -1,105 +1,159 @@ package laserdisc package protocol -import org.scalatest.OptionValues - -abstract class BListExtPSpec extends BaseSpec with OptionValues with BListP { - - "The Blocking List extended protocol" when { - - "using blpop" should { - - "fail to compile" when { - "given key but missing read instance" in { - """blpop[Bar](Key("a"))""" shouldNot compile - } - "given key and timeout but missing read instance" in { - """blpop[Bar](Key("a"), PosInt(1))""" shouldNot compile - } - } - - "roundtrip successfully" when { - //BLPOP with no timeout specified - [1..5#"given [#key1#]" in forAll([#"key1"#], "returned value") { ([#k1: Key#], i: Int) => - val protocol = blpop[Int]([#k1#]) - - protocol.encode shouldBe Arr(Bulk("BLPOP"), [#Bulk(k1)#], Bulk(##0)) - protocol.decode(Arr(Bulk(k##1), Bulk(i))) onRight (_.value shouldBe KV(k##1, i)) - }# - ] - //BLPOP with no timeout specified and specific read instance - [1..5#"given [#key1#] and specific read instance" in forAll([#"key1"#], "returned value") { ([#k1: Key#], i: Int) => - val protocol = blpop[Foo]([#k1#]) - - protocol.encode shouldBe Arr(Bulk("BLPOP"), [#Bulk(k1)#], Bulk(##0)) - protocol.decode(Arr(Bulk(k##1), Bulk(i))) onRight (_.value shouldBe KV(k##1, Foo(i))) - }# - ] - //BLPOP with timeout specified - [1..4#"given [#key1#] and positive timeout" in forAll([#"key1"#], "timeout", "returned value") { ([#k1: Key#], pi: PosInt, i: Int) => - val protocol = blpop[Int]([#k1#], pi) - - protocol.encode shouldBe Arr(Bulk("BLPOP"), [#Bulk(k1)#], Bulk(pi)) - protocol.decode(Arr(Bulk(k##1), Bulk(i))) onRight (_.value shouldBe KV(k##1, i)) - }# - ] - //BLPOP with timeout specified and specific read instance - [1..4#"given [#key1#], positive timeout and specific read instance" in forAll([#"key1"#], "timeout", "returned value") { ([#k1: Key#], pi: PosInt, i: Int) => - val protocol = blpop[Foo]([#k1#], pi) - - protocol.encode shouldBe Arr(Bulk("BLPOP"), [#Bulk(k1)#], Bulk(pi)) - protocol.decode(Arr(Bulk(k##1), Bulk(i))) onRight (_.value shouldBe KV(k##1, Foo(i))) - }# - ] - } - +import org.scalacheck.Prop.forAll + +final class BListExtPSpec extends BListPSpec { + test("The Blocking List extended protocol using blpop fails to compile given key but missing read instance") { + assertNoDiff( + compileErrors("""blpop[Bar](Key("a"))"""), + """|error: + |Implicit not found Read[laserdisc.protocol.Bulk, laserdisc.Bar]. + | + |Try writing your own, for example: + | + |implicit final val myRead: Read[laserdisc.protocol.Bulk, laserdisc.Bar] = new Read[laserdisc.protocol.Bulk, laserdisc.Bar] { + | override final def read(a: laserdisc.protocol.Bulk): Option[laserdisc.Bar] = ??? + |} + | + |Note 1: you can use the factory method Read.instance instead of creating it manually as shown above + |Note 2: make sure to inspect the combinators as you may be able to leverage some other Read instance + | + |blpop[Bar](Key("a")) + | ^ + |""".stripMargin + ) + } + test("The Blocking List extended protocol using blpop fails to compile given key and timeout but missing read instance") { + assertNoDiff( + compileErrors("""blpop[Bar](Key("a"), PosInt(1))"""), + """|error: + |Implicit not found Read[laserdisc.protocol.Bulk, laserdisc.Bar]. + | + |Try writing your own, for example: + | + |implicit final val myRead: Read[laserdisc.protocol.Bulk, laserdisc.Bar] = new Read[laserdisc.protocol.Bulk, laserdisc.Bar] { + | override final def read(a: laserdisc.protocol.Bulk): Option[laserdisc.Bar] = ??? + |} + | + |Note 1: you can use the factory method Read.instance instead of creating it manually as shown above + |Note 2: make sure to inspect the combinators as you may be able to leverage some other Read instance + | + |blpop[Bar](Key("a"), PosInt(1)) + | ^ + |""".stripMargin + ) + } + //BLPOP with no timeout specified + [1..5#property("The Blocking List extended protocol using blpop roundtrips successfully given [#key1#]") { + forAll { ([#k1: Key#], i: Int) => + val protocol = blpop[Int]([#k1#]) + assertEquals(protocol.encode, Arr(Bulk("BLPOP"), [#Bulk(k1)#], Bulk(##0))) + protocol.decode(Arr(Bulk(k##1), Bulk(i))) onRight (_ contains KV(k##1, i)) } - - "using brpop" should { - - "fail to compile" when { - "given key but missing read instance" in { - """brpop[Bar](Key("a"))""" shouldNot compile - } - "given key and timeout but missing read instance" in { - """brpop[Bar](Key("a"), PosInt(1))""" shouldNot compile - } - } - - "roundtrip successfully" when { - //BRPOP with no timeout specified - [1..5#"given [#key1#]" in forAll([#"key1"#], "returned value") { ([#k1: Key#], i: Int) => - val protocol = brpop[Int]([#k1#]) - - protocol.encode shouldBe Arr(Bulk("BRPOP"), [#Bulk(k1)#], Bulk(##0)) - protocol.decode(Arr(Bulk(k##1), Bulk(i))) onRight (_.value shouldBe KV(k##1, i)) - }# - ] - //BRPOP with no timeout specified and specific read instance - [1..5#"given [#key1#] and specific read instance" in forAll([#"key1"#], "returned value") { ([#k1: Key#], i: Int) => - val protocol = brpop[Foo]([#k1#]) - - protocol.encode shouldBe Arr(Bulk("BRPOP"), [#Bulk(k1)#], Bulk(##0)) - protocol.decode(Arr(Bulk(k##1), Bulk(i))) onRight (_.value shouldBe KV(k##1, Foo(i))) - }# - ] - //BRPOP with timeout specified - [1..4#"given [#key1#] and positive timeout" in forAll([#"key1"#], "timeout", "returned value") { ([#k1: Key#], pi: PosInt, i: Int) => - val protocol = brpop[Int]([#k1#], pi) - - protocol.encode shouldBe Arr(Bulk("BRPOP"), [#Bulk(k1)#], Bulk(pi)) - protocol.decode(Arr(Bulk(k##1), Bulk(i))) onRight (_.value shouldBe KV(k##1, i)) - }# - ] - //BRPOP with timeout specified and specific read instance - [1..4#"given [#key1#], positive timeout and specific read instance" in forAll([#"key1"#], "timeout", "returned value") { ([#k1: Key#], pi: PosInt, i: Int) => - val protocol = brpop[Foo]([#k1#], pi) - - protocol.encode shouldBe Arr(Bulk("BRPOP"), [#Bulk(k1)#], Bulk(pi)) - protocol.decode(Arr(Bulk(k##1), Bulk(i))) onRight (_.value shouldBe KV(k##1, Foo(i))) - }# - ] - } + }# + ] + //BLPOP with no timeout specified and specific read instance + [1..5#property("The Blocking List extended protocol using blpop roundtrips successfully given [#key1#] and specific read instance") { + forAll { ([#k1: Key#], i: Int) => + val protocol = blpop[Foo]([#k1#]) + assertEquals(protocol.encode, Arr(Bulk("BLPOP"), [#Bulk(k1)#], Bulk(##0))) + protocol.decode(Arr(Bulk(k##1), Bulk(i))) onRight (_ contains KV(k##1, Foo(i))) + } + }# + ] + //BLPOP with timeout specified + [1..5#property("The Blocking List extended protocol using blpop roundtrips successfully given [#key1#] and positive timeout") { + forAll { ([#k1: Key#], pi: PosInt, i: Int) => + val protocol = blpop[Int]([#k1#], pi) + assertEquals(protocol.encode, Arr(Bulk("BLPOP"), [#Bulk(k1)#], Bulk(pi))) + protocol.decode(Arr(Bulk(k##1), Bulk(i))) onRight (_ contains KV(k##1, i)) } + }# + ] + //BLPOP with timeout specified and specific read instance + [1..5#property("The Blocking List extended protocol using blpop roundtrips successfully given [#key1#], positive timeout and specific read instance") { + forAll { ([#k1: Key#], pi: PosInt, i: Int) => + val protocol = blpop[Foo]([#k1#], pi) + assertEquals(protocol.encode, Arr(Bulk("BLPOP"), [#Bulk(k1)#], Bulk(pi))) + protocol.decode(Arr(Bulk(k##1), Bulk(i))) onRight (_ contains KV(k##1, Foo(i))) + } + }# + ] + test("The Blocking List extended protocol using brpop fails to compile given key but missing read instance") { + assertNoDiff( + compileErrors("""brpop[Bar](Key("a"))"""), + """|error: + |Implicit not found Read[laserdisc.protocol.Bulk, laserdisc.Bar]. + | + |Try writing your own, for example: + | + |implicit final val myRead: Read[laserdisc.protocol.Bulk, laserdisc.Bar] = new Read[laserdisc.protocol.Bulk, laserdisc.Bar] { + | override final def read(a: laserdisc.protocol.Bulk): Option[laserdisc.Bar] = ??? + |} + | + |Note 1: you can use the factory method Read.instance instead of creating it manually as shown above + |Note 2: make sure to inspect the combinators as you may be able to leverage some other Read instance + | + |brpop[Bar](Key("a")) + | ^ + |""".stripMargin + ) + } + test("The Blocking List extended protocol using brpop fails to compile given key and timeout but missing read instance") { + assertNoDiff( + compileErrors("""brpop[Bar](Key("a"), PosInt(1))"""), + """|error: + |Implicit not found Read[laserdisc.protocol.Bulk, laserdisc.Bar]. + | + |Try writing your own, for example: + | + |implicit final val myRead: Read[laserdisc.protocol.Bulk, laserdisc.Bar] = new Read[laserdisc.protocol.Bulk, laserdisc.Bar] { + | override final def read(a: laserdisc.protocol.Bulk): Option[laserdisc.Bar] = ??? + |} + | + |Note 1: you can use the factory method Read.instance instead of creating it manually as shown above + |Note 2: make sure to inspect the combinators as you may be able to leverage some other Read instance + | + |brpop[Bar](Key("a"), PosInt(1)) + | ^ + |""".stripMargin + ) } + //BRPOP with no timeout specified + [1..5#property("The Blocking List extended protocol using brpop roundtrips successfully given [#key1#]") { + forAll { ([#k1: Key#], i: Int) => + val protocol = brpop[Int]([#k1#]) + assertEquals(protocol.encode, Arr(Bulk("BRPOP"), [#Bulk(k1)#], Bulk(##0))) + protocol.decode(Arr(Bulk(k##1), Bulk(i))) onRight (_ contains KV(k##1, i)) + } + }# + ] + //BRPOP with no timeout specified and specific read instance + [1..5#property("The Blocking List extended protocol using brpop roundtrips successfully given [#key1#] and specific read instance") { + forAll { ([#k1: Key#], i: Int) => + val protocol = brpop[Foo]([#k1#]) + assertEquals(protocol.encode, Arr(Bulk("BRPOP"), [#Bulk(k1)#], Bulk(##0))) + protocol.decode(Arr(Bulk(k##1), Bulk(i))) onRight (_ contains KV(k##1, Foo(i))) + } + }# + ] + //BRPOP with timeout specified + [1..5#property("The Blocking List extended protocol using brpop roundtrips successfully given [#key1#] and positive timeout") { + forAll { ([#k1: Key#], pi: PosInt, i: Int) => + val protocol = brpop[Int]([#k1#], pi) + assertEquals(protocol.encode, Arr(Bulk("BRPOP"), [#Bulk(k1)#], Bulk(pi))) + protocol.decode(Arr(Bulk(k##1), Bulk(i))) onRight (_ contains KV(k##1, i)) + } + }# + ] + //BRPOP with timeout specified and specific read instance + [1..5#property("The Blocking List extended protocol using brpop roundtrips successfully given [#key1#], positive timeout and specific read instance") { + forAll { ([#k1: Key#], pi: PosInt, i: Int) => + val protocol = brpop[Foo]([#k1#], pi) + assertEquals(protocol.encode, Arr(Bulk("BRPOP"), [#Bulk(k1)#], Bulk(pi))) + protocol.decode(Arr(Bulk(k##1), Bulk(i))) onRight (_ contains KV(k##1, Foo(i))) + } + }# + ] } diff --git a/core/src/test/boilerplate/GeoExtPSpec.scala.template b/core/src/test/boilerplate/GeoExtPSpec.scala.template index aa3e31d2..283bf189 100644 --- a/core/src/test/boilerplate/GeoExtPSpec.scala.template +++ b/core/src/test/boilerplate/GeoExtPSpec.scala.template @@ -1,85 +1,33 @@ package laserdisc package protocol -abstract class GeoExtPSpec extends BaseSpec with GeoP { - import geotypes._ - import org.scalacheck.{Arbitrary, Gen} - import org.scalacheck.Arbitrary.arbitrary - - protected implicit final val geoHashShow: Show[GeoHash] = Show.unsafeFromToString - - protected implicit final val geoCoordinatesArb: Arbitrary[GeoCoordinates] = Arbitrary { - for { - lat <- arbitrary[Latitude] - long <- arbitrary[Longitude] - } yield GeoCoordinates(lat, long) - } - protected implicit final val geoPositionArb: Arbitrary[GeoPosition] = Arbitrary { - for { - m <- arbitrary[Key] - lat <- arbitrary[Latitude] - long <- arbitrary[Longitude] - } yield GeoPosition(m, lat, long) - } - protected implicit final val geoUnitArb: Arbitrary[GeoUnit] = Arbitrary { - Gen.oneOf(GeoUnit.meters, GeoUnit.kilometers, GeoUnit.miles, GeoUnit.feet) - } +import org.scalacheck.Prop.forAll - protected final val geoPositionToBulkList: GeoPosition => List[Bulk] = { - case GeoPosition(m, lat, long) => Bulk(long) :: Bulk(lat) :: Bulk(m) :: Nil - } - protected final val nonNegDoubleOptionToBulk: Option[NonNegDouble] => GenBulk = _.fold(NullBulk: GenBulk)(Bulk(_)) - protected final val oneOrMoreGeoCoordinatesOptionToArr: OneOrMore[Option[GeoCoordinates]] => GenArr = _.value.foldLeft(NilArr: GenArr) { - case (NilArr, Some(GeoCoordinates(lat, long))) => Arr(Arr(Bulk(long), Bulk(lat))) - case (NilArr, None) => Arr(NilArr) - case (Arr(e), Some(GeoCoordinates(lat, long))) => Arr(e :+ Arr(Bulk(long), Bulk(lat))) - case (Arr(e), None) => Arr(e :+ NilArr) - } - protected final val oneOrMoreGeoHashOptionToArr: OneOrMore[Option[GeoHash]] => GenArr = _.value.foldLeft(NilArr: GenArr) { - case (NilArr, Some(gh)) => Arr(Bulk(gh)) - case (NilArr, None) => Arr(NullBulk) - case (Arr(e), Some(gh)) => Arr(e :+ Bulk(gh)) - case (Arr(e), None) => Arr(e :+ NullBulk) - } - - "The Geo extended protocol" when { - - "using geoadd" should { - "roundtrip successfully" when { - [1..4#"given key and [#position1#]" in forAll("key", [#"position1"#], "added") { (k: Key, [#p1: GeoPosition#], nni: NonNegInt) => - val protocol = geoadd(k, [#p1#]) +final class GeoExtPSpec extends GeoPSpec { + import geotypes._ - protocol.encode shouldBe Arr(Bulk("GEOADD") :: Bulk(k) :: List([#p1#]).flatMap(geoPositionToBulkList)) - protocol.decode(Num(nni.value.toLong)) onRight (_ shouldBe nni) - }# - ] - } + [1..5#property("The Geo extended protocol using geoadd roundtrips successfully given key and [#position1#]") { + forAll { (k: Key, [#p1: GeoPosition#], nni: NonNegInt) => + val protocol = geoadd(k, [#p1#]) + assertEquals(protocol.encode, Arr(Bulk("GEOADD") :: Bulk(k) :: List([#p1#]).flatMap(geoPositionToBulkList))) + assertEquals(protocol.decode(Num(nni.value.toLong)), nni) } - - "using geohash" should { - - "roundtrip successfully" when { - [1..4#"given key and [#member1#]" in forAll("key", [#"member1"#], "geo hashes") { (k: Key, [#m1: Key#], oghs: OneOrMore[Option[GeoHash]]) => - val protocol = geohash(k, [#m1#]) - - protocol.encode shouldBe Arr(Bulk("GEOHASH") :: Bulk(k) :: List([#Bulk(m1)#])) - protocol.decode(oneOrMoreGeoHashOptionToArr(oghs)) onRight (_ shouldBe oghs.value) - }# - ] - } + }# + ] + [1..5#property("The Geo extended protocol using geohash roundtrips successfully given key and [#member1#]") { + forAll { (k: Key, [#m1: Key#], oghs: OneOrMore[Option[GeoHash]]) => + val protocol = geohash(k, [#m1#]) + assertEquals(protocol.encode, Arr(Bulk("GEOHASH") :: Bulk(k) :: List([#Bulk(m1)#]))) + assertEquals(protocol.decode(oneOrMoreGeoHashOptionToArr(oghs)), oghs.value) } - - "using geopos" should { - - "roundtrip successfully" when { - [1..4#"given key and [#member1#]" in forAll("key", [#"member1"#], "coordinates") { (k: Key, [#m1: Key#], ocs: OneOrMore[Option[GeoCoordinates]]) => - val protocol = geopos(k, [#m1#]) - - protocol.encode shouldBe Arr(Bulk("GEOPOS") :: Bulk(k) :: List([#Bulk(m1)#])) - protocol.decode(oneOrMoreGeoCoordinatesOptionToArr(ocs)) onRight (_ shouldBe ocs.value) - }# - ] - } + }# + ] + [1..5#property("The Geo extended protocol using geopos roundtrips successfully given key and [#member1#]") { + forAll { (k: Key, [#m1: Key#], ocs: OneOrMore[Option[GeoCoordinates]]) => + val protocol = geopos(k, [#m1#]) + assertEquals(protocol.encode, Arr(Bulk("GEOPOS") :: Bulk(k) :: List([#Bulk(m1)#]))) + assertEquals(protocol.decode(oneOrMoreGeoCoordinatesOptionToArr(ocs)), ocs.value) } - } + }# + ] } diff --git a/core/src/test/boilerplate/HashExtPSpec.scala.template b/core/src/test/boilerplate/HashExtPSpec.scala.template index 70c2e1d4..81986b5a 100644 --- a/core/src/test/boilerplate/HashExtPSpec.scala.template +++ b/core/src/test/boilerplate/HashExtPSpec.scala.template @@ -1,51 +1,35 @@ package laserdisc package protocol -abstract class HashExtPSpec extends BaseSpec with HashP { - - "The Hash extended protocol" when { - - "using hdel" should { - - "roundtrip successfully" when { - [1..4#"given key and [#field1#]" in forAll("key", [#"field1"#], "deleted") { (k: Key, [#f1: Key#], nni: NonNegInt) => - val protocol = hdel(k, [#f1#]) - - protocol.encode shouldBe Arr(Bulk("HDEL"), Bulk(k), [#Bulk(f1)#]) - protocol.decode(Num(nni.value.toLong)) onRight (_ shouldBe nni) - }# - ] - } +import org.scalacheck.Prop.forAll + +final class HashExtPSpec extends HashPSpec { + [1..5#property("The Hash extended protocol using hdel roundtrips successfully given key and [#field1#]") { + forAll { (k: Key, [#f1: Key#], nni: NonNegInt) => + val protocol = hdel(k, [#f1#]) + assertEquals(protocol.encode, Arr(Bulk("HDEL"), Bulk(k), [#Bulk(f1)#])) + assertEquals(protocol.decode(Num(nni.value.toLong)), nni) } - - "using hmget" should { - - "roundtrip successfully" when { - [1..5#"given key and [#field1#]" in forAll("key", [#"field1"#]) { (k: Key, [#f1: Key#]) => - forAll([#"returned value1"#]) { ([#v1: Int#]) => - val protocol = hmget[[#Int#]](k, [#f1#]) - - protocol.encode shouldBe Arr(Bulk("HMGET"), Bulk(k), [#Bulk(f1)#]) - protocol.decode(Arr([#Bulk(v1)#])) onRight (_ shouldBe (([#v1#]))) - } - }# - ] + }# + ] + [1..5#property("The Hash extended protocol using hmget roundtrips successfully given key and [#field1#]") { + forAll { (k: Key, [#f1: Key#]) => + forAll { ([#v1: Int#]) => + val protocol = hmget[[#Int#]](k, [#f1#]) + assertEquals(protocol.encode, Arr(Bulk("HMGET"), Bulk(k), [#Bulk(f1)#])) + assertEquals(protocol.decode(Arr([#Bulk(v1)#])), (([#v1#]))) } } - - "using hmset" should { - - "roundtrip successfully" when { - [1..5#"given key and [#field1#]" in forAll("key", [#"field1"#]) { (k: Key, [#f1: Key#]) => - forAll([#"value1"#]) { ([#v1: Int#]) => - val protocol = hmset(k, [#f1, v1#]) - - protocol.encode shouldBe Arr(Bulk("HMSET"), Bulk(k), [#Bulk(f1), Bulk(v1)#]) - protocol.decode(Str(OK.value)) onRight (_ shouldBe OK) - } - }# - ] + }# + ] + [1..5#property("The Hash extended protocol using hmset roundtrips successfully given key and [#field1#]") { + forAll { (k: Key, [#f1: Key#]) => + forAll { ([#v1: Int#]) => + val protocol = hmset(k, [#f1, v1#]) + assertEquals(protocol.encode, Arr(Bulk("HMSET"), Bulk(k), [#Bulk(f1), Bulk(v1)#])) + assertEquals(protocol.decode(Str(OK.value)), OK) } } - } + }# + ] } diff --git a/core/src/test/boilerplate/HyperLogLogExtPSpec.scala.template b/core/src/test/boilerplate/HyperLogLogExtPSpec.scala.template index 8b354bc6..544ac3cd 100644 --- a/core/src/test/boilerplate/HyperLogLogExtPSpec.scala.template +++ b/core/src/test/boilerplate/HyperLogLogExtPSpec.scala.template @@ -1,47 +1,31 @@ package laserdisc package protocol -abstract class HyperLogLogExtPSpec extends BaseSpec with HyperLogLogP { - - "The HyperLogLog extended protocol" when { - - "using pfadd" should { - - "roundtrip successfully" when { - [1..4#"given key and [#element1#]" in forAll("key", [#"element1"#], "return value") { (k: Key, [#e1: Key#], b: Boolean) => - val protocol = pfadd(k, [#e1#]) - - protocol.encode shouldBe Arr(Bulk("PFADD"), Bulk(k), [#Bulk(e1)#]) - protocol.decode(boolToNum(b)) onRight (_ shouldBe b) - }# - ] - } +import org.scalacheck.Prop.forAll + +final class HyperLogLogExtPSpec extends HyperLogLogPSpec { + [1..5#property("The HyperLogLog extended protocol using pfadd roundtrips successfully given key and [#element1#]") { + forAll { (k: Key, [#e1: Key#], b: Boolean) => + val protocol = pfadd(k, [#e1#]) + assertEquals(protocol.encode, Arr(Bulk("PFADD"), Bulk(k), [#Bulk(e1)#])) + assertEquals(protocol.decode(boolToNum(b)), b) } - - "using pfcount" should { - - "roundtrip successfully" when { - [1..5#"given [#key1#]" in forAll([#"key1"#], "return value") { ([#k1: Key#], nni: NonNegInt) => - val protocol = pfcount([#k1#]) - - protocol.encode shouldBe Arr(Bulk("PFCOUNT"), [#Bulk(k1)#]) - protocol.decode(Num(nni.value.toLong)) onRight (_ shouldBe nni) - }# - ] - } + }# + ] + [1..5#property("The HyperLogLog extended protocol using pfcount roundtrips successfully given [#key1#]") { + forAll { ([#k1: Key#], nni: NonNegInt) => + val protocol = pfcount([#k1#]) + assertEquals(protocol.encode, Arr(Bulk("PFCOUNT"), [#Bulk(k1)#])) + assertEquals(protocol.decode(Num(nni.value.toLong)), nni) } - - "using pfmerge" should { - - "roundtrip successfully" when { - [2..4#"given [#sourcekey1#] and destinationkey" in forAll([#"sourcekey1"#], "destinationkey") { ([#sk1: Key#], dk: Key) => - val protocol = pfmerge([#sk1#], dk) - - protocol.encode shouldBe Arr(Bulk("PFMERGE"), Bulk(dk), [#Bulk(sk1)#]) - protocol.decode(Str(OK.value)) onRight (_ shouldBe OK) - }# - ] - } + }# + ] + [2..5#property("The HyperLogLog extended protocol using pfmerge roundtrips successfully given [#sourcekey1#] and destinationkey") { + forAll { ([#sk1: Key#], dk: Key) => + val protocol = pfmerge([#sk1#], dk) + assertEquals(protocol.encode, Arr(Bulk("PFMERGE"), Bulk(dk), [#Bulk(sk1)#])) + assertEquals(protocol.decode(Str(OK.value)), OK) } - } + }# + ] } diff --git a/core/src/test/boilerplate/KeyExtPSpec.scala.template b/core/src/test/boilerplate/KeyExtPSpec.scala.template index 547679c9..a21f5b25 100644 --- a/core/src/test/boilerplate/KeyExtPSpec.scala.template +++ b/core/src/test/boilerplate/KeyExtPSpec.scala.template @@ -1,105 +1,65 @@ package laserdisc package protocol -abstract class KeyExtPSpec extends BaseSpec with KeyP { +import org.scalacheck.Prop.forAll + +final class KeyExtPSpec extends KeyPSpec { import keytypes._ - import org.scalacheck.{Arbitrary, Gen} - protected implicit final val keyMigrateModeArb: Arbitrary[KeyMigrateMode] = Arbitrary { - Gen.oneOf(KeyMigrateMode.both, KeyMigrateMode.copy, KeyMigrateMode.replace) - } - protected implicit final val nokeyOrOkArb: Arbitrary[NOKEY | OK] = Arbitrary( - Gen.oneOf(NOKEY, OK).map { - case NOKEY => Left(NOKEY) - case OK => Right(OK) + [1..5#property("The Key extended protocol using del roundtrips successfully given [#key1#]") { + forAll { ([#k1: Key#], nni: NonNegInt) => + val protocol = del([#k1#]) + assertEquals(protocol.encode, Arr(Bulk("DEL"), [#Bulk(k1)#])) + assertEquals(protocol.decode(Num(nni.value.toLong)), nni) } - ) - - protected final val noKeyOrOkToStr: (NOKEY | OK) => Str = { - case Left(_) => Str(NOKEY.value) - case Right(_) => Str(OK.value) - } - - "The Key extended protocol" when { - - "using del" should { - - "roundtrip successfully" when { - [1..5#"given [#key1#]" in forAll([#"key1"#], "deleted") { ([#k1: Key#], nni: NonNegInt) => - val protocol = del([#k1#]) - - protocol.encode shouldBe Arr(Bulk("DEL"), [#Bulk(k1)#]) - protocol.decode(Num(nni.value.toLong)) onRight (_ shouldBe nni) - }# - ] - } + }# + ] + [1..5#property("The Key extended protocol using exists roundtrips successfully given [#key1#]") { + forAll { ([#k1: Key#], opi: Option[PosInt]) => + val protocol = exists([#k1#]) + assertEquals(protocol.encode, Arr(Bulk("EXISTS"), [#Bulk(k1)#])) + assertEquals(protocol.decode(Num(opi.fold(##0L)(_.value.toLong))), opi) } - - "using exists" should { - - "roundtrip successfully" when { - [1..5#"given [#key1#]" in forAll([#"key1"#], "exists") { ([#k1: Key#], opi: Option[PosInt]) => - val protocol = exists([#k1#]) - - protocol.encode shouldBe Arr(Bulk("EXISTS"), [#Bulk(k1)#]) - protocol.decode(Num(opi.fold(##0L)(_.value.toLong))) onRight (_ shouldBe opi) - }# - ] - } + }# + ] + [2..6#property("The Key extended protocol using migrate roundtrips successfully given [#key1#], host, port, db index and timeout") { + forAll { (keys: ([#Key#]), input: (Host, Port, DbIndex, NonNegInt, NOKEY | OK)) => + val ([#k1#]) = keys + val (h, p, dbi, nni, nkOrOk) = input + val protocol = migrate([#k1#], h, p, dbi, nni) + assertEquals(protocol.encode, Arr( + Bulk("MIGRATE") :: Bulk(h) :: Bulk(p) :: Bulk("") :: Bulk(dbi) :: Bulk(nni) :: Bulk("KEYS") :: [#Bulk(k1)# ::] :: Nil + )) + assertEquals(protocol.decode(noKeyOrOkToStr(nkOrOk)), nkOrOk) } - - "using migrate" should { - - "roundtrip successfully" when { - [2..6#"given [#key1#], host, port, db index and timeout" in forAll("[#key1#]", "host, port, db index, timeout, response") { (keys: ([#Key#]), input: (Host, Port, DbIndex, NonNegInt, NOKEY | OK)) => - val ([#k1#]) = keys - val (h, p, dbi, nni, nkOrOk) = input - val protocol = migrate([#k1#], h, p, dbi, nni) - - protocol.encode shouldBe Arr( - Bulk("MIGRATE") :: Bulk(h) :: Bulk(p) :: Bulk("") :: Bulk(dbi) :: Bulk(nni) :: Bulk("KEYS") :: [#Bulk(k1)# ::] :: Nil - ) - protocol.decode(noKeyOrOkToStr(nkOrOk)) onRight (_ shouldBe nkOrOk) - }# - ] - [2..6#"given [#key1#], host, port, db index, timeout and migrate mode" in forAll("[#key1#]", "host, port, db index, timeout, migrate mode, response") { (keys: ([#Key#]), input: (Host, Port, DbIndex, NonNegInt, KeyMigrateMode, NOKEY | OK)) => - val ([#k1#]) = keys - val (h, p, dbi, nni, mm, nkOrOk) = input - val protocol = migrate([#k1#], h, p, dbi, nni, mm) - - protocol.encode shouldBe Arr( - Bulk("MIGRATE") :: Bulk(h) :: Bulk(p) :: Bulk("") :: Bulk(dbi) :: Bulk(nni) :: mm.params.map(Bulk(_)) ::: (Bulk("KEYS") :: [#Bulk(k1)# ::] :: Nil) - ) - protocol.decode(noKeyOrOkToStr(nkOrOk)) onRight (_ shouldBe nkOrOk) - }# - ] - } + }# + ] + [2..6#property("The Key extended protocol using migrate roundtrips successfully given [#key1#], host, port, db index, timeout and migrate mode") { + forAll { (keys: ([#Key#]), input: (Host, Port, DbIndex, NonNegInt, KeyMigrateMode, NOKEY | OK)) => + val ([#k1#]) = keys + val (h, p, dbi, nni, mm, nkOrOk) = input + val protocol = migrate([#k1#], h, p, dbi, nni, mm) + assertEquals(protocol.encode, Arr( + Bulk("MIGRATE") :: Bulk(h) :: Bulk(p) :: Bulk("") :: Bulk(dbi) :: Bulk(nni) :: mm.params.map(Bulk(_)) ::: (Bulk("KEYS") :: [#Bulk(k1)# ::] :: Nil) + )) + assertEquals(protocol.decode(noKeyOrOkToStr(nkOrOk)), nkOrOk) } - - "using touch" should { - - "roundtrip successfully" when { - [1..5#"given [#key1#]" in forAll([#"key1"#], "touched") { ([#k1: Key#], nni: NonNegInt) => - val protocol = touch([#k1#]) - - protocol.encode shouldBe Arr(Bulk("TOUCH"), [#Bulk(k1)#]) - protocol.decode(Num(nni.value.toLong)) onRight (_ shouldBe nni) - }# - ] - } + }# + ] + [1..5#property("The Key extended protocol using touch roundtrips successfully given [#key1#]") { + forAll { ([#k1: Key#], nni: NonNegInt) => + val protocol = touch([#k1#]) + assertEquals(protocol.encode, Arr(Bulk("TOUCH"), [#Bulk(k1)#])) + assertEquals(protocol.decode(Num(nni.value.toLong)), nni) } - - "using unlink" should { - - "roundtrip successfully" when { - [1..5#"given [#key1#]" in forAll([#"key1"#], "unlinked") { ([#k1: Key#], nni: NonNegInt) => - val protocol = unlink([#k1#]) - - protocol.encode shouldBe Arr(Bulk("UNLINK"), [#Bulk(k1)#]) - protocol.decode(Num(nni.value.toLong)) onRight (_ shouldBe nni) - }# - ] - } + }# + ] + [1..5#property("The Key extended protocol using unlink roundtrips successfully given [#key1#]") { + forAll { ([#k1: Key#], nni: NonNegInt) => + val protocol = unlink([#k1#]) + assertEquals(protocol.encode, Arr(Bulk("UNLINK"), [#Bulk(k1)#])) + assertEquals(protocol.decode(Num(nni.value.toLong)), nni) } - } + }# + ] } diff --git a/core/src/test/boilerplate/ListExtPSpec.scala.template b/core/src/test/boilerplate/ListExtPSpec.scala.template index 4f3f133f..db473fd9 100644 --- a/core/src/test/boilerplate/ListExtPSpec.scala.template +++ b/core/src/test/boilerplate/ListExtPSpec.scala.template @@ -1,34 +1,23 @@ package laserdisc package protocol -abstract class ListExtPSpec extends BaseSpec with ListP { - - "The List extended protocol" when { - - "using lpush" should { - - "roundtrip successfully" when { - [1..4#"given key and [#value1#]" in forAll("key", [#"value1"#], "pushed") { (k: Key, [#v1: Int#], pi: PosInt) => - val protocol = lpush(k, [#v1#]) - - protocol.encode shouldBe Arr(Bulk("LPUSH"), Bulk(k), [#Bulk(v1)#]) - protocol.decode(Num(pi.value.toLong)) onRight (_ shouldBe pi) - }# - ] - } +import org.scalacheck.Prop.forAll + +final class ListExtPSpec extends ListPSpec { + [1..5#property("The List extended protocol using lpush roundtrips successfully given key and [#value1#]") { + forAll { (k: Key, [#v1: Int#], pi: PosInt) => + val protocol = lpush(k, [#v1#]) + assertEquals(protocol.encode, Arr(Bulk("LPUSH"), Bulk(k), [#Bulk(v1)#])) + assertEquals(protocol.decode(Num(pi.value.toLong)), pi) } - - "using rpush" should { - - "roundtrip successfully" when { - [1..4#"given key and [#value1#]" in forAll("key", [#"value1"#], "pushed") { (k: Key, [#v1: Int#], pi: PosInt) => - val protocol = rpush(k, [#v1#]) - - protocol.encode shouldBe Arr(Bulk("RPUSH"), Bulk(k), [#Bulk(v1)#]) - protocol.decode(Num(pi.value.toLong)) onRight (_ shouldBe pi) - }# - ] - } + }# + ] + [1..5#property("The List extended protocol using rpush roundtrips successfully given key and [#value1#]") { + forAll { (k: Key, [#v1: Int#], pi: PosInt) => + val protocol = rpush(k, [#v1#]) + assertEquals(protocol.encode, Arr(Bulk("RPUSH"), Bulk(k), [#Bulk(v1)#])) + assertEquals(protocol.decode(Num(pi.value.toLong)), pi) } - } + }# + ] } diff --git a/core/src/test/scala/laserdisc/BaseSpec.scala b/core/src/test/scala/laserdisc/BaseSpec.scala index c1a3bd65..4dd0ec57 100644 --- a/core/src/test/scala/laserdisc/BaseSpec.scala +++ b/core/src/test/scala/laserdisc/BaseSpec.scala @@ -5,27 +5,21 @@ import eu.timepit.refined.api._ import eu.timepit.refined.generic.Equal import eu.timepit.refined.scalacheck.reftype.arbitraryRefType import eu.timepit.refined.scalacheck.{CollectionInstancesBinCompat1, NumericInstances, StringInstances} +import munit.ScalaCheckSuite import org.scalacheck.Arbitrary.arbitrary import org.scalacheck.Gen._ -import org.scalacheck.{Arbitrary, Gen, Shrink} -import org.scalatest.matchers.should.Matchers -import org.scalatest.wordspec.AnyWordSpec -import org.scalatest.{Assertion, EitherValues} +import org.scalacheck.{Arbitrary, Gen} import scala.Double.{NaN, MaxValue => DMax, MinValue => DMin} import scala.Int.{MaxValue => IMax, MinValue => IMin} import scala.Long.{MaxValue => LMax, MinValue => LMin} abstract class BaseSpec - extends AnyWordSpec - with Matchers - with EitherValues + extends ScalaCheckSuite + with ScalaCheckSettings with CollectionInstancesBinCompat1 with NumericInstances - with StringInstances - with ScalaCheckSettings { - - implicit def noShrink[T]: Shrink[T] = Shrink.shrinkAny + with StringInstances { protected final type EmptyString = String Refined Equal[W.`""`.T] protected final val EmptyString: EmptyString = RefType.applyRefM[EmptyString]("") @@ -104,11 +98,12 @@ abstract class BaseSpec final val twoOrMoreWeightedKeysIsValid: List[(Key, ValidDouble)] => Boolean = Validate[List[(Key, ValidDouble)], TwoOrMoreRef].isValid final val validDoubleIsValid: Double => Boolean = Validate[Double, ValidDoubleRef].isValid - final val connectionNameGen: Gen[String] = strGen(nonEmptyListOf(noSpaceUtf8BMPCharGen)) :| "connection name" - final val dbIndexGen: Gen[Int] = chooseNum(0, DbIndexMaxValueWit.value) :| "db index" - final val directionGen: Gen[Direction] = Gen.oneOf(Direction.asc, Direction.desc) :| "direction" - final val geoHashGen: Gen[String] = strOfNGen(11, 1 -> numChar, 9 -> alphaLowerChar) :| "geo hash" - final val globPatternGen: Gen[String] = nonEmptyListOf(globGen).map(_.mkString) :| "glob pattern" + final val connectionNameGen: Gen[String] = + strGen(nonEmptyListOf(noSpaceUtf8BMPCharGen)).filter(connectionNameIsValid) :| "connection name" + final val dbIndexGen: Gen[Int] = chooseNum(0, DbIndexMaxValueWit.value).filter(dbIndexIsValid) :| "db index" + final val directionGen: Gen[Direction] = Gen.oneOf(Direction.asc, Direction.desc) :| "direction" + final val geoHashGen: Gen[String] = strOfNGen(11, 1 -> numChar, 9 -> alphaLowerChar).filter(geoHashIsValid) :| "geo hash" + final val globPatternGen: Gen[String] = nonEmptyListOf(globGen).map(_.mkString).filter(globPatternIsValid) :| "glob pattern" final val hostGen: Gen[Host] = { val hosts = 7 -> Gen .oneOf(allNICsGen, lbGen, rfc1123Gen, rfc1918Gen, rfc5737Gen, rfc3927Gen, rfc2544Gen) @@ -120,22 +115,25 @@ abstract class BaseSpec } :| "host" final val hostOrEmptyGen: Gen[EmptyString | Host] = frequency(10 -> hostGen.map(Right.apply), 1 -> const(EmptyString).map(Left.apply)) :| "host or empty string" - final val keyGen: Gen[Key] = strGen(nonEmptyListOf(utf8BMPCharGen)).filter(keyIsValid).map(Key.unsafeFrom) :| "key" - final val latitudeGen: Gen[Double] = chooseNum(LatitudeMinValueWit.value, LatitudeMaxValueWit.value) :| "latitude" - final val longitudeGen: Gen[Double] = chooseNum(LongitudeMinValueWit.value, LongitudeMaxValueWit.value) :| "longitude" - final val nodeIdGen: Gen[String] = strOfNSameFreqGen(40, hexGen) :| "node id" - final val nonNegDoubleGen: Gen[Double] = chooseNum(0.0d, DMax) :| "double >= 0.0D" - final val nonNegIntGen: Gen[Int] = chooseNum(0, IMax) :| "int >= 0" - final val nonNegLongGen: Gen[Long] = chooseNum(0L, LMax) :| "long >= 0L" - final val nonZeroDoubleGen: Gen[Double] = chooseNum(DMin, DMax).suchThat(d => d != 0.0d && d != NaN) :| "double != 0.0D and != NaN" - final val nonZeroIntGen: Gen[Int] = chooseNum(IMin, IMax).suchThat(_ != 0) :| "int != 0" - final val nonZeroLongGen: Gen[Long] = chooseNum(LMin, LMax).suchThat(_ != 0L) :| "long != 0L" - final val portGen: Gen[Port] = chooseNum(PortMinValueWit.value, PortMaxValueWit.value).map(Port.unsafeFrom) :| "port" - final val rangeOffsetGen: Gen[Int] = chooseNum(0, RangeOffsetMaxValueWit.value) :| "range offset" - final val slotGen: Gen[Slot] = chooseNum(0, SlotMaxValueWit.value).map(Slot.unsafeFrom) :| "slot" - final val stringLengthGen: Gen[Long] = chooseNum(0L, StringLengthMaxValueWit.value) :| "string length" - final val stringsWithSpacesGen: Gen[String] = strGen(listOf(frequency(1 -> spaceGen, 10 -> noSpaceUtf8BMPCharGen))) :| "string w/ spaces" - final val validDoubleGen: Gen[Double] = chooseNum(DMin, DMax).suchThat(_ != NaN) :| "double != NaN" + final val keyGen: Gen[Key] = strGen(nonEmptyListOf(utf8BMPCharGen)).filter(keyIsValid).map(Key.unsafeFrom) :| "key" + final val latitudeGen: Gen[Double] = chooseNum(LatitudeMinValueWit.value, LatitudeMaxValueWit.value).filter(latitudeIsValid) :| "latitude" + final val longitudeGen: Gen[Double] = + chooseNum(LongitudeMinValueWit.value, LongitudeMaxValueWit.value).filter(longitudeIsValid) :| "longitude" + final val nodeIdGen: Gen[String] = strOfNSameFreqGen(40, hexGen).filter(nodeIdIsValid) :| "node id" + final val nonNegDoubleGen: Gen[Double] = chooseNum(0.0d, DMax).filter(nonNegDoubleIsValid) :| "double >= 0.0D" + final val nonNegIntGen: Gen[Int] = chooseNum(0, IMax).filter(nonNegIntIsValid) :| "int >= 0" + final val nonNegLongGen: Gen[Long] = chooseNum(0L, LMax).filter(nonNegLongIsValid) :| "long >= 0L" + final val nonZeroDoubleGen: Gen[Double] = + chooseNum(DMin, DMax).suchThat(d => d != 0.0d && d != NaN).filter(nonZeroDoubleIsValid) :| "double != 0.0D and != NaN" + final val nonZeroIntGen: Gen[Int] = chooseNum(IMin, IMax).suchThat(_ != 0).filter(nonZeroIntIsValid) :| "int != 0" + final val nonZeroLongGen: Gen[Long] = chooseNum(LMin, LMax).suchThat(_ != 0L).filter(nonZeroLongIsValid) :| "long != 0L" + final val portGen: Gen[Port] = chooseNum(PortMinValueWit.value, PortMaxValueWit.value).map(Port.unsafeFrom) :| "port" + final val rangeOffsetGen: Gen[Int] = chooseNum(0, RangeOffsetMaxValueWit.value).filter(rangeOffsetIsValid) :| "range offset" + final val slotGen: Gen[Slot] = chooseNum(0, SlotMaxValueWit.value).map(Slot.unsafeFrom) :| "slot" + final val stringLengthGen: Gen[Long] = chooseNum(0L, StringLengthMaxValueWit.value).filter(stringLengthIsValid) :| "string length" + final val stringsWithSpacesGen: Gen[String] = + strGen(listOf(frequency(1 -> spaceGen, 10 -> noSpaceUtf8BMPCharGen))).filterNot(connectionNameIsValid) :| "string w/ spaces" + final val validDoubleGen: Gen[Double] = chooseNum(DMin, DMax).suchThat(_ != NaN) :| "double != NaN" implicit final val connectionNameArb: Arbitrary[ConnectionName] = arbitraryRefType(connectionNameGen) implicit final val directionArb: Arbitrary[Direction] = Arbitrary(directionGen) @@ -164,12 +162,20 @@ abstract class BaseSpec final val boolToNum: Boolean => Num = b => Num(if (b) 1 else 0) - protected[this] implicit final class EitherSyntax[A, B](private val eab: Either[A, B]) { - def onRight[C](f: B => Assertion): Assertion = - eab.fold(err => fail(s"It Should be right but was left with $err"), f) + private[laserdisc] def assertEquals[A, B](eab: Either[A, B], b: B): Unit = + eab.fold(err => fail(s"It Should be right but was left with $err"), r => assertEquals(r, b)) + + private[laserdisc] def assertLeftEquals[A, B](eab: Either[A, B], a: A): Unit = + eab.fold(l => assertEquals(l, a), res => fail(s"It Should be left but was right with $res")) - def onLeft[C](e: A => Assertion): Assertion = - eab.fold(e, res => fail(s"It Should be left but was right with $res")) + private[laserdisc] def fails[A, B](eab: Either[A, B], a: A): Unit = + eab.fold(e => assertEquals(e, a), res => fail(s"It Should be left but was right with $res")) + + final val succeed = assert(cond = true) + + protected[this] implicit final class EitherSyntax[A, B](private val eab: Either[A, B]) { + def onRight[C](f: B => Boolean): Unit = + eab.fold(err => fail(s"It Should be right but was left with $err"), b => assert(f(b))) def onRightAll[C](f: B => Unit): Unit = eab.fold(err => fail(s"It Should be right but was left with $err"), f) diff --git a/core/src/test/scala/laserdisc/RESPFrameFixture.scala b/core/src/test/scala/laserdisc/RESPFrameFixture.scala index b2b063ba..29822f02 100644 --- a/core/src/test/scala/laserdisc/RESPFrameFixture.scala +++ b/core/src/test/scala/laserdisc/RESPFrameFixture.scala @@ -2,9 +2,13 @@ package laserdisc import eu.timepit.refined.types.string.NonEmptyString import laserdisc.protocol._ +import org.scalacheck.Gen.chooseNum +import org.scalacheck.{Arbitrary, Gen} import scodec.bits.BitVector -private[laserdisc] trait RESPFrameFixture { +import scala.Long.{MaxValue, MinValue} + +private[laserdisc] trait RESPFrameFixture extends HighPriorityGenerators { final val shortStr = " Short string repeated string repeated string repeated" final val mediumStr = "Medium size string repeated medium size string repeated medium size string repeated medium size string repeated medium size string repeated medium size string repeated medium size string repeated medium size string repeated medium size string" @@ -138,3 +142,97 @@ private[laserdisc] trait RESPFrameFixture { aggregate._2 } } + +private[laserdisc] trait HighPriorityGenerators extends LowPriorityGenerators { + + private[this] def oneOrMoreProtocol(gen: Gen[ProtocolEncoded]): Gen[OneOrMore[ProtocolEncoded]] = + Gen.chooseNum(1, Math.min(scalaCheckTestParameters.maxSize, 300)) flatMap (Gen.listOfN(_, gen)) map OneOrMore.unsafeFrom + + private[this] def listProtocol(gen: Gen[ProtocolEncoded]): Gen[List[ProtocolEncoded]] = + Gen.chooseNum(1, 20) flatMap (Gen.listOfN(_, gen)) + + private[this] final def noArrEncoded( + implicit bulk: Gen[BulkEncoded], + num: Gen[NumEncoded], + str: Gen[StrEncoded], + err: Gen[ErrEncoded], + emptyBulk: Gen[EmptyBulkEncoded], + nullBulk: Gen[NullBulkEncoded], + emptyArr: Gen[EmptyArrEncoded], + nullArr: Gen[NullArrEncoded] + ): Gen[ProtocolEncoded] = + Gen.frequency( + 10 -> bulk, + 10 -> num, + 10 -> str, + 10 -> err, + 3 -> emptyBulk, + 3 -> nullBulk, + 3 -> emptyArr, + 3 -> nullArr + ) + + private[this] final val noArrArrEncoded: Gen[ArrEncoded] = + listProtocol(noArrEncoded) map ArrEncoded.apply + + private[this] final def oneLevelArrEncoded(arrGen: Gen[ArrEncoded]): Gen[ArrEncoded] = + listProtocol( + Gen.frequency( + 20 -> noArrEncoded, + 3 -> arrGen + ) + ) map ArrEncoded.apply + + private[this] final def xLevelsNestedArrEncoded(x: Int): Gen[ArrEncoded] = { + @scala.annotation.tailrec + def loop(left: Int, soFar: Gen[ArrEncoded]): Gen[ArrEncoded] = + if (left > 0) loop(left - 1, oneLevelArrEncoded(soFar)) + else soFar + + loop(x, oneLevelArrEncoded(noArrArrEncoded)) + } + + private[this] final val protocolEncoded: Gen[ProtocolEncoded] = + Gen.frequency( + 50 -> noArrEncoded, + 25 -> oneLevelArrEncoded(noArrArrEncoded), + 25 -> noArrArrEncoded, + 10 -> xLevelsNestedArrEncoded(5) + ) + + private[laserdisc] implicit final val oneOrMoreProtocols: Arbitrary[OneOrMore[ProtocolEncoded]] = + Arbitrary(oneOrMoreProtocol(protocolEncoded)) +} + +private[laserdisc] trait LowPriorityGenerators extends BaseSpec { + protected implicit final def string: Gen[String] = Gen.listOf(utf8BMPCharGen) map (_.mkString) + + protected implicit final def nonEmptyString(implicit strGen: Gen[String]): Gen[NonEmptyString] = + strGen.filter(_.nonEmpty) map NonEmptyString.unsafeFrom + + protected implicit final def bulkEncoded(implicit nesGen: Gen[NonEmptyString]): Gen[BulkEncoded] = + nesGen map BulkEncoded.apply + + protected implicit final val long: Gen[Long] = chooseNum(MinValue, MaxValue) + + protected implicit final def numEncoded(implicit lnGen: Gen[Long]): Gen[NumEncoded] = + lnGen map NumEncoded.apply + + protected implicit final def strEncoded(implicit sGen: Gen[String]): Gen[StrEncoded] = + sGen.map(s => s.replace(CRLF, "")).filter(_.nonEmpty) map StrEncoded.apply + + protected implicit final def errEncoded(implicit nesGen: Gen[NonEmptyString]): Gen[ErrEncoded] = + nesGen map ErrEncoded.apply + + protected implicit final val emptyBulkEncoded: Gen[EmptyBulkEncoded] = + Gen.const(EmptyBulkEncoded()) + + protected implicit final val nullBulkEncoded: Gen[NullBulkEncoded] = + Gen.const(NullBulkEncoded()) + + protected implicit final val emptyArrEncoded: Gen[EmptyArrEncoded] = + Gen.const(EmptyArrEncoded()) + + protected implicit final val nullArrEncoded: Gen[NullArrEncoded] = + Gen.const(NullArrEncoded()) +} diff --git a/core/src/test/scala/laserdisc/RefinedTypesSpec.scala b/core/src/test/scala/laserdisc/RefinedTypesSpec.scala deleted file mode 100644 index 0077c7ce..00000000 --- a/core/src/test/scala/laserdisc/RefinedTypesSpec.scala +++ /dev/null @@ -1,730 +0,0 @@ -package laserdisc - -final class RefinedTypesSpec extends BaseSpec { - "ConnectionName" should { - "fail to compile" when { - "given an empty String" in { - """ConnectionName("")""" shouldNot compile - } - "given a space" in { - """ConnectionName(" ")""" shouldNot compile - } - } - - "fail at runtime" when { - "provided non literal cases of Strings that contain spaces" in forAll(stringsWithSpacesGen) { s => - whenever(!connectionNameIsValid(s)) { - an[IllegalArgumentException] should be thrownBy ConnectionName.unsafeFrom(s) - } - } - } - - "compile" when { - "given non empty String with no spaces" in { - """ConnectionName("a")""" should compile - } - } - - "refine correctly" when { - "provided non literal cases of non empty Strings with no spaces" in forAll(connectionNameGen) { s => - whenever(connectionNameIsValid(s)) { - ConnectionName.from(s) onRight (_.value shouldBe s) - } - } - } - } - - "DbIndex" should { - "fail to compile" when { - "given out of range Int (< 0)" in { - "DbIndex(-1)" shouldNot compile - } - "given out of range Int (> 15)" in { - "DbIndex(16)" shouldNot compile - } - } - - "fail at runtime" when { - "provided non literal cases of out of range Ints" in forAll { (i: Int) => - whenever(!dbIndexIsValid(i)) { - an[IllegalArgumentException] should be thrownBy DbIndex.unsafeFrom(i) - } - } - } - - "compile" when { - "given in range Int" in { - "DbIndex(0)" should compile - } - } - - "refine correctly" when { - "provided non literal cases of in range Ints" in forAll(dbIndexGen) { i => - whenever(dbIndexIsValid(i)) { - DbIndex.from(i) onRight (_.value shouldBe i) - } - } - } - } - - "GeoHash" should { - "fail to compile" when { - "given a non conformant String (length < 11)" in { - """GeoHash("abcdefghij")""" shouldNot compile - } - "given a non conformant String (length > 11)" in { - """GeoHash("abcdefghijkl")""" shouldNot compile - } - "given a non conformant String (uppercase)" in { - """GeoHash("abCdefghijk")""" shouldNot compile - } - "given a non conformant String (invalid chars)" in { - """GeoHash("abcd&fghijk")""" shouldNot compile - } - } - - "fail at runtime" when { - "provided non literal cases of non conformant Strings" in forAll { (s: String) => - whenever(!geoHashIsValid(s)) { - an[IllegalArgumentException] should be thrownBy GeoHash.unsafeFrom(s) - } - } - } - - "compile" when { - "given conformant String" in { - """GeoHash("abcd3fgh1jk")""" should compile - } - } - - "refine correctly" when { - "provided non literal cases of conformant Strings" in forAll(geoHashGen) { s => - whenever(geoHashIsValid(s)) { - GeoHash.from(s) onRight (_.value shouldBe s) - } - } - } - } - - "GlobPattern" should { - "fail to compile" when { - "given an empty String" in { - """GlobPattern("")""" shouldNot compile - } - "given a non conformant String" in { - """GlobPattern("!")""" shouldNot compile - } - } - - "fail at runtime" when { - "provided non literal cases of non conformant Strings" in forAll { (s: String) => - whenever(!globPatternIsValid(s)) { - an[IllegalArgumentException] should be thrownBy GlobPattern.unsafeFrom(s) - } - } - } - - "compile" when { - "given conformant String" in { - """GlobPattern("abc*fg?1jk")""" should compile - """GlobPattern("a[bc*]fg?1jk")""" should compile - } - } - - "refine correctly" when { - "provided non literal cases of conformant Strings" in forAll(globPatternGen) { s => - whenever(globPatternIsValid(s)) { - GlobPattern.from(s) onRight (_.value shouldBe s) - } - } - } - } - - "Host" should { - "fail to compile" when { - "given an empty String" in { - """Host("")""" shouldNot compile - } - "given a dash-ending hostname (RFC-1123)" in { - """Host("A0c-")""" shouldNot compile - } - "given a dash-beginning hostname (RFC-1123)" in { - """Host("-A0c")""" shouldNot compile - } - "given a hostname label whose length is > 63 (RFC-1123)" in { - """Host("o123456701234567012345670123456701234567012345670123456701234567")""" shouldNot compile - } - "given a hostname whose length is > 255 (RFC-1123)" in { - """Host( - "o12345670123456701234567012345670123456701234567012345670123456" + - ".o12345670123456701234567012345670123456701234567012345670123456" + - ".o12345670123456701234567012345670123456701234567012345670123456" + - ".o12345670123456701234567012345670123456701234567012345670123456" + - ".a" - )""" shouldNot compile - } - "given an invalid (non-private) IP address" in { - """Host("1.1.1.1")""" shouldNot compile - } - } - - "compile" when { - "given all NICs (0.0.0.0)" in { - """Host("0.0.0.0")""" should compile - } - "given loopback address (127.0.0.1)" in { - """Host("127.0.0.1")""" should compile - } - "given localhost (RFC-1123)" in { - """Host("localhost")""" should compile - } - "given domain.local (RFC-1123)" in { - """Host("domain.local")""" should compile - } - "given any digit hostname (RFC-1123)" in { - """Host("01234")""" should compile - } - "given any dash inside hostname (RFC-1123)" in { - """Host("01234-abc")""" should compile - } - "given 10. IPv4 (RFC-1918)" in { - """Host("10.1.2.3")""" should compile - } - "given 172.(15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31). IPv4 (RFC-1918)" in { - """Host("10.15.1.2")""" should compile - """Host("10.16.1.2")""" should compile - """Host("10.17.1.2")""" should compile - """Host("10.18.1.2")""" should compile - """Host("10.19.1.2")""" should compile - """Host("10.20.1.2")""" should compile - """Host("10.21.1.2")""" should compile - """Host("10.22.1.2")""" should compile - """Host("10.23.1.2")""" should compile - """Host("10.24.1.2")""" should compile - """Host("10.25.1.2")""" should compile - """Host("10.26.1.2")""" should compile - """Host("10.27.1.2")""" should compile - """Host("10.28.1.2")""" should compile - """Host("10.29.1.2")""" should compile - """Host("10.30.1.2")""" should compile - """Host("10.31.1.2")""" should compile - } - "given 192.168. IPv4 (RFC-1918)" in { - """Host("192.168.1.2")""" should compile - } - "given 192.0.2. IPv4 (RFC-5737)" in { - """Host("192.0.2.1")""" should compile - } - "given 198.51.100. IPv4 (RFC-5737)" in { - """Host("198.51.100.1")""" should compile - } - "given 203.0.113. IPv4 (RFC-5737)" in { - """Host("203.0.113.1")""" should compile - } - "given 169.254. IPv4 (RFC-3927)" in { - """Host("169.254.1.2")""" should compile - } - "given 198.(18|19). IPv4 (RFC-2544)" in { - """Host("198.18.1.2")""" should compile - """Host("198.19.1.2")""" should compile - } - } - } - - "Key" should { - "fail to compile" when { - "given an empty String" in { - """Key("")""" shouldNot compile - } - } - - "compile" when { - "given a non empty String" in { - """Key("a")""" should compile - } - } - } - - "Latitude" should { - "fail to compile" when { - "given out of range Double (< -85.05112878D)" in { - "Latitude(-85.05112879D)" shouldNot compile - } - "given out of range Double (> 85.05112878D)" in { - "Latitude(85.05112879D)" shouldNot compile - } - } - - "fail at runtime" when { - "provided non literal cases of out of range Doubles (d < -85.05112878D | d > 85.05112878D)" in forAll { (d: Double) => - whenever(!latitudeIsValid(d)) { - an[IllegalArgumentException] should be thrownBy Latitude.unsafeFrom(d) - } - } - } - - "compile" when { - "given edge cases (-85.05112878D)" in { - "Latitude(-85.05112878D)" should compile - } - "given edge cases (85.05112878D)" in { - "Latitude(85.05112878D)" should compile - } - } - - "refine correctly" when { - "provided non literal cases of in range Doubles (-85.05112878D <= d <= 85.05112878D)" in forAll(latitudeGen) { d => - whenever(latitudeIsValid(d)) { - Latitude.from(d) onRight (_.value shouldBe d) - } - } - } - } - - "Longitude" should { - "fail to compile" when { - "given out of range Double (< -180.0D)" in { - "Longitude(-180.00000001D)" shouldNot compile - } - "given out of range Double (> 180.0D)" in { - "Longitude(180.00000001D)" shouldNot compile - } - } - - "fail at runtime" when { - "provided non literal cases of out of range Doubles (d < -180.0D | d > 180.0D)" in forAll { (d: Double) => - whenever(!longitudeIsValid(d)) { - an[IllegalArgumentException] should be thrownBy Longitude.unsafeFrom(d) - } - } - } - - "compile" when { - "given edge cases (-180.0D)" in { - "Longitude(-180.0D)" should compile - } - "given edge cases (180.0D)" in { - "Longitude(180.0D)" should compile - } - } - - "refine correctly" when { - "provided non literal cases of in range Doubles (-180.0D <= d <= 180.0D)" in forAll(longitudeGen) { d => - whenever(longitudeIsValid(d)) { - Longitude.from(d) onRight (_.value shouldBe d) - } - } - } - } - - "NodeId" should { - "fail to compile" when { - "given a non conformant String (length < 40)" in { - """NodeId("0123456789abcdef0123456789abcdef0123456")""" shouldNot compile - } - "given a non conformant String (length > 40)" in { - """NodeId("0123456789abcdef0123456789abcdef012345678")""" shouldNot compile - } - "given a non conformant String (uppercase)" in { - """NodeId("0123456789abcdEf0123456789abcdef01234567")""" shouldNot compile - } - "given a non conformant String (invalid chars)" in { - """NodeId("0123456789abcd&f0123456789abcdef01234567&fghijk")""" shouldNot compile - } - } - - "fail at runtime" when { - "provided non literal cases of non conformant Strings" in forAll { (s: String) => - whenever(!nodeIdIsValid(s)) { - an[IllegalArgumentException] should be thrownBy NodeId.unsafeFrom(s) - } - } - } - - "compile" when { - "given conformant String" in { - """NodeId("0123456789abcdef0123456789abcdef01234567")""" should compile - } - } - - "refine correctly" when { - "provided non literal cases of conformant Strings" in forAll(nodeIdGen) { s => - whenever(nodeIdIsValid(s)) { - NodeId.from(s) onRight (_.value shouldBe s) - } - } - } - } - - "NonNegDouble" should { - "fail to compile" when { - "given out of range Double (< 0.0D)" in { - "NonNegDouble(-0.00000001D)" shouldNot compile - } - "given NaN Double" in { - "NonNegDouble(Double.NaN)" shouldNot compile - } - } - - "fail at runtime" when { - "provided non literal cases of out of range Doubles (d < 0.0D)" in forAll { (d: Double) => - whenever(!nonNegDoubleIsValid(d)) { - an[IllegalArgumentException] should be thrownBy NonNegDouble.unsafeFrom(d) - } - } - } - - "compile" when { - "given edge cases (0.0D)" in { - "NonNegDouble(0.0D)" should compile - } - } - - "refine correctly" when { - "provided non literal cases of in range Doubles (d >= 0.0D)" in forAll(nonNegDoubleGen) { d => - whenever(nonNegDoubleIsValid(d)) { - NonNegDouble.from(d) onRight (_.value shouldBe d) - } - } - } - } - - "NonNegInt" should { - "fail to compile" when { - "given out of range Ints (< 0)" in { - "NonNegInt(-1)" shouldNot compile - } - } - - "fail at runtime" when { - "provided non literal cases of out of range Ints (i < 0)" in forAll { (i: Int) => - whenever(!nonNegIntIsValid(i)) { - an[IllegalArgumentException] should be thrownBy NonNegInt.unsafeFrom(i) - } - } - } - - "compile" when { - "given edge cases (0)" in { - "NonNegInt(0)" should compile - } - } - - "refine correctly" when { - "provided non literal cases of in range Ints (i > 0)" in forAll(nonNegIntGen) { i => - whenever(nonNegIntIsValid(i)) { - NonNegInt.from(i) onRight (_.value shouldBe i) - } - } - } - } - - "NonNegLong" should { - "fail to compile" when { - "given out of range Longs (< 0L)" in { - "NonNegLong(-1L)" shouldNot compile - } - } - - "fail at runtime" when { - "provided non literal cases of out of range Longs (l < 0L)" in forAll { (l: Long) => - whenever(!nonNegLongIsValid(l)) { - an[IllegalArgumentException] should be thrownBy NonNegLong.unsafeFrom(l) - } - } - } - - "compile" when { - "given edge cases (0L)" in { - "NonNegLong(0L)" should compile - } - } - - "refine correctly" when { - "provided non literal cases of in range Longs (l > 0L)" in forAll(nonNegLongGen) { l => - whenever(nonNegLongIsValid(l)) { - NonNegLong.from(l) onRight (_.value shouldBe l) - } - } - } - } - - "NonZeroDouble" should { - "fail to compile" when { - "given 0.0D" in { - "NonZeroDouble(0.0D)" shouldNot compile - } - "given NaN" in { - "NonZeroDouble(Double.NaN)" shouldNot compile - } - } - - "refine correctly" when { - "provided non literal cases of valid Doubles (d != 0.0D)" in forAll(nonZeroDoubleGen) { d => - whenever(nonZeroDoubleIsValid(d)) { - NonZeroDouble.from(d) onRight (_.value shouldBe d) - } - } - } - } - - "NonZeroInt" should { - "fail to compile" when { - "given 0" in { - "NonZeroInt(0)" shouldNot compile - } - } - - "refine correctly" when { - "provided non literal cases of valid Ints (i != 0)" in forAll(nonZeroIntGen) { i => - whenever(nonZeroIntIsValid(i)) { - NonZeroInt.from(i) onRight (_.value shouldBe i) - } - } - } - } - - "NonZeroLong" should { - "fail to compile" when { - "given 0L" in { - "NonZeroLong(0L)" shouldNot compile - } - } - - "refine correctly" when { - "provided non literal cases of valid Longs (l != 0L)" in forAll(nonZeroLongGen) { l => - whenever(nonZeroLongIsValid(l)) { - NonZeroLong.from(l) onRight (_.value shouldBe l) - } - } - } - } - - "OneOrMore" should { - "fail at runtime" when { - "provided empty List" in { - an[IllegalArgumentException] should be thrownBy OneOrMore.unsafeFrom(List.empty[Int]) - } - } - - "refine correctly" when { - "provided non literal cases of non empty Lists (length > 0)" in forAll { (is: List[Int]) => - whenever(is.nonEmpty) { - OneOrMore.from(is) onRight (_.value shouldBe is) - } - } - } - } - - "OneOrMoreKeys" should { - "fail to compile" when { - "given non literal empty List" in { - "OneOrMoreKeys(List.empty)" shouldNot compile - } - "given non literal non empty List" in { - """OneOrMoreKeys(List(Key("a")))""" shouldNot compile - } - } - - "fail at runtime" when { - "provided empty List" in { - an[IllegalArgumentException] should be thrownBy OneOrMoreKeys.unsafeFrom(List.empty) - } - } - - "refine correctly" when { - "provided non literal cases of non empty Lists (length > 0)" in forAll { (ks: List[Key]) => - whenever(ks.nonEmpty) { - OneOrMoreKeys.from(ks) onRight (_.value shouldBe ks) - } - } - } - } - - "RangeOffset" should { - "fail to compile" when { - "given out of range Int (< 0)" in { - "RangeOffset(-1)" shouldNot compile - } - "given out of range Int (> 536870911)" in { - "RangeOffset(536870912)" shouldNot compile - } - } - - "fail at runtime" when { - "provided non literal cases of out of range Ints (i < 0 | i > 536870911)" in forAll { (i: Int) => - whenever(!rangeOffsetIsValid(i)) { - an[IllegalArgumentException] should be thrownBy RangeOffset.unsafeFrom(i) - } - } - } - - "compile" when { - "given edge cases (0)" in { - "RangeOffset(0)" should compile - } - "given edge cases (536870911)" in { - "RangeOffset(536870911)" should compile - } - } - - "refine correctly" when { - "provided non literal cases of in range Ints (0 <= i <= 536870911)" in forAll(rangeOffsetGen) { i => - whenever(rangeOffsetIsValid(i)) { - RangeOffset.from(i) onRight (_.value shouldBe i) - } - } - } - } - - "Slot" should { - "fail to compile" when { - "given out of range Int (< 0)" in { - "Slot(-1)" shouldNot compile - } - "given out of range Int (> 16383)" in { - "Slot(16384)" shouldNot compile - } - } - - "fail at runtime" when { - "provided non literal cases of out of range Ints (i < 0 | i > 16383)" in forAll { (i: Int) => - whenever(!slotIsValid(i)) { - an[IllegalArgumentException] should be thrownBy Slot.unsafeFrom(i) - } - } - } - - "compile" when { - "given edge cases (0)" in { - "Slot(0)" should compile - } - "given edge cases (16383)" in { - "Slot(16383)" should compile - } - } - } - - "StringLength" should { - "fail to compile" when { - "given out of range Long (< 0L)" in { - "StringLength(-1L)" shouldNot compile - } - "given out of range Long (> 4294967295L)" in { - "StringLength(4294967296L)" shouldNot compile - } - } - - "fail at runtime" when { - "provided non literal cases of out of range Longs (l < 0L | l > 4294967295L)" in forAll { (l: Long) => - whenever(!stringLengthIsValid(l)) { - an[IllegalArgumentException] should be thrownBy StringLength.unsafeFrom(l) - } - } - } - - "compile" when { - "given edge cases (0L)" in { - "StringLength(0L)" should compile - } - "given edge cases (4294967295L)" in { - "StringLength(4294967295L)" should compile - } - } - - "refine correctly" when { - "provided non literal cases of in range Longs (0L <= l <= 4294967295L)" in forAll(stringLengthGen) { l => - whenever(stringLengthIsValid(l)) { - StringLength.from(l) onRight (_.value shouldBe l) - } - } - } - } - - "TwoOrMoreKeys" should { - "fail to compile" when { - "given non literal empty List" in { - "TwoOrMoreKeys(List.empty)" shouldNot compile - } - "given non literal single element List" in { - """TwoOrMoreKeys(List(Key("a")))""" shouldNot compile - } - "given non literal List of two elements" in { - """TwoOrMoreKeys(List(Key("a"), Key("b")))""" shouldNot compile - } - } - - "fail at runtime" when { - "provided empty List" in { - an[IllegalArgumentException] should be thrownBy TwoOrMoreKeys.unsafeFrom(List.empty) - } - "provided single element List" in { - an[IllegalArgumentException] should be thrownBy TwoOrMoreKeys.unsafeFrom(List(Key("a"))) - } - } - - "refine correctly" when { - "provided non literal cases of Lists of length > 1" in forAll { (ks: List[Key]) => - whenever(ks.size > 1) { - TwoOrMoreKeys.from(ks) onRight (_.value shouldBe ks) - } - } - } - } - - "TwoOrMoreWeightedKeys" should { - "fail to compile" when { - "given non literal empty List" in { - "TwoOrMoreWeightedKeys(List.empty)" shouldNot compile - } - "given non literal single element List" in { - """TwoOrMoreWeightedKeys(List(Key("a") -> ValidDouble(42.0D)))""" shouldNot compile - } - "given non literal List of two elements" in { - """TwoOrMoreWeightedKeys(List(Key("a") -> ValidDouble(42.0D), Key("b") -> ValidDouble(23.0D)))""" shouldNot compile - } - } - - "fail at runtime" when { - "provided empty List" in { - an[IllegalArgumentException] should be thrownBy TwoOrMoreWeightedKeys.unsafeFrom(List.empty) - } - "provided single element List" in { - an[IllegalArgumentException] should be thrownBy TwoOrMoreWeightedKeys.unsafeFrom(List(Key("a") -> ValidDouble(42.0d))) - } - } - - "refine correctly" when { - "provided non literal cases of Lists of length > 1" in forAll { (kvds: List[(Key, ValidDouble)]) => - whenever(kvds.size > 1) { - TwoOrMoreWeightedKeys.from(kvds) onRight (_.value shouldBe kvds) - } - } - } - } - - "ValidDouble" should { - "fail to compile" when { - "given Double.NaN" in { - "ValidDouble(Double.NaN)" shouldNot compile - } - } - - "compile" when { - "given edge cases (-1.7976931348623157E308) -> can't use Double.MinValue as not a literal" in { - "ValidDouble(-1.7976931348623157E308)" should compile - } - "given edge cases (Double.MaxValue)" in { - "ValidDouble(Double.MaxValue)" should compile - } - } - - "refine correctly" when { - "provided non literal cases of valid Doubles (d != Double.NaN)" in forAll { (d: Double) => - whenever(validDoubleIsValid(d)) { - ValidDouble.from(d) onRight (_.value shouldBe d) - } - } - } - } -} diff --git a/core/src/test/scala/laserdisc/protocol/ArrSpec.scala b/core/src/test/scala/laserdisc/protocol/ArrSpec.scala index 3d6fb0c2..f3ac1823 100644 --- a/core/src/test/scala/laserdisc/protocol/ArrSpec.scala +++ b/core/src/test/scala/laserdisc/protocol/ArrSpec.scala @@ -3,30 +3,26 @@ package protocol import shapeless._ -final class ArrSpec extends BaseSpec { - import laserdisc.auto._ +final class ArrSpec extends BaseSpec with EitherSyntax { + test("decoding the correct type Arr(Bulk) ==> Seq[A] with the wrong encoding gives details about the decoding error") { + def protocol = Protocol("CUSTOM", _: String :: HNil).as[Arr, Seq[Long]] + val request = "id" :: HNil + val response = Arr(Num(1L), Num(2L), Num(3L), Num(4L)) - "decoding the correct type Arr(Bulk) ==> Seq[A] with the wrong encoding" should { - "give details about the decoding error" in { - def protocol = Protocol("CUSTOM", _: String :: HNil).as[Arr, Seq[Long]] - val request = "id" :: HNil - val response = Arr(Num(1L), Num(2L), Num(3L), Num(4L)) - - protocol(request).decode(response) onLeft { e => - e.getMessage shouldBe "RESP type(s) of Arr(Num(1),Num(2),Num(3),Num(4)) matched but failed to deserialize correctly with error Arr(Bulk) ==> Seq[A] error at element 1: Unexpected for Bulk. Was Num(4)" - } - } + assertLeftEquals( + protocol(request).decode(response) leftMap (_.getMessage), + "RESP type(s) of Arr(Num(1),Num(2),Num(3),Num(4)) matched but failed to deserialize correctly with error Arr(Bulk) ==> Seq[A] error at element 1: Unexpected for Bulk. Was Num(4)" + ) } - "decoding the correct type Arr(Bulk) ==> Seq[Option[A]] with the wrong encoding" should { - "give details about the decoding error" in { - def protocol = Protocol("CUSTOM", _: String :: HNil).as[Arr, Seq[Option[Long]]] - val request = "id" :: HNil - val response = Arr(Num(1L), NullBulk, Num(3L), NullBulk) + test("decoding the correct type Arr(Bulk) ==> Seq[Option[A]] with the wrong encoding gives details about the decoding error") { + def protocol = Protocol("CUSTOM", _: String :: HNil).as[Arr, Seq[Option[Long]]] + val request = "id" :: HNil + val response = Arr(Num(1L), NullBulk, Num(3L), NullBulk) - protocol(request).decode(response) onLeft { e => - e.getMessage shouldBe "RESP type(s) of Arr(Num(1),NullBulk,Num(3),NullBulk) matched but failed to deserialize correctly with error Arr(Bulk) ==> Seq[Option[A]] error at element 2: Unexpected for Bulk. Was Num(3)" - } - } + assertLeftEquals( + protocol(request).decode(response) leftMap (_.getMessage), + "RESP type(s) of Arr(Num(1),NullBulk,Num(3),NullBulk) matched but failed to deserialize correctly with error Arr(Bulk) ==> Seq[Option[A]] error at element 2: Unexpected for Bulk. Was Num(3)" + ) } } diff --git a/core/src/test/scala/laserdisc/protocol/BListPSpec.scala b/core/src/test/scala/laserdisc/protocol/BListPSpec.scala index d68e7400..d104c6c7 100644 --- a/core/src/test/scala/laserdisc/protocol/BListPSpec.scala +++ b/core/src/test/scala/laserdisc/protocol/BListPSpec.scala @@ -1,91 +1,117 @@ package laserdisc package protocol -final class BListPSpec extends BListExtPSpec { - "The Blocking List protocol" when { - "using blpop" should { - "fail to compile" when { - "given one key and timeout but missing read instance" in { - """blpop[Bar](OneOrMoreKeys.unsafeFrom(List(Key("a"))), NonNegInt(0))""" shouldNot compile - } - } +import org.scalacheck.Prop.forAll - "roundtrip successfully" when { - "given one or more keys and timeout" in forAll("keys", "timeout", "returned value") { (ks: OneOrMoreKeys, nni: NonNegInt, i: Int) => - val protocol = blpop[Int](ks, nni) +abstract class BListPSpec extends BaseSpec with BListP { - protocol.encode shouldBe Arr((Bulk("BLPOP") :: ks.value.map(Bulk(_))) :+ Bulk(nni)) - protocol.decode(Arr(Bulk(ks.value.headOption.value), Bulk(i))) onRight (_.value shouldBe KV(ks.value.headOption.value, i)) - } - "given one or more keys, timeout and specific read instance" in { - forAll("keys", "timeout", "returned value") { (ks: OneOrMoreKeys, nni: NonNegInt, i: Int) => - val protocol = blpop[Foo](ks, nni) - - protocol.encode shouldBe Arr((Bulk("BLPOP") :: ks.value.map(Bulk(_))) :+ Bulk(nni)) - protocol.decode(Arr(Bulk(ks.value.headOption.value), Bulk(i))) onRight (_.value shouldBe KV(ks.value.headOption.value, Foo(i))) - } - } - } - } + test("The Blocking List protocol using blpop fails to compile given one key and timeout but missing read instance") { + assertNoDiff( + compileErrors("""blpop[Bar](OneOrMoreKeys.unsafeFrom(List(Key("a"))), NonNegInt(0))"""), + """|error: + |Implicit not found Read[laserdisc.protocol.Bulk, laserdisc.Bar]. + | + |Try writing your own, for example: + | + |implicit final val myRead: Read[laserdisc.protocol.Bulk, laserdisc.Bar] = new Read[laserdisc.protocol.Bulk, laserdisc.Bar] { + | override final def read(a: laserdisc.protocol.Bulk): Option[laserdisc.Bar] = ??? + |} + | + |Note 1: you can use the factory method Read.instance instead of creating it manually as shown above + |Note 2: make sure to inspect the combinators as you may be able to leverage some other Read instance + | + |blpop[Bar](OneOrMoreKeys.unsafeFrom(List(Key("a"))), NonNegInt(0)) + | ^ + |""".stripMargin + ) + } - "using brpop" should { - "fail to compile" when { - "given one key and timeout but missing read instance" in { - """brpop[Bar](OneOrMoreKeys.unsafeFrom(List(Key("a"))), NonNegInt(0))""" shouldNot compile - } - } + test("The Blocking List protocol using brpop fails to compile given one key and timeout but missing read instance") { + assertNoDiff( + compileErrors("""brpop[Bar](OneOrMoreKeys.unsafeFrom(List(Key("a"))), NonNegInt(0))"""), + """|error: + |Implicit not found Read[laserdisc.protocol.Bulk, laserdisc.Bar]. + | + |Try writing your own, for example: + | + |implicit final val myRead: Read[laserdisc.protocol.Bulk, laserdisc.Bar] = new Read[laserdisc.protocol.Bulk, laserdisc.Bar] { + | override final def read(a: laserdisc.protocol.Bulk): Option[laserdisc.Bar] = ??? + |} + | + |Note 1: you can use the factory method Read.instance instead of creating it manually as shown above + |Note 2: make sure to inspect the combinators as you may be able to leverage some other Read instance + | + |brpop[Bar](OneOrMoreKeys.unsafeFrom(List(Key("a"))), NonNegInt(0)) + | ^ + |""".stripMargin + ) + } - "roundtrip successfully" when { - "given one or more keys and timeout" in forAll("keys", "timeout", "returned value") { (ks: OneOrMoreKeys, nni: NonNegInt, i: Int) => - val protocol = brpop[Int](ks, nni) + property("The Blocking List protocol using blpop roundtrips successfully given one or more keys and timeout") { + forAll { (ks: OneOrMoreKeys, t: NonNegInt, i: Int) => + val protocol = blpop[Int](ks, t) + assertEquals(protocol.encode, Arr((Bulk("BLPOP") :: ks.value.map(Bulk(_))) :+ Bulk(t))) + protocol.decode(Arr(Bulk(ks.value.head), Bulk(i))) onRight (_ contains KV(ks.value.head, i)) + } + } - protocol.encode shouldBe Arr((Bulk("BRPOP") :: ks.value.map(Bulk(_))) :+ Bulk(nni)) - protocol.decode(Arr(Bulk(ks.value.headOption.value), Bulk(i))) onRight (_.value shouldBe KV(ks.value.headOption.value, i)) - } - "given one or more keys, timeout and specific read instance" in { - forAll("keys", "timeout", "returned value") { (ks: OneOrMoreKeys, nni: NonNegInt, i: Int) => - val protocol = brpop[Foo](ks, nni) + property("The Blocking List protocol using blpop roundtrips successfully given one or more keys, timeout and specific read instance") { + forAll { (ks: OneOrMoreKeys, t: NonNegInt, i: Int) => + val protocol = blpop[Foo](ks, t) + assertEquals(protocol.encode, Arr((Bulk("BLPOP") :: ks.value.map(Bulk(_))) :+ Bulk(t))) + protocol.decode(Arr(Bulk(ks.value.head), Bulk(i))) onRight (_ contains KV(ks.value.head, Foo(i))) + } + } - protocol.encode shouldBe Arr((Bulk("BRPOP") :: ks.value.map(Bulk(_))) :+ Bulk(nni)) - protocol.decode(Arr(Bulk(ks.value.headOption.value), Bulk(i))) onRight (_.value shouldBe KV(ks.value.headOption.value, Foo(i))) - } - } - } + property("The Blocking List protocol using brpop roundtrips successfully given one or more keys and timeout") { + forAll { (ks: OneOrMoreKeys, t: NonNegInt, i: Int) => + val protocol = brpop[Int](ks, t) + assertEquals(protocol.encode, Arr((Bulk("BRPOP") :: ks.value.map(Bulk(_))) :+ Bulk(t))) + protocol.decode(Arr(Bulk(ks.value.head), Bulk(i))) onRight (_ contains KV(ks.value.head, i)) } + } - "using brpoplpush" should { - "roundtrip successfully" when { - "given source key and destination key" in forAll("source key", "destination key", "returned value") { (s: Key, d: Key, i: Int) => - val protocol = brpoplpush[Int](s, d) + property("The Blocking List protocol using brpop roundtrips successfully given one or more keys, timeout and specific read instance") { + forAll { (ks: OneOrMoreKeys, t: NonNegInt, i: Int) => + val protocol = brpop[Foo](ks, t) + assertEquals(protocol.encode, Arr((Bulk("BRPOP") :: ks.value.map(Bulk(_))) :+ Bulk(t))) + protocol.decode(Arr(Bulk(ks.value.head), Bulk(i))) onRight (_ contains KV(ks.value.head, Foo(i))) + } + } - protocol.encode shouldBe Arr(Bulk("BRPOPLPUSH"), Bulk(s), Bulk(d), Bulk(0)) - protocol.decode(Bulk(i)) onRight (_.value shouldBe i) - } - "given source key, destination key and specific read instance" in { - forAll("source key", "destination key", "returned value") { (s: Key, d: Key, i: Int) => - val protocol = brpoplpush[Foo](s, d) + property("The Blocking List protocol using brpoplpush roundtrips successfully given source key and destination key") { + forAll { (s: Key, d: Key, i: Int) => + val protocol = brpoplpush[Int](s, d) + assertEquals(protocol.encode, Arr(Bulk("BRPOPLPUSH"), Bulk(s), Bulk(d), Bulk(0))) + protocol.decode(Bulk(i)) onRight (_ contains i) + } + } - protocol.encode shouldBe Arr(Bulk("BRPOPLPUSH"), Bulk(s), Bulk(d), Bulk(0)) - protocol.decode(Bulk(i)) onRight (_.value shouldBe Foo(i)) - } - } - "given source key, destination key and timeout" in { - forAll("source key", "destination key", "timeout", "returned value") { (s: Key, d: Key, pi: PosInt, i: Int) => - val protocol = brpoplpush[Int](s, d, pi) + property( + "The Blocking List protocol using brpoplpush roundtrips successfully given source key, destination key and specific read instance" + ) { + forAll { (s: Key, d: Key, i: Int) => + val protocol = brpoplpush[Foo](s, d) + assertEquals(protocol.encode, Arr(Bulk("BRPOPLPUSH"), Bulk(s), Bulk(d), Bulk(0))) + protocol.decode(Bulk(i)) onRight (_ contains Foo(i)) + } + } - protocol.encode shouldBe Arr(Bulk("BRPOPLPUSH"), Bulk(s), Bulk(d), Bulk(pi)) - protocol.decode(Bulk(i)) onRight (_.value shouldBe i) - } - } - "given source key, destination key, timeout and specific read instance" in { - forAll("source key", "destination key", "timeout", "returned value") { (s: Key, d: Key, pi: PosInt, i: Int) => - val protocol = brpoplpush[Foo](s, d, pi) + property("The Blocking List protocol using brpoplpush roundtrips successfully given source key, destination key and timeout") { + forAll { (s: Key, d: Key, pi: PosInt, i: Int) => + val protocol = brpoplpush[Int](s, d, pi) + assertEquals(protocol.encode, Arr(Bulk("BRPOPLPUSH"), Bulk(s), Bulk(d), Bulk(pi))) + protocol.decode(Bulk(i)) onRight (_ contains i) + } + } - protocol.encode shouldBe Arr(Bulk("BRPOPLPUSH"), Bulk(s), Bulk(d), Bulk(pi)) - protocol.decode(Bulk(i)) onRight (_.value shouldBe Foo(i)) - } - } - } + property( + "The Blocking List protocol using brpoplpush roundtrips successfully given source key, destination key, timeout and specific read instance" + ) { + forAll { (s: Key, d: Key, pi: PosInt, i: Int) => + val protocol = brpoplpush[Foo](s, d, pi) + assertEquals(protocol.encode, Arr(Bulk("BRPOPLPUSH"), Bulk(s), Bulk(d), Bulk(pi))) + protocol.decode(Bulk(i)) onRight (_ contains Foo(i)) } } } diff --git a/core/src/test/scala/laserdisc/protocol/ClusterPSpec.scala b/core/src/test/scala/laserdisc/protocol/ClusterPSpec.scala index 738ec056..9865d065 100644 --- a/core/src/test/scala/laserdisc/protocol/ClusterPSpec.scala +++ b/core/src/test/scala/laserdisc/protocol/ClusterPSpec.scala @@ -1,36 +1,41 @@ package laserdisc package protocol +import org.scalacheck.Prop.forAll + final class ClusterPSpec extends BaseSpec with ClusterP { import clustertypes._ import org.scalacheck.{Arbitrary, Gen} import org.scalacheck.Gen._ - private[this] implicit final val clusterFailoverModeArb: Arbitrary[ClusterFailoverMode] = Arbitrary { + private[this] implicit val clusterFailoverModeArb: Arbitrary[ClusterFailoverMode] = Arbitrary { Gen.oneOf(ClusterFailoverMode.force, ClusterFailoverMode.takeover) } - private[this] implicit final val clusterResetModeArb: Arbitrary[ClusterResetMode] = Arbitrary { + private[this] implicit val clusterResetModeArb: Arbitrary[ClusterResetMode] = Arbitrary { Gen.oneOf(ClusterResetMode.hard, ClusterResetMode.soft) } - private[this] implicit final val clusterSetSlotModeArb: Arbitrary[ClusterSetSlotMode] = Arbitrary { + private[this] implicit val clusterSetSlotModeArb: Arbitrary[ClusterSetSlotMode] = Arbitrary { Gen.oneOf(ClusterSetSlotMode.importing, ClusterSetSlotMode.migrating, ClusterSetSlotMode.node) } - private[this] final val kvPairPattern = KVPairRegex.pattern - private[this] final val infoIsValid: Map[String, String] => Boolean = _.forall { case (k, v) => kvPairPattern.matcher(s"$k:$v").matches } - private[this] final val infoGen: Gen[Map[String, String]] = nonEmptyMap(identifier.flatMap(k => alphaStr.map(k -> _))) :| "info map" - private[this] implicit final val infoShow: Show[Map[String, String]] = Show.instance(_.map { case (k, v) => s"$k:$v" }.mkString(CRLF)) + private[this] final val kvPairPattern = KVPairRegex.pattern + private[this] val infoIsValid: Map[String, String] => Boolean = _.forall { case (k, v) => kvPairPattern.matcher(s"$k:$v").matches } + private[this] val infoGen: Gen[Map[String, String]] = + nonEmptyMap(identifier.flatMap(k => alphaStr.map(k -> _))).filter(infoIsValid) :| "info map" + private[this] implicit val infoShow: Show[Map[String, String]] = Show.instance { + _.map { case (k, v) => s"$k:$v" }.mkString(CRLF) + } - private[this] final type RawNode = + private[this] type RawNode = (String, EmptyString | Host, Port, Option[Port], Seq[String], String, Int, Int, Int, String, Seq[String]) - private[this] final type RawNodes = List[RawNode] - private[this] final val rawNodeToString: RawNode => String = { + private[this] type RawNodes = List[RawNode] + private[this] val rawNodeToString: RawNode => String = { case (nid, h, p, None, fs, mnid, ps, pr, ce, l, ss) => s"$nid ${h.fold(_.value, _.value)}:$p ${fs.mkString(COMMA)} $mnid $ps $pr $ce $l${ss.mkString(SPACE, SPACE, "")}" case (nid, h, p, Some(cp), fs, mnid, ps, pr, ce, l, ss) => s"$nid ${h.fold(_.value, _.value)}:$p@$cp ${fs.mkString(COMMA)} $mnid $ps $pr $ce $l${ss.mkString(SPACE, SPACE, "")}" } - private[this] final val nodesIsValid: RawNodes => Boolean = { + private[this] val nodesIsValid: RawNodes => Boolean = { val nid = NodeIdRegexWit.value val ip = IPv4RegexWit.value val domain = Rfc1123HostnameRegexWit.value @@ -41,7 +46,7 @@ final class ClusterPSpec extends BaseSpec with ClusterP { _.map(rawNodeToString).forall { case R(_*) => true; case _ => false } } - private[this] final val nodesGen: Gen[RawNodes] = { + private[this] val nodesGen: Gen[RawNodes] = { val flag = Gen.oneOf("myself", "master", "slave", "fail?", "fail", "handshake", "noaddr") val slotType = Gen.oneOf( slotGen.map(_.toString), @@ -65,35 +70,41 @@ final class ClusterPSpec extends BaseSpec with ClusterP { choose(1, 10).flatMap(listOfN(_, rawNode)) :| "raw nodes info" } - private[this] implicit final val nodesShow: Show[RawNodes] = Show.instance { + private[this] implicit val nodesShow: Show[RawNodes] = Show.instance { _.map(rawNodeToString).mkString(LF) } - private[this] final val validateNodes: RawNodes => ClusterNodes => Unit = + private[this] val validateNodes: RawNodes => ClusterNodes => Unit = rns => ns => ns.nodes.zip(rns).foreach { case (ClusterNode(n0, ClusterAddress(h0, p0, cp0), fs0, m0, ps0, pr0, ce0, l0, ss0), (n, h, p, cp, fs, m, ps, pr, ce, l, ss)) => - n0.value shouldBe n - h0 shouldBe h.fold(_ => LoopbackHost, identity) - p0 shouldBe p - cp0 shouldBe cp.getOrElse(p) - (if (fs0.isEmpty) Seq("noflags") else fs0.map(Show[ClusterFlag].show)) shouldBe fs - m0.fold("-")(_.value) shouldBe m - ps0.value shouldBe ps - pr0.value shouldBe pr - ce0.value shouldBe ce - Show[ClusterLinkState].show(l0) shouldBe l - ss0.map { - case ClusterSingleSlotType(s) => s.toString - case ClusterRangeSlotType(f, t) => s"$f-$t" - case ClusterImportingSlotType(s, in) => s"[$s-<-$in]" - case ClusterMigratingSlotType(s, mn) => s"[$s->-$mn]" - } shouldBe ss + assertEquals(n0.value, n) + assertEquals(h0, h.fold(_ => LoopbackHost, identity)) + assertEquals(p0, p) + assertEquals(cp0, cp.getOrElse(p)) + assertEquals( + if (fs0.isEmpty) Seq("noflags") else fs0.map(Show[ClusterFlag].show), + fs + ) + assertEquals(m0.fold("-")(_.value), m) + assertEquals(ps0.value, ps) + assertEquals(pr0.value, pr) + assertEquals(ce0.value, ce) + assertEquals(Show[ClusterLinkState].show(l0), l) + assertEquals( + ss0.map { + case ClusterSingleSlotType(s) => s.toString + case ClusterRangeSlotType(f, t) => s"$f-$t" + case ClusterImportingSlotType(s, in) => s"[$s-<-$in]" + case ClusterMigratingSlotType(s, mn) => s"[$s->-$mn]" + }, + ss + ) } - private[this] final type RawSlot = (Slot, Slot, Seq[(EmptyString | Host, Port, Option[NodeId])]) - private[this] final type RawSlots = List[RawSlot] - private[this] final val slotsGen: Gen[RawSlots] = (for { + private[this] type RawSlot = (Slot, Slot, Seq[(EmptyString | Host, Port, Option[NodeId])]) + private[this] type RawSlots = List[RawSlot] + private[this] val slotsGen: Gen[RawSlots] = (for { isOld <- Gen.oneOf(true, false) rss <- nonEmptyListOf( for { @@ -109,7 +120,7 @@ final class ClusterPSpec extends BaseSpec with ClusterP { else (snd, fst, mrs) ) } yield rss) :| "raw slots info" - private[this] final val slotsToArr: RawSlots => Arr = ss => + private[this] val slotsToArr: RawSlots => Arr = ss => Arr( ss.map { case (f, t, rs) => @@ -124,286 +135,216 @@ final class ClusterPSpec extends BaseSpec with ClusterP { } ) - "The Cluster protocol" when { - "using addslots" should { - "roundtrip successfully" when { - "given one or more slots" in forAll("slots") { ss: OneOrMore[Slot] => - val protocol = addslots(ss) - - protocol.encode shouldBe Arr(Bulk("CLUSTER") :: Bulk("ADDSLOTS") :: ss.value.map(Bulk(_))) - protocol.decode(Str(OK.value)) onRight (_ shouldBe OK) - } - } + property("The Cluster protocol using addslots roundtrips successfully given one or more slots") { + forAll { ss: OneOrMore[Slot] => + val protocol = addslots(ss) + assertEquals(protocol.encode, Arr(Bulk("CLUSTER") :: Bulk("ADDSLOTS") :: ss.value.map(Bulk(_)))) + assertEquals(protocol.decode(Str(OK.value)), OK) } + } - "using clusterinfo" should { - "roundtrip successfully" when { - "using val" in forAll(infoGen) { info => - whenever(infoIsValid(info)) { - val protocol = clusterinfo - - protocol.encode shouldBe Arr(Bulk("CLUSTER"), Bulk("INFO")) - protocol.decode(Bulk(info)) onRight (result => - info.forall { case (k, v) => result.selectDynamic(k).fold(_ => false, _ == v) } shouldBe true - ) - } - } - } + property("The Cluster protocol using clusterinfo roundtrips successfully using val") { + forAll(infoGen) { info => + val protocol = clusterinfo + assertEquals(protocol.encode, Arr(Bulk("CLUSTER"), Bulk("INFO"))) + protocol.decode(Bulk(info)) onRight (result => info.forall { case (k, v) => result.selectDynamic(k).fold(_ => false, _ == v) }) } + } - "using countfailurereports" should { - "roundtrip successfully" when { - "given a nodeId" in forAll("node id", "failure reports") { (n: NodeId, nni: NonNegInt) => - val protocol = countfailurereports(n) - - protocol.encode shouldBe Arr(Bulk("CLUSTER"), Bulk("COUNT-FAILURE-REPORTS"), Bulk(n)) - protocol.decode(Num(nni.value.toLong)) onRight (_ shouldBe nni) - } - } + property("The Cluster protocol using countfailurereports roundtrips successfully given a nodeId") { + forAll { (n: NodeId, nni: NonNegInt) => + val protocol = countfailurereports(n) + assertEquals(protocol.encode, Arr(Bulk("CLUSTER"), Bulk("COUNT-FAILURE-REPORTS"), Bulk(n))) + assertEquals(protocol.decode(Num(nni.value.toLong)), nni) } + } - "using countkeysinslot" should { - "roundtrip successfully" when { - "given a slot" in forAll("slot", "keys in slot") { (s: Slot, nni: NonNegInt) => - val protocol = countkeysinslot(s) - - protocol.encode shouldBe Arr(Bulk("CLUSTER"), Bulk("COUNTKEYSINSLOT"), Bulk(s)) - protocol.decode(Num(nni.value.toLong)) onRight (_ shouldBe nni) - } - } + property("The Cluster protocol using countfailurereports roundtrips successfully given a nodeId") { + forAll { (n: NodeId, nni: NonNegInt) => + val protocol = countfailurereports(n) + assertEquals(protocol.encode, Arr(Bulk("CLUSTER"), Bulk("COUNT-FAILURE-REPORTS"), Bulk(n))) + assertEquals(protocol.decode(Num(nni.value.toLong)), nni) } + } - "using delslots" should { - "roundtrip successfully" when { - "given one or more slots" in forAll("slots") { ss: OneOrMore[Slot] => - val protocol = delslots(ss) - - protocol.encode shouldBe Arr(Bulk("CLUSTER") :: Bulk("DELSLOTS") :: ss.value.map(Bulk(_))) - protocol.decode(Str(OK.value)) onRight (_ shouldBe OK) - } - } + property("The Cluster protocol using countkeysinslot roundtrips successfully given a slot") { + forAll { (s: Slot, nni: NonNegInt) => + val protocol = countkeysinslot(s) + assertEquals(protocol.encode, Arr(Bulk("CLUSTER"), Bulk("COUNTKEYSINSLOT"), Bulk(s))) + assertEquals(protocol.decode(Num(nni.value.toLong)), nni) } + } - "using failover" should { - "roundtrip successfully" when { - "using val" in { - val protocol = failover - - protocol.encode shouldBe Arr(Bulk("CLUSTER"), Bulk("FAILOVER")) - protocol.decode(Str(OK.value)) onRight (_ shouldBe OK) - } - "given mode" in forAll("failover mode") { m: ClusterFailoverMode => - val protocol = failover(m) - - protocol.encode shouldBe Arr(Bulk("CLUSTER"), Bulk("FAILOVER"), Bulk(m)) - protocol.decode(Str(OK.value)) onRight (_ shouldBe OK) - } - } + property("The Cluster protocol using delslots roundtrips successfully given one or more slots") { + forAll { ss: OneOrMore[Slot] => + val protocol = delslots(ss) + assertEquals(protocol.encode, Arr(Bulk("CLUSTER") :: Bulk("DELSLOTS") :: ss.value.map(Bulk(_)))) + assertEquals(protocol.decode(Str(OK.value)), OK) } + } - "using forget" should { - "roundtrip successfully" when { - "given a nodeId" in forAll("node id") { n: NodeId => - val protocol = forget(n) + test("The Cluster protocol using failover roundtrips successfully using val") { + val protocol = failover + assertEquals(protocol.encode, Arr(Bulk("CLUSTER"), Bulk("FAILOVER"))) + assertEquals(protocol.decode(Str(OK.value)), OK) + } - protocol.encode shouldBe Arr(Bulk("CLUSTER"), Bulk("FORGET"), Bulk(n)) - protocol.decode(Str(OK.value)) onRight (_ shouldBe OK) - } - } + property("The Cluster protocol using failover roundtrips successfully given mode") { + forAll { m: ClusterFailoverMode => + val protocol = failover(m) + assertEquals(protocol.encode, Arr(Bulk("CLUSTER"), Bulk("FAILOVER"), Bulk(m))) + assertEquals(protocol.decode(Str(OK.value)), OK) } + } - "using getkeysinslot" should { - "roundtrip successfully" when { - "given a slot and a count" in forAll("slot", "count", "keys in slot") { (s: Slot, pi: PosInt, ks: List[Key]) => - val protocol = getkeysinslot(s, pi) - - protocol.encode shouldBe Arr(Bulk("CLUSTER"), Bulk("GETKEYSINSLOT"), Bulk(s), Bulk(pi)) - protocol.decode(Arr(ks.map(Bulk(_)))) onRight (_ shouldBe ks) - } - } + property("The Cluster protocol using forget roundtrips successfully given a nodeId") { + forAll { n: NodeId => + val protocol = forget(n) + assertEquals(protocol.encode, Arr(Bulk("CLUSTER"), Bulk("FORGET"), Bulk(n))) + assertEquals(protocol.decode(Str(OK.value)), OK) } + } - "using keyslot" should { - "roundtrip successfully" when { - "given a key" in forAll("key", "slot") { (k: Key, s: Slot) => - val protocol = keyslot(k) - - protocol.encode shouldBe Arr(Bulk("CLUSTER"), Bulk("KEYSLOT"), Bulk(k)) - protocol.decode(Num(s.value.toLong)) onRight (_ shouldBe s) - } - } + property("The Cluster protocol using getkeysinslot roundtrips successfully given a slot and a count") { + forAll { (s: Slot, pi: PosInt, ks: List[Key]) => + val protocol = getkeysinslot(s, pi) + assertEquals(protocol.encode, Arr(Bulk("CLUSTER"), Bulk("GETKEYSINSLOT"), Bulk(s), Bulk(pi))) + assertEquals(protocol.decode(Arr(ks.map(Bulk(_)))), ks) } + } - "using meet" should { - "roundtrip successfully" when { - "given a host and a port" in forAll("host", "port") { (h: Host, p: Port) => - val protocol = meet(h, p) - - protocol.encode shouldBe Arr(Bulk("CLUSTER"), Bulk("MEET"), Bulk(h), Bulk(p)) - protocol.decode(Str(OK.value)) onRight (_ shouldBe OK) - } - } + property("The Cluster protocol using keyslot roundtrips successfully given a key") { + forAll { (k: Key, s: Slot) => + val protocol = keyslot(k) + assertEquals(protocol.encode, Arr(Bulk("CLUSTER"), Bulk("KEYSLOT"), Bulk(k))) + assertEquals(protocol.decode(Num(s.value.toLong)), s) } + } - "using nodes" should { - "roundtrip successfully" when { - "using val" in forAll(nodesGen) { ns => - whenever(nodesIsValid(ns)) { - val protocol = nodes - - protocol.encode shouldBe Arr(Bulk("CLUSTER"), Bulk("NODES")) - protocol.decode(Bulk(ns)) onRightAll validateNodes(ns) - } - } - } + property("The Cluster protocol using meet roundtrips successfully given a host and a port") { + forAll { (h: Host, p: Port) => + val protocol = meet(h, p) + assertEquals(protocol.encode, Arr(Bulk("CLUSTER"), Bulk("MEET"), Bulk(h), Bulk(p))) + assertEquals(protocol.decode(Str(OK.value)), OK) } + } - "using readonly" should { - "roundtrip successfully" when { - "using val" in { - val protocol = readonly - - protocol.encode shouldBe Arr(Bulk("CLUSTER"), Bulk("READONLY")) - protocol.decode(Str(OK.value)) onRight (_ shouldBe OK) - } - } + property("The Cluster protocol using nodes roundtrips successfully using val") { + forAll(nodesGen filter nodesIsValid) { ns => + val protocol = nodes + assertEquals(protocol.encode, Arr(Bulk("CLUSTER"), Bulk("NODES"))) + protocol.decode(Bulk(ns)) onRightAll validateNodes(ns) } + } - "using readwrite" should { - "roundtrip successfully" when { - "using val" in { - val protocol = readwrite - - protocol.encode shouldBe Arr(Bulk("CLUSTER"), Bulk("READWRITE")) - protocol.decode(Str(OK.value)) onRight (_ shouldBe OK) - } - } - } + property("The Cluster protocol using readonly roundtrips successfully using val") { + val protocol = readonly + assertEquals(protocol.encode, Arr(Bulk("CLUSTER"), Bulk("READONLY"))) + assertEquals(protocol.decode(Str(OK.value)), OK) + } - "using replicas" should { - "roundtrip successfully" when { - "given a nodeId" in forAll(nodeIdArb.arbitrary, nodesGen) { (n, ns) => - whenever(nodesIsValid(ns)) { - val protocol = replicas(n) + property("The Cluster protocol using readwrite roundtrips successfully using val") { + val protocol = readwrite + assertEquals(protocol.encode, Arr(Bulk("CLUSTER"), Bulk("READWRITE"))) + assertEquals(protocol.decode(Str(OK.value)), OK) + } - protocol.encode shouldBe Arr(Bulk("CLUSTER"), Bulk("REPLICAS"), Bulk(n)) - protocol.decode(Bulk(ns)) onRightAll validateNodes(ns) - } - } - } + property("The Cluster protocol using replicas roundtrips successfully given a nodeId") { + forAll(nodeIdArb.arbitrary, nodesGen filter nodesIsValid) { (n, ns) => + val protocol = replicas(n) + assertEquals(protocol.encode, Arr(Bulk("CLUSTER"), Bulk("REPLICAS"), Bulk(n))) + protocol.decode(Bulk(ns)) onRightAll validateNodes(ns) } + } - "using replicate" should { - "roundtrip successfully" when { - "given a nodeId" in forAll("node id") { n: NodeId => - val protocol = replicate(n) - - protocol.encode shouldBe Arr(Bulk("CLUSTER"), Bulk("REPLICATE"), Bulk(n)) - protocol.decode(Str(OK.value)) onRight (_ shouldBe OK) - } - } + property("The Cluster protocol using replicate roundtrips successfully given a nodeId") { + forAll { n: NodeId => + val protocol = replicate(n) + assertEquals(protocol.encode, Arr(Bulk("CLUSTER"), Bulk("REPLICATE"), Bulk(n))) + assertEquals(protocol.decode(Str(OK.value)), OK) } + } - "using reset" should { - "roundtrip successfully" when { - "using val" in { - val protocol = reset - - protocol.encode shouldBe Arr(Bulk("CLUSTER"), Bulk("RESET"), Bulk("SOFT")) - protocol.decode(Str(OK.value)) onRight (_ shouldBe OK) - } - "given reset mode" in forAll("reset mode") { m: ClusterResetMode => - val protocol = reset(m) + property("The Cluster protocol using reset roundtrips successfully using val") { + val protocol = reset + assertEquals(protocol.encode, Arr(Bulk("CLUSTER"), Bulk("RESET"), Bulk("SOFT"))) + assertEquals(protocol.decode(Str(OK.value)), OK) + } - protocol.encode shouldBe Arr(Bulk("CLUSTER"), Bulk("RESET"), Bulk(m)) - protocol.decode(Str(OK.value)) onRight (_ shouldBe OK) - } - } + property("The Cluster protocol using reset roundtrips successfully given reset mode") { + forAll { m: ClusterResetMode => + val protocol = reset(m) + assertEquals(protocol.encode, Arr(Bulk("CLUSTER"), Bulk("RESET"), Bulk(m))) + assertEquals(protocol.decode(Str(OK.value)), OK) } + } - "using saveconfig" should { - "roundtrip successfully" when { - "using val" in { - val protocol = saveconfig + property("The Cluster protocol using saveconfig roundtrips successfully using val") { + val protocol = saveconfig + assertEquals(protocol.encode, Arr(Bulk("CLUSTER"), Bulk("SAVECONFIG"))) + assertEquals(protocol.decode(Str(OK.value)), OK) + } - protocol.encode shouldBe Arr(Bulk("CLUSTER"), Bulk("SAVECONFIG")) - protocol.decode(Str(OK.value)) onRight (_ shouldBe OK) - } - } + property("The Cluster protocol using setconfigepoch roundtrips successfully given a config epoch") { + forAll { nni: NonNegInt => + val protocol = setconfigepoch(nni) + assertEquals(protocol.encode, Arr(Bulk("CLUSTER"), Bulk("SET-CONFIG-EPOCH"), Bulk(nni))) + assertEquals(protocol.decode(Str(OK.value)), OK) } + } - "using setconfigepoch" should { - "roundtrip successfully" when { - "given a config epoch" in forAll("config epoch") { nni: NonNegInt => - val protocol = setconfigepoch(nni) - - protocol.encode shouldBe Arr(Bulk("CLUSTER"), Bulk("SET-CONFIG-EPOCH"), Bulk(nni)) - protocol.decode(Str(OK.value)) onRight (_ shouldBe OK) - } - } + property("The Cluster protocol using setslot roundtrips successfully given a slot") { + forAll { s: Slot => + val protocol = setslot(s) + assertEquals(protocol.encode, Arr(Bulk("CLUSTER"), Bulk("SETSLOT"), Bulk(s), Bulk("STABLE"))) + assertEquals(protocol.decode(Str(OK.value)), OK) } + } - "using setslot" should { - "roundtrip successfully" when { - "given a slot" in forAll("slot") { s: Slot => - val protocol = setslot(s) - - protocol.encode shouldBe Arr(Bulk("CLUSTER"), Bulk("SETSLOT"), Bulk(s), Bulk("STABLE")) - protocol.decode(Str(OK.value)) onRight (_ shouldBe OK) - } - "given a slot, mode and a node id" in forAll("slot", "mode", "node id") { (s: Slot, m: ClusterSetSlotMode, nid: NodeId) => - val protocol = setslot(s, m, nid) + property("The Cluster protocol using setslot roundtrips successfully given a slot, mode and a node id") { + forAll { (s: Slot, m: ClusterSetSlotMode, nid: NodeId) => + val protocol = setslot(s, m, nid) + assertEquals(protocol.encode, Arr(Bulk("CLUSTER"), Bulk("SETSLOT"), Bulk(s), Bulk(m), Bulk(nid))) + assertEquals(protocol.decode(Str(OK.value)), OK) + } + } - protocol.encode shouldBe Arr(Bulk("CLUSTER"), Bulk("SETSLOT"), Bulk(s), Bulk(m), Bulk(nid)) - protocol.decode(Str(OK.value)) onRight (_ shouldBe OK) - } - } + property("The Cluster protocol using slaves roundtrips successfully given a nodeId") { + forAll(nodeIdArb.arbitrary, nodesGen filter nodesIsValid) { (n, ns) => + val protocol = slaves(n) + assertEquals(protocol.encode, Arr(Bulk("CLUSTER"), Bulk("SLAVES"), Bulk(n))) + protocol.decode(Bulk(ns)) onRightAll validateNodes(ns) } + } - "using slaves" should { - "roundtrip successfully" when { - "given a nodeId" in forAll(nodeIdArb.arbitrary, nodesGen) { (n, ns) => - whenever(nodesIsValid(ns)) { - val protocol = slaves(n) + property("The Cluster protocol using slots roundtrips successfully using val") { + forAll(slotsGen) { ss => + val protocol = slots + val ssWithLoopback = ss.map { + case (f, t, assigned) => + ( + f, + t, + assigned.foldRight[List[(String, Port, Option[NodeId])]](Nil) { + case ((Left(_), p, n), css) => (LoopbackHost.value, p, n) :: css + case ((Right(h), p, n), css) => (h.value, p, n) :: css + } + ) + }.toSet - protocol.encode shouldBe Arr(Bulk("CLUSTER"), Bulk("SLAVES"), Bulk(n)) - protocol.decode(Bulk(ns)) onRightAll validateNodes(ns) + assertEquals(protocol.encode, Arr(Bulk("CLUSTER"), Bulk("SLOTS"))) + protocol.decode(slotsToArr(ss)) onRightAll (_.slots.foreach { + case (ClusterRangeSlotType(f, t), ClusterNewSlotInfo(ClusterHostPortNodeId(mh, mp, mid), rs)) => + val mrs = (mh.value, mp, Some(mid)) :: rs.foldLeft(List.empty[(String, Port, Option[NodeId])]) { + case (acc, ClusterHostPortNodeId(h, p, nid)) => acc :+ ((h.value, p, Some(nid))) } - } - } - } - - "using slots" should { - "roundtrip successfully" when { - "using val" in forAll(slotsGen) { ss => - val protocol = slots - val ssWithLoopback = ss.map { - case (f, t, assigned) => - ( - f, - t, - assigned.foldRight[List[(String, Port, Option[NodeId])]](Nil) { - case ((Left(_), p, n), css) => (LoopbackHost.value, p, n) :: css - case ((Right(h), p, n), css) => (h.value, p, n) :: css - } - ) - }.toSet - - protocol.encode shouldBe Arr(Bulk("CLUSTER"), Bulk("SLOTS")) - protocol.decode(slotsToArr(ss)) onRightAll (_.slots.foreach { - case (ClusterRangeSlotType(f, t), ClusterNewSlotInfo(ClusterHostPortNodeId(mh, mp, mid), rs)) => - val mrs = (mh.value, mp, Some(mid)) :: rs.foldLeft(List.empty[(String, Port, Option[NodeId])]) { - case (acc, ClusterHostPortNodeId(h, p, nid)) => acc :+ ((h.value, p, Some(nid))) - } - ssWithLoopback should contain((f, t, mrs)) - case (ClusterRangeSlotType(f, t), ClusterOldSlotInfo(ClusterHostPort(mh, mp), rs)) => - val mrs = (mh.value, mp, None) :: rs.foldLeft(List.empty[(String, Port, Option[NodeId])]) { - case (acc, ClusterHostPort(h, p)) => acc :+ ((h.value, p, None)) - } - ssWithLoopback should contain((f, t, mrs)) - }) - } - } + assert(ssWithLoopback.contains((f, t, mrs))) + case (ClusterRangeSlotType(f, t), ClusterOldSlotInfo(ClusterHostPort(mh, mp), rs)) => + val mrs = (mh.value, mp, None) :: rs.foldLeft(List.empty[(String, Port, Option[NodeId])]) { + case (acc, ClusterHostPort(h, p)) => acc :+ ((h.value, p, None)) + } + assert(ssWithLoopback.contains((f, t, mrs))) + }) } } } diff --git a/core/src/test/scala/laserdisc/protocol/ConnectionPSpec.scala b/core/src/test/scala/laserdisc/protocol/ConnectionPSpec.scala index 169d0b9b..be5d27b7 100644 --- a/core/src/test/scala/laserdisc/protocol/ConnectionPSpec.scala +++ b/core/src/test/scala/laserdisc/protocol/ConnectionPSpec.scala @@ -1,88 +1,74 @@ package laserdisc package protocol -final class ConnectionPSpec extends BaseSpec with ConnectionP { - "The Connection protocol" when { - "using auth" should { - "roundtrip successfully" when { - "given non empty password" in forAll("non empty password") { key: Key => - val protocol = auth(key) +import org.scalacheck.Prop.forAll - protocol.encode shouldBe Arr(Bulk("AUTH"), Bulk(key.value)) - protocol.decode(Str(OK.value)) onRight (_ shouldBe OK) - } - } +final class ConnectionPSpec extends BaseSpec with ConnectionP { + property("The Connection protocol using auth roundtrips successfully given non empty password") { + forAll { key: Key => + val protocol = auth(key) + assertEquals(protocol.encode, Arr(Bulk("AUTH"), Bulk(key.value))) + assertEquals(protocol.decode(Str(OK.value)), OK) } + } - "using echo" should { - "roundtrip successfully" when { - "given any String message" in forAll("string") { s: String => - val protocol = echo(s) - - protocol.encode shouldBe Arr(Bulk("ECHO"), Bulk(s)) - protocol.decode(Bulk(s)) onRight (_ shouldBe s) - } - "given any Int message" in forAll("int") { i: Int => - val protocol = echo(i) - - protocol.encode shouldBe Arr(Bulk("ECHO"), Bulk(i)) - protocol.decode(Bulk(i)) onRight (_ shouldBe i) - } - } + property("The Connection protocol using echo roundtrips successfully given any String message") { + forAll { s: String => + val protocol = echo(s) + assertEquals(protocol.encode, Arr(Bulk("ECHO"), Bulk(s))) + assertEquals(protocol.decode(Bulk(s)), s) } + } - "using ping" should { - "roundript successfully" when { - "given any String message" in forAll("string") { s: String => - val protocol = ping(s) - - protocol.encode shouldBe Arr(Bulk("PING"), Bulk(s)) - protocol.decode(Bulk(s)) onRight (_ shouldBe s) - } - "given any Int message" in forAll("int") { i: Int => - val protocol = ping(i) - - protocol.encode shouldBe Arr(Bulk("PING"), Bulk(i)) - protocol.decode(Bulk(i)) onRight (_ shouldBe i) - } - "using val to get back PONG message" in { - val protocol = ping - - protocol.encode shouldBe Arr(Bulk("PING")) - protocol.decode(Str(PONG.value)) onRight (_ shouldBe PONG) - } - } + property("The Connection protocol using echo roundtrips successfully given any Int message") { + forAll { i: Int => + val protocol = echo(i) + assertEquals(protocol.encode, Arr(Bulk("ECHO"), Bulk(i))) + assertEquals(protocol.decode(Bulk(i)), i) } + } - "using quit" should { - "roundtrip successfully" in { - val protocol = quit + property("The Connection protocol using ping roundtrips successfully given any String message") { + forAll { s: String => + val protocol = ping(s) + assertEquals(protocol.encode, Arr(Bulk("PING"), Bulk(s))) + assertEquals(protocol.decode(Bulk(s)), s) + } + } - protocol.encode shouldBe Arr(Bulk("QUIT")) - protocol.decode(Str(OK.value)) onRight (_ shouldBe OK) - } + property("The Connection protocol using ping roundtrips successfully given any Int message") { + forAll { i: Int => + val protocol = ping(i) + assertEquals(protocol.encode, Arr(Bulk("PING"), Bulk(i))) + assertEquals(protocol.decode(Bulk(i)), i) } + } - "using select" should { - "roundtrip successfully" when { - "given valid DbIndexes" in forAll("db index") { dbi: DbIndex => - val protocol = select(dbi) + property("The Connection protocol using ping roundtrips successfully using val to get back PONG message") { + val protocol = ping + assertEquals(protocol.encode, Arr(Bulk("PING"))) + assertEquals(protocol.decode(Str(PONG.value)), PONG) + } - protocol.encode shouldBe Arr(Bulk("SELECT"), Bulk(dbi)) - protocol.decode(Str(OK.value)) onRight (_ shouldBe OK) - } - } - } + property("The Connection protocol using quit roundtrips successfully") { + val protocol = quit + assertEquals(protocol.encode, Arr(Bulk("QUIT"))) + assertEquals(protocol.decode(Str(OK.value)), OK) + } - "using swapdb" should { - "roundtrip successfully" when { - "given valid DbIndexes" in forAll("db index 1", "db index 2") { (dbi1: DbIndex, dbi2: DbIndex) => - val protocol = swapdb(dbi1, dbi2) + property("The Connection protocol using select roundtrips successfully given valid DbIndexes") { + forAll { dbi: DbIndex => + val protocol = select(dbi) + assertEquals(protocol.encode, Arr(Bulk("SELECT"), Bulk(dbi))) + assertEquals(protocol.decode(Str(OK.value)), OK) + } + } - protocol.encode shouldBe Arr(Bulk("SWAPDB"), Bulk(dbi1), Bulk(dbi2)) - protocol.decode(Str(OK.value)) onRight (_ shouldBe OK) - } - } + property("The Connection protocol using swapdb roundtrips successfully given valid DbIndexes") { + forAll { (dbi1: DbIndex, dbi2: DbIndex) => + val protocol = swapdb(dbi1, dbi2) + assertEquals(protocol.encode, Arr(Bulk("SWAPDB"), Bulk(dbi1), Bulk(dbi2))) + assertEquals(protocol.decode(Str(OK.value)), OK) } } } diff --git a/core/src/test/scala/laserdisc/protocol/GeoPSpec.scala b/core/src/test/scala/laserdisc/protocol/GeoPSpec.scala index c2fef0b7..555075bd 100644 --- a/core/src/test/scala/laserdisc/protocol/GeoPSpec.scala +++ b/core/src/test/scala/laserdisc/protocol/GeoPSpec.scala @@ -1,12 +1,50 @@ package laserdisc package protocol -final class GeoPSpec extends GeoExtPSpec { +import org.scalacheck.Prop.forAll + +abstract class GeoPSpec extends BaseSpec with GeoP { import geotypes._ import org.scalacheck.{Arbitrary, Gen} import org.scalacheck.Arbitrary.arbitrary import org.scalacheck.Gen.listOf + protected implicit final val geoHashShow: Show[GeoHash] = Show.unsafeFromToString + + protected implicit final val geoCoordinatesArb: Arbitrary[GeoCoordinates] = Arbitrary { + for { + lat <- arbitrary[Latitude] + long <- arbitrary[Longitude] + } yield GeoCoordinates(lat, long) + } + protected implicit final val geoPositionArb: Arbitrary[GeoPosition] = Arbitrary { + for { + m <- arbitrary[Key] + lat <- arbitrary[Latitude] + long <- arbitrary[Longitude] + } yield GeoPosition(m, lat, long) + } + protected implicit final val geoUnitArb: Arbitrary[GeoUnit] = Arbitrary { + Gen.oneOf(GeoUnit.meters, GeoUnit.kilometers, GeoUnit.miles, GeoUnit.feet) + } + + protected final val geoPositionToBulkList: GeoPosition => List[Bulk] = { + case GeoPosition(m, lat, long) => Bulk(long) :: Bulk(lat) :: Bulk(m) :: Nil + } + protected final val nonNegDoubleOptionToBulk: Option[NonNegDouble] => GenBulk = _.fold(NullBulk: GenBulk)(Bulk(_)) + protected final val oneOrMoreGeoCoordinatesOptionToArr: OneOrMore[Option[GeoCoordinates]] => GenArr = _.value.foldLeft(NilArr: GenArr) { + case (NilArr, Some(GeoCoordinates(lat, long))) => Arr(Arr(Bulk(long), Bulk(lat))) + case (NilArr, None) => Arr(NilArr) + case (Arr(e), Some(GeoCoordinates(lat, long))) => Arr(e :+ Arr(Bulk(long), Bulk(lat))) + case (Arr(e), None) => Arr(e :+ NilArr) + } + protected final val oneOrMoreGeoHashOptionToArr: OneOrMore[Option[GeoHash]] => GenArr = _.value.foldLeft(NilArr: GenArr) { + case (NilArr, Some(gh)) => Arr(Bulk(gh)) + case (NilArr, None) => Arr(NullBulk) + case (Arr(e), Some(gh)) => Arr(e :+ Bulk(gh)) + case (Arr(e), None) => Arr(e :+ NullBulk) + } + private[this] implicit final val geoKeyAndCoordArb: Arbitrary[GeoKeyAndCoord] = Arbitrary { for { k <- arbitrary[Key] @@ -84,754 +122,797 @@ final class GeoPSpec extends GeoExtPSpec { case GeoKeyCoordDistAndHash(k, GeoCoordinates(lat, long), d, h) => Arr(Bulk(k), Bulk(d), Num(h.value), Arr(Bulk(long), Bulk(lat))) }) - "The Geo protocol" when { - "using geoadd" should { - "roundtrip successfully" when { - "given key and positions" in forAll("key", "positions", "added") { (k: Key, ps: OneOrMore[GeoPosition], nni: NonNegInt) => - val protocol = geoadd(k, ps) + property("The Geo protocol using geoadd roundtrips successfully given key and positions") { + forAll { (k: Key, ps: OneOrMore[GeoPosition], nni: NonNegInt) => + val protocol = geoadd(k, ps) + assertEquals(protocol.encode, Arr(Bulk("GEOADD") :: Bulk(k) :: ps.value.flatMap(geoPositionToBulkList))) + assertEquals(protocol.decode(Num(nni.value.toLong)), nni) + } + } - protocol.encode shouldBe Arr(Bulk("GEOADD") :: Bulk(k) :: ps.value.flatMap(geoPositionToBulkList)) - protocol.decode(Num(nni.value.toLong)) onRight (_ shouldBe nni) - } - } + property("The Geo protocol using geodist roundtrips successfully given key and members") { + forAll { (k: Key, m1: Key, m2: Key, onnd: Option[NonNegDouble]) => + val protocol = geodist(k, m1, m2) + assertEquals(protocol.encode, Arr(Bulk("GEODIST"), Bulk(k), Bulk(m1), Bulk(m2))) + assertEquals(protocol.decode(nonNegDoubleOptionToBulk(onnd)), onnd) } + } - "using geodist" should { - "roundtrip successfully" when { - "given key and members" in { - forAll("key", "member 1", "member 2", "maybe distance") { (k: Key, m1: Key, m2: Key, onnd: Option[NonNegDouble]) => - val protocol = geodist(k, m1, m2) - - protocol.encode shouldBe Arr(Bulk("GEODIST"), Bulk(k), Bulk(m1), Bulk(m2)) - protocol.decode(nonNegDoubleOptionToBulk(onnd)) onRight (_ shouldBe onnd) - } - } - "given key, members and unit" in { - forAll("key", "member 1", "member 2", "unit", "maybe distance") { - (k: Key, m1: Key, m2: Key, u: GeoUnit, onnd: Option[NonNegDouble]) => - val protocol = geodist(k, m1, m2, u) - - protocol.encode shouldBe Arr(Bulk("GEODIST"), Bulk(k), Bulk(m1), Bulk(m2), Bulk(u)) - protocol.decode(nonNegDoubleOptionToBulk(onnd)) onRight (_ shouldBe onnd) - } - } - } + property("The Geo protocol using geodist roundtrips successfully given key, members and unit") { + forAll { (k: Key, m1: Key, m2: Key, u: GeoUnit, onnd: Option[NonNegDouble]) => + val protocol = geodist(k, m1, m2, u) + assertEquals(protocol.encode, Arr(Bulk("GEODIST"), Bulk(k), Bulk(m1), Bulk(m2), Bulk(u))) + assertEquals(protocol.decode(nonNegDoubleOptionToBulk(onnd)), onnd) } + } - "using geohash" should { - "roundtrip successfully" when { - "given key and members" in { - forAll("key", "members", "geo hashes") { (k: Key, ms: OneOrMoreKeys, oghs: OneOrMore[Option[GeoHash]]) => - val protocol = geohash(k, ms) + property("The Geo protocol using geohash roundtrips successfully given key and members") { + forAll { (k: Key, ms: OneOrMoreKeys, oghs: OneOrMore[Option[GeoHash]]) => + val protocol = geohash(k, ms) + assertEquals(protocol.encode, Arr(Bulk("GEOHASH") :: Bulk(k) :: ms.value.map(m => Bulk(m)))) + assertEquals(protocol.decode(oneOrMoreGeoHashOptionToArr(oghs)), oghs.value) + } + } - protocol.encode shouldBe Arr(Bulk("GEOHASH") :: Bulk(k) :: ms.value.map(m => Bulk(m))) - protocol.decode(oneOrMoreGeoHashOptionToArr(oghs)) onRight (_ shouldBe oghs.value) - } - } - } + property("The Geo protocol using geopos roundtrips successfully given key and members") { + forAll { (k: Key, ms: OneOrMoreKeys, ocs: OneOrMore[Option[GeoCoordinates]]) => + val protocol = geopos(k, ms) + assertEquals(protocol.encode, Arr(Bulk("GEOPOS") :: Bulk(k) :: ms.value.map(m => Bulk(m)))) + assertEquals(protocol.decode(oneOrMoreGeoCoordinatesOptionToArr(ocs)), ocs.value) } + } - "using geopos" should { - "roundtrip successfully" when { - "given key and members" in { - forAll("key", "members", "coordinates") { (k: Key, ms: OneOrMoreKeys, ocs: OneOrMore[Option[GeoCoordinates]]) => - val protocol = geopos(k, ms) + property("The Geo protocol using georadius roundtrips successfully given key, coordinates, radius and unit") { + forAll { (k: Key, c: GeoCoordinates, r: NonNegDouble, u: GeoUnit, ms: List[Key]) => + val protocol = georadius(k, c, r, u) + assertEquals(protocol.encode, Arr(Bulk("GEORADIUS"), Bulk(k), Bulk(c.longitude), Bulk(c.latitude), Bulk(r), Bulk(u))) + assertEquals(protocol.decode(Arr(ms.map(Bulk(_)))), ms) + } + } - protocol.encode shouldBe Arr(Bulk("GEOPOS") :: Bulk(k) :: ms.value.map(m => Bulk(m))) - protocol.decode(oneOrMoreGeoCoordinatesOptionToArr(ocs)) onRight (_ shouldBe ocs.value) - } - } + property("The Geo protocol using georadius roundtrips successfully given key, coordinates, radius, unit and limit") { + forAll { (k: Key, c: GeoCoordinates, r: NonNegDouble, u: GeoUnit, l: PosInt, ms: List[Key]) => + val protocol = georadius(k, c, r, u, l) + assertEquals( + protocol.encode, + Arr( + Bulk("GEORADIUS"), + Bulk(k), + Bulk(c.longitude), + Bulk(c.latitude), + Bulk(r), + Bulk(u), + Bulk("COUNT"), + Bulk(l) + ) + ) + assertEquals(protocol.decode(Arr(ms.map(Bulk(_)))), ms) + } + } + + property("The Geo protocol using georadius roundtrips successfully given key, coordinates, radius, unit and direction") { + forAll { (k: Key, c: GeoCoordinates, r: NonNegDouble, u: GeoUnit, d: Direction, ms: List[Key]) => + val protocol = georadius(k, c, r, u, d) + assertEquals( + protocol.encode, + Arr( + Bulk("GEORADIUS"), + Bulk(k), + Bulk(c.longitude), + Bulk(c.latitude), + Bulk(r), + Bulk(u), + Bulk(d) + ) + ) + assertEquals(protocol.decode(Arr(ms.map(Bulk(_)))), ms) + } + } + + property("The Geo protocol using georadius roundtrips successfully given key, coordinates, radius, unit, limit and direction") { + forAll { (input: (Key, GeoCoordinates, NonNegDouble, GeoUnit, PosInt, Direction), ms: List[Key]) => + val (k, c, r, u, l, d) = input + val protocol = georadius(k, c, r, u, l, d) + assertEquals( + protocol.encode, + Arr( + Bulk("GEORADIUS"), + Bulk(k), + Bulk(c.longitude), + Bulk(c.latitude), + Bulk(r), + Bulk(u), + Bulk("COUNT"), + Bulk(l), + Bulk(d) + ) + ) + assertEquals(protocol.decode(Arr(ms.map(Bulk(_)))), ms) + } + } + + property("The Geo protocol using georadius roundtrips successfully given key, coordinates, radius, unit and radius mode") { + forAll { (k: Key, c: GeoCoordinates, r: NonNegDouble, u: GeoUnit, rmAndRes: (GeoRadiusMode, List[_])) => + val (rm, res) = rmAndRes + implicit val ev = rm.r + val protocol = georadius(k, c, r, u, rm) + assertEquals( + protocol.encode, + Arr( + Bulk("GEORADIUS") :: + Bulk(k) :: + Bulk(c.longitude) :: + Bulk(c.latitude) :: + Bulk(r) :: + Bulk(u) :: + rm.params.map(Bulk(_)) + ) + ) + assertEquals(protocol.decode(listToArr(res)), res) + } + } + + property("The Geo protocol using georadius roundtrips successfully given key, coordinates, radius, unit, limit and radius mode") { + forAll { (k: Key, c: GeoCoordinates, r: NonNegDouble, u: GeoUnit, l: PosInt, rmAndRes: (GeoRadiusMode, List[_])) => + val (rm, res) = rmAndRes + implicit val ev = rm.r + val protocol = georadius(k, c, r, u, l, rm) + assertEquals( + protocol.encode, + Arr( + Bulk("GEORADIUS") :: + Bulk(k) :: + Bulk(c.longitude) :: + Bulk(c.latitude) :: + Bulk(r) :: + Bulk(u) :: + Bulk("COUNT") :: + Bulk(l) :: + rm.params.map(Bulk(_)) + ) + ) + assertEquals(protocol.decode(listToArr(res)), res) + } + } + + property("The Geo protocol using georadius roundtrips successfully given key, coordinates, radius, unit, direction and radius mode") { + forAll { (k: Key, c: GeoCoordinates, r: NonNegDouble, u: GeoUnit, d: Direction, rmAndRes: (GeoRadiusMode, List[_])) => + val (rm, res) = rmAndRes + implicit val ev = rm.r + val protocol = georadius(k, c, r, u, d, rm) + assertEquals( + protocol.encode, + Arr( + Bulk("GEORADIUS") :: + Bulk(k) :: + Bulk(c.longitude) :: + Bulk(c.latitude) :: + Bulk(r) :: + Bulk(u) :: + Bulk(d) :: + rm.params.map(Bulk(_)) + ) + ) + assertEquals(protocol.decode(listToArr(res)), res) + } + } + + property( + "The Geo protocol using georadius roundtrips successfully given key, coordinates, radius, unit, limit, direction and radius mode" + ) { + forAll { (input: (Key, GeoCoordinates, NonNegDouble, GeoUnit, PosInt, Direction), rmAndRes: (GeoRadiusMode, List[_])) => + val (k, c, r, u, l, d) = input + val (rm, res) = rmAndRes + implicit val ev = rm.r + val protocol = georadius(k, c, r, u, l, d, rm) + assertEquals( + protocol.encode, + Arr( + Bulk("GEORADIUS") :: + Bulk(k) :: + Bulk(c.longitude) :: + Bulk(c.latitude) :: + Bulk(r) :: + Bulk(u) :: + Bulk("COUNT") :: + Bulk(l) :: + Bulk(d) :: + rm.params.map(Bulk(_)) + ) + ) + assertEquals(protocol.decode(listToArr(res)), res) + } + } + + property("The Geo protocol using georadius roundtrips successfully given key, coordinates, radius, unit and store mode") { + forAll { (k: Key, c: GeoCoordinates, r: NonNegDouble, u: GeoUnit, sm: GeoStoreMode, nni: NonNegInt) => + val protocol = georadius(k, c, r, u, sm) + assertEquals( + protocol.encode, + Arr( + Bulk("GEORADIUS") :: + Bulk(k) :: + Bulk(c.longitude) :: + Bulk(c.latitude) :: + Bulk(r) :: + Bulk(u) :: + sm.params.map(Bulk(_)) + ) + ) + assertEquals(protocol.decode(Num(nni.value.toLong)), nni) + } + } + + property("The Geo protocol using georadius roundtrips successfully given key, coordinates, radius, unit, limit and store mode") { + forAll { (k: Key, c: GeoCoordinates, r: NonNegDouble, u: GeoUnit, l: PosInt, sm: GeoStoreMode) => + forAll { nni: NonNegInt => + val protocol = georadius(k, c, r, u, l, sm) + assertEquals( + protocol.encode, + Arr( + Bulk("GEORADIUS") :: + Bulk(k) :: + Bulk(c.longitude) :: + Bulk(c.latitude) :: + Bulk(r) :: + Bulk(u) :: + Bulk("COUNT") :: + Bulk(l) :: + sm.params.map(Bulk(_)) + ) + ) + assertEquals(protocol.decode(Num(nni.value.toLong)), nni) } } + } - "using georadius" should { - "roundtrip successfully" when { - "given key, coordinates, radius and unit" in { - forAll("key", "coordinates", "radius", "unit", "members") { - (k: Key, c: GeoCoordinates, r: NonNegDouble, u: GeoUnit, ms: List[Key]) => - val protocol = georadius(k, c, r, u) - - protocol.encode shouldBe Arr(Bulk("GEORADIUS"), Bulk(k), Bulk(c.longitude), Bulk(c.latitude), Bulk(r), Bulk(u)) - protocol.decode(Arr(ms.map(Bulk(_)))) onRight (_ shouldBe ms) - } - } - "given key, coordinates, radius, unit and limit" in { - forAll("key", "coordinates", "radius", "unit", "limit", "members") { - (k: Key, c: GeoCoordinates, r: NonNegDouble, u: GeoUnit, l: PosInt, ms: List[Key]) => - val protocol = georadius(k, c, r, u, l) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUS"), - Bulk(k), - Bulk(c.longitude), - Bulk(c.latitude), - Bulk(r), - Bulk(u), - Bulk("COUNT"), - Bulk(l) - ) - protocol.decode(Arr(ms.map(Bulk(_)))) onRight (_ shouldBe ms) - } - } - "given key, coordinates, radius, unit and direction" in { - forAll("key", "coordinates", "radius", "unit", "direction", "members") { - (k: Key, c: GeoCoordinates, r: NonNegDouble, u: GeoUnit, d: Direction, ms: List[Key]) => - val protocol = georadius(k, c, r, u, d) - - protocol.encode shouldBe Arr(Bulk("GEORADIUS"), Bulk(k), Bulk(c.longitude), Bulk(c.latitude), Bulk(r), Bulk(u), Bulk(d)) - protocol.decode(Arr(ms.map(Bulk(_)))) onRight (_ shouldBe ms) - } - } - "given key, coordinates, radius, unit, limit and direction" in { - forAll("key, coordinates, radius, unit, limit, direction", "members") { - (input: (Key, GeoCoordinates, NonNegDouble, GeoUnit, PosInt, Direction), ms: List[Key]) => - val (k, c, r, u, l, d) = input - val protocol = georadius(k, c, r, u, l, d) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUS"), - Bulk(k), - Bulk(c.longitude), - Bulk(c.latitude), - Bulk(r), - Bulk(u), - Bulk("COUNT"), - Bulk(l), - Bulk(d) - ) - protocol.decode(Arr(ms.map(Bulk(_)))) onRight (_ shouldBe ms) - } - } - "given key, coordinates, radius, unit and radius mode" in { - forAll("key", "coordinates", "radius", "unit", "radius mode & result") { - (k: Key, c: GeoCoordinates, r: NonNegDouble, u: GeoUnit, rmAndRes: (GeoRadiusMode, List[_])) => - val (rm, res) = rmAndRes - implicit val ev = rm.r - val protocol = georadius(k, c, r, u, rm) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUS") :: - Bulk(k) :: - Bulk(c.longitude) :: - Bulk(c.latitude) :: - Bulk(r) :: - Bulk(u) :: - rm.params.map(Bulk(_)) - ) - protocol.decode(listToArr(res)) onRight (_ shouldBe res) - } - } - "given key, coordinates, radius, unit, limit and radius mode" in { - forAll("key", "coordinates", "radius", "unit", "limit", "radius mode & result") { - (k: Key, c: GeoCoordinates, r: NonNegDouble, u: GeoUnit, l: PosInt, rmAndRes: (GeoRadiusMode, List[_])) => - val (rm, res) = rmAndRes - implicit val ev = rm.r - val protocol = georadius(k, c, r, u, l, rm) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUS") :: - Bulk(k) :: - Bulk(c.longitude) :: - Bulk(c.latitude) :: - Bulk(r) :: - Bulk(u) :: - Bulk("COUNT") :: - Bulk(l) :: - rm.params.map(Bulk(_)) - ) - protocol.decode(listToArr(res)) onRight (_ shouldBe res) - } - } - "given key, coordinates, radius, unit, direction and radius mode" in { - forAll("key", "coordinates", "radius", "unit", "direction", "radius mode & result") { - (k: Key, c: GeoCoordinates, r: NonNegDouble, u: GeoUnit, d: Direction, rmAndRes: (GeoRadiusMode, List[_])) => - val (rm, res) = rmAndRes - implicit val ev = rm.r - val protocol = georadius(k, c, r, u, d, rm) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUS") :: - Bulk(k) :: - Bulk(c.longitude) :: - Bulk(c.latitude) :: - Bulk(r) :: - Bulk(u) :: - Bulk(d) :: - rm.params.map(Bulk(_)) - ) - protocol.decode(listToArr(res)) onRight (_ shouldBe res) - } - } - "given key, coordinates, radius, unit, limit, direction and radius mode" in { - forAll("key, coordinates, radius, unit, limit, direction", "radius mode & result") { - (input: (Key, GeoCoordinates, NonNegDouble, GeoUnit, PosInt, Direction), rmAndRes: (GeoRadiusMode, List[_])) => - val (k, c, r, u, l, d) = input - val (rm, res) = rmAndRes - implicit val ev = rm.r - val protocol = georadius(k, c, r, u, l, d, rm) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUS") :: - Bulk(k) :: - Bulk(c.longitude) :: - Bulk(c.latitude) :: - Bulk(r) :: - Bulk(u) :: - Bulk("COUNT") :: - Bulk(l) :: - Bulk(d) :: - rm.params.map(Bulk(_)) - ) - protocol.decode(listToArr(res)) onRight (_ shouldBe res) - } - } - "given key, coordinates, radius, unit and store mode" in { - forAll("key", "coordinates", "radius", "unit", "store mode", "stored") { - (k: Key, c: GeoCoordinates, r: NonNegDouble, u: GeoUnit, sm: GeoStoreMode, nni: NonNegInt) => - val protocol = georadius(k, c, r, u, sm) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUS") :: Bulk(k) :: Bulk(c.longitude) :: Bulk(c.latitude) :: Bulk(r) :: Bulk(u) :: sm.params.map(Bulk(_)) - ) - protocol.decode(Num(nni.value.toLong)) onRight (_ shouldBe nni) - } - } - "given key, coordinates, radius, unit, limit and store mode" in { - forAll("key", "coordinates", "radius", "unit", "limit", "store mode") { - (k: Key, c: GeoCoordinates, r: NonNegDouble, u: GeoUnit, l: PosInt, sm: GeoStoreMode) => - forAll("stored") { nni: NonNegInt => - val protocol = georadius(k, c, r, u, l, sm) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUS") :: - Bulk(k) :: - Bulk(c.longitude) :: - Bulk(c.latitude) :: - Bulk(r) :: - Bulk(u) :: - Bulk("COUNT") :: - Bulk(l) :: - sm.params.map(Bulk(_)) - ) - protocol.decode(Num(nni.value.toLong)) onRight (_ shouldBe nni) - } - } - } - "given key, coordinates, radius, unit, direction and store mode" in { - forAll("key", "coordinates", "radius", "unit", "direction", "store mode") { - (k: Key, c: GeoCoordinates, r: NonNegDouble, u: GeoUnit, d: Direction, sm: GeoStoreMode) => - forAll("stored") { nni: NonNegInt => - val protocol = georadius(k, c, r, u, d, sm) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUS") :: - Bulk(k) :: - Bulk(c.longitude) :: - Bulk(c.latitude) :: - Bulk(r) :: - Bulk(u) :: - Bulk(d) :: - sm.params.map(Bulk(_)) - ) - protocol.decode(Num(nni.value.toLong)) onRight (_ shouldBe nni) - } - } - } - "given key, coordinates, radius, unit, limit, direction and store mode" in { - forAll("key, coordinates, radius, unit, limit, direction", "store mode", "stored") { - (input: (Key, GeoCoordinates, NonNegDouble, GeoUnit, PosInt, Direction), sm: GeoStoreMode, nni: NonNegInt) => - val (k, c, r, u, l, d) = input - val protocol = georadius(k, c, r, u, l, d, sm) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUS") :: - Bulk(k) :: - Bulk(c.longitude) :: - Bulk(c.latitude) :: - Bulk(r) :: - Bulk(u) :: - Bulk("COUNT") :: - Bulk(l) :: - Bulk(d) :: - sm.params.map(Bulk(_)) - ) - protocol.decode(Num(nni.value.toLong)) onRight (_ shouldBe nni) - } - } - - "given key, member, radius and unit" in { - forAll("key", "member", "radius", "unit", "members") { (k: Key, m: Key, r: NonNegDouble, u: GeoUnit, ms: List[Key]) => - val protocol = georadius(k, m, r, u) - - protocol.encode shouldBe Arr(Bulk("GEORADIUSBYMEMBER"), Bulk(k), Bulk(m), Bulk(r), Bulk(u)) - protocol.decode(Arr(ms.map(Bulk(_)))) onRight (_ shouldBe ms) - } - } - "given key, member, radius, unit and limit" in { - forAll("key", "coordinates", "radius", "unit", "limit", "members") { - (k: Key, m: Key, r: NonNegDouble, u: GeoUnit, l: PosInt, ms: List[Key]) => - val protocol = georadius(k, m, r, u, l) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUSBYMEMBER"), - Bulk(k), - Bulk(m), - Bulk(r), - Bulk(u), - Bulk("COUNT"), - Bulk(l) - ) - protocol.decode(Arr(ms.map(Bulk(_)))) onRight (_ shouldBe ms) - } - } - "given key, member, radius, unit and direction" in { - forAll("key", "member", "radius", "unit", "direction", "members") { - (k: Key, m: Key, r: NonNegDouble, u: GeoUnit, d: Direction, ms: List[Key]) => - val protocol = georadius(k, m, r, u, d) - - protocol.encode shouldBe Arr(Bulk("GEORADIUSBYMEMBER"), Bulk(k), Bulk(m), Bulk(r), Bulk(u), Bulk(d)) - protocol.decode(Arr(ms.map(Bulk(_)))) onRight (_ shouldBe ms) - } - } - "given key, member, radius, unit, limit and direction" in { - forAll("key, member, radius, unit, limit, direction", "members") { - (input: (Key, Key, NonNegDouble, GeoUnit, PosInt, Direction), ms: List[Key]) => - val (k, m, r, u, l, d) = input - val protocol = georadius(k, m, r, u, l, d) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUSBYMEMBER"), - Bulk(k), - Bulk(m), - Bulk(r), - Bulk(u), - Bulk("COUNT"), - Bulk(l), - Bulk(d) - ) - protocol.decode(Arr(ms.map(Bulk(_)))) onRight (_ shouldBe ms) - } - } - "given key, member, radius, unit and radius mode" in { - forAll("key", "member", "radius", "unit", "radius mode & result") { - (k: Key, m: Key, r: NonNegDouble, u: GeoUnit, rmAndRes: (GeoRadiusMode, List[_])) => - val (rm, res) = rmAndRes - implicit val ev = rm.r - val protocol = georadius(k, m, r, u, rm) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUSBYMEMBER") :: - Bulk(k) :: - Bulk(m) :: - Bulk(r) :: - Bulk(u) :: - rm.params.map(Bulk(_)) - ) - protocol.decode(listToArr(res)) onRight (_ shouldBe res) - } - } - "given key, member, radius, unit, limit and radius mode" in { - forAll("key", "member", "radius", "unit", "limit", "radius mode & result") { - (k: Key, m: Key, r: NonNegDouble, u: GeoUnit, l: PosInt, rmAndRes: (GeoRadiusMode, List[_])) => - val (rm, res) = rmAndRes - implicit val ev = rm.r - val protocol = georadius(k, m, r, u, l, rm) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUSBYMEMBER") :: - Bulk(k) :: - Bulk(m) :: - Bulk(r) :: - Bulk(u) :: - Bulk("COUNT") :: - Bulk(l) :: - rm.params.map(Bulk(_)) - ) - protocol.decode(listToArr(res)) onRight (_ shouldBe res) - } - } - "given key, member, radius, unit, direction and radius mode" in { - forAll("key", "member", "radius", "unit", "direction", "radius mode & result") { - (k: Key, m: Key, r: NonNegDouble, u: GeoUnit, d: Direction, rmAndRes: (GeoRadiusMode, List[_])) => - val (rm, res) = rmAndRes - implicit val ev = rm.r - val protocol = georadius(k, m, r, u, d, rm) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUSBYMEMBER") :: - Bulk(k) :: - Bulk(m) :: - Bulk(r) :: - Bulk(u) :: - Bulk(d) :: - rm.params.map(Bulk(_)) - ) - protocol.decode(listToArr(res)) onRight (_ shouldBe res) - } - } - "given key, member, radius, unit, limit, direction and radius mode" in { - forAll("key, member, radius, unit, limit, direction", "radius mode & result") { - (input: (Key, Key, NonNegDouble, GeoUnit, PosInt, Direction), rmAndRes: (GeoRadiusMode, List[_])) => - val (k, m, r, u, l, d) = input - val (rm, res) = rmAndRes - implicit val ev = rm.r - val protocol = georadius(k, m, r, u, l, d, rm) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUSBYMEMBER") :: - Bulk(k) :: - Bulk(m) :: - Bulk(r) :: - Bulk(u) :: - Bulk("COUNT") :: - Bulk(l) :: - Bulk(d) :: - rm.params.map(Bulk(_)) - ) - protocol.decode(listToArr(res)) onRight (_ shouldBe res) - } - } - "given key, member, radius, unit and store mode" in { - forAll("key", "member", "radius", "unit", "store mode", "stored") { - (k: Key, m: Key, r: NonNegDouble, u: GeoUnit, sm: GeoStoreMode, nni: NonNegInt) => - val protocol = georadius(k, m, r, u, sm) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUSBYMEMBER") :: Bulk(k) :: Bulk(m) :: Bulk(r) :: Bulk(u) :: sm.params.map(Bulk(_)) - ) - protocol.decode(Num(nni.value.toLong)) onRight (_ shouldBe nni) - } - } - "given key, member, radius, unit, limit and store mode" in { - forAll("key", "member", "radius", "unit", "limit", "store mode") { - (k: Key, m: Key, r: NonNegDouble, u: GeoUnit, l: PosInt, sm: GeoStoreMode) => - forAll("stored") { nni: NonNegInt => - val protocol = georadius(k, m, r, u, l, sm) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUSBYMEMBER") :: - Bulk(k) :: - Bulk(m) :: - Bulk(r) :: - Bulk(u) :: - Bulk("COUNT") :: - Bulk(l) :: - sm.params.map(Bulk(_)) - ) - protocol.decode(Num(nni.value.toLong)) onRight (_ shouldBe nni) - } - } - } - "given key, member, radius, unit, direction and store mode" in { - forAll("key, member, radius, unit, direction, store mode", "stored") { - (input: (Key, Key, NonNegDouble, GeoUnit, Direction, GeoStoreMode), nni: NonNegInt) => - val (k, m, r, u, d, sm) = input - val protocol = georadius(k, m, r, u, d, sm) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUSBYMEMBER") :: Bulk(k) :: Bulk(m) :: Bulk(r) :: Bulk(u) :: Bulk(d) :: sm.params.map(Bulk(_)) - ) - protocol.decode(Num(nni.value.toLong)) onRight (_ shouldBe nni) - } - } - "given key, member, radius, unit, limit, direction and store mode" in { - forAll("key, member, radius, unit, limit, direction", "store mode", "stored") { - (input: (Key, Key, NonNegDouble, GeoUnit, PosInt, Direction), sm: GeoStoreMode, nni: NonNegInt) => - val (k, m, r, u, l, d) = input - val protocol = georadius(k, m, r, u, l, d, sm) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUSBYMEMBER") :: - Bulk(k) :: - Bulk(m) :: - Bulk(r) :: - Bulk(u) :: - Bulk("COUNT") :: - Bulk(l) :: - Bulk(d) :: - sm.params.map(Bulk(_)) - ) - protocol.decode(Num(nni.value.toLong)) onRight (_ shouldBe nni) - } - } + property("The Geo protocol using georadius roundtrips successfully given key, coordinates, radius, unit, direction and store mode") { + forAll { (k: Key, c: GeoCoordinates, r: NonNegDouble, u: GeoUnit, d: Direction, sm: GeoStoreMode) => + forAll { nni: NonNegInt => + val protocol = georadius(k, c, r, u, d, sm) + assertEquals( + protocol.encode, + Arr( + Bulk("GEORADIUS") :: + Bulk(k) :: + Bulk(c.longitude) :: + Bulk(c.latitude) :: + Bulk(r) :: + Bulk(u) :: + Bulk(d) :: + sm.params.map(Bulk(_)) + ) + ) + assertEquals(protocol.decode(Num(nni.value.toLong)), nni) } } + } + + property( + "The Geo protocol using georadius roundtrips successfully given key, coordinates, radius, unit, limit, direction and store mode" + ) { + forAll { (input: (Key, GeoCoordinates, NonNegDouble, GeoUnit, PosInt, Direction), sm: GeoStoreMode, nni: NonNegInt) => + val (k, c, r, u, l, d) = input + val protocol = georadius(k, c, r, u, l, d, sm) + assertEquals( + protocol.encode, + Arr( + Bulk("GEORADIUS") :: + Bulk(k) :: + Bulk(c.longitude) :: + Bulk(c.latitude) :: + Bulk(r) :: + Bulk(u) :: + Bulk("COUNT") :: + Bulk(l) :: + Bulk(d) :: + sm.params.map(Bulk(_)) + ) + ) + assertEquals(protocol.decode(Num(nni.value.toLong)), nni) + } + } + + property("The Geo protocol using georadius roundtrips successfully given key, member, radius and unit") { + forAll { (k: Key, m: Key, r: NonNegDouble, u: GeoUnit, ms: List[Key]) => + val protocol = georadius(k, m, r, u) + assertEquals(protocol.encode, Arr(Bulk("GEORADIUSBYMEMBER"), Bulk(k), Bulk(m), Bulk(r), Bulk(u))) + assertEquals(protocol.decode(Arr(ms.map(Bulk(_)))), ms) + } + } + + property("The Geo protocol using georadius roundtrips successfully given key, member, radius, unit and limit") { + forAll { (k: Key, m: Key, r: NonNegDouble, u: GeoUnit, l: PosInt, ms: List[Key]) => + val protocol = georadius(k, m, r, u, l) + assertEquals( + protocol.encode, + Arr( + Bulk("GEORADIUSBYMEMBER"), + Bulk(k), + Bulk(m), + Bulk(r), + Bulk(u), + Bulk("COUNT"), + Bulk(l) + ) + ) + assertEquals(protocol.decode(Arr(ms.map(Bulk(_)))), ms) + } + } + + property("The Geo protocol using georadius roundtrips successfully given key, member, radius, unit and direction") { + forAll { (k: Key, m: Key, r: NonNegDouble, u: GeoUnit, d: Direction, ms: List[Key]) => + val protocol = georadius(k, m, r, u, d) + assertEquals(protocol.encode, Arr(Bulk("GEORADIUSBYMEMBER"), Bulk(k), Bulk(m), Bulk(r), Bulk(u), Bulk(d))) + assertEquals(protocol.decode(Arr(ms.map(Bulk(_)))), ms) + } + } + + property("The Geo protocol using georadius roundtrips successfully given key, member, radius, unit, limit and direction") { + forAll { (input: (Key, Key, NonNegDouble, GeoUnit, PosInt, Direction), ms: List[Key]) => + val (k, m, r, u, l, d) = input + val protocol = georadius(k, m, r, u, l, d) + assertEquals( + protocol.encode, + Arr( + Bulk("GEORADIUSBYMEMBER"), + Bulk(k), + Bulk(m), + Bulk(r), + Bulk(u), + Bulk("COUNT"), + Bulk(l), + Bulk(d) + ) + ) + assertEquals(protocol.decode(Arr(ms.map(Bulk(_)))), ms) + } + } + + property("The Geo protocol using georadius roundtrips successfully given key, member, radius, unit and radius mode") { + forAll { (k: Key, m: Key, r: NonNegDouble, u: GeoUnit, rmAndRes: (GeoRadiusMode, List[_])) => + val (rm, res) = rmAndRes + implicit val ev = rm.r + val protocol = georadius(k, m, r, u, rm) + assertEquals( + protocol.encode, + Arr( + Bulk("GEORADIUSBYMEMBER") :: + Bulk(k) :: + Bulk(m) :: + Bulk(r) :: + Bulk(u) :: + rm.params.map(Bulk(_)) + ) + ) + assertEquals(protocol.decode(listToArr(res)), res) + } + } - "using ro.georadius" should { - "roundtrip successfully" when { - "given key, coordinates, radius and unit" in { - forAll("key", "coordinates", "radius", "unit", "members") { - (k: Key, c: GeoCoordinates, r: NonNegDouble, u: GeoUnit, ms: List[Key]) => - val protocol = ro.georadius(k, c, r, u) - - protocol.encode shouldBe Arr(Bulk("GEORADIUS_RO"), Bulk(k), Bulk(c.longitude), Bulk(c.latitude), Bulk(r), Bulk(u)) - protocol.decode(Arr(ms.map(Bulk(_)))) onRight (_ shouldBe ms) - } - } - "given key, coordinates, radius, unit and limit" in { - forAll("key", "coordinates", "radius", "unit", "limit", "members") { - (k: Key, c: GeoCoordinates, r: NonNegDouble, u: GeoUnit, l: PosInt, ms: List[Key]) => - val protocol = ro.georadius(k, c, r, u, l) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUS_RO"), - Bulk(k), - Bulk(c.longitude), - Bulk(c.latitude), - Bulk(r), - Bulk(u), - Bulk("COUNT"), - Bulk(l) - ) - protocol.decode(Arr(ms.map(Bulk(_)))) onRight (_ shouldBe ms) - } - } - "given key, coordinates, radius, unit and direction" in { - forAll("key", "coordinates", "radius", "unit", "direction", "members") { - (k: Key, c: GeoCoordinates, r: NonNegDouble, u: GeoUnit, d: Direction, ms: List[Key]) => - val protocol = ro.georadius(k, c, r, u, d) - - protocol.encode shouldBe Arr(Bulk("GEORADIUS_RO"), Bulk(k), Bulk(c.longitude), Bulk(c.latitude), Bulk(r), Bulk(u), Bulk(d)) - protocol.decode(Arr(ms.map(Bulk(_)))) onRight (_ shouldBe ms) - } - } - "given key, coordinates, radius, unit, limit and direction" in { - forAll("key, coordinates, radius, unit, limit, direction", "members") { - (input: (Key, GeoCoordinates, NonNegDouble, GeoUnit, PosInt, Direction), ms: List[Key]) => - val (k, c, r, u, l, d) = input - val protocol = ro.georadius(k, c, r, u, l, d) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUS_RO"), - Bulk(k), - Bulk(c.longitude), - Bulk(c.latitude), - Bulk(r), - Bulk(u), - Bulk("COUNT"), - Bulk(l), - Bulk(d) - ) - protocol.decode(Arr(ms.map(Bulk(_)))) onRight (_ shouldBe ms) - } - } - "given key, coordinates, radius, unit and radius mode" in { - forAll("key", "coordinates", "radius", "unit", "radius mode & result") { - (k: Key, c: GeoCoordinates, r: NonNegDouble, u: GeoUnit, rmAndRes: (GeoRadiusMode, List[_])) => - val (rm, res) = rmAndRes - implicit val ev = rm.r - val protocol = ro.georadius(k, c, r, u, rm) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUS_RO") :: - Bulk(k) :: - Bulk(c.longitude) :: - Bulk(c.latitude) :: - Bulk(r) :: - Bulk(u) :: - rm.params.map(Bulk(_)) - ) - protocol.decode(listToArr(res)) onRight (_ shouldBe res) - } - } - "given key, coordinates, radius, unit, limit and radius mode" in { - forAll("key", "coordinates", "radius", "unit", "limit", "radius mode & result") { - (k: Key, c: GeoCoordinates, r: NonNegDouble, u: GeoUnit, l: PosInt, rmAndRes: (GeoRadiusMode, List[_])) => - val (rm, res) = rmAndRes - implicit val ev = rm.r - val protocol = ro.georadius(k, c, r, u, l, rm) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUS_RO") :: - Bulk(k) :: - Bulk(c.longitude) :: - Bulk(c.latitude) :: - Bulk(r) :: - Bulk(u) :: - Bulk("COUNT") :: - Bulk(l) :: - rm.params.map(Bulk(_)) - ) - protocol.decode(listToArr(res)) onRight (_ shouldBe res) - } - } - "given key, coordinates, radius, unit, direction and radius mode" in { - forAll("key", "coordinates", "radius", "unit", "direction", "radius mode & result") { - (k: Key, c: GeoCoordinates, r: NonNegDouble, u: GeoUnit, d: Direction, rmAndRes: (GeoRadiusMode, List[_])) => - val (rm, res) = rmAndRes - implicit val ev = rm.r - val protocol = ro.georadius(k, c, r, u, d, rm) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUS_RO") :: - Bulk(k) :: - Bulk(c.longitude) :: - Bulk(c.latitude) :: - Bulk(r) :: - Bulk(u) :: - Bulk(d) :: - rm.params.map(Bulk(_)) - ) - protocol.decode(listToArr(res)) onRight (_ shouldBe res) - } - } - "given key, coordinates, radius, unit, limit, direction and radius mode" in { - forAll("key, coordinates, radius, unit, limit, direction", "radius mode & result") { - (input: (Key, GeoCoordinates, NonNegDouble, GeoUnit, PosInt, Direction), rmAndRes: (GeoRadiusMode, List[_])) => - val (k, c, r, u, l, d) = input - val (rm, res) = rmAndRes - implicit val ev = rm.r - val protocol = ro.georadius(k, c, r, u, l, d, rm) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUS_RO") :: - Bulk(k) :: - Bulk(c.longitude) :: - Bulk(c.latitude) :: - Bulk(r) :: - Bulk(u) :: - Bulk("COUNT") :: - Bulk(l) :: - Bulk(d) :: - rm.params.map(Bulk(_)) - ) - protocol.decode(listToArr(res)) onRight (_ shouldBe res) - } - } - - "given key, member, radius and unit" in { - forAll("key", "member", "radius", "unit", "members") { (k: Key, m: Key, r: NonNegDouble, u: GeoUnit, ms: List[Key]) => - val protocol = ro.georadius(k, m, r, u) - - protocol.encode shouldBe Arr(Bulk("GEORADIUSBYMEMBER_RO"), Bulk(k), Bulk(m), Bulk(r), Bulk(u)) - protocol.decode(Arr(ms.map(Bulk(_)))) onRight (_ shouldBe ms) - } - } - "given key, member, radius, unit and limit" in { - forAll("key", "coordinates", "radius", "unit", "limit", "members") { - (k: Key, m: Key, r: NonNegDouble, u: GeoUnit, l: PosInt, ms: List[Key]) => - val protocol = ro.georadius(k, m, r, u, l) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUSBYMEMBER_RO"), - Bulk(k), - Bulk(m), - Bulk(r), - Bulk(u), - Bulk("COUNT"), - Bulk(l) - ) - protocol.decode(Arr(ms.map(Bulk(_)))) onRight (_ shouldBe ms) - } - } - "given key, member, radius, unit and direction" in { - forAll("key", "member", "radius", "unit", "direction", "members") { - (k: Key, m: Key, r: NonNegDouble, u: GeoUnit, d: Direction, ms: List[Key]) => - val protocol = ro.georadius(k, m, r, u, d) - - protocol.encode shouldBe Arr(Bulk("GEORADIUSBYMEMBER_RO"), Bulk(k), Bulk(m), Bulk(r), Bulk(u), Bulk(d)) - protocol.decode(Arr(ms.map(Bulk(_)))) onRight (_ shouldBe ms) - } - } - "given key, member, radius, unit, limit and direction" in { - forAll("key, member, radius, unit, limit, direction", "members") { - (input: (Key, Key, NonNegDouble, GeoUnit, PosInt, Direction), ms: List[Key]) => - val (k, m, r, u, l, d) = input - val protocol = ro.georadius(k, m, r, u, l, d) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUSBYMEMBER_RO"), - Bulk(k), - Bulk(m), - Bulk(r), - Bulk(u), - Bulk("COUNT"), - Bulk(l), - Bulk(d) - ) - protocol.decode(Arr(ms.map(Bulk(_)))) onRight (_ shouldBe ms) - } - } - "given key, member, radius, unit and radius mode" in { - forAll("key", "member", "radius", "unit", "radius mode & result") { - (k: Key, m: Key, r: NonNegDouble, u: GeoUnit, rmAndRes: (GeoRadiusMode, List[_])) => - val (rm, res) = rmAndRes - implicit val ev = rm.r - val protocol = ro.georadius(k, m, r, u, rm) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUSBYMEMBER_RO") :: - Bulk(k) :: - Bulk(m) :: - Bulk(r) :: - Bulk(u) :: - rm.params.map(Bulk(_)) - ) - protocol.decode(listToArr(res)) onRight (_ shouldBe res) - } - } - "given key, member, radius, unit, limit and radius mode" in { - forAll("key", "member", "radius", "unit", "limit", "radius mode & result") { - (k: Key, m: Key, r: NonNegDouble, u: GeoUnit, l: PosInt, rmAndRes: (GeoRadiusMode, List[_])) => - val (rm, res) = rmAndRes - implicit val ev = rm.r - val protocol = ro.georadius(k, m, r, u, l, rm) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUSBYMEMBER_RO") :: - Bulk(k) :: - Bulk(m) :: - Bulk(r) :: - Bulk(u) :: - Bulk("COUNT") :: - Bulk(l) :: - rm.params.map(Bulk(_)) - ) - protocol.decode(listToArr(res)) onRight (_ shouldBe res) - } - } - "given key, member, radius, unit, direction and radius mode" in { - forAll("key", "member", "radius", "unit", "direction", "radius mode & result") { - (k: Key, m: Key, r: NonNegDouble, u: GeoUnit, d: Direction, rmAndRes: (GeoRadiusMode, List[_])) => - val (rm, res) = rmAndRes - implicit val ev = rm.r - val protocol = ro.georadius(k, m, r, u, d, rm) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUSBYMEMBER_RO") :: - Bulk(k) :: - Bulk(m) :: - Bulk(r) :: - Bulk(u) :: - Bulk(d) :: - rm.params.map(Bulk(_)) - ) - protocol.decode(listToArr(res)) onRight (_ shouldBe res) - } - } - "given key, member, radius, unit, limit, direction and radius mode" in { - forAll("key, member, radius, unit, limit, direction", "radius mode & result") { - (input: (Key, Key, NonNegDouble, GeoUnit, PosInt, Direction), rmAndRes: (GeoRadiusMode, List[_])) => - val (k, m, r, u, l, d) = input - val (rm, res) = rmAndRes - implicit val ev = rm.r - val protocol = ro.georadius(k, m, r, u, l, d, rm) - - protocol.encode shouldBe Arr( - Bulk("GEORADIUSBYMEMBER_RO") :: - Bulk(k) :: - Bulk(m) :: - Bulk(r) :: - Bulk(u) :: - Bulk("COUNT") :: - Bulk(l) :: - Bulk(d) :: - rm.params.map(Bulk(_)) - ) - protocol.decode(listToArr(res)) onRight (_ shouldBe res) - } - } + property("The Geo protocol using georadius roundtrips successfully given key, member, radius, unit, limit and radius mode") { + forAll { (k: Key, m: Key, r: NonNegDouble, u: GeoUnit, l: PosInt, rmAndRes: (GeoRadiusMode, List[_])) => + val (rm, res) = rmAndRes + implicit val ev = rm.r + val protocol = georadius(k, m, r, u, l, rm) + assertEquals( + protocol.encode, + Arr( + Bulk("GEORADIUSBYMEMBER") :: + Bulk(k) :: + Bulk(m) :: + Bulk(r) :: + Bulk(u) :: + Bulk("COUNT") :: + Bulk(l) :: + rm.params.map(Bulk(_)) + ) + ) + assertEquals(protocol.decode(listToArr(res)), res) + } + } + + property("The Geo protocol using georadius roundtrips successfully given key, member, radius, unit, direction and radius mode") { + forAll { (k: Key, m: Key, r: NonNegDouble, u: GeoUnit, d: Direction, rmAndRes: (GeoRadiusMode, List[_])) => + val (rm, res) = rmAndRes + implicit val ev = rm.r + val protocol = georadius(k, m, r, u, d, rm) + assertEquals( + protocol.encode, + Arr( + Bulk("GEORADIUSBYMEMBER") :: + Bulk(k) :: + Bulk(m) :: + Bulk(r) :: + Bulk(u) :: + Bulk(d) :: + rm.params.map(Bulk(_)) + ) + ) + assertEquals(protocol.decode(listToArr(res)), res) + } + } + + property("The Geo protocol using georadius roundtrips successfully given key, member, radius, unit, limit, direction and radius mode") { + forAll { (input: (Key, Key, NonNegDouble, GeoUnit, PosInt, Direction), rmAndRes: (GeoRadiusMode, List[_])) => + val (k, m, r, u, l, d) = input + val (rm, res) = rmAndRes + implicit val ev = rm.r + val protocol = georadius(k, m, r, u, l, d, rm) + assertEquals( + protocol.encode, + Arr( + Bulk("GEORADIUSBYMEMBER") :: + Bulk(k) :: + Bulk(m) :: + Bulk(r) :: + Bulk(u) :: + Bulk("COUNT") :: + Bulk(l) :: + Bulk(d) :: + rm.params.map(Bulk(_)) + ) + ) + assertEquals(protocol.decode(listToArr(res)), res) + } + } + + property("The Geo protocol using georadius roundtrips successfully given key, member, radius, unit and store mode") { + forAll { (k: Key, m: Key, r: NonNegDouble, u: GeoUnit, sm: GeoStoreMode, nni: NonNegInt) => + val protocol = georadius(k, m, r, u, sm) + assertEquals(protocol.encode, Arr(Bulk("GEORADIUSBYMEMBER") :: Bulk(k) :: Bulk(m) :: Bulk(r) :: Bulk(u) :: sm.params.map(Bulk(_)))) + assertEquals(protocol.decode(Num(nni.value.toLong)), nni) + } + } + + property("The Geo protocol using georadius roundtrips successfully given key, member, radius, unit, limit and store mode") { + forAll { (k: Key, m: Key, r: NonNegDouble, u: GeoUnit, l: PosInt, sm: GeoStoreMode) => + forAll { nni: NonNegInt => + val protocol = georadius(k, m, r, u, l, sm) + assertEquals( + protocol.encode, + Arr( + Bulk("GEORADIUSBYMEMBER") :: + Bulk(k) :: + Bulk(m) :: + Bulk(r) :: + Bulk(u) :: + Bulk("COUNT") :: + Bulk(l) :: + sm.params.map(Bulk(_)) + ) + ) + assertEquals(protocol.decode(Num(nni.value.toLong)), nni) } } } + + property("The Geo protocol using georadius roundtrips successfully given key, member, radius, unit, direction and store mode") { + forAll { (input: (Key, Key, NonNegDouble, GeoUnit, Direction, GeoStoreMode), nni: NonNegInt) => + val (k, m, r, u, d, sm) = input + val protocol = georadius(k, m, r, u, d, sm) + assertEquals( + protocol.encode, + Arr(Bulk("GEORADIUSBYMEMBER") :: Bulk(k) :: Bulk(m) :: Bulk(r) :: Bulk(u) :: Bulk(d) :: sm.params.map(Bulk(_))) + ) + assertEquals(protocol.decode(Num(nni.value.toLong)), nni) + } + } + + property("The Geo protocol using georadius roundtrips successfully given key, member, radius, unit, limit, direction and store mode") { + forAll { (input: (Key, Key, NonNegDouble, GeoUnit, PosInt, Direction), sm: GeoStoreMode, nni: NonNegInt) => + val (k, m, r, u, l, d) = input + val protocol = georadius(k, m, r, u, l, d, sm) + assertEquals( + protocol.encode, + Arr( + Bulk("GEORADIUSBYMEMBER") :: + Bulk(k) :: + Bulk(m) :: + Bulk(r) :: + Bulk(u) :: + Bulk("COUNT") :: + Bulk(l) :: + Bulk(d) :: + sm.params.map(Bulk(_)) + ) + ) + assertEquals(protocol.decode(Num(nni.value.toLong)), nni) + } + } + + property("The Geo protocol using ro.georadius roundtrips successfully given key, coordinates, radius and unit") { + forAll { (k: Key, c: GeoCoordinates, r: NonNegDouble, u: GeoUnit, ms: List[Key]) => + val protocol = ro.georadius(k, c, r, u) + assertEquals(protocol.encode, Arr(Bulk("GEORADIUS_RO"), Bulk(k), Bulk(c.longitude), Bulk(c.latitude), Bulk(r), Bulk(u))) + assertEquals(protocol.decode(Arr(ms.map(Bulk(_)))), ms) + } + } + + property("The Geo protocol using ro.georadius roundtrips successfully given key, coordinates, radius, unit and limit") { + forAll { (k: Key, c: GeoCoordinates, r: NonNegDouble, u: GeoUnit, l: PosInt, ms: List[Key]) => + val protocol = ro.georadius(k, c, r, u, l) + assertEquals( + protocol.encode, + Arr( + Bulk("GEORADIUS_RO"), + Bulk(k), + Bulk(c.longitude), + Bulk(c.latitude), + Bulk(r), + Bulk(u), + Bulk("COUNT"), + Bulk(l) + ) + ) + assertEquals(protocol.decode(Arr(ms.map(Bulk(_)))), ms) + } + } + + property("The Geo protocol using ro.georadius roundtrips successfully given key, coordinates, radius, unit and direction") { + forAll { (k: Key, c: GeoCoordinates, r: NonNegDouble, u: GeoUnit, d: Direction, ms: List[Key]) => + val protocol = ro.georadius(k, c, r, u, d) + assertEquals(protocol.encode, Arr(Bulk("GEORADIUS_RO"), Bulk(k), Bulk(c.longitude), Bulk(c.latitude), Bulk(r), Bulk(u), Bulk(d))) + assertEquals(protocol.decode(Arr(ms.map(Bulk(_)))), ms) + } + } + + property("The Geo protocol using ro.georadius roundtrips successfully given key, coordinates, radius, unit, limit and direction") { + forAll { (input: (Key, GeoCoordinates, NonNegDouble, GeoUnit, PosInt, Direction), ms: List[Key]) => + val (k, c, r, u, l, d) = input + val protocol = ro.georadius(k, c, r, u, l, d) + assertEquals( + protocol.encode, + Arr( + Bulk("GEORADIUS_RO"), + Bulk(k), + Bulk(c.longitude), + Bulk(c.latitude), + Bulk(r), + Bulk(u), + Bulk("COUNT"), + Bulk(l), + Bulk(d) + ) + ) + assertEquals(protocol.decode(Arr(ms.map(Bulk(_)))), ms) + } + } + + property("The Geo protocol using ro.georadius roundtrips successfully given key, coordinates, radius, unit and radius mode") { + forAll { (k: Key, c: GeoCoordinates, r: NonNegDouble, u: GeoUnit, rmAndRes: (GeoRadiusMode, List[_])) => + val (rm, res) = rmAndRes + implicit val ev = rm.r + val protocol = ro.georadius(k, c, r, u, rm) + assertEquals( + protocol.encode, + Arr( + Bulk("GEORADIUS_RO") :: + Bulk(k) :: + Bulk(c.longitude) :: + Bulk(c.latitude) :: + Bulk(r) :: + Bulk(u) :: + rm.params.map(Bulk(_)) + ) + ) + assertEquals(protocol.decode(listToArr(res)), res) + } + } + + property("The Geo protocol using ro.georadius roundtrips successfully given key, coordinates, radius, unit, limit and radius mode") { + forAll { (k: Key, c: GeoCoordinates, r: NonNegDouble, u: GeoUnit, l: PosInt, rmAndRes: (GeoRadiusMode, List[_])) => + val (rm, res) = rmAndRes + implicit val ev = rm.r + val protocol = ro.georadius(k, c, r, u, l, rm) + assertEquals( + protocol.encode, + Arr( + Bulk("GEORADIUS_RO") :: + Bulk(k) :: + Bulk(c.longitude) :: + Bulk(c.latitude) :: + Bulk(r) :: + Bulk(u) :: + Bulk("COUNT") :: + Bulk(l) :: + rm.params.map(Bulk(_)) + ) + ) + assertEquals(protocol.decode(listToArr(res)), res) + } + } + + property("The Geo protocol using ro.georadius roundtrips successfully given key, coordinates, radius, unit, direction and radius mode") { + forAll { (k: Key, c: GeoCoordinates, r: NonNegDouble, u: GeoUnit, d: Direction, rmAndRes: (GeoRadiusMode, List[_])) => + val (rm, res) = rmAndRes + implicit val ev = rm.r + val protocol = ro.georadius(k, c, r, u, d, rm) + assertEquals( + protocol.encode, + Arr( + Bulk("GEORADIUS_RO") :: + Bulk(k) :: + Bulk(c.longitude) :: + Bulk(c.latitude) :: + Bulk(r) :: + Bulk(u) :: + Bulk(d) :: + rm.params.map(Bulk(_)) + ) + ) + assertEquals(protocol.decode(listToArr(res)), res) + } + } + + property( + "The Geo protocol using ro.georadius roundtrips successfully given key, coordinates, radius, unit, limit, direction and radius mode" + ) { + forAll { (input: (Key, GeoCoordinates, NonNegDouble, GeoUnit, PosInt, Direction), rmAndRes: (GeoRadiusMode, List[_])) => + val (k, c, r, u, l, d) = input + val (rm, res) = rmAndRes + implicit val ev = rm.r + val protocol = ro.georadius(k, c, r, u, l, d, rm) + assertEquals( + protocol.encode, + Arr( + Bulk("GEORADIUS_RO") :: + Bulk(k) :: + Bulk(c.longitude) :: + Bulk(c.latitude) :: + Bulk(r) :: + Bulk(u) :: + Bulk("COUNT") :: + Bulk(l) :: + Bulk(d) :: + rm.params.map(Bulk(_)) + ) + ) + assertEquals(protocol.decode(listToArr(res)), res) + } + } + + property("The Geo protocol using ro.georadius roundtrips successfully given key, member, radius and unit") { + forAll { (k: Key, m: Key, r: NonNegDouble, u: GeoUnit, ms: List[Key]) => + val protocol = ro.georadius(k, m, r, u) + assertEquals(protocol.encode, Arr(Bulk("GEORADIUSBYMEMBER_RO"), Bulk(k), Bulk(m), Bulk(r), Bulk(u))) + assertEquals(protocol.decode(Arr(ms.map(Bulk(_)))), ms) + } + } + + property("The Geo protocol using ro.georadius roundtrips successfully given key, member, radius, unit and limit") { + forAll { (k: Key, m: Key, r: NonNegDouble, u: GeoUnit, l: PosInt, ms: List[Key]) => + val protocol = ro.georadius(k, m, r, u, l) + assertEquals( + protocol.encode, + Arr( + Bulk("GEORADIUSBYMEMBER_RO"), + Bulk(k), + Bulk(m), + Bulk(r), + Bulk(u), + Bulk("COUNT"), + Bulk(l) + ) + ) + assertEquals(protocol.decode(Arr(ms.map(Bulk(_)))), ms) + } + } + + property("The Geo protocol using ro.georadius roundtrips successfully given key, member, radius, unit and direction") { + forAll { (k: Key, m: Key, r: NonNegDouble, u: GeoUnit, d: Direction, ms: List[Key]) => + val protocol = ro.georadius(k, m, r, u, d) + assertEquals(protocol.encode, Arr(Bulk("GEORADIUSBYMEMBER_RO"), Bulk(k), Bulk(m), Bulk(r), Bulk(u), Bulk(d))) + assertEquals(protocol.decode(Arr(ms.map(Bulk(_)))), ms) + } + } + + property("The Geo protocol using ro.georadius roundtrips successfully given key, member, radius, unit, limit and direction") { + forAll { (input: (Key, Key, NonNegDouble, GeoUnit, PosInt, Direction), ms: List[Key]) => + val (k, m, r, u, l, d) = input + val protocol = ro.georadius(k, m, r, u, l, d) + assertEquals( + protocol.encode, + Arr( + Bulk("GEORADIUSBYMEMBER_RO"), + Bulk(k), + Bulk(m), + Bulk(r), + Bulk(u), + Bulk("COUNT"), + Bulk(l), + Bulk(d) + ) + ) + assertEquals(protocol.decode(Arr(ms.map(Bulk(_)))), ms) + } + } + + property("The Geo protocol using ro.georadius roundtrips successfully given key, member, radius, unit and radius mode") { + forAll { (k: Key, m: Key, r: NonNegDouble, u: GeoUnit, rmAndRes: (GeoRadiusMode, List[_])) => + val (rm, res) = rmAndRes + implicit val ev = rm.r + val protocol = ro.georadius(k, m, r, u, rm) + assertEquals( + protocol.encode, + Arr( + Bulk("GEORADIUSBYMEMBER_RO") :: + Bulk(k) :: + Bulk(m) :: + Bulk(r) :: + Bulk(u) :: + rm.params.map(Bulk(_)) + ) + ) + assertEquals(protocol.decode(listToArr(res)), res) + } + } + + property("The Geo protocol using ro.georadius roundtrips successfully given key, member, radius, unit, limit and radius mode") { + forAll { (k: Key, m: Key, r: NonNegDouble, u: GeoUnit, l: PosInt, rmAndRes: (GeoRadiusMode, List[_])) => + val (rm, res) = rmAndRes + implicit val ev = rm.r + val protocol = ro.georadius(k, m, r, u, l, rm) + assertEquals( + protocol.encode, + Arr( + Bulk("GEORADIUSBYMEMBER_RO") :: + Bulk(k) :: + Bulk(m) :: + Bulk(r) :: + Bulk(u) :: + Bulk("COUNT") :: + Bulk(l) :: + rm.params.map(Bulk(_)) + ) + ) + assertEquals(protocol.decode(listToArr(res)), res) + } + } + + property("The Geo protocol using ro.georadius roundtrips successfully given key, member, radius, unit, direction and radius mode") { + forAll { (k: Key, m: Key, r: NonNegDouble, u: GeoUnit, d: Direction, rmAndRes: (GeoRadiusMode, List[_])) => + val (rm, res) = rmAndRes + implicit val ev = rm.r + val protocol = ro.georadius(k, m, r, u, d, rm) + assertEquals( + protocol.encode, + Arr( + Bulk("GEORADIUSBYMEMBER_RO") :: + Bulk(k) :: + Bulk(m) :: + Bulk(r) :: + Bulk(u) :: + Bulk(d) :: + rm.params.map(Bulk(_)) + ) + ) + assertEquals(protocol.decode(listToArr(res)), res) + } + } + + property( + "The Geo protocol using ro.georadius roundtrips successfully given key, member, radius, unit, limit, direction and radius mode" + ) { + forAll { (input: (Key, Key, NonNegDouble, GeoUnit, PosInt, Direction), rmAndRes: (GeoRadiusMode, List[_])) => + val (k, m, r, u, l, d) = input + val (rm, res) = rmAndRes + implicit val ev = rm.r + val protocol = ro.georadius(k, m, r, u, l, d, rm) + assertEquals( + protocol.encode, + Arr( + Bulk("GEORADIUSBYMEMBER_RO") :: + Bulk(k) :: + Bulk(m) :: + Bulk(r) :: + Bulk(u) :: + Bulk("COUNT") :: + Bulk(l) :: + Bulk(d) :: + rm.params.map(Bulk(_)) + ) + ) + assertEquals(protocol.decode(listToArr(res)), res) + } + } } diff --git a/core/src/test/scala/laserdisc/protocol/HashPSpec.scala b/core/src/test/scala/laserdisc/protocol/HashPSpec.scala index aa2d7eb3..166b7c37 100644 --- a/core/src/test/scala/laserdisc/protocol/HashPSpec.scala +++ b/core/src/test/scala/laserdisc/protocol/HashPSpec.scala @@ -1,7 +1,9 @@ package laserdisc package protocol -final class HashPSpec extends HashExtPSpec { +import org.scalacheck.Prop.forAll + +abstract class HashPSpec extends BaseSpec with HashP { import shapeless._ private[this] final val scanKVToArr: ScanKV => Arr = scanKV => @@ -10,250 +12,254 @@ final class HashPSpec extends HashExtPSpec { scanKV.maybeValues.fold(NilArr: GenArr)(kvs => Arr(kvs.flatMap { case KV(k, v) => List(Bulk(k.value), Bulk(v)) }.toList)) ) - "The Hash protocol" when { - "using hdel" should { - "roundtrip successfully" when { - "given key and fields" in forAll { (k: Key, fs: OneOrMoreKeys, nni: NonNegInt) => - val protocol = hdel(k, fs) - - protocol.encode shouldBe Arr(Bulk("HDEL") :: Bulk(k) :: fs.value.map(Bulk(_))) - protocol.decode(Num(nni.value.toLong)) onRight (_ shouldBe nni) - } - } + property("The Hash protocol using hdel roundtrips successfully given key and fields") { + forAll { (k: Key, fs: OneOrMoreKeys, nni: NonNegInt) => + val protocol = hdel(k, fs) + assertEquals(protocol.encode, Arr(Bulk("HDEL") :: Bulk(k) :: fs.value.map(Bulk(_)))) + assertEquals(protocol.decode(Num(nni.value.toLong)), nni) } + } - "using hexists" should { - "roundtrip successfully" when { - "given key and field" in forAll { (k: Key, f: Key, b: Boolean) => - val protocol = hexists(k, f) - - protocol.encode shouldBe Arr(Bulk("HEXISTS"), Bulk(k), Bulk(f)) - protocol.decode(boolToNum(b)) onRight (_ shouldBe b) - } - } + property("The Hash protocol using hexists roundtrips successfully given key and field") { + forAll { (k: Key, f: Key, b: Boolean) => + val protocol = hexists(k, f) + assertEquals(protocol.encode, Arr(Bulk("HEXISTS"), Bulk(k), Bulk(f))) + assertEquals(protocol.decode(boolToNum(b)), b) } + } - "using hget" should { - "fail to compile" when { - "given key and field but missing read instance" in { - """hget[Bar](Key("a"), Key("f"))""" shouldNot compile - } - } - - "roundtrip successfully" when { - "given key and field" in forAll("key", "field", "returned value") { (k: Key, f: Key, s: String) => - val protocol = hget(k, f) - - protocol.encode shouldBe Arr(Bulk("HGET"), Bulk(k), Bulk(f)) - protocol.decode(Bulk(s)) onRight (_ shouldBe Some(Bulk(s))) - } - "given key, field and specific read instance" in forAll("key", "field", "returned value") { (k: Key, f: Key, i: Int) => - val protocol = hget[Foo](k, f) + test("The Hash protocol using hget fails to compile given key and field but missing read instance") { + assertNoDiff( + compileErrors("""hget[Bar](Key("a"), Key("f"))"""), + """|error: + |Implicit not found Read[laserdisc.protocol.Bulk, laserdisc.Bar]. + | + |Try writing your own, for example: + | + |implicit final val myRead: Read[laserdisc.protocol.Bulk, laserdisc.Bar] = new Read[laserdisc.protocol.Bulk, laserdisc.Bar] { + | override final def read(a: laserdisc.protocol.Bulk): Option[laserdisc.Bar] = ??? + |} + | + |Note 1: you can use the factory method Read.instance instead of creating it manually as shown above + |Note 2: make sure to inspect the combinators as you may be able to leverage some other Read instance + | + |hget[Bar](Key("a"), Key("f")) + | ^ + |""".stripMargin + ) + } - protocol.encode shouldBe Arr(Bulk("HGET"), Bulk(k), Bulk(f)) - protocol.decode(Bulk(i)) onRight (_ shouldBe Some(Foo(i))) - } - } + property("The Hash protocol using hget roundtrips successfully given key and field") { + forAll { (k: Key, f: Key, s: String) => + val protocol = hget(k, f) + assertEquals(protocol.encode, Arr(Bulk("HGET"), Bulk(k), Bulk(f))) + assertEquals(protocol.decode(Bulk(s)), Some(Bulk(s))) } + } - "using hgetall" should { - "fail to compile" when { - "given key but missing read instance" in { - """hgetall[Map[String, Int]](Key("a"))""" shouldNot compile - } - } - - "roundtrip successfully" when { - "given key" in forAll("key", "returned field", "returned value") { (k: Key, f: Key, v: String) => - val protocol = hgetall(k) - - protocol.encode shouldBe Arr(Bulk("HGETALL"), Bulk(k)) - protocol.decode(Arr(Bulk(f), Bulk(v))) onRight (_ shouldBe Arr(Bulk(f), Bulk(v))) - } - "given key and specific read instance (Map[Key, String])" in { - forAll("key", "returned field", "returned value") { (k: Key, f: Key, v: String) => - val protocol = hgetall[Map[Key, String]](k) - - protocol.encode shouldBe Arr(Bulk("HGETALL"), Bulk(k)) - protocol.decode(Arr(Bulk(f), Bulk(v))) onRight (_ shouldBe Map(f -> v)) - } - } - "given key and specific read instance (Key :: String :: HNil)" in { - forAll("key", "returned field", "returned value") { (k: Key, f: Key, v: String) => - val protocol = hgetall[Key :: String :: HNil](k) - - protocol.encode shouldBe Arr(Bulk("HGETALL"), Bulk(k)) - protocol.decode(Arr(Bulk(f), Bulk(v))) onRight (_ shouldBe f :: v :: HNil) - } - } - } + property("The Hash protocol using hget roundtrips successfully given key, field and specific read instance") { + forAll { (k: Key, f: Key, i: Int) => + val protocol = hget[Foo](k, f) + assertEquals(protocol.encode, Arr(Bulk("HGET"), Bulk(k), Bulk(f))) + assertEquals(protocol.decode(Bulk(i)), Some(Foo(i))) } + } - "using hincrby" should { - "roundtrip successfully" when { - "given key, field and long increment" in { - forAll("key", "field", "increment", "incremented value") { (k: Key, f: Key, nzl: NonZeroLong, l: Long) => - val protocol = hincrby(k, f, nzl) - - protocol.encode shouldBe Arr(Bulk("HINCRBY"), Bulk(k), Bulk(f), Bulk(nzl)) - protocol.decode(Num(l)) onRight (_ shouldBe l) - } - } - "given key, field and double increment" in { - forAll("key", "field", "increment", "incremented value") { (k: Key, f: Key, nzd: NonZeroDouble, d: Double) => - val protocol = hincrby(k, f, nzd) + test("The Hash protocol using hgetall fails to compile given key but missing read instance") { + assertNoDiff( + compileErrors("""hgetall[Map[String, Int]](Key("a"))"""), + """|error: + |Implicit not found Read[laserdisc.protocol.Arr, Map[String,Int]]. + | + |Try writing your own, for example: + | + |implicit final val myRead: Read[laserdisc.protocol.Arr, Map[String,Int]] = new Read[laserdisc.protocol.Arr, Map[String,Int]] { + | override final def read(a: laserdisc.protocol.Arr): Option[Map[String,Int]] = ??? + |} + | + |Note 1: you can use the factory method Read.instance instead of creating it manually as shown above + |Note 2: make sure to inspect the combinators as you may be able to leverage some other Read instance + | + |hgetall[Map[String, Int]](Key("a")) + | ^ + |""".stripMargin + ) + } - protocol.encode shouldBe Arr(Bulk("HINCRBYFLOAT"), Bulk(k), Bulk(f), Bulk(nzd)) - protocol.decode(Bulk(d)) onRight (_ shouldBe d) - } - } - } + property("The Hash protocol using hgetall roundtrips successfully given key") { + forAll { (k: Key, f: Key, v: String) => + val protocol = hgetall(k) + assertEquals(protocol.encode, Arr(Bulk("HGETALL"), Bulk(k))) + assertEquals(protocol.decode(Arr(Bulk(f), Bulk(v))), Arr(Bulk(f), Bulk(v))) } + } - "using hkeys" should { - "roundtrip successfully" when { - "given key" in forAll("key", "returned keys") { (k: Key, ks: List[Key]) => - val protocol = hkeys(k) - - protocol.encode shouldBe Arr(Bulk("HKEYS"), Bulk(k)) - protocol.decode(Arr(ks.map(Bulk(_)))) onRight (_ shouldBe ks) - } - } + property("The Hash protocol using hgetall roundtrips successfully given key and specific read instance (Map[Key, String])") { + forAll { (k: Key, f: Key, v: String) => + val protocol = hgetall[Map[Key, String]](k) + assertEquals(protocol.encode, Arr(Bulk("HGETALL"), Bulk(k))) + assertEquals(protocol.decode(Arr(Bulk(f), Bulk(v))), Map(f -> v)) } + } - "using hlen" should { - "roundtrip successfully" when { - "given key" in forAll("key", "length") { (k: Key, nni: NonNegInt) => - val protocol = hlen(k) - - protocol.encode shouldBe Arr(Bulk("HLEN"), Bulk(k)) - protocol.decode(Num(nni.value.toLong)) onRight (_ shouldBe nni) - } - } + property("The Hash protocol using hgetall roundtrips successfully given key and specific read instance (Key :: String :: HNil)") { + forAll { (k: Key, f: Key, v: String) => + val protocol = hgetall[Key :: String :: HNil](k) + assertEquals(protocol.encode, Arr(Bulk("HGETALL"), Bulk(k))) + assertEquals(protocol.decode(Arr(Bulk(f), Bulk(v))), f :: v :: HNil) } + } - "using hmget" should { - "roundtrip successfully" when { - "given key and one field" in forAll { (k: Key, f: Key, i: Int) => - val protocol = hmget[Int](k, f) - - protocol.encode shouldBe Arr(Bulk("HMGET"), Bulk(k), Bulk(f)) - protocol.decode(Arr(Bulk(i))) onRight (_ shouldBe i) - } - "given key and two fields" in forAll { (k: Key, f1: Key, f2: Key, i: Int, s: String) => - val protocol = hmget[Int, String](k, f1, f2) - - protocol.encode shouldBe Arr(Bulk("HMGET"), Bulk(k), Bulk(f1), Bulk(f2)) - protocol.decode(Arr(Bulk(i), Bulk(s))) onRight (_ shouldBe (i -> s)) - } - } - - "using hmset" should { - "fail to compile" when { - "given key and HNil" in { - """hmset(Key("a"), HNil)""" shouldNot compile - } - } + property("The Hash protocol using hincrby roundtrips successfully given key, field and long increment") { + forAll { (k: Key, f: Key, nzl: NonZeroLong, l: Long) => + val protocol = hincrby(k, f, nzl) + assertEquals(protocol.encode, Arr(Bulk("HINCRBY"), Bulk(k), Bulk(f), Bulk(nzl))) + assertEquals(protocol.decode(Num(l)), l) + } + } - "roundtrip successfully" when { - "given key and HList of (Key, A) pairs" in forAll { (k: Key, f1: Key, i: Int, f2: Key, s: String) => - val protocol = hmset(k, (f1 -> i) :: (f2 -> s) :: HNil) + property("The Hash protocol using hincrby roundtrips successfully given key, field and double increment") { + forAll { (k: Key, f: Key, nzd: NonZeroDouble, d: Double) => + val protocol = hincrby(k, f, nzd) + assertEquals(protocol.encode, Arr(Bulk("HINCRBYFLOAT"), Bulk(k), Bulk(f), Bulk(nzd))) + assertEquals(protocol.decode(Bulk(d)), d) + } + } - protocol.encode shouldBe Arr(Bulk("HMSET"), Bulk(k), Bulk(f1), Bulk(i), Bulk(f2), Bulk(s)) - protocol.decode(Str(OK.value)) onRight (_ shouldBe OK) - } - "given key and a Product (Baz)" in forAll { (k: Key, baz: Baz) => - val protocol = hmset(k, baz) + property("The Hash protocol using hkeys roundtrips successfully given key") { + forAll { (k: Key, ks: List[Key]) => + val protocol = hkeys(k) + assertEquals(protocol.encode, Arr(Bulk("HKEYS"), Bulk(k))) + assertEquals(protocol.decode(Arr(ks.map(Bulk(_)))), ks) + } + } - protocol.encode shouldBe Arr(Bulk("HMSET"), Bulk(k), Bulk("f1"), Bulk(baz.f1), Bulk("f2"), Bulk(baz.f2)) - protocol.decode(Str(OK.value)) onRight (_ shouldBe OK) - } - } - } + property("The Hash protocol using hlen roundtrips successfully given key") { + forAll { (k: Key, nni: NonNegInt) => + val protocol = hlen(k) + assertEquals(protocol.encode, Arr(Bulk("HLEN"), Bulk(k))) + assertEquals(protocol.decode(Num(nni.value.toLong)), nni) } + } - "using hscan" should { - "roundtrip successfully" when { - "given key and cursor" in forAll("key", "cursor", "scan result") { (k: Key, nnl: NonNegLong, skv: ScanKV) => - val protocol = hscan(k, nnl) + property("The Hash protocol using hmget roundtrips successfully given key and one field") { + forAll { (k: Key, f: Key, i: Int) => + val protocol = hmget[Int](k, f) + assertEquals(protocol.encode, Arr(Bulk("HMGET"), Bulk(k), Bulk(f))) + assertEquals(protocol.decode(Arr(Bulk(i))), i) + } + } - protocol.encode shouldBe Arr(Bulk("HSCAN"), Bulk(k), Bulk(nnl)) - protocol.decode(scanKVToArr(skv)) onRight (_ shouldBe skv) - } - "given key, cursor and glob pattern" in { - forAll("key", "cursor", "glob pattern", "scan result") { (k: Key, nnl: NonNegLong, g: GlobPattern, skv: ScanKV) => - val protocol = hscan(k, nnl, g) + property("The Hash protocol using hmget roundtrips successfully given key and two fields") { + forAll { (k: Key, f1: Key, f2: Key, i: Int, s: String) => + val protocol = hmget[Int, String](k, f1, f2) + assertEquals(protocol.encode, Arr(Bulk("HMGET"), Bulk(k), Bulk(f1), Bulk(f2))) + assertEquals(protocol.decode(Arr(Bulk(i), Bulk(s))), i -> s) + } + } - protocol.encode shouldBe Arr(Bulk("HSCAN"), Bulk(k), Bulk(nnl), Bulk("MATCH"), Bulk(g)) - protocol.decode(scanKVToArr(skv)) onRight (_ shouldBe skv) - } - } - "given key, cursor and count" in { - forAll("key", "cursor", "count", "scan result") { (k: Key, nnl: NonNegLong, pi: PosInt, skv: ScanKV) => - val protocol = hscan(k, nnl, pi) + test("The Hash protocol using hmset fails to compile given key and HNil") { + assertNoDiff( + compileErrors("""hmset(Key("a"), HNil)"""), + """|error: + |Implicit not found RESPParamWrite[shapeless.HNil.type]. + | + |Normally you would not need to define one manually, as one will be derived for you automatically iff: + |- an instance of Show[shapeless.HNil.type] is in scope + |- shapeless.HNil.type is a List whose LUB has a RESPParamWrite instance defined + |- shapeless.HNil.type is an HList whose elements all have a RESPParamWrite instance defined + | + |hmset(Key("a"), HNil) + | ^ + |""".stripMargin + ) + } - protocol.encode shouldBe Arr(Bulk("HSCAN"), Bulk(k), Bulk(nnl), Bulk("COUNT"), Bulk(pi)) - protocol.decode(scanKVToArr(skv)) onRight (_ shouldBe skv) - } - } - "given key, cursor, glob pattern and count" in forAll("key", "cursor", "glob pattern", "count", "scan result") { - (k: Key, nnl: NonNegLong, g: GlobPattern, pi: PosInt, skv: ScanKV) => - val protocol = hscan(k, nnl, g, pi) + property("The Hash protocol using hmset roundtrips successfully given key and HList of (Key, A) pairs") { + forAll { (k: Key, f1: Key, i: Int, f2: Key, s: String) => + val protocol = hmset(k, (f1 -> i) :: (f2 -> s) :: HNil) + assertEquals(protocol.encode, Arr(Bulk("HMSET"), Bulk(k), Bulk(f1), Bulk(i), Bulk(f2), Bulk(s))) + assertEquals(protocol.decode(Str(OK.value)), OK) + } + } - protocol.encode shouldBe Arr(Bulk("HSCAN"), Bulk(k), Bulk(nnl), Bulk("MATCH"), Bulk(g), Bulk("COUNT"), Bulk(pi)) - protocol.decode(scanKVToArr(skv)) onRight (_ shouldBe skv) - } - } + property("The Hash protocol using hmset roundtrips successfully given key and a Product (Baz)") { + forAll { (k: Key, baz: Baz) => + val protocol = hmset(k, baz) + assertEquals(protocol.encode, Arr(Bulk("HMSET"), Bulk(k), Bulk("f1"), Bulk(baz.f1), Bulk("f2"), Bulk(baz.f2))) + assertEquals(protocol.decode(Str(OK.value)), OK) } + } - "using hset" should { - "roundtrip successfully" when { - "given key, field and value" in forAll("key", "field", "value", "success") { (k: Key, f: Key, v: Int, b: Boolean) => - val protocol = hset(k, f, v) + property("The Hash protocol using hscan roundtrips successfully given key and cursor") { + forAll { (k: Key, nnl: NonNegLong, skv: ScanKV) => + val protocol = hscan(k, nnl) + assertEquals(protocol.encode, Arr(Bulk("HSCAN"), Bulk(k), Bulk(nnl))) + assertEquals(protocol.decode(scanKVToArr(skv)), skv) + } + } - protocol.encode shouldBe Arr(Bulk("HSET"), Bulk(k), Bulk(f), Bulk(v)) - protocol.decode(boolToNum(b)) onRight (_ shouldBe b) - } - } + property("The Hash protocol using hscan roundtrips successfully given key, cursor and glob pattern") { + forAll { (k: Key, nnl: NonNegLong, g: GlobPattern, skv: ScanKV) => + val protocol = hscan(k, nnl, g) + assertEquals(protocol.encode, Arr(Bulk("HSCAN"), Bulk(k), Bulk(nnl), Bulk("MATCH"), Bulk(g))) + assertEquals(protocol.decode(scanKVToArr(skv)), skv) } + } - "using hsetnx" should { - "roundtrip successfully" when { - "given key, field and value" in forAll("key", "field", "value", "success") { (k: Key, f: Key, v: Int, b: Boolean) => - val protocol = hsetnx(k, f, v) + property("The Hash protocol using hscan roundtrips successfully given key, cursor and count") { + forAll { (k: Key, nnl: NonNegLong, pi: PosInt, skv: ScanKV) => + val protocol = hscan(k, nnl, pi) + assertEquals(protocol.encode, Arr(Bulk("HSCAN"), Bulk(k), Bulk(nnl), Bulk("COUNT"), Bulk(pi))) + assertEquals(protocol.decode(scanKVToArr(skv)), skv) + } + } - protocol.encode shouldBe Arr(Bulk("HSETNX"), Bulk(k), Bulk(f), Bulk(v)) - protocol.decode(boolToNum(b)) onRight (_ shouldBe b) - } - } + property("The Hash protocol using hscan roundtrips successfully given key, cursor, glob pattern and count") { + forAll { (k: Key, nnl: NonNegLong, g: GlobPattern, pi: PosInt, skv: ScanKV) => + val protocol = hscan(k, nnl, g, pi) + assertEquals(protocol.encode, Arr(Bulk("HSCAN"), Bulk(k), Bulk(nnl), Bulk("MATCH"), Bulk(g), Bulk("COUNT"), Bulk(pi))) + assertEquals(protocol.decode(scanKVToArr(skv)), skv) } + } - "using hstrlen" should { - "roundtrip successfully" when { - "given key and field" in forAll("key", "field", "length") { (k: Key, f: Key, nni: NonNegInt) => - val protocol = hstrlen(k, f) + property("The Hash protocol using hset roundtrips successfully given key, field and value") { + forAll { (k: Key, f: Key, v: Int, b: Boolean) => + val protocol = hset(k, f, v) + assertEquals(protocol.encode, Arr(Bulk("HSET"), Bulk(k), Bulk(f), Bulk(v))) + assertEquals(protocol.decode(boolToNum(b)), b) + } + } - protocol.encode shouldBe Arr(Bulk("HSTRLEN"), Bulk(k), Bulk(f)) - protocol.decode(Num(nni.value.toLong)) onRight (_ shouldBe nni) - } - } + property("The Hash protocol using hsetnx roundtrips successfully given key, field and value") { + forAll { (k: Key, f: Key, v: Int, b: Boolean) => + val protocol = hsetnx(k, f, v) + assertEquals(protocol.encode, Arr(Bulk("HSETNX"), Bulk(k), Bulk(f), Bulk(v))) + assertEquals(protocol.decode(boolToNum(b)), b) } + } - "using hvals" should { - "roundtrip successfully" when { - "given key (expecting one field)" in forAll { (k: Key, i: Int) => - val protocol = hvals[Int :: HNil](k) + property("The Hash protocol using hstrlen roundtrips successfully given key and field") { + forAll { (k: Key, f: Key, nni: NonNegInt) => + val protocol = hstrlen(k, f) + assertEquals(protocol.encode, Arr(Bulk("HSTRLEN"), Bulk(k), Bulk(f))) + assertEquals(protocol.decode(Num(nni.value.toLong)), nni) + } + } - protocol.encode shouldBe Arr(Bulk("HVALS"), Bulk(k)) - protocol.decode(Arr(Bulk(i))) onRight (_ shouldBe (i :: HNil)) - } - "given key (expecting two fields)" in forAll { (k: Key, i: Int, s: String) => - val protocol = hvals[Int :: String :: HNil](k) + property("The Hash protocol using hvals roundtrips successfully given key (expecting one field)") { + forAll { (k: Key, i: Int) => + val protocol = hvals[Int :: HNil](k) + assertEquals(protocol.encode, Arr(Bulk("HVALS"), Bulk(k))) + assertEquals(protocol.decode(Arr(Bulk(i))), i :: HNil) + } + } - protocol.encode shouldBe Arr(Bulk("HVALS"), Bulk(k)) - protocol.decode(Arr(Bulk(i), Bulk(s))) onRight (_ shouldBe (i :: s :: HNil)) - } - } + property("The Hash protocol using hvals roundtrips successfully given key (expecting two fields)") { + forAll { (k: Key, i: Int, s: String) => + val protocol = hvals[Int :: String :: HNil](k) + assertEquals(protocol.encode, Arr(Bulk("HVALS"), Bulk(k))) + assertEquals(protocol.decode(Arr(Bulk(i), Bulk(s))), i :: s :: HNil) } } } diff --git a/core/src/test/scala/laserdisc/protocol/HyperLogLogPSpec.scala b/core/src/test/scala/laserdisc/protocol/HyperLogLogPSpec.scala index f3b2507d..f3d7677e 100644 --- a/core/src/test/scala/laserdisc/protocol/HyperLogLogPSpec.scala +++ b/core/src/test/scala/laserdisc/protocol/HyperLogLogPSpec.scala @@ -1,39 +1,31 @@ package laserdisc package protocol -final class HyperLogLogPSpec extends HyperLogLogExtPSpec { - "The HyperLogLog protocol" when { - "using pfadd" should { - "roundtrip successfully" when { - "given key and elements" in forAll("key", "elements", "added") { (k: Key, es: OneOrMoreKeys, b: Boolean) => - val protocol = pfadd(k, es) +import org.scalacheck.Prop.forAll - protocol.encode shouldBe Arr(Bulk("PFADD") :: Bulk(k) :: es.value.map(Bulk(_))) - protocol.decode(boolToNum(b)) onRight (_ shouldBe b) - } - } - } - - "using pfcount" should { - "roundtrip successfully" when { - "given keys" in forAll("keys", "count") { (ks: OneOrMoreKeys, nni: NonNegInt) => - val protocol = pfcount(ks) +abstract class HyperLogLogPSpec extends BaseSpec with HyperLogLogP { - protocol.encode shouldBe Arr(Bulk("PFCOUNT") :: ks.value.map(Bulk(_))) - protocol.decode(Num(nni.value.toLong)) onRight (_ shouldBe nni) - } - } + property("The HyperLogLog protocol using pfadd roundtrips successfully given key and elements") { + forAll { (k: Key, es: OneOrMoreKeys, b: Boolean) => + val protocol = pfadd(k, es) + assertEquals(protocol.encode, Arr(Bulk("PFADD") :: Bulk(k) :: es.value.map(Bulk(_)))) + assertEquals(protocol.decode(boolToNum(b)), b) } + } - "using pfmerge" should { - "roundtrip successfully" when { - "given two or more source keys and a destination key" in forAll("source keys", "destination key") { (sks: TwoOrMoreKeys, dk: Key) => - val protocol = pfmerge(sks, dk) + property("The HyperLogLog protocol using pfcount roundtrips successfully given keys") { + forAll { (ks: OneOrMoreKeys, nni: NonNegInt) => + val protocol = pfcount(ks) + assertEquals(protocol.encode, Arr(Bulk("PFCOUNT") :: ks.value.map(Bulk(_)))) + assertEquals(protocol.decode(Num(nni.value.toLong)), nni) + } + } - protocol.encode shouldBe Arr(Bulk("PFMERGE") :: Bulk(dk) :: sks.value.map(Bulk(_))) - protocol.decode(Str(OK.value)) onRight (_ shouldBe OK) - } - } + property("The HyperLogLog protocol using pfmerge roundtrips successfully given two or more source keys and a destination key") { + forAll { (sks: TwoOrMoreKeys, dk: Key) => + val protocol = pfmerge(sks, dk) + assertEquals(protocol.encode, Arr(Bulk("PFMERGE") :: Bulk(dk) :: sks.value.map(Bulk(_)))) + assertEquals(protocol.decode(Str(OK.value)), OK) } } } diff --git a/core/src/test/scala/laserdisc/protocol/KeyPSpec.scala b/core/src/test/scala/laserdisc/protocol/KeyPSpec.scala index dc764f04..59898648 100644 --- a/core/src/test/scala/laserdisc/protocol/KeyPSpec.scala +++ b/core/src/test/scala/laserdisc/protocol/KeyPSpec.scala @@ -1,7 +1,9 @@ package laserdisc package protocol -final class KeyPSpec extends KeyExtPSpec { +import org.scalacheck.Prop.forAll + +abstract class KeyPSpec extends BaseSpec with KeyP { import keytypes._ import org.scalacheck.{Arbitrary, Gen} import org.scalacheck.Gen.const @@ -47,382 +49,326 @@ final class KeyPSpec extends KeyExtPSpec { scanKey.values.fold(NilArr: GenArr)(ks => Arr(ks.map(k => Bulk(k.value)).toList)) ) - "The Key protocol" when { - "using del" should { - "roundtrip successfully" when { - "given keys" in forAll("keys", "deleted") { (ks: OneOrMoreKeys, nni: NonNegInt) => - val protocol = del(ks) - - protocol.encode shouldBe Arr(Bulk("DEL") :: ks.value.map(Bulk(_))) - protocol.decode(Num(nni.value.toLong)) onRight (_ shouldBe nni) - } - } - } - - "using dump" should { - "roundtrip successfully" when { - "given a key" in forAll("key", "dump") { (k: Key, os: Option[String]) => - val protocol = dump(k) - - protocol.encode shouldBe Arr(Bulk("DUMP"), Bulk(k)) - protocol.decode(os.fold(NullBulk: GenBulk)(Bulk(_))) onRight (_ shouldBe os.map(Bulk(_))) - } - } - } - - "using exists" should { - "roundtrip successfully" when { - "given keys" in forAll("keys", "exists") { (ks: OneOrMoreKeys, opi: Option[PosInt]) => - val protocol = exists(ks) - - protocol.encode shouldBe Arr(Bulk("EXISTS") :: ks.value.map(Bulk(_))) - protocol.decode(Num(opi.fold(0L)(_.value.toLong))) onRight (_ shouldBe opi) - } - } - } - - "using expire" should { - "roundtrip successfully" when { - "given key and expiration" in forAll("key", "expiration", "expired") { (k: Key, nni: NonNegInt, b: Boolean) => - val protocol = expire(k, nni) - - protocol.encode shouldBe Arr(Bulk("EXPIRE"), Bulk(k), Bulk(nni)) - protocol.decode(boolToNum(b)) onRight (_ shouldBe b) - } - } - } - - "using expireat" should { - "roundtrip successfully" when { - "given key and expire timestamp" in forAll("key", "timestamp", "expired") { (k: Key, nni: NonNegInt, b: Boolean) => - val protocol = expireat(k, nni) - - protocol.encode shouldBe Arr(Bulk("EXPIREAT"), Bulk(k), Bulk(nni)) - protocol.decode(boolToNum(b)) onRight (_ shouldBe b) - } - } - } - - "using keys" should { - "roundtrip successfully" when { - "given glob pattern" in forAll("glob pattern", "keys") { (g: GlobPattern, ks: List[Key]) => - val protocol = keys(g) - - protocol.encode shouldBe Arr(Bulk("KEYS"), Bulk(g)) - protocol.decode(Arr(ks.map(Bulk(_)))) onRight (_ shouldBe ks) - } - } - } - - "using migrate" should { - "roundtrip successfully" when { - "given key, host, port, db index and timeout" in forAll("key", "host", "port", "db index", "timeout", "response") { - (k: Key, h: Host, p: Port, dbi: DbIndex, nni: NonNegInt, nkOrOk: NOKEY | OK) => - val protocol = migrate(k, h, p, dbi, nni) - - protocol.encode shouldBe Arr(Bulk("MIGRATE"), Bulk(h), Bulk(p), Bulk(k), Bulk(dbi), Bulk(nni)) - protocol.decode(noKeyOrOkToStr(nkOrOk)) onRight (_ shouldBe nkOrOk) - } - "given keys, host, port, db index and timeout" in forAll("keys", "host", "port", "db index", "timeout", "response") { - (ks: TwoOrMoreKeys, h: Host, p: Port, dbi: DbIndex, nni: NonNegInt, nkOrOk: NOKEY | OK) => - val protocol = migrate(ks, h, p, dbi, nni) - - protocol.encode shouldBe Arr( - Bulk("MIGRATE") :: Bulk(h) :: Bulk(p) :: Bulk("") :: Bulk(dbi) :: Bulk(nni) :: Bulk("KEYS") :: ks.value.map(Bulk(_)) - ) - protocol.decode(noKeyOrOkToStr(nkOrOk)) onRight (_ shouldBe nkOrOk) - } - "given key, host, port, db index, timeout and migrate mode" in { - forAll("key, host, port, db index, timeout, response", "migrate mode") { - (input: (Key, Host, Port, DbIndex, NonNegInt, NOKEY | OK), mm: KeyMigrateMode) => - val (k, h, p, dbi, nni, nkOrOk) = input - val protocol = migrate(k, h, p, dbi, nni, mm) - - protocol.encode shouldBe Arr( - Bulk("MIGRATE") :: Bulk(h) :: Bulk(p) :: Bulk(k) :: Bulk(dbi) :: Bulk(nni) :: mm.params.map(Bulk(_)) - ) - protocol.decode(noKeyOrOkToStr(nkOrOk)) onRight (_ shouldBe nkOrOk) - } - } - "given keys, host, port, db index, timeout and migrate mode" in { - forAll("keys, host, port, db index, timeout, response", "migrate mode") { - (input: (TwoOrMoreKeys, Host, Port, DbIndex, NonNegInt, NOKEY | OK), mm: KeyMigrateMode) => - val (ks, h, p, dbi, nni, nkOrOk) = input - val protocol = migrate(ks, h, p, dbi, nni, mm) - - protocol.encode shouldBe Arr( - Bulk("MIGRATE") :: Bulk(h) :: Bulk(p) :: Bulk("") :: Bulk(dbi) :: Bulk(nni) :: - mm.params.map(Bulk(_)) ::: (Bulk("KEYS") :: ks.value.map(Bulk(_))) - ) - protocol.decode(noKeyOrOkToStr(nkOrOk)) onRight (_ shouldBe nkOrOk) - } - } - } - } - - "using move" should { - "roundtrip successfully" when { - "given key and db" in forAll("key", "db", "moved") { (k: Key, db: DbIndex, b: Boolean) => - val protocol = move(k, db) - - protocol.encode shouldBe Arr(Bulk("MOVE"), Bulk(k), Bulk(db)) - protocol.decode(boolToNum(b)) onRight (_ shouldBe b) - } - } - } - - "using obj.encoding" should { - "roundtrip successfully" when { - "given key" in forAll("key", "encoding") { (k: Key, oe: Option[KeyEncoding]) => - val protocol = obj.encoding(k) - - protocol.encode shouldBe Arr(Bulk("OBJECT"), Bulk("ENCODING"), Bulk(k)) - protocol.decode(oe.fold(NullBulk: GenBulk)(Bulk(_))) onRight (_ shouldBe oe) - } - } - } - - "using obj.freq" should { - "roundtrip successfully" when { - "given key" in forAll("key", "frequency") { (k: Key, nni: NonNegInt) => - val protocol = obj.freq(k) - - protocol.encode shouldBe Arr(Bulk("OBJECT"), Bulk("FREQ"), Bulk(k)) - protocol.decode(Num(nni.value.toLong)) onRight (_ shouldBe nni) - } - } - } - - "using obj.idletime" should { - "roundtrip successfully" when { - "given key" in forAll("key", "idle time") { (k: Key, nni: NonNegInt) => - val protocol = obj.idletime(k) - - protocol.encode shouldBe Arr(Bulk("OBJECT"), Bulk("IDLETIME"), Bulk(k)) - protocol.decode(Num(nni.value.toLong)) onRight (_ shouldBe nni) - } - } - } - - "using obj.refcount" should { - "roundtrip successfully" when { - "given key" in forAll("key", "ref count") { (k: Key, nni: NonNegInt) => - val protocol = obj.refcount(k) - - protocol.encode shouldBe Arr(Bulk("OBJECT"), Bulk("REFCOUNT"), Bulk(k)) - protocol.decode(Num(nni.value.toLong)) onRight (_ shouldBe nni) - } - } - } - - "using persist" should { - "roundtrip successfully" when { - "given key" in forAll("key", "persisted") { (k: Key, b: Boolean) => - val protocol = persist(k) - - protocol.encode shouldBe Arr(Bulk("PERSIST"), Bulk(k)) - protocol.decode(boolToNum(b)) onRight (_ shouldBe b) - } - } - } - - "using pexpire" should { - "roundtrip successfully" when { - "given key" in forAll("key", "timeout", "persisted") { (k: Key, nnl: NonNegLong, b: Boolean) => - val protocol = pexpire(k, nnl) - - protocol.encode shouldBe Arr(Bulk("PEXPIRE"), Bulk(k), Bulk(nnl)) - protocol.decode(boolToNum(b)) onRight (_ shouldBe b) - } - } - } - - "using pexpireat" should { - "roundtrip successfully" when { - "given key" in forAll("key", "timestamp", "persisted") { (k: Key, nnl: NonNegLong, b: Boolean) => - val protocol = pexpireat(k, nnl) - - protocol.encode shouldBe Arr(Bulk("PEXPIREAT"), Bulk(k), Bulk(nnl)) - protocol.decode(boolToNum(b)) onRight (_ shouldBe b) - } - } - } - - "using pttl" should { - "roundtrip successfully" when { - "given key" in forAll("key", "ttl") { (k: Key, ttl: KeyTTLResponse) => - val protocol = pttl(k) - - protocol.encode shouldBe Arr(Bulk("PTTL"), Bulk(k)) - protocol.decode(ttlResponseToNum(ttl)) onRight (_ shouldBe ttl) - } - } - } - - "using randomkey" should { - "roundtrip successfully" when { - "using val" in { (ok: Option[Key]) => - val protocol = randomkey - - protocol.encode shouldBe Arr(Bulk("RANDOMKEY")) - protocol.decode(ok.fold(NullBulk: GenBulk)(Bulk(_))) onRight (_ shouldBe ok) - } - } - } - - "using rename" should { - "roundtrip successfully" when { - "given old key and new key" in forAll("old key", "new key") { (ok: Key, nk: Key) => - val protocol = rename(ok, nk) - - protocol.encode shouldBe Arr(Bulk("RENAME"), Bulk(ok), Bulk(nk)) - protocol.decode(Str(OK.value)) onRight (_ shouldBe OK) - } - } - } - - "using renamenx" should { - "roundtrip successfully" when { - "given old key and new key" in forAll("old key", "new key", "renamed") { (ok: Key, nk: Key, b: Boolean) => - val protocol = renamenx(ok, nk) - - protocol.encode shouldBe Arr(Bulk("RENAMENX"), Bulk(ok), Bulk(nk)) - protocol.decode(boolToNum(b)) onRight (_ shouldBe b) - } - } - } - - "using restore" should { - "roundtrip successfully" when { - "given key, ttl and serialized value" in forAll("key", "ttl", "serialized value") { (k: Key, nnl: NonNegLong, s: String) => - val protocol = restore(k, nnl, Bulk(s)) - - protocol.encode shouldBe Arr(Bulk("RESTORE"), Bulk(k), Bulk(nnl), Bulk(s)) - protocol.decode(Str(OK.value)) onRight (_ shouldBe OK) - } - "given key, ttl, serialized value and mode" in { - forAll("key", "ttl", "serialized value", "mode") { (k: Key, nnl: NonNegLong, s: String, m: KeyRestoreMode) => - val protocol = restore(k, nnl, Bulk(s), m) - - protocol.encode shouldBe Arr(Bulk("RESTORE") :: Bulk(k) :: Bulk(nnl) :: Bulk(s) :: m.params.map(Bulk(_))) - protocol.decode(Str(OK.value)) onRight (_ shouldBe OK) - } - } - "given key, ttl, serialized value and eviction" in { - forAll("key", "ttl", "serialized value", "eviction") { (k: Key, nnl: NonNegLong, s: String, e: KeyRestoreEviction) => - val protocol = restore(k, nnl, Bulk(s), e) - - protocol.encode shouldBe Arr(Bulk("RESTORE"), Bulk(k), Bulk(nnl), Bulk(s), Bulk(e.param), Bulk(e.seconds)) - protocol.decode(Str(OK.value)) onRight (_ shouldBe OK) - } - } - "given key, ttl, serialized value, mode and eviction" in forAll("key", "ttl", "serialized value", "mode", "eviction") { - (k: Key, nnl: NonNegLong, s: String, m: KeyRestoreMode, e: KeyRestoreEviction) => - val protocol = restore(k, nnl, Bulk(s), m, e) - - protocol.encode shouldBe Arr( - Bulk("RESTORE") :: Bulk(k) :: Bulk(nnl) :: Bulk(s) :: m.params.map(Bulk(_)) ::: (Bulk(e.param) :: Bulk(e.seconds) :: Nil) - ) - protocol.decode(Str(OK.value)) onRight (_ shouldBe OK) - } - } - } - - "using scan" should { - "roundtrip successfully" when { - "given cursor" in forAll("cursor", "scan result") { (nnl: NonNegLong, sk: Scan[Key]) => - val protocol = scan(nnl) - - protocol.encode shouldBe Arr(Bulk("SCAN"), Bulk(nnl)) - protocol.decode(scanKeyToArr(sk)) onRight (_ shouldBe sk) - } - "given cursor and glob pattern" in { - forAll("cursor", "glob pattern", "scan result") { (nnl: NonNegLong, g: GlobPattern, sk: Scan[Key]) => - val protocol = scan(nnl, g) - - protocol.encode shouldBe Arr(Bulk("SCAN"), Bulk(nnl), Bulk("MATCH"), Bulk(g)) - protocol.decode(scanKeyToArr(sk)) onRight (_ shouldBe sk) - } - } - "given cursor and count" in { - forAll("cursor", "count", "scan result") { (nnl: NonNegLong, pi: PosInt, sk: Scan[Key]) => - val protocol = scan(nnl, pi) - - protocol.encode shouldBe Arr(Bulk("SCAN"), Bulk(nnl), Bulk("COUNT"), Bulk(pi)) - protocol.decode(scanKeyToArr(sk)) onRight (_ shouldBe sk) - } - } - "given cursor, glob pattern and count" in forAll("cursor", "glob pattern", "count", "scan result") { - (nnl: NonNegLong, g: GlobPattern, pi: PosInt, sk: Scan[Key]) => - val protocol = scan(nnl, g, pi) - - protocol.encode shouldBe Arr(Bulk("SCAN"), Bulk(nnl), Bulk("MATCH"), Bulk(g), Bulk("COUNT"), Bulk(pi)) - protocol.decode(scanKeyToArr(sk)) onRight (_ shouldBe sk) - } - } - } - - //FIXME add SORT - - "using touch" should { - "roundtrip successfully" when { - "given keys" in forAll("keys", "touched") { (ks: OneOrMoreKeys, nni: NonNegInt) => - val protocol = touch(ks) - - protocol.encode shouldBe Arr(Bulk("TOUCH") :: ks.value.map(Bulk(_))) - protocol.decode(Num(nni.value.toLong)) onRight (_ shouldBe nni) - } - } - } - - "using ttl" should { - "roundtrip successfully" when { - "given key" in forAll("key", "ttl") { (k: Key, ttl0: KeyTTLResponse) => - val protocol = ttl(k) - - protocol.encode shouldBe Arr(Bulk("TTL"), Bulk(k)) - protocol.decode(ttlResponseToNum(ttl0)) onRight (_ shouldBe ttl0) - } - } - } - - "using typeof" should { - "roundtrip successfully" when { - "given key" in forAll("key", "type") { (k: Key, ot: Option[KeyType]) => - val protocol = typeof(k) - - protocol.encode shouldBe Arr(Bulk("TYPE"), Bulk(k)) - protocol.decode(ot.fold(Str("none"))(Str(_))) onRight (_ shouldBe ot) - } - } - } - - "using unlink" should { - "roundtrip successfully" when { - "given keys" in forAll("keys", "unlinked") { (ks: OneOrMoreKeys, nni: NonNegInt) => - val protocol = unlink(ks) - - protocol.encode shouldBe Arr(Bulk("UNLINK") :: ks.value.map(Bulk(_))) - protocol.decode(Num(nni.value.toLong)) onRight (_ shouldBe nni) - } - } - } - - "using wait" should { - "roundtrip successfully" when { - "given replicas" in forAll("replicas", "acknowledgements") { (pi1: PosInt, pi2: PosInt) => - val protocol = wait(pi1) - - protocol.encode shouldBe Arr(Bulk("WAIT"), Bulk(pi1), Bulk(0)) - protocol.decode(Num(pi2.value.toLong)) onRight (_ shouldBe pi2) - } - "given replicas and timeout" in forAll("replicas", "timeout", "acknowledgements") { (pi1: PosInt, pl: PosLong, pi2: PosInt) => - val protocol = wait(pi1, pl) - - protocol.encode shouldBe Arr(Bulk("WAIT"), Bulk(pi1), Bulk(pl)) - protocol.decode(Num(pi2.value.toLong)) onRight (_ shouldBe pi2) - } - } + protected final val noKeyOrOkToStr: (NOKEY | OK) => Str = { + case Left(_) => Str(NOKEY.value) + case Right(_) => Str(OK.value) + } + + protected implicit final val keyMigrateModeArb: Arbitrary[KeyMigrateMode] = Arbitrary { + Gen.oneOf(KeyMigrateMode.both, KeyMigrateMode.copy, KeyMigrateMode.replace) + } + protected implicit final val nokeyOrOkArb: Arbitrary[NOKEY | OK] = Arbitrary( + Gen.oneOf(NOKEY, OK).map { + case NOKEY => Left(NOKEY) + case OK => Right(OK) + } + ) + + property("The Key protocol using del roundtrips successfully given keys") { + forAll { (ks: OneOrMoreKeys, nni: NonNegInt) => + val protocol = del(ks) + assertEquals(protocol.encode, Arr(Bulk("DEL") :: ks.value.map(Bulk(_)))) + assertEquals(protocol.decode(Num(nni.value.toLong)), nni) + } + } + + property("The Key protocol using dump roundtrips successfully given a key") { + forAll { (k: Key, os: Option[String]) => + val protocol = dump(k) + assertEquals(protocol.encode, Arr(Bulk("DUMP"), Bulk(k))) + assertEquals(protocol.decode(os.fold(NullBulk: GenBulk)(Bulk(_))), os.map(Bulk(_))) + } + } + + property("The Key protocol using exists roundtrips successfully given keys") { + forAll { (ks: OneOrMoreKeys, opi: Option[PosInt]) => + val protocol = exists(ks) + assertEquals(protocol.encode, Arr(Bulk("EXISTS") :: ks.value.map(Bulk(_)))) + assertEquals(protocol.decode(Num(opi.fold(0L)(_.value.toLong))), opi) + } + } + + property("The Key protocol using expire roundtrips successfully given key and expiration") { + forAll { (k: Key, nni: NonNegInt, b: Boolean) => + val protocol = expire(k, nni) + assertEquals(protocol.encode, Arr(Bulk("EXPIRE"), Bulk(k), Bulk(nni))) + assertEquals(protocol.decode(boolToNum(b)), b) + } + } + + property("The Key protocol using expireat roundtrips successfully given key and expire timestamp") { + forAll { (k: Key, nni: NonNegInt, b: Boolean) => + val protocol = expireat(k, nni) + assertEquals(protocol.encode, Arr(Bulk("EXPIREAT"), Bulk(k), Bulk(nni))) + assertEquals(protocol.decode(boolToNum(b)), b) + } + } + + property("The Key protocol using keys roundtrips successfully given glob pattern") { + forAll { (g: GlobPattern, ks: List[Key]) => + val protocol = keys(g) + assertEquals(protocol.encode, Arr(Bulk("KEYS"), Bulk(g))) + assertEquals(protocol.decode(Arr(ks.map(Bulk(_)))), ks) + } + } + + property("The Key protocol using migrate roundtrips successfully given key, host, port, db index and timeout") { + forAll { (k: Key, h: Host, p: Port, dbi: DbIndex, nni: NonNegInt, nkOrOk: NOKEY | OK) => + val protocol = migrate(k, h, p, dbi, nni) + assertEquals(protocol.encode, Arr(Bulk("MIGRATE"), Bulk(h), Bulk(p), Bulk(k), Bulk(dbi), Bulk(nni))) + assertEquals(protocol.decode(noKeyOrOkToStr(nkOrOk)), nkOrOk) + } + } + + property("The Key protocol using migrate roundtrips successfully given keys, host, port, db index and timeout") { + forAll { (ks: TwoOrMoreKeys, h: Host, p: Port, dbi: DbIndex, nni: NonNegInt, nkOrOk: NOKEY | OK) => + val protocol = migrate(ks, h, p, dbi, nni) + assertEquals( + protocol.encode, + Arr( + Bulk("MIGRATE") :: + Bulk(h) :: + Bulk(p) :: + Bulk("") :: + Bulk(dbi) :: + Bulk(nni) :: + Bulk("KEYS") :: + ks.value.map(Bulk(_)) + ) + ) + assertEquals(protocol.decode(noKeyOrOkToStr(nkOrOk)), nkOrOk) + } + } + + property("The Key protocol using migrate roundtrips successfully given key, host, port, db index, timeout and migrate mode") { + forAll { (input: (Key, Host, Port, DbIndex, NonNegInt, NOKEY | OK), mm: KeyMigrateMode) => + val (k, h, p, dbi, nni, nkOrOk) = input + val protocol = migrate(k, h, p, dbi, nni, mm) + assertEquals( + protocol.encode, + Arr(Bulk("MIGRATE") :: Bulk(h) :: Bulk(p) :: Bulk(k) :: Bulk(dbi) :: Bulk(nni) :: mm.params.map(Bulk(_))) + ) + assertEquals(protocol.decode(noKeyOrOkToStr(nkOrOk)), nkOrOk) + } + } + + property("The Key protocol using migrate roundtrips successfully given keys, host, port, db index, timeout and migrate mode") { + forAll { (input: (TwoOrMoreKeys, Host, Port, DbIndex, NonNegInt, NOKEY | OK), mm: KeyMigrateMode) => + val (ks, h, p, dbi, nni, nkOrOk) = input + val protocol = migrate(ks, h, p, dbi, nni, mm) + assertEquals( + protocol.encode, + Arr( + Bulk("MIGRATE") :: Bulk(h) :: Bulk(p) :: Bulk("") :: Bulk(dbi) :: Bulk(nni) :: mm.params + .map(Bulk(_)) ::: (Bulk("KEYS") :: ks.value.map(Bulk(_))) + ) + ) + assertEquals(protocol.decode(noKeyOrOkToStr(nkOrOk)), nkOrOk) + } + } + + property("The Key protocol using move roundtrips successfully given key and db") { + forAll { (k: Key, db: DbIndex, b: Boolean) => + val protocol = move(k, db) + assertEquals(protocol.encode, Arr(Bulk("MOVE"), Bulk(k), Bulk(db))) + assertEquals(protocol.decode(boolToNum(b)), b) + } + } + + property("The Key protocol using obj.encoding roundtrips successfully given key") { + forAll { (k: Key, oe: Option[KeyEncoding]) => + val protocol = obj.encoding(k) + assertEquals(protocol.encode, Arr(Bulk("OBJECT"), Bulk("ENCODING"), Bulk(k))) + assertEquals(protocol.decode(oe.fold(NullBulk: GenBulk)(Bulk(_))), oe) + } + } + + property("The Key protocol using obj.freq roundtrips successfully given key") { + forAll { (k: Key, nni: NonNegInt) => + val protocol = obj.freq(k) + assertEquals(protocol.encode, Arr(Bulk("OBJECT"), Bulk("FREQ"), Bulk(k))) + assertEquals(protocol.decode(Num(nni.value.toLong)), nni) + } + } + + property("The Key protocol using obj.idletime roundtrips successfully given key") { + forAll { (k: Key, nni: NonNegInt) => + val protocol = obj.idletime(k) + assertEquals(protocol.encode, Arr(Bulk("OBJECT"), Bulk("IDLETIME"), Bulk(k))) + assertEquals(protocol.decode(Num(nni.value.toLong)), nni) + } + } + + property("The Key protocol using obj.refcount roundtrips successfully given key") { + forAll { (k: Key, nni: NonNegInt) => + val protocol = obj.refcount(k) + assertEquals(protocol.encode, Arr(Bulk("OBJECT"), Bulk("REFCOUNT"), Bulk(k))) + assertEquals(protocol.decode(Num(nni.value.toLong)), nni) + } + } + + property("The Key protocol using persist roundtrips successfully given key") { + forAll { (k: Key, b: Boolean) => + val protocol = persist(k) + assertEquals(protocol.encode, Arr(Bulk("PERSIST"), Bulk(k))) + assertEquals(protocol.decode(boolToNum(b)), b) + } + } + + property("The Key protocol using pexpire roundtrips successfully given key") { + forAll { (k: Key, nnl: NonNegLong, b: Boolean) => + val protocol = pexpire(k, nnl) + assertEquals(protocol.encode, Arr(Bulk("PEXPIRE"), Bulk(k), Bulk(nnl))) + assertEquals(protocol.decode(boolToNum(b)), b) + } + } + + property("The Key protocol using pexpireat roundtrips successfully given key") { + forAll { (k: Key, nnl: NonNegLong, b: Boolean) => + val protocol = pexpireat(k, nnl) + assertEquals(protocol.encode, Arr(Bulk("PEXPIREAT"), Bulk(k), Bulk(nnl))) + assertEquals(protocol.decode(boolToNum(b)), b) + } + } + + property("The Key protocol using pttl roundtrips successfully given key") { + forAll { (k: Key, ttl: KeyTTLResponse) => + val protocol = pttl(k) + assertEquals(protocol.encode, Arr(Bulk("PTTL"), Bulk(k))) + assertEquals(protocol.decode(ttlResponseToNum(ttl)), ttl) + } + } + + property("The Key protocol using randomkey roundtrips successfully using val") { + forAll { (ok: Option[Key]) => + val protocol = randomkey + assertEquals(protocol.encode, Arr(Bulk("RANDOMKEY"))) + assertEquals(protocol.decode(ok.fold(NullBulk: GenBulk)(Bulk(_))), ok) + } + } + + property("The Key protocol using rename roundtrips successfully given old key and new key") { + forAll { (ok: Key, nk: Key) => + val protocol = rename(ok, nk) + assertEquals(protocol.encode, Arr(Bulk("RENAME"), Bulk(ok), Bulk(nk))) + assertEquals(protocol.decode(Str(OK.value)), OK) + } + } + + property("The Key protocol using renamenx roundtrips successfully given old key and new key") { + forAll { (ok: Key, nk: Key, b: Boolean) => + val protocol = renamenx(ok, nk) + assertEquals(protocol.encode, Arr(Bulk("RENAMENX"), Bulk(ok), Bulk(nk))) + assertEquals(protocol.decode(boolToNum(b)), b) + } + } + + property("The Key protocol using restore roundtrips successfully given key, ttl and serialized value") { + forAll { (k: Key, nnl: NonNegLong, s: String) => + val protocol = restore(k, nnl, Bulk(s)) + assertEquals(protocol.encode, Arr(Bulk("RESTORE"), Bulk(k), Bulk(nnl), Bulk(s))) + assertEquals(protocol.decode(Str(OK.value)), OK) + } + } + + property("The Key protocol using restore roundtrips successfully given key, ttl, serialized value and mode") { + forAll { (k: Key, nnl: NonNegLong, s: String, m: KeyRestoreMode) => + val protocol = restore(k, nnl, Bulk(s), m) + assertEquals(protocol.encode, Arr(Bulk("RESTORE") :: Bulk(k) :: Bulk(nnl) :: Bulk(s) :: m.params.map(Bulk(_)))) + assertEquals(protocol.decode(Str(OK.value)), OK) + } + } + + property("The Key protocol using restore roundtrips successfully given key, ttl, serialized value and eviction") { + forAll { (k: Key, nnl: NonNegLong, s: String, e: KeyRestoreEviction) => + val protocol = restore(k, nnl, Bulk(s), e) + assertEquals(protocol.encode, Arr(Bulk("RESTORE"), Bulk(k), Bulk(nnl), Bulk(s), Bulk(e.param), Bulk(e.seconds))) + assertEquals(protocol.decode(Str(OK.value)), OK) + } + } + + property("The Key protocol using restore roundtrips successfully given key, ttl, serialized value, mode and eviction") { + forAll { (k: Key, nnl: NonNegLong, s: String, m: KeyRestoreMode, e: KeyRestoreEviction) => + val protocol = restore(k, nnl, Bulk(s), m, e) + assertEquals( + protocol.encode, + Arr( + Bulk("RESTORE") :: Bulk(k) :: Bulk(nnl) :: Bulk(s) :: m.params.map(Bulk(_)) ::: (Bulk(e.param) :: Bulk(e.seconds) :: Nil) + ) + ) + assertEquals(protocol.decode(Str(OK.value)), OK) + } + } + + property("The Key protocol using scan roundtrips successfully given cursor") { + forAll { (nnl: NonNegLong, sk: Scan[Key]) => + val protocol = scan(nnl) + assertEquals(protocol.encode, Arr(Bulk("SCAN"), Bulk(nnl))) + assertEquals(protocol.decode(scanKeyToArr(sk)), sk) + } + } + + property("The Key protocol using scan roundtrips successfully given cursor and glob pattern") { + forAll { (nnl: NonNegLong, g: GlobPattern, sk: Scan[Key]) => + val protocol = scan(nnl, g) + assertEquals(protocol.encode, Arr(Bulk("SCAN"), Bulk(nnl), Bulk("MATCH"), Bulk(g))) + assertEquals(protocol.decode(scanKeyToArr(sk)), sk) + } + } + + property("The Key protocol using scan roundtrips successfully given cursor and count") { + forAll { (nnl: NonNegLong, pi: PosInt, sk: Scan[Key]) => + val protocol = scan(nnl, pi) + assertEquals(protocol.encode, Arr(Bulk("SCAN"), Bulk(nnl), Bulk("COUNT"), Bulk(pi))) + assertEquals(protocol.decode(scanKeyToArr(sk)), sk) + } + } + + property("The Key protocol using scan roundtrips successfully given cursor, glob pattern and count") { + forAll { (nnl: NonNegLong, g: GlobPattern, pi: PosInt, sk: Scan[Key]) => + val protocol = scan(nnl, g, pi) + assertEquals(protocol.encode, Arr(Bulk("SCAN"), Bulk(nnl), Bulk("MATCH"), Bulk(g), Bulk("COUNT"), Bulk(pi))) + assertEquals(protocol.decode(scanKeyToArr(sk)), sk) + } + } + + property("The Key protocol using touch roundtrips successfully given keys") { + forAll { (ks: OneOrMoreKeys, nni: NonNegInt) => + val protocol = touch(ks) + assertEquals(protocol.encode, Arr(Bulk("TOUCH") :: ks.value.map(Bulk(_)))) + assertEquals(protocol.decode(Num(nni.value.toLong)), nni) + } + } + + property("The Key protocol using ttl roundtrips successfully given key") { + forAll { (k: Key, ttl0: KeyTTLResponse) => + val protocol = ttl(k) + assertEquals(protocol.encode, Arr(Bulk("TTL"), Bulk(k))) + assertEquals(protocol.decode(ttlResponseToNum(ttl0)), ttl0) + } + } + + property("The Key protocol using typeof roundtrips successfully given key") { + forAll { (k: Key, ot: Option[KeyType]) => + val protocol = typeof(k) + assertEquals(protocol.encode, Arr(Bulk("TYPE"), Bulk(k))) + assertEquals(protocol.decode(ot.fold(Str("none"))(Str(_))), ot) + } + } + + property("The Key protocol using unlink roundtrips successfully given keys") { + forAll { (ks: OneOrMoreKeys, nni: NonNegInt) => + val protocol = unlink(ks) + assertEquals(protocol.encode, Arr(Bulk("UNLINK") :: ks.value.map(Bulk(_)))) + assertEquals(protocol.decode(Num(nni.value.toLong)), nni) + } + } + + property("The Key protocol using wait roundtrips successfully given replicas and timeout") { + forAll { (pi1: PosInt, pl: PosLong, pi2: PosInt) => + val protocol = wait(pi1, pl) + assertEquals(protocol.encode, Arr(Bulk("WAIT"), Bulk(pi1), Bulk(pl))) + assertEquals(protocol.decode(Num(pi2.value.toLong)), pi2) } } } diff --git a/core/src/test/scala/laserdisc/protocol/ListPSpec.scala b/core/src/test/scala/laserdisc/protocol/ListPSpec.scala index ea948791..d8c173c1 100644 --- a/core/src/test/scala/laserdisc/protocol/ListPSpec.scala +++ b/core/src/test/scala/laserdisc/protocol/ListPSpec.scala @@ -1,7 +1,9 @@ package laserdisc package protocol -final class ListPSpec extends ListExtPSpec { +import org.scalacheck.Prop.forAll + +abstract class ListPSpec extends BaseSpec with ListP { import listtypes._ import org.scalacheck.{Arbitrary, Gen} @@ -9,165 +11,115 @@ final class ListPSpec extends ListExtPSpec { Gen.oneOf(ListPosition.after, ListPosition.before) } - "The List protocol" when { - "using lindex" should { - "roundtrip successfully" when { - "given key and index" in forAll("key", "index", "value") { (k: Key, i: Index, oi: Option[Int]) => - val protocol = lindex[Int](k, i) - - protocol.encode shouldBe Arr(Bulk("LINDEX"), Bulk(k), Bulk(i)) - protocol.decode(oi.fold(NullBulk: GenBulk)(Bulk(_))) onRight (_ shouldBe oi) - } - } + property("The List protocol using lindex roundtrips successfully given key and index") { + forAll { (k: Key, i: Index, oi: Option[Int]) => + val protocol = lindex[Int](k, i) + assertEquals(protocol.encode, Arr(Bulk("LINDEX"), Bulk(k), Bulk(i))) + assertEquals(protocol.decode(oi.fold(NullBulk: GenBulk)(Bulk(_))), oi) } + } - "using linsert" should { - "roundtrip successfully" when { - "given key, position, pivot and value" in { - forAll("key", "position", "pivot", "value", "inserted") { (k: Key, p: ListPosition, pi: String, v: String, opi: Option[PosInt]) => - val protocol = linsert(k, p, pi, v) - - protocol.encode shouldBe Arr(Bulk("LINSERT"), Bulk(k), Bulk(p), Bulk(pi), Bulk(v)) - protocol.decode(Num(opi.fold(-1L)(_.value.toLong))) onRight (_ shouldBe opi) - } - } - } + property("The List protocol using linsert roundtrips successfully given key, position, pivot and value") { + forAll { (k: Key, p: ListPosition, pi: String, v: String, opi: Option[PosInt]) => + val protocol = linsert(k, p, pi, v) + assertEquals(protocol.encode, Arr(Bulk("LINSERT"), Bulk(k), Bulk(p), Bulk(pi), Bulk(v))) + assertEquals(protocol.decode(Num(opi.fold(-1L)(_.value.toLong))), opi) } + } - "using llen" should { - "roundtrip successfully" when { - "given key" in forAll("key", "length") { (k: Key, nni: NonNegInt) => - val protocol = llen(k) - - protocol.encode shouldBe Arr(Bulk("LLEN"), Bulk(k)) - protocol.decode(Num(nni.value.toLong)) onRight (_ shouldBe nni) - } - } + property("The List protocol using llen roundtrips successfully given key") { + forAll { (k: Key, nni: NonNegInt) => + val protocol = llen(k) + assertEquals(protocol.encode, Arr(Bulk("LLEN"), Bulk(k))) + assertEquals(protocol.decode(Num(nni.value.toLong)), nni) } + } - "using lpop" should { - "roundtrip successfully" when { - "given key" in forAll("key", "popped value") { (k: Key, os: Option[String]) => - val protocol = lpop[String](k) - - protocol.encode shouldBe Arr(Bulk("LPOP"), Bulk(k)) - protocol.decode(os.fold(NullBulk: GenBulk)(Bulk(_))) onRight (_ shouldBe os) - } - } + property("The List protocol using lpop roundtrips successfully given key") { + forAll { (k: Key, os: Option[String]) => + val protocol = lpop[String](k) + assertEquals(protocol.encode, Arr(Bulk("LPOP"), Bulk(k))) + assertEquals(protocol.decode(os.fold(NullBulk: GenBulk)(Bulk(_))), os) } + } - "using lpush" should { - "roundtrip successfully" when { - "given key and values" in forAll("key", "values", "pushed") { (k: Key, is: OneOrMore[Int], pi: PosInt) => - val protocol = lpush(k, is) - - protocol.encode shouldBe Arr(Bulk("LPUSH") :: Bulk(k) :: is.value.map(Bulk(_))) - protocol.decode(Num(pi.value.toLong)) onRight (_ shouldBe pi) - } - } + property("The List protocol using lpush roundtrips successfully given key and values") { + forAll { (k: Key, is: OneOrMore[Int], pi: PosInt) => + val protocol = lpush(k, is) + assertEquals(protocol.encode, Arr(Bulk("LPUSH") :: Bulk(k) :: is.value.map(Bulk(_)))) + assertEquals(protocol.decode(Num(pi.value.toLong)), pi) } + } - "using lpushx" should { - "roundtrip successfully" when { - "given key and value" in forAll("key", "value", "pushed") { (k: Key, i: Int, opi: Option[PosInt]) => - val protocol = lpushx(k, i) - - protocol.encode shouldBe Arr(Bulk("LPUSHX"), Bulk(k), Bulk(i)) - protocol.decode(Num(opi.fold(0L)(_.value.toLong))) onRight (_ shouldBe opi) - } - } + property("The List protocol using lpushx roundtrips successfully given key and value") { + forAll { (k: Key, i: Int, opi: Option[PosInt]) => + val protocol = lpushx(k, i) + assertEquals(protocol.encode, Arr(Bulk("LPUSHX"), Bulk(k), Bulk(i))) + assertEquals(protocol.decode(Num(opi.fold(0L)(_.value.toLong))), opi) } + } - "using lrange" should { - "roundtrip successfully" when { - "given key, start index and end index" in { - forAll("key", "start index", "end index", "values") { (k: Key, si: Index, ei: Index, is: List[Int]) => - val protocol = lrange[Int](k, si, ei) - - protocol.encode shouldBe Arr(Bulk("LRANGE"), Bulk(k), Bulk(si), Bulk(ei)) - protocol.decode(Arr(is.map(Bulk(_)))) onRight (_ shouldBe is) - } - } - } + property("The List protocol using lrange roundtrips successfully given key, start index and end index") { + forAll { (k: Key, si: Index, ei: Index, is: List[Int]) => + val protocol = lrange[Int](k, si, ei) + assertEquals(protocol.encode, Arr(Bulk("LRANGE"), Bulk(k), Bulk(si), Bulk(ei))) + assertEquals(protocol.decode(Arr(is.map(Bulk(_)))), is) } + } - "using lrem" should { - "roundtrip successfully" when { - "given key, count and value" in forAll("key", "count", "value", "removed") { (k: Key, i: Index, s: String, nni: NonNegInt) => - val protocol = lrem(k, i, s) - - protocol.encode shouldBe Arr(Bulk("LREM"), Bulk(k), Bulk(i), Bulk(s)) - protocol.decode(Num(nni.value.toLong)) onRight (_ shouldBe nni) - } - } + property("The List protocol using lrem roundtrips successfully given key, count and value") { + forAll { (k: Key, i: Index, s: String, nni: NonNegInt) => + val protocol = lrem(k, i, s) + assertEquals(protocol.encode, Arr(Bulk("LREM"), Bulk(k), Bulk(i), Bulk(s))) + assertEquals(protocol.decode(Num(nni.value.toLong)), nni) } + } - "using lset" should { - "roundtrip successfully" when { - "given key, count and value" in forAll("key", "count", "value") { (k: Key, i: Index, s: String) => - val protocol = lset(k, i, s) - - protocol.encode shouldBe Arr(Bulk("LSET"), Bulk(k), Bulk(i), Bulk(s)) - protocol.decode(Str(OK.value)) onRight (_ shouldBe OK) - } - } + property("The List protocol using lset roundtrips successfully given key, count and value") { + forAll { (k: Key, i: Index, s: String) => + val protocol = lset(k, i, s) + assertEquals(protocol.encode, Arr(Bulk("LSET"), Bulk(k), Bulk(i), Bulk(s))) + assertEquals(protocol.decode(Str(OK.value)), OK) } + } - "using ltrim" should { - "roundtrip successfully" when { - "given key, start index and end index" in forAll("key", "start index", "end index") { (k: Key, si: Index, ei: Index) => - val protocol = ltrim(k, si, ei) - - protocol.encode shouldBe Arr(Bulk("LTRIM"), Bulk(k), Bulk(si), Bulk(ei)) - protocol.decode(Str(OK.value)) onRight (_ shouldBe OK) - } - } + property("The List protocol using ltrim roundtrips successfully given key, start index and end index") { + forAll { (k: Key, si: Index, ei: Index) => + val protocol = ltrim(k, si, ei) + assertEquals(protocol.encode, Arr(Bulk("LTRIM"), Bulk(k), Bulk(si), Bulk(ei))) + assertEquals(protocol.decode(Str(OK.value)), OK) } + } - "using rpop" should { - "roundtrip successfully" when { - "given key" in forAll("key", "popped value") { (k: Key, os: Option[String]) => - val protocol = rpop[String](k) - - protocol.encode shouldBe Arr(Bulk("RPOP"), Bulk(k)) - protocol.decode(os.fold(NullBulk: GenBulk)(Bulk(_))) onRight (_ shouldBe os) - } - } + property("The List protocol using rpop roundtrips successfully given key") { + forAll { (k: Key, os: Option[String]) => + val protocol = rpop[String](k) + assertEquals(protocol.encode, Arr(Bulk("RPOP"), Bulk(k))) + assertEquals(protocol.decode(os.fold(NullBulk: GenBulk)(Bulk(_))), os) } + } - "using rpoplpush" should { - "roundtrip successfully" when { - "given source key and destination key" in { - forAll("source key", "destination", "popped value") { (sk: Key, dk: Key, os: Option[String]) => - val protocol = rpoplpush[String](sk, dk) - - protocol.encode shouldBe Arr(Bulk("RPOPLPUSH"), Bulk(sk), Bulk(dk)) - protocol.decode(os.fold(NullBulk: GenBulk)(Bulk(_))) onRight (_ shouldBe os) - } - } - } + property("The List protocol using rpoplpush roundtrips successfully given source key and destination key") { + forAll { (sk: Key, dk: Key, os: Option[String]) => + val protocol = rpoplpush[String](sk, dk) + assertEquals(protocol.encode, Arr(Bulk("RPOPLPUSH"), Bulk(sk), Bulk(dk))) + assertEquals(protocol.decode(os.fold(NullBulk: GenBulk)(Bulk(_))), os) } + } - "using rpush" should { - "roundtrip successfully" when { - "given key and values" in forAll("key", "values", "pushed") { (k: Key, is: OneOrMore[Int], pi: PosInt) => - val protocol = rpush(k, is) - - protocol.encode shouldBe Arr(Bulk("RPUSH") :: Bulk(k) :: is.value.map(Bulk(_))) - protocol.decode(Num(pi.value.toLong)) onRight (_ shouldBe pi) - } - } + property("The List protocol using rpush roundtrips successfully given key and values") { + forAll { (k: Key, is: OneOrMore[Int], pi: PosInt) => + val protocol = rpush(k, is) + assertEquals(protocol.encode, Arr(Bulk("RPUSH") :: Bulk(k) :: is.value.map(Bulk(_)))) + assertEquals(protocol.decode(Num(pi.value.toLong)), pi) } + } - "using rpushx" should { - "roundtrip successfully" when { - "given key and value" in forAll("key", "value", "pushed") { (k: Key, i: Int, opi: Option[PosInt]) => - val protocol = rpushx(k, i) - - protocol.encode shouldBe Arr(Bulk("RPUSHX"), Bulk(k), Bulk(i)) - protocol.decode(Num(opi.fold(0L)(_.value.toLong))) onRight (_ shouldBe opi) - } - } + property("The List protocol using rpushx roundtrips successfully given key and value") { + forAll { (k: Key, i: Int, opi: Option[PosInt]) => + val protocol = rpushx(k, i) + assertEquals(protocol.encode, Arr(Bulk("RPUSHX"), Bulk(k), Bulk(i))) + assertEquals(protocol.decode(Num(opi.fold(0L)(_.value.toLong))), opi) } } } diff --git a/core/src/test/scala/laserdisc/protocol/RESPCodecsSpec.scala b/core/src/test/scala/laserdisc/protocol/RESPCodecsSpec.scala index 7925683a..10df2b43 100644 --- a/core/src/test/scala/laserdisc/protocol/RESPCodecsSpec.scala +++ b/core/src/test/scala/laserdisc/protocol/RESPCodecsSpec.scala @@ -3,14 +3,14 @@ package protocol import java.nio.charset.StandardCharsets.UTF_8 -import org.scalacheck.{Arbitrary, Gen} import org.scalacheck.Arbitrary.arbitrary import org.scalacheck.Gen._ -import org.scalatest.EitherValues +import org.scalacheck.Prop.forAll +import org.scalacheck.{Arbitrary, Gen} import scodec.bits.BitVector import scodec.{Codec, Err => SErr} -object RESPCodecsSpec extends EitherValues { +object RESPCodecsSpec { private[this] final object functions { private[this] final val attemptDecode = (bits: BitVector) => Codec[RESP].decodeValue(bits) private[this] final val requireEncode = (resp: RESP) => Codec[RESP].encode(resp).require @@ -90,67 +90,106 @@ final class RESPCodecsSpec extends BaseSpec { private[this] implicit val genArrArb: Arbitrary[GenArr] = Arbitrary(genArrGen) private[this] implicit val respListArb: Arbitrary[List[RESP]] = Arbitrary(respListGen) - "A RESP codec" when { - "handling unknown protocol type" should { - "fail with correct error message" in forAll { c: Char => - s"$c".RESP.left.value.messageWithContext shouldBe s"unidentified RESP type (Hex: ${c.toHex})" - } + property("A RESP codec handling unknown protocol type fails with correct error message") { + forAll { c: Char => + assertLeftEquals( + s"$c".RESP leftMap (_.messageWithContext), + s"unidentified RESP type (Hex: ${c.toHex})" + ) } + } - "handling simple strings" should { - "decode them correctly" in forAll { s: String => s"+$s$CRLF".RESP onRight (_ shouldBe Str(s)) } - "encode them correctly" in forAll { s: Str => s.wireFormat shouldBe s"+${s.value}$CRLF" } - "roundtrip with no errors" in forAll { s: Str => s.roundTrip shouldBe s } - } + property("A RESP codec handling simple strings decodes them correctly") { + forAll { s: String => assertEquals(s"+$s$CRLF".RESP, Str(s)) } + } - "handling errors" should { - "decode them correctly" in forAll { s: String => s"-$s$CRLF".RESP onRight (_ shouldBe Err(s)) } - "encode them correctly" in forAll { e: Err => e.wireFormat shouldBe s"-${e.message}$CRLF" } - "roundtrip with no errors" in forAll { e: Err => e.roundTrip shouldBe e } - } + property("A RESP codec handling simple strings decodes them correctly") { + forAll { s: Str => assertEquals(s.wireFormat, s"+${s.value}$CRLF") } + } - "handling integers" should { - "decode them correctly" in forAll { l: Long => s":$l$CRLF".RESP onRight (_ shouldBe Num(l)) } - "encode them correctly" in forAll { n: Num => n.wireFormat shouldBe s":${n.value}$CRLF" } - "roundtrip with no errors" in forAll { n: Num => n.roundTrip shouldBe n } - } + property("A RESP codec handling simple strings roundtrips with no errors") { + forAll { s: Str => assertEquals(s.roundTrip, s) } + } - "handling bulk strings" should { - "fail with correct error message when decoding size < -1" in { - s"$$-2${CRLF}bla$CRLF".RESP.left.value.messageWithContext shouldBe "size: failed to decode bulk-string of size -2" - } - "decode them correctly" in forAll { os: Option[String] => - os match { - case None => s"$$-1$CRLF".RESP onRight (_ shouldBe NullBulk) - case Some(s) => s"$$${s.bytesLength}$CRLF$s$CRLF".RESP onRight (_ shouldBe Bulk(s)) - } - } - "encode them correctly" in forAll { b: GenBulk => - b match { - case NullBulk => b.wireFormat shouldBe s"$$-1$CRLF" - case Bulk(bs) => b.wireFormat shouldBe s"$$${bs.bytesLength}$CRLF$bs$CRLF" - } + property("A RESP codec handling errors decodes them correctly") { + forAll { s: String => assertEquals(s"-$s$CRLF".RESP, Err(s)) } + } + + property("A RESP codec handling errors encodes them correctly") { + forAll { e: Err => assertEquals(e.wireFormat, s"-${e.message}$CRLF") } + } + + property("A RESP codec handling errors roundtrips with no errors") { + forAll { e: Err => assertEquals(e.roundTrip, e) } + } + + property("A RESP codec handling integers decodes them correctly") { + forAll { l: Long => assertEquals(s":$l$CRLF".RESP, Num(l)) } + } + + property("A RESP codec handling integers encodes them correctly") { + forAll { n: Num => assertEquals(n.wireFormat, s":${n.value}$CRLF") } + } + + property("A RESP codec handling integers roundtrips with no errors") { + forAll { n: Num => assertEquals(n.roundTrip, n) } + } + + property("A RESP codec handling bulk strings fails with correct error message when decoding size < -1") { + assertLeftEquals( + s"$$-2${CRLF}bla$CRLF".RESP leftMap (_.messageWithContext), + "size: failed to decode bulk-string of size -2" + ) + } + + property("A RESP codec handling bulk strings decodes them correctly") { + forAll { os: Option[String] => + os match { + case None => assertEquals(s"$$-1$CRLF".RESP, NullBulk) + case Some(s) => assertEquals(s"$$${s.bytesLength}$CRLF$s$CRLF".RESP, Bulk(s)) } - "roundtrip with no errors" in forAll { b: GenBulk => b.roundTrip shouldBe b } } + } - "handling arrays" should { - "fail with correct error message when decoding size < -1" in { - s"*-2${CRLF}bla$CRLF".RESP.left.value.messageWithContext shouldBe "size: failed to decode array of size -2" + property("A RESP codec handling bulk strings encodes them correctly") { + forAll { b: GenBulk => + b match { + case NullBulk => assertEquals(b.wireFormat, s"$$-1$CRLF") + case Bulk(bs) => assertEquals(b.wireFormat, s"$$${bs.bytesLength}$CRLF$bs$CRLF") } - "decode them correctly" in forAll { ors: Option[List[RESP]] => - ors match { - case None => s"*-1$CRLF".RESP onRight (_ shouldBe NilArr) - case Some(xs) => s"*${xs.length}$CRLF${xs.wireFormat}".RESP onRight (_ shouldBe Arr(xs)) - } + } + } + + property("A RESP codec handling bulk strings roundtrips with no errors") { + forAll { b: GenBulk => assertEquals(b.roundTrip, b) } + } + + property("A RESP codec handling arrays fails with correct error message when decoding size < -1") { + assertLeftEquals( + s"*-2${CRLF}bla$CRLF".RESP leftMap (_.messageWithContext), + "size: failed to decode array of size -2" + ) + } + + property("A RESP codec handling bulk strings decodes them correctly") { + forAll { ors: Option[List[RESP]] => + ors match { + case None => assertEquals(s"*-1$CRLF".RESP, NilArr) + case Some(xs) => assertEquals(s"*${xs.length}$CRLF${xs.wireFormat}".RESP, Arr(xs)) } - "encode them correctly" in forAll { a: GenArr => - a match { - case NilArr => a.wireFormat shouldBe s"*-1$CRLF" - case Arr(xs) => a.wireFormat shouldBe s"*${xs.length}$CRLF${xs.wireFormat}" - } + } + } + + property("A RESP codec handling bulk strings encodes them correctly") { + forAll { a: GenArr => + a match { + case NilArr => assertEquals(a.wireFormat, s"*-1$CRLF") + case Arr(xs) => assertEquals(a.wireFormat, s"*${xs.length}$CRLF${xs.wireFormat}") } - "roundtrip with no errors" in forAll { a: GenArr => a.roundTrip shouldBe a } } } + + property("A RESP codec handling bulk strings roundtrips with no errors") { + forAll { a: GenArr => assertEquals(a.roundTrip, a) } + } } diff --git a/core/src/test/scala/laserdisc/protocol/RESPFrameArrSpec.scala b/core/src/test/scala/laserdisc/protocol/RESPFrameArrSpec.scala index ff640633..b32b8d8a 100644 --- a/core/src/test/scala/laserdisc/protocol/RESPFrameArrSpec.scala +++ b/core/src/test/scala/laserdisc/protocol/RESPFrameArrSpec.scala @@ -2,216 +2,208 @@ package laserdisc package protocol import org.scalacheck.Gen +import org.scalacheck.Prop.forAll import scodec.bits.BitVector final class RESPFrameArrSpec extends BaseSpec with RESPFrameFixture { - "An empty GenArr Frame" when { - "appending a bit vector that represent an empty array" should { - "produce a complete frame with the bits of an empty bulk" in { - val inputVector = BitVector("*0\r\n".getBytes) - EmptyFrame.append(inputVector.toByteBuffer) should be(Right(CompleteFrame(inputVector))) - } - } - "appending a bit vector that contains only the size of a non empty array" should { - "produce an incomplete frame with the bits of the received size" in { - val inputVector = BitVector("*32\r\n".getBytes) - EmptyFrame.append(inputVector.toByteBuffer) should be(Right(IncompleteFrame(inputVector, 0))) - } - } + test("Appending an empty array bit vector to an empty GenArr frame gives a complete frame with the bits of an empty bulk") { + val inputVector = BitVector("*0\r\n".getBytes) + assertEquals(EmptyFrame.append(inputVector.toByteBuffer), CompleteFrame(inputVector)) } - "A non empty GenArr Frame" when { - "appending a bit vector that completes it" should { - "produce a complete frame with the correct bits" in { - val nonEmptyFrame = IncompleteFrame(BitVector("*1\r\n$16\r\nTest bulk str".getBytes), 0) - val inputVector = BitVector("ing\r\n".getBytes) - val expected = BitVector("*1\r\n$16\r\nTest bulk string\r\n".getBytes) - nonEmptyFrame.append(inputVector.toByteBuffer) should be(Right(CompleteFrame(expected))) - } - } + test( + "Appending a bit vector that contains only the size of a non empty array to an empty GenArr frame gives an incomplete frame with the bits of the size" + ) { + val inputVector = BitVector("*32\r\n".getBytes) + assertEquals(EmptyFrame.append(inputVector.toByteBuffer), IncompleteFrame(inputVector, 0)) + } - "appending a bit vector that doesn't complete the array but has complete objects" should { - "produce an incomplete frame with the correct partial and 0 as missing count" in { - val nonEmptyFrame = IncompleteFrame(BitVector("*3\r\n$16\r\nTest bulk str".getBytes), 0) - val inputVector = BitVector("ing\r\n:100\r\n".getBytes) - val expected = BitVector("*3\r\n$16\r\nTest bulk string\r\n:100\r\n".getBytes) - nonEmptyFrame.append(inputVector.toByteBuffer) should be(Right(IncompleteFrame(expected, 0))) - } - } + test("Appending to a non empty GenArr frame a bit vector that completes it gives a correct complete frame") { + val nonEmptyFrame = IncompleteFrame(BitVector("*1\r\n$16\r\nTest bulk str".getBytes), 0) + val inputVector = BitVector("ing\r\n".getBytes) + val expected = BitVector("*1\r\n$16\r\nTest bulk string\r\n".getBytes) + assertEquals(nonEmptyFrame.append(inputVector.toByteBuffer), CompleteFrame(expected)) + } - "appending a bit vector that completes the array" should { - "produce a complete frame with the correct bits" in { - val nonEmptyFrame = IncompleteFrame(BitVector("*3\r\n$16\r\nTest bulk str".getBytes), 0) - val inputVector = BitVector("ing\r\n:100\r\n+A simple string\r\n".getBytes) - val expected = BitVector("*3\r\n$16\r\nTest bulk string\r\n:100\r\n+A simple string\r\n".getBytes) - nonEmptyFrame.append(inputVector.toByteBuffer) should be(Right(CompleteFrame(expected))) - } - } + test( + "Appending to a non empty GenArr frame a bit vector that doesn't complete it but has complete results gives an incomplete frame with the correct partial and 0 as missing count" + ) { + val nonEmptyFrame = IncompleteFrame(BitVector("*3\r\n$16\r\nTest bulk str".getBytes), 0) + val inputVector = BitVector("ing\r\n:100\r\n".getBytes) + val expected = BitVector("*3\r\n$16\r\nTest bulk string\r\n:100\r\n".getBytes) + assertEquals(nonEmptyFrame.append(inputVector.toByteBuffer), IncompleteFrame(expected, 0)) + } - "appending a bit vector with more than one array" should { - "produce more than one frame with a list of the complete ones and an empty remainder" in { - val nonEmptyFrame = IncompleteFrame(BitVector("*3\r\n$16\r\nTest bulk str".getBytes), 0) - val inputVector = BitVector("ing\r\n:100\r\n+A simple string\r\n*2\r\n$8\r\nAnother1\r\n-An error\r\n".getBytes) - nonEmptyFrame.append(inputVector.toByteBuffer) should be( - Right( - MoreThanOneFrame( - Vector( - CompleteFrame(BitVector("*3\r\n$16\r\nTest bulk string\r\n:100\r\n+A simple string\r\n".getBytes)), - CompleteFrame(BitVector("*2\r\n$8\r\nAnother1\r\n-An error\r\n".getBytes)) - ), - BitVector.empty - ) - ) - ) - } - } + test("Appending to a non empty GenArr frame a bit vector that completes the array gives a correct complete frame") { + val nonEmptyFrame = IncompleteFrame(BitVector("*3\r\n$16\r\nTest bulk str".getBytes), 0) + val inputVector = BitVector("ing\r\n:100\r\n+A simple string\r\n".getBytes) + val expected = BitVector("*3\r\n$16\r\nTest bulk string\r\n:100\r\n+A simple string\r\n".getBytes) + assertEquals(nonEmptyFrame.append(inputVector.toByteBuffer), CompleteFrame(expected)) + } - "appending a bit vector with more than one array plus a reminder" should { - "produce more than one frame with a list of the complete ones and the correct remainder" in { - val nonEmptyFrame = IncompleteFrame(BitVector("*3\r\n$16\r\nTest bulk str".getBytes), 0) - val inputVector = BitVector("ing\r\n:100\r\n+A simple string\r\n*2\r\n$8\r\nAnother1\r\n-An error\r\n$17\r\nAnother bulk ".getBytes) - nonEmptyFrame.append(inputVector.toByteBuffer) should be( - Right( - MoreThanOneFrame( - Vector( - CompleteFrame(BitVector("*3\r\n$16\r\nTest bulk string\r\n:100\r\n+A simple string\r\n".getBytes)), - CompleteFrame(BitVector("*2\r\n$8\r\nAnother1\r\n-An error\r\n".getBytes)) - ), - BitVector("$17\r\nAnother bulk ".getBytes()) - ) - ) - ) - } - } + test( + "Appending to a non empty GenArr frame a bit vector with more than one array gives more than one frame with a list of the complete ones and an empty remainder" + ) { + val nonEmptyFrame = IncompleteFrame(BitVector("*3\r\n$16\r\nTest bulk str".getBytes), 0) + val inputVector = BitVector("ing\r\n:100\r\n+A simple string\r\n*2\r\n$8\r\nAnother1\r\n-An error\r\n".getBytes) + assertEquals( + nonEmptyFrame.append(inputVector.toByteBuffer), + MoreThanOneFrame( + Vector( + CompleteFrame(BitVector("*3\r\n$16\r\nTest bulk string\r\n:100\r\n+A simple string\r\n".getBytes)), + CompleteFrame(BitVector("*2\r\n$8\r\nAnother1\r\n-An error\r\n".getBytes)) + ), + BitVector.empty + ) + ) + } + + test( + "Appending to a non empty GenArr frame a bit vector with more than one array plus a reminder gives more than one frame with a list of the complete ones and the correct remainder" + ) { + val nonEmptyFrame = IncompleteFrame(BitVector("*3\r\n$16\r\nTest bulk str".getBytes), 0) + val inputVector = BitVector("ing\r\n:100\r\n+A simple string\r\n*2\r\n$8\r\nAnother1\r\n-An error\r\n$17\r\nAnother bulk ".getBytes) + assertEquals( + nonEmptyFrame.append(inputVector.toByteBuffer), + MoreThanOneFrame( + Vector( + CompleteFrame(BitVector("*3\r\n$16\r\nTest bulk string\r\n:100\r\n+A simple string\r\n".getBytes)), + CompleteFrame(BitVector("*2\r\n$8\r\nAnother1\r\n-An error\r\n".getBytes)) + ), + BitVector("$17\r\nAnother bulk ".getBytes()) + ) + ) + } + + test( + "Appending to a non empty GenArr frame a bit vector with multiple null arrays gives more than one frame with a list of the complete ones and the correct remainder" + ) { + val nonEmptyFrame = IncompleteFrame(BitVector("*3\r\n$16\r\nTest bulk str".getBytes), 0) + val inputVector = BitVector("ing\r\n:100\r\n+A simple string\r\n*-1\r\n*-1\r\n*-1\r\n*-1\r\n*-1\r\n".getBytes) + assertEquals( + nonEmptyFrame.append(inputVector.toByteBuffer), + MoreThanOneFrame( + Vector( + CompleteFrame(BitVector("*3\r\n$16\r\nTest bulk string\r\n:100\r\n+A simple string\r\n".getBytes)), + CompleteFrame(BitVector("*-1\r\n".getBytes)), + CompleteFrame(BitVector("*-1\r\n".getBytes)), + CompleteFrame(BitVector("*-1\r\n".getBytes)), + CompleteFrame(BitVector("*-1\r\n".getBytes)), + CompleteFrame(BitVector("*-1\r\n".getBytes)) + ), + BitVector.empty + ) + ) + } + + test( + "Appending to a non empty GenArr frame a bit vector with multiple null arrays the last of which not complete gives more than one frame with a list of the complete ones and the correct remainder" + ) { + val nonEmptyFrame = IncompleteFrame(BitVector("*3\r\n$16\r\nTest bulk str".getBytes), 0) + val inputVector = BitVector("ing\r\n:100\r\n+A simple string\r\n*-1\r\n*-1\r\n*-1\r\n*-1\r\n*-1\r\n*".getBytes) + assertEquals( + nonEmptyFrame.append(inputVector.toByteBuffer), + MoreThanOneFrame( + Vector( + CompleteFrame(BitVector("*3\r\n$16\r\nTest bulk string\r\n:100\r\n+A simple string\r\n".getBytes)), + CompleteFrame(BitVector("*-1\r\n".getBytes)), + CompleteFrame(BitVector("*-1\r\n".getBytes)), + CompleteFrame(BitVector("*-1\r\n".getBytes)), + CompleteFrame(BitVector("*-1\r\n".getBytes)), + CompleteFrame(BitVector("*-1\r\n".getBytes)) + ), + BitVector("*".getBytes()) + ) + ) + } - "appending a bit vector with multiple null arrays" should { - "produce more than one frame with a list of the complete ones and the correct remainder" in { - val nonEmptyFrame = IncompleteFrame(BitVector("*3\r\n$16\r\nTest bulk str".getBytes), 0) - val inputVector = BitVector("ing\r\n:100\r\n+A simple string\r\n*-1\r\n*-1\r\n*-1\r\n*-1\r\n*-1\r\n".getBytes) - nonEmptyFrame.append(inputVector.toByteBuffer) should be( - Right( - MoreThanOneFrame( + test( + "Appending to a non empty GenArr frame a bit vector with multiple arrays interleaved with null arrays gives as `complete` more than one frame with a list of the complete ones in the right order" + ) { + val nonEmptyFrame = IncompleteFrame(BitVector("*3\r\n$16\r\nTest bulk str".getBytes), 0) + val inputVector = BitVector( + "ing\r\n:100\r\n+A simple string\r\n*-1\r\n*-1\r\n*2\r\n$8\r\nAnother1\r\n-An error\r\n*-1\r\n*-1\r\n*-1\r\n".getBytes + ) + nonEmptyFrame + .append(inputVector.toByteBuffer) + .fold( + err => fail(s"expected a result but failed with $err"), + { + case r @ MoreThanOneFrame(_, _) => + assertEquals( + r.complete, Vector( CompleteFrame(BitVector("*3\r\n$16\r\nTest bulk string\r\n:100\r\n+A simple string\r\n".getBytes)), CompleteFrame(BitVector("*-1\r\n".getBytes)), CompleteFrame(BitVector("*-1\r\n".getBytes)), + CompleteFrame(BitVector("*2\r\n$8\r\nAnother1\r\n-An error\r\n".getBytes)), CompleteFrame(BitVector("*-1\r\n".getBytes)), CompleteFrame(BitVector("*-1\r\n".getBytes)), CompleteFrame(BitVector("*-1\r\n".getBytes)) - ), - BitVector.empty + ) ) - ) - ) - } - } + case _ => fail(s"expected a MoreThanOne type") + } + ) + } - "appending a bit vector with multiple null arrays the last of which not complete" should { - "produce more than one frame with a list of the complete ones and the correct remainder" in { - val nonEmptyFrame = IncompleteFrame(BitVector("*3\r\n$16\r\nTest bulk str".getBytes), 0) - val inputVector = BitVector("ing\r\n:100\r\n+A simple string\r\n*-1\r\n*-1\r\n*-1\r\n*-1\r\n*-1\r\n*".getBytes) - nonEmptyFrame.append(inputVector.toByteBuffer) should be( - Right( - MoreThanOneFrame( + test( + "Appending to a non empty GenArr frame a bit vector with multiple arrays containing nested arrays gives as `complete` more than one frame with a list of the complete ones in the correct order" + ) { + val nonEmptyFrame = IncompleteFrame(BitVector("*3\r\n$16\r\nTest bulk str".getBytes), 0) + val inputVector = BitVector( + "ing\r\n:100\r\n+A simple string\r\n*-1\r\n*2\r\n$8\r\nAnother1\r\n-An error\r\n*3\r\n$8\r\nAnother1\r\n*3\r\n*2\r\n+Simple string\r\n*2\r\n$3\r\nfoo\r\n-an error\r\n:13\r\n:12\r\n-An error\r\n*-1\r\n".getBytes + ) + nonEmptyFrame + .append(inputVector.toByteBuffer) + .fold( + err => fail(s"expected a result but failed with $err"), + { + case r @ MoreThanOneFrame(_, _) => + assertEquals( + r.complete, Vector( CompleteFrame(BitVector("*3\r\n$16\r\nTest bulk string\r\n:100\r\n+A simple string\r\n".getBytes)), CompleteFrame(BitVector("*-1\r\n".getBytes)), - CompleteFrame(BitVector("*-1\r\n".getBytes)), - CompleteFrame(BitVector("*-1\r\n".getBytes)), - CompleteFrame(BitVector("*-1\r\n".getBytes)), + CompleteFrame(BitVector("*2\r\n$8\r\nAnother1\r\n-An error\r\n".getBytes)), + CompleteFrame( + BitVector( + "*3\r\n$8\r\nAnother1\r\n*3\r\n*2\r\n+Simple string\r\n*2\r\n$3\r\nfoo\r\n-an error\r\n:13\r\n:12\r\n-An error\r\n".getBytes + ) + ), CompleteFrame(BitVector("*-1\r\n".getBytes)) - ), - BitVector("*".getBytes()) + ) ) - ) - ) - } - } - - "appending a bit vector with multiple arrays interleaved with null arrays" should { - "produce as result of `complete` more than one frame with a list of the complete arrays in the correct order" in { - val nonEmptyFrame = IncompleteFrame(BitVector("*3\r\n$16\r\nTest bulk str".getBytes), 0) - val inputVector = - BitVector("ing\r\n:100\r\n+A simple string\r\n*-1\r\n*-1\r\n*2\r\n$8\r\nAnother1\r\n-An error\r\n*-1\r\n*-1\r\n*-1\r\n".getBytes) - nonEmptyFrame - .append(inputVector.toByteBuffer) - .fold( - err => fail(s"expected a result but failed with $err"), - { - case r @ MoreThanOneFrame(_, _) => - r.complete shouldBe Vector( - CompleteFrame(BitVector("*3\r\n$16\r\nTest bulk string\r\n:100\r\n+A simple string\r\n".getBytes)), - CompleteFrame(BitVector("*-1\r\n".getBytes)), - CompleteFrame(BitVector("*-1\r\n".getBytes)), - CompleteFrame(BitVector("*2\r\n$8\r\nAnother1\r\n-An error\r\n".getBytes)), - CompleteFrame(BitVector("*-1\r\n".getBytes)), - CompleteFrame(BitVector("*-1\r\n".getBytes)), - CompleteFrame(BitVector("*-1\r\n".getBytes)) - ) - case _ => fail(s"expected a MoreThanOne type") - } - ) - } - } - - "appending a bit vector with multiple arrays containing nested arrays" should { - "produce as result of `complete` more than one frame with a list of the complete arrays in the correct order" in { - val nonEmptyFrame = IncompleteFrame(BitVector("*3\r\n$16\r\nTest bulk str".getBytes), 0) - val inputVector = BitVector( - "ing\r\n:100\r\n+A simple string\r\n*-1\r\n*2\r\n$8\r\nAnother1\r\n-An error\r\n*3\r\n$8\r\nAnother1\r\n*3\r\n*2\r\n+Simple string\r\n*2\r\n$3\r\nfoo\r\n-an error\r\n:13\r\n:12\r\n-An error\r\n*-1\r\n".getBytes - ) - nonEmptyFrame - .append(inputVector.toByteBuffer) - .fold( - err => fail(s"expected a result but failed with $err"), - { - case r @ MoreThanOneFrame(_, _) => - r.complete shouldBe Vector( - CompleteFrame(BitVector("*3\r\n$16\r\nTest bulk string\r\n:100\r\n+A simple string\r\n".getBytes)), - CompleteFrame(BitVector("*-1\r\n".getBytes)), - CompleteFrame(BitVector("*2\r\n$8\r\nAnother1\r\n-An error\r\n".getBytes)), - CompleteFrame( - BitVector( - "*3\r\n$8\r\nAnother1\r\n*3\r\n*2\r\n+Simple string\r\n*2\r\n$3\r\nfoo\r\n-an error\r\n:13\r\n:12\r\n-An error\r\n".getBytes - ) - ), - CompleteFrame(BitVector("*-1\r\n".getBytes)) - ) - case _ => fail(s"expected a MoreThanOne type") - } - ) - } - } - - "appending multi level arrays in chunks of different size" should { - "give the exact encoding" in { - - val chunkSize: Gen[Int] = - Gen.frequency( - 8 -> Gen.choose(128, 1024), - 2 -> Gen.choose(1025, 4096) - ) - - forAll(chunkSize) { chunkSize: Int => - val inputChunks = groupInChunks(bytesOf(arrFiveLevelsList), chunkSize) - - val frames = appendChunks(inputChunks).map(f => new String(f.bits.toByteArray)) - - frames.take(1).head == NullArrEncoded().encoded - frames.take(1).head == StrEncoded("OK").encoded - frames.take(1).head == ArrEncoded(arrThreeLevelsList).encoded - frames.take(1).head == NumEncoded(21).encoded - frames.take(1).head == ArrEncoded(mixedNoArrList).encoded - frames.take(1).head == StrEncoded("").encoded - frames.take(1).head == ArrEncoded(arrOneLevelList).encoded - frames.take(1).head == NumEncoded(Long.MaxValue).encoded - frames.take(1).head == ArrEncoded(arrFourLevelsList).encoded - frames.take(1).head == StrEncoded(shortStr).encoded - frames.take(1).head == ArrEncoded(arrTwoLevelsList).encoded - frames.take(1).head == StrEncoded("PONG").encoded + case _ => fail(s"expected a MoreThanOne type") } - } + ) + } + + property("Appending multi-level arrays in chunks of different size to a empty GenArr frame gives the exact encoding") { + val chunkSize: Gen[Int] = + Gen.frequency( + 8 -> Gen.choose(128, 1024), + 2 -> Gen.choose(1025, 4096) + ) + + forAll(chunkSize) { chunkSize: Int => + val inputChunks = groupInChunks(bytesOf(arrFiveLevelsList), chunkSize) + + val frames = appendChunks(inputChunks).map(f => new String(f.bits.toByteArray)) + + assertEquals(frames.head, NullArrEncoded().encoded) + assertEquals(frames.drop(1).head, StrEncoded("OK").encoded) + assertEquals(frames.drop(2).head, ArrEncoded(arrThreeLevelsList).encoded) + assertEquals(frames.drop(3).head, NumEncoded(21).encoded) + assertEquals(frames.drop(4).head, ArrEncoded(mixedNoArrList).encoded) + assertEquals(frames.drop(5).head, StrEncoded("").encoded) + assertEquals(frames.drop(6).head, ArrEncoded(arrOneLevelList).encoded) + assertEquals(frames.drop(7).head, NumEncoded(Long.MaxValue).encoded) + assertEquals(frames.drop(8).head, ArrEncoded(arrFourLevelsList).encoded) + assertEquals(frames.drop(9).head, StrEncoded(shortStr).encoded) + assertEquals(frames.drop(10).head, ArrEncoded(arrTwoLevelsList).encoded) + assertEquals(frames.drop(11).head, StrEncoded("PONG").encoded) } } } diff --git a/core/src/test/scala/laserdisc/protocol/RESPFrameBulkSpec.scala b/core/src/test/scala/laserdisc/protocol/RESPFrameBulkSpec.scala index 9de6c49b..373a6af5 100644 --- a/core/src/test/scala/laserdisc/protocol/RESPFrameBulkSpec.scala +++ b/core/src/test/scala/laserdisc/protocol/RESPFrameBulkSpec.scala @@ -4,245 +4,263 @@ package protocol import scodec.bits.BitVector final class RESPFrameBulkSpec extends BaseSpec { - "An empty Bulk Frame" when { - "appending a bit vector that's complete" should { - "produce Complete with all the bits" in { - val inputVector = BitVector("$16\r\nTest bulk string\r\n".getBytes) - EmptyFrame.append(inputVector.toByteBuffer) should be(Right(CompleteFrame(inputVector))) - } - } - "appending a bit vector that's complete and includes unsafe characters (\\r\\n)" should { - "produce Complete with all the bits" in { - val inputVector = BitVector("$16\r\nTest \n\r ! string\r\n".getBytes) - EmptyFrame.append(inputVector.toByteBuffer) should be(Right(CompleteFrame(inputVector))) - } - } + test("Appending to an empty Bulk frame a bit vector bit vector that's complete gives Complete with all the bits") { + val inputVector = BitVector("$16\r\nTest bulk string\r\n".getBytes) + assertEquals(EmptyFrame.append(inputVector.toByteBuffer), CompleteFrame(inputVector)) + } - "appending a bit vector that represent an empty bulk" should { - "produce Complete with a empty content" in { - val inputVector = BitVector("$0\r\n\r\n".getBytes) - EmptyFrame.append(inputVector.toByteBuffer) should be(Right(CompleteFrame(inputVector))) - } - } + test( + "Appending to an empty Bulk frame a bit vector that's complete and includes unsafe characters (\\r\\n) gives Complete with all the bits" + ) { + val inputVector = BitVector("$16\r\nTest \n\r ! string\r\n".getBytes) + assertEquals(EmptyFrame.append(inputVector.toByteBuffer), CompleteFrame(inputVector)) + } - "appending a bit vector that's not complete" should { - "produce Incomplete with the correct partial and the correct missing count" in { - val inputVector = BitVector("$16\r\nTest bulk string".getBytes) - EmptyFrame.append(inputVector.toByteBuffer) should be(Right(IncompleteFrame(inputVector, 16))) - } - } + test("Appending to an empty Bulk frame an empty bulk bit vector gives Complete with an empty content") { + val inputVector = BitVector("$0\r\n\r\n".getBytes) + assertEquals(EmptyFrame.append(inputVector.toByteBuffer), CompleteFrame(inputVector)) + } - "appending a bit vector with multiple messages all complete" should { - "produce MoreThanOne with a list of the complete ones and an empty remainder" in { - val inputVector = BitVector("$16\r\nTest bulk string\r\n$16\r\nTest bulk string\r\n$16\r\nTest bulk string\r\n".getBytes) - EmptyFrame.append(inputVector.toByteBuffer) should be( - Right( - MoreThanOneFrame( - Vector.fill(3)(CompleteFrame(BitVector("$16\r\nTest bulk string\r\n".getBytes()))), - BitVector.empty - ) - ) - ) - } - } + test( + "Appending to an empty Bulk frame a bit vector that's not complete gives Incomplete with the correct partial and the correct missing count" + ) { + val inputVector = BitVector("$16\r\nTest bulk string".getBytes) + assertEquals(EmptyFrame.append(inputVector.toByteBuffer), IncompleteFrame(inputVector, 16)) + } - "appending a bit vector with multiple messages with the last not complete" should { - "produce MoreThanOne with a list of the complete ones and a remainder with the incomplete bits" in { - val inputVector = BitVector("$16\r\nTest bulk string\r\n$16\r\nTest bulk string\r\n$16\r\nTest bulk".getBytes) - EmptyFrame.append(inputVector.toByteBuffer) should be( - Right( - MoreThanOneFrame( - Vector.fill(2)(CompleteFrame(BitVector("$16\r\nTest bulk string\r\n".getBytes()))), - BitVector("$16\r\nTest bulk".getBytes()) - ) - ) - ) - } - } + test( + "Appending to an empty Bulk frame a bit vector with a number split in two chunks gives MoreThanOneFrame with a follow up partial bulk string" + ) { + val incompleteFirstInput = BitVector(":2".getBytes) + val secondInput = BitVector(s"1$CRLF$$18${CRLF}Test bulk".getBytes) + assertEquals( + EmptyFrame.append(incompleteFirstInput.toByteBuffer).flatMap(_.append(secondInput.toByteBuffer)), + MoreThanOneFrame( + Vector(CompleteFrame(BitVector(s":21$CRLF".getBytes))), + BitVector(s"$$18${CRLF}Test bulk".getBytes) + ) + ) + } - "appending a bit vector with multiple null bulk all complete" should { - "produce MoreThanOne with a list of the complete ones and an empty remainder" in { - val inputVector = BitVector("$-1\r\n$-1\r\n$-1\r\n$-1\r\n$-1\r\n$-1\r\n".getBytes) - EmptyFrame.append(inputVector.toByteBuffer) should be( - Right( - MoreThanOneFrame( - Vector.fill(6)(CompleteFrame(BitVector("$-1\r\n".getBytes()))), - BitVector.empty - ) - ) - ) - } - } + test( + "Appending to an empty Bulk frame a bit vector with multiple messages all complete gives MoreThanOne with a list of the complete ones and an empty remainder" + ) { + val inputVector = BitVector("$16\r\nTest bulk string\r\n$16\r\nTest bulk string\r\n$16\r\nTest bulk string\r\n".getBytes) + assertEquals( + EmptyFrame.append(inputVector.toByteBuffer), + MoreThanOneFrame( + Vector.fill(3)(CompleteFrame(BitVector("$16\r\nTest bulk string\r\n".getBytes()))), + BitVector.empty + ) + ) + } - "appending a bit vector with multiple null bulk with the last not complete" should { - "produce MoreThanOne with a list of the complete ones and a remainder with the incomplete bits" in { - val inputVector = BitVector("$-1\r\n$-1\r\n$-1\r\n$-1\r\n$".getBytes) - EmptyFrame.append(inputVector.toByteBuffer) should be( - Right( - MoreThanOneFrame( - Vector.fill(4)(CompleteFrame(BitVector("$-1\r\n".getBytes()))), - BitVector("$".getBytes()) - ) - ) - ) - } - } + test( + "Appending to an empty Bulk frame a bit vector with multiple messages with the last not complete gives MoreThanOne with a list of the complete ones and a remainder with the incomplete bits" + ) { + val inputVector = BitVector("$16\r\nTest bulk string\r\n$16\r\nTest bulk string\r\n$16\r\nTest bulk".getBytes) + assertEquals( + EmptyFrame.append(inputVector.toByteBuffer), + MoreThanOneFrame( + Vector.fill(2)(CompleteFrame(BitVector("$16\r\nTest bulk string\r\n".getBytes()))), + BitVector("$16\r\nTest bulk".getBytes()) + ) + ) + } - "appending a bit vector with multiple different messages with the last not complete" should { - "produce MoreThanOne with a list of the complete ones in the inverted order and a remainder with the incomplete bits" in { - val inputVector = BitVector( - "$18\r\nTest bulk string 1\r\n$18\r\nTest bulk string 2\r\n$18\r\nTest bulk string 3\r\n$18\r\nTest bulk string 4\r\n$18\r\nTest bulk".getBytes - ) - EmptyFrame.append(inputVector.toByteBuffer) should be( - Right( - MoreThanOneFrame( - Vector( - CompleteFrame(BitVector("$18\r\nTest bulk string 1\r\n".getBytes())), - CompleteFrame(BitVector("$18\r\nTest bulk string 2\r\n".getBytes())), - CompleteFrame(BitVector("$18\r\nTest bulk string 3\r\n".getBytes())), - CompleteFrame(BitVector("$18\r\nTest bulk string 4\r\n".getBytes())) - ), - BitVector("$18\r\nTest bulk".getBytes()) - ) - ) - ) - } + test( + "Appending to an empty Bulk frame a bit vector with multiple null bulk all complete gives MoreThanOne with a list of the complete ones and an empty remainder" + ) { + val inputVector = BitVector("$-1\r\n$-1\r\n$-1\r\n$-1\r\n$-1\r\n$-1\r\n".getBytes) + assertEquals( + EmptyFrame.append(inputVector.toByteBuffer), + MoreThanOneFrame( + Vector.fill(6)(CompleteFrame(BitVector("$-1\r\n".getBytes()))), + BitVector.empty + ) + ) + } - "produce MoreThanOne where the call to complete should give a vector with the complete ones in the original order" in { - val inputVector = BitVector( - "$18\r\nTest bulk string 1\r\n$18\r\nTest bulk string 2\r\n$18\r\nTest bulk string 3\r\n$18\r\nTest bulk string 4\r\n$18\r\nTest bulk".getBytes - ) - EmptyFrame.append(inputVector.toByteBuffer) onRight { - case r @ MoreThanOneFrame(_, _) => - r.complete shouldBe Vector( - CompleteFrame(BitVector("$18\r\nTest bulk string 1\r\n".getBytes())), - CompleteFrame(BitVector("$18\r\nTest bulk string 2\r\n".getBytes())), - CompleteFrame(BitVector("$18\r\nTest bulk string 3\r\n".getBytes())), - CompleteFrame(BitVector("$18\r\nTest bulk string 4\r\n".getBytes())) - ) - case _ => fail(s"expected a MoreThanOne type") - } - } - } + test( + "Appending to an empty Bulk frame a bit vector with multiple null bulk all complete gives MoreThanOne with a list of the complete ones and an empty remainder" + ) { + val inputVector = BitVector("$-1\r\n$-1\r\n$-1\r\n$-1\r\n$-1\r\n$-1\r\n".getBytes) + assertEquals( + EmptyFrame.append(inputVector.toByteBuffer), + MoreThanOneFrame( + Vector.fill(6)(CompleteFrame(BitVector("$-1\r\n".getBytes()))), + BitVector.empty + ) + ) } - "A non empty Bulk Frame" when { - "appending a bit vector that completes it" should { - "produce Complete with all the bits" in { - val nonEmptyFrame = IncompleteFrame(BitVector("$16\r\nTest bulk str".getBytes), 0) - val inputVector = BitVector("ing\r\n".getBytes) - val expected = BitVector("$16\r\nTest bulk string\r\n".getBytes) - nonEmptyFrame.append(inputVector.toByteBuffer) should be(Right(CompleteFrame(expected))) - } - } + test( + "Appending to an empty Bulk frame a bit vector with multiple null bulk with the last not complete gives MoreThanOne with a list of the complete ones and a remainder with the incomplete bits" + ) { + val inputVector = BitVector("$-1\r\n$-1\r\n$-1\r\n$-1\r\n$".getBytes) + assertEquals( + EmptyFrame.append(inputVector.toByteBuffer), + MoreThanOneFrame( + Vector.fill(4)(CompleteFrame(BitVector("$-1\r\n".getBytes()))), + BitVector("$".getBytes()) + ) + ) + } - "appending a bit vector that doesn't complete it" should { - "produce Incomplete with the correct partial and the correct missing count" in { - val nonEmptyFrame = IncompleteFrame(BitVector("$16\r\nTest bul".getBytes), 0) - val inputVector = BitVector("k str".getBytes) - val expected = BitVector("$16\r\nTest bulk str".getBytes) - nonEmptyFrame.append(inputVector.toByteBuffer) should be(Right(IncompleteFrame(expected, 40))) - } - } + test( + "Appending to an empty Bulk frame a bit vector with multiple different messages with the last not complete gives MoreThanOne with a list of the complete ones in the inverted order and a remainder with the incomplete bits" + ) { + val inputVector = BitVector( + "$18\r\nTest bulk string 1\r\n$18\r\nTest bulk string 2\r\n$18\r\nTest bulk string 3\r\n$18\r\nTest bulk string 4\r\n$18\r\nTest bulk".getBytes + ) + assertEquals( + EmptyFrame.append(inputVector.toByteBuffer), + MoreThanOneFrame( + Vector( + CompleteFrame(BitVector("$18\r\nTest bulk string 1\r\n".getBytes())), + CompleteFrame(BitVector("$18\r\nTest bulk string 2\r\n".getBytes())), + CompleteFrame(BitVector("$18\r\nTest bulk string 3\r\n".getBytes())), + CompleteFrame(BitVector("$18\r\nTest bulk string 4\r\n".getBytes())) + ), + BitVector("$18\r\nTest bulk".getBytes()) + ) + ) + } - "appending a bit vector with multiple messages all complete" should { - "produce MoreThanOne with a list of the complete ones and an empty remainder" in { - val nonEmptyFrame = IncompleteFrame(BitVector("$16\r\nTest bulk s".getBytes), 0) - val inputVector = BitVector("tring\r\n$16\r\nTest bulk string\r\n$16\r\nTest bulk string\r\n".getBytes) - nonEmptyFrame.append(inputVector.toByteBuffer) should be( - Right( - MoreThanOneFrame( - Vector.fill(3)(CompleteFrame(BitVector("$16\r\nTest bulk string\r\n".getBytes()))), - BitVector.empty - ) + test( + "Appending to an empty Bulk frame a bit vector with multiple different messages with the last not complete gives MoreThanOne where the call to complete should give a vector with the complete ones in the original order" + ) { + val inputVector = BitVector( + "$18\r\nTest bulk string 1\r\n$18\r\nTest bulk string 2\r\n$18\r\nTest bulk string 3\r\n$18\r\nTest bulk string 4\r\n$18\r\nTest bulk".getBytes + ) + EmptyFrame.append(inputVector.toByteBuffer) onRightAll { + case r @ MoreThanOneFrame(_, _) => + assertEquals( + r.complete, + Vector( + CompleteFrame(BitVector("$18\r\nTest bulk string 1\r\n".getBytes())), + CompleteFrame(BitVector("$18\r\nTest bulk string 2\r\n".getBytes())), + CompleteFrame(BitVector("$18\r\nTest bulk string 3\r\n".getBytes())), + CompleteFrame(BitVector("$18\r\nTest bulk string 4\r\n".getBytes())) ) ) - } + case _ => fail(s"expected a MoreThanOne type") } + } - "appending a bit vector with multiple messages with the last not complete" should { - "produce MoreThanOne with a list of the complete ones and a remainder with the incomplete bits" in { - val nonEmptyFrame = IncompleteFrame(BitVector("$16\r\nTest bulk s".getBytes), 0) - val inputVector = BitVector("tring\r\n$16\r\nTest bulk string\r\n$16\r\nTest bulk".getBytes) - nonEmptyFrame.append(inputVector.toByteBuffer) should be( - Right( - MoreThanOneFrame( - Vector.fill(2)(CompleteFrame(BitVector("$16\r\nTest bulk string\r\n".getBytes()))), - BitVector("$16\r\nTest bulk".getBytes()) - ) - ) - ) - } - } + test("Appending to a non empty Bulk frame a bit vector that completes it gives Complete with all the bits") { + val nonEmptyFrame = IncompleteFrame(BitVector("$16\r\nTest bulk str".getBytes), 0) + val inputVector = BitVector("ing\r\n".getBytes) + val expected = BitVector("$16\r\nTest bulk string\r\n".getBytes) + assertEquals(nonEmptyFrame.append(inputVector.toByteBuffer), CompleteFrame(expected)) + } - "appending a bit vector with multiple null bulk all complete" should { - "produce MoreThanOne with a list of the complete ones and an empty remainder" in { - val nonEmptyFrame = IncompleteFrame(BitVector("$-".getBytes), 0) - val inputVector = BitVector("1\r\n$-1\r\n$-1\r\n$-1\r\n$-1\r\n$-1\r\n".getBytes) - nonEmptyFrame.append(inputVector.toByteBuffer) should be( - Right( - MoreThanOneFrame( - Vector.fill(6)(CompleteFrame(BitVector("$-1\r\n".getBytes()))), - BitVector.empty - ) - ) - ) - } - } + test( + "Appending to a non empty Bulk frame a bit vector that doesn't complete it gives Incomplete with the correct partial and the correct missing count" + ) { + val nonEmptyFrame = IncompleteFrame(BitVector("$16\r\nTest bul".getBytes), 0) + val inputVector = BitVector("k str".getBytes) + val expected = BitVector("$16\r\nTest bulk str".getBytes) + assertEquals(nonEmptyFrame.append(inputVector.toByteBuffer), IncompleteFrame(expected, 40)) + } - "appending a bit vector with multiple null bulk with the last not complete" should { - "produce MoreThanOne with a list of the complete ones and a remainder with the incomplete bits" in { - val nonEmptyFrame = IncompleteFrame(BitVector("$-".getBytes), 0) - val inputVector = BitVector("1\r\n$-1\r\n$-1\r\n$-1\r\n$".getBytes) - nonEmptyFrame.append(inputVector.toByteBuffer) should be( - Right( - MoreThanOneFrame( - Vector.fill(4)(CompleteFrame(BitVector("$-1\r\n".getBytes()))), - BitVector("$".getBytes()) - ) - ) - ) - } - } + test( + "Appending to a non empty Bulk frame a bit vector with multiple messages all complete gives MoreThanOne with a list of the complete ones and an empty remainder" + ) { + val nonEmptyFrame = IncompleteFrame(BitVector("$16\r\nTest bulk s".getBytes), 0) + val inputVector = BitVector("tring\r\n$16\r\nTest bulk string\r\n$16\r\nTest bulk string\r\n".getBytes) + assertEquals( + nonEmptyFrame.append(inputVector.toByteBuffer), + MoreThanOneFrame( + Vector.fill(3)(CompleteFrame(BitVector("$16\r\nTest bulk string\r\n".getBytes()))), + BitVector.empty + ) + ) + } - "appending a bit vector with multiple different messages with the last not complete" should { - "produce MoreThanOne with a list of the complete ones in the inverted order and a remainder with the incomplete bits" in { - val nonEmptyFrame = IncompleteFrame(BitVector("$21\r\nTest bulk s".getBytes), 0) - val inputVector = BitVector( - "tring 1 11\r\n$17\r\nTest bulk string2\r\n$20\r\nTest bulk string 3 1\r\n$19\r\nTest bulk string 40\r\n$18\r\nTest bulk".getBytes - ) - nonEmptyFrame.append(inputVector.toByteBuffer) should be( - Right( - MoreThanOneFrame( - Vector( - CompleteFrame(BitVector("$21\r\nTest bulk string 1 11\r\n".getBytes())), - CompleteFrame(BitVector("$17\r\nTest bulk string2\r\n".getBytes())), - CompleteFrame(BitVector("$20\r\nTest bulk string 3 1\r\n".getBytes())), - CompleteFrame(BitVector("$19\r\nTest bulk string 40\r\n".getBytes())) - ), - BitVector("$18\r\nTest bulk".getBytes()) - ) - ) - ) - } + test( + "Appending to a non empty Bulk frame a bit vector with multiple messages with the last not complete gives MoreThanOne with a list of the complete ones and a remainder with the incomplete bits" + ) { + val nonEmptyFrame = IncompleteFrame(BitVector("$16\r\nTest bulk s".getBytes), 0) + val inputVector = BitVector("tring\r\n$16\r\nTest bulk string\r\n$16\r\nTest bulk".getBytes) + assertEquals( + nonEmptyFrame.append(inputVector.toByteBuffer), + MoreThanOneFrame( + Vector.fill(2)(CompleteFrame(BitVector("$16\r\nTest bulk string\r\n".getBytes()))), + BitVector("$16\r\nTest bulk".getBytes()) + ) + ) + } - "produce MoreThanOne where the call to complete should give a vector with the complete ones in the original order" in { - val nonEmptyFrame = IncompleteFrame(BitVector("$21\r\nTest bulk s".getBytes), 0) - val inputVector = BitVector( - "tring 1 11\r\n$17\r\nTest bulk string2\r\n$20\r\nTest bulk string 3 1\r\n$19\r\nTest bulk string 40\r\n$18\r\nTest bulk".getBytes + test( + "Appending to a non empty Bulk frame a bit vector with multiple null bulk all complete gives MoreThanOne with a list of the complete ones and an empty remainder" + ) { + val nonEmptyFrame = IncompleteFrame(BitVector("$-".getBytes), 0) + val inputVector = BitVector("1\r\n$-1\r\n$-1\r\n$-1\r\n$-1\r\n$-1\r\n".getBytes) + assertEquals( + nonEmptyFrame.append(inputVector.toByteBuffer), + MoreThanOneFrame( + Vector.fill(6)(CompleteFrame(BitVector("$-1\r\n".getBytes()))), + BitVector.empty + ) + ) + } + + test( + "Appending to a non empty Bulk frame a bit vector with multiple null bulk with the last not complete gives MoreThanOne with a list of the complete ones and a remainder with the incomplete bits" + ) { + val nonEmptyFrame = IncompleteFrame(BitVector("$-".getBytes), 0) + val inputVector = BitVector("1\r\n$-1\r\n$-1\r\n$-1\r\n$".getBytes) + assertEquals( + nonEmptyFrame.append(inputVector.toByteBuffer), + MoreThanOneFrame( + Vector.fill(4)(CompleteFrame(BitVector("$-1\r\n".getBytes()))), + BitVector("$".getBytes()) + ) + ) + } + + test( + "Appending to a non empty Bulk frame a bit vector with multiple different messages with the last not complete gives MoreThanOne with a list of the complete ones in the inverted order and a remainder with the incomplete bits" + ) { + val nonEmptyFrame = IncompleteFrame(BitVector("$21\r\nTest bulk s".getBytes), 0) + val inputVector = BitVector( + "tring 1 11\r\n$17\r\nTest bulk string2\r\n$20\r\nTest bulk string 3 1\r\n$19\r\nTest bulk string 40\r\n$18\r\nTest bulk".getBytes + ) + assertEquals( + nonEmptyFrame.append(inputVector.toByteBuffer), + MoreThanOneFrame( + Vector( + CompleteFrame(BitVector("$21\r\nTest bulk string 1 11\r\n".getBytes())), + CompleteFrame(BitVector("$17\r\nTest bulk string2\r\n".getBytes())), + CompleteFrame(BitVector("$20\r\nTest bulk string 3 1\r\n".getBytes())), + CompleteFrame(BitVector("$19\r\nTest bulk string 40\r\n".getBytes())) + ), + BitVector("$18\r\nTest bulk".getBytes()) + ) + ) + } + + test( + "Appending to a non empty Bulk frame a bit vector with multiple different messages with the last not complete gives MoreThanOne where the call to complete should give a vector with the complete ones in the original order" + ) { + val nonEmptyFrame = IncompleteFrame(BitVector("$21\r\nTest bulk s".getBytes), 0) + val inputVector = BitVector( + "tring 1 11\r\n$17\r\nTest bulk string2\r\n$20\r\nTest bulk string 3 1\r\n$19\r\nTest bulk string 40\r\n$18\r\nTest bulk".getBytes + ) + nonEmptyFrame.append(inputVector.toByteBuffer) onRightAll { + case r @ MoreThanOneFrame(_, _) => + assertEquals( + r.complete, + Vector( + CompleteFrame(BitVector("$21\r\nTest bulk string 1 11\r\n".getBytes())), + CompleteFrame(BitVector("$17\r\nTest bulk string2\r\n".getBytes())), + CompleteFrame(BitVector("$20\r\nTest bulk string 3 1\r\n".getBytes())), + CompleteFrame(BitVector("$19\r\nTest bulk string 40\r\n".getBytes())) + ) ) - nonEmptyFrame.append(inputVector.toByteBuffer) onRight { - case r @ MoreThanOneFrame(_, _) => - r.complete shouldBe Vector( - CompleteFrame(BitVector("$21\r\nTest bulk string 1 11\r\n".getBytes())), - CompleteFrame(BitVector("$17\r\nTest bulk string2\r\n".getBytes())), - CompleteFrame(BitVector("$20\r\nTest bulk string 3 1\r\n".getBytes())), - CompleteFrame(BitVector("$19\r\nTest bulk string 40\r\n".getBytes())) - ) - case _ => fail(s"expected a MoreThanOne type") - } - } + case _ => fail(s"expected a MoreThanOne type") } } } diff --git a/core/src/test/scala/laserdisc/protocol/RESPFrameMixedSpec.scala b/core/src/test/scala/laserdisc/protocol/RESPFrameMixedSpec.scala index 2f05f882..efdf4ee1 100644 --- a/core/src/test/scala/laserdisc/protocol/RESPFrameMixedSpec.scala +++ b/core/src/test/scala/laserdisc/protocol/RESPFrameMixedSpec.scala @@ -1,185 +1,87 @@ package laserdisc package protocol -import eu.timepit.refined.types.string.NonEmptyString -import org.scalacheck.Gen.chooseNum -import org.scalacheck.{Arbitrary, Gen} +import org.scalacheck.Prop.forAll import scodec.bits.BitVector -import scala.Long.{MaxValue, MinValue} +final class RESPFrameMixedSpec extends RESPFrameFixture { -final class RESPFrameMixedSpec extends HighPriorityGenerators { - "A non empty mixed Frame" when { - "appending a bit vector composed of a complete sequence of integers, simple strings, bulk strings and errors" should { - "produce MoreThanOne with a list of all the complete items" in { - val nonEmptyFrame = IncompleteFrame(BitVector("$16\r\nTest bulk str".getBytes), 0) - val inputVector = BitVector( - "ing\r\n+OK\r\n$0\r\n\r\n+Another simple string\r\n*3\r\n$16\r\nTest bulk string\r\n:100\r\n+A simple string\r\n-Possible error message\r\n*0\r\n:1\r\n:2\r\n*2\r\n$8\r\nAnother1\r\n-An error\r\n:177\r\n+Another simple string\r\n$21\r\nTest bulk string 1 11\r\n*5\r\n$16\r\nTest bulk string\r\n:13\r\n-1234 An error with numbers\r\n:100\r\n+A simple string\r\n-And an error message\r\n".getBytes + test( + "Appending to a non empty mixed frame a bit vector composed of a complete sequence of integers, simple strings, bulk strings and errors gives MoreThanOne with a list of all the complete items" + ) { + val nonEmptyFrame = IncompleteFrame(BitVector("$16\r\nTest bulk str".getBytes), 0) + val inputVector = BitVector( + "ing\r\n+OK\r\n$0\r\n\r\n+Another simple string\r\n*3\r\n$16\r\nTest bulk string\r\n:100\r\n+A simple string\r\n-Possible error message\r\n*0\r\n:1\r\n:2\r\n*2\r\n$8\r\nAnother1\r\n-An error\r\n:177\r\n+Another simple string\r\n$21\r\nTest bulk string 1 11\r\n*5\r\n$16\r\nTest bulk string\r\n:13\r\n-1234 An error with numbers\r\n:100\r\n+A simple string\r\n-And an error message\r\n".getBytes + ) + nonEmptyFrame.append(inputVector.toByteBuffer) onRightAll { + case r @ MoreThanOneFrame(_, _) => + assertEquals( + r.complete, + Vector( + CompleteFrame(BitVector("$16\r\nTest bulk string\r\n".getBytes())), + CompleteFrame(BitVector("+OK\r\n".getBytes())), + CompleteFrame(BitVector("$0\r\n\r\n".getBytes())), + CompleteFrame(BitVector("+Another simple string\r\n".getBytes())), + CompleteFrame(BitVector("*3\r\n$16\r\nTest bulk string\r\n:100\r\n+A simple string\r\n".getBytes)), + CompleteFrame(BitVector("-Possible error message\r\n".getBytes())), + CompleteFrame(BitVector("*0\r\n".getBytes())), + CompleteFrame(BitVector(":1\r\n".getBytes())), + CompleteFrame(BitVector(":2\r\n".getBytes())), + CompleteFrame(BitVector("*2\r\n$8\r\nAnother1\r\n-An error\r\n".getBytes)), + CompleteFrame(BitVector(":177\r\n".getBytes())), + CompleteFrame(BitVector("+Another simple string\r\n".getBytes())), + CompleteFrame(BitVector("$21\r\nTest bulk string 1 11\r\n".getBytes())), + CompleteFrame( + BitVector( + "*5\r\n$16\r\nTest bulk string\r\n:13\r\n-1234 An error with numbers\r\n:100\r\n+A simple string\r\n".getBytes + ) + ), + CompleteFrame(BitVector("-And an error message\r\n".getBytes())) + ) ) - nonEmptyFrame.append(inputVector.toByteBuffer) onRight { - case r @ MoreThanOneFrame(_, _) => - r.complete shouldBe Vector( - CompleteFrame(BitVector("$16\r\nTest bulk string\r\n".getBytes())), - CompleteFrame(BitVector("+OK\r\n".getBytes())), - CompleteFrame(BitVector("$0\r\n\r\n".getBytes())), - CompleteFrame(BitVector("+Another simple string\r\n".getBytes())), - CompleteFrame(BitVector("*3\r\n$16\r\nTest bulk string\r\n:100\r\n+A simple string\r\n".getBytes)), - CompleteFrame(BitVector("-Possible error message\r\n".getBytes())), - CompleteFrame(BitVector("*0\r\n".getBytes())), - CompleteFrame(BitVector(":1\r\n".getBytes())), - CompleteFrame(BitVector(":2\r\n".getBytes())), - CompleteFrame(BitVector("*2\r\n$8\r\nAnother1\r\n-An error\r\n".getBytes)), - CompleteFrame(BitVector(":177\r\n".getBytes())), - CompleteFrame(BitVector("+Another simple string\r\n".getBytes())), - CompleteFrame(BitVector("$21\r\nTest bulk string 1 11\r\n".getBytes())), - CompleteFrame( - BitVector( - "*5\r\n$16\r\nTest bulk string\r\n:13\r\n-1234 An error with numbers\r\n:100\r\n+A simple string\r\n".getBytes - ) - ), - CompleteFrame(BitVector("-And an error message\r\n".getBytes())) - ) - case _ => fail(s"expected a MoreThanOne type") - } - } + case _ => fail(s"expected a MoreThanOne type") } + } - "appending a bit vector composed of sequence of integers, simple strings, bulk strings and errors that are not complete" should { - "produce MoreThanOne with a list of all the complete items plus the remainder" in { - val nonEmptyFrame = IncompleteFrame(BitVector("$16\r\nTest bulk str".getBytes), 0) - val inputVector = BitVector( - "ing\r\n+OK\r\n+Another simple string\r\n-Possible error message\r\n:1\r\n:2\r\n:177\r\n+Another simple string\r\n$21\r\nTest bulk string 1 11\r\n-And an error message\r\n".getBytes + test( + "Appending to a non empty mixed frame a bit vector composed of sequence of integers, simple strings, bulk strings and errors that are not complete gives MoreThanOne with a list of all the complete items plus the remainder" + ) { + val nonEmptyFrame = IncompleteFrame(BitVector("$16\r\nTest bulk str".getBytes), 0) + val inputVector = BitVector( + "ing\r\n+OK\r\n+Another simple string\r\n-Possible error message\r\n:1\r\n:2\r\n:177\r\n+Another simple string\r\n$21\r\nTest bulk string 1 11\r\n-And an error message\r\n".getBytes + ) + nonEmptyFrame.append(inputVector.toByteBuffer) onRightAll { + case r @ MoreThanOneFrame(_, _) => + assertEquals( + r.complete, + Vector( + CompleteFrame(BitVector("$16\r\nTest bulk string\r\n".getBytes())), + CompleteFrame(BitVector("+OK\r\n".getBytes())), + CompleteFrame(BitVector("+Another simple string\r\n".getBytes())), + CompleteFrame(BitVector("-Possible error message\r\n".getBytes())), + CompleteFrame(BitVector(":1\r\n".getBytes())), + CompleteFrame(BitVector(":2\r\n".getBytes())), + CompleteFrame(BitVector(":177\r\n".getBytes())), + CompleteFrame(BitVector("+Another simple string\r\n".getBytes())), + CompleteFrame(BitVector("$21\r\nTest bulk string 1 11\r\n".getBytes())), + CompleteFrame(BitVector("-And an error message\r\n".getBytes())) + ) ) - nonEmptyFrame.append(inputVector.toByteBuffer) onRight { - case r @ MoreThanOneFrame(_, _) => - r.complete shouldBe Vector( - CompleteFrame(BitVector("$16\r\nTest bulk string\r\n".getBytes())), - CompleteFrame(BitVector("+OK\r\n".getBytes())), - CompleteFrame(BitVector("+Another simple string\r\n".getBytes())), - CompleteFrame(BitVector("-Possible error message\r\n".getBytes())), - CompleteFrame(BitVector(":1\r\n".getBytes())), - CompleteFrame(BitVector(":2\r\n".getBytes())), - CompleteFrame(BitVector(":177\r\n".getBytes())), - CompleteFrame(BitVector("+Another simple string\r\n".getBytes())), - CompleteFrame(BitVector("$21\r\nTest bulk string 1 11\r\n".getBytes())), - CompleteFrame(BitVector("-And an error message\r\n".getBytes())) - ) - case _ => fail(s"expected a MoreThanOne type") - } - } + case _ => fail(s"expected a MoreThanOne type") } } - "An empty Frame" when { - "appending a random sequence of complete messages" should { - "produce MoreThanOne with all the complete items" in { - forAll { testSet: OneOrMore[ProtocolEncoded] => - val vector = BitVector(testSet.value.map(_.encoded).mkString.getBytes) + property("Appending to an empty frame a random sequence of complete messages gives MoreThanOne with all the complete items") { + forAll { testSet: OneOrMore[ProtocolEncoded] => + val vector = BitVector(testSet.value.map(_.encoded).mkString.getBytes) - EmptyFrame.append(vector.toByteBuffer) onRight { - case MoreThanOneFrame(complete, remainder) => - complete.size shouldBe testSet.value.size - remainder should be(empty) - case CompleteFrame(_) => succeed - case other => fail(s"expected a MoreThanOne type. Was $other") - } - } + EmptyFrame.append(vector.toByteBuffer) onRightAll { + case MoreThanOneFrame(complete, remainder) => + assertEquals(complete.size, testSet.value.size) + assert(remainder.isEmpty) + case CompleteFrame(_) => succeed + case other => fail(s"expected a MoreThanOne type. Was $other") } } } } - -private[protocol] trait HighPriorityGenerators extends LowPriorityGenerators { - - private[this] def oneOrMoreProtocol(gen: Gen[ProtocolEncoded]): Gen[OneOrMore[ProtocolEncoded]] = - Gen.chooseNum(1, Math.min(generatorDrivenConfig.sizeRange, 300)) flatMap (Gen.listOfN(_, gen)) map OneOrMore.unsafeFrom - - private[this] def listProtocol(gen: Gen[ProtocolEncoded]): Gen[List[ProtocolEncoded]] = - Gen.chooseNum(1, 20) flatMap (Gen.listOfN(_, gen)) - - private[this] final def noArrEncoded( - implicit bulk: Gen[BulkEncoded], - num: Gen[NumEncoded], - str: Gen[StrEncoded], - err: Gen[ErrEncoded], - emptyBulk: Gen[EmptyBulkEncoded], - nullBulk: Gen[NullBulkEncoded], - emptyArr: Gen[EmptyArrEncoded], - nullArr: Gen[NullArrEncoded] - ): Gen[ProtocolEncoded] = - Gen.frequency( - 10 -> bulk, - 10 -> num, - 10 -> str, - 10 -> err, - 3 -> emptyBulk, - 3 -> nullBulk, - 3 -> emptyArr, - 3 -> nullArr - ) - - private[this] final val noArrArrEncoded: Gen[ArrEncoded] = - listProtocol(noArrEncoded) map ArrEncoded.apply - - private[this] final def oneLevelArrEncoded(arrGen: Gen[ArrEncoded]): Gen[ArrEncoded] = - listProtocol( - Gen.frequency( - 20 -> noArrEncoded, - 3 -> arrGen - ) - ) map ArrEncoded.apply - - private[this] final def xLevelsNestedArrEncoded(x: Int): Gen[ArrEncoded] = { - - @scala.annotation.tailrec - def loop(left: Int, soFar: Gen[ArrEncoded]): Gen[ArrEncoded] = - if (left > 0) loop(left - 1, oneLevelArrEncoded(soFar)) - else soFar - - loop(x, oneLevelArrEncoded(noArrArrEncoded)) - } - - private[this] final val protocolEncoded: Gen[ProtocolEncoded] = - Gen.frequency( - 50 -> noArrEncoded, - 25 -> oneLevelArrEncoded(noArrArrEncoded), - 25 -> noArrArrEncoded, - 10 -> xLevelsNestedArrEncoded(5) - ) - - private[protocol] implicit final val oneOrMoreProtocols: Arbitrary[OneOrMore[ProtocolEncoded]] = - Arbitrary(oneOrMoreProtocol(protocolEncoded)) -} - -private[protocol] trait LowPriorityGenerators extends BaseSpec { - protected implicit final def string: Gen[String] = Gen.listOf(utf8BMPCharGen) map (_.mkString) - - protected implicit final def nonEmptyString(implicit strGen: Gen[String]): Gen[NonEmptyString] = - strGen.filter(_.nonEmpty) map NonEmptyString.unsafeFrom - - protected implicit final def bulkEncoded(implicit nesGen: Gen[NonEmptyString]): Gen[BulkEncoded] = - nesGen map BulkEncoded.apply - - protected implicit final val long: Gen[Long] = chooseNum(MinValue, MaxValue) - - protected implicit final def numEncoded(implicit lnGen: Gen[Long]): Gen[NumEncoded] = - lnGen map NumEncoded.apply - - protected implicit final def strEncoded(implicit sGen: Gen[String]): Gen[StrEncoded] = - sGen.map(s => s.replace(CRLF, "")).filter(_.nonEmpty) map StrEncoded.apply - - protected implicit final def errEncoded(implicit nesGen: Gen[NonEmptyString]): Gen[ErrEncoded] = - nesGen map ErrEncoded.apply - - protected implicit final val emptyBulkEncoded: Gen[EmptyBulkEncoded] = - Gen.const(EmptyBulkEncoded()) - - protected implicit final val nullBulkEncoded: Gen[NullBulkEncoded] = - Gen.const(NullBulkEncoded()) - - protected implicit final val emptyArrEncoded: Gen[EmptyArrEncoded] = - Gen.const(EmptyArrEncoded()) - - protected implicit final val nullArrEncoded: Gen[NullArrEncoded] = - Gen.const(NullArrEncoded()) -} diff --git a/core/src/test/scala/laserdisc/protocol/RESPFrameNumSpec.scala b/core/src/test/scala/laserdisc/protocol/RESPFrameNumSpec.scala deleted file mode 100644 index 4d251d39..00000000 --- a/core/src/test/scala/laserdisc/protocol/RESPFrameNumSpec.scala +++ /dev/null @@ -1,25 +0,0 @@ -package laserdisc -package protocol - -import scodec.bits.BitVector - -final class RESPFrameNumSpec extends BaseSpec { - "An empty Bulk Frame" when { - - "appending a bit vector with a number split in two chunks" should { - "produce MoreThanOneFrame with a follow up partial bulk string" in { - val incompleteFirstInput = BitVector(":2".getBytes) - val secondInput = BitVector(s"1$CRLF$$18${CRLF}Test bulk".getBytes) - - EmptyFrame.append(incompleteFirstInput.toByteBuffer).flatMap(_.append(secondInput.toByteBuffer)) should be( - Right( - MoreThanOneFrame( - Vector(CompleteFrame(BitVector(s":21$CRLF".getBytes))), - BitVector(s"$$18${CRLF}Test bulk".getBytes) - ) - ) - ) - } - } - } -} diff --git a/core/src/test/scala/laserdisc/protocol/RESPFunctionsSpec.scala b/core/src/test/scala/laserdisc/protocol/RESPFunctionsSpec.scala index c70fae88..6aa430d2 100644 --- a/core/src/test/scala/laserdisc/protocol/RESPFunctionsSpec.scala +++ b/core/src/test/scala/laserdisc/protocol/RESPFunctionsSpec.scala @@ -1,84 +1,66 @@ package laserdisc package protocol -import BitVectorDecoding.{Complete, CompleteWithRemainder, Incomplete, MissingBits} +import laserdisc.protocol.BitVectorDecoding.{Complete, CompleteWithRemainder, Incomplete, MissingBits} import scodec.bits.BitVector final class RESPFunctionsSpec extends BaseSpec { - "A RESP codec" when { - "checking the state of a bit vector with the size prefix not complete" should { - "produce IncompleteVector" in { - RESP.stateOf(BitVector("$2362".getBytes)) should be(Right(Incomplete)) - } - } - "checking the state of a bit vector with only the data type selector" should { - "produce IncompleteVector" in { - RESP.stateOf(BitVector("$".getBytes)) should be(Right(Incomplete)) - } - } + test("A RESP codec checking the state of a bit vector with the size prefix not complete gives IncompleteVector") { + assertEquals(RESP.stateOf(BitVector("$2362".getBytes)), Incomplete) + } - "checking the state of a bit vector that's complete" should { - "produce CompleteVector" in { - RESP.stateOf(BitVector("$16\r\nTest bulk string\r\n".getBytes)) should be(Right(Complete)) - } - } + test("A RESP codec checking the state of a bit vector with only the data type selector gives IncompleteVector") { + assertEquals(RESP.stateOf(BitVector("$".getBytes)), Incomplete) + } - "checking the state of a bit vector whit the size prefix complete and an incomplete payload" should { - "produce MissingBits with the correct number of bits missing" in { - RESP.stateOf(BitVector("$40\r\nIncomplete test bulk string".getBytes)) should be(Right(MissingBits(120))) - } - } + test("A RESP codec checking the state of a bit vector that's complete gives CompleteVector") { + assertEquals(RESP.stateOf(BitVector("$16\r\nTest bulk string\r\n".getBytes)), Complete) + } - "checking the state of a bit vector that represents an empty bulk" should { - "produce CompleteVector" in { - RESP.stateOf(BitVector("$-1\r\n".getBytes)) should be(Right(Complete)) - } - } + test( + "A RESP codec checking the state of a bit vector with the size prefix complete and an incomplete payload gives MissingBits with the correct number of bits missing" + ) { + assertEquals(RESP.stateOf(BitVector("$40\r\nIncomplete test bulk string".getBytes)), MissingBits(120)) + } - "checking the state of an incomplete bit vector that represents an empty bulk" should { - "produce MissingBits" in { - RESP.stateOf(BitVector("$-".getBytes)) should be(Right(Incomplete)) - } - } + test("A RESP codec checking the state of a bit vector that represents an empty bulk gives CompleteVector") { + assertEquals(RESP.stateOf(BitVector("$-1\r\n".getBytes)), Complete) + } - "checking the state of a bulk bit vector that contains one message complete and one not complete" should { - "produce CompleteWithRemainder" in { - RESP.stateOf(BitVector("$16\r\nTest bulk string\r\n$16\r\nTest bulk".getBytes)) should be( - Right( - CompleteWithRemainder( - BitVector("$16\r\nTest bulk string\r\n".toCharArray.map(_.toByte)), - BitVector("$16\r\nTest bulk".toCharArray.map(_.toByte)) - ) - ) - ) - } - } + test("A RESP codec checking the state of an incomplete bit vector that represents an empty bulk gives MissingBits") { + assertEquals(RESP.stateOf(BitVector("$-".getBytes)), Incomplete) + } + + test( + "A RESP codec checking the state of a bulk bit vector that contains one message complete and one not complete gives CompleteWithRemainder" + ) { + assertEquals( + RESP.stateOf(BitVector("$16\r\nTest bulk string\r\n$16\r\nTest bulk".getBytes)), + CompleteWithRemainder( + BitVector("$16\r\nTest bulk string\r\n".toCharArray.map(_.toByte)), + BitVector("$16\r\nTest bulk".toCharArray.map(_.toByte)) + ) + ) + } - "checking the state of a bulk bit vector that contains more than one complete messages" should { - "produce CompleteWithRemainder" in { - RESP.stateOf(BitVector("$16\r\nTest bulk string\r\n$16\r\nTest bulk string\r\n$16\r\nTest bulk string\r\n".getBytes)) should be( - Right( - CompleteWithRemainder( - BitVector("$16\r\nTest bulk string\r\n".toCharArray.map(_.toByte)), - BitVector("$16\r\nTest bulk string\r\n$16\r\nTest bulk string\r\n".toCharArray.map(_.toByte)) - ) - ) - ) - } - } + test("A RESP codec checking the state of a bulk bit vector that contains more than one complete messages gives CompleteWithRemainder") { + assertEquals( + RESP.stateOf(BitVector("$16\r\nTest bulk string\r\n$16\r\nTest bulk string\r\n$16\r\nTest bulk string\r\n".getBytes)), + CompleteWithRemainder( + BitVector("$16\r\nTest bulk string\r\n".toCharArray.map(_.toByte)), + BitVector("$16\r\nTest bulk string\r\n$16\r\nTest bulk string\r\n".toCharArray.map(_.toByte)) + ) + ) + } - "checking the state of a bulk bit vector that contains more than one null message" should { - "produce CompleteWithRemainder" in { - RESP.stateOf(BitVector("$-1\r\n$-1\r\n$-1\r\n".getBytes)) should be( - Right( - CompleteWithRemainder( - BitVector("$-1\r\n".toCharArray.map(_.toByte)), - BitVector("$-1\r\n$-1\r\n".toCharArray.map(_.toByte)) - ) - ) - ) - } - } + test("A RESP codec checking the state of a bulk bit vector that contains more than one null message gives CompleteWithRemainder") { + assertEquals( + RESP.stateOf(BitVector("$-1\r\n$-1\r\n$-1\r\n".getBytes)), + CompleteWithRemainder( + BitVector("$-1\r\n".toCharArray.map(_.toByte)), + BitVector("$-1\r\n$-1\r\n".toCharArray.map(_.toByte)) + ) + ) } } diff --git a/core/src/test/scala/laserdisc/protocol/StringPSpec.scala b/core/src/test/scala/laserdisc/protocol/StringPSpec.scala index 87c8c171..6759f26c 100644 --- a/core/src/test/scala/laserdisc/protocol/StringPSpec.scala +++ b/core/src/test/scala/laserdisc/protocol/StringPSpec.scala @@ -1,24 +1,22 @@ package laserdisc package protocol -final class StringPSpec extends BaseSpec { +final class StringPSpec extends BaseSpec with EitherSyntax { import laserdisc.auto._ - "The String protocol" when { - "decoding the wrong type" should { - "give details about the decoding error" in { - val correct = strings.set("a", 23) - correct.decode(Arr(Bulk("wrong type"))) onLeft { e => e.getMessage shouldBe "RESP type(s) did not match: Arr(Bulk(wrong type))" } - } - } + test("The String protocol decoding the wrong type gives details about the decoding error") { + val correct = strings.set("a", 23) + assertLeftEquals( + correct.decode(Arr(Bulk("wrong type"))) leftMap (_.getMessage), + "RESP type(s) did not match: Arr(Bulk(wrong type))" + ) + } - "decoding the correct type with the wrong encoding" should { - "give details about the decoding error" in { - val correct = strings.set("a", 23) - correct.decode(Str("wrong")) onLeft { e => - e.getMessage shouldBe "RESP type(s) of Str(wrong) matched but failed to deserialize correctly with error Read Error: expected Str(OK) but was Str(wrong)" - } - } - } + test("The String protocol decoding the correct type with the wrong encoding gives details about the decoding error") { + val correct = strings.set("a", 23) + assertLeftEquals( + correct.decode(Str("wrong")) leftMap (_.getMessage), + "RESP type(s) of Str(wrong) matched but failed to deserialize correctly with error Read Error: expected Str(OK) but was Str(wrong)" + ) } } diff --git a/core/src/test/scala/laserdisc/refined/types/ConnectionNameSuite.scala b/core/src/test/scala/laserdisc/refined/types/ConnectionNameSuite.scala new file mode 100644 index 00000000..9d6fb68a --- /dev/null +++ b/core/src/test/scala/laserdisc/refined/types/ConnectionNameSuite.scala @@ -0,0 +1,42 @@ +package laserdisc +package refined.types + +import org.scalacheck.Prop.forAll + +final class ConnectionNameSuite extends BaseSpec { + test("ConnectionName fails to compile given an empty string") { + assertNoDiff( + compileErrors("""ConnectionName("")"""), + """|error: Left predicate of (!isEmpty() && ()) failed: Predicate isEmpty() did not fail. + |ConnectionName("") + | ^ + |""".stripMargin + ) + } + + test("ConnectionName fails to compile given a space") { + assertNoDiff( + compileErrors("""ConnectionName(" ")"""), + """|error: Right predicate of (!isEmpty( ) && ((!isWhitespace(' ') && !isControl(' ')))) failed: Predicate failed: ((!isWhitespace(' ') && !isControl(' '))). + |ConnectionName(" ") + | ^ + |""".stripMargin + ) + } + + property("ConnectionName fails at runtime provided non literal cases of strings that contain spaces") { + forAll(stringsWithSpacesGen.filterNot(connectionNameIsValid)) { s => + intercept[IllegalArgumentException](ConnectionName.unsafeFrom(s)) + } + } + + test("ConnectionName compiles given non empty String with no spaces") { + ConnectionName("a") + } + + property("ConnectionName refines correctly provided non literal cases of non empty strings with no spaces") { + forAll(connectionNameGen) { s => + ConnectionName.from(s) onRight (_.value == s) + } + } +} diff --git a/core/src/test/scala/laserdisc/refined/types/DbIndexSuite.scala b/core/src/test/scala/laserdisc/refined/types/DbIndexSuite.scala new file mode 100644 index 00000000..308f2105 --- /dev/null +++ b/core/src/test/scala/laserdisc/refined/types/DbIndexSuite.scala @@ -0,0 +1,42 @@ +package laserdisc +package refined.types + +import org.scalacheck.Prop._ + +final class DbIndexSuite extends BaseSpec { + test("DbIndex fail to compile given out of range Int (< 0)") { + assertNoDiff( + compileErrors("DbIndex(-1)"), + """|error: Left predicate of (!(-1 < 0) && !(-1 > 15)) failed: Predicate (-1 < 0) did not fail. + |DbIndex(-1) + | ^ + |""".stripMargin + ) + } + + test("DbIndex fail to compile given out of range Int (> 15)") { + assertNoDiff( + compileErrors("DbIndex(16)"), + """|error: Right predicate of (!(16 < 0) && !(16 > 15)) failed: Predicate (16 > 15) did not fail. + |DbIndex(16) + | ^ + |""".stripMargin + ) + } + + property("DbIndex fails at runtime provided non literal cases of out of range Ints") { + forAll(ints.filterNot(dbIndexIsValid)) { i => + intercept[IllegalArgumentException](DbIndex.unsafeFrom(i)) + } + } + + test("DbIndex compiles given in range Int") { + DbIndex(0) + } + + property("DbIndex refines correctly provided non literal cases of in range Ints") { + forAll(dbIndexGen) { i => + DbIndex.from(i) onRight (_.value == i) + } + } +} diff --git a/core/src/test/scala/laserdisc/refined/types/GeoHashSuite.scala b/core/src/test/scala/laserdisc/refined/types/GeoHashSuite.scala new file mode 100644 index 00000000..c8397887 --- /dev/null +++ b/core/src/test/scala/laserdisc/refined/types/GeoHashSuite.scala @@ -0,0 +1,62 @@ +package laserdisc +package refined.types + +import org.scalacheck.Prop.forAll + +final class GeoHashSuite extends BaseSpec { + test("GeoHash fails to compile given a non conformant String (length < 11)") { + assertNoDiff( + compileErrors("""GeoHash("abcdefghij")"""), + """|error: Predicate failed: "abcdefghij".matches("[a-z0-9]{11}"). + |GeoHash("abcdefghij") + | ^ + |""".stripMargin + ) + } + + test("GeoHash fails to compile given a non conformant String (length > 11)") { + assertNoDiff( + compileErrors("""GeoHash("abcdefghijkl")"""), + """|error: Predicate failed: "abcdefghijkl".matches("[a-z0-9]{11}"). + |GeoHash("abcdefghijkl") + | ^ + |""".stripMargin + ) + } + + test("GeoHash fails to compile given a non conformant String (uppercase)") { + assertNoDiff( + compileErrors("""GeoHash("abCdefghijk")"""), + """|error: Predicate failed: "abCdefghijk".matches("[a-z0-9]{11}"). + |GeoHash("abCdefghijk") + | ^ + |""".stripMargin + ) + } + + test("GeoHash fails to compile given a non conformant String (invalid chars)") { + assertNoDiff( + compileErrors("""GeoHash("abcd&fghijk")"""), + """|error: Predicate failed: "abcd&fghijk".matches("[a-z0-9]{11}"). + |GeoHash("abcd&fghijk") + | ^ + |""".stripMargin + ) + } + + property("GeoHash fails at runtime provided non literal cases of non conformant Strings") { + forAll(strings.filterNot(geoHashIsValid)) { s => + intercept[IllegalArgumentException](GeoHash.unsafeFrom(s)) + } + } + + test("GeoHash compiles given conformant String") { + GeoHash("abcd3fgh1jk") + } + + property("GeoHash refines correctly provided non literal cases of conformant Strings") { + forAll(geoHashGen) { s => + GeoHash.from(s) onRight (_.value == s) + } + } +} diff --git a/core/src/test/scala/laserdisc/refined/types/GlobPatternSuite.scala b/core/src/test/scala/laserdisc/refined/types/GlobPatternSuite.scala new file mode 100644 index 00000000..7db27918 --- /dev/null +++ b/core/src/test/scala/laserdisc/refined/types/GlobPatternSuite.scala @@ -0,0 +1,43 @@ +package laserdisc +package refined.types + +import org.scalacheck.Prop.forAll + +final class GlobPatternSuite extends BaseSpec { + test("GlobPattern fails to compile given an empty String") { + assertNoDiff( + compileErrors("""GlobPattern("")"""), + """|error: Predicate failed: "".matches("(\[?[\w\*\?]+\]?)+"). + |GlobPattern("") + | ^ + |""".stripMargin + ) + } + + test("GlobPattern fails to compile given a non conformant String") { + assertNoDiff( + compileErrors("""GlobPattern("!")"""), + """|error: Predicate failed: "!".matches("(\[?[\w\*\?]+\]?)+"). + |GlobPattern("!") + | ^ + |""".stripMargin + ) + } + + property("GlobPattern fails at runtime provided non literal cases of non conformant Strings") { + forAll(strings.filterNot(globPatternIsValid)) { s => + intercept[IllegalArgumentException](GlobPattern.unsafeFrom(s)) + } + } + + test("GlobPattern compiles given conformant String") { + GlobPattern("abc*fg?1jk") + GlobPattern("a[bc*]fg?1jk") + } + + property("GlobPattern refines correctly provided non literal cases of conformant Strings") { + forAll(globPatternGen) { s => + GlobPattern.from(s) onRight (_.value == s) + } + } +} diff --git a/core/src/test/scala/laserdisc/refined/types/HostSuite.scala b/core/src/test/scala/laserdisc/refined/types/HostSuite.scala new file mode 100644 index 00000000..bedd837c --- /dev/null +++ b/core/src/test/scala/laserdisc/refined/types/HostSuite.scala @@ -0,0 +1,95 @@ +package laserdisc +package refined.types + +final class HostSuite extends BaseSpec { + test("Host fails to compile given an empty String") { + assert(!compileErrors("""Host("")""").isEmpty) + } + + test("Host fails to compile given a dash-ending hostname (RFC-1123)") { + assert(!compileErrors("""Host("A0c-")""").isEmpty) + } + + test("Host fails to compile given a dash-beginning hostname (RFC-1123)") { + assert(!compileErrors("""Host("-A0c")""").isEmpty) + } + + test("Host fails to compile given a hostname label whose length is > 63 (RFC-1123)") { + assert(!compileErrors("""Host("o123456701234567012345670123456701234567012345670123456701234567")""").isEmpty) + } + + test("Host fails to compile given a hostname whose length is > 255 (RFC-1123)") { + assert( + !compileErrors( + """Host( + "o12345670123456701234567012345670123456701234567012345670123456" + + ".o12345670123456701234567012345670123456701234567012345670123456" + + ".o12345670123456701234567012345670123456701234567012345670123456" + + ".o12345670123456701234567012345670123456701234567012345670123456" + + ".a" + )""" + ).isEmpty + ) + } + + test("Host fails to compile given an invalid (non-private) IP address") { + assert(!compileErrors("""Host("1.1.1.1")""").isEmpty) + } + + test("Host compiles given all NICs (0.0.0.0)") { + Host("0.0.0.0") + } + test("Host compiles given loopback address (127.0.0.1)") { + Host("127.0.0.1") + } + test("Host compiles given localhost (RFC-1123)") { + Host("localhost") + } + test("Host compiles given domain.local (RFC-1123)") { + Host("domain.local") + } + test("Host compiles given any digit hostname (RFC-1123)") { + Host("01234") + } + test("Host compiles given any dash inside hostname (RFC-1123)") { + Host("01234-abc") + } + test("Host compiles given 10. IPv4 (RFC-1918)") { + Host("10.1.2.3") + } + test("Host compiles given 172.(15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31). IPv4 (RFC-1918)") { + Host("10.15.1.2") + Host("10.16.1.2") + Host("10.17.1.2") + Host("10.18.1.2") + Host("10.19.1.2") + Host("10.20.1.2") + Host("10.21.1.2") + Host("10.22.1.2") + Host("10.23.1.2") + Host("10.24.1.2") + Host("10.25.1.2") + Host("10.26.1.2") + Host("10.27.1.2") + Host("10.28.1.2") + Host("10.29.1.2") + Host("10.30.1.2") + Host("10.31.1.2") + } + test("Host compiles given 192.168. IPv4 (RFC-1918)") { + Host("192.168.1.2") + } + test("Host compiles given 192.0.2. IPv4 (RFC-5737)") { + Host("192.0.2.1") + } + test("Host compiles given 198.51.100. IPv4 (RFC-5737)") { + Host("198.51.100.1") + } + test("Host compiles given 169.254. IPv4 (RFC-3927)") { + Host("169.254.1.2") + } + test("Host compiles given 198.(18|19). IPv4 (RFC-2544)") { + Host("198.18.1.2") + Host("198.19.1.2") + } +} diff --git a/core/src/test/scala/laserdisc/refined/types/KeySuite.scala b/core/src/test/scala/laserdisc/refined/types/KeySuite.scala new file mode 100644 index 00000000..11c5bc57 --- /dev/null +++ b/core/src/test/scala/laserdisc/refined/types/KeySuite.scala @@ -0,0 +1,18 @@ +package laserdisc +package refined.types + +final class KeySuite extends BaseSpec { + test("Key fails to compile given an empty String") { + assertNoDiff( + compileErrors("""Key("")"""), + """|error: Left predicate of (!isEmpty() && ()) failed: Predicate isEmpty() did not fail. + |Key("") + | ^ + |""".stripMargin + ) + } + + test("Key compiles given a non empty String") { + Key("a") + } +} diff --git a/core/src/test/scala/laserdisc/refined/types/LatitudeSuite.scala b/core/src/test/scala/laserdisc/refined/types/LatitudeSuite.scala new file mode 100644 index 00000000..34dc26d4 --- /dev/null +++ b/core/src/test/scala/laserdisc/refined/types/LatitudeSuite.scala @@ -0,0 +1,46 @@ +package laserdisc +package refined.types + +import org.scalacheck.Prop.forAll + +final class LatitudeSuite extends BaseSpec { + test("Latitude fails to compile given out of range Double (< -85.05112878D)") { + assertNoDiff( + compileErrors("Latitude(-85.05112879D)"), + """|error: Left predicate of (!(-85.05112879 < -85.05112878) && !(-85.05112879 > 85.05112878)) failed: Predicate (-85.05112879 < -85.05112878) did not fail. + |Latitude(-85.05112879D) + | ^ + |""".stripMargin + ) + } + + test("Latitude fails to compile given out of range Double (> 85.05112878D)") { + assertNoDiff( + compileErrors("Latitude(85.05112879D)"), + """|error: Right predicate of (!(85.05112879 < -85.05112878) && !(85.05112879 > 85.05112878)) failed: Predicate (85.05112879 > 85.05112878) did not fail. + |Latitude(85.05112879D) + | ^ + |""".stripMargin + ) + } + + property("Latitude fails at runtime provided non literal cases of out of range Doubles (d < -85.05112878D | d > 85.05112878D)") { + forAll(doubles.filterNot(latitudeIsValid)) { d => + intercept[IllegalArgumentException](Latitude.unsafeFrom(d)) + } + } + + test("Key compiles given edge cases (-85.05112878D)") { + Latitude(-85.05112878d) + } + + test("Key compiles given edge cases (85.05112878D)") { + Latitude(85.05112878d) + } + + property("Latitude refines correctly provided non literal cases of in range Doubles (-85.05112878D <= d <= 85.05112878D)") { + forAll(latitudeGen) { l => + Latitude.from(l) onRight (_.value == l) + } + } +} diff --git a/core/src/test/scala/laserdisc/refined/types/LongitudeSuite.scala b/core/src/test/scala/laserdisc/refined/types/LongitudeSuite.scala new file mode 100644 index 00000000..de3c276e --- /dev/null +++ b/core/src/test/scala/laserdisc/refined/types/LongitudeSuite.scala @@ -0,0 +1,46 @@ +package laserdisc +package refined.types + +import org.scalacheck.Prop.forAll + +final class LongitudeSuite extends BaseSpec { + test("Longitude fails to compile given out of range Double (< -180.0D)") { + assertNoDiff( + compileErrors("Longitude(-180.00000001D)"), + """|error: Left predicate of (!(-180.00000001 < -180.0) && !(-180.00000001 > 180.0)) failed: Predicate (-180.00000001 < -180.0) did not fail. + |Longitude(-180.00000001D) + | ^ + |""".stripMargin + ) + } + + test("Longitude fails to compile given out of range Double (> 180.0D)") { + assertNoDiff( + compileErrors("Longitude(180.00000001D)"), + """|error: Right predicate of (!(180.00000001 < -180.0) && !(180.00000001 > 180.0)) failed: Predicate (180.00000001 > 180.0) did not fail. + |Longitude(180.00000001D) + | ^ + |""".stripMargin + ) + } + + property("Longitude fails at runtime provided non literal cases of out of range Doubles (d < -180.0D | d > 180.0D)") { + forAll(doubles.filterNot(longitudeIsValid)) { d => + intercept[IllegalArgumentException](Longitude.unsafeFrom(d)) + } + } + + test("Longitude compiles given edge cases (-180.0D)") { + Longitude(-180.0d) + } + + test("Longitude compiles given edge cases (180.0D)") { + Longitude(180.0d) + } + + property("Longitude refines correctly provided non literal cases of in range Doubles (-180.0D <= d <= 180.0D)") { + forAll(longitudeGen) { l => + Longitude.from(l) onRight (_.value == l) + } + } +} diff --git a/core/src/test/scala/laserdisc/refined/types/NodeIdSuite.scala b/core/src/test/scala/laserdisc/refined/types/NodeIdSuite.scala new file mode 100644 index 00000000..a3e661a0 --- /dev/null +++ b/core/src/test/scala/laserdisc/refined/types/NodeIdSuite.scala @@ -0,0 +1,62 @@ +package laserdisc +package refined.types + +import org.scalacheck.Prop.forAll + +final class NodeIdSuite extends BaseSpec { + test("NodeId fails to compile given a non conformant String (length < 40)") { + assertNoDiff( + compileErrors("""NodeId("0123456789abcdef0123456789abcdef0123456")"""), + """|error: Predicate failed: "0123456789abcdef0123456789abcdef0123456".matches("[0-9a-f]{40}"). + |NodeId("0123456789abcdef0123456789abcdef0123456") + | ^ + |""".stripMargin + ) + } + + test("NodeId fails to compile given a non conformant String (length > 40)") { + assertNoDiff( + compileErrors("""NodeId("0123456789abcdef0123456789abcdef012345678")"""), + """|error: Predicate failed: "0123456789abcdef0123456789abcdef012345678".matches("[0-9a-f]{40}"). + |NodeId("0123456789abcdef0123456789abcdef012345678") + | ^ + |""".stripMargin + ) + } + + test("NodeId fails to compile given a non conformant String (uppercase)") { + assertNoDiff( + compileErrors("""NodeId("0123456789abcdEf0123456789abcdef01234567")"""), + """|error: Predicate failed: "0123456789abcdEf0123456789abcdef01234567".matches("[0-9a-f]{40}"). + |NodeId("0123456789abcdEf0123456789abcdef01234567") + | ^ + |""".stripMargin + ) + } + + test("NodeId fails to compile given a non conformant String (invalid chars)") { + assertNoDiff( + compileErrors("""NodeId("0123456789abcd&f0123456789abcdef01234567&fghijk")"""), + """|error: Predicate failed: "0123456789abcd&f0123456789abcdef01234567&fghijk".matches("[0-9a-f]{40}"). + |NodeId("0123456789abcd&f0123456789abcdef01234567&fghijk") + | ^ + |""".stripMargin + ) + } + + property("NodeId fails at runtime provided non literal cases of non conformant Strings") { + forAll(strings.filterNot(nodeIdIsValid)) { d => + intercept[IllegalArgumentException](NodeId.unsafeFrom(d)) + } + } + + test("NodeId compiles given conformant String") { + NodeId("0123456789abcdef0123456789abcdef01234567") + } + + property("NodeId refines correctly provided non literal cases of conformant Strings") { + forAll(nodeIdGen) { i => + NodeId.from(i) onRight (_.value == i) + } + } +} diff --git a/core/src/test/scala/laserdisc/refined/types/NonNegDoubleSuite.scala b/core/src/test/scala/laserdisc/refined/types/NonNegDoubleSuite.scala new file mode 100644 index 00000000..69159339 --- /dev/null +++ b/core/src/test/scala/laserdisc/refined/types/NonNegDoubleSuite.scala @@ -0,0 +1,42 @@ +package laserdisc +package refined.types + +import org.scalacheck.Prop.forAll + +final class NonNegDoubleSuite extends BaseSpec { + test("NonNegDouble fails to compile given out of range Double (< 0.0D)") { + assertNoDiff( + compileErrors("NonNegDouble(-0.00000001D)"), + """|error: Right predicate of ((-1.0E-8 != NaN) && !(-1.0E-8 < 0.0)) failed: Predicate (-1.0E-8 < 0.0) did not fail. + |NonNegDouble(-0.00000001D) + | ^ + |""".stripMargin + ) + } + + test("NonNegDouble fails to compile given NaN Double") { + assertNoDiff( + compileErrors("NonNegDouble(Double.NaN)"), + """|error: Left predicate of ((NaN != NaN) && !(NaN < 0.0)) failed: Predicate failed: (NaN != NaN). + |NonNegDouble(Double.NaN) + | ^ + |""".stripMargin + ) + } + + property("NonNegDouble fails at runtime provided non literal cases of out of range Doubles (d < 0.0D)") { + forAll(doubles.filterNot(nonNegDoubleIsValid)) { d => + intercept[IllegalArgumentException](NonNegDouble.unsafeFrom(d)) + } + } + + test("NonNegDouble compiles given edge cases (0.0D)") { + NonNegDouble(0.0d) + } + + property("NonNegDouble refines correctly provided non literal cases of in range Doubles (d >= 0.0D)") { + forAll(nonNegDoubleGen) { d => + NonNegDouble.from(d) onRight (_.value == d) + } + } +} diff --git a/core/src/test/scala/laserdisc/refined/types/NonNegIntSuite.scala b/core/src/test/scala/laserdisc/refined/types/NonNegIntSuite.scala new file mode 100644 index 00000000..2abd4394 --- /dev/null +++ b/core/src/test/scala/laserdisc/refined/types/NonNegIntSuite.scala @@ -0,0 +1,32 @@ +package laserdisc +package refined.types + +import org.scalacheck.Prop.forAll + +final class NonNegIntSuite extends BaseSpec { + test("NonNegInt fails to compile given out of range Ints (< 0)") { + assertNoDiff( + compileErrors("NonNegInt(-1)"), + """|error: Predicate (-1 < 0) did not fail. + |NonNegInt(-1) + | ^ + |""".stripMargin + ) + } + + property("NonNegInt fails at runtime provided non literal cases of out of range Ints (i < 0)") { + forAll(ints.filterNot(nonNegIntIsValid)) { i => + intercept[IllegalArgumentException](NonNegInt.unsafeFrom(i)) + } + } + + test("NonNegInt compiles given edge cases (0)") { + NonNegInt(0) + } + + property("NonNegInt refines correctly provided non literal cases of in range Ints (i > 0)") { + forAll(nonNegIntGen) { i => + NonNegInt.from(i) onRight (_.value == i) + } + } +} diff --git a/core/src/test/scala/laserdisc/refined/types/NonNegLongSuite.scala b/core/src/test/scala/laserdisc/refined/types/NonNegLongSuite.scala new file mode 100644 index 00000000..e8e2795c --- /dev/null +++ b/core/src/test/scala/laserdisc/refined/types/NonNegLongSuite.scala @@ -0,0 +1,32 @@ +package laserdisc +package refined.types + +import org.scalacheck.Prop.forAll + +final class NonNegLongSuite extends BaseSpec { + test("NonNegLong fails to compile given out of range Longs (< 0L)") { + assertNoDiff( + compileErrors("NonNegLong(-1L)"), + """|error: Predicate (-1 < 0) did not fail. + |NonNegLong(-1L) + | ^ + |""".stripMargin + ) + } + + property("NonNegLong fails at runtime provided non literal cases of out of range Longs (l < 0L)") { + forAll(longs.filterNot(nonNegLongIsValid)) { l => + intercept[IllegalArgumentException](NonNegLong.unsafeFrom(l)) + } + } + + test("NonNegLong compiles given edge cases (0L)") { + NonNegLong(0L) + } + + property("NonNegLong refines correctly provided non literal cases of in range Longs (l > 0L)") { + forAll(nonNegLongGen) { l => + NonNegLong.from(l) onRight (_.value == l) + } + } +} diff --git a/core/src/test/scala/laserdisc/refined/types/NonZeroDoubleSuite.scala b/core/src/test/scala/laserdisc/refined/types/NonZeroDoubleSuite.scala new file mode 100644 index 00000000..6cccb251 --- /dev/null +++ b/core/src/test/scala/laserdisc/refined/types/NonZeroDoubleSuite.scala @@ -0,0 +1,32 @@ +package laserdisc +package refined.types + +import org.scalacheck.Prop.forAll + +final class NonZeroDoubleSuite extends BaseSpec { + test("NonZeroDouble fails to compile given 0.0D") { + assertNoDiff( + compileErrors("NonZeroDouble(0.0D)"), + """|error: Right predicate of ((0.0 != NaN) && !(0.0 == 0.0)) failed: Predicate (0.0 == 0.0) did not fail. + |NonZeroDouble(0.0D) + | ^ + |""".stripMargin + ) + } + + test("NonZeroDouble fails to compile given NaN") { + assertNoDiff( + compileErrors("NonZeroDouble(Double.NaN)"), + """|error: Left predicate of ((NaN != NaN) && !(NaN == 0.0)) failed: Predicate failed: (NaN != NaN). + |NonZeroDouble(Double.NaN) + | ^ + |""".stripMargin + ) + } + + property("NonZeroDouble refines correctly provided non literal cases of valid Doubles (d != 0.0D)") { + forAll(nonZeroDoubleGen) { d => + NonZeroDouble.from(d) onRight (_.value == d) + } + } +} diff --git a/core/src/test/scala/laserdisc/refined/types/NonZeroIntSuite.scala b/core/src/test/scala/laserdisc/refined/types/NonZeroIntSuite.scala new file mode 100644 index 00000000..c7c64012 --- /dev/null +++ b/core/src/test/scala/laserdisc/refined/types/NonZeroIntSuite.scala @@ -0,0 +1,22 @@ +package laserdisc +package refined.types + +import org.scalacheck.Prop.forAll + +final class NonZeroIntSuite extends BaseSpec { + test("NonZeroInt fails to compile given 0") { + assertNoDiff( + compileErrors("NonZeroInt(0)"), + """|error: Predicate (0 == 0) did not fail. + |NonZeroInt(0) + | ^ + |""".stripMargin + ) + } + + property("NonZeroInt refines correctly provided non literal cases of valid Ints (i != 0)") { + forAll(nonZeroIntGen) { i => + NonZeroInt.from(i) onRight (_.value == i) + } + } +} diff --git a/core/src/test/scala/laserdisc/refined/types/NonZeroLongSuite.scala b/core/src/test/scala/laserdisc/refined/types/NonZeroLongSuite.scala new file mode 100644 index 00000000..36d19f85 --- /dev/null +++ b/core/src/test/scala/laserdisc/refined/types/NonZeroLongSuite.scala @@ -0,0 +1,22 @@ +package laserdisc +package refined.types + +import org.scalacheck.Prop.forAll + +final class NonZeroLongSuite extends BaseSpec { + test("NonZeroLong fails to compile given 0L") { + assertNoDiff( + compileErrors("NonZeroLong(0L)"), + """|error: Predicate (0 == 0) did not fail. + |NonZeroLong(0L) + | ^ + |""".stripMargin + ) + } + + property("NonZeroLong refines correctly provided non literal cases of valid Longs (l != 0L)") { + forAll(nonZeroLongGen) { l => + NonZeroLong.from(l) onRight (_.value == l) + } + } +} diff --git a/core/src/test/scala/laserdisc/refined/types/OneOrMoreKeysSuite.scala b/core/src/test/scala/laserdisc/refined/types/OneOrMoreKeysSuite.scala new file mode 100644 index 00000000..e06b9ae8 --- /dev/null +++ b/core/src/test/scala/laserdisc/refined/types/OneOrMoreKeysSuite.scala @@ -0,0 +1,36 @@ +package laserdisc +package refined.types + +import org.scalacheck.Prop.forAll + +final class OneOrMoreKeysSuite extends BaseSpec { + test("OneOrMoreKeys fails to compile given non literal empty List") { + assertNoDiff( + compileErrors("OneOrMoreKeys(List.empty)"), + """|error: compile-time refinement only works with literals + |OneOrMoreKeys(List.empty) + | ^ + |""".stripMargin + ) + } + + test("OneOrMoreKeys fails to compile given non literal non empty List") { + assertNoDiff( + compileErrors("""OneOrMoreKeys(List(Key("a")))"""), + """|error: compile-time refinement only works with literals + |OneOrMoreKeys(List(Key("a"))) + | ^ + |""".stripMargin + ) + } + + test("OneOrMoreKeys fails at runtime provided empty List") { + intercept[IllegalArgumentException](OneOrMoreKeys.unsafeFrom(List.empty)) + } + + property("OneOrMoreKeys refines correctly provided non literal cases of non empty Lists (length > 0)") { + forAll(keyLists.filter(_.nonEmpty)) { ks => + OneOrMoreKeys.from(ks) onRight (_.value == ks) + } + } +} diff --git a/core/src/test/scala/laserdisc/refined/types/OneOrMoreSuite.scala b/core/src/test/scala/laserdisc/refined/types/OneOrMoreSuite.scala new file mode 100644 index 00000000..915e2811 --- /dev/null +++ b/core/src/test/scala/laserdisc/refined/types/OneOrMoreSuite.scala @@ -0,0 +1,16 @@ +package laserdisc +package refined.types + +import org.scalacheck.Prop.forAll + +final class OneOrMoreSuite extends BaseSpec { + property("OneOrMore fails at runtime provided empty List") { + intercept[IllegalArgumentException](OneOrMore.unsafeFrom(List.empty[Int])) + } + + property("OneOrMore refines correctly provided non literal cases of non empty Lists (length > 0)") { + forAll(intLists.filter(_.nonEmpty)) { l => + OneOrMore.from(l) onRight (_.value == l) + } + } +} diff --git a/core/src/test/scala/laserdisc/refined/types/RangeOffsetSuite.scala b/core/src/test/scala/laserdisc/refined/types/RangeOffsetSuite.scala new file mode 100644 index 00000000..eff63ae3 --- /dev/null +++ b/core/src/test/scala/laserdisc/refined/types/RangeOffsetSuite.scala @@ -0,0 +1,46 @@ +package laserdisc +package refined.types + +import org.scalacheck.Prop.forAll + +final class RangeOffsetSuite extends BaseSpec { + test("RangeOffset fails to compile given out of range Int (< 0)") { + assertNoDiff( + compileErrors("RangeOffset(-1)"), + """|error: Left predicate of (!(-1 < 0) && !(-1 > 536870911)) failed: Predicate (-1 < 0) did not fail. + |RangeOffset(-1) + | ^ + |""".stripMargin + ) + } + + test("RangeOffset fails to compile given out of range Int (> 536870911)") { + assertNoDiff( + compileErrors("RangeOffset(536870912)"), + """|error: Right predicate of (!(536870912 < 0) && !(536870912 > 536870911)) failed: Predicate (536870912 > 536870911) did not fail. + |RangeOffset(536870912) + | ^ + |""".stripMargin + ) + } + + property("RangeOffset fails at runtime provided non literal cases of out of range Ints (i < 0 | i > 536870911)") { + forAll(ints.filterNot(rangeOffsetIsValid)) { i => + intercept[IllegalArgumentException](RangeOffset.unsafeFrom(i)) + } + } + + test("RangeOffset compiles given edge cases (0)") { + RangeOffset(0) + } + + test("RangeOffset compiles given edge cases (536870911)") { + RangeOffset(536870911) + } + + property("RangeOffset refines correctly provided non literal cases of in range Ints (0 <= i <= 536870911)") { + forAll(rangeOffsetGen) { i => + RangeOffset.from(i) onRight (_.value == i) + } + } +} diff --git a/core/src/test/scala/laserdisc/refined/types/SlotSuite.scala b/core/src/test/scala/laserdisc/refined/types/SlotSuite.scala new file mode 100644 index 00000000..fe59f8cb --- /dev/null +++ b/core/src/test/scala/laserdisc/refined/types/SlotSuite.scala @@ -0,0 +1,40 @@ +package laserdisc +package refined.types + +import org.scalacheck.Prop.forAll + +final class SlotSuite extends BaseSpec { + test("Slot fails to compile given out of range Int (< 0)") { + assertNoDiff( + compileErrors("Slot(-1)"), + """|error: Left predicate of (!(-1 < 0) && !(-1 > 16383)) failed: Predicate (-1 < 0) did not fail. + |Slot(-1) + | ^ + |""".stripMargin + ) + } + + test("Slot fails to compile given out of range Int (> 16383)") { + assertNoDiff( + compileErrors("Slot(16384)"), + """|error: Right predicate of (!(16384 < 0) && !(16384 > 16383)) failed: Predicate (16384 > 16383) did not fail. + |Slot(16384) + | ^ + |""".stripMargin + ) + } + + property("Slot fails at runtime provided non literal cases of out of range Ints (i < 0 | i > 16383)") { + forAll(ints.filterNot(slotIsValid)) { i => + intercept[IllegalArgumentException](Slot.unsafeFrom(i)) + } + } + + test("NonNegLong compiles given edge cases (0)") { + Slot(0) + } + + test("NonNegLong compiles given edge cases (16383)") { + Slot(16383) + } +} diff --git a/core/src/test/scala/laserdisc/refined/types/StringLengthSuite.scala b/core/src/test/scala/laserdisc/refined/types/StringLengthSuite.scala new file mode 100644 index 00000000..b4fc5fc7 --- /dev/null +++ b/core/src/test/scala/laserdisc/refined/types/StringLengthSuite.scala @@ -0,0 +1,46 @@ +package laserdisc +package refined.types + +import org.scalacheck.Prop.forAll + +final class StringLengthSuite extends BaseSpec { + test("StringLength fails to compile given out of range Long (< 0L)") { + assertNoDiff( + compileErrors("StringLength(-1L)"), + """|error: Left predicate of (!(-1 < 0) && !(-1 > 4294967295)) failed: Predicate (-1 < 0) did not fail. + |StringLength(-1L) + | ^ + |""".stripMargin + ) + } + + test("StringLength fails to compile given out of range Long (> 4294967295L)") { + assertNoDiff( + compileErrors("StringLength(4294967296L)"), + """|error: Right predicate of (!(4294967296 < 0) && !(4294967296 > 4294967295)) failed: Predicate (4294967296 > 4294967295) did not fail. + |StringLength(4294967296L) + | ^ + |""".stripMargin + ) + } + + property("StringLength fails at runtime provided non literal cases of out of range Longs (l < 0L | l > 4294967295L)") { + forAll(longs.filterNot(stringLengthIsValid)) { l => + intercept[IllegalArgumentException](StringLength.unsafeFrom(l)) + } + } + + test("StringLength compiles given edge cases (0L)") { + StringLength(0L) + } + + test("StringLength compiles given edge cases (4294967295L)") { + StringLength(4294967295L) + } + + property("StringLength refines correctly provided non literal cases of in range Longs (0L <= l <= 4294967295L)") { + forAll(stringLengthGen) { l => + StringLength.from(l) onRight (_.value == l) + } + } +} diff --git a/core/src/test/scala/laserdisc/refined/types/TwoOrMoreKeysSuite.scala b/core/src/test/scala/laserdisc/refined/types/TwoOrMoreKeysSuite.scala new file mode 100644 index 00000000..3f4a2e14 --- /dev/null +++ b/core/src/test/scala/laserdisc/refined/types/TwoOrMoreKeysSuite.scala @@ -0,0 +1,50 @@ +package laserdisc +package refined.types + +import org.scalacheck.Prop.forAll + +final class TwoOrMoreKeysSuite extends BaseSpec { + test("TwoOrMoreKeys fails to compile given non literal empty List") { + assertNoDiff( + compileErrors("TwoOrMoreKeys(List.empty)"), + """|error: compile-time refinement only works with literals + |TwoOrMoreKeys(List.empty) + | ^ + |""".stripMargin + ) + } + + test("TwoOrMoreKeys fails to compile given non literal single element List") { + assertNoDiff( + compileErrors("""TwoOrMoreKeys(List(Key("a")))"""), + """|error: compile-time refinement only works with literals + |TwoOrMoreKeys(List(Key("a"))) + | ^ + |""".stripMargin + ) + } + + test("TwoOrMoreKeys fails to compile given non literal List of two elements") { + assertNoDiff( + compileErrors("""TwoOrMoreKeys(List(Key("a"), Key("b")))"""), + """|error: compile-time refinement only works with literals + |TwoOrMoreKeys(List(Key("a"), Key("b"))) + | ^ + |""".stripMargin + ) + } + + property("TwoOrMoreKeys fails at runtime provided empty List") { + intercept[IllegalArgumentException](TwoOrMoreKeys.unsafeFrom(List.empty)) + } + + property("TwoOrMoreKeys fails at runtime provided single element List") { + intercept[IllegalArgumentException](TwoOrMoreKeys.unsafeFrom(List(Key("a")))) + } + + property("TwoOrMoreKeys refines correctly provided non literal cases of Lists of length > 1") { + forAll(keyLists.filter(_.size > 1)) { ks => + TwoOrMoreKeys.from(ks) onRight (_.value == ks) + } + } +} diff --git a/core/src/test/scala/laserdisc/refined/types/TwoOrMoreWeightedKeysSuite.scala b/core/src/test/scala/laserdisc/refined/types/TwoOrMoreWeightedKeysSuite.scala new file mode 100644 index 00000000..96ac0501 --- /dev/null +++ b/core/src/test/scala/laserdisc/refined/types/TwoOrMoreWeightedKeysSuite.scala @@ -0,0 +1,50 @@ +package laserdisc +package refined.types + +import org.scalacheck.Prop.forAll + +final class TwoOrMoreWeightedKeysSuite extends BaseSpec { + test("TwoOrMoreWeightedKeys fails to compile given non literal empty List") { + assertNoDiff( + compileErrors("TwoOrMoreWeightedKeys(List.empty)"), + """|error: compile-time refinement only works with literals + |TwoOrMoreWeightedKeys(List.empty) + | ^ + |""".stripMargin + ) + } + + test("TwoOrMoreWeightedKeys fails to compile given non literal single element List") { + assertNoDiff( + compileErrors("""TwoOrMoreWeightedKeys(List(Key("a") -> ValidDouble(42.0D)))"""), + """|error: compile-time refinement only works with literals + |TwoOrMoreWeightedKeys(List(Key("a") -> ValidDouble(42.0D))) + | ^ + |""".stripMargin + ) + } + + test("TwoOrMoreWeightedKeys fails to compile given non literal List of two elements") { + assertNoDiff( + compileErrors("""TwoOrMoreWeightedKeys(List(Key("a") -> ValidDouble(42.0D), Key("b") -> ValidDouble(23.0D)))"""), + """|error: compile-time refinement only works with literals + |TwoOrMoreWeightedKeys(List(Key("a") -> ValidDouble(42.0D), Key("b") -> ValidDouble(23.0D))) + | ^ + |""".stripMargin + ) + } + + test("TwoOrMoreWeightedKeys fails at runtime provided empty List") { + intercept[IllegalArgumentException](TwoOrMoreWeightedKeys.unsafeFrom(List.empty)) + } + + test("TwoOrMoreWeightedKeys fails at runtime provided single element List") { + intercept[IllegalArgumentException](TwoOrMoreWeightedKeys.unsafeFrom(List(Key("a") -> ValidDouble(42.0d)))) + } + + property("TwoOrMoreWeightedKeys refines correctly provided non literal cases of Lists of length > 1") { + forAll(wightedKeyLists.filter(_.size > 1)) { ks => + TwoOrMoreWeightedKeys.from(ks) onRight (_.value == ks) + } + } +} diff --git a/core/src/test/scala/laserdisc/refined/types/ValidDoubleSuite.scala b/core/src/test/scala/laserdisc/refined/types/ValidDoubleSuite.scala new file mode 100644 index 00000000..8255d1c9 --- /dev/null +++ b/core/src/test/scala/laserdisc/refined/types/ValidDoubleSuite.scala @@ -0,0 +1,30 @@ +package laserdisc +package refined.types + +import org.scalacheck.Prop.forAll + +final class ValidDoubleSuite extends BaseSpec { + test("ValidDouble fails to compile given Double.NaN") { + assertNoDiff( + compileErrors("ValidDouble(Double.NaN)"), + """|error: Predicate failed: (NaN != NaN). + |ValidDouble(Double.NaN) + | ^ + |""".stripMargin + ) + } + + test("ValidDouble compiles given edge cases (-1.7976931348623157E308) -> can't use Double.MinValue as not a literal") { + ValidDouble(-1.7976931348623157e308) + } + + test("ValidDouble compiles given edge cases (Double.MaxValue)") { + ValidDouble(Double.MaxValue) + } + + property("ValidDouble refines correctly provided non literal cases of valid Doubles (d != Double.NaN)") { + forAll(doubles.filter(validDoubleIsValid)) { d => + ValidDouble.from(d) onRight (_.value == d) + } + } +} diff --git a/core/src/test/scala/laserdisc/refined/types/package.scala b/core/src/test/scala/laserdisc/refined/types/package.scala new file mode 100644 index 00000000..0e2d73b7 --- /dev/null +++ b/core/src/test/scala/laserdisc/refined/types/package.scala @@ -0,0 +1,20 @@ +package laserdisc +package refined + +import org.scalacheck.Gen.listOf +import org.scalacheck.{Arbitrary, Gen, Prop} + +package object types { + private[types] val ints: Gen[Int] = implicitly[Arbitrary[Int]].arbitrary + private[types] val longs: Gen[Long] = implicitly[Arbitrary[Long]].arbitrary + private[types] val doubles: Gen[Double] = implicitly[Arbitrary[Double]].arbitrary + private[types] val strings: Gen[String] = implicitly[Arbitrary[String]].arbitrary + private[types] val intLists: Gen[List[Int]] = implicitly[Arbitrary[List[Int]]].arbitrary + + private[types] def keyLists(implicit k: Arbitrary[Key]): Gen[List[Key]] = listOf(k.arbitrary) + + private[types] def wightedKeyLists(implicit kv: Arbitrary[(Key, ValidDouble)]): Gen[List[(Key, ValidDouble)]] = + listOf(kv.arbitrary) + + private[types] implicit val illegalArgumentException: IllegalArgumentException => Prop = _ => Prop.passed +}