From 7bc7726c2a1a62b7b05a1314ed114bb8b5209d56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Lipski?= Date: Tue, 19 Dec 2023 12:24:06 +0000 Subject: [PATCH] Add flags to selectively disable serializability checks in messages/events/state (#383) --- docs/GUIDE.md | 15 ++ .../scala/org/virtuslab/ash/ClassKind.scala | 21 +++ .../scala/org/virtuslab/ash/ClassType.scala | 21 --- ...SerializabilityCheckerCompilerPlugin.scala | 12 ++ ...bilityCheckerCompilerPluginComponent.scala | 123 ++++++++-------- .../ash/SerializabilityCheckerOptions.scala | 3 + ...es.scala => MySerializableAnnotated.scala} | 0 ...scala => MySerializableNotAnnotated.scala} | 0 .../test/resources/ReplyEffectTestEvent.scala | 6 - .../ReplyEffectTestEventAndState.scala | 10 ++ .../test/resources/ReplyEffectTestState.scala | 6 - ...tyCheckerCompilerPluginComponentSpec.scala | 135 +++++++++++------- 12 files changed, 205 insertions(+), 147 deletions(-) create mode 100644 serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/ClassKind.scala delete mode 100644 serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/ClassType.scala rename serializability-checker-compiler-plugin/src/test/resources/{MySerializableYes.scala => MySerializableAnnotated.scala} (100%) rename serializability-checker-compiler-plugin/src/test/resources/{MySerializableNo.scala => MySerializableNotAnnotated.scala} (100%) create mode 100644 serializability-checker-compiler-plugin/src/test/resources/ReplyEffectTestEventAndState.scala diff --git a/docs/GUIDE.md b/docs/GUIDE.md index fd1bb46f..358660ba 100644 --- a/docs/GUIDE.md +++ b/docs/GUIDE.md @@ -118,6 +118,21 @@ This option disables detection of messages/events/state based on type of argumen This option disables detection of messages/events/state based on return type of the function given as argument to method. This detection is enabled by default. If you want to disable it, add the following setting:
`Compile / scalacOptions += "-P:serializability-checker-plugin:--disable-detection-higher-order-function"`

+- `--exclude-messages` + +This option disables serializability checks on all messages. These checks are enabled by default. If you want to disable them, add the following setting:
+`Compile / scalacOptions += "-P:serializability-checker-plugin:--exclude-messages"`

+ +- `--exclude-persistent-events` + +This option disables serializability checks on all persistent events. These checks are enabled by default. If you want to disable them, add the following setting:
+`Compile / scalacOptions += "-P:serializability-checker-plugin:--exclude-persistent-events"`

+ +- `--exclude-persistent-states` + +This option disables serializability checks on all persistent state classes. These checks are enabled by default. If you want to disable them, add the following setting:
+`Compile / scalacOptions += "-P:serializability-checker-plugin:--exclude-persistent-states"`

+ - `--types-explicitly-marked-as-serializable=,,...` This option can be used to pass a comma-separated list of fully-qualified names of types that should be considered serializable by the checker, even if they do **not** extend a designated serializability trait. diff --git a/serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/ClassKind.scala b/serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/ClassKind.scala new file mode 100644 index 00000000..aeb485e4 --- /dev/null +++ b/serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/ClassKind.scala @@ -0,0 +1,21 @@ +package org.virtuslab.ash + +sealed trait ClassKind { + val name: String +} + +object ClassKind { + case object Message extends ClassKind { + val name = "message" + } + case object PersistentEvent extends ClassKind { + val name = "persistent event" + } + case object PersistentState extends ClassKind { + val name = "persistent state" + } + + case object Ignore extends ClassKind { + val name = "ignore" + } +} diff --git a/serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/ClassType.scala b/serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/ClassType.scala deleted file mode 100644 index 13c276a5..00000000 --- a/serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/ClassType.scala +++ /dev/null @@ -1,21 +0,0 @@ -package org.virtuslab.ash - -sealed trait ClassType { - val name: String -} - -object ClassType { - case object Message extends ClassType { - val name = "message" - } - case object PersistentEvent extends ClassType { - val name = "persistent event" - } - case object PersistentState extends ClassType { - val name = "persistent state" - } - - case object Ignore extends ClassType { - val name = "ignore" - } -} diff --git a/serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/SerializabilityCheckerCompilerPlugin.scala b/serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/SerializabilityCheckerCompilerPlugin.scala index 39604002..00c71e5a 100644 --- a/serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/SerializabilityCheckerCompilerPlugin.scala +++ b/serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/SerializabilityCheckerCompilerPlugin.scala @@ -22,11 +22,17 @@ class SerializabilityCheckerCompilerPlugin(override val global: Global) extends return false pluginOptions.verbose = options.contains(verbose) + pluginOptions.detectFromGenerics = !options.contains(disableGenerics) pluginOptions.detectFromGenericMethods = !options.contains(disableGenericMethods) pluginOptions.detectFromMethods = !options.contains(disableMethods) pluginOptions.detectFromUntypedMethods = !options.contains(disableMethodsUntyped) pluginOptions.detectFromHigherOrderFunctions = !options.contains(disableHigherOrderFunctions) + + pluginOptions.includeMessages = !options.contains(excludeMessages) + pluginOptions.includePersistentEvents = !options.contains(excludePersistentEvents) + pluginOptions.includePersistentStates = !options.contains(excludePersistentStates) + options.find(_.startsWith(typesExplicitlyMarkedAsSerializable)).foreach { opt => pluginOptions.typesExplicitlyMarkedAsSerializable = opt.stripPrefix(typesExplicitlyMarkedAsSerializable).split(",").toSeq.map(_.strip()) @@ -41,6 +47,9 @@ class SerializabilityCheckerCompilerPlugin(override val global: Global) extends |$disableMethods - disables detection of messages/events/state based on type of arguments to a method, e.g. akka.actor.typed.ActorRef.tell |$disableMethodsUntyped - disables detection of messages/events/state based on type of arguments to a method that takes Any, used for Akka Classic |$disableHigherOrderFunctions - disables detection of messages/events/state based on return type of the function given as argument to method + |$excludeMessages - exclude all messages from the serializability check + |$excludePersistentEvents - exclude all events from the serializability check + |$excludePersistentStates - exclude all states from the serializability check |$typesExplicitlyMarkedAsSerializable - comma-separated list of fully-qualified names of types that should be considered serializable by this checker, even if they do NOT extend a designated serializability trait |""".stripMargin) } @@ -54,6 +63,9 @@ object SerializabilityCheckerCompilerPlugin { val disableMethods = "--disable-detection-methods" val disableMethodsUntyped = "--disable-detection-untyped-methods" val disableHigherOrderFunctions = "--disable-detection-higher-order-function" + val excludeMessages = "--exclude-messages" + val excludePersistentEvents = "--exclude-persistent-events" + val excludePersistentStates = "--exclude-persistent-states" val typesExplicitlyMarkedAsSerializable = "--types-explicitly-marked-as-serializable=" } val serializabilityTraitType = "org.virtuslab.ash.annotation.SerializabilityTrait" diff --git a/serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/SerializabilityCheckerCompilerPluginComponent.scala b/serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/SerializabilityCheckerCompilerPluginComponent.scala index 114f9241..62dc0db1 100644 --- a/serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/SerializabilityCheckerCompilerPluginComponent.scala +++ b/serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/SerializabilityCheckerCompilerPluginComponent.scala @@ -12,7 +12,7 @@ class SerializabilityCheckerCompilerPluginComponent(val pluginOptions: Serializa import global._ // just to avoid using tuples where possible - private case class TypeWithClassType(typ: Type, classType: ClassType) + private case class TypeWithClassKind(typ: Type, classKind: ClassKind) override val phaseName: String = "serializability-checker" override val runsAfter: List[String] = List("refchecks") @@ -45,74 +45,74 @@ class SerializabilityCheckerCompilerPluginComponent(val pluginOptions: Serializa private val ignoredTypePrefixes = List("akka.") - private val genericsToTypes: Map[String, Seq[ClassType]] = Map( - "akka.actor.typed.ActorSystem" -> Seq(ClassType.Message), - "akka.actor.typed.ActorRef" -> Seq(ClassType.Message), - "akka.actor.typed.Behavior" -> Seq(ClassType.Message), - "akka.actor.typed.RecipientRef" -> Seq(ClassType.Message), - "akka.pattern.PipeToSupport.PipeableFuture" -> Seq(ClassType.Message), - "akka.pattern.PipeToSupport.PipeableCompletionStage" -> Seq(ClassType.Message), - "akka.persistence.typed.scaladsl.ReplyEffect" -> Seq(ClassType.PersistentEvent, ClassType.PersistentState), - "akka.persistence.typed.scaladsl.Effect" -> Seq(ClassType.PersistentEvent), - "akka.persistence.typed.scaladsl.EffectBuilder" -> Seq(ClassType.PersistentEvent), - "akka.projection.eventsourced.EventEnvelope" -> Seq(ClassType.PersistentEvent, ClassType.PersistentState)) - - private val genericMethodsToTypes: Map[String, Seq[ClassType]] = Map( - "akka.actor.typed.scaladsl.ActorContext.ask" -> Seq(ClassType.Message, ClassType.Message), - "akka.actor.typed.scaladsl.AskPattern.Askable.$qmark" -> Seq(ClassType.Message), - "akka.pattern.PipeToSupport.pipe" -> Seq(ClassType.Message), - "akka.pattern.PipeToSupport.pipeCompletionStage" -> Seq(ClassType.Message)) - - private val concreteMethodsToTypes: Map[String, Seq[ClassType]] = Map( - "akka.actor.typed.ActorRef.ActorRefOps.$bang" -> Seq(ClassType.Message), - "akka.actor.typed.ActorRef.tell" -> Seq(ClassType.Message), - "akka.actor.typed.RecipientRef.tell" -> Seq(ClassType.Message)) - - private val concreteUntypedMethodsToTypes: Map[String, Seq[ClassType]] = Map( - "akka.actor.ActorRef.tell" -> Seq(ClassType.Message, ClassType.Ignore), - "akka.actor.ActorRef.$bang" -> Seq(ClassType.Message), - "akka.actor.ActorRef.forward" -> Seq(ClassType.Message), - "akka.pattern.AskSupport.ask" -> Seq(ClassType.Ignore, ClassType.Message, ClassType.Ignore), - "akka.pattern.AskSupport.askWithStatus" -> Seq(ClassType.Ignore, ClassType.Message, ClassType.Ignore), - "akka.pattern.AskableActorRef.ask" -> Seq(ClassType.Message), - "akka.pattern.AskableActorRef.askWithStatus" -> Seq(ClassType.Message), - "akka.pattern.AskableActorRef.$qmark" -> Seq(ClassType.Message), - "akka.pattern.AskableActorSelection.ask" -> Seq(ClassType.Message), - "akka.pattern.AskableActorSelection.$qmark" -> Seq(ClassType.Message), - "akka.pattern.ExplicitAskSupport.ask" -> Seq(ClassType.Ignore, ClassType.Message, ClassType.Ignore)) - - private val concreteHigherOrderFunctionsToTypes: Map[String, Seq[ClassType]] = Map( - "akka.pattern.ExplicitlyAskableActorRef.ask" -> Seq(ClassType.Message), - "akka.pattern.ExplicitlyAskableActorRef.$qmark" -> Seq(ClassType.Message), - "akka.pattern.ExplicitlyAskableActorSelection.ask" -> Seq(ClassType.Message), - "akka.pattern.ExplicitlyAskableActorSelection.$qmark" -> Seq(ClassType.Message)) - - private val combinedMap: Map[String, Seq[ClassType]] = - genericsToTypes ++ genericMethodsToTypes ++ concreteMethodsToTypes ++ concreteUntypedMethodsToTypes ++ concreteHigherOrderFunctionsToTypes + private val genericsToKinds: Map[String, Seq[ClassKind]] = Map( + "akka.actor.typed.ActorSystem" -> Seq(ClassKind.Message), + "akka.actor.typed.ActorRef" -> Seq(ClassKind.Message), + "akka.actor.typed.Behavior" -> Seq(ClassKind.Message), + "akka.actor.typed.RecipientRef" -> Seq(ClassKind.Message), + "akka.pattern.PipeToSupport.PipeableFuture" -> Seq(ClassKind.Message), + "akka.pattern.PipeToSupport.PipeableCompletionStage" -> Seq(ClassKind.Message), + "akka.persistence.typed.scaladsl.ReplyEffect" -> Seq(ClassKind.PersistentEvent, ClassKind.PersistentState), + "akka.persistence.typed.scaladsl.Effect" -> Seq(ClassKind.PersistentEvent), + "akka.persistence.typed.scaladsl.EffectBuilder" -> Seq(ClassKind.PersistentEvent), + "akka.projection.eventsourced.EventEnvelope" -> Seq(ClassKind.PersistentEvent, ClassKind.PersistentState)) + + private val genericMethodsToKinds: Map[String, Seq[ClassKind]] = Map( + "akka.actor.typed.scaladsl.ActorContext.ask" -> Seq(ClassKind.Message, ClassKind.Message), + "akka.actor.typed.scaladsl.AskPattern.Askable.$qmark" -> Seq(ClassKind.Message), + "akka.pattern.PipeToSupport.pipe" -> Seq(ClassKind.Message), + "akka.pattern.PipeToSupport.pipeCompletionStage" -> Seq(ClassKind.Message)) + + private val concreteMethodsToKinds: Map[String, Seq[ClassKind]] = Map( + "akka.actor.typed.ActorRef.ActorRefOps.$bang" -> Seq(ClassKind.Message), + "akka.actor.typed.ActorRef.tell" -> Seq(ClassKind.Message), + "akka.actor.typed.RecipientRef.tell" -> Seq(ClassKind.Message)) + + private val concreteUntypedMethodsToKinds: Map[String, Seq[ClassKind]] = Map( + "akka.actor.ActorRef.tell" -> Seq(ClassKind.Message, ClassKind.Ignore), + "akka.actor.ActorRef.$bang" -> Seq(ClassKind.Message), + "akka.actor.ActorRef.forward" -> Seq(ClassKind.Message), + "akka.pattern.AskSupport.ask" -> Seq(ClassKind.Ignore, ClassKind.Message, ClassKind.Ignore), + "akka.pattern.AskSupport.askWithStatus" -> Seq(ClassKind.Ignore, ClassKind.Message, ClassKind.Ignore), + "akka.pattern.AskableActorRef.ask" -> Seq(ClassKind.Message), + "akka.pattern.AskableActorRef.askWithStatus" -> Seq(ClassKind.Message), + "akka.pattern.AskableActorRef.$qmark" -> Seq(ClassKind.Message), + "akka.pattern.AskableActorSelection.ask" -> Seq(ClassKind.Message), + "akka.pattern.AskableActorSelection.$qmark" -> Seq(ClassKind.Message), + "akka.pattern.ExplicitAskSupport.ask" -> Seq(ClassKind.Ignore, ClassKind.Message, ClassKind.Ignore)) + + private val concreteHigherOrderFunctionsToKinds: Map[String, Seq[ClassKind]] = Map( + "akka.pattern.ExplicitlyAskableActorRef.ask" -> Seq(ClassKind.Message), + "akka.pattern.ExplicitlyAskableActorRef.$qmark" -> Seq(ClassKind.Message), + "akka.pattern.ExplicitlyAskableActorSelection.ask" -> Seq(ClassKind.Message), + "akka.pattern.ExplicitlyAskableActorSelection.$qmark" -> Seq(ClassKind.Message)) + + private val symbolsToKinds: Map[String, Seq[ClassKind]] = + genericsToKinds ++ genericMethodsToKinds ++ concreteMethodsToKinds ++ concreteUntypedMethodsToKinds ++ concreteHigherOrderFunctionsToKinds override def apply(unit: global.CompilationUnit): Unit = { val body = unit.body val reporter = CrossVersionReporter(global) - val genericsNames = genericsToTypes.keySet - val genericMethods = genericMethodsToTypes.keySet - val concreteMethods = concreteMethodsToTypes.keySet - val concreteUntypedMethods = concreteUntypedMethodsToTypes.keySet - val concreteHigherOrderFunctions = concreteHigherOrderFunctionsToTypes.keySet + val genericsNames = genericsToKinds.keySet + val genericMethods = genericMethodsToKinds.keySet + val concreteMethods = concreteMethodsToKinds.keySet + val concreteUntypedMethods = concreteUntypedMethodsToKinds.keySet + val concreteHigherOrderFunctions = concreteHigherOrderFunctionsToKinds.keySet - def extractTypes(args: List[Tree], x: Tree): List[(TypeWithClassType, Position)] = + def extractTypes(args: List[Tree], x: Tree): List[(TypeWithClassKind, Position)] = args .map(_.tpe) - .zip(combinedMap(x.symbol.fullName)) - .map(typeClassTypeTuple => (TypeWithClassType(typeClassTypeTuple._1, typeClassTypeTuple._2), x.pos)) + .zip(symbolsToKinds(x.symbol.fullName)) + .map(typeClassTypeTuple => (TypeWithClassKind(typeClassTypeTuple._1, typeClassTypeTuple._2), x.pos)) - val detectedTypes: Iterable[(TypeWithClassType, Position)] = body + val detectedTypes: Iterable[(TypeWithClassKind, Position)] = body .collect { case _: ApplyToImplicitArgs => Nil case x: TypeTree if genericsNames.contains(x.tpe.typeSymbol.fullName) && pluginOptions.detectFromGenerics => x.tpe.typeArgs - .zip(combinedMap(x.tpe.typeSymbol.fullName)) - .map(typeClassTypeTuple => (TypeWithClassType(typeClassTypeTuple._1, typeClassTypeTuple._2), x.pos)) + .zip(genericsToKinds(x.tpe.typeSymbol.fullName)) + .map(typeClassTypeTuple => (TypeWithClassKind(typeClassTypeTuple._1, typeClassTypeTuple._2), x.pos)) case x @ TypeApply(_, args) if genericMethods.contains(x.symbol.fullName) && pluginOptions.detectFromGenericMethods => extractTypes(args, x) @@ -127,13 +127,16 @@ class SerializabilityCheckerCompilerPluginComponent(val pluginOptions: Serializa pluginOptions.detectFromHigherOrderFunctions => extractTypes(args, x).flatMap { resultTuple => resultTuple._1.typ.typeArguments match { - case List(_, out) => Some(resultTuple.copy(_1 = TypeWithClassType(out, resultTuple._1.classType))) + case List(_, out) => Some(resultTuple.copy(_1 = TypeWithClassKind(out, resultTuple._1.classKind))) case _ => None } } } .flatten - .filterNot(_._1.classType == ClassType.Ignore) + .filter(_._1.classKind != ClassKind.Ignore) + .filter(pluginOptions.includeMessages || _._1.classKind != ClassKind.Message) + .filter(pluginOptions.includePersistentEvents || _._1.classKind != ClassKind.PersistentEvent) + .filter(pluginOptions.includePersistentStates || _._1.classKind != ClassKind.PersistentState) .groupBy(_._1.typ) .map(_._2.head) @@ -167,8 +170,8 @@ class SerializabilityCheckerCompilerPluginComponent(val pluginOptions: Serializa reporter.error( detectedPosition, s"""${typeWithClassType.typ - .toString()} is used as Akka ${typeWithClassType.classType.name} but does not extend a trait annotated with $serializabilityTraitType. - |Passing an object of a class that does NOT extend a trait annotated with $serializabilityTraitType as a ${typeWithClassType.classType.name} + .toString()} is used as Akka ${typeWithClassType.classKind.name} but does not extend a trait annotated with $serializabilityTraitType. + |Passing an object of a class that does NOT extend a trait annotated with $serializabilityTraitType as a ${typeWithClassType.classKind.name} |may cause Akka to fall back to Java serialization during runtime. | |""".stripMargin) diff --git a/serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/SerializabilityCheckerOptions.scala b/serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/SerializabilityCheckerOptions.scala index aff6acc2..4a7126f8 100644 --- a/serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/SerializabilityCheckerOptions.scala +++ b/serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/SerializabilityCheckerOptions.scala @@ -11,4 +11,7 @@ class SerializabilityCheckerOptions( var detectFromMethods: Boolean = true, var detectFromUntypedMethods: Boolean = true, var detectFromHigherOrderFunctions: Boolean = true, + var includeMessages: Boolean = true, + var includePersistentEvents: Boolean = true, + var includePersistentStates: Boolean = true, var typesExplicitlyMarkedAsSerializable: Seq[String] = Seq.empty) diff --git a/serializability-checker-compiler-plugin/src/test/resources/MySerializableYes.scala b/serializability-checker-compiler-plugin/src/test/resources/MySerializableAnnotated.scala similarity index 100% rename from serializability-checker-compiler-plugin/src/test/resources/MySerializableYes.scala rename to serializability-checker-compiler-plugin/src/test/resources/MySerializableAnnotated.scala diff --git a/serializability-checker-compiler-plugin/src/test/resources/MySerializableNo.scala b/serializability-checker-compiler-plugin/src/test/resources/MySerializableNotAnnotated.scala similarity index 100% rename from serializability-checker-compiler-plugin/src/test/resources/MySerializableNo.scala rename to serializability-checker-compiler-plugin/src/test/resources/MySerializableNotAnnotated.scala diff --git a/serializability-checker-compiler-plugin/src/test/resources/ReplyEffectTestEvent.scala b/serializability-checker-compiler-plugin/src/test/resources/ReplyEffectTestEvent.scala index 6c1e82cc..f204e173 100644 --- a/serializability-checker-compiler-plugin/src/test/resources/ReplyEffectTestEvent.scala +++ b/serializability-checker-compiler-plugin/src/test/resources/ReplyEffectTestEvent.scala @@ -1,12 +1,6 @@ package org.random.project -import akka.actor.typed.ActorSystem -import akka.persistence.query.Offset -import akka.projection.eventsourced.EventEnvelope -import akka.projection.eventsourced.scaladsl.EventSourcedProvider -import akka.projection.scaladsl.SourceProvider import akka.persistence.typed.scaladsl.ReplyEffect -import akka.actor.typed.Behavior import org.virtuslab.ash.annotation.SerializabilityTrait object ReplyEffectTestEvent { diff --git a/serializability-checker-compiler-plugin/src/test/resources/ReplyEffectTestEventAndState.scala b/serializability-checker-compiler-plugin/src/test/resources/ReplyEffectTestEventAndState.scala new file mode 100644 index 00000000..164377b2 --- /dev/null +++ b/serializability-checker-compiler-plugin/src/test/resources/ReplyEffectTestEventAndState.scala @@ -0,0 +1,10 @@ +package org.random.project + +import akka.persistence.typed.scaladsl.ReplyEffect + +object ReplyEffectTestEventAndState { + trait Event extends MySerializable + trait State extends MySerializable + + def test: ReplyEffect[Event, State] = ??? +} diff --git a/serializability-checker-compiler-plugin/src/test/resources/ReplyEffectTestState.scala b/serializability-checker-compiler-plugin/src/test/resources/ReplyEffectTestState.scala index b1c9c106..c22a3ba7 100644 --- a/serializability-checker-compiler-plugin/src/test/resources/ReplyEffectTestState.scala +++ b/serializability-checker-compiler-plugin/src/test/resources/ReplyEffectTestState.scala @@ -1,12 +1,6 @@ package org.random.project -import akka.actor.typed.ActorSystem -import akka.persistence.query.Offset -import akka.projection.eventsourced.EventEnvelope -import akka.projection.eventsourced.scaladsl.EventSourcedProvider -import akka.projection.scaladsl.SourceProvider import akka.persistence.typed.scaladsl.ReplyEffect -import akka.actor.typed.Behavior import org.virtuslab.ash.annotation.SerializabilityTrait object ReplyEffectTestState { diff --git a/serializability-checker-compiler-plugin/src/test/scala/org/virtuslab/ash/SerializabilityCheckerCompilerPluginComponentSpec.scala b/serializability-checker-compiler-plugin/src/test/scala/org/virtuslab/ash/SerializabilityCheckerCompilerPluginComponentSpec.scala index a1c6bbb1..f0d5bbab 100644 --- a/serializability-checker-compiler-plugin/src/test/scala/org/virtuslab/ash/SerializabilityCheckerCompilerPluginComponentSpec.scala +++ b/serializability-checker-compiler-plugin/src/test/scala/org/virtuslab/ash/SerializabilityCheckerCompilerPluginComponentSpec.scala @@ -11,154 +11,181 @@ class SerializabilityCheckerCompilerPluginComponentSpec extends AnyWordSpecLike private def getResourceAsString(name: String) = new String(File(getClass.getClassLoader.getResource(name)).loadBytes) - private val serYesCode = getResourceAsString("MySerializableYes.scala") - private val serNoCode = getResourceAsString("MySerializableNo.scala") + private val mySerializableAnnotated = getResourceAsString("MySerializableAnnotated.scala") + private val mySerializableNotAnnotated = getResourceAsString("MySerializableNotAnnotated.scala") - private def testCode(resourceName: String, errorTypes: ClassType = ClassType.Message, chosenDisableFlags: List[String]) = { + private def expectSerializabilityErrors( + resourceName: String, + expectedErrorType: ClassKind = ClassKind.Message, + pluginFlags: List[String]) = { val code = getResourceAsString(resourceName) - SerializabilityCheckerCompiler.compileCode(List(code, serYesCode), chosenDisableFlags) should be("") - val noOut = SerializabilityCheckerCompiler.compileCode(List(code, serNoCode), chosenDisableFlags) - noOut should include("error") - noOut should include(errorTypes.name) + SerializabilityCheckerCompiler.compileCode(List(code, mySerializableAnnotated), pluginFlags) should be("") + val output = SerializabilityCheckerCompiler.compileCode(List(code, mySerializableNotAnnotated), pluginFlags) + output should include("error") + output should include(expectedErrorType.name) + } + + private def expectNoSerializabilityErrors(resourceName: String, pluginFlags: List[String]) = { + val code = getResourceAsString(resourceName) + SerializabilityCheckerCompiler.compileCode(List(code, mySerializableAnnotated), pluginFlags) should be("") + SerializabilityCheckerCompiler.compileCode(List(code, mySerializableNotAnnotated), pluginFlags) should be("") } "Serializability checker compiler plugin" should { "correctly detect and traverse to serialization marker trait" when { "given Behavior" in { - testCode( + expectSerializabilityErrors( "BehaviorTest.scala", - chosenDisableFlags = List(disableGenericMethods, disableMethods, disableMethodsUntyped, disableHigherOrderFunctions)) + pluginFlags = List(disableGenericMethods, disableMethods, disableMethodsUntyped, disableHigherOrderFunctions)) } "given EventEnvelope" in { - testCode( + expectSerializabilityErrors( "EventEnvelopeTest.scala", - ClassType.PersistentEvent, - chosenDisableFlags = List(disableGenericMethods, disableMethods, disableMethodsUntyped, disableHigherOrderFunctions)) + ClassKind.PersistentEvent, + pluginFlags = List(disableGenericMethods, disableMethods, disableMethodsUntyped, disableHigherOrderFunctions)) } "given Persistent State in ReplyEffect" in { - testCode( + expectSerializabilityErrors( "ReplyEffectTestState.scala", - ClassType.PersistentState, - chosenDisableFlags = List(disableGenericMethods, disableMethods, disableMethodsUntyped, disableHigherOrderFunctions)) + ClassKind.PersistentState, + pluginFlags = List(disableGenericMethods, disableMethods, disableMethodsUntyped, disableHigherOrderFunctions)) } "give Persistent Event in ReplyEffect" in { - testCode( + expectSerializabilityErrors( "ReplyEffectTestEvent.scala", - ClassType.PersistentEvent, - chosenDisableFlags = List(disableGenericMethods, disableMethods, disableMethodsUntyped, disableHigherOrderFunctions)) + ClassKind.PersistentEvent, + pluginFlags = List(disableGenericMethods, disableMethods, disableMethodsUntyped, disableHigherOrderFunctions)) } "given Effect" in { - testCode( + expectSerializabilityErrors( "EffectTest.scala", - ClassType.PersistentEvent, - chosenDisableFlags = List(disableGenericMethods, disableMethods, disableMethodsUntyped, disableHigherOrderFunctions)) + ClassKind.PersistentEvent, + pluginFlags = List(disableGenericMethods, disableMethods, disableMethodsUntyped, disableHigherOrderFunctions)) } "given ask pattern" in { - testCode( + expectSerializabilityErrors( "AskTest.scala", - chosenDisableFlags = List(disableGenerics, disableMethods, disableMethodsUntyped, disableHigherOrderFunctions)) + pluginFlags = List(disableGenerics, disableMethods, disableMethodsUntyped, disableHigherOrderFunctions)) } "given tell pattern" in { - testCode( + expectSerializabilityErrors( "TellTest.scala", - chosenDisableFlags = List(disableGenerics, disableGenericMethods, disableMethodsUntyped, disableHigherOrderFunctions)) + pluginFlags = List(disableGenerics, disableGenericMethods, disableMethodsUntyped, disableHigherOrderFunctions)) } "given ask pattern with sign" in { - testCode( + expectSerializabilityErrors( "AskSignTest.scala", - chosenDisableFlags = List(disableGenerics, disableMethods, disableMethodsUntyped, disableHigherOrderFunctions)) + pluginFlags = List(disableGenerics, disableMethods, disableMethodsUntyped, disableHigherOrderFunctions)) } "given tell pattern with sign" in { - testCode( + expectSerializabilityErrors( "TellSignTest.scala", - chosenDisableFlags = List(disableGenerics, disableGenericMethods, disableMethodsUntyped, disableHigherOrderFunctions)) + pluginFlags = List(disableGenerics, disableGenericMethods, disableMethodsUntyped, disableHigherOrderFunctions)) } "given pipe pattern" in { - testCode( + expectSerializabilityErrors( "PipeTest.scala", - chosenDisableFlags = List(disableGenerics, disableMethods, disableMethodsUntyped, disableHigherOrderFunctions)) + pluginFlags = List(disableGenerics, disableMethods, disableMethodsUntyped, disableHigherOrderFunctions)) } "given classic tell pattern" in { - testCode( + expectSerializabilityErrors( "TellClassicTest.scala", - chosenDisableFlags = List(disableGenerics, disableGenericMethods, disableMethods, disableHigherOrderFunctions)) + pluginFlags = List(disableGenerics, disableGenericMethods, disableMethods, disableHigherOrderFunctions)) } "given classic tell sing pattern" in { - testCode( + expectSerializabilityErrors( "TellSignClassicTest.scala", - chosenDisableFlags = List(disableGenerics, disableGenericMethods, disableMethods, disableHigherOrderFunctions)) + pluginFlags = List(disableGenerics, disableGenericMethods, disableMethods, disableHigherOrderFunctions)) } "given classic ask pattern" in { - testCode( + expectSerializabilityErrors( "AskClassicTest.scala", - chosenDisableFlags = List(disableGenerics, disableGenericMethods, disableMethods, disableHigherOrderFunctions)) + pluginFlags = List(disableGenerics, disableGenericMethods, disableMethods, disableHigherOrderFunctions)) } "given classic ask pattern with sign" in { - testCode( + expectSerializabilityErrors( "AskSignClassicTest.scala", - chosenDisableFlags = List(disableGenerics, disableGenericMethods, disableMethods, disableHigherOrderFunctions)) + pluginFlags = List(disableGenerics, disableGenericMethods, disableMethods, disableHigherOrderFunctions)) } "given classic ask pattern with higher order function" in { - testCode( + expectSerializabilityErrors( "AskHigherOrderClassicTest.scala", - chosenDisableFlags = List(disableGenerics, disableGenericMethods, disableMethods, disableMethodsUntyped)) + pluginFlags = List(disableGenerics, disableGenericMethods, disableMethods, disableMethodsUntyped)) } "RecipientRef type is used instead of ActorRef to reference an Actor" in { - testCode( + expectSerializabilityErrors( "AskRecipientRefTest.scala", - chosenDisableFlags = List(disableGenerics, disableMethods, disableMethodsUntyped, disableHigherOrderFunctions)) + pluginFlags = List(disableGenerics, disableMethods, disableMethodsUntyped, disableHigherOrderFunctions)) } } - "whitelist all Akka types from checks" in { - val akkaWhitelist = getResourceAsString("AkkaWhitelistTest.scala") + "exclude messages from checks" in { + expectNoSerializabilityErrors("AskTest.scala", pluginFlags = List(excludeMessages)) + } + + "exclude events and/or state from checks" in { - val out = SerializabilityCheckerCompiler.compileCode(List(serYesCode, akkaWhitelist)) - out should have size 0 + expectSerializabilityErrors( + "ReplyEffectTestEventAndState.scala", + expectedErrorType = ClassKind.PersistentState, + pluginFlags = List(excludePersistentEvents)) + + expectSerializabilityErrors( + "ReplyEffectTestEventAndState.scala", + expectedErrorType = ClassKind.PersistentEvent, + pluginFlags = List(excludePersistentStates)) + + expectNoSerializabilityErrors( + "ReplyEffectTestEventAndState.scala", + pluginFlags = List(excludePersistentEvents, excludePersistentStates)) + } + + "whitelist all Akka types from checks" in { + expectNoSerializabilityErrors("AkkaWhitelistTest.scala", pluginFlags = List()) } "be able to detect serializer trait in generics" in { - testCode( + expectSerializabilityErrors( "GenericsTest.scala", - chosenDisableFlags = List(disableGenericMethods, disableMethods, disableMethodsUntyped, disableHigherOrderFunctions)) + pluginFlags = List(disableGenericMethods, disableMethods, disableMethodsUntyped, disableHigherOrderFunctions)) } "detect lack of upper bounds in generics" in { val code = getResourceAsString("GenericsTest2.scala") - SerializabilityCheckerCompiler.compileCode(List(serNoCode, code)) should include("error") + SerializabilityCheckerCompiler.compileCode(List(mySerializableNotAnnotated, code)) should include("error") } "ignore Any and Nothing" in { - testCode( + expectSerializabilityErrors( "AnyNothingTest.scala", - chosenDisableFlags = List(disableGenericMethods, disableMethods, disableMethodsUntyped, disableHigherOrderFunctions)) + pluginFlags = List(disableGenericMethods, disableMethods, disableMethodsUntyped, disableHigherOrderFunctions)) } "respect Akka serializers" in { val code = getResourceAsString("AkkaSerializabilityTraitsTest.scala") - SerializabilityCheckerCompiler.compileCode(List(serNoCode, code)) should be("") + SerializabilityCheckerCompiler.compileCode(List(mySerializableNotAnnotated, code)) should be("") } "recognize upper bound type for [_] wildcard usage as scala.Any for ._$ types" in { val code = getResourceAsString("GenericsTest3.scala") - SerializabilityCheckerCompiler.compileCode(List(serNoCode, code)) should be("") + SerializabilityCheckerCompiler.compileCode(List(mySerializableNotAnnotated, code)) should be("") } "fail on usage of Either as a message" in {