!str,htc replace and remove OneBoundedInterpreter

main work by @drewhk with contributions from @2m and @rkuhn

This work uncovered many well-hidden bugs in existing Stages, in
particular StatefulStage. These were hidden by the behavior of
OneBoundedInterpreter that normally behaves more orderly than it
guarantees in general, especially with respect to the timeliness of
delivery of upstream termination signals; the bugs were then that
internal state was not flushed when onComplete arrived “too early”.
This commit is contained in:
Roland Kuhn 2015-10-31 14:46:10 +01:00
parent 20f54435f1
commit 556012b7ee
107 changed files with 2456 additions and 3061 deletions

View file

@ -4,17 +4,24 @@
package akka.stream.impl.fusing
import akka.stream.stage._
import akka.stream.testkit.AkkaSpec
import akka.testkit.EventFilter
import scala.util.control.NoStackTrace
import akka.stream.Supervision
class InterpreterSpec extends InterpreterSpecKit {
class InterpreterSpec extends AkkaSpec with GraphInterpreterSpecKit {
import Supervision.stoppingDecider
/*
* These tests were writtern for the previous veryion of the interpreter, the so called OneBoundedInterpreter.
* These stages are now properly emulated by the GraphInterpreter and many of the edge cases were relevant to
* the execution model of the old one. Still, these tests are very valuable, so please do not remove.
*/
"Interpreter" must {
"implement map correctly" in new TestSetup(Seq(Map((x: Int) x + 1, stoppingDecider))) {
"implement map correctly" in new OneBoundedSetup[Int](Seq(Map((x: Int) x + 1, stoppingDecider))) {
lastEvents() should be(Set.empty)
downstream.requestOne()
@ -33,7 +40,7 @@ class InterpreterSpec extends InterpreterSpecKit {
lastEvents() should be(Set(OnComplete))
}
"implement chain of maps correctly" in new TestSetup(Seq(
"implement chain of maps correctly" in new OneBoundedSetup[Int](Seq(
Map((x: Int) x + 1, stoppingDecider),
Map((x: Int) x * 2, stoppingDecider),
Map((x: Int) x + 1, stoppingDecider))) {
@ -56,7 +63,7 @@ class InterpreterSpec extends InterpreterSpecKit {
lastEvents() should be(Set(Cancel))
}
"work with only boundary ops" in new TestSetup(Seq.empty) {
"work with only boundary ops" in new OneBoundedSetup[Int](Seq.empty) {
lastEvents() should be(Set.empty)
downstream.requestOne()
@ -69,7 +76,7 @@ class InterpreterSpec extends InterpreterSpecKit {
lastEvents() should be(Set(OnComplete))
}
"implement one-to-many many-to-one chain correctly" in new TestSetup(Seq(
"implement one-to-many many-to-one chain correctly" in new OneBoundedSetup[Int](Seq(
Doubler(),
Filter((x: Int) x != 0, stoppingDecider))) {
@ -94,7 +101,7 @@ class InterpreterSpec extends InterpreterSpecKit {
lastEvents() should be(Set(OnComplete))
}
"implement many-to-one one-to-many chain correctly" in new TestSetup(Seq(
"implement many-to-one one-to-many chain correctly" in new OneBoundedSetup[Int](Seq(
Filter((x: Int) x != 0, stoppingDecider),
Doubler())) {
@ -119,7 +126,7 @@ class InterpreterSpec extends InterpreterSpecKit {
lastEvents() should be(Set(Cancel))
}
"implement take" in new TestSetup(Seq(Take(2))) {
"implement take" in new OneBoundedSetup[Int](Seq(Take(2))) {
lastEvents() should be(Set.empty)
@ -136,7 +143,7 @@ class InterpreterSpec extends InterpreterSpecKit {
lastEvents() should be(Set(OnNext(1), Cancel, OnComplete))
}
"implement take inside a chain" in new TestSetup(Seq(
"implement take inside a chain" in new OneBoundedSetup[Int](Seq(
Filter((x: Int) x != 0, stoppingDecider),
Take(2),
Map((x: Int) x + 1, stoppingDecider))) {
@ -159,7 +166,7 @@ class InterpreterSpec extends InterpreterSpecKit {
lastEvents() should be(Set(Cancel, OnComplete, OnNext(3)))
}
"implement fold" in new TestSetup(Seq(Fold(0, (agg: Int, x: Int) agg + x, stoppingDecider))) {
"implement fold" in new OneBoundedSetup[Int](Seq(Fold(0, (agg: Int, x: Int) agg + x, stoppingDecider))) {
lastEvents() should be(Set.empty)
downstream.requestOne()
@ -178,7 +185,7 @@ class InterpreterSpec extends InterpreterSpecKit {
lastEvents() should be(Set(OnNext(3), OnComplete))
}
"implement fold with proper cancel" in new TestSetup(Seq(Fold(0, (agg: Int, x: Int) agg + x, stoppingDecider))) {
"implement fold with proper cancel" in new OneBoundedSetup[Int](Seq(Fold(0, (agg: Int, x: Int) agg + x, stoppingDecider))) {
lastEvents() should be(Set.empty)
@ -198,7 +205,7 @@ class InterpreterSpec extends InterpreterSpecKit {
lastEvents() should be(Set(Cancel))
}
"work if fold completes while not in a push position" in new TestSetup(Seq(Fold(0, (agg: Int, x: Int) agg + x, stoppingDecider))) {
"work if fold completes while not in a push position" in new OneBoundedSetup[Int](Seq(Fold(0, (agg: Int, x: Int) agg + x, stoppingDecider))) {
lastEvents() should be(Set.empty)
@ -209,7 +216,7 @@ class InterpreterSpec extends InterpreterSpecKit {
lastEvents() should be(Set(OnComplete, OnNext(0)))
}
"implement grouped" in new TestSetup(Seq(Grouped(3))) {
"implement grouped" in new OneBoundedSetup[Int](Seq(Grouped(3))) {
lastEvents() should be(Set.empty)
downstream.requestOne()
@ -234,7 +241,7 @@ class InterpreterSpec extends InterpreterSpecKit {
lastEvents() should be(Set(OnNext(Vector(3)), OnComplete))
}
"implement conflate" in new TestSetup(Seq(Conflate(
"implement conflate" in new OneBoundedSetup[Int](Seq(Conflate(
(in: Int) in,
(agg: Int, x: Int) agg + x,
stoppingDecider))) {
@ -266,7 +273,7 @@ class InterpreterSpec extends InterpreterSpecKit {
lastEvents() should be(Set(Cancel))
}
"implement expand" in new TestSetup(Seq(Expand(
"implement expand" in new OneBoundedSetup[Int](Seq(Expand(
(in: Int) in,
(agg: Int) (agg, agg)))) {
@ -294,7 +301,7 @@ class InterpreterSpec extends InterpreterSpecKit {
lastEvents() should be(Set(OnComplete))
}
"work with conflate-conflate" in new TestSetup(Seq(
"work with conflate-conflate" in new OneBoundedSetup[Int](Seq(
Conflate(
(in: Int) in,
(agg: Int, x: Int) agg + x,
@ -332,7 +339,7 @@ class InterpreterSpec extends InterpreterSpecKit {
}
"work with expand-expand" in new TestSetup(Seq(
"work with expand-expand" in new OneBoundedSetup[Int](Seq(
Expand(
(in: Int) in,
(agg: Int) (agg, agg + 1)),
@ -369,7 +376,7 @@ class InterpreterSpec extends InterpreterSpecKit {
lastEvents() should be(Set(OnComplete, OnNext(12)))
}
"implement conflate-expand" in new TestSetup(Seq(
"implement conflate-expand" in new OneBoundedSetup[Int](Seq(
Conflate(
(in: Int) in,
(agg: Int, x: Int) agg + x,
@ -405,12 +412,7 @@ class InterpreterSpec extends InterpreterSpecKit {
lastEvents() should be(Set(Cancel))
}
"implement expand-conflate" in {
pending
// Needs to detect divergent loops
}
"implement doubler-conflate" in new TestSetup(Seq(
"implement doubler-conflate" in new OneBoundedSetup[Int](Seq(
Doubler(),
Conflate(
(in: Int) in,
@ -429,7 +431,8 @@ class InterpreterSpec extends InterpreterSpecKit {
}
"work with jumpback table and completed elements" in new TestSetup(Seq(
// Note, the new interpreter has no jumpback table, still did not want to remove the test
"work with jumpback table and completed elements" in new OneBoundedSetup[Int](Seq(
Map((x: Int) x, stoppingDecider),
Map((x: Int) x, stoppingDecider),
KeepGoing(),
@ -461,7 +464,7 @@ class InterpreterSpec extends InterpreterSpecKit {
}
"work with pushAndFinish if upstream completes with pushAndFinish" in new TestSetup(Seq(
"work with pushAndFinish if upstream completes with pushAndFinish" in new OneBoundedSetup[Int](Seq(
new PushFinishStage)) {
lastEvents() should be(Set.empty)
@ -469,11 +472,11 @@ class InterpreterSpec extends InterpreterSpecKit {
downstream.requestOne()
lastEvents() should be(Set(RequestOne))
upstream.onNextAndComplete("foo")
lastEvents() should be(Set(OnNext("foo"), OnComplete))
upstream.onNextAndComplete(0)
lastEvents() should be(Set(OnNext(0), OnComplete))
}
"work with pushAndFinish if indirect upstream completes with pushAndFinish" in new TestSetup(Seq(
"work with pushAndFinish if indirect upstream completes with pushAndFinish" in new OneBoundedSetup[Int](Seq(
Map((x: Any) x, stoppingDecider),
new PushFinishStage,
Map((x: Any) x, stoppingDecider))) {
@ -483,24 +486,24 @@ class InterpreterSpec extends InterpreterSpecKit {
downstream.requestOne()
lastEvents() should be(Set(RequestOne))
upstream.onNextAndComplete("foo")
lastEvents() should be(Set(OnNext("foo"), OnComplete))
upstream.onNextAndComplete(1)
lastEvents() should be(Set(OnNext(1), OnComplete))
}
"work with pushAndFinish if upstream completes with pushAndFinish and downstream immediately pulls" in new TestSetup(Seq(
"work with pushAndFinish if upstream completes with pushAndFinish and downstream immediately pulls" in new OneBoundedSetup[Int](Seq(
new PushFinishStage,
Fold("", (x: String, y: String) x + y, stoppingDecider))) {
Fold(0, (x: Int, y: Int) x + y, stoppingDecider))) {
lastEvents() should be(Set.empty)
downstream.requestOne()
lastEvents() should be(Set(RequestOne))
upstream.onNextAndComplete("foo")
lastEvents() should be(Set(OnNext("foo"), OnComplete))
upstream.onNextAndComplete(1)
lastEvents() should be(Set(OnNext(1), OnComplete))
}
"report error if pull is called while op is terminating" in new TestSetup(Seq(new PushPullStage[Any, Any] {
"report error if pull is called while op is terminating" in new OneBoundedSetup[Int](Seq(new PushPullStage[Any, Any] {
override def onPull(ctx: Context[Any]): SyncDirective = ctx.pull()
override def onPush(elem: Any, ctx: Context[Any]): SyncDirective = ctx.pull()
override def onUpstreamFinish(ctx: Context[Any]): TerminationDirective = ctx.absorbTermination()
@ -514,12 +517,12 @@ class InterpreterSpec extends InterpreterSpecKit {
val ev = lastEvents()
ev.nonEmpty should be(true)
ev.forall {
case OnError(_: IllegalStateException) true
case _ false
case OnError(_: IllegalArgumentException) true
case _ false
} should be(true)
}
"implement take-take" in new TestSetup(Seq(
"implement take-take" in new OneBoundedSetup[Int](Seq(
Take(1),
Take(1))) {
lastEvents() should be(Set.empty)
@ -527,12 +530,12 @@ class InterpreterSpec extends InterpreterSpecKit {
downstream.requestOne()
lastEvents() should be(Set(RequestOne))
upstream.onNext("foo")
lastEvents() should be(Set(OnNext("foo"), OnComplete, Cancel))
upstream.onNext(1)
lastEvents() should be(Set(OnNext(1), OnComplete, Cancel))
}
"implement take-take with pushAndFinish from upstream" in new TestSetup(Seq(
"implement take-take with pushAndFinish from upstream" in new OneBoundedSetup[Int](Seq(
Take(1),
Take(1))) {
lastEvents() should be(Set.empty)
@ -540,8 +543,8 @@ class InterpreterSpec extends InterpreterSpecKit {
downstream.requestOne()
lastEvents() should be(Set(RequestOne))
upstream.onNextAndComplete("foo")
lastEvents() should be(Set(OnNext("foo"), OnComplete))
upstream.onNextAndComplete(1)
lastEvents() should be(Set(OnNext(1), OnComplete))
}
@ -551,7 +554,7 @@ class InterpreterSpec extends InterpreterSpecKit {
override def onDownstreamFinish(ctx: Context[Int]): TerminationDirective = ctx.absorbTermination()
}
"not allow absorbTermination from onDownstreamFinish()" in new TestSetup(Seq(
"not allow absorbTermination from onDownstreamFinish()" in new OneBoundedSetup[Int](Seq(
new InvalidAbsorbTermination)) {
lastEvents() should be(Set.empty)
@ -564,4 +567,51 @@ class InterpreterSpec extends InterpreterSpecKit {
}
private[akka] case class Doubler[T]() extends PushPullStage[T, T] {
var oneMore: Boolean = false
var lastElem: T = _
override def onPush(elem: T, ctx: Context[T]): SyncDirective = {
lastElem = elem
oneMore = true
ctx.push(elem)
}
override def onPull(ctx: Context[T]): SyncDirective = {
if (oneMore) {
oneMore = false
ctx.push(lastElem)
} else ctx.pull()
}
}
private[akka] case class KeepGoing[T]() extends PushPullStage[T, T] {
var lastElem: T = _
override def onPush(elem: T, ctx: Context[T]): SyncDirective = {
lastElem = elem
ctx.push(elem)
}
override def onPull(ctx: Context[T]): SyncDirective = {
if (ctx.isFinishing) {
ctx.push(lastElem)
} else ctx.pull()
}
override def onUpstreamFinish(ctx: Context[T]): TerminationDirective = ctx.absorbTermination()
}
// This test is related to issue #17351
private[akka] class PushFinishStage(onPostStop: () Unit = () ()) extends PushStage[Any, Any] {
override def onPush(elem: Any, ctx: Context[Any]): SyncDirective =
ctx.pushAndFinish(elem)
override def onUpstreamFinish(ctx: Context[Any]): TerminationDirective =
ctx.fail(akka.stream.testkit.Utils.TE("Cannot happen"))
override def postStop(): Unit =
onPostStop()
}
}