From 659eb401464172b37520f73e3134a9f144c0ddfb Mon Sep 17 00:00:00 2001 From: Sebastian Alfers Date: Tue, 26 Oct 2021 16:41:18 +0200 Subject: [PATCH] Allow initial state to be null while using EventSourcedBehaviorTestKit (#30823) * Allow initial state to be null while using EventSourcedBehaviorTestKit --- .../EventSourcedBehaviorTestKitImpl.scala | 8 ++++---- .../javadsl/EventSourcedBehaviorTestKit.scala | 3 --- .../EventSourcedBehaviorTestKit.scala | 2 -- .../EventSourcedBehaviorTestKitSpec.scala | 20 ++++++++++++++++--- ...pr-30823-null-state-actor-message.excludes | 7 +++++++ .../internal/EventSourcedBehaviorImpl.scala | 7 ++++++- .../persistence/typed/internal/Running.scala | 5 ++--- 7 files changed, 36 insertions(+), 16 deletions(-) create mode 100644 akka-persistence-typed/src/main/mima-filters/2.6.17.backwards.excludes/pr-30823-null-state-actor-message.excludes diff --git a/akka-persistence-testkit/src/main/scala/akka/persistence/testkit/internal/EventSourcedBehaviorTestKitImpl.scala b/akka-persistence-testkit/src/main/scala/akka/persistence/testkit/internal/EventSourcedBehaviorTestKitImpl.scala index 4987519777..098d9e25cb 100644 --- a/akka-persistence-testkit/src/main/scala/akka/persistence/testkit/internal/EventSourcedBehaviorTestKitImpl.scala +++ b/akka-persistence-testkit/src/main/scala/akka/persistence/testkit/internal/EventSourcedBehaviorTestKitImpl.scala @@ -8,7 +8,6 @@ import scala.collection.immutable import scala.concurrent.Await import scala.reflect.ClassTag import scala.util.control.NonFatal - import akka.actor.testkit.typed.scaladsl.ActorTestKit import akka.actor.testkit.typed.scaladsl.SerializationTestKit import akka.actor.typed.ActorRef @@ -26,6 +25,7 @@ import akka.persistence.testkit.scaladsl.EventSourcedBehaviorTestKit.Serializati import akka.persistence.testkit.scaladsl.PersistenceTestKit import akka.persistence.typed.PersistenceId import akka.persistence.typed.internal.EventSourcedBehaviorImpl +import akka.persistence.typed.internal.EventSourcedBehaviorImpl.GetStateReply import akka.stream.scaladsl.Sink /** @@ -99,7 +99,7 @@ import akka.stream.scaladsl.Sink PersistenceQuery(system).readJournalFor[CurrentEventsByPersistenceIdQuery](PersistenceTestKitReadJournal.Identifier) private val probe = actorTestKit.createTestProbe[Any]() - private val stateProbe = actorTestKit.createTestProbe[State]() + private val stateProbe = actorTestKit.createTestProbe[GetStateReply[State]]() private var actor: ActorRef[Command] = actorTestKit.spawn(behavior) private def internalActor = actor.unsafeUpcast[Any] private val persistenceId: PersistenceId = { @@ -175,7 +175,7 @@ import akka.stream.scaladsl.Sink override def getState(): State = { internalActor ! EventSourcedBehaviorImpl.GetState(stateProbe.ref) - stateProbe.receiveMessage() + stateProbe.receiveMessage().currentState } private def preCommandCheck(command: Command): Unit = { @@ -212,7 +212,7 @@ import akka.stream.scaladsl.Sink internalActor ! EventSourcedBehaviorImpl.GetState(stateProbe.ref) try { val state = stateProbe.receiveMessage() - RestartResultImpl(state) + RestartResultImpl(state.currentState) } catch { case NonFatal(_) => throw new IllegalStateException("Could not restart. Maybe exception from event handler. See logs.") diff --git a/akka-persistence-testkit/src/main/scala/akka/persistence/testkit/javadsl/EventSourcedBehaviorTestKit.scala b/akka-persistence-testkit/src/main/scala/akka/persistence/testkit/javadsl/EventSourcedBehaviorTestKit.scala index 9a131ba410..ff629d7aac 100644 --- a/akka-persistence-testkit/src/main/scala/akka/persistence/testkit/javadsl/EventSourcedBehaviorTestKit.scala +++ b/akka-persistence-testkit/src/main/scala/akka/persistence/testkit/javadsl/EventSourcedBehaviorTestKit.scala @@ -6,11 +6,8 @@ package akka.persistence.testkit.javadsl import java.util.{ List => JList } import java.util.function.{ Function => JFunction } - import scala.reflect.ClassTag - import com.typesafe.config.Config - import akka.actor.typed.ActorRef import akka.actor.typed.ActorSystem import akka.actor.typed.Behavior diff --git a/akka-persistence-testkit/src/main/scala/akka/persistence/testkit/scaladsl/EventSourcedBehaviorTestKit.scala b/akka-persistence-testkit/src/main/scala/akka/persistence/testkit/scaladsl/EventSourcedBehaviorTestKit.scala index 112ddbd19b..0f330234ab 100644 --- a/akka-persistence-testkit/src/main/scala/akka/persistence/testkit/scaladsl/EventSourcedBehaviorTestKit.scala +++ b/akka-persistence-testkit/src/main/scala/akka/persistence/testkit/scaladsl/EventSourcedBehaviorTestKit.scala @@ -6,10 +6,8 @@ package akka.persistence.testkit.scaladsl import scala.collection.immutable import scala.reflect.ClassTag - import com.typesafe.config.Config import com.typesafe.config.ConfigFactory - import akka.actor.testkit.typed.scaladsl.ActorTestKit import akka.actor.typed.ActorRef import akka.actor.typed.ActorSystem diff --git a/akka-persistence-testkit/src/test/scala/akka/persistence/testkit/scaladsl/EventSourcedBehaviorTestKitSpec.scala b/akka-persistence-testkit/src/test/scala/akka/persistence/testkit/scaladsl/EventSourcedBehaviorTestKitSpec.scala index 2033f3c0dc..6e8e29b716 100644 --- a/akka-persistence-testkit/src/test/scala/akka/persistence/testkit/scaladsl/EventSourcedBehaviorTestKitSpec.scala +++ b/akka-persistence-testkit/src/test/scala/akka/persistence/testkit/scaladsl/EventSourcedBehaviorTestKitSpec.scala @@ -5,9 +5,7 @@ package akka.persistence.testkit.scaladsl import java.io.NotSerializableException - import org.scalatest.wordspec.AnyWordSpecLike - import akka.Done import akka.actor.testkit.typed.TestException import akka.actor.testkit.typed.scaladsl.LogCapturing @@ -17,7 +15,7 @@ import akka.actor.typed.ActorRef import akka.actor.typed.Behavior import akka.actor.typed.scaladsl.ActorContext import akka.actor.typed.scaladsl.Behaviors -import akka.persistence.testkit.scaladsl.EventSourcedBehaviorTestKitSpec.TestCounter.NotSerializableState +import akka.persistence.testkit.scaladsl.EventSourcedBehaviorTestKitSpec.TestCounter.{ NotSerializableState, NullState } import akka.persistence.typed.PersistenceId import akka.persistence.typed.internal.JournalFailureException import akka.persistence.typed.scaladsl.Effect @@ -43,6 +41,7 @@ object EventSourcedBehaviorTestKitSpec { sealed trait State final case class RealState(value: Int, history: Vector[Int]) extends State with CborSerializable + final case class NullState() extends State with CborSerializable case object IncrementWithNotSerializableEvent extends Command with CborSerializable final case class NotSerializableEvent(delta: Int) extends Event @@ -120,6 +119,8 @@ object EventSourcedBehaviorTestKitSpec { NotSerializableState(value + delta, history :+ value) case (state: NotSerializableState, _) => throw new IllegalStateException(state.toString) + case (null, _) => NullState() + case (NullState(), _) => NullState() }) } } @@ -129,17 +130,30 @@ class EventSourcedBehaviorTestKitSpec extends ScalaTestWithActorTestKit(EventSourcedBehaviorTestKit.config) with AnyWordSpecLike with LogCapturing { + import EventSourcedBehaviorTestKitSpec._ private val persistenceId = PersistenceId.ofUniqueId("test") private val behavior = TestCounter(persistenceId) + private def createTestKitNull() = { + EventSourcedBehaviorTestKit[TestCounter.Command, TestCounter.Event, TestCounter.State]( + system, + TestCounter(persistenceId, null)) + } + private def createTestKit() = { EventSourcedBehaviorTestKit[TestCounter.Command, TestCounter.Event, TestCounter.State](system, behavior) } "EventSourcedBehaviorTestKit" must { + "handle null state" in { + val eventSourcedTestKit = createTestKitNull() + val result = eventSourcedTestKit.runCommand(TestCounter.Increment) + result.state shouldBe NullState() + } + "run commands" in { val eventSourcedTestKit = createTestKit() diff --git a/akka-persistence-typed/src/main/mima-filters/2.6.17.backwards.excludes/pr-30823-null-state-actor-message.excludes b/akka-persistence-typed/src/main/mima-filters/2.6.17.backwards.excludes/pr-30823-null-state-actor-message.excludes new file mode 100644 index 0000000000..594da95725 --- /dev/null +++ b/akka-persistence-typed/src/main/mima-filters/2.6.17.backwards.excludes/pr-30823-null-state-actor-message.excludes @@ -0,0 +1,7 @@ +# this is an internal api to avoid sending state as null vioa an Actor message +ProblemFilters.exclude[IncompatibleSignatureProblem]("akka.persistence.typed.internal.EventSourcedBehaviorImpl#GetState.this") +ProblemFilters.exclude[IncompatibleSignatureProblem]("akka.persistence.typed.internal.EventSourcedBehaviorImpl#GetState.apply") +ProblemFilters.exclude[IncompatibleSignatureProblem]("akka.persistence.typed.internal.EventSourcedBehaviorImpl#GetState.unapply") +ProblemFilters.exclude[IncompatibleSignatureProblem]("akka.persistence.typed.internal.EventSourcedBehaviorImpl#GetState.replyTo") +ProblemFilters.exclude[IncompatibleSignatureProblem]("akka.persistence.typed.internal.EventSourcedBehaviorImpl#GetState.copy") +ProblemFilters.exclude[IncompatibleSignatureProblem]("akka.persistence.typed.internal.EventSourcedBehaviorImpl#GetState.copy$default$1") \ No newline at end of file diff --git a/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/EventSourcedBehaviorImpl.scala b/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/EventSourcedBehaviorImpl.scala index 3ebb23d96d..9a70728d8e 100644 --- a/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/EventSourcedBehaviorImpl.scala +++ b/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/EventSourcedBehaviorImpl.scala @@ -72,7 +72,12 @@ private[akka] object EventSourcedBehaviorImpl { * Used by EventSourcedBehaviorTestKit to retrieve the state. * Can't be a Signal because those are not stashed. */ - final case class GetState[State](replyTo: ActorRef[State]) extends InternalProtocol + final case class GetState[State](replyTo: ActorRef[GetStateReply[State]]) extends InternalProtocol + + /** + * Used to send a state being `null` as an Actor message + */ + final case class GetStateReply[State](currentState: State) /** * Used to start the replication stream at the correct sequence number diff --git a/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/Running.scala b/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/Running.scala index cc35a03725..b5a9d8cf4d 100644 --- a/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/Running.scala +++ b/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/Running.scala @@ -9,7 +9,6 @@ import java.time.LocalDateTime import java.time.ZoneId import java.time.format.DateTimeFormatter import java.util.concurrent.atomic.AtomicReference - import scala.annotation.tailrec import scala.collection.immutable import akka.actor.UnhandledMessage @@ -49,7 +48,7 @@ import akka.persistence.typed.{ SnapshotMetadata, SnapshotSelectionCriteria } -import akka.persistence.typed.internal.EventSourcedBehaviorImpl.{ GetSeenSequenceNr, GetState } +import akka.persistence.typed.internal.EventSourcedBehaviorImpl.{ GetSeenSequenceNr, GetState, GetStateReply } import akka.persistence.typed.internal.InternalProtocol.ReplicatedEventEnvelope import akka.persistence.typed.internal.JournalInteractions.EventToPersist import akka.persistence.typed.internal.Running.WithSeqNrAccessible @@ -384,7 +383,7 @@ private[akka] object Running { // Used by EventSourcedBehaviorTestKit to retrieve the state. def onGetState(get: GetState[S]): Behavior[InternalProtocol] = { - get.replyTo ! state.state + get.replyTo ! GetStateReply(state.state) this }