227 lines
7.9 KiB
Scala
227 lines
7.9 KiB
Scala
/**
|
|
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
|
|
*/
|
|
package akka.stream.impl.fusing
|
|
|
|
import akka.stream.Attributes
|
|
import akka.stream.impl.fusing.GraphStages.SimpleLinearGraphStage
|
|
import akka.stream.stage._
|
|
import akka.testkit.AkkaSpec
|
|
import akka.stream.testkit.Utils.TE
|
|
|
|
import scala.concurrent.duration._
|
|
|
|
class LifecycleInterpreterSpec extends AkkaSpec with GraphInterpreterSpecKit {
|
|
import akka.stream.Supervision._
|
|
|
|
"Interpreter" must {
|
|
|
|
"call preStart in order on stages" in new OneBoundedSetup[String](
|
|
PreStartAndPostStopIdentity(onStart = () ⇒ testActor ! "start-a"),
|
|
PreStartAndPostStopIdentity(onStart = () ⇒ testActor ! "start-b"),
|
|
PreStartAndPostStopIdentity(onStart = () ⇒ testActor ! "start-c")) {
|
|
expectMsg("start-a")
|
|
expectMsg("start-b")
|
|
expectMsg("start-c")
|
|
expectNoMsg(300.millis)
|
|
upstream.onComplete()
|
|
}
|
|
|
|
"call postStop in order on stages - when upstream completes" in new OneBoundedSetup[String](
|
|
PreStartAndPostStopIdentity(onUpstreamCompleted = () ⇒ testActor ! "complete-a", onStop = () ⇒ testActor ! "stop-a"),
|
|
PreStartAndPostStopIdentity(onUpstreamCompleted = () ⇒ testActor ! "complete-b", onStop = () ⇒ testActor ! "stop-b"),
|
|
PreStartAndPostStopIdentity(onUpstreamCompleted = () ⇒ testActor ! "complete-c", onStop = () ⇒ testActor ! "stop-c")) {
|
|
upstream.onComplete()
|
|
expectMsg("complete-a")
|
|
expectMsg("stop-a")
|
|
expectMsg("complete-b")
|
|
expectMsg("stop-b")
|
|
expectMsg("complete-c")
|
|
expectMsg("stop-c")
|
|
expectNoMsg(300.millis)
|
|
}
|
|
|
|
"call postStop in order on stages - when upstream onErrors" in new OneBoundedSetup[String](
|
|
PreStartAndPostStopIdentity(
|
|
onUpstreamFailed = ex ⇒ testActor ! ex.getMessage,
|
|
onStop = () ⇒ testActor ! "stop-c")) {
|
|
val msg = "Boom! Boom! Boom!"
|
|
upstream.onError(TE(msg))
|
|
expectMsg(msg)
|
|
expectMsg("stop-c")
|
|
expectNoMsg(300.millis)
|
|
}
|
|
|
|
"call postStop in order on stages - when downstream cancels" in new OneBoundedSetup[String](
|
|
PreStartAndPostStopIdentity(onStop = () ⇒ testActor ! "stop-a"),
|
|
PreStartAndPostStopIdentity(onStop = () ⇒ testActor ! "stop-b"),
|
|
PreStartAndPostStopIdentity(onStop = () ⇒ testActor ! "stop-c")) {
|
|
downstream.cancel()
|
|
expectMsg("stop-c")
|
|
expectMsg("stop-b")
|
|
expectMsg("stop-a")
|
|
expectNoMsg(300.millis)
|
|
}
|
|
|
|
"call preStart before postStop" in new OneBoundedSetup[String](
|
|
PreStartAndPostStopIdentity(onStart = () ⇒ testActor ! "start-a", onStop = () ⇒ testActor ! "stop-a")) {
|
|
expectMsg("start-a")
|
|
expectNoMsg(300.millis)
|
|
upstream.onComplete()
|
|
expectMsg("stop-a")
|
|
expectNoMsg(300.millis)
|
|
}
|
|
|
|
"onError when preStart fails" in new OneBoundedSetup[String](
|
|
PreStartFailer(() ⇒ throw TE("Boom!"))) {
|
|
lastEvents() should ===(Set(Cancel, OnError(TE("Boom!"))))
|
|
}
|
|
|
|
"not blow up when postStop fails" in new OneBoundedSetup[String](
|
|
PostStopFailer(() ⇒ throw TE("Boom!"))) {
|
|
upstream.onComplete()
|
|
lastEvents() should ===(Set(OnComplete))
|
|
}
|
|
|
|
"onError when preStart fails with stages after" in new OneBoundedSetup[String](
|
|
Map((x: Int) ⇒ x, stoppingDecider).toGS,
|
|
PreStartFailer(() ⇒ throw TE("Boom!")),
|
|
Map((x: Int) ⇒ x, stoppingDecider).toGS) {
|
|
lastEvents() should ===(Set(Cancel, OnError(TE("Boom!"))))
|
|
}
|
|
|
|
"continue with stream shutdown when postStop fails" in new OneBoundedSetup[String](
|
|
PostStopFailer(() ⇒ throw TE("Boom!"))) {
|
|
lastEvents() should ===(Set())
|
|
|
|
upstream.onComplete()
|
|
lastEvents should ===(Set(OnComplete))
|
|
}
|
|
|
|
"postStop when pushAndFinish called if upstream completes with pushAndFinish" in new OneBoundedSetup[String](
|
|
new PushFinishStage(onPostStop = () ⇒ testActor ! "stop")) {
|
|
|
|
lastEvents() should be(Set.empty)
|
|
|
|
downstream.requestOne()
|
|
lastEvents() should be(Set(RequestOne))
|
|
|
|
upstream.onNextAndComplete("foo")
|
|
lastEvents() should be(Set(OnNext("foo"), OnComplete))
|
|
expectMsg("stop")
|
|
}
|
|
|
|
"postStop when pushAndFinish called with pushAndFinish if indirect upstream completes with pushAndFinish" in new OneBoundedSetup[String](
|
|
Map((x: Any) ⇒ x, stoppingDecider).toGS,
|
|
new PushFinishStage(onPostStop = () ⇒ testActor ! "stop"),
|
|
Map((x: Any) ⇒ x, stoppingDecider).toGS) {
|
|
|
|
lastEvents() should be(Set.empty)
|
|
|
|
downstream.requestOne()
|
|
lastEvents() should be(Set(RequestOne))
|
|
|
|
upstream.onNextAndComplete("foo")
|
|
lastEvents() should be(Set(OnNext("foo"), OnComplete))
|
|
expectMsg("stop")
|
|
}
|
|
|
|
"postStop when pushAndFinish called with pushAndFinish if upstream completes with pushAndFinish and downstream immediately pulls" in new OneBoundedSetup[String](
|
|
new PushFinishStage(onPostStop = () ⇒ testActor ! "stop"),
|
|
Fold("", (x: String, y: String) ⇒ x + y)) {
|
|
|
|
lastEvents() should be(Set.empty)
|
|
|
|
downstream.requestOne()
|
|
lastEvents() should be(Set(RequestOne))
|
|
|
|
upstream.onNextAndComplete("foo")
|
|
lastEvents() should be(Set(OnNext("foo"), OnComplete))
|
|
expectMsg("stop")
|
|
}
|
|
|
|
}
|
|
|
|
private[akka] case class PreStartAndPostStopIdentity[T](
|
|
onStart: () ⇒ Unit = () ⇒ (),
|
|
onStop: () ⇒ Unit = () ⇒ (),
|
|
onUpstreamCompleted: () ⇒ Unit = () ⇒ (),
|
|
onUpstreamFailed: Throwable ⇒ Unit = ex ⇒ ()) extends SimpleLinearGraphStage[T] {
|
|
|
|
override def createLogic(attributes: Attributes): GraphStageLogic =
|
|
new GraphStageLogic(shape) with InHandler with OutHandler {
|
|
override def preStart(): Unit = onStart()
|
|
override def postStop(): Unit = onStop()
|
|
|
|
override def onPush(): Unit = push(out, grab(in))
|
|
override def onPull(): Unit = pull(in)
|
|
|
|
override def onUpstreamFinish(): Unit = {
|
|
onUpstreamCompleted()
|
|
super.onUpstreamFinish()
|
|
}
|
|
|
|
override def onUpstreamFailure(cause: Throwable): Unit = {
|
|
onUpstreamFailed(cause)
|
|
super.onUpstreamFailure(cause)
|
|
}
|
|
|
|
setHandlers(in, out, this)
|
|
}
|
|
|
|
override def toString = "PreStartAndPostStopIdentity"
|
|
}
|
|
|
|
private[akka] case class PreStartFailer[T](pleaseThrow: () ⇒ Unit) extends SimpleLinearGraphStage[T] {
|
|
|
|
override def createLogic(attributes: Attributes): GraphStageLogic =
|
|
new GraphStageLogic(shape) with InHandler with OutHandler {
|
|
override def preStart(): Unit = pleaseThrow()
|
|
|
|
override def onPush(): Unit = push(out, grab(in))
|
|
override def onPull(): Unit = pull(in)
|
|
|
|
setHandlers(in, out, this)
|
|
}
|
|
|
|
override def toString = "PreStartFailer"
|
|
}
|
|
|
|
private[akka] case class PostStopFailer[T](pleaseThrow: () ⇒ Unit) extends SimpleLinearGraphStage[T] {
|
|
|
|
override def createLogic(attributes: Attributes): GraphStageLogic =
|
|
new GraphStageLogic(shape) with InHandler with OutHandler {
|
|
override def onUpstreamFinish(): Unit = completeStage()
|
|
|
|
override def onPush(): Unit = push(out, grab(in))
|
|
override def onPull(): Unit = pull(in)
|
|
|
|
override def postStop(): Unit = pleaseThrow()
|
|
|
|
setHandlers(in, out, this)
|
|
}
|
|
|
|
override def toString = "PostStopFailer"
|
|
}
|
|
|
|
// This test is related to issue #17351
|
|
private[akka] class PushFinishStage(onPostStop: () ⇒ Unit = () ⇒ ()) extends SimpleLinearGraphStage[Any] {
|
|
|
|
override def createLogic(attributes: Attributes): GraphStageLogic =
|
|
new GraphStageLogic(shape) with InHandler with OutHandler {
|
|
override def onPush(): Unit = {
|
|
push(out, grab(in))
|
|
completeStage()
|
|
}
|
|
override def onPull(): Unit = pull(in)
|
|
|
|
override def onUpstreamFinish(): Unit = failStage(TE("Cannot happen"))
|
|
|
|
override def postStop(): Unit = onPostStop()
|
|
|
|
setHandlers(in, out, this)
|
|
}
|
|
|
|
override def toString = "PushFinish"
|
|
}
|
|
}
|