From a81dbd6df21a1f85a5e2cc26671e0978d917cb25 Mon Sep 17 00:00:00 2001 From: "Flavio W. Brasil" Date: Wed, 24 Jun 2015 22:58:59 +0200 Subject: [PATCH 1/4] use utest instead of specs2 --- .gitignore | 1 + project/Build.scala | 8 +- src/test/scala/io/getclump/ClumpApiSpec.scala | 596 +++++++++--------- .../io/getclump/ClumpExecutionSpec.scala | 231 +++---- .../scala/io/getclump/ClumpFetcherSpec.scala | 164 +++-- .../scala/io/getclump/ClumpSourceSpec.scala | 140 ++-- .../scala/io/getclump/IntegrationSpec.scala | 365 +++++------ src/test/scala/io/getclump/SourcesSpec.scala | 339 +++++----- src/test/scala/io/getclump/Spec.scala | 6 +- 9 files changed, 926 insertions(+), 924 deletions(-) diff --git a/.gitignore b/.gitignore index 2ec5078..8aef1f9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ target .classpath .project .settings +.cache-* diff --git a/project/Build.scala b/project/Build.scala index 732f44c..516c5c2 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -6,12 +6,10 @@ import sbtrelease.ReleasePlugin._ object Build extends Build { val commonSettings = Seq( organization := "io.getclump", - scalaVersion := "2.10.4", + scalaVersion := "2.11.5", crossScalaVersions := Seq("2.10.4", "2.11.5"), - libraryDependencies ++= Seq( - "org.specs2" %% "specs2" % "2.4.2" % "test", - "org.mockito" % "mockito-core" % "1.9.5" % "test" - ), + libraryDependencies += "com.lihaoyi" %% "utest" % "0.3.1", + testFrameworks += new TestFramework("utest.runner.Framework"), scalacOptions ++= Seq( "-deprecation", "-encoding", "UTF-8", diff --git a/src/test/scala/io/getclump/ClumpApiSpec.scala b/src/test/scala/io/getclump/ClumpApiSpec.scala index 38bc23c..85d1912 100644 --- a/src/test/scala/io/getclump/ClumpApiSpec.scala +++ b/src/test/scala/io/getclump/ClumpApiSpec.scala @@ -1,372 +1,392 @@ package io.getclump -import org.junit.runner.RunWith -import org.specs2.runner.JUnitRunner +import utest._ -@RunWith(classOf[JUnitRunner]) -class ClumpApiSpec extends Spec { +object ClumpApiSpec extends Spec { - "the Clump object" >> { + val tests = TestSuite { - "allows to create a constant clump" >> { + "the Clump object" - { - "from a future (Clump.future)" >> { + "allows to create a constant clump" - { - "success" >> { - "optional" >> { - "defined" in { - clumpResult(Clump.future(Future.successful(Some(1)))) mustEqual Some(1) + "from a future (Clump.future)" - { + + "success" - { + "optional" - { + "defined" - { + assert(clumpResult(Clump.future(Future.successful(Some(1)))) == Some(1)) + } + "undefined" - { + assert(clumpResult(Clump.future(Future.successful(None))) == None) + } } - "undefined" in { - clumpResult(Clump.future(Future.successful(None))) mustEqual None + "non-optional" - { + assert(clumpResult(Clump.future(Future.successful(1))) == Some(1)) } } - "non-optional" in { - clumpResult(Clump.future(Future.successful(1))) mustEqual Some(1) + + "failure" - { + intercept[IllegalStateException] { + clumpResult(Clump.future(Future.failed(new IllegalStateException))) + } } } - "failure" in { - clumpResult(Clump.future(Future.failed(new IllegalStateException))) must throwA[IllegalStateException] - } - } + "from a value (Clump.apply)" - { + "propogates exceptions" - { + val clump = Clump { throw new IllegalStateException } + intercept[IllegalStateException] { + clumpResult(clump) + } + } - "from a value (Clump.apply)" >> { - "propogates exceptions" in { - val clump = Clump { throw new IllegalStateException } - clumpResult(clump) must throwA[IllegalStateException] + "no exception" - { + assert(clumpResult(Clump(1)) == Some(1)) + } } - "no exception" in { - clumpResult(Clump(1)) mustEqual Some(1) + "from a value (Clump.value)" - { + assert(clumpResult(Clump.value(1)) == Some(1)) } - } - "from a value (Clump.value)" in { - clumpResult(Clump.value(1)) mustEqual Some(1) - } + "from a value (Clump.successful)" - { + assert(clumpResult(Clump.successful(1)) == Some(1)) + } - "from a value (Clump.successful)" in { - clumpResult(Clump.successful(1)) mustEqual Some(1) - } + "from an option (Clump.value)" - { - "from an option (Clump.value)" >> { + "defined" - { + assert(clumpResult(Clump.value(Option(1))) == Option(1)) + } - "defined" in { - clumpResult(Clump.value(Option(1))) mustEqual Option(1) + "empty" - { + assert(clumpResult(Clump.value(None)) == None) + } } - "empty" in { - clumpResult(Clump.value(None)) mustEqual None + "failed (Clump.exception)" - { + intercept[IllegalStateException] { + clumpResult(Clump.exception(new IllegalStateException)) + } } - } - "failed (Clump.exception)" in { - clumpResult(Clump.exception(new IllegalStateException)) must throwA[IllegalStateException] + "failed (Clump.failed)" - { + intercept[IllegalStateException] { + clumpResult(Clump.failed(new IllegalStateException)) + } + } } - "failed (Clump.failed)" in { - clumpResult(Clump.failed(new IllegalStateException)) must throwA[IllegalStateException] + "allows to create a clump traversing multiple inputs (Clump.traverse)" - { + "list" - { + val inputs = List(1, 2, 3) + val clump = Clump.traverse(inputs)(i => Clump.value(i + 1)) + assert(clumpResult(clump) == Some(List(2, 3, 4))) + } + "set" - { + val inputs = Set(1, 2, 3) + val clump = Clump.traverse(inputs)(i => Clump.value(i + 1)) + assert(clumpResult(clump) == Some(Set(2, 3, 4))) + } + "seq" - { + val inputs = Seq(1, 2, 3) + val clump = Clump.traverse(inputs)(i => Clump.value(i + 1)) + assert(clumpResult(clump) == Some(Seq(2, 3, 4))) + } } - } - "allows to create a clump traversing multiple inputs (Clump.traverse)" in { - "list" in { - val inputs = List(1, 2, 3) - val clump = Clump.traverse(inputs)(i => Clump.value(i + 1)) - clumpResult(clump) ==== Some(List(2, 3, 4)) - } - "set" in { - val inputs = Set(1, 2, 3) - val clump = Clump.traverse(inputs)(i => Clump.value(i + 1)) - clumpResult(clump) ==== Some(Set(2, 3, 4)) - } - "seq" in { - val inputs = Seq(1, 2, 3) - val clump = Clump.traverse(inputs)(i => Clump.value(i + 1)) - clumpResult(clump) ==== Some(Seq(2, 3, 4)) + "allows to collect multiple clumps - only one (Clump.collect)" - { + "list" - { + val clumps = List(Clump.value(1), Clump.value(2)) + assert(clumpResult(Clump.collect(clumps)) == Some(List(1, 2))) + } + "set" - { + val clumps = Set(Clump.value(1), Clump.value(2)) + assert(clumpResult(Clump.collect(clumps)) == Some(Set(1, 2))) + } + "seq" - { + val clumps = Seq(Clump.value(1), Clump.value(2)) + assert(clumpResult(Clump.collect(clumps)) == Some(Seq(1, 2))) + } } - } - "allows to collect multiple clumps in only one (Clump.collect)" >> { - "list" in { - val clumps = List(Clump.value(1), Clump.value(2)) - clumpResult(Clump.collect(clumps)) mustEqual Some(List(1, 2)) + "allows to create an empty Clump (Clump.empty)" - { + assert(clumpResult(Clump.empty) == None) } - "set" in { - val clumps = Set(Clump.value(1), Clump.value(2)) - clumpResult(Clump.collect(clumps)) mustEqual Some(Set(1, 2)) - } - "seq" in { - val clumps = Seq(Clump.value(1), Clump.value(2)) - clumpResult(Clump.collect(clumps)) mustEqual Some(Seq(1, 2)) - } - } - "allows to create an empty Clump (Clump.empty)" in { - clumpResult(Clump.empty) ==== None - } - - "allows to join clumps" >> { + "allows to join clumps" - { - def c(int: Int) = Clump.value(int) + def c(int: Int) = Clump.value(int) - "2 instances" in { - val clump = Clump.join(c(1), c(2)) - clumpResult(clump) mustEqual Some(1, 2) - } - "3 instances" in { - val clump = Clump.join(c(1), c(2), c(3)) - clumpResult(clump) mustEqual Some(1, 2, 3) - } - "4 instances" in { - val clump = Clump.join(c(1), c(2), c(3), c(4)) - clumpResult(clump) mustEqual Some(1, 2, 3, 4) - } - "5 instances" in { - val clump = Clump.join(c(1), c(2), c(3), c(4), c(5)) - clumpResult(clump) mustEqual Some(1, 2, 3, 4, 5) - } - "6 instances" in { - val clump = Clump.join(c(1), c(2), c(3), c(4), c(5), c(6)) - clumpResult(clump) mustEqual Some(1, 2, 3, 4, 5, 6) - } - "7 instances" in { - val clump = Clump.join(c(1), c(2), c(3), c(4), c(5), c(6), c(7)) - clumpResult(clump) mustEqual Some(1, 2, 3, 4, 5, 6, 7) - } - "8 instances" in { - val clump = Clump.join(c(1), c(2), c(3), c(4), c(5), c(6), c(7), c(8)) - clumpResult(clump) mustEqual Some(1, 2, 3, 4, 5, 6, 7, 8) - } - "9 instances" in { - val clump = Clump.join(c(1), c(2), c(3), c(4), c(5), c(6), c(7), c(8), c(9)) - clumpResult(clump) mustEqual Some(1, 2, 3, 4, 5, 6, 7, 8, 9) - } - "10 instances" in { - val clump = Clump.join(c(1), c(2), c(3), c(4), c(5), c(6), c(7), c(8), c(9), c(10)) - clumpResult(clump) mustEqual Some(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + "2 instances" - { + val clump = Clump.join(c(1), c(2)) + assert(clumpResult(clump) == Some(1, 2)) + } + "3 instances" - { + val clump = Clump.join(c(1), c(2), c(3)) + assert(clumpResult(clump) == Some(1, 2, 3)) + } + "4 instances" - { + val clump = Clump.join(c(1), c(2), c(3), c(4)) + assert(clumpResult(clump) == Some(1, 2, 3, 4)) + } + "5 instances" - { + val clump = Clump.join(c(1), c(2), c(3), c(4), c(5)) + assert(clumpResult(clump) == Some(1, 2, 3, 4, 5)) + } + "6 instances" - { + val clump = Clump.join(c(1), c(2), c(3), c(4), c(5), c(6)) + assert(clumpResult(clump) == Some(1, 2, 3, 4, 5, 6)) + } + "7 instances" - { + val clump = Clump.join(c(1), c(2), c(3), c(4), c(5), c(6), c(7)) + assert(clumpResult(clump) == Some(1, 2, 3, 4, 5, 6, 7)) + } + "8 instances" - { + val clump = Clump.join(c(1), c(2), c(3), c(4), c(5), c(6), c(7), c(8)) + assert(clumpResult(clump) == Some(1, 2, 3, 4, 5, 6, 7, 8)) + } + "9 instances" - { + val clump = Clump.join(c(1), c(2), c(3), c(4), c(5), c(6), c(7), c(8), c(9)) + assert(clumpResult(clump) == Some(1, 2, 3, 4, 5, 6, 7, 8, 9)) + } + "10 instances" - { + val clump = Clump.join(c(1), c(2), c(3), c(4), c(5), c(6), c(7), c(8), c(9), c(10)) + assert(clumpResult(clump) == Some(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) + } } } - } - "a Clump instance" >> { + "a Clump instance" - { - "can be mapped to a new clump" >> { + "can be mapped to a new clump" - { - "using simple a value transformation (clump.map)" in { - clumpResult(Clump.value(1).map(_ + 1)) mustEqual Some(2) - } - - "using a transformation that creates a new clump (clump.flatMap)" >> { - "both clumps are defined" in { - clumpResult(Clump.value(1).flatMap(i => Clump.value(i + 1))) mustEqual Some(2) + "using simple a value transformation (clump.map)" - { + assert(clumpResult(Clump.value(1).map(_ + 1)) == Some(2)) } - "initial clump is undefined" in { - clumpResult(Clump.value(None).flatMap(i => Clump.value(2))) mustEqual None + + "using a transformation that creates a new clump (clump.flatMap)" - { + "both clumps are defined" - { + assert(clumpResult(Clump.value(1).flatMap(i => Clump.value(i + 1))) == Some(2)) + } + "initial clump is undefined" - { + assert(clumpResult(Clump.value(None).flatMap(i => Clump.value(2))) == None) + } } } - } - "can be joined with another clump and produce a new clump with the value of both (clump.join)" >> { - "both clumps are defined" in { - clumpResult(Clump.value(1).join(Clump.value(2))) mustEqual Some(1, 2) - } - "one of them is undefined" in { - clumpResult(Clump.value(1).join(Clump.value(None))) mustEqual None + "can be joined with another clump and produce a new clump with the value of both (clump.join)" - { + "both clumps are defined" - { + assert(clumpResult(Clump.value(1).join(Clump.value(2))) == Some(1, 2)) + } + "one of them is undefined" - { + assert(clumpResult(Clump.value(1).join(Clump.value(None))) == None) + } } - } - "allows to recover from failures" >> { + "allows to recover from failures" - { - "using a function that recovers using a new value (clump.handle)" >> { - "exception happens" in { - val clump = - Clump.exception(new IllegalStateException).handle { - case e: IllegalStateException => Some(2) + "using a function that recovers using a new value (clump.handle)" - { + "exception happens" - { + val clump = + Clump.exception(new IllegalStateException).handle { + case e: IllegalStateException => Some(2) + } + assert(clumpResult(clump) == Some(2)) + } + "exception doesn't happen" - { + val clump = + Clump.value(1).handle { + case e: IllegalStateException => None + } + assert(clumpResult(clump) == Some(1)) + } + "exception isn't caught" - { + val clump = + Clump.exception(new NullPointerException).handle { + case e: IllegalStateException => Some(1) + } + intercept[NullPointerException] { + clumpResult(clump) } - clumpResult(clump) mustEqual Some(2) + } } - "exception doesn't happen" in { - val clump = - Clump.value(1).handle { - case e: IllegalStateException => None + + "using a function that recovers using a new value (clump.recover)" - { + "exception happens" - { + val clump = + Clump.exception(new IllegalStateException).recover { + case e: IllegalStateException => Some(2) + } + assert(clumpResult(clump) == Some(2)) + } + "exception doesn't happen" - { + val clump = + Clump.value(1).recover { + case e: IllegalStateException => None + } + assert(clumpResult(clump) == Some(1)) + } + "exception isn't caught" - { + val clump = + Clump.exception(new NullPointerException).recover { + case e: IllegalStateException => Some(1) + } + intercept[NullPointerException] { + clumpResult(clump) } - clumpResult(clump) mustEqual Some(1) + } } - "exception isn't caught" in { - val clump = - Clump.exception(new NullPointerException).handle { - case e: IllegalStateException => Some(1) + + "using a function that recovers the failure using a new clump (clump.rescue)" - { + "exception happens" - { + val clump = + Clump.exception(new IllegalStateException).rescue { + case e: IllegalStateException => Clump.value(2) + } + assert(clumpResult(clump) == Some(2)) + } + "exception doesn't happen" - { + val clump = + Clump.value(1).rescue { + case e: IllegalStateException => Clump.value(None) + } + assert(clumpResult(clump) == Some(1)) + } + "exception isn't caught" - { + val clump = + Clump.exception(new NullPointerException).rescue { + case e: IllegalStateException => Clump.value(1) + } + intercept[NullPointerException] { + clumpResult(clump) } - clumpResult(clump) must throwA[NullPointerException] + } } - } - "using a function that recovers using a new value (clump.recover)" >> { - "exception happens" in { - val clump = - Clump.exception(new IllegalStateException).recover { - case e: IllegalStateException => Some(2) + "using a function that recovers the failure using a new clump (clump.recoverWith)" - { + "exception happens" - { + val clump = + Clump.exception(new IllegalStateException).recoverWith { + case e: IllegalStateException => Clump.value(2) + } + assert(clumpResult(clump) == Some(2)) + } + "exception doesn't happen" - { + val clump = + Clump.value(1).recoverWith { + case e: IllegalStateException => Clump.value(None) + } + assert(clumpResult(clump) == Some(1)) + } + "exception isn't caught" - { + val clump = + Clump.exception(new NullPointerException).recoverWith { + case e: IllegalStateException => Clump.value(1) + } + intercept[NullPointerException] { + clumpResult(clump) } - clumpResult(clump) mustEqual Some(2) + } } - "exception doesn't happen" in { - val clump = - Clump.value(1).recover { - case e: IllegalStateException => None - } - clumpResult(clump) mustEqual Some(1) + + "using a function that recovers using a new value (clump.fallback) on any exception" - { + "exception happens" - { + val clump = Clump.exception(new IllegalStateException).fallback(Some(1)) + assert(clumpResult(clump) == Some(1)) + } + + "exception doesn't happen" - { + val clump = Clump.value(1).fallback(Some(2)) + assert(clumpResult(clump) == Some(1)) + } } - "exception isn't caught" in { - val clump = - Clump.exception(new NullPointerException).recover { - case e: IllegalStateException => Some(1) - } - clumpResult(clump) must throwA[NullPointerException] + + "using a function that recovers using a new clump (clump.fallbackTo) on any exception" - { + "exception happens" - { + val clump = Clump.exception(new IllegalStateException).fallbackTo(Clump.value(1)) + assert(clumpResult(clump) == Some(1)) + } + + "exception doesn't happen" - { + val clump = Clump.value(1).fallbackTo(Clump.value(2)) + assert(clumpResult(clump) == Some(1)) + } } } - "using a function that recovers the failure using a new clump (clump.rescue)" >> { - "exception happens" in { - val clump = - Clump.exception(new IllegalStateException).rescue { - case e: IllegalStateException => Clump.value(2) - } - clumpResult(clump) mustEqual Some(2) - } - "exception doesn't happen" in { - val clump = - Clump.value(1).rescue { - case e: IllegalStateException => Clump.value(None) - } - clumpResult(clump) mustEqual Some(1) - } - "exception isn't caught" in { - val clump = - Clump.exception(new NullPointerException).rescue { - case e: IllegalStateException => Clump.value(1) - } - clumpResult(clump) must throwA[NullPointerException] - } + "can have its result filtered (clump.filter)" - { + assert(clumpResult(Clump.value(1).filter(_ != 1)) == None) + assert(clumpResult(Clump.value(1).filter(_ == 1)) == Some(1)) } - "using a function that recovers the failure using a new clump (clump.recoverWith)" >> { - "exception happens" in { - val clump = - Clump.exception(new IllegalStateException).recoverWith { - case e: IllegalStateException => Clump.value(2) - } - clumpResult(clump) mustEqual Some(2) - } - "exception doesn't happen" in { - val clump = - Clump.value(1).recoverWith { - case e: IllegalStateException => Clump.value(None) - } - clumpResult(clump) mustEqual Some(1) + "uses a covariant type parameter" - { + trait A + class B extends A + class C extends A + val clump: Clump[List[A]] = Clump.traverse(List(new B, new C))(Clump.value(_)) + } + + "allows to defined a fallback value (clump.orElse)" - { + "undefined" - { + assert(clumpResult(Clump.empty.orElse(1)) == Some(1)) } - "exception isn't caught" in { - val clump = - Clump.exception(new NullPointerException).recoverWith { - case e: IllegalStateException => Clump.value(1) - } - clumpResult(clump) must throwA[NullPointerException] + "defined" - { + assert(clumpResult(Clump.value(Some(1)).orElse(2)) == Some(1)) } } - "using a function that recovers using a new value (clump.fallback) on any exception" >> { - "exception happens" in { - val clump = Clump.exception(new IllegalStateException).fallback(Some(1)) - clumpResult(clump) mustEqual Some(1) + "allows to defined a fallback clump (clump.orElse)" - { + "undefined" - { + assert(clumpResult(Clump.empty.orElse(Clump.value(1))) == Some(1)) } - - "exception doesn't happen" in { - val clump = Clump.value(1).fallback(Some(2)) - clumpResult(clump) mustEqual Some(1) + "defined" - { + assert(clumpResult(Clump.value(Some(1)).orElse(Clump.value(2))) == Some(1)) } } - "using a function that recovers using a new clump (clump.fallbackTo) on any exception" >> { - "exception happens" in { - val clump = Clump.exception(new IllegalStateException).fallbackTo(Clump.value(1)) - clumpResult(clump) mustEqual Some(1) + "can represent its result as a collection (clump.list) when its type is a collection" - { + "list" - { + assert(awaitResult(Clump.value(List(1, 2)).list) == List(1, 2)) } - - "exception doesn't happen" in { - val clump = Clump.value(1).fallbackTo(Clump.value(2)) - clumpResult(clump) mustEqual Some(1) + "set" - { + assert(awaitResult(Clump.value(Set(1, 2)).list) == Set(1, 2)) + } + "seq" - { + assert(awaitResult(Clump.value(Seq(1, 2)).list) == Seq(1, 2)) + } + "not a collection" - { + compileError("Clump.value(1).flatten") } } - } - - "can have its result filtered (clump.filter)" in { - clumpResult(Clump.value(1).filter(_ != 1)) mustEqual None - clumpResult(Clump.value(1).filter(_ == 1)) mustEqual Some(1) - } - "uses a covariant type parameter" in { - trait A - class B extends A - class C extends A - val clump = Clump.traverse(List(new B, new C))(Clump.value(_)) - (clump: Clump[List[A]]) must beAnInstanceOf[Clump[List[A]]] - } - - "allows to defined a fallback value (clump.orElse)" >> { - "undefined" in { - clumpResult(Clump.empty.orElse(1)) ==== Some(1) - } - "defined" in { - clumpResult(Clump.value(Some(1)).orElse(2)) ==== Some(1) - } - } + "can provide a result falling back to a default (clump.getOrElse)" - { + "initial clump is undefined" - { + assert(awaitResult(Clump.value(None).getOrElse(1)) == 1) + } - "allows to defined a fallback clump (clump.orElse)" >> { - "undefined" in { - clumpResult(Clump.empty.orElse(Clump.value(1))) ==== Some(1) - } - "defined" in { - clumpResult(Clump.value(Some(1)).orElse(Clump.value(2))) ==== Some(1) + "initial clump is defined" - { + assert(awaitResult(Clump.value(Some(2)).getOrElse(1)) == 2) + } } - } - "can represent its result as a collection (clump.list) when its type is a collection" >> { - "list" in { - awaitResult(Clump.value(List(1, 2)).list) ==== List(1, 2) - } - "set" in { - awaitResult(Clump.value(Set(1, 2)).list) ==== Set(1, 2) - } - "seq" in { - awaitResult(Clump.value(Seq(1, 2)).list) ==== Seq(1, 2) + "has a utility method (clump.apply) for unwrapping optional result" - { + assert(awaitResult(Clump.value(1).apply()) == 1) + intercept[NoSuchElementException] { + awaitResult(Clump.value[Int](None)()) + } } - // Clump.value(1).flatten //doesn't compile - } - "can provide a result falling back to a default (clump.getOrElse)" >> { - "initial clump is undefined" in { - awaitResult(Clump.value(None).getOrElse(1)) ==== 1 - } + "can be made optional (clump.optional) to avoid lossy joins" - { + val clump: Clump[String] = Clump.empty + val optionalClump: Clump[Option[String]] = clump.optional + assert(clumpResult(optionalClump) == Some(None)) - "initial clump is defined" in { - awaitResult(Clump.value(Some(2)).getOrElse(1)) ==== 2 + val valueClump: Clump[String] = Clump.value("foo") + assert(clumpResult(valueClump.join(clump)) == None) + assert(clumpResult(valueClump.join(optionalClump)) == Some("foo", None)) } } - - "has a utility method (clump.apply) for unwrapping optional result" in { - awaitResult(Clump.value(1).apply()) ==== 1 - awaitResult(Clump.value[Int](None)()) must throwA[NoSuchElementException] - } - - "can be made optional (clump.optional) to avoid lossy joins" in { - val clump: Clump[String] = Clump.empty - val optionalClump: Clump[Option[String]] = clump.optional - clumpResult(optionalClump) ==== Some(None) - - val valueClump: Clump[String] = Clump.value("foo") - clumpResult(valueClump.join(clump)) ==== None - clumpResult(valueClump.join(optionalClump)) ==== Some("foo", None) - } } } diff --git a/src/test/scala/io/getclump/ClumpExecutionSpec.scala b/src/test/scala/io/getclump/ClumpExecutionSpec.scala index 554bc27..20258a6 100644 --- a/src/test/scala/io/getclump/ClumpExecutionSpec.scala +++ b/src/test/scala/io/getclump/ClumpExecutionSpec.scala @@ -1,14 +1,11 @@ package io.getclump +import utest._ import scala.collection.mutable.ListBuffer -import org.junit.runner.RunWith -import org.specs2.specification.Scope -import org.specs2.runner.JUnitRunner -@RunWith(classOf[JUnitRunner]) -class ClumpExecutionSpec extends Spec { +object ClumpExecutionSpec extends Spec { - trait Context extends Scope { + trait Context { val source1Fetches = ListBuffer[Set[Int]]() val source2Fetches = ListBuffer[Set[Int]]() @@ -21,137 +18,141 @@ class ClumpExecutionSpec extends Spec { val source2 = Clump.source((i: Set[Int]) => fetchFunction(source2Fetches, i)) } - "batches requests" >> { + val tests = TestSuite { + "batches requests" - { - "for multiple clumps created from traversed inputs" in new Context { - val clump = - Clump.traverse(List(1, 2, 3, 4)) { - i => - if (i <= 2) - source1.get(i) - else - source2.get(i) - } + "for multiple clumps created from traversed inputs" - new Context { + val clump = + Clump.traverse(List(1, 2, 3, 4)) { + i => + if (i <= 2) + source1.get(i) + else + source2.get(i) + } + + assert(clumpResult(clump) == Some(List(10, 20, 30, 40))) + assert(source1Fetches == List(Set(1, 2))) + assert(source2Fetches == List(Set(3, 4))) + } - clumpResult(clump) mustEqual Some(List(10, 20, 30, 40)) - source1Fetches mustEqual List(Set(1, 2)) - source2Fetches mustEqual List(Set(3, 4)) - } + "for multiple clumps collected into only one clump" - new Context { + val clump = Clump.collect(source1.get(1), source1.get(2), source2.get(3), source2.get(4)) - "for multiple clumps collected into only one clump" in new Context { - val clump = Clump.collect(source1.get(1), source1.get(2), source2.get(3), source2.get(4)) + assert(clumpResult(clump) == Some(List(10, 20, 30, 40))) + assert(source1Fetches == List(Set(1, 2))) + assert(source2Fetches == List(Set(3, 4))) + } - clumpResult(clump) mustEqual Some(List(10, 20, 30, 40)) - source1Fetches mustEqual List(Set(1, 2)) - source2Fetches mustEqual List(Set(3, 4)) - } + "for clumps created inside nested flatmaps" - new Context { + val clump1 = Clump.value(1).flatMap(source1.get(_)).flatMap(source2.get(_)) + val clump2 = Clump.value(2).flatMap(source1.get(_)).flatMap(source2.get(_)) - "for clumps created inside nested flatmaps" in new Context { - val clump1 = Clump.value(1).flatMap(source1.get(_)).flatMap(source2.get(_)) - val clump2 = Clump.value(2).flatMap(source1.get(_)).flatMap(source2.get(_)) + assert(clumpResult(Clump.collect(clump1, clump2)) == Some(List(100, 200))) + assert(source1Fetches == List(Set(1, 2))) + assert(source2Fetches == List(Set(20, 10))) + } - clumpResult(Clump.collect(clump1, clump2)) mustEqual Some(List(100, 200)) - source1Fetches mustEqual List(Set(1, 2)) - source2Fetches mustEqual List(Set(20, 10)) - } + "for clumps composed using for comprehension" - { - "for clumps composed using for comprehension" >> { + "one level" - new Context { + val clump = + for { + int <- Clump.collect(source1.get(1), source1.get(2), source2.get(3), source2.get(4)) + } yield int - "one level" in new Context { - val clump = - for { - int <- Clump.collect(source1.get(1), source1.get(2), source2.get(3), source2.get(4)) - } yield int + assert(clumpResult(clump) == Some(List(10, 20, 30, 40))) + assert(source1Fetches == List(Set(1, 2))) + assert(source2Fetches == List(Set(3, 4))) + } - clumpResult(clump) mustEqual Some(List(10, 20, 30, 40)) - source1Fetches mustEqual List(Set(1, 2)) - source2Fetches mustEqual List(Set(3, 4)) - } + "two levels" - new Context { + val clump = + for { + ints1 <- Clump.collect(source1.get(1), source1.get(2)) + ints2 <- Clump.collect(source2.get(3), source2.get(4)) + } yield (ints1, ints2) - "two levels" in new Context { - val clump = - for { - ints1 <- Clump.collect(source1.get(1), source1.get(2)) - ints2 <- Clump.collect(source2.get(3), source2.get(4)) - } yield (ints1, ints2) - - clumpResult(clump) mustEqual Some(List(10, 20), List(30, 40)) - source1Fetches mustEqual List(Set(1, 2)) - source2Fetches mustEqual List(Set(3, 4)) - } + assert(clumpResult(clump) == Some(List(10, 20), List(30, 40))) + assert(source1Fetches == List(Set(1, 2))) + assert(source2Fetches == List(Set(3, 4))) + } - "with a filter condition" in new Context { - val clump = - for { - ints1 <- Clump.collect(source1.get(1), source1.get(2)) - int2 <- source2.get(3) if (int2 != 999) - } yield (ints1, int2) - - clumpResult(clump) mustEqual Some(List(10, 20), 30) - source1Fetches mustEqual List(Set(1, 2)) - source2Fetches mustEqual List(Set(3)) - } + "with a filter condition" - new Context { + val clump = + for { + ints1 <- Clump.collect(source1.get(1), source1.get(2)) + int2 <- source2.get(3) if (int2 != 999) + } yield (ints1, int2) - "using a join" in new Context { - val clump = - for { - ints1 <- Clump.collect(source1.get(1), source1.get(2)) - ints2 <- source2.get(3).join(source2.get(4)) - } yield (ints1, ints2) - - clumpResult(clump) mustEqual Some(List(10, 20), (30, 40)) - source1Fetches mustEqual List(Set(1, 2)) - source2Fetches mustEqual List(Set(3, 4)) - } + assert(clumpResult(clump) == Some(List(10, 20), 30)) + assert(source1Fetches == List(Set(1, 2))) + assert(source2Fetches == List(Set(3))) + } - "using a future clump as base" in new Context { - val clump = - for { - int <- Clump.future(Future.successful(Some(1))) - collect1 <- Clump.collect(source1.get(int)) - collect2 <- Clump.collect(source2.get(int)) - } yield (collect1, collect2) - - clumpResult(clump) mustEqual Some((List(10), List(10))) - source1Fetches mustEqual List(Set(1)) - source2Fetches mustEqual List(Set(1)) - } + "using a join" - new Context { + val clump = + for { + ints1 <- Clump.collect(source1.get(1), source1.get(2)) + ints2 <- source2.get(3).join(source2.get(4)) + } yield (ints1, ints2) - "complex scenario" in new Context { - val clump = - for { - const1 <- Clump.value(1) - const2 <- Clump.value(2) - collect1 <- Clump.collect(source1.get(const1), source2.get(const2)) - collect2 <- Clump.collect(source1.get(const1), source2.get(const2)) if (true) - (join1a, join1b) <- Clump.value(4).join(Clump.value(5)) - join2 <- source1.get(collect1).join(source2.get(join1b)) - } yield (const1, const2, collect1, collect2, (join1a, join1b), join2) - - clumpResult(clump) mustEqual Some((1, 2, List(10, 20), List(10, 20), (4, 5), (List(100, 200), 50))) - source1Fetches mustEqual List(Set(1), Set(10, 20)) - source2Fetches mustEqual List(Set(2), Set(5)) + assert(clumpResult(clump) == Some(List(10, 20), (30, 40))) + assert(source1Fetches == List(Set(1, 2))) + assert(source2Fetches == List(Set(3, 4))) + } + + "using a future clump as base" - new Context { + val clump = + for { + int <- Clump.future(Future.successful(Some(1))) + collect1 <- Clump.collect(source1.get(int)) + collect2 <- Clump.collect(source2.get(int)) + } yield (collect1, collect2) + + assert(clumpResult(clump) == Some((List(10), List(10)))) + assert(source1Fetches == List(Set(1))) + assert(source2Fetches == List(Set(1))) + } + + "complex scenario" - new Context { + val clump = + for { + const1 <- Clump.value(1) + const2 <- Clump.value(2) + collect1 <- Clump.collect(source1.get(const1), source2.get(const2)) + collect2 <- Clump.collect(source1.get(const1), source2.get(const2)) if (true) + (join1a, join1b) <- Clump.value(4).join(Clump.value(5)) + join2 <- source1.get(collect1).join(source2.get(join1b)) + } yield (const1, const2, collect1, collect2, (join1a, join1b), join2) + + assert(clumpResult(clump) == Some((1, 2, List(10, 20), List(10, 20), (4, 5), (List(100, 200), 50)))) + assert(source1Fetches == List(Set(1), Set(10, 20))) + assert(source2Fetches == List(Set(2), Set(5))) + } } } - } - "executes joined clumps in parallel" in new Context { - val promises = List(Promise[Map[Int, Int]](), Promise[Map[Int, Int]]()) + "executes joined clumps - parallel" - new Context { + val promises = List(Promise[Map[Int, Int]](), Promise[Map[Int, Int]]()) - val promisesIterator = promises.iterator + val promisesIterator = promises.iterator - protected override def fetchFunction(fetches: ListBuffer[Set[Int]], inputs: Set[Int]) = - promisesIterator.next.future + protected override def fetchFunction(fetches: ListBuffer[Set[Int]], inputs: Set[Int]) = + promisesIterator.next.future - val clump = source1.get(1).join(source2.get(2)) + val clump = source1.get(1).join(source2.get(2)) - val future: Future[Option[(Int, Int)]] = clump.get + val future: Future[Option[(Int, Int)]] = clump.get - promises.size mustEqual 2 - } + assert(promises.size == 2) + } - "short-circuits the computation in case of a failure" in new Context { - val clump = Clump.exception[Int](new IllegalStateException).map(_ => throw new NullPointerException) - clumpResult(clump) must throwA[IllegalStateException] + "short-circuits the computation - case of a failure" - new Context { + val clump = Clump.exception[Int](new IllegalStateException).map(_ => throw new NullPointerException) + intercept[IllegalStateException] { + clumpResult(clump) + } + } } } \ No newline at end of file diff --git a/src/test/scala/io/getclump/ClumpFetcherSpec.scala b/src/test/scala/io/getclump/ClumpFetcherSpec.scala index 64a5cfb..02970cd 100644 --- a/src/test/scala/io/getclump/ClumpFetcherSpec.scala +++ b/src/test/scala/io/getclump/ClumpFetcherSpec.scala @@ -1,114 +1,100 @@ package io.getclump -import org.junit.runner.RunWith -import org.mockito.Mockito.times -import org.mockito.Mockito.verify -import org.mockito.Mockito.verifyNoMoreInteractions -import org.mockito.Mockito.when -import org.specs2.specification.Scope -import org.specs2.runner.JUnitRunner +import utest._ -@RunWith(classOf[JUnitRunner]) -class ClumpFetcherSpec extends Spec { +object ClumpFetcherSpec extends Spec { - trait SetContext extends Scope { + val tests = TestSuite { - trait TestRepository { - def fetch(inputs: Set[Int]): Future[Map[Int, Int]] - } + "memoizes the results of previous fetches" - { + object repo { + def fetch(inputs: List[Int]) = + inputs match { + case List(1, 2) => Future(Map(1 -> 10, 2 -> 20)) + case List(3) => Future(Map(3 -> 30)) + } + } - val repo = smartMock[TestRepository] - } + val source = Clump.source(repo.fetch _) + val clump1 = Clump.traverse(List(1, 2))(source.get) + val clump2 = Clump.traverse(List(2, 3))(source.get) - trait ListContext extends Scope { + val clump = + for { + v1 <- clump1 + v2 <- clump2 + } yield (v1, v2) - trait TestRepository { - def fetch(inputs: List[Int]): Future[List[String]] + assert(clumpResult(clump) == Some((List(10, 20), List(20, 30)))) } - val repo = smartMock[TestRepository] - } - - "memoizes the results of previous fetches" in new SetContext { - val source = Clump.source(repo.fetch _) - - when(repo.fetch(Set(1, 2))).thenReturn(Future(Map(1 -> 10, 2 -> 20))) - when(repo.fetch(Set(3))).thenReturn(Future(Map(3 -> 30))) - - val clump1 = Clump.traverse(List(1, 2))(source.get) - val clump2 = Clump.traverse(List(2, 3))(source.get) - - val clump = - for { - v1 <- clump1 - v2 <- clump2 - } yield (v1, v2) - - clumpResult(clump) mustEqual Some((List(10, 20), List(20, 30))) - - verify(repo).fetch(Set(1, 2)) - verify(repo).fetch(Set(3)) - verifyNoMoreInteractions(repo) - } - - "limits the batch size" in new SetContext { - val source = Clump.source(repo.fetch _).maxBatchSize(2) + "limits the batch size" - { + object repo { + def fetch(inputs: List[Int]) = + inputs match { + case List(1, 2) => Future(Map(1 -> 10, 2 -> 20)) + case List(3) => Future(Map(3 -> 30)) + } + } - when(repo.fetch(Set(1, 2))).thenReturn(Future(Map(1 -> 10, 2 -> 20))) - when(repo.fetch(Set(3))).thenReturn(Future(Map(3 -> 30))) + val source = Clump.source(repo.fetch _).maxBatchSize(2) - val clump = Clump.traverse(List(1, 2, 3))(source.get) + val clump = Clump.traverse(List(1, 2, 3))(source.get) - clumpResult(clump) mustEqual Some(List(10, 20, 30)) - - verify(repo).fetch(Set(1, 2)) - verify(repo).fetch(Set(3)) - verifyNoMoreInteractions(repo) - } + assert(clumpResult(clump) == Some(List(10, 20, 30))) + } - "retries failed fetches" >> { - "success (below the retries limit)" in new SetContext { - val source = - Clump.source(repo.fetch _).maxRetries { - case e: IllegalStateException => 1 + "retries failed fetches" - { + "success (below the retries limit)" - { + object repo { + private var firstCall = true + def fetch(inputs: List[Int]) = + if (firstCall) { + firstCall = false + Future.failed(new IllegalStateException) + } else + inputs match { + case List(1) => Future(Map(1 -> 10)) + } } - when(repo.fetch(Set(1))) - .thenReturn(Future.failed(new IllegalStateException)) - .thenReturn(Future(Map(1 -> 10))) + val source = + Clump.source(repo.fetch _).maxRetries { + case e: IllegalStateException => 1 + } - clumpResult(source.get(1)) mustEqual Some(10) + assert(clumpResult(source.get(1)) == Some(10)) + } - verify(repo, times(2)).fetch(Set(1)) - verifyNoMoreInteractions(repo) - } - - "failure (above the retries limit)" in new SetContext { - val source = - Clump.source(repo.fetch _).maxRetries { - case e: IllegalStateException => 1 + "failure (above the retries limit)" - { + object repo { + def fetch(inputs: List[Int]): Future[Map[Int, Int]] = + Future.failed(new IllegalStateException) } - when(repo.fetch(Set(1))) - .thenReturn(Future.failed(new IllegalStateException)) - .thenReturn(Future.failed(new IllegalStateException)) - - clumpResult(source.get(1)) must throwA[IllegalStateException] + val source = + Clump.source(repo.fetch _).maxRetries { + case e: IllegalStateException => 1 + } - verify(repo, times(2)).fetch(Set(1)) - verifyNoMoreInteractions(repo) + intercept[IllegalStateException] { + clumpResult(source.get(1)) + } + } } - } - - "honours call order for fetches" in new ListContext { - val source = Clump.source(repo.fetch _)(_.toInt) - when(repo.fetch(List(1, 2, 3))).thenReturn(Future(List("1", "2", "3"))) - when(repo.fetch(List(1, 3, 2))).thenReturn(Future(List("1", "3", "2"))) - clumpResult(Clump.traverse(List(1, 2, 3))(source.get)) - verify(repo).fetch(List(1, 2, 3)) - - clumpResult(Clump.traverse(List(1, 3, 2))(source.get)) - verify(repo).fetch(List(1, 3, 2)) + "honours call order for fetches" - { + object repo { + def fetch(inputs: List[Int]) = + inputs match { + case List(1, 2, 3) => Future(List("1", "2", "3")) + case List(1, 3, 2) => Future(List("1", "3", "2")) + } + } + val source = Clump.source(repo.fetch _)(_.toInt) + + clumpResult(Clump.traverse(List(1, 2, 3))(source.get)) + clumpResult(Clump.traverse(List(1, 3, 2))(source.get)) + } } } \ No newline at end of file diff --git a/src/test/scala/io/getclump/ClumpSourceSpec.scala b/src/test/scala/io/getclump/ClumpSourceSpec.scala index f64d55d..af55c85 100644 --- a/src/test/scala/io/getclump/ClumpSourceSpec.scala +++ b/src/test/scala/io/getclump/ClumpSourceSpec.scala @@ -1,105 +1,91 @@ package io.getclump -import org.junit.runner.RunWith -import org.mockito.Mockito.verify -import org.mockito.Mockito.verifyNoMoreInteractions -import org.mockito.Mockito.when -import org.specs2.specification.Scope -import org.specs2.runner.JUnitRunner - -@RunWith(classOf[JUnitRunner]) -class ClumpSourceSpec extends Spec { - - trait Context extends Scope { - trait TestRepository { - def fetch(inputs: Set[Int]): Future[Map[Int, Int]] - def fetchWithScope(fromScope: Int, inputs: Set[Int]): Future[Map[Int, Int]] - } - - val repo = smartMock[TestRepository] - } +import utest._ - "fetches an individual clump" in new Context { - val source = Clump.source(repo.fetch _) +object ClumpSourceSpec extends Spec { - when(repo.fetch(Set(1))).thenReturn(Future(Map(1 -> 2))) + val tests = TestSuite { - clumpResult(source.get(1)) mustEqual Some(2) - - verify(repo).fetch(Set(1)) - verifyNoMoreInteractions(repo) - } + "fetches an individual clump" - { + object repo { + def fetch(inputs: List[Int]) = + inputs match { + case List(1) => Future(Map(1 -> 2)) + } + } - "fetches multiple clumps" >> { - - "using list" in new Context { val source = Clump.source(repo.fetch _) - when(repo.fetch(Set(1, 2))).thenReturn(Future(Map(1 -> 10, 2 -> 20))) - - val clump = source.get(List(1, 2)) - - clumpResult(clump) ==== Some(List(10, 20)) - - verify(repo).fetch(Set(1, 2)) - verifyNoMoreInteractions(repo) + assert(clumpResult(source.get(1)) == Some(2)) } - "using set" in new Context { - val source = Clump.source(repo.fetch _) + "fetches multiple clumps" - { - when(repo.fetch(Set(1, 2))).thenReturn(Future(Map(1 -> 10, 2 -> 20))) + "using list" - { + object repo { + def fetch(inputs: List[Int]) = + inputs match { + case List(1, 2) => Future(Map(1 -> 10, 2 -> 20)) + } + } - val clump = source.get(Set(1, 2)) + val source = Clump.source(repo.fetch _) - clumpResult(clump) ==== Some(Set(10, 20)) + val clump = source.get(List(1, 2)) - verify(repo).fetch(Set(1, 2)) - verifyNoMoreInteractions(repo) - } - } + assert(clumpResult(clump) == Some(List(10, 20))) + } - "can be used as a non-singleton" >> { - "without values from the outer scope" in new Context { - val source = Clump.source(repo.fetch _) + "can be used as a non-singleton" - { + "without values from the outer scope" - { + object repo { + def fetch(inputs: List[Int]) = + inputs match { + case List(1) => Future(Map(1 -> 2)) + } + } + val source = Clump.source(repo.fetch _) - when(repo.fetch(Set(1))).thenReturn(Future(Map(1 -> 2))) + val clump = + Clump.collect { + for (i <- 0 until 5) yield { + source.get(1) + } + } - val clump = - Clump.collect { - (for (i <- 0 until 5) yield { - source.get(List(1)) - }).toList + assert(clumpResult(clump) == Some(List(2, 2, 2, 2, 2))) } - awaitResult(clump.get) + "with values from the outer scope" - { + val scope = 1 - verify(repo).fetch(Set(1)) - verifyNoMoreInteractions(repo) - } + object repo { + def fetchWithScope(fromScope: Int, inputs: List[Int]) = + (fromScope, inputs) match { + case (scope, List(1)) => Future(Map(1 -> 2)) + } + } - "with values from the outer scope" in new Context { - val scope = 1 - val source = Clump.source((inputs: Set[Int]) => repo.fetchWithScope(scope, inputs)) + val source = Clump.source((inputs: List[Int]) => repo.fetchWithScope(scope, inputs)) - when(repo.fetchWithScope(scope, Set(1))).thenReturn(Future(Map(1 -> 2))) + val clump = + Clump.collect { + for (i <- 0 until 5) yield { + source.get(1) + } + } - val clump = - Clump.collect { - (for (i <- 0 until 5) yield { - source.get(List(1)) - }).toList + assert(clumpResult(clump) == Some(List(2, 2, 2, 2, 2))) } + } - awaitResult(clump.get) - - verify(repo).fetchWithScope(1, Set(1)) - verifyNoMoreInteractions(repo) + "limits the batch size to 100 by default" - { + object repo { + def fetch(inputs: List[Int]): Future[Map[Int, Int]] = ??? + } + val source = Clump.source(repo.fetch _) + assert(source.maxBatchSize == 100) + } } } - - "limits the batch size to 100 by default" in new Context { - val source = Clump.source(repo.fetch _) - source.maxBatchSize mustEqual 100 - } } \ No newline at end of file diff --git a/src/test/scala/io/getclump/IntegrationSpec.scala b/src/test/scala/io/getclump/IntegrationSpec.scala index 64b756d..e626a77 100644 --- a/src/test/scala/io/getclump/IntegrationSpec.scala +++ b/src/test/scala/io/getclump/IntegrationSpec.scala @@ -1,10 +1,9 @@ package io.getclump -import org.junit.runner.RunWith -import org.specs2.runner.JUnitRunner +import utest._ + +object IntegrationSpec extends Spec { -@RunWith(classOf[JUnitRunner]) -class IntegrationSpec extends Spec { val tweetRepository = new TweetRepository val userRepository = new UserRepository val zipUserRepository = new ZipUserRepository @@ -22,79 +21,90 @@ class IntegrationSpec extends Spec { val likes = Clump.source(likeRepository.likesFor _)(_.likeId) val tracks = Clump.source(trackRepository.tracksFor _)(_.trackId) - "A Clump should batch calls to services" in { - val tweetRepositoryMock = mock[TweetRepository] - val tweets = Clump.source(tweetRepositoryMock.tweetsFor _) - - val userRepositoryMock = mock[UserRepository] - val users = Clump.source(userRepositoryMock.usersFor _) - val topTracks = Clump.sourceSingle(topTracksRepository.topTracksFor _) - - tweetRepositoryMock.tweetsFor(Set(1L, 2L, 3L)) returns - Future.successful(Map( - 1L -> Tweet("Tweet1", 10), - 2L -> Tweet("Tweet2", 20), - 3L -> Tweet("Tweet3", 30) - )) - - userRepositoryMock.usersFor(Set(10L, 20L, 30L)) returns - Future.successful(Map( - 10L -> User(10, "User10"), - 20L -> User(20, "User20"), - 30L -> User(30, "User30") - )) - - val enrichedTweets = Clump.traverse(1, 2, 3) { tweetId => - for { - tweet <- tweets.get(tweetId) - user <- users.get(tweet.userId) - tracks <- topTracks.get(user) - } yield (tweet, user, tracks) - } + val tests = TestSuite { + + "A Clump should batch calls to services" - { + val tweetRepositoryMock = new TweetRepository { + override def tweetsFor(ids: Set[Long]) = + ids.toList match { + case List(1L, 2L, 3L) => + Future.successful(Map( + 1L -> Tweet("Tweet1", 10), + 2L -> Tweet("Tweet2", 20), + 3L -> Tweet("Tweet3", 30))) + } - awaitResult(enrichedTweets.get) ==== Some(List( - (Tweet("Tweet1", 10), User(10, "User10"), Set(Track(10, "Track10"), Track(11, "Track11"), Track(12, "Track12"))), - (Tweet("Tweet2", 20), User(20, "User20"), Set(Track(20, "Track20"), Track(21, "Track21"), Track(22, "Track22"))), - (Tweet("Tweet3", 30), User(30, "User30"), Set(Track(30, "Track30"), Track(31, "Track31"), Track(32, "Track32"))))) - } + } + val tweets = Clump.source(tweetRepositoryMock.tweetsFor _) + + val userRepositoryMock = new UserRepository { + override def usersFor(ids: Set[Long]) = + ids.toList match { + case List(10L, 20L, 30L) => + Future.successful(Map( + 10L -> User(10, "User10"), + 20L -> User(20, "User20"), + 30L -> User(30, "User30"))) + } + } + val users = Clump.source(userRepositoryMock.usersFor _) + val topTracks = Clump.sourceSingle(topTracksRepository.topTracksFor _) - "A Clump should batch calls to parameterized services" in { - val parameterizedTweetRepositoryMock = mock[ParameterizedTweetRepository] - val tweets = Clump.source(parameterizedTweetRepositoryMock.tweetsFor _) - - val parameterizedUserRepositoryMock = mock[ParameterizedUserRepository] - val users = Clump.source(parameterizedUserRepositoryMock.usersFor _)(_.userId) - - parameterizedTweetRepositoryMock.tweetsFor("foo", Set(1, 2, 3)) returns - Future.successful(Map( - 1L -> Tweet("Tweet1", 10), - 2L -> Tweet("Tweet2", 20), - 3L -> Tweet("Tweet3", 30) - )) - - parameterizedUserRepositoryMock.usersFor("bar", Set(10, 20, 30)) returns - Future.successful(Set( - User(10, "User10"), - User(20, "User20"), - User(30, "User30") - )) - - val enrichedTweets = Clump.traverse(1, 2, 3) { tweetId => - for { - tweet <- tweets.get("foo", tweetId) - user <- users.get("bar", tweet.userId) - } yield (tweet, user) + val enrichedTweets = Clump.traverse(1, 2, 3) { tweetId => + for { + tweet <- tweets.get(tweetId) + user <- users.get(tweet.userId) + tracks <- topTracks.get(user) + } yield (tweet, user, tracks) + } + + assert(awaitResult(enrichedTweets.get) == Some(List( + (Tweet("Tweet1", 10), User(10, "User10"), Set(Track(10, "Track10"), Track(11, "Track11"), Track(12, "Track12"))), + (Tweet("Tweet2", 20), User(20, "User20"), Set(Track(20, "Track20"), Track(21, "Track21"), Track(22, "Track22"))), + (Tweet("Tweet3", 30), User(30, "User30"), Set(Track(30, "Track30"), Track(31, "Track31"), Track(32, "Track32")))))) } - awaitResult(enrichedTweets.get) ==== Some(List( - (Tweet("Tweet1", 10), User(10, "User10")), - (Tweet("Tweet2", 20), User(20, "User20")), - (Tweet("Tweet3", 30), User(30, "User30")))) - } + "A Clump should batch calls to parameterized services" - { + val parameterizedTweetRepositoryMock = new ParameterizedTweetRepository { + override def tweetsFor(prefix: String, ids: Set[Long]) = + (prefix, ids.toList) match { + case ("foo", List(1, 2, 3)) => + Future.successful(Map( + 1L -> Tweet("Tweet1", 10), + 2L -> Tweet("Tweet2", 20), + 3L -> Tweet("Tweet3", 30))) + } + } + val tweets = Clump.source(parameterizedTweetRepositoryMock.tweetsFor _) + + val parameterizedUserRepositoryMock = new ParameterizedUserRepository { + override def usersFor(prefix: String, ids: Set[Long]) = + (prefix, ids.toList) match { + case ("bar", List(10, 20, 30)) => + Future.successful(Set( + User(10, "User10"), + User(20, "User20"), + User(30, "User30"))) + } + } + val users = Clump.source(parameterizedUserRepositoryMock.usersFor _)(_.userId) - "it should be able to be used in complex nested fetches" in { - val timelineIds = List(1, 3) - val enrichedTimelines = Clump.traverse(timelineIds) { id => + val enrichedTweets = Clump.traverse(1, 2, 3) { tweetId => + for { + tweet <- tweets.get("foo", tweetId) + user <- users.get("bar", tweet.userId) + } yield (tweet, user) + } + + assert(awaitResult(enrichedTweets.get) == Some(List( + (Tweet("Tweet1", 10), User(10, "User10")), + (Tweet("Tweet2", 20), User(20, "User20")), + (Tweet("Tweet3", 30), User(30, "User30"))))) + } + + "it should be able to be used - complex nested fetches" - { + val timelineIds = List(1, 3) + val enrichedTimelines = Clump.traverse(timelineIds) { id => for { timeline <- timelines.get(id) enrichedLikes <- Clump.traverse(timeline.likeIds) { id => @@ -106,145 +116,146 @@ class IntegrationSpec extends Spec { } yield (timeline, enrichedLikes) } - awaitResult(enrichedTimelines.get) ==== Some(List( - (Timeline(1, List(10, 20)), List( - (Like(10, List(100, 200), List(1000, 2000)), List(Track(100, "Track100"), Track(200, "Track200")), List(User(1000, "User1000"), User(2000, "User2000"))), - (Like(20, List(200, 400), List(2000, 4000)), List(Track(200, "Track200"), Track(400, "Track400")), List(User(2000, "User2000"), User(4000, "User4000"))))), - (Timeline(3, List(30, 60)), List( - (Like(30, List(300, 600), List(3000, 6000)), List(Track(300, "Track300"), Track(600, "Track600")), List(User(3000, "User3000"), User(6000, "User6000"))), - (Like(60, List(600, 1200), List(6000, 12000)), List(Track(600, "Track600"), Track(1200, "Track1200")), List(User(6000, "User6000"), User(12000, "User12000"))))))) - } + assert(awaitResult(enrichedTimelines.get) == Some(List( + (Timeline(1, List(10, 20)), List( + (Like(10, List(100, 200), List(1000, 2000)), List(Track(100, "Track100"), Track(200, "Track200")), List(User(1000, "User1000"), User(2000, "User2000"))), + (Like(20, List(200, 400), List(2000, 4000)), List(Track(200, "Track200"), Track(400, "Track400")), List(User(2000, "User2000"), User(4000, "User4000"))))), + (Timeline(3, List(30, 60)), List( + (Like(30, List(300, 600), List(3000, 6000)), List(Track(300, "Track300"), Track(600, "Track600")), List(User(3000, "User3000"), User(6000, "User6000"))), + (Like(60, List(600, 1200), List(6000, 12000)), List(Track(600, "Track600"), Track(1200, "Track1200")), List(User(6000, "User6000"), User(12000, "User12000")))))))) + } - "it should be usable with regular maps and flatMaps" in { - val tweetIds = List(1L, 2L, 3L) - val enrichedTweets: Clump[List[(Tweet, User)]] = - Clump.traverse(tweetIds) { tweetId => - tweets.get(tweetId).flatMap(tweet => - users.get(tweet.userId).map(user => (tweet, user))) - } + "it should be usable with regular maps and flatMaps" - { + val tweetIds = List(1L, 2L, 3L) + val enrichedTweets: Clump[List[(Tweet, User)]] = + Clump.traverse(tweetIds) { tweetId => + tweets.get(tweetId).flatMap(tweet => + users.get(tweet.userId).map(user => (tweet, user))) + } + + assert(awaitResult(enrichedTweets.get) == Some(List( + (Tweet("Tweet1", 10), User(10, "User10")), + (Tweet("Tweet2", 20), User(20, "User20")), + (Tweet("Tweet3", 30), User(30, "User30"))))) + } - awaitResult(enrichedTweets.get) ==== Some(List( - (Tweet("Tweet1", 10), User(10, "User10")), - (Tweet("Tweet2", 20), User(20, "User20")), - (Tweet("Tweet3", 30), User(30, "User30")))) - } + "it should allow unwrapping Clumped lists with clump.list" - { + val enrichedTweets: Clump[List[(Tweet, User)]] = Clump.traverse(1, 2, 3) { tweetId => + for { + tweet <- tweets.get(tweetId) + user <- users.get(tweet.userId) + } yield (tweet, user) + } - "it should allow unwrapping Clumped lists with clump.list" in { - val enrichedTweets: Clump[List[(Tweet, User)]] = Clump.traverse(1, 2, 3) { tweetId => - for { - tweet <- tweets.get(tweetId) - user <- users.get(tweet.userId) - } yield (tweet, user) + assert(awaitResult(enrichedTweets.list) == List( + (Tweet("Tweet1", 10), User(10, "User10")), + (Tweet("Tweet2", 20), User(20, "User20")), + (Tweet("Tweet3", 30), User(30, "User30")))) } - awaitResult(enrichedTweets.list) ==== List( - (Tweet("Tweet1", 10), User(10, "User10")), - (Tweet("Tweet2", 20), User(20, "User20")), - (Tweet("Tweet3", 30), User(30, "User30"))) - } + "it should work with Clump.sourceZip" - { + val enrichedTweets = Clump.traverse(1, 2, 3) { tweetId => + for { + tweet <- tweets.get(tweetId) + user <- zippedUsers.get(tweet.userId) + } yield (tweet, user) + } - "it should work with Clump.sourceZip" in { - val enrichedTweets = Clump.traverse(1, 2, 3) { tweetId => - for { - tweet <- tweets.get(tweetId) - user <- zippedUsers.get(tweet.userId) - } yield (tweet, user) + assert(awaitResult(enrichedTweets.get) == Some(List( + (Tweet("Tweet1", 10), User(10, "User10")), + (Tweet("Tweet2", 20), User(20, "User20")), + (Tweet("Tweet3", 30), User(30, "User30"))))) } - awaitResult(enrichedTweets.get) ==== Some(List( - (Tweet("Tweet1", 10), User(10, "User10")), - (Tweet("Tweet2", 20), User(20, "User20")), - (Tweet("Tweet3", 30), User(30, "User30")))) - } + "A Clump can have a partial result" - { + val onlyFullObjectGraph: Clump[List[(Tweet, User)]] = Clump.traverse(1, 2, 3) { tweetId => + for { + tweet <- tweets.get(tweetId) + user <- filteredUsers.get(tweet.userId) + } yield (tweet, user) + } - "A Clump can have a partial result" in { - val onlyFullObjectGraph: Clump[List[(Tweet, User)]] = Clump.traverse(1, 2, 3) { tweetId => - for { - tweet <- tweets.get(tweetId) - user <- filteredUsers.get(tweet.userId) - } yield (tweet, user) - } + assert(awaitResult(onlyFullObjectGraph.get) == Some(List((Tweet("Tweet2", 20), User(20, "User20"))))) - awaitResult(onlyFullObjectGraph.get) ==== Some(List((Tweet("Tweet2", 20), User(20, "User20")))) + val partialResponses: Clump[List[(Tweet, Option[User])]] = Clump.traverse(1, 2, 3) { tweetId => + for { + tweet <- tweets.get(tweetId) + user <- filteredUsers.get(tweet.userId).optional + } yield (tweet, user) + } - val partialResponses: Clump[List[(Tweet, Option[User])]] = Clump.traverse(1, 2, 3) { tweetId => - for { - tweet <- tweets.get(tweetId) - user <- filteredUsers.get(tweet.userId).optional - } yield (tweet, user) + assert(awaitResult(partialResponses.get) == Some(List( + (Tweet("Tweet1", 10), None), + (Tweet("Tweet2", 20), Some(User(20, "User20"))), + (Tweet("Tweet3", 30), None)))) } - - awaitResult(partialResponses.get) ==== Some(List( - (Tweet("Tweet1", 10), None), - (Tweet("Tweet2", 20), Some(User(20, "User20"))), - (Tweet("Tweet3", 30), None))) } -} -case class Tweet(body: String, userId: Long) + case class Tweet(body: String, userId: Long) -case class User(userId: Long, name: String) + case class User(userId: Long, name: String) -case class Timeline(timelineId: Int, likeIds: List[Long]) + case class Timeline(timelineId: Int, likeIds: List[Long]) -case class Like(likeId: Long, trackIds: List[Long], userIds: List[Long]) + case class Like(likeId: Long, trackIds: List[Long], userIds: List[Long]) -case class Track(trackId: Long, name: String) + case class Track(trackId: Long, name: String) -class TweetRepository { - def tweetsFor(ids: Set[Long]): Future[Map[Long, Tweet]] = { - Future.successful(ids.map(id => id -> Tweet(s"Tweet$id", id * 10)).toMap) + class TweetRepository { + def tweetsFor(ids: Set[Long]): Future[Map[Long, Tweet]] = { + Future.successful(ids.map(id => id -> Tweet(s"Tweet$id", id * 10)).toMap) + } } -} -trait ParameterizedTweetRepository { - def tweetsFor(prefix: String, ids: Set[Long]): Future[Map[Long, Tweet]] -} + trait ParameterizedTweetRepository { + def tweetsFor(prefix: String, ids: Set[Long]): Future[Map[Long, Tweet]] + } -class UserRepository { - def usersFor(ids: Set[Long]): Future[Map[Long, User]] = { - Future.successful(ids.map(id => id -> User(id, s"User$id")).toMap) + class UserRepository { + def usersFor(ids: Set[Long]): Future[Map[Long, User]] = { + Future.successful(ids.map(id => id -> User(id, s"User$id")).toMap) + } } -} -trait ParameterizedUserRepository { - def usersFor(prefix: String, ids: Set[Long]): Future[Set[User]] -} + trait ParameterizedUserRepository { + def usersFor(prefix: String, ids: Set[Long]): Future[Set[User]] + } -class ZipUserRepository { - def usersFor(ids: List[Long]): Future[List[User]] = { - Future.successful(ids.map(id => User(id, s"User$id"))) + class ZipUserRepository { + def usersFor(ids: List[Long]): Future[List[User]] = { + Future.successful(ids.map(id => User(id, s"User$id"))) + } } -} -class FilteredUserRepository { - def usersFor(ids: Set[Long]): Future[Set[User]] = { - Future.successful(ids.filter(_ % 20 == 0).map(id => User(id, s"User$id"))) + class FilteredUserRepository { + def usersFor(ids: Set[Long]): Future[Set[User]] = { + Future.successful(ids.filter(_ % 20 == 0).map(id => User(id, s"User$id"))) + } } -} -class TimelineRepository { - def timelinesFor(ids: Set[Int]): Future[Set[Timeline]] = { - Future.successful(ids.map(id => Timeline(id, List(id * 10, id * 20)))) + class TimelineRepository { + def timelinesFor(ids: Set[Int]): Future[Set[Timeline]] = { + Future.successful(ids.map(id => Timeline(id, List(id * 10, id * 20)))) + } } -} -class LikeRepository { - def likesFor(ids: Set[Long]): Future[Set[Like]] = { - Future.successful(ids.map(id => Like(id, List(id * 10, id * 20), List(id * 100, id * 200)))) + class LikeRepository { + def likesFor(ids: Set[Long]): Future[Set[Like]] = { + Future.successful(ids.map(id => Like(id, List(id * 10, id * 20), List(id * 100, id * 200)))) + } } -} -class TrackRepository { - def tracksFor(ids: Set[Long]): Future[Set[Track]] = { - Future.successful(ids.map(id => Track(id, s"Track$id"))) + class TrackRepository { + def tracksFor(ids: Set[Long]): Future[Set[Track]] = { + Future.successful(ids.map(id => Track(id, s"Track$id"))) + } } -} -class TopTracksRepository { - def topTracksFor(user: User): Future[Set[Track]] = { - def track(id: Long): Track = Track(id, s"Track$id") - val userId = user.userId - Future.successful(Set(track(userId), track(userId + 1), track(userId + 2))) + class TopTracksRepository { + def topTracksFor(user: User): Future[Set[Track]] = { + def track(id: Long): Track = Track(id, s"Track$id") + val userId = user.userId + Future.successful(Set(track(userId), track(userId + 1), track(userId + 2))) + } } } diff --git a/src/test/scala/io/getclump/SourcesSpec.scala b/src/test/scala/io/getclump/SourcesSpec.scala index e6b6a2d..9115031 100644 --- a/src/test/scala/io/getclump/SourcesSpec.scala +++ b/src/test/scala/io/getclump/SourcesSpec.scala @@ -1,197 +1,198 @@ package io.getclump -import org.junit.runner.RunWith -import org.specs2.runner.JUnitRunner +import utest._ -@RunWith(classOf[JUnitRunner]) -class SourcesSpec extends Spec { +object SourcesSpec extends Spec { - "creates a clump source" >> { - "set input" in { - def fetch(inputs: Set[Int]) = Future.successful(inputs.map(i => i -> i.toString).toMap) - val source = Clump.source(fetch _) - clumpResult(source.get(1)) mustEqual Some("1") - } - "list input" in { - def fetch(inputs: List[Int]) = Future.successful(inputs.map(i => i -> i.toString).toMap) - val source = Clump.source(fetch _) - clumpResult(source.get(1)) mustEqual Some("1") - } - "extra params" >> { - "one" in { - def fetch(param1: Int, values: List[Int]) = - Future(values.map(v => v -> v * param1).toMap) - val source = Clump.source(fetch _) - val clump = Clump.collect(source.get(2, 3), source.get(2, 4), source.get(3, 5)) - clumpResult(clump) mustEqual Some(List(6, 8, 15)) - } - "two" in { - def fetch(param1: Int, param2: String, values: List[Int]) = - Future(values.map(v => v -> List(param1, param2, v)).toMap) - val source = Clump.source(fetch _) - val clump = Clump.collect(source.get(1, "2", 3), source.get(1, "2", 4), source.get(2, "3", 5)) - clumpResult(clump) mustEqual Some(List(List(1, "2", 3), List(1, "2", 4), List(2, "3", 5))) - } - "three" in { - def fetch(param1: Int, param2: String, param3: List[String], values: List[Int]) = - Future(values.map(v => v -> List(param1, param2, param3, v)).toMap) + val tests = TestSuite { + + "creates a clump source" - { + "set input" - { + def fetch(inputs: Set[Int]) = Future.successful(inputs.map(i => i -> i.toString).toMap) val source = Clump.source(fetch _) - val clump = Clump.collect(source.get(1, "2", List("a"), 3), source.get(1, "2", List("a"), 4), source.get(2, "3", List("b"), 5)) - clumpResult(clump) mustEqual Some(List(List(1, "2", List("a"), 3), List(1, "2", List("a"), 4), List(2, "3", List("b"), 5))) + assert(clumpResult(source.get(1)) == Some("1")) } - "four" in { - def fetch(param1: Int, param2: String, param3: List[String], param4: Boolean, values: List[Int]) = - Future(values.map(v => v -> List(param1, param2, param3, param4, v)).toMap) + "list input" - { + def fetch(inputs: List[Int]) = Future.successful(inputs.map(i => i -> i.toString).toMap) val source = Clump.source(fetch _) - val clump = Clump.collect(source.get(1, "2", List("a"), true, 3), source.get(1, "2", List("a"), true, 4), source.get(2, "3", List("b"), false, 5)) - clumpResult(clump) mustEqual Some(List(List(1, "2", List("a"), true, 3), List(1, "2", List("a"), true, 4), List(2, "3", List("b"), false, 5))) + assert(clumpResult(source.get(1)) == Some("1")) + } + "extra params" - { + "one" - { + def fetch(param1: Int, values: List[Int]) = + Future(values.map(v => v -> v * param1).toMap) + val source = Clump.source(fetch _) + val clump = Clump.collect(source.get(2, 3), source.get(2, 4), source.get(3, 5)) + assert(clumpResult(clump) == Some(List(6, 8, 15))) + } + "two" - { + def fetch(param1: Int, param2: String, values: List[Int]) = + Future(values.map(v => v -> List(param1, param2, v)).toMap) + val source = Clump.source(fetch _) + val clump = Clump.collect(source.get(1, "2", 3), source.get(1, "2", 4), source.get(2, "3", 5)) + assert(clumpResult(clump) == Some(List(List(1, "2", 3), List(1, "2", 4), List(2, "3", 5)))) + } + "three" - { + def fetch(param1: Int, param2: String, param3: List[String], values: List[Int]) = + Future(values.map(v => v -> List(param1, param2, param3, v)).toMap) + val source = Clump.source(fetch _) + val clump = Clump.collect(source.get(1, "2", List("a"), 3), source.get(1, "2", List("a"), 4), source.get(2, "3", List("b"), 5)) + assert(clumpResult(clump) == Some(List(List(1, "2", List("a"), 3), List(1, "2", List("a"), 4), List(2, "3", List("b"), 5)))) + } + "four" - { + def fetch(param1: Int, param2: String, param3: List[String], param4: Boolean, values: List[Int]) = + Future(values.map(v => v -> List(param1, param2, param3, param4, v)).toMap) + val source = Clump.source(fetch _) + val clump = Clump.collect(source.get(1, "2", List("a"), true, 3), source.get(1, "2", List("a"), true, 4), source.get(2, "3", List("b"), false, 5)) + assert(clumpResult(clump) == Some(List(List(1, "2", List("a"), true, 3), List(1, "2", List("a"), true, 4), List(2, "3", List("b"), false, 5)))) + } } } - } - "creates a clump source with key function" >> { - "set input" in { - def fetch(inputs: Set[Int]) = Future.successful(inputs.map(_.toString)) - val source = Clump.source(fetch _)(_.toInt) - clumpResult(source.get(1)) mustEqual Some("1") - } - "seq input" in { - def fetch(inputs: Seq[Int]) = Future.successful(inputs.map(_.toString)) - val source = Clump.source(fetch _)(_.toInt) - clumpResult(source.get(1)) mustEqual Some("1") - } - "extra params" >> { - "one" in { - def fetch(param1: Int, values: List[Int]) = Future(values.map((param1, _))) - val source = Clump.source(fetch _)(_._2) - val clump = Clump.collect(source.get(2, 3), source.get(2, 4), source.get(3, 5)) - clumpResult(clump) mustEqual Some(List((2, 3), (2, 4), (3, 5))) - } - "two" in { - def fetch(param1: Int, param2: String, values: List[Int]) = Future(values.map((param1, param2, _))) - val source = Clump.source(fetch _)(_._3) - val clump = Clump.collect(source.get(1, "2", 3), source.get(1, "2", 4), source.get(2, "3", 5)) - clumpResult(clump) mustEqual Some(List((1, "2", 3), (1, "2", 4), (2, "3", 5))) - } - "three" in { - def fetch(param1: Int, param2: String, param3: List[String], values: List[Int]) = - Future(values.map((param1, param2, param3, _))) - val source = Clump.source(fetch _)(_._4) - val clump = Clump.collect(source.get(1, "2", List("a"), 3), source.get(1, "2", List("a"), 4), source.get(2, "3", List("b"), 5)) - clumpResult(clump) mustEqual Some(List((1, "2", List("a"), 3), (1, "2", List("a"), 4), (2, "3", List("b"), 5))) - } - "four" in { - def fetch(param1: Int, param2: String, param3: List[String], param4: Boolean, values: List[Int]) = - Future(values.map((param1, param2, param3, param4, _))) - val source = Clump.source(fetch _)(_._5) - val clump = Clump.collect(source.get(1, "2", List("a"), true, 3), source.get(1, "2", List("a"), true, 4), source.get(2, "3", List("b"), false, 5)) - clumpResult(clump) mustEqual Some(List((1, "2", List("a"), true, 3), (1, "2", List("a"), true, 4), (2, "3", List("b"), false, 5))) + "creates a clump source with key function" - { + "set input" - { + def fetch(inputs: Set[Int]) = Future.successful(inputs.map(_.toString)) + val source = Clump.source(fetch _)(_.toInt) + assert(clumpResult(source.get(1)) == Some("1")) + } + "seq input" - { + def fetch(inputs: Seq[Int]) = Future.successful(inputs.map(_.toString)) + val source = Clump.source(fetch _)(_.toInt) + assert(clumpResult(source.get(1)) == Some("1")) + } + "extra params" - { + "one" - { + def fetch(param1: Int, values: List[Int]) = Future(values.map((param1, _))) + val source = Clump.source(fetch _)(_._2) + val clump = Clump.collect(source.get(2, 3), source.get(2, 4), source.get(3, 5)) + assert(clumpResult(clump) == Some(List((2, 3), (2, 4), (3, 5)))) + } + "two" - { + def fetch(param1: Int, param2: String, values: List[Int]) = Future(values.map((param1, param2, _))) + val source = Clump.source(fetch _)(_._3) + val clump = Clump.collect(source.get(1, "2", 3), source.get(1, "2", 4), source.get(2, "3", 5)) + assert(clumpResult(clump) == Some(List((1, "2", 3), (1, "2", 4), (2, "3", 5)))) + } + "three" - { + def fetch(param1: Int, param2: String, param3: List[String], values: List[Int]) = + Future(values.map((param1, param2, param3, _))) + val source = Clump.source(fetch _)(_._4) + val clump = Clump.collect(source.get(1, "2", List("a"), 3), source.get(1, "2", List("a"), 4), source.get(2, "3", List("b"), 5)) + assert(clumpResult(clump) == Some(List((1, "2", List("a"), 3), (1, "2", List("a"), 4), (2, "3", List("b"), 5)))) + } + "four" - { + def fetch(param1: Int, param2: String, param3: List[String], param4: Boolean, values: List[Int]) = + Future(values.map((param1, param2, param3, param4, _))) + val source = Clump.source(fetch _)(_._5) + val clump = Clump.collect(source.get(1, "2", List("a"), true, 3), source.get(1, "2", List("a"), true, 4), source.get(2, "3", List("b"), false, 5)) + assert(clumpResult(clump) == Some(List((1, "2", List("a"), true, 3), (1, "2", List("a"), true, 4), (2, "3", List("b"), false, 5)))) + } } } - } - "creates a clump source with zip as the key function" >> { - "list input" in { - def fetch(inputs: List[Int]) = Future.successful(inputs.map(_.toString)) - val source = Clump.sourceZip(fetch _) - clumpResult(source.get(1)) mustEqual Some("1") - } - "extra params" >> { - "one" in { - def fetch(param1: Int, inputs: List[Int]) = Future.successful(inputs.map(_ + param1).map(_.toString)) - val source = Clump.sourceZip(fetch _) - clumpResult(source.get(1, 2)) mustEqual Some("3") - } - "two" in { - def fetch(param1: Int, param2: String, inputs: List[Int]) = Future.successful(inputs.map(_ + param1).map(_ + param2)) + "creates a clump source with zip as the key function" - { + "list input" - { + def fetch(inputs: List[Int]) = Future.successful(inputs.map(_.toString)) val source = Clump.sourceZip(fetch _) - clumpResult(source.get(1, "a", 2)) mustEqual Some("3a") - } - "three" in { - def fetch(param1: Int, param2: String, param3: List[String], inputs: List[Int]) = Future.successful(inputs.map(_ + param1).map(_ + param2).map(_ + param3.fold("")(_ + _))) - val source = Clump.sourceZip(fetch _) - clumpResult(source.get(1, "a", List("b", "c"), 2)) mustEqual Some("3abc") - } - "four" in { - def fetch(param1: Int, param2: String, param3: List[String], param4: Boolean, inputs: List[Int]) = Future.successful(inputs.map(_ + param1).map(_ + param2).map(_ + param3.fold("")(_ + _)).map(_ + s"-$param4")) - val source = Clump.sourceZip(fetch _) - clumpResult(source.get(1, "a", List("b", "c"), true, 2)) mustEqual Some("3abc-true") + assert(clumpResult(source.get(1)) == Some("1")) + } + "extra params" - { + "one" - { + def fetch(param1: Int, inputs: List[Int]) = Future.successful(inputs.map(_ + param1).map(_.toString)) + val source = Clump.sourceZip(fetch _) + assert(clumpResult(source.get(1, 2)) == Some("3")) + } + "two" - { + def fetch(param1: Int, param2: String, inputs: List[Int]) = Future.successful(inputs.map(_ + param1).map(_ + param2)) + val source = Clump.sourceZip(fetch _) + assert(clumpResult(source.get(1, "a", 2)) == Some("3a")) + } + "three" - { + def fetch(param1: Int, param2: String, param3: List[String], inputs: List[Int]) = Future.successful(inputs.map(_ + param1).map(_ + param2).map(_ + param3.fold("")(_ + _))) + val source = Clump.sourceZip(fetch _) + assert(clumpResult(source.get(1, "a", List("b", "c"), 2)) == Some("3abc")) + } + "four" - { + def fetch(param1: Int, param2: String, param3: List[String], param4: Boolean, inputs: List[Int]) = Future.successful(inputs.map(_ + param1).map(_ + param2).map(_ + param3.fold("")(_ + _)).map(_ + s"-$param4")) + val source = Clump.sourceZip(fetch _) + assert(clumpResult(source.get(1, "a", List("b", "c"), true, 2)) == Some("3abc-true")) + } } } - } - - "creates a clump source from a singly keyed fetch function" >> { - "single key input" in { - def fetch(input: Int) = Future.successful(Set(input, input + 1, input + 2)) - val source = Clump.sourceSingle(fetch _) + "creates a clump source from a singly keyed fetch function" - { + "single key input" - { + def fetch(input: Int) = Future.successful(Set(input, input + 1, input + 2)) - clumpResult(source.get(1)) ==== Some(Set(1, 2, 3)) - clumpResult(source.get(2)) ==== Some(Set(2, 3, 4)) - clumpResult(source.get(List(1, 2))) ==== Some(List(Set(1, 2, 3), Set(2, 3, 4))) - } - "extra params" >> { - "one" in { - def fetch(param1: String, input: Int) = Future.successful(input + param1) - val source = Clump.sourceSingle(fetch _) - clumpResult(source.get("a", 2)) mustEqual Some("2a") - } - "two" in { - def fetch(param1: String, param2: List[Int], input: Int) = Future.successful(input + param1 + param2.mkString) val source = Clump.sourceSingle(fetch _) - clumpResult(source.get("a", List(1, 2), 3)) mustEqual Some("3a12") - } - "three" in { - def fetch(param1: String, param2: List[Int], param3: Int, input: Int) = Future.successful(input + param1 + param2.mkString + param3) - val source = Clump.sourceSingle(fetch _) - clumpResult(source.get("a", List(1, 2), 3, 4)) mustEqual Some("4a123") - } - "four" in { - def fetch(param1: String, param2: List[Int], param3: Int, param4: List[String], input: Int) = Future.successful(input + param1 + param2.mkString + param3 + param4.mkString) - val source = Clump.sourceSingle(fetch _) - clumpResult(source.get("a", List(1, 2), 3, List("b","c"), 4)) mustEqual Some("4a123bc") + + assert(clumpResult(source.get(1)) == Some(Set(1, 2, 3))) + assert(clumpResult(source.get(2)) == Some(Set(2, 3, 4))) + assert(clumpResult(source.get(List(1, 2))) == Some(List(Set(1, 2, 3), Set(2, 3, 4)))) + } + "extra params" - { + "one" - { + def fetch(param1: String, input: Int) = Future.successful(input + param1) + val source = Clump.sourceSingle(fetch _) + assert(clumpResult(source.get("a", 2)) == Some("2a")) + } + "two" - { + def fetch(param1: String, param2: List[Int], input: Int) = Future.successful(input + param1 + param2.mkString) + val source = Clump.sourceSingle(fetch _) + assert(clumpResult(source.get("a", List(1, 2), 3)) == Some("3a12")) + } + "three" - { + def fetch(param1: String, param2: List[Int], param3: Int, input: Int) = Future.successful(input + param1 + param2.mkString + param3) + val source = Clump.sourceSingle(fetch _) + assert(clumpResult(source.get("a", List(1, 2), 3, 4)) == Some("4a123")) + } + "four" - { + def fetch(param1: String, param2: List[Int], param3: Int, param4: List[String], input: Int) = Future.successful(input + param1 + param2.mkString + param3 + param4.mkString) + val source = Clump.sourceSingle(fetch _) + assert(clumpResult(source.get("a", List(1, 2), 3, List("b", "c"), 4)) == Some("4a123bc")) + } } } - } - "creates a clump source from various input/ouput type fetch functions (ClumpSource.apply)" in { - def setToSet: Set[Int] => Future[Set[String]] = { inputs => Future.successful(inputs.map(_.toString)) } - def listToList: List[Int] => Future[List[String]] = { inputs => Future.successful(inputs.map(_.toString)) } - def iterableToIterable: Iterable[Int] => Future[Iterable[String]] = { inputs => Future.successful(inputs.map(_.toString)) } - def setToList: Set[Int] => Future[List[String]] = { inputs => Future.successful(inputs.map(_.toString).toList) } - def listToSet: List[Int] => Future[Set[String]] = { inputs => Future.successful(inputs.map(_.toString).toSet) } - def setToIterable: Set[Int] => Future[Iterable[String]] = { inputs => Future.successful(inputs.map(_.toString)) } - def listToIterable: List[Int] => Future[Iterable[String]] = { inputs => Future.successful(inputs.map(_.toString)) } - def iterableToList: Iterable[Int] => Future[List[String]] = { inputs => Future.successful(inputs.map(_.toString).toList) } - def iterableToSet: Iterable[Int] => Future[List[String]] = { inputs => Future.successful(inputs.map(_.toString).toList) } - - def testSource(source: ClumpSource[Int, String]) = - clumpResult(source.get(List(1, 2))) mustEqual Some(List("1", "2")) - - def extractId(string: String) = string.toInt - - testSource(Clump.source(setToSet)(extractId)) - testSource(Clump.source(listToList)(extractId)) - testSource(Clump.source(iterableToIterable)(extractId)) - testSource(Clump.source(setToList)(extractId)) - testSource(Clump.source(listToSet)(extractId)) - testSource(Clump.source(setToIterable)(extractId)) - testSource(Clump.source(listToIterable)(extractId)) - testSource(Clump.source(iterableToList)(extractId)) - testSource(Clump.source(iterableToSet)(extractId)) - } + "creates a clump source from various input/ouput type fetch functions (ClumpSource.apply)" - { + def setToSet: Set[Int] => Future[Set[String]] = { inputs => Future.successful(inputs.map(_.toString)) } + def listToList: List[Int] => Future[List[String]] = { inputs => Future.successful(inputs.map(_.toString)) } + def iterableToIterable: Iterable[Int] => Future[Iterable[String]] = { inputs => Future.successful(inputs.map(_.toString)) } + def setToList: Set[Int] => Future[List[String]] = { inputs => Future.successful(inputs.map(_.toString).toList) } + def listToSet: List[Int] => Future[Set[String]] = { inputs => Future.successful(inputs.map(_.toString).toSet) } + def setToIterable: Set[Int] => Future[Iterable[String]] = { inputs => Future.successful(inputs.map(_.toString)) } + def listToIterable: List[Int] => Future[Iterable[String]] = { inputs => Future.successful(inputs.map(_.toString)) } + def iterableToList: Iterable[Int] => Future[List[String]] = { inputs => Future.successful(inputs.map(_.toString).toList) } + def iterableToSet: Iterable[Int] => Future[List[String]] = { inputs => Future.successful(inputs.map(_.toString).toList) } + + def testSource(source: ClumpSource[Int, String]) = + assert(clumpResult(source.get(List(1, 2))) == Some(List("1", "2"))) + + def extractId(string: String) = string.toInt + + testSource(Clump.source(setToSet)(extractId)) + testSource(Clump.source(listToList)(extractId)) + testSource(Clump.source(iterableToIterable)(extractId)) + testSource(Clump.source(setToList)(extractId)) + testSource(Clump.source(listToSet)(extractId)) + testSource(Clump.source(setToIterable)(extractId)) + testSource(Clump.source(listToIterable)(extractId)) + testSource(Clump.source(iterableToList)(extractId)) + testSource(Clump.source(iterableToSet)(extractId)) + } - "creates a clump source from various input/ouput type fetch functions (ClumpSource.from)" in { + "creates a clump source from various input/ouput type fetch functions (ClumpSource.from)" - { - def setToMap: Set[Int] => Future[Map[Int, String]] = { inputs => Future.successful(inputs.map(input => (input, input.toString)).toMap) } - def listToMap: List[Int] => Future[Map[Int, String]] = { inputs => Future.successful(inputs.map(input => (input, input.toString)).toMap) } - def iterableToMap: Iterable[Int] => Future[Map[Int, String]] = { inputs => Future.successful(inputs.map(input => (input, input.toString)).toMap) } + def setToMap: Set[Int] => Future[Map[Int, String]] = { inputs => Future.successful(inputs.map(input => (input, input.toString)).toMap) } + def listToMap: List[Int] => Future[Map[Int, String]] = { inputs => Future.successful(inputs.map(input => (input, input.toString)).toMap) } + def iterableToMap: Iterable[Int] => Future[Map[Int, String]] = { inputs => Future.successful(inputs.map(input => (input, input.toString)).toMap) } - def testSource(source: ClumpSource[Int, String]) = - clumpResult(source.get(List(1, 2))) mustEqual Some(List("1", "2")) + def testSource(source: ClumpSource[Int, String]) = + assert(clumpResult(source.get(List(1, 2))) == Some(List("1", "2"))) - testSource(Clump.source(setToMap)) - testSource(Clump.source(listToMap)) - testSource(Clump.source(iterableToMap)) + testSource(Clump.source(setToMap)) + testSource(Clump.source(listToMap)) + testSource(Clump.source(iterableToMap)) + } } } \ No newline at end of file diff --git a/src/test/scala/io/getclump/Spec.scala b/src/test/scala/io/getclump/Spec.scala index 686768f..e30ab1a 100644 --- a/src/test/scala/io/getclump/Spec.scala +++ b/src/test/scala/io/getclump/Spec.scala @@ -1,10 +1,8 @@ package io.getclump -import org.specs2.mock.Mockito -import org.specs2.mutable.Specification -import org.specs2.time.NoTimeConversions +import utest._ -trait Spec extends Specification with Mockito with NoTimeConversions { +trait Spec extends TestSuite { protected def clumpResult[T](clump: Clump[T]) = awaitResult(clump.get) From 06119123cbb80ac20887387c4591083238b6ca62 Mon Sep 17 00:00:00 2001 From: "Flavio W. Brasil" Date: Thu, 25 Jun 2015 09:46:46 +0200 Subject: [PATCH 2/4] cross build for scalajs --- project/Build.scala | 54 ++++--- project/plugins.sbt | 4 +- .../io/getclump/package-twitter.scala.tmpl | 2 - src/main/scala/io/getclump/package.scala | 2 - src/test/scala/io/getclump/ClumpApiSpec.scala | 148 +++++++++--------- .../io/getclump/ClumpExecutionSpec.scala | 35 +++-- .../scala/io/getclump/ClumpFetcherSpec.scala | 23 ++- .../scala/io/getclump/ClumpSourceSpec.scala | 8 +- .../scala/io/getclump/IntegrationSpec.scala | 33 ++-- src/test/scala/io/getclump/SourcesSpec.scala | 52 +++--- src/test/scala/io/getclump/Spec.scala | 18 ++- 11 files changed, 213 insertions(+), 166 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 516c5c2..1b3cb4e 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -2,13 +2,15 @@ import com.typesafe.sbt.pgp.PgpKeys import sbt.Keys._ import sbt._ import sbtrelease.ReleasePlugin._ +import org.scalajs.sbtplugin.cross.CrossProject +import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._ object Build extends Build { val commonSettings = Seq( organization := "io.getclump", - scalaVersion := "2.11.5", - crossScalaVersions := Seq("2.10.4", "2.11.5"), - libraryDependencies += "com.lihaoyi" %% "utest" % "0.3.1", + scalaVersion := "2.11.6", + crossScalaVersions := Seq("2.10.4", "2.11.6"), + libraryDependencies += "com.lihaoyi" %%% "utest" % "0.3.1", testFrameworks += new TestFramework("utest.runner.Framework"), scalacOptions ++= Seq( "-deprecation", @@ -56,22 +58,34 @@ object Build extends Build { ) - lazy val clumpScala = Project(id = "clump-scala", base = file(".")) - .settings(name := "clump-scala") - .settings(commonSettings: _*) - .settings(target <<= target(_ / "clump-scala")) - .aggregate(clumpTwitter) + lazy val clump = + Project(id = "clump", base = file(".")) + .settings(scalaSource in Test := file("root")) + .settings(scalaSource in Compile := file("root")) + .settings(publish := { }) + .aggregate(clumpScalaJs, clumpScalaJvm, clumpTwitter) - lazy val clumpTwitter = Project(id = "clump-twitter", base = file(".")) - .settings(name := "clump-twitter") - .settings(commonSettings: _*) - .settings(libraryDependencies += "com.twitter" %% "util-core" % "6.22.0") - .settings(target <<= target(_ / "clump-twitter")) - .settings(excludeFilter in unmanagedSources := "package.scala") - .settings(sourceGenerators in Compile += Def.task { - val source = sourceDirectory.value / "main" / "scala" / "io" / "getclump" / "package-twitter.scala.tmpl" - val file = sourceManaged.value / "main" / "scala" / "io" / "getclump" / "package.scala" - IO.copyFile(source, file) - Seq(file) - }.taskValue) + + lazy val clumpScala: CrossProject = + CrossProject(id = "clump-scala", base = file("."), CrossType.Pure) + .settings(name := "clump-scala") + .settings(commonSettings: _*) + .settings(target <<= target(_ / "clump-scala")) + + lazy val clumpScalaJvm = clumpScala.jvm.aggregate(clumpScalaJs) + lazy val clumpScalaJs = clumpScala.js + + lazy val clumpTwitter = + Project(id = "clump-twitter", base = file(".")) + .settings(name := "clump-twitter") + .settings(commonSettings: _*) + .settings(libraryDependencies += "com.twitter" %% "util-core" % "6.22.0") + .settings(target <<= target(_ / "clump-twitter")) + .settings(excludeFilter in unmanagedSources := "package.scala") + .settings(sourceGenerators in Compile += Def.task { + val source = sourceDirectory.value / "main" / "scala" / "io" / "getclump" / "package-twitter.scala.tmpl" + val file = sourceManaged.value / "main" / "scala" / "io" / "getclump" / "package.scala" + IO.copyFile(source, file) + Seq(file) + }.taskValue) } diff --git a/project/plugins.sbt b/project/plugins.sbt index 2a023a8..4750a1d 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -14,4 +14,6 @@ addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.5.0") addSbtPlugin("com.github.gseitz" % "sbt-release" % "0.8.5") -addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") \ No newline at end of file +addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") + +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.3") diff --git a/src/main/scala/io/getclump/package-twitter.scala.tmpl b/src/main/scala/io/getclump/package-twitter.scala.tmpl index f1099ca..5320981 100644 --- a/src/main/scala/io/getclump/package-twitter.scala.tmpl +++ b/src/main/scala/io/getclump/package-twitter.scala.tmpl @@ -27,6 +27,4 @@ package object getclump { def sequence[T](futures: Seq[Future[T]]) = Future.collect(futures) } - private[getclump] def awaitResult[T](future: Future[T]) = - com.twitter.util.Await.result(future) } diff --git a/src/main/scala/io/getclump/package.scala b/src/main/scala/io/getclump/package.scala index 5ae0848..ec869fa 100644 --- a/src/main/scala/io/getclump/package.scala +++ b/src/main/scala/io/getclump/package.scala @@ -10,6 +10,4 @@ package object getclump { private[getclump]type Future[+T] = scala.concurrent.Future[T] private[getclump] val Future = scala.concurrent.Future - private[getclump] def awaitResult[T](future: Future[T]) = - scala.concurrent.Await.result(future, scala.concurrent.duration.Duration.Inf) } diff --git a/src/test/scala/io/getclump/ClumpApiSpec.scala b/src/test/scala/io/getclump/ClumpApiSpec.scala index 85d1912..f7391e1 100644 --- a/src/test/scala/io/getclump/ClumpApiSpec.scala +++ b/src/test/scala/io/getclump/ClumpApiSpec.scala @@ -15,20 +15,20 @@ object ClumpApiSpec extends Spec { "success" - { "optional" - { "defined" - { - assert(clumpResult(Clump.future(Future.successful(Some(1)))) == Some(1)) + assertResult(Clump.future(Future.successful(Some(1))), Some(1)) } "undefined" - { - assert(clumpResult(Clump.future(Future.successful(None))) == None) + assertResult(Clump.future(Future.successful(None)), None) } } "non-optional" - { - assert(clumpResult(Clump.future(Future.successful(1))) == Some(1)) + assertResult(Clump.future(Future.successful(1)), Some(1)) } } "failure" - { - intercept[IllegalStateException] { - clumpResult(Clump.future(Future.failed(new IllegalStateException))) + assertFailure[IllegalStateException] { + Clump.future(Future.failed(new IllegalStateException)) } } } @@ -36,44 +36,44 @@ object ClumpApiSpec extends Spec { "from a value (Clump.apply)" - { "propogates exceptions" - { val clump = Clump { throw new IllegalStateException } - intercept[IllegalStateException] { - clumpResult(clump) + assertFailure[IllegalStateException] { + clump } } "no exception" - { - assert(clumpResult(Clump(1)) == Some(1)) + assertResult(Clump(1), Some(1)) } } "from a value (Clump.value)" - { - assert(clumpResult(Clump.value(1)) == Some(1)) + assertResult(Clump.value(1), Some(1)) } "from a value (Clump.successful)" - { - assert(clumpResult(Clump.successful(1)) == Some(1)) + assertResult(Clump.successful(1), Some(1)) } "from an option (Clump.value)" - { "defined" - { - assert(clumpResult(Clump.value(Option(1))) == Option(1)) + assertResult(Clump.value(Option(1)), Option(1)) } "empty" - { - assert(clumpResult(Clump.value(None)) == None) + assertResult(Clump.value(None), None) } } "failed (Clump.exception)" - { - intercept[IllegalStateException] { - clumpResult(Clump.exception(new IllegalStateException)) + assertFailure[IllegalStateException] { + Clump.exception(new IllegalStateException) } } "failed (Clump.failed)" - { - intercept[IllegalStateException] { - clumpResult(Clump.failed(new IllegalStateException)) + assertFailure[IllegalStateException] { + Clump.failed(new IllegalStateException) } } } @@ -82,37 +82,37 @@ object ClumpApiSpec extends Spec { "list" - { val inputs = List(1, 2, 3) val clump = Clump.traverse(inputs)(i => Clump.value(i + 1)) - assert(clumpResult(clump) == Some(List(2, 3, 4))) + assertResult(clump, Some(List(2, 3, 4))) } "set" - { val inputs = Set(1, 2, 3) val clump = Clump.traverse(inputs)(i => Clump.value(i + 1)) - assert(clumpResult(clump) == Some(Set(2, 3, 4))) + assertResult(clump, Some(Set(2, 3, 4))) } "seq" - { val inputs = Seq(1, 2, 3) val clump = Clump.traverse(inputs)(i => Clump.value(i + 1)) - assert(clumpResult(clump) == Some(Seq(2, 3, 4))) + assertResult(clump, Some(Seq(2, 3, 4))) } } "allows to collect multiple clumps - only one (Clump.collect)" - { "list" - { val clumps = List(Clump.value(1), Clump.value(2)) - assert(clumpResult(Clump.collect(clumps)) == Some(List(1, 2))) + assertResult(Clump.collect(clumps), Some(List(1, 2))) } "set" - { val clumps = Set(Clump.value(1), Clump.value(2)) - assert(clumpResult(Clump.collect(clumps)) == Some(Set(1, 2))) + assertResult(Clump.collect(clumps), Some(Set(1, 2))) } "seq" - { val clumps = Seq(Clump.value(1), Clump.value(2)) - assert(clumpResult(Clump.collect(clumps)) == Some(Seq(1, 2))) + assertResult(Clump.collect(clumps), Some(Seq(1, 2))) } } "allows to create an empty Clump (Clump.empty)" - { - assert(clumpResult(Clump.empty) == None) + assertResult(Clump.empty, None) } "allows to join clumps" - { @@ -121,39 +121,39 @@ object ClumpApiSpec extends Spec { "2 instances" - { val clump = Clump.join(c(1), c(2)) - assert(clumpResult(clump) == Some(1, 2)) + assertResult(clump, Some(1, 2)) } "3 instances" - { val clump = Clump.join(c(1), c(2), c(3)) - assert(clumpResult(clump) == Some(1, 2, 3)) + assertResult(clump, Some(1, 2, 3)) } "4 instances" - { val clump = Clump.join(c(1), c(2), c(3), c(4)) - assert(clumpResult(clump) == Some(1, 2, 3, 4)) + assertResult(clump, Some(1, 2, 3, 4)) } "5 instances" - { val clump = Clump.join(c(1), c(2), c(3), c(4), c(5)) - assert(clumpResult(clump) == Some(1, 2, 3, 4, 5)) + assertResult(clump, Some(1, 2, 3, 4, 5)) } "6 instances" - { val clump = Clump.join(c(1), c(2), c(3), c(4), c(5), c(6)) - assert(clumpResult(clump) == Some(1, 2, 3, 4, 5, 6)) + assertResult(clump, Some(1, 2, 3, 4, 5, 6)) } "7 instances" - { val clump = Clump.join(c(1), c(2), c(3), c(4), c(5), c(6), c(7)) - assert(clumpResult(clump) == Some(1, 2, 3, 4, 5, 6, 7)) + assertResult(clump, Some(1, 2, 3, 4, 5, 6, 7)) } "8 instances" - { val clump = Clump.join(c(1), c(2), c(3), c(4), c(5), c(6), c(7), c(8)) - assert(clumpResult(clump) == Some(1, 2, 3, 4, 5, 6, 7, 8)) + assertResult(clump, Some(1, 2, 3, 4, 5, 6, 7, 8)) } "9 instances" - { val clump = Clump.join(c(1), c(2), c(3), c(4), c(5), c(6), c(7), c(8), c(9)) - assert(clumpResult(clump) == Some(1, 2, 3, 4, 5, 6, 7, 8, 9)) + assertResult(clump, Some(1, 2, 3, 4, 5, 6, 7, 8, 9)) } "10 instances" - { val clump = Clump.join(c(1), c(2), c(3), c(4), c(5), c(6), c(7), c(8), c(9), c(10)) - assert(clumpResult(clump) == Some(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) + assertResult(clump, Some(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) } } } @@ -163,25 +163,25 @@ object ClumpApiSpec extends Spec { "can be mapped to a new clump" - { "using simple a value transformation (clump.map)" - { - assert(clumpResult(Clump.value(1).map(_ + 1)) == Some(2)) + assertResult(Clump.value(1).map(_ + 1), Some(2)) } "using a transformation that creates a new clump (clump.flatMap)" - { "both clumps are defined" - { - assert(clumpResult(Clump.value(1).flatMap(i => Clump.value(i + 1))) == Some(2)) + assertResult(Clump.value(1).flatMap(i => Clump.value(i + 1)), Some(2)) } "initial clump is undefined" - { - assert(clumpResult(Clump.value(None).flatMap(i => Clump.value(2))) == None) + assertResult(Clump.value(None).flatMap(i => Clump.value(2)), None) } } } "can be joined with another clump and produce a new clump with the value of both (clump.join)" - { "both clumps are defined" - { - assert(clumpResult(Clump.value(1).join(Clump.value(2))) == Some(1, 2)) + assertResult(Clump.value(1).join(Clump.value(2)), Some(1, 2)) } "one of them is undefined" - { - assert(clumpResult(Clump.value(1).join(Clump.value(None))) == None) + assertResult(Clump.value(1).join(Clump.value(None)), None) } } @@ -193,22 +193,22 @@ object ClumpApiSpec extends Spec { Clump.exception(new IllegalStateException).handle { case e: IllegalStateException => Some(2) } - assert(clumpResult(clump) == Some(2)) + assertResult(clump, Some(2)) } "exception doesn't happen" - { val clump = Clump.value(1).handle { case e: IllegalStateException => None } - assert(clumpResult(clump) == Some(1)) + assertResult(clump, Some(1)) } "exception isn't caught" - { val clump = Clump.exception(new NullPointerException).handle { case e: IllegalStateException => Some(1) } - intercept[NullPointerException] { - clumpResult(clump) + assertFailure[NullPointerException] { + clump } } } @@ -219,22 +219,22 @@ object ClumpApiSpec extends Spec { Clump.exception(new IllegalStateException).recover { case e: IllegalStateException => Some(2) } - assert(clumpResult(clump) == Some(2)) + assertResult(clump, Some(2)) } "exception doesn't happen" - { val clump = Clump.value(1).recover { case e: IllegalStateException => None } - assert(clumpResult(clump) == Some(1)) + assertResult(clump, Some(1)) } "exception isn't caught" - { val clump = Clump.exception(new NullPointerException).recover { case e: IllegalStateException => Some(1) } - intercept[NullPointerException] { - clumpResult(clump) + assertFailure[NullPointerException] { + clump } } } @@ -245,22 +245,22 @@ object ClumpApiSpec extends Spec { Clump.exception(new IllegalStateException).rescue { case e: IllegalStateException => Clump.value(2) } - assert(clumpResult(clump) == Some(2)) + assertResult(clump, Some(2)) } "exception doesn't happen" - { val clump = Clump.value(1).rescue { case e: IllegalStateException => Clump.value(None) } - assert(clumpResult(clump) == Some(1)) + assertResult(clump, Some(1)) } "exception isn't caught" - { val clump = Clump.exception(new NullPointerException).rescue { case e: IllegalStateException => Clump.value(1) } - intercept[NullPointerException] { - clumpResult(clump) + assertFailure[NullPointerException] { + clump } } } @@ -271,22 +271,22 @@ object ClumpApiSpec extends Spec { Clump.exception(new IllegalStateException).recoverWith { case e: IllegalStateException => Clump.value(2) } - assert(clumpResult(clump) == Some(2)) + assertResult(clump, Some(2)) } "exception doesn't happen" - { val clump = Clump.value(1).recoverWith { case e: IllegalStateException => Clump.value(None) } - assert(clumpResult(clump) == Some(1)) + assertResult(clump, Some(1)) } "exception isn't caught" - { val clump = Clump.exception(new NullPointerException).recoverWith { case e: IllegalStateException => Clump.value(1) } - intercept[NullPointerException] { - clumpResult(clump) + assertFailure[NullPointerException] { + clump } } } @@ -294,31 +294,31 @@ object ClumpApiSpec extends Spec { "using a function that recovers using a new value (clump.fallback) on any exception" - { "exception happens" - { val clump = Clump.exception(new IllegalStateException).fallback(Some(1)) - assert(clumpResult(clump) == Some(1)) + assertResult(clump, Some(1)) } "exception doesn't happen" - { val clump = Clump.value(1).fallback(Some(2)) - assert(clumpResult(clump) == Some(1)) + assertResult(clump, Some(1)) } } "using a function that recovers using a new clump (clump.fallbackTo) on any exception" - { "exception happens" - { val clump = Clump.exception(new IllegalStateException).fallbackTo(Clump.value(1)) - assert(clumpResult(clump) == Some(1)) + assertResult(clump, Some(1)) } "exception doesn't happen" - { val clump = Clump.value(1).fallbackTo(Clump.value(2)) - assert(clumpResult(clump) == Some(1)) + assertResult(clump, Some(1)) } } } "can have its result filtered (clump.filter)" - { - assert(clumpResult(Clump.value(1).filter(_ != 1)) == None) - assert(clumpResult(Clump.value(1).filter(_ == 1)) == Some(1)) + assertResult(Clump.value(1).filter(_ != 1), None) + assertResult(Clump.value(1).filter(_ == 1), Some(1)) } "uses a covariant type parameter" - { @@ -330,31 +330,31 @@ object ClumpApiSpec extends Spec { "allows to defined a fallback value (clump.orElse)" - { "undefined" - { - assert(clumpResult(Clump.empty.orElse(1)) == Some(1)) + assertResult(Clump.empty.orElse(1), Some(1)) } "defined" - { - assert(clumpResult(Clump.value(Some(1)).orElse(2)) == Some(1)) + assertResult(Clump.value(Some(1)).orElse(2), Some(1)) } } "allows to defined a fallback clump (clump.orElse)" - { "undefined" - { - assert(clumpResult(Clump.empty.orElse(Clump.value(1))) == Some(1)) + assertResult(Clump.empty.orElse(Clump.value(1)), Some(1)) } "defined" - { - assert(clumpResult(Clump.value(Some(1)).orElse(Clump.value(2))) == Some(1)) + assertResult(Clump.value(Some(1)).orElse(Clump.value(2)), Some(1)) } } "can represent its result as a collection (clump.list) when its type is a collection" - { "list" - { - assert(awaitResult(Clump.value(List(1, 2)).list) == List(1, 2)) + Clump.value(List(1, 2)).list.map(result => assert(result == List(1, 2))) } "set" - { - assert(awaitResult(Clump.value(Set(1, 2)).list) == Set(1, 2)) + Clump.value(Set(1, 2)).list.map(result => assert(result == Set(1, 2))) } "seq" - { - assert(awaitResult(Clump.value(Seq(1, 2)).list) == Seq(1, 2)) + Clump.value(Seq(1, 2)).list.map(result => assert(result == Seq(1, 2))) } "not a collection" - { compileError("Clump.value(1).flatten") @@ -363,29 +363,29 @@ object ClumpApiSpec extends Spec { "can provide a result falling back to a default (clump.getOrElse)" - { "initial clump is undefined" - { - assert(awaitResult(Clump.value(None).getOrElse(1)) == 1) + Clump.value(None).getOrElse(1).map(result => assert(result == 1)) } "initial clump is defined" - { - assert(awaitResult(Clump.value(Some(2)).getOrElse(1)) == 2) + Clump.value(Some(2)).getOrElse(1).map(result => assert(result == 2)) } } "has a utility method (clump.apply) for unwrapping optional result" - { - assert(awaitResult(Clump.value(1).apply()) == 1) - intercept[NoSuchElementException] { - awaitResult(Clump.value[Int](None)()) + Clump.value(1).apply().map(result => assert(result == 1)) + assertFailure[NoSuchElementException] { + Clump.value[Int](None)() } } "can be made optional (clump.optional) to avoid lossy joins" - { val clump: Clump[String] = Clump.empty val optionalClump: Clump[Option[String]] = clump.optional - assert(clumpResult(optionalClump) == Some(None)) + assertResult(optionalClump, Some(None)) val valueClump: Clump[String] = Clump.value("foo") - assert(clumpResult(valueClump.join(clump)) == None) - assert(clumpResult(valueClump.join(optionalClump)) == Some("foo", None)) + assertResult(valueClump.join(clump), None) + assertResult(valueClump.join(optionalClump), Some("foo", None)) } } } diff --git a/src/test/scala/io/getclump/ClumpExecutionSpec.scala b/src/test/scala/io/getclump/ClumpExecutionSpec.scala index 20258a6..1416de5 100644 --- a/src/test/scala/io/getclump/ClumpExecutionSpec.scala +++ b/src/test/scala/io/getclump/ClumpExecutionSpec.scala @@ -31,26 +31,29 @@ object ClumpExecutionSpec extends Spec { source2.get(i) } - assert(clumpResult(clump) == Some(List(10, 20, 30, 40))) - assert(source1Fetches == List(Set(1, 2))) - assert(source2Fetches == List(Set(3, 4))) + assertResult(clump, Some(List(10, 20, 30, 40))).map { _ => + assert(source1Fetches == List(Set(1, 2))) + assert(source2Fetches == List(Set(3, 4))) + } } "for multiple clumps collected into only one clump" - new Context { val clump = Clump.collect(source1.get(1), source1.get(2), source2.get(3), source2.get(4)) - assert(clumpResult(clump) == Some(List(10, 20, 30, 40))) + assertResult(clump, Some(List(10, 20, 30, 40))).map { _ => assert(source1Fetches == List(Set(1, 2))) assert(source2Fetches == List(Set(3, 4))) + } } "for clumps created inside nested flatmaps" - new Context { val clump1 = Clump.value(1).flatMap(source1.get(_)).flatMap(source2.get(_)) val clump2 = Clump.value(2).flatMap(source1.get(_)).flatMap(source2.get(_)) - assert(clumpResult(Clump.collect(clump1, clump2)) == Some(List(100, 200))) + assertResult(Clump.collect(clump1, clump2), Some(List(100, 200))).map { _ => assert(source1Fetches == List(Set(1, 2))) assert(source2Fetches == List(Set(20, 10))) + } } "for clumps composed using for comprehension" - { @@ -61,9 +64,10 @@ object ClumpExecutionSpec extends Spec { int <- Clump.collect(source1.get(1), source1.get(2), source2.get(3), source2.get(4)) } yield int - assert(clumpResult(clump) == Some(List(10, 20, 30, 40))) + assertResult(clump, Some(List(10, 20, 30, 40))).map { _ => assert(source1Fetches == List(Set(1, 2))) assert(source2Fetches == List(Set(3, 4))) + } } "two levels" - new Context { @@ -73,9 +77,10 @@ object ClumpExecutionSpec extends Spec { ints2 <- Clump.collect(source2.get(3), source2.get(4)) } yield (ints1, ints2) - assert(clumpResult(clump) == Some(List(10, 20), List(30, 40))) + assertResult(clump, Some(List(10, 20), List(30, 40))).map { _ => assert(source1Fetches == List(Set(1, 2))) assert(source2Fetches == List(Set(3, 4))) + } } "with a filter condition" - new Context { @@ -85,9 +90,10 @@ object ClumpExecutionSpec extends Spec { int2 <- source2.get(3) if (int2 != 999) } yield (ints1, int2) - assert(clumpResult(clump) == Some(List(10, 20), 30)) + assertResult(clump, Some(List(10, 20), 30)).map { _ => assert(source1Fetches == List(Set(1, 2))) assert(source2Fetches == List(Set(3))) + } } "using a join" - new Context { @@ -97,9 +103,10 @@ object ClumpExecutionSpec extends Spec { ints2 <- source2.get(3).join(source2.get(4)) } yield (ints1, ints2) - assert(clumpResult(clump) == Some(List(10, 20), (30, 40))) + assertResult(clump, Some(List(10, 20), (30, 40))).map { _ => assert(source1Fetches == List(Set(1, 2))) assert(source2Fetches == List(Set(3, 4))) + } } "using a future clump as base" - new Context { @@ -110,9 +117,10 @@ object ClumpExecutionSpec extends Spec { collect2 <- Clump.collect(source2.get(int)) } yield (collect1, collect2) - assert(clumpResult(clump) == Some((List(10), List(10)))) + assertResult(clump, Some((List(10), List(10)))).map { _ => assert(source1Fetches == List(Set(1))) assert(source2Fetches == List(Set(1))) + } } "complex scenario" - new Context { @@ -126,9 +134,10 @@ object ClumpExecutionSpec extends Spec { join2 <- source1.get(collect1).join(source2.get(join1b)) } yield (const1, const2, collect1, collect2, (join1a, join1b), join2) - assert(clumpResult(clump) == Some((1, 2, List(10, 20), List(10, 20), (4, 5), (List(100, 200), 50)))) + assertResult(clump, Some((1, 2, List(10, 20), List(10, 20), (4, 5), (List(100, 200), 50)))).map { _ => assert(source1Fetches == List(Set(1), Set(10, 20))) assert(source2Fetches == List(Set(2), Set(5))) + } } } } @@ -150,8 +159,8 @@ object ClumpExecutionSpec extends Spec { "short-circuits the computation - case of a failure" - new Context { val clump = Clump.exception[Int](new IllegalStateException).map(_ => throw new NullPointerException) - intercept[IllegalStateException] { - clumpResult(clump) + assertFailure[IllegalStateException] { + clump } } } diff --git a/src/test/scala/io/getclump/ClumpFetcherSpec.scala b/src/test/scala/io/getclump/ClumpFetcherSpec.scala index 02970cd..ee3a46b 100644 --- a/src/test/scala/io/getclump/ClumpFetcherSpec.scala +++ b/src/test/scala/io/getclump/ClumpFetcherSpec.scala @@ -25,7 +25,7 @@ object ClumpFetcherSpec extends Spec { v2 <- clump2 } yield (v1, v2) - assert(clumpResult(clump) == Some((List(10, 20), List(20, 30)))) + assertResult(clump, Some((List(10, 20), List(20, 30)))) } "limits the batch size" - { @@ -41,7 +41,7 @@ object ClumpFetcherSpec extends Spec { val clump = Clump.traverse(List(1, 2, 3))(source.get) - assert(clumpResult(clump) == Some(List(10, 20, 30))) + assertResult(clump, Some(List(10, 20, 30))) } "retries failed fetches" - { @@ -63,7 +63,7 @@ object ClumpFetcherSpec extends Spec { case e: IllegalStateException => 1 } - assert(clumpResult(source.get(1)) == Some(10)) + assertResult(source.get(1), Some(10)) } "failure (above the retries limit)" - { @@ -77,24 +77,31 @@ object ClumpFetcherSpec extends Spec { case e: IllegalStateException => 1 } - intercept[IllegalStateException] { - clumpResult(source.get(1)) + assertFailure[IllegalStateException] { + source.get(1) } } } "honours call order for fetches" - { object repo { - def fetch(inputs: List[Int]) = + var fetches = List[List[Int]]() + def fetch(inputs: List[Int]) = { + fetches :+= inputs inputs match { case List(1, 2, 3) => Future(List("1", "2", "3")) case List(1, 3, 2) => Future(List("1", "3", "2")) } + } } val source = Clump.source(repo.fetch _)(_.toInt) - clumpResult(Clump.traverse(List(1, 2, 3))(source.get)) - clumpResult(Clump.traverse(List(1, 3, 2))(source.get)) + for { + _ <- Clump.traverse(List(1, 2, 3))(source.get).get + _ <- Clump.traverse(List(1, 3, 2))(source.get).get + } yield { + assert(repo.fetches == List(List(1, 2, 3), List(1, 3, 2))) + } } } } \ No newline at end of file diff --git a/src/test/scala/io/getclump/ClumpSourceSpec.scala b/src/test/scala/io/getclump/ClumpSourceSpec.scala index af55c85..64a8ac8 100644 --- a/src/test/scala/io/getclump/ClumpSourceSpec.scala +++ b/src/test/scala/io/getclump/ClumpSourceSpec.scala @@ -16,7 +16,7 @@ object ClumpSourceSpec extends Spec { val source = Clump.source(repo.fetch _) - assert(clumpResult(source.get(1)) == Some(2)) + assertResult(source.get(1), Some(2)) } "fetches multiple clumps" - { @@ -33,7 +33,7 @@ object ClumpSourceSpec extends Spec { val clump = source.get(List(1, 2)) - assert(clumpResult(clump) == Some(List(10, 20))) + assertResult(clump, Some(List(10, 20))) } "can be used as a non-singleton" - { @@ -53,7 +53,7 @@ object ClumpSourceSpec extends Spec { } } - assert(clumpResult(clump) == Some(List(2, 2, 2, 2, 2))) + assertResult(clump, Some(List(2, 2, 2, 2, 2))) } "with values from the outer scope" - { @@ -75,7 +75,7 @@ object ClumpSourceSpec extends Spec { } } - assert(clumpResult(clump) == Some(List(2, 2, 2, 2, 2))) + assertResult(clump, Some(List(2, 2, 2, 2, 2))) } } diff --git a/src/test/scala/io/getclump/IntegrationSpec.scala b/src/test/scala/io/getclump/IntegrationSpec.scala index e626a77..119b9e9 100644 --- a/src/test/scala/io/getclump/IntegrationSpec.scala +++ b/src/test/scala/io/getclump/IntegrationSpec.scala @@ -58,7 +58,7 @@ object IntegrationSpec extends Spec { } yield (tweet, user, tracks) } - assert(awaitResult(enrichedTweets.get) == Some(List( + assertResult(enrichedTweets, Some(List( (Tweet("Tweet1", 10), User(10, "User10"), Set(Track(10, "Track10"), Track(11, "Track11"), Track(12, "Track12"))), (Tweet("Tweet2", 20), User(20, "User20"), Set(Track(20, "Track20"), Track(21, "Track21"), Track(22, "Track22"))), (Tweet("Tweet3", 30), User(30, "User30"), Set(Track(30, "Track30"), Track(31, "Track31"), Track(32, "Track32")))))) @@ -96,7 +96,7 @@ object IntegrationSpec extends Spec { } yield (tweet, user) } - assert(awaitResult(enrichedTweets.get) == Some(List( + assertResult(enrichedTweets, Some(List( (Tweet("Tweet1", 10), User(10, "User10")), (Tweet("Tweet2", 20), User(20, "User20")), (Tweet("Tweet3", 30), User(30, "User30"))))) @@ -116,7 +116,7 @@ object IntegrationSpec extends Spec { } yield (timeline, enrichedLikes) } - assert(awaitResult(enrichedTimelines.get) == Some(List( + assertResult(enrichedTimelines, Some(List( (Timeline(1, List(10, 20)), List( (Like(10, List(100, 200), List(1000, 2000)), List(Track(100, "Track100"), Track(200, "Track200")), List(User(1000, "User1000"), User(2000, "User2000"))), (Like(20, List(200, 400), List(2000, 4000)), List(Track(200, "Track200"), Track(400, "Track400")), List(User(2000, "User2000"), User(4000, "User4000"))))), @@ -133,7 +133,7 @@ object IntegrationSpec extends Spec { users.get(tweet.userId).map(user => (tweet, user))) } - assert(awaitResult(enrichedTweets.get) == Some(List( + assertResult(enrichedTweets, Some(List( (Tweet("Tweet1", 10), User(10, "User10")), (Tweet("Tweet2", 20), User(20, "User20")), (Tweet("Tweet3", 30), User(30, "User30"))))) @@ -147,10 +147,12 @@ object IntegrationSpec extends Spec { } yield (tweet, user) } - assert(awaitResult(enrichedTweets.list) == List( - (Tweet("Tweet1", 10), User(10, "User10")), - (Tweet("Tweet2", 20), User(20, "User20")), - (Tweet("Tweet3", 30), User(30, "User30")))) + enrichedTweets.list.map { result => + assert(result == List( + (Tweet("Tweet1", 10), User(10, "User10")), + (Tweet("Tweet2", 20), User(20, "User20")), + (Tweet("Tweet3", 30), User(30, "User30")))) + } } "it should work with Clump.sourceZip" - { @@ -161,10 +163,13 @@ object IntegrationSpec extends Spec { } yield (tweet, user) } - assert(awaitResult(enrichedTweets.get) == Some(List( - (Tweet("Tweet1", 10), User(10, "User10")), - (Tweet("Tweet2", 20), User(20, "User20")), - (Tweet("Tweet3", 30), User(30, "User30"))))) + enrichedTweets.list.map { result => + println("aaa", result) + assert(result == List( + (Tweet("Tweet1", 10), User(10, "User10")), + (Tweet("Tweet2", 20), User(20, "User20")), + (Tweet("Tweet3", 30), User(30, "User30")))) + } } "A Clump can have a partial result" - { @@ -175,7 +180,7 @@ object IntegrationSpec extends Spec { } yield (tweet, user) } - assert(awaitResult(onlyFullObjectGraph.get) == Some(List((Tweet("Tweet2", 20), User(20, "User20"))))) + assertResult(onlyFullObjectGraph, Some(List((Tweet("Tweet2", 20), User(20, "User20"))))) val partialResponses: Clump[List[(Tweet, Option[User])]] = Clump.traverse(1, 2, 3) { tweetId => for { @@ -184,7 +189,7 @@ object IntegrationSpec extends Spec { } yield (tweet, user) } - assert(awaitResult(partialResponses.get) == Some(List( + assertResult(partialResponses, Some(List( (Tweet("Tweet1", 10), None), (Tweet("Tweet2", 20), Some(User(20, "User20"))), (Tweet("Tweet3", 30), None)))) diff --git a/src/test/scala/io/getclump/SourcesSpec.scala b/src/test/scala/io/getclump/SourcesSpec.scala index 9115031..195e322 100644 --- a/src/test/scala/io/getclump/SourcesSpec.scala +++ b/src/test/scala/io/getclump/SourcesSpec.scala @@ -10,12 +10,12 @@ object SourcesSpec extends Spec { "set input" - { def fetch(inputs: Set[Int]) = Future.successful(inputs.map(i => i -> i.toString).toMap) val source = Clump.source(fetch _) - assert(clumpResult(source.get(1)) == Some("1")) + assertResult(source.get(1), Some("1")) } "list input" - { def fetch(inputs: List[Int]) = Future.successful(inputs.map(i => i -> i.toString).toMap) val source = Clump.source(fetch _) - assert(clumpResult(source.get(1)) == Some("1")) + assertResult(source.get(1), Some("1")) } "extra params" - { "one" - { @@ -23,28 +23,28 @@ object SourcesSpec extends Spec { Future(values.map(v => v -> v * param1).toMap) val source = Clump.source(fetch _) val clump = Clump.collect(source.get(2, 3), source.get(2, 4), source.get(3, 5)) - assert(clumpResult(clump) == Some(List(6, 8, 15))) + assertResult(clump, Some(List(6, 8, 15))) } "two" - { def fetch(param1: Int, param2: String, values: List[Int]) = Future(values.map(v => v -> List(param1, param2, v)).toMap) val source = Clump.source(fetch _) val clump = Clump.collect(source.get(1, "2", 3), source.get(1, "2", 4), source.get(2, "3", 5)) - assert(clumpResult(clump) == Some(List(List(1, "2", 3), List(1, "2", 4), List(2, "3", 5)))) + assertResult(clump, Some(List(List(1, "2", 3), List(1, "2", 4), List(2, "3", 5)))) } "three" - { def fetch(param1: Int, param2: String, param3: List[String], values: List[Int]) = Future(values.map(v => v -> List(param1, param2, param3, v)).toMap) val source = Clump.source(fetch _) val clump = Clump.collect(source.get(1, "2", List("a"), 3), source.get(1, "2", List("a"), 4), source.get(2, "3", List("b"), 5)) - assert(clumpResult(clump) == Some(List(List(1, "2", List("a"), 3), List(1, "2", List("a"), 4), List(2, "3", List("b"), 5)))) + assertResult(clump, Some(List(List(1, "2", List("a"), 3), List(1, "2", List("a"), 4), List(2, "3", List("b"), 5)))) } "four" - { def fetch(param1: Int, param2: String, param3: List[String], param4: Boolean, values: List[Int]) = Future(values.map(v => v -> List(param1, param2, param3, param4, v)).toMap) val source = Clump.source(fetch _) val clump = Clump.collect(source.get(1, "2", List("a"), true, 3), source.get(1, "2", List("a"), true, 4), source.get(2, "3", List("b"), false, 5)) - assert(clumpResult(clump) == Some(List(List(1, "2", List("a"), true, 3), List(1, "2", List("a"), true, 4), List(2, "3", List("b"), false, 5)))) + assertResult(clump, Some(List(List(1, "2", List("a"), true, 3), List(1, "2", List("a"), true, 4), List(2, "3", List("b"), false, 5)))) } } } @@ -53,39 +53,39 @@ object SourcesSpec extends Spec { "set input" - { def fetch(inputs: Set[Int]) = Future.successful(inputs.map(_.toString)) val source = Clump.source(fetch _)(_.toInt) - assert(clumpResult(source.get(1)) == Some("1")) + assertResult(source.get(1), Some("1")) } "seq input" - { def fetch(inputs: Seq[Int]) = Future.successful(inputs.map(_.toString)) val source = Clump.source(fetch _)(_.toInt) - assert(clumpResult(source.get(1)) == Some("1")) + assertResult(source.get(1), Some("1")) } "extra params" - { "one" - { def fetch(param1: Int, values: List[Int]) = Future(values.map((param1, _))) val source = Clump.source(fetch _)(_._2) val clump = Clump.collect(source.get(2, 3), source.get(2, 4), source.get(3, 5)) - assert(clumpResult(clump) == Some(List((2, 3), (2, 4), (3, 5)))) + assertResult(clump, Some(List((2, 3), (2, 4), (3, 5)))) } "two" - { def fetch(param1: Int, param2: String, values: List[Int]) = Future(values.map((param1, param2, _))) val source = Clump.source(fetch _)(_._3) val clump = Clump.collect(source.get(1, "2", 3), source.get(1, "2", 4), source.get(2, "3", 5)) - assert(clumpResult(clump) == Some(List((1, "2", 3), (1, "2", 4), (2, "3", 5)))) + assertResult(clump, Some(List((1, "2", 3), (1, "2", 4), (2, "3", 5)))) } "three" - { def fetch(param1: Int, param2: String, param3: List[String], values: List[Int]) = Future(values.map((param1, param2, param3, _))) val source = Clump.source(fetch _)(_._4) val clump = Clump.collect(source.get(1, "2", List("a"), 3), source.get(1, "2", List("a"), 4), source.get(2, "3", List("b"), 5)) - assert(clumpResult(clump) == Some(List((1, "2", List("a"), 3), (1, "2", List("a"), 4), (2, "3", List("b"), 5)))) + assertResult(clump, Some(List((1, "2", List("a"), 3), (1, "2", List("a"), 4), (2, "3", List("b"), 5)))) } "four" - { def fetch(param1: Int, param2: String, param3: List[String], param4: Boolean, values: List[Int]) = Future(values.map((param1, param2, param3, param4, _))) val source = Clump.source(fetch _)(_._5) val clump = Clump.collect(source.get(1, "2", List("a"), true, 3), source.get(1, "2", List("a"), true, 4), source.get(2, "3", List("b"), false, 5)) - assert(clumpResult(clump) == Some(List((1, "2", List("a"), true, 3), (1, "2", List("a"), true, 4), (2, "3", List("b"), false, 5)))) + assertResult(clump, Some(List((1, "2", List("a"), true, 3), (1, "2", List("a"), true, 4), (2, "3", List("b"), false, 5)))) } } } @@ -94,28 +94,28 @@ object SourcesSpec extends Spec { "list input" - { def fetch(inputs: List[Int]) = Future.successful(inputs.map(_.toString)) val source = Clump.sourceZip(fetch _) - assert(clumpResult(source.get(1)) == Some("1")) + assertResult(source.get(1), Some("1")) } "extra params" - { "one" - { def fetch(param1: Int, inputs: List[Int]) = Future.successful(inputs.map(_ + param1).map(_.toString)) val source = Clump.sourceZip(fetch _) - assert(clumpResult(source.get(1, 2)) == Some("3")) + assertResult(source.get(1, 2), Some("3")) } "two" - { def fetch(param1: Int, param2: String, inputs: List[Int]) = Future.successful(inputs.map(_ + param1).map(_ + param2)) val source = Clump.sourceZip(fetch _) - assert(clumpResult(source.get(1, "a", 2)) == Some("3a")) + assertResult(source.get(1, "a", 2), Some("3a")) } "three" - { def fetch(param1: Int, param2: String, param3: List[String], inputs: List[Int]) = Future.successful(inputs.map(_ + param1).map(_ + param2).map(_ + param3.fold("")(_ + _))) val source = Clump.sourceZip(fetch _) - assert(clumpResult(source.get(1, "a", List("b", "c"), 2)) == Some("3abc")) + assertResult(source.get(1, "a", List("b", "c"), 2), Some("3abc")) } "four" - { def fetch(param1: Int, param2: String, param3: List[String], param4: Boolean, inputs: List[Int]) = Future.successful(inputs.map(_ + param1).map(_ + param2).map(_ + param3.fold("")(_ + _)).map(_ + s"-$param4")) val source = Clump.sourceZip(fetch _) - assert(clumpResult(source.get(1, "a", List("b", "c"), true, 2)) == Some("3abc-true")) + assertResult(source.get(1, "a", List("b", "c"), true, 2), Some("3abc-true")) } } } @@ -126,30 +126,30 @@ object SourcesSpec extends Spec { val source = Clump.sourceSingle(fetch _) - assert(clumpResult(source.get(1)) == Some(Set(1, 2, 3))) - assert(clumpResult(source.get(2)) == Some(Set(2, 3, 4))) - assert(clumpResult(source.get(List(1, 2))) == Some(List(Set(1, 2, 3), Set(2, 3, 4)))) + assertResult(source.get(1), Some(Set(1, 2, 3))) + assertResult(source.get(2), Some(Set(2, 3, 4))) + assertResult(source.get(List(1, 2)), Some(List(Set(1, 2, 3), Set(2, 3, 4)))) } "extra params" - { "one" - { def fetch(param1: String, input: Int) = Future.successful(input + param1) val source = Clump.sourceSingle(fetch _) - assert(clumpResult(source.get("a", 2)) == Some("2a")) + assertResult(source.get("a", 2), Some("2a")) } "two" - { def fetch(param1: String, param2: List[Int], input: Int) = Future.successful(input + param1 + param2.mkString) val source = Clump.sourceSingle(fetch _) - assert(clumpResult(source.get("a", List(1, 2), 3)) == Some("3a12")) + assertResult(source.get("a", List(1, 2), 3), Some("3a12")) } "three" - { def fetch(param1: String, param2: List[Int], param3: Int, input: Int) = Future.successful(input + param1 + param2.mkString + param3) val source = Clump.sourceSingle(fetch _) - assert(clumpResult(source.get("a", List(1, 2), 3, 4)) == Some("4a123")) + assertResult(source.get("a", List(1, 2), 3, 4), Some("4a123")) } "four" - { def fetch(param1: String, param2: List[Int], param3: Int, param4: List[String], input: Int) = Future.successful(input + param1 + param2.mkString + param3 + param4.mkString) val source = Clump.sourceSingle(fetch _) - assert(clumpResult(source.get("a", List(1, 2), 3, List("b", "c"), 4)) == Some("4a123bc")) + assertResult(source.get("a", List(1, 2), 3, List("b", "c"), 4), Some("4a123bc")) } } } @@ -166,7 +166,7 @@ object SourcesSpec extends Spec { def iterableToSet: Iterable[Int] => Future[List[String]] = { inputs => Future.successful(inputs.map(_.toString).toList) } def testSource(source: ClumpSource[Int, String]) = - assert(clumpResult(source.get(List(1, 2))) == Some(List("1", "2"))) + assertResult(source.get(List(1, 2)), Some(List("1", "2"))) def extractId(string: String) = string.toInt @@ -188,7 +188,7 @@ object SourcesSpec extends Spec { def iterableToMap: Iterable[Int] => Future[Map[Int, String]] = { inputs => Future.successful(inputs.map(input => (input, input.toString)).toMap) } def testSource(source: ClumpSource[Int, String]) = - assert(clumpResult(source.get(List(1, 2))) == Some(List("1", "2"))) + assertResult(source.get(List(1, 2)), Some(List("1", "2"))) testSource(Clump.source(setToMap)) testSource(Clump.source(listToMap)) diff --git a/src/test/scala/io/getclump/Spec.scala b/src/test/scala/io/getclump/Spec.scala index e30ab1a..1335275 100644 --- a/src/test/scala/io/getclump/Spec.scala +++ b/src/test/scala/io/getclump/Spec.scala @@ -1,9 +1,23 @@ package io.getclump import utest._ +import scala.reflect.ClassTag trait Spec extends TestSuite { - protected def clumpResult[T](clump: Clump[T]) = - awaitResult(clump.get) + object NoError extends Exception + + protected def assertFailure[T: ClassTag](f: Future[_]) = + f.map(_ => throw NoError).recover { + case NoError => throw new IllegalStateException("A failure was expected.") + case e: T => None + } + + protected def assertFailure[T: ClassTag](f: Clump[_]): Future[_] = + assertFailure[T](f.get) + + protected def assertResult[T](clump: Clump[T], expected: T) = + clump.get.map { result => + assert(result == expected) + } } From 1a16fa059aaea00b1ecfa831c14275bdf0e120b0 Mon Sep 17 00:00:00 2001 From: "Flavio W. Brasil" Date: Thu, 25 Jun 2015 09:53:09 +0200 Subject: [PATCH 3/4] update readme --- README.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d6136a6..382b97d 100644 --- a/README.md +++ b/README.md @@ -139,21 +139,31 @@ The execution model leverages on Applicative Functors to express the independenc # Getting started # -To use clump, just add the dependency to the project's build configuration. There are two versions of the project: +To use clump, just add the dependency to the project's build configuration. There are three versions of the project: -1. `clump-scala`, that uses Scala Futures and doesn't have external dependencies. +1. `clump-scalaJVM`, that uses Scala Futures and doesn't have external dependencies. +2. `clump-scalaJS`, for usage with ScalaJS. 2. `clump-twitter`, that uses Twitter Futures and has the dependency to `twitter-util`. __Important__: Change ```x.x.x``` with the latest version listed by the [CHANGELOG.md](https://github.com/getclump/clump/blob/master/CHANGELOG.md) file. SBT +clump-scalaJVM ```scala libraryDependencies ++= Seq( "io.getclump" %% "clump-scala" % "x.x.x" ) ``` +clump-scalaJS +```scala +libraryDependencies ++= Seq( + "io.getclump" %%% "clump-scala" % "x.x.x" +) +``` + +clump-twitter ```scala libraryDependencies ++= Seq( "io.getclump" %% "clump-twitter" % "x.x.x" From f836be8a6e8161c277cba7dbe12aa2596d8647be Mon Sep 17 00:00:00 2001 From: "Flavio W. Brasil" Date: Thu, 25 Jun 2015 09:53:43 +0200 Subject: [PATCH 4/4] fix readme enumeration --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 382b97d..22a194c 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ To use clump, just add the dependency to the project's build configuration. Ther 1. `clump-scalaJVM`, that uses Scala Futures and doesn't have external dependencies. 2. `clump-scalaJS`, for usage with ScalaJS. -2. `clump-twitter`, that uses Twitter Futures and has the dependency to `twitter-util`. +3. `clump-twitter`, that uses Twitter Futures and has the dependency to `twitter-util`. __Important__: Change ```x.x.x``` with the latest version listed by the [CHANGELOG.md](https://github.com/getclump/clump/blob/master/CHANGELOG.md) file.