/akka-persistence/src/test/scala/akka/persistence/PersistentViewSpec.scala

https://github.com/migue/akka · Scala · 333 lines · 265 code · 63 blank · 5 comment · 10 complexity · 27ef4959c7c2c5519c48fca82d203151 MD5 · raw file

  1. /**
  2. * Copyright (C) 2014 Typesafe Inc. <http://www.typesafe.com>
  3. */
  4. package akka.persistence
  5. import akka.actor._
  6. import akka.persistence.JournalProtocol.ReplayMessages
  7. import akka.testkit._
  8. import com.typesafe.config.Config
  9. import scala.concurrent.duration._
  10. object PersistentViewSpec {
  11. private class TestPersistentActor(name: String, probe: ActorRef) extends NamedPersistentActor(name) {
  12. def receiveCommand = {
  13. case msg
  14. persist(msg) { m probe ! s"${m}-${lastSequenceNr}" }
  15. }
  16. override def receiveRecover: Receive = {
  17. case _ // do nothing...
  18. }
  19. }
  20. private class TestPersistentView(name: String, probe: ActorRef, interval: FiniteDuration, var failAt: Option[String]) extends PersistentView {
  21. def this(name: String, probe: ActorRef, interval: FiniteDuration) =
  22. this(name, probe, interval, None)
  23. def this(name: String, probe: ActorRef) =
  24. this(name, probe, 100.milliseconds)
  25. override def autoUpdateInterval: FiniteDuration = interval.dilated(context.system)
  26. override val persistenceId: String = name
  27. override val viewId: String = name + "-view"
  28. var last: String = _
  29. def receive = {
  30. case "get"
  31. probe ! last
  32. case "boom"
  33. throw new TestException("boom")
  34. case RecoveryFailure(cause)
  35. throw cause // restart
  36. case payload if isPersistent && shouldFailOn(payload)
  37. throw new TestException("boom")
  38. case payload if isPersistent
  39. last = s"replicated-${payload}-${lastSequenceNr}"
  40. probe ! last
  41. }
  42. override def postRestart(reason: Throwable): Unit = {
  43. super.postRestart(reason)
  44. failAt = None
  45. }
  46. def shouldFailOn(m: Any): Boolean =
  47. failAt.foldLeft(false) { (a, f) a || (m == f) }
  48. }
  49. private class PassiveTestPersistentView(name: String, probe: ActorRef, var failAt: Option[String]) extends PersistentView {
  50. override val persistenceId: String = name
  51. override val viewId: String = name + "-view"
  52. override def autoUpdate: Boolean = false
  53. override def autoUpdateReplayMax: Long = 0L // no message replay during initial recovery
  54. var last: String = _
  55. def receive = {
  56. case "get"
  57. probe ! last
  58. case payload if isPersistent && shouldFailOn(payload)
  59. throw new TestException("boom")
  60. case payload
  61. last = s"replicated-${payload}-${lastSequenceNr}"
  62. }
  63. override def postRestart(reason: Throwable): Unit = {
  64. super.postRestart(reason)
  65. failAt = None
  66. }
  67. def shouldFailOn(m: Any): Boolean =
  68. failAt.foldLeft(false) { (a, f) a || (m == f) }
  69. }
  70. private class ActiveTestPersistentView(name: String, probe: ActorRef) extends PersistentView {
  71. override val persistenceId: String = name
  72. override val viewId: String = name + "-view"
  73. override def autoUpdateInterval: FiniteDuration = 50.millis
  74. override def autoUpdateReplayMax: Long = 2
  75. def receive = {
  76. case payload
  77. probe ! s"replicated-${payload}-${lastSequenceNr}"
  78. }
  79. }
  80. private class BecomingPersistentView(name: String, probe: ActorRef) extends PersistentView {
  81. override def persistenceId = name
  82. override def viewId = name + "-view"
  83. def receive = Actor.emptyBehavior
  84. context.become {
  85. case payload probe ! s"replicated-${payload}-${lastSequenceNr}"
  86. }
  87. }
  88. private class PersistentOrNotTestPersistentView(name: String, probe: ActorRef) extends PersistentView {
  89. override val persistenceId: String = name
  90. override val viewId: String = name + "-view"
  91. def receive = {
  92. case payload if isPersistent probe ! s"replicated-${payload}-${lastSequenceNr}"
  93. case payload probe ! s"normal-${payload}-${lastSequenceNr}"
  94. }
  95. }
  96. private class SnapshottingPersistentView(name: String, probe: ActorRef) extends PersistentView {
  97. override val persistenceId: String = name
  98. override val viewId: String = s"${name}-replicator"
  99. override def autoUpdateInterval: FiniteDuration = 100.microseconds.dilated(context.system)
  100. var last: String = _
  101. def receive = {
  102. case "get"
  103. probe ! last
  104. case "snap"
  105. saveSnapshot(last)
  106. case "restart"
  107. throw new TestException("restart requested")
  108. case SaveSnapshotSuccess(_)
  109. probe ! "snapped"
  110. case SnapshotOffer(metadata, snapshot: String)
  111. last = snapshot
  112. probe ! last
  113. case payload
  114. last = s"replicated-${payload}-${lastSequenceNr}"
  115. probe ! last
  116. }
  117. }
  118. }
  119. abstract class PersistentViewSpec(config: Config) extends AkkaSpec(config) with PersistenceSpec with ImplicitSender {
  120. import akka.persistence.PersistentViewSpec._
  121. var persistentActor: ActorRef = _
  122. var view: ActorRef = _
  123. var persistentActorProbe: TestProbe = _
  124. var viewProbe: TestProbe = _
  125. override protected def beforeEach(): Unit = {
  126. super.beforeEach()
  127. persistentActorProbe = TestProbe()
  128. viewProbe = TestProbe()
  129. persistentActor = system.actorOf(Props(classOf[TestPersistentActor], name, persistentActorProbe.ref))
  130. persistentActor ! "a"
  131. persistentActor ! "b"
  132. persistentActorProbe.expectMsg("a-1")
  133. persistentActorProbe.expectMsg("b-2")
  134. }
  135. override protected def afterEach(): Unit = {
  136. system.stop(persistentActor)
  137. system.stop(view)
  138. super.afterEach()
  139. }
  140. def subscribeToReplay(probe: TestProbe): Unit =
  141. system.eventStream.subscribe(probe.ref, classOf[ReplayMessages])
  142. "A persistent view" must {
  143. "receive past updates from a persistent actor" in {
  144. view = system.actorOf(Props(classOf[TestPersistentView], name, viewProbe.ref))
  145. viewProbe.expectMsg("replicated-a-1")
  146. viewProbe.expectMsg("replicated-b-2")
  147. }
  148. "receive live updates from a persistent actor" in {
  149. view = system.actorOf(Props(classOf[TestPersistentView], name, viewProbe.ref))
  150. viewProbe.expectMsg("replicated-a-1")
  151. viewProbe.expectMsg("replicated-b-2")
  152. persistentActor ! "c"
  153. viewProbe.expectMsg("replicated-c-3")
  154. }
  155. "run updates at specified interval" in {
  156. view = system.actorOf(Props(classOf[TestPersistentView], name, viewProbe.ref, 2.seconds))
  157. // initial update is done on start
  158. viewProbe.expectMsg("replicated-a-1")
  159. viewProbe.expectMsg("replicated-b-2")
  160. // live updates takes 5 seconds to replicate
  161. persistentActor ! "c"
  162. viewProbe.expectNoMsg(1.second)
  163. viewProbe.expectMsg("replicated-c-3")
  164. }
  165. "run updates on user request" in {
  166. view = system.actorOf(Props(classOf[TestPersistentView], name, viewProbe.ref, 5.seconds))
  167. viewProbe.expectMsg("replicated-a-1")
  168. viewProbe.expectMsg("replicated-b-2")
  169. persistentActor ! "c"
  170. persistentActorProbe.expectMsg("c-3")
  171. view ! Update(await = false)
  172. viewProbe.expectMsg("replicated-c-3")
  173. }
  174. "run updates on user request and await update" in {
  175. view = system.actorOf(Props(classOf[TestPersistentView], name, viewProbe.ref, 5.seconds))
  176. viewProbe.expectMsg("replicated-a-1")
  177. viewProbe.expectMsg("replicated-b-2")
  178. persistentActor ! "c"
  179. persistentActorProbe.expectMsg("c-3")
  180. view ! Update(await = true)
  181. view ! "get"
  182. viewProbe.expectMsg("replicated-c-3")
  183. }
  184. "run updates again on failure outside an update cycle" in {
  185. view = system.actorOf(Props(classOf[TestPersistentView], name, viewProbe.ref, 5.seconds))
  186. viewProbe.expectMsg("replicated-a-1")
  187. viewProbe.expectMsg("replicated-b-2")
  188. view ! "boom"
  189. viewProbe.expectMsg("replicated-a-1")
  190. viewProbe.expectMsg("replicated-b-2")
  191. }
  192. "run updates again on failure during an update cycle" in {
  193. persistentActor ! "c"
  194. persistentActorProbe.expectMsg("c-3")
  195. view = system.actorOf(Props(classOf[TestPersistentView], name, viewProbe.ref, 5.seconds, Some("b")))
  196. viewProbe.expectMsg("replicated-a-1")
  197. viewProbe.expectMsg("replicated-a-1")
  198. viewProbe.expectMsg("replicated-b-2")
  199. viewProbe.expectMsg("replicated-c-3")
  200. }
  201. "run size-limited updates on user request" in {
  202. persistentActor ! "c"
  203. persistentActor ! "d"
  204. persistentActor ! "e"
  205. persistentActor ! "f"
  206. persistentActorProbe.expectMsg("c-3")
  207. persistentActorProbe.expectMsg("d-4")
  208. persistentActorProbe.expectMsg("e-5")
  209. persistentActorProbe.expectMsg("f-6")
  210. view = system.actorOf(Props(classOf[PassiveTestPersistentView], name, viewProbe.ref, None))
  211. view ! Update(await = true, replayMax = 2)
  212. view ! "get"
  213. viewProbe.expectMsg("replicated-b-2")
  214. view ! Update(await = true, replayMax = 1)
  215. view ! "get"
  216. viewProbe.expectMsg("replicated-c-3")
  217. view ! Update(await = true, replayMax = 4)
  218. view ! "get"
  219. viewProbe.expectMsg("replicated-f-6")
  220. }
  221. "run size-limited updates automatically" in {
  222. val replayProbe = TestProbe()
  223. persistentActor ! "c"
  224. persistentActor ! "d"
  225. persistentActorProbe.expectMsg("c-3")
  226. persistentActorProbe.expectMsg("d-4")
  227. subscribeToReplay(replayProbe)
  228. view = system.actorOf(Props(classOf[ActiveTestPersistentView], name, viewProbe.ref))
  229. viewProbe.expectMsg("replicated-a-1")
  230. viewProbe.expectMsg("replicated-b-2")
  231. viewProbe.expectMsg("replicated-c-3")
  232. viewProbe.expectMsg("replicated-d-4")
  233. replayProbe.expectMsgPF() { case ReplayMessages(1L, _, 2L, _, _, _) }
  234. replayProbe.expectMsgPF() { case ReplayMessages(3L, _, 2L, _, _, _) }
  235. replayProbe.expectMsgPF() { case ReplayMessages(5L, _, 2L, _, _, _) }
  236. }
  237. "support context.become" in {
  238. view = system.actorOf(Props(classOf[BecomingPersistentView], name, viewProbe.ref))
  239. viewProbe.expectMsg("replicated-a-1")
  240. viewProbe.expectMsg("replicated-b-2")
  241. }
  242. "check if an incoming message is persistent" in {
  243. persistentActor ! "c"
  244. persistentActorProbe.expectMsg("c-3")
  245. view = system.actorOf(Props(classOf[PersistentOrNotTestPersistentView], name, viewProbe.ref))
  246. view ! "d"
  247. view ! "e"
  248. viewProbe.expectMsg("replicated-a-1")
  249. viewProbe.expectMsg("replicated-b-2")
  250. viewProbe.expectMsg("replicated-c-3")
  251. viewProbe.expectMsg("normal-d-3")
  252. viewProbe.expectMsg("normal-e-3")
  253. persistentActor ! "f"
  254. viewProbe.expectMsg("replicated-f-4")
  255. }
  256. "take snapshots" in {
  257. view = system.actorOf(Props(classOf[SnapshottingPersistentView], name, viewProbe.ref))
  258. viewProbe.expectMsg("replicated-a-1")
  259. viewProbe.expectMsg("replicated-b-2")
  260. view ! "snap"
  261. viewProbe.expectMsg("snapped")
  262. view ! "restart"
  263. persistentActor ! "c"
  264. viewProbe.expectMsg("replicated-b-2")
  265. viewProbe.expectMsg("replicated-c-3")
  266. }
  267. }
  268. }
  269. class LeveldbPersistentViewSpec extends PersistentViewSpec(PersistenceSpec.config("leveldb", "LeveldbPersistentViewSpec"))
  270. class InmemPersistentViewSpec extends PersistentViewSpec(PersistenceSpec.config("inmem", "InmemPersistentViewSpec"))