2014-10-06 11:28:13 +02:00
|
|
|
/**
|
|
|
|
|
* Copyright (C) 2014 Typesafe Inc. <http://www.typesafe.com>
|
|
|
|
|
*/
|
2014-10-27 14:35:41 +01:00
|
|
|
package akka.stream.scaladsl
|
2014-10-06 11:28:13 +02:00
|
|
|
|
2015-01-29 10:21:54 +01:00
|
|
|
import scala.concurrent.Await
|
2014-10-06 11:28:13 +02:00
|
|
|
import scala.concurrent.duration._
|
2015-01-29 10:21:54 +01:00
|
|
|
import scala.util.{ Success, Failure }
|
2014-10-06 11:28:13 +02:00
|
|
|
import scala.util.control.NoStackTrace
|
2015-10-21 22:45:39 +02:00
|
|
|
import akka.stream.{ SourceShape, ActorMaterializer }
|
2015-04-24 11:45:03 +03:00
|
|
|
import akka.stream.testkit._
|
2015-10-21 22:45:39 +02:00
|
|
|
import akka.stream.impl.{ PublisherSource, ReactiveStreamsCompliance }
|
2014-10-06 11:28:13 +02:00
|
|
|
|
|
|
|
|
class SourceSpec extends AkkaSpec {
|
|
|
|
|
|
2015-06-23 18:28:53 +02:00
|
|
|
implicit val materializer = ActorMaterializer()
|
2014-10-06 11:28:13 +02:00
|
|
|
|
2015-04-16 16:05:49 +02:00
|
|
|
"Single Source" must {
|
2014-10-06 11:28:13 +02:00
|
|
|
"produce element" in {
|
2015-11-03 12:53:24 +01:00
|
|
|
val p = Source.single(1).runWith(Sink.publisher(false))
|
2015-04-24 11:45:03 +03:00
|
|
|
val c = TestSubscriber.manualProbe[Int]()
|
2014-10-06 11:28:13 +02:00
|
|
|
p.subscribe(c)
|
|
|
|
|
val sub = c.expectSubscription()
|
|
|
|
|
sub.request(1)
|
|
|
|
|
c.expectNext(1)
|
|
|
|
|
c.expectComplete()
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-16 16:05:49 +02:00
|
|
|
"reject later subscriber" in {
|
2015-11-03 12:53:24 +01:00
|
|
|
val p = Source.single(1).runWith(Sink.publisher(false))
|
2015-04-24 11:45:03 +03:00
|
|
|
val c1 = TestSubscriber.manualProbe[Int]()
|
|
|
|
|
val c2 = TestSubscriber.manualProbe[Int]()
|
2014-10-06 11:28:13 +02:00
|
|
|
p.subscribe(c1)
|
|
|
|
|
|
|
|
|
|
val sub1 = c1.expectSubscription()
|
|
|
|
|
sub1.request(1)
|
|
|
|
|
c1.expectNext(1)
|
|
|
|
|
c1.expectComplete()
|
2015-04-16 16:05:49 +02:00
|
|
|
|
2014-10-06 11:28:13 +02:00
|
|
|
p.subscribe(c2)
|
2015-04-16 16:05:49 +02:00
|
|
|
c2.expectSubscriptionAndError()
|
2014-10-06 11:28:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"Empty Source" must {
|
|
|
|
|
"complete immediately" in {
|
2015-11-03 12:53:24 +01:00
|
|
|
val p = Source.empty.runWith(Sink.publisher(false))
|
2015-04-24 11:45:03 +03:00
|
|
|
val c = TestSubscriber.manualProbe[Int]()
|
2014-10-06 11:28:13 +02:00
|
|
|
p.subscribe(c)
|
2015-03-03 10:57:25 +01:00
|
|
|
c.expectSubscriptionAndComplete()
|
2014-10-06 11:28:13 +02:00
|
|
|
|
2015-04-16 16:05:49 +02:00
|
|
|
// reject additional subscriber
|
2015-04-24 11:45:03 +03:00
|
|
|
val c2 = TestSubscriber.manualProbe[Int]()
|
2014-10-06 11:28:13 +02:00
|
|
|
p.subscribe(c2)
|
2015-04-16 16:05:49 +02:00
|
|
|
c2.expectSubscriptionAndError()
|
2014-10-06 11:28:13 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"Failed Source" must {
|
|
|
|
|
"emit error immediately" in {
|
|
|
|
|
val ex = new RuntimeException with NoStackTrace
|
2015-11-03 12:53:24 +01:00
|
|
|
val p = Source.failed(ex).runWith(Sink.publisher(false))
|
2015-04-24 11:45:03 +03:00
|
|
|
val c = TestSubscriber.manualProbe[Int]()
|
2014-10-06 11:28:13 +02:00
|
|
|
p.subscribe(c)
|
2015-03-03 10:57:25 +01:00
|
|
|
c.expectSubscriptionAndError(ex)
|
2014-10-06 11:28:13 +02:00
|
|
|
|
2015-04-16 16:05:49 +02:00
|
|
|
// reject additional subscriber
|
2015-04-24 11:45:03 +03:00
|
|
|
val c2 = TestSubscriber.manualProbe[Int]()
|
2014-10-06 11:28:13 +02:00
|
|
|
p.subscribe(c2)
|
2015-04-16 16:05:49 +02:00
|
|
|
c2.expectSubscriptionAndError()
|
2014-10-06 11:28:13 +02:00
|
|
|
}
|
|
|
|
|
}
|
2014-11-28 10:41:57 +01:00
|
|
|
|
2015-10-21 22:45:39 +02:00
|
|
|
"Maybe Source" must {
|
2015-10-31 14:46:10 +01:00
|
|
|
"complete materialized future with None when stream cancels" in Utils.assertAllStagesStopped {
|
2015-10-21 22:45:39 +02:00
|
|
|
val neverSource = Source.maybe[Int]
|
2015-11-03 12:53:24 +01:00
|
|
|
val pubSink = Sink.publisher[Int](false)
|
2015-01-29 10:21:54 +01:00
|
|
|
|
2015-01-28 14:19:50 +01:00
|
|
|
val (f, neverPub) = neverSource.toMat(pubSink)(Keep.both).run()
|
2015-01-29 10:21:54 +01:00
|
|
|
|
2015-10-21 22:45:39 +02:00
|
|
|
val c = TestSubscriber.manualProbe[Int]()
|
2015-01-29 10:21:54 +01:00
|
|
|
neverPub.subscribe(c)
|
|
|
|
|
val subs = c.expectSubscription()
|
|
|
|
|
|
|
|
|
|
subs.request(1000)
|
|
|
|
|
c.expectNoMsg(300.millis)
|
|
|
|
|
|
|
|
|
|
subs.cancel()
|
2015-10-21 22:45:39 +02:00
|
|
|
Await.result(f.future, 500.millis) shouldEqual None
|
2015-01-29 10:21:54 +01:00
|
|
|
}
|
|
|
|
|
|
2015-10-31 14:46:10 +01:00
|
|
|
"allow external triggering of empty completion" in Utils.assertAllStagesStopped {
|
2015-10-21 22:45:39 +02:00
|
|
|
val neverSource = Source.maybe[Int].filter(_ ⇒ false)
|
2015-01-29 10:21:54 +01:00
|
|
|
val counterSink = Sink.fold[Int, Int](0) { (acc, _) ⇒ acc + 1 }
|
|
|
|
|
|
2015-01-28 14:19:50 +01:00
|
|
|
val (neverPromise, counterFuture) = neverSource.toMat(counterSink)(Keep.both).run()
|
2015-01-29 10:21:54 +01:00
|
|
|
|
|
|
|
|
// external cancellation
|
2015-10-21 22:45:39 +02:00
|
|
|
neverPromise.trySuccess(None) shouldEqual true
|
2015-01-29 10:21:54 +01:00
|
|
|
|
2015-10-21 22:45:39 +02:00
|
|
|
Await.result(counterFuture, 500.millis) shouldEqual 0
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-31 14:46:10 +01:00
|
|
|
"allow external triggering of non-empty completion" in Utils.assertAllStagesStopped {
|
2015-10-21 22:45:39 +02:00
|
|
|
val neverSource = Source.maybe[Int]
|
|
|
|
|
val counterSink = Sink.head[Int]
|
|
|
|
|
|
|
|
|
|
val (neverPromise, counterFuture) = neverSource.toMat(counterSink)(Keep.both).run()
|
|
|
|
|
|
|
|
|
|
// external cancellation
|
|
|
|
|
neverPromise.trySuccess(Some(6)) shouldEqual true
|
|
|
|
|
|
|
|
|
|
Await.result(counterFuture, 500.millis) shouldEqual 6
|
2015-01-29 10:21:54 +01:00
|
|
|
}
|
|
|
|
|
|
2015-10-31 14:46:10 +01:00
|
|
|
"allow external triggering of onError" in Utils.assertAllStagesStopped {
|
2015-10-21 22:45:39 +02:00
|
|
|
val neverSource = Source.maybe[Int]
|
2015-01-29 10:21:54 +01:00
|
|
|
val counterSink = Sink.fold[Int, Int](0) { (acc, _) ⇒ acc + 1 }
|
|
|
|
|
|
2015-01-28 14:19:50 +01:00
|
|
|
val (neverPromise, counterFuture) = neverSource.toMat(counterSink)(Keep.both).run()
|
2015-01-29 10:21:54 +01:00
|
|
|
|
|
|
|
|
// external cancellation
|
|
|
|
|
neverPromise.failure(new Exception("Boom") with NoStackTrace)
|
|
|
|
|
|
2015-02-26 17:55:10 +01:00
|
|
|
val ready = Await.ready(counterFuture, 500.millis)
|
2015-01-29 10:21:54 +01:00
|
|
|
val Failure(ex) = ready.value.get
|
|
|
|
|
ex.getMessage should include("Boom")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-28 14:19:50 +01:00
|
|
|
"Composite Source" must {
|
|
|
|
|
"merge from many inputs" in {
|
2015-06-18 22:24:24 +02:00
|
|
|
val probes = Seq.fill(5)(TestPublisher.manualProbe[Int]())
|
2015-01-28 14:19:50 +01:00
|
|
|
val source = Source.subscriber[Int]
|
2015-04-24 11:45:03 +03:00
|
|
|
val out = TestSubscriber.manualProbe[Int]
|
2015-01-28 14:19:50 +01:00
|
|
|
|
2015-11-30 15:45:37 +01:00
|
|
|
val s = Source.fromGraph(GraphDSL.create(source, source, source, source, source)(Seq(_, _, _, _, _)) { implicit b ⇒
|
2015-01-28 14:19:50 +01:00
|
|
|
(i0, i1, i2, i3, i4) ⇒
|
2015-11-30 15:45:37 +01:00
|
|
|
import GraphDSL.Implicits._
|
2015-01-28 14:19:50 +01:00
|
|
|
val m = b.add(Merge[Int](5))
|
|
|
|
|
i0.outlet ~> m.in(0)
|
|
|
|
|
i1.outlet ~> m.in(1)
|
|
|
|
|
i2.outlet ~> m.in(2)
|
|
|
|
|
i3.outlet ~> m.in(3)
|
|
|
|
|
i4.outlet ~> m.in(4)
|
2015-10-21 22:45:39 +02:00
|
|
|
SourceShape(m.out)
|
|
|
|
|
}).to(Sink(out)).run()
|
2015-01-28 14:19:50 +01:00
|
|
|
|
|
|
|
|
for (i ← 0 to 4) probes(i).subscribe(s(i))
|
|
|
|
|
val sub = out.expectSubscription()
|
|
|
|
|
sub.request(10)
|
|
|
|
|
|
|
|
|
|
val subs = for (i ← 0 to 4) {
|
|
|
|
|
val s = probes(i).expectSubscription()
|
|
|
|
|
s.expectRequest()
|
|
|
|
|
s.sendNext(i)
|
|
|
|
|
s.sendComplete()
|
2014-11-28 10:41:57 +01:00
|
|
|
}
|
|
|
|
|
|
2015-01-28 14:19:50 +01:00
|
|
|
val gotten = for (_ ← 0 to 4) yield out.expectNext()
|
|
|
|
|
gotten.toSet should ===(Set(0, 1, 2, 3, 4))
|
|
|
|
|
out.expectComplete()
|
2014-11-28 10:41:57 +01:00
|
|
|
}
|
2015-06-29 23:47:31 -04:00
|
|
|
|
|
|
|
|
"combine from many inputs with simplified API" in {
|
|
|
|
|
val probes = Seq.fill(3)(TestPublisher.manualProbe[Int]())
|
|
|
|
|
val source = for (i ← 0 to 2) yield Source(probes(i))
|
|
|
|
|
val out = TestSubscriber.manualProbe[Int]
|
|
|
|
|
|
|
|
|
|
Source.combine(source(0), source(1), source(2))(Merge(_)).to(Sink(out)).run()
|
|
|
|
|
|
|
|
|
|
val sub = out.expectSubscription()
|
|
|
|
|
sub.request(3)
|
|
|
|
|
|
|
|
|
|
for (i ← 0 to 2) {
|
|
|
|
|
val s = probes(i).expectSubscription()
|
|
|
|
|
s.expectRequest()
|
|
|
|
|
s.sendNext(i)
|
|
|
|
|
s.sendComplete()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val gotten = for (_ ← 0 to 2) yield out.expectNext()
|
|
|
|
|
gotten.toSet should ===(Set(0, 1, 2))
|
|
|
|
|
out.expectComplete()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"combine from two inputs with simplified API" in {
|
|
|
|
|
val probes = Seq.fill(2)(TestPublisher.manualProbe[Int]())
|
|
|
|
|
val source = Source(probes(0)) :: Source(probes(1)) :: Nil
|
|
|
|
|
val out = TestSubscriber.manualProbe[Int]
|
|
|
|
|
|
|
|
|
|
Source.combine(source(0), source(1))(Merge(_)).to(Sink(out)).run()
|
|
|
|
|
|
|
|
|
|
val sub = out.expectSubscription()
|
|
|
|
|
sub.request(3)
|
|
|
|
|
|
|
|
|
|
for (i ← 0 to 1) {
|
|
|
|
|
val s = probes(i).expectSubscription()
|
|
|
|
|
s.expectRequest()
|
|
|
|
|
s.sendNext(i)
|
|
|
|
|
s.sendComplete()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val gotten = for (_ ← 0 to 1) yield out.expectNext()
|
|
|
|
|
gotten.toSet should ===(Set(0, 1))
|
|
|
|
|
out.expectComplete()
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-28 10:41:57 +01:00
|
|
|
}
|
2015-01-28 14:19:50 +01:00
|
|
|
|
2015-02-26 12:36:46 +01:00
|
|
|
"Repeat Source" must {
|
|
|
|
|
"repeat as long as it takes" in {
|
2015-11-30 15:45:37 +01:00
|
|
|
import GraphDSL.Implicits._
|
2015-03-05 12:21:17 +01:00
|
|
|
val result = Await.result(Source.repeat(42).grouped(10000).runWith(Sink.head), 1.second)
|
2015-02-26 12:36:46 +01:00
|
|
|
result.size should ===(10000)
|
|
|
|
|
result.toSet should ===(Set(42))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-04 15:22:33 +01:00
|
|
|
"Iterator Source" must {
|
|
|
|
|
"properly iterate" in {
|
|
|
|
|
val result = Await.result(Source(() ⇒ Iterator.iterate(false)(!_)).grouped(10).runWith(Sink.head), 1.second)
|
|
|
|
|
result should ===(Seq(false, true, false, true, false, true, false, true, false, true))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-03 10:57:25 +01:00
|
|
|
}
|