2014-09-04 14:31:22 +02:00
|
|
|
/**
|
|
|
|
|
* Copyright (C) 2014 Typesafe Inc. <http://www.typesafe.com>
|
|
|
|
|
*/
|
|
|
|
|
package akka.stream.scaladsl2
|
|
|
|
|
|
|
|
|
|
import akka.stream.testkit.AkkaSpec
|
|
|
|
|
import akka.stream.Transformer
|
2014-09-11 08:56:58 +02:00
|
|
|
import akka.stream.OverflowStrategy
|
2014-09-30 15:40:08 +02:00
|
|
|
import akka.stream.testkit.StreamTestKit.SubscriberProbe
|
|
|
|
|
import akka.stream.testkit.StreamTestKit.PublisherProbe
|
|
|
|
|
|
|
|
|
|
object FlowGraphCompileSpec {
|
|
|
|
|
class Fruit
|
|
|
|
|
class Apple extends Fruit
|
|
|
|
|
}
|
2014-09-04 14:31:22 +02:00
|
|
|
|
|
|
|
|
class FlowGraphCompileSpec extends AkkaSpec {
|
2014-09-30 15:40:08 +02:00
|
|
|
import FlowGraphCompileSpec._
|
2014-09-04 14:31:22 +02:00
|
|
|
|
|
|
|
|
implicit val mat = FlowMaterializer()
|
|
|
|
|
|
|
|
|
|
def op[In, Out]: () ⇒ Transformer[In, Out] = { () ⇒
|
|
|
|
|
new Transformer[In, Out] {
|
|
|
|
|
override def onNext(elem: In) = List(elem.asInstanceOf[Out])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-10-02 17:32:08 +02:00
|
|
|
val f1 = Flow[String].transform("f1", op[String, String])
|
|
|
|
|
val f2 = Flow[String].transform("f2", op[String, String])
|
|
|
|
|
val f3 = Flow[String].transform("f3", op[String, String])
|
|
|
|
|
val f4 = Flow[String].transform("f4", op[String, String])
|
|
|
|
|
val f5 = Flow[String].transform("f5", op[String, String])
|
|
|
|
|
val f6 = Flow[String].transform("f6", op[String, String])
|
|
|
|
|
|
|
|
|
|
val in1 = IterableTap(List("a", "b", "c"))
|
|
|
|
|
val in2 = IterableTap(List("d", "e", "f"))
|
|
|
|
|
val out1 = PublisherDrain[String]
|
|
|
|
|
val out2 = FutureDrain[String]
|
2014-09-04 14:31:22 +02:00
|
|
|
|
|
|
|
|
"FlowGraph" should {
|
|
|
|
|
"build simple merge" in {
|
|
|
|
|
FlowGraph { b ⇒
|
|
|
|
|
val merge = Merge[String]
|
|
|
|
|
b.
|
|
|
|
|
addEdge(in1, f1, merge).
|
|
|
|
|
addEdge(in2, f2, merge).
|
|
|
|
|
addEdge(merge, f3, out1)
|
2014-09-10 11:25:38 +02:00
|
|
|
}.run()
|
2014-09-04 14:31:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"build simple broadcast" in {
|
|
|
|
|
FlowGraph { b ⇒
|
|
|
|
|
val bcast = Broadcast[String]
|
|
|
|
|
b.
|
|
|
|
|
addEdge(in1, f1, bcast).
|
|
|
|
|
addEdge(bcast, f2, out1).
|
|
|
|
|
addEdge(bcast, f3, out2)
|
2014-09-10 11:25:38 +02:00
|
|
|
}.run()
|
2014-09-04 14:31:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"build simple merge - broadcast" in {
|
|
|
|
|
FlowGraph { b ⇒
|
|
|
|
|
val merge = Merge[String]
|
|
|
|
|
val bcast = Broadcast[String]
|
|
|
|
|
b.
|
|
|
|
|
addEdge(in1, f1, merge).
|
|
|
|
|
addEdge(in2, f2, merge).
|
|
|
|
|
addEdge(merge, f3, bcast).
|
|
|
|
|
addEdge(bcast, f4, out1).
|
|
|
|
|
addEdge(bcast, f5, out2)
|
2014-09-10 11:25:38 +02:00
|
|
|
}.run()
|
2014-09-04 14:31:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"build simple merge - broadcast with implicits" in {
|
|
|
|
|
FlowGraph { implicit b ⇒
|
|
|
|
|
import FlowGraphImplicits._
|
|
|
|
|
val merge = Merge[String]
|
|
|
|
|
val bcast = Broadcast[String]
|
|
|
|
|
in1 ~> f1 ~> merge ~> f2 ~> bcast ~> f3 ~> out1
|
|
|
|
|
in2 ~> f4 ~> merge
|
|
|
|
|
bcast ~> f5 ~> out2
|
2014-09-10 11:25:38 +02:00
|
|
|
}.run()
|
2014-09-04 14:31:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* in ---> f1 -+-> f2 -+-> f3 ---> out1
|
|
|
|
|
* ^ |
|
|
|
|
|
* | V
|
|
|
|
|
* f5 <-+- f4
|
|
|
|
|
* |
|
|
|
|
|
* V
|
|
|
|
|
* f6 ---> out2
|
|
|
|
|
*/
|
|
|
|
|
"detect cycle in " in {
|
|
|
|
|
intercept[IllegalArgumentException] {
|
|
|
|
|
FlowGraph { b ⇒
|
|
|
|
|
val merge = Merge[String]
|
|
|
|
|
val bcast1 = Broadcast[String]
|
|
|
|
|
val bcast2 = Broadcast[String]
|
2014-10-02 17:32:08 +02:00
|
|
|
val feedbackLoopBuffer = Flow[String].buffer(10, OverflowStrategy.dropBuffer)
|
2014-09-04 14:31:22 +02:00
|
|
|
b.
|
|
|
|
|
addEdge(in1, f1, merge).
|
|
|
|
|
addEdge(merge, f2, bcast1).
|
|
|
|
|
addEdge(bcast1, f3, out1).
|
2014-09-11 08:56:58 +02:00
|
|
|
addEdge(bcast1, feedbackLoopBuffer, bcast2).
|
2014-09-04 14:31:22 +02:00
|
|
|
addEdge(bcast2, f5, merge). // cycle
|
|
|
|
|
addEdge(bcast2, f6, out2)
|
|
|
|
|
}
|
|
|
|
|
}.getMessage.toLowerCase should include("cycle")
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"express complex topologies in a readable way" in {
|
2014-09-11 08:56:58 +02:00
|
|
|
FlowGraph { implicit b ⇒
|
|
|
|
|
b.allowCycles()
|
|
|
|
|
val merge = Merge[String]
|
|
|
|
|
val bcast1 = Broadcast[String]
|
|
|
|
|
val bcast2 = Broadcast[String]
|
2014-10-02 17:32:08 +02:00
|
|
|
val feedbackLoopBuffer = Flow[String].buffer(10, OverflowStrategy.dropBuffer)
|
2014-09-11 08:56:58 +02:00
|
|
|
import FlowGraphImplicits._
|
|
|
|
|
in1 ~> f1 ~> merge ~> f2 ~> bcast1 ~> f3 ~> out1
|
|
|
|
|
bcast1 ~> feedbackLoopBuffer ~> bcast2 ~> f5 ~> merge
|
|
|
|
|
bcast2 ~> f6 ~> out2
|
|
|
|
|
}.run()
|
2014-09-04 14:31:22 +02:00
|
|
|
}
|
|
|
|
|
|
2014-09-10 12:56:18 +02:00
|
|
|
"build broadcast - merge" in {
|
|
|
|
|
FlowGraph { implicit b ⇒
|
|
|
|
|
val bcast = Broadcast[String]
|
|
|
|
|
val bcast2 = Broadcast[String]
|
|
|
|
|
val merge = Merge[String]
|
|
|
|
|
import FlowGraphImplicits._
|
|
|
|
|
in1 ~> f1 ~> bcast ~> f2 ~> merge ~> f3 ~> out1
|
2014-09-11 07:43:05 +02:00
|
|
|
bcast ~> f4 ~> merge
|
2014-09-10 12:56:18 +02:00
|
|
|
}.run()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"build wikipedia Topological_sorting" in {
|
|
|
|
|
// see https://en.wikipedia.org/wiki/Topological_sorting#mediaviewer/File:Directed_acyclic_graph.png
|
|
|
|
|
FlowGraph { implicit b ⇒
|
|
|
|
|
val b3 = Broadcast[String]
|
|
|
|
|
val b7 = Broadcast[String]
|
|
|
|
|
val b11 = Broadcast[String]
|
|
|
|
|
val m8 = Merge[String]
|
|
|
|
|
val m9 = Merge[String]
|
|
|
|
|
val m10 = Merge[String]
|
|
|
|
|
val m11 = Merge[String]
|
2014-10-02 17:32:08 +02:00
|
|
|
val in3 = IterableTap(List("b"))
|
|
|
|
|
val in5 = IterableTap(List("b"))
|
|
|
|
|
val in7 = IterableTap(List("a"))
|
|
|
|
|
val out2 = PublisherDrain[String]
|
|
|
|
|
val out9 = PublisherDrain[String]
|
|
|
|
|
val out10 = PublisherDrain[String]
|
|
|
|
|
def f(s: String) = Flow[String].transform(s, op[String, String])
|
2014-09-10 12:56:18 +02:00
|
|
|
import FlowGraphImplicits._
|
|
|
|
|
|
|
|
|
|
in7 ~> f("a") ~> b7 ~> f("b") ~> m11 ~> f("c") ~> b11 ~> f("d") ~> out2
|
|
|
|
|
b11 ~> f("e") ~> m9 ~> f("f") ~> out9
|
|
|
|
|
b7 ~> f("g") ~> m8 ~> f("h") ~> m9
|
|
|
|
|
b11 ~> f("i") ~> m10 ~> f("j") ~> out10
|
|
|
|
|
in5 ~> f("k") ~> m11
|
|
|
|
|
in3 ~> f("l") ~> b3 ~> f("m") ~> m8
|
|
|
|
|
b3 ~> f("n") ~> m10
|
|
|
|
|
}.run()
|
|
|
|
|
}
|
|
|
|
|
|
2014-10-02 17:32:08 +02:00
|
|
|
"attachTap and attachDrain" in {
|
2014-09-04 14:31:22 +02:00
|
|
|
val mg = FlowGraph { b ⇒
|
|
|
|
|
val merge = Merge[String]
|
2014-10-02 17:32:08 +02:00
|
|
|
val undefinedSrc1 = UndefinedTap[String]
|
|
|
|
|
val undefinedSrc2 = UndefinedTap[String]
|
|
|
|
|
val undefinedDrain1 = UndefinedDrain[String]
|
2014-09-04 14:31:22 +02:00
|
|
|
b.
|
|
|
|
|
addEdge(undefinedSrc1, f1, merge).
|
2014-10-02 17:32:08 +02:00
|
|
|
addEdge(UndefinedTap[String]("src2"), f2, merge).
|
|
|
|
|
addEdge(merge, f3, undefinedDrain1)
|
2014-09-04 14:31:22 +02:00
|
|
|
|
2014-10-02 17:32:08 +02:00
|
|
|
b.attachTap(undefinedSrc1, in1)
|
|
|
|
|
b.attachTap(UndefinedTap[String]("src2"), in2)
|
|
|
|
|
b.attachDrain(undefinedDrain1, out1)
|
2014-09-04 14:31:22 +02:00
|
|
|
|
|
|
|
|
}.run()
|
|
|
|
|
out1.publisher(mg) should not be (null)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"build partial flow graphs" in {
|
2014-10-02 17:32:08 +02:00
|
|
|
val undefinedSrc1 = UndefinedTap[String]
|
|
|
|
|
val undefinedSrc2 = UndefinedTap[String]
|
|
|
|
|
val undefinedDrain1 = UndefinedDrain[String]
|
2014-09-04 14:31:22 +02:00
|
|
|
val bcast = Broadcast[String]
|
|
|
|
|
|
|
|
|
|
val partial1 = PartialFlowGraph { implicit b ⇒
|
|
|
|
|
import FlowGraphImplicits._
|
|
|
|
|
val merge = Merge[String]
|
2014-10-02 17:32:08 +02:00
|
|
|
undefinedSrc1 ~> f1 ~> merge ~> f2 ~> bcast ~> f3 ~> undefinedDrain1
|
2014-09-04 14:31:22 +02:00
|
|
|
undefinedSrc2 ~> f4 ~> merge
|
|
|
|
|
|
|
|
|
|
}
|
2014-10-02 17:32:08 +02:00
|
|
|
partial1.undefinedTaps should be(Set(undefinedSrc1, undefinedSrc2))
|
|
|
|
|
partial1.undefinedDrains should be(Set(undefinedDrain1))
|
2014-09-04 14:31:22 +02:00
|
|
|
|
|
|
|
|
val partial2 = PartialFlowGraph(partial1) { implicit b ⇒
|
|
|
|
|
import FlowGraphImplicits._
|
2014-10-02 17:32:08 +02:00
|
|
|
b.attachTap(undefinedSrc1, in1)
|
|
|
|
|
b.attachTap(undefinedSrc2, in2)
|
|
|
|
|
bcast ~> f5 ~> UndefinedDrain[String]("drain2")
|
2014-09-04 14:31:22 +02:00
|
|
|
}
|
2014-10-02 17:32:08 +02:00
|
|
|
partial2.undefinedTaps should be(Set.empty)
|
|
|
|
|
partial2.undefinedDrains should be(Set(undefinedDrain1, UndefinedDrain[String]("drain2")))
|
2014-09-04 14:31:22 +02:00
|
|
|
|
|
|
|
|
FlowGraph(partial2) { implicit b ⇒
|
2014-10-02 17:32:08 +02:00
|
|
|
b.attachDrain(undefinedDrain1, out1)
|
|
|
|
|
b.attachDrain(UndefinedDrain[String]("drain2"), out2)
|
2014-09-10 11:25:38 +02:00
|
|
|
}.run()
|
2014-09-04 14:31:22 +02:00
|
|
|
}
|
|
|
|
|
|
2014-09-11 07:45:19 +02:00
|
|
|
"make it optional to specify flows" in {
|
|
|
|
|
FlowGraph { implicit b ⇒
|
|
|
|
|
val merge = Merge[String]
|
|
|
|
|
val bcast = Broadcast[String]
|
|
|
|
|
import FlowGraphImplicits._
|
|
|
|
|
in1 ~> merge ~> bcast ~> out1
|
|
|
|
|
in2 ~> merge
|
|
|
|
|
bcast ~> out2
|
|
|
|
|
}.run()
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-09 15:22:49 +02:00
|
|
|
"chain input and output ports" in {
|
|
|
|
|
FlowGraph { implicit b ⇒
|
|
|
|
|
val zip = Zip[Int, String]
|
2014-10-02 17:32:08 +02:00
|
|
|
val out = PublisherDrain[(Int, String)]
|
2014-09-09 15:22:49 +02:00
|
|
|
import FlowGraphImplicits._
|
2014-10-02 17:32:08 +02:00
|
|
|
Source(List(1, 2, 3)) ~> zip.left ~> out
|
|
|
|
|
Source(List("a", "b", "c")) ~> zip.right
|
2014-09-09 15:22:49 +02:00
|
|
|
}.run()
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-30 14:03:22 +02:00
|
|
|
"build unzip - zip" in {
|
|
|
|
|
FlowGraph { implicit b ⇒
|
|
|
|
|
val zip = Zip[Int, String]
|
|
|
|
|
val unzip = Unzip[Int, String]
|
2014-10-02 17:32:08 +02:00
|
|
|
val out = PublisherDrain[(Int, String)]
|
2014-09-30 14:03:22 +02:00
|
|
|
import FlowGraphImplicits._
|
2014-10-02 17:32:08 +02:00
|
|
|
Source(List(1 -> "a", 2 -> "b", 3 -> "c")) ~> unzip.in
|
|
|
|
|
unzip.left ~> Flow[Int].map(_ * 2) ~> zip.left
|
2014-09-30 14:03:22 +02:00
|
|
|
unzip.right ~> zip.right
|
|
|
|
|
zip.out ~> out
|
|
|
|
|
}.run()
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-09 15:22:49 +02:00
|
|
|
"distinguish between input and output ports" in {
|
|
|
|
|
intercept[IllegalArgumentException] {
|
|
|
|
|
FlowGraph { implicit b ⇒
|
|
|
|
|
val zip = Zip[Int, String]
|
2014-09-30 14:03:22 +02:00
|
|
|
val unzip = Unzip[Int, String]
|
2014-10-02 17:32:08 +02:00
|
|
|
val wrongOut = PublisherDrain[(Int, Int)]
|
|
|
|
|
val whatever = PublisherDrain[Any]
|
2014-09-09 15:22:49 +02:00
|
|
|
import FlowGraphImplicits._
|
2014-10-02 17:32:08 +02:00
|
|
|
"Flow(List(1, 2, 3)) ~> zip.left ~> wrongOut" shouldNot compile
|
|
|
|
|
"""Flow(List("a", "b", "c")) ~> zip.left""" shouldNot compile
|
|
|
|
|
"""Flow(List("a", "b", "c")) ~> zip.out""" shouldNot compile
|
2014-09-09 15:22:49 +02:00
|
|
|
"zip.left ~> zip.right" shouldNot compile
|
2014-10-02 17:32:08 +02:00
|
|
|
"Flow(List(1, 2, 3)) ~> zip.left ~> wrongOut" shouldNot compile
|
|
|
|
|
"""Flow(List(1 -> "a", 2 -> "b", 3 -> "c")) ~> unzip.in ~> whatever""" shouldNot compile
|
2014-09-09 15:22:49 +02:00
|
|
|
}
|
|
|
|
|
}.getMessage should include("empty")
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-12 12:12:32 +02:00
|
|
|
"check maximumInputCount" in {
|
|
|
|
|
intercept[IllegalArgumentException] {
|
|
|
|
|
FlowGraph { implicit b ⇒
|
|
|
|
|
val bcast = Broadcast[String]
|
|
|
|
|
import FlowGraphImplicits._
|
|
|
|
|
in1 ~> bcast ~> out1
|
|
|
|
|
in2 ~> bcast // wrong
|
|
|
|
|
}
|
|
|
|
|
}.getMessage should include("at most 1 incoming")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"check maximumOutputCount" in {
|
|
|
|
|
intercept[IllegalArgumentException] {
|
|
|
|
|
FlowGraph { implicit b ⇒
|
|
|
|
|
val merge = Merge[String]
|
|
|
|
|
import FlowGraphImplicits._
|
|
|
|
|
in1 ~> merge ~> out1
|
|
|
|
|
merge ~> out2 // wrong
|
|
|
|
|
}
|
|
|
|
|
}.getMessage should include("at most 1 outgoing")
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-30 15:40:08 +02:00
|
|
|
"build with variance" in {
|
2014-10-02 17:32:08 +02:00
|
|
|
val out = SubscriberDrain(SubscriberProbe[Fruit]())
|
2014-09-30 15:40:08 +02:00
|
|
|
FlowGraph { b ⇒
|
|
|
|
|
val merge = Merge[Fruit]
|
|
|
|
|
b.
|
2014-10-02 17:32:08 +02:00
|
|
|
addEdge(Source[Fruit](() ⇒ Some(new Apple)), merge).
|
|
|
|
|
addEdge(Source[Apple](() ⇒ Some(new Apple)), merge).
|
|
|
|
|
addEdge(merge, Flow[Fruit].map(identity), out)
|
2014-09-30 15:40:08 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"build with implicits and variance" in {
|
|
|
|
|
PartialFlowGraph { implicit b ⇒
|
2014-10-02 17:32:08 +02:00
|
|
|
val inA = PublisherTap(PublisherProbe[Fruit]())
|
|
|
|
|
val inB = PublisherTap(PublisherProbe[Apple]())
|
|
|
|
|
val outA = SubscriberDrain(SubscriberProbe[Fruit]())
|
|
|
|
|
val outB = SubscriberDrain(SubscriberProbe[Fruit]())
|
2014-09-30 15:40:08 +02:00
|
|
|
val merge = Merge[Fruit]
|
2014-10-01 13:24:57 +02:00
|
|
|
val unzip = Unzip[Int, String]
|
2014-10-02 17:32:08 +02:00
|
|
|
val whatever = PublisherDrain[Any]
|
2014-09-30 15:40:08 +02:00
|
|
|
import FlowGraphImplicits._
|
2014-10-02 17:32:08 +02:00
|
|
|
Source[Fruit](() ⇒ Some(new Apple)) ~> merge
|
|
|
|
|
Source[Apple](() ⇒ Some(new Apple)) ~> merge
|
2014-09-30 15:40:08 +02:00
|
|
|
inA ~> merge
|
|
|
|
|
inB ~> merge
|
2014-10-02 17:32:08 +02:00
|
|
|
inA ~> Flow[Fruit].map(identity) ~> merge
|
|
|
|
|
inB ~> Flow[Apple].map(identity) ~> merge
|
|
|
|
|
UndefinedTap[Apple] ~> merge
|
|
|
|
|
UndefinedTap[Apple] ~> Flow[Fruit].map(identity) ~> merge
|
|
|
|
|
UndefinedTap[Apple] ~> Flow[Apple].map(identity) ~> merge
|
|
|
|
|
merge ~> Flow[Fruit].map(identity) ~> outA
|
|
|
|
|
|
|
|
|
|
Source[Apple](() ⇒ Some(new Apple)) ~> Broadcast[Apple] ~> merge
|
|
|
|
|
Source[Apple](() ⇒ Some(new Apple)) ~> Broadcast[Apple] ~> outB
|
|
|
|
|
Source[Apple](() ⇒ Some(new Apple)) ~> Broadcast[Apple] ~> UndefinedDrain[Fruit]
|
2014-09-30 15:40:08 +02:00
|
|
|
inB ~> Broadcast[Apple] ~> merge
|
|
|
|
|
|
2014-10-02 17:32:08 +02:00
|
|
|
Source(List(1 -> "a", 2 -> "b", 3 -> "c")) ~> unzip.in
|
2014-10-01 13:24:57 +02:00
|
|
|
unzip.right ~> whatever
|
2014-10-02 17:32:08 +02:00
|
|
|
unzip.left ~> UndefinedDrain[Any]
|
2014-10-01 13:24:57 +02:00
|
|
|
|
2014-10-02 17:32:08 +02:00
|
|
|
"UndefinedTap[Fruit] ~> Flow[Apple].map(identity) ~> merge" shouldNot compile
|
|
|
|
|
"UndefinedTap[Fruit] ~> Broadcast[Apple]" shouldNot compile
|
2014-09-30 15:40:08 +02:00
|
|
|
"merge ~> Broadcast[Apple]" shouldNot compile
|
2014-10-02 17:32:08 +02:00
|
|
|
"merge ~> Flow[Fruit].map(identity) ~> Broadcast[Apple]" shouldNot compile
|
2014-09-30 15:40:08 +02:00
|
|
|
"inB ~> merge ~> Broadcast[Apple]" shouldNot compile
|
|
|
|
|
"inA ~> Broadcast[Apple]" shouldNot compile
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-04 14:31:22 +02:00
|
|
|
}
|
|
|
|
|
}
|