From c9cae12c2f4ce3e0b8075293b94beb7afd49e090 Mon Sep 17 00:00:00 2001 From: Karel Cemus Date: Thu, 1 Feb 2024 01:08:22 +0100 Subject: [PATCH 1/5] Migration from specs2 to scalatest (#273) * Migration from specs2 to scalatest * Added changelog note --- CHANGELOG.md | 18 +- build.sbt | 7 +- .../scala/play/api/cache/redis/CacheApi.scala | 2 +- .../cache/redis/configuration/RedisHost.scala | 1 + .../redis/configuration/RedisInstance.scala | 2 +- .../cache/redis/connector/RedisCommands.scala | 9 +- .../connector/RedisConnectorProvider.scala | 2 +- .../cache/redis/impl/InvocationPolicy.scala | 9 +- .../api/cache/redis/impl/RedisCache.scala | 3 +- .../cache/redis/impl/RedisListJavaImpl.scala | 19 +- src/test/resources/reference.conf | 2 +- .../ScalaSpecificSerializerSpec.scala | 64 -- .../cache/redis/ClusterRedisContainer.scala | 20 - .../play/api/cache/redis/ExpirationSpec.scala | 12 +- .../play/api/cache/redis/Implicits.scala | 118 ---- .../api/cache/redis/RecoveryPolicySpec.scala | 48 +- .../redis/RedisCacheComponentsSpec.scala | 61 +- .../cache/redis/RedisCacheModuleSpec.scala | 278 ++++---- .../play/api/cache/redis/RedisContainer.scala | 19 - .../scala/play/api/cache/redis/Shutdown.scala | 13 - .../redis/StandaloneRedisContainer.scala | 11 - .../configuration/HostnameResolverSpec.scala | 4 +- .../redis/configuration/RedisHostSpec.scala | 68 +- .../RedisInstanceManagerSpec.scala | 166 +++-- .../RedisInstanceManagerTest.scala | 25 - .../RedisInstanceProviderSpec.scala | 15 +- .../configuration/RedisTimeoutsSpec.scala | 93 +-- .../redis/connector/ExpectedFutureSpec.scala | 40 +- .../redis/connector/FailEagerlySpec.scala | 90 +-- .../redis/connector/MockedConnector.scala | 30 - .../redis/connector/RedisClusterSpec.scala | 150 ++--- .../connector/RedisConnectorFailureSpec.scala | 339 ++++++---- .../redis/connector/RedisConnectorSpec.scala | 511 -------------- .../connector/RedisRequestTimeoutSpec.scala | 58 +- .../redis/connector/RedisSentinelSpec.scala | 140 ++-- .../redis/connector/RedisStandaloneSpec.scala | 618 +++++++++++++++++ .../redis/connector/SerializerImplicits.scala | 21 - .../redis/connector/SerializerSpec.scala | 154 +++-- .../api/cache/redis/connector/TestCase.scala | 20 - .../cache/redis/impl/AsyncJavaRedisSpec.scala | 596 +++++++++-------- .../api/cache/redis/impl/AsyncRedisMock.scala | 244 +++++++ .../api/cache/redis/impl/AsyncRedisSpec.scala | 117 ++-- .../api/cache/redis/impl/BuildersSpec.scala | 132 ++-- .../redis/impl/InvocationPolicySpec.scala | 51 +- .../redis/impl/RedisCacheImplicits.scala | 166 ----- .../api/cache/redis/impl/RedisCacheSpec.scala | 625 +++++++++++------- .../cache/redis/impl/RedisConnectorMock.scala | 458 +++++++++++++ .../cache/redis/impl/RedisJavaListSpec.scala | 345 +++++----- .../cache/redis/impl/RedisJavaMapSpec.scala | 128 ++-- .../cache/redis/impl/RedisJavaSetSpec.scala | 72 +- .../cache/redis/impl/RedisListJavaMock.scala | 131 ++++ .../api/cache/redis/impl/RedisListSpec.scala | 565 ++++++++-------- .../cache/redis/impl/RedisMapJavaMock.scala | 89 +++ .../api/cache/redis/impl/RedisMapSpec.scala | 298 +++++---- .../cache/redis/impl/RedisPrefixSpec.scala | 60 +- .../cache/redis/impl/RedisRuntimeMock.scala | 41 ++ .../cache/redis/impl/RedisRuntimeSpec.scala | 77 ++- .../cache/redis/impl/RedisSetJavaMock.scala | 62 ++ .../api/cache/redis/impl/RedisSetSpec.scala | 182 +++-- .../cache/redis/impl/RedisSortedSetSpec.scala | 190 +++--- .../api/cache/redis/impl/SyncRedisSpec.scala | 107 ++- .../play/api/cache/redis/test/BaseSpec.scala | 165 +++++ .../cache/redis/test/FakeApplication.scala | 19 + .../{ => test}/ForAllTestContainer.scala | 8 +- .../play/api/cache/redis/test/Helpers.scala | 24 + .../api/cache/redis/test/OrElseProbe.scala | 39 ++ .../redis/test/RedisClusterContainer.scala | 39 ++ .../api/cache/redis/test/RedisContainer.scala | 24 + .../{ => test}/RedisContainerConfig.scala | 5 +- .../redis/{logging => test}/RedisLogger.scala | 6 +- .../redis/test/RedisSentinelContainer.scala | 41 ++ .../RedisSettingsTest.scala | 6 +- .../redis/test/RedisStandaloneContainer.scala | 16 + .../cache/redis/test/SimulatedException.scala | 11 + .../redis/test/StoppableApplication.scala | 45 ++ 75 files changed, 5129 insertions(+), 3315 deletions(-) delete mode 100644 src/test/scala-2.13/play/api/cache/redis/connector/ScalaSpecificSerializerSpec.scala delete mode 100644 src/test/scala/play/api/cache/redis/ClusterRedisContainer.scala delete mode 100644 src/test/scala/play/api/cache/redis/Implicits.scala delete mode 100644 src/test/scala/play/api/cache/redis/RedisContainer.scala delete mode 100644 src/test/scala/play/api/cache/redis/Shutdown.scala delete mode 100644 src/test/scala/play/api/cache/redis/StandaloneRedisContainer.scala delete mode 100644 src/test/scala/play/api/cache/redis/configuration/RedisInstanceManagerTest.scala delete mode 100644 src/test/scala/play/api/cache/redis/connector/MockedConnector.scala delete mode 100644 src/test/scala/play/api/cache/redis/connector/RedisConnectorSpec.scala create mode 100644 src/test/scala/play/api/cache/redis/connector/RedisStandaloneSpec.scala delete mode 100644 src/test/scala/play/api/cache/redis/connector/SerializerImplicits.scala delete mode 100644 src/test/scala/play/api/cache/redis/connector/TestCase.scala create mode 100644 src/test/scala/play/api/cache/redis/impl/AsyncRedisMock.scala delete mode 100644 src/test/scala/play/api/cache/redis/impl/RedisCacheImplicits.scala create mode 100644 src/test/scala/play/api/cache/redis/impl/RedisConnectorMock.scala create mode 100644 src/test/scala/play/api/cache/redis/impl/RedisListJavaMock.scala create mode 100644 src/test/scala/play/api/cache/redis/impl/RedisMapJavaMock.scala create mode 100644 src/test/scala/play/api/cache/redis/impl/RedisRuntimeMock.scala create mode 100644 src/test/scala/play/api/cache/redis/impl/RedisSetJavaMock.scala create mode 100644 src/test/scala/play/api/cache/redis/test/BaseSpec.scala create mode 100644 src/test/scala/play/api/cache/redis/test/FakeApplication.scala rename src/test/scala/play/api/cache/redis/{ => test}/ForAllTestContainer.scala (54%) create mode 100644 src/test/scala/play/api/cache/redis/test/Helpers.scala create mode 100644 src/test/scala/play/api/cache/redis/test/OrElseProbe.scala create mode 100644 src/test/scala/play/api/cache/redis/test/RedisClusterContainer.scala create mode 100644 src/test/scala/play/api/cache/redis/test/RedisContainer.scala rename src/test/scala/play/api/cache/redis/{ => test}/RedisContainerConfig.scala (54%) rename src/test/scala/play/api/cache/redis/{logging => test}/RedisLogger.scala (73%) create mode 100644 src/test/scala/play/api/cache/redis/test/RedisSentinelContainer.scala rename src/test/scala/play/api/cache/redis/{configuration => test}/RedisSettingsTest.scala (60%) create mode 100644 src/test/scala/play/api/cache/redis/test/RedisStandaloneContainer.scala create mode 100644 src/test/scala/play/api/cache/redis/test/SimulatedException.scala create mode 100644 src/test/scala/play/api/cache/redis/test/StoppableApplication.scala diff --git a/CHANGELOG.md b/CHANGELOG.md index 16312284..a34f7a0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ Updated to Play `2.9.0` and dropped `Scala 2.12` since it was discontinued from the Play framework. Minimal supported Java version is now `11`. [#265](https://github.com/KarelCemus/play-redis/pull/265) +Migrated from test framework `specs2` to `scalatest` because specs2 is not friendly to +cross-compilation between Scala 2.13 and Scala 3. Test are uses `testcontainers` to run +standalone, cluster or sentinel instance. However, current redis connector is not friendly +and there are several significant limitations in the implementation, therefore the tests +for cluster are using fixed port mapping and tests for sentinel are disabled since it seems +that sentinel implementation is not fully reliable, therefore sentinel is not officially +supported at this moment. [#273](https://github.com/KarelCemus/play-redis/pull/273) + ### [:link: 2.7.0](https://github.com/KarelCemus/play-redis/tree/2.7.0) SET command supports milliseconds, previous versions used seconds [#247](https://github.com/KarelCemus/play-redis/issues/247) @@ -30,17 +38,17 @@ Note: This should not be a breaking change since it was not possible to properly the value in Java without encountering the exception. Introduced another source `aws-cluster`, which is a cluster with nodes defined by DNS record. For example, -Amazon AWS uses this kind of cluster definition. Therefore this type of a cluster resolves +Amazon AWS uses this kind of cluster definition. Therefore this type of a cluster resolves a domain main to extract all nodes. See [#221](https://github.com/KarelCemus/play-redis/pull/221) for more details. ### [:link: 2.5.0](https://github.com/KarelCemus/play-redis/tree/2.5.0) -Added `expiresIn(key: String): Option[Duration]` implementing PTTL +Added `expiresIn(key: String): Option[Duration]` implementing PTTL command to get expiration of the given key. [#204](https://github.com/KarelCemus/play-redis/pull/204) Introduced asynchronous implementation of advanced Java API for redis cache. The API wraps the Scala version thus provides slightly worse performance and deals with -the lack of `classTag` in Play's API design. **This API implementation is experimental +the lack of `classTag` in Play's API design. **This API implementation is experimental and may change in future.** Feedback will be welcome. [#206](https://github.com/KarelCemus/play-redis/issues/206) Added `getFields(fields: String*)` and `getFields(fields: Iterable[String])` into `RedisMap` API @@ -55,7 +63,7 @@ Cross-compiled with Scala 2.13 [#211](https://github.com/KarelCemus/play-redis/i Update to Play `2.7.0` [#202](https://github.com/KarelCemus/play-redis/pull/202) -Added `getAll[T: ClassTag](keys: Iterable[String]): Result[Seq[Option[T]]]` into `AbstractCacheApi` +Added `getAll[T: ClassTag](keys: Iterable[String]): Result[Seq[Option[T]]]` into `AbstractCacheApi` in order to also accept collections aside vararg. [#194](https://github.com/KarelCemus/play-redis/pull/194) Fixed `getOrElse` method in Synchronous API with non-empty cache prefix. [#196](https://github.com/KarelCemus/play-redis/pull/196) @@ -71,7 +79,7 @@ Returned keys are automatically unprefixed. [#184](https://github.com/KarelCemus Support of plain arrays in JavaRedis [#176](https://github.com/KarelCemus/play-redis/pull/176). -Connection timeout introduced in [#147](https://github.com/KarelCemus/play-redis/issues/147) +Connection timeout introduced in [#147](https://github.com/KarelCemus/play-redis/issues/147) is now configurable and can be disabled [#174](https://github.com/KarelCemus/play-redis/pull/174). Removed deprecations introduced in [2.0.0](https://github.com/KarelCemus/play-redis/tree/2.0.0) diff --git a/build.sbt b/build.sbt index d25a5030..78f6ea88 100644 --- a/build.sbt +++ b/build.sbt @@ -21,11 +21,12 @@ libraryDependencies ++= Seq( // redis connector "io.github.rediscala" %% "rediscala" % "1.14.0-akka", // test framework with mockito extension - "org.specs2" %% "specs2-mock" % "4.20.3" % Test, + "org.scalatest" %% "scalatest" % "3.2.17" % Test, + "org.scalamock" %% "scalamock" % "5.2.0" % Test, // test module for play framework "com.typesafe.play" %% "play-test" % playVersion.value % Test, // to run integration tests - "com.dimafeng" %% "testcontainers-scala-core" % "0.41.0" % Test + "com.dimafeng" %% "testcontainers-scala-core" % "0.41.2" % Test ) resolvers ++= Seq( @@ -40,3 +41,5 @@ enablePlugins(CustomReleasePlugin) // exclude from tests coverage coverageExcludedFiles := ".*exceptions.*" + +Test / test := (Test / testOnly).toTask(" * -- -l \"org.scalatest.Ignore\"").value diff --git a/src/main/scala/play/api/cache/redis/CacheApi.scala b/src/main/scala/play/api/cache/redis/CacheApi.scala index ba303080..267118b7 100644 --- a/src/main/scala/play/api/cache/redis/CacheApi.scala +++ b/src/main/scala/play/api/cache/redis/CacheApi.scala @@ -27,7 +27,7 @@ private[redis] trait AbstractCacheApi[Result[_]] { * @param key cache storage keys * @return stored record, Some if exists, otherwise None */ - def getAll[T: ClassTag](key: String*): Result[Seq[Option[T]]] = getAll(key) + final def getAll[T: ClassTag](key: String*): Result[Seq[Option[T]]] = getAll(key) /** * Retrieve the values of all specified keys from the cache. diff --git a/src/main/scala/play/api/cache/redis/configuration/RedisHost.scala b/src/main/scala/play/api/cache/redis/configuration/RedisHost.scala index dfde44e9..fafbc716 100644 --- a/src/main/scala/play/api/cache/redis/configuration/RedisHost.scala +++ b/src/main/scala/play/api/cache/redis/configuration/RedisHost.scala @@ -46,6 +46,7 @@ object RedisHost extends ConfigLoader[RedisHost] { host = config.getString(path / "host"), port = config.getInt(path / "port"), database = config.getOption(path / "database", _.getInt), + username = config.getOption(path / "username", _.getString), password = config.getOption(path / "password", _.getString) ) diff --git a/src/main/scala/play/api/cache/redis/configuration/RedisInstance.scala b/src/main/scala/play/api/cache/redis/configuration/RedisInstance.scala index 03c2f334..adab2ba5 100644 --- a/src/main/scala/play/api/cache/redis/configuration/RedisInstance.scala +++ b/src/main/scala/play/api/cache/redis/configuration/RedisInstance.scala @@ -71,7 +71,7 @@ trait RedisStandalone extends RedisInstance with RedisHost { object RedisStandalone { - def apply(name: String, host: RedisHost, settings: RedisSettings) = + def apply(name: String, host: RedisHost, settings: RedisSettings): RedisStandalone = create(name, host, settings) @inline diff --git a/src/main/scala/play/api/cache/redis/connector/RedisCommands.scala b/src/main/scala/play/api/cache/redis/connector/RedisCommands.scala index 4c2c0bc5..ffb0fd1d 100644 --- a/src/main/scala/play/api/cache/redis/connector/RedisCommands.scala +++ b/src/main/scala/play/api/cache/redis/connector/RedisCommands.scala @@ -1,13 +1,10 @@ package play.api.cache.redis.connector import javax.inject._ - import scala.concurrent.Future - import play.api.Logger import play.api.cache.redis.configuration._ import play.api.inject.ApplicationLifecycle - import akka.actor.ActorSystem import redis.{RedisClient => RedisStandaloneClient, RedisCluster => RedisClusterClient, _} @@ -17,7 +14,7 @@ import redis.{RedisClient => RedisStandaloneClient, RedisCluster => RedisCluster */ private[connector] class RedisCommandsProvider(instance: RedisInstance)(implicit system: ActorSystem, lifecycle: ApplicationLifecycle) extends Provider[RedisCommands] { - lazy val get = instance match { + lazy val get: RedisCommands = instance match { case cluster: RedisCluster => new RedisCommandsCluster(cluster).get case standalone: RedisStandalone => new RedisCommandsStandalone(standalone).get case sentinel: RedisSentinel => new RedisCommandsSentinel(sentinel).get @@ -115,7 +112,7 @@ private[connector] class RedisCommandsCluster(configuration: RedisCluster)(impli } // $COVERAGE-OFF$ - def start() = { + def start(): Unit = { def servers = nodes.map { case RedisHost(host, port, Some(database), _, _) => s" $host:$port?database=$database" case RedisHost(host, port, None, _, _) => s" $host:$port" @@ -126,7 +123,7 @@ private[connector] class RedisCommandsCluster(configuration: RedisCluster)(impli def stop(): Future[Unit] = Future successful { log.info("Stopping the redis cluster cache actor ...") - client.stop() + Option(client).foreach(_.stop()) log.info("Redis cluster cache stopped.") } // $COVERAGE-ON$ diff --git a/src/main/scala/play/api/cache/redis/connector/RedisConnectorProvider.scala b/src/main/scala/play/api/cache/redis/connector/RedisConnectorProvider.scala index 909a004a..32bc7d60 100644 --- a/src/main/scala/play/api/cache/redis/connector/RedisConnectorProvider.scala +++ b/src/main/scala/play/api/cache/redis/connector/RedisConnectorProvider.scala @@ -12,7 +12,7 @@ import akka.actor.ActorSystem */ private[redis] class RedisConnectorProvider(instance: RedisInstance, serializer: AkkaSerializer)(implicit system: ActorSystem, lifecycle: ApplicationLifecycle, runtime: RedisRuntime) extends Provider[RedisConnector] { - private lazy val commands = new RedisCommandsProvider(instance).get + private[connector] lazy val commands = new RedisCommandsProvider(instance).get lazy val get = new RedisConnectorImpl(serializer, commands) } diff --git a/src/main/scala/play/api/cache/redis/impl/InvocationPolicy.scala b/src/main/scala/play/api/cache/redis/impl/InvocationPolicy.scala index 986f68c7..ec0a74db 100644 --- a/src/main/scala/play/api/cache/redis/impl/InvocationPolicy.scala +++ b/src/main/scala/play/api/cache/redis/impl/InvocationPolicy.scala @@ -15,9 +15,14 @@ sealed trait InvocationPolicy { } object EagerInvocation extends InvocationPolicy { - def invoke[T](f: => Future[Any], thenReturn: T)(implicit context: ExecutionContext) = { f; Future successful thenReturn } + override def invoke[T](f: => Future[Any], thenReturn: T)(implicit context: ExecutionContext): Future[T] = { + f + Future successful thenReturn + } } object LazyInvocation extends InvocationPolicy { - def invoke[T](f: => Future[Any], thenReturn: T)(implicit context: ExecutionContext) = f.map(_ => thenReturn) + override def invoke[T](f: => Future[Any], thenReturn: T)(implicit context: ExecutionContext): Future[T] = { + f.map(_ => thenReturn) + } } diff --git a/src/main/scala/play/api/cache/redis/impl/RedisCache.scala b/src/main/scala/play/api/cache/redis/impl/RedisCache.scala index 85bf43b9..7f4a5e35 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisCache.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisCache.scala @@ -66,9 +66,8 @@ private[impl] class RedisCache[Result[_]](redis: RedisConnector, builder: Builde doMatching(pattern).recoverWithDefault(Seq.empty[String]) } - def getOrElse[T: ClassTag](key: String, expiration: Duration)(orElse: => T) = key.prefixed { key => + def getOrElse[T: ClassTag](key: String, expiration: Duration)(orElse: => T) = getOrFuture(key, expiration)(orElse.toFuture).recoverWithDefault(orElse) - } def getOrFuture[T: ClassTag](key: String, expiration: Duration)(orElse: => Future[T]): Future[T] = key.prefixed { key => redis.get[T](key).flatMap { diff --git a/src/main/scala/play/api/cache/redis/impl/RedisListJavaImpl.scala b/src/main/scala/play/api/cache/redis/impl/RedisListJavaImpl.scala index f4a9b68a..fee9850b 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisListJavaImpl.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisListJavaImpl.scala @@ -10,6 +10,17 @@ class RedisListJavaImpl[Elem](internal: RedisList[Elem, Future])(implicit runtim def This = this + private lazy val modifier: AsyncRedisList.AsyncRedisListModification[Elem] = newModifier() + private lazy val viewer: AsyncRedisList.AsyncRedisListView[Elem] = newViewer() + + protected def newViewer(): AsyncRedisList.AsyncRedisListView[Elem] = { + new AsyncRedisListViewJavaImpl(internal.view) + } + + protected def newModifier(): AsyncRedisList.AsyncRedisListModification[Elem] = { + new AsyncRedisListModificationJavaImpl(internal.modify) + } + def prepend(element: Elem): CompletionStage[AsyncRedisList[Elem]] = { async { implicit context => internal.prepend(element).map(_ => this) @@ -70,13 +81,9 @@ class RedisListJavaImpl[Elem](internal: RedisList[Elem, Future])(implicit runtim } } - def view(): AsyncRedisList.AsyncRedisListView[Elem] = { - new AsyncRedisListViewJavaImpl(internal.view) - } + def view(): AsyncRedisList.AsyncRedisListView[Elem] = viewer - def modify(): AsyncRedisList.AsyncRedisListModification[Elem] = { - new AsyncRedisListModificationJavaImpl(internal.modify) - } + def modify(): AsyncRedisList.AsyncRedisListModification[Elem] = modifier private class AsyncRedisListViewJavaImpl(view: internal.RedisListView) extends AsyncRedisList.AsyncRedisListView[Elem] { diff --git a/src/test/resources/reference.conf b/src/test/resources/reference.conf index 37f29bf7..03486f3b 100644 --- a/src/test/resources/reference.conf +++ b/src/test/resources/reference.conf @@ -18,5 +18,5 @@ akka { warn-about-java-serializer-usage = off } - loggers = ["play.api.cache.redis.logging.RedisLogger"] + loggers = ["play.api.cache.redis.test.RedisLogger"] } diff --git a/src/test/scala-2.13/play/api/cache/redis/connector/ScalaSpecificSerializerSpec.scala b/src/test/scala-2.13/play/api/cache/redis/connector/ScalaSpecificSerializerSpec.scala deleted file mode 100644 index 60d4d209..00000000 --- a/src/test/scala-2.13/play/api/cache/redis/connector/ScalaSpecificSerializerSpec.scala +++ /dev/null @@ -1,64 +0,0 @@ -package play.api.cache.redis.connector - -import play.api.inject.guice.GuiceApplicationBuilder - -import org.specs2.mock.Mockito -import org.specs2.mutable.Specification - -class ScalaSpecificSerializerSpec extends Specification with Mockito { - import SerializerImplicits._ - - private val system = GuiceApplicationBuilder().build().actorSystem - - private implicit val serializer: AkkaSerializer = new AkkaSerializerImpl(system) - - "AkkaEncoder" should "encode" >> { - - "custom classes" in { - SimpleObject("B", 3).encoded mustEqual - """ - |rO0ABXNyAD9wbGF5LmFwaS5jYWNoZS5yZWRpcy5jb25uZWN0b3IuU2VyaWFsaXplckltcGxpY2l0 - |cyRTaW1wbGVPYmplY3TyYCEG2fNkUQIAAkkABXZhbHVlTAADa2V5dAASTGphdmEvbGFuZy9TdHJp - |bmc7eHAAAAADdAABQg== - """.stripMargin.removeAllWhitespaces - } - - "list" in { - List("A", "B", "C").encoded mustEqual - """ - |rO0ABXNyADJzY2FsYS5jb2xsZWN0aW9uLmdlbmVyaWMuRGVmYXVsdFNlcmlhbGl6YXRpb25Qcm94e - |QAAAAAAAAADAwABTAAHZmFjdG9yeXQAGkxzY2FsYS9jb2xsZWN0aW9uL0ZhY3Rvcnk7eHBzcgAqc2 - |NhbGEuY29sbGVjdGlvbi5JdGVyYWJsZUZhY3RvcnkkVG9GYWN0b3J5AAAAAAAAAAMCAAFMAAdmYWN - |0b3J5dAAiTHNjYWxhL2NvbGxlY3Rpb24vSXRlcmFibGVGYWN0b3J5O3hwc3IAJnNjYWxhLnJ1bnRp - |bWUuTW9kdWxlU2VyaWFsaXphdGlvblByb3h5AAAAAAAAAAECAAFMAAttb2R1bGVDbGFzc3QAEUxqY - |XZhL2xhbmcvQ2xhc3M7eHB2cgAgc2NhbGEuY29sbGVjdGlvbi5pbW11dGFibGUuTGlzdCQAAAAAAA - |AAAwIAAHhwdwT/////dAABQXQAAUJ0AAFDc3EAfgAGdnIAJnNjYWxhLmNvbGxlY3Rpb24uZ2VuZXJ - |pYy5TZXJpYWxpemVFbmQkAAAAAAAAAAMCAAB4cHg= - """.stripMargin.removeAllWhitespaces - } - } - - "AkkaDecoder" should "decode" >> { - - "custom classes" in { - """ - |rO0ABXNyAD9wbGF5LmFwaS5jYWNoZS5yZWRpcy5jb25uZWN0b3IuU2VyaWFsaXplckltcGxpY2l0 - |cyRTaW1wbGVPYmplY3TyYCEG2fNkUQIAAkkABXZhbHVlTAADa2V5dAASTGphdmEvbGFuZy9TdHJp - |bmc7eHAAAAADdAABQg== - """.stripMargin.removeAllWhitespaces.decoded[SimpleObject] mustEqual SimpleObject("B", 3) - } - - "list" in { - """ - |rO0ABXNyADJzY2FsYS5jb2xsZWN0aW9uLmdlbmVyaWMuRGVmYXVsdFNlcmlhbGl6YXRpb25Qcm94e - |QAAAAAAAAADAwABTAAHZmFjdG9yeXQAGkxzY2FsYS9jb2xsZWN0aW9uL0ZhY3Rvcnk7eHBzcgAqc2 - |NhbGEuY29sbGVjdGlvbi5JdGVyYWJsZUZhY3RvcnkkVG9GYWN0b3J5AAAAAAAAAAMCAAFMAAdmYWN - |0b3J5dAAiTHNjYWxhL2NvbGxlY3Rpb24vSXRlcmFibGVGYWN0b3J5O3hwc3IAJnNjYWxhLnJ1bnRp - |bWUuTW9kdWxlU2VyaWFsaXphdGlvblByb3h5AAAAAAAAAAECAAFMAAttb2R1bGVDbGFzc3QAEUxqY - |XZhL2xhbmcvQ2xhc3M7eHB2cgAgc2NhbGEuY29sbGVjdGlvbi5pbW11dGFibGUuTGlzdCQAAAAAAA - |AAAwIAAHhwdwT/////dAABQXQAAUJ0AAFDc3EAfgAGdnIAJnNjYWxhLmNvbGxlY3Rpb24uZ2VuZXJ - |pYy5TZXJpYWxpemVFbmQkAAAAAAAAAAMCAAB4cHg= - """.stripMargin.removeAllWhitespaces.decoded[List[String]] mustEqual List("A", "B", "C") - } - } -} diff --git a/src/test/scala/play/api/cache/redis/ClusterRedisContainer.scala b/src/test/scala/play/api/cache/redis/ClusterRedisContainer.scala deleted file mode 100644 index da359612..00000000 --- a/src/test/scala/play/api/cache/redis/ClusterRedisContainer.scala +++ /dev/null @@ -1,20 +0,0 @@ -package play.api.cache.redis - -trait ClusterRedisContainer extends RedisContainer { - - protected def redisMaster = 4 - - protected def redisSlaves = 1 - - override protected lazy val redisConfig: RedisContainerConfig = - RedisContainerConfig( - "grokzen/redis-cluster:latest", - 0.until(redisMaster * (redisSlaves + 1)).map(7000 + _), - Map( - "IP" -> "0.0.0.0", - "INITIAL_PORT" -> "7000", - "MASTERS" -> s"$redisMaster", - "SLAVES_PER_MASTER" -> s"$redisSlaves", - ), - ) -} diff --git a/src/test/scala/play/api/cache/redis/ExpirationSpec.scala b/src/test/scala/play/api/cache/redis/ExpirationSpec.scala index 8f160d79..51a24a6b 100644 --- a/src/test/scala/play/api/cache/redis/ExpirationSpec.scala +++ b/src/test/scala/play/api/cache/redis/ExpirationSpec.scala @@ -1,6 +1,6 @@ package play.api.cache.redis -import org.specs2.mutable.Specification +import play.api.cache.redis.test._ import java.time.Instant import java.util.Date @@ -9,7 +9,7 @@ import scala.concurrent.duration._ /** *

This specification tests expiration conversion

*/ -class ExpirationSpec extends Specification { +class ExpirationSpec extends UnitSpec { "Expiration" should { @@ -20,12 +20,16 @@ class ExpirationSpec extends Specification { val expirationTo = expiration + 1.second "from java.util.Date" in { - new Date(expireAt.toEpochMilli).asExpiration must beBetween(expirationFrom, expirationTo) + val expiration = new Date(expireAt.toEpochMilli).asExpiration + expiration mustBe >=(expirationFrom) + expiration mustBe <=(expirationTo) } "from java.time.LocalDateTime" in { import java.time._ - LocalDateTime.ofInstant(expireAt, ZoneId.systemDefault()).asExpiration must beBetween(expirationFrom, expirationTo) + val expiration = LocalDateTime.ofInstant(expireAt, ZoneId.systemDefault()).asExpiration + expiration mustBe >=(expirationFrom) + expiration mustBe <=(expirationTo) } } } diff --git a/src/test/scala/play/api/cache/redis/Implicits.scala b/src/test/scala/play/api/cache/redis/Implicits.scala deleted file mode 100644 index abd74bf5..00000000 --- a/src/test/scala/play/api/cache/redis/Implicits.scala +++ /dev/null @@ -1,118 +0,0 @@ -package play.api.cache.redis - -import java.util.concurrent.Callable - -import scala.collection.mutable -import scala.concurrent._ -import scala.concurrent.duration._ -import scala.language.implicitConversions -import scala.util._ - -import play.api.cache.redis.configuration._ - -import akka.actor.ActorSystem - -import org.specs2.execute.{AsResult, Result} -import org.specs2.matcher.Expectations -import org.specs2.mock.mockito._ -import org.specs2.specification.{Around, Scope} - -object Implicits { - - val defaultCacheName = "play" - val localhost = "localhost" - val localhostIp = "127.0.0.1" - val dockerIp = localhostIp - val defaultPort = 6379 - - val defaults = RedisSettingsTest("akka.actor.default-dispatcher", "lazy", RedisTimeouts(1.second, None, 500.millis), "log-and-default", "standalone") - - val defaultInstance = RedisStandalone(defaultCacheName, RedisHost(localhost, defaultPort), defaults) - - implicit def implicitlyImmutableSeq[T](value: mutable.ListBuffer[T]): Seq[T] = value.toSeq - - implicit def implicitlyAny2Some[T](value: T): Option[T] = Some(value) - - implicit def implicitlyAny2future[T](value: T): Future[T] = Future.successful(value) - - implicit def implicitlyEx2future(ex: Throwable): Future[Nothing] = Future.failed(ex) - - implicit def implicitlyAny2success[T](value: T): Try[T] = Success(value) - - implicit def implicitlyAny2failure(ex: Throwable): Try[Nothing] = Failure(ex) - - implicit class FutureAwait[T](val future: Future[T]) extends AnyVal { - def awaitForFuture = Await.result(future, 2.minutes) - } - - implicit def implicitlyAny2Callable[T](f: => T): Callable[T] = new Callable[T] { - def call() = f - } - - implicit class RichFutureObject(val future: Future.type) extends AnyVal { - /** returns a future resolved in given number of seconds */ - def after[T](seconds: Int, value: => T)(implicit system: ActorSystem, ec: ExecutionContext): Future[T] = { - val promise = Promise[T]() - // after a timeout, resolve the promise - akka.pattern.after(seconds.seconds, system.scheduler) { - promise.success(value) - promise.future - } - // return the promise - promise.future - } - - def after(seconds: Int)(implicit system: ActorSystem, ec: ExecutionContext): Future[Unit] = { - after(seconds, ()) - } - } -} - -trait ReducedMockito extends MocksCreation - with CalledMatchers - with MockitoStubs - with CapturedArgument - // with MockitoMatchers - // with ArgThat - with Expectations - with MockitoFunctions { - - override def argThat[T, U <: T](m: org.specs2.matcher.Matcher[U]): T = super.argThat(m) -} - -object MockitoImplicits extends ReducedMockito - -trait WithApplication { - import play.api.Application - import play.api.inject.guice.GuiceApplicationBuilder - - protected def builder = new GuiceApplicationBuilder() - - private lazy val theBuilder = builder - - protected lazy val injector = theBuilder.injector() - - protected lazy val application: Application = injector.instanceOf[Application] - - implicit protected lazy val system: ActorSystem = injector.instanceOf[ActorSystem] -} - -trait WithHocon { - import play.api.Configuration - - import com.typesafe.config.ConfigFactory - - protected def hocon: String - - protected lazy val config = { - val reference = ConfigFactory.load() - val local = ConfigFactory.parseString(hocon.stripMargin) - local.withFallback(reference) - } - - protected lazy val configuration = Configuration(config) -} - -abstract class WithConfiguration(val hocon: String) extends WithHocon with Around with Scope { - def around[T: AsResult](t: => T): Result = AsResult.effectively(t) -} diff --git a/src/test/scala/play/api/cache/redis/RecoveryPolicySpec.scala b/src/test/scala/play/api/cache/redis/RecoveryPolicySpec.scala index 172f089a..907e4eb0 100644 --- a/src/test/scala/play/api/cache/redis/RecoveryPolicySpec.scala +++ b/src/test/scala/play/api/cache/redis/RecoveryPolicySpec.scala @@ -1,37 +1,31 @@ package play.api.cache.redis -import scala.concurrent.Future - import play.api.Logger +import play.api.cache.redis.test._ -import org.specs2.concurrent.ExecutionEnv -import org.specs2.mutable.Specification +import scala.concurrent.Future -class RecoveryPolicySpec(implicit ee: ExecutionEnv) extends Specification with ReducedMockito { +class RecoveryPolicySpec extends AsyncUnitSpec { - class BasicPolicy extends RecoveryPolicy { - def recoverFrom[T](rerun: => Future[T], default: => Future[T], failure: RedisException) = default - } + private val rerun = Future.successful(10) + private val default = Future.successful(0) - val rerun = Future.successful(10) - val default = Future.successful(0) - - object ex { - private val internal = new IllegalArgumentException("Internal cause") - val unexpectedAny = UnexpectedResponseException(None, "TEST-CMD") - val unexpectedKey = UnexpectedResponseException(Some("some key"), "TEST-CMD") - val failedAny = ExecutionFailedException(None, "TEST-CMD", "TEST-CMD", internal) - val failedKey = ExecutionFailedException(Some("key"), "TEST-CMD", "TEST-CMD key value", internal) - val timeout = TimeoutException(internal) - val serialization = SerializationException("some key", "TEST-CMD", internal) + private object ex { + private val internal = new IllegalArgumentException("Simulated Internal cause") + val unexpectedAny: UnexpectedResponseException = UnexpectedResponseException(None, "TEST-CMD") + val unexpectedKey: UnexpectedResponseException = UnexpectedResponseException(Some("some key"), "TEST-CMD") + val failedAny: ExecutionFailedException = ExecutionFailedException(None, "TEST-CMD", "TEST-CMD", internal) + val failedKey: ExecutionFailedException = ExecutionFailedException(Some("key"), "TEST-CMD", "TEST-CMD key value", internal) + val timeout: TimeoutException = TimeoutException(internal) + val serialization: SerializationException = SerializationException("some key", "TEST-CMD", internal) def any: UnexpectedResponseException = unexpectedAny } "Recovery Policy" should { "log detailed report" in { - val policy = new BasicPolicy with DetailedReports { - override val log = mock[Logger] + val policy = new RecoverWithDefault with DetailedReports { + override val log: Logger = Logger(getClass) } // note: there should be tested a logger and the message @@ -44,8 +38,8 @@ class RecoveryPolicySpec(implicit ee: ExecutionEnv) extends Specification with R } "log condensed report" in { - val policy = new BasicPolicy with CondensedReports { - override val log = mock[Logger] + val policy = new RecoverWithDefault with CondensedReports { + override val log: Logger = Logger(getClass) } // note: there should be tested a logger and the message @@ -58,14 +52,12 @@ class RecoveryPolicySpec(implicit ee: ExecutionEnv) extends Specification with R } "fail through" in { - val policy = new BasicPolicy with FailThrough - - policy.recoverFrom(rerun, default, ex.any) must throwA(ex.any).await + val policy = new FailThrough {} + policy.recoverFrom(rerun, default, ex.any).assertingFailure(ex.any) } "recover with default" in { - val policy = new BasicPolicy with RecoverWithDefault - + val policy = new RecoverWithDefault {} policy.recoverFrom(rerun, default, ex.any) mustEqual default } } diff --git a/src/test/scala/play/api/cache/redis/RedisCacheComponentsSpec.scala b/src/test/scala/play/api/cache/redis/RedisCacheComponentsSpec.scala index f8ca9019..14db7b83 100644 --- a/src/test/scala/play/api/cache/redis/RedisCacheComponentsSpec.scala +++ b/src/test/scala/play/api/cache/redis/RedisCacheComponentsSpec.scala @@ -1,49 +1,48 @@ package play.api.cache.redis +import akka.actor.ActorSystem import play.api._ +import play.api.cache.redis.test._ import play.api.inject.ApplicationLifecycle -import org.specs2.mutable.Specification - -class RedisCacheComponentsSpec extends Specification with WithApplication with StandaloneRedisContainer { - import Implicits._ - - object components extends RedisCacheComponents with WithHocon { - def actorSystem = system - def applicationLifecycle = injector.instanceOf[ApplicationLifecycle] - def environment = injector.instanceOf[Environment] - lazy val syncRedis = cacheApi("play").sync - override lazy val configuration = Configuration(config) - override protected def hocon: String = s"play.cache.redis.port: ${container.mappedPort(defaultPort)}" - } - private type Cache = CacheApi +class RedisCacheComponentsSpec extends IntegrationSpec with RedisStandaloneContainer { - private lazy val cache = components.syncRedis + private val prefix = "components-sync" - val prefix = "components-sync" + test("miss on get") { cache => + cache.get[String](s"$prefix-test-1") mustEqual None + } - "RedisComponents" should { + test(" hit after set") { cache => + cache.set(s"$prefix-test-2", "value") + cache.get[String](s"$prefix-test-2") mustEqual Some("value") + } - "provide api" >> { - "miss on get" in { - cache.get[String](s"$prefix-test-1") must beNone - } + test("return positive exists on existing keys") { cache => + cache.set(s"$prefix-test-11", "value") + cache.exists(s"$prefix-test-11") mustEqual true + } - "hit after set" in { - cache.set(s"$prefix-test-2", "value") - cache.get[String](s"$prefix-test-2") must beSome[Any] - cache.get[String](s"$prefix-test-2") must beSome("value") + private def test(name: String)(cache: CacheApi => Assertion): Unit = { + s"should $name" in { + + val components: TestComponents = new TestComponents { + override lazy val configuration: Configuration = Helpers.configuration.fromHocon( + s"play.cache.redis.port: ${container.mappedPort(defaultPort)}" + ) + override lazy val actorSystem: ActorSystem = system + override lazy val applicationLifecycle: ApplicationLifecycle = injector.instanceOf[ApplicationLifecycle] + override lazy val environment: Environment = injector.instanceOf[Environment] + override lazy val syncRedis: CacheApi = cacheApi("play").sync } - "positive exists on existing keys" in { - cache.set(s"$prefix-test-11", "value") - cache.exists(s"$prefix-test-11") must beTrue + components.runInApplication { + cache(components.syncRedis) } } } - override def afterAll() = { - Shutdown.run.awaitForFuture - super.afterAll() + private trait TestComponents extends RedisCacheComponents with FakeApplication { + def syncRedis: CacheApi } } diff --git a/src/test/scala/play/api/cache/redis/RedisCacheModuleSpec.scala b/src/test/scala/play/api/cache/redis/RedisCacheModuleSpec.scala index 8735aa78..a2dca40f 100644 --- a/src/test/scala/play/api/cache/redis/RedisCacheModuleSpec.scala +++ b/src/test/scala/play/api/cache/redis/RedisCacheModuleSpec.scala @@ -1,179 +1,175 @@ package play.api.cache.redis -import javax.inject.Provider +import akka.actor.ActorSystem +import play.api.cache.redis.configuration.{RedisStandalone, RedisTimeouts} +import play.api.cache.redis.test._ +import play.api.inject._ +import play.api.inject.guice.GuiceApplicationBuilder +import play.cache.NamedCacheImpl -import scala.concurrent.duration._ +import scala.concurrent.duration.DurationInt import scala.reflect.ClassTag -import play.api.inject._ +class RedisCacheModuleSpec extends IntegrationSpec with RedisStandaloneContainer { +import Helpers._ + + private final val defaultCacheName: String = "play" + + test("bind defaults") { + _.bindings(new RedisCacheModule).configure(s"play.cache.redis.port" -> container.mappedPort(defaultPort)) + } { injector => + injector.checkBinding[RedisConnector] + injector.checkBinding[CacheApi] + injector.checkBinding[CacheAsyncApi] + injector.checkBinding[play.cache.AsyncCacheApi] + injector.checkBinding[play.cache.SyncCacheApi] + injector.checkBinding[play.cache.redis.AsyncCacheApi] + injector.checkBinding[play.api.cache.AsyncCacheApi] + injector.checkBinding[play.api.cache.SyncCacheApi] + } -import akka.actor.ActorSystem -import org.specs2.execute.{AsResult, Result} -import org.specs2.mutable._ -import org.specs2.specification.Scope - -/** - *

This specification tests expiration conversion

- */ -class RedisCacheModuleSpec extends Specification { - - import Implicits._ - import RedisCacheModuleSpec._ - - "RedisCacheModule" should { - - "bind defaults" in new WithApplication with Scope with Around { - override protected def builder = super.builder.bindings(new RedisCacheModule) - def around[T: AsResult](t: => T): Result = runAndStop(t) - - injector.instanceOf[RedisConnector] must beAnInstanceOf[RedisConnector] - injector.instanceOf[CacheApi] must beAnInstanceOf[CacheApi] - injector.instanceOf[CacheAsyncApi] must beAnInstanceOf[CacheAsyncApi] - injector.instanceOf[play.cache.AsyncCacheApi] must beAnInstanceOf[play.cache.AsyncCacheApi] - injector.instanceOf[play.cache.SyncCacheApi] must beAnInstanceOf[play.cache.SyncCacheApi] - injector.instanceOf[play.cache.redis.AsyncCacheApi] must beAnInstanceOf[play.cache.redis.AsyncCacheApi] - injector.instanceOf[play.api.cache.AsyncCacheApi] must beAnInstanceOf[play.api.cache.AsyncCacheApi] - injector.instanceOf[play.api.cache.SyncCacheApi] must beAnInstanceOf[play.api.cache.SyncCacheApi] + test("not bind defaults") { + _.bindings(new RedisCacheModule) + .configure("play.cache.redis.bind-default" -> false) + .configure(s"play.cache.redis.port" -> container.mappedPort(defaultPort)) + } { injector => + // bind named caches + injector.checkNamedBinding[CacheApi] + injector.checkNamedBinding[CacheAsyncApi] + + // but do not bind defaults + assertThrows[com.google.inject.ConfigurationException] { + injector.instanceOf[CacheApi] } - - "not bind defaults" in new WithHocon with WithApplication with Scope with Around { - override protected def builder = super.builder.bindings(new RedisCacheModule).configure(configuration) - def around[T: AsResult](t: => T): Result = runAndStop(t) - protected def hocon = "play.cache.redis.bind-default: false" - - // bind named caches - injector.instanceOf(binding[CacheApi].namedCache(defaultCacheName)) must beAnInstanceOf[CacheApi] - injector.instanceOf(binding[CacheAsyncApi].namedCache(defaultCacheName)) must beAnInstanceOf[CacheAsyncApi] - - // but do not bind defaults - injector.instanceOf[CacheApi] must throwA[com.google.inject.ConfigurationException] - injector.instanceOf[CacheAsyncApi] must throwA[com.google.inject.ConfigurationException] + assertThrows[com.google.inject.ConfigurationException] { + injector.instanceOf[CacheAsyncApi] } + } - "bind named cache in simple mode" in new WithApplication with Scope with Around { - override protected def builder = super.builder.bindings(new RedisCacheModule) - def around[T: AsResult](t: => T): Result = runAndStop(t) - def checkBinding[T <: AnyRef: ClassTag] = { - injector.instanceOf(binding[T].namedCache(defaultCacheName)) must beAnInstanceOf[T] - } - - checkBinding[RedisConnector] - checkBinding[CacheApi] - checkBinding[CacheAsyncApi] - checkBinding[play.cache.AsyncCacheApi] - checkBinding[play.cache.SyncCacheApi] - checkBinding[play.api.cache.AsyncCacheApi] - checkBinding[play.api.cache.SyncCacheApi] - } + test("bind named cache in simple mode") { + _.bindings(new RedisCacheModule) + } { injector => + injector.checkNamedBinding[RedisConnector] + injector.checkNamedBinding[CacheApi] + injector.checkNamedBinding[CacheAsyncApi] + injector.checkNamedBinding[play.cache.AsyncCacheApi] + injector.checkNamedBinding[play.cache.SyncCacheApi] + injector.checkNamedBinding[play.api.cache.AsyncCacheApi] + injector.checkNamedBinding[play.api.cache.SyncCacheApi] + } - "bind named caches" in new WithHocon with WithApplication with Scope with Around { - override protected def builder = super.builder.bindings(new RedisCacheModule).configure(configuration) - def around[T: AsResult](t: => T): Result = runAndStop(t) - protected def hocon = - """ + test("bind named caches") { + _.bindings(new RedisCacheModule).configure( + configuration.fromHocon( + s""" |play.cache.redis { | instances { - | | play { - | host: localhost - | port: 6379 + | host: ${container.host} + | port: ${container.mappedPort(defaultPort)} | database: 1 | } - | | other { - | host: redis.localhost.cz - | port: 6378 + | host: ${container.host} + | port: ${container.mappedPort(defaultPort)} | database: 2 | password: something | } | } - | | default-cache: other |} """.stripMargin - val other = "other" - - // something is bound to the default cache name - injector.instanceOf(binding[RedisConnector].namedCache(defaultCacheName)) must beAnInstanceOf[RedisConnector] - injector.instanceOf(binding[CacheApi].namedCache(defaultCacheName)) must beAnInstanceOf[CacheApi] - injector.instanceOf(binding[CacheAsyncApi].namedCache(defaultCacheName)) must beAnInstanceOf[CacheAsyncApi] - injector.instanceOf(binding[play.cache.AsyncCacheApi].namedCache(defaultCacheName)) must beAnInstanceOf[play.cache.AsyncCacheApi] - injector.instanceOf(binding[play.cache.SyncCacheApi].namedCache(defaultCacheName)) must beAnInstanceOf[play.cache.SyncCacheApi] - injector.instanceOf(binding[play.api.cache.AsyncCacheApi].namedCache(defaultCacheName)) must beAnInstanceOf[play.api.cache.AsyncCacheApi] - injector.instanceOf(binding[play.api.cache.SyncCacheApi].namedCache(defaultCacheName)) must beAnInstanceOf[play.api.cache.SyncCacheApi] - - // something is bound to the other cache name - injector.instanceOf(binding[RedisConnector].namedCache(other)) must beAnInstanceOf[RedisConnector] - injector.instanceOf(binding[CacheApi].namedCache(other)) must beAnInstanceOf[CacheApi] - injector.instanceOf(binding[CacheAsyncApi].namedCache(other)) must beAnInstanceOf[CacheAsyncApi] - injector.instanceOf(binding[play.cache.AsyncCacheApi].namedCache(other)) must beAnInstanceOf[play.cache.AsyncCacheApi] - injector.instanceOf(binding[play.cache.SyncCacheApi].namedCache(other)) must beAnInstanceOf[play.cache.SyncCacheApi] - injector.instanceOf(binding[play.api.cache.AsyncCacheApi].namedCache(other)) must beAnInstanceOf[play.api.cache.AsyncCacheApi] - injector.instanceOf(binding[play.api.cache.SyncCacheApi].namedCache(other)) must beAnInstanceOf[play.api.cache.SyncCacheApi] - - // the other cache is a default - injector.instanceOf(binding[RedisConnector].namedCache(other)) mustEqual injector.instanceOf[RedisConnector] - injector.instanceOf(binding[CacheApi].namedCache(other)) mustEqual injector.instanceOf[CacheApi] - injector.instanceOf(binding[CacheAsyncApi].namedCache(other)) mustEqual injector.instanceOf[CacheAsyncApi] - injector.instanceOf(binding[play.cache.AsyncCacheApi].namedCache(other)) mustEqual injector.instanceOf[play.cache.AsyncCacheApi] - injector.instanceOf(binding[play.cache.SyncCacheApi].namedCache(other)) mustEqual injector.instanceOf[play.cache.SyncCacheApi] - injector.instanceOf(binding[play.api.cache.AsyncCacheApi].namedCache(other)) mustEqual injector.instanceOf[play.api.cache.AsyncCacheApi] - injector.instanceOf(binding[play.api.cache.SyncCacheApi].namedCache(other)) mustEqual injector.instanceOf[play.api.cache.SyncCacheApi] - } - - "resolve custom redis instance" in new WithHocon with WithApplication with Scope with Around { - override protected def builder = super.builder.bindings(new RedisCacheModule).configure(configuration).bindings( - binding[RedisInstance].namedCache(defaultCacheName).to(MyRedisInstance) ) - def around[T: AsResult](t: => T): Result = runAndStop(t) - protected def hocon = "play.cache.redis.source: custom" + ) + } { injector => + val other = "other" + + // something is bound to the default cache name + injector.checkNamedBinding[RedisConnector] + injector.checkNamedBinding[CacheApi] + injector.checkNamedBinding[CacheAsyncApi] + injector.checkNamedBinding[play.cache.AsyncCacheApi] + injector.checkNamedBinding[play.cache.SyncCacheApi] + injector.checkNamedBinding[play.api.cache.AsyncCacheApi] + injector.checkNamedBinding[play.api.cache.SyncCacheApi] + + // something is bound to the other cache name + injector.checkNamedBinding[RedisConnector](other) + injector.checkNamedBinding[CacheApi](other) + injector.checkNamedBinding[CacheAsyncApi](other) + injector.checkNamedBinding[play.cache.AsyncCacheApi](other) + injector.checkNamedBinding[play.cache.SyncCacheApi](other) + injector.checkNamedBinding[play.api.cache.AsyncCacheApi](other) + injector.checkNamedBinding[play.api.cache.SyncCacheApi](other) + + // the other cache is a default + injector.instanceOf(binding[RedisConnector].namedCache(other)) mustEqual injector.instanceOf[RedisConnector] + injector.instanceOf(binding[CacheApi].namedCache(other)) mustEqual injector.instanceOf[CacheApi] + injector.instanceOf(binding[CacheAsyncApi].namedCache(other)) mustEqual injector.instanceOf[CacheAsyncApi] + injector.instanceOf(binding[play.cache.AsyncCacheApi].namedCache(other)) mustEqual injector.instanceOf[play.cache.AsyncCacheApi] + injector.instanceOf(binding[play.cache.SyncCacheApi].namedCache(other)) mustEqual injector.instanceOf[play.cache.SyncCacheApi] + injector.instanceOf(binding[play.api.cache.AsyncCacheApi].namedCache(other)) mustEqual injector.instanceOf[play.api.cache.AsyncCacheApi] + injector.instanceOf(binding[play.api.cache.SyncCacheApi].namedCache(other)) mustEqual injector.instanceOf[play.api.cache.SyncCacheApi] + } - // bind named caches - injector.instanceOf[RedisConnector] must beAnInstanceOf[RedisConnector] - injector.instanceOf[CacheApi] must beAnInstanceOf[CacheApi] - injector.instanceOf[CacheAsyncApi] must beAnInstanceOf[CacheAsyncApi] + test("resolve custom redis instance") { + _.bindings(new RedisCacheModule) + .configure("play.cache.redis.source" -> "custom") + .bindings(binding[RedisInstance].namedCache(defaultCacheName).to(MyRedisInstance)) + } { injector => + injector.checkBinding[RedisConnector] + injector.checkBinding[CacheApi] + injector.checkBinding[CacheAsyncApi] + } - // Note: there should be tested which recovery policy instance is actually used - } + private object MyRedisInstance extends RedisStandalone { + override lazy val name: String = defaultCacheName + override lazy val invocationContext: String = "akka.actor.default-dispatcher" + override lazy val invocationPolicy: String = "lazy" + override lazy val timeout: RedisTimeouts = RedisTimeouts(1.second) + override lazy val recovery: String = "log-and-default" + override lazy val source: String = "my-instance" + override lazy val prefix: Option[String] = None + override lazy val host: String = container.host + override lazy val port: Int = container.mappedPort(defaultPort) + override lazy val database: Option[Int] = None + override lazy val username: Option[String] = None + override lazy val password: Option[String] = None } -} -object RedisCacheModuleSpec { - import Implicits._ - import play.api.cache.redis.configuration._ - import play.cache.NamedCacheImpl + private def binding[T: ClassTag]: BindingKey[T] = + BindingKey(implicitly[ClassTag[T]].runtimeClass.asInstanceOf[Class[T]]) - class AnyProvider[T](instance: => T) extends Provider[T] { - lazy val get = instance + private implicit class RichBindingKey[T](private val key: BindingKey[T]) { + def namedCache(name: String): BindingKey[T] = key.qualifiedWith(new NamedCacheImpl(name)) } - def binding[T: ClassTag]: BindingKey[T] = BindingKey(implicitly[ClassTag[T]].runtimeClass.asInstanceOf[Class[T]]) + private implicit class InjectorAssertions(private val injector: Injector) { - implicit class RichBindingKey[T](val key: BindingKey[T]) { - def namedCache(name: String) = key.qualifiedWith(new NamedCacheImpl(name)) - } + def checkBinding[T <: AnyRef : ClassTag]: Assertion = { + injector.instanceOf(binding[T]) mustBe a[T] + } - def runAndStop[T: AsResult](t: => T)(implicit system: ActorSystem) = { - try { - AsResult.effectively(t) - } finally { - Shutdown.run(system) + def checkNamedBinding[T <: AnyRef : ClassTag]: Assertion = { + checkNamedBinding(defaultCacheName) + } + + def checkNamedBinding[T <: AnyRef : ClassTag](name: String): Assertion = { + injector.instanceOf(binding[T].namedCache(name)) mustBe a[T] } } - object MyRedisInstance extends RedisStandalone { - - override def name = defaultCacheName - override def invocationContext = "akka.actor.default-dispatcher" - override def invocationPolicy = "lazy" - override def timeout = RedisTimeouts(1.second) - override def recovery = "log-and-default" - override def source = "my-instance" - override def prefix = None - override def host = localhost - override def port = defaultPort - override def database = None - override def username = None - override def password = None + + private def test(name: String)(createBuilder: GuiceApplicationBuilder => GuiceApplicationBuilder)(f: Injector => Assertion): Unit = { + s"should $name" in { + + val builder = createBuilder(new GuiceApplicationBuilder) + val injector = builder.injector() + val application = StoppableApplication(injector.instanceOf[ActorSystem]) + + application.runInApplication { + f(injector) + } + } } } diff --git a/src/test/scala/play/api/cache/redis/RedisContainer.scala b/src/test/scala/play/api/cache/redis/RedisContainer.scala deleted file mode 100644 index b0682d46..00000000 --- a/src/test/scala/play/api/cache/redis/RedisContainer.scala +++ /dev/null @@ -1,19 +0,0 @@ -package play.api.cache.redis - -import com.dimafeng.testcontainers.GenericContainer -import org.testcontainers.containers.wait.strategy.Wait - -trait RedisContainer extends ForAllTestContainer { - - protected def redisConfig: RedisContainerConfig - - private lazy val config = redisConfig - - override val newContainer = GenericContainer( - dockerImage = config.redisDockerImage, - exposedPorts = config.redisPorts, - env = config.redisEnvironment, - waitStrategy = Wait.forListeningPorts(config.redisPorts: _*), - ) -} - diff --git a/src/test/scala/play/api/cache/redis/Shutdown.scala b/src/test/scala/play/api/cache/redis/Shutdown.scala deleted file mode 100644 index 56fb6a20..00000000 --- a/src/test/scala/play/api/cache/redis/Shutdown.scala +++ /dev/null @@ -1,13 +0,0 @@ -package play.api.cache.redis - -import akka.actor.{ActorSystem, CoordinatedShutdown} - -trait Shutdown extends RedisCacheComponents { - - def shutdown() = Shutdown.run -} - -object Shutdown { - - def run(implicit system: ActorSystem) = CoordinatedShutdown(system).run(CoordinatedShutdown.UnknownReason) -} diff --git a/src/test/scala/play/api/cache/redis/StandaloneRedisContainer.scala b/src/test/scala/play/api/cache/redis/StandaloneRedisContainer.scala deleted file mode 100644 index 505e0276..00000000 --- a/src/test/scala/play/api/cache/redis/StandaloneRedisContainer.scala +++ /dev/null @@ -1,11 +0,0 @@ -package play.api.cache.redis - -trait StandaloneRedisContainer extends RedisContainer { - - override protected lazy val redisConfig: RedisContainerConfig = - RedisContainerConfig( - redisDockerImage = "redis:latest", - redisPorts = Seq(6379), - redisEnvironment = Map.empty - ) -} diff --git a/src/test/scala/play/api/cache/redis/configuration/HostnameResolverSpec.scala b/src/test/scala/play/api/cache/redis/configuration/HostnameResolverSpec.scala index 79ac9649..1360552a 100644 --- a/src/test/scala/play/api/cache/redis/configuration/HostnameResolverSpec.scala +++ b/src/test/scala/play/api/cache/redis/configuration/HostnameResolverSpec.scala @@ -1,8 +1,8 @@ package play.api.cache.redis.configuration -import org.specs2.mutable.Specification +import play.api.cache.redis.test.UnitSpec -class HostnameResolverSpec extends Specification { +class HostnameResolverSpec extends UnitSpec { import HostnameResolver._ "hostname is resolved to IP address" in { diff --git a/src/test/scala/play/api/cache/redis/configuration/RedisHostSpec.scala b/src/test/scala/play/api/cache/redis/configuration/RedisHostSpec.scala index 312f7120..501480d4 100644 --- a/src/test/scala/play/api/cache/redis/configuration/RedisHostSpec.scala +++ b/src/test/scala/play/api/cache/redis/configuration/RedisHostSpec.scala @@ -1,35 +1,53 @@ package play.api.cache.redis.configuration -import play.api.cache.redis._ -import org.specs2.mutable.Specification import play.api.ConfigLoader +import play.api.cache.redis.test._ -class RedisHostSpec extends Specification { - import Implicits._ +import scala.language.implicitConversions + +class RedisHostSpec extends UnitSpec with ImplicitOptionMaterialization { private implicit val loader: ConfigLoader[RedisHost] = RedisHost - "host with database and password" in new WithConfiguration( - """ - |play.cache.redis { - | host: localhost - | port: 6378 - | database: 1 - | password: something - |} - """ - ) { - configuration.get[RedisHost]("play.cache.redis") mustEqual RedisHost("localhost", 6378, database = 1, password = "something") + "host with database, username, and password" in { + val configuration = Helpers.configuration.fromHocon { + """play.cache.redis { + | host: localhost + | port: 6378 + | database: 1 + | username: my-user + | password: something + |} + """.stripMargin + } + configuration.get[RedisHost]("play.cache.redis") mustEqual RedisHost( + host = "localhost", port = 6378, database = 1, username = "my-user", password = "something" + ) + } + + "host with database, password but without a username" in { + val configuration = Helpers.configuration.fromHocon { + """play.cache.redis { + | host: localhost + | port: 6378 + | database: 1 + | password: something + |} + """.stripMargin + } + configuration.get[RedisHost]("play.cache.redis") mustEqual RedisHost( + host = "localhost", port = 6378, database = 1, username = None, password = "something" + ) } - "host without database and password" in new WithConfiguration( - """ - |play.cache.redis { - | host: localhost - | port: 6378 - |} - """ - ) { + "host without database and password" in { + val configuration = Helpers.configuration.fromHocon { + """play.cache.redis { + | host: localhost + | port: 6378 + |} + """.stripMargin + } configuration.get[RedisHost]("play.cache.redis") mustEqual RedisHost("localhost", 6378, database = 0) } @@ -37,6 +55,8 @@ class RedisHostSpec extends Specification { RedisHost.fromConnectionString("redis://redis:something@localhost:6378") mustEqual RedisHost("localhost", 6378, username = "redis", password = "something") RedisHost.fromConnectionString("redis://localhost:6378") mustEqual RedisHost("localhost", 6378) // test invalid string - RedisHost.fromConnectionString("redis:/localhost:6378") must throwA[IllegalArgumentException] + assertThrows[IllegalArgumentException] { + RedisHost.fromConnectionString("redis:/localhost:6378") + } } } diff --git a/src/test/scala/play/api/cache/redis/configuration/RedisInstanceManagerSpec.scala b/src/test/scala/play/api/cache/redis/configuration/RedisInstanceManagerSpec.scala index 5643a84f..35704f3a 100644 --- a/src/test/scala/play/api/cache/redis/configuration/RedisInstanceManagerSpec.scala +++ b/src/test/scala/play/api/cache/redis/configuration/RedisInstanceManagerSpec.scala @@ -1,34 +1,38 @@ package play.api.cache.redis.configuration -import scala.concurrent.duration._ +import play.api.cache.redis.test._ +import play.api.{ConfigLoader, Configuration} -import org.specs2.mutable.Specification +import scala.concurrent.duration._ +import scala.language.implicitConversions -class RedisInstanceManagerSpec extends Specification { - import play.api.cache.redis.Implicits._ +class RedisInstanceManagerSpec extends UnitSpec with ImplicitOptionMaterialization{ - private implicit def implicitlyInstance2resolved(instance: RedisInstance): RedisInstanceProvider = new ResolvedRedisInstance(instance) - private implicit def implicitlyString2unresolved(name: String): RedisInstanceProvider = new UnresolvedRedisInstance(name) + "default configuration" in new TestCase { - private val extras = RedisSettingsTest("my-dispatcher", "eager", RedisTimeouts(5.minutes, 5.seconds, 300.millis), "log-and-fail", "standalone", "redis.") + protected override def hocon: String = + """ + |play.cache.redis {} + """ - "default configuration" in new WithRedisInstanceManager( - """ - |play.cache.redis {} - """ - ) { - val defaultCache: RedisInstanceProvider = RedisStandalone(defaultCacheName, RedisHost(localhost, defaultPort, database = 0), defaults) + private val defaultCache: RedisInstanceProvider = + RedisStandalone( + name = defaultCacheName, + host = RedisHost(localhost, defaultPort, database = 0), + settings = defaultsSettings + ) manager mustEqual RedisInstanceManagerTest(defaultCacheName)(defaultCache) manager.instanceOf(defaultCacheName) mustEqual defaultCache - manager.instanceOfOption(defaultCacheName) must beSome(defaultCache) - manager.instanceOfOption("other") must beNone + manager.instanceOfOption(defaultCacheName) mustEqual Some(defaultCache) + manager.instanceOfOption("other") mustEqual None manager.defaultInstance mustEqual defaultCache } - "single default instance" in new WithRedisInstanceManager( + "single default instance" in new TestCase { + protected override def hocon: String = """ |play.cache.redis { | host: redis.localhost.cz @@ -46,13 +50,15 @@ class RedisInstanceManagerSpec extends Specification { | recovery: log-and-fail |} """ - ) { + private val settings: RedisSettings = RedisSettingsTest(invocationContext = "my-dispatcher", invocationPolicy = "eager", timeout = RedisTimeouts(5.minutes, 5.seconds, 300.millis), recovery = "log-and-fail", source = "standalone", prefix = "redis.") + manager mustEqual RedisInstanceManagerTest(defaultCacheName)( - RedisStandalone(defaultCacheName, RedisHost("redis.localhost.cz", 6378, database = 2, password = "something"), extras) + RedisStandalone(defaultCacheName, RedisHost("redis.localhost.cz", 6378, database = 2, password = "something"), settings) ) } - "named caches" in new WithRedisInstanceManager( + "named caches" in new TestCase { + protected override def hocon: String = """ |play.cache.redis { | instances { @@ -83,9 +89,12 @@ class RedisInstanceManagerSpec extends Specification { | default-cache: other |} """ - ) { - val defaultCache: RedisInstanceProvider = RedisStandalone(defaultCacheName, RedisHost(localhost, defaultPort, database = 1), extras) - val otherCache: RedisInstanceProvider = RedisStandalone("other", RedisHost("redis.localhost.cz", 6378, database = 2, password = "something"), defaults) + + private val otherSettings: RedisSettings = RedisSettingsTest( + invocationContext = "my-dispatcher", invocationPolicy = "eager", timeout = RedisTimeouts(5.minutes, 5.seconds, 300.millis), recovery = "log-and-fail", source = "standalone", prefix = "redis.") + + private val defaultCache: RedisInstanceProvider = RedisStandalone(defaultCacheName, RedisHost(localhost, defaultPort, database = 1), otherSettings) + private val otherCache: RedisInstanceProvider = RedisStandalone("other", RedisHost("redis.localhost.cz", 6378, database = 2, password = "something"), defaultsSettings) manager mustEqual RedisInstanceManagerTest("other")(defaultCache, otherCache) manager.instanceOf(defaultCacheName) mustEqual defaultCache @@ -93,7 +102,8 @@ class RedisInstanceManagerSpec extends Specification { manager.defaultInstance mustEqual otherCache } - "cluster mode" in new WithRedisInstanceManager( + "cluster mode" in new TestCase { + protected override def hocon: String = """ |play.cache.redis { | instances { @@ -109,15 +119,16 @@ class RedisInstanceManagerSpec extends Specification { | } |} """ - ) { - def node(port: Int) = RedisHost(localhost, port) + private def node(port: Int) = RedisHost(localhost, port) manager mustEqual RedisInstanceManagerTest(defaultCacheName)( - RedisCluster(defaultCacheName, node(6380) :: node(6381) :: node(6382) :: node(6383) :: Nil, defaults.copy(source = "cluster")) + RedisCluster( + name = defaultCacheName, nodes = node(6380) :: node(6381) :: node(6382) :: node(6383) :: Nil, settings = defaultsSettings.copy(source = "cluster")) ) } - "AWS cluster mode" in new WithRedisInstanceManager( + "AWS cluster mode" in new TestCase { + protected override def hocon: String = """ |play.cache.redis { | instances { @@ -128,13 +139,13 @@ class RedisInstanceManagerSpec extends Specification { | } |} """ - ) { - val provider = manager.defaultInstance.asInstanceOf[ResolvedRedisInstance] - val instance = provider.instance.asInstanceOf[RedisCluster] + private val provider = manager.defaultInstance.asInstanceOf[ResolvedRedisInstance] + private val instance = provider.instance.asInstanceOf[RedisCluster] instance.nodes must contain(RedisHost("127.0.0.1", 6379)) } - "sentinel mode" in new WithRedisInstanceManager( + "sentinel mode" in new TestCase { + protected override def hocon: String = """ |play.cache.redis { | instances { @@ -152,48 +163,53 @@ class RedisInstanceManagerSpec extends Specification { | } |} """ - ) { - def node(port: Int) = RedisHost(localhost, port) + + private def node(port: Int) = RedisHost(localhost, port) manager mustEqual RedisInstanceManagerTest(defaultCacheName)( - RedisSentinel(defaultCacheName, "primary", node(6380) :: node(6381) :: node(6382) :: Nil, defaults.copy(source = "sentinel"), password = "my-password", database = 1) + RedisSentinel( + name = defaultCacheName, masterGroup = "primary", sentinels = node(6380) :: node(6381) :: node(6382) :: Nil, settings = defaultsSettings.copy(source = "sentinel"), password = "my-password", database = 1) ) } - "connection string mode" in new WithRedisInstanceManager( + "connection string mode" in new TestCase { + protected override def hocon: String = """ |play.cache.redis { | source: "connection-string" | connection-string: "redis://localhost:6379" |} """ - ) { - manager mustEqual RedisInstanceManagerTest(defaultCacheName)( - RedisStandalone(defaultCacheName, RedisHost(localhost, defaultPort), defaults.copy(source = "connection-string")) + + manager mustEqual RedisInstanceManagerTest(defaultCacheName)( + RedisStandalone(defaultCacheName, RedisHost(localhost, defaultPort), defaultsSettings.copy(source = "connection-string")) ) } - "custom mode" in new WithRedisInstanceManager( + "custom mode" in new TestCase { + protected override def hocon: String = """ |play.cache.redis { | source: custom |} """ - ) { + manager mustEqual RedisInstanceManagerTest(defaultCacheName)(defaultCacheName) } - "typo in mode with simple syntax" in new WithRedisInstanceManager( + "typo in mode with simple syntax" in new TestCase { + protected override def hocon: String = """ |play.cache.redis { | source: typo |} """ - ) { - manager.defaultInstance must throwA[IllegalStateException] + + the[IllegalStateException] thrownBy manager.defaultInstance } - "typo in mode with advanced syntax" in new WithRedisInstanceManager( + "typo in mode with advanced syntax" in new TestCase { + protected override def hocon: String = """ |play.cache.redis { | instances { @@ -203,11 +219,12 @@ class RedisInstanceManagerSpec extends Specification { | } |} """ - ) { - manager.defaultInstance must throwA[IllegalStateException] + + the[IllegalStateException] thrownBy manager.defaultInstance } - "fail when requesting undefined cache" in new WithRedisInstanceManager( + "fail when requesting undefined cache" in new TestCase { + protected override def hocon: String = """ |play.cache.redis { | instances { @@ -219,12 +236,57 @@ class RedisInstanceManagerSpec extends Specification { | default-cache: other |} """ - ) { - manager.instanceOfOption(defaultCacheName) must beSome[RedisInstanceProvider] - manager.instanceOfOption("other") must beNone + manager.instanceOfOption(defaultCacheName) mustBe a[Some[_]] + manager.instanceOfOption("other") mustEqual None - manager.instanceOf("other") must throwA[IllegalArgumentException] - manager.defaultInstance must throwA[IllegalArgumentException] + the[IllegalArgumentException] thrownBy manager.instanceOf("other") + the[IllegalArgumentException] thrownBy manager.defaultInstance + } + + private trait TestCase { + + private implicit val loader: ConfigLoader[RedisInstanceManager] = RedisInstanceManager + + private val configuration: Configuration = Helpers.configuration.fromHocon(hocon) + + protected val manager: RedisInstanceManager = configuration.get[RedisInstanceManager]("play.cache.redis") + + protected def hocon: String + + protected implicit def implicitlyInstance2resolved(instance: RedisInstance): RedisInstanceProvider = + new ResolvedRedisInstance(instance) + + protected implicit def implicitlyString2unresolved(name: String): RedisInstanceProvider = + new UnresolvedRedisInstance(name) + + protected final case class RedisInstanceManagerTest( + default: String)( + providers: RedisInstanceProvider* + ) extends RedisInstanceManager { + + override def caches: Set[String] = providers.map(_.name).toSet + + override def instanceOfOption(name: String): Option[RedisInstanceProvider] = providers.find(_.name == name) + + override def defaultInstance: RedisInstanceProvider = providers.find(_.name == default) getOrElse { + throw new RuntimeException("Default instance is not defined.") + } + + } } } + + + + + + + + + + + + + + diff --git a/src/test/scala/play/api/cache/redis/configuration/RedisInstanceManagerTest.scala b/src/test/scala/play/api/cache/redis/configuration/RedisInstanceManagerTest.scala deleted file mode 100644 index 7c81c3dd..00000000 --- a/src/test/scala/play/api/cache/redis/configuration/RedisInstanceManagerTest.scala +++ /dev/null @@ -1,25 +0,0 @@ -package play.api.cache.redis.configuration - -import play.api.ConfigLoader -import play.api.cache.redis._ - -/** - * Simple implementation for tests - */ -case class RedisInstanceManagerTest(default: String)(providers: RedisInstanceProvider*) extends RedisInstanceManager { - - def caches = providers.map(_.name).toSet - - def instanceOfOption(name: String) = providers.find(_.name == name) - - def defaultInstance = providers.find(_.name == default) getOrElse { - throw new RuntimeException("Default instance is not defined.") - } -} - -abstract class WithRedisInstanceManager(hocon: String) extends WithConfiguration(hocon) { - - private implicit val loader: ConfigLoader[RedisInstanceManager] = RedisInstanceManager - - protected val manager = configuration.get[RedisInstanceManager]("play.cache.redis") -} diff --git a/src/test/scala/play/api/cache/redis/configuration/RedisInstanceProviderSpec.scala b/src/test/scala/play/api/cache/redis/configuration/RedisInstanceProviderSpec.scala index 0a9e016e..c3ff1d67 100644 --- a/src/test/scala/play/api/cache/redis/configuration/RedisInstanceProviderSpec.scala +++ b/src/test/scala/play/api/cache/redis/configuration/RedisInstanceProviderSpec.scala @@ -1,14 +1,15 @@ package play.api.cache.redis.configuration -import org.specs2.mutable.Specification +import play.api.cache.redis.test.UnitSpec -class RedisInstanceProviderSpec extends Specification { - import play.api.cache.redis.Implicits._ +class RedisInstanceProviderSpec extends UnitSpec { - val defaultCache = RedisStandalone(defaultCacheName, RedisHost(localhost, defaultPort, database = 0), defaults) + private val defaultCache: RedisStandalone = + RedisStandalone( + name = defaultCacheName, host = RedisHost(localhost, defaultPort, database = Some(0)), settings = defaultsSettings) - implicit val resolver: RedisInstanceResolver = new RedisInstanceResolver { - def resolve = { +private implicit val resolver: RedisInstanceResolver = new RedisInstanceResolver { + def resolve: PartialFunction[String, RedisStandalone] = { case `defaultCacheName` => defaultCache } } @@ -22,6 +23,6 @@ class RedisInstanceProviderSpec extends Specification { } "fail when not able to resolve" in { - new UnresolvedRedisInstance("other").resolved must throwA[Exception] + the[Exception] thrownBy new UnresolvedRedisInstance("other").resolved } } diff --git a/src/test/scala/play/api/cache/redis/configuration/RedisTimeoutsSpec.scala b/src/test/scala/play/api/cache/redis/configuration/RedisTimeoutsSpec.scala index e3eac464..c9d752a8 100644 --- a/src/test/scala/play/api/cache/redis/configuration/RedisTimeoutsSpec.scala +++ b/src/test/scala/play/api/cache/redis/configuration/RedisTimeoutsSpec.scala @@ -1,65 +1,74 @@ package play.api.cache.redis.configuration import scala.concurrent.duration._ - import play.api.cache.redis._ +import play.api.cache.redis.test.{Helpers, ImplicitOptionMaterialization, UnitSpec} -import org.specs2.mutable.Specification - -class RedisTimeoutsSpec extends Specification { - import Implicits._ +class RedisTimeoutsSpec extends UnitSpec with ImplicitOptionMaterialization{ private def orDefault = RedisTimeouts(1.second, None, 500.millis) - "load defined timeouts" in new WithConfiguration( - """ - |play.cache.redis { - | - | sync-timeout: 5s - | redis-timeout: 7s - | connection-timeout: 300ms - |} + "load defined timeouts" in { + val configuration = Helpers.configuration.fromHocon { + """ + |play.cache.redis { + | + | sync-timeout: 5s + | redis-timeout: 7s + | connection-timeout: 300ms + |} """ - ) { - RedisTimeouts.load(config, "play.cache.redis")(RedisTimeouts.requiredDefault) mustEqual RedisTimeouts(5.seconds, 7.seconds, 300.millis) + } + val expected = RedisTimeouts(5.seconds, 7.seconds, 300.millis) + val actual = RedisTimeouts.load(configuration.underlying, "play.cache.redis")(RedisTimeouts.requiredDefault) + actual mustEqual expected } - "load defined high timeouts" in new WithConfiguration( + "load defined high timeouts" in { + val configuration = Helpers.configuration.fromHocon { + """ + |play.cache.redis { + | + | sync-timeout: 500s + | redis-timeout: 700s + | connection-timeout: 900s + |} """ - |play.cache.redis { - | - | sync-timeout: 500s - | redis-timeout: 700s - | connection-timeout: 900s - |} - """ - ) { - RedisTimeouts.load(config, "play.cache.redis")(RedisTimeouts.requiredDefault) mustEqual RedisTimeouts(500.seconds, 700.seconds, 900.seconds) + } + val expected = RedisTimeouts(500.seconds, 700.seconds, 900.seconds) + val actual = RedisTimeouts.load(configuration.underlying, "play.cache.redis")(RedisTimeouts.requiredDefault) + actual mustEqual expected } - "load with default timeouts" in new WithConfiguration( - """ - |play.cache.redis { - |} + "load with default timeouts" in { + val configuration = Helpers.configuration.fromHocon { + """ + |play.cache.redis { + |} """ - ) { - RedisTimeouts.load(config, "play.cache.redis")(orDefault) mustEqual RedisTimeouts(1.second, None, connection = 500.millis) + } + val expected = RedisTimeouts(1.second, None, connection = 500.millis) + val actual = RedisTimeouts.load(configuration.underlying, "play.cache.redis")(orDefault) + actual mustEqual expected } - "load with disabled timeouts" in new WithConfiguration( - """ - |play.cache.redis { - | redis-timeout: null - | connection-timeout: null - |} + "load with disabled timeouts" in { + val configuration = Helpers.configuration.fromHocon { + """ + |play.cache.redis { + | redis-timeout: null + | connection-timeout: null + |} """ - ) { - RedisTimeouts.load(config, "play.cache.redis")(orDefault) mustEqual RedisTimeouts(sync = 1.second, redis = None, connection = None) + } + val expected = RedisTimeouts(sync = 1.second, redis = None, connection = None) + val actual = RedisTimeouts.load(configuration.underlying, "play.cache.redis")(orDefault) + actual mustEqual expected } "load defaults" in { - RedisTimeouts.requiredDefault.sync must throwA[RuntimeException] - RedisTimeouts.requiredDefault.redis must beNone - RedisTimeouts.requiredDefault.connection must beNone + the[RuntimeException] thrownBy RedisTimeouts.requiredDefault.sync + RedisTimeouts.requiredDefault.redis mustEqual None + RedisTimeouts.requiredDefault.connection mustEqual None } } diff --git a/src/test/scala/play/api/cache/redis/connector/ExpectedFutureSpec.scala b/src/test/scala/play/api/cache/redis/connector/ExpectedFutureSpec.scala index 21ede99e..00214d1d 100644 --- a/src/test/scala/play/api/cache/redis/connector/ExpectedFutureSpec.scala +++ b/src/test/scala/play/api/cache/redis/connector/ExpectedFutureSpec.scala @@ -1,45 +1,43 @@ package play.api.cache.redis.connector -import scala.concurrent.{ExecutionContext, Future} - import play.api.cache.redis._ +import play.api.cache.redis.test.{AsyncUnitSpec, SimulatedException} -import org.specs2.concurrent.ExecutionEnv -import org.specs2.mutable.Specification +import scala.concurrent.{ExecutionContext, Future} -class ExpectedFutureSpec(implicit ee: ExecutionEnv) extends Specification { +class ExpectedFutureSpec extends AsyncUnitSpec { import ExpectedFutureSpec._ - class Suite(name: String)(implicit f: ExpectationBuilder[String]) { + private class TestSuite(name: String)(implicit f: ExpectationBuilder[String]) { - name >> { + name should { "expected value" in { - Future.successful("expected").asExpected must beEqualTo("ok").await + Future.successful("expected").asExpected.assertingEqual("ok") } "unexpected value" in { - Future.successful("unexpected").asExpected must throwA[UnexpectedResponseException].await + Future.successful("unexpected").asExpected.assertingFailure[UnexpectedResponseException] } "failing expectation" in { - Future.successful("failing").asExpected must throwA[ExecutionFailedException].await + Future.successful("failing").asExpected.assertingFailure[ExecutionFailedException] } "failing future inside redis" in { - Future.failed[String](TimeoutException(simulatedFailure)).asExpected must throwA[TimeoutException].await + Future.failed[String](TimeoutException(SimulatedException)).asExpected.assertingFailure[TimeoutException] } "failing future with runtime exception" in { - Future.failed[String](simulatedFailure).asExpected must throwA[ExecutionFailedException].await + Future.failed[String](SimulatedException).asExpected.assertingFailure[ExecutionFailedException] } } } - new Suite("Execution without the key")((future: Future[String]) => future.executing(cmd)) + new TestSuite("Execution without the key")((future: Future[String]) => future.executing(cmd)) - new Suite("Execution with the key")((future: Future[String]) => future.executing(cmd).withKey("key")) + new TestSuite("Execution with the key")((future: Future[String]) => future.executing(cmd).withKey("key")) "building a command" in { Future.successful("expected").executing(cmd).toString mustEqual s"ExpectedFuture($cmd)" @@ -52,20 +50,16 @@ class ExpectedFutureSpec(implicit ee: ExecutionEnv) extends Specification { object ExpectedFutureSpec { - val cmd = "TEST CMD" + private val cmd = "TEST CMD" - def simulatedFailure = new RuntimeException("Simulated runtime failure") - - val expectation: PartialFunction[Any, String] = { - case "failing" => throw simulatedFailure + private val expectation: PartialFunction[Any, String] = { + case "failing" => throw SimulatedException case "expected" => "ok" } - implicit class ExpectationBuilder[T](val f: Future[T] => ExpectedFuture[String]) extends AnyVal { - def apply(future: Future[T]): ExpectedFuture[String] = f(future) - } + private type ExpectationBuilder[T] = Future[T] => ExpectedFuture[String] - implicit class FutureBuilder[T](val future: Future[T]) extends AnyVal { + private implicit class FutureBuilder[T](private val future: Future[T]) extends AnyVal { def asExpected(implicit ev: ExpectationBuilder[T], context: ExecutionContext): Future[String] = ev(future).expects(expectation) def asCommand(implicit ev: ExpectationBuilder[T]): String = ev(future).toString } diff --git a/src/test/scala/play/api/cache/redis/connector/FailEagerlySpec.scala b/src/test/scala/play/api/cache/redis/connector/FailEagerlySpec.scala index b282804d..8f59cb8c 100644 --- a/src/test/scala/play/api/cache/redis/connector/FailEagerlySpec.scala +++ b/src/test/scala/play/api/cache/redis/connector/FailEagerlySpec.scala @@ -1,45 +1,47 @@ package play.api.cache.redis.connector -import scala.concurrent.Future -import scala.concurrent.duration._ - -import play.api.cache.redis._ - -import akka.actor.ActorSystem -import org.specs2.concurrent.ExecutionEnv -import org.specs2.mutable.Specification +import akka.actor.{ActorSystem, Scheduler} +import play.api.cache.redis.test._ -class FailEagerlySpec(implicit ee: ExecutionEnv) extends Specification with WithApplication { +import scala.concurrent.duration._ +import scala.concurrent.{ExecutionContext, Future, Promise} +class FailEagerlySpec extends AsyncUnitSpec with ImplicitFutureMaterialization { import FailEagerlySpec._ - import Implicits._ - import MockitoImplicits._ - "FailEagerly" should { + test("not fail regular requests when disconnected") { failEagerly => + val cmd = mock[RedisCommandTest[String]] + (() => cmd.returning).expects().returns("response") + // run the test + failEagerly.isConnected mustEqual false + failEagerly.send(cmd).assertingEqual("response") + } - "not fail regular requests when disconnected" in { - val impl = new FailEagerlyImpl - val cmd = mock[RedisCommandTest].returning returns "response" - // run the test - impl.isConnected must beFalse - impl.send[String](cmd) must beEqualTo("response").await - } + test("do fail long running requests when disconnected") { failEagerly => + val cmd = mock[RedisCommandTest[String]] + (() => cmd.returning).expects().returns(Promise[String]().future) + // run the test + failEagerly.isConnected mustEqual false + failEagerly.send(cmd).assertingFailure[redis.actors.NoConnectionException.type] + } - "do fail long running requests when disconnected" in { - val impl = new FailEagerlyImpl - val cmd = mock[RedisCommandTest].returning returns Future.after(seconds = 3, "response") - // run the test - impl.isConnected must beFalse - impl.send[String](cmd) must throwA[redis.actors.NoConnectionException.type].awaitFor(5.seconds) - } + test("not fail long running requests when connected ") { failEagerly => + val cmd = mock[RedisCommandTest[String]] + (() => cmd.returning).expects().returns(Promise[String]().future) + failEagerly.markConnected() + // run the test + failEagerly.isConnected mustEqual true + failEagerly.send(cmd).assertTimeout(200.millis) + } - "not fail long running requests when connected " in { - val impl = new FailEagerlyImpl - val cmd = mock[RedisCommandTest].returning returns Future.after(seconds = 3, "response") - impl.markConnected() - // run the test - impl.isConnected must beTrue - impl.send[String](cmd) must beEqualTo("response").awaitFor(5.seconds) + def test(name: String)(f: FailEagerlyImpl => Future[Assertion]): Unit = { + name in { + val system = ActorSystem("test", classLoader = Some(getClass.getClassLoader)) + val application = StoppableApplication(system) + application.runAsyncInApplication { + val impl = new FailEagerlyImpl()(system) + f(impl) + } } } } @@ -49,27 +51,27 @@ object FailEagerlySpec { import redis.RedisCommand import redis.protocol.RedisReply - trait RedisCommandTest extends RedisCommand[RedisReply, String] { - def returning: Future[String] + trait RedisCommandTest[T] extends RedisCommand[RedisReply, T] { + def returning: Future[T] } class FailEagerlyBase(implicit system: ActorSystem) extends RequestTimeout { - protected implicit val scheduler = system.scheduler - implicit val executionContext = system.dispatcher + protected implicit val scheduler: Scheduler = system.scheduler + implicit val executionContext: ExecutionContext = system.dispatcher - def send[T](redisCommand: RedisCommand[_ <: RedisReply, T]) = { - redisCommand.asInstanceOf[RedisCommandTest].returning.asInstanceOf[Future[T]] + def send[T](redisCommand: RedisCommand[_ <: RedisReply, T]): Future[T] = { + redisCommand.asInstanceOf[RedisCommandTest[T]].returning } } - class FailEagerlyImpl(implicit system: ActorSystem) extends FailEagerlyBase with FailEagerly { + final class FailEagerlyImpl(implicit system: ActorSystem) extends FailEagerlyBase with FailEagerly { - def connectionTimeout = Some(300.millis) + def connectionTimeout: Option[FiniteDuration] = Some(100.millis) - def isConnected = connected + def isConnected: Boolean = connected - def markConnected() = connected = true + def markConnected(): Unit = connected = true - def markDisconnected() = connected = false + def markDisconnected(): Unit = connected = false } } diff --git a/src/test/scala/play/api/cache/redis/connector/MockedConnector.scala b/src/test/scala/play/api/cache/redis/connector/MockedConnector.scala deleted file mode 100644 index 5d70f14e..00000000 --- a/src/test/scala/play/api/cache/redis/connector/MockedConnector.scala +++ /dev/null @@ -1,30 +0,0 @@ -package play.api.cache.redis.connector - -import scala.concurrent.ExecutionContext -import scala.concurrent.duration._ - -import play.api.cache.redis._ -import play.api.cache.redis.impl._ - -import org.specs2.execute.{AsResult, Result} -import org.specs2.specification.{Around, Scope} -import redis.RedisCommands - -abstract class MockedConnector extends Around with Scope with WithRuntime with WithApplication { - import MockitoImplicits._ - - protected val serializer = mock[AkkaSerializer] - - protected val commands = mock[RedisCommands] - - protected val connector: RedisConnector = new RedisConnectorImpl(serializer, commands) - - def around[T: AsResult](t: => T): Result = { - AsResult.effectively(t) - } -} - -trait WithRuntime { - - implicit protected val runtime: RedisRuntime = RedisRuntime("connector", syncTimeout = 5.seconds, ExecutionContext.global, new LogAndFailPolicy, LazyInvocation) -} diff --git a/src/test/scala/play/api/cache/redis/connector/RedisClusterSpec.scala b/src/test/scala/play/api/cache/redis/connector/RedisClusterSpec.scala index 3c3b8f79..c5658052 100644 --- a/src/test/scala/play/api/cache/redis/connector/RedisClusterSpec.scala +++ b/src/test/scala/play/api/cache/redis/connector/RedisClusterSpec.scala @@ -1,98 +1,88 @@ package play.api.cache.redis.connector -import scala.concurrent.{ExecutionContext, Future} -import scala.concurrent.duration._ - +import akka.actor.ActorSystem import play.api.cache.redis._ import play.api.cache.redis.configuration._ import play.api.cache.redis.impl._ -import play.api.inject.ApplicationLifecycle - -import org.specs2.concurrent.ExecutionEnv -import org.specs2.mutable.Specification -import org.specs2.specification.{AfterAll, BeforeAll} - -/** - *

Specification of the low level connector implementing basic commands

- */ -class RedisClusterSpec(implicit ee: ExecutionEnv) extends Specification with WithApplication with ClusterRedisContainer { - - args(skipAll=true) - - import Implicits._ - - implicit private val lifecycle: ApplicationLifecycle = application.injector.instanceOf[ApplicationLifecycle] +import play.api.cache.redis.test._ - implicit private val runtime: RedisRuntime = RedisRuntime("cluster", syncTimeout = 5.seconds, ExecutionContext.global, new LogAndFailPolicy, LazyInvocation) - - private val serializer = new AkkaSerializerImpl(system) - - private lazy val containerIpAddress = container.containerIpAddress - - private lazy val clusterInstance = RedisCluster( - name = defaultCacheName, - nodes = RedisHost(containerIpAddress, container.mappedPort(7000)) :: - RedisHost(containerIpAddress, container.mappedPort(7001)) :: - RedisHost(containerIpAddress, container.mappedPort(7002)) :: - RedisHost(containerIpAddress, container.mappedPort(7003)) :: - Nil, - settings = defaults - ) - - private lazy val connector: RedisConnector = new RedisConnectorProvider(clusterInstance, serializer).get - - val prefix = "cluster-test" - - "Redis cluster" should { +import scala.concurrent.duration._ +import scala.concurrent.{ExecutionContext, Future} - "pong on ping" in new TestCase { - connector.ping() must not(throwA[Throwable]).await - } +class RedisClusterSpec extends IntegrationSpec with RedisClusterContainer { - "miss on get" in new TestCase { - connector.get[String](s"$prefix-$idx") must beNone.await - } + test("pong on ping") { connector => + connector.ping().assertingSuccess + } - "hit after set" in new TestCase { - connector.set(s"$prefix-$idx", "value") must beTrue.await - connector.get[String](s"$prefix-$idx") must beSome[Any].await - connector.get[String](s"$prefix-$idx") must beSome("value").await - } + test("miss on get") { connector => + connector.get[String]("miss-on-get").assertingEqual(None) + } - "ignore set if not exists when already defined" in new TestCase { - connector.set(s"$prefix-if-not-exists-when-exists", "previous") must beTrue.await - connector.set(s"$prefix-if-not-exists-when-exists", "value", ifNotExists = true) must beFalse.await - connector.get[String](s"$prefix-if-not-exists-when-exists") must beSome("previous").await - } + test("hit after set") { connector => + for { + _ <- connector.set("hit-after-set", "value").assertingEqual(true) + _ <- connector.get[String]("hit-after-set").assertingEqual(Some("value")) + } yield Passed + } - "perform set if not exists when undefined" in new TestCase { - connector.get[String](s"$prefix-if-not-exists") must beNone.await - connector.set(s"$prefix-if-not-exists", "value", ifNotExists = true) must beTrue.await - connector.get[String](s"$prefix-if-not-exists") must beSome("value").await - connector.set(s"$prefix-if-not-exists", "other", ifNotExists = true) must beFalse.await - connector.get[String](s"$prefix-if-not-exists") must beSome("value").await - } + test("ignore set if not exists when already defined") { connector => + for { + _ <- connector.set("if-not-exists-when-exists", "previous").assertingEqual(true) + _ <- connector.set("if-not-exists-when-exists", "value", ifNotExists = true).assertingEqual(false) + _ <- connector.get[String]("if-not-exists-when-exists").assertingEqual(Some("previous")) + } yield Passed + } - "perform set if not exists with expiration" in new TestCase { - connector.get[String](s"$prefix-if-not-exists-with-expiration") must beNone.await - connector.set(s"$prefix-if-not-exists-with-expiration", "value", 2.seconds, ifNotExists = true) must beTrue.await - connector.get[String](s"$prefix-if-not-exists-with-expiration") must beSome("value").await - // wait until the first duration expires - Future.after(3) must not(throwA[Throwable]).awaitFor(4.seconds) - connector.get[String](s"$prefix-if-not-exists-with-expiration") must beNone.await - } + test("perform set if not exists when undefined") { connector => + for { + _ <- connector.get[String]("if-not-exists").assertingEqual(None) + _ <- connector.set("if-not-exists", "value", ifNotExists = true).assertingEqual(true) + _ <- connector.get[String]("if-not-exists").assertingEqual(Some("value")) + _ <- connector.set("if-not-exists", "other", ifNotExists = true).assertingEqual(false) + _ <- connector.get[String]("if-not-exists").assertingEqual(Some("value")) + } yield Passed } - override def beforeAll() = { - super.beforeAll() - // initialize the connector by flushing the database - connector.matching(s"$prefix-*").flatMap { - keys => Future.sequence(keys.map(connector.remove(_))) - }.awaitForFuture + test("perform set if not exists with expiration") { connector => + for { + _ <- connector.get[String]("if-not-exists-with-expiration").assertingEqual(None) + _ <- connector.set("if-not-exists-with-expiration", "value", 500.millis, ifNotExists = true).assertingEqual(true) + _ <- connector.get[String]("if-not-exists-with-expiration").assertingEqual(Some("value")) + // wait until the first duration expires + _ <- Future.after(700.millis, ()) + _ <- connector.get[String]("if-not-exists-with-expiration").assertingEqual(None) + } yield Passed } - override def afterAll() = { - Shutdown.run.awaitForFuture - super.afterAll() + def test(name: String)(f: RedisConnector => Future[Assertion]): Unit = { + name in { + implicit val system: ActorSystem = ActorSystem("test", classLoader = Some(getClass.getClassLoader)) + implicit val runtime: RedisRuntime = RedisRuntime("cluster", syncTimeout = 5.seconds, ExecutionContext.global, new LogAndFailPolicy, LazyInvocation) + implicit val application: StoppableApplication = StoppableApplication(system) + val serializer = new AkkaSerializerImpl(system) + + lazy val clusterInstance = RedisCluster( + name = "play", + nodes = 0.until(redisMaster).map { i => + RedisHost(container.containerIpAddress, container.mappedPort(initialPort + i)) + }.toList, + settings = RedisSettings.load( + config = Helpers.configuration.default.underlying, + path = "play.cache.redis" + ) + ) + + application.runAsyncInApplication { + for { + connector <- Future(new RedisConnectorProvider(clusterInstance, serializer).get) + // initialize the connector by flushing the database + keys <- connector.matching("*") + _ <- Future.sequence(keys.map(connector.remove(_))) + // run the test + _ <- f(connector) + } yield Passed + } + } } } diff --git a/src/test/scala/play/api/cache/redis/connector/RedisConnectorFailureSpec.scala b/src/test/scala/play/api/cache/redis/connector/RedisConnectorFailureSpec.scala index 6a2221d0..a12cfe26 100644 --- a/src/test/scala/play/api/cache/redis/connector/RedisConnectorFailureSpec.scala +++ b/src/test/scala/play/api/cache/redis/connector/RedisConnectorFailureSpec.scala @@ -1,182 +1,275 @@ package play.api.cache.redis.connector -import scala.concurrent.Future -import scala.concurrent.duration._ -import scala.reflect.ClassTag -import scala.util.Failure - import play.api.cache.redis._ - -import org.specs2.concurrent.ExecutionEnv -import org.specs2.mutable.Specification +import play.api.cache.redis.test._ import redis._ +import redis.api.{BEFORE, ListPivot} -class RedisConnectorFailureSpec(implicit ee: ExecutionEnv) extends Specification with ReducedMockito { - - import Implicits._ +import scala.concurrent.duration._ +import scala.concurrent.{ExecutionContext, Future} +import scala.reflect.ClassTag +import scala.util.{Failure, Success} - import org.mockito.ArgumentMatchers._ +class RedisConnectorFailureSpec extends AsyncUnitSpec with ImplicitFutureMaterialization { - private val key = "key" - private val value = "value" private val score = 1D + private val encodedValue = "encoded" + private val disconnected = Future.failed(SimulatedException) - private val simulatedEx = new RuntimeException("Simulated failure.") - private val simulatedFailure = Failure(simulatedEx) + "Serializer fail" when { - private val someValue = Some(value) - - private val disconnected = Future.failed(new IllegalStateException("Simulated redis status: disconnected.")) + test("serialization fails") { (serializer, _, connector) => + for { + _ <- serializer.failOnEncode(cacheValue) + _ <- connector.set(cacheKey, cacheValue).assertingFailure[SerializationException] + } yield Passed + } - private def anySerializer = org.mockito.ArgumentMatchers.any[ByteStringSerializer[String]] - private def anyDeserializer = org.mockito.ArgumentMatchers.any[ByteStringDeserializer[String]] + test("decoder fails") { (serializer, commands, connector) => + for { + _ <- serializer.failOnDecode(cacheValue) + _ = (commands.get[String](_: String)(_: ByteStringDeserializer[String])).expects(cacheKey, *).returns(Some(cacheValue)) + _ <- connector.get[String](cacheKey).assertingFailure[SerializationException] + } yield Passed + } + } - "Serializer failure" should { + "Redis returns error code" when { - "fail when serialization fails" in new MockedConnector { - serializer.encode(any[Any]) returns simulatedFailure - // run the test - connector.set(key, value) must throwA[SerializationException].await + test("SET returning false") { (serializer, commands, connector) => + for { + _ <- serializer.encode(cacheValue, encodedValue) + _ = (commands.set[String](_: String, _: String, _: Option[Long], _: Option[Long], _: Boolean, _: Boolean)(_: ByteStringSerializer[String])) + .expects(cacheKey, encodedValue, None, None, false, false, *) + .returns(false) + _ <- connector.set(cacheKey, cacheValue).assertingEqual(false) + } yield Passed } - "fail when decoder fails" in new MockedConnector { - serializer.decode(anyString)(any()) returns simulatedFailure - commands.get[String](key) returns someValue - // run the test - connector.get[String](key) must throwA[SerializationException].await + test("EXPIRE returning false") { (_, commands, connector) => + for { + _ <- (commands.expire _).expects(cacheKey, 1.minute.toSeconds).returns(false) + _ <- connector.expire(cacheKey, 1.minute).assertingSuccess + } yield Passed } + } - "Redis returning error code" should { + "Connector fails" when { + + test("failed SET") { (serializer, commands, connector) => + for { + _ <- serializer.encode(cacheValue, encodedValue) + _ = (commands.set[String](_: String, _: String, _: Option[Long], _: Option[Long], _: Boolean, _: Boolean)(_: ByteStringSerializer[String])) + .expects(cacheKey, encodedValue, None, None, false, false, *) + .returns(disconnected) + + _ <- serializer.encode(cacheValue, encodedValue) + _ = (commands.set[String](_: String, _: String, _: Option[Long], _: Option[Long], _: Boolean, _: Boolean)(_: ByteStringSerializer[String])) + .expects(cacheKey, encodedValue, None, Some(1.minute.toMillis), false, false, *) + .returns(disconnected) + + _ <- serializer.encode(cacheValue, encodedValue) + _ = (commands.set[String](_: String, _: String, _: Option[Long], _: Option[Long], _: Boolean, _: Boolean)(_: ByteStringSerializer[String])) + .expects(cacheKey, encodedValue, None, None, true, false, *) + .returns(disconnected) + + _ <- connector.set(cacheKey, cacheValue).assertingFailure[ExecutionFailedException, SimulatedException] + _ <- connector.set(cacheKey, cacheValue, 1.minute).assertingFailure[ExecutionFailedException, SimulatedException] + _ <- connector.set(cacheKey, cacheValue, ifNotExists = true).assertingFailure[ExecutionFailedException, SimulatedException] + } yield Passed + } - "SET returning false" in new MockedConnector { - serializer.encode(anyString) returns "encoded" - commands.set[String](anyString, anyString, any[Some[Long]], any[Some[Long]], anyBoolean, anyBoolean)(anySerializer) returns false - // run the test - connector.set(key, value) must not(throwA[Throwable]).await + test("failed MSET") { (serializer, commands, connector) => + for { + _ <- serializer.encode(cacheValue, encodedValue) + _ = (commands.mset[String](_: Map[String, String])(_: ByteStringSerializer[String])) + .expects(Map(cacheKey -> encodedValue), *) + .returns(disconnected) + _ <- connector.mSet(cacheKey -> cacheValue).assertingFailure[ExecutionFailedException, SimulatedException] + } yield Passed } - "EXPIRE returning false" in new MockedConnector { - commands.expire(anyString, any[Long]) returns false - // run the test - connector.expire(key, 1.minute) must not(throwA[Throwable]).await + test("failed MSETNX") { (serializer, commands, connector) => + for { + _ <- serializer.encode(cacheValue, encodedValue) + _ = (commands.msetnx[String](_: Map[String, String])(_: ByteStringSerializer[String])) + .expects(Map(cacheKey -> encodedValue), *) + .returns(disconnected) + _ <- connector.mSetIfNotExist(cacheKey -> cacheValue).assertingFailure[ExecutionFailedException, SimulatedException] + } yield Passed } - } - "Connector failure" should { + test("failed EXPIRE") { (_, commands, connector) => + for { + _ <- (commands.expire(_: String, _: Long)).expects(cacheKey, 1.minute.toSeconds).returns(disconnected) + _ <- connector.expire(cacheKey, 1.minute).assertingFailure[ExecutionFailedException, SimulatedException] + } yield Passed + } - "failed SET" in new MockedConnector { - serializer.encode(anyString) returns "encoded" - commands.set(anyString, anyString, any, any, anyBoolean, anyBoolean)(anySerializer) returns disconnected - // run the test - connector.set(key, value) must throwA[ExecutionFailedException].await - connector.set(key, value, 1.minute) must throwA[ExecutionFailedException].await - connector.set(key, value, ifNotExists = true) must throwA[ExecutionFailedException].await + test("failed INCRBY") { (_, commands, connector) => + for { + _ <- (commands.incrby(_: String, _: Long)).expects(cacheKey, 1L).returns(disconnected) + _ <- connector.increment(cacheKey, 1L).assertingFailure[ExecutionFailedException, SimulatedException] + } yield Passed } - "failed MSET" in new MockedConnector { - serializer.encode(anyString) returns "encoded" - commands.mset[String](any[Map[String, String]])(anySerializer) returns disconnected - // run the test - connector.mSet(key -> value) must throwA[ExecutionFailedException].await + test("failed LRANGE") { (_, commands, connector) => + for { + _ <- (commands.lrange[String](_: String, _: Long, _: Long)(_: ByteStringDeserializer[String])) + .expects(cacheKey, 0, -1, *) + .returns(disconnected) + _ <- connector.listSlice[String](cacheKey, 0, -1).assertingFailure[ExecutionFailedException, SimulatedException] + } yield Passed } - "failed MSETNX" in new MockedConnector { - serializer.encode(anyString) returns "encoded" - commands.msetnx[String](any[Map[String, String]])(anySerializer) returns disconnected - // run the test - connector.mSetIfNotExist(key -> value) must throwA[ExecutionFailedException].await + test("failed LREM") { (serializer, commands, connector) => + for { + _ <- serializer.encode(cacheValue, encodedValue) + _ = (commands.lrem(_: String, _: Long, _: String)(_: ByteStringSerializer[String])) + .expects(cacheKey, 2L, encodedValue, *) + .returns(disconnected) + _ <- connector.listRemove(cacheKey, cacheValue, 2).assertingFailure[ExecutionFailedException, SimulatedException] + } yield Passed } - "failed EXPIRE" in new MockedConnector { - commands.expire(anyString, anyLong) returns disconnected - // run the test - connector.expire(key, 1.minute) must throwA[ExecutionFailedException].await + test("failed LTRIM") { (_, commands, connector) => + for { + _ <- (commands.ltrim(_: String, _: Long, _: Long)).expects(cacheKey, 1L, 5L).returns(disconnected) + _ <- connector.listTrim(cacheKey, 1, 5).assertingFailure[ExecutionFailedException, SimulatedException] + } yield Passed } - "failed INCRBY" in new MockedConnector { - commands.incrby(anyString, anyLong) returns disconnected - // run the test - connector.increment(key, 1L) must throwA[ExecutionFailedException].await + test("failed LINSERT") { (serializer, commands, connector) => + for { + _ <- serializer.encode("pivot", "encodedPivot") + _ <- serializer.encode(cacheValue, encodedValue) + _ = (commands.linsert[String](_: String, _: ListPivot, _: String, _: String)(_: ByteStringSerializer[String])) + .expects(cacheKey, BEFORE, "encodedPivot", encodedValue, *) + .returns(disconnected) + // run the test + _ <- connector.listInsert(cacheKey, "pivot", cacheValue).assertingFailure[ExecutionFailedException, SimulatedException] + } yield Passed } - "failed LRANGE" in new MockedConnector { - serializer.encode(anyString) returns "encoded" - commands.lrange[String](anyString, anyLong, anyLong)(anyDeserializer) returns disconnected - // run the test - connector.listSlice[String](key, 0, -1) must throwA[ExecutionFailedException].await + test("failed HINCRBY") { (_, commands, connector) => + for { + _ <- (commands.hincrby(_: String, _: String, _: Long)).expects(cacheKey, "field", 1L).returns(disconnected) + // run the test + _ <- connector.hashIncrement(cacheKey, "field", 1).assertingFailure[ExecutionFailedException, SimulatedException] + } yield Passed } - "failed LREM" in new MockedConnector { - serializer.encode(anyString) returns "encoded" - commands.lrem(anyString, anyLong, anyString)(anySerializer) returns disconnected - // run the test - connector.listRemove(key, value, 2) must throwA[ExecutionFailedException].await + test("failed HSET") { (serializer, commands, connector) => +for { + _ <- serializer.encode(cacheValue, encodedValue) + _ = (commands.hset[String](_: String, _: String, _: String)(_: ByteStringSerializer[String])) + .expects(cacheKey, "field", encodedValue, *) + .returns(disconnected) + _ <- connector.hashSet(cacheKey, "field", cacheValue).assertingFailure[ExecutionFailedException, SimulatedException] + } yield Passed } - "failed LTRIM" in new MockedConnector { - commands.ltrim(anyString, anyLong, anyLong) returns disconnected - // run the test - connector.listTrim(key, 1, 5) must throwA[ExecutionFailedException].await + test("failed ZADD") { (serializer, commands, connector) => + for { + _ <- serializer.encode(cacheValue, encodedValue) + _ = (commands.zaddMock[String](_: String, _: Seq[(Double, String)])(_: ByteStringSerializer[String])) + .expects(cacheKey, Seq((score, encodedValue)), *) + .returns(disconnected) + _ <- connector.sortedSetAdd(cacheKey, (score, cacheValue)).assertingFailure[ExecutionFailedException, SimulatedException] + } yield Passed } - "failed LINSERT" in new MockedConnector { - serializer.encode(anyString) returns "encoded" - commands.linsert[String](anyString, any[api.ListPivot], anyString, anyString)(anySerializer) returns disconnected - // run the test - connector.listInsert(key, "pivot", value) must throwA[ExecutionFailedException].await + test("failed ZCARD") { (_, commands, connector) => + for { + _ <- (commands.zcard(_: String)).expects(cacheKey).returns(disconnected) + _ <- connector.sortedSetSize(cacheKey).assertingFailure[ExecutionFailedException, SimulatedException] + } yield Passed } - "failed HINCRBY" in new MockedConnector { - commands.hincrby(anyString, anyString, anyLong) returns disconnected - // run the test - connector.hashIncrement(key, "field", 1) must throwA[ExecutionFailedException].await + test("failed ZSCORE") { (serializer, commands, connector) => + for { + _ <- serializer.encode(cacheValue, encodedValue) + _ = (commands.zscore[String](_: String, _: String)(_: ByteStringSerializer[String])) + .expects(cacheKey, encodedValue, *) + .returns(disconnected) + _ <- connector.sortedSetScore(cacheKey, cacheValue).assertingFailure[ExecutionFailedException, SimulatedException] + } yield Passed } - "failed HSET" in new MockedConnector { - serializer.encode(anyString) returns "encoded" - commands.hset[String](anyString, anyString, anyString)(anySerializer) returns disconnected - // run the test - connector.hashSet(key, "field", value) must throwA[ExecutionFailedException].await + test("failed ZREM") { (serializer, commands, connector) => + for { + _ <- serializer.encode(cacheValue, encodedValue) + _ = (commands.zremMock(_: String, _: Seq[String])(_: ByteStringSerializer[String])) + .expects(cacheKey, Seq(encodedValue), *) + .returns(disconnected) + _ <- connector.sortedSetRemove(cacheKey, cacheValue).assertingFailure[ExecutionFailedException, SimulatedException] + } yield Passed } - "failed ZADD" in new MockedConnector { - serializer.encode(anyString) returns "encoded" - commands.zadd[String](anyString, any[(Double, String)])(anySerializer) returns disconnected - // run the test - connector.sortedSetAdd(key, (score, value)) must throwA[ExecutionFailedException].await + test("failed ZRANGE") { (_, commands, connector) => + for { + _ <- (commands.zrange[String](_: String, _: Long, _: Long)(_: ByteStringDeserializer[String])) + .expects(cacheKey, 1, 5, *) + .returns(disconnected) + _ <- connector.sortedSetRange[String](cacheKey, 1, 5).assertingFailure[ExecutionFailedException, SimulatedException] + } yield Passed } - "failed ZCARD" in new MockedConnector { - commands.zcard(anyString) returns disconnected - // run the test - connector.sortedSetSize(key) must throwA[ExecutionFailedException].await + test("failed ZREVRANGE") { (_, commands, connector) => + for { + _ <- (commands.zrevrange[String](_: String, _: Long, _: Long)(_: ByteStringDeserializer[String])) + .expects(cacheKey, 1, 5, *) + .returns(disconnected) + _ <- connector.sortedSetReverseRange[String](cacheKey, 1, 5).assertingFailure[ExecutionFailedException, SimulatedException] + } yield Passed } + } + + private def test(name: String)(f: (SerializerAssertions, RedisCommandsMock, RedisConnector) => Future[Assertion]): Unit = { + name in { + implicit val runtime: RedisRuntime = mock[RedisRuntime] + val serializer = mock[AkkaSerializer] + val commands = mock[RedisCommandsMock] + val connector: RedisConnector = new RedisConnectorImpl(serializer, commands) - "failed ZSCORE" in new MockedConnector { - serializer.encode(anyString) returns "encoded" - commands.zscore[String](anyString, anyString)(anySerializer) returns disconnected - // run the test - connector.sortedSetScore(key, value) must throwA[ExecutionFailedException].await + (() => runtime.context).expects().returns(ExecutionContext.global).anyNumberOfTimes() + + f(new SerializerAssertions(serializer), commands, connector) } + } + + private class SerializerAssertions(mock: AkkaSerializer) { - "failed ZREM" in new MockedConnector { - serializer.encode(anyString) returns "encoded" - commands.zrem[String](anyString, anyString)(anySerializer) returns disconnected - // run the test - connector.sortedSetRemove(key, value) must throwA[ExecutionFailedException].await + def failOnEncode[T](value: T): Future[Unit] = { + Future.successful { + (mock.encode(_: Any)).expects(value).returns(Failure(SimulatedException)) + } } - "failed ZRANGE" in new MockedConnector { - commands.zrange[String](anyString, anyLong, anyLong)(anyDeserializer) returns disconnected - // run the test - connector.sortedSetRange[String](key, 1, 5) must throwA[ExecutionFailedException].await + def encode[T](value: T, encoded: String): Future[Unit] = { + Future.successful { + (mock.encode(_: Any)).expects(value).returns(Success(encoded)) + } } - "failed ZREVRANGE" in new MockedConnector { - commands.zrevrange[String](anyString, anyLong, anyLong)(anyDeserializer) returns disconnected - // run the test - connector.sortedSetReverseRange[String](key, 1, 5) must throwA[ExecutionFailedException].await + def failOnDecode(value: String): Future[Unit] = { + Future.successful { + (mock.decode(_: String)(_: ClassTag[String])).expects(value, *).returns(Failure(SimulatedException)) + } } } + + private trait RedisCommandsMock extends RedisCommands { + + final override def zadd[V: ByteStringSerializer](key: String, scoreMembers: (Double, V)*): Future[Long] = + zaddMock(key, scoreMembers) + + def zaddMock[V: ByteStringSerializer](key: String, scoreMembers: Seq[(Double, V)]): Future[Long] + + override final def zrem[V: ByteStringSerializer](key: String, members: V*): Future[Long] = + zremMock(key, members) + + def zremMock[V: ByteStringSerializer](key: String, members: Seq[V]): Future[Long] + } } diff --git a/src/test/scala/play/api/cache/redis/connector/RedisConnectorSpec.scala b/src/test/scala/play/api/cache/redis/connector/RedisConnectorSpec.scala deleted file mode 100644 index dcbfc156..00000000 --- a/src/test/scala/play/api/cache/redis/connector/RedisConnectorSpec.scala +++ /dev/null @@ -1,511 +0,0 @@ -package play.api.cache.redis.connector - -import org.specs2.concurrent.ExecutionEnv -import org.specs2.mutable.Specification -import play.api.cache.redis._ -import play.api.cache.redis.configuration.{RedisHost, RedisStandalone} -import play.api.cache.redis.impl._ -import play.api.inject.ApplicationLifecycle - -import scala.concurrent.duration._ -import scala.concurrent.{ExecutionContext, Future} - -/** - *

Specification of the low level connector implementing basic commands

- */ -class RedisConnectorSpec(implicit ee: ExecutionEnv) extends Specification with WithApplication with StandaloneRedisContainer { - import Implicits._ - - implicit private val lifecycle: ApplicationLifecycle = application.injector.instanceOf[ApplicationLifecycle] - - implicit private val runtime: RedisRuntime = RedisRuntime("connector", syncTimeout = 5.seconds, ExecutionContext.global, new LogAndFailPolicy, LazyInvocation) - - private val serializer = new AkkaSerializerImpl(system) - - private lazy val connector: RedisConnector = new RedisConnectorProvider( - RedisStandalone(defaultCacheName, RedisHost(container.containerIpAddress, container.mappedPort(defaultPort)), defaults), - serializer - ).get - - val prefix = "connector-test" - - "RedisConnector" should { - - "pong on ping" in new TestCase { - connector.ping() must not(throwA[Throwable]).await - } - - "miss on get" in new TestCase { - connector.get[String](s"$prefix-$idx") must beNone.await - } - - "hit after set" in new TestCase { - connector.set(s"$prefix-$idx", "value") must beTrue.await - connector.get[String](s"$prefix-$idx") must beSome[Any].await - connector.get[String](s"$prefix-$idx") must beSome("value").await - } - - "ignore set if not exists when already defined" in new TestCase { - connector.set(s"$prefix-if-not-exists-when-exists", "previous") must beTrue.await - connector.set(s"$prefix-if-not-exists-when-exists", "value", ifNotExists = true) must beFalse.await - connector.get[String](s"$prefix-if-not-exists-when-exists") must beSome("previous").await - } - - "perform set if not exists when undefined" in new TestCase { - connector.get[String](s"$prefix-if-not-exists") must beNone.await - connector.set(s"$prefix-if-not-exists", "value", ifNotExists = true) must beTrue.await - connector.get[String](s"$prefix-if-not-exists") must beSome("value").await - connector.set(s"$prefix-if-not-exists", "other", ifNotExists = true) must beFalse.await - connector.get[String](s"$prefix-if-not-exists") must beSome("value").await - } - - "perform set if not exists with expiration" in new TestCase { - connector.get[String](s"$prefix-if-not-exists-with-expiration") must beNone.await - connector.set(s"$prefix-if-not-exists-with-expiration", "value", 2.seconds, ifNotExists = true) must beTrue.await - connector.get[String](s"$prefix-if-not-exists-with-expiration") must beSome("value").await - // wait until the first duration expires - Future.after(3) must not(throwA[Throwable]).awaitFor(4.seconds) - connector.get[String](s"$prefix-if-not-exists-with-expiration") must beNone.await - } - - "hit after mset" in new TestCase { - connector.mSet(s"$prefix-mset-$idx-1" -> "value-1", s"$prefix-mset-$idx-2" -> "value-2").awaitForFuture - connector.mGet[String](s"$prefix-mset-$idx-1", s"$prefix-mset-$idx-2", s"$prefix-mset-$idx-3") must beEqualTo(List(Some("value-1"), Some("value-2"), None)).await - connector.mSet(s"$prefix-mset-$idx-3" -> "value-3", s"$prefix-mset-$idx-2" -> null).awaitForFuture - connector.mGet[String](s"$prefix-mset-$idx-1", s"$prefix-mset-$idx-2", s"$prefix-mset-$idx-3") must beEqualTo(List(Some("value-1"), None, Some("value-3"))).await - connector.mSet(s"$prefix-mset-$idx-3" -> null).awaitForFuture - connector.mGet[String](s"$prefix-mset-$idx-1", s"$prefix-mset-$idx-2", s"$prefix-mset-$idx-3") must beEqualTo(List(Some("value-1"), None, None)).await - } - - "ignore msetnx if already defined" in new TestCase { - connector.mSetIfNotExist(s"$prefix-msetnx-$idx-1" -> "value-1", s"$prefix-msetnx-$idx-2" -> "value-2") must beTrue.await - connector.mGet[String](s"$prefix-msetnx-$idx-1", s"$prefix-msetnx-$idx-2") must beEqualTo(List(Some("value-1"), Some("value-2"))).await - connector.mSetIfNotExist(s"$prefix-msetnx-$idx-3" -> "value-3", s"$prefix-msetnx-$idx-2" -> "value-2") must beFalse.await - } - - "expire refreshes expiration" in new TestCase { - connector.set(s"$prefix-$idx", "value", 2.second).await - connector.get[String](s"$prefix-$idx") must beSome("value").await - connector.expire(s"$prefix-$idx", 1.minute).awaitForFuture - // wait until the first duration expires - Future.after(3) must not(throwA[Throwable]).awaitFor(4.seconds) - connector.get[String](s"$prefix-$idx") must beSome("value").await - } - - "expires in returns finite duration" in new TestCase { - connector.set(s"$prefix-$idx", "value", 2.second).await - connector.expiresIn(s"$prefix-$idx") must beSome(beLessThanOrEqualTo(Duration("2 s"))).await - } - - "expires in returns infinite duration" in new TestCase { - connector.set(s"$prefix-$idx", "value").await - connector.expiresIn(s"$prefix-$idx") must beSome(Duration.Inf: Duration).await - } - - "expires in returns not defined key" in new TestCase { - connector.expiresIn(s"$prefix-$idx") must beNone.await - connector.set(s"$prefix-$idx", "value", 1.second).await - connector.expiresIn(s"$prefix-$idx") must beSome[Duration].await - // wait until the first duration expires - Future.after(2) must not(throwA[Throwable]).awaitFor(3.seconds) - connector.expiresIn(s"$prefix-$idx") must beNone.await - } - - "positive exists on existing keys" in new TestCase { - connector.set(s"$prefix-$idx", "value").await - connector.exists(s"$prefix-$idx") must beTrue.await - } - - "negative exists on expired and missing keys" in new TestCase { - connector.set(s"$prefix-$idx-1", "value", 1.second).await - // wait until the duration expires - Future.after(2) must not(throwA[Throwable]).awaitFor(3.seconds) - connector.exists(s"$prefix-$idx-1") must beFalse.await - connector.exists(s"$prefix-$idx-2") must beFalse.await - } - - "miss after remove" in new TestCase { - connector.set(s"$prefix-$idx", "value").await - connector.get[String](s"$prefix-$idx") must beSome[Any].await - connector.remove(s"$prefix-$idx") must not(throwA[Throwable]).await - connector.get[String](s"$prefix-$idx") must beNone.await - } - - "remove on empty key" in new TestCase { - connector.get[String](s"$prefix-$idx-A") must beNone.await - connector.remove(s"$prefix-$idx-A") must not(throwA[Throwable]).await - connector.get[String](s"$prefix-$idx-A") must beNone.await - } - - "remove with empty args" in new TestCase { - val toBeRemoved = List.empty - connector.remove(toBeRemoved: _*) must not(throwA[Throwable]).await - } - - "clear with setting null" in new TestCase { - connector.set(s"$prefix-$idx", "value").await - connector.get[String](s"$prefix-$idx") must beSome[Any].await - connector.set(s"$prefix-$idx", null).await - connector.get[String](s"$prefix-$idx") must beNone.await - } - - "miss after timeout" in new TestCase { - // set - connector.set(s"$prefix-$idx", "value", 1.second).await - connector.get[String](s"$prefix-$idx") must beSome[Any].await - // wait until it expires - Future.after(2) must not(throwA[Throwable]).awaitFor(3.seconds) - // miss - connector.get[String](s"$prefix-$idx") must beNone.await - } - - "find all matching keys" in new TestCase { - connector.set(s"$prefix-$idx-key-A", "value", 3.second).await - connector.set(s"$prefix-$idx-note-A", "value", 3.second).await - connector.set(s"$prefix-$idx-key-B", "value", 3.second).await - connector.matching(s"$prefix-$idx*").map(_.toSet) must beEqualTo(Set(s"$prefix-$idx-key-A", s"$prefix-$idx-note-A", s"$prefix-$idx-key-B")).await - connector.matching(s"$prefix-$idx*A").map(_.toSet) must beEqualTo(Set(s"$prefix-$idx-key-A", s"$prefix-$idx-note-A")).await - connector.matching(s"$prefix-$idx-key-*").map(_.toSet) must beEqualTo(Set(s"$prefix-$idx-key-A", s"$prefix-$idx-key-B")).await - connector.matching(s"$prefix-${idx}A*") must beEqualTo(Seq.empty).await - } - - "remove multiple keys at once" in new TestCase { - connector.set(s"$prefix-remove-multiple-1", "value").await - connector.get[String](s"$prefix-remove-multiple-1") must beSome[Any].await - connector.set(s"$prefix-remove-multiple-2", "value").await - connector.get[String](s"$prefix-remove-multiple-2") must beSome[Any].await - connector.set(s"$prefix-remove-multiple-3", "value").await - connector.get[String](s"$prefix-remove-multiple-3") must beSome[Any].await - connector.remove(s"$prefix-remove-multiple-1", s"$prefix-remove-multiple-2", s"$prefix-remove-multiple-3").awaitForFuture - connector.get[String](s"$prefix-remove-multiple-1") must beNone.await - connector.get[String](s"$prefix-remove-multiple-2") must beNone.await - connector.get[String](s"$prefix-remove-multiple-3") must beNone.await - } - - "remove in batch" in new TestCase { - connector.set(s"$prefix-remove-batch-1", "value").await - connector.get[String](s"$prefix-remove-batch-1") must beSome[Any].await - connector.set(s"$prefix-remove-batch-2", "value").await - connector.get[String](s"$prefix-remove-batch-2") must beSome[Any].await - connector.set(s"$prefix-remove-batch-3", "value").await - connector.get[String](s"$prefix-remove-batch-3") must beSome[Any].await - connector.remove(s"$prefix-remove-batch-1", s"$prefix-remove-batch-2", s"$prefix-remove-batch-3").awaitForFuture - connector.get[String](s"$prefix-remove-batch-1") must beNone.await - connector.get[String](s"$prefix-remove-batch-2") must beNone.await - connector.get[String](s"$prefix-remove-batch-3") must beNone.await - } - - "set a zero when not exists and then increment" in new TestCase { - connector.increment(s"$prefix-incr-null", 1) must beEqualTo(1).await - } - - "throw an exception when not integer" in new TestCase { - connector.set(s"$prefix-incr-string", "value").await - connector.increment(s"$prefix-incr-string", 1) must throwA[ExecutionFailedException].await - } - - "increment by one" in new TestCase { - connector.set(s"$prefix-incr-by-one", 5).await - connector.increment(s"$prefix-incr-by-one", 1) must beEqualTo(6).await - connector.increment(s"$prefix-incr-by-one", 1) must beEqualTo(7).await - connector.increment(s"$prefix-incr-by-one", 1) must beEqualTo(8).await - } - - "increment by some" in new TestCase { - connector.set(s"$prefix-incr-by-some", 5).await - connector.increment(s"$prefix-incr-by-some", 1) must beEqualTo(6).await - connector.increment(s"$prefix-incr-by-some", 2) must beEqualTo(8).await - connector.increment(s"$prefix-incr-by-some", 3) must beEqualTo(11).await - } - - "decrement by one" in new TestCase { - connector.set(s"$prefix-decr-by-one", 5).await - connector.increment(s"$prefix-decr-by-one", -1) must beEqualTo(4).await - connector.increment(s"$prefix-decr-by-one", -1) must beEqualTo(3).await - connector.increment(s"$prefix-decr-by-one", -1) must beEqualTo(2).await - connector.increment(s"$prefix-decr-by-one", -1) must beEqualTo(1).await - connector.increment(s"$prefix-decr-by-one", -1) must beEqualTo(0).await - connector.increment(s"$prefix-decr-by-one", -1) must beEqualTo(-1).await - } - - "decrement by some" in new TestCase { - connector.set(s"$prefix-decr-by-some", 5).await - connector.increment(s"$prefix-decr-by-some", -1) must beEqualTo(4).await - connector.increment(s"$prefix-decr-by-some", -2) must beEqualTo(2).await - connector.increment(s"$prefix-decr-by-some", -3) must beEqualTo(-1).await - } - - "append like set when value is undefined" in new TestCase { - connector.get[String](s"$prefix-append-to-null") must beNone.await - connector.append(s"$prefix-append-to-null", "value").awaitForFuture - connector.get[String](s"$prefix-append-to-null") must beSome("value").await - } - - "append to existing string" in new TestCase { - connector.set(s"$prefix-append-to-some", "some").await - connector.get[String](s"$prefix-append-to-some") must beSome("some").await - connector.append(s"$prefix-append-to-some", " value").awaitForFuture - connector.get[String](s"$prefix-append-to-some") must beSome("some value").await - } - - "list push left" in new TestCase { - connector.listPrepend(s"$prefix-list-prepend", "A", "B", "C") must beEqualTo(3).await - connector.listPrepend(s"$prefix-list-prepend", "D", "E", "F") must beEqualTo(6).await - connector.listSlice[String](s"$prefix-list-prepend", 0, -1) must beEqualTo(List("F", "E", "D", "C", "B", "A")).await - } - - "list push right" in new TestCase { - connector.listAppend(s"$prefix-list-append", "A", "B", "C") must beEqualTo(3).await - connector.listAppend(s"$prefix-list-append", "D", "E", "A") must beEqualTo(6).await - connector.listSlice[String](s"$prefix-list-append", 0, -1) must beEqualTo(List("A", "B", "C", "D", "E", "A")).await - } - - "list size" in new TestCase { - connector.listSize(s"$prefix-list-size") must beEqualTo(0).await - connector.listPrepend(s"$prefix-list-size", "A", "B", "C") must beEqualTo(3).await - connector.listSize(s"$prefix-list-size") must beEqualTo(3).await - } - - "list overwrite at index" in new TestCase { - connector.listPrepend(s"$prefix-list-set", "C", "B", "A") must beEqualTo(3).await - connector.listSetAt(s"$prefix-list-set", 1, "D").awaitForFuture - connector.listSlice[String](s"$prefix-list-set", 0, -1) must beEqualTo(List("A", "D", "C")).await - connector.listSetAt(s"$prefix-list-set", 3, "D") must throwA[IndexOutOfBoundsException].await - } - - "list pop head" in new TestCase { - connector.listHeadPop[String](s"$prefix-list-pop") must beNone.await - connector.listPrepend(s"$prefix-list-pop", "C", "B", "A") must beEqualTo(3).await - connector.listHeadPop[String](s"$prefix-list-pop") must beSome("A").await - connector.listHeadPop[String](s"$prefix-list-pop") must beSome("B").await - connector.listHeadPop[String](s"$prefix-list-pop") must beSome("C").await - connector.listHeadPop[String](s"$prefix-list-pop") must beNone.await - } - - "list slice view" in new TestCase { - connector.listSlice[String](s"$prefix-list-slice", 0, -1) must beEqualTo(List.empty).await - connector.listPrepend(s"$prefix-list-slice", "C", "B", "A") must beEqualTo(3).await - connector.listSlice[String](s"$prefix-list-slice", 0, -1) must beEqualTo(List("A", "B", "C")).await - connector.listSlice[String](s"$prefix-list-slice", 0, 0) must beEqualTo(List("A")).await - connector.listSlice[String](s"$prefix-list-slice", -2, -1) must beEqualTo(List("B", "C")).await - } - - "list remove by value" in new TestCase { - connector.listRemove(s"$prefix-list-remove", "A", count = 1) must beEqualTo(0).await - connector.listPrepend(s"$prefix-list-remove", "A", "B", "C") must beEqualTo(3).await - connector.listRemove(s"$prefix-list-remove", "A", count = 1) must beEqualTo(1).await - connector.listSize(s"$prefix-list-remove") must beEqualTo(2).await - } - - "list trim" in new TestCase { - connector.listPrepend(s"$prefix-list-trim", "C", "B", "A") must beEqualTo(3).await - connector.listTrim(s"$prefix-list-trim", 1, 2).awaitForFuture - connector.listSize(s"$prefix-list-trim") must beEqualTo(2).await - connector.listSlice[String](s"$prefix-list-trim", 0, -1) must beEqualTo(List("B", "C")).await - } - - "list insert" in new TestCase { - connector.listSize(s"$prefix-list-insert-1") must beEqualTo(0).await - connector.listInsert(s"$prefix-list-insert-1", "C", "B") must beNone.await - connector.listPrepend(s"$prefix-list-insert-1", "C", "A") must beEqualTo(2).await - connector.listInsert(s"$prefix-list-insert-1", "C", "B") must beSome(3L).await - connector.listInsert(s"$prefix-list-insert-1", "E", "D") must beNone.await - connector.listSlice[String](s"$prefix-list-insert-1", 0, -1) must beEqualTo(List("A", "B", "C")).await - } - - "list set to invalid type" in new TestCase { - connector.set(s"$prefix-list-invalid-$idx", "value") must not(throwA[Throwable]).await - connector.get[String](s"$prefix-list-invalid-$idx") must beSome("value").await - connector.listPrepend(s"$prefix-list-invalid-$idx", "A") must throwA[IllegalArgumentException].await - connector.listAppend(s"$prefix-list-invalid-$idx", "C", "B") must throwA[IllegalArgumentException].await - connector.listInsert(s"$prefix-list-invalid-$idx", "C", "B") must throwA[IllegalArgumentException].await - } - - "set add" in new TestCase { - connector.setSize(s"$prefix-set-add") must beEqualTo(0).await - connector.setAdd(s"$prefix-set-add", "A", "B") must beEqualTo(2).await - connector.setSize(s"$prefix-set-add") must beEqualTo(2).await - connector.setAdd(s"$prefix-set-add", "C", "B") must beEqualTo(1).await - connector.setSize(s"$prefix-set-add") must beEqualTo(3).await - } - - "set add into invalid type" in new TestCase { - connector.set(s"$prefix-set-invalid-$idx", "value") must not(throwA[Throwable]).await - connector.get[String](s"$prefix-set-invalid-$idx") must beSome("value").await - connector.setAdd(s"$prefix-set-invalid-$idx", "A", "B") must throwA[IllegalArgumentException].await - } - - "set rank" in new TestCase { - connector.setSize(s"$prefix-set-rank") must beEqualTo(0).await - connector.setAdd(s"$prefix-set-rank", "A", "B") must beEqualTo(2).await - connector.setSize(s"$prefix-set-rank") must beEqualTo(2).await - - connector.setIsMember(s"$prefix-set-rank", "A") must beTrue.await - connector.setIsMember(s"$prefix-set-rank", "B") must beTrue.await - connector.setIsMember(s"$prefix-set-rank", "C") must beFalse.await - - connector.setAdd(s"$prefix-set-rank", "C", "B") must beEqualTo(1).await - - connector.setIsMember(s"$prefix-set-rank", "A") must beTrue.await - connector.setIsMember(s"$prefix-set-rank", "B") must beTrue.await - connector.setIsMember(s"$prefix-set-rank", "C") must beTrue.await - } - - "set size" in new TestCase { - connector.setSize(s"$prefix-set-size") must beEqualTo(0).await - connector.setAdd(s"$prefix-set-size", "A", "B") must beEqualTo(2).await - connector.setSize(s"$prefix-set-size") must beEqualTo(2).await - } - - "set rem" in new TestCase { - connector.setSize(s"$prefix-set-rem") must beEqualTo(0).await - connector.setAdd(s"$prefix-set-rem", "A", "B", "C") must beEqualTo(3).await - connector.setSize(s"$prefix-set-rem") must beEqualTo(3).await - - connector.setRemove(s"$prefix-set-rem", "A") must beEqualTo(1).await - connector.setSize(s"$prefix-set-rem") must beEqualTo(2).await - connector.setRemove(s"$prefix-set-rem", "B", "C", "D") must beEqualTo(2).await - connector.setSize(s"$prefix-set-rem") must beEqualTo(0).await - } - - "set slice" in new TestCase { - connector.setSize(s"$prefix-set-slice") must beEqualTo(0).await - connector.setAdd(s"$prefix-set-slice", "A", "B", "C") must beEqualTo(3).await - connector.setSize(s"$prefix-set-slice") must beEqualTo(3).await - - connector.setMembers[String](s"$prefix-set-slice") must beEqualTo(Set("A", "B", "C")).await - - connector.setSize(s"$prefix-set-slice") must beEqualTo(3).await - } - - "hash set values" in new TestCase { - val key = s"$prefix-hash-set" - - connector.hashSize(key) must beEqualTo(0).await - connector.hashGetAll[String](key) must beEqualTo(Map.empty).await - connector.hashKeys(key) must beEqualTo(Set.empty).await - connector.hashValues[String](key) must beEqualTo(Set.empty).await - - connector.hashGet[String](key, "KA") must beNone.await - connector.hashSet(key, "KA", "VA1") must beTrue.await - connector.hashGet[String](key, "KA") must beSome("VA1").await - connector.hashSet(key, "KA", "VA2") must beFalse.await - connector.hashGet[String](key, "KA") must beSome("VA2").await - connector.hashSet(key, "KB", "VB") must beTrue.await - - connector.hashGet[String](key, Seq("KA", "KB", "KC")) must beEqualTo(Seq(Some("VA2"), Some("VB"), None)).await - - connector.hashExists(key, "KB") must beTrue.await - connector.hashExists(key, "KC") must beFalse.await - - connector.hashSize(key) must beEqualTo(2).await - connector.hashGetAll[String](key) must beEqualTo(Map("KA" -> "VA2", "KB" -> "VB")).await - connector.hashKeys(key) must beEqualTo(Set("KA", "KB")).await - connector.hashValues[String](key) must beEqualTo(Set("VA2", "VB")).await - - connector.hashRemove(key, "KB") must beEqualTo(1).await - connector.hashRemove(key, "KC") must beEqualTo(0).await - connector.hashExists(key, "KB") must beFalse.await - connector.hashExists(key, "KA") must beTrue.await - - connector.hashSize(key) must beEqualTo(1).await - connector.hashGetAll[String](key) must beEqualTo(Map("KA" -> "VA2")).await - connector.hashKeys(key) must beEqualTo(Set("KA")).await - connector.hashValues[String](key) must beEqualTo(Set("VA2")).await - - connector.hashSet(key, "KD", 5) must beTrue.await - connector.hashIncrement(key, "KD", 2) must beEqualTo(7).await - connector.hashGet[Int](key, "KD") must beSome(7).await - } - - "hash set into invalid type" in new TestCase { - connector.set(s"$prefix-hash-invalid-$idx", "value") must not(throwA[Throwable]).await - connector.get[String](s"$prefix-hash-invalid-$idx") must beSome("value").await - connector.hashSet(s"$prefix-hash-invalid-$idx", "KA", "VA1") must throwA[IllegalArgumentException].await - } - - "sorted set add" in new TestCase { - connector.sortedSetAdd(s"$prefix-sorted-set-add", (1, "A")) must beEqualTo(1).await - connector.sortedSetSize(s"$prefix-sorted-set-add") must beEqualTo(1).await - connector.sortedSetSize(s"$prefix-sorted-set-add") must beEqualTo(1).await - connector.sortedSetAdd(s"$prefix-sorted-set-add", (2, "B"), (3, "C")) must beEqualTo(2).await - connector.sortedSetSize(s"$prefix-sorted-set-add") must beEqualTo(3).await - connector.sortedSetAdd(s"$prefix-sorted-set-add", (1, "A")) must beEqualTo(0).await - connector.sortedSetSize(s"$prefix-sorted-set-add") must beEqualTo(3).await - } - - "sorted set add invalid type" in new TestCase { - connector.set(s"$prefix-sorted-set-invalid-$idx", "value") must not(throwA[Throwable]).await - connector.get[String](s"$prefix-sorted-set-invalid-$idx") must beSome("value").await - connector.sortedSetAdd(s"$prefix-sorted-set-invalid-$idx", 1D -> "VA1") must throwA[IllegalArgumentException].await - } - - "sorted set score" in new TestCase { - connector.sortedSetSize(s"$prefix-sorted-set-score") must beEqualTo(0).await - connector.sortedSetAdd(s"$prefix-sorted-set-score", 1D -> "A", 3D -> "B") must beEqualTo(2).await - connector.sortedSetSize(s"$prefix-sorted-set-score") must beEqualTo(2).await - - connector.sortedSetScore(s"$prefix-sorted-set-score", "A") must beSome(1D).await - connector.sortedSetScore(s"$prefix-sorted-set-score", "B") must beSome(3D).await - connector.sortedSetScore(s"$prefix-sorted-set-score", "C") must beNone.await - - connector.sortedSetAdd(s"$prefix-sorted-set-score", 2D -> "C", 4D -> "B") must beEqualTo(1).await - - connector.sortedSetScore(s"$prefix-sorted-set-score", "A") must beSome(1D).await - connector.sortedSetScore(s"$prefix-sorted-set-score", "B") must beSome(4D).await - connector.sortedSetScore(s"$prefix-sorted-set-score", "C") must beSome(2D).await - } - - "sorted set size" in new TestCase { - connector.sortedSetSize(s"$prefix-sorted-set-size") must beEqualTo(0).await - connector.sortedSetAdd(s"$prefix-sorted-set-size", (1, "A"), (2, "B")) must beEqualTo(2).await - connector.sortedSetSize(s"$prefix-sorted-set-size") must beEqualTo(2).await - } - - "sorted set remove" in new TestCase { - connector.sortedSetSize(s"$prefix-sorted-set-rem") must beEqualTo(0).await - connector.sortedSetAdd(s"$prefix-sorted-set-rem", 1D -> "A", 2D -> "B", 3D -> "C") must beEqualTo(3).await - connector.sortedSetSize(s"$prefix-sorted-set-rem") must beEqualTo(3).await - - connector.sortedSetRemove(s"$prefix-sorted-set-rem", "A") must beEqualTo(1).await - connector.sortedSetSize(s"$prefix-sorted-set-rem") must beEqualTo(2).await - connector.sortedSetRemove(s"$prefix-sorted-set-rem", "B", "C", "D") must beEqualTo(2).await - connector.sortedSetSize(s"$prefix-sorted-set-rem") must beEqualTo(0).await - } - - "sorted set range" in new TestCase { - connector.sortedSetSize(s"$prefix-sorted-set-range") must beEqualTo(0).await - connector.sortedSetAdd(s"$prefix-sorted-set-range", 1D -> "A", 2D -> "B", 4D -> "C") must beEqualTo(3).await - connector.sortedSetSize(s"$prefix-sorted-set-range") must beEqualTo(3).await - - connector.sortedSetRange[String](s"$prefix-sorted-set-range", 0, 1) must beEqualTo(Vector("A", "B")).await - connector.sortedSetRange[String](s"$prefix-sorted-set-range", 0, 4) must beEqualTo(Vector("A", "B", "C")).await - connector.sortedSetRange[String](s"$prefix-sorted-set-range", 1, 9) must beEqualTo(Vector("B", "C")).await - - connector.sortedSetSize(s"$prefix-sorted-set-range") must beEqualTo(3).await - } - - "sorted set reverse range" in new TestCase { - connector.sortedSetSize(s"$prefix-sorted-set-reverse-range") must beEqualTo(0).await - connector.sortedSetAdd(s"$prefix-sorted-set-reverse-range", 1D -> "A", 2D -> "B", 4D -> "C") must beEqualTo(3).await - connector.sortedSetSize(s"$prefix-sorted-set-reverse-range") must beEqualTo(3).await - - connector.sortedSetReverseRange[String](s"$prefix-sorted-set-reverse-range", 0, 1) must beEqualTo(Vector("C", "B")).await - connector.sortedSetReverseRange[String](s"$prefix-sorted-set-reverse-range", 0, 4) must beEqualTo(Vector("C", "B", "A")).await - connector.sortedSetReverseRange[String](s"$prefix-sorted-set-reverse-range", 1, 9) must beEqualTo(Vector("B", "A")).await - - connector.sortedSetSize(s"$prefix-sorted-set-reverse-range") must beEqualTo(3).await - } - } - - override def beforeAll(): Unit = { - super.beforeAll() - // initialize the connector by flushing the database - connector.matching(s"$prefix-*").flatMap(connector.remove).awaitForFuture - } - - override def afterAll(): Unit = { - Shutdown.run.awaitForFuture - super.afterAll() - } -} diff --git a/src/test/scala/play/api/cache/redis/connector/RedisRequestTimeoutSpec.scala b/src/test/scala/play/api/cache/redis/connector/RedisRequestTimeoutSpec.scala index 93037280..dfb0c659 100644 --- a/src/test/scala/play/api/cache/redis/connector/RedisRequestTimeoutSpec.scala +++ b/src/test/scala/play/api/cache/redis/connector/RedisRequestTimeoutSpec.scala @@ -1,48 +1,46 @@ package play.api.cache.redis.connector -import scala.concurrent.Future -import scala.concurrent.duration._ - -import play.api.cache.redis._ +import akka.actor.{ActorSystem, Scheduler} +import play.api.cache.redis.test.{AsyncUnitSpec, StoppableApplication} +import redis.RedisCommand +import redis.protocol.RedisReply -import akka.actor.ActorSystem -import org.specs2.concurrent.ExecutionEnv -import org.specs2.mutable.Specification +import scala.concurrent.duration._ +import scala.concurrent.{ExecutionContextExecutor, Future, Promise} -class RedisRequestTimeoutSpec(implicit ee: ExecutionEnv) extends Specification with WithApplication { +class RedisRequestTimeoutSpec extends AsyncUnitSpec { - import Implicits._ - import MockitoImplicits._ - import RedisRequestTimeoutSpec._ + override protected def testTimeout: FiniteDuration = 3.seconds - "RedisRequestTimeout" should { + "fail long running requests when connected but timeout defined" in { + implicit val system: ActorSystem = ActorSystem("test") + val application = StoppableApplication(system) - "fail long running requests when connected but timeout defined" in { - val impl = new RedisRequestTimeoutImpl(timeout = 1.second) - val cmd = mock[RedisCommandTest].returning returns Future.after(seconds = 3, "response") + application.runAsyncInApplication { + val redisCommandMock = mock[RedisCommandTest[String]] + (() => redisCommandMock.returning).expects().returns(Promise[String]().future) + val redisRequest = new RedisRequestTimeoutImpl(timeout = Some(1.second)) // run the test - impl.send[String](cmd) must throwA[redis.actors.NoConnectionException.type].awaitFor(5.seconds) + redisRequest.send[String](redisCommandMock).assertingFailure[redis.actors.NoConnectionException.type] } } -} - -object RedisRequestTimeoutSpec { - - import redis.RedisCommand - import redis.protocol.RedisReply - trait RedisCommandTest extends RedisCommand[RedisReply, String] { - def returning: Future[String] + private trait RedisCommandTest[T] extends RedisCommand[RedisReply, T] { + def returning: Future[T] } - class RequestTimeoutBase(implicit system: ActorSystem) extends RequestTimeout { - protected implicit val scheduler = system.scheduler - implicit val executionContext = system.dispatcher + private class RequestTimeoutBase(implicit system: ActorSystem) extends RequestTimeout { + protected implicit val scheduler: Scheduler = system.scheduler + implicit val executionContext: ExecutionContextExecutor = system.dispatcher - def send[T](redisCommand: RedisCommand[_ <: RedisReply, T]) = { - redisCommand.asInstanceOf[RedisCommandTest].returning.asInstanceOf[Future[T]] + def send[T](redisCommand: RedisCommand[_ <: RedisReply, T]): Future[T] = { + redisCommand.asInstanceOf[RedisCommandTest[T]].returning } } - class RedisRequestTimeoutImpl(val timeout: Option[FiniteDuration])(implicit system: ActorSystem) extends RequestTimeoutBase with RedisRequestTimeout + private class RedisRequestTimeoutImpl( + override val timeout: Option[FiniteDuration] + )(implicit + system: ActorSystem + ) extends RequestTimeoutBase with RedisRequestTimeout } diff --git a/src/test/scala/play/api/cache/redis/connector/RedisSentinelSpec.scala b/src/test/scala/play/api/cache/redis/connector/RedisSentinelSpec.scala index 6532f101..6f73d0d7 100644 --- a/src/test/scala/play/api/cache/redis/connector/RedisSentinelSpec.scala +++ b/src/test/scala/play/api/cache/redis/connector/RedisSentinelSpec.scala @@ -1,83 +1,95 @@ package play.api.cache.redis.connector -import org.specs2.concurrent.ExecutionEnv -import org.specs2.mutable.Specification -import org.specs2.specification.{AfterAll, BeforeAll} +import akka.actor.ActorSystem +import org.scalatest.Ignore import play.api.cache.redis._ -import play.api.cache.redis.configuration.{RedisHost, RedisSentinel} +import play.api.cache.redis.configuration._ import play.api.cache.redis.impl._ -import play.api.inject.ApplicationLifecycle +import play.api.cache.redis.test._ import scala.concurrent.duration._ import scala.concurrent.{ExecutionContext, Future} -/** - *

Specification of the low level connector implementing basic commands

- */ -class RedisSentinelSpec(implicit ee: ExecutionEnv) extends Specification with BeforeAll with AfterAll with WithApplication { +@Ignore +class RedisSentinelSpec extends IntegrationSpec with RedisSentinelContainer { - args(skipAll=true) - - import Implicits._ - - implicit private val lifecycle: ApplicationLifecycle = application.injector.instanceOf[ApplicationLifecycle] - - implicit private val runtime: RedisRuntime = RedisRuntime("sentinel", syncTimeout = 5.seconds, ExecutionContext.global, new LogAndFailPolicy, LazyInvocation) - - private val serializer = new AkkaSerializerImpl(system) - - private lazy val sentinelInstance = RedisSentinel(defaultCacheName, masterGroup = "sentinel5000", sentinels = RedisHost(dockerIp, 5000) :: RedisHost(dockerIp, 5001) :: RedisHost(dockerIp, 5002) :: Nil, defaults) - - private lazy val connector: RedisConnector = new RedisConnectorProvider(sentinelInstance, serializer).get - - val prefix = "sentinel-test" - - "Redis sentinel (separate)" should { - - "pong on ping" in new TestCase { - connector.ping() must not(throwA[Throwable]).await - } - - "miss on get" in new TestCase { - connector.get[String](s"$prefix-$idx") must beNone.await - } + test("pong on ping") { connector => + for { + _ <- connector.ping().assertingSuccess + } yield Passed + } - "hit after set" in new TestCase { - connector.set(s"$prefix-$idx", "value") must beTrue.await - connector.get[String](s"$prefix-$idx") must beSome[Any].await - connector.get[String](s"$prefix-$idx") must beSome("value").await - } + test("miss on get") { connector => + for { + _ <- connector.get[String]("miss-on-get").assertingEqual(None) + } yield Passed + } - "ignore set if not exists when already defined" in new TestCase { - connector.set(s"$prefix-if-not-exists-when-exists", "previous") must beTrue.await - connector.set(s"$prefix-if-not-exists-when-exists", "value", ifNotExists = true) must beFalse.await - connector.get[String](s"$prefix-if-not-exists-when-exists") must beSome("previous").await - } + test("hit after set") { connector => + for { + _ <- connector.set("hit-after-set", "value").assertingEqual(true) + _ <- connector.get[String]("hit-after-set").assertingEqual(Some("value")) + } yield Passed + } - "perform set if not exists when undefined" in new TestCase { - connector.get[String](s"$prefix-if-not-exists") must beNone.await - connector.set(s"$prefix-if-not-exists", "value", ifNotExists = true) must beTrue.await - connector.get[String](s"$prefix-if-not-exists") must beSome("value").await - connector.set(s"$prefix-if-not-exists", "other", ifNotExists = true) must beFalse.await - connector.get[String](s"$prefix-if-not-exists") must beSome("value").await - } + test("ignore set if not exists when already defined") { connector => + for { + _ <- connector.set("if-not-exists-when-exists", "previous").assertingEqual(true) + _ <- connector.set("if-not-exists-when-exists", "value", ifNotExists = true).assertingEqual(false) + _ <- connector.get[String]("if-not-exists-when-exists").assertingEqual(Some("previous")) + } yield Passed + } - "perform set if not exists with expiration" in new TestCase { - connector.get[String](s"$prefix-if-not-exists-with-expiration") must beNone.await - connector.set(s"$prefix-if-not-exists-with-expiration", "value", 2.seconds, ifNotExists = true) must beTrue.await - connector.get[String](s"$prefix-if-not-exists-with-expiration") must beSome("value").await - // wait until the first duration expires - Future.after(3) must not(throwA[Throwable]).awaitFor(4.seconds) - connector.get[String](s"$prefix-if-not-exists-with-expiration") must beNone.await - } + test("perform set if not exists when undefined") { connector => + for { + _ <- connector.get[String]("if-not-exists").assertingEqual(None) + _ <- connector.set("if-not-exists", "value", ifNotExists = true).assertingEqual(true) + _ <- connector.get[String]("if-not-exists").assertingEqual(Some("value")) + _ <- connector.set("if-not-exists", "other", ifNotExists = true).assertingEqual(false) + _ <- connector.get[String]("if-not-exists").assertingEqual(Some("value")) + } yield Passed } - def beforeAll(): Unit = { - // initialize the connector by flushing the database - connector.matching(s"$prefix-*").flatMap(connector.remove).awaitForFuture + test("perform set if not exists with expiration") { connector => + for { + _ <- connector.get[String]("if-not-exists-with-expiration").assertingEqual(None) + _ <- connector.set("if-not-exists-with-expiration", "value", 300.millis, ifNotExists = true).assertingEqual(true) + _ <- connector.get[String]("if-not-exists-with-expiration").assertingEqual(Some("value")) + // wait until the first duration expires + _ <- Future.after(700.millis, ()) + _ <- connector.get[String]("if-not-exists-with-expiration").assertingEqual(None) + } yield Passed } - def afterAll(): Unit = { - Shutdown.run + def test(name: String)(f: RedisConnector => Future[Assertion]): Unit = { + name in { + implicit val system: ActorSystem = ActorSystem("test", classLoader = Some(getClass.getClassLoader)) + implicit val runtime: RedisRuntime = RedisRuntime("sentinel", syncTimeout = 5.seconds, ExecutionContext.global, new LogAndFailPolicy, LazyInvocation) + implicit val application: StoppableApplication = StoppableApplication(system) + val serializer = new AkkaSerializerImpl(system) + + lazy val sentinelInstance = RedisSentinel( + name = "sentinel", + masterGroup = master, + sentinels = 0.until(nodes).map { i => + RedisHost(container.containerIpAddress, container.mappedPort(sentinelPort + i)) + }.toList, + settings = RedisSettings.load( + config = Helpers.configuration.default.underlying, + path = "play.cache.redis" + ) + ) + + application.runAsyncInApplication { + val connector: RedisConnector = new RedisConnectorProvider(sentinelInstance, serializer).get + for { + // initialize the connector by flushing the database + keys <- connector.matching("*") + _ <- Future.sequence(keys.map(connector.remove(_))) + // run the test + _ <- f(connector) + } yield Passed + } + } } } diff --git a/src/test/scala/play/api/cache/redis/connector/RedisStandaloneSpec.scala b/src/test/scala/play/api/cache/redis/connector/RedisStandaloneSpec.scala new file mode 100644 index 00000000..b37ee667 --- /dev/null +++ b/src/test/scala/play/api/cache/redis/connector/RedisStandaloneSpec.scala @@ -0,0 +1,618 @@ +package play.api.cache.redis.connector + +import akka.actor.ActorSystem +import play.api.cache.redis._ +import play.api.cache.redis.configuration._ +import play.api.cache.redis.impl._ +import play.api.cache.redis.test._ + +import scala.concurrent.duration._ +import scala.concurrent.{ExecutionContext, Future} + +class RedisStandaloneSpec extends IntegrationSpec with RedisStandaloneContainer { + + test("pong on ping") { (_, connector) => + for { + _ <- connector.ping().assertingSuccess + } yield Passed + } + + test("miss on get") { (cacheKey, connector) => + for { + _ <- connector.get[String](cacheKey).assertingEqual(None) + } yield Passed + } + + test("hit after set") { (cacheKey, connector) => + for { + _ <- connector.set(cacheKey, "value").assertingEqual(true) + _ <- connector.get[String](cacheKey).assertingEqual(Some("value")) + } yield Passed + } + + test("ignore set if not exists when already defined") { (cacheKey, connector) => + for { + _ <- connector.set(cacheKey, "previous").assertingEqual(true) + _ <- connector.set(cacheKey, "value", ifNotExists = true).assertingEqual(false) + _ <- connector.get[String](cacheKey).assertingEqual(Some("previous")) + } yield Passed + } + + test("perform set if not exists when undefined") { (cacheKey, connector) => + for { + _ <- connector.get[String](cacheKey).assertingEqual(None) + _ <- connector.set(cacheKey, "value", ifNotExists = true).assertingEqual(true) + _ <- connector.get[String](cacheKey).assertingEqual(Some("value")) + _ <- connector.set(cacheKey, "other", ifNotExists = true).assertingEqual(false) + _ <- connector.get[String](cacheKey).assertingEqual(Some("value")) + } yield Passed + } + + test("perform set if not exists with expiration") { (cacheKey, connector) => + for { + _ <- connector.get[String](cacheKey).assertingEqual(None) + _ <- connector.set(cacheKey, "value", 300.millis, ifNotExists = true).assertingEqual(true) + _ <- connector.get[String](cacheKey).assertingEqual(Some("value")) + // wait until the first duration expires + _ <- Future.waitFor(400.millis) + _ <- connector.get[String](cacheKey).assertingEqual(None) + } yield Passed + } + + test("hit after mset") { (cacheKey, connector) => + for { + _ <- connector.mSet(s"$cacheKey-1" -> "value-1", s"$cacheKey-2" -> "value-2") + _ <- connector.mGet[String](s"$cacheKey-1", s"$cacheKey-2", s"$cacheKey-3").assertingEqual(List(Some("value-1"), Some("value-2"), None)) + _ <- connector.mSet(s"$cacheKey-3" -> "value-3", s"$cacheKey-2" -> null) + _ <- connector.mGet[String](s"$cacheKey-1", s"$cacheKey-2", s"$cacheKey-3").assertingEqual(List(Some("value-1"), None, Some("value-3"))) + _ <- connector.mSet(s"$cacheKey-3" -> null) + _ <- connector.mGet[String](s"$cacheKey-1", s"$cacheKey-2", s"$cacheKey-3").assertingEqual(List(Some("value-1"), None, None)) + } yield Passed + } + + test("ignore msetnx if already defined") { (cacheKey, connector) => + for { + _ <- connector.mSetIfNotExist(s"$cacheKey-1" -> "value-1", s"$cacheKey-2" -> "value-2").assertingEqual(true) + _ <- connector.mGet[String](s"$cacheKey-1", s"$cacheKey-2").assertingEqual(List(Some("value-1"), Some("value-2"))) + _ <- connector.mSetIfNotExist(s"$cacheKey-3" -> "value-3", s"$cacheKey-2" -> "value-2").assertingEqual(false) + } yield Passed + } + + test("expire refreshes expiration") { (cacheKey, connector) => + for { + _ <- connector.set(cacheKey, "value", 200.millis) + _ <- connector.get[String](cacheKey).assertingEqual(Some("value")) + _ <- connector.expire(cacheKey, 1000.millis) + // wait until the first duration expires + _ <- Future.waitFor(300.millis) + _ <- connector.get[String](cacheKey).assertingEqual(Some("value")) + } yield Passed + } + + test("expires in returns finite duration") { (cacheKey, connector) => + for { + _ <- connector.set(cacheKey, "value", 2.second) + _ <- connector.expiresIn(cacheKey).assertingCondition(_.exists(_ <= 2.seconds)) + } yield Passed + } + + test("expires in returns infinite duration") { (cacheKey, connector) => + for { + _ <- connector.set(cacheKey, "value") + _ <- connector.expiresIn(cacheKey).assertingEqual(Some(Duration.Inf)) + } yield Passed + } + + test("expires in returns not defined key") { (cacheKey, connector) => + for { + _ <- connector.expiresIn(cacheKey).assertingEqual(None) + _ <- connector.set(cacheKey, "value", 200.millis) + _ <- connector.expiresIn(cacheKey).assertingCondition(_.exists(_ <= 200.millis)) + // wait until the first duration expires + _ <- Future.waitFor(300.millis) + _ <- connector.expiresIn(cacheKey).assertingEqual(None) + } yield Passed + } + + test("positive exists on existing keys") { (cacheKey, connector) => + for { + _ <- connector.set(cacheKey, "value") + _ <- connector.exists(cacheKey).assertingEqual(true) + } yield Passed + } + + test("negative exists on expired and missing keys") { (cacheKey, connector) => + for { + _ <- connector.set(s"$cacheKey-1", "value", 200.millis) + _ <- connector.exists(s"$cacheKey-1").assertingEqual(true) + // wait until the duration expires + _ <- Future.waitFor(250.millis) + _ <- connector.exists(s"$cacheKey-1").assertingEqual(false) + _ <- connector.exists(s"$cacheKey-2").assertingEqual(false) + } yield Passed + } + + test("miss after remove") { (cacheKey, connector) => + for { + _ <- connector.set(cacheKey, "value") + _ <- connector.get[String](cacheKey).assertingEqual(Some("value")) + _ <- connector.remove(cacheKey).assertingSuccess + _ <- connector.get[String](cacheKey).assertingEqual(None) + } yield Passed + } + + test("remove on empty key") { (cacheKey, connector) => + for { + _ <- connector.get[String](cacheKey).assertingEqual(None) + _ <- connector.remove(cacheKey).assertingSuccess + _ <- connector.get[String](cacheKey).assertingEqual(None) + } yield Passed + } + + test("remove with empty args") { (_, connector) => + for { + _ <- connector.remove(List.empty: _*).assertingSuccess + } yield Passed + } + + test("clear with setting null") { (cacheKey, connector) => + for { + _ <- connector.set(cacheKey, "value") + _ <- connector.get[String](cacheKey).assertingEqual(Some("value")) + _ <- connector.set(cacheKey, null) + _ <- connector.get[String](cacheKey).assertingEqual(None) + } yield Passed + } + + test("miss after timeout") { (cacheKey, connector) => + for { + // set + _ <- connector.set(cacheKey, "value", 200.millis) + _ <- connector.get[String](cacheKey).assertingEqual(Some("value")) + // wait until it expires + _ <- Future.waitFor(250.millis) + // miss + _ <- connector.get[String](cacheKey).assertingEqual(None) + } yield Passed + } + + test("find all matching keys") { (cacheKey, connector) => + for { + _ <- connector.set(s"$cacheKey-key-A", "value", 3.second) + _ <- connector.set(s"$cacheKey-note-A", "value", 3.second) + _ <- connector.set(s"$cacheKey-key-B", "value", 3.second) + _ <- connector.matching(s"$cacheKey-*").map(_.toSet).assertingEqual(Set(s"$cacheKey-key-A", s"$cacheKey-note-A", s"$cacheKey-key-B")) + _ <- connector.matching(s"$cacheKey-*A").map(_.toSet).assertingEqual(Set(s"$cacheKey-key-A", s"$cacheKey-note-A")) + _ <- connector.matching(s"$cacheKey-key-*").map(_.toSet).assertingEqual(Set(s"$cacheKey-key-A", s"$cacheKey-key-B")) + _ <- connector.matching(s"$cacheKey-* A * ").assertingEqual(Seq.empty) + } + yield Passed + } + + test("remove multiple keys at once") { (cacheKey, connector) => + for { + _ <- connector.set(s"$cacheKey-1", "value") + _ <- connector.get[String](s"$cacheKey-1").assertingEqual(Some("value")) + _ <- connector.set(s"$cacheKey-2", "value") + _ <- connector.get[String](s"$cacheKey-2").assertingEqual(Some("value")) + _ <- connector.set(s"$cacheKey-3", "value") + _ <- connector.get[String](s"$cacheKey-3").assertingEqual(Some("value")) + _ <- connector.remove(s"$cacheKey-1", s"$cacheKey-2", s"$cacheKey-3") + _ <- connector.get[String](s"$cacheKey-1").assertingEqual(None) + _ <- connector.get[String](s"$cacheKey-2").assertingEqual(None) + _ <- connector.get[String](s"$cacheKey-3").assertingEqual(None) + } yield Passed + } + + test("remove in batch") { (cacheKey, connector) => + for { + _ <- connector.set(s"$cacheKey-1", "value") + _ <- connector.get[String](s"$cacheKey-1").assertingEqual(Some("value")) + _ <- connector.set(s"$cacheKey-2", "value") + _ <- connector.get[String](s"$cacheKey-2").assertingEqual(Some("value")) + _ <- connector.set(s"$cacheKey-3", "value") + _ <- connector.get[String](s"$cacheKey-3").assertingEqual(Some("value")) + _ <- connector.remove(s"$cacheKey-1", s"$cacheKey-2", s"$cacheKey-3") + _ <- connector.get[String](s"$cacheKey-1").assertingEqual(None) + _ <- connector.get[String](s"$cacheKey-2").assertingEqual(None) + _ <- connector.get[String](s"$cacheKey-3").assertingEqual(None) + } yield Passed + } + + test("set a zero when not exists and then increment") { (cacheKey, connector) => + for { + _ <- connector.increment(cacheKey, 1).assertingEqual(1) + } yield Passed + } + + test("throw an exception when not integer") { (cacheKey, connector) => + for { + _ <- connector.set(cacheKey, "value") + _ <- connector.increment(cacheKey, 1).assertingFailure[ExecutionFailedException] + } yield Passed + } + + test("increment by one") { (cacheKey, connector) => + for { + _ <- connector.set(cacheKey, 5) + _ <- connector.increment(cacheKey, 1).assertingEqual(6) + _ <- connector.increment(cacheKey, 1).assertingEqual(7) + _ <- connector.increment(cacheKey, 1).assertingEqual(8) + } yield Passed + } + + test("increment by some") { (cacheKey, connector) => + for { + _ <- connector.set(cacheKey, 5) + _ <- connector.increment(cacheKey, 1).assertingEqual(6) + _ <- connector.increment(cacheKey, 2).assertingEqual(8) + _ <- connector.increment(cacheKey, 3).assertingEqual(11) + } yield Passed + } + + test("decrement by one") { (cacheKey, connector) => + for { + _ <- connector.set(cacheKey, 5) + _ <- connector.increment(cacheKey, -1).assertingEqual(4) + _ <- connector.increment(cacheKey, -1).assertingEqual(3) + _ <- connector.increment(cacheKey, -1).assertingEqual(2) + _ <- connector.increment(cacheKey, -1).assertingEqual(1) + _ <- connector.increment(cacheKey, -1).assertingEqual(0) + _ <- connector.increment(cacheKey, -1).assertingEqual(-1) + } yield Passed + } + + test("decrement by some") { (cacheKey, connector) => + for { + _ <- connector.set(cacheKey, 5) + _ <- connector.increment(cacheKey, -1).assertingEqual(4) + _ <- connector.increment(cacheKey, -2).assertingEqual(2) + _ <- connector.increment(cacheKey, -3).assertingEqual(-1) + } yield Passed + } + + test("append like set when value is undefined") { (cacheKey, connector) => + for { + _ <- connector.get[String](cacheKey).assertingEqual(None) + _ <- connector.append(cacheKey, "value") + _ <- connector.get[String](cacheKey).assertingEqual(Some("value")) + } yield Passed + } + + test("append to existing string") { (cacheKey, connector) => + for { + _ <- connector.set(cacheKey, "some") + _ <- connector.get[String](cacheKey).assertingEqual(Some("some")) + _ <- connector.append(cacheKey, " value") + _ <- connector.get[String](cacheKey).assertingEqual(Some("some value")) + } yield Passed + } + + test("list push left") { (cacheKey, connector) => + for { + _ <- connector.listPrepend(cacheKey, "A", "B", "C").assertingEqual(3) + _ <- connector.listPrepend(cacheKey, "D", "E", "F").assertingEqual(6) + _ <- connector.listSlice[String](cacheKey, 0, -1).assertingEqual(List("F", "E", "D", "C", "B", "A")) + } yield Passed + } + + test("list push right") { (cacheKey, connector) => + for { + _ <- connector.listAppend(cacheKey, "A", "B", "C").assertingEqual(3) + _ <- connector.listAppend(cacheKey, "D", "E", "A").assertingEqual(6) + _ <- connector.listSlice[String](cacheKey, 0, -1).assertingEqual(List("A", "B", "C", "D", "E", "A")) + } yield Passed + } + + test("list size") { (cacheKey, connector) => + for { + _ <- connector.listSize(cacheKey).assertingEqual(0) + _ <- connector.listPrepend(cacheKey, "A", "B", "C").assertingEqual(3) + _ <- connector.listSize(cacheKey).assertingEqual(3) + } yield Passed + } + + test("list overwrite at index") { (cacheKey, connector) => + for { + _ <- connector.listPrepend(cacheKey, "C", "B", "A").assertingEqual(3) + _ <- connector.listSetAt(cacheKey, 1, "D") + _ <- connector.listSlice[String](cacheKey, 0, -1).assertingEqual(List("A", "D", "C")) + _ <- connector.listSetAt(cacheKey, 3, "D").assertingFailure[IndexOutOfBoundsException] + } yield Passed + } + + test("list pop head") { (cacheKey, connector) => + for { + _ <- connector.listHeadPop[String](cacheKey).assertingEqual(None) + _ <- connector.listPrepend(cacheKey, "C", "B", "A").assertingEqual(3) + _ <- connector.listHeadPop[String](cacheKey).assertingEqual(Some("A")) + _ <- connector.listHeadPop[String](cacheKey).assertingEqual(Some("B")) + _ <- connector.listHeadPop[String](cacheKey).assertingEqual(Some("C")) + _ <- connector.listHeadPop[String](cacheKey).assertingEqual(None) + } yield Passed + } + + test("list slice view") { (cacheKey, connector) => + for { + _ <- connector.listSlice[String](cacheKey, 0, -1).assertingEqual(List.empty) + _ <- connector.listPrepend(cacheKey, "C", "B", "A").assertingEqual(3) + _ <- connector.listSlice[String](cacheKey, 0, -1).assertingEqual(List("A", "B", "C")) + _ <- connector.listSlice[String](cacheKey, 0, 0).assertingEqual(List("A")) + _ <- connector.listSlice[String](cacheKey, -2, -1).assertingEqual(List("B", "C")) + } yield Passed + } + + test("list remove by value") { (cacheKey, connector) => + for { + _ <- connector.listRemove(cacheKey, "A", count = 1).assertingEqual(0) + _ <- connector.listPrepend(cacheKey, "A", "B", "C").assertingEqual(3) + _ <- connector.listRemove(cacheKey, "A", count = 1).assertingEqual(1) + _ <- connector.listSize(cacheKey).assertingEqual(2) + } yield Passed + } + + test("list trim") { (cacheKey, connector) => + for { + _ <- connector.listPrepend(cacheKey, "C", "B", "A").assertingEqual(3) + _ <- connector.listTrim(cacheKey, 1, 2) + _ <- connector.listSize(cacheKey).assertingEqual(2) + _ <- connector.listSlice[String](cacheKey, 0, -1).assertingEqual(List("B", "C")) + } yield Passed + } + + test("list insert") { (cacheKey, connector) => + for { + _ <- connector.listSize(cacheKey).assertingEqual(0) + _ <- connector.listInsert(cacheKey, "C", "B").assertingEqual(None) + _ <- connector.listPrepend(cacheKey, "C", "A").assertingEqual(2) + _ <- connector.listInsert(cacheKey, "C", "B").assertingEqual(Some(3L)) + _ <- connector.listInsert(cacheKey, "E", "D").assertingEqual(None) + _ <- connector.listSlice[String](cacheKey, 0, -1).assertingEqual(List("A", "B", "C")) + } yield Passed + } + + test("list set to invalid type") { (cacheKey, connector) => + for { + _ <- connector.set(cacheKey, "value").assertingSuccess + _ <- connector.get[String](cacheKey).assertingEqual(Some("value")) + _ <- connector.listPrepend(cacheKey, "A").assertingFailure[IllegalArgumentException] + _ <- connector.listAppend(cacheKey, "C", "B").assertingFailure[IllegalArgumentException] + _ <- connector.listInsert(cacheKey, "C", "B").assertingFailure[IllegalArgumentException] + } yield Passed + } + + test("set add") { (cacheKey, connector) => + for { + _ <- connector.setSize(cacheKey).assertingEqual(0) + _ <- connector.setAdd(cacheKey, "A", "B").assertingEqual(2) + _ <- connector.setSize(cacheKey).assertingEqual(2) + _ <- connector.setAdd(cacheKey, "C", "B").assertingEqual(1) + _ <- connector.setSize(cacheKey).assertingEqual(3) + } yield Passed + } + + test("set add into invalid type") { (cacheKey, connector) => + for { + _ <- connector.set(cacheKey, "value").assertingSuccess + _ <- connector.get[String](cacheKey).assertingEqual(Some("value")) + _ <- connector.setAdd(cacheKey, "A", "B").assertingFailure[IllegalArgumentException] + } yield Passed + } + + test("set rank") { (cacheKey, connector) => + for { + _ <- connector.setSize(cacheKey).assertingEqual(0) + _ <- connector.setAdd(cacheKey, "A", "B").assertingEqual(2) + _ <- connector.setSize(cacheKey).assertingEqual(2) + + _ <- connector.setIsMember(cacheKey, "A").assertingEqual(true) + _ <- connector.setIsMember(cacheKey, "B").assertingEqual(true) + _ <- connector.setIsMember(cacheKey, "C").assertingEqual(false) + + _ <- connector.setAdd(cacheKey, "C", "B").assertingEqual(1) + + _ <- connector.setIsMember(cacheKey, "A").assertingEqual(true) + _ <- connector.setIsMember(cacheKey, "B").assertingEqual(true) + _ <- connector.setIsMember(cacheKey, "C").assertingEqual(true) + } yield Passed + } + + test("set size") { (cacheKey, connector) => + for { + _ <- connector.setSize(cacheKey).assertingEqual(0) + _ <- connector.setAdd(cacheKey, "A", "B").assertingEqual(2) + _ <- connector.setSize(cacheKey).assertingEqual(2) + } yield Passed + } + + test("set rem") { (cacheKey, connector) => + for { + _ <- connector.setSize(cacheKey).assertingEqual(0) + _ <- connector.setAdd(cacheKey, "A", "B", "C").assertingEqual(3) + _ <- connector.setSize(cacheKey).assertingEqual(3) + + _ <- connector.setRemove(cacheKey, "A").assertingEqual(1) + _ <- connector.setSize(cacheKey).assertingEqual(2) + _ <- connector.setRemove(cacheKey, "B", "C", "D").assertingEqual(2) + _ <- connector.setSize(cacheKey).assertingEqual(0) + } yield Passed + } + + test("set slice") { (cacheKey, connector) => + for { + _ <- connector.setSize(cacheKey).assertingEqual(0) + _ <- connector.setAdd(cacheKey, "A", "B", "C").assertingEqual(3) + _ <- connector.setSize(cacheKey).assertingEqual(3) + + _ <- connector.setMembers[String](cacheKey).assertingEqual(Set("A", "B", "C")) + + _ <- connector.setSize(cacheKey).assertingEqual(3) + } yield Passed + } + + test("hash set values") { (cacheKey, connector) => + for { + _ <- connector.hashSize(cacheKey).assertingEqual(0) + _ <- connector.hashGetAll[String] (cacheKey).assertingEqual(Map.empty) + _ <- connector.hashKeys(cacheKey).assertingEqual(Set.empty) + _ <- connector.hashValues[String] (cacheKey).assertingEqual(Set.empty) + + _ <- connector.hashGet[String] (cacheKey, "KA").assertingEqual(None) + _ <- connector.hashSet(cacheKey, "KA", "VA1").assertingEqual(true) + _ <- connector.hashGet[String] (cacheKey, "KA").assertingEqual(Some("VA1")) + _ <- connector.hashSet(cacheKey, "KA", "VA2").assertingEqual(false) + _ <- connector.hashGet[String] (cacheKey, "KA").assertingEqual(Some("VA2")) + _ <- connector.hashSet(cacheKey, "KB", "VB").assertingEqual(true) + + _ <- connector.hashGet[String] (cacheKey, Seq("KA", "KB", "KC")).assertingEqual(Seq(Some("VA2"), Some("VB"), None)) + + _ <- connector.hashExists(cacheKey, "KB").assertingEqual(true) + _ <- connector.hashExists(cacheKey, "KC").assertingEqual(false) + + _ <- connector.hashSize(cacheKey).assertingEqual(2) + _ <- connector.hashGetAll[String] (cacheKey).assertingEqual(Map("KA" -> "VA2", "KB" -> "VB")) + _ <- connector.hashKeys(cacheKey).assertingEqual(Set("KA", "KB")) + _ <- connector.hashValues[String] (cacheKey).assertingEqual(Set("VA2", "VB")) + + _ <- connector.hashRemove(cacheKey, "KB").assertingEqual(1) + _ <- connector.hashRemove(cacheKey, "KC").assertingEqual(0) + _ <- connector.hashExists(cacheKey, "KB").assertingEqual(false) + _ <- connector.hashExists(cacheKey, "KA").assertingEqual(true) + + _ <- connector.hashSize(cacheKey).assertingEqual(1) + _ <- connector.hashGetAll[String] (cacheKey).assertingEqual(Map("KA" -> "VA2")) + _ <- connector.hashKeys(cacheKey).assertingEqual(Set("KA")) + _ <- connector.hashValues[String] (cacheKey).assertingEqual(Set("VA2")) + + _ <- connector.hashSet(cacheKey, "KD", 5).assertingEqual(true) + _ <- connector.hashIncrement(cacheKey, "KD", 2).assertingEqual(7) + _ <- connector.hashGet[Int](cacheKey, "KD").assertingEqual(Some(7)) + } yield Passed + } + + test("hash set into invalid type") { (cacheKey, connector) => + for { + _ <- connector.set(cacheKey, "value").assertingSuccess + _ <- connector.get[String](cacheKey).assertingEqual(Some("value")) + _ <- connector.hashSet(cacheKey, "KA", "VA1").assertingFailure[IllegalArgumentException] + } yield Passed + } + + test("sorted set add") { (cacheKey, connector) => + for { + _ <- connector.sortedSetAdd(cacheKey, (1, "A")).assertingEqual(1) + _ <- connector.sortedSetSize(cacheKey).assertingEqual(1) + _ <- connector.sortedSetSize(cacheKey).assertingEqual(1) + _ <- connector.sortedSetAdd(cacheKey, (2, "B"), (3, "C")).assertingEqual(2) + _ <- connector.sortedSetSize(cacheKey).assertingEqual(3) + _ <- connector.sortedSetAdd(cacheKey, (1, "A")).assertingEqual(0) + _ <- connector.sortedSetSize(cacheKey).assertingEqual(3) + } yield Passed + } + + test("sorted set add invalid type") { (cacheKey, connector) => + for { + _ <- connector.set(cacheKey, "value").assertingSuccess + _ <- connector.get[String](cacheKey).assertingEqual(Some("value")) + _ <- connector.sortedSetAdd(cacheKey, 1D -> "VA1").assertingFailure[IllegalArgumentException] + } yield Passed + } + + test("sorted set score") { (cacheKey, connector) => + for { + _ <- connector.sortedSetSize(cacheKey).assertingEqual(0) + _ <- connector.sortedSetAdd(cacheKey, 1D -> "A", 3D -> "B").assertingEqual(2) + _ <- connector.sortedSetSize(cacheKey).assertingEqual(2) + + _ <- connector.sortedSetScore(cacheKey, "A").assertingEqual(Some(1D)) + _ <- connector.sortedSetScore(cacheKey, "B").assertingEqual(Some(3D)) + _ <- connector.sortedSetScore(cacheKey, "C").assertingEqual(None) + + _ <- connector.sortedSetAdd(cacheKey, 2D -> "C", 4D -> "B").assertingEqual(1) + + _ <- connector.sortedSetScore(cacheKey, "A").assertingEqual(Some(1D)) + _ <- connector.sortedSetScore(cacheKey, "B").assertingEqual(Some(4D)) + _ <- connector.sortedSetScore(cacheKey, "C").assertingEqual(Some(2D)) + } yield Passed + } + + test("sorted set size") { (cacheKey, connector) => + for { + _ <- connector.sortedSetSize(cacheKey).assertingEqual(0) + _ <- connector.sortedSetAdd(cacheKey, (1, "A"), (2, "B")).assertingEqual(2) + _ <- connector.sortedSetSize(cacheKey).assertingEqual(2) + } yield Passed + } + + test("sorted set remove") { (cacheKey, connector) => + for { + _ <- connector.sortedSetSize(cacheKey).assertingEqual(0) + _ <- connector.sortedSetAdd(cacheKey, 1D -> "A", 2D -> "B", 3D -> "C").assertingEqual(3) + _ <- connector.sortedSetSize(cacheKey).assertingEqual(3) + + _ <- connector.sortedSetRemove(cacheKey, "A").assertingEqual(1) + _ <- connector.sortedSetSize(cacheKey).assertingEqual(2) + _ <- connector.sortedSetRemove(cacheKey, "B", "C", "D").assertingEqual(2) + _ <- connector.sortedSetSize(cacheKey).assertingEqual(0) + } yield Passed + } + + test("sorted set range") { (cacheKey, connector) => + for { + _ <- connector.sortedSetSize(cacheKey).assertingEqual(0) + _ <- connector.sortedSetAdd(cacheKey, 1D -> "A", 2D -> "B", 4D -> "C").assertingEqual(3) + _ <- connector.sortedSetSize(cacheKey).assertingEqual(3) + + _ <- connector.sortedSetRange[String](cacheKey, 0, 1).assertingEqual(Vector("A", "B")) + _ <- connector.sortedSetRange[String](cacheKey, 0, 4).assertingEqual(Vector("A", "B", "C")) + _ <- connector.sortedSetRange[String](cacheKey, 1, 9).assertingEqual(Vector("B", "C")) + + _ <- connector.sortedSetSize(cacheKey).assertingEqual(3) + } yield Passed + } + + test("sorted set reverse range") { (cacheKey, connector) => + for { + _ <- connector.sortedSetSize(cacheKey).assertingEqual(0) + _ <- connector.sortedSetAdd(cacheKey, 1D -> "A", 2D -> "B", 4D -> "C").assertingEqual(3) + _ <- connector.sortedSetSize(cacheKey).assertingEqual(3) + + _ <- connector.sortedSetReverseRange[String](cacheKey, 0, 1).assertingEqual(Vector("C", "B")) + _ <- connector.sortedSetReverseRange[String](cacheKey, 0, 4).assertingEqual(Vector("C", "B", "A")) + _ <- connector.sortedSetReverseRange[String](cacheKey, 1, 9).assertingEqual(Vector("B", "A")) + + _ <- connector.sortedSetSize(cacheKey).assertingEqual(3) + } yield Passed + } + + def test(name: String)(f: (String, RedisConnector) => Future[Assertion]): Unit = { + name in { + implicit val system: ActorSystem = ActorSystem("test", classLoader = Some(getClass.getClassLoader)) + implicit val runtime: RedisRuntime = RedisRuntime("standalone", syncTimeout = 5.seconds, ExecutionContext.global, new LogAndFailPolicy, LazyInvocation) + implicit val application: StoppableApplication = StoppableApplication(system) + val serializer = new AkkaSerializerImpl(system) + + lazy val instance = RedisStandalone( + name = "play", + host = RedisHost(container.containerIpAddress, container.mappedPort(defaultPort)), + settings = RedisSettings.load( + config = Helpers.configuration.default.underlying, + path = "play.cache.redis" + ) + ) + + val cacheKey = name.toLowerCase().replace(" ", "-") + + application.runAsyncInApplication { + for { + connector <- Future(new RedisConnectorProvider(instance, serializer).get) + // initialize the connector by flushing the database + _ <- connector.invalidate() + // run the test + _ <- f(cacheKey, connector) + } yield Passed + } + } + } + + } diff --git a/src/test/scala/play/api/cache/redis/connector/SerializerImplicits.scala b/src/test/scala/play/api/cache/redis/connector/SerializerImplicits.scala deleted file mode 100644 index 281d4a55..00000000 --- a/src/test/scala/play/api/cache/redis/connector/SerializerImplicits.scala +++ /dev/null @@ -1,21 +0,0 @@ -package play.api.cache.redis.connector - -import scala.reflect.ClassTag - -object SerializerImplicits { - - implicit class ValueEncoder(val any: Any) extends AnyVal { - def encoded(implicit serializer: AkkaSerializer): String = serializer.encode(any).get - } - - implicit class StringDecoder(val string: String) extends AnyVal { - def decoded[T: ClassTag](implicit serializer: AkkaSerializer): T = serializer.decode[T](string).get - } - - implicit class StringOps(val string: String) extends AnyVal { - def removeAllWhitespaces = string.replaceAll("\\s", "") - } - - /** Plain test object to be cached */ - case class SimpleObject(key: String, value: Int) -} diff --git a/src/test/scala/play/api/cache/redis/connector/SerializerSpec.scala b/src/test/scala/play/api/cache/redis/connector/SerializerSpec.scala index a148cf35..838ec754 100644 --- a/src/test/scala/play/api/cache/redis/connector/SerializerSpec.scala +++ b/src/test/scala/play/api/cache/redis/connector/SerializerSpec.scala @@ -1,129 +1,201 @@ package play.api.cache.redis.connector -import java.util.Date - -import play.api.inject.guice.GuiceApplicationBuilder +import akka.actor.ActorSystem import play.api.cache.redis._ +import play.api.cache.redis.test._ -import org.specs2.mock.Mockito -import org.specs2.mutable.Specification - -class SerializerSpec extends Specification with Mockito { - import SerializerImplicits._ +import java.util.Date +import scala.reflect.ClassTag +import scala.util.Random - private val system = GuiceApplicationBuilder().build().actorSystem +class SerializerSpec extends AsyncUnitSpec { - private implicit val serializer: AkkaSerializer = new AkkaSerializerImpl(system) + import SerializerSpec._ - "AkkaEncoder" should "encode" >> { + "encode" when { - "byte" in { + test("byte") { implicit serializer => 0xAB.toByte.encoded mustEqual "-85" JavaTypes.byteValue.encoded mustEqual "5" } - "byte[]" in { + test("byte[]") { implicit serializer => JavaTypes.bytesValue.encoded mustEqual "AQID" } - "char" in { + test("char") { implicit serializer => 'a'.encoded mustEqual "a" 'b'.encoded mustEqual "b" 'š'.encoded mustEqual "š" } - "boolean" in { + test("boolean") { implicit serializer => true.encoded mustEqual "true" } - "short" in { + test("short") { implicit serializer => 12.toShort.toByte.encoded mustEqual "12" } - "int" in { + test("int") { implicit serializer => 15.encoded mustEqual "15" } - "long" in { + test("long") { implicit serializer => 144L.encoded mustEqual "144" } - "float" in { + test("float") { implicit serializer => 1.23f.encoded mustEqual "1.23" } - "double" in { + test("double") { implicit serializer => 3.14.encoded mustEqual "3.14" } - "string" in { + test("string") { implicit serializer => "some string".encoded mustEqual "some string" } - "date" in { + test("date") { implicit serializer => new Date(123).encoded mustEqual "rO0ABXNyAA5qYXZhLnV0aWwuRGF0ZWhqgQFLWXQZAwAAeHB3CAAAAAAAAAB7eA==" } - "null" in { - new ValueEncoder(null).encoded must throwA[UnsupportedOperationException] + test("null") { implicit serializer => + assertThrows[UnsupportedOperationException] { + new ValueEncoder(null).encoded + } + } + + test("custom classes") { implicit serializer => + SimpleObject("B", 3).encoded mustEqual + """ + |rO0ABXNyADpwbGF5LmFwaS5jYWNoZS5yZWRpcy5jb25uZWN0b3IuU2VyaWFsaXplclNwZWMkU2ltc + |GxlT2JqZWN0LqzdylZUVb0CAAJJAAV2YWx1ZUwAA2tleXQAEkxqYXZhL2xhbmcvU3RyaW5nO3hwAA + |AAA3QAAUI= + """.stripMargin.withoutWhitespaces + } + + test("list") { implicit serializer => + List("A", "B", "C").encoded mustEqual + """ + |rO0ABXNyADJzY2FsYS5jb2xsZWN0aW9uLmdlbmVyaWMuRGVmYXVsdFNlcmlhbGl6YXRpb25Qcm94e + |QAAAAAAAAADAwABTAAHZmFjdG9yeXQAGkxzY2FsYS9jb2xsZWN0aW9uL0ZhY3Rvcnk7eHBzcgAqc2 + |NhbGEuY29sbGVjdGlvbi5JdGVyYWJsZUZhY3RvcnkkVG9GYWN0b3J5AAAAAAAAAAMCAAFMAAdmYWN + |0b3J5dAAiTHNjYWxhL2NvbGxlY3Rpb24vSXRlcmFibGVGYWN0b3J5O3hwc3IAJnNjYWxhLnJ1bnRp + |bWUuTW9kdWxlU2VyaWFsaXphdGlvblByb3h5AAAAAAAAAAECAAFMAAttb2R1bGVDbGFzc3QAEUxqY + |XZhL2xhbmcvQ2xhc3M7eHB2cgAgc2NhbGEuY29sbGVjdGlvbi5pbW11dGFibGUuTGlzdCQAAAAAAA + |AAAwIAAHhwdwT/////dAABQXQAAUJ0AAFDc3EAfgAGdnIAJnNjYWxhLmNvbGxlY3Rpb24uZ2VuZXJ + |pYy5TZXJpYWxpemVFbmQkAAAAAAAAAAMCAAB4cHg= + """.stripMargin.withoutWhitespaces } } - "AkkaDecoder" should "decode" >> { + "decode" when { - "byte" in { + test("byte") { implicit serializer => "-85".decoded[Byte] mustEqual 0xAB.toByte } - "byte[]" in { + test("byte[]") { implicit serializer => "YWJj".decoded[Array[Byte]] mustEqual Array("a".head.toByte, "b".head.toByte, "c".head.toByte) } - "char" in { + test("char") { implicit serializer => "a".decoded[Char] mustEqual 'a' "b".decoded[Char] mustEqual 'b' "š".decoded[Char] mustEqual 'š' } - "boolean" in { + test("boolean") { implicit serializer => "true".decoded[Boolean] mustEqual true } - "short" in { + test("short") { implicit serializer => "12".decoded[Short] mustEqual 12.toShort.toByte } - "int" in { + test("int") { implicit serializer => "15".decoded[Int] mustEqual 15 } - "long" in { + test("long") { implicit serializer => "144".decoded[Long] mustEqual 144L } - "float" in { + test("float") { implicit serializer => "1.23".decoded[Float] mustEqual 1.23f } - "double" in { + test("double") { implicit serializer => "3.14".decoded[Double] mustEqual 3.14 } - "string" in { + test("string") { implicit serializer => "some string".decoded[String] mustEqual "some string" } - "null" in { - "".decoded[String] must beNull + test("null") { implicit serializer => + "".decoded[String] mustEqual null } - "date" in { + test("date") { implicit serializer => "rO0ABXNyAA5qYXZhLnV0aWwuRGF0ZWhqgQFLWXQZAwAAeHB3CAAAAAAAAAB7eA==".decoded[Date] mustEqual new Date(123) } - "invalid type" in { - def decoded: Date = "something".decoded[Date] - decoded must throwA[IllegalArgumentException] + test("invalid type") { implicit serializer => + assertThrows[IllegalArgumentException] { + "something".decoded[Date] + } + } + + test("custom classes") { implicit serializer => + """ + |rO0ABXNyADpwbGF5LmFwaS5jYWNoZS5yZWRpcy5jb25uZWN0b3IuU2VyaWFsaXplclNwZWMkU2ltc + |GxlT2JqZWN0LqzdylZUVb0CAAJJAAV2YWx1ZUwAA2tleXQAEkxqYXZhL2xhbmcvU3RyaW5nO3hwAA + |AAA3QAAUI= + """.stripMargin.withoutWhitespaces.decoded[SimpleObject] mustEqual SimpleObject("B", 3) + } + + test("list") { implicit serializer => + """ + |rO0ABXNyADJzY2FsYS5jb2xsZWN0aW9uLmdlbmVyaWMuRGVmYXVsdFNlcmlhbGl6YXRpb25Qcm94e + |QAAAAAAAAADAwABTAAHZmFjdG9yeXQAGkxzY2FsYS9jb2xsZWN0aW9uL0ZhY3Rvcnk7eHBzcgAqc2 + |NhbGEuY29sbGVjdGlvbi5JdGVyYWJsZUZhY3RvcnkkVG9GYWN0b3J5AAAAAAAAAAMCAAFMAAdmYWN + |0b3J5dAAiTHNjYWxhL2NvbGxlY3Rpb24vSXRlcmFibGVGYWN0b3J5O3hwc3IAJnNjYWxhLnJ1bnRp + |bWUuTW9kdWxlU2VyaWFsaXphdGlvblByb3h5AAAAAAAAAAECAAFMAAttb2R1bGVDbGFzc3QAEUxqY + |XZhL2xhbmcvQ2xhc3M7eHB2cgAgc2NhbGEuY29sbGVjdGlvbi5pbW11dGFibGUuTGlzdCQAAAAAAA + |AAAwIAAHhwdwT/////dAABQXQAAUJ0AAFDc3EAfgAGdnIAJnNjYWxhLmNvbGxlY3Rpb24uZ2VuZXJ + |pYy5TZXJpYWxpemVFbmQkAAAAAAAAAAMCAAB4cHg= + """.stripMargin.withoutWhitespaces.decoded[List[String]] mustEqual List("A", "B", "C") } } + + private def test(name: String)(f: AkkaSerializer => Unit): Unit = { + name in { + val system = ActorSystem.apply(s"test-${Random.nextInt()}", classLoader = Some(getClass.getClassLoader)) + val serializer: AkkaSerializer = new AkkaSerializerImpl(system) + f(serializer) + system.terminate().map(_ => Passed) + } + } +} + +object SerializerSpec { + + private implicit class ValueEncoder(private val any: Any) extends AnyVal { + def encoded(implicit s: AkkaSerializer): String = s.encode(any).get + } + + private implicit class StringDecoder(private val string: String) extends AnyVal { + def decoded[T: ClassTag](implicit s: AkkaSerializer): T = s.decode[T](string).get + } + + private implicit class StringOps(private val string: String) extends AnyVal { + def withoutWhitespaces: String = string.replaceAll("\\s", "") + } + + /** Plain test object to be cached */ + private final case class SimpleObject(key: String, value: Int) } + diff --git a/src/test/scala/play/api/cache/redis/connector/TestCase.scala b/src/test/scala/play/api/cache/redis/connector/TestCase.scala deleted file mode 100644 index 7cbca8e9..00000000 --- a/src/test/scala/play/api/cache/redis/connector/TestCase.scala +++ /dev/null @@ -1,20 +0,0 @@ -package play.api.cache.redis.connector - -import java.util.concurrent.atomic.AtomicInteger - -import org.specs2.execute.{AsResult, Result} -import org.specs2.specification.{Around, Scope} - -abstract class TestCase extends Around with Scope { - - protected val idx = TestCase.last.incrementAndGet() - - override def around[T: AsResult](t: => T): Result = { - AsResult.effectively(t) - } -} - -object TestCase { - - val last = new AtomicInteger() -} diff --git a/src/test/scala/play/api/cache/redis/impl/AsyncJavaRedisSpec.scala b/src/test/scala/play/api/cache/redis/impl/AsyncJavaRedisSpec.scala index 572b0e84..34bbc0a9 100644 --- a/src/test/scala/play/api/cache/redis/impl/AsyncJavaRedisSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/AsyncJavaRedisSpec.scala @@ -1,321 +1,383 @@ package play.api.cache.redis.impl -import java.util.Optional - -import scala.concurrent.duration.Duration - import play.api.cache.redis._ +import play.api.cache.redis.test._ +import play.api.{Environment, Mode} import play.cache.redis._ -import org.specs2.concurrent.ExecutionEnv -import org.specs2.mutable.Specification - -class AsyncJavaRedisSpec(implicit ee: ExecutionEnv) extends Specification with ReducedMockito { - import Implicits._ - import JavaCompatibility._ - import RedisCacheImplicits._ - - import org.mockito.ArgumentMatchers._ - - "Java Redis Cache" should { - - "get and miss" in new MockedJavaRedis { - async.get[String](anyString)(anyClassTag) returns None - cache.get[String](key).asScala must beEqualTo(Optional.empty).await - } - - "get and hit" in new MockedJavaRedis { - async.get[String](beEq(key))(anyClassTag) returns Some(value) - async.get[String](beEq(classTagKey))(anyClassTag) returns Some(classTag) - cache.get[String](key).asScala must beEqualTo(Optional.of(value)).await - } - - "get null" in new MockedJavaRedis { - async.get[String](beEq(classTagKey))(anyClassTag) returns Some("null") - cache.get[String](key).asScala must beEqualTo(Optional.empty).await - there was one(async).get[String](classTagKey) - } +import java.util.Optional +import scala.concurrent.Future +import scala.concurrent.duration._ +import scala.jdk.CollectionConverters.IterableHasAsScala + +class AsyncJavaRedisSpec extends AsyncUnitSpec with AsyncRedisMock with RedisRuntimeMock { +import Helpers._ + + private val expiration = 5.seconds + private val expirationLong = expiration.toSeconds + private val expirationInt = expirationLong.intValue + private val classTag = "java.lang.String" + + test("get and miss") { (async, cache) => + for { + _ <- async.expect.getClassTag(cacheKey, None) + _ <- cache.get[String](cacheKey).assertingEqual(Optional.empty) + } yield Passed + } - "set" in new MockedJavaRedis { - async.set(anyString, anyString, any[Duration]) returns execDone - cache.set(key, value).asScala must beDone.await - there was one(async).set(key, value, Duration.Inf) - there was one(async).set(classTagKey, classTag, Duration.Inf) - } + test("get and hit") { (async, cache) => + for { + _ <- async.expect.getClassTag(cacheKey, Some(classTag)) + _ <- async.expect.get[String](cacheKey, Some(cacheValue)) + _ <- cache.get[String](cacheKey).assertingEqual(Optional.of(cacheValue)) + } yield Passed + } - "set with expiration" in new MockedJavaRedis { - async.set(anyString, anyString, any[Duration]) returns execDone - cache.set(key, value, expiration.toSeconds.toInt).asScala must beDone.await - there was one(async).set(key, value, expiration) - there was one(async).set(classTagKey, classTag, expiration) + test("get null") { (async, cache) => + for { + _ <- async.expect.getClassTag(cacheKey, Some("null")) + _ <- cache.get[String](cacheKey).assertingEqual(Optional.empty) + } yield Passed } - "set null" in new MockedJavaRedis { - async.set(anyString, any, any[Duration]) returns execDone - cache.set(key, null: AnyRef).asScala must beDone.await - there was one(async).set(key, null, Duration.Inf) - there was one(async).set(classTagKey, "null", Duration.Inf) + test("set") { (async, cache) => + for { + _ <- async.expect.set(cacheKey, cacheValue, Duration.Inf) + _ <- cache.set(cacheKey, cacheValue).assertingDone + } yield Passed } - "get or else (hit)" in new MockedJavaRedis with OrElse { - async.get[String](beEq(key))(anyClassTag) returns Some(value) - async.get[String](beEq(classTagKey))(anyClassTag) returns Some(classTag) - cache.getOrElse(key, doElse(value)).asScala must beEqualTo(value).await - cache.getOrElseUpdate(key, doFuture(value).asJava).asScala must beEqualTo(value).await - orElse mustEqual 0 - there was two(async).get[String](key) - there was two(async).get[String](classTagKey) + test("set with expiration") { (async, cache) => + for { + _ <- async.expect.set(cacheKey, cacheValue, expiration) + _ <- cache.set(cacheKey, cacheValue, expiration.toSeconds.toInt).assertingDone + } yield Passed } - "get or else (miss)" in new MockedJavaRedis with OrElse { - async.get[String](beEq(classTagKey))(anyClassTag) returns None - async.set(anyString, anyString, any[Duration]) returns execDone - cache.getOrElse(key, doElse(value)).asScala must beEqualTo(value).await - cache.getOrElseUpdate(key, doFuture(value).asJava).asScala must beEqualTo(value).await - orElse mustEqual 2 - there was two(async).get[String](classTagKey) - there was two(async).set(key, value, Duration.Inf) - there was two(async).set(classTagKey, classTag, Duration.Inf) - } + test("set null") { (async, cache) => + for { + _ <- async.expect.set[AnyRef](cacheKey, null, Duration.Inf) + _ <- cache.set(cacheKey, null: AnyRef).assertingDone + } yield Passed + } - "get or else with expiration (hit)" in new MockedJavaRedis with OrElse { - async.get[String](beEq(key))(anyClassTag) returns Some(value) - async.get[String](beEq(classTagKey))(anyClassTag) returns Some(classTag) - cache.getOrElse(key, doElse(value), expiration.toSeconds.toInt).asScala must beEqualTo(value).await - cache.getOrElseUpdate(key, doFuture(value).asJava, expiration.toSeconds.toInt).asScala must beEqualTo(value).await - orElse mustEqual 0 - there was two(async).get[String](key) - } + test("get or else (sync)") { (async, cache) => + for { + _ <- async.expect.getClassTag(cacheKey, None) + _ <- async.expect.set(cacheKey, cacheValue, Duration.Inf) + _ <- async.expect.getClassTag(cacheKey, Some(classTag)) + _ <- async.expect.get[String](cacheKey, Some(cacheValue)) + orElse = probe.orElse.const(cacheValue) + _ <- cache.getOrElse(cacheKey, orElse.execute _).assertingEqual(cacheValue) + _ <- cache.getOrElse(cacheKey, orElse.execute _).assertingEqual(cacheValue) + _ = orElse.calls mustEqual 1 + } yield Passed + } - "get or else with expiration (miss)" in new MockedJavaRedis with OrElse { - async.get[String](beEq(classTagKey))(anyClassTag) returns None - async.set(anyString, anyString, any[Duration]) returns execDone - cache.getOrElse(key, doElse(value), expiration.toSeconds.toInt).asScala must beEqualTo(value).await - cache.getOrElseUpdate(key, doFuture(value).asJava, expiration.toSeconds.toInt).asScala must beEqualTo(value).await - orElse mustEqual 2 - there was two(async).get[String](classTagKey) - there was two(async).set(key, value, expiration) - there was two(async).set(classTagKey, classTag, expiration) - } + test("get or else (async)") { (async, cache) => + for { + _ <- async.expect.getClassTag(cacheKey, None) + _ <- async.expect.set(cacheKey, cacheValue, Duration.Inf) + _ <- async.expect.getClassTag(cacheKey, Some(classTag)) + _ <- async.expect.get[String](cacheKey, Some(cacheValue)) + orElse = probe.orElse.asyncJava(cacheValue) + _ <- cache.getOrElseUpdate(cacheKey, orElse.execute _).assertingEqual(cacheValue) + _ <- cache.getOrElseUpdate(cacheKey, orElse.execute _).assertingEqual(cacheValue) + _ = orElse.calls mustEqual 1 + } yield Passed + } - "get optional (none)" in new MockedJavaRedis { - async.get[String](anyString)(anyClassTag) returns None - cache.getOptional[String](key).asScala must beEqualTo(Optional.ofNullable(null)).await - } + test("get or else with expiration (sync)") { (async, cache) => + for { + _ <- async.expect.getClassTag(cacheKey, None) + _ <- async.expect.set(cacheKey, cacheValue, expiration) + _ <- async.expect.getClassTag(cacheKey, Some(classTag)) + _ <- async.expect.get[String](cacheKey, Some(cacheValue)) + orElse = probe.orElse.const(cacheValue) + _ <- cache.getOrElse(cacheKey, orElse.execute _, expiration.toSeconds.toInt).assertingEqual(cacheValue) + _ <- cache.getOrElse(cacheKey, orElse.execute _, expiration.toSeconds.toInt).assertingEqual(cacheValue) + _ = orElse.calls mustEqual 1 + } yield Passed + } - "get optional (some)" in new MockedJavaRedis { - async.get[String](anyString)(anyClassTag) returns Some("value") - async.get[String](beEq(classTagKey))(anyClassTag) returns Some(classTag) - cache.getOptional[String](key).asScala must beEqualTo(Optional.ofNullable("value")).await - } + test("get or else with expiration (async)") { (async, cache) => + for { + _ <- async.expect.getClassTag(cacheKey, None) + _ <- async.expect.set(cacheKey, cacheValue, expiration) + _ <- async.expect.getClassTag(cacheKey, Some(classTag)) + _ <- async.expect.get[String](cacheKey, Some(cacheValue)) + orElse = probe.orElse.asyncJava(cacheValue) + _ <- cache.getOrElseUpdate(cacheKey, orElse.execute _, expiration.toSeconds.toInt).assertingEqual(cacheValue) + _ <- cache.getOrElseUpdate(cacheKey, orElse.execute _, expiration.toSeconds.toInt).assertingEqual(cacheValue) + _ = orElse.calls mustEqual 1 + } yield Passed + } - "remove" in new MockedJavaRedis { - async.remove(anyString) returns execDone - cache.remove(key).asScala must beDone.await - there was one(async).remove(key) - there was one(async).remove(classTagKey) - } + test("get optional (none)") { (async, cache) => + for { + _ <- async.expect.getClassTag(cacheKey, None) + _ <- cache.getOptional[String](cacheKey).assertingEqual(Optional.ofNullable(null)) + } yield Passed + } - "remove all" in new MockedJavaRedis { - async.invalidate() returns execDone - cache.removeAll().asScala must beDone.await - there was one(async).invalidate() + test("get optional (some)") { (async, cache) => + for { + _ <- async.expect.getClassTag(cacheKey, Some(classTag)) + _ <- async.expect.get[String](cacheKey, Some(cacheValue)) + _ <- cache.getOptional[String](cacheKey).assertingEqual(Optional.ofNullable(cacheValue)) + } yield Passed } - "get and set 'byte'" in new MockedJavaRedis { - val byte = JavaTypes.byteValue - - // set a value - // note: there should be hit on "byte" but the value is wrapped instead - async.set(anyString, beEq(byte), any[Duration]) returns execDone - async.set(anyString, beEq("byte"), any[Duration]) returns execDone - async.set(anyString, beEq("java.lang.Byte"), any[Duration]) returns execDone - cache.set(key, byte).asScala must beDone.await + test("remove") { (async, cache) => + for { + _ <- async.expect.remove(cacheKey) + _ <- cache.remove(cacheKey).assertingDone + } yield Passed + } + test("get and set 'byte'") { (async, cache) => + val byte = JavaTypes.byteValue + for { + // set a cacheValue + // note: there should be hit on "byte" but the cacheValue is wrapped instead + _ <- async.expect.setValue(cacheKey, byte, Duration.Inf) + _ <- async.expect.setClassTag(cacheKey, "java.lang.Byte", Duration.Inf) + _ <- cache.set(cacheKey, byte).assertingDone // hit on GET - async.get[Byte](beEq(key))(anyClassTag) returns Some(byte) - async.get[String](beEq(classTagKey))(anyClassTag) returns Some("java.lang.Byte") - cache.get[Byte](key).asScala must beEqualTo(Optional.ofNullable(byte)).await - } + _ <- async.expect.getClassTag(cacheKey, Some("java.lang.Byte")) + _ <- async.expect.get[java.lang.Byte](cacheKey, Some(byte)) + _ <- cache.get[Byte](cacheKey).assertingEqual(Optional.ofNullable(byte)) + } yield Passed + } - "get and set 'byte[]'" in new MockedJavaRedis { - val bytes = JavaTypes.bytesValue + test("get and set 'byte[]'") { (async, cache) => + val scalaBytes = JavaTypes.bytesValue + val javaBytes = scalaBytes.map[java.lang.Byte](x => x) + for { + // set a cacheValue + _ <- async.expect.setValue(cacheKey, scalaBytes, Duration.Inf) + _ <- async.expect.setClassTag(cacheKey, "byte[]", Duration.Inf) + _ <- cache.set(cacheKey, scalaBytes).assertingDone + // hit on GET + _ <- async.expect.getClassTag(cacheKey, Some("byte[]")) + _ <- async.expect.get[Array[java.lang.Byte]](cacheKey, Some(javaBytes)) + _ <- cache.get[Array[java.lang.Byte]](cacheKey).assertingEqual(Optional.ofNullable(javaBytes)) + } yield Passed + } - // set a value - async.set(anyString, beEq(bytes), any[Duration]) returns execDone - async.set(anyString, beEq("byte[]"), any[Duration]) returns execDone - cache.set(key, bytes).asScala must beDone.await + test("get all") { (async, cache) => + for { + _ <- async.expect.getAllKeys[String](Iterable(cacheKey, cacheKey, cacheKey), Seq(Some(cacheValue), None, None)) + _ <- cache + .getAll(classOf[String], cacheKey, cacheKey, cacheKey) + .asserting(_.asScala.toList mustEqual List(Optional.of(cacheValue), Optional.empty, Optional.empty)) + } yield Passed + } - // hit on GET - async.get[Array[Byte]](beEq(key))(anyClassTag) returns Some(bytes) - async.get[String](beEq(classTagKey))(anyClassTag) returns Some("byte[]") - cache.get[Array[Byte]](key).asScala must beEqualTo(Optional.ofNullable(bytes)).await - } + test("get all (keys in a collection)") { (async, cache) => + import JavaCompatibility.JavaList + for { + _ <- async.expect.getAllKeys[String](Iterable(cacheKey, cacheKey, cacheKey), Seq(Some(cacheValue), None, None)) + _ <- cache + .getAll(classOf[String], JavaList(cacheKey, cacheKey, cacheKey)) + .asserting(_.asScala.toList mustEqual List(Optional.of(cacheValue), Optional.empty, Optional.empty)) + } yield Passed + } - "get all" in new MockedJavaRedis { - async.getAll[String](beEq(Iterable(key, key, key)))(anyClassTag) returns Seq(Some(value), None, None) - cache.getAll(classOf[String], key, key, key).asScala.map(_.asScala) must beEqualTo(Seq(Optional.of(value), Optional.empty, Optional.empty)).await - } + test("set if not exists (exists)") { (async, cache) => + for { + _ <- async.expect.setIfNotExists(cacheKey, cacheValue, Duration.Inf, exists = false) + _ <- cache.setIfNotExists(cacheKey, cacheValue).assertingEqual(false) + } yield Passed + } - "get all (keys in a collection)" in new MockedJavaRedis { - async.getAll[String](beEq(Iterable(key, key, key)))(anyClassTag) returns Seq(Some(value), None, None) - cache.getAll(classOf[String], JavaList(key, key, key)).asScala.map(_.asScala) must beEqualTo(Seq(Optional.of(value), Optional.empty, Optional.empty)).await - } + test("set if not exists (not exists)") { (async, cache) => + for { + _ <- async.expect.setIfNotExists(cacheKey, cacheValue, Duration.Inf, exists = true) + _ <- cache.setIfNotExists(cacheKey, cacheValue).assertingEqual(true) + } yield Passed + } - "set if not exists (exists)" in new MockedJavaRedis { - async.setIfNotExists(beEq(key), beEq(value), any[Duration]) returns false - async.setIfNotExists(beEq(classTagKey), beEq(classTag), any[Duration]) returns false - cache.setIfNotExists(key, value).asScala.map(Boolean.unbox) must beFalse.await - there was one(async).setIfNotExists(key, value, null) - there was one(async).setIfNotExists(classTagKey, classTag, null) - } + test("set if not exists (exists) with expiration") { (async, cache) => + for { + _ <- async.expect.setIfNotExists(cacheKey, cacheValue, expiration, exists = false) + _ <- cache.setIfNotExists(cacheKey, cacheValue, expirationInt).assertingEqual(false) + } yield Passed + } - "set if not exists (not exists)" in new MockedJavaRedis { - async.setIfNotExists(beEq(key), beEq(value), any[Duration]) returns true - async.setIfNotExists(beEq(classTagKey), beEq(classTag), any[Duration]) returns true - cache.setIfNotExists(key, value).asScala.map(Boolean.unbox) must beTrue.await - there was one(async).setIfNotExists(key, value, null) - there was one(async).setIfNotExists(classTagKey, classTag, null) - } + test("set if not exists (not exists) with expiration") { (async, cache) => + for { + _ <- async.expect.setIfNotExists(cacheKey, cacheValue, expiration, exists = true) + _ <- cache.setIfNotExists(cacheKey, cacheValue, expirationInt).assertingEqual(true) + } yield Passed + } - "set if not exists (exists) with expiration" in new MockedJavaRedis { - async.setIfNotExists(beEq(key), beEq(value), any[Duration]) returns false - async.setIfNotExists(beEq(classTagKey), beEq(classTag), any[Duration]) returns false - cache.setIfNotExists(key, value, expirationInt).asScala.map(Boolean.unbox) must beFalse.await - there was one(async).setIfNotExists(key, value, expiration) - there was one(async).setIfNotExists(classTagKey, classTag, expiration) - } + test("set all") { (async, cache) => + val values = Seq((cacheKey, cacheValue), (otherKey, otherValue)) + for { + _ <- async.expect.setAll(values: _*) + javaValues = values.map { case (k, v) => new KeyValue(k, v) } + _ <- cache.setAll(javaValues: _*).assertingDone + } yield Passed + } - "set if not exists (not exists) with expiration" in new MockedJavaRedis { - async.setIfNotExists(beEq(key), beEq(value), any[Duration]) returns true - async.setIfNotExists(beEq(classTagKey), beEq(classTag), any[Duration]) returns true - cache.setIfNotExists(key, value, expirationInt).asScala.map(Boolean.unbox) must beTrue.await - there was one(async).setIfNotExists(key, value, expiration) - there was one(async).setIfNotExists(classTagKey, classTag, expiration) - } + test("set all if not exists (exists)") { (async, cache) => + val values = Seq((cacheKey, cacheValue), (otherKey, otherValue)) + for { + _ <- async.expect.setAllIfNotExist(values, exists = false) + javaValues = values.map { case (k, v) => new KeyValue(k, v) } + _ <- cache.setAllIfNotExist(javaValues: _*).assertingEqual(false) + } yield Passed + } - "set all" in new MockedJavaRedis { - async.setAll(anyVarArgs) returns Done - cache.setAll(new KeyValue(key, value), new KeyValue(other, value)).asScala must beDone.await - there was one(async).setAll((key, value), (classTagKey, classTag), (other, value), (classTagOther, classTag)) - } + test("set all if not exists (not exists)") { (async, cache) => + val values = Seq((cacheKey, cacheValue), (otherKey, otherValue)) + for { + _ <- async.expect.setAllIfNotExist(values, exists = true) + javaValues = values.map { case (k, v) => new KeyValue(k, v) } + _ <- cache.setAllIfNotExist(javaValues: _*).assertingEqual(true) + } yield Passed + } - "set all if not exists (exists)" in new MockedJavaRedis { - async.setAllIfNotExist(anyVarArgs) returns false - cache.setAllIfNotExist(new KeyValue(key, value), new KeyValue(other, value)).asScala.map(Boolean.unbox) must beFalse.await - there was one(async).setAllIfNotExist((key, value), (classTagKey, classTag), (other, value), (classTagOther, classTag)) - } + test("append") { (async, cache) => + for { + _ <- async.expect.append(cacheKey, cacheValue, Duration.Inf) + _ <- async.expect.setClassTagIfNotExists(cacheKey, cacheValue, Duration.Inf, exists = false) + _ <- cache.append(cacheKey, cacheValue).assertingDone + } yield Passed + } - "set all if not exists (not exists)" in new MockedJavaRedis { - async.setAllIfNotExist(anyVarArgs) returns true - cache.setAllIfNotExist(new KeyValue(key, value), new KeyValue(other, value)).asScala.map(Boolean.unbox) must beTrue.await - there was one(async).setAllIfNotExist((key, value), (classTagKey, classTag), (other, value), (classTagOther, classTag)) - } + test("append with expiration") { (async, cache) => + for { + _ <- async.expect.append(cacheKey, cacheValue, expiration) + _ <- async.expect.setClassTagIfNotExists(cacheKey, cacheValue, expiration, exists = false) + _ <- cache.append(cacheKey, cacheValue, expirationInt).assertingDone + } yield Passed + } - "append" in new MockedJavaRedis { - async.append(anyString, anyString, any[Duration]) returns Done - async.setIfNotExists(anyString, anyString, any[Duration]) returns false - cache.append(key, value).asScala must beDone.await - there was one(async).append(key, value, null) - there was one(async).setIfNotExists(classTagKey, classTag, null) - } + test("expire") { (async, cache) => + for { + _ <- async.expect.expire(cacheKey, expiration) + _ <- cache.expire(cacheKey, expirationInt).assertingDone + } yield Passed + } - "append with expiration" in new MockedJavaRedis { - async.append(anyString, anyString, any[Duration]) returns Done - async.setIfNotExists(anyString, anyString, any[Duration]) returns false - cache.append(key, value, expirationInt).asScala must beDone.await - there was one(async).append(key, value, expiration) - there was one(async).setIfNotExists(classTagKey, classTag, expiration) - } + test("expires in (defined)") { (async, cache) => + for { + _ <- async.expect.expiresIn(cacheKey, Some(expiration)) + _ <- cache.expiresIn(cacheKey).assertingEqual(Optional.of(expirationLong)) + } yield Passed + } - "expire" in new MockedJavaRedis { - async.expire(anyString, any[Duration]) returns Done - cache.expire(key, expirationInt).asScala must beDone.await - there was one(async).expire(key, expiration) - there was one(async).expire(classTagKey, expiration) - } + test("expires in (undefined)") { (async, cache) => + for { + _ <- async.expect.expiresIn(cacheKey, None) + _ <- cache.expiresIn(cacheKey).assertingEqual(Optional.empty) + } yield Passed + } - "expires in (defined)" in new MockedJavaRedis { - async.expiresIn(anyString) returns Some(expiration) - cache.expiresIn(key).asScala must beEqualTo(Optional.of(expirationLong)).await - there was one(async).expiresIn(key) - there was no(async).expiresIn(classTagKey) - } + test("matching") { (async, cache) => + for { + _ <- async.expect.matching("pattern", Seq(cacheKey)) + _ <- cache.matching("pattern").asserting(_.asScala.toList mustEqual List(cacheKey)) + } yield Passed + } - "expires in (undefined)" in new MockedJavaRedis { - async.expiresIn(anyString) returns None - cache.expiresIn(key).asScala must beEqualTo(Optional.empty).await - there was one(async).expiresIn(key) - there was no(async).expiresIn(classTagKey) - } + test("remove multiple") { (async, cache) => + for { + _ <- async.expect.removeAll(cacheKey, otherKey) + _ <- cache.remove(cacheKey, otherKey).assertingDone + } yield Passed + } - "matching" in new MockedJavaRedis { - async.matching(anyString) returns Seq(key) - cache.matching("pattern").asScala.map(_.asScala) must beEqualTo(Seq(key)).await - there was one(async).matching("pattern") - } + test("remove all (invalidate)") { (async, cache) => + for { + _ <- async.expect.invalidate() + _ <- cache.removeAll().assertingDone + } yield Passed + } - "remove multiple" in new MockedJavaRedis { - async.removeAll(anyVarArgs) returns Done - cache.remove(key, key, key, key).asScala must beDone.await - there was one(async).removeAll(key, classTagKey, key, classTagKey, key, classTagKey, key, classTagKey) - } + test("remove all (some keys provided)") { (async, cache) => + for { + _ <- async.expect.removeAll(cacheKey, otherKey) + _ <- cache.removeAllKeys(cacheKey, otherKey).assertingDone + } yield Passed + } - "remove all" in new MockedJavaRedis { - async.removeAll(anyVarArgs) returns Done - cache.removeAllKeys(key, key, key, key).asScala must beDone.await - there was one(async).removeAll(key, classTagKey, key, classTagKey, key, classTagKey, key, classTagKey) + test("remove matching") { (async, cache) => + for { + _ <- async.expect.removeMatching("pattern") + _ <- cache.removeMatching("pattern").assertingDone + } yield Passed } - "remove matching" in new MockedJavaRedis { - async.removeMatching(anyString) returns Done - cache.removeMatching("pattern").asScala must beDone.await - there was one(async).removeMatching("pattern") - there was one(async).removeMatching("classTag::pattern") - } + test("exists") { (async, cache) => + for { + _ <- async.expect.exists(cacheKey, exists = true) + _ <- cache.exists(cacheKey).assertingEqual(true) + } yield Passed + } - "exists" in new MockedJavaRedis { - async.exists(beEq(key)) returns true - cache.exists(key).asScala.map(Boolean.unbox) must beTrue.await - there was one(async).exists(key) - there was no(async).exists(classTagKey) - } + test("increment") { (async, cache) => + for { + _ <- async.expect.increment(cacheKey, 1L, result = 10L) + _ <- async.expect.increment(cacheKey, 2L, result = 20L) + _ <- cache.increment(cacheKey).assertingEqual(10L) + _ <- cache.increment(cacheKey, 2L).assertingEqual(20L) + } yield Passed + } - "increment" in new MockedJavaRedis { - async.increment(beEq(key), anyLong) returns 10L - cache.increment(key).asScala.map(Long.unbox) must beEqualTo(10L).await - cache.increment(key, 2L).asScala.map(Long.unbox) must beEqualTo(10L).await - there was one(async).increment(key, by = 1L) - there was one(async).increment(key, by = 2L) - } + test("decrement") { (async, cache) => + for { + _ <- async.expect.decrement(cacheKey, 1L, result = 10L) + _ <- async.expect.decrement(cacheKey, 2L, result = 20L) + _ <- cache.decrement(cacheKey).assertingEqual(10L) + _ <- cache.decrement(cacheKey, 2L).assertingEqual(20L) + } yield Passed + } - "decrement" in new MockedJavaRedis { - async.decrement(beEq(key), anyLong) returns 10L - cache.decrement(key).asScala.map(Long.unbox) must beEqualTo(10L).await - cache.decrement(key, 2L).asScala.map(Long.unbox) must beEqualTo(10L).await - there was one(async).decrement(key, by = 1L) - there was one(async).decrement(key, by = 2L) - } + test("create list") { (async, cache) => + trait RedisListMock extends RedisList[String, Future] + val list = mock[RedisListMock] + for { + _ <- async.expect.list[String](cacheKey, list) + _ <- cache.list(cacheKey, classOf[String]) mustBe a[AsyncRedisList[_]] + } yield Passed + } - "create list" in new MockedJavaRedis { - private val list = mock[RedisList[String, Future]] - async.list(beEq(key))(anyClassTag[String]) returns list - cache.list(key, classOf[String]) must beAnInstanceOf[AsyncRedisList[String]] - there was one(async).list[String](key) - } + test("create set") { (async, cache) => + trait RedisSetMock extends RedisSet[String, Future] + val set = mock[RedisSetMock] + for { + _ <- async.expect.set[String](cacheKey, set) + _ <- cache.set(cacheKey, classOf[String]) mustBe a[AsyncRedisSet[_]] + } yield Passed + } - "create set" in new MockedJavaRedis { - private val set = mock[RedisSet[String, Future]] - async.set(beEq(key))(anyClassTag[String]) returns set - cache.set(key, classOf[String]) must beAnInstanceOf[AsyncRedisSet[String]] - there was one(async).set[String](key) - } + test("create map") { (async, cache) => + trait RedisMapMock extends RedisMap[String, Future] + val map = mock[RedisMapMock] + for { + _ <- async.expect.map[String](cacheKey, map) + _ <- cache.map(cacheKey, classOf[String]) mustBe a[AsyncRedisMap[_]] + } yield Passed + } - "create map" in new MockedJavaRedis { - private val map = mock[RedisMap[String, Future]] - async.map(beEq(key))(anyClassTag[String]) returns map - cache.map(key, classOf[String]) must beAnInstanceOf[AsyncRedisMap[String]] - there was one(async).map[String](key) + private def test(name: String)(f: (AsyncRedisMock, play.cache.redis.AsyncCacheApi) => Future[Assertion]): Unit = { + name in { + implicit val runtime: RedisRuntime = redisRuntime( + invocationPolicy = LazyInvocation, + recoveryPolicy = recoveryPolicy.default, + ) + implicit val environment: Environment = Environment( + rootPath = new java.io.File("."), + classLoader = getClass.getClassLoader, + mode = Mode.Test + ) + val async = mock[AsyncRedisMock] + val cache: play.cache.redis.AsyncCacheApi = new AsyncJavaRedis(async) + + f(async, cache) } } } diff --git a/src/test/scala/play/api/cache/redis/impl/AsyncRedisMock.scala b/src/test/scala/play/api/cache/redis/impl/AsyncRedisMock.scala new file mode 100644 index 00000000..a60d7bb6 --- /dev/null +++ b/src/test/scala/play/api/cache/redis/impl/AsyncRedisMock.scala @@ -0,0 +1,244 @@ +package play.api.cache.redis.impl + +import akka.Done +import org.scalamock.scalatest.AsyncMockFactoryBase +import play.api.cache.redis._ + +import scala.concurrent.Future +import scala.concurrent.duration.Duration +import scala.reflect.ClassTag + +private[impl] trait AsyncRedisMock { this: AsyncMockFactoryBase => + + protected[impl] trait AsyncRedisMock extends AsyncRedis { + + final override def removeAll(keys: String*): AsynchronousResult[Done] = + removeAllKeys(keys) + + def removeAllKeys(keys: Seq[String]): AsynchronousResult[Done] + + final override def getAll[T: ClassTag](keys: Iterable[String]): AsynchronousResult[Seq[Option[T]]] = + getAllKeys(keys) + + def getAllKeys[T: ClassTag](keys: Iterable[String]): AsynchronousResult[Seq[Option[T]]] + } + + final protected implicit class AsyncRedisOps(async: AsyncRedisMock) { + def expect: AsyncRedisExpectation = + new AsyncRedisExpectation(async) + } + + protected final class AsyncRedisExpectation(async: AsyncRedisMock) { + + private def classTagKey(key: String): String = s"classTag::$key" + + private def classTagValue: Any => String = { + case null => "null" + case v if v.getClass == classOf[String] => "java.lang.String" + case other => throw new IllegalArgumentException(s"Unexpected value for classTag: ${other.getClass.getSimpleName}") + } + + def getClassTag(key: String, value: Option[String]): Future[Unit] = + get(classTagKey(key), value) + + def get[T: ClassTag](key: String, value: Option[T]): Future[Unit] = + Future.successful { + (async.get(_: String)(_: ClassTag[_])) + .expects(key, implicitly[ClassTag[T]]) + .returning(Future.successful(value)) + .once() + } + + def getAllKeys[T: ClassTag](keys: Iterable[String], values: Seq[Option[T]]): Future[Unit] = + Future.successful { + (async.getAllKeys(_: Iterable[String])(_: ClassTag[_])) + .expects(keys, implicitly[ClassTag[T]]) + .returning(Future.successful(values)) + .once() + } + + def setValue[T](key: String, value: T, duration: Duration): Future[Unit] = + Future.successful { + (async.set(_: String, _: Any, _: Duration)) + .expects(key, if (value == null) * else value, duration) + .returning(Future.successful(Done)) + .once() + } + + def setClassTag[T: ClassTag](key: String, value: T, duration: Duration): Future[Unit] = + setValue(classTagKey(key), value, duration) + + def set[T: ClassTag](key: String, value: T, duration: Duration): Future[Unit] = + for { + _ <- setValue(key, value, duration) + _ <- setClassTag(key, classTagValue(value), duration) + } yield () + + def setValueIfNotExists[T: ClassTag](key: String, value: T, duration: Duration, exists: Boolean): Future[Unit] = + Future.successful { + (async.setIfNotExists(_: String, _: Any, _: Duration)) + .expects(key, if (value == null) * else value, duration) + .returning(Future.successful(exists)) + .once() + } + + def setClassTagIfNotExists[T: ClassTag](key: String, value: T, duration: Duration, exists: Boolean): Future[Unit] = + setValueIfNotExists(classTagKey(key), classTagValue(value), duration, exists) + + def setIfNotExists[T: ClassTag](key: String, value: T, duration: Duration, exists: Boolean): Future[Unit] = + for { + _ <- setValueIfNotExists(key, value, duration, exists) + _ <- setClassTagIfNotExists(key, value, duration, exists) + } yield () + + def setAll[T: ClassTag](values: (String, Any)*): Future[Unit] = + Future.successful { + val valuesWithClassTags = values.flatMap { + case (k, v) => Seq((k, v), (classTagKey(k), classTagValue(v))) + } + (async.setAll _) + .expects(valuesWithClassTags) + .returning(Future.successful(Done)) + .once() + } + + def setAllIfNotExist[T: ClassTag](values: Seq[(String, Any)], exists: Boolean): Future[Unit] = + Future.successful { + val valuesWithClassTags = values.flatMap { + case (k, v) => Seq((k, v), (classTagKey(k), classTagValue(v))) + } + (async.setAllIfNotExist _) + .expects(valuesWithClassTags) + .returning(Future.successful(exists)) + .once() + } + + def expire(key: String, duration: Duration): Future[Unit] = + Future.successful { + (async.expire(_: String, _: Duration)) + .expects(classTagKey(key), duration) + .returning(Future.successful(Done)) + .once() + (async.expire(_: String, _: Duration)) + .expects(key, duration) + .returning(Future.successful(Done)) + .once() + } + + def expiresIn(key: String, duration: Option[Duration]): Future[Unit] = + Future.successful { + (async.expiresIn(_: String)) + .expects(key) + .returning(Future.successful(duration)) + .once() + } + + def matching(pattern: String, keys: Seq[String]): Future[Unit] = + Future.successful { + (async.matching(_: String)) + .expects(pattern) + .returning(Future.successful(keys)) + .once() + } + + def removeMatching(pattern: String): Future[Unit] = { + def removePattern(patternToRemove: String) = + (async.removeMatching(_: String)) + .expects(patternToRemove) + .returning(Future.successful(Done)) + .once() + + Future.successful { + removePattern(pattern) + removePattern(classTagKey(pattern)) + } + } + + def exists(key: String, exists: Boolean): Future[Unit] = + Future.successful { + (async.exists(_: String)) + .expects(key) + .returning(Future.successful(exists)) + .once() + } + + def increment(key: String, by: Long, result: Long): Future[Unit] = + Future.successful { + (async.increment(_: String, _: Long)) + .expects(key, by) + .returning(Future.successful(result)) + .once() + } + + def decrement(key: String, by:Long, result:Long): Future[Unit] = + Future.successful { + (async.decrement(_: String, _:Long)) + .expects(key, by) + .returning(Future.successful(result)) + .once() + } + + def remove(key: String): Future[Unit] = + Future.successful { + (async.remove(_: String)) + .expects(classTagKey(key)) + .returning(Future.successful(Done)) + .once() + (async.remove(_: String)) + .expects(key) + .returning(Future.successful(Done)) + .once() + } + + def removeAll(keys: String*): Future[Unit] = + Future.successful { + val keysWithClassTags = keys.flatMap { + key => Seq(key, classTagKey(key)) + } + (async.removeAllKeys _) + .expects(keysWithClassTags) + .returning(Future.successful(Done)) + .once() + } + + def append(key: String, value: String, expiration: Duration): Future[Unit] = + Future.successful { + (async.append(_: String, _: String, _: Duration)) + .expects(key, value, expiration) + .returning(Future.successful(Done)) + .once() + } + + def invalidate(): Future[Unit] = + Future.successful { + (() => async.invalidate()) + .expects() + .returning(Future.successful(Done)) + .once() + } + + def list[T: ClassTag](key: String, mock: RedisList[T, AsynchronousResult]): Future[Unit] = + Future.successful { + (async.list[T](_: String)(_: ClassTag[T])) + .expects(key, implicitly[ClassTag[T]]) + .returning(mock) + .once() + } + + def set[T: ClassTag](key: String, mock: RedisSet[T, AsynchronousResult]): Future[Unit] = + Future.successful { + (async.set[T](_: String)(_: ClassTag[T])) + .expects(key, implicitly[ClassTag[T]]) + .returning(mock) + .once() + } + + def map[T: ClassTag](key: String, mock: RedisMap[T, AsynchronousResult]): Future[Unit] = + Future.successful { + (async.map[T](_: String)(_: ClassTag[T])) + .expects(key, implicitly[ClassTag[T]]) + .returning(mock) + .once() + } + } +} \ No newline at end of file diff --git a/src/test/scala/play/api/cache/redis/impl/AsyncRedisSpec.scala b/src/test/scala/play/api/cache/redis/impl/AsyncRedisSpec.scala index d98d4311..d32a6111 100644 --- a/src/test/scala/play/api/cache/redis/impl/AsyncRedisSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/AsyncRedisSpec.scala @@ -1,68 +1,83 @@ package play.api.cache.redis.impl import scala.concurrent.duration._ - import play.api.cache.redis._ +import play.api.cache.redis.test._ -import org.specs2.concurrent.ExecutionEnv -import org.specs2.mutable.Specification +import scala.concurrent.Future -class AsyncRedisSpec(implicit ee: ExecutionEnv) extends Specification with ReducedMockito { - import Implicits._ - import RedisCacheImplicits._ - import org.mockito.ArgumentMatchers._ +class AsyncRedisSpec extends AsyncUnitSpec with RedisConnectorMock with RedisRuntimeMock with ImplicitFutureMaterialization { + import Helpers._ - "AsyncRedis" should { + test("removeAll") { (connector, cache) => + for { + _ <- connector.expect.invalidate() + _ <- cache.removeAll().assertingDone + } yield Passed + } - "removeAll" in new MockedAsyncRedis { - connector.invalidate() returns unit - cache.removeAll() must beDone.await - there was one(connector).invalidate() - } + test("getOrElseUpdate (hit)") { (connector, cache) => + for { + _ <- connector.expect.get(cacheKey, Some(cacheValue)) + orElse = probe.orElse.async(cacheValue) + _ <- cache.getOrElseUpdate[String](cacheKey)(orElse.execute()).assertingEqual(cacheValue) + _ = orElse.calls mustEqual 0 + } yield Passed + } - "getOrElseUpdate (hit)" in new MockedAsyncRedis with OrElse { - connector.get[String](anyString)(anyClassTag) returns Some(value) - cache.getOrElseUpdate(key)(doFuture(value)) must beEqualTo(value).await - orElse mustEqual 0 - } + test("getOrElseUpdate (miss)") { (connector, cache) => + for { + _ <- connector.expect.get[String](cacheKey, None) + _ <- connector.expect.set(cacheKey, cacheValue, Duration.Inf, result = true) + orElse = probe.orElse.async(cacheValue) + _ <- cache.getOrElseUpdate[String](cacheKey)(orElse.execute()).assertingEqual(cacheValue) + _ = orElse.calls mustEqual 1 + } yield Passed + } - "getOrElseUpdate (miss)" in new MockedAsyncRedis with OrElse { - connector.get[String](anyString)(anyClassTag) returns None - connector.set(anyString, anyString, any[Duration], anyBoolean) returns true - cache.getOrElseUpdate(key)(doFuture(value)) must beEqualTo(value).await - orElse mustEqual 1 - } + test("getOrElseUpdate (failure)") { (connector, cache) => + for { + _ <- connector.expect.get[String](cacheKey, SimulatedException.asRedis) + orElse = probe.orElse.async(cacheValue) + _ <- cache.getOrElseUpdate[String](cacheKey)(orElse.execute()).assertingEqual(cacheValue) + _ = orElse.calls mustEqual 1 + } yield Passed + } - "getOrElseUpdate (failure)" in new MockedAsyncRedis with OrElse { - connector.get[String](anyString)(anyClassTag) returns ex - cache.getOrElseUpdate(key)(doFuture(value)) must beEqualTo(value).await - orElse mustEqual 1 - } + test("getOrElseUpdate (failing orElse)") { (connector, cache) => + for { + _ <- connector.expect.get[String](cacheKey, None) + orElse = probe.orElse.failing(SimulatedException.asRedis) + _ <- cache.getOrElseUpdate[String](cacheKey)(orElse.execute()).assertingFailure[TimeoutException] + _ = orElse.calls mustEqual 2 + } yield Passed + } - "getOrElseUpdate (failing orElse)" in new MockedAsyncRedis with OrElse { - connector.get[String](anyString)(anyClassTag) returns None - cache.getOrElseUpdate[String](key)(failedFuture) must throwA[TimeoutException].await - orElse mustEqual 2 - } + test("getOrElseUpdate (rerun)", policy = recoveryPolicy.rerun) { (connector, cache) => + for { + _ <- connector.expect.get[String](cacheKey, None) + _ <- connector.expect.get[String](cacheKey, None) + _ <- connector.expect.set(cacheKey, cacheValue, Duration.Inf, result = true) + orElse = probe.orElse.generic( + Future.failed(SimulatedException.asRedis), + Future.successful(cacheValue), + ) + _ <- cache.getOrElseUpdate[String](cacheKey)(orElse.execute()).assertingEqual(cacheValue) + _ = orElse.calls mustEqual 2 + } yield Passed + } + + private def test(name: String, policy: RecoveryPolicy = recoveryPolicy.default)(f: (RedisConnectorMock, AsyncRedis) => Future[Assertion]): Unit = { + name in { + implicit val runtime: RedisRuntime = redisRuntime( + invocationPolicy = LazyInvocation, + recoveryPolicy = policy, + ) + val connector = mock[RedisConnectorMock] + val cache: AsyncRedis = new AsyncRedisImpl(connector) - "getOrElseUpdate (rerun)" in new MockedAsyncRedis with OrElse with Attempts { - override protected def policy = new RecoveryPolicy { - def recoverFrom[T](rerun: => Future[T], default: => Future[T], failure: RedisException) = rerun - } - connector.get[String](anyString)(anyClassTag) returns None - connector.set(anyString, anyString, any[Duration], anyBoolean) returns true - // run the test - cache.getOrElseUpdate(key) { - attempts match { - case 0 => attempt(failedFuture) - case _ => attempt(doFuture(value)) - } - } must beEqualTo(value).await - // verification - orElse mustEqual 2 - there were two(connector).get[String](anyString)(anyClassTag) - there was one(connector).set(key, value, Duration.Inf, ifNotExists = false) + f(connector, cache) } } } diff --git a/src/test/scala/play/api/cache/redis/impl/BuildersSpec.scala b/src/test/scala/play/api/cache/redis/impl/BuildersSpec.scala index 7ea241d5..e3725e81 100644 --- a/src/test/scala/play/api/cache/redis/impl/BuildersSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/BuildersSpec.scala @@ -1,29 +1,32 @@ package play.api.cache.redis.impl -import scala.concurrent.duration._ -import scala.concurrent.{ExecutionContext, Future} - -import play.api.cache.redis._ - import akka.pattern.AskTimeoutException -import org.specs2.concurrent.ExecutionEnv -import org.specs2.mock.Mockito -import org.specs2.mutable.Specification -import org.specs2.specification._ +import play.api.cache.redis._ +import play.api.cache.redis.test._ -class BuildersSpec(implicit ee: ExecutionEnv) extends Specification with ReducedMockito with WithApplication { +import scala.concurrent.duration._ +import scala.concurrent.{Future, Promise} +class BuildersSpec extends AsyncUnitSpec with RedisRuntimeMock { import Builders._ - import BuildersSpec._ - import Implicits._ - def defaultTask = Future.successful("default") + private case class Task( + response: String, + execution: () => Future[String], + ) extends (() => Future[String]) { - def regularTask = Future("response") + def this(response: String)(f: String => Future[String]) = + this(response, () => f(response)) - def longTask = Future.after(seconds = 2, "response") + def apply(): Future[String] = execution() + } - def failingTask = Future.failed(TimeoutException(new IllegalArgumentException("Simulated failure."))) + private object Task { + val resolved: Task = new Task("default response")(Future.successful) + val regular: Task = new Task("regular response")(Future(_)) + def failing(): Future[String] = Future.failed(TimeoutException(SimulatedException)) + def infinite(): Future[String] = Promise[String]().future + } "AsynchronousBuilder" should { @@ -31,26 +34,29 @@ class BuildersSpec(implicit ee: ExecutionEnv) extends Specification with Reduced AsynchronousBuilder.name mustEqual "AsynchronousBuilder" } - "run" in new RuntimeMock { - AsynchronousBuilder.toResult(regularTask, defaultTask) must beEqualTo("response").await + "run regular task" in { + implicit val runtime: RedisRuntime = redisRuntime() + AsynchronousBuilder.toResult(Task.regular(), Task.resolved()).assertingEqual(Task.regular.response) } - "run long running task" in new RuntimeMock { - AsynchronousBuilder.toResult(longTask, defaultTask) must beEqualTo("response").awaitFor(3.seconds) + "run resolved task" in { + implicit val runtime: RedisRuntime = redisRuntime() + AsynchronousBuilder.toResult(Task.resolved(),Task.regular()).assertingEqual(Task.resolved.response) } - "recover with default policy" in new RuntimeMock { - runtime.policy returns defaultPolicy - AsynchronousBuilder.toResult(failingTask, defaultTask) must beEqualTo("default").await + "recover with default policy" in { + implicit val runtime: RedisRuntime = redisRuntime(recoveryPolicy = recoveryPolicy.default) + AsynchronousBuilder.toResult(Task.failing(),Task.resolved()).assertingEqual(Task.resolved.response) } - "recover with fail through policy" in new RuntimeMock { - runtime.policy returns failThrough - AsynchronousBuilder.toResult(failingTask, defaultTask) must throwA[TimeoutException].await + "recover with fail through policy" in { + implicit val runtime: RedisRuntime = redisRuntime(recoveryPolicy = recoveryPolicy.failThrough) + AsynchronousBuilder.toResult(Task.failing(),Task.resolved()).assertingFailure[TimeoutException] } - "map value" in new RuntimeMock { - AsynchronousBuilder.map(Future(5))(_ + 5) must beEqualTo(10).await + "map value" in { + implicit val runtime: RedisRuntime = redisRuntime(recoveryPolicy = recoveryPolicy.failThrough) + AsynchronousBuilder.map(Future(5))(_ + 5).assertingEqual(10) } } @@ -60,51 +66,55 @@ class BuildersSpec(implicit ee: ExecutionEnv) extends Specification with Reduced SynchronousBuilder.name mustEqual "SynchronousBuilder" } - "run" in new RuntimeMock { - SynchronousBuilder.toResult(regularTask, defaultTask) must beEqualTo("response") + "run regular task" in { + implicit val runtime: RedisRuntime = redisRuntime() + SynchronousBuilder.toResult(Task.regular(), Task.resolved()) mustEqual Task.regular.response } - "recover from failure with default policy" in new RuntimeMock { - runtime.policy returns defaultPolicy - SynchronousBuilder.toResult(failingTask, defaultTask) must beEqualTo("default") + "run resolved task" in { + implicit val runtime: RedisRuntime = redisRuntime() + SynchronousBuilder.toResult(Task.resolved(), Task.regular()) mustEqual Task.resolved.response } - "recover from failure with fail through policy" in new RuntimeMock { - runtime.policy returns failThrough - SynchronousBuilder.toResult(failingTask, defaultTask) must throwA[TimeoutException] + "recover from failure with default policy" in { + implicit val runtime: RedisRuntime = redisRuntime(recoveryPolicy = recoveryPolicy.default) + SynchronousBuilder.toResult(Task.failing(), Task.resolved()) mustEqual Task.resolved.response } - "recover from timeout due to long running task" in new RuntimeMock { - runtime.policy returns failThrough - SynchronousBuilder.toResult(longTask, defaultTask) must throwA[TimeoutException] + "don't recover from failure with fail through policy" in { + implicit val runtime: RedisRuntime = redisRuntime(recoveryPolicy = recoveryPolicy.failThrough) + assertThrows[TimeoutException] { + SynchronousBuilder.toResult(Task.failing(), Task.resolved()) + } } - "recover from akka ask timeout" in new RuntimeMock { - runtime.policy returns failThrough - val actorFailure = Future.failed(new AskTimeoutException("Simulated actor ask timeout")) - SynchronousBuilder.toResult(actorFailure, defaultTask) must throwA[TimeoutException] + "don't recover on timeout due to long running task with fail through policy" in { + implicit val runtime: RedisRuntime = redisRuntime( + recoveryPolicy = recoveryPolicy.failThrough, + timeout = 1.millis + ) + assertThrows[TimeoutException] { + SynchronousBuilder.toResult(Task.infinite(), Task.resolved()) + } } - "map value" in new RuntimeMock { - SynchronousBuilder.map(5)(_ + 5) must beEqualTo(10) + "recover from timeout due to long running task with default policy" in { + implicit val runtime: RedisRuntime = redisRuntime( + recoveryPolicy = recoveryPolicy.default, + timeout = 1.millis + ) + SynchronousBuilder.toResult(Task.infinite(), Task.resolved()) mustEqual Task.resolved.response } - } -} - -object BuildersSpec { - - trait RuntimeMock extends Scope { - - import MockitoImplicits._ - - private val timeout = akka.util.Timeout(1.second) - implicit protected val runtime: RedisRuntime = mock[RedisRuntime] - runtime.timeout returns timeout - runtime.context returns ExecutionContext.global - - protected def failThrough = new FailThrough {} + "recover from akka ask timeout" in { + implicit val runtime: RedisRuntime = redisRuntime(recoveryPolicy = recoveryPolicy.default) + val actorFailure = Future.failed(new AskTimeoutException("Simulated actor ask timeout")) + SynchronousBuilder.toResult(actorFailure, Task.resolved()) mustEqual Task.resolved.response + } - protected def defaultPolicy = new RecoverWithDefault {} + "map value" in { + implicit val runtime: RedisRuntime = redisRuntime() + SynchronousBuilder.map(5)(_ + 5) mustEqual 10 + } } } diff --git a/src/test/scala/play/api/cache/redis/impl/InvocationPolicySpec.scala b/src/test/scala/play/api/cache/redis/impl/InvocationPolicySpec.scala index dc9297e5..1595ad5e 100644 --- a/src/test/scala/play/api/cache/redis/impl/InvocationPolicySpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/InvocationPolicySpec.scala @@ -1,39 +1,34 @@ package play.api.cache.redis.impl -import scala.concurrent.Future -import scala.concurrent.duration._ - import play.api.cache.redis._ +import play.api.cache.redis.test.UnitSpec -import org.specs2.concurrent.ExecutionEnv -import org.specs2.mutable.Specification - -class InvocationPolicySpec(implicit ee: ExecutionEnv) extends Specification with ReducedMockito with WithApplication { - - import Implicits._ - import RedisCacheImplicits._ +import scala.concurrent._ +import scala.util.Success - val andThen = "then" +class InvocationPolicySpec extends UnitSpec { - def longTask(andThen: => Unit) = Future.after(seconds = 2, { andThen; "result" }) + private implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.parasitic - "InvocationPolicy" should { + private class Probe { + private val promise = Promise[Unit]() + def resolve(): Unit = promise.success(()) + def run(): Future[Unit] = promise.future + } - "invoke lazily, i.e., slowly" in { - var resolved = false - val promise = longTask { resolved = true } - LazyInvocation.invoke(promise, andThen) must beEqualTo(andThen).awaitFor(3.seconds) - resolved mustEqual true - promise.isCompleted mustEqual true - } + "invoke lazily, i.e., slowly" in { + val probe = new Probe + val outcome = LazyInvocation.invoke(probe.run(), Done) + outcome.value mustEqual None + probe.resolve() + outcome.value mustEqual Some(Success(Done)) + } - "invoke eagerly, i.e., return immediately" in { - var resolved = false - val promise = longTask { resolved = true } - EagerInvocation.invoke(promise, andThen) must beEqualTo(andThen).awaitFor(3.seconds) - resolved mustEqual false - promise must beEqualTo("result").awaitFor(3.seconds) - resolved mustEqual true - } + "invoke eagerly, i.e., return immediately" in { + val probe = new Probe + val outcome = EagerInvocation.invoke(probe.run(), Done) + outcome.isCompleted mustEqual true + probe.resolve() + outcome.isCompleted mustEqual true } } diff --git a/src/test/scala/play/api/cache/redis/impl/RedisCacheImplicits.scala b/src/test/scala/play/api/cache/redis/impl/RedisCacheImplicits.scala deleted file mode 100644 index 7db3b3c6..00000000 --- a/src/test/scala/play/api/cache/redis/impl/RedisCacheImplicits.scala +++ /dev/null @@ -1,166 +0,0 @@ -package play.api.cache.redis.impl - -import scala.collection.mutable -import scala.concurrent.duration._ -import scala.concurrent.{ExecutionContext, Future} -import scala.reflect.ClassTag - -import play.api.Environment -import play.api.cache.redis._ - -import org.mockito.InOrder -import org.specs2.matcher.Matchers -import org.specs2.specification.Scope - -object RedisCacheImplicits { - import MockitoImplicits._ - - type Future[T] = scala.concurrent.Future[T] - - def anyClassTag[T: ClassTag] = org.mockito.ArgumentMatchers.any[ClassTag[T]] - - //noinspection UnitMethodIsParameterless - def unit: Unit = () - - def execDone: Done = Done - def beDone = Matchers.beEqualTo(Done) - - def anyVarArgs[T] = org.mockito.ArgumentMatchers.any[T] - - def beEq[T](value: T) = org.mockito.ArgumentMatchers.eq(value) - - def there = MockitoImplicits.there - def one[T <: AnyRef](mock: T)(implicit anOrder: Option[InOrder] = inOrder()) = MockitoImplicits.one(mock) - def two[T <: AnyRef](mock: T)(implicit anOrder: Option[InOrder] = inOrder()) = MockitoImplicits.two(mock) - - val ex = TimeoutException(new IllegalArgumentException("Simulated failure.")) - - object NoSuchElementException extends NoSuchElementException - - trait AbstractMocked extends Scope { - protected val key = "key" - protected val value = "value" - protected val other = "other" - - protected def invocation = LazyInvocation - - protected def policy: RecoveryPolicy = new RecoverWithDefault {} - - protected implicit val runtime: RedisRuntime = mock[RedisRuntime] - runtime.context returns ExecutionContext.global - runtime.invocation returns invocation - runtime.prefix returns RedisEmptyPrefix - runtime.policy returns policy - } - - class MockedConnector extends AbstractMocked { - protected val connector = mock[RedisConnector] - } - - class MockedCache extends MockedConnector { - protected lazy val cache = new RedisCache(connector, Builders.AsynchronousBuilder) - } - - class MockedList extends MockedCache { - protected val data = new mutable.ListBuffer[String] - data.appendAll(Iterable(other, value, value)) - - protected val list = cache.list[String]("key") - } - - class MockedSet extends MockedCache { - protected val data = mutable.Set[String](other, value) - - protected val set = cache.set[String]("key") - } - - class MockedSortedSet extends MockedCache { - protected val scoreValue: (Double, String) = (1.0, "value") - protected val otherScoreValue: (Double, String) = (2.0, "other") - - protected val set = cache.zset[String]("key") - } - - class MockedMap extends MockedCache { - protected val field = "field" - - protected val map = cache.map[String]("key") - } - - class MockedAsyncRedis extends MockedConnector { - protected val cache: AsyncRedis = new AsyncRedisImpl(connector) - } - - class MockedSyncRedis extends MockedConnector { - protected val cache = new SyncRedis(connector) - runtime.timeout returns akka.util.Timeout(1.second) - } - - class MockedJavaRedis extends AbstractMocked { - protected val expiration = 5.seconds - protected val expirationLong = expiration.toSeconds - protected val expirationInt = expirationLong.intValue - protected val classTag = "java.lang.String" - protected val classTagKey = s"classTag::$key" - protected val classTagOther = s"classTag::$other" - - protected implicit val environment: Environment = mock[Environment] - protected val async = mock[AsyncRedis] - protected val cache: play.cache.redis.AsyncCacheApi = new AsyncJavaRedis(async) - - environment.classLoader returns getClass.getClassLoader - } - - class MockedJavaList extends AbstractMocked { - protected val internal = mock[RedisList[String, Future]] - protected val view = mock[internal.RedisListView] - protected val modifier = mock[internal.RedisListModification] - protected val list = new RedisListJavaImpl(internal) - internal.view returns view - internal.modify returns modifier - } - - class MockedJavaSet extends MockedJavaRedis { - protected val internal = mock[RedisSet[String, Future]] - protected val set = new RedisSetJavaImpl(internal) - } - - class MockedJavaMap extends MockedJavaRedis { - val field = "field" - protected val internal = mock[RedisMap[String, Future]] - protected val map = new RedisMapJavaImpl(internal) - } - - trait OrElse extends Scope { - - protected var orElse = 0 - - def doElse[T](value: T): T = { - orElse += 1 - value - } - - def doFuture[T](value: T): Future[T] = { - Future.successful(doElse(value)) - } - - def failedFuture: Future[Nothing] = { - orElse += 1 - Future.failed(failure) - } - - def fail = throw failure - - private def failure = TimeoutException(new IllegalArgumentException("This should no be reached")) - } - - trait Attempts extends Scope { - - protected var attempts = 0 - - def attempt[T](f: => T): T = { - attempts += 1 - f - } - } -} diff --git a/src/test/scala/play/api/cache/redis/impl/RedisCacheSpec.scala b/src/test/scala/play/api/cache/redis/impl/RedisCacheSpec.scala index 2a6a0ac7..9c71c56c 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisCacheSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisCacheSpec.scala @@ -1,318 +1,433 @@ package play.api.cache.redis.impl -import scala.concurrent.duration._ - import play.api.cache.redis._ +import play.api.cache.redis.test._ -import org.specs2.concurrent.ExecutionEnv -import org.specs2.mutable.Specification - -class RedisCacheSpec(implicit ee: ExecutionEnv) extends Specification with ReducedMockito { - import Implicits._ - import RedisCacheImplicits._ +import scala.concurrent.Future +import scala.concurrent.duration.Duration - import org.mockito.ArgumentMatchers._ +class RedisCacheSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConnectorMock with ImplicitFutureMaterialization { + import Helpers._ - val expiration = 1.second + test("get and miss") { (cache, connector) => + for { + _ <- connector.expect.get[String](cacheKey, result = None) + _ <- cache.get[String](cacheKey).assertingEqual(None) + } yield Passed + } - "Redis Cache" should { + test("get and hit") { (cache, connector) => + for { + _ <- connector.expect.get[String](cacheKey, result = Some(cacheValue)) + _ <- cache.get[String](cacheKey).assertingEqual(Some(cacheValue)) + } yield Passed + } - "get and miss" in new MockedCache { - connector.get[String](anyString)(anyClassTag) returns None - cache.get[String](key) must beNone.await - } + test("get recover with default") { (cache, connector) => + for { + _ <- connector.expect.get[String](cacheKey, result = failure) + _ <- cache.get[String](cacheKey).assertingEqual(None) + } yield Passed + } - "get and hit" in new MockedCache { - connector.get[String](anyString)(anyClassTag) returns Some(value) - cache.get[String](key) must beSome(value).await - } + test("get all") { (cache, connector) => + for { + _ <- connector.expect.mGet[String](Seq(cacheKey, cacheKey, cacheKey), result = Seq(Some(cacheValue), None, None)) + _ <- cache.getAll[String](cacheKey, cacheKey, cacheKey).assertingEqual(Seq(Some(cacheValue), None, None)) + } yield Passed + } - "get recover with default" in new MockedCache { - connector.get[String](anyString)(anyClassTag) returns ex - cache.get[String](key) must beNone.await - } + test("get all recover with default") { (cache, connector) => + for { + _ <- connector.expect.mGet[String](Seq(cacheKey, cacheKey, cacheKey), result = failure) + _ <- cache.getAll[String](cacheKey, cacheKey, cacheKey).assertingEqual(Seq(None, None, None)) + } yield Passed + } - "get all" in new MockedCache { - connector.mGet[String](anyVarArgs)(anyClassTag) returns Seq(Some(value), None, None) - cache.getAll[String](key, key, key) must beEqualTo(Seq(Some(value), None, None)).await - } + test("get all (keys in a collection)") { (cache, connector) => + for { + _ <- connector.expect.mGet[String](Seq(cacheKey, cacheKey, cacheKey), result = Seq(Some(cacheValue), None, None)) + _ <- cache.getAll[String](Seq(cacheKey, cacheKey, cacheKey)).assertingEqual(Seq(Some(cacheValue), None, None)) + } yield Passed + } - "get all recover with default" in new MockedCache { - connector.mGet[String](anyVarArgs)(anyClassTag) returns ex - cache.getAll[String](key, key, key) must beEqualTo(Seq(None, None, None)).await + test("set") { (cache, connector) => + for { + _ <- connector.expect.set(cacheKey, cacheValue, result = true) + _ <- cache.set(cacheKey, cacheValue).assertingDone + } yield Passed } - "get all (keys in a collection)" in new MockedCache { - connector.mGet[String](anyVarArgs)(anyClassTag) returns Seq(Some(value), None, None) - cache.getAll[String](Seq(key, key, key)) must beEqualTo(Seq(Some(value), None, None)).await - } + test("set recover with default") { (cache, connector) => + for { + _ <- connector.expect.set(cacheKey, cacheValue, result = failure) + _ <- cache.set(cacheKey, cacheValue).assertingDone + } yield Passed + } - "set" in new MockedCache { - connector.set(anyString, anyString, any[Duration], beEq(false)) returns true - cache.set(key, value) must beDone.await - } + test("set if not exists (exists)") { (cache, connector) => + for { + _ <- connector.expect.set(cacheKey, cacheValue, setIfNotExists=true,result = false) + _ <- cache.setIfNotExists(cacheKey, cacheValue).assertingEqual(false) + } yield Passed + } - "set recover with default" in new MockedCache { - connector.set(anyString, anyString, any[Duration], beEq(false)) returns ex - cache.set(key, value) must beDone.await - } + test("set if not exists (not exists)") { (cache, connector) => + for { + _ <- connector.expect.set(cacheKey, cacheValue, setIfNotExists = true, result = true) + _ <- cache.setIfNotExists(cacheKey, cacheValue).assertingEqual(true) + } yield Passed + } - "set if not exists (exists)" in new MockedCache { - connector.set(anyString, anyString, any[Duration], beEq(true)) returns false - cache.setIfNotExists(key, value) must beFalse.await - } + test("set if not exists (exists) with expiration") { (cache, connector) => + for { + _ <- connector.expect.set(cacheKey, cacheValue, cacheExpiration, setIfNotExists = true, result = false) + _ <- cache.setIfNotExists(cacheKey, cacheValue, cacheExpiration).assertingEqual(false) + } yield Passed + } - "set if not exists (not exists)" in new MockedCache { - connector.set(anyString, anyString, any[Duration], beEq(true)) returns true - cache.setIfNotExists(key, value) must beTrue.await - } + test("set if not exists (not exists) with expiration") { (cache, connector) => + for { + _ <- connector.expect.set(cacheKey, cacheValue, cacheExpiration, setIfNotExists = true, result = true) + _ <- cache.setIfNotExists(cacheKey, cacheValue, cacheExpiration).assertingEqual(true) + } yield Passed + } - "set if not exists (exists) with expiration" in new MockedCache { - connector.set(anyString, anyString, any[Duration], beEq(true)) returns false - connector.expire(anyString, any[Duration]) returns unit - cache.setIfNotExists(key, value, expiration) must beFalse.await - } + test("set if not exists recover with default") { (cache, connector) => + for { + _ <- connector.expect.set(cacheKey, cacheValue, setIfNotExists = true, result = failure) + _ <- cache.setIfNotExists(cacheKey, cacheValue).assertingEqual(true) + } yield Passed + } - "set if not exists (not exists) with expiration" in new MockedCache { - connector.set(anyString, anyString, any[Duration], beEq(true)) returns true - connector.expire(anyString, any[Duration]) returns unit - cache.setIfNotExists(key, value, expiration) must beTrue.await - } + test("set all") { (cache, connector) => + for { + _ <- connector.expect.mSet(Seq(cacheKey -> cacheValue)) + _ <- cache.setAll(cacheKey -> cacheValue).assertingDone + } yield Passed + } - "set if not exists recover with default" in new MockedCache { - connector.set(anyString, anyString, any[Duration], beEq(true)) returns ex - cache.setIfNotExists(key, value) must beTrue.await - } + test("set all recover with default") { (cache, connector) => + for { + _ <- connector.expect.mSet(Seq(cacheKey -> cacheValue), result = failure) + _ <- cache.setAll(cacheKey -> cacheValue).assertingDone + } yield Passed + } - "set all" in new MockedCache { - connector.mSet(anyVarArgs) returns unit - cache.setAll(key -> value) must beDone.await - } + test("set all if not exists (exists)") { (cache, connector) => + for { + _ <- connector.expect.mSetIfNotExist(Seq(cacheKey -> cacheValue), result = false) + _ <- cache.setAllIfNotExist(cacheKey -> cacheValue).assertingEqual(false) + } yield Passed + } - "set all recover with default" in new MockedCache { - connector.mSet(anyVarArgs) returns ex - cache.setAll(key -> value) must beDone.await - } + test("set all if not exists (not exists)") { (cache, connector) => + for { + _ <- connector.expect.mSetIfNotExist(Seq(cacheKey -> cacheValue), result = true) + _ <- cache.setAllIfNotExist(cacheKey -> cacheValue).assertingEqual(true) + } yield Passed + } - "set all if not exists (exists)" in new MockedCache { - connector.mSetIfNotExist(anyVarArgs) returns false - cache.setAllIfNotExist(key -> value) must beFalse.await - } + test("set all if not exists recover with default") { (cache, connector) => + for { + _ <- connector.expect.mSetIfNotExist(Seq(cacheKey -> cacheValue), result = failure) + _ <- cache.setAllIfNotExist(cacheKey -> cacheValue).assertingEqual(true) + } yield Passed + } - "set all if not exists (not exists)" in new MockedCache { - connector.mSetIfNotExist(anyVarArgs) returns true - cache.setAllIfNotExist(key -> value) must beTrue.await - } + test("append") { (cache, connector) => + for { + _ <- connector.expect.append(cacheKey, cacheValue, result = 10L) + _ <- cache.append(cacheKey, cacheValue).assertingDone + } yield Passed + } - "set all if not exists recover with default" in new MockedCache { - connector.mSetIfNotExist(anyVarArgs) returns ex - cache.setAllIfNotExist(key -> value) must beTrue.await - } + test("append with expiration (newly set key)") { (cache, connector) => + for { + _ <- connector.expect.append(cacheKey, cacheValue, result = cacheValue.length.toLong) + _ <- connector.expect.expire(cacheKey, cacheExpiration) + _ <- cache.append(cacheKey, cacheValue, cacheExpiration).assertingDone + } yield Passed + } - "append" in new MockedCache { - connector.append(anyString, anyString) returns 5L - cache.append(key, value) must beDone.await - } + test("append with expiration (already existing key)") { (cache, connector) => + for { + _ <- connector.expect.append(cacheKey, cacheValue, result = cacheValue.length.toLong + 10) + _ <- cache.append(cacheKey, cacheValue, cacheExpiration).assertingDone + } yield Passed + } - "append with expiration" in new MockedCache { - connector.append(anyString, anyString) returns 5L - connector.expire(anyString, any[Duration]) returns unit - cache.append(key, value, expiration) must beDone.await - } + test("append recover with default") { (cache, connector) => + for { + _ <- connector.expect.append(cacheKey, cacheValue, result = failure) + _ <- cache.append(cacheKey, cacheValue).assertingDone + } yield Passed + } - "append recover with default" in new MockedCache { - connector.append(anyString, anyString) returns ex - cache.append(key, value) must beDone.await - } + test("expire") { (cache, connector) => + for { + _ <- connector.expect.expire(cacheKey, cacheExpiration) + _ <- cache.expire(cacheKey, cacheExpiration).assertingDone + } yield Passed + } - "expire" in new MockedCache { - connector.expire(anyString, any[Duration]) returns unit - cache.expire(key, expiration) must beDone.await - } + test("expire recover with default") { (cache, connector) => + for { + _ <- connector.expect.expire(cacheKey, cacheExpiration, result = failure) + _ <- cache.expire(cacheKey, cacheExpiration).assertingDone + } yield Passed + } - "expire recover with default" in new MockedCache { - connector.expire(anyString, any[Duration]) returns ex - cache.expire(key, expiration) must beDone.await - } + test("expires in") { (cache, connector) => + for { + _ <- connector.expect.expiresIn(cacheKey, result = Some(Duration("1500 ms"))) + _ <- cache.expiresIn(cacheKey).assertingEqual(Some(Duration("1500 ms"))) + } yield Passed + } - "expires in" in new MockedCache { - connector.expiresIn(anyString) returns Some(Duration("1500 ms")) - cache.expiresIn(key) must beSome(Duration("1500 ms")).await - } + test("expires in recover with default") { (cache, connector) => + for { + _ <- connector.expect.expiresIn(cacheKey, result = failure) + _ <- cache.expiresIn(cacheKey).assertingEqual(None) + } yield Passed + } - "expires in recover with default" in new MockedCache { - connector.expiresIn(anyString) returns ex - cache.expiresIn(key) must beNone.await - } + test("matching") { (cache, connector) => + for { + _ <- connector.expect.matching("pattern", result = Seq(cacheKey)) + _ <- cache.matching("pattern").assertingEqual(Seq(cacheKey)) + } yield Passed + } - "matching" in new MockedCache { - connector.matching(anyString) returns Seq(key) - cache.matching("pattern") must beEqualTo(Seq(key)).await - } + test("matching recover with default") { (cache, connector) => + for { + _ <- connector.expect.matching("pattern", result = failure) + _ <- cache.matching("pattern").assertingEqual(Seq.empty) + } yield Passed + } - "matching recover with default" in new MockedCache { - connector.matching(anyString) returns ex - cache.matching("pattern") must beEqualTo(Seq.empty).await - } + test("matching with a prefix", prefix = Some("the-prefix")) { (cache, connector) => + for { + _ <- connector.expect.matching(s"the-prefix:pattern", result = Seq(s"the-prefix:$cacheKey")) + _ <- cache.matching("pattern").assertingEqual(Seq(cacheKey)) + } yield Passed + } - "matching with a prefix" in new MockedCache { - // define a non-empty prefix - val prefix = "prefix" - runtime.prefix returns new RedisPrefixImpl(prefix) - connector.matching(beEq(s"$prefix:pattern")) returns Seq(s"$prefix:$key") - cache.matching("pattern") must beEqualTo(Seq(key)).await - } + test("get or else (hit)") { (cache, connector) => + for { + _ <- connector.expect.get[String](cacheKey, result = Some(cacheValue)) + orElse = probe.orElse.const(otherValue) + _ <- cache.getOrElse[String](cacheKey)(orElse.execute()).assertingEqual(cacheValue) + _ = orElse.calls mustEqual 0 + } yield Passed + } - "get or else (hit)" in new MockedCache with OrElse { - connector.get[String](anyString)(anyClassTag) returns Some(value) - cache.getOrElse(key)(doElse(value)) must beEqualTo(value).await - orElse mustEqual 0 - } + test("get or else (miss)") { (cache, connector) => + for { + _ <- connector.expect.get[String](cacheKey, result = None) + orElse = probe.orElse.const(cacheValue) + _ <- connector.expect.set(cacheKey, cacheValue, result = true) + _ <- cache.getOrElse[String](cacheKey)(orElse.execute()).assertingEqual(cacheValue) + _ = orElse.calls mustEqual 1 + } yield Passed + } - "get or else (miss)" in new MockedCache with OrElse { - connector.get[String](anyString)(anyClassTag) returns None - connector.set(anyString, anyString, any[Duration], beEq(false)) returns true - cache.getOrElse(key)(doElse(value)) must beEqualTo(value).await - orElse mustEqual 1 - } + test("get or else (failure)") { (cache, connector) => + for { + _ <- connector.expect.get[String](cacheKey, result = failure) + orElse = probe.orElse.const(cacheValue) + _ <- cache.getOrElse(cacheKey)(orElse.execute()).assertingEqual(cacheValue) + _ = orElse.calls mustEqual 1 + } yield Passed + } - "get or else (failure)" in new MockedCache with OrElse { - connector.get[String](anyString)(anyClassTag) returns ex - cache.getOrElse(key)(doElse(value)) must beEqualTo(value).await - orElse mustEqual 1 - } + test("get or else (prefixed,miss)", prefix = Some("the-prefix")) { (cache, connector) => + for { + _ <- connector.expect.get[String](s"the-prefix:$cacheKey", result = None) + _ <- connector.expect.set(s"the-prefix:$cacheKey", cacheValue, result = true) + orElse = probe.orElse.const(cacheValue) + _ <- cache.getOrElse(cacheKey)(orElse.execute()).assertingEqual(cacheValue) + _ = orElse.calls mustEqual 1 + } yield Passed + } - "get or else (prefixed,miss)" in new MockedSyncRedis with OrElse { - runtime.prefix returns new RedisPrefixImpl("prefix") - connector.get[String](beEq(s"prefix:$key"))(anyClassTag) returns None - connector.set(beEq(s"prefix:$key"), anyString, any[Duration], anyBoolean) returns true - cache.getOrElse(key)(doElse(value)) must beEqualTo(value) - orElse mustEqual 1 - } + test("get or future (hit)") { (cache, connector) => + for { + _ <- connector.expect.get[String](cacheKey, result = Some(cacheValue)) + orElse = probe.orElse.async(otherValue) + _ <- cache.getOrFuture(cacheKey)(orElse.execute()).assertingEqual(cacheValue) + _ = orElse.calls mustEqual 0 + } yield Passed + } - "get or future (hit)" in new MockedCache with OrElse { - connector.get[String](anyString)(anyClassTag) returns Some(value) - cache.getOrFuture(key)(doFuture(value)) must beEqualTo(value).await - orElse mustEqual 0 - } + test("get or future (miss)") { (cache, connector) => + for { + _ <- connector.expect.get[String](cacheKey, result = None) + _ <- connector.expect.set(cacheKey, cacheValue, result = true) + orElse = probe.orElse.async(cacheValue) + _ <- cache.getOrFuture(cacheKey)(orElse.execute()).assertingEqual(cacheValue) + _ = orElse.calls mustEqual 1 + } yield Passed + } - "get or future (miss)" in new MockedCache with OrElse { - connector.get[String](anyString)(anyClassTag) returns None - connector.set(anyString, anyString, any[Duration], beEq(false)) returns true - cache.getOrFuture(key)(doFuture(value)) must beEqualTo(value).await - orElse mustEqual 1 - } + test("get or future (failure)") { (cache, connector) => + for { + _ <- connector.expect.get[String](cacheKey, result = failure) + orElse = probe.orElse.async(cacheValue) + _ <- cache.getOrFuture(cacheKey)(orElse.execute()).assertingEqual(cacheValue) + _ = orElse.calls mustEqual 1 + } yield Passed + } - "get or future (failure)" in new MockedCache with OrElse { - connector.get[String](anyString)(anyClassTag) returns ex - cache.getOrFuture(key)(doFuture(value)) must beEqualTo(value).await - orElse mustEqual 1 - } + test("get or future (failing orElse)") { (cache, connector) => + for { + _ <- connector.expect.get[String](cacheKey, result = None) + orElse = probe.orElse.failing(failure) + _ <- cache.getOrFuture[String](cacheKey)(orElse.execute()).assertingFailure[RedisException] + _ = orElse.calls mustEqual 2 + } yield Passed + } - "get or future (failing orElse)" in new MockedCache with OrElse { - connector.get[String](anyString)(anyClassTag) returns None - cache.getOrFuture[String](key)(failedFuture) must throwA[TimeoutException].await - orElse mustEqual 2 - } + test("get or future (rerun)", policy = recoveryPolicy.rerun) { (cache, connector) => + for { + _ <- connector.expect.get[String](cacheKey, result = None) + _ <- connector.expect.get[String](cacheKey, result = None) + _ <- connector.expect.set(cacheKey, cacheValue, result = true) + orElse = probe.orElse.generic[Future[String]](failure, cacheValue, otherValue) + _ <- cache.getOrFuture(cacheKey)(orElse.execute()).assertingEqual(cacheValue) + _ = orElse.calls mustEqual 2 + } yield Passed + } - "get or future (rerun)" in new MockedCache with OrElse with Attempts { - override protected def policy = new RecoveryPolicy { - def recoverFrom[T](rerun: => Future[T], default: => Future[T], failure: RedisException) = rerun - } - connector.get[String](anyString)(anyClassTag) returns None - connector.set(anyString, anyString, any[Duration], beEq(false)) returns true - // run the test - cache.getOrFuture(key) { - attempts match { - case 0 => attempt(failedFuture) - case _ => attempt(doFuture(value)) - } - } must beEqualTo(value).await - // verification - orElse mustEqual 2 - there were two(connector).get[String](anyString)(anyClassTag) - there was one(connector).set(key, value, Duration.Inf, ifNotExists = false) - } + test("remove") { (cache, connector) => + for { + _ <- connector.expect.remove(cacheKey) + _ <- cache.remove(cacheKey).assertingDone + } yield Passed + } - "remove" in new MockedCache { - connector.remove(anyVarArgs) returns unit - cache.remove(key) must beDone.await - } + test("remove recover with default") { (cache, connector) => + for { + _ <- connector.expect.remove(Seq(cacheKey), result = failure) + _ <- cache.remove(cacheKey).assertingDone + } yield Passed + } - "remove recover with default" in new MockedCache { - connector.remove(anyVarArgs) returns ex - cache.remove(key) must beDone.await - } + test("remove multiple") { (cache, connector) => + for { + _ <- connector.expect.remove(cacheKey, cacheKey, cacheKey, cacheKey) + _ <- cache.remove(cacheKey, cacheKey, cacheKey, cacheKey).assertingDone + } yield Passed + } - "remove multiple" in new MockedCache { - connector.remove(anyVarArgs) returns unit - cache.remove(key, key, key, key) must beDone.await - } + test("remove multiple recover with default") { (cache, connector) => + for { + _ <- connector.expect.remove(Seq(cacheKey, cacheKey, cacheKey, cacheKey), result = failure) + _ <- cache.remove(cacheKey, cacheKey, cacheKey, cacheKey).assertingDone + } yield Passed + } - "remove multiple recover with default" in new MockedCache { - connector.remove(anyVarArgs) returns ex - cache.remove(key, key, key, key) must beDone.await - } + test("remove all") { (cache, connector) => + for { + _ <- connector.expect.remove(cacheKey, cacheKey, cacheKey, cacheKey) + _ <- cache.removeAll(Seq(cacheKey, cacheKey, cacheKey, cacheKey): _*).assertingDone + } yield Passed + } - "remove all" in new MockedCache { - connector.remove(anyVarArgs) returns unit - cache.removeAll(Seq(key, key, key, key): _*) must beDone.await - } + test("remove all recover with default") { (cache, connector) => + for { + _ <- connector.expect.remove(Seq(cacheKey, cacheKey, cacheKey, cacheKey), result = failure) + _ <- cache.removeAll(Seq(cacheKey, cacheKey, cacheKey, cacheKey): _*).assertingDone + } yield Passed + } - "remove all recover with default" in new MockedCache { - connector.remove(anyVarArgs) returns ex - cache.removeAll(Seq(key, key, key, key): _*) must beDone.await - } + test("remove matching") { (cache, connector) => + for { + _ <- connector.expect.matching("pattern", result = Seq(cacheKey, cacheKey)) + _ <- connector.expect.remove(cacheKey, cacheKey) + _ <- cache.removeMatching("pattern").assertingDone + } yield Passed + } - "remove matching" in new MockedCache { - connector.matching(beEq("pattern")) returns Seq(key, key) - connector.remove(key, key) returns unit - cache.removeMatching("pattern") must beDone.await - } + test("remove matching recover with default") { (cache, connector) => + for { + _ <- connector.expect.matching("pattern", result = failure) + _ <- cache.removeMatching("pattern").assertingDone + } yield Passed + } - "remove matching recover with default" in new MockedCache { - connector.matching(anyVarArgs) returns ex - cache.removeMatching("pattern") must beDone.await - } + test("invalidate") { (cache, connector) => + for { + _ <- connector.expect.invalidate() + _ <- cache.invalidate().assertingDone + } yield Passed + } - "invalidate" in new MockedCache { - connector.invalidate() returns unit - cache.invalidate() must beDone.await - } + test("invalidate recover with default") { (cache, connector) => + for { + _ <- connector.expect.invalidate(result = failure) + _ <- cache.invalidate().assertingDone + } yield Passed + } - "invalidate recover with default" in new MockedCache { - connector.invalidate() returns ex - cache.invalidate() must beDone.await - } + test("exists") { (cache, connector) => + for { + _ <- connector.expect.exists(cacheKey, result = true) + _ <- cache.exists(cacheKey).assertingEqual(true) + } yield Passed + } - "exists" in new MockedCache { - connector.exists(key) returns true - cache.exists(key) must beTrue.await - } + test("exists recover with default") { (cache, connector) => + for { + _ <- connector.expect.exists(cacheKey, result = failure) + _ <- cache.exists(cacheKey).assertingEqual(false) + } yield Passed + } - "exists recover with default" in new MockedCache { - connector.exists(key) returns ex - cache.exists(key) must beFalse.await - } + test("increment") { (cache, connector) => + for { + _ <- connector.expect.increment(cacheKey, 5L, result = 10L) + _ <- cache.increment(cacheKey, 5L).assertingEqual(10L) + } yield Passed + } - "increment" in new MockedCache { - connector.increment(key, 5L) returns 10L - cache.increment(key, 5L) must beEqualTo(10L).await - } + test("increment recover with default") { (cache, connector) => + for { + _ <- connector.expect.increment(cacheKey, 5L, result = failure) + _ <- cache.increment(cacheKey, 5L).assertingEqual(5L) + } yield Passed + } - "increment recover with default" in new MockedCache { - connector.increment(key, 5L) returns ex - cache.increment(key, 5L) must beEqualTo(5L).await - } + test("decrement") { (cache, connector) => + for { + _ <- connector.expect.increment(cacheKey, -5L, result = 10L) + _ <- cache.decrement(cacheKey, 5L).assertingEqual(10L) + } yield Passed + } - "decrement" in new MockedCache { - connector.increment(key, -5L) returns 10L - cache.decrement(key, 5L) must beEqualTo(10L).await - } + test("decrement recover with default") { (cache, connector) => + for { + _ <- connector.expect.increment(cacheKey, -5L, result = failure) + _ <- cache.decrement(cacheKey, 5L).assertingEqual(-5L) + } yield Passed + } - "decrement recover with default" in new MockedCache { - connector.increment(key, -5L) returns ex - cache.decrement(key, 5L) must beEqualTo(-5L).await + private def test( + name: String, + policy: RecoveryPolicy = recoveryPolicy.default, + prefix: Option[String] = None, + )( + f: (RedisCache[AsynchronousResult], RedisConnectorMock) => Future[Assertion] + ): Unit = { + name in { + implicit val runtime: RedisRuntime = redisRuntime( + invocationPolicy = LazyInvocation, + recoveryPolicy = policy, + prefix = prefix.fold[RedisPrefix](RedisEmptyPrefix)(new RedisPrefixImpl(_)), + ) + val connector: RedisConnectorMock = mock[RedisConnectorMock] + val cache: RedisCache[AsynchronousResult] = new RedisCache[AsynchronousResult](connector, Builders.AsynchronousBuilder) + f(cache, connector) } } -} + } diff --git a/src/test/scala/play/api/cache/redis/impl/RedisConnectorMock.scala b/src/test/scala/play/api/cache/redis/impl/RedisConnectorMock.scala new file mode 100644 index 00000000..18ae2d44 --- /dev/null +++ b/src/test/scala/play/api/cache/redis/impl/RedisConnectorMock.scala @@ -0,0 +1,458 @@ +package play.api.cache.redis.impl + +import org.scalamock.scalatest.AsyncMockFactoryBase +import play.api.cache.redis._ + +import scala.concurrent.Future +import scala.concurrent.duration.Duration +import scala.reflect.ClassTag +import scala.util.{Failure, Try} + +private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => + + protected[impl] trait RedisConnectorMock extends RedisConnector { + + final override def remove(keys: String*): Future[Unit] = + removeValues(keys) + + def removeValues(keys: Seq[String]): Future[Unit] + + override final def mGet[T: ClassTag](keys: String*): Future[Seq[Option[T]]] = + mGetKeys[T](keys) + + def mGetKeys[T: ClassTag](keys: Seq[String]): Future[Seq[Option[T]]] + + final override def mSet(keyValues: (String, Any)*): Future[Unit] = + mSetValues(keyValues) + + def mSetValues(keyValues: Seq[(String, Any)]): Future[Unit] + + final override def mSetIfNotExist(keyValues: (String, Any)*): Future[Boolean] = + mSetIfNotExistValues(keyValues) + + def mSetIfNotExistValues(keyValues: Seq[(String, Any)]): Future[Boolean] + + final override def listPrepend(key: String, value: Any*): Future[Long] = + listPrependValues(key, value) + + def listPrependValues(key: String, values: Seq[Any]): Future[Long] + + final override def listAppend(key: String, value: Any*): Future[Long] = + listAppendValues(key, value) + + def listAppendValues(key: String, values: Seq[Any]): Future[Long] + + final override def setAdd(key: String, value: Any*): Future[Long] = + setAddValues(key, value) + + def setAddValues(key: String, values: Seq[Any]): Future[Long] + + final override def setRemove(key: String, value: Any*): Future[Long] = + setRemoveValues(key, value) + + def setRemoveValues(key: String, values: Seq[Any]): Future[Long] + + final override def sortedSetAdd(key: String, scoreValues: (Double, Any)*): Future[Long] = + sortedSetAddValues(key, scoreValues) + + def sortedSetAddValues(key: String, values: Seq[(Double, Any)]): Future[Long] + + final override def sortedSetRemove(key: String, value: Any*): Future[Long] = + sortedSetRemoveValues(key, value) + + def sortedSetRemoveValues(key: String, values: Seq[Any]): Future[Long] + + final override def hashGet[T: ClassTag](key: String, field: String): Future[Option[T]] = + hashGetField[T](key, field) + + def hashGetField[T: ClassTag](key: String, field: String): Future[Option[T]] + + final override def hashGet[T: ClassTag](key: String, fields: Seq[String]): Future[Seq[Option[T]]] = + hashGetFields[T](key, fields) + + def hashGetFields[T: ClassTag](key: String, fields: Seq[String]): Future[Seq[Option[T]]] + + final override def hashRemove(key: String, field: String*): Future[Long] = + hashRemoveValues(key, field) + + def hashRemoveValues(key: String, fields: Seq[String]): Future[Long] + + final override def hashGetAll[T: ClassTag](key: String): Future[Map[String, T]] = + hashGetAllValues[T](key) + + def hashGetAllValues[T: ClassTag](key: String): Future[Map[String, T]] + } + + final protected implicit class RedisConnectorExpectationOps(connector: RedisConnectorMock) { + def expect: RedisConnectorExpectation = + new RedisConnectorExpectation(connector) + } + + protected final class RedisConnectorExpectation(connector: RedisConnectorMock) { + + def get[T: ClassTag](key: String, result: Try[Option[T]]): Future[Unit] = + Future.successful { + (connector.get(_: String)(_: ClassTag[_])) + .expects(key, implicitly[ClassTag[T]]) + .returning(Future.fromTry(result)) + .once() + } + + def get[T: ClassTag](key: String, result: Option[T]): Future[Unit] = + get(key, Try(result)) + + def get[T: ClassTag](key: String, result: Throwable): Future[Unit] = + get[T](key, Failure(result)) + + def mGet[T: ClassTag](keys: Seq[String], result: Future[Seq[Option[T]]]): Future[Unit] = + Future.successful { + (connector.mGetKeys(_: Seq[String])(_: ClassTag[_])) + .expects(keys, implicitly[ClassTag[T]]) + .returning(result) + .once() + } + + def set[T](key: String, value: T, duration: Duration = Duration.Inf, setIfNotExists: Boolean = false, result: Future[Boolean]): Future[Unit] = + Future.successful { + (connector.set(_: String, _: Any, _: Duration, _: Boolean)) + .expects(key, if (value == null) * else value, duration, setIfNotExists) + .returning(result) + .once() + } + + def mSet(keyValues: Seq[(String, Any)], result: Future[Unit]=Future.unit): Future[Unit] = + Future.successful { + (connector.mSetValues(_: Seq[(String, Any)])) + .expects(keyValues) + .returning(result) + .once() + } + + def mSetIfNotExist(keyValues: Seq[(String, Any)], result: Future[Boolean]): Future[Unit] = + Future.successful { + (connector.mSetIfNotExistValues(_: Seq[(String, Any)])) + .expects(keyValues) + .returning(result) + .once() + } + + def expire(key: String, duration: Duration, result: Future[Unit] = Future.unit): Future[Unit] = + Future.successful { + (connector.expire(_: String, _: Duration)) + .expects(key, duration) + .returning(result) + .once() + } + + def expiresIn(key: String, result: Future[Option[Duration]]): Future[Unit] = + Future.successful { + (connector.expiresIn(_: String)) + .expects(key) + .returning(result) + .once() + } + + def remove(keys: String*): Future[Unit] = + remove(keys, Future.unit) + + def remove(keys: Seq[String], result: Future[Unit]): Future[Unit] = + Future.successful { + (connector.removeValues(_: Seq[String])) + .expects(keys) + .returning(result) + .once() + } + + def invalidate(result: Future[Unit] = Future.unit): Future[Unit] = + Future.successful { + (() => connector.invalidate()) + .expects() + .returning(result) + .once() + } + + def exists(key: String, result: Future[Boolean]): Future[Unit] = + Future.successful { + (connector.exists(_: String)) + .expects(key) + .returning(result) + .once() + } + + def increment(key: String, by: Long, result: Future[Long]): Future[Unit] = + Future.successful { + (connector.increment(_: String, _: Long)) + .expects(key, by) + .returning(result) + .once() + } + + def append(key: String, value: String, result: Future[Long]): Future[Unit] = + Future.successful { + (connector.append(_: String, _: String)) + .expects(key, value) + .returning(result) + .once() + } + + def matching(pattern: String, result: Future[Seq[String]]): Future[Unit] = + Future.successful { + (connector.matching(_: String)) + .expects(pattern) + .returning(result) + .once() + } + + def listPrepend(key: String, values: Seq[String], result: Future[Long]= Future.successful(5L)): Future[Unit] = + Future.successful { + (connector.listPrependValues(_: String, _: Seq[Any])) + .expects(key, values) + .returning(result) + .once() + } + + def listAppend[T:ClassTag](key: String, values: Seq[T], result: Future[Long] = Future.successful(5L)): Future[Unit] = + Future.successful { + (connector.listAppendValues(_: String, _: Seq[Any])) + .expects(key, values) + .returning(result) + .once() + } + + def listSlice[T: ClassTag](key: String, start: Int, end: Int, result: Future[Seq[T]]): Future[Unit] = + Future.successful { + (connector.listSlice(_: String, _: Int, _: Int)(_: ClassTag[_])) + .expects(key, start, end, implicitly[ClassTag[T]]) + .returning(result) + .once() + } + + def listHeadPop[T:ClassTag](key: String, result: Future[Option[T]]): Future[Unit] = + Future.successful { + (connector.listHeadPop(_: String)(_: ClassTag[_])) + .expects(key, implicitly[ClassTag[T]]) + .returning(result) + .once() + } + + def listSize(key: String, result: Future[Long]): Future[Unit] = + Future.successful { + (connector.listSize(_: String)) + .expects(key) + .returning(result) + .once() + } + + def listInsert(key: String, pivot: String, value: String, result: Future[Option[Long]]): Future[Unit] = + Future.successful { + (connector.listInsert(_: String, _: String, _: Any)) + .expects(key, pivot, value) + .returning(result) + .once() + } + + def listSetAt(key: String, index: Int, value: String, result: Future[Unit]): Future[Unit] = + Future.successful { + (connector.listSetAt(_: String, _: Int, _: Any)) + .expects(key, index, value) + .returning(result) + .once() + } + + def listRemove(key: String, value: String, count: Int, result: Future[Long]): Future[Unit] = + Future.successful { + (connector.listRemove(_: String, _:Any, _: Int)) + .expects(key, value, count) + .returning(result) + .once() + } + + def listRemoveAt(key: String, index: Int, result: Future[Long]): Future[Unit] = + Future.successful { + (connector.listSetAt(_: String, _: Int, _: Any)) + .expects(key, index, "play-redis:DELETED") + .returning(Future.unit) + .once() + (connector.listRemove(_: String, _: Any, _: Int)) + .expects(key, "play-redis:DELETED", 0) + .returning(result) + .once() + } + + def listTrim(key: String, start: Int, end: Int, result: Future[Unit] = Future.unit): Future[Unit] = + Future.successful { + (connector.listTrim(_: String, _: Int, _: Int)) + .expects(key, start, end) + .returning(result) + .once() + } + + def setAdd(key: String, values: Seq[String], result: Future[Long] = Future.successful(5L)): Future[Unit] = + Future.successful { + (connector.setAddValues(_: String, _: Seq[Any])) + .expects(key, values) + .returning(result) + .once() + } + + def setIsMember(key: String, value: String, result: Future[Boolean]): Future[Unit] = + Future.successful { + (connector.setIsMember(_: String, _: Any)) + .expects(key, value) + .returning(result) + .once() + } + + def setRemove(key: String, values: Seq[String], result: Future[Long] = Future.successful(1L)): Future[Unit] = + Future.successful { + (connector.setRemoveValues(_: String, _: Seq[Any])) + .expects(key, values) + .returning(result) + .once() + } + + def setMembers[T: ClassTag](key: String, result: Future[Set[Any]]): Future[Unit] = + Future.successful { + (connector.setMembers(_: String)(_: ClassTag[_])) + .expects(key, implicitly[ClassTag[T]]) + .returning(result) + .once() + } + + def setSize(key: String, result: Future[Long]): Future[Unit] = + Future.successful { + (connector.setSize(_: String)) + .expects(key) + .returning(result) + .once() + } + + def sortedSetAdd(key: String, values: Seq[(Double, String)], result: Future[Long] = Future.successful(1L)): Future[Unit] = + Future.successful { + (connector.sortedSetAddValues(_: String, _: Seq[(Double, Any)])) + .expects(key, values) + .returning(result) + .once() + } + + def sortedSetScore(key: String, value: String, result: Future[Option[Double]]): Future[Unit] = + Future.successful { + (connector.sortedSetScore(_: String, _: Any)) + .expects(key, value) + .returning(result) + .once() + } + + def sortedSetRemove(key: String, values: Seq[String], result: Future[Long] = Future.successful(1L)): Future[Unit] = + Future.successful { + (connector.sortedSetRemoveValues(_: String, _: Seq[Any])) + .expects(key, values) + .returning(result) + .once() + } + + def sortedSetRange[T: ClassTag](key: String, start: Long, end: Long, result: Future[Seq[String]]): Future[Unit] = + Future.successful { + (connector.sortedSetRange(_: String, _: Long, _: Long)(_: ClassTag[_])) + .expects(key, start, end, implicitly[ClassTag[T]]) + .returning(result) + .once() + } + + def sortedSetReverseRange[T: ClassTag](key: String, start: Long, end: Long, result: Future[Seq[String]]): Future[Unit] = + Future.successful { + (connector.sortedSetReverseRange(_: String, _: Long, _: Long)(_: ClassTag[_])) + .expects(key, start, end, implicitly[ClassTag[T]]) + .returning(result) + .once() + } + + def sortedSetSize(key: String, result: Future[Long]): Future[Unit] = + Future.successful { + (connector.sortedSetSize(_: String)) + .expects(key) + .returning(result) + .once() + } + + def hashSet(key: String, field: String, value: String, result: Future[Boolean]): Future[Unit] = + Future.successful { + (connector.hashSet(_: String, _: String, _: Any)) + .expects(key, field, value) + .returning(result) + .once() + } + + def hashGet[T: ClassTag](key: String, field: String, result: Future[Option[T]]): Future[Unit] = + Future.successful { + (connector.hashGetField(_: String, _: String)(_: ClassTag[_])) + .expects(key, field, implicitly[ClassTag[T]]) + .returning(result) + .once() + } + + def hashGet[T: ClassTag](key: String, fields: Seq[String], result: Future[Seq[Option[T]]]): Future[Unit] = + Future.successful { + (connector.hashGetFields(_: String, _: Seq[String])(_: ClassTag[_])) + .expects(key, fields, implicitly[ClassTag[T]]) + .returning(result) + .once() + } + + def hashExists(key: String, field: String, result: Future[Boolean]): Future[Unit] = + Future.successful { + (connector.hashExists(_: String, _: String)) + .expects(key, field) + .returning(result) + .once() + } + + def hashRemove(key: String, fields: Seq[String], result: Future[Long] = Future.successful(1L)): Future[Unit] = + Future.successful { + (connector.hashRemoveValues(_: String, _: Seq[String])) + .expects(key, fields) + .returning(result) + .once() + } + + def hashIncrement(key: String, field: String, by: Long, result: Future[Long]): Future[Unit] = + Future.successful { + (connector.hashIncrement(_: String, _: String, _: Long)) + .expects(key, field, by) + .returning(result) + .once() + } + + def hashGetAll[T: ClassTag](key: String, result: Future[Map[String, T]]): Future[Unit] = + Future.successful { + (connector.hashGetAllValues(_: String)(_: ClassTag[_])) + .expects(key, implicitly[ClassTag[T]]) + .returning(result) + .once() + } + + def hashKeys(key: String, result: Future[Set[String]]): Future[Unit] = + Future.successful { + (connector.hashKeys(_: String)) + .expects(key) + .returning(result) + .once() + } + + def hashValues[T: ClassTag](key: String, result: Future[Set[T]]): Future[Unit] = + Future.successful { + (connector.hashValues[T](_: String)(_: ClassTag[T])) + .expects(key, implicitly[ClassTag[T]]) + .returning(result) + .once() + } + + def hashSize(key: String, result: Future[Long]): Future[Unit] = + Future.successful { + (connector.hashSize(_: String)) + .expects(key) + .returning(result) + .once() + } + } +} \ No newline at end of file diff --git a/src/test/scala/play/api/cache/redis/impl/RedisJavaListSpec.scala b/src/test/scala/play/api/cache/redis/impl/RedisJavaListSpec.scala index d4b9b776..1ad2cb89 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisJavaListSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisJavaListSpec.scala @@ -1,193 +1,224 @@ package play.api.cache.redis.impl import play.api.cache.redis._ +import play.api.cache.redis.test._ +import play.cache.redis.AsyncRedisList -import org.mockito.ArgumentMatchers -import org.specs2.concurrent.ExecutionEnv -import org.specs2.mutable.Specification +import scala.concurrent.Future +import scala.jdk.CollectionConverters._ +import scala.jdk.OptionConverters._ -class RedisJavaListSpec(implicit ee: ExecutionEnv) extends Specification with ReducedMockito { - import Implicits._ - import JavaCompatibility._ - import RedisCacheImplicits._ +class RedisJavaListSpec extends AsyncUnitSpec with RedisListJavaMock with RedisRuntimeMock { - import ArgumentMatchers._ - - "Redis List" should { - - "prepend" in new MockedJavaList { - internal.prepend(value) returns internal - list.prepend(value).asScala must beEqualTo(list).await - there were one(internal).prepend(value) - } + test("prepend") { (list, internal) => + for { + _ <- internal.expect.prepend(cacheValue) + _ <- list.prepend(cacheValue).assertingEqual(list) + } yield Passed + } - "append" in new MockedJavaList { - internal.append(value) returns internal - list.append(value).asScala must beEqualTo(list).await - there were one(internal).append(value) - } + test("append") { (list, internal) => + for { + _ <- internal.expect.append(cacheValue) + _ <- list.append(cacheValue).assertingEqual(list) + } yield Passed + } - "apply (hit)" in new MockedJavaList { - internal.apply(beEq(5)) returns value - list.apply(5).asScala must beEqualTo(value).await - there were one(internal).apply(5) - } + test("apply (hit)") { (list, internal) => + for { + _ <- internal.expect.apply(5, Some(cacheValue)) + _ <- list.apply(5).assertingEqual(cacheValue) + } yield Passed + } - "apply (miss or fail)" in new MockedJavaList { - internal.apply(beEq(5)) returns NoSuchElementException - list.apply(5).asScala must throwA[NoSuchElementException].await - there were one(internal).apply(5) - } + test("apply (miss or fail)") { (list, internal) => + for { + _ <- internal.expect.apply(5, None) + _ <- list.apply(5).assertingFailure[NoSuchElementException] + } yield Passed + } - "get (miss)" in new MockedJavaList { - internal.get(beEq(5)) returns None - list.get(5).asScala must beEqualTo(None.asJava).await - there were one(internal).get(5) - } + test("get (miss)") { (list, internal) => + for { + _ <- internal.expect.get(5, None) + _ <- list.get(5).assertingEqual(None.toJava) + } yield Passed + } - "get (hit)" in new MockedJavaList { - internal.get(beEq(5)) returns Some(value) - list.get(5).asScala must beEqualTo(Some(value).asJava).await - there were one(internal).get(5) - } + test("get (hit)") { (list, internal) => + for { + _ <- internal.expect.get(5, Some(cacheValue)) + _ <- list.get(5).assertingEqual(Some(cacheValue).toJava) + } yield Passed + } - "head (non-empty)" in new MockedJavaList { - internal.apply(beEq(0)) returns value - list.head.asScala must beEqualTo(value).await - there were one(internal).apply(0) - } + test("head (non-empty)") { (list, internal) => + for { + _ <- internal.expect.apply(0, Some(cacheValue)) + _ <- list.head.assertingEqual(cacheValue) + } yield Passed + } - "head (empty)" in new MockedJavaList { - internal.apply(beEq(0)) returns NoSuchElementException - list.head.asScala must throwA(NoSuchElementException).await - there were one(internal).apply(0) - } + test("head (empty)") { (list, internal) => + for { + _ <- internal.expect.apply(0, None) + _ <- list.head.assertingFailure[NoSuchElementException] + } yield Passed + } - "headOption (non-empty)" in new MockedJavaList { - internal.get(beEq(0)) returns Some(value) - list.headOption.asScala must beEqualTo(Some(value).asJava).await - there were one(internal).get(0) - } + test("headOption (non-empty)") { (list, internal) => + for { + _ <- internal.expect.get(0, Some(cacheValue)) + _ <- list.headOption().assertingEqual(Some(cacheValue).toJava) + } yield Passed + } - "headOption (empty)" in new MockedJavaList { - internal.get(beEq(0)) returns None - list.headOption.asScala must beEqualTo(None.asJava).await - there were one(internal).get(0) - } + test("headOption (empty)") { (list, internal) => + for { + _ <- internal.expect.get(0, None) + _ <- list.headOption().assertingEqual(None.toJava) + } yield Passed + } - "head pop" in new MockedJavaList { - internal.headPop returns None - list.headPop().asScala must beEqualTo(None.asJava).await - there were one(internal).headPop - } + test("head pop") { (list, internal) => + for { + _ <- internal.expect.headPop(None) + _ <- list.headPop().assertingEqual(None.toJava) + } yield Passed + } - "last (non-empty)" in new MockedJavaList { - internal.apply(beEq(-1)) returns value - list.last.asScala must beEqualTo(value).await - there were one(internal).apply(-1) - } + test("last (non-empty)") { (list, internal) => + for { + _ <- internal.expect.apply(-1, Some(cacheValue)) + _ <- list.last().assertingEqual(cacheValue) + } yield Passed + } - "last (empty)" in new MockedJavaList { - internal.apply(beEq(-1)) returns NoSuchElementException - list.last.asScala must throwA(NoSuchElementException).await - there were one(internal).apply(-1) - } + test("last (empty)") { (list, internal) => + for { + _ <- internal.expect.apply(-1, None) + _ <- list.last().assertingFailure[NoSuchElementException] + } yield Passed + } - "lastOption (non-empty)" in new MockedJavaList { - internal.get(beEq(-1)) returns Some(value) - list.lastOption.asScala must beEqualTo(Some(value).asJava).await - there were one(internal).get(-1) - } + test("lastOption (non-empty)") { (list, internal) => + for { + _ <- internal.expect.get(-1, Some(cacheValue)) + _ <- list.lastOption().assertingEqual(Some(cacheValue).toJava) + } yield Passed + } - "lastOption (empty)" in new MockedJavaList { - internal.get(beEq(-1)) returns None - list.lastOption.asScala must beEqualTo(None.asJava).await - there were one(internal).get(-1) - } + test("lastOption (empty)") { (list, internal) => + for { + _ <- internal.expect.get(-1, None) + _ <- list.lastOption().assertingEqual(None.toJava) + } yield Passed + } - "toList" in new MockedJavaList { - view.slice(beEq(0), beEq(-1)) returns List.empty[String] - list.toList.asScala must beEqualTo(List.empty.asJava).await - there were one(internal).view - there were one(view).slice(0, -1) - } + test("toList") { (list, internal) => + for { + _ <- internal.expect.view.slice(0, -1, List.empty) + _ <- list.toList.assertingEqual(List.empty.asJava) + } yield Passed + } - "insert before" in new MockedJavaList { - internal.insertBefore("pivot", value) returns Some(5L) - list.insertBefore("pivot", value).asScala must beEqualTo(Some(5L).asJava).await - there were one(internal).insertBefore("pivot", value) - } + test("insert before") { (list, internal) => + for { + _ <- internal.expect.insertBefore("pivot", cacheValue, Some(5L)) + _ <- list.insertBefore("pivot", cacheValue).assertingEqual(Option(5L).map(long2Long).toJava) + } yield Passed + } - "set at position" in new MockedJavaList { - internal.set(beEq(2), beEq(value)) returns internal - list.set(2, value).asScala must beEqualTo(list).await - there were one(internal).set(2, value) - } + test("set at position") { (list, internal) => + for { + _ <- internal.expect.set(2, cacheValue) + _ <- list.set(2, cacheValue).assertingEqual(list) + } yield Passed + } - "remove element" in new MockedJavaList { - internal.remove(beEq(value), anyInt) returns internal - list.remove(value).asScala must beEqualTo(list).await - there were one(internal).remove(value) - } + test("remove element") { (list, internal) => + for { + _ <- internal.expect.remove(cacheValue) + _ <- list.remove(cacheValue).assertingEqual(list) + } yield Passed + } - "remove with count" in new MockedJavaList { - internal.remove(beEq(value), beEq(2)) returns internal - list.remove(value, 2).asScala must beEqualTo(list).await - there were one(internal).remove(value, 2) - } + test("remove with count") { (list, internal) => + for { + _ <- internal.expect.remove(cacheValue, 2) + _ <- list.remove(cacheValue, 2).assertingEqual(list) + } yield Passed + } - "remove at position" in new MockedJavaList { - internal.removeAt(beEq(2)) returns internal - list.removeAt(2).asScala must beEqualTo(list).await - there were one(internal).removeAt(2) - } + test("remove at position") { (list, internal) => + for { + _ <- internal.expect.removeAt(2) + _ <- list.removeAt(2).assertingEqual(list) + } yield Passed + } - "view all" in new MockedJavaList { - view.slice(beEq(0), beEq(-1)) returns List.empty[String] - list.view().all().asScala must beEqualTo(List.empty.asJava).await - there were one(internal).view - there were one(view).slice(0, -1) - } + test("view all") { (list, internal) => + for { + _ <- internal.expect.view.slice(0, -1, List.empty) + _ <- list.view().all().assertingEqual(List.empty.asJava) + } yield Passed + } - "view take" in new MockedJavaList { - view.slice(beEq(0), beEq(1)) returns List.empty[String] - list.view().take(2).asScala must beEqualTo(List.empty.asJava).await - there were one(internal).view - there were one(view).slice(0, 1) - } + test("view take") { (list, internal) => + for { + _ <- internal.expect.view.slice(0, 1, List.empty) + _ <- list.view().take(2).assertingEqual(List.empty.asJava) + } yield Passed + } - "view drop" in new MockedJavaList { - view.slice(beEq(2), beEq(-1)) returns List.empty[String] - list.view().drop(2).asScala must beEqualTo(List.empty.asJava).await - there were one(internal).view - there were one(view).slice(2, -1) - } + test("view drop") { (list, internal) => + for { + _ <- internal.expect.view.slice(2, -1, List.empty) + _ <- list.view().drop(2).assertingEqual(List.empty.asJava) + } yield Passed + } - "view slice" in new MockedJavaList { - view.slice(beEq(1), beEq(2)) returns List.empty[String] - list.view().slice(1, 2).asScala must beEqualTo(List.empty.asJava).await - there were one(internal).view - there were one(view).slice(1, 2) - } + test("view slice") { (list, internal) => + for { + _ <- internal.expect.view.slice(1, 2, List.empty) + _ <- list.view().slice(1, 2).assertingEqual(List.empty.asJava) + } yield Passed + } - "modify collection" in new MockedJavaList { - list.modify().collection() mustEqual list - } + test("modify clear") { (list, internal) => + for { + _ <- internal.expect.modify.clear() + _ <- list.modify().clear().assertingEqual(list.modify()) + } yield Passed + } - "modify clear" in new MockedJavaList { - private val javaModifier = list.modify() - modifier.clear() returns modifier - javaModifier.clear().asScala must beEqualTo(javaModifier).await - there were one(internal).modify - there were one(modifier).clear() - } + test("modify slice") { (list, internal) => + for { + _ <- internal.expect.modify.slice(1, 2) + _ <- list.modify().slice(1, 2).assertingEqual(list.modify()) + } yield Passed + } - "modify slice" in new MockedJavaList { - private val javaModifier = list.modify() - modifier.slice(beEq(1), beEq(2)) returns modifier - javaModifier.slice(1, 2).asScala must beEqualTo(javaModifier).await - there were one(internal).modify - there were one(modifier).slice(1, 2) + private def test( + name: String, + policy: RecoveryPolicy = recoveryPolicy.default + )( + f: (AsyncRedisList[String], RedisListMock) => Future[Assertion] + ): Unit = { + name in { + implicit val runtime: RedisRuntime = redisRuntime( + invocationPolicy = LazyInvocation, + recoveryPolicy = policy, + ) + val internal: RedisListMock = mock[RedisListMock] + val view: internal.RedisListView = mock[internal.RedisListView] + val modifier: internal.RedisListModification = mock[internal.RedisListModification] + val list: AsyncRedisList[String] = new RedisListJavaImpl(internal) + + (() => internal.view).expects().returns(view).anyNumberOfTimes() + (() => internal.modify).expects().returns(modifier).anyNumberOfTimes() + + f(list, internal) } } } diff --git a/src/test/scala/play/api/cache/redis/impl/RedisJavaMapSpec.scala b/src/test/scala/play/api/cache/redis/impl/RedisJavaMapSpec.scala index 77333d9c..c860e31e 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisJavaMapSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisJavaMapSpec.scala @@ -1,71 +1,93 @@ package play.api.cache.redis.impl import play.api.cache.redis._ +import play.api.cache.redis.test._ +import play.cache.redis.AsyncRedisMap -import org.specs2.concurrent.ExecutionEnv -import org.specs2.mutable.Specification +import scala.concurrent.Future +import scala.jdk.CollectionConverters._ +import scala.jdk.OptionConverters._ -class RedisJavaMapSpec(implicit ee: ExecutionEnv) extends Specification with ReducedMockito { - import Implicits._ - import JavaCompatibility._ - import RedisCacheImplicits._ +class RedisJavaMapSpec extends AsyncUnitSpec with RedisMapJavaMock with RedisRuntimeMock { - import org.mockito.ArgumentMatchers._ - - "Redis Map" should { + test("set") { (cache, internal) => + for { + _ <- internal.expect.add(cacheKey, cacheValue) + _ <- cache.add(cacheKey, cacheValue).assertingEqual(cache) + } yield Passed + } - "set" in new MockedJavaMap { - internal.add(beEq(field), beEq(value)) returns internal - map.add(field, value).asScala must beEqualTo(map).await - there were one(internal).add(field, value) - } + test("get") { (cache, internal) => + for { + _ <- internal.expect.get(cacheKey, Some(cacheValue)) + _ <- cache.get(cacheKey).assertingEqual(Option(cacheValue).toJava) + } yield Passed + } - "get" in new MockedJavaMap { - internal.get(beEq(field)) returns Some(value) - map.get(field).asScala must beEqualTo(Some(value).asJava).await - there were one(internal).get(field) - } + test("contains") { (cache, internal) => + for { + _ <- internal.expect.contains(cacheKey, result = true) + _ <- cache.contains(cacheKey).assertingEqual(true) + } yield Passed + } - "contains" in new MockedJavaMap { - internal.contains(beEq(field)) returns true - map.contains(field).asScala.map(Boolean.unbox) must beEqualTo(true).await - there were one(internal).contains(field) - } + test("remove") { (cache, internal) => + for { + _ <- internal.expect.remove(cacheKey, otherKey) + _ <- cache.remove(cacheKey, otherKey).assertingEqual(cache) + } yield Passed + } - "remove" in new MockedJavaMap { - internal.remove(anyVarArgs[String]) returns internal - map.remove(field, other).asScala must beEqualTo(map).await - there were one(internal).remove(field, other) - } + test("increment") { (cache, internal) => + for { + _ <- internal.expect.increment(cacheKey, by = 1L, result = 4L) + _ <- cache.increment(cacheKey).assertingEqual(4L) + } yield Passed + } - "increment" in new MockedJavaMap { - internal.increment(beEq(field), anyLong()) returns 4L - map.increment(field).asScala.map(Long.unbox) must beEqualTo(4L).await - there were one(internal).increment(field) - } + test("increment by") { (cache, internal) => + for { + _ <- internal.expect.increment(cacheKey, by = 2L, result = 6L) + _ <- cache.increment(cacheKey, 2L).assertingEqual(6L) + } yield Passed + } - "increment by" in new MockedJavaMap { - internal.increment(beEq(field), beEq(2L)) returns 6L - map.increment(field, 2L).asScala.map(Long.unbox) must beEqualTo(6L).await - there were one(internal).increment(field, 2L) - } + test("toMap") { (cache, internal) => + for { + _ <- internal.expect.toMap(cacheKey -> cacheValue) + _ <- cache.toMap.assertingEqual(Map(cacheKey -> cacheValue).asJava) + } yield Passed + } - "toMap" in new MockedJavaMap { - internal.toMap returns Map(key -> value) - map.toMap().asScala must beEqualTo(Map(key -> value).asJava).await - there were one(internal).toMap - } + test("keySet") { (cache, internal) => + for { + _ <- internal.expect.keySet(cacheKey, otherKey) + _ <- cache.keySet().assertingEqual(Set(cacheKey, otherKey).asJava) + } yield Passed + } - "keySet" in new MockedJavaMap { - internal.keySet returns Set(key, other) - map.keySet().asScala must beEqualTo(Set(key, other).asJava).await - there were one(internal).keySet - } + test("values") { (cache, internal) => + for { + _ <- internal.expect.values(otherValue, cacheValue) + _ <- cache.values().assertingEqual(Set(otherValue, cacheValue).asJava) + } yield Passed + } - "values" in new MockedJavaMap { - internal.values returns Set(value, other) - map.values().asScala must beEqualTo(Set(value, other).asJava).await - there were one(internal).values + private def test( + name: String, + policy: RecoveryPolicy = recoveryPolicy.default + )( + f: (AsyncRedisMap[String], RedisMapMock) => Future[Assertion] + ): Unit = { + name in { + implicit val runtime: RedisRuntime = redisRuntime( + invocationPolicy = LazyInvocation, + recoveryPolicy = policy, + ) + val internal: RedisMapMock = mock[RedisMapMock] + val map: AsyncRedisMap[String] = new RedisMapJavaImpl(internal) + + f(map, internal) } } } diff --git a/src/test/scala/play/api/cache/redis/impl/RedisJavaSetSpec.scala b/src/test/scala/play/api/cache/redis/impl/RedisJavaSetSpec.scala index a2c56f33..bc836b44 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisJavaSetSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisJavaSetSpec.scala @@ -1,39 +1,59 @@ package play.api.cache.redis.impl import play.api.cache.redis._ +import play.api.cache.redis.test._ +import play.cache.redis.{AsyncRedisList, AsyncRedisSet} -import org.specs2.concurrent.ExecutionEnv -import org.specs2.mutable.Specification +import scala.concurrent.Future +import scala.jdk.CollectionConverters._ +import scala.jdk.OptionConverters._ -class RedisJavaSetSpec(implicit ee: ExecutionEnv) extends Specification with ReducedMockito { - import Implicits._ - import JavaCompatibility._ - import RedisCacheImplicits._ +class RedisJavaSetSpec extends AsyncUnitSpec with RedisSetJavaMock with RedisRuntimeMock { - "Redis Set" should { + test("add") { (set, internal) => + for { + _ <- internal.expect.add(cacheKey, cacheValue) + _ <- set.add(cacheKey, cacheValue).assertingEqual(set) + } yield Passed + } - "add" in new MockedJavaSet { - internal.add(anyVarArgs[String]) returns internal - set.add(key, value).asScala must beEqualTo(set).await - there were one(internal).add(key, value) - } - "contains" in new MockedJavaSet { - internal.contains(beEq(key)) returns true - set.contains(key).asScala.map(Boolean.unbox) must beTrue.await - there were one(internal).contains(key) - } + test("contains") { (set, internal) => + for { + _ <- internal.expect.contains(cacheKey, result = true) + _ <- set.contains(cacheKey).assertingEqual(true) + } yield Passed + } - "remove" in new MockedJavaSet { - internal.remove(anyVarArgs[String]) returns internal - set.remove(key, value).asScala must beEqualTo(set).await - there were one(internal).remove(key, value) - } + test("remove") { (set, internal) => + for { + _ <- internal.expect.remove(cacheKey, cacheValue) + _ <- set.remove(cacheKey, cacheValue).assertingEqual(set) + } yield Passed + } + + test("toSet") { (set, internal) => + for { + _ <- internal.expect.toSet(cacheKey, cacheValue) + _ <- set.toSet.assertingEqual(Set(cacheKey, cacheValue).asJava) + } yield Passed + } - "toSet" in new MockedJavaSet { - internal.toSet returns Set(key, value) - set.toSet.asScala must beEqualTo(Set(key, value).asJava).await - there were one(internal).toSet + private def test( + name: String, + policy: RecoveryPolicy = recoveryPolicy.default + )( + f: (AsyncRedisSet[String], RedisSetMock) => Future[Assertion] + ): Unit = { + name in { + implicit val runtime: RedisRuntime = redisRuntime( + invocationPolicy = LazyInvocation, + recoveryPolicy = policy, + ) + val internal: RedisSetMock = mock[RedisSetMock] + val set: AsyncRedisSet[String] = new RedisSetJavaImpl(internal) + + f(set, internal) } } } diff --git a/src/test/scala/play/api/cache/redis/impl/RedisListJavaMock.scala b/src/test/scala/play/api/cache/redis/impl/RedisListJavaMock.scala new file mode 100644 index 00000000..55de8dc6 --- /dev/null +++ b/src/test/scala/play/api/cache/redis/impl/RedisListJavaMock.scala @@ -0,0 +1,131 @@ +package play.api.cache.redis.impl + +import org.scalamock.scalatest.AsyncMockFactoryBase +import play.api.cache.redis._ + +import scala.concurrent.Future + +private[impl] trait RedisListJavaMock { this: AsyncMockFactoryBase => + + protected[impl] trait RedisListMock extends RedisList[String, Future] + + final protected implicit class RedisListOps(list: RedisListMock) { + def expect: RedisListExpectation = + new RedisListExpectation(list) + } + + protected final class RedisListExpectation(list: RedisListMock) { + + def apply(index: Int, value: Option[String]): Future[Unit] = + Future.successful { + (list.apply(_: Int)) + .expects(index) + .returning( + value.fold[Future[String]]( + Future.failed(new NoSuchElementException()) + )( + Future.successful + ) + ) + .once() + } + + def get(index: Int, value: Option[String]): Future[Unit] = + Future.successful { + (list.get(_: Int)) + .expects(index) + .returning(Future.successful(value)) + .once() + } + + def prepend(value: String): Future[Unit] = + Future.successful { + (list.prepend(_: String)) + .expects(value) + .returning(Future.successful(list)) + .once() + } + + def append(value: String): Future[Unit] = + Future.successful { + (list.append(_: String)) + .expects(value) + .returning(Future.successful(list)) + .once() + } + + def headPop(value: Option[String]): Future[Unit] = + Future.successful { + (() => list.headPop) + .expects() + .returning(Future.successful(value)) + .once() + } + + def insertBefore(pivot: String, value: String, newSize: Option[Long]): Future[Unit] = + Future.successful { + (list.insertBefore(_: String, _: String)) + .expects(pivot, value) + .returning(Future.successful(newSize)) + .once() + } + + def set(index: Int, value: String): Future[Unit] = + Future.successful { + (list.set(_: Int, _: String)) + .expects(index, value) + .returning(Future.successful(list)) + .once() + } + + def remove(value: String, count: Int = 1): Future[Unit] = + Future.successful { + (list.remove(_: String, _: Int)) + .expects(value, count) + .returning(Future.successful(list)) + .once() + } + + def removeAt(index: Int): Future[Unit] = + Future.successful { + (list.removeAt(_: Int)) + .expects(index) + .returning(Future.successful(list)) + .once() + } + + def view: RedisListViewExpectation = new RedisListViewExpectation(list) + + def modify: RedisListModificationExpectation = new RedisListModificationExpectation(list) + } + + protected final class RedisListViewExpectation(list: RedisListMock) { + + def slice(from: Int, to: Int, value: List[String]): Future[Unit] = + Future.successful { + (list.view.slice(_: Int, _: Int)) + .expects(from, to) + .returning(Future.successful(value)) + .once() + } + } + + protected final class RedisListModificationExpectation(list: RedisListMock) { + + def clear(): Future[Unit] = + Future.successful { + (() => list.modify.clear()) + .expects() + .returning(Future.successful(list.modify)) + .once() + } + + def slice(from: Int, to: Int): Future[Unit] = + Future.successful { + (list.modify.slice(_: Int, _: Int)) + .expects(from, to) + .returning(Future.successful(list.modify)) + .once() + } + } +} \ No newline at end of file diff --git a/src/test/scala/play/api/cache/redis/impl/RedisListSpec.scala b/src/test/scala/play/api/cache/redis/impl/RedisListSpec.scala index 5f02a3ae..8e6b4182 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisListSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisListSpec.scala @@ -1,311 +1,354 @@ package play.api.cache.redis.impl -import scala.concurrent.duration._ - import play.api.cache.redis._ +import play.api.cache.redis.impl.Builders.AsynchronousBuilder +import play.api.cache.redis.test._ + +import scala.concurrent.Future + +class RedisListSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConnectorMock with ImplicitFutureMaterialization { + + test("prepend (all variants)") { (list, connector) => + for { + _ <- connector.expect.listPrepend(otherKey, Seq(cacheValue)) + _ <- connector.expect.listPrepend(otherKey, Seq(cacheValue)) + _ <- connector.expect.listPrepend(otherKey, Seq(cacheValue, otherValue)) + _ <- list.prepend(cacheValue).assertingEqual(list) + _ <- (List(cacheValue, otherValue) ++: list).assertingEqual(list) + _ <- (cacheValue +: list).assertingEqual(list) + } yield Passed + } -import org.specs2.concurrent.ExecutionEnv -import org.specs2.mutable.Specification - -class RedisListSpec(implicit ee: ExecutionEnv) extends Specification with ReducedMockito { - import Implicits._ - import RedisCacheImplicits._ - - import org.mockito.ArgumentMatchers._ - import org.mockito._ - - val expiration = 1.second - - "Redis List" should { - - "prepend (all variants)" in new MockedList { - connector.listPrepend(key, value) returns 5L - connector.listPrepend(key, value, value) returns 10L - // verify - list.prepend(value) must beEqualTo(list).await - List(value, value) ++: list must beEqualTo(list).await - value +: list must beEqualTo(list).await - - there were two(connector).listPrepend(key, value) - there were one(connector).listPrepend(key, value, value) - } - - "prepend (failing)" in new MockedList { - connector.listPrepend(key, value) returns ex - // verify - list.prepend(value) must beEqualTo(list).await - - there were one(connector).listPrepend(key, value) - } - - "append (all variants)" in new MockedList { - connector.listAppend(key, value) returns 5L - connector.listAppend(key, value, value) returns 10L - // verify - list.append(value) must beEqualTo(list).await - list :+ value must beEqualTo(list).await - list :++ Seq(value, value) must beEqualTo(list).await + test("prepend (failing)") { (list, connector) => + for { + _ <- connector.expect.listPrepend(otherKey, Seq(cacheValue), result = failure) + _ <- list.prepend(cacheValue).assertingEqual(list) + } yield Passed + } - there were two(connector).listAppend(key, value) - there were one(connector).listAppend(key, value, value) - } + test("append (all variants)") { (list, connector) => + for { + _ <- connector.expect.listAppend(otherKey,Seq( cacheValue)) + _ <- connector.expect.listAppend(otherKey,Seq( cacheValue)) + _ <- connector.expect.listAppend(otherKey,Seq( cacheValue, cacheValue)) + _ <- list.append(cacheValue).assertingEqual(list) + _ <- (list :+ cacheValue).assertingEqual(list) + _ <- (list :++ Seq(cacheValue, cacheValue)).assertingEqual(list) + } yield Passed + } - "append (failing)" in new MockedList { - connector.listAppend(key, value) returns ex - // verify - list.append(value) must beEqualTo(list).await + test("append (failing)") { (list, connector) => + for { + _ <- connector.expect.listAppend(otherKey, Seq(cacheValue), result = failure) + _ <- list.append(cacheValue).assertingEqual(list) + } yield Passed + } - there were one(connector).listAppend(key, value) + test("get (miss)") { (list, connector) => + for { + _ <- connector.expect.listSlice(otherKey, 5, 5, Seq(cacheValue)) + _ <- list.get(5).assertingEqual(Some(cacheValue)) + } yield Passed } - "get (miss)" in new MockedList { - connector.listSlice[String](beEq(key), anyInt, anyInt)(anyClassTag) returns Seq(value) - list.get(5) must beSome(value).await - } + test("get (hit)") { (list, connector) => + for { + _ <- connector.expect.listSlice(otherKey, 5, 5, Seq.empty[String]) + _ <- list.get(5).assertingEqual(None) + } yield Passed + } - "get (hit)" in new MockedList { - connector.listSlice[String](beEq(key), anyInt, anyInt)(anyClassTag) returns Seq.empty[String] - list.get(5) must beNone.await - } + test("get (failure)") { (list, connector) => + for { + _ <- connector.expect.listSlice[String](otherKey, 5, 5, result = failure) + _ <- list.get(5).assertingEqual(None) + } yield Passed + } - "get (failure)" in new MockedList { - connector.listSlice[String](beEq(key), anyInt, anyInt)(anyClassTag) returns ex - list.get(5) must beNone.await - } + test("apply (hit)") { (list, connector) => + for { + _ <- connector.expect.listSlice(otherKey, 5, 5, Seq(cacheValue)) + _ <- list(5).assertingEqual(cacheValue) + } yield Passed + } - "apply (hit)" in new MockedList { - connector.listSlice[String](beEq(key), anyInt, anyInt)(anyClassTag) returns Seq(value) - list(5) must beEqualTo(value).await - } + test("apply (miss)") { (list, connector) => + for { + _ <- connector.expect.listSlice(otherKey, 5, 5, Seq.empty[String]) + _ <- list(5).assertingFailure[NoSuchElementException] + } yield Passed + } - "apply (miss)" in new MockedList { - connector.listSlice[String](beEq(key), anyInt, anyInt)(anyClassTag) returns Seq.empty[String] - list(5) must throwA[NoSuchElementException].await - } + test("apply (failing)") { (list, connector) => + for { + _ <- connector.expect.listSlice[String](otherKey, 5, 5, result = failure) + _ <- list(5).assertingFailure[NoSuchElementException] + } yield Passed + } - "apply (failing)" in new MockedList { - connector.listSlice[String](beEq(key), anyInt, anyInt)(anyClassTag) returns ex - list(5) must throwA[NoSuchElementException].await - } + test("head pop") { (list, connector) => + for { + _ <- connector.expect.listHeadPop[String](otherKey, result = None) + _ <- list.headPop.assertingEqual(None) + } yield Passed + } - "head pop" in new MockedList { - connector.listHeadPop[String](beEq(key))(anyClassTag) returns None - list.headPop must beNone.await - } + test("head pop (failing)") { (list, connector) => + for { + _ <- connector.expect.listHeadPop[String](otherKey, result = failure) + _ <- list.headPop.assertingEqual(None) + } yield Passed + } - "head pop (failing)" in new MockedList { - connector.listHeadPop[String](beEq(key))(anyClassTag) returns ex - list.headPop must beNone.await - } + test("size") { (list, connector) => + for { + _ <- connector.expect.listSize(otherKey, 2L) + _ <- list.size.assertingEqual(2L) + } yield Passed + } - "size" in new MockedList { - connector.listSize(key) returns 2L - list.size must beEqualTo(2L).await - } + test("size (failing)") { (list, connector) => + for { + _ <- connector.expect.listSize(otherKey, result = failure) + _ <- list.size.assertingEqual(0L) + } yield Passed + } - "size (failing)" in new MockedList { - connector.listSize(key) returns ex - list.size must beEqualTo(0L).await - } + test("insert before") { (list, connector) => + for { + _ <- connector.expect.listInsert(otherKey, "pivot", cacheValue, Some(5L)) + _ <- list.insertBefore("pivot", cacheValue).assertingEqual(Some(5L)) + } yield Passed + } - "insert before" in new MockedList { - connector.listInsert(key, "pivot", value) returns Some(5L) - list.insertBefore("pivot", value) must beSome(5L).await - there were one(connector).listInsert(key, "pivot", value) - } + test("insert before (failing)") { (list, connector) => + for { + _ <- connector.expect.listInsert(otherKey, "pivot", cacheValue, result = failure) + _ <- list.insertBefore("pivot", cacheValue).assertingEqual(None) + } yield Passed + } - "insert before (failing)" in new MockedList { - connector.listInsert(key, "pivot", value) returns ex - list.insertBefore("pivot", value) must beNone.await - there were one(connector).listInsert(key, "pivot", value) - } + test("set at position") { (list, connector) => + for { + _ <- connector.expect.listSetAt(otherKey, 5, cacheValue, result = ()) + _ <- list.set(5, cacheValue).assertingEqual(list) + } yield Passed + } - "set at position" in new MockedList { - connector.listSetAt(beEq(key), anyInt, beEq(value)) returns unit - list.set(5, value) must beEqualTo(list).await - there were one(connector).listSetAt(key, 5, value) - } + test("set at position (failing)") { (list, connector) => + for { + _ <- connector.expect.listSetAt(otherKey, 5, cacheValue, result = failure) + _ <- list.set(5, cacheValue).assertingEqual(list) + } yield Passed + } - "set at position (failing)" in new MockedList { - connector.listSetAt(beEq(key), anyInt, beEq(value)) returns ex - list.set(5, value) must beEqualTo(list).await - there were one(connector).listSetAt(key, 5, value) - } + test("empty list") { (list, connector) => + for { + _ <- connector.expect.listSize(otherKey, result = 0L) + _ <- connector.expect.listSize(otherKey, result = 0L) + _ <- list.isEmpty.assertingEqual(true) + _ <- list.nonEmpty.assertingEqual(false) + } yield Passed + } - "empty list" in new MockedList { - connector.listSize(beEq(key)) returns 0L - list.isEmpty must beTrue.await - list.nonEmpty must beFalse.await - } + test("non-empty list") { (list, connector) => + for { + _ <- connector.expect.listSize(otherKey, 1L) + _ <- connector.expect.listSize(otherKey, 1L) + _ <- list.isEmpty.assertingEqual(false) + _ <- list.nonEmpty.assertingEqual(true) + } yield Passed + } - "non-empty list" in new MockedList { - connector.listSize(beEq(key)) returns 1L - list.isEmpty must beFalse.await - list.nonEmpty must beTrue.await - } + test("empty/non-empty list (failing)") { (list, connector) => + for { + _ <- connector.expect.listSize(otherKey, result = failure) + _ <- connector.expect.listSize(otherKey, result = failure) + _ <- list.isEmpty.assertingEqual(true) + _ <- list.nonEmpty.assertingEqual(false) + } yield Passed + } - "empty/non-empty list (failing)" in new MockedList { - connector.listSize(beEq(key)) returns ex - list.isEmpty must beTrue.await - list.nonEmpty must beFalse.await - } + test("remove element") { (list, connector) => + for { + _ <- connector.expect.listRemove(otherKey, cacheValue, 1, result = 1L) + _ <- list.remove(cacheValue).assertingEqual(list) + } yield Passed + } - "remove element" in new MockedList { - connector.listRemove(anyString, anyString, anyInt) returns 1L - list.remove(value) must beEqualTo(list).await - there were one(connector).listRemove(key, value, 1) - } + test("remove element (failing)") { (list, connector) => + for { + _ <- connector.expect.listRemove(otherKey, cacheValue, 1, result = failure) + _ <- list.remove(cacheValue).assertingEqual(list) + } yield Passed + } - "remove element (failing)" in new MockedList { - connector.listRemove(anyString, anyString, anyInt) returns ex - list.remove(value) must beEqualTo(list).await - there were one(connector).listRemove(key, value, 1) - } + test("remove at position") { (list, connector) => + for { + _ <- connector.expect.listRemoveAt(otherKey, 1, result = 1L) + _ <- list.removeAt(1).assertingEqual(list) + } yield Passed + } - "remove at position" in new MockedList { - Mockito.when(connector.listSetAt(anyString, anyInt, anyString)).thenAnswer { - AdditionalAnswers.answer { - new stubbing.Answer3[Future[Unit], String, Int, String] { - def answer(key: String, position: Int, value: String) = { - data(position) = value - unit - } - } - } - } - - Mockito.when(connector.listRemove(anyString, anyString, anyInt)).thenAnswer { - AdditionalAnswers.answer { - new stubbing.Answer3[Future[Long], String, String, Int] { - def answer(key: String, value: String, count: Int) = { - val index = data.indexOf(value) - if (index > -1) data.remove(index, 1) - if (index > -1) 1L else 0L - } - } - } - } - - list.removeAt(0) must beEqualTo(list).await - data mustEqual Seq(value, value) - - list.removeAt(1) must beEqualTo(list).await - data mustEqual Seq(value) - } + test("remove at position (failing)") { (list, connector) => + for { + _ <- connector.expect.listRemoveAt(otherKey, 1, result = failure) + _ <- list.removeAt(1).assertingEqual(list) + } yield Passed + } - "remove at position (failing)" in new MockedList { - connector.listSetAt(anyString, anyInt, anyString) returns ex - list.removeAt(0) must beEqualTo(list).await - there were one(connector).listSetAt(key, 0, "play-redis:DELETED") - } + test("view all") { (list, connector) => + for { + _ <- connector.expect.listSlice(otherKey, 0, -1, result = Seq(cacheValue)) + _ <- list.view.all.assertingEqual(Seq(cacheValue)) + } yield Passed + } - "view all" in new MockedList { - connector.listSlice[String](anyString, anyInt, anyInt)(anyClassTag) returns (data: Seq[String]) - list.view.all must beEqualTo(data).await - there were one(connector).listSlice[String](key, 0, -1) - } + test("view take") { (list, connector) => + for { + _ <- connector.expect.listSlice(otherKey, 0, 1, result = Seq(cacheValue)) + _ <- list.view.take(2).assertingEqual(Seq(cacheValue)) + } yield Passed + } - "view take" in new MockedList { - connector.listSlice[String](anyString, anyInt, anyInt)(anyClassTag) returns (data: Seq[String]) - list.view.take(2) must beEqualTo(data).await - there were one(connector).listSlice[String](key, 0, 1) - } + test("view drop") { (list, connector) => + for { + _ <- connector.expect.listSlice(otherKey, 2, -1, result = Seq(cacheValue)) + _ <- list.view.drop(2).assertingEqual(Seq(cacheValue)) + } yield Passed + } - "view drop" in new MockedList { - connector.listSlice[String](anyString, anyInt, anyInt)(anyClassTag) returns (data: Seq[String]) - list.view.drop(2) must beEqualTo(data).await - there were one(connector).listSlice[String](key, 2, -1) - } + test("view slice") { (list, connector) => + for { + _ <- connector.expect.listSlice(otherKey, 1, 2, result = Seq(cacheValue)) + _ <- list.view.slice(1, 2).assertingEqual(Seq(cacheValue)) + } yield Passed + } - "view slice" in new MockedList { - connector.listSlice[String](anyString, anyInt, anyInt)(anyClassTag) returns (data: Seq[String]) - list.view.slice(1, 2) must beEqualTo(data).await - there were one(connector).listSlice[String](key, 1, 2) - } + test("view slice (failing)") { (list, connector) => + for { + _ <- connector.expect.listSlice[String](otherKey, 1, 2, result = failure) + _ <- list.view.slice(1, 2).assertingEqual(Seq.empty) + } yield Passed + } - "view slice (failing)" in new MockedList { - connector.listSlice[String](anyString, anyInt, anyInt)(anyClassTag) returns ex - list.view.slice(1, 2) must beEqualTo(Seq.empty).await - } + test("head (non-empty)") { (list, connector) => + for { + _ <- connector.expect.listSlice(otherKey, 0, 0, result = Seq(cacheValue)) + _ <- connector.expect.listSlice(otherKey, 0, 0, result = Seq(cacheValue)) + _ <- list.head.assertingEqual(cacheValue) + _ <- list.headOption.assertingEqual(Some(cacheValue)) + } yield Passed + } - "head (non-empty)" in new MockedList { - connector.listSlice[String](anyString, anyInt, anyInt)(anyClassTag) returns data.headOption.toSeq - list.head must beEqualTo(data.head).await - list.headOption must beSome(data.head).await - there were two(connector).listSlice[String](key, 0, 0) - } + test("head (empty)") { (list, connector) => + for { + _ <- connector.expect.listSlice(otherKey, 0, 0, result = Seq.empty[String]) + _ <- connector.expect.listSlice(otherKey, 0, 0, result = Seq.empty[String]) + _ <- list.head.assertingFailure[NoSuchElementException] + _ <- list.headOption.assertingEqual(None) + } yield Passed + } - "head (empty)" in new MockedList { - connector.listSlice[String](anyString, anyInt, anyInt)(anyClassTag) returns Seq.empty[String] - list.head must throwA[NoSuchElementException].await - list.headOption must beNone.await - there were two(connector).listSlice[String](key, 0, 0) - } + test("head (failing)") { (list, connector) => + for { + _ <- connector.expect.listSlice[String](otherKey, 0, 0, result = failure) + _ <- connector.expect.listSlice[String](otherKey, 0, 0, result = failure) + _ <- list.head.assertingFailure[NoSuchElementException] + _ <- list.headOption.assertingEqual(None) + } yield Passed + } - "head (failing)" in new MockedList { - connector.listSlice[String](anyString, anyInt, anyInt)(anyClassTag) returns ex - list.head must throwA[NoSuchElementException].await - list.headOption must beNone.await - } + test("last (non-empty)") { (list, connector) => + for { + _ <- connector.expect.listSlice[String](otherKey, -1, -1, result = Seq(cacheValue)) + _ <- connector.expect.listSlice[String](otherKey, -1, -1, result = Seq(cacheValue)) + _ <- list.last.assertingEqual(cacheValue) + _ <- list.lastOption.assertingEqual(Some(cacheValue)) + } yield Passed + } - "last (non-empty)" in new MockedList { - connector.listSlice[String](anyString, anyInt, anyInt)(anyClassTag) returns data.headOption.toSeq - list.last must beEqualTo(data.head).await - list.lastOption must beSome(data.head).await - there were two(connector).listSlice[String](key, -1, -1) - } + test("last (empty)") { (list, connector) => + for { + _ <- connector.expect.listSlice(otherKey, -1, -1, result = Seq.empty[String]) + _ <- connector.expect.listSlice(otherKey, -1, -1, result = Seq.empty[String]) + _ <- list.last.assertingFailure[NoSuchElementException] + _ <- list.lastOption.assertingEqual(None) + } yield Passed + } - "last (empty)" in new MockedList { - connector.listSlice[String](anyString, anyInt, anyInt)(anyClassTag) returns Seq.empty[String] - list.last must throwA[NoSuchElementException].await - list.lastOption must beNone.await - there were two(connector).listSlice[String](key, -1, -1) - } + test("last (failing)") { (list, connector) => + for { + _ <- connector.expect.listSlice[String](otherKey, 0, 0, result = failure) + _ <- connector.expect.listSlice[String](otherKey, 0, 0, result = failure) + _ <- list.head.assertingFailure[NoSuchElementException] + _ <- list.headOption.assertingEqual(None) + } yield Passed + } - "last (failing)" in new MockedList { - connector.listSlice[String](anyString, anyInt, anyInt)(anyClassTag) returns ex - list.head must throwA[NoSuchElementException].await - list.headOption must beNone.await - } + test("toList") { (list, connector) => + for { + _ <- connector.expect.listSlice(otherKey, 0, -1, result = Seq(cacheValue, otherValue)) + _ <- list.toList.assertingEqual(Seq(cacheValue, otherValue)) + } yield Passed + } - "toList" in new MockedList { - connector.listSlice[String](anyString, anyInt, anyInt)(anyClassTag) returns (data: Seq[String]) - list.toList must beEqualTo(data).await - there were one(connector).listSlice[String](key, 0, -1) - } + test("toList (failing)") { (list, connector) => + for { + _ <- connector.expect.listSlice[String](otherKey, 0, -1, result = failure) + _ <- list.toList.assertingEqual(Seq.empty) + } yield Passed + } - "toList (failing)" in new MockedList { - connector.listSlice[String](anyString, anyInt, anyInt)(anyClassTag) returns ex - list.toList must beEqualTo(Seq.empty).await - there were one(connector).listSlice[String](key, 0, -1) - } + test("modify collection") { (list, _) => + for { + _ <- Future.successful(list.modify.collection).assertingEqual(list) + } yield Passed + } - "modify collection" in new MockedList { - list.modify.collection mustEqual list - } + test("modify take") { (list, connector) => + for { + _ <- connector.expect.listTrim(otherKey, 0, 1) + _ <- list.modify.take(2).assertingSuccess + } yield Passed + } - "modify take" in new MockedList { - connector.listTrim(anyString, anyInt, anyInt) returns unit - list.modify.take(2) must not(throwA[Throwable]).await - there were one(connector).listTrim(key, 0, 1) - } + test("modify drop") { (list, connector) => + for { + _ <- connector.expect.listTrim(otherKey, 2, -1) + _ <- list.modify.drop(2).assertingSuccess + } yield Passed + } - "modify drop" in new MockedList { - connector.listTrim(anyString, anyInt, anyInt) returns unit - list.modify.drop(2) must not(throwA[Throwable]).await - there were one(connector).listTrim(key, 2, -1) - } + test("modify clear") { (list, connector) => + for { + _ <- connector.expect.remove(otherKey) + _ <- list.modify.clear().assertingSuccess + } yield Passed + } - "modify clear" in new MockedList { - connector.remove(anyVarArgs) returns unit - list.modify.clear() must not(throwA[Throwable]).await - there were one(connector).remove(key) - } + test("modify slice") { (list, connector) => + for { + _ <- connector.expect.listTrim(otherKey, 1, 2) + _ <- list.modify.slice(1, 2).assertingSuccess + } yield Passed + } - "modify slice" in new MockedList { - connector.listTrim(anyString, anyInt, anyInt) returns unit - list.modify.slice(1, 2) must not(throwA[Throwable]).await - there were one(connector).listTrim(key, 1, 2) + private def test( + name: String, + policy: RecoveryPolicy = recoveryPolicy.default + )( + f: (RedisList[String, AsynchronousResult], RedisConnectorMock) => Future[Assertion] + ): Unit = { + name in { + implicit val runtime: RedisRuntime = redisRuntime( + invocationPolicy = LazyInvocation, + recoveryPolicy = policy, + ) + implicit val builder: Builders.AsynchronousBuilder.type = AsynchronousBuilder + val connector: RedisConnectorMock = mock[RedisConnectorMock] + val list: RedisList[String, AsynchronousResult] = new RedisListImpl[String, AsynchronousResult](otherKey, connector) + f(list, connector) } } } diff --git a/src/test/scala/play/api/cache/redis/impl/RedisMapJavaMock.scala b/src/test/scala/play/api/cache/redis/impl/RedisMapJavaMock.scala new file mode 100644 index 00000000..4e186387 --- /dev/null +++ b/src/test/scala/play/api/cache/redis/impl/RedisMapJavaMock.scala @@ -0,0 +1,89 @@ +package play.api.cache.redis.impl + +import org.scalamock.scalatest.AsyncMockFactoryBase +import play.api.cache.redis._ + +import scala.concurrent.Future + +private[impl] trait RedisMapJavaMock { this: AsyncMockFactoryBase => + + protected[impl] trait RedisMapMock extends RedisMap[String, Future] { + + override final def remove(field: String*): Future[RedisMap[String, Future]] = + removeValues(field) + + def removeValues(field: Seq[String]): Future[RedisMap[String, Future]] + } + + final protected implicit class RedisMapOps(map: RedisMapMock) { + def expect: RedisMapExpectation = + new RedisMapExpectation(map) + } + + protected final class RedisMapExpectation(map: RedisMapMock) { + + def add(key: String, value: String): Future[Unit] = + Future.successful { + (map.add(_: String, _: String)) + .expects(key, value) + .returning(Future.successful(map)) + .once() + } + + def get(key: String, value: Option[String]): Future[Unit] = + Future.successful { + (map.get(_: String)) + .expects(key) + .returning(Future.successful(value)) + .once() + } + + def contains(key: String, result: Boolean): Future[Unit] = + Future.successful { + (map.contains(_: String)) + .expects(key) + .returning(Future.successful(result)) + .once() + } + + def remove(key: String*): Future[Unit] = + Future.successful { + (map.removeValues(_: Seq[String])) + .expects(key) + .returning(Future.successful(map)) + .once() + } + + def increment(key: String, by: Long, result: Long): Future[Unit] = + Future.successful { + (map.increment(_: String, _: Long)) + .expects(key, by) + .returning(Future.successful(result)) + .once() + } + + def toMap(values: (String, String)*): Future[Unit] = + Future.successful { + (() => map.toMap) + .expects() + .returning(Future.successful(values.toMap)) + .once() + } + + def keySet(keys: String*): Future[Unit] = + Future.successful { + (() => map.keySet) + .expects() + .returning(Future.successful(keys.toSet)) + .once() + } + + def values(values: String*): Future[Unit] = + Future.successful { + (() => map.values) + .expects() + .returning(Future.successful(values.toSet)) + .once() + } + } +} \ No newline at end of file diff --git a/src/test/scala/play/api/cache/redis/impl/RedisMapSpec.scala b/src/test/scala/play/api/cache/redis/impl/RedisMapSpec.scala index 7de61087..fdbbc50c 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisMapSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisMapSpec.scala @@ -1,155 +1,205 @@ package play.api.cache.redis.impl import play.api.cache.redis._ +import play.api.cache.redis.impl.Builders.AsynchronousBuilder +import play.api.cache.redis.test._ -import org.specs2.concurrent.ExecutionEnv -import org.specs2.mutable.Specification +import scala.concurrent.Future -class RedisMapSpec(implicit ee: ExecutionEnv) extends Specification with ReducedMockito { +class RedisMapSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConnectorMock with ImplicitFutureMaterialization { - import Implicits._ - import RedisCacheImplicits._ - - import org.mockito.ArgumentMatchers._ - - "Redis Map" should { + test("set") { (map, connector) => + for { + _ <- connector.expect.hashSet(cacheKey, field, cacheValue, result = true) + _ <- map.add(field, cacheValue).assertingEqual(map) + } yield Passed + } - "set" in new MockedMap { - connector.hashSet(anyString, anyString, anyString) returns true - map.add(field, value) must beEqualTo(map).await - there were one(connector).hashSet(key, field, value) - } + test("set (failing)") { (map, connector) => + for { + _ <- connector.expect.hashSet(cacheKey, field, cacheValue, result = failure) + _ <- map.add(field, cacheValue).assertingEqual(map) + } yield Passed + } - "set (failing)" in new MockedMap { - connector.hashSet(anyString, anyString, anyString) returns ex - map.add(field, value) must beEqualTo(map).await - there were one(connector).hashSet(key, field, value) - } + test("get") { (map, connector) => + for { + _ <- connector.expect.hashGet[String](cacheKey, field, result = Some(cacheValue)) + _ <- connector.expect.hashGet[String](cacheKey, otherValue, result = None) + _ <- map.get(field).assertingEqual(Some(cacheValue)) + _ <- map.get(otherValue).assertingEqual(None) + } yield Passed + } - "get" in new MockedMap { - connector.hashGet[String](anyString, beEq(field))(anyClassTag) returns Some(value) - connector.hashGet[String](anyString, beEq(other))(anyClassTag) returns None - map.get(field) must beSome(value).await - map.get(other) must beNone.await - there were one(connector).hashGet[String](key, field) - there were one(connector).hashGet[String](key, other) - } + test("get (failing)") { (map, connector) => + for { + _ <- connector.expect.hashGet[String](cacheKey, field, result = failure) + _ <- map.get(field).assertingEqual(None) + } yield Passed + } - "get (failing)" in new MockedMap { - connector.hashGet[String](anyString, beEq(field))(anyClassTag) returns ex - map.get(field) must beNone.await - there were one(connector).hashGet[String](key, field) - } + test("get fields") { (map, connector) => + for { + _ <- connector.expect.hashGet[String](cacheKey, Seq(field, otherValue), result = Seq(Some(cacheValue), None)) + _ <- connector.expect.hashGet[String](cacheKey, Seq(field, otherValue), result = Seq(Some(cacheValue), None)) + _ <- map.getFields(field, otherValue).assertingEqual(Seq(Some(cacheValue), None)) + _ <- map.getFields(Seq(field, otherValue)).assertingEqual(Seq(Some(cacheValue), None)) + } yield Passed + } - "get fields" in new MockedMap { - connector.hashGet[String](anyString, beEq(Seq(field, other)))(anyClassTag) returns Seq(Some(value), None) - map.getFields(field, other) must beEqualTo(Seq(Some(value), None)).await - map.getFields(Seq(field, other)) must beEqualTo(Seq(Some(value), None)).await - there were two(connector).hashGet[String](key, Seq(field, other)) - } + test("get fields (failing)") { (map, connector) => + for { + _ <- connector.expect.hashGet[String](cacheKey, Seq(field, otherValue), result = failure) + _ <- connector.expect.hashGet[String](cacheKey, Seq(field, otherValue), result = failure) + _ <- map.getFields(field, otherValue).assertingEqual(Seq(None, None)) + _ <- map.getFields(Seq(field, otherValue)).assertingEqual(Seq(None, None)) + } yield Passed + } - "get fields (failing)" in new MockedMap { - connector.hashGet[String](anyString, beEq(Seq(field, other)))(anyClassTag) returns ex - map.getFields(field, other) must beEqualTo(Seq(None, None)).await - map.getFields(Seq(field, other)) must beEqualTo(Seq(None, None)).await - there were two(connector).hashGet[String](key, Seq(field, other)) - } + test("contains") { (map, connector) => + for { + _ <- connector.expect.hashExists(cacheKey, field, result = true) + _ <- connector.expect.hashExists(cacheKey, otherValue, result = false) + _ <- map.contains(field).assertingEqual(true) + _ <- map.contains(otherValue).assertingEqual(false) + } yield Passed + } - "contains" in new MockedMap { - connector.hashExists(anyString, beEq(field)) returns true - connector.hashExists(anyString, beEq(other)) returns false - map.contains(field) must beTrue.await - map.contains(other) must beFalse.await - } + test("contains (failing)") { (map, connector) => + for { + _ <- connector.expect.hashExists(cacheKey, field, result = failure) + _ <- map.contains(field).assertingEqual(false) + } yield Passed + } - "contains (failing)" in new MockedMap { - connector.hashExists(anyString, anyString) returns ex - map.contains(field) must beFalse.await - there were one(connector).hashExists(key, field) - } + test("remove") { (map, connector) => + for { + _ <- connector.expect.hashRemove(cacheKey, Seq(field)) + _ <- connector.expect.hashRemove(cacheKey, Seq(field, otherValue)) + _ <- map.remove(field).assertingEqual(map) + _ <- map.remove(field, otherValue).assertingEqual(map) + } yield Passed + } - "remove" in new MockedMap { - connector.hashRemove(anyString, anyVarArgs) returns 1L - map.remove(field) must beEqualTo(map).await - map.remove(field, other) must beEqualTo(map).await - there were one(connector).hashRemove(key, field) - there were one(connector).hashRemove(key, field, other) - } + test("remove (failing)") { (map, connector) => + for { + _ <- connector.expect.hashRemove(cacheKey, Seq(field), result = failure) + _ <- map.remove(field).assertingEqual(map) + } yield Passed + } - "remove (failing)" in new MockedMap { - connector.hashRemove(anyString, anyVarArgs) returns ex - map.remove(field) must beEqualTo(map).await - there were one(connector).hashRemove(key, field) - } + test("increment") { (map, connector) => + for { + _ <- connector.expect.hashIncrement(cacheKey, field, 2L, result = 5L) + _ <- map.increment(field, 2L).assertingEqual(5L) + } yield Passed + } - "increment" in new MockedMap { - connector.hashIncrement(anyString, beEq(field), anyLong) returns 5L - map.increment(field, 2L) must beEqualTo(5L).await - there were one(connector).hashIncrement(key, field, 2L) - } + test("increment (failing)") { (map, connector) => + for { + _ <- connector.expect.hashIncrement(cacheKey, field, 2L, result = failure) + _ <- map.increment(field, 2L).assertingEqual(2L) + } yield Passed + } - "increment (failing)" in new MockedMap { - connector.hashIncrement(anyString, beEq(field), anyLong) returns ex - map.increment(field, 2L) must beEqualTo(2L).await - there were one(connector).hashIncrement(key, field, 2L) - } + test("toMap") { (map, connector) => + for { + _ <- connector.expect.hashGetAll[String](cacheKey, result = Map(field -> cacheValue)) + _ <- map.toMap.assertingEqual(Map(field -> cacheValue)) + } yield Passed + } - "toMap" in new MockedMap { - connector.hashGetAll[String](anyString)(anyClassTag) returns Map(field -> value) - map.toMap must beEqualTo(Map(field -> value)).await - } + test("toMap (failing)") { (map, connector) => + for { + _ <- connector.expect.hashGetAll[String](cacheKey, result = failure) + _ <- map.toMap.assertingEqual(Map.empty) + } yield Passed + } - "toMap (failing)" in new MockedMap { - connector.hashGetAll[String](anyString)(anyClassTag) returns ex - map.toMap must beEqualTo(Map.empty).await - } + test("keySet") { (map, connector) => + for { + _ <- connector.expect.hashKeys(cacheKey, result = Set(field)) + _ <- map.keySet.assertingEqual(Set(field)) + } yield Passed + } - "keySet" in new MockedMap { - connector.hashKeys(anyString) returns Set(field) - map.keySet must beEqualTo(Set(field)).await - } + test("keySet (failing)") { (map, connector) => + for { + _ <- connector.expect.hashKeys(cacheKey, result = failure) + _ <- map.keySet.assertingEqual(Set.empty) + } yield Passed + } - "keySet (failing)" in new MockedMap { - connector.hashKeys(anyString) returns ex - map.keySet must beEqualTo(Set.empty).await - } + test("values") { (map, connector) => + for { + _ <- connector.expect.hashValues[String](cacheKey, result = Set(cacheValue)) + _ <- map.values.assertingEqual(Set(cacheValue)) + } yield Passed + } - "values" in new MockedMap { - connector.hashValues[String](anyString)(anyClassTag) returns Set(value) - map.values must beEqualTo(Set(value)).await - } + test("values (failing)") { (map, connector) => + for { + _ <- connector.expect.hashValues[String](cacheKey, result = failure) + _ <- map.values.assertingEqual(Set.empty) + } yield Passed + } - "values (failing)" in new MockedMap { - connector.hashValues[String](anyString)(anyClassTag) returns ex - map.values must beEqualTo(Set.empty).await - } + test("size") { (map, connector) => + for { + _ <- connector.expect.hashSize(cacheKey, result = 2L) + _ <- map.size.assertingEqual(2L) + } yield Passed + } - "size" in new MockedMap { - connector.hashSize(key) returns 2L - map.size must beEqualTo(2L).await - } + test("size (failing)") { (map, connector) => + for { + _ <- connector.expect.hashSize(cacheKey, result = failure) + _ <- map.size.assertingEqual(0L) + } yield Passed + } - "size (failing)" in new MockedMap { - connector.hashSize(key) returns ex - map.size must beEqualTo(0L).await - } + test("empty map") { (map, connector) => + for { + _ <- connector.expect.hashSize(cacheKey, result = 0L) + _ <- connector.expect.hashSize(cacheKey, result = 0L) + _ <- map.isEmpty.assertingEqual(true) + _ <- map.nonEmpty.assertingEqual(false) + } yield Passed + } - "empty map" in new MockedMap { - connector.hashSize(beEq(key)) returns 0L - map.isEmpty must beTrue.await - map.nonEmpty must beFalse.await - } + test("non-empty map") { (map, connector) => + for { + _ <- connector.expect.hashSize(cacheKey, result = 1L) + _ <- connector.expect.hashSize(cacheKey, result = 1L) + _ <- map.isEmpty.assertingEqual(false) + _ <- map.nonEmpty.assertingEqual(true) + } yield Passed + } - "non-empty map" in new MockedMap { - connector.hashSize(beEq(key)) returns 1L - map.isEmpty must beFalse.await - map.nonEmpty must beTrue.await - } + test("empty/non-empty map (failing)") { (map, connector) => + for { + _ <- connector.expect.hashSize(cacheKey, result = failure) + _ <- connector.expect.hashSize(cacheKey, result = failure) + _ <- map.isEmpty.assertingEqual(true) + _ <- map.nonEmpty.assertingEqual(false) + } yield Passed + } - "empty/non-empty map (failing)" in new MockedMap { - connector.hashSize(beEq(key)) returns ex - map.isEmpty must beTrue.await - map.nonEmpty must beFalse.await + private def test( + name: String, + policy: RecoveryPolicy = recoveryPolicy.default + )( + f: (RedisMap[String, AsynchronousResult], RedisConnectorMock) => Future[Assertion] + ): Unit = { + name in { + implicit val runtime: RedisRuntime = redisRuntime( + invocationPolicy = LazyInvocation, + recoveryPolicy = policy, + ) + implicit val builder: Builders.AsynchronousBuilder.type = AsynchronousBuilder + val connector: RedisConnectorMock = mock[RedisConnectorMock] + val map: RedisMap[String, AsynchronousResult] = new RedisMapImpl[String, AsynchronousResult](cacheKey, connector) + f(map, connector) } } -} + } diff --git a/src/test/scala/play/api/cache/redis/impl/RedisPrefixSpec.scala b/src/test/scala/play/api/cache/redis/impl/RedisPrefixSpec.scala index 31481927..e0ecc5cb 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisPrefixSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisPrefixSpec.scala @@ -1,36 +1,44 @@ package play.api.cache.redis.impl -import play.api.cache.redis._ +import play.api.cache.redis.test._ -import org.specs2.concurrent.ExecutionEnv -import org.specs2.mutable.Specification +import scala.concurrent.Future -class RedisPrefixSpec(implicit ee: ExecutionEnv) extends Specification with ReducedMockito { +class RedisPrefixSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConnectorMock with ImplicitFutureMaterialization { - import Implicits._ - import RedisCacheImplicits._ - - import org.mockito.ArgumentMatchers._ - - "RedisPrefix" should { + test("apply when defined", prefix = new RedisPrefixImpl("prefix")) { (connector, cache) => + for { + // get one + _ <- connector.expect.get[String](s"prefix:$cacheKey", None) + _ <- cache.get[String](cacheKey).assertingEqual(None) + // get multiple + _ <- connector.expect.mGet[String](Seq(s"prefix:$cacheKey", s"prefix:$otherKey"), Seq(None, Some(cacheValue))) + _ <- cache.getAll[String](cacheKey, otherKey).assertingEqual(Seq(None, Some(cacheValue))) + } yield Passed + } - "apply when defined" in new MockedCache { - runtime.prefix returns new RedisPrefixImpl("prefix") - connector.get[String](anyString)(anyClassTag) returns None - connector.mGet[String](anyVarArgs)(anyClassTag) returns Seq(None, Some(value)) - // run the test - cache.get[String](key) must beNone.await - cache.getAll[String](key, other) must beEqualTo(Seq(None, Some(value))).await - there were one(connector).get[String](s"prefix:$key") - there were one(connector).mGet[String](s"prefix:$key", s"prefix:$other") - } + test("not apply when is empty", prefix = RedisEmptyPrefix) { (connector, cache) => + for { + // get one + _ <- connector.expect.get[String](cacheKey, None) + _ <- cache.get[String](cacheKey).assertingEqual(None) + // get multiple + _ <- connector.expect.mGet[String](Seq(cacheKey, otherKey), Seq(None, Some(cacheValue))) + _ <- cache.getAll[String](cacheKey, otherKey).assertingEqual(Seq(None, Some(cacheValue))) + } yield Passed + } - "not apply when is empty" in new MockedCache { - runtime.prefix returns RedisEmptyPrefix - connector.get[String](anyString)(anyClassTag) returns None - // run the test - cache.get[String](key) must beNone.await - there were one(connector).get[String](key) + private def test( + name: String, + prefix: RedisPrefix + )( + f: (RedisConnectorMock, AsyncRedis) => Future[Assertion] + ): Unit = { + name in { + implicit val runtime: RedisRuntime = redisRuntime(prefix = prefix) + val connector = mock[RedisConnectorMock] + val cache: AsyncRedis = new AsyncRedisImpl(connector) + f(connector, cache) } } } diff --git a/src/test/scala/play/api/cache/redis/impl/RedisRuntimeMock.scala b/src/test/scala/play/api/cache/redis/impl/RedisRuntimeMock.scala new file mode 100644 index 00000000..0bc693e9 --- /dev/null +++ b/src/test/scala/play/api/cache/redis/impl/RedisRuntimeMock.scala @@ -0,0 +1,41 @@ +package play.api.cache.redis.impl + +import akka.util.Timeout +import org.scalamock.scalatest.AsyncMockFactoryBase +import play.api.cache.redis.{FailThrough, RecoverWithDefault, RecoveryPolicy, RedisException} + +import scala.concurrent.{ExecutionContext, Future} +import scala.concurrent.duration._ + +private[impl] trait RedisRuntimeMock { outer: AsyncMockFactoryBase => + + protected object recoveryPolicy { + + private class RerunPolicy extends RecoveryPolicy { + override def recoverFrom[T]( + rerun: => Future[T], + default: => Future[T], + failure: RedisException + ): Future[T] = rerun + } + + val failThrough: RecoveryPolicy = new FailThrough {} + val default: RecoveryPolicy = new RecoverWithDefault {} + val rerun: RecoveryPolicy = new RerunPolicy + } + + protected def redisRuntime( + invocationPolicy: InvocationPolicy = EagerInvocation, + recoveryPolicy: RecoveryPolicy = outer.recoveryPolicy.failThrough, + timeout: FiniteDuration = 200.millis, + prefix: RedisPrefix = RedisEmptyPrefix, + ): RedisRuntime = { + val runtime = mock[RedisRuntime] + (() => runtime.context).expects().returns(ExecutionContext.global).anyNumberOfTimes() + (() => runtime.invocation).expects().returns(invocationPolicy).anyNumberOfTimes() + (() => runtime.prefix).expects().returns(prefix).anyNumberOfTimes() + (() => runtime.policy).expects().returns(recoveryPolicy).anyNumberOfTimes() + (() => runtime.timeout).expects().returns(Timeout(timeout)).anyNumberOfTimes() + runtime + } +} diff --git a/src/test/scala/play/api/cache/redis/impl/RedisRuntimeSpec.scala b/src/test/scala/play/api/cache/redis/impl/RedisRuntimeSpec.scala index 96390493..bd2aef9c 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisRuntimeSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisRuntimeSpec.scala @@ -1,52 +1,67 @@ package play.api.cache.redis.impl +import akka.actor.ActorSystem +import akka.util.Timeout import play.api.cache.redis._ +import play.api.cache.redis.configuration.{RedisHost, RedisStandalone} +import play.api.cache.redis.test.UnitSpec -import org.specs2.mutable.Specification +class RedisRuntimeSpec extends UnitSpec { + import RedisRuntime._ -class RedisRuntimeSpec extends Specification with WithApplication { + private implicit val recoveryResolver: RecoveryPolicyResolver = + new RecoveryPolicyResolverImpl - import Implicits._ + private implicit val system: ActorSystem = ActorSystem("test") - implicit val recoveryResolver: RecoveryPolicyResolver = new RecoveryPolicyResolverImpl - - "RedisRuntime" should { - import RedisRuntime._ - - "be build from config (A)" in { - val runtime = RedisRuntime( - instance = defaultInstance, - recovery = "log-and-fail", - invocation = "eager", - prefix = None - ) - - runtime.timeout mustEqual akka.util.Timeout(defaultInstance.timeout.sync) - runtime.policy must beAnInstanceOf[LogAndFailPolicy] - runtime.invocation mustEqual EagerInvocation - runtime.prefix mustEqual RedisEmptyPrefix - } + "be build from config (A)" in { + val instance = RedisStandalone( + name = "standalone", + host = RedisHost(localhost, defaultPort), + settings = defaultsSettings + ) + val runtime = RedisRuntime( + instance = instance, + recovery = "log-and-fail", + invocation = "eager", + prefix = None + ) + runtime.timeout mustEqual Timeout(instance.timeout.sync) + runtime.policy mustBe a[LogAndFailPolicy] + runtime.invocation mustEqual EagerInvocation + runtime.prefix mustEqual RedisEmptyPrefix + } "be build from config (B)" in { + val instance = RedisStandalone( + name = "standalone", + host = RedisHost(localhost, defaultPort), + settings = defaultsSettings + ) val runtime = RedisRuntime( - instance = defaultInstance, + instance = instance, recovery = "log-and-default", invocation = "lazy", prefix = Some("prefix") ) - - runtime.policy must beAnInstanceOf[LogAndDefaultPolicy] + runtime.policy mustBe a[LogAndDefaultPolicy] runtime.invocation mustEqual LazyInvocation runtime.prefix mustEqual new RedisPrefixImpl("prefix") } "be build from config (C)" in { - RedisRuntime( - instance = defaultInstance, - recovery = "log-and-default", - invocation = "other", - prefix = Some("prefix") - ) must throwA[IllegalArgumentException] + val instance = RedisStandalone( + name = "standalone", + host = RedisHost(localhost, defaultPort), + settings = defaultsSettings + ) + assertThrows[IllegalArgumentException] { + RedisRuntime( + instance = instance, + recovery = "log-and-default", + invocation = "other", + prefix = Some("prefix") + ) + } } - } } diff --git a/src/test/scala/play/api/cache/redis/impl/RedisSetJavaMock.scala b/src/test/scala/play/api/cache/redis/impl/RedisSetJavaMock.scala new file mode 100644 index 00000000..728e69aa --- /dev/null +++ b/src/test/scala/play/api/cache/redis/impl/RedisSetJavaMock.scala @@ -0,0 +1,62 @@ +package play.api.cache.redis.impl + +import org.scalamock.scalatest.AsyncMockFactoryBase +import play.api.cache.redis._ + +import scala.concurrent.Future + +private[impl] trait RedisSetJavaMock { this: AsyncMockFactoryBase => + + protected[impl] trait RedisSetMock extends RedisSet[String, Future] { + + override final def add(values: String*): Future[RedisSet[String, Future]] = + addValues(values) + + def addValues(value: Seq[String]): Future[RedisSet[String, Future]] + + override final def remove(values: String*): Future[RedisSet[String, Future]] = + removeValues(values) + + def removeValues(value: Seq[String]): Future[RedisSet[String, Future]] + } + + final protected implicit class RedisSetOps(set: RedisSetMock) { + def expect: RedisSetExpectation = + new RedisSetExpectation(set) + } + + protected final class RedisSetExpectation(set: RedisSetMock) { + + def add(value: String*): Future[Unit] = + Future.successful { + (set.addValues(_: Seq[String])) + .expects(value) + .returning(Future.successful(set)) + .once() + } + + def contains(value: String, result: Boolean): Future[Unit] = + Future.successful { + (set.contains(_: String)) + .expects(value) + .returning(Future.successful(result)) + .once() + } + + def remove(value: String*): Future[Unit] = + Future.successful { + (set.removeValues(_: Seq[String])) + .expects(value) + .returning(Future.successful(set)) + .once() + } + + def toSet(values: String*): Future[Unit] = + Future.successful { + (() => set.toSet) + .expects() + .returning(Future.successful(values.toSet)) + .once() + } + } +} \ No newline at end of file diff --git a/src/test/scala/play/api/cache/redis/impl/RedisSetSpec.scala b/src/test/scala/play/api/cache/redis/impl/RedisSetSpec.scala index c216f08a..add20ac6 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisSetSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisSetSpec.scala @@ -1,95 +1,131 @@ package play.api.cache.redis.impl import play.api.cache.redis._ +import play.api.cache.redis.impl.Builders.AsynchronousBuilder +import play.api.cache.redis.test._ -import org.specs2.concurrent.ExecutionEnv -import org.specs2.mutable.Specification +import scala.concurrent.Future -class RedisSetSpec(implicit ee: ExecutionEnv) extends Specification with ReducedMockito { - import Implicits._ - import RedisCacheImplicits._ +class RedisSetSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConnectorMock with ImplicitFutureMaterialization { - import org.mockito.ArgumentMatchers._ - - "Redis Set" should { + test("add") { (set, connector) => + for { + _ <- connector.expect.setAdd(cacheKey, Seq(cacheValue)) + _ <- connector.expect.setAdd(cacheKey, Seq(cacheValue, otherValue)) + _ <- set.add(cacheValue).assertingEqual(set) + _ <- set.add(cacheValue, otherValue).assertingEqual(set) + } yield Passed + } - "add" in new MockedSet { - connector.setAdd(anyString, anyVarArgs) returns 5L - set.add(value) must beEqualTo(set).await - set.add(value, other) must beEqualTo(set).await - there were one(connector).setAdd(key, value) - there were one(connector).setAdd(key, value, other) - } + test("add (failing)") { (set, connector) => + for { + _ <- connector.expect.setAdd(cacheKey, Seq(cacheValue), result = failure) + _ <- set.add(cacheValue).assertingEqual(set) + } yield Passed + } - "add (failing)" in new MockedSet { - connector.setAdd(anyString, anyVarArgs) returns ex - set.add(value) must beEqualTo(set).await - there were one(connector).setAdd(key, value) - } + test("contains") { (set, connector) => + for { + _ <- connector.expect.setIsMember(cacheKey, cacheValue, result = true) + _ <- connector.expect.setIsMember(cacheKey, otherValue, result = false) + _ <- set.contains(cacheValue).assertingEqual(true) + _ <- set.contains(otherValue).assertingEqual(false) + } yield Passed + } - "contains" in new MockedSet { - connector.setIsMember(anyString, beEq(value)) returns true - connector.setIsMember(anyString, beEq(other)) returns false - set.contains(value) must beTrue.await - set.contains(other) must beFalse.await - } + test("contains (failing)") { (set, connector) => + for { + _ <- connector.expect.setIsMember(cacheKey, cacheValue, result = failure) + _ <- set.contains(cacheValue).assertingEqual(false) + } yield Passed + } - "contains (failing)" in new MockedSet { - connector.setIsMember(anyString, anyString) returns ex - set.contains(value) must beFalse.await - there were one(connector).setIsMember(key, value) - } + test("remove") { (set, connector) => + for { + _ <- connector.expect.setRemove(cacheKey, Seq(cacheValue)) + _ <- connector.expect.setRemove(cacheKey, Seq(otherValue, cacheValue)) + _ <- set.remove(cacheValue).assertingEqual(set) + _ <- set.remove(otherValue, cacheValue).assertingEqual(set) + } yield Passed + } - "remove" in new MockedSet { - connector.setRemove(anyString, anyVarArgs) returns 1L - set.remove(value) must beEqualTo(set).await - set.remove(other, value) must beEqualTo(set).await - there were one(connector).setRemove(key, value) - there were one(connector).setRemove(key, other, value) - } + test("remove (failing)") { (set, connector) => + for { + _ <- connector.expect.setRemove(cacheKey, Seq(cacheValue), result = failure) + _ <- set.remove(cacheValue).assertingEqual(set) + } yield Passed + } - "remove (failing)" in new MockedSet { - connector.setRemove(anyString, anyVarArgs) returns ex - set.remove(value) must beEqualTo(set).await - there were one(connector).setRemove(key, value) - } + test("toSet") { (set, connector) => + for { + _ <- connector.expect.setMembers[String](cacheKey, result = Set[Any](cacheValue, otherValue)) + _ <- set.toSet.assertingEqual(Set(cacheValue, otherValue)) + } yield Passed + } - "toSet" in new MockedSet { - connector.setMembers[String](anyString)(anyClassTag) returns (data.toSet: Set[String]) - set.toSet must beEqualTo(data).await - } + test("toSet (failing)") { (set, connector) => + for { + _ <- connector.expect.setMembers[String](cacheKey, result = failure) + _ <- set.toSet.assertingEqual(Set.empty) + } yield Passed + } - "toSet (failing)" in new MockedSet { - connector.setMembers[String](anyString)(anyClassTag) returns ex - set.toSet must beEqualTo(Set.empty).await - } + test("size") { (set, connector) => + for { + _ <- connector.expect.setSize(cacheKey, result = 2L) + _ <- set.size.assertingEqual(2L) + } yield Passed + } - "size" in new MockedSet { - connector.setSize(key) returns 2L - set.size must beEqualTo(2L).await - } + test("size (failing)") { (set, connector) => + for { + _ <- connector.expect.setSize(cacheKey, result = failure) + _ <- set.size.assertingEqual(0L) + } yield Passed + } - "size (failing)" in new MockedSet { - connector.setSize(key) returns ex - set.size must beEqualTo(0L).await - } + test("empty set") { (set, connector) => + for { + _ <- connector.expect.setSize(cacheKey, result = 0L) + _ <- connector.expect.setSize(cacheKey, result = 0L) + _ <- set.isEmpty.assertingEqual(true) + _ <- set.nonEmpty.assertingEqual(false) + } yield Passed + } - "empty set" in new MockedSet { - connector.setSize(beEq(key)) returns 0L - set.isEmpty must beTrue.await - set.nonEmpty must beFalse.await - } + test("non-empty set") { (set, connector) => + for { + _ <- connector.expect.setSize(cacheKey, result = 1L) + _ <- connector.expect.setSize(cacheKey, result = 1L) + _ <- set.isEmpty.assertingEqual(false) + _ <- set.nonEmpty.assertingEqual(true) + } yield Passed + } - "non-empty set" in new MockedSet { - connector.setSize(beEq(key)) returns 1L - set.isEmpty must beFalse.await - set.nonEmpty must beTrue.await - } + test("empty/non-empty set (failing)") { (set, connector) => + for { + _ <- connector.expect.setSize(cacheKey, result = failure) + _ <- connector.expect.setSize(cacheKey, result = failure) + _ <- set.isEmpty.assertingEqual(true) + _ <- set.nonEmpty.assertingEqual(false) + } yield Passed + } - "empty/non-empty set (failing)" in new MockedSet { - connector.setSize(beEq(key)) returns ex - set.isEmpty must beTrue.await - set.nonEmpty must beFalse.await + private def test( + name: String, + policy: RecoveryPolicy = recoveryPolicy.default + )( + f: (RedisSet[String, AsynchronousResult], RedisConnectorMock) => Future[Assertion] + ): Unit = { + name in { + implicit val runtime: RedisRuntime = redisRuntime( + invocationPolicy = LazyInvocation, + recoveryPolicy = policy, + ) + implicit val builder: Builders.AsynchronousBuilder.type = AsynchronousBuilder + val connector: RedisConnectorMock = mock[RedisConnectorMock] + val set: RedisSet[String, AsynchronousResult] = new RedisSetImpl[String, AsynchronousResult](cacheKey, connector) + f(set, connector) } } } diff --git a/src/test/scala/play/api/cache/redis/impl/RedisSortedSetSpec.scala b/src/test/scala/play/api/cache/redis/impl/RedisSortedSetSpec.scala index d0a867af..22cdbdb7 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisSortedSetSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisSortedSetSpec.scala @@ -1,98 +1,134 @@ package play.api.cache.redis.impl -import org.specs2.concurrent.ExecutionEnv -import org.specs2.mutable.Specification import play.api.cache.redis._ +import play.api.cache.redis.impl.Builders.AsynchronousBuilder +import play.api.cache.redis.test._ -import scala.reflect.ClassTag +import scala.concurrent.Future -class RedisSortedSetSpec(implicit ee: ExecutionEnv) extends Specification with ReducedMockito { - import Implicits._ - import RedisCacheImplicits._ - import org.mockito.ArgumentMatchers._ +class RedisSortedSetSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConnectorMock with ImplicitFutureMaterialization { - "Redis Set" should { + private val scoreValue: (Double, String) = (1.0, "cacheValue") + private val otherScoreValue: (Double, String) = (2.0, "other") - "add" in new MockedSortedSet { - connector.sortedSetAdd(anyString, anyVarArgs) returns 5L - set.add(scoreValue) must beEqualTo(set).await - set.add(scoreValue, otherScoreValue) must beEqualTo(set).await - there were one(connector).sortedSetAdd(key, scoreValue) - there were one(connector).sortedSetAdd(key, scoreValue, otherScoreValue) - } + test("add") { (set, connector) => + for { + _ <- connector.expect.sortedSetAdd(cacheKey, Seq(scoreValue)) + _ <- connector.expect.sortedSetAdd(cacheKey, Seq(scoreValue, otherScoreValue)) + _ <- set.add(scoreValue).assertingEqual(set) + _ <- set.add(scoreValue, otherScoreValue).assertingEqual(set) + } yield Passed + } - "add (failing)" in new MockedSortedSet { - connector.sortedSetAdd(anyString, anyVarArgs) returns ex - set.add(scoreValue) must beEqualTo(set).await - there were one(connector).sortedSetAdd(key, scoreValue) - } + test("add (failing)") { (set, connector) => + for { + _ <- connector.expect.sortedSetAdd(cacheKey, Seq(scoreValue), result = failure) + _ <- set.add(scoreValue).assertingEqual(set) + } yield Passed + } - "contains (hit)" in new MockedSortedSet { - connector.sortedSetScore(beEq(key), beEq(other)) returns Some(1D) - set.contains(other) must beTrue.await - there was one(connector).sortedSetScore(key, other) - } + test("contains (hit)") { (set, connector) => + for { + _ <- connector.expect.sortedSetScore(cacheKey, otherValue, result = Some(1D)) + _ <- set.contains(otherValue).assertingEqual(true) + } yield Passed + } - "contains (miss)" in new MockedSortedSet { - connector.sortedSetScore(beEq(key), beEq(other)) returns None - set.contains(other) must beFalse.await - there was one(connector).sortedSetScore(key, other) - } + test("contains (miss)") { (set, connector) => + for { + _ <- connector.expect.sortedSetScore(cacheKey, otherValue, result = None) + _ <- set.contains(otherValue).assertingEqual(false) + } yield Passed + } - "remove" in new MockedSortedSet { - connector.sortedSetRemove(anyString, anyVarArgs) returns 1L - set.remove(value) must beEqualTo(set).await - set.remove(other, value) must beEqualTo(set).await - there were one(connector).sortedSetRemove(key, value) - there were one(connector).sortedSetRemove(key, other, value) - } + test("remove") { (set, connector) => + for { + _ <- connector.expect.sortedSetRemove(cacheKey, Seq(cacheValue)) + _ <- connector.expect.sortedSetRemove(cacheKey, Seq(otherValue, cacheValue)) + _ <- set.remove(cacheValue).assertingEqual(set) + _ <- set.remove(otherValue, cacheValue).assertingEqual(set) + } yield Passed + } - "remove (failing)" in new MockedSortedSet { - connector.sortedSetRemove(anyString, anyVarArgs) returns ex - set.remove(value) must beEqualTo(set).await - there was one(connector).sortedSetRemove(key, value) - } + test("remove (failing)") { (set, connector) => + for { + _ <- connector.expect.sortedSetRemove(cacheKey, Seq(cacheValue), result = failure) + _ <- set.remove(cacheValue).assertingEqual(set) + } yield Passed + } - "range" in new MockedSortedSet { - val data = Seq(value, other) - connector.sortedSetRange[String](anyString, anyLong, anyLong)(anyClassTag) returns data - set.range(1, 5) must beEqualTo(data).await - there was one(connector).sortedSetRange(key, 1, 5)(implicitly[ClassTag[String]]) - } + test("range") { (set, connector) => + val data = Seq(cacheValue, otherValue) + for { + _ <- connector.expect.sortedSetRange[String](cacheKey, 1, 5, result = data) + _ <- set.range(1, 5).assertingEqual(data) + } yield Passed + } - "range (reversed)" in new MockedSortedSet { - val data = Seq(value, other) - connector.sortedSetReverseRange[String](anyString, anyLong, anyLong)(anyClassTag) returns data - set.range(1, 5, isReverse = true) must beEqualTo(data).await - there was one(connector).sortedSetReverseRange(key, 1, 5)(implicitly[ClassTag[String]]) - } + test("range (reversed)") { (set, connector) => + val data = Seq(cacheValue, otherValue) + for { + _ <- connector.expect.sortedSetReverseRange[String](cacheKey, 1, 5, result = data) + _ <- set.range(1, 5, isReverse = true).assertingEqual(data) + } yield Passed + } - "size" in new MockedSortedSet { - connector.sortedSetSize(key) returns 2L - set.size must beEqualTo(2L).await - } + test("size") { (set, connector) => + for { + _ <- connector.expect.sortedSetSize(cacheKey, result = 2L) + _ <- set.size.assertingEqual(2L) + } yield Passed + } - "size (failing)" in new MockedSortedSet { - connector.sortedSetSize(key) returns ex - set.size must beEqualTo(0L).await - } + test("size (failing)") { (set, connector) => + for { + _ <- connector.expect.sortedSetSize(cacheKey, result = failure) + _ <- set.size.assertingEqual(0L) + } yield Passed + } - "empty set" in new MockedSortedSet { - connector.sortedSetSize(beEq(key)) returns 0L - set.isEmpty must beTrue.await - set.nonEmpty must beFalse.await - } + test("empty set") { (set, connector) => + for { + _ <- connector.expect.sortedSetSize(cacheKey, result = 0L) + _ <- connector.expect.sortedSetSize(cacheKey, result = 0L) + _ <- set.isEmpty.assertingEqual(true) + _ <- set.nonEmpty.assertingEqual(false) + } yield Passed + } - "non-empty set" in new MockedSortedSet { - connector.sortedSetSize(beEq(key)) returns 1L - set.isEmpty must beFalse.await - set.nonEmpty must beTrue.await - } + test("non-empty set") { (set, connector) => + for { + _ <- connector.expect.sortedSetSize(cacheKey, result = 1L) + _ <- connector.expect.sortedSetSize(cacheKey, result = 1L) + _ <- set.isEmpty.assertingEqual(false) + _ <- set.nonEmpty.assertingEqual(true) + } yield Passed + } + + test("empty/non-empty set (failing)") { (set, connector) => + for { + _ <- connector.expect.sortedSetSize(cacheKey, result = failure) + _ <- connector.expect.sortedSetSize(cacheKey, result = failure) + _ <- set.isEmpty.assertingEqual(true) + _ <- set.nonEmpty.assertingEqual(false) + } yield Passed + } - "empty/non-empty set (failing)" in new MockedSortedSet { - connector.sortedSetSize(beEq(key)) returns ex - set.isEmpty must beTrue.await - set.nonEmpty must beFalse.await + private def test( + name: String, + policy: RecoveryPolicy = recoveryPolicy.default + )( + f: (RedisSortedSet[String, AsynchronousResult], RedisConnectorMock) => Future[Assertion] + ): Unit = { + name in { + implicit val runtime: RedisRuntime = redisRuntime( + invocationPolicy = LazyInvocation, + recoveryPolicy = policy, + ) + implicit val builder: Builders.AsynchronousBuilder.type = AsynchronousBuilder + val connector: RedisConnectorMock = mock[RedisConnectorMock] + val set: RedisSortedSet[String, AsynchronousResult] = new RedisSortedSetImpl[String, AsynchronousResult](cacheKey, connector) + f(set, connector) } } -} + } diff --git a/src/test/scala/play/api/cache/redis/impl/SyncRedisSpec.scala b/src/test/scala/play/api/cache/redis/impl/SyncRedisSpec.scala index 62b9fcab..abc7109b 100644 --- a/src/test/scala/play/api/cache/redis/impl/SyncRedisSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/SyncRedisSpec.scala @@ -1,47 +1,88 @@ package play.api.cache.redis.impl -import scala.concurrent.duration._ - import play.api.cache.redis._ +import play.api.cache.redis.test._ -import org.specs2.concurrent.ExecutionEnv -import org.specs2.mutable.Specification +import scala.concurrent.Future -class SyncRedisSpec(implicit ee: ExecutionEnv) extends Specification with ReducedMockito { +class SyncRedisSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConnectorMock with ImplicitFutureMaterialization { + import Helpers._ - import Implicits._ - import RedisCacheImplicits._ + test("get or else (hit)") { (cache, connector) => + for { + _ <- connector.expect.get[String](cacheKey, result = Some(cacheValue)) + orElse = probe.orElse.const(otherValue) + _ = cache.getOrElse(cacheKey)(orElse.execute()) mustEqual cacheValue + _ = orElse.calls mustEqual 0 + } yield Passed + } - import org.mockito.ArgumentMatchers._ + test("get or else (miss)") { (cache, connector) => + for { + _ <- connector.expect.get[String](cacheKey, result = None) + _ <- connector.expect.set(cacheKey, cacheValue, result = true) + orElse = probe.orElse.const(cacheValue) + _ = cache.getOrElse(cacheKey)(orElse.execute()) mustEqual cacheValue + _ = orElse.calls mustEqual 1 + } yield Passed + } - "SyncRedis" should { + test("get or else (failure in get)") { (cache, connector) => + for { + _ <- connector.expect.get[String](cacheKey, result = failure) + _ <- connector.expect.set(cacheKey, cacheValue, result = true) + orElse = probe.orElse.const(cacheValue) + _ = cache.getOrElse(cacheKey)(orElse.execute()) mustEqual cacheValue + _ = orElse.calls mustEqual 1 + } yield Passed + } - "get or else (hit)" in new MockedSyncRedis with OrElse { - connector.get[String](anyString)(anyClassTag) returns Some(value) - cache.getOrElse(key)(doElse(value)) must beEqualTo(value) - orElse mustEqual 0 - } + test("get or else (failure in set)") { (cache, connector) => + for { + _ <- connector.expect.get[String](cacheKey, result = None) + _ <- connector.expect.set(cacheKey, cacheValue, result = failure) + orElse = probe.orElse.const(cacheValue) + _ = cache.getOrElse(cacheKey)(orElse.execute()) mustEqual cacheValue + _ = orElse.calls mustEqual 1 + } yield Passed + } - "get or else (miss)" in new MockedSyncRedis with OrElse { - connector.get[String](anyString)(anyClassTag) returns None - connector.set(anyString, anyString, any[Duration], anyBoolean) returns true - cache.getOrElse(key)(doElse(value)) must beEqualTo(value) - orElse mustEqual 1 - } + test("get or else (failure in orElse)") { (cache, connector) => + for { + _ <- connector.expect.get[String](cacheKey, result = failure) + _ <- connector.expect.set(cacheKey, cacheValue, result = true) + orElse = probe.orElse.const(cacheValue) + _ = cache.getOrElse(cacheKey)(orElse.execute()) mustEqual cacheValue + _ = orElse.calls mustEqual 1 + } yield Passed + } - "get or else (failure)" in new MockedSyncRedis with OrElse { - connector.get[String](anyString)(anyClassTag) returns ex - connector.set(anyString, anyString, any[Duration], anyBoolean) returns true - cache.getOrElse(key)(doElse(value)) must beEqualTo(value) - orElse mustEqual 1 - } + test("get or else (prefixed,miss)", prefix = Some("the-prefix")) { (cache, connector) => + for { + _ <- connector.expect.get[String](s"the-prefix:$cacheKey", result = None) + _ <- connector.expect.set(s"the-prefix:$cacheKey", cacheValue, result = true) + orElse = probe.orElse.const(cacheValue) + _ = cache.getOrElse(cacheKey)(orElse.execute()) mustEqual cacheValue + _ = orElse.calls mustEqual 1 + } yield Passed + } - "get or else (prefixed,miss)" in new MockedSyncRedis with OrElse { - runtime.prefix returns new RedisPrefixImpl("prefix") - connector.get[String](beEq(s"prefix:$key"))(anyClassTag) returns None - connector.set(beEq(s"prefix:$key"), anyString, any[Duration], anyBoolean) returns true - cache.getOrElse(key)(doElse(value)) must beEqualTo(value) - orElse mustEqual 1 + private def test( + name: String, + policy: RecoveryPolicy = recoveryPolicy.default, + prefix: Option[String] = None, + )( + f: (RedisCache[SynchronousResult], RedisConnectorMock) => Future[Assertion] + ): Unit = { + name in { + implicit val runtime: RedisRuntime = redisRuntime( + invocationPolicy = LazyInvocation, + recoveryPolicy = policy, + prefix = prefix.fold[RedisPrefix](RedisEmptyPrefix)(new RedisPrefixImpl(_)), + ) + val connector: RedisConnectorMock = mock[RedisConnectorMock] + val cache: RedisCache[SynchronousResult] = new SyncRedis(connector) + f(cache, connector) } } -} + } diff --git a/src/test/scala/play/api/cache/redis/test/BaseSpec.scala b/src/test/scala/play/api/cache/redis/test/BaseSpec.scala new file mode 100644 index 00000000..2f173560 --- /dev/null +++ b/src/test/scala/play/api/cache/redis/test/BaseSpec.scala @@ -0,0 +1,165 @@ +package play.api.cache.redis.test + +import akka.Done +import org.scalactic.source.Position +import org.scalamock.scalatest.AsyncMockFactory +import org.scalatest.matchers.must.Matchers +import org.scalatest.wordspec.{AnyWordSpecLike, AsyncWordSpecLike} +import org.scalatest._ +import play.api.cache.redis.RedisException +import play.api.cache.redis.configuration._ + +import java.util.concurrent.{CompletionStage, TimeoutException} +import scala.concurrent.duration._ +import scala.concurrent.{Await, ExecutionContext, Future} +import scala.language.implicitConversions +import scala.reflect.ClassTag +import scala.util.{Failure, Success, Try} + +trait DefaultValues { + + protected final val defaultCacheName: String = "play" + protected final val localhost = "localhost" + protected final val defaultPort: Int = 6379 + + val defaultsSettings: RedisSettingsTest = + RedisSettingsTest( + invocationContext = "akka.actor.default-dispatcher", + invocationPolicy = "lazy", + timeout = RedisTimeouts(1.second, None, Some(500.millis)), + recovery = "log-and-default", + source = "standalone" + ) + + protected final val cacheKey: String = "cache-key" + protected final val cacheValue: String = "cache-value" + protected final val otherKey: String = "other-key" + protected final val otherValue: String = "other-value" + protected final val field: String = "field" + + protected final val cacheExpiration: FiniteDuration = 1.minute + + protected final val failure: RedisException = SimulatedException.asRedis + +} + +trait ImplicitOptionMaterialization { + protected implicit def implicitlyAny2Some[T](value: T): Option[T] = Some(value) +} + +trait ImplicitFutureMaterialization { + protected implicit def implicitlyThrowable2Future[T](cause: Throwable): Future[T] = Future.failed(cause) + protected implicit def implicitlyAny2Future[T](value: T): Future[T] = Future.successful(value) +} + +trait TimeLimitedSpec extends AsyncTestSuiteMixin with AsyncUtilities { + this: AsyncTestSuite => + + protected def testTimeout: FiniteDuration = 1.second + + private class TimeLimitedTest(inner: NoArgAsyncTest) extends NoArgAsyncTest { + override def apply(): FutureOutcome = restrict(inner()) + + override val configMap: ConfigMap = inner.configMap + override val name: String = inner.name + override val scopes: IndexedSeq[String] = inner.scopes + override val text: String = inner.text + override val tags: Set[String] = inner.tags + override val pos: Option[Position] = inner.pos + } + + private def restrict(test: FutureOutcome): FutureOutcome = { + val result: Future[Outcome] = Future.firstCompletedOf( + Seq( + Future.after(testTimeout, ()).map(_ => fail(s"Test didn't finish within $testTimeout.")), + test.toFuture + ) + ) + new FutureOutcome(result) + } + + abstract override def withFixture(test: NoArgAsyncTest): FutureOutcome = { + super.withFixture(new TimeLimitedTest(test)) + } +} + +trait AsyncUtilities { this: AsyncTestSuite => + + implicit class FutureAsyncUtilities(future: Future.type) { + def after[T](duration: FiniteDuration, value: T): Future[T] = + Future(Await.result(Future.never, duration)).recover(_ => value) + + def waitFor(duration: FiniteDuration): Future[Unit] = + after(duration, ()) + } + +} + +trait FutureAssertions { this: BaseSpec => + import scala.jdk.FutureConverters._ + + implicit def completionStageToFutureOps[T](future: CompletionStage[T]): FutureAssertionOps[T] = + new FutureAssertionOps(future.asScala) + + implicit def completionStageToFutureDoneOps(future: CompletionStage[Done]): FutureAssertionDoneOps = + new FutureAssertionDoneOps(future.asScala) + + implicit class FutureAssertionOps[T](future: Future[T]) { + + def asserting(f: T => Assertion): Future[Assertion] = + future.map(f) + + def assertingCondition(f: T => Boolean): Future[Assertion] = + future.map(v => assert(f(v))) + + def assertingEqual(expected: => T): Future[Assertion] = + asserting(_ mustEqual expected) + + def assertingTry(f: Try[T] => Assertion): Future[Assertion] = + future.map(Success.apply).recover { case ex => Failure(ex) }.map(f) + + def assertingFailure[Cause <: Throwable : ClassTag]: Future[Assertion] = + future.map(value => fail(s"Expected exception but got $value")).recover { case ex => ex mustBe a[Cause] } + + def assertingFailure[Cause <: Throwable : ClassTag, InnerCause <: Throwable : ClassTag]: Future[Assertion] = + future.map(value => fail(s"Expected exception but got $value")).recover { case ex => + ex mustBe a[Cause] + ex.getCause mustBe a[InnerCause] + } + + def assertingFailure(cause: Throwable): Future[Assertion] = + future.map(value => fail(s"Expected exception but got $value")).recover { case ex => ex mustEqual cause } + + def assertingSuccess: Future[Assertion] = + future.recover(cause => fail(s"Got unexpected exception", cause)).map(_ => Passed) + + def assertTimeout(timeout: FiniteDuration): Future[Assertion] = { + Future.firstCompletedOf( + Seq( + Future(Thread.sleep(timeout.toMillis)).map(_ => throw new TimeoutException(s"Expected timeout after $timeout")), + future.map(value => fail(s"Expected timeout but got $value")) + ) + ).assertingFailure[TimeoutException] + } + } + + implicit class FutureAssertionDoneOps(future: Future[Done]) { + def assertingDone: Future[Assertion] = future.assertingEqual(Done) + } +} + +trait BaseSpec extends Matchers with AsyncMockFactory { + + protected type Assertion = org.scalatest.Assertion + protected val Passed: Assertion = org.scalatest.Succeeded + + override implicit def executionContext: ExecutionContext = ExecutionContext.global +} + +trait AsyncBaseSpec extends BaseSpec with AsyncWordSpecLike with FutureAssertions with AsyncUtilities with TimeLimitedSpec + +trait UnitSpec extends BaseSpec with AnyWordSpecLike with DefaultValues + +trait AsyncUnitSpec extends AsyncBaseSpec with DefaultValues + +trait IntegrationSpec extends AsyncBaseSpec diff --git a/src/test/scala/play/api/cache/redis/test/FakeApplication.scala b/src/test/scala/play/api/cache/redis/test/FakeApplication.scala new file mode 100644 index 00000000..f496134c --- /dev/null +++ b/src/test/scala/play/api/cache/redis/test/FakeApplication.scala @@ -0,0 +1,19 @@ +package play.api.cache.redis.test + +import akka.actor.ActorSystem +import play.api.inject.Injector + +trait FakeApplication extends StoppableApplication { + import play.api.Application + import play.api.inject.guice.GuiceApplicationBuilder + + private lazy val theBuilder: GuiceApplicationBuilder = builder + + protected lazy val injector: Injector = theBuilder.injector() + + protected lazy val application: Application = injector.instanceOf[Application] + + implicit protected lazy val system: ActorSystem = injector.instanceOf[ActorSystem] + + protected def builder: GuiceApplicationBuilder = new GuiceApplicationBuilder() +} diff --git a/src/test/scala/play/api/cache/redis/ForAllTestContainer.scala b/src/test/scala/play/api/cache/redis/test/ForAllTestContainer.scala similarity index 54% rename from src/test/scala/play/api/cache/redis/ForAllTestContainer.scala rename to src/test/scala/play/api/cache/redis/test/ForAllTestContainer.scala index 224f0a7c..eddb85e9 100644 --- a/src/test/scala/play/api/cache/redis/ForAllTestContainer.scala +++ b/src/test/scala/play/api/cache/redis/test/ForAllTestContainer.scala @@ -1,11 +1,11 @@ -package play.api.cache.redis +package play.api.cache.redis.test import com.dimafeng.testcontainers.SingleContainer -import org.specs2.specification.BeforeAfterAll +import org.scalatest.{BeforeAndAfterAll, Suite} -trait ForAllTestContainer extends BeforeAfterAll { +trait ForAllTestContainer extends BeforeAndAfterAll {this: Suite => - def newContainer: SingleContainer[_] + protected def newContainer: SingleContainer[_] final protected lazy val container = newContainer diff --git a/src/test/scala/play/api/cache/redis/test/Helpers.scala b/src/test/scala/play/api/cache/redis/test/Helpers.scala new file mode 100644 index 00000000..6d78fb82 --- /dev/null +++ b/src/test/scala/play/api/cache/redis/test/Helpers.scala @@ -0,0 +1,24 @@ +package play.api.cache.redis.test + +object Helpers { + + object configuration { + import com.typesafe.config.ConfigFactory + import play.api.Configuration + + def default: Configuration = { + Configuration(ConfigFactory.load()) + } + + def fromHocon(hocon: String): Configuration = { + val reference = ConfigFactory.load() + val local = ConfigFactory.parseString(hocon.stripMargin) + Configuration(local.withFallback(reference)) + } + } + + object probe { + + val orElse: OrElseProbe.type = OrElseProbe + } +} diff --git a/src/test/scala/play/api/cache/redis/test/OrElseProbe.scala b/src/test/scala/play/api/cache/redis/test/OrElseProbe.scala new file mode 100644 index 00000000..cfe9c490 --- /dev/null +++ b/src/test/scala/play/api/cache/redis/test/OrElseProbe.scala @@ -0,0 +1,39 @@ +package play.api.cache.redis.test + +import java.util.concurrent.CompletionStage +import scala.concurrent.Future +import scala.jdk.FutureConverters.FutureOps + + +final class OrElseProbe[T](queue: LazyList[T]) { + + private var called: Int = 0 + private var next = queue + + def calls: Int = called + + def execute(): T = { + called += 1 + val result = next.head + next = next.tail + result + } +} + +object OrElseProbe { + + def const[T](value: T): OrElseProbe[T] = + new OrElseProbe(LazyList.continually(value)) + + def async[T](value: T): OrElseProbe[Future[T]] = + new OrElseProbe(LazyList.continually(Future.successful(value))) + + def asyncJava[T](value: T): OrElseProbe[CompletionStage[T]] = + new OrElseProbe(LazyList.continually(Future.successful(value).asJava)) + + def failing[T](exception: Throwable): OrElseProbe[Future[T]] = + new OrElseProbe(LazyList.continually(Future.failed(exception))) + + def generic[T](values: T*): OrElseProbe[T] = + new OrElseProbe(LazyList(values: _*)) +} diff --git a/src/test/scala/play/api/cache/redis/test/RedisClusterContainer.scala b/src/test/scala/play/api/cache/redis/test/RedisClusterContainer.scala new file mode 100644 index 00000000..28648902 --- /dev/null +++ b/src/test/scala/play/api/cache/redis/test/RedisClusterContainer.scala @@ -0,0 +1,39 @@ +package play.api.cache.redis.test + +import org.scalatest.Suite +import play.api.Logger + +import scala.concurrent.duration._ + +trait RedisClusterContainer extends RedisContainer { this: Suite => + + private val log = Logger("play.api.cache.redis.test") + + protected def redisMaster = 4 + + protected def redisSlaves = 1 + + protected final def initialPort = 7000 + + private val waitForStart = 4.seconds + + override protected lazy val redisConfig: RedisContainerConfig = + RedisContainerConfig( + redisDockerImage = "grokzen/redis-cluster:7.0.10", + redisMappedPorts = Seq.empty, + redisFixedPorts = 0.until(redisMaster * (redisSlaves + 1)).map(initialPort + _), + redisEnvironment = Map( + "IP" -> "0.0.0.0", + "INITIAL_PORT" -> initialPort.toString, + "MASTERS" -> redisMaster.toString, + "SLAVES_PER_MASTER" -> redisSlaves.toString, + ), + ) + + override def beforeAll(): Unit = { + super.beforeAll() + log.info(s"Waiting for Redis Cluster to start on ${container.containerIpAddress}, will wait for $waitForStart") + Thread.sleep(waitForStart.toMillis) + log.info(s"Finished waiting for Redis Cluster to start on ${container.containerIpAddress}") + } +} diff --git a/src/test/scala/play/api/cache/redis/test/RedisContainer.scala b/src/test/scala/play/api/cache/redis/test/RedisContainer.scala new file mode 100644 index 00000000..448918f7 --- /dev/null +++ b/src/test/scala/play/api/cache/redis/test/RedisContainer.scala @@ -0,0 +1,24 @@ +package play.api.cache.redis.test + +import com.dimafeng.testcontainers.GenericContainer +import org.scalatest.Suite +import org.testcontainers.containers.FixedHostPortGenericContainer +import org.testcontainers.containers.wait.strategy.Wait + +trait RedisContainer extends ForAllTestContainer { this: Suite => + + protected def redisConfig: RedisContainerConfig + + private lazy val config = redisConfig + + //noinspection ScalaDeprecation + protected override final val newContainer: GenericContainer = { + val container: FixedHostPortGenericContainer[_] = new FixedHostPortGenericContainer(config.redisDockerImage) + container.withExposedPorts(config.redisMappedPorts.map(int2Integer): _*) + config.redisEnvironment.foreach { case (k, v) => container.withEnv(k, v) } + container.waitingFor(Wait.forListeningPorts(config.redisMappedPorts ++ config.redisFixedPorts: _*)) + config.redisFixedPorts.foreach { port => container.withFixedExposedPort(port, port) } + new GenericContainer(container) + } +} + diff --git a/src/test/scala/play/api/cache/redis/RedisContainerConfig.scala b/src/test/scala/play/api/cache/redis/test/RedisContainerConfig.scala similarity index 54% rename from src/test/scala/play/api/cache/redis/RedisContainerConfig.scala rename to src/test/scala/play/api/cache/redis/test/RedisContainerConfig.scala index f5cdb544..6c498eb4 100644 --- a/src/test/scala/play/api/cache/redis/RedisContainerConfig.scala +++ b/src/test/scala/play/api/cache/redis/test/RedisContainerConfig.scala @@ -1,7 +1,8 @@ -package play.api.cache.redis +package play.api.cache.redis.test final case class RedisContainerConfig( redisDockerImage: String, - redisPorts: Seq[Int], + redisMappedPorts: Seq[Int], + redisFixedPorts: Seq[Int], redisEnvironment: Map[String, String], ) diff --git a/src/test/scala/play/api/cache/redis/logging/RedisLogger.scala b/src/test/scala/play/api/cache/redis/test/RedisLogger.scala similarity index 73% rename from src/test/scala/play/api/cache/redis/logging/RedisLogger.scala rename to src/test/scala/play/api/cache/redis/test/RedisLogger.scala index ef54badc..536b794f 100644 --- a/src/test/scala/play/api/cache/redis/logging/RedisLogger.scala +++ b/src/test/scala/play/api/cache/redis/test/RedisLogger.scala @@ -1,4 +1,4 @@ -package play.api.cache.redis.logging +package play.api.cache.redis.test import akka.event.Logging.{InitializeLogger, LoggerInitialized} import akka.event.slf4j.Slf4jLogger @@ -9,11 +9,11 @@ import akka.event.slf4j.Slf4jLogger */ class RedisLogger extends Slf4jLogger { - private def doReceive: PartialFunction[Any, Unit] = { + private val doReceive: PartialFunction[Any, Unit] = { case InitializeLogger(_) => sender() ! LoggerInitialized } - override def receive = { + override def receive: PartialFunction[Any, Unit] = { doReceive.orElse(super.receive) } } diff --git a/src/test/scala/play/api/cache/redis/test/RedisSentinelContainer.scala b/src/test/scala/play/api/cache/redis/test/RedisSentinelContainer.scala new file mode 100644 index 00000000..00a9d087 --- /dev/null +++ b/src/test/scala/play/api/cache/redis/test/RedisSentinelContainer.scala @@ -0,0 +1,41 @@ +package play.api.cache.redis.test + +import org.scalatest.Suite +import play.api.Logger + +import scala.concurrent.duration.DurationInt + +trait RedisSentinelContainer extends RedisContainer { + this: Suite => + + private val log = Logger("play.api.cache.redis.test") + + protected def nodes: Int = 3 + + protected final def initialPort: Int = 7000 + + protected final def sentinelPort: Int = initialPort - 2000 + + protected def master: String = s"sentinel$initialPort" + + private val waitForStart = 7.seconds + + override protected lazy val redisConfig: RedisContainerConfig = + RedisContainerConfig( + redisDockerImage = "grokzen/redis-cluster:7.0.10", + redisMappedPorts = Seq.empty, + redisFixedPorts = 0.until(nodes).flatMap(i => Seq(initialPort + i, sentinelPort + i)), + redisEnvironment = Map( + "IP" -> "0.0.0.0", + "INITIAL_PORT" -> initialPort.toString, + "SENTINEL" -> "true" + ), + ) + + override def beforeAll(): Unit = { + super.beforeAll() + log.info(s"Waiting for Redis Sentinel to start on ${container.containerIpAddress}, will wait for $waitForStart") + Thread.sleep(waitForStart.toMillis) + log.info(s"Finished waiting for Redis Sentinel to start on ${container.containerIpAddress}") + } +} diff --git a/src/test/scala/play/api/cache/redis/configuration/RedisSettingsTest.scala b/src/test/scala/play/api/cache/redis/test/RedisSettingsTest.scala similarity index 60% rename from src/test/scala/play/api/cache/redis/configuration/RedisSettingsTest.scala rename to src/test/scala/play/api/cache/redis/test/RedisSettingsTest.scala index fb0424ce..7e46d76a 100644 --- a/src/test/scala/play/api/cache/redis/configuration/RedisSettingsTest.scala +++ b/src/test/scala/play/api/cache/redis/test/RedisSettingsTest.scala @@ -1,6 +1,8 @@ -package play.api.cache.redis.configuration +package play.api.cache.redis.test -case class RedisSettingsTest( +import play.api.cache.redis.configuration._ + +final case class RedisSettingsTest( invocationContext: String, invocationPolicy: String, timeout: RedisTimeouts, diff --git a/src/test/scala/play/api/cache/redis/test/RedisStandaloneContainer.scala b/src/test/scala/play/api/cache/redis/test/RedisStandaloneContainer.scala new file mode 100644 index 00000000..e09ea24f --- /dev/null +++ b/src/test/scala/play/api/cache/redis/test/RedisStandaloneContainer.scala @@ -0,0 +1,16 @@ +package play.api.cache.redis.test + +import org.scalatest.Suite + +trait RedisStandaloneContainer extends RedisContainer { this: Suite => + + protected lazy val defaultPort: Int = 6379 + + override protected lazy val redisConfig: RedisContainerConfig = + RedisContainerConfig( + redisDockerImage = "redis:latest", + redisMappedPorts = Seq(defaultPort), + redisFixedPorts = Seq.empty, + redisEnvironment = Map.empty + ) +} diff --git a/src/test/scala/play/api/cache/redis/test/SimulatedException.scala b/src/test/scala/play/api/cache/redis/test/SimulatedException.scala new file mode 100644 index 00000000..84153c7f --- /dev/null +++ b/src/test/scala/play/api/cache/redis/test/SimulatedException.scala @@ -0,0 +1,11 @@ +package play.api.cache.redis.test + +import play.api.cache.redis.TimeoutException + +import scala.util.control.NoStackTrace + +sealed class SimulatedException extends RuntimeException("Simulated failure.") with NoStackTrace { + def asRedis: TimeoutException = TimeoutException(this) +} + +object SimulatedException extends SimulatedException diff --git a/src/test/scala/play/api/cache/redis/test/StoppableApplication.scala b/src/test/scala/play/api/cache/redis/test/StoppableApplication.scala new file mode 100644 index 00000000..6c2c392b --- /dev/null +++ b/src/test/scala/play/api/cache/redis/test/StoppableApplication.scala @@ -0,0 +1,45 @@ +package play.api.cache.redis.test + +import akka.Done +import akka.actor.{ActorSystem, CoordinatedShutdown} +import org.scalatest.Assertion +import play.api.inject.ApplicationLifecycle + +import scala.concurrent.{ExecutionContext, Future} +import scala.util.{Failure, Success} + +trait StoppableApplication extends ApplicationLifecycle { + + private var hooks: List[() => Future[_]] = Nil + + protected def system: ActorSystem + + def shutdownAsync(): Future[Done] = + CoordinatedShutdown(system).run(CoordinatedShutdown.UnknownReason) + + def runAsyncInApplication(block: => Future[Assertion])(implicit ec: ExecutionContext): Future[Assertion] = { + block.map(Success(_)).recover(Failure(_)) + .flatMap(result => Future.sequence(hooks.map(_.apply())).map(_ => result)) + .flatMap(result => shutdownAsync().map(_ => result)) + .flatMap(result => system.terminate().map(_ => result)) + .flatMap(Future.fromTry) + } + + final def runInApplication(block: => Assertion)(implicit ec: ExecutionContext): Future[Assertion] = { + runAsyncInApplication(Future(block)) + } + + final override def addStopHook(hook: () => Future[_]): Unit = + hooks = hook :: hooks + + final override def stop(): Future[_] = Future.unit + +} + +object StoppableApplication { + + def apply(actorSystem: ActorSystem): StoppableApplication = + new StoppableApplication { + override protected def system: ActorSystem = actorSystem + } +} From 1b9cdda1c6c28a54d970315ceacc6dfaa6fe4988 Mon Sep 17 00:00:00 2001 From: Karel Cemus Date: Fri, 2 Feb 2024 03:18:07 +0100 Subject: [PATCH 2/5] Removed deprecations, installed wart remover (#274) --- build.sbt | 23 ++ project/CustomReleasePlugin.scala | 16 +- project/ReleaseUtilities.scala | 1 + project/plugins.sbt | 3 + .../scala/play/api/cache/redis/CacheApi.scala | 1 - .../play/api/cache/redis/Expiration.scala | 4 +- .../play/api/cache/redis/RecoveryPolicy.scala | 37 ++-- .../api/cache/redis/RedisCacheModule.scala | 23 +- .../api/cache/redis/RedisCollection.scala | 2 - .../play/api/cache/redis/RedisList.scala | 2 - .../scala/play/api/cache/redis/RedisMap.scala | 2 - .../scala/play/api/cache/redis/RedisSet.scala | 2 - .../play/api/cache/redis/RedisSortedSet.scala | 1 - .../cache/redis/configuration/Equals.scala | 4 +- .../configuration/HostnameResolver.scala | 4 +- .../configuration/RedisConfigLoader.scala | 3 +- .../cache/redis/configuration/RedisHost.scala | 6 +- .../redis/configuration/RedisInstance.scala | 46 ++-- .../configuration/RedisInstanceManager.scala | 33 +-- .../configuration/RedisInstanceProvider.scala | 102 +++++---- .../redis/configuration/RedisSettings.scala | 35 ++- .../redis/configuration/RedisTimeouts.scala | 19 +- .../redis/connector/AkkaSerializer.scala | 58 +++-- .../cache/redis/connector/RedisCommands.scala | 43 ++-- .../redis/connector/RedisConnectorImpl.scala | 107 +++++---- .../redis/connector/RequestTimeout.scala | 10 +- .../api/cache/redis/connector/package.scala | 8 +- .../play/api/cache/redis/exceptions.scala | 30 +-- .../api/cache/redis/impl/AsyncJavaRedis.scala | 3 - .../api/cache/redis/impl/AsyncRedisImpl.scala | 2 +- .../play/api/cache/redis/impl/Builders.scala | 3 +- .../cache/redis/impl/InvocationPolicy.scala | 2 +- .../cache/redis/impl/JavaCompatibility.scala | 18 +- .../api/cache/redis/impl/RedisCache.scala | 207 ++++++++++-------- .../api/cache/redis/impl/RedisCaches.scala | 2 +- .../api/cache/redis/impl/RedisListImpl.scala | 56 ++--- .../cache/redis/impl/RedisListJavaImpl.scala | 38 ++-- .../api/cache/redis/impl/RedisMapImpl.scala | 38 ++-- .../api/cache/redis/impl/RedisPrefix.scala | 18 +- .../api/cache/redis/impl/RedisRuntime.scala | 2 +- .../api/cache/redis/impl/RedisSetImpl.scala | 22 +- .../cache/redis/impl/RedisSortedSetImpl.scala | 4 +- .../play/api/cache/redis/impl/SyncRedis.scala | 25 ++- .../scala/play/api/cache/redis/impl/dsl.scala | 8 +- .../scala/play/api/cache/redis/package.scala | 10 + .../cache/redis/RedisCacheModuleSpec.scala | 35 +-- .../RedisInstanceManagerSpec.scala | 4 +- .../redis/connector/RedisClusterSpec.scala | 42 ++-- .../cache/redis/impl/AsyncJavaRedisSpec.scala | 15 -- .../api/cache/redis/impl/AsyncRedisMock.scala | 6 +- .../cache/redis/impl/RedisConnectorMock.scala | 2 +- .../play/api/cache/redis/test/BaseSpec.scala | 6 +- .../redis/test/RedisClusterContainer.scala | 5 +- .../api/cache/redis/test/RedisContainer.scala | 5 +- .../redis/test/RedisSentinelContainer.scala | 1 + 55 files changed, 638 insertions(+), 566 deletions(-) diff --git a/build.sbt b/build.sbt index 78f6ea88..ccad203a 100644 --- a/build.sbt +++ b/build.sbt @@ -43,3 +43,26 @@ enablePlugins(CustomReleasePlugin) coverageExcludedFiles := ".*exceptions.*" Test / test := (Test / testOnly).toTask(" * -- -l \"org.scalatest.Ignore\"").value + +wartremoverWarnings ++= Warts.allBut( + Wart.Any, + Wart.AnyVal, + Wart.AsInstanceOf, + Wart.AutoUnboxing, + Wart.DefaultArguments, + Wart.GlobalExecutionContext, + Wart.ImplicitConversion, + Wart.ImplicitParameter, + Wart.IterableOps, + Wart.NonUnitStatements, + Wart.Nothing, + Wart.Null, + Wart.OptionPartial, + Wart.Overloading, + Wart.PlatformDefault, + Wart.StringPlusAny, + Wart.Throw, + Wart.ToString, + Wart.TryPartial, + Wart.Var, +) diff --git a/project/CustomReleasePlugin.scala b/project/CustomReleasePlugin.scala index d73b395e..6ddef4c7 100644 --- a/project/CustomReleasePlugin.scala +++ b/project/CustomReleasePlugin.scala @@ -1,15 +1,15 @@ import com.github.sbt.git.{GitVersioning, SbtGit} -import sbt.* -import sbt.Keys.* -import sbtrelease.* +import sbt._ +import sbt.Keys._ +import sbtrelease._ import xerial.sbt.Sonatype object CustomReleasePlugin extends AutoPlugin { - import ReleasePlugin.autoImport.* - import ReleaseStateTransformations.* - import ReleaseUtilities.* - import Sonatype.autoImport.* + import ReleasePlugin.autoImport._ + import ReleaseStateTransformations._ + import ReleaseUtilities._ + import Sonatype.autoImport._ object autoImport { val playVersion = settingKey[String]("Version of Play framework") @@ -27,7 +27,7 @@ object CustomReleasePlugin extends AutoPlugin { ) } - override def projectSettings = Seq[Setting[_]]( + override def projectSettings: Seq[Setting[_]] = Seq[Setting[_]]( publishMavenStyle := true, pomIncludeRepository := { _ => false }, // customized release process diff --git a/project/ReleaseUtilities.scala b/project/ReleaseUtilities.scala index f5e66740..b27eebca 100644 --- a/project/ReleaseUtilities.scala +++ b/project/ReleaseUtilities.scala @@ -2,6 +2,7 @@ import scala.sys.process.ProcessLogger import sbt._ import sbtrelease._ +import scala.language.implicitConversions object ReleaseUtilities { diff --git a/project/plugins.sbt b/project/plugins.sbt index 81b0b226..034784ae 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -12,3 +12,6 @@ addSbtPlugin("com.github.sbt" % "sbt-git" % "2.0.1") addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.21") addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.2.1") addSbtPlugin("com.github.sbt" % "sbt-release" % "1.1.0") + +// linters +addSbtPlugin("org.wartremover" % "sbt-wartremover" % "3.1.6") diff --git a/src/main/scala/play/api/cache/redis/CacheApi.scala b/src/main/scala/play/api/cache/redis/CacheApi.scala index 267118b7..a51dff3e 100644 --- a/src/main/scala/play/api/cache/redis/CacheApi.scala +++ b/src/main/scala/play/api/cache/redis/CacheApi.scala @@ -2,7 +2,6 @@ package play.api.cache.redis import scala.concurrent.Future import scala.concurrent.duration.Duration -import scala.language.higherKinds import scala.reflect.ClassTag /** diff --git a/src/main/scala/play/api/cache/redis/Expiration.scala b/src/main/scala/play/api/cache/redis/Expiration.scala index e57309cb..bcae616b 100644 --- a/src/main/scala/play/api/cache/redis/Expiration.scala +++ b/src/main/scala/play/api/cache/redis/Expiration.scala @@ -28,8 +28,8 @@ private[redis] trait ExpirationImplicits { class Expiration(val expireAt: Long) extends AnyVal { /** returns now in milliseconds */ - private def now = System.currentTimeMillis() + private def now: Long = System.currentTimeMillis() /** converts given timestamp indication expiration date into duration from now */ - def asExpiration = (expireAt - now).milliseconds + def asExpiration: FiniteDuration = (expireAt - now).milliseconds } diff --git a/src/main/scala/play/api/cache/redis/RecoveryPolicy.scala b/src/main/scala/play/api/cache/redis/RecoveryPolicy.scala index 4fbc43b0..86f13fa6 100644 --- a/src/main/scala/play/api/cache/redis/RecoveryPolicy.scala +++ b/src/main/scala/play/api/cache/redis/RecoveryPolicy.scala @@ -44,21 +44,22 @@ trait RecoveryPolicy { trait Reports extends RecoveryPolicy { /** logger instance */ - protected val log = Logger("play.api.cache.redis") - - protected def message(failure: RedisException): String = failure match { - case TimeoutException(cause) => s"Command execution timed out." - case SerializationException(key, message, cause) => s"$message for key '$key'." - case ExecutionFailedException(Some(key), command, statement, cause) => s"Command $command for key '$key' failed." - case ExecutionFailedException(None, command, statement, cause) => s"Command $command failed." - case UnexpectedResponseException(Some(key), command) => s"Command $command for key '$key' returned unexpected response." - case UnexpectedResponseException(None, command) => s"Command $command returned unexpected response." - } + protected val log: Logger = Logger("play.api.cache.redis") + + protected def message(failure: RedisException): String = + failure match { + case TimeoutException(_) => s"Command execution timed out." + case SerializationException(key, message, _) => s"$message for key '$key'." + case ExecutionFailedException(Some(key), command, _, _) => s"Command $command for key '$key' failed." + case ExecutionFailedException(None, command, _, _) => s"Command $command failed." + case UnexpectedResponseException(Some(key), command) => s"Command $command for key '$key' returned unexpected response." + case UnexpectedResponseException(None, command) => s"Command $command returned unexpected response." + } protected def doLog(message: String, cause: Option[Throwable]): Unit /** reports a failure into logs */ - protected def report(failure: RedisException) = { + private def report(failure: RedisException): Unit = { // create a failure report and report a failure doLog(message(failure), Option(failure.getCause)) } @@ -115,14 +116,14 @@ trait RecoverWithDefault extends RecoveryPolicy { /** * When the command fails, it logs the failure and fails the whole operation. */ -private[redis] class LogAndFailPolicy @Inject() () extends FailThrough with DetailedReports +private[redis] class LogAndFailPolicy @Inject() extends FailThrough with DetailedReports /** * When the command fails, it logs the failure and returns default value * to prevent application failure. The returned value is neutral to the * operation and it should behave like there is no cache */ -private[redis] class LogAndDefaultPolicy @Inject() () extends RecoverWithDefault with DetailedReports +private[redis] class LogAndDefaultPolicy @Inject() extends RecoverWithDefault with DetailedReports /** * When the command fails, it logs the failure and returns default value @@ -132,7 +133,7 @@ private[redis] class LogAndDefaultPolicy @Inject() () extends RecoverWithDefault * LogCondensed produces condensed messages without a stacktrace to avoid * extensive logs */ -private[redis] class LogCondensedAndDefaultPolicy @Inject() () extends RecoverWithDefault with CondensedReports +private[redis] class LogCondensedAndDefaultPolicy @Inject() extends RecoverWithDefault with CondensedReports /** * When the command fails, it logs the failure and fails the whole operation. @@ -140,7 +141,7 @@ private[redis] class LogCondensedAndDefaultPolicy @Inject() () extends RecoverWi * LogCondensed produces condensed messages without a stacktrace to avoid * extensive logs */ -private[redis] class LogCondensedAndFailPolicy @Inject() () extends FailThrough with CondensedReports +private[redis] class LogCondensedAndFailPolicy @Inject() extends FailThrough with CondensedReports /** * This resolver represents an abstraction over translation @@ -154,7 +155,7 @@ trait RecoveryPolicyResolver { // $COVERAGE-OFF$ class RecoveryPolicyResolverImpl extends RecoveryPolicyResolver { - val resolve: PartialFunction[String, RecoveryPolicy] = { + override val resolve: PartialFunction[String, RecoveryPolicy] = { case "log-and-fail" => new LogAndFailPolicy case "log-and-default" => new LogAndDefaultPolicy case "log-condensed-and-fail" => new LogCondensedAndFailPolicy @@ -164,7 +165,7 @@ class RecoveryPolicyResolverImpl extends RecoveryPolicyResolver { object RecoveryPolicyResolver { - def bindings = Seq( + def bindings: Seq[Binding[_]] = Seq( bind[RecoveryPolicy].qualifiedWith("log-and-fail").to[LogAndFailPolicy], bind[RecoveryPolicy].qualifiedWith("log-and-default").to[LogAndDefaultPolicy], bind[RecoveryPolicy].qualifiedWith("log-condensed-and-fail").to[LogCondensedAndFailPolicy], @@ -177,7 +178,7 @@ object RecoveryPolicyResolver { /** resolves a policies with guice enabled */ class RecoveryPolicyResolverGuice @Inject() (injector: Injector) extends RecoveryPolicyResolver { - def resolve = { + override def resolve: PartialFunction[String, RecoveryPolicy] = { case name => injector instanceOf bind[RecoveryPolicy].qualifiedWith(name) } } diff --git a/src/main/scala/play/api/cache/redis/RedisCacheModule.scala b/src/main/scala/play/api/cache/redis/RedisCacheModule.scala index a827597d..0058f98c 100644 --- a/src/main/scala/play/api/cache/redis/RedisCacheModule.scala +++ b/src/main/scala/play/api/cache/redis/RedisCacheModule.scala @@ -1,10 +1,8 @@ package play.api.cache.redis import javax.inject._ - import scala.language.implicitConversions import scala.reflect.ClassTag - import play.api.inject._ import play.cache._ @@ -15,7 +13,7 @@ import play.cache._ @Singleton class RedisCacheModule extends Module { - override def bindings(environment: play.api.Environment, config: play.api.Configuration) = { + override def bindings(environment: play.api.Environment, config: play.api.Configuration): Seq[Binding[_]] = { def bindDefault = config.get[Boolean]("play.cache.redis.bind-default") // read the config and get the configuration of the redis @@ -44,7 +42,7 @@ trait ProviderImplicits { } private[redis] class RichBindingKey[T](val key: BindingKey[T]) extends AnyVal { - @inline def named(name: String) = key.qualifiedWith(new NamedCacheImpl(name)) + @inline def named(name: String): BindingKey[T] = key.qualifiedWith(new NamedCacheImpl(name)) } trait GuiceProviderImplicits extends ProviderImplicits { @@ -61,8 +59,8 @@ object GuiceProvider extends ProviderImplicits { private def namedBinding[T: ClassTag](f: impl.RedisCaches => T) = new QualifiedBindingKey(bind[T], f) - def bindings(instance: RedisInstanceProvider) = { - implicit val name = new CacheName(instance.name) + def bindings(instance: RedisInstanceProvider): Seq[Binding[_]] = { + implicit val name: CacheName = new CacheName(instance.name) Seq[Binding[_]]( // bind implementation of all caches @@ -80,9 +78,9 @@ object GuiceProvider extends ProviderImplicits { ).map(_.toBindings) } - def defaults(instance: RedisInstanceProvider) = { - implicit val name = new CacheName(instance.name) - @inline def defaultBinding[T: ClassTag](implicit cacheName: CacheName): Binding[T] = bind[T].to(bind[T].named(name)) + def defaults(instance: RedisInstanceProvider): Seq[Binding[_]] = { + implicit val name: CacheName = new CacheName(instance.name) + @inline def defaultBinding[T: ClassTag]: Binding[T] = bind[T].to(bind[T].named(name)) Seq( // bind implementation of all caches @@ -105,7 +103,7 @@ object GuiceProvider extends ProviderImplicits { class GuiceRedisCacheProvider(instance: RedisInstanceProvider) extends Provider[RedisCaches] with GuiceProviderImplicits { @Inject() var injector: Injector = _ - lazy val get: RedisCaches = new impl.RedisCachesProvider( + override lazy val get: RedisCaches = new impl.RedisCachesProvider( instance = instance.resolved(bind[configuration.RedisInstanceResolver]), serializer = bind[connector.AkkaSerializer], environment = bind[play.api.Environment] @@ -118,17 +116,18 @@ class GuiceRedisCacheProvider(instance: RedisInstanceProvider) extends Provider[ class NamedCacheInstanceProvider[T](f: RedisCaches => T)(implicit name: CacheName) extends Provider[T] with GuiceProviderImplicits { @Inject() var injector: Injector = _ - lazy val get = f(bind[RedisCaches].named(name)) + override lazy val get: T = f(bind[RedisCaches].named(name)) } class CacheName(val name: String) extends AnyVal + object CacheName { implicit def name2string(name: CacheName): String = name.name } @Singleton class GuiceRedisInstanceResolver @Inject() (val injector: Injector) extends configuration.RedisInstanceResolver with GuiceProviderImplicits { - def resolve = { + override def resolve: PartialFunction[String, RedisInstance] = { case name => bind[RedisInstance].named(name) } } diff --git a/src/main/scala/play/api/cache/redis/RedisCollection.scala b/src/main/scala/play/api/cache/redis/RedisCollection.scala index da466b15..8b8bfa7a 100644 --- a/src/main/scala/play/api/cache/redis/RedisCollection.scala +++ b/src/main/scala/play/api/cache/redis/RedisCollection.scala @@ -1,7 +1,5 @@ package play.api.cache.redis -import scala.language.higherKinds - private[redis] trait RedisCollection[Collection, Result[_]] { type This >: this.type diff --git a/src/main/scala/play/api/cache/redis/RedisList.scala b/src/main/scala/play/api/cache/redis/RedisList.scala index 220e17c6..187cf65c 100644 --- a/src/main/scala/play/api/cache/redis/RedisList.scala +++ b/src/main/scala/play/api/cache/redis/RedisList.scala @@ -1,7 +1,5 @@ package play.api.cache.redis -import scala.language.higherKinds - /** * Redis Lists are simply lists of strings, sorted by insertion order. * It is possible to add elements to a Redis List pushing new elements diff --git a/src/main/scala/play/api/cache/redis/RedisMap.scala b/src/main/scala/play/api/cache/redis/RedisMap.scala index 8a281e52..ed281232 100644 --- a/src/main/scala/play/api/cache/redis/RedisMap.scala +++ b/src/main/scala/play/api/cache/redis/RedisMap.scala @@ -1,7 +1,5 @@ package play.api.cache.redis -import scala.language.higherKinds - /** * Redis Hashes are simply hash maps with strings as keys. It is possible to add * elements to a Redis Hashes by adding new elements into the collection. diff --git a/src/main/scala/play/api/cache/redis/RedisSet.scala b/src/main/scala/play/api/cache/redis/RedisSet.scala index 7bf29ce2..80a846a0 100644 --- a/src/main/scala/play/api/cache/redis/RedisSet.scala +++ b/src/main/scala/play/api/cache/redis/RedisSet.scala @@ -1,7 +1,5 @@ package play.api.cache.redis -import scala.language.higherKinds - /** * Redis Sets are simply unsorted sets of objects. It is possible to add * elements to a Redis Set by adding new elements into the collection. diff --git a/src/main/scala/play/api/cache/redis/RedisSortedSet.scala b/src/main/scala/play/api/cache/redis/RedisSortedSet.scala index 95e12e34..8c9df70e 100644 --- a/src/main/scala/play/api/cache/redis/RedisSortedSet.scala +++ b/src/main/scala/play/api/cache/redis/RedisSortedSet.scala @@ -1,7 +1,6 @@ package play.api.cache.redis import scala.collection.immutable.TreeSet -import scala.language.higherKinds trait RedisSortedSet[Elem, Result[_]] extends RedisCollection[TreeSet[Elem], Result] { override type This = RedisSortedSet[Elem, Result] diff --git a/src/main/scala/play/api/cache/redis/configuration/Equals.scala b/src/main/scala/play/api/cache/redis/configuration/Equals.scala index 22a0e196..24151e1a 100644 --- a/src/main/scala/play/api/cache/redis/configuration/Equals.scala +++ b/src/main/scala/play/api/cache/redis/configuration/Equals.scala @@ -1,11 +1,13 @@ package play.api.cache.redis.configuration +import play.api.cache.redis._ + private[configuration] object Equals { // $COVERAGE-OFF$ @inline def check[T](a: T, b: T)(property: (T => Any)*): Boolean = { - property.forall(property => property(a) == property(b)) + property.forall(property => property(a) === property(b)) } // $COVERAGE-ON$ } diff --git a/src/main/scala/play/api/cache/redis/configuration/HostnameResolver.scala b/src/main/scala/play/api/cache/redis/configuration/HostnameResolver.scala index eb69f46d..aba0bdc3 100644 --- a/src/main/scala/play/api/cache/redis/configuration/HostnameResolver.scala +++ b/src/main/scala/play/api/cache/redis/configuration/HostnameResolver.scala @@ -4,7 +4,7 @@ import java.net.InetAddress object HostnameResolver { - implicit class HostNameResolver(val name: String) extends AnyVal { - def resolvedIpAddress = InetAddress.getByName(name).getHostAddress + implicit class HostNameResolver(private val name: String) extends AnyVal { + def resolvedIpAddress: String = InetAddress.getByName(name).getHostAddress } } diff --git a/src/main/scala/play/api/cache/redis/configuration/RedisConfigLoader.scala b/src/main/scala/play/api/cache/redis/configuration/RedisConfigLoader.scala index 9bc0aaf9..6ac8e738 100644 --- a/src/main/scala/play/api/cache/redis/configuration/RedisConfigLoader.scala +++ b/src/main/scala/play/api/cache/redis/configuration/RedisConfigLoader.scala @@ -1,6 +1,7 @@ package play.api.cache.redis.configuration import com.typesafe.config.Config +import play.api.cache.redis._ /** * Config loader helper, provides some useful methods @@ -19,7 +20,7 @@ private[configuration] object RedisConfigLoader { } implicit class ConfigPath(val path: String) extends AnyVal { - def /(suffix: String): String = if (path == "") suffix else s"$path.$suffix" + def /(suffix: String): String = if (path === "") suffix else s"$path.$suffix" } def required(path: String) = throw new IllegalStateException(s"Configuration key '$path' is missing.") diff --git a/src/main/scala/play/api/cache/redis/configuration/RedisHost.scala b/src/main/scala/play/api/cache/redis/configuration/RedisHost.scala index fafbc716..30183859 100644 --- a/src/main/scala/play/api/cache/redis/configuration/RedisHost.scala +++ b/src/main/scala/play/api/cache/redis/configuration/RedisHost.scala @@ -20,9 +20,9 @@ trait RedisHost { def password: Option[String] // $COVERAGE-OFF$ /** trait-specific equals */ - override def equals(obj: scala.Any) = equalsAsHost(obj) + override def equals(obj: scala.Any): Boolean = equalsAsHost(obj) /** trait-specific equals, invokable from children */ - protected def equalsAsHost(obj: scala.Any) = obj match { + protected def equalsAsHost(obj: scala.Any): Boolean = obj match { case that: RedisHost => Equals.check(this, that)(_.host, _.port, _.username, _.database, _.password) case _ => false } @@ -40,7 +40,7 @@ object RedisHost extends ConfigLoader[RedisHost] { import RedisConfigLoader._ /** expected format of the environment variable */ - private val ConnectionString = "redis://((?[^:]+):(?[^@]+)@)?(?[^:]+):(?[0-9]+)".r("auth", "username", "password", "host", "port") + private val ConnectionString = "redis://((?[^:]+):(?[^@]+)@)?(?[^:]+):(?[0-9]+)".r def load(config: Config, path: String): RedisHost = apply( host = config.getString(path / "host"), diff --git a/src/main/scala/play/api/cache/redis/configuration/RedisInstance.scala b/src/main/scala/play/api/cache/redis/configuration/RedisInstance.scala index adab2ba5..ba28889c 100644 --- a/src/main/scala/play/api/cache/redis/configuration/RedisInstance.scala +++ b/src/main/scala/play/api/cache/redis/configuration/RedisInstance.scala @@ -1,5 +1,7 @@ package play.api.cache.redis.configuration +import play.api.cache.redis._ + /** * Abstraction over clusters and standalone instances. This trait * encapsulates a common settings and simplifies pattern matching. @@ -9,10 +11,10 @@ sealed trait RedisInstance extends RedisSettings { def name: String // $COVERAGE-OFF$ /** trait-specific equals */ - override def equals(obj: scala.Any) = equalsAsInstance(obj) + override def equals(obj: scala.Any): Boolean = equalsAsInstance(obj) /** trait-specific equals, invokable from children */ - protected def equalsAsInstance(obj: scala.Any) = obj match { - case that: RedisInstance => this.name == that.name && equalsAsSettings(that) + protected def equalsAsInstance(obj: scala.Any): Boolean = obj match { + case that: RedisInstance => this.name === that.name && equalsAsSettings(that) case _ => false } // $COVERAGE-ON$ @@ -22,13 +24,13 @@ sealed trait RedisInstance extends RedisSettings { * Type of Redis Instance - a cluster. It encapsulates common settings of the instance * and the list of cluster nodes. */ -trait RedisCluster extends RedisInstance { +sealed trait RedisCluster extends RedisInstance { /** nodes definition when cluster is defined */ def nodes: List[RedisHost] // $COVERAGE-OFF$ /** trait-specific equals */ - override def equals(obj: scala.Any) = obj match { - case that: RedisCluster => equalsAsInstance(that) && this.nodes == that.nodes + override def equals(obj: scala.Any): Boolean = obj match { + case that: RedisCluster => equalsAsInstance(that) && this.nodes === that.nodes case _ => false } /** to string */ @@ -38,15 +40,15 @@ trait RedisCluster extends RedisInstance { object RedisCluster { - def apply(name: String, nodes: List[RedisHost], settings: RedisSettings) = + def apply(name: String, nodes: List[RedisHost], settings: RedisSettings): RedisCluster = create(name, nodes, settings) @inline - private def create(_name: String, _nodes: List[RedisHost], _settings: RedisSettings) = + private def create(_name: String, _nodes: List[RedisHost], _settings: RedisSettings): RedisCluster = new RedisCluster with RedisDelegatingSettings { - val name = _name - val nodes = _nodes - val settings = _settings + override val name: String = _name + override val nodes: List[RedisHost] = _nodes + override val settings: RedisSettings = _settings } } @@ -54,15 +56,15 @@ object RedisCluster { * A type of Redis Instance - a standalone instance. It encapsulates * common settings of the instance and provides a connection settings. */ -trait RedisStandalone extends RedisInstance with RedisHost { +sealed trait RedisStandalone extends RedisInstance with RedisHost { // $COVERAGE-OFF$ /** trait-specific equals */ - override def equals(obj: scala.Any) = obj match { + override def equals(obj: scala.Any): Boolean = obj match { case that: RedisStandalone => equalsAsInstance(that) && equalsAsHost(that) case _ => false } /** to string */ - override def toString = database match { + override def toString: String = database match { case Some(database) => s"Standalone($name@$host:$port?db=$database)" case None => s"Standalone($name@$host:$port)" } @@ -75,11 +77,11 @@ object RedisStandalone { create(name, host, settings) @inline - private def create(_name: String, _host: RedisHost, _settings: RedisSettings) = + private def create(_name: String, _host: RedisHost, _settings: RedisSettings): RedisStandalone = new RedisStandalone with RedisDelegatingSettings with RedisDelegatingHost { - val name = _name - val innerHost = _host - val settings = _settings + override val name: String = _name + override val innerHost: RedisHost = _host + override val settings: RedisSettings = _settings } } @@ -87,7 +89,7 @@ object RedisStandalone { * Type of Redis Instance - a sentinel. It encapsulates common settings of * the instance, name of the master group, and the list of sentinel nodes. */ -trait RedisSentinel extends RedisInstance { +sealed trait RedisSentinel extends RedisInstance { def sentinels: List[RedisHost] def masterGroup: String @@ -96,7 +98,7 @@ trait RedisSentinel extends RedisInstance { def database: Option[Int] override def equals(obj: scala.Any): Boolean = obj match { - case that: RedisSentinel => equalsAsInstance(that) && this.sentinels == that.sentinels + case that: RedisSentinel => equalsAsInstance(that) && this.sentinels === that.sentinels } /** to string */ override def toString = s"Sentinel[${sentinels mkString ","}]" @@ -112,12 +114,12 @@ object RedisSentinel { username: Option[String] = None, password: Option[String] = None, database: Option[Int] = None - ): RedisSentinel with RedisDelegatingSettings = + ): RedisSentinel = create(name, masterGroup, username, password, database, sentinels, settings) @inline private def create(_name: String, _masterGroup: String, _username: Option[String], _password: Option[String], _database: Option[Int], - _sentinels: List[RedisHost], _settings: RedisSettings) = + _sentinels: List[RedisHost], _settings: RedisSettings): RedisSentinel = new RedisSentinel with RedisDelegatingSettings { override val name: String = _name override val masterGroup: String = _masterGroup diff --git a/src/main/scala/play/api/cache/redis/configuration/RedisInstanceManager.scala b/src/main/scala/play/api/cache/redis/configuration/RedisInstanceManager.scala index d1c417da..abf8a6e5 100644 --- a/src/main/scala/play/api/cache/redis/configuration/RedisInstanceManager.scala +++ b/src/main/scala/play/api/cache/redis/configuration/RedisInstanceManager.scala @@ -1,8 +1,7 @@ package play.api.cache.redis.configuration import play.api.ConfigLoader -import play.api.cache.redis.JavaCompatibilityBase - +import play.api.cache.redis._ import com.typesafe.config.Config /** @@ -34,7 +33,7 @@ trait RedisInstanceManager extends Iterable[RedisInstanceProvider] { def iterator: Iterator[RedisInstanceProvider] = caches.view.flatMap(instanceOfOption).iterator // $COVERAGE-OFF$ - override def equals(obj: scala.Any) = obj match { + override def equals(obj: scala.Any): Boolean = obj match { case that: RedisInstanceManager => Equals.check(this, that)(_.caches, _.defaultInstance, _.toSet) case _ => false } @@ -44,9 +43,9 @@ trait RedisInstanceManager extends Iterable[RedisInstanceProvider] { private[redis] object RedisInstanceManager extends ConfigLoader[RedisInstanceManager] { import RedisConfigLoader._ - def load(config: Config, path: String) = { + override def load(config: Config, path: String): RedisInstanceManager = { // read default settings - implicit val defaults = RedisSettings.load(config, path) + implicit val defaults: RedisSettings = RedisSettings.load(config, path) // check if the list of instances is defined or whether to use // a fallback definition directly under the configuration root val hasInstances = config.hasPath(path / "instances") @@ -63,17 +62,19 @@ class RedisInstanceManagerImpl(config: Config, path: String)(implicit defaults: import RedisConfigLoader._ /** names of all known redis caches */ - def caches: Set[String] = config.getObject(path / "instances").keySet.asScala.toSet + override def caches: Set[String] = config.getObject(path / "instances").keySet.asScala.toSet - def defaultCacheName = config.getString(path / "default-cache") + def defaultCacheName: String = config.getString(path / "default-cache") - def defaultInstance = instanceOfOption(defaultCacheName) getOrElse { - throw new IllegalArgumentException(s"Default cache '$defaultCacheName' is not defined.") - } + override def defaultInstance: RedisInstanceProvider = + instanceOfOption(defaultCacheName) getOrElse { + throw new IllegalArgumentException(s"Default cache '$defaultCacheName' is not defined.") + } /** returns a configuration of a single named redis instance */ - def instanceOfOption(name: String): Option[RedisInstanceProvider] = - if (config hasPath (path / "instances" / name)) Some(RedisInstanceProvider.load(config, path / "instances" / name, name)) else None + override def instanceOfOption(name: String): Option[RedisInstanceProvider] = + if (config hasPath (path / "instances" / name)) Some(RedisInstanceProvider.load(config, path / "instances" / name, name)) + else None } /** @@ -85,11 +86,11 @@ class RedisInstanceManagerFallback(config: Config, path: String)(implicit defaul private val name = config.getString(path / "default-cache") /** names of all known redis caches */ - def caches: Set[String] = Set(name) + override def caches: Set[String] = Set(name) - def defaultInstance = RedisInstanceProvider.load(config, path, name) + override def defaultInstance: RedisInstanceProvider = RedisInstanceProvider.load(config, path, name) /** returns a configuration of a single named redis instance */ - def instanceOfOption(name: String): Option[RedisInstanceProvider] = - if (name == this.name) Some(defaultInstance) else None + override def instanceOfOption(name: String): Option[RedisInstanceProvider] = + if (name === this.name) Some(defaultInstance) else None } diff --git a/src/main/scala/play/api/cache/redis/configuration/RedisInstanceProvider.scala b/src/main/scala/play/api/cache/redis/configuration/RedisInstanceProvider.scala index 7fa40f20..c0c78e4e 100644 --- a/src/main/scala/play/api/cache/redis/configuration/RedisInstanceProvider.scala +++ b/src/main/scala/play/api/cache/redis/configuration/RedisInstanceProvider.scala @@ -15,32 +15,32 @@ sealed trait RedisInstanceProvider extends Any { def resolved(implicit resolver: RedisInstanceResolver): RedisInstance } -class ResolvedRedisInstance(val instance: RedisInstance) extends RedisInstanceProvider { - def name: String = instance.name - def resolved(implicit resolver: RedisInstanceResolver) = instance +final class ResolvedRedisInstance(val instance: RedisInstance) extends RedisInstanceProvider { + override def name: String = instance.name + override def resolved(implicit resolver: RedisInstanceResolver): RedisInstance = instance // $COVERAGE-OFF$ - override def equals(obj: scala.Any) = obj match { - case that: ResolvedRedisInstance => this.name == that.name && this.instance == that.instance + override def equals(obj: scala.Any): Boolean = obj match { + case that: ResolvedRedisInstance => this.name === that.name && this.instance === that.instance case _ => false } - override def hashCode() = name.hashCode + override def hashCode(): Int = name.hashCode override def toString = s"ResolvedRedisInstance($name@$instance)" // $COVERAGE-ON$ } -class UnresolvedRedisInstance(val name: String) extends RedisInstanceProvider { - def resolved(implicit resolver: RedisInstanceResolver) = resolver resolve name +final class UnresolvedRedisInstance(val name: String) extends RedisInstanceProvider { + override def resolved(implicit resolver: RedisInstanceResolver): RedisInstance = resolver resolve name // $COVERAGE-OFF$ - override def equals(obj: scala.Any) = obj match { - case that: UnresolvedRedisInstance => this.name == that.name + override def equals(obj: scala.Any): Boolean = obj match { + case that: UnresolvedRedisInstance => this.name === that.name case _ => false } - override def hashCode() = name.hashCode + override def hashCode(): Int = name.hashCode override def toString = s"UnresolvedRedisInstance($name)" // $COVERAGE-ON$ @@ -49,7 +49,7 @@ class UnresolvedRedisInstance(val name: String) extends RedisInstanceProvider { private[configuration] object RedisInstanceProvider extends RedisConfigInstanceLoader[RedisInstanceProvider] { import RedisConfigLoader._ - def load(config: Config, path: String, name: String)(implicit defaults: RedisSettings) = { + override def load(config: Config, path: String, name: String)(implicit defaults: RedisSettings): RedisInstanceProvider = { config.getOption(path / "source", _.getString) getOrElse defaults.source match { // required static configuration of the standalone instance using application.conf case "standalone" => RedisInstanceStandalone @@ -79,13 +79,14 @@ private[configuration] object RedisInstanceProvider extends RedisConfigInstanceL * Statically configured single standalone redis instance */ private[configuration] object RedisInstanceStandalone extends RedisConfigInstanceLoader[RedisInstanceProvider] { - def load(config: Config, path: String, instanceName: String)(implicit defaults: RedisSettings) = new ResolvedRedisInstance( - RedisStandalone.apply( - name = instanceName, - host = RedisHost.load(config, path), - settings = RedisSettings.withFallback(defaults).load(config, path) + override def load(config: Config, path: String, instanceName: String)(implicit defaults: RedisSettings) = + new ResolvedRedisInstance( + RedisStandalone.apply( + name = instanceName, + host = RedisHost.load(config, path), + settings = RedisSettings.withFallback(defaults).load(config, path) + ) ) - ) } /** @@ -95,13 +96,14 @@ private[configuration] object RedisInstanceCluster extends RedisConfigInstanceLo import JavaCompatibilityBase._ import RedisConfigLoader._ - def load(config: Config, path: String, instanceName: String)(implicit defaults: RedisSettings) = new ResolvedRedisInstance( - RedisCluster.apply( - name = instanceName, - nodes = config.getConfigList(path / "cluster").asScala.map(config => RedisHost.load(config)).toList, - settings = RedisSettings.withFallback(defaults).load(config, path) + override def load(config: Config, path: String, instanceName: String)(implicit defaults: RedisSettings) = + new ResolvedRedisInstance( + RedisCluster.apply( + name = instanceName, + nodes = config.getConfigList(path / "cluster").asScala.map(config => RedisHost.load(config)).toList, + settings = RedisSettings.withFallback(defaults).load(config, path) + ) ) - ) } /** @@ -110,13 +112,14 @@ private[configuration] object RedisInstanceCluster extends RedisConfigInstanceLo private[configuration] object RedisInstanceAwsCluster extends RedisConfigInstanceLoader[RedisInstanceProvider] { import RedisConfigLoader._ - def load(config: Config, path: String, instanceName: String)(implicit defaults: RedisSettings) = new ResolvedRedisInstance( - RedisCluster.apply( - name = instanceName, - nodes = InetAddress.getAllByName(config.getString(path / "host")).map(address => RedisHost(address.getHostAddress, 6379)).toList, - settings = RedisSettings.withFallback(defaults).load(config, path) + override def load(config: Config, path: String, instanceName: String)(implicit defaults: RedisSettings) = + new ResolvedRedisInstance( + RedisCluster.apply( + name = instanceName, + nodes = InetAddress.getAllByName(config.getString(path / "host")).map(address => RedisHost(address.getHostAddress, 6379)).toList, + settings = RedisSettings.withFallback(defaults).load(config, path) + ) ) - ) } /** @@ -126,13 +129,14 @@ private[configuration] object RedisInstanceAwsCluster extends RedisConfigInstanc private[configuration] object RedisInstanceEnvironmental extends RedisConfigInstanceLoader[RedisInstanceProvider] { import RedisConfigLoader._ - def load(config: Config, path: String, instanceName: String)(implicit defaults: RedisSettings) = new ResolvedRedisInstance( - RedisStandalone.apply( - name = instanceName, - host = RedisHost.fromConnectionString(config getString path./("connection-string")), - settings = RedisSettings.withFallback(defaults).load(config, path) + override def load(config: Config, path: String, instanceName: String)(implicit defaults: RedisSettings) = + new ResolvedRedisInstance( + RedisStandalone.apply( + name = instanceName, + host = RedisHost.fromConnectionString(config getString path./("connection-string")), + settings = RedisSettings.withFallback(defaults).load(config, path) + ) ) - ) } /** @@ -142,23 +146,25 @@ private[configuration] object RedisInstanceSentinel extends RedisConfigInstanceL import JavaCompatibilityBase._ import RedisConfigLoader._ - def load(config: Config, path: String, instanceName: String)(implicit defaults: RedisSettings) = new ResolvedRedisInstance( - RedisSentinel.apply( - name = instanceName, - sentinels = config.getConfigList(path / "sentinels").asScala.map(config => RedisHost.load(config)).toList, - masterGroup = config.getString(path / "master-group"), - password = config.getOption(path / "password", _.getString), - database = config.getOption(path / "database", _.getInt), - settings = RedisSettings.withFallback(defaults).load(config, path) + override def load(config: Config, path: String, instanceName: String)(implicit defaults: RedisSettings) = + new ResolvedRedisInstance( + RedisSentinel.apply( + name = instanceName, + sentinels = config.getConfigList(path / "sentinels").asScala.map(config => RedisHost.load(config)).toList, + masterGroup = config.getString(path / "master-group"), + password = config.getOption(path / "password", _.getString), + database = config.getOption(path / "database", _.getInt), + settings = RedisSettings.withFallback(defaults).load(config, path) + ) ) - ) } /** * This binder indicates that the user provides his own configuration of this named cache. */ private[configuration] object RedisInstanceCustom extends RedisConfigInstanceLoader[RedisInstanceProvider] { - def load(config: Config, path: String, instanceName: String)(implicit defaults: RedisSettings) = new UnresolvedRedisInstance( - name = instanceName - ) + override def load(config: Config, path: String, instanceName: String)(implicit defaults: RedisSettings) = + new UnresolvedRedisInstance( + name = instanceName + ) } diff --git a/src/main/scala/play/api/cache/redis/configuration/RedisSettings.scala b/src/main/scala/play/api/cache/redis/configuration/RedisSettings.scala index d8e1f4fe..10276d0d 100644 --- a/src/main/scala/play/api/cache/redis/configuration/RedisSettings.scala +++ b/src/main/scala/play/api/cache/redis/configuration/RedisSettings.scala @@ -23,9 +23,9 @@ trait RedisSettings { def prefix: Option[String] // $COVERAGE-OFF$ /** trait-specific equals */ - override def equals(obj: scala.Any) = equalsAsSettings(obj) + override def equals(obj: scala.Any): Boolean = equalsAsSettings(obj) /** trait-specific equals, invokable from children */ - protected def equalsAsSettings(obj: scala.Any) = obj match { + protected def equalsAsSettings(obj: scala.Any): Boolean = obj match { case that: RedisSettings => Equals.check(this, that)(_.invocationContext, _.invocationPolicy, _.timeout, _.recovery, _.source, _.prefix) case _ => false } @@ -35,7 +35,7 @@ trait RedisSettings { object RedisSettings extends ConfigLoader[RedisSettings] { import RedisConfigLoader._ - def load(config: Config, path: String) = apply( + override def load(config: Config, path: String): RedisSettings = apply( dispatcher = loadInvocationContext(config, path).get, invocationPolicy = loadInvocationPolicy(config, path).get, recovery = loadRecovery(config, path).get, @@ -44,8 +44,8 @@ object RedisSettings extends ConfigLoader[RedisSettings] { prefix = loadPrefix(config, path) ) - def withFallback(fallback: RedisSettings) = new ConfigLoader[RedisSettings] { - def load(config: Config, path: String) = apply( + def withFallback(fallback: RedisSettings): ConfigLoader[RedisSettings] = + (config: Config, path: String) => apply( dispatcher = loadInvocationContext(config, path) getOrElse fallback.invocationContext, invocationPolicy = loadInvocationPolicy(config, path) getOrElse fallback.invocationPolicy, recovery = loadRecovery(config, path) getOrElse fallback.recovery, @@ -53,19 +53,18 @@ object RedisSettings extends ConfigLoader[RedisSettings] { source = loadSource(config, path) getOrElse fallback.source, prefix = loadPrefix(config, path) orElse fallback.prefix ) - } def apply(dispatcher: String, invocationPolicy: String, timeout: RedisTimeouts, recovery: String, source: String, prefix: Option[String] = None): RedisSettings = create(dispatcher, invocationPolicy, prefix, timeout, recovery, source) @inline private def create(_dispatcher: String, _invocation: String, _prefix: Option[String], _timeout: RedisTimeouts, _recovery: String, _source: String) = new RedisSettings { - val invocationContext = _dispatcher - val invocationPolicy = _invocation - val prefix = _prefix - val recovery = _recovery - val timeout = _timeout - val source = _source + override val invocationContext: String = _dispatcher + override val invocationPolicy: String = _invocation + override val prefix: Option[String] = _prefix + override val recovery: String = _recovery + override val timeout: RedisTimeouts = _timeout + override val source: String = _source } private def loadInvocationContext(config: Config, path: String): Option[String] = @@ -92,10 +91,10 @@ object RedisSettings extends ConfigLoader[RedisSettings] { */ trait RedisDelegatingSettings extends RedisSettings { def settings: RedisSettings - def prefix = settings.prefix - def source = settings.source - def timeout = settings.timeout - def recovery = settings.recovery - def invocationContext = settings.invocationContext - def invocationPolicy = settings.invocationPolicy + override def prefix: Option[String] = settings.prefix + override def source: String = settings.source + override def timeout: RedisTimeouts = settings.timeout + override def recovery: String = settings.recovery + override def invocationContext: String = settings.invocationContext + override def invocationPolicy: String = settings.invocationPolicy } diff --git a/src/main/scala/play/api/cache/redis/configuration/RedisTimeouts.scala b/src/main/scala/play/api/cache/redis/configuration/RedisTimeouts.scala index 3a72bccd..37dd758b 100644 --- a/src/main/scala/play/api/cache/redis/configuration/RedisTimeouts.scala +++ b/src/main/scala/play/api/cache/redis/configuration/RedisTimeouts.scala @@ -1,12 +1,9 @@ package play.api.cache.redis.configuration import java.util.concurrent.TimeUnit - import scala.concurrent.duration.FiniteDuration - -import play.api.Logger - import com.typesafe.config.Config +import play.api.cache.redis._ /** * Aggregates the timeout configuration settings @@ -23,7 +20,7 @@ trait RedisTimeouts { def connection: Option[FiniteDuration] } -case class RedisTimeoutsImpl( +final case class RedisTimeoutsImpl( /** sync timeout applies in sync API and indicates how long to wait before the future is resolved */ sync: FiniteDuration, @@ -36,8 +33,8 @@ case class RedisTimeoutsImpl( ) extends RedisTimeouts { // $COVERAGE-OFF$ - override def equals(obj: scala.Any) = obj match { - case that: RedisTimeouts => this.sync == that.sync && this.redis == that.redis && this.connection == that.connection + override def equals(obj: scala.Any): Boolean = obj match { + case that: RedisTimeouts => this.sync === that.sync && this.redis === that.redis && this.connection === that.connection case _ => false } // $COVERAGE-ON$ @@ -47,16 +44,16 @@ object RedisTimeouts { import RedisConfigLoader._ def requiredDefault: RedisTimeouts = new RedisTimeouts { - def sync = required("sync-timeout") - def redis = None - def connection = None + override def sync: FiniteDuration = required("sync-timeout") + override def redis: Option[FiniteDuration] = None + override def connection: Option[FiniteDuration] = None } @inline def apply(sync: FiniteDuration, redis: Option[FiniteDuration] = None, connection: Option[FiniteDuration] = None): RedisTimeouts = RedisTimeoutsImpl(sync, redis, connection) - def load(config: Config, path: String)(default: RedisTimeouts) = RedisTimeouts( + def load(config: Config, path: String)(default: RedisTimeouts): RedisTimeouts = RedisTimeouts( sync = loadSyncTimeout(config, path) getOrElse default.sync, redis = loadRedisTimeout(config, path) getOrElse default.redis, connection = loadConnectionTimeout(config, path) getOrElse default.connection diff --git a/src/main/scala/play/api/cache/redis/connector/AkkaSerializer.scala b/src/main/scala/play/api/cache/redis/connector/AkkaSerializer.scala index 966cf177..4d30bf25 100644 --- a/src/main/scala/play/api/cache/redis/connector/AkkaSerializer.scala +++ b/src/main/scala/play/api/cache/redis/connector/AkkaSerializer.scala @@ -1,12 +1,10 @@ package play.api.cache.redis.connector import java.util.Base64 - import javax.inject._ import scala.language.implicitConversions import scala.reflect.ClassTag import scala.util._ - import play.api.cache.redis._ import akka.actor.ActorSystem import akka.serialization._ @@ -62,19 +60,19 @@ private[connector] class AkkaEncoder(serializer: Serialization) { } /** determines whether the given value is a primitive */ - protected def isPrimitive(candidate: Any): Boolean = + private def isPrimitive(candidate: Any): Boolean = candidate.getClass.isPrimitive || Primitives.primitives.contains(candidate.getClass) /** unsafe method converting AnyRef into bytes */ - protected def anyRefToBinary(anyRef: AnyRef): Array[Byte] = + private def anyRefToBinary(anyRef: AnyRef): Array[Byte] = serializer.findSerializerFor(anyRef).toBinary(anyRef) /** Produces BASE64 encoded string from an array of bytes */ - protected def binaryToString(bytes: Array[Byte]): String = + private def binaryToString(bytes: Array[Byte]): String = Base64.getEncoder.encodeToString(bytes) /** unsafe method converting AnyRef into BASE64 string */ - protected def anyRefToString(value: AnyRef): String = + private def anyRefToString(value: AnyRef): String = (anyRefToBinary _ andThen binaryToString)(value) } @@ -97,32 +95,32 @@ private[connector] class AkkaDecoder(serializer: Serialization) { untypedDecode[T](value).asInstanceOf[T] /** unsafe method decoding a string into an object. It directly throws exceptions. It does not perform type cast */ - protected def untypedDecode[T](value: String)(implicit tag: ClassTag[T]): Any = value match { + private def untypedDecode[T](value: String)(implicit tag: ClassTag[T]): Any = value match { // AnyVal is not supported by default, have to be implemented manually case "" => null - case _ if tag == Nothing => throw new IllegalArgumentException("Type Nothing is not supported. You have probably forgot to specify expected data type.") - case string if tag == Java.String => string - case boolean if tag == Java.Boolean || tag == Scala.Boolean => boolean.toBoolean - case byte if tag == Java.Byte || tag == Scala.Byte => byte.toByte - case char if tag == Java.Char || tag == Scala.Char => char.charAt(0) - case short if tag == Java.Short || tag == Scala.Short => short.toShort - case int if tag == Java.Int || tag == Scala.Int => int.toInt - case long if tag == Java.Long || tag == Scala.Long => long.toLong - case float if tag == Java.Float || tag == Scala.Float => float.toFloat - case double if tag == Java.Double || tag == Scala.Double => double.toDouble + case _ if tag =~= Nothing => throw new IllegalArgumentException("Type Nothing is not supported. You have probably forgot to specify expected data type.") + case string if tag =~= Java.String => string + case boolean if tag =~= Java.Boolean || tag =~= Scala.Boolean => boolean.toBoolean + case byte if tag =~= Java.Byte || tag =~= Scala.Byte => byte.toByte + case char if tag =~= Java.Char || tag =~= Scala.Char => char.charAt(0) + case short if tag =~= Java.Short || tag =~= Scala.Short => short.toShort + case int if tag =~= Java.Int || tag =~= Scala.Int => int.toInt + case long if tag =~= Java.Long || tag =~= Scala.Long => long.toLong + case float if tag =~= Java.Float || tag =~= Scala.Float => float.toFloat + case double if tag =~= Java.Double || tag =~= Scala.Double => double.toDouble case anyRef => stringToAnyRef[T](anyRef) } /** consumes BASE64 string and returns array of bytes */ - protected def stringToBinary(base64: String): Array[Byte] = + private def stringToBinary(base64: String): Array[Byte] = Base64.getDecoder.decode(base64) /** deserializes the binary stream into the object */ - protected def binaryToAnyRef[T](binary: Array[Byte])(implicit classTag: ClassTag[T]): AnyRef = + private def binaryToAnyRef[T](binary: Array[Byte])(implicit classTag: ClassTag[T]): AnyRef = serializer.deserialize(binary, classTag.runtimeClass.asInstanceOf[Class[_ <: AnyRef]]).get /** converts BASE64 string directly into the object */ - protected def stringToAnyRef[T: ClassTag](base64: String): AnyRef = + private def stringToAnyRef[T: ClassTag](base64: String): AnyRef = (stringToBinary _ andThen binaryToAnyRef[T])(base64) } @@ -171,7 +169,7 @@ private[connector] class AkkaSerializerImpl @Inject() (system: ActorSystem) exte private[connector] object Primitives { /** primitive types with simplified encoding */ - val primitives = Seq( + val primitives: Seq[Class[_]] = Seq( classOf[Boolean], classOf[java.lang.Boolean], classOf[Byte], classOf[java.lang.Byte], classOf[Char], classOf[java.lang.Character], @@ -189,15 +187,15 @@ private[connector] object Primitives { */ private[connector] object JavaClassTag { - val Byte = ClassTag(classOf[java.lang.Byte]) - val Short = ClassTag(classOf[java.lang.Short]) - val Char = ClassTag(classOf[java.lang.Character]) - val Int = ClassTag(classOf[java.lang.Integer]) - val Long = ClassTag(classOf[java.lang.Long]) - val Float = ClassTag(classOf[java.lang.Float]) - val Double = ClassTag(classOf[java.lang.Double]) - val Boolean = ClassTag(classOf[java.lang.Boolean]) - val String = ClassTag(classOf[String]) + val Byte: ClassTag[Byte] = ClassTag(classOf[java.lang.Byte]) + val Short: ClassTag[Short] = ClassTag(classOf[java.lang.Short]) + val Char: ClassTag[Char] = ClassTag(classOf[java.lang.Character]) + val Int: ClassTag[Int] = ClassTag(classOf[java.lang.Integer]) + val Long: ClassTag[Long] = ClassTag(classOf[java.lang.Long]) + val Float: ClassTag[Float] = ClassTag(classOf[java.lang.Float]) + val Double: ClassTag[Double] = ClassTag(classOf[java.lang.Double]) + val Boolean: ClassTag[Boolean] = ClassTag(classOf[java.lang.Boolean]) + val String: ClassTag[String] = ClassTag(classOf[String]) } class AkkaSerializerProvider @Inject() (implicit system: ActorSystem) extends Provider[AkkaSerializer] { diff --git a/src/main/scala/play/api/cache/redis/connector/RedisCommands.scala b/src/main/scala/play/api/cache/redis/connector/RedisCommands.scala index ffb0fd1d..1ceded07 100644 --- a/src/main/scala/play/api/cache/redis/connector/RedisCommands.scala +++ b/src/main/scala/play/api/cache/redis/connector/RedisCommands.scala @@ -5,9 +5,11 @@ import scala.concurrent.Future import play.api.Logger import play.api.cache.redis.configuration._ import play.api.inject.ApplicationLifecycle -import akka.actor.ActorSystem +import akka.actor.{ActorSystem, Scheduler} import redis.{RedisClient => RedisStandaloneClient, RedisCluster => RedisClusterClient, _} +import scala.concurrent.duration.FiniteDuration + /** * Dispatches a provider of the redis commands implementation. Use with Guice * or some other DI container. @@ -24,14 +26,14 @@ private[connector] class RedisCommandsProvider(instance: RedisInstance)(implicit private[connector] trait AbstractRedisCommands { /** logger instance */ - protected def log = Logger("play.api.cache.redis") + protected def log: Logger = Logger("play.api.cache.redis") def lifecycle: ApplicationLifecycle /** an implementation of the redis commands */ def client: RedisCommands - lazy val get = client + lazy val get: RedisCommands = client /** action invoked on the start of the actor */ def start(): Unit @@ -63,25 +65,25 @@ private[connector] class RedisCommandsStandalone(configuration: RedisStandalone) password = password ) with FailEagerly with RedisRequestTimeout { - protected val connectionTimeout = configuration.timeout.connection + protected val connectionTimeout: Option[FiniteDuration] = configuration.timeout.connection - protected val timeout = configuration.timeout.redis + protected val timeout: Option[FiniteDuration] = configuration.timeout.redis - protected implicit val scheduler = system.scheduler + protected implicit val scheduler: Scheduler = system.scheduler - override def send[T](redisCommand: RedisCommand[_ <: protocol.RedisReply, T]) = super.send(redisCommand) + override def send[T](redisCommand: RedisCommand[_ <: protocol.RedisReply, T]): Future[T] = super.send(redisCommand) - override def onConnectStatus = (status: Boolean) => connected = status + override def onConnectStatus: Boolean => Unit = (status: Boolean) => connected = status } // $COVERAGE-OFF$ - def start() = database.fold { + override def start(): Unit = database.fold { log.info(s"Redis cache actor started. It is connected to $host:$port") } { database => log.info(s"Redis cache actor started. It is connected to $host:$port?database=$database") } - def stop(): Future[Unit] = Future successful { + override def stop(): Future[Unit] = Future successful { log.info("Stopping the redis cache actor ...") client.stop() log.info("Redis cache stopped.") @@ -106,14 +108,14 @@ private[connector] class RedisCommandsCluster(configuration: RedisCluster)(impli case RedisHost(host, port, database, username, password) => RedisServer(host.resolvedIpAddress, port, username, password, database) } ) with RedisRequestTimeout { - protected val timeout = configuration.timeout.redis + protected val timeout: Option[FiniteDuration] = configuration.timeout.redis - protected implicit val scheduler = system.scheduler + protected implicit val scheduler: Scheduler = system.scheduler } // $COVERAGE-OFF$ - def start(): Unit = { - def servers = nodes.map { + override def start(): Unit = { + def servers: Seq[String] = nodes.map { case RedisHost(host, port, Some(database), _, _) => s" $host:$port?database=$database" case RedisHost(host, port, None, _, _) => s" $host:$port" } @@ -121,7 +123,7 @@ private[connector] class RedisCommandsCluster(configuration: RedisCluster)(impli log.info(s"Redis cluster cache actor started. It is connected to ${servers mkString ", "}") } - def stop(): Future[Unit] = Future successful { + override def stop(): Future[Unit] = Future successful { log.info("Stopping the redis cluster cache actor ...") Option(client).foreach(_.stop()) log.info("Redis cluster cache stopped.") @@ -148,21 +150,20 @@ private[connector] class RedisCommandsSentinel(configuration: RedisSentinel)(imp password = configuration.password, db = configuration.database ) with RedisRequestTimeout { - protected val connectionTimeout = configuration.timeout.connection - protected val timeout = configuration.timeout.redis + protected val timeout: Option[FiniteDuration] = configuration.timeout.redis - protected implicit val scheduler = system.scheduler + protected implicit val scheduler: Scheduler = system.scheduler - override def send[T](redisCommand: RedisCommand[_ <: protocol.RedisReply, T]) = super.send(redisCommand) + override def send[T](redisCommand: RedisCommand[_ <: protocol.RedisReply, T]): Future[T] = super.send(redisCommand) } // $COVERAGE-OFF$ - def start(): Unit = { + override def start(): Unit = { log.info(s"Redis sentinel cache actor started. It is connected to ${configuration.toString}") } - def stop(): Future[Unit] = Future successful { + override def stop(): Future[Unit] = Future successful { log.info("Stopping the redis sentinel cache actor ...") client.stop() log.info("Redis sentinel cache stopped.") diff --git a/src/main/scala/play/api/cache/redis/connector/RedisConnectorImpl.scala b/src/main/scala/play/api/cache/redis/connector/RedisConnectorImpl.scala index 8a302745..a5ce873d 100644 --- a/src/main/scala/play/api/cache/redis/connector/RedisConnectorImpl.scala +++ b/src/main/scala/play/api/cache/redis/connector/RedisConnectorImpl.scala @@ -1,14 +1,11 @@ package play.api.cache.redis.connector import java.util.concurrent.TimeUnit - import scala.concurrent.Future import scala.concurrent.duration.Duration import scala.reflect.ClassTag - import play.api.Logger import play.api.cache.redis._ - import redis._ /** @@ -25,9 +22,9 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R import runtime._ /** logger instance */ - protected val log = Logger("play.api.cache.redis") + protected val log: Logger = Logger("play.api.cache.redis") - def get[T: ClassTag](key: String): Future[Option[T]] = + override def get[T: ClassTag](key: String): Future[Option[T]] = redis.get[String](key) executing "GET" withKey key expects { case Some(response: String) => log.trace(s"Hit on key '$key'.") @@ -37,7 +34,7 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R None } - def mGet[T: ClassTag](keys: String*): Future[Seq[Option[T]]] = + override def mGet[T: ClassTag](keys: String*): Future[Seq[Option[T]]] = redis.mget[String](keys: _*) executing "MGET" withKeys keys expects { // list is always returned case list => keys.zip(list).map { @@ -56,9 +53,9 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R case ex => serializationFailed(key, "Deserialization failed", ex) }.get - def set(key: String, value: Any, expiration: Duration, ifNotExists: Boolean): Future[Boolean] = + override def set(key: String, value: Any, expiration: Duration, ifNotExists: Boolean): Future[Boolean] = // no value to set - if (value == null) remove(key).map(_ => true) + if (Option(value).isEmpty) remove(key).map(_ => true) // set the value else encode(key, value) flatMap (doSet(key, _, expiration, ifNotExists)) @@ -71,7 +68,7 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R /** implements the advanced set operation storing already encoded value into the storage */ private def doSet(key: String, value: String, expiration: Duration, ifNotExists: Boolean): Future[Boolean] = - redis.set( + redis.set[String]( key, value, pxMilliseconds = if (expiration.isFinite) Some(expiration.toMillis) else None, @@ -82,9 +79,9 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R case false => log.debug(s"Set on key '$key' ignored. Condition was not met.") } - def mSet(keyValues: (String, Any)*): Future[Unit] = mSetUsing(mSetEternally, (), keyValues: _*) + override def mSet(keyValues: (String, Any)*): Future[Unit] = mSetUsing(mSetEternally, (), keyValues: _*) - def mSetIfNotExist(keyValues: (String, Any)*): Future[Boolean] = mSetUsing(mSetEternallyIfNotExist, true, keyValues: _*) + override def mSetIfNotExist(keyValues: (String, Any)*): Future[Boolean] = mSetUsing(mSetEternallyIfNotExist, true, keyValues: _*) /** eternally stores or removes all given values, using the given mSet implementation */ private def mSetUsing[T](mSet: Seq[(String, String)] => Future[T], default: T, keyValues: (String, Any)*): Future[T] = { @@ -110,13 +107,13 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R case false => log.debug(s"Set if not exists on keys ${keyValues.map(_.key) mkString " "} ignored. Some value already exists.") } - def expire(key: String, expiration: Duration): Future[Unit] = + override def expire(key: String, expiration: Duration): Future[Unit] = redis.expire(key, expiration.toSeconds.toInt) executing "EXPIRE" withKey key andParameter s"$expiration" logging { case true => log.debug(s"Expiration set on key '$key'.") // expiration was set case false => log.debug(s"Expiration set on key '$key' failed. Key does not exist.") // Nothing was removed } - def expiresIn(key: String): Future[Option[Duration]] = + override def expiresIn(key: String): Future[Option[Duration]] = redis.pttl(key) executing "PTTL" withKey key expects { case -2 => log.debug(s"PTTL on key '$key' returns -2, it does not exist.") @@ -129,7 +126,7 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R Some(Duration(expiration, TimeUnit.MILLISECONDS)) } - def matching(pattern: String): Future[Seq[String]] = + override def matching(pattern: String): Future[Seq[String]] = redis.keys(pattern) executing "KEYS" withKey pattern logging { case keys => log.debug(s"KEYS on '$pattern' responded '${keys.mkString(", ")}'.") } @@ -138,19 +135,19 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R // either a mock or would clear a redis while // the tests are in progress // $COVERAGE-OFF$ - def invalidate(): Future[Unit] = + override def invalidate(): Future[Unit] = redis.flushdb() executing "FLUSHDB" logging { case _ => log.info("Invalidated.") // cache was invalidated } // $COVERAGE-ON$ - def exists(key: String): Future[Boolean] = + override def exists(key: String): Future[Boolean] = redis.exists(key) executing "EXISTS" withKey key logging { case true => log.debug(s"Key '$key' exists.") case false => log.debug(s"Key '$key' doesn't exist.") } - def remove(keys: String*): Future[Unit] = + override def remove(keys: String*): Future[Unit] = if (keys.nonEmpty) { // if any key to remove do it redis.del(keys: _*) executing "DEL" withKeys keys logging { // Nothing was removed @@ -162,45 +159,45 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R Future.successful(()) // otherwise return immediately } - def ping(): Future[Unit] = + override def ping(): Future[Unit] = redis.ping() executing "PING" logging { case "PONG" => () } - def increment(key: String, by: Long): Future[Long] = + override def increment(key: String, by: Long): Future[Long] = redis.incrby(key, by) executing "INCRBY" withKey key andParameter s"$by" logging { case value => log.debug(s"The value at key '$key' was incremented by $by to $value.") } - def append(key: String, value: String): Future[Long] = + override def append(key: String, value: String): Future[Long] = redis.append(key, value) executing "APPEND" withKey key andParameter value logging { case length => log.debug(s"The value was appended to key '$key'.") } - def listPrepend(key: String, values: Any*): Future[Long] = + override def listPrepend(key: String, values: Any*): Future[Long] = Future.sequence(values.map(encode(key, _))).flatMap(redis.lpush(key, _: _*)) executing "LPUSH" withKey key andParameters values logging { case length => log.debug(s"The $length values was prepended to key '$key'.") } recover { case ExecutionFailedException(_, _, _, ex) if ex.getMessage startsWith "WRONGTYPE" => - log.warn(s"Value at '$key' is not a list.") + log.warn(s"Value at '$key' is not a list to be prepended.") throw new IllegalArgumentException(s"Value at '$key' is not a list.") } - def listAppend(key: String, values: Any*) = + override def listAppend(key: String, values: Any*): Future[Long] = Future.sequence(values.map(encode(key, _))).flatMap(redis.rpush(key, _: _*)) executing "RPUSH" withKey key andParameters values logging { case length => log.debug(s"The $length values was appended to key '$key'.") } recover { case ExecutionFailedException(_, _, _, ex) if ex.getMessage startsWith "WRONGTYPE" => - log.warn(s"Value at '$key' is not a list.") + log.warn(s"Value at '$key' is not a list to be appended.") throw new IllegalArgumentException(s"Value at '$key' is not a list.") } - def listSize(key: String) = + override def listSize(key: String): Future[Long] = redis.llen(key) executing "LLEN" withKey key logging { case length => log.debug(s"The collection at '$key' has $length items.") } - def listSetAt(key: String, position: Int, value: Any) = + override def listSetAt(key: String, position: Int, value: Any): Future[Unit] = encode(key, value).flatMap(redis.lset(key, position, _)) executing "LSET" withKey key andParameter value logging { case _ => log.debug(s"Updated value at $position in '$key' to $value.") } recover { @@ -209,7 +206,7 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R throw new IndexOutOfBoundsException("Index out of range") } - def listHeadPop[T: ClassTag](key: String) = + override def listHeadPop[T: ClassTag](key: String): Future[Option[T]] = redis.lpop[String](key) executing "LPOP" withKey key expects { case Some(encoded) => log.trace(s"Hit on head in key '$key'.") @@ -219,24 +216,24 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R None } - def listSlice[T: ClassTag](key: String, start: Int, end: Int) = + override def listSlice[T: ClassTag](key: String, start: Int, end: Int): Future[Seq[T]] = redis.lrange[String](key, start, end) executing "LRANGE" withKey key andParameters s"$start $end" expects { case values => log.debug(s"The range on '$key' from $start to $end included returned ${values.size} values.") values.map(decode[T](key, _)) } - def listRemove(key: String, value: Any, count: Int) = + override def listRemove(key: String, value: Any, count: Int): Future[Long] = encode(key, value).flatMap(redis.lrem(key, count, _)) executing "LREM" withKey key andParameters s"$value $count" logging { case removed => log.debug(s"Removed $removed occurrences of $value in '$key'.") } - def listTrim(key: String, start: Int, end: Int) = + override def listTrim(key: String, start: Int, end: Int): Future[Unit] = redis.ltrim(key, start, end) executing "LTRIM" withKey key andParameter s"$start $end" logging { case _ => log.debug(s"Trimmed collection at '$key' to $start:$end ") } - def listInsert(key: String, pivot: Any, value: Any) = for { + override def listInsert(key: String, pivot: Any, value: Any): Future[Option[Long]] = for { pivot <- encode(key, pivot) value <- encode(key, value) result <- redis.linsert(key, api.BEFORE, pivot, value) executing "LINSERT" withKey key andParameter s"$pivot $value" expects { @@ -246,14 +243,14 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R case length => log.debug(s"Inserted $value into the list at '$key'. New size is $length.") Some(length) - } recover { + } recover[Option[Long]] { case ExecutionFailedException(_, _, _, ex) if ex.getMessage startsWith "WRONGTYPE" => log.warn(s"Value at '$key' is not a list.") throw new IllegalArgumentException(s"Value at '$key' is not a list.") } } yield result - def setAdd(key: String, values: Any*) = { + override def setAdd(key: String, values: Any*): Future[Long] = { // encodes the value def toEncoded(value: Any) = encode(key, value) Future.sequence(values map toEncoded).flatMap(redis.sadd(key, _: _*)) executing "SADD" withKey key andParameters values expects { @@ -267,34 +264,34 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R } } - def setSize(key: String) = + override def setSize(key: String): Future[Long] = redis.scard(key) executing "SCARD" withKey key logging { case length => log.debug(s"The collection at '$key' has $length items.") } - def setMembers[T: ClassTag](key: String) = + override def setMembers[T: ClassTag](key: String): Future[Set[T]] = redis.smembers[String](key) executing "SMEMBERS" withKey key expects { case items => log.debug(s"Returned ${items.size} items from the collection at '$key'.") items.map(decode[T](key, _)).toSet } - def setIsMember(key: String, value: Any) = + override def setIsMember(key: String, value: Any): Future[Boolean] = encode(key, value).flatMap(redis.sismember(key, _)) executing "SISMEMBER" withKey key andParameter value logging { case true => log.debug(s"Item $value exists in the collection at '$key'.") case false => log.debug(s"Item $value does not exist in the collection at '$key'") } - def setRemove(key: String, values: Any*) = { + override def setRemove(key: String, values: Any*): Future[Long] = { // encodes the value - def toEncoded(value: Any) = encode(key, value) + def toEncoded(value: Any): Future[String] = encode(key, value) Future.sequence(values map toEncoded).flatMap(redis.srem(key, _: _*)) executing "SREM" withKey key andParameters values logging { case removed => log.debug(s"Removed $removed elements from the collection at '$key'.") } } - def sortedSetAdd(key: String, scoreValues: (Double, Any)*) = { + override def sortedSetAdd(key: String, scoreValues: (Double, Any)*): Future[Long] = { // encodes the value def toEncoded(scoreValue: (Double, Any)) = encode(key, scoreValue._2).map((scoreValue._1, _)) @@ -309,19 +306,19 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R } } - def sortedSetSize(key: String) = + override def sortedSetSize(key: String): Future[Long] = redis.zcard(key) executing "ZCARD" withKey key logging { case length => log.debug(s"The zset at '$key' has $length items.") } - def sortedSetScore(key: String, value: Any) = { + override def sortedSetScore(key: String, value: Any): Future[Option[Double]] = { encode(key, value) flatMap (redis.zscore(key, _)) executing "ZSCORE" withKey key andParameter value logging { case Some(score) => log.debug(s"The score of item: $value is $score in the collection at '$key'.") case None => log.debug(s"Item $value does not exist in the collection at '$key'") } } - def sortedSetRemove(key: String, values: Any*) = { + override def sortedSetRemove(key: String, values: Any*): Future[Long] = { // encodes the value def toEncoded(value: Any) = encode(key, value) @@ -330,7 +327,7 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R } } - def sortedSetRange[T: ClassTag](key: String, start: Long, stop: Long) = { + override def sortedSetRange[T: ClassTag](key: String, start: Long, stop: Long): Future[Seq[T]] = { redis.zrange[String](key, start, stop) executing "ZRANGE" withKey key andParameter s"$start $stop" expects { case encodedSeq => log.debug(s"Got range from $start to $stop in the zset at '$key'.") @@ -338,7 +335,7 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R } } - def sortedSetReverseRange[T: ClassTag](key: String, start: Long, stop: Long) = { + override def sortedSetReverseRange[T: ClassTag](key: String, start: Long, stop: Long): Future[Seq[T]] = { redis.zrevrange[String](key, start, stop) executing "ZREVRANGE" withKey key andParameter s"$start $stop" expects { case encodedSeq => log.debug(s"Got reverse range from $start to $stop in the zset at '$key'.") @@ -346,23 +343,23 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R } } - def hashRemove(key: String, fields: String*) = + override def hashRemove(key: String, fields: String*): Future[Long] = redis.hdel(key, fields: _*) executing "HDEL" withKey key andParameters fields logging { case removed => log.debug(s"Removed $removed elements from the collection at '$key'.") } - def hashIncrement(key: String, field: String, incrementBy: Long) = + override def hashIncrement(key: String, field: String, incrementBy: Long): Future[Long] = redis.hincrby(key, field, incrementBy) executing "HINCRBY" withKey key andParameters s"$field $incrementBy" logging { case value => log.debug(s"Field '$field' in '$key' was incremented to $value.") } - def hashExists(key: String, field: String) = + override def hashExists(key: String, field: String): Future[Boolean] = redis.hexists(key, field) executing "HEXISTS" withKey key andParameter field logging { case true => log.debug(s"Item $field exists in the collection at '$key'.") case false => log.debug(s"Item $field does not exist in the collection at '$key'") } - def hashGet[T: ClassTag](key: String, field: String) = + override def hashGet[T: ClassTag](key: String, field: String): Future[Option[T]] = redis.hget[String](key, field) executing "HGET" withKey key andParameter field expects { case Some(encoded) => log.debug(s"Item $field exists in the collection at '$key'.") @@ -372,36 +369,36 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R None } - def hashGet[T: ClassTag](key: String, fields: Seq[String]): Future[Seq[Option[T]]] = + override def hashGet[T: ClassTag](key: String, fields: Seq[String]): Future[Seq[Option[T]]] = redis.hmget[String](key, fields: _*) executing "HMGET" withKey key andParameters fields expects { case encoded => log.debug(s"Collection at '$key' with fields '$fields' has returned ${encoded.size} items.") encoded.map(_.map(decode[T](key, _))) } - def hashGetAll[T: ClassTag](key: String) = + override def hashGetAll[T: ClassTag](key: String): Future[Map[String, T]] = redis.hgetall[String](key) executing "HGETALL" withKey key expects { case empty if empty.isEmpty => log.debug(s"Collection at '$key' is empty.") - Map.empty + Map.empty[String, T] case encoded => log.debug(s"Collection at '$key' has ${encoded.size} items.") encoded.map { case (itemKey, value) => itemKey -> decode[T](itemKey, value) } } - def hashSize(key: String) = + override def hashSize(key: String): Future[Long] = redis.hlen(key) executing "HLEN" withKey key logging { case length => log.debug(s"The collection at '$key' has $length items.") } - def hashKeys(key: String) = + override def hashKeys(key: String): Future[Set[String]] = redis.hkeys(key) executing "HKEYS" withKey key expects { case keys => log.debug(s"The collection at '$key' defines: ${keys mkString " "}.") keys.toSet } - def hashSet(key: String, field: String, value: Any) = + override def hashSet(key: String, field: String, value: Any): Future[Boolean] = encode(key, value).flatMap(redis.hset(key, field, _)) executing "HSET" withKey key andParameters s"$field $value" logging { case true => log.debug(s"Item $field in the collection at '$key' was inserted.") case false => log.debug(s"Item $field in the collection at '$key' was updated.") @@ -411,7 +408,7 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R throw new IllegalArgumentException(s"Value at '$key' is not a map.") } - def hashValues[T: ClassTag](key: String) = + override def hashValues[T: ClassTag](key: String): Future[Set[T]] = redis.hvals[String](key) executing "HVALS" withKey key expects { case values => log.debug(s"The collection at '$key' contains ${values.size} values.") diff --git a/src/main/scala/play/api/cache/redis/connector/RequestTimeout.scala b/src/main/scala/play/api/cache/redis/connector/RequestTimeout.scala index bc16f04c..b3a3e886 100644 --- a/src/main/scala/play/api/cache/redis/connector/RequestTimeout.scala +++ b/src/main/scala/play/api/cache/redis/connector/RequestTimeout.scala @@ -22,7 +22,7 @@ object RequestTimeout { // fails @inline - def fail(failAfter: FiniteDuration)(implicit scheduler: Scheduler, context: ExecutionContext) = { + def fail(failAfter: FiniteDuration)(implicit scheduler: Scheduler, context: ExecutionContext): Future[Nothing] = { after(failAfter, scheduler)(Future.failed(redis.actors.NoConnectionException)) } @@ -48,9 +48,9 @@ trait FailEagerly extends RequestTimeout { @inline protected def connectionTimeout: Option[FiniteDuration] - abstract override def send[T](redisCommand: RedisCommand[_ <: protocol.RedisReply, T]) = { + abstract override def send[T](redisCommand: RedisCommand[_ <: protocol.RedisReply, T]): Future[T] = { // proceed with the command - @inline def continue = super.send(redisCommand) + @inline def continue: Future[T] = super.send(redisCommand) // based on connection status if (connected) continue else connectionTimeout.fold(continue)(invokeOrFail(continue, _)) } @@ -69,9 +69,9 @@ trait RedisRequestTimeout extends RequestTimeout { /** indicates the timeout on the redis request */ protected def timeout: Option[FiniteDuration] - abstract override def send[T](redisCommand: RedisCommand[_ <: protocol.RedisReply, T]) = { + abstract override def send[T](redisCommand: RedisCommand[_ <: protocol.RedisReply, T]): Future[T] = { // proceed with the command - @inline def continue = super.send(redisCommand) + @inline def continue: Future[T] = super.send(redisCommand) // based on connection status if (initialized) timeout.fold(continue)(invokeOrFail(continue, _)) else continue } diff --git a/src/main/scala/play/api/cache/redis/connector/package.scala b/src/main/scala/play/api/cache/redis/connector/package.scala index 3838de4c..76638403 100644 --- a/src/main/scala/play/api/cache/redis/connector/package.scala +++ b/src/main/scala/play/api/cache/redis/connector/package.scala @@ -10,11 +10,11 @@ package object connector { implicit class TupleHelper[+A, +B](val tuple: (A, B)) extends AnyVal { @inline def key: A = tuple._1 @inline def value: B = tuple._2 - @inline def asString = s"$key $value" - @inline def isNull = value == null + @inline def asString: String = s"$key $value" + @inline def isNull: Boolean = Option(value).isEmpty } - implicit class StringWhen(val value: String) extends AnyVal { - def when(condition: Boolean) = if (condition) value else "" + implicit class StringWhen(private val value: String) extends AnyVal { + def when(condition: Boolean): String = if (condition) value else "" } } diff --git a/src/main/scala/play/api/cache/redis/exceptions.scala b/src/main/scala/play/api/cache/redis/exceptions.scala index bd00e971..276efacc 100644 --- a/src/main/scala/play/api/cache/redis/exceptions.scala +++ b/src/main/scala/play/api/cache/redis/exceptions.scala @@ -9,23 +9,27 @@ sealed abstract class RedisException(message: String, cause: Throwable) extends /** * Request timeouts - */ -case class TimeoutException(cause: Throwable) extends RedisException("Command execution timed out", cause) +*/ +final case class TimeoutException( + cause: Throwable) extends RedisException("Command execution timed out", cause) /** - * Command execution failed with exception - */ -case class ExecutionFailedException(key: Option[String], command: String, statement: String, cause: Throwable) extends RedisException(s"Execution of '$command'${key.map(key => s" for key '$key'") getOrElse ""} failed", cause) + * Command execution failed with exception + */ +final case class ExecutionFailedException( + key: Option[String], command: String, statement: String, cause: Throwable) extends RedisException(s"Execution of '$command'${key.map(key => s" for key '$key'") getOrElse ""} failed", cause) /** - * Request succeeded but returned unexpected value - */ -case class UnexpectedResponseException(key: Option[String], command: String) extends RedisException(s"Command '$command'${key.map(key => s" for key '$key'") getOrElse ""} returned unexpected response") + * Request succeeded but returned unexpected value + */ +final case class UnexpectedResponseException( + key: Option[String], command: String) extends RedisException(s"Command '$command'${key.map(key => s" for key '$key'") getOrElse ""} returned unexpected response") /** - * Value serialization or deserialization failed. - */ -case class SerializationException(key: String, message: String, cause: Throwable) extends RedisException(s"$message for $key", cause) + * Value serialization or deserialization failed. + */ +final case class SerializationException( + key: String, message: String, cause: Throwable) extends RedisException(s"$message for $key", cause) /** * Helper trait providing simplified and unified API to exception handling in play-redis @@ -39,12 +43,12 @@ trait ExceptionImplicits { /** helper indicating serialization failure, it throws an exception */ @throws[SerializationException] - def serializationFailed(key: String, message: String, cause: Throwable) = + def serializationFailed(key: String, message: String, cause: Throwable): Nothing = throw SerializationException(key, message, cause) /** helper indicating command execution timed out */ @throws[TimeoutException] - def timedOut(cause: Throwable) = + def timedOut(cause: Throwable): Nothing = throw TimeoutException(cause) /** helper indicating the command execution returned unexpected exception */ diff --git a/src/main/scala/play/api/cache/redis/impl/AsyncJavaRedis.scala b/src/main/scala/play/api/cache/redis/impl/AsyncJavaRedis.scala index 8c370d0d..873de9d7 100644 --- a/src/main/scala/play/api/cache/redis/impl/AsyncJavaRedis.scala +++ b/src/main/scala/play/api/cache/redis/impl/AsyncJavaRedis.scala @@ -52,9 +52,6 @@ private[impl] class AsyncJavaRedis(internal: CacheAsyncApi)(implicit environment } } - @deprecated(message = "Method `getOptional` was deprecated in Play 2.8. Use `get` instead.", since = "2.6.0") - override def getOptional[T](key: String): CompletionStage[Optional[T]] = get(key) - def getOrElse[T](key: String, block: Callable[T]): CompletionStage[T] = getOrElseUpdate[T](key, (() => Future.successful(block.call()).asJava).asJava) diff --git a/src/main/scala/play/api/cache/redis/impl/AsyncRedisImpl.scala b/src/main/scala/play/api/cache/redis/impl/AsyncRedisImpl.scala index 0d5347e7..40f01615 100644 --- a/src/main/scala/play/api/cache/redis/impl/AsyncRedisImpl.scala +++ b/src/main/scala/play/api/cache/redis/impl/AsyncRedisImpl.scala @@ -13,7 +13,7 @@ private[impl] trait AsyncRedis extends play.api.cache.AsyncCacheApi with CacheAs private[impl] class AsyncRedisImpl(redis: RedisConnector)(implicit runtime: RedisRuntime) extends RedisCache(redis, Builders.AsynchronousBuilder) with AsyncRedis { - def getOrElseUpdate[T: ClassTag](key: String, expiration: Duration)(orElse: => Future[T]) = + def getOrElseUpdate[T: ClassTag](key: String, expiration: Duration)(orElse: => Future[T]): Future[T] = getOrFuture[T](key, expiration)(orElse) def removeAll(): Future[Done] = invalidate() diff --git a/src/main/scala/play/api/cache/redis/impl/Builders.scala b/src/main/scala/play/api/cache/redis/impl/Builders.scala index 5cb8d4a9..35c62cb6 100644 --- a/src/main/scala/play/api/cache/redis/impl/Builders.scala +++ b/src/main/scala/play/api/cache/redis/impl/Builders.scala @@ -1,7 +1,6 @@ package play.api.cache.redis.impl import scala.concurrent.Future -import scala.language.higherKinds /** * Transforms future result produced by redis implementation to the result of the desired type @@ -51,7 +50,7 @@ object Builders { Try { // wait for the result Await.result(run, runtime.timeout.duration) - }.recover { + }.recover[T] { // it timed out, produce an expected exception case cause: AskTimeoutException => timedOut(cause) case cause: java.util.concurrent.TimeoutException => timedOut(cause) diff --git a/src/main/scala/play/api/cache/redis/impl/InvocationPolicy.scala b/src/main/scala/play/api/cache/redis/impl/InvocationPolicy.scala index ec0a74db..949d0ace 100644 --- a/src/main/scala/play/api/cache/redis/impl/InvocationPolicy.scala +++ b/src/main/scala/play/api/cache/redis/impl/InvocationPolicy.scala @@ -16,7 +16,7 @@ sealed trait InvocationPolicy { object EagerInvocation extends InvocationPolicy { override def invoke[T](f: => Future[Any], thenReturn: T)(implicit context: ExecutionContext): Future[T] = { - f + f: Unit Future successful thenReturn } } diff --git a/src/main/scala/play/api/cache/redis/impl/JavaCompatibility.scala b/src/main/scala/play/api/cache/redis/impl/JavaCompatibility.scala index 7e49eec2..d6b56e5d 100644 --- a/src/main/scala/play/api/cache/redis/impl/JavaCompatibility.scala +++ b/src/main/scala/play/api/cache/redis/impl/JavaCompatibility.scala @@ -5,7 +5,7 @@ import scala.language.implicitConversions import scala.reflect.ClassTag import play.api.Environment -import play.api.cache.redis.JavaCompatibilityBase +import play.api.cache.redis._ import akka.Done @@ -22,7 +22,7 @@ private[impl] object JavaCompatibility extends JavaCompatibilityBase { object JavaList { def apply[T](values: T*): JavaList[T] = { val list = new java.util.ArrayList[T]() - list.addAll(values.asJava) + list.addAll(values.asJava): Unit list } } @@ -33,9 +33,7 @@ private[impl] object JavaCompatibility extends JavaCompatibilityBase { } implicit class Java8Callable[T](val f: () => T) extends AnyVal { - @inline def asJava: Callable[T] = new Callable[T] { - def call(): T = f() - } + @inline def asJava: Callable[T] = () => f() } implicit class Java8Optional[T](val option: Option[T]) extends AnyVal { @@ -47,7 +45,7 @@ private[impl] object JavaCompatibility extends JavaCompatibilityBase { } implicit class RichFuture(val future: Future.type) extends AnyVal { - @inline def from[T](futures: Future[T]*)(implicit ec: ExecutionContext): Future[Seq[T]] = future.sequence(futures.toSeq) + @inline def from[T](futures: Future[T]*)(implicit ec: ExecutionContext): Future[Seq[T]] = future.sequence(futures) } @inline implicit def class2tag[T](classOf: Class[T]): ClassTag[T] = ClassTag(classOf) @@ -55,18 +53,18 @@ private[impl] object JavaCompatibility extends JavaCompatibilityBase { @inline def async[T](doAsync: ExecutionContext => Future[T])(implicit runtime: RedisRuntime): CompletionStage[T] = { doAsync { // save the HTTP context if any and restore it later for orElse clause - play.core.j.HttpExecutionContext.fromThread(runtime.context) + play.core.j.ClassLoaderExecutionContext.fromThread(runtime.context) }.asJava } @inline def classTagKey(key: String): String = s"classTag::$key" @inline def classTagOf(value: Any): String = { - if (value == null) "null" else value.getClass.getCanonicalName + if (Option(value).isEmpty) "null" else value.getClass.getCanonicalName } @inline def classTagFrom[T](tag: String)(implicit environment: Environment): ClassTag[T] = { - if (tag == "null") ClassTag.Null.asInstanceOf[ClassTag[T]] + if (tag === "null") ClassTag.Null.asInstanceOf[ClassTag[T]] else ClassTag(classTagNameToClass(tag, environment)) } @@ -76,7 +74,7 @@ private[impl] object JavaCompatibility extends JavaCompatibilityBase { // $COVERAGE-OFF$ /** java primitives are serialized into their type names instead of classes */ - def classTagNameToClass(name: String, environment: Environment): Class[_] = name match { + private def classTagNameToClass(name: String, environment: Environment): Class[_] = name match { case "boolean[]" => classOf[Array[java.lang.Boolean]] case "byte[]" => classOf[Array[java.lang.Byte]] case "char[]" => classOf[Array[java.lang.Character]] diff --git a/src/main/scala/play/api/cache/redis/impl/RedisCache.scala b/src/main/scala/play/api/cache/redis/impl/RedisCache.scala index 7f4a5e35..5aa38560 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisCache.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisCache.scala @@ -2,7 +2,7 @@ package play.api.cache.redis.impl import scala.concurrent._ import scala.concurrent.duration.Duration -import scala.language.{higherKinds, implicitConversions} +import scala.language.implicitConversions import scala.reflect.ClassTag import play.api.cache.redis._ @@ -15,119 +15,138 @@ private[impl] class RedisCache[Result[_]](redis: RedisConnector, builder: Builde @inline implicit protected def implicitBuilder: Builders.ResultBuilder[Result] = builder - def get[T: ClassTag](key: String) = key.prefixed { key => - redis.get[T](key).recoverWithDefault(None) - } - - def getAll[T: ClassTag](keys: Iterable[String]): Result[Seq[Option[T]]] = keys.toSeq.prefixed { keys => - redis.mGet[T](keys: _*).recoverWithDefault(keys.toList.map(_ => None)) - } - - def set(key: String, value: Any, expiration: Duration) = key.prefixed { key => - redis.set(key, value, expiration).map(_ => (): Unit).recoverWithDone - } - - def setIfNotExists(key: String, value: Any, expiration: Duration) = key.prefixed { key => - redis.set(key, value, expiration, ifNotExists = true).recoverWithDefault(true) - } - - def setAll(keyValues: (String, Any)*): Result[Done] = keyValues.prefixed { keyValues => - redis.mSet(keyValues: _*).recoverWithDone - } - - def setAllIfNotExist(keyValues: (String, Any)*): Result[Boolean] = keyValues.prefixed { keyValues => - redis.mSetIfNotExist(keyValues: _*).recoverWithDefault(true) - } - - def append(key: String, value: String, expiration: Duration): Result[Done] = key.prefixed { key => - redis.append(key, value).flatMap { result => - // if the new string length is equal to the appended string, it means they should equal - // when the finite duration is required, set it - if (result == value.length && expiration.isFinite) redis.expire(key, expiration) else Future.successful[Unit](()) - }.recoverWithDone - } - - def expire(key: String, expiration: Duration) = key.prefixed { key => - redis.expire(key, expiration).recoverWithDone - } + override def get[T: ClassTag](key: String): Result[Option[T]] = + key.prefixed { key => + redis.get[T](key).recoverWithDefault(None) + } + + override def getAll[T: ClassTag](keys: Iterable[String]): Result[Seq[Option[T]]] = + keys.toSeq.prefixed { keys => + redis.mGet[T](keys: _*).recoverWithDefault(keys.toList.map(_ => None)) + } + + override def set(key: String, value: Any, expiration: Duration): Result[Done] = + key.prefixed { key => + redis.set(key, value, expiration).map(_ => (): Unit).recoverWithDone + } + + override def setIfNotExists(key: String, value: Any, expiration: Duration): Result[Boolean] = + key.prefixed { key => + redis.set(key, value, expiration, ifNotExists = true).recoverWithDefault(true) + } + + override def setAll(keyValues: (String, Any)*): Result[Done] = + keyValues.prefixed { keyValues => + redis.mSet(keyValues: _*).recoverWithDone + } + + override def setAllIfNotExist(keyValues: (String, Any)*): Result[Boolean] = + keyValues.prefixed { keyValues => + redis.mSetIfNotExist(keyValues: _*).recoverWithDefault(true) + } + + override def append(key: String, value: String, expiration: Duration): Result[Done] = + key.prefixed { key => + redis.append(key, value).flatMap { result => + // if the new string length is equal to the appended string, it means they should equal + // when the finite duration is required, set it + if (result === value.length && expiration.isFinite) redis.expire(key, expiration) else Future.successful[Unit](()) + }.recoverWithDone + } + + override def expire(key: String, expiration: Duration): Result[Done] = + key.prefixed { key => + redis.expire(key, expiration).recoverWithDone + } /** - * cached implementation of the matching function - * - * - when a prefix is empty, it simply delegates the invocation to the connector - * - when a prefix is defined, it unprefixes the keys when returned - */ + * cached implementation of the matching function + * + * - when a prefix is empty, it simply delegates the invocation to the connector + * - when a prefix is defined, it unprefixes the keys when returned + */ private val doMatching = runtime.prefix match { case RedisEmptyPrefix => (pattern: String) => redis.matching(pattern) - case prefix => (pattern: String) => redis.matching(pattern).map(_.unprefixed) + case _ => (pattern: String) => redis.matching(pattern).map(_.unprefixed) } - def matching(pattern: String) = pattern.prefixed { pattern => + override def matching(pattern: String): Result[Seq[String]] = pattern.prefixed { pattern => doMatching(pattern).recoverWithDefault(Seq.empty[String]) } - def getOrElse[T: ClassTag](key: String, expiration: Duration)(orElse: => T) = + override def getOrElse[T: ClassTag](key: String, expiration: Duration)(orElse: => T): Result[T] = getOrFuture(key, expiration)(orElse.toFuture).recoverWithDefault(orElse) - def getOrFuture[T: ClassTag](key: String, expiration: Duration)(orElse: => Future[T]): Future[T] = key.prefixed { key => - redis.get[T](key).flatMap { - // cache hit, return the unwrapped value - case Some(value) => value.toFuture - // cache miss, compute the value, store it into the cache but do not wait for the result and ignore it, directly return the value - case None => orElse flatMap { value => runtime.invocation.invoke(redis.set(key, value, expiration), thenReturn = value) } - }.recoverWithFuture(orElse) - } - - def remove(key: String) = key.prefixed { key => - redis.remove(key).recoverWithDone - } - - def remove(key1: String, key2: String, keys: String*) = (key1 +: key2 +: keys).prefixed { keys => + override def getOrFuture[T: ClassTag](key: String, expiration: Duration)(orElse: => Future[T]): Future[T] = + key.prefixed { key => + redis.get[T](key).flatMap { + // cache hit, return the unwrapped value + case Some(value) => value.toFuture + // cache miss, compute the value, store it into the cache but do not wait for the result and ignore it, directly return the value + case None => orElse flatMap { value => runtime.invocation.invoke(redis.set(key, value, expiration), thenReturn = value) } + }.recoverWithFuture(orElse) + } + + override def remove(key: String): Result[Done] = + key.prefixed { key => + redis.remove(key).recoverWithDone + } + + override def remove(key1: String, key2: String, keys: String*): Result[Done] = (key1 +: key2 +: keys).prefixed { keys => redis.remove(keys: _*).recoverWithDone } - def removeAll(keys: String*): Result[Done] = keys.prefixed { keys => - redis.remove(keys: _*).recoverWithDone - } + override def removeAll(keys: String*): Result[Done] = + keys.prefixed { keys => + redis.remove(keys: _*).recoverWithDone + } - def removeMatching(pattern: String): Result[Done] = pattern.prefixed { pattern => + override def removeMatching(pattern: String): Result[Done] = pattern.prefixed { pattern => redis.matching(pattern).flatMap(keys => redis.remove(keys: _*)).recoverWithDone } - def invalidate() = + override def invalidate(): Result[Done] = redis.invalidate().recoverWithDone - def exists(key: String) = key.prefixed { key => - redis.exists(key).recoverWithDefault(false) - } - - def expiresIn(key: String) = key.prefixed { key => - redis.expiresIn(key).recoverWithDefault(None) - } - - def increment(key: String, by: Long) = key.prefixed { key => - redis.increment(key, by).recoverWithDefault(by) - } - - def decrement(key: String, by: Long) = key.prefixed { key => - increment(key, -by) - } - - def list[T: ClassTag](key: String): RedisList[T, Result] = key.prefixed { key => - new RedisListImpl(key, redis) - } - - def set[T: ClassTag](key: String): RedisSet[T, Result] = key.prefixed { key => - new RedisSetImpl(key, redis) - } - - def map[T: ClassTag](key: String): RedisMap[T, Result] = key.prefixed { key => - new RedisMapImpl(key, redis) - } - - def zset[T: ClassTag](key: String): RedisSortedSet[T, Result] = key.prefixed { key => - new RedisSortedSetImpl(key, redis) - } + override def exists(key: String): Result[Boolean] = + key.prefixed { key => + redis.exists(key).recoverWithDefault(false) + } + + override def expiresIn(key: String): Result[Option[Duration]] = + key.prefixed { key => + redis.expiresIn(key).recoverWithDefault(None) + } + + override def increment(key: String, by: Long): Result[Long] = + key.prefixed { key => + redis.increment(key, by).recoverWithDefault(by) + } + + override def decrement(key: String, by: Long): Result[Long] = + key.prefixed { key => + increment(key, -by) + } + + override def list[T: ClassTag](key: String): RedisList[T, Result] = + key.prefixed { key => + new RedisListImpl[T, Result](key, redis) + } + + override def set[T: ClassTag](key: String): RedisSet[T, Result] = + key.prefixed { key => + new RedisSetImpl[T, Result](key, redis) + } + + override def map[T: ClassTag](key: String): RedisMap[T, Result] = + key.prefixed { key => + new RedisMapImpl[T, Result](key, redis) + } + + override def zset[T: ClassTag](key: String): RedisSortedSet[T, Result] = + key.prefixed { key => + new RedisSortedSetImpl[T, Result](key, redis) + } // $COVERAGE-OFF$ override def toString = s"RedisCache(name=${runtime.name})" diff --git a/src/main/scala/play/api/cache/redis/impl/RedisCaches.scala b/src/main/scala/play/api/cache/redis/impl/RedisCaches.scala index e1989f9b..47614789 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisCaches.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisCaches.scala @@ -28,7 +28,7 @@ private[redis] class RedisCachesProvider(instance: RedisInstance, serializer: co private implicit def implicitEnvironment: Environment = environment - lazy val get = new RedisCaches { + lazy val get: RedisCaches = new RedisCaches { lazy val redisConnector: RedisConnector = new connector.RedisConnectorProvider(instance, serializer).get lazy val async: AsyncRedis = new AsyncRedisImpl(redisConnector) lazy val sync: CacheApi = new SyncRedis(redisConnector) diff --git a/src/main/scala/play/api/cache/redis/impl/RedisListImpl.scala b/src/main/scala/play/api/cache/redis/impl/RedisListImpl.scala index dc6db5d8..241b8aa6 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisListImpl.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisListImpl.scala @@ -1,6 +1,6 @@ package play.api.cache.redis.impl -import scala.language.{higherKinds, implicitConversions} +import scala.language.implicitConversions import scala.reflect.ClassTag import play.api.cache.redis._ @@ -14,76 +14,76 @@ private[impl] class RedisListImpl[Elem: ClassTag, Result[_]](key: String, redis: @inline private def This: This = this - def prepend(element: Elem) = prependAll(element) + override def prepend(element: Elem): Result[This] = prependAll(element) - def append(element: Elem) = appendAll(element) + override def append(element: Elem): Result[This] = appendAll(element) - def +:(element: Elem) = prependAll(element) + override def +:(element: Elem): Result[This] = prependAll(element) - def :+(element: Elem) = appendAll(element) + override def :+(element: Elem): Result[This] = appendAll(element) - def ++:(elements: Iterable[Elem]) = prependAll(elements.toSeq: _*) + override def ++:(elements: Iterable[Elem]): Result[This] = prependAll(elements.toSeq: _*) - def :++(elements: Iterable[Elem]) = appendAll(elements.toSeq: _*) + override def :++(elements: Iterable[Elem]): Result[This] = appendAll(elements.toSeq: _*) - private def prependAll(elements: Elem*) = + private def prependAll(elements: Elem*): Result[This] = redis.listPrepend(key, elements: _*).map(_ => This).recoverWithDefault(This) - private def appendAll(elements: Elem*) = + private def appendAll(elements: Elem*): Result[This] = redis.listAppend(key, elements: _*).map(_ => This).recoverWithDefault(This) - def apply(index: Int) = redis.listSlice[Elem](key, index, index).map { + override def apply(index: Int): Result[Elem] = redis.listSlice[Elem](key, index, index).map { _.headOption getOrElse (throw new NoSuchElementException(s"Element at index $index is missing.")) }.recoverWithDefault { throw new NoSuchElementException(s"Element at index $index is missing.") } - def get(index: Int) = + override def get(index: Int): Result[Option[Elem]] = redis.listSlice[Elem](key, index, index).map(_.headOption).recoverWithDefault(None) - def headPop = redis.listHeadPop[Elem](key).recoverWithDefault(None) + override def headPop: Result[Option[Elem]] = redis.listHeadPop[Elem](key).recoverWithDefault(None) - def size = redis.listSize(key).recoverWithDefault(0) + override def size: Result[Long] = redis.listSize(key).recoverWithDefault(0) - def insertBefore(pivot: Elem, element: Elem) = + override def insertBefore(pivot: Elem, element: Elem): Result[Option[Long]] = redis.listInsert(key, pivot, element).recoverWithDefault(None) - def set(position: Int, element: Elem) = + override def set(position: Int, element: Elem): Result[This] = redis.listSetAt(key, position, element).map(_ => This).recoverWithDefault(This) - def isEmpty = - redis.listSize(key).map(_ == 0).recoverWithDefault(true) + override def isEmpty: Result[Boolean] = + redis.listSize(key).map(_ === 0).recoverWithDefault(true) - def nonEmpty = + override def nonEmpty: Result[Boolean] = redis.listSize(key).map(_ > 0).recoverWithDefault(false) - def view = ListView + override def view: RedisListView = ListView - object ListView extends RedisListView { - def slice(start: Int, end: Int) = redis.listSlice[Elem](key, start, end).recoverWithDefault(Seq.empty) + private object ListView extends RedisListView { + override def slice(start: Int, end: Int): Result[Seq[Elem]] = redis.listSlice[Elem](key, start, end).recoverWithDefault(Seq.empty) } - def modify = ListModifier + override def modify: RedisListModification = ListModifier - object ListModifier extends RedisListModification { + private object ListModifier extends RedisListModification { - def collection = This + override def collection: This = This - def clear() = + override def clear(): Result[RedisListModification] = redis.remove(key).map { _ => this: RedisListModification }.recoverWithDefault(this) - def slice(start: Int, end: Int) = + override def slice(start: Int, end: Int): Result[RedisListModification] = redis.listTrim(key, start, end).map { _ => this: RedisListModification }.recoverWithDefault(this) } - def remove(element: Elem, count: Int) = + override def remove(element: Elem, count: Int): Result[This] = redis.listRemove(key, element, count).map(_ => This).recoverWithDefault(This) - def removeAt(position: Int) = + override def removeAt(position: Int): Result[This] = redis.listSetAt(key, position, "play-redis:DELETED").flatMap { _ => redis.listRemove(key, "play-redis:DELETED", count = 0) }.map(_ => This).recoverWithDefault(This) diff --git a/src/main/scala/play/api/cache/redis/impl/RedisListJavaImpl.scala b/src/main/scala/play/api/cache/redis/impl/RedisListJavaImpl.scala index fee9850b..25d0a7f2 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisListJavaImpl.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisListJavaImpl.scala @@ -8,86 +8,86 @@ import play.cache.redis.AsyncRedisList class RedisListJavaImpl[Elem](internal: RedisList[Elem, Future])(implicit runtime: RedisRuntime) extends AsyncRedisList[Elem] { import JavaCompatibility._ - def This = this + def This: RedisListJavaImpl[Elem] = this private lazy val modifier: AsyncRedisList.AsyncRedisListModification[Elem] = newModifier() private lazy val viewer: AsyncRedisList.AsyncRedisListView[Elem] = newViewer() - protected def newViewer(): AsyncRedisList.AsyncRedisListView[Elem] = { + private def newViewer(): AsyncRedisList.AsyncRedisListView[Elem] = { new AsyncRedisListViewJavaImpl(internal.view) } - protected def newModifier(): AsyncRedisList.AsyncRedisListModification[Elem] = { + private def newModifier(): AsyncRedisList.AsyncRedisListModification[Elem] = { new AsyncRedisListModificationJavaImpl(internal.modify) } - def prepend(element: Elem): CompletionStage[AsyncRedisList[Elem]] = { + override def prepend(element: Elem): CompletionStage[AsyncRedisList[Elem]] = { async { implicit context => internal.prepend(element).map(_ => this) } } - def append(element: Elem): CompletionStage[AsyncRedisList[Elem]] = { + override def append(element: Elem): CompletionStage[AsyncRedisList[Elem]] = { async { implicit context => internal.append(element).map(_ => this) } } - def apply(index: Int): CompletionStage[Elem] = { + override def apply(index: Int): CompletionStage[Elem] = { async { implicit context => internal.apply(index) } } - def get(index: Int): CompletionStage[Optional[Elem]] = { + override def get(index: Int): CompletionStage[Optional[Elem]] = { async { implicit context => internal.get(index).map(_.asJava) } } - def headPop(): CompletionStage[Optional[Elem]] = { + override def headPop(): CompletionStage[Optional[Elem]] = { async { implicit context => internal.headPop.map(_.asJava) } } - def insertBefore(pivot: Elem, element: Elem): CompletionStage[Optional[java.lang.Long]] = { + override def insertBefore(pivot: Elem, element: Elem): CompletionStage[Optional[java.lang.Long]] = { async { implicit context => internal.insertBefore(pivot, element).map(_.map(Long.box).asJava) } } - def set(position: Int, element: Elem): CompletionStage[AsyncRedisList[Elem]] = { + override def set(position: Int, element: Elem): CompletionStage[AsyncRedisList[Elem]] = { async { implicit context => internal.set(position, element).map(_ => this) } } - def remove(element: Elem): CompletionStage[AsyncRedisList[Elem]] = { + override def remove(element: Elem): CompletionStage[AsyncRedisList[Elem]] = { async { implicit context => internal.remove(element).map(_ => this) } } - def remove(element: Elem, count: Int): CompletionStage[AsyncRedisList[Elem]] = { + override def remove(element: Elem, count: Int): CompletionStage[AsyncRedisList[Elem]] = { async { implicit context => internal.remove(element, count).map(_ => this) } } - def removeAt(position: Int): CompletionStage[AsyncRedisList[Elem]] = { + override def removeAt(position: Int): CompletionStage[AsyncRedisList[Elem]] = { async { implicit context => internal.removeAt(position).map(_ => this) } } - def view(): AsyncRedisList.AsyncRedisListView[Elem] = viewer + override def view(): AsyncRedisList.AsyncRedisListView[Elem] = viewer - def modify(): AsyncRedisList.AsyncRedisListModification[Elem] = modifier + override def modify(): AsyncRedisList.AsyncRedisListModification[Elem] = modifier private class AsyncRedisListViewJavaImpl(view: internal.RedisListView) extends AsyncRedisList.AsyncRedisListView[Elem] { - def slice(from: Int, end: Int): CompletionStage[JavaList[Elem]] = { + override def slice(from: Int, end: Int): CompletionStage[JavaList[Elem]] = { async { implicit context => view.slice(from, end).map(_.asJava) } @@ -96,15 +96,15 @@ class RedisListJavaImpl[Elem](internal: RedisList[Elem, Future])(implicit runtim private class AsyncRedisListModificationJavaImpl(modification: internal.RedisListModification) extends AsyncRedisList.AsyncRedisListModification[Elem] { - def collection(): AsyncRedisList[Elem] = This + override def collection(): AsyncRedisList[Elem] = This - def clear(): CompletionStage[AsyncRedisList.AsyncRedisListModification[Elem]] = { + override def clear(): CompletionStage[AsyncRedisList.AsyncRedisListModification[Elem]] = { async { implicit context => modification.clear().map(_ => this) } } - def slice(from: Int, end: Int): CompletionStage[AsyncRedisList.AsyncRedisListModification[Elem]] = { + override def slice(from: Int, end: Int): CompletionStage[AsyncRedisList.AsyncRedisListModification[Elem]] = { async { implicit context => modification.slice(from, end).map(_ => this) } diff --git a/src/main/scala/play/api/cache/redis/impl/RedisMapImpl.scala b/src/main/scala/play/api/cache/redis/impl/RedisMapImpl.scala index 960eb716..7704f71b 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisMapImpl.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisMapImpl.scala @@ -1,6 +1,6 @@ package play.api.cache.redis.impl -import scala.language.{higherKinds, implicitConversions} +import scala.language.implicitConversions import scala.reflect.ClassTag import play.api.cache.redis._ @@ -14,27 +14,39 @@ private[impl] class RedisMapImpl[Elem: ClassTag, Result[_]](key: String, redis: @inline private def This: This = this - def add(field: String, value: Elem) = redis.hashSet(key, field, value).map(_ => This).recoverWithDefault(This) + override def add(field: String, value: Elem): Result[This] = + redis.hashSet(key, field, value).map(_ => This).recoverWithDefault(This) - def get(field: String) = redis.hashGet[Elem](key, field).recoverWithDefault(None) + override def get(field: String): Result[Option[Elem]] = + redis.hashGet[Elem](key, field).recoverWithDefault(None) - def getFields(fields: Iterable[String]): Result[Seq[Option[Elem]]] = redis.hashGet[Elem](key, fields.toSeq).recoverWithDefault(Seq.fill(fields.size)(None)) + override def getFields(fields: Iterable[String]): Result[Seq[Option[Elem]]] = + redis.hashGet[Elem](key, fields.toSeq).recoverWithDefault(Seq.fill(fields.size)(None)) - def contains(field: String) = redis.hashExists(key, field).recoverWithDefault(false) + override def contains(field: String): Result[Boolean] = + redis.hashExists(key, field).recoverWithDefault(false) - def remove(fields: String*) = redis.hashRemove(key, fields: _*).map(_ => This).recoverWithDefault(This) + override def remove(fields: String*): Result[This] = + redis.hashRemove(key, fields: _*).map(_ => This).recoverWithDefault(This) - def increment(field: String, incrementBy: Long) = redis.hashIncrement(key, field, incrementBy).recoverWithDefault(incrementBy) + override def increment(field: String, incrementBy: Long): Result[Long] = + redis.hashIncrement(key, field, incrementBy).recoverWithDefault(incrementBy) - def toMap = redis.hashGetAll[Elem](key).recoverWithDefault(Map.empty) + override def toMap: Result[Map[String, Elem]] = + redis.hashGetAll[Elem](key).recoverWithDefault(Map.empty) - def keySet = redis.hashKeys(key).recoverWithDefault(Set.empty) + override def keySet: Result[Set[String]] = + redis.hashKeys(key).recoverWithDefault(Set.empty) - def values = redis.hashValues[Elem](key).recoverWithDefault(Set.empty) + override def values: Result[Set[Elem]] = + redis.hashValues[Elem](key).recoverWithDefault(Set.empty) - def size = redis.hashSize(key).recoverWithDefault(0) + override def size: Result[Long] = + redis.hashSize(key).recoverWithDefault(0) - def isEmpty = redis.hashSize(key).map(_ == 0).recoverWithDefault(true) + override def isEmpty: Result[Boolean] = + redis.hashSize(key).map(_ === 0).recoverWithDefault(true) - def nonEmpty = redis.hashSize(key).map(_ > 0).recoverWithDefault(false) + override def nonEmpty: Result[Boolean] = + redis.hashSize(key).map(_ > 0).recoverWithDefault(false) } diff --git a/src/main/scala/play/api/cache/redis/impl/RedisPrefix.scala b/src/main/scala/play/api/cache/redis/impl/RedisPrefix.scala index a2730e92..308ccd36 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisPrefix.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisPrefix.scala @@ -11,16 +11,16 @@ sealed trait RedisPrefix extends Any { @inline def unprefixed(key: Seq[String]): Seq[String] } -class RedisPrefixImpl(val prefix: String) extends AnyVal with RedisPrefix { - @inline def prefixed(key: String) = s"$prefix:$key" - @inline def unprefixed(key: String) = key.drop(prefix.length + 1) - @inline def prefixed(keys: Seq[String]) = keys.map(prefixed) - @inline def unprefixed(keys: Seq[String]) = keys.map(unprefixed) +final class RedisPrefixImpl(val prefix: String) extends AnyVal with RedisPrefix { + @inline override def prefixed(key: String) = s"$prefix:$key" + @inline override def unprefixed(key: String): String = key.drop(prefix.length + 1) + @inline override def prefixed(keys: Seq[String]): Seq[String] = keys.map(prefixed) + @inline override def unprefixed(keys: Seq[String]): Seq[String] = keys.map(unprefixed) } object RedisEmptyPrefix extends RedisPrefix { - @inline def prefixed(key: String) = key - @inline def unprefixed(key: String) = key - @inline def prefixed(keys: Seq[String]) = keys - @inline def unprefixed(keys: Seq[String]) = keys + @inline override def prefixed(key: String): String = key + @inline override def unprefixed(key: String): String = key + @inline override def prefixed(keys: Seq[String]): Seq[String] = keys + @inline override def unprefixed(keys: Seq[String]): Seq[String] = keys } diff --git a/src/main/scala/play/api/cache/redis/impl/RedisRuntime.scala b/src/main/scala/play/api/cache/redis/impl/RedisRuntime.scala index 212f04d7..8d91866f 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisRuntime.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisRuntime.scala @@ -19,7 +19,7 @@ private[redis] trait RedisRuntime extends connector.RedisRuntime { implicit def timeout: akka.util.Timeout } -private[redis] case class RedisRuntimeImpl( +private[redis] final case class RedisRuntimeImpl( name: String, context: ExecutionContext, policy: RecoveryPolicy, diff --git a/src/main/scala/play/api/cache/redis/impl/RedisSetImpl.scala b/src/main/scala/play/api/cache/redis/impl/RedisSetImpl.scala index e96ca41a..355270f0 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisSetImpl.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisSetImpl.scala @@ -1,10 +1,10 @@ package play.api.cache.redis.impl -import scala.language.{higherKinds, implicitConversions} -import scala.reflect.ClassTag - import play.api.cache.redis._ +import scala.language.implicitConversions +import scala.reflect.ClassTag + /**

Implementation of Set API using redis-server cache implementation.

*/ private[impl] class RedisSetImpl[Elem: ClassTag, Result[_]](key: String, redis: RedisConnector)(implicit builder: Builders.ResultBuilder[Result], runtime: RedisRuntime) extends RedisSet[Elem, Result] { @@ -14,31 +14,31 @@ private[impl] class RedisSetImpl[Elem: ClassTag, Result[_]](key: String, redis: @inline private def This: This = this - def add(elements: Elem*) = { + override def add(elements: Elem*): Result[RedisSet[Elem, Result]] = { redis.setAdd(key, elements: _*).map(_ => This).recoverWithDefault(This) } - def contains(element: Elem) = { + override def contains(element: Elem): Result[Boolean] = { redis.setIsMember(key, element).recoverWithDefault(false) } - def remove(element: Elem*) = { + override def remove(element: Elem*): Result[RedisSet[Elem, Result]] = { redis.setRemove(key, element: _*).map(_ => This).recoverWithDefault(This) } - def toSet = { + override def toSet: Result[Set[Elem]] = { redis.setMembers[Elem](key).recoverWithDefault(Set.empty) } - def size = { + override def size: Result[Long] = { redis.setSize(key).recoverWithDefault(0) } - def isEmpty = { - redis.setSize(key).map(_ == 0).recoverWithDefault(true) + override def isEmpty: Result[Boolean] = { + redis.setSize(key).map(_ === 0).recoverWithDefault(true) } - def nonEmpty = { + override def nonEmpty: Result[Boolean] = { redis.setSize(key).map(_ > 0).recoverWithDefault(false) } } diff --git a/src/main/scala/play/api/cache/redis/impl/RedisSortedSetImpl.scala b/src/main/scala/play/api/cache/redis/impl/RedisSortedSetImpl.scala index 84e1bb43..b7a068a0 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisSortedSetImpl.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisSortedSetImpl.scala @@ -2,7 +2,7 @@ package play.api.cache.redis.impl import play.api.cache.redis._ -import scala.language.{higherKinds, implicitConversions} +import scala.language.implicitConversions import scala.reflect.ClassTag /**

Implementation of Set API using redis-server cache implementation.

*/ @@ -42,7 +42,7 @@ private[impl] class RedisSortedSetImpl[Elem: ClassTag, Result[_]]( redis.sortedSetSize(key).recoverWithDefault(0) override def isEmpty: Result[Boolean] = - builder.map(size)(_ == 0) + builder.map(size)(_ === 0) override def nonEmpty: Result[Boolean] = builder.map(isEmpty)(x => !x) diff --git a/src/main/scala/play/api/cache/redis/impl/SyncRedis.scala b/src/main/scala/play/api/cache/redis/impl/SyncRedis.scala index c82330d9..84c6c503 100644 --- a/src/main/scala/play/api/cache/redis/impl/SyncRedis.scala +++ b/src/main/scala/play/api/cache/redis/impl/SyncRedis.scala @@ -8,20 +8,21 @@ import play.api.cache.redis._ /** * Implementation of **synchronous** and **blocking** Redis API. It also implements standard Play Scala CacheApi */ -private[impl] class SyncRedis(redis: RedisConnector)(implicit runtime: RedisRuntime) extends RedisCache(redis, Builders.SynchronousBuilder) with CacheApi { +private[impl] class SyncRedis(redis: RedisConnector)(implicit runtime: RedisRuntime) extends RedisCache[SynchronousResult](redis, Builders.SynchronousBuilder) with CacheApi { // helpers for dsl import dsl._ - override def getOrElse[T: ClassTag](key: String, expiration: Duration)(orElse: => T) = key.prefixed { key => - // note: this method is overridden so the `orElse` won't be included in the timeout - // compute orElse and try to set it into the cache - def computeAndSet = { - // compute - val value = orElse - // set the value and finally return the computed value regardless the result of set - runtime.invocation.invoke(redis.set(key, value, expiration), thenReturn = value) recoverWithDefault value + override def getOrElse[T: ClassTag](key: String, expiration: Duration)(orElse: => T): T = + key.prefixed { key => + // note: this method is overridden so the `orElse` won't be included in the timeout + // compute orElse and try to set it into the cache + def computeAndSet: SynchronousResult[T] = { + // compute + val value = orElse + // set the value and finally return the computed value regardless the result of set + runtime.invocation.invoke(redis.set(key, value, expiration), thenReturn = value).recoverWithDefault(value) + } + // try to hit the cache, return on hit, set and return orElse on miss or failure + redis.get[T](key).recoverWithDefault(Some(computeAndSet)).getOrElse(computeAndSet) } - // try to hit the cache, return on hit, set and return orElse on miss or failure - redis.get[T](key).recoverWithDefault(Some(computeAndSet)) getOrElse computeAndSet - } } diff --git a/src/main/scala/play/api/cache/redis/impl/dsl.scala b/src/main/scala/play/api/cache/redis/impl/dsl.scala index 4b85c41b..9b471a7d 100644 --- a/src/main/scala/play/api/cache/redis/impl/dsl.scala +++ b/src/main/scala/play/api/cache/redis/impl/dsl.scala @@ -1,7 +1,7 @@ package play.api.cache.redis.impl import scala.concurrent.{ExecutionContext, Future} -import scala.language.{higherKinds, implicitConversions} +import scala.language.implicitConversions import play.api.cache.redis._ @@ -23,7 +23,7 @@ private[impl] object dsl { implicit class RecoveryFuture[T](future: => Future[T]) { /** Transforms the promise into desired builder results, possibly recovers with provided default value */ - @inline def recoverWithDefault[Result[X]](default: => T)(implicit builder: Builders.ResultBuilder[Result], runtime: RedisRuntime): Result[T] = + @inline def recoverWithDefault[Result[_]](default: => T)(implicit builder: Builders.ResultBuilder[Result], runtime: RedisRuntime): Result[T] = builder.toResult(future, Future.successful(default)) /** recovers from the execution but returns future, not Result */ @@ -37,12 +37,12 @@ private[impl] object dsl { /** helper function enabling us to recover from command execution */ implicit class RecoveryUnitFuture(val future: Future[Unit]) extends AnyVal { /** Transforms the promise into desired builder results, possibly recovers with provided default value */ - @inline def recoverWithDone[Result[X]](implicit builder: Builders.ResultBuilder[Result], runtime: RedisRuntime): Result[Done] = + @inline def recoverWithDone[Result[_]](implicit builder: Builders.ResultBuilder[Result], runtime: RedisRuntime): Result[Done] = builder.toResult(future.map(unitAsDone), Future.successful(Done)) } /** maps units into akka.Done */ - @inline private def unitAsDone(unit: Unit) = Done + @inline private def unitAsDone(unit: Unit): Done = Done /** applies prefixer to produce final cache key */ implicit class CacheKey(val key: String) extends AnyVal { diff --git a/src/main/scala/play/api/cache/redis/package.scala b/src/main/scala/play/api/cache/redis/package.scala index 3599d765..cc121201 100644 --- a/src/main/scala/play/api/cache/redis/package.scala +++ b/src/main/scala/play/api/cache/redis/package.scala @@ -14,4 +14,14 @@ package object redis extends AnyRef with ExpirationImplicits with ExceptionImpli private[redis] type RedisInstance = configuration.RedisInstance private[redis] type RedisInstanceProvider = configuration.RedisInstanceProvider + + @SuppressWarnings(Array("org.wartremover.warts.Equals")) + implicit final class AnyOps[A](private val self: A) extends AnyVal { + def ===(other: A): Boolean = self == other + } + + @SuppressWarnings(Array("org.wartremover.warts.Equals")) + implicit final class HigherKindedAnyOps[F[_]](private val self: F[_]) extends AnyVal { + def =~=(other: F[_]): Boolean = self == other + } } diff --git a/src/test/scala/play/api/cache/redis/RedisCacheModuleSpec.scala b/src/test/scala/play/api/cache/redis/RedisCacheModuleSpec.scala index a2dca40f..468088af 100644 --- a/src/test/scala/play/api/cache/redis/RedisCacheModuleSpec.scala +++ b/src/test/scala/play/api/cache/redis/RedisCacheModuleSpec.scala @@ -1,7 +1,7 @@ package play.api.cache.redis import akka.actor.ActorSystem -import play.api.cache.redis.configuration.{RedisStandalone, RedisTimeouts} +import play.api.cache.redis.configuration.{RedisHost, RedisSettings, RedisStandalone, RedisTimeouts} import play.api.cache.redis.test._ import play.api.inject._ import play.api.inject.guice.GuiceApplicationBuilder @@ -122,20 +122,25 @@ import Helpers._ injector.checkBinding[CacheAsyncApi] } - private object MyRedisInstance extends RedisStandalone { - override lazy val name: String = defaultCacheName - override lazy val invocationContext: String = "akka.actor.default-dispatcher" - override lazy val invocationPolicy: String = "lazy" - override lazy val timeout: RedisTimeouts = RedisTimeouts(1.second) - override lazy val recovery: String = "log-and-default" - override lazy val source: String = "my-instance" - override lazy val prefix: Option[String] = None - override lazy val host: String = container.host - override lazy val port: Int = container.mappedPort(defaultPort) - override lazy val database: Option[Int] = None - override lazy val username: Option[String] = None - override lazy val password: Option[String] = None - } + private lazy val MyRedisInstance: RedisStandalone = + RedisStandalone( + name = defaultCacheName, + host = RedisHost( + host = container.host, + port = container.mappedPort(defaultPort), + database = None, + username = None, + password = None, + ), + settings = RedisSettings( + dispatcher = "akka.actor.default-dispatcher", + invocationPolicy = "lazy", + timeout = RedisTimeouts(1.second), + recovery = "log-and-default", + source = "my-instance", + prefix = None, + ) + ) private def binding[T: ClassTag]: BindingKey[T] = BindingKey(implicitly[ClassTag[T]].runtimeClass.asInstanceOf[Class[T]]) diff --git a/src/test/scala/play/api/cache/redis/configuration/RedisInstanceManagerSpec.scala b/src/test/scala/play/api/cache/redis/configuration/RedisInstanceManagerSpec.scala index 35704f3a..bf38cb6f 100644 --- a/src/test/scala/play/api/cache/redis/configuration/RedisInstanceManagerSpec.scala +++ b/src/test/scala/play/api/cache/redis/configuration/RedisInstanceManagerSpec.scala @@ -267,9 +267,9 @@ class RedisInstanceManagerSpec extends UnitSpec with ImplicitOptionMaterializati override def caches: Set[String] = providers.map(_.name).toSet - override def instanceOfOption(name: String): Option[RedisInstanceProvider] = providers.find(_.name == name) + override def instanceOfOption(name: String): Option[RedisInstanceProvider] = providers.find(_.name === name) - override def defaultInstance: RedisInstanceProvider = providers.find(_.name == default) getOrElse { + override def defaultInstance: RedisInstanceProvider = providers.find(_.name === default) getOrElse { throw new RuntimeException("Default instance is not defined.") } diff --git a/src/test/scala/play/api/cache/redis/connector/RedisClusterSpec.scala b/src/test/scala/play/api/cache/redis/connector/RedisClusterSpec.scala index c5658052..759d6258 100644 --- a/src/test/scala/play/api/cache/redis/connector/RedisClusterSpec.scala +++ b/src/test/scala/play/api/cache/redis/connector/RedisClusterSpec.scala @@ -11,6 +11,8 @@ import scala.concurrent.{ExecutionContext, Future} class RedisClusterSpec extends IntegrationSpec with RedisClusterContainer { + override protected def testTimeout: FiniteDuration = 60.seconds + test("pong on ping") { connector => connector.ping().assertingSuccess } @@ -57,10 +59,6 @@ class RedisClusterSpec extends IntegrationSpec with RedisClusterContainer { def test(name: String)(f: RedisConnector => Future[Assertion]): Unit = { name in { - implicit val system: ActorSystem = ActorSystem("test", classLoader = Some(getClass.getClassLoader)) - implicit val runtime: RedisRuntime = RedisRuntime("cluster", syncTimeout = 5.seconds, ExecutionContext.global, new LogAndFailPolicy, LazyInvocation) - implicit val application: StoppableApplication = StoppableApplication(system) - val serializer = new AkkaSerializerImpl(system) lazy val clusterInstance = RedisCluster( name = "play", @@ -73,16 +71,34 @@ class RedisClusterSpec extends IntegrationSpec with RedisClusterContainer { ) ) - application.runAsyncInApplication { - for { - connector <- Future(new RedisConnectorProvider(clusterInstance, serializer).get) - // initialize the connector by flushing the database - keys <- connector.matching("*") - _ <- Future.sequence(keys.map(connector.remove(_))) - // run the test - _ <- f(connector) - } yield Passed + def runTest: Future[Assertion] = { + implicit val system: ActorSystem = ActorSystem("test", classLoader = Some(getClass.getClassLoader)) + implicit val runtime: RedisRuntime = RedisRuntime("cluster", syncTimeout = 5.seconds, ExecutionContext.global, new LogAndFailPolicy, LazyInvocation) + implicit val application: StoppableApplication = StoppableApplication(system) + val serializer = new AkkaSerializerImpl(system) + + application.runAsyncInApplication { + for { + connector <- Future(new RedisConnectorProvider(clusterInstance, serializer).get) + // initialize the connector by flushing the database + keys <- connector.matching("*") + _ <- Future.sequence(keys.map(connector.remove(_))) + // run the test + _ <- f(connector) + } yield Passed + } } + + @SuppressWarnings(Array("org.wartremover.warts.Recursion")) + def makeAttempt(id: Int): Future[Assertion] = { + runTest.recoverWith { + case cause: Throwable if id <= 1 => + log.error(s"RedisClusterSpec: test '$name', attempt $id failed, will retry", cause) + Future.waitFor(1.second).flatMap(_ => makeAttempt(id + 1)) + } + } + + makeAttempt(1) } } } diff --git a/src/test/scala/play/api/cache/redis/impl/AsyncJavaRedisSpec.scala b/src/test/scala/play/api/cache/redis/impl/AsyncJavaRedisSpec.scala index 34bbc0a9..32519527 100644 --- a/src/test/scala/play/api/cache/redis/impl/AsyncJavaRedisSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/AsyncJavaRedisSpec.scala @@ -113,21 +113,6 @@ import Helpers._ } yield Passed } - test("get optional (none)") { (async, cache) => - for { - _ <- async.expect.getClassTag(cacheKey, None) - _ <- cache.getOptional[String](cacheKey).assertingEqual(Optional.ofNullable(null)) - } yield Passed - } - - test("get optional (some)") { (async, cache) => - for { - _ <- async.expect.getClassTag(cacheKey, Some(classTag)) - _ <- async.expect.get[String](cacheKey, Some(cacheValue)) - _ <- cache.getOptional[String](cacheKey).assertingEqual(Optional.ofNullable(cacheValue)) - } yield Passed - } - test("remove") { (async, cache) => for { _ <- async.expect.remove(cacheKey) diff --git a/src/test/scala/play/api/cache/redis/impl/AsyncRedisMock.scala b/src/test/scala/play/api/cache/redis/impl/AsyncRedisMock.scala index a60d7bb6..98fc3df0 100644 --- a/src/test/scala/play/api/cache/redis/impl/AsyncRedisMock.scala +++ b/src/test/scala/play/api/cache/redis/impl/AsyncRedisMock.scala @@ -34,7 +34,7 @@ private[impl] trait AsyncRedisMock { this: AsyncMockFactoryBase => private def classTagValue: Any => String = { case null => "null" - case v if v.getClass == classOf[String] => "java.lang.String" + case v if v.getClass =~= classOf[String] => "java.lang.String" case other => throw new IllegalArgumentException(s"Unexpected value for classTag: ${other.getClass.getSimpleName}") } @@ -60,7 +60,7 @@ private[impl] trait AsyncRedisMock { this: AsyncMockFactoryBase => def setValue[T](key: String, value: T, duration: Duration): Future[Unit] = Future.successful { (async.set(_: String, _: Any, _: Duration)) - .expects(key, if (value == null) * else value, duration) + .expects(key, if (Option(value).isEmpty) * else value, duration) .returning(Future.successful(Done)) .once() } @@ -77,7 +77,7 @@ private[impl] trait AsyncRedisMock { this: AsyncMockFactoryBase => def setValueIfNotExists[T: ClassTag](key: String, value: T, duration: Duration, exists: Boolean): Future[Unit] = Future.successful { (async.setIfNotExists(_: String, _: Any, _: Duration)) - .expects(key, if (value == null) * else value, duration) + .expects(key, if (Option(value).isEmpty) * else value, duration) .returning(Future.successful(exists)) .once() } diff --git a/src/test/scala/play/api/cache/redis/impl/RedisConnectorMock.scala b/src/test/scala/play/api/cache/redis/impl/RedisConnectorMock.scala index 18ae2d44..6fc2fa34 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisConnectorMock.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisConnectorMock.scala @@ -115,7 +115,7 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def set[T](key: String, value: T, duration: Duration = Duration.Inf, setIfNotExists: Boolean = false, result: Future[Boolean]): Future[Unit] = Future.successful { (connector.set(_: String, _: Any, _: Duration, _: Boolean)) - .expects(key, if (value == null) * else value, duration, setIfNotExists) + .expects(key, if (Option(value).isEmpty) * else value, duration, setIfNotExists) .returning(result) .once() } diff --git a/src/test/scala/play/api/cache/redis/test/BaseSpec.scala b/src/test/scala/play/api/cache/redis/test/BaseSpec.scala index 2f173560..aef52b13 100644 --- a/src/test/scala/play/api/cache/redis/test/BaseSpec.scala +++ b/src/test/scala/play/api/cache/redis/test/BaseSpec.scala @@ -86,7 +86,7 @@ trait TimeLimitedSpec extends AsyncTestSuiteMixin with AsyncUtilities { trait AsyncUtilities { this: AsyncTestSuite => implicit class FutureAsyncUtilities(future: Future.type) { - def after[T](duration: FiniteDuration, value: T): Future[T] = + def after[T](duration: FiniteDuration, value: => T): Future[T] = Future(Await.result(Future.never, duration)).recover(_ => value) def waitFor(duration: FiniteDuration): Future[Unit] = @@ -95,7 +95,7 @@ trait AsyncUtilities { this: AsyncTestSuite => } -trait FutureAssertions { this: BaseSpec => +trait FutureAssertions extends AsyncUtilities { this: BaseSpec => import scala.jdk.FutureConverters._ implicit def completionStageToFutureOps[T](future: CompletionStage[T]): FutureAssertionOps[T] = @@ -136,7 +136,7 @@ trait FutureAssertions { this: BaseSpec => def assertTimeout(timeout: FiniteDuration): Future[Assertion] = { Future.firstCompletedOf( Seq( - Future(Thread.sleep(timeout.toMillis)).map(_ => throw new TimeoutException(s"Expected timeout after $timeout")), + Future.after(timeout, throw new TimeoutException(s"Expected timeout after $timeout")), future.map(value => fail(s"Expected timeout but got $value")) ) ).assertingFailure[TimeoutException] diff --git a/src/test/scala/play/api/cache/redis/test/RedisClusterContainer.scala b/src/test/scala/play/api/cache/redis/test/RedisClusterContainer.scala index 28648902..bdc62aee 100644 --- a/src/test/scala/play/api/cache/redis/test/RedisClusterContainer.scala +++ b/src/test/scala/play/api/cache/redis/test/RedisClusterContainer.scala @@ -7,7 +7,7 @@ import scala.concurrent.duration._ trait RedisClusterContainer extends RedisContainer { this: Suite => - private val log = Logger("play.api.cache.redis.test") + protected val log = Logger("play.api.cache.redis.test") protected def redisMaster = 4 @@ -15,7 +15,7 @@ trait RedisClusterContainer extends RedisContainer { this: Suite => protected final def initialPort = 7000 - private val waitForStart = 4.seconds + private val waitForStart = 6.seconds override protected lazy val redisConfig: RedisContainerConfig = RedisContainerConfig( @@ -30,6 +30,7 @@ trait RedisClusterContainer extends RedisContainer { this: Suite => ), ) + @SuppressWarnings(Array("org.wartremover.warts.ThreadSleep")) override def beforeAll(): Unit = { super.beforeAll() log.info(s"Waiting for Redis Cluster to start on ${container.containerIpAddress}, will wait for $waitForStart") diff --git a/src/test/scala/play/api/cache/redis/test/RedisContainer.scala b/src/test/scala/play/api/cache/redis/test/RedisContainer.scala index 448918f7..322d93bf 100644 --- a/src/test/scala/play/api/cache/redis/test/RedisContainer.scala +++ b/src/test/scala/play/api/cache/redis/test/RedisContainer.scala @@ -5,13 +5,16 @@ import org.scalatest.Suite import org.testcontainers.containers.FixedHostPortGenericContainer import org.testcontainers.containers.wait.strategy.Wait +import scala.annotation.nowarn + trait RedisContainer extends ForAllTestContainer { this: Suite => protected def redisConfig: RedisContainerConfig private lazy val config = redisConfig - //noinspection ScalaDeprecation + @nowarn("cat=deprecation") + @SuppressWarnings(Array("org.wartremover.warts.ForeachEntry")) protected override final val newContainer: GenericContainer = { val container: FixedHostPortGenericContainer[_] = new FixedHostPortGenericContainer(config.redisDockerImage) container.withExposedPorts(config.redisMappedPorts.map(int2Integer): _*) diff --git a/src/test/scala/play/api/cache/redis/test/RedisSentinelContainer.scala b/src/test/scala/play/api/cache/redis/test/RedisSentinelContainer.scala index 00a9d087..e7a422db 100644 --- a/src/test/scala/play/api/cache/redis/test/RedisSentinelContainer.scala +++ b/src/test/scala/play/api/cache/redis/test/RedisSentinelContainer.scala @@ -32,6 +32,7 @@ trait RedisSentinelContainer extends RedisContainer { ), ) + @SuppressWarnings(Array("org.wartremover.warts.ThreadSleep")) override def beforeAll(): Unit = { super.beforeAll() log.info(s"Waiting for Redis Sentinel to start on ${container.containerIpAddress}, will wait for $waitForStart") From 0042f4efa70fbbaaca885a97c43e9e39ef6d1ba9 Mon Sep 17 00:00:00 2001 From: Karel Cemus Date: Sat, 3 Feb 2024 00:51:10 +0100 Subject: [PATCH 3/5] Scalafmt, Scalafix, and tpolecat (#275) * Installed scalafix SBT plugin * Installed tpolecat * Installed scalafmt --- .github/workflows/build-test.yml | 2 +- .scalafix.conf | 19 + .scalafmt.conf | 168 +++++ build.sbt | 38 +- project/CustomReleasePlugin.scala | 32 +- project/DocumentationUpdate.scala | 10 +- project/ReleaseUtilities.scala | 16 +- project/plugins.sbt | 9 +- .../java/play/cache/redis/AsyncRedisList.java | 22 +- .../scala/play/api/cache/redis/CacheApi.scala | 378 +++++++---- .../play/api/cache/redis/Expiration.scala | 14 +- .../play/api/cache/redis/RecoveryPolicy.scala | 101 +-- .../cache/redis/RedisCacheComponents.scala | 8 +- .../api/cache/redis/RedisCacheModule.scala | 64 +- .../api/cache/redis/RedisCollection.scala | 3 +- .../play/api/cache/redis/RedisList.scala | 385 ++++++----- .../scala/play/api/cache/redis/RedisMap.scala | 90 ++- .../scala/play/api/cache/redis/RedisSet.scala | 55 +- .../play/api/cache/redis/RedisSortedSet.scala | 65 +- .../cache/redis/configuration/Equals.scala | 4 +- .../configuration/HostnameResolver.scala | 1 + .../configuration/RedisConfigLoader.scala | 32 +- .../cache/redis/configuration/RedisHost.scala | 43 +- .../redis/configuration/RedisInstance.scala | 38 +- .../configuration/RedisInstanceManager.scala | 26 +- .../configuration/RedisInstanceProvider.scala | 83 +-- .../redis/configuration/RedisSettings.scala | 39 +- .../redis/configuration/RedisTimeouts.scala | 45 +- .../redis/connector/AkkaSerializer.scala | 160 +++-- .../redis/connector/ExpectedFuture.scala | 28 +- .../cache/redis/connector/RedisCommands.scala | 70 +- .../redis/connector/RedisConnector.scala | 627 +++++++++++------- .../redis/connector/RedisConnectorImpl.scala | 270 ++++---- .../connector/RedisConnectorProvider.scala | 9 +- .../redis/connector/RequestTimeout.scala | 39 +- .../api/cache/redis/connector/package.scala | 4 +- .../play/api/cache/redis/exceptions.scala | 36 +- .../api/cache/redis/impl/AsyncJavaRedis.scala | 179 +++-- .../api/cache/redis/impl/AsyncRedisImpl.scala | 8 +- .../play/api/cache/redis/impl/Builders.scala | 20 +- .../cache/redis/impl/InvocationPolicy.scala | 21 +- .../cache/redis/impl/JavaCompatibility.scala | 33 +- .../api/cache/redis/impl/RedisCache.scala | 53 +- .../api/cache/redis/impl/RedisCaches.scala | 11 +- .../api/cache/redis/impl/RedisListImpl.scala | 66 +- .../cache/redis/impl/RedisListJavaImpl.scala | 53 +- .../api/cache/redis/impl/RedisMapImpl.scala | 6 +- .../cache/redis/impl/RedisMapJavaImpl.scala | 32 +- .../api/cache/redis/impl/RedisPrefix.scala | 6 +- .../api/cache/redis/impl/RedisRuntime.scala | 19 +- .../api/cache/redis/impl/RedisSetImpl.scala | 23 +- .../cache/redis/impl/RedisSetJavaImpl.scala | 13 +- .../cache/redis/impl/RedisSortedSetImpl.scala | 12 +- .../play/api/cache/redis/impl/SyncRedis.scala | 8 +- .../scala/play/api/cache/redis/impl/dsl.scala | 37 +- .../scala/play/api/cache/redis/package.scala | 5 +- .../play/api/cache/redis/ExpirationSpec.scala | 5 +- .../api/cache/redis/RecoveryPolicySpec.scala | 5 +- .../redis/RedisCacheComponentsSpec.scala | 14 +- .../cache/redis/RedisCacheModuleSpec.scala | 65 +- .../configuration/HostnameResolverSpec.scala | 1 + .../redis/configuration/RedisHostSpec.scala | 17 +- .../RedisInstanceManagerSpec.scala | 141 ++-- .../RedisInstanceProviderSpec.scala | 14 +- .../configuration/RedisTimeoutsSpec.scala | 9 +- .../redis/connector/ExpectedFutureSpec.scala | 10 +- .../redis/connector/FailEagerlySpec.scala | 11 +- .../redis/connector/RedisClusterSpec.scala | 26 +- .../connector/RedisConnectorFailureSpec.scala | 133 ++-- .../connector/RedisRequestTimeoutSpec.scala | 16 +- .../redis/connector/RedisSentinelSpec.scala | 21 +- .../redis/connector/RedisStandaloneSpec.scala | 128 ++-- .../redis/connector/SerializerSpec.scala | 17 +- .../cache/redis/impl/AsyncJavaRedisSpec.scala | 64 +- .../api/cache/redis/impl/AsyncRedisMock.scala | 104 +-- .../api/cache/redis/impl/AsyncRedisSpec.scala | 17 +- .../api/cache/redis/impl/BuildersSpec.scala | 35 +- .../redis/impl/InvocationPolicySpec.scala | 3 +- .../api/cache/redis/impl/RedisCacheSpec.scala | 26 +- .../cache/redis/impl/RedisConnectorMock.scala | 162 +++-- .../cache/redis/impl/RedisJavaListSpec.scala | 8 +- .../cache/redis/impl/RedisJavaMapSpec.scala | 10 +- .../cache/redis/impl/RedisJavaSetSpec.scala | 12 +- .../cache/redis/impl/RedisListJavaMock.scala | 67 +- .../api/cache/redis/impl/RedisListSpec.scala | 28 +- .../cache/redis/impl/RedisMapJavaMock.scala | 27 +- .../api/cache/redis/impl/RedisMapSpec.scala | 12 +- .../cache/redis/impl/RedisPrefixSpec.scala | 12 +- .../cache/redis/impl/RedisRuntimeMock.scala | 13 +- .../cache/redis/impl/RedisRuntimeSpec.scala | 63 +- .../cache/redis/impl/RedisSetJavaMock.scala | 23 +- .../api/cache/redis/impl/RedisSetSpec.scala | 10 +- .../cache/redis/impl/RedisSortedSetSpec.scala | 12 +- .../api/cache/redis/impl/SyncRedisSpec.scala | 34 +- .../play/api/cache/redis/test/BaseSpec.scala | 84 +-- .../cache/redis/test/FakeApplication.scala | 1 + .../redis/test/ForAllTestContainer.scala | 11 +- .../play/api/cache/redis/test/Helpers.scala | 6 +- .../api/cache/redis/test/OrElseProbe.scala | 3 +- .../redis/test/RedisClusterContainer.scala | 11 +- .../api/cache/redis/test/RedisContainer.scala | 8 +- .../api/cache/redis/test/RedisLogger.scala | 8 +- .../redis/test/RedisSentinelContainer.scala | 11 +- .../cache/redis/test/RedisSettingsTest.scala | 3 +- .../redis/test/RedisStandaloneContainer.scala | 3 +- .../redis/test/StoppableApplication.scala | 17 +- 106 files changed, 3003 insertions(+), 2176 deletions(-) create mode 100644 .scalafix.conf create mode 100644 .scalafmt.conf diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 047ffdd9..460d02a7 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -35,4 +35,4 @@ jobs: env: COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - sbt --client "+clean; +compile; +Test/compile; +coverage; +test; +coverageReport; +coveralls;" + sbt --client "+clean; +compile; +Test/compile; lint; +coverage; +test; +coverageReport; +coveralls;" diff --git a/.scalafix.conf b/.scalafix.conf new file mode 100644 index 00000000..5b164b84 --- /dev/null +++ b/.scalafix.conf @@ -0,0 +1,19 @@ +rules = [ + ExplicitResultTypes + LeakingImplicitClassVal + NoAutoTupling + NoValInForComprehension + OrganizeImports + ProcedureSyntax + RedundantSyntax + RemoveUnused +] + +RemoveUnused { + imports = false // handled by OrganizeImports +} + +OrganizeImports { + # Allign with IntelliJ IDEA so that they don't fight each other + groupedImports = Merge +} diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 00000000..aadbc820 --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1,168 @@ +version = "3.7.17" +runner.dialect = scala213 +maxColumn = 300 + +// https://scalameta.org/scalafmt/docs/configuration.html#projectgit +project.git = true + +fileOverride { + "glob:**/project/**.scala" { + runner.dialect = scala212 + } + "glob:**/*.sbt" { + runner.dialect = sbt1 + } +} + +// --------------------- +// alignment +// --------------------- +align.preset = most +// align code behind -> e.g. in for ... yield +align.arrowEnumeratorGenerator = true +// multiline strings +align.stripMargin = true +assumeStandardLibraryStripMargin = true +// align also multiline statements +align.multiline = true +// don't break lines just because of alignment +align.allowOverflow = true +// align '=' +align.tokens."+" = [ + { + code = "=" + owners = [{ + parents = ["Template"] + }] + } + { + code = "=" + owners = [{ + regex = "Enumerator\\." + parents = ["Term\\.ForYield"] + }] + } +] +// align vals and types equally +align.treeCategory."Defn.Val" = "given/val/var/def" +align.treeCategory."Defn.Type" = "given/val/var/def" + +// --------------------- +// indentation +// --------------------- +// identation of function calls +indent.callSite = 2 +// indentation of "if" and "while" +indent.ctrlSite = 2 +// identation of definition of parameters in cls and fce +indent.defnSite = 2 +// indentation of "extends" and "with" +indent.extendSite = 2 +// indent nested match and infix expressions +// this causes issues with // comments, indents them +// indent.relativeToLhsLastLine = [match, infix] +// indentation of multiline nested parentheses +binPack.indentCallSiteOnce = false +binPack.indentCallSiteSingleArg = false + + +// --------------------- +// indent operator +// --------------------- +// apply indentation exempts only in some cases +//indentOperator.preset = "spray" +// exempt indentation of 2nd and additional lines in special cases +indentOperator.exemptScope = all + + +// --------------------- +// new lines +// --------------------- +// remove line breaks, leads to horizontal code +//newlines.source = fold +// newlines around statements +newlines.topLevelStatementBlankLines = [ + { + blanks = 1 + } + { + minBreaks = 1 + blanks = 1 + } +] +// newlines after package, class, trait header if body is not trivial +newlines.topLevelBodyIfMinStatements = [] +newlines.topLevelBodyMinStatements = 2 +newlines.beforeTemplateBodyIfBreakInParentCtors = true +// force newline in case/if/while when the body is multiline +newlines.beforeMultiline = keep +newlines.alwaysBeforeElseAfterCurlyIf = false +// break when assignment is multiline +// newlines.forceBeforeMultilineAssign = any +// formatting of bounds of type parameters: upper <:, lower >:, view <%, and context : bounds +newlines.beforeTypeBounds = unfold +// newlines in { case ... => f() } lambdas +// newlines.beforeCurlyLambdaParams = never +// newlines.afterCurlyLambdaParams = squash +// spaces after implicit in fce parameter list +//newlines.implicitParamListModifierForce = [after] +//newlines.avoidForSimpleOverflow = [punct, tooLong, slc] +// skip newlines in the result type +newlines.avoidInResultType = true +// don't put newline before : of the result type if line overflows +newlines.sometimesBeforeColonInMethodReturnType = false +// chains of calls +newlines.selectChains = keep +// in interpolation +newlines.inInterpolation = avoid +// dangling parenteses +danglingParentheses.preset = true +danglingParentheses.exclude = [] + +// config style arguments +//optIn.configStyleArguments = true +//runner.optimizer.forceConfigStyleOnOffset = 80 +//runner.optimizer.forceConfigStyleMinArgCount = 1 + + +// rewrite rules +// - drop redundant braces like { } around a block +// - drop redundant parentheses like ( ) around a statement +// - sort implicit final private lazy +// - prefer for { } yield () over for (;) yield () +// - organize imports +rewrite.rules = [RedundantBraces, RedundantParens, SortModifiers, PreferCurlyFors, Imports] +rewrite.redundantBraces.stringInterpolation = true +rewrite.imports.sort = scalastyle +rewrite.trailingCommas.style = always +// rewrite tokens to different characters +rewriteTokens = { + "⇒": "=>" + "→": "->" + "←": "<-" +} + +// vertical align a line breaks +verticalMultiline.atDefnSite = true +verticalMultiline.arityThreshold = 100 +verticalMultiline.newlineAfterOpenParen = true + +// wrap long standalone comments +comments.wrap = standalone +comments.wrapStandaloneSlcAsSlc = true + +// javadoc formatting +docstrings.style = SpaceAsterisk +docstrings.blankFirstLine = yes +docstrings.removeEmpty = true +docstrings.oneline = fold +docstrings.wrapMaxColumn = 80 + +// all calls in chains start on a new line +includeNoParensInSelectChains = true +// keep chains on multiple lines if already multiline +optIn.breakChainOnFirstMethodDot = true + +// scala 3 config +rewrite.scala3.convertToNewSyntax = true +//rewrite.trailingCommas.style = "multiple" diff --git a/build.sbt b/build.sbt index ccad203a..e5817d35 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,7 @@ +import org.typelevel.sbt.tpolecat.DevMode import sbt.Keys._ import sbt._ +import org.typelevel.scalacoptions._ normalizedName := "play-redis" @@ -17,25 +19,25 @@ playVersion := "2.9.0" libraryDependencies ++= Seq( // play framework cache API - "com.typesafe.play" %% "play-cache" % playVersion.value % Provided, + "com.typesafe.play" %% "play-cache" % playVersion.value % Provided, // redis connector - "io.github.rediscala" %% "rediscala" % "1.14.0-akka", + "io.github.rediscala" %% "rediscala" % "1.14.0-akka", // test framework with mockito extension - "org.scalatest" %% "scalatest" % "3.2.17" % Test, - "org.scalamock" %% "scalamock" % "5.2.0" % Test, + "org.scalatest" %% "scalatest" % "3.2.17" % Test, + "org.scalamock" %% "scalamock" % "5.2.0" % Test, // test module for play framework - "com.typesafe.play" %% "play-test" % playVersion.value % Test, + "com.typesafe.play" %% "play-test" % playVersion.value % Test, // to run integration tests - "com.dimafeng" %% "testcontainers-scala-core" % "0.41.2" % Test + "com.dimafeng" %% "testcontainers-scala-core" % "0.41.2" % Test, ) resolvers ++= Seq( - "Typesafe repository" at "https://repo.typesafe.com/typesafe/releases/" + "Typesafe repository" at "https://repo.typesafe.com/typesafe/releases/", ) javacOptions ++= Seq("-Xlint:unchecked", "-encoding", "UTF-8") -scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked") +scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked", "-Ywarn-unused") enablePlugins(CustomReleasePlugin) @@ -44,6 +46,11 @@ coverageExcludedFiles := ".*exceptions.*" Test / test := (Test / testOnly).toTask(" * -- -l \"org.scalatest.Ignore\"").value +semanticdbEnabled := true +semanticdbOptions += "-P:semanticdb:synthetics:on" +semanticdbVersion := scalafixSemanticdb.revision +ThisBuild / scalafixScalaBinaryVersion := CrossVersion.binaryScalaVersion(scalaVersion.value) + wartremoverWarnings ++= Warts.allBut( Wart.Any, Wart.AnyVal, @@ -66,3 +73,18 @@ wartremoverWarnings ++= Warts.allBut( Wart.TryPartial, Wart.Var, ) + +tpolecatDevModeOptions ~= { opts => + opts.filterNot(Set(ScalacOptions.warnError)) +} + +Test / tpolecatExcludeOptions ++= Set( + ScalacOptions.warnValueDiscard, + ScalacOptions.warnNonUnitStatement, +) + +ThisBuild / tpolecatCiModeEnvVar := "CI" +ThisBuild / tpolecatDefaultOptionsMode := DevMode + +addCommandAlias("fix", "; scalafixAll; scalafmtAll; scalafmtSbt") +addCommandAlias("lint", "; scalafmtSbtCheck; scalafmtCheckAll; scalafixAll --check") diff --git a/project/CustomReleasePlugin.scala b/project/CustomReleasePlugin.scala index 6ddef4c7..c9545029 100644 --- a/project/CustomReleasePlugin.scala +++ b/project/CustomReleasePlugin.scala @@ -19,38 +19,37 @@ object CustomReleasePlugin extends AutoPlugin { override def requires: Plugins = ReleasePlugin && GitVersioning && Sonatype - private def customizedReleaseProcess: Seq[ReleaseStep] = { + private def customizedReleaseProcess: Seq[ReleaseStep] = Seq[ReleaseStep]( checkSnapshotDependencies, inquireVersions, DocumentationUpdate.updateDocumentation, ) - } - override def projectSettings: Seq[Setting[_]] = Seq[Setting[_]]( - publishMavenStyle := true, - pomIncludeRepository := { _ => false }, + override def projectSettings: Seq[Setting[?]] = Seq[Setting[?]]( + publishMavenStyle := true, + pomIncludeRepository := { _ => false }, // customized release process - releaseProcess := customizedReleaseProcess, + releaseProcess := customizedReleaseProcess, // release details - homepage := Some(url("https://github.com/karelcemus/play-redis")), - licenses := Seq("Apache 2" -> url("https://www.apache.org/licenses/LICENSE-2.0")), - scmInfo := Some( + homepage := Some(url("https://github.com/karelcemus/play-redis")), + licenses := Seq("Apache 2" -> url("https://www.apache.org/licenses/LICENSE-2.0")), + scmInfo := Some( ScmInfo( url("https://github.com/KarelCemus/play-i18n.git"), - "scm:git@github.com:KarelCemus/play-i18n.git" - ) + "scm:git@github.com:KarelCemus/play-i18n.git", + ), ), - developers := List( - Developer(id = "karel.cemus", name = "Karel Cemus", email = "", url = url("https://github.com/KarelCemus/")) + developers := List( + Developer(id = "karel.cemus", name = "Karel Cemus", email = "", url = url("https://github.com/KarelCemus/")), ), // Publish settings - publishTo := sonatypePublishToBundle.value, + publishTo := sonatypePublishToBundle.value, // git tags without "v" prefix SbtGit.git.gitTagToVersionNumber := { tag: String => if (tag matches "[0-9]+\\..*") Some(tag) else None - } + }, ) private lazy val inquireVersions: ReleaseStep = { implicit st: State => @@ -65,9 +64,10 @@ object CustomReleasePlugin extends AutoPlugin { st.log.info("Press enter to use the default value") - //flatten the Option[Option[String]] as the get returns an Option, and the value inside is an Option + // flatten the Option[Option[String]] as the get returns an Option, and the value inside is an Option val releaseV = readVersion(suggestedReleaseV, "Release version [%s] : ", useDefs, st.get(ReleaseKeys.commandLineReleaseVersion).flatten) st.put(ReleaseKeys.versions, (releaseV, releaseV)) } + } diff --git a/project/DocumentationUpdate.scala b/project/DocumentationUpdate.scala index 85f2b572..9979978e 100644 --- a/project/DocumentationUpdate.scala +++ b/project/DocumentationUpdate.scala @@ -30,9 +30,8 @@ object DocumentationUpdate { private def artifactId(implicit st: State) = st.extracted.get(normalizedName) /** SBT dependency definition */ - private def sbtDependency(version: String)(implicit st: State) = { + private def sbtDependency(version: String)(implicit st: State) = s""" "$groupId" %% "$artifactId" % "$version" """.trim - } /** Major and minor version of Play framework */ private def playMinorVersion(implicit st: State) = { @@ -45,16 +44,16 @@ object DocumentationUpdate { def replacement(version: String)(implicit st: State) = s"$version" } - def updateDocumentation: ReleaseStep = ReleaseStep({ implicit st: State => + def updateDocumentation: ReleaseStep = ReleaseStep { implicit st: State => val commitMessage = s"Documentation updated to version $next" val program = List[State => State]( bumpVersionInDoc(_), bumpLatestVersionInReadme(_), - commitVersion(commitMessage)(_) + commitVersion(commitMessage)(_), ).reduce(_ andThen _) program(st) - }) + } private def bumpVersionInDoc(implicit st: State): State = { val latest = vcs.latest @@ -99,4 +98,5 @@ object DocumentationUpdate { } newState } + } diff --git a/project/ReleaseUtilities.scala b/project/ReleaseUtilities.scala index b27eebca..45db85c4 100644 --- a/project/ReleaseUtilities.scala +++ b/project/ReleaseUtilities.scala @@ -12,11 +12,10 @@ object ReleaseUtilities { def extracted = Project.extract(st) } - def vcs(implicit st: State): Vcs = { + def vcs(implicit st: State): Vcs = Project.extract(st).get(releaseVcs).getOrElse { sys.error("Aborting release. Working directory is not a repository of a recognized VCS.") } - } def processLogger(implicit st: State): ProcessLogger = new ProcessLogger { override def err(s: => String): Unit = st.log.info(s) @@ -29,16 +28,15 @@ object ReleaseUtilities { } /** - * Helper class implementing a transform operation over a file. - * The file is opened, transformed, and saved. + * Helper class implementing a transform operation over a file. The file is + * opened, transformed, and saved. */ implicit class FilesUpdater(val files: Seq[File]) extends AnyVal { - def transform(transform: String => String): Unit = { - files.foreach { - file => IO.write(file, transform(IO.read(file))) + def transform(transform: String => String): Unit = + files.foreach { file => + IO.write(file, transform(IO.read(file))) } - } def getAbsolutePaths: Seq[String] = files.map(_.getAbsolutePath) } @@ -46,7 +44,9 @@ object ReleaseUtilities { implicit def file2updater(file: File): FilesUpdater = new FilesUpdater(Seq(file)) implicit class RichVcs(private val thiz: Vcs) extends AnyVal { + /** latest version extracted from git tag on the current branch */ def latest(implicit st: State) = vcs.cmd("describe", "--abbrev=0", "--tags").!!.trim } + } diff --git a/project/plugins.sbt b/project/plugins.sbt index 034784ae..fbc63766 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -8,10 +8,13 @@ addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.9") addSbtPlugin("org.scoverage" % "sbt-coveralls" % "1.3.11") // library release -addSbtPlugin("com.github.sbt" % "sbt-git" % "2.0.1") +addSbtPlugin("com.github.sbt" % "sbt-git" % "2.0.1") addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.21") -addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.2.1") -addSbtPlugin("com.github.sbt" % "sbt-release" % "1.1.0") +addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.2.1") +addSbtPlugin("com.github.sbt" % "sbt-release" % "1.1.0") // linters +addSbtPlugin("org.typelevel" % "sbt-tpolecat" % "0.5.0") addSbtPlugin("org.wartremover" % "sbt-wartremover" % "3.1.6") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.11.1") diff --git a/src/main/java/play/cache/redis/AsyncRedisList.java b/src/main/java/play/cache/redis/AsyncRedisList.java index 23ad7b17..9847a9e2 100644 --- a/src/main/java/play/cache/redis/AsyncRedisList.java +++ b/src/main/java/play/cache/redis/AsyncRedisList.java @@ -65,7 +65,7 @@ public interface AsyncRedisList { * @param index position of the element * @return element at the index or exception */ - CompletionStage apply(int index); + CompletionStage apply(long index); /** * Returns the element at index index in the list stored at key. @@ -83,7 +83,7 @@ public interface AsyncRedisList { * @return Some(element) at the index, None if no element exists, * or exception when the value is not a list */ - CompletionStage> get(int index); + CompletionStage> get(long index); /** * @return first element of the collection or an exception @@ -154,7 +154,7 @@ default CompletionStage> toList() { * @param element elements to be inserted * @return this collection to chain commands */ - CompletionStage> set(int position, Elem element); + CompletionStage> set(long position, Elem element); /** * Removes first value equal to the given value from the list. @@ -183,7 +183,7 @@ default CompletionStage> toList() { * @param count first N occurrences * @return this collection to chain commands */ - CompletionStage> remove(Elem element, int count); + CompletionStage> remove(Elem element, long count); /** * Removes the element at the given position. If the index @@ -194,7 +194,7 @@ default CompletionStage> toList() { * @param position element index to be removed * @return this collection to chain commands */ - CompletionStage> removeAt(int position); + CompletionStage> removeAt(long position); /** * @return read-only operations over the collection, does not modify data @@ -214,7 +214,7 @@ interface AsyncRedisListView { * @param n takes initial N elements * @return first N elements of the collection if exist */ - default CompletionStage> take(int n) { + default CompletionStage> take(long n) { return slice(0, n - 1); } @@ -224,7 +224,7 @@ default CompletionStage> take(int n) { * @param n ignore initial N elements * @return rest of the collection ignoring initial N elements */ - default CompletionStage> drop(int n) { + default CompletionStage> drop(long n) { return slice(n, -1); } @@ -260,7 +260,7 @@ default CompletionStage> all() { * @param end index of the last element included * @return collection at the specified range */ - CompletionStage> slice(int from, int end); + CompletionStage> slice(long from, long end); } interface AsyncRedisListModification { @@ -276,7 +276,7 @@ interface AsyncRedisListModification { * @param n takes initial N elements * @return this object to chain commands */ - default CompletionStage> take(int n) { + default CompletionStage> take(long n) { return slice(0, n - 1); } @@ -286,7 +286,7 @@ default CompletionStage> take(int n) { * @param n ignore initial N elements * @return this object to chain commands */ - default CompletionStage> drop(int n) { + default CompletionStage> drop(long n) { return slice(n, -1); } @@ -318,7 +318,7 @@ default CompletionStage> drop(int n) { * @param end index of the last element included * @return this object to chain commands */ - CompletionStage> slice(int from, int end); + CompletionStage> slice(long from, long end); } } \ No newline at end of file diff --git a/src/main/scala/play/api/cache/redis/CacheApi.scala b/src/main/scala/play/api/cache/redis/CacheApi.scala index a51dff3e..c7b4dca1 100644 --- a/src/main/scala/play/api/cache/redis/CacheApi.scala +++ b/src/main/scala/play/api/cache/redis/CacheApi.scala @@ -5,34 +5,41 @@ import scala.concurrent.duration.Duration import scala.reflect.ClassTag /** - *

Cache API inspired by basic Play play.api.cache.CacheApi. It implements all its - * operations and in addition it declares couple more useful operations handful - * with cache storage. Furthermore, due to its parametrization it allows to decide - * whether it produces blocking results or non-blocking promises.

+ *

Cache API inspired by basic Play play.api.cache.CacheApi. It implements + * all its operations and in addition it declares couple more useful operations + * handful with cache storage. Furthermore, due to its parametrization it + * allows to decide whether it produces blocking results or non-blocking + * promises.

*/ private[redis] trait AbstractCacheApi[Result[_]] { /** * Retrieve a value from the cache. * - * @param key cache storage key - * @return stored record, Some if exists, otherwise None + * @param key + * cache storage key + * @return + * stored record, Some if exists, otherwise None */ def get[T: ClassTag](key: String): Result[Option[T]] /** * Retrieve the values of all specified keys from the cache. * - * @param key cache storage keys - * @return stored record, Some if exists, otherwise None + * @param key + * cache storage keys + * @return + * stored record, Some if exists, otherwise None */ final def getAll[T: ClassTag](key: String*): Result[Seq[Option[T]]] = getAll(key) /** * Retrieve the values of all specified keys from the cache. * - * @param keys a collection of cache storage keys - * @return stored record, Some if exists, otherwise None + * @param keys + * a collection of cache storage keys + * @return + * stored record, Some if exists, otherwise None */ def getAll[T: ClassTag](keys: Iterable[String]): Result[Seq[Option[T]]] @@ -43,14 +50,18 @@ private[redis] trait AbstractCacheApi[Result[_]] { * Lazy invocation (default): The method **does wait** for the result of * `set` and when it fails, it applies the recovery policy. * - * Eager invocation: The method **does not wait** for the result of `set` - * if the value is not cached and also does not apply recovery policy if - * the `set` fails. - * - * @param key cache storage key - * @param expiration expiration period in seconds. - * @param orElse The default function to invoke if the value was not found in cache. - * @return stored or default record, Some if exists, otherwise None + * Eager invocation: The method **does not wait** for the result of `set` if + * the value is not cached and also does not apply recovery policy if the + * `set` fails. + * + * @param key + * cache storage key + * @param expiration + * expiration period in seconds. + * @param orElse + * The default function to invoke if the value was not found in cache. + * @return + * stored or default record, Some if exists, otherwise None */ def getOrElse[T: ClassTag](key: String, expiration: Duration = Duration.Inf)(orElse: => T): Result[T] @@ -61,104 +72,138 @@ private[redis] trait AbstractCacheApi[Result[_]] { * Lazy invocation (default): The method **does wait** for the result of * `set` and when it fails, it applies the recovery policy. * - * Eager invocation: The method **does not wait** for the result of `set` - * if the value is not cached and also does not apply recovery policy if - * the `set` fails. - * - * @param key cache storage key - * @param expiration expiration period in seconds. - * @param orElse The default function to invoke if the value was not found in cache. - * @return stored or default record, Some if exists, otherwise None + * Eager invocation: The method **does not wait** for the result of `set` if + * the value is not cached and also does not apply recovery policy if the + * `set` fails. + * + * @param key + * cache storage key + * @param expiration + * expiration period in seconds. + * @param orElse + * The default function to invoke if the value was not found in cache. + * @return + * stored or default record, Some if exists, otherwise None */ def getOrFuture[T: ClassTag](key: String, expiration: Duration = Duration.Inf)(orElse: => Future[T]): Future[T] /** * Determines whether value exists in cache. * - * @param key cache storage key - * @return record existence, true if exists, otherwise false + * @param key + * cache storage key + * @return + * record existence, true if exists, otherwise false */ def exists(key: String): Result[Boolean] /** - * Retrieves all keys matching the given pattern. This method invokes KEYS command + * Retrieves all keys matching the given pattern. This method invokes KEYS + * command * * '''Warning:''' complexity is O(n) where n are all keys in the database * - * @param pattern valid KEYS pattern with wildcards - * @return list of matching keys + * @param pattern + * valid KEYS pattern with wildcards + * @return + * list of matching keys */ def matching(pattern: String): Result[Seq[String]] /** - * Set a value into the cache. Expiration time in seconds (0 second means eternity). - * - * @param key cache storage key - * @param value value to store - * @param expiration record duration in seconds - * @return promise + * Set a value into the cache. Expiration time in seconds (0 second means + * eternity). + * + * @param key + * cache storage key + * @param value + * value to store + * @param expiration + * record duration in seconds + * @return + * promise */ def set(key: String, value: Any, expiration: Duration = Duration.Inf): Result[Done] /** - * Set a value into the cache if the given key is not already used, otherwise do nothing. - * Expiration time in seconds (0 second means eternity). - * - * Note: When expiration is defined, it is not an atomic operation. Redis does not - * provide a command for store-if-not-exists with duration. First, it sets the value - * if exists. Then, if the operation succeeded, it sets its expiration date. - * - * Note: When recovery policy used, it recovers with TRUE to indicate - * **"the lock was acquired"** despite actually **not storing** anything. - * - * @param key cache storage key - * @param value value to store - * @param expiration record duration in seconds - * @return true if value was set, false if was ignored because it existed before + * Set a value into the cache if the given key is not already used, otherwise + * do nothing. Expiration time in seconds (0 second means eternity). + * + * Note: When expiration is defined, it is not an atomic operation. Redis + * does not provide a command for store-if-not-exists with duration. First, + * it sets the value if exists. Then, if the operation succeeded, it sets its + * expiration date. + * + * Note: When recovery policy used, it recovers with TRUE to indicate **"the + * lock was acquired"** despite actually **not storing** anything. + * + * @param key + * cache storage key + * @param value + * value to store + * @param expiration + * record duration in seconds + * @return + * true if value was set, false if was ignored because it existed before */ def setIfNotExists(key: String, value: Any, expiration: Duration = Duration.Inf): Result[Boolean] /** - * Sets the given keys to their respective values for eternity. If any value is null, - * the particular key is excluded from the operation and removed from cache instead. - * The operation is atomic when there are no nulls. It replaces existing values. - * - * @param keyValues cache storage key-value pairs to store - * @return promise + * Sets the given keys to their respective values for eternity. If any value + * is null, the particular key is excluded from the operation and removed + * from cache instead. The operation is atomic when there are no nulls. It + * replaces existing values. + * + * @param keyValues + * cache storage key-value pairs to store + * @return + * promise */ def setAll(keyValues: (String, Any)*): Result[Done] /** - * Sets the given keys to their respective values for eternity. It sets all values if none of them - * exist, if at least a single of them exists, it does not set any value, thus it is either all or none. - * If any value is null, the particular key is excluded from the operation and removed from cache instead. - * The operation is atomic when there are no nulls. - * - * @param keyValues cache storage key-value pairs to store - * @return true if value was set, false if any value already existed before + * Sets the given keys to their respective values for eternity. It sets all + * values if none of them exist, if at least a single of them exists, it does + * not set any value, thus it is either all or none. If any value is null, + * the particular key is excluded from the operation and removed from cache + * instead. The operation is atomic when there are no nulls. + * + * @param keyValues + * cache storage key-value pairs to store + * @return + * true if value was set, false if any value already existed before */ def setAllIfNotExist(keyValues: (String, Any)*): Result[Boolean] /** - * If key already exists and is a string, this command appends the value at the end of - * the string. If key does not exist it is created and set as an empty string, so APPEND - * will be similar to SET in this special case. - * - * If it sets new value, it subsequently calls EXPIRE to set required expiration time - * - * @param key cache storage key - * @param value value to append - * @param expiration record duration, applies only when appends to nothing - * @return promise + * If key already exists and is a string, this command appends the value at + * the end of the string. If key does not exist it is created and set as an + * empty string, so APPEND will be similar to SET in this special case. + * + * If it sets new value, it subsequently calls EXPIRE to set required + * expiration time + * + * @param key + * cache storage key + * @param value + * value to append + * @param expiration + * record duration, applies only when appends to nothing + * @return + * promise */ def append(key: String, value: String, expiration: Duration = Duration.Inf): Result[Done] /** - * refreshes expiration time on a given key, useful, e.g., when we want to refresh session duration - * - * @param key cache storage key - * @param expiration new expiration in seconds - * @return promise + * refreshes expiration time on a given key, useful, e.g., when we want to + * refresh session duration + * + * @param key + * cache storage key + * @param expiration + * new expiration in seconds + * @return + * promise */ def expire(key: String, expiration: Duration): Result[Done] @@ -166,132 +211,173 @@ private[redis] trait AbstractCacheApi[Result[_]] { * Returns the remaining time to live of a key that has an expire set, * useful, e.g., when we want to check remaining session duration * - * @param key cache storage key - * @return the remaining time to live of a key. Some finite duration when - * the value exists and the expiration is set, Some infinite duration - * when the value exists but never expires, and None when the key does - * not exist. + * @param key + * cache storage key + * @return + * the remaining time to live of a key. Some finite duration when the value + * exists and the expiration is set, Some infinite duration when the value + * exists but never expires, and None when the key does not exist. */ def expiresIn(key: String): Result[Option[Duration]] /** * Remove a value under the given key from the cache * - * @param key cache storage key - * @return promise + * @param key + * cache storage key + * @return + * promise */ def remove(key: String): Result[Done] /** * Remove all values from the cache * - * @param key1 cache storage key - * @param key2 cache storage key - * @param keys cache storage keys - * @return promise + * @param key1 + * cache storage key + * @param key2 + * cache storage key + * @param keys + * cache storage keys + * @return + * promise */ def remove(key1: String, key2: String, keys: String*): Result[Done] /** - * Removes all keys in arguments. The other remove methods are for syntax sugar + * Removes all keys in arguments. The other remove methods are for syntax + * sugar * - * @param keys cache storage keys - * @return promise + * @param keys + * cache storage keys + * @return + * promise */ def removeAll(keys: String*): Result[Done] /** - *

Removes all keys matching the given pattern. This command has no direct support - * in Redis, it is combination of KEYS and DEL commands.

- * - *
    - *
  1. `KEYS pattern` command finds all keys matching the given pattern
  2. - *
  3. `DEL keys` expires all of them
  4. - *
- * - *

This is usable in scenarios when multiple keys contains same part of the key, such as - * record identification, user identification, etc. For example, we may have keys such - * as 'page/$id/header', 'page/$id/body', 'page/$id/footer' and we want to remove them - * all when the page is changed. We use the benefit of the '''naming convention''' we use and - * execute `removeAllMatching( s"page/$id/*" )`, which invalidates everything related to - * the given page. The benefit is we do not need to maintain the list of all keys to invalidate, - * we invalidate them all at once.

- * - *

* '''Warning:''' complexity is O(n) where n are all keys in the database

- * - * @param pattern this must be valid KEYS pattern - * @return nothing + *

Removes all keys matching the given pattern. This command has no direct + * support in Redis, it is combination of KEYS and DEL commands.

+ * + *
  1. `KEYS pattern` command finds all keys matching the given + * pattern
  2. `DEL keys` expires all of them
+ * + *

This is usable in scenarios when multiple keys contains same part of + * the key, such as record identification, user identification, etc. For + * example, we may have keys such as 'page/$id/header', + * 'page/$id/body', 'page/$id/footer' and we want to remove them all + * when the page is changed. We use the benefit of the '''naming + * convention''' we use and execute `removeAllMatching( s"page/$id/*" + * )`, which invalidates everything related to the given page. The benefit is + * we do not need to maintain the list of all keys to invalidate, we + * invalidate them all at once.

+ * + *

* '''Warning:''' complexity is O(n) where n are all keys in the + * database

+ * + * @param pattern + * this must be valid KEYS pattern + * @return + * nothing */ def removeMatching(pattern: String): Result[Done] /** * Remove all keys in cache * - * @return promise + * @return + * promise */ def invalidate(): Result[Done] /** - * Increments the stored string value representing 10-based signed integer - * by given value. - * - * @param key cache storage key - * @param by size of increment - * @return the value after the increment + * Increments the stored string value representing 10-based signed integer by + * given value. + * + * @param key + * cache storage key + * @param by + * size of increment + * @return + * the value after the increment * @since 1.3.0 */ def increment(key: String, by: Long = 1): Result[Long] /** - * Decrements the stored string value representing 10-based signed integer - * by given value. - * - * @param key cache storage key - * @param by size of decrement - * @return the value after the decrement + * Decrements the stored string value representing 10-based signed integer by + * given value. + * + * @param key + * cache storage key + * @param by + * size of decrement + * @return + * the value after the decrement * @since 1.3.0 */ def decrement(key: String, by: Long = 1): Result[Long] /** - * Scala wrapper around Redis list-related commands. This simplifies use of the lists. - * - * @param key the key storing the list - * @tparam T type of elements within the list - * @return Scala wrapper + * Scala wrapper around Redis list-related commands. This simplifies use of + * the lists. + * + * @param key + * the key storing the list + * @tparam T + * type of elements within the list + * @return + * Scala wrapper */ def list[T: ClassTag](key: String): RedisList[T, Result] /** - * Scala wrapper around Redis set-related commands. This simplifies use of the sets. - * - * @param key the key storing the set - * @tparam T type of elements within the set - * @return Scala wrapper + * Scala wrapper around Redis set-related commands. This simplifies use of + * the sets. + * + * @param key + * the key storing the set + * @tparam T + * type of elements within the set + * @return + * Scala wrapper */ def set[T: ClassTag](key: String): RedisSet[T, Result] /** - * Scala wrapper around Redis hash-related commands. This simplifies use of the hashes, i.e., maps. - * - * @param key the key storing the map - * @tparam T type of elements within the map - * @return Scala wrapper + * Scala wrapper around Redis hash-related commands. This simplifies use of + * the hashes, i.e., maps. + * + * @param key + * the key storing the map + * @tparam T + * type of elements within the map + * @return + * Scala wrapper */ def map[T: ClassTag](key: String): RedisMap[T, Result] /** * Scala wrapper around Redis sorted-set-related commands. * - * @param key the key storing the map - * @tparam T type of elements within the sorted-set - * @return Scala wrapper + * @param key + * the key storing the map + * @tparam T + * type of elements within the sorted-set + * @return + * Scala wrapper */ def zset[T: ClassTag](key: String): RedisSortedSet[T, Result] } -/** Synchronous and blocking implementation of the connection to the redis database */ +/** + * Synchronous and blocking implementation of the connection to the redis + * database + */ trait CacheApi extends AbstractCacheApi[SynchronousResult] -/** Asynchronous non-blocking implementation of the connection to the redis database */ +/** + * Asynchronous non-blocking implementation of the connection to the redis + * database + */ trait CacheAsyncApi extends AbstractCacheApi[AsynchronousResult] diff --git a/src/main/scala/play/api/cache/redis/Expiration.scala b/src/main/scala/play/api/cache/redis/Expiration.scala index bcae616b..5f2e8b11 100644 --- a/src/main/scala/play/api/cache/redis/Expiration.scala +++ b/src/main/scala/play/api/cache/redis/Expiration.scala @@ -1,17 +1,18 @@ package play.api.cache.redis import scala.concurrent.duration._ -import scala.language.implicitConversions /** - * Provides implicit converters to convert expiration date into duration, which is accepted by CacheApi. - * The conversion is performed from now, i.e., the formula is: + * Provides implicit converters to convert expiration date into duration, which + * is accepted by CacheApi. The conversion is performed from now, i.e., the + * formula is: * * {{{ * expireAt in seconds - now in seconds = duration in seconds * }}} */ private[redis] trait ExpirationImplicits { + import java.time.{LocalDateTime, ZoneId} import java.util.Date @@ -23,13 +24,16 @@ private[redis] trait ExpirationImplicits { /** * computes cache duration from the given expiration date time. * - * @param expireAt The class accepts timestamp in milliseconds since 1970 + * @param expireAt + * The class accepts timestamp in milliseconds since 1970 */ class Expiration(val expireAt: Long) extends AnyVal { /** returns now in milliseconds */ private def now: Long = System.currentTimeMillis() - /** converts given timestamp indication expiration date into duration from now */ + /** + * converts given timestamp indication expiration date into duration from now + */ def asExpiration: FiniteDuration = (expireAt - now).milliseconds } diff --git a/src/main/scala/play/api/cache/redis/RecoveryPolicy.scala b/src/main/scala/play/api/cache/redis/RecoveryPolicy.scala index 86f13fa6..fc918686 100644 --- a/src/main/scala/play/api/cache/redis/RecoveryPolicy.scala +++ b/src/main/scala/play/api/cache/redis/RecoveryPolicy.scala @@ -1,12 +1,11 @@ package play.api.cache.redis -import javax.inject.Inject - -import scala.concurrent.Future - import play.api.Logger import play.api.inject._ +import javax.inject.Inject +import scala.concurrent.Future + /** * Recovery policy triggers when a request fails. Based on the implementation, * it may try it again, recover with a default value or just simply log the @@ -17,14 +16,19 @@ import play.api.inject._ trait RecoveryPolicy { /** - * When a failure occurs, this method handles it. It may re-run it, return default value, - * log it or propagate the exception. + * When a failure occurs, this method handles it. It may re-run it, return + * default value, log it or propagate the exception. * - * @param rerun failed request (cache operation) - * @param default default value neutral to the operation - * @param failure incident report - * @tparam T expected result type - * @return failure recovery or exception + * @param rerun + * failed request (cache operation) + * @param default + * default value neutral to the operation + * @param failure + * incident report + * @tparam T + * expected result type + * @return + * failure recovery or exception */ def recoverFrom[T](rerun: => Future[T], default: => Future[T], failure: RedisException): Future[T] @@ -32,14 +36,13 @@ trait RecoveryPolicy { /** name of the policy used for internal purposes */ def name: String = this.getClass.getSimpleName - override def toString = s"RecoveryPolicy($name)" + override def toString: String = s"RecoveryPolicy($name)" // $COVERAGE-ON$ } /** - * Abstract recovery policy provides a general helpers for - * failure reporting. These might be usable when implementing - * own recovery policy. + * Abstract recovery policy provides a general helpers for failure reporting. + * These might be usable when implementing own recovery policy. */ trait Reports extends RecoveryPolicy { @@ -48,21 +51,20 @@ trait Reports extends RecoveryPolicy { protected def message(failure: RedisException): String = failure match { - case TimeoutException(_) => s"Command execution timed out." - case SerializationException(key, message, _) => s"$message for key '$key'." + case TimeoutException(_) => "Command execution timed out." + case SerializationException(key, message, _) => s"$message for key '$key'." case ExecutionFailedException(Some(key), command, _, _) => s"Command $command for key '$key' failed." case ExecutionFailedException(None, command, _, _) => s"Command $command failed." - case UnexpectedResponseException(Some(key), command) => s"Command $command for key '$key' returned unexpected response." - case UnexpectedResponseException(None, command) => s"Command $command returned unexpected response." + case UnexpectedResponseException(Some(key), command) => s"Command $command for key '$key' returned unexpected response." + case UnexpectedResponseException(None, command) => s"Command $command returned unexpected response." } protected def doLog(message: String, cause: Option[Throwable]): Unit /** reports a failure into logs */ - private def report(failure: RedisException): Unit = { + private def report(failure: RedisException): Unit = // create a failure report and report a failure doLog(message(failure), Option(failure.getCause)) - } abstract override def recoverFrom[T](rerun: => Future[T], default: => Future[T], failure: RedisException): Future[T] = { // log it and let through @@ -70,47 +72,44 @@ trait Reports extends RecoveryPolicy { // dive into super.recoverFrom(rerun, default, failure) } + } -/** - * Detailed reports policy produces logs with failure causes - */ +/** Detailed reports policy produces logs with failure causes */ trait DetailedReports extends Reports { protected def doLog(message: String, cause: Option[Throwable]): Unit = cause.fold(log.error(message))(log.error(message, _)) + } /** - * Condensed reports policy produces logs without causes, i.e., logs - * are shorter but less informative. + * Condensed reports policy produces logs without causes, i.e., logs are + * shorter but less informative. */ trait CondensedReports extends Reports { protected def doLog(message: String, cause: Option[Throwable]): Unit = log.error(message) + } -/** - * It fails on failure, i.e., propages the exception to upper layers - */ +/** It fails on failure, i.e., propages the exception to upper layers */ trait FailThrough extends RecoveryPolicy { - override def recoverFrom[T](rerun: => Future[T], default: => Future[T], failure: RedisException): Future[T] = { + override def recoverFrom[T](rerun: => Future[T], default: => Future[T], failure: RedisException): Future[T] = // fail through Future.failed(failure) - } + } -/** - * Recovers with a default value instead of failing through - */ +/** Recovers with a default value instead of failing through */ trait RecoverWithDefault extends RecoveryPolicy { - override def recoverFrom[T](rerun: => Future[T], default: => Future[T], failure: RedisException): Future[T] = { + override def recoverFrom[T](rerun: => Future[T], default: => Future[T], failure: RedisException): Future[T] = // return default value default - } + } /** @@ -119,16 +118,16 @@ trait RecoverWithDefault extends RecoveryPolicy { private[redis] class LogAndFailPolicy @Inject() extends FailThrough with DetailedReports /** - * When the command fails, it logs the failure and returns default value - * to prevent application failure. The returned value is neutral to the - * operation and it should behave like there is no cache + * When the command fails, it logs the failure and returns default value to + * prevent application failure. The returned value is neutral to the operation + * and it should behave like there is no cache */ private[redis] class LogAndDefaultPolicy @Inject() extends RecoverWithDefault with DetailedReports /** - * When the command fails, it logs the failure and returns default value - * to prevent application failure. The returned value is neutral to the - * operation and it should behave like there is no cache + * When the command fails, it logs the failure and returns default value to + * prevent application failure. The returned value is neutral to the operation + * and it should behave like there is no cache * * LogCondensed produces condensed messages without a stacktrace to avoid * extensive logs @@ -144,9 +143,9 @@ private[redis] class LogCondensedAndDefaultPolicy @Inject() extends RecoverWithD private[redis] class LogCondensedAndFailPolicy @Inject() extends FailThrough with CondensedReports /** - * This resolver represents an abstraction over translation - * of the policy name into the instance. It has two subclasses, - * one for guice and the other for compile-time injection. + * This resolver represents an abstraction over translation of the policy name + * into the instance. It has two subclasses, one for guice and the other for + * compile-time injection. */ trait RecoveryPolicyResolver { def resolve: PartialFunction[String, RecoveryPolicy] @@ -155,32 +154,36 @@ trait RecoveryPolicyResolver { // $COVERAGE-OFF$ class RecoveryPolicyResolverImpl extends RecoveryPolicyResolver { + override val resolve: PartialFunction[String, RecoveryPolicy] = { case "log-and-fail" => new LogAndFailPolicy case "log-and-default" => new LogAndDefaultPolicy case "log-condensed-and-fail" => new LogCondensedAndFailPolicy case "log-condensed-and-default" => new LogCondensedAndDefaultPolicy } + } object RecoveryPolicyResolver { - def bindings: Seq[Binding[_]] = Seq( + def bindings: Seq[Binding[?]] = Seq( bind[RecoveryPolicy].qualifiedWith("log-and-fail").to[LogAndFailPolicy], bind[RecoveryPolicy].qualifiedWith("log-and-default").to[LogAndDefaultPolicy], bind[RecoveryPolicy].qualifiedWith("log-condensed-and-fail").to[LogCondensedAndFailPolicy], bind[RecoveryPolicy].qualifiedWith("log-condensed-and-default").to[LogCondensedAndDefaultPolicy], // finally bind the resolver - bind[RecoveryPolicyResolver].to[RecoveryPolicyResolverGuice] + bind[RecoveryPolicyResolver].to[RecoveryPolicyResolverGuice], ) + } /** resolves a policies with guice enabled */ class RecoveryPolicyResolverGuice @Inject() (injector: Injector) extends RecoveryPolicyResolver { - override def resolve: PartialFunction[String, RecoveryPolicy] = { - case name => injector instanceOf bind[RecoveryPolicy].qualifiedWith(name) + override def resolve: PartialFunction[String, RecoveryPolicy] = { case name => + injector instanceOf bind[RecoveryPolicy].qualifiedWith(name) } + } // $COVERAGE-ON$ diff --git a/src/main/scala/play/api/cache/redis/RedisCacheComponents.scala b/src/main/scala/play/api/cache/redis/RedisCacheComponents.scala index 876dcaf6..22541d3e 100644 --- a/src/main/scala/play/api/cache/redis/RedisCacheComponents.scala +++ b/src/main/scala/play/api/cache/redis/RedisCacheComponents.scala @@ -1,13 +1,11 @@ package play.api.cache.redis -import scala.language.implicitConversions - import play.api.inject.ApplicationLifecycle import play.api.{Configuration, Environment} /** - *

Components for compile-time dependency injection. - * It binds components from configuration package

+ *

Components for compile-time dependency injection. It binds components + * from configuration package

*/ trait RedisCacheComponents { implicit def actorSystem: akka.actor.ActorSystem @@ -33,7 +31,7 @@ trait RedisCacheComponents { private lazy val manager = configuration.get("play.cache.redis")(play.api.cache.redis.configuration.RedisInstanceManager) - /** translates the cache name into the configuration */ + /** translates the cache name into the configuration */ private def redisInstance(name: String)(implicit resolver: RedisInstanceResolver): RedisInstance = manager.instanceOf(name).resolved(resolver) private def cacheApi(instance: RedisInstance): impl.RedisCaches = new impl.RedisCachesProvider(instance, akkaSerializer, environment).get diff --git a/src/main/scala/play/api/cache/redis/RedisCacheModule.scala b/src/main/scala/play/api/cache/redis/RedisCacheModule.scala index 0058f98c..f2a75a22 100644 --- a/src/main/scala/play/api/cache/redis/RedisCacheModule.scala +++ b/src/main/scala/play/api/cache/redis/RedisCacheModule.scala @@ -1,19 +1,19 @@ package play.api.cache.redis -import javax.inject._ -import scala.language.implicitConversions -import scala.reflect.ClassTag import play.api.inject._ import play.cache._ +import javax.inject._ +import scala.reflect.ClassTag + /** - * Play framework module implementing play.api.cache.CacheApi for redis-server key/value storage. For more details - * see README. + * Play framework module implementing play.api.cache.CacheApi for redis-server + * key/value storage. For more details see README. */ @Singleton class RedisCacheModule extends Module { - override def bindings(environment: play.api.Environment, config: play.api.Configuration): Seq[Binding[_]] = { + override def bindings(environment: play.api.Environment, config: play.api.Configuration): Seq[Binding[?]] = { def bindDefault = config.get[Boolean]("play.cache.redis.bind-default") // read the config and get the configuration of the redis @@ -25,7 +25,7 @@ class RedisCacheModule extends Module { val commons = Seq( // bind serializer bind[connector.AkkaSerializer].toProvider[connector.AkkaSerializerProvider], - bind[configuration.RedisInstanceResolver].to[GuiceRedisInstanceResolver] + bind[configuration.RedisInstanceResolver].to[GuiceRedisInstanceResolver], ) // bind recovery resolver val recovery = RecoveryPolicyResolver.bindings @@ -35,6 +35,7 @@ class RedisCacheModule extends Module { // return all bindings commons ++ caches ++ recovery ++ defaults } + } trait ProviderImplicits { @@ -47,38 +48,38 @@ private[redis] class RichBindingKey[T](val key: BindingKey[T]) extends AnyVal { trait GuiceProviderImplicits extends ProviderImplicits { def injector: Injector - protected implicit def implicitInjection[X](key: BindingKey[X]): X = injector instanceOf key + implicit protected def implicitInjection[X](key: BindingKey[X]): X = injector instanceOf key } object GuiceProvider extends ProviderImplicits { private class QualifiedBindingKey[T](key: BindingKey[T], f: impl.RedisCaches => T) { @inline private def provider(f: impl.RedisCaches => T)(implicit name: CacheName): Provider[T] = new NamedCacheInstanceProvider(f) - def toBindings(implicit name: CacheName): Binding[_] = key.named(name).to(provider(f)) + def toBindings(implicit name: CacheName): Binding[?] = key.named(name).to(provider(f)) } private def namedBinding[T: ClassTag](f: impl.RedisCaches => T) = new QualifiedBindingKey(bind[T], f) - def bindings(instance: RedisInstanceProvider): Seq[Binding[_]] = { + def bindings(instance: RedisInstanceProvider): Seq[Binding[?]] = { implicit val name: CacheName = new CacheName(instance.name) - Seq[Binding[_]]( + Seq[Binding[?]]( // bind implementation of all caches - bind[impl.RedisCaches].named(name).to(new GuiceRedisCacheProvider(instance)) - ) ++ Seq[QualifiedBindingKey[_]]( - // expose a single-implementation providers - namedBinding(_.redisConnector), - namedBinding(_.sync), - namedBinding(_.async), - namedBinding(_.scalaAsync), - namedBinding(_.scalaSync), - namedBinding(_.javaSync), - namedBinding[play.cache.AsyncCacheApi](_.javaAsync), - namedBinding[play.cache.redis.AsyncCacheApi](_.javaAsync) - ).map(_.toBindings) + bind[impl.RedisCaches].named(name).to(new GuiceRedisCacheProvider(instance)), + ) ++ Seq[QualifiedBindingKey[?]]( + // expose a single-implementation providers + namedBinding(_.redisConnector), + namedBinding(_.sync), + namedBinding(_.async), + namedBinding(_.scalaAsync), + namedBinding(_.scalaSync), + namedBinding(_.javaSync), + namedBinding[play.cache.AsyncCacheApi](_.javaAsync), + namedBinding[play.cache.redis.AsyncCacheApi](_.javaAsync), + ).map(_.toBindings) } - def defaults(instance: RedisInstanceProvider): Seq[Binding[_]] = { + def defaults(instance: RedisInstanceProvider): Seq[Binding[?]] = { implicit val name: CacheName = new CacheName(instance.name) @inline def defaultBinding[T: ClassTag]: Binding[T] = bind[T].to(bind[T].named(name)) @@ -96,22 +97,25 @@ object GuiceProvider extends ProviderImplicits { defaultBinding[play.api.cache.AsyncCacheApi], // java default api defaultBinding[play.cache.SyncCacheApi], - defaultBinding[play.cache.AsyncCacheApi] + defaultBinding[play.cache.AsyncCacheApi], ) } + } class GuiceRedisCacheProvider(instance: RedisInstanceProvider) extends Provider[RedisCaches] with GuiceProviderImplicits { @Inject() var injector: Injector = _ + override lazy val get: RedisCaches = new impl.RedisCachesProvider( instance = instance.resolved(bind[configuration.RedisInstanceResolver]), serializer = bind[connector.AkkaSerializer], - environment = bind[play.api.Environment] + environment = bind[play.api.Environment], )( system = bind[akka.actor.ActorSystem], lifecycle = bind[ApplicationLifecycle], - recovery = bind[RecoveryPolicyResolver] + recovery = bind[RecoveryPolicyResolver], ).get + } class NamedCacheInstanceProvider[T](f: RedisCaches => T)(implicit name: CacheName) extends Provider[T] with GuiceProviderImplicits { @@ -127,7 +131,9 @@ object CacheName { @Singleton class GuiceRedisInstanceResolver @Inject() (val injector: Injector) extends configuration.RedisInstanceResolver with GuiceProviderImplicits { - override def resolve: PartialFunction[String, RedisInstance] = { - case name => bind[RedisInstance].named(name) + + override def resolve: PartialFunction[String, RedisInstance] = { case name => + bind[RedisInstance].named(name) } + } diff --git a/src/main/scala/play/api/cache/redis/RedisCollection.scala b/src/main/scala/play/api/cache/redis/RedisCollection.scala index 8b8bfa7a..2ca8086a 100644 --- a/src/main/scala/play/api/cache/redis/RedisCollection.scala +++ b/src/main/scala/play/api/cache/redis/RedisCollection.scala @@ -11,7 +11,8 @@ private[redis] trait RedisCollection[Collection, Result[_]] { * * Time complexity: O(1) * - * @return size of the list + * @return + * size of the list */ def size: Result[Long] diff --git a/src/main/scala/play/api/cache/redis/RedisList.scala b/src/main/scala/play/api/cache/redis/RedisList.scala index 187cf65c..c525ba5c 100644 --- a/src/main/scala/play/api/cache/redis/RedisList.scala +++ b/src/main/scala/play/api/cache/redis/RedisList.scala @@ -1,167 +1,177 @@ package play.api.cache.redis /** - * Redis Lists are simply lists of strings, sorted by insertion order. - * It is possible to add elements to a Redis List pushing new elements - * on the head (on the left) or on the tail (on the right) of the list. + * Redis Lists are simply lists of strings, sorted by insertion order. It is + * possible to add elements to a Redis List pushing new elements on the head + * (on the left) or on the tail (on the right) of the list. * - * @tparam Elem Data type of the inserted element + * @tparam Elem + * Data type of the inserted element */ trait RedisList[Elem, Result[_]] extends RedisCollection[List[Elem], Result] { override type This = RedisList[Elem, Result] /** - * Insert all the specified values at the head of the list stored at key. - * If key does not exist, it is created as empty list before performing - * the push operations. When key holds a value that is not a list, an - * error is returned. + * Insert all the specified values at the head of the list stored at key. If + * key does not exist, it is created as empty list before performing the push + * operations. When key holds a value that is not a list, an error is + * returned. * - * It is possible to push multiple elements using a single command call - * just specifying multiple arguments at the end of the command. Elements - * are inserted one after the other to the head of the list, from the - * leftmost element to the rightmost element. So for instance the command - * LPUSH mylist a b c will result into a list containing c as first element, - * b as second element and a as third element. + * It is possible to push multiple elements using a single command call just + * specifying multiple arguments at the end of the command. Elements are + * inserted one after the other to the head of the list, from the leftmost + * element to the rightmost element. So for instance the command LPUSH mylist + * a b c will result into a list containing c as first element, b as second + * element and a as third element. * - * @param element element to be prepended - * @return this collection to chain commands + * @param element + * element to be prepended + * @return + * this collection to chain commands */ def prepend(element: Elem): Result[This] /** - * Insert all the specified values at the tail of the list stored at key. - * If key does not exist, it is created as empty list before performing - * the push operation. When key holds a value that is not a list, an error - * is returned. - * * - * It is possible to push multiple elements using a single command call - * just specifying multiple arguments at the end of the command. Elements - * are inserted one after the other to the tail of the list, from the - * leftmost element to the rightmost element. So for instance the command + * Insert all the specified values at the tail of the list stored at key. If + * key does not exist, it is created as empty list before performing the push + * operation. When key holds a value that is not a list, an error is + * returned. * It is possible to push multiple elements using a single + * command call just specifying multiple arguments at the end of the command. + * Elements are inserted one after the other to the tail of the list, from + * the leftmost element to the rightmost element. So for instance the command * RPUSH mylist a b c will result into a list containing a as first element, * b as second element and c as third element. * - * @param element to be apended - * @return this collection to chain commands + * @param element + * to be apended + * @return + * this collection to chain commands */ def append(element: Elem): Result[This] /** - * Insert all the specified values at the head of the list stored at key. - * If key does not exist, it is created as empty list before performing - * the push operations. When key holds a value that is not a list, an - * error is returned. + * Insert all the specified values at the head of the list stored at key. If + * key does not exist, it is created as empty list before performing the push + * operations. When key holds a value that is not a list, an error is + * returned. * - * It is possible to push multiple elements using a single command call - * just specifying multiple arguments at the end of the command. Elements - * are inserted one after the other to the head of the list, from the - * leftmost element to the rightmost element. So for instance the command - * LPUSH mylist a b c will result into a list containing c as first element, - * b as second element and a as third element. + * It is possible to push multiple elements using a single command call just + * specifying multiple arguments at the end of the command. Elements are + * inserted one after the other to the head of the list, from the leftmost + * element to the rightmost element. So for instance the command LPUSH mylist + * a b c will result into a list containing c as first element, b as second + * element and a as third element. * - * @param element element to be prepended - * @return this collection to chain commands + * @param element + * element to be prepended + * @return + * this collection to chain commands */ def +:(element: Elem): Result[This] /** - * Insert all the specified values at the tail of the list stored at key. - * If key does not exist, it is created as empty list before performing - * the push operation. When key holds a value that is not a list, an error - * is returned. - * * - * It is possible to push multiple elements using a single command call - * just specifying multiple arguments at the end of the command. Elements - * are inserted one after the other to the tail of the list, from the - * leftmost element to the rightmost element. So for instance the command + * Insert all the specified values at the tail of the list stored at key. If + * key does not exist, it is created as empty list before performing the push + * operation. When key holds a value that is not a list, an error is + * returned. * It is possible to push multiple elements using a single + * command call just specifying multiple arguments at the end of the command. + * Elements are inserted one after the other to the tail of the list, from + * the leftmost element to the rightmost element. So for instance the command * RPUSH mylist a b c will result into a list containing a as first element, * b as second element and c as third element. * - * @param element to be apended - * @return this collection to chain commands + * @param element + * to be apended + * @return + * this collection to chain commands */ def :+(element: Elem): Result[This] /** - * Insert all the specified values at the head of the list stored at key. - * If key does not exist, it is created as empty list before performing - * the push operations. When key holds a value that is not a list, an - * error is returned. + * Insert all the specified values at the head of the list stored at key. If + * key does not exist, it is created as empty list before performing the push + * operations. When key holds a value that is not a list, an error is + * returned. * - * It is possible to push multiple elements using a single command call - * just specifying multiple arguments at the end of the command. Elements - * are inserted one after the other to the head of the list, from the - * leftmost element to the rightmost element. So for instance the command - * LPUSH mylist a b c will result into a list containing c as first element, - * b as second element and a as third element. + * It is possible to push multiple elements using a single command call just + * specifying multiple arguments at the end of the command. Elements are + * inserted one after the other to the head of the list, from the leftmost + * element to the rightmost element. So for instance the command LPUSH mylist + * a b c will result into a list containing c as first element, b as second + * element and a as third element. * - * @param elements element to be prepended - * @return this collection to chain commands + * @param elements + * element to be prepended + * @return + * this collection to chain commands */ def ++:(elements: Iterable[Elem]): Result[This] /** - * Insert all the specified values at the tail of the list stored at key. - * If key does not exist, it is created as empty list before performing - * the push operation. When key holds a value that is not a list, an error - * is returned. - * * - * It is possible to push multiple elements using a single command call - * just specifying multiple arguments at the end of the command. Elements - * are inserted one after the other to the tail of the list, from the - * leftmost element to the rightmost element. So for instance the command + * Insert all the specified values at the tail of the list stored at key. If + * key does not exist, it is created as empty list before performing the push + * operation. When key holds a value that is not a list, an error is + * returned. * It is possible to push multiple elements using a single + * command call just specifying multiple arguments at the end of the command. + * Elements are inserted one after the other to the tail of the list, from + * the leftmost element to the rightmost element. So for instance the command * RPUSH mylist a b c will result into a list containing a as first element, * b as second element and c as third element. * - * @param elements to be apended - * @return this collection to chain commands + * @param elements + * to be apended + * @return + * this collection to chain commands */ def :++(elements: Iterable[Elem]): Result[This] /** - * Returns the element at index index in the list stored at key. - * The index is zero-based, so 0 means the first element, 1 the - * second element and so on. Negative indices can be used to designate - * elements starting at the tail of the list. Here, -1 means - * the last element, -2 means the penultimate and so forth. When - * the value at key is not a list, an error is returned. + * Returns the element at index index in the list stored at key. The index is + * zero-based, so 0 means the first element, 1 the second element and so on. + * Negative indices can be used to designate elements starting at the tail of + * the list. Here, -1 means the last element, -2 means the penultimate and so + * forth. When the value at key is not a list, an error is returned. * - * Time complexity: O(N) where N is the number of elements to traverse - * to get to the element at index. This makes asking for the first or - * the last element of the list O(1). - * - * @param index position of the element - * @return element at the index or exception + * Time complexity: O(N) where N is the number of elements to traverse to get + * to the element at index. This makes asking for the first or the last + * element of the list O(1). * + * @param index + * position of the element + * @return + * element at the index or exception */ - def apply(index: Int): Result[Elem] + def apply(index: Long): Result[Elem] /** - * Returns the element at index index in the list stored at key. - * The index is zero-based, so 0 means the first element, 1 the - * second element and so on. Negative indices can be used to designate - * elements starting at the tail of the list. Here, -1 means - * the last element, -2 means the penultimate and so forth. When - * the value at key is not a list, an error is returned. + * Returns the element at index index in the list stored at key. The index is + * zero-based, so 0 means the first element, 1 the second element and so on. + * Negative indices can be used to designate elements starting at the tail of + * the list. Here, -1 means the last element, -2 means the penultimate and so + * forth. When the value at key is not a list, an error is returned. * - * Time complexity: O(N) where N is the number of elements to traverse - * to get to the element at index. This makes asking for the first or - * the last element of the list O(1). + * Time complexity: O(N) where N is the number of elements to traverse to get + * to the element at index. This makes asking for the first or the last + * element of the list O(1). * - * @param index position of the element - * @return Some( element ) at the index, None if no element exists, - * or exception when the value is not a list + * @param index + * position of the element + * @return + * Some( element ) at the index, None if no element exists, or exception + * when the value is not a list */ - def get(index: Int): Result[Option[Elem]] + def get(index: Long): Result[Option[Elem]] /** - * @return first element of the collection or an exception + * @return + * first element of the collection or an exception */ def head: Result[Elem] = apply(0) /** - * @return first element of the collection or None + * @return + * first element of the collection or None */ def headOption: Result[Option[Elem]] = get(0) @@ -170,51 +180,61 @@ trait RedisList[Elem, Result[_]] extends RedisCollection[List[Elem], Result] { * * Time complexity: O(1) * - * @return head element if exists + * @return + * head element if exists */ def headPop: Result[Option[Elem]] /** - * @return last element of the collection or an exception + * @return + * last element of the collection or an exception */ def last: Result[Elem] = apply(-1) /** - * @return last element of the collection or None + * @return + * last element of the collection or None */ def lastOption: Result[Option[Elem]] = get(-1) /** - * @return Helper to this.view.all returning all object in the list + * @return + * Helper to this.view.all returning all object in the list */ def toList: Result[Seq[Elem]] = view.all /** - * Inserts value in the list stored at key either before the - * reference value pivot. When key does not exist, it is considered - * an empty list and no operation is performed. An error is returned - * when key exists but does not hold a list value. + * Inserts value in the list stored at key either before the reference value + * pivot. When key does not exist, it is considered an empty list and no + * operation is performed. An error is returned when key exists but does not + * hold a list value. * - * Time complexity: O(N) where N is the number of elements to traverse - * before seeing the value pivot. This means that inserting somewhere - * on the left end on the list (head) can be considered O(1) and - * inserting somewhere on the right end (tail) is O(N). + * Time complexity: O(N) where N is the number of elements to traverse before + * seeing the value pivot. This means that inserting somewhere on the left + * end on the list (head) can be considered O(1) and inserting somewhere on + * the right end (tail) is O(N). * - * @param pivot insert before this value - * @param element elements to be inserted - * @return new size of the collection or None if pivot not found + * @param pivot + * insert before this value + * @param element + * elements to be inserted + * @return + * new size of the collection or None if pivot not found */ def insertBefore(pivot: Elem, element: Elem): Result[Option[Long]] /** - * Sets the list element at index to value. For more information on the - * index argument, see LINDEX. An error is returned for out of range indexes. + * Sets the list element at index to value. For more information on the index + * argument, see LINDEX. An error is returned for out of range indexes. * - * @param position position to insert at - * @param element elements to be inserted - * @return this collection to chain commands + * @param position + * position to insert at + * @param element + * elements to be inserted + * @return + * this collection to chain commands */ - def set(position: Int, element: Elem): Result[This] + def set(position: Long, element: Elem): Result[This] /** * Removes first N values equal to the given value from the list. @@ -225,30 +245,37 @@ trait RedisList[Elem, Result[_]] extends RedisCollection[List[Elem], Result] { * * Time complexity: O(N) * - * @param element element to be removed from the list - * @param count first N occurrences - * @return this collection to chain commands + * @param element + * element to be removed from the list + * @param count + * first N occurrences + * @return + * this collection to chain commands */ - def remove(element: Elem, count: Int = 1): Result[This] + def remove(element: Elem, count: Long = 1L): Result[This] /** - * Removes the element at the given position. If the index - * is out of range, it throws an exception. + * Removes the element at the given position. If the index is out of range, + * it throws an exception. * * Time complexity: O(N) * - * @param position element index to be removed - * @return this collection to chain commands + * @param position + * element index to be removed + * @return + * this collection to chain commands */ - def removeAt(position: Int): Result[This] + def removeAt(position: Long): Result[This] /** - * @return read-only operations over the collection, does not modify data + * @return + * read-only operations over the collection, does not modify data */ def view: RedisListView /** - * @return write-only operations over the collection, modify data + * @return + * write-only operations over the collection, modify data */ def modify: RedisListModification @@ -257,86 +284,99 @@ trait RedisList[Elem, Result[_]] extends RedisCollection[List[Elem], Result] { /** * Helper method of slice. For more details see that method. * - * @param n takes initial N elements - * @return first N elements of the collection if exist + * @param n + * takes initial N elements + * @return + * first N elements of the collection if exist */ - def take(n: Int): Result[Seq[Elem]] = slice(0, n - 1) + def take(n: Long): Result[Seq[Elem]] = slice(0, n - 1) /** * Helper method of slice. For more details see that method. * - * @param n ignore initial N elements - * @return rest of the collection ignoring initial N elements + * @param n + * ignore initial N elements + * @return + * rest of the collection ignoring initial N elements */ - def drop(n: Int): Result[Seq[Elem]] = slice(n, -1) + def drop(n: Long): Result[Seq[Elem]] = slice(n, -1) /** * Helper method of slice. For more details see that method. * - * @return whole collection + * @return + * whole collection */ def all: Result[Seq[Elem]] = slice(0, -1) /** - * Returns the specified elements of the list stored at key. - * The offsets start and stop are zero-based indexes, with 0 being - * the first element of the list (the head of the list), 1 being the - * next element and so on. These offsets can also be negative numbers - * indicating offsets starting at the end of the list. - * For example, -1 is the last element of the list, -2 the penultimate, - * and so on. + * Returns the specified elements of the list stored at key. The offsets + * start and stop are zero-based indexes, with 0 being the first element of + * the list (the head of the list), 1 being the next element and so on. + * These offsets can also be negative numbers indicating offsets starting + * at the end of the list. For example, -1 is the last element of the list, + * -2 the penultimate, and so on. * - * Time complexity: O(S+N) where S is the distance of start offset from HEAD - * for small lists, from nearest end (HEAD or TAIL) for large lists; + * Time complexity: O(S+N) where S is the distance of start offset from + * HEAD for small lists, from nearest end (HEAD or TAIL) for large lists; * and N is the number of elements in the specified range. * - * Out-of-range indexes - * Out of range indexes will not produce an error. If start is larger than - * the end of the list, an empty list is returned. If stop is larger than - * the actual end of the list, Redis will treat it like the last element - * of the list. + * Out-of-range indexes Out of range indexes will not produce an error. If + * start is larger than the end of the list, an empty list is returned. If + * stop is larger than the actual end of the list, Redis will treat it like + * the last element of the list. * - * @param from initial index - * @param end index of the last element included - * @return collection at the specified range + * @param from + * initial index + * @param end + * index of the last element included + * @return + * collection at the specified range */ - def slice(from: Int, end: Int): Result[Seq[Elem]] + def slice(from: Long, end: Long): Result[Seq[Elem]] } trait RedisListModification { /** - * @return RedisList object with Redis API + * @return + * RedisList object with Redis API */ def collection: This /** * Helper method of slice. For more details see that method. * - * @param n takes initial N elements - * @return this object to chain commands + * @param n + * takes initial N elements + * @return + * this object to chain commands */ - def take(n: Int): Result[RedisListModification] = slice(0, n - 1) + def take(n: Long): Result[RedisListModification] = slice(0, n - 1) /** * Helper method of slice. For more details see that method. * - * @param n ignore initial N elements - * @return this object to chain commands + * @param n + * ignore initial N elements + * @return + * this object to chain commands */ - def drop(n: Int): Result[RedisListModification] = slice(n, -1) + def drop(n: Long): Result[RedisListModification] = slice(n, -1) /** * Helper method of slice. Wiping the whole collection * - * @return this object to chain commands + * @return + * this object to chain commands */ def clear(): Result[RedisListModification] /** * Trim an existing list so that it will contain only the specified range * of elements specified. Both start and stop are zero-based indexes, where - * 0 is the first element of the list (the head), 1 the next element and so on. + * 0 is the first element of the list (the head), 1 the next element and so + * on. * * For example: LTRIM foobar 0 2 will modify the list stored at foobar so * that only the first three elements of the list will remain. @@ -350,11 +390,14 @@ trait RedisList[Elem, Result[_]] extends RedisCollection[List[Elem], Result] { * (which causes key to be removed). If end is larger than the end of the * list, Redis will treat it like the last element of the list. * - * @param from initial index - * @param end index of the last element included - * @return this object to chain commands + * @param from + * initial index + * @param end + * index of the last element included + * @return + * this object to chain commands */ - def slice(from: Int, end: Int): Result[RedisListModification] + def slice(from: Long, end: Long): Result[RedisListModification] } } diff --git a/src/main/scala/play/api/cache/redis/RedisMap.scala b/src/main/scala/play/api/cache/redis/RedisMap.scala index ed281232..303242df 100644 --- a/src/main/scala/play/api/cache/redis/RedisMap.scala +++ b/src/main/scala/play/api/cache/redis/RedisMap.scala @@ -1,12 +1,13 @@ package play.api.cache.redis /** - * Redis Hashes are simply hash maps with strings as keys. It is possible to add - * elements to a Redis Hashes by adding new elements into the collection. + * Redis Hashes are simply hash maps with strings as keys. It is possible to + * add elements to a Redis Hashes by adding new elements into the collection. * * This simplified wrapper implements only unordered Maps. * - * @tparam Elem Data type of the inserted element + * @tparam Elem + * Data type of the inserted element */ trait RedisMap[Elem, Result[_]] extends RedisCollection[Map[String, Elem], Result] { @@ -15,87 +16,112 @@ trait RedisMap[Elem, Result[_]] extends RedisCollection[Map[String, Elem], Resul /** * Insert the value at the given key into the map * - * @param field key - * @param value inserted value - * @return the map for the chaining calls + * @param field + * key + * @param value + * inserted value + * @return + * the map for the chaining calls */ def add(field: String, value: Elem): Result[This] /** * Returns the value at the given key into the map * - * @param field key - * @return Some if the value exists in the map, None otherwise + * @param field + * key + * @return + * Some if the value exists in the map, None otherwise */ def get(field: String): Result[Option[Elem]] /** - * Returns the values stored at given keys in the map. The collection - * or results has same size as the collection of given fields, it preserves + * Returns the values stored at given keys in the map. The collection or + * results has same size as the collection of given fields, it preserves * ordering. * - * @param fields keys to get - * @return Some if the value exists in the map, None otherwise + * @param fields + * keys to get + * @return + * Some if the value exists in the map, None otherwise */ def getFields(fields: String*): Result[Seq[Option[Elem]]] = getFields(fields) /** - * Returns the values stored at given keys in the map. The collection - * or results has same size as the collection of given fields, it preserves + * Returns the values stored at given keys in the map. The collection or + * results has same size as the collection of given fields, it preserves * ordering. * - * @param fields keys to get - * @return Some if the value exists in the map, None otherwise + * @param fields + * keys to get + * @return + * Some if the value exists in the map, None otherwise */ def getFields(fields: Iterable[String]): Result[Seq[Option[Elem]]] /** - *

Tests if the field is contained in the map. Returns true if exists, otherwise returns false

+ *

Tests if the field is contained in the map. Returns true if exists, + * otherwise returns false

* - * @note Time complexity: O(1) - * @param field tested field - * @return true if exists in the map, otherwise false + * @note + * Time complexity: O(1) + * @param field + * tested field + * @return + * true if exists in the map, otherwise false */ def contains(field: String): Result[Boolean] /** - *

Removes the specified members from the sorted map stored at key. Non existing members are ignored. - * An error is returned when key exists and does not hold a sorted map.

+ *

Removes the specified members from the sorted map stored at key. Non + * existing members are ignored. An error is returned when key exists and + * does not hold a sorted map.

* - * @note Time complexity: O(N) where N is the number of members to be removed. - * @param field fields to be removed - * @return the map for chaining calls + * @note + * Time complexity: O(N) where N is the number of members + * to be removed. + * @param field + * fields to be removed + * @return + * the map for chaining calls */ def remove(field: String*): Result[This] /** * Increment a value at the given key in the map * - * @param field key - * @param incrementBy increment by this - * @return value after incrementation + * @param field + * key + * @param incrementBy + * increment by this + * @return + * value after incrementation */ def increment(field: String, incrementBy: Long = 1): Result[Long] /** *

Returns all elements in the map

* - * @note Time complexity: O(N) where N is the map cardinality. - * @return all elements in the map + * @note + * Time complexity: O(N) where N is the map cardinality. + * @return + * all elements in the map */ def toMap: Result[Map[String, Elem]] /** * Returns all keys defined in the map * - * @return all used keys + * @return + * all used keys */ def keySet: Result[Set[String]] /** * Returns all values stored in the map * - * @return all stored values + * @return + * all stored values */ def values: Result[Set[Elem]] } diff --git a/src/main/scala/play/api/cache/redis/RedisSet.scala b/src/main/scala/play/api/cache/redis/RedisSet.scala index 80a846a0..30550f9e 100644 --- a/src/main/scala/play/api/cache/redis/RedisSet.scala +++ b/src/main/scala/play/api/cache/redis/RedisSet.scala @@ -6,48 +6,65 @@ package play.api.cache.redis * * This simplified wrapper implements only unordered Sets. * - * @tparam Elem Data type of the inserted element + * @tparam Elem + * Data type of the inserted element */ trait RedisSet[Elem, Result[_]] extends RedisCollection[Set[Elem], Result] { override type This = RedisSet[Elem, Result] /** - *

Add the specified members to the set stored at key. Specified members that are already a member of this - * set are ignored. If key does not exist, a new set is created before adding the specified members.

+ *

Add the specified members to the set stored at key. Specified members + * that are already a member of this set are ignored. If key does not exist, + * a new set is created before adding the specified members.

* - * @note An error is returned when the value stored at key is not a set. - * @note Time complexity: O(1) for each element added, so O(N) to add N elements when the - * command is called with multiple arguments. - * @param element elements to be added - * @return the set for chaining calls + * @note + * An error is returned when the value stored at key is not a set. + * @note + * Time complexity: O(1) for each element added, so O(N) + * to add N elements when the command is called with multiple arguments. + * @param element + * elements to be added + * @return + * the set for chaining calls */ def add(element: Elem*): Result[This] /** - *

Tests if the element is contained in the set. Returns true if exists, otherwise returns false

+ *

Tests if the element is contained in the set. Returns true if exists, + * otherwise returns false

* - * @note Time complexity: O(1) - * @param element tested element - * @return true if exists in the set, otherwise false + * @note + * Time complexity: O(1) + * @param element + * tested element + * @return + * true if exists in the set, otherwise false */ def contains(element: Elem): Result[Boolean] /** - *

Removes the specified members from the sorted set stored at key. Non existing members are ignored. - * An error is returned when key exists and does not hold a sorted set.

+ *

Removes the specified members from the sorted set stored at key. Non + * existing members are ignored. An error is returned when key exists and + * does not hold a sorted set.

* - * @note Time complexity: O(N) where N is the number of members to be removed. - * @param element elements to be removed - * @return the set for chaining calls + * @note + * Time complexity: O(N) where N is the number of members + * to be removed. + * @param element + * elements to be removed + * @return + * the set for chaining calls */ def remove(element: Elem*): Result[This] /** *

Returns all elements in the set

* - * @note Time complexity: O(N) where N is the set cardinality. - * @return all elements in the set + * @note + * Time complexity: O(N) where N is the set cardinality. + * @return + * all elements in the set */ def toSet: Result[Set[Elem]] } diff --git a/src/main/scala/play/api/cache/redis/RedisSortedSet.scala b/src/main/scala/play/api/cache/redis/RedisSortedSet.scala index 8c9df70e..8e683871 100644 --- a/src/main/scala/play/api/cache/redis/RedisSortedSet.scala +++ b/src/main/scala/play/api/cache/redis/RedisSortedSet.scala @@ -6,45 +6,64 @@ trait RedisSortedSet[Elem, Result[_]] extends RedisCollection[TreeSet[Elem], Res override type This = RedisSortedSet[Elem, Result] /** - * Adds all the specified members with the specified scores to the sorted set stored at key. - * It is possible to specify multiple score / member pairs. If a specified member is already - * a member of the sorted set, the score is updated and the element reinserted at the right - * position to ensure the correct ordering. + * Adds all the specified members with the specified scores to the sorted set + * stored at key. It is possible to specify multiple score / member pairs. If + * a specified member is already a member of the sorted set, the score is + * updated and the element reinserted at the right position to ensure the + * correct ordering. * - * If key does not exist, a new sorted set with the specified members as sole members is created, - * like if the sorted set was empty. + * If key does not exist, a new sorted set with the specified members as sole + * members is created, like if the sorted set was empty. * - * @note If the key exists but does not hold a sorted set, an error is returned. - * @note Time complexity: O(log(N)) for each item added, where N is the number of elements in the sorted set. - * @param scoreValues values and corresponding scores to be added - * @return the sorted set for chaining calls + * @note + * If the key exists but does not hold a sorted set, an error is returned. + * @note + * Time complexity: O(log(N)) for each item added, where N + * is the number of elements in the sorted set. + * @param scoreValues + * values and corresponding scores to be added + * @return + * the sorted set for chaining calls */ def add(scoreValues: (Double, Elem)*): Result[This] /** - *

Tests if the element is contained in the sorted set. Returns true if exists, otherwise returns false

+ *

Tests if the element is contained in the sorted set. Returns true if + * exists, otherwise returns false

* - * @note Time complexity: O(1) - * @param element tested element - * @return true if exists in the set, otherwise false + * @note + * Time complexity: O(1) + * @param element + * tested element + * @return + * true if exists in the set, otherwise false */ def contains(element: Elem): Result[Boolean] /** - *

Removes the specified members from the sorted set stored at key. Non existing members are ignored. - * An error is returned when key exists and does not hold a sorted set.

+ *

Removes the specified members from the sorted set stored at key. Non + * existing members are ignored. An error is returned when key exists and + * does not hold a sorted set.

* - * @note Time complexity: O(M*log(N)) with N being the number of elements in the sorted set and M the number of elements to be removed. - * @param element elements to be removed - * @return the sorted set for chaining calls + * @note + * Time complexity: O(M*log(N)) with N being the number of + * elements in the sorted set and M the number of elements to be removed. + * @param element + * elements to be removed + * @return + * the sorted set for chaining calls */ def remove(element: Elem*): Result[This] /** - * Returns the specified range of elements in the sorted set stored at key which sorted in order specified by param `isReverse`. - * @param start the start index of the range - * @param stop the stop index of the range - * @param isReverse whether sorted in descending order or not + * Returns the specified range of elements in the sorted set stored at key + * which sorted in order specified by param `isReverse`. + * @param start + * the start index of the range + * @param stop + * the stop index of the range + * @param isReverse + * whether sorted in descending order or not * @return */ def range(start: Long, stop: Long, isReverse: Boolean = false): Result[Seq[Elem]] diff --git a/src/main/scala/play/api/cache/redis/configuration/Equals.scala b/src/main/scala/play/api/cache/redis/configuration/Equals.scala index 24151e1a..1816d77c 100644 --- a/src/main/scala/play/api/cache/redis/configuration/Equals.scala +++ b/src/main/scala/play/api/cache/redis/configuration/Equals.scala @@ -6,8 +6,8 @@ private[configuration] object Equals { // $COVERAGE-OFF$ @inline - def check[T](a: T, b: T)(property: (T => Any)*): Boolean = { + def check[T](a: T, b: T)(property: (T => Any)*): Boolean = property.forall(property => property(a) === property(b)) - } // $COVERAGE-ON$ + } diff --git a/src/main/scala/play/api/cache/redis/configuration/HostnameResolver.scala b/src/main/scala/play/api/cache/redis/configuration/HostnameResolver.scala index aba0bdc3..abe56558 100644 --- a/src/main/scala/play/api/cache/redis/configuration/HostnameResolver.scala +++ b/src/main/scala/play/api/cache/redis/configuration/HostnameResolver.scala @@ -7,4 +7,5 @@ object HostnameResolver { implicit class HostNameResolver(private val name: String) extends AnyVal { def resolvedIpAddress: String = InetAddress.getByName(name).getHostAddress } + } diff --git a/src/main/scala/play/api/cache/redis/configuration/RedisConfigLoader.scala b/src/main/scala/play/api/cache/redis/configuration/RedisConfigLoader.scala index 6ac8e738..d0a91f37 100644 --- a/src/main/scala/play/api/cache/redis/configuration/RedisConfigLoader.scala +++ b/src/main/scala/play/api/cache/redis/configuration/RedisConfigLoader.scala @@ -3,34 +3,30 @@ package play.api.cache.redis.configuration import com.typesafe.config.Config import play.api.cache.redis._ -/** - * Config loader helper, provides some useful methods - * easing config load - */ +/** Config loader helper, provides some useful methods easing config load */ private[configuration] object RedisConfigLoader { - implicit class ConfigOption(val config: Config) extends AnyVal { - def getOption[T](path: String, f: Config => String => T): Option[T] = { + implicit class ConfigOption(private val config: Config) extends AnyVal { + + def getOption[T](path: String, f: Config => String => T): Option[T] = if (config hasPath path) Some(f(config)(path)) else None - } - def getNullable[T](path: String, f: Config => String => T): Option[Option[T]] = { + def getNullable[T](path: String, f: Config => String => T): Option[Option[T]] = if (config hasPathOrNull path) Some(getOption(path, f)) else None - } + } - implicit class ConfigPath(val path: String) extends AnyVal { + implicit class ConfigPath(private val path: String) extends AnyVal { def /(suffix: String): String = if (path === "") suffix else s"$path.$suffix" } - def required(path: String) = throw new IllegalStateException(s"Configuration key '$path' is missing.") + def required(path: String): Nothing = throw new IllegalStateException(s"Configuration key '$path' is missing.") } /** - * Extended RedisConfig loader, it requires a default settings - * to be able to actually load the configuration. This default - * settings are used as a fallback value when the overloading - * settings are missing + * Extended RedisConfig loader, it requires a default settings to be able to + * actually load the configuration. This default settings are used as a + * fallback value when the overloading settings are missing */ private[configuration] trait RedisConfigLoader[T] { outer => @@ -38,9 +34,9 @@ private[configuration] trait RedisConfigLoader[T] { outer => } /** - * Extended RedisConfig loader to a produce redis instance, it requires - * a default settings to be able to actually load the configuration and its name. This default - * settings are used as a fallback value when the overloading + * Extended RedisConfig loader to a produce redis instance, it requires a + * default settings to be able to actually load the configuration and its name. + * This default settings are used as a fallback value when the overloading * settings are missing */ private[configuration] trait RedisConfigInstanceLoader[T] { outer => diff --git a/src/main/scala/play/api/cache/redis/configuration/RedisHost.scala b/src/main/scala/play/api/cache/redis/configuration/RedisHost.scala index 30183859..e65cbc48 100644 --- a/src/main/scala/play/api/cache/redis/configuration/RedisHost.scala +++ b/src/main/scala/play/api/cache/redis/configuration/RedisHost.scala @@ -1,31 +1,35 @@ package play.api.cache.redis.configuration -import play.api.ConfigLoader - import com.typesafe.config.Config +import play.api.ConfigLoader -/** - * Configures a single node either a standalone or within a cluster. - */ +/** Configures a single node either a standalone or within a cluster. */ trait RedisHost { + /** host with redis server */ def host: String + /** port redis listens on */ def port: Int + /** redis database identifier to work with */ def database: Option[Int] + /** when enabled security, this returns username for the AUTH command */ def username: Option[String] + /** when enabled security, this returns password for the AUTH command */ def password: Option[String] // $COVERAGE-OFF$ /** trait-specific equals */ override def equals(obj: scala.Any): Boolean = equalsAsHost(obj) + /** trait-specific equals, invokable from children */ protected def equalsAsHost(obj: scala.Any): Boolean = obj match { case that: RedisHost => Equals.check(this, that)(_.host, _.port, _.username, _.database, _.password) case _ => false } + /** to string */ override def toString: String = (password, database) match { case (Some(password), Some(database)) => s"redis://${username.getOrElse("redis")}:$password@$host:$port?db=$database" @@ -34,6 +38,7 @@ trait RedisHost { case (None, None) => s"redis://$host:$port" } // $COVERAGE-ON$ + } object RedisHost extends ConfigLoader[RedisHost] { @@ -47,21 +52,22 @@ object RedisHost extends ConfigLoader[RedisHost] { port = config.getInt(path / "port"), database = config.getOption(path / "database", _.getInt), username = config.getOption(path / "username", _.getString), - password = config.getOption(path / "password", _.getString) + password = config.getOption(path / "password", _.getString), ) /** read environment url or throw an exception */ def fromConnectionString(connectionString: String): RedisHost = ConnectionString findFirstMatchIn connectionString match { // read the environment variable and fill missing information from the local configuration file - case Some(matcher) => new RedisHost { - override val host: String = matcher.group("host") - override val port: Int = matcher.group("port").toInt - override val database: Option[Nothing] = None - override val username: Option[String] = Option(matcher.group("username")) - override val password: Option[String] = Option(matcher.group("password")) - } + case Some(matcher) => + new RedisHost { + override val host: String = matcher.group("host") + override val port: Int = matcher.group("port").toInt + override val database: Option[Nothing] = None + override val username: Option[String] = Option(matcher.group("username")) + override val password: Option[String] = Option(matcher.group("password")) + } // unexpected format - case None => throw new IllegalArgumentException(s"Unexpected format of the connection string: '$connectionString'. Expected format is 'redis://[user:password@]host:port'.") + case None => throw new IllegalArgumentException(s"Unexpected format of the connection string: '$connectionString'. Expected format is 'redis://[user:password@]host:port'.") } def apply(host: String, port: Int, database: Option[Int] = None, username: Option[String] = None, password: Option[String] = None): RedisHost = @@ -77,16 +83,13 @@ object RedisHost extends ConfigLoader[RedisHost] { } // $COVERAGE-OFF$ - def unapply(host: RedisHost): Option[(String, Int, Option[Int],Option[String], Option[String])] = { + def unapply(host: RedisHost): Some[(String, Int, Option[Int], Option[String], Option[String])] = Some((host.host, host.port, host.database, host.username, host.password)) - } // $COVERAGE-ON$ + } -/** - * - * A helper trait delegating properties into the inner settings object - */ +/** A helper trait delegating properties into the inner settings object */ trait RedisDelegatingHost extends RedisHost { def innerHost: RedisHost override def host: String = innerHost.host diff --git a/src/main/scala/play/api/cache/redis/configuration/RedisInstance.scala b/src/main/scala/play/api/cache/redis/configuration/RedisInstance.scala index ba28889c..41cb9170 100644 --- a/src/main/scala/play/api/cache/redis/configuration/RedisInstance.scala +++ b/src/main/scala/play/api/cache/redis/configuration/RedisInstance.scala @@ -3,38 +3,44 @@ package play.api.cache.redis.configuration import play.api.cache.redis._ /** - * Abstraction over clusters and standalone instances. This trait - * encapsulates a common settings and simplifies pattern matching. + * Abstraction over clusters and standalone instances. This trait encapsulates + * a common settings and simplifies pattern matching. */ sealed trait RedisInstance extends RedisSettings { + /** name of the redis instance */ def name: String // $COVERAGE-OFF$ /** trait-specific equals */ override def equals(obj: scala.Any): Boolean = equalsAsInstance(obj) + /** trait-specific equals, invokable from children */ protected def equalsAsInstance(obj: scala.Any): Boolean = obj match { case that: RedisInstance => this.name === that.name && equalsAsSettings(that) case _ => false } // $COVERAGE-ON$ + } /** - * Type of Redis Instance - a cluster. It encapsulates common settings of the instance - * and the list of cluster nodes. + * Type of Redis Instance - a cluster. It encapsulates common settings of the + * instance and the list of cluster nodes. */ sealed trait RedisCluster extends RedisInstance { + /** nodes definition when cluster is defined */ def nodes: List[RedisHost] + // $COVERAGE-OFF$ /** trait-specific equals */ override def equals(obj: scala.Any): Boolean = obj match { case that: RedisCluster => equalsAsInstance(that) && this.nodes === that.nodes case _ => false } + /** to string */ - override def toString = s"Cluster[${nodes mkString ","}]" + override def toString: String = s"Cluster[${nodes mkString ","}]" // $COVERAGE-ON$ } @@ -50,25 +56,29 @@ object RedisCluster { override val nodes: List[RedisHost] = _nodes override val settings: RedisSettings = _settings } + } /** - * A type of Redis Instance - a standalone instance. It encapsulates - * common settings of the instance and provides a connection settings. + * A type of Redis Instance - a standalone instance. It encapsulates common + * settings of the instance and provides a connection settings. */ sealed trait RedisStandalone extends RedisInstance with RedisHost { + // $COVERAGE-OFF$ /** trait-specific equals */ override def equals(obj: scala.Any): Boolean = obj match { case that: RedisStandalone => equalsAsInstance(that) && equalsAsHost(that) case _ => false } + /** to string */ override def toString: String = database match { case Some(database) => s"Standalone($name@$host:$port?db=$database)" case None => s"Standalone($name@$host:$port)" } // $COVERAGE-ON$ + } object RedisStandalone { @@ -83,11 +93,12 @@ object RedisStandalone { override val innerHost: RedisHost = _host override val settings: RedisSettings = _settings } + } /** - * Type of Redis Instance - a sentinel. It encapsulates common settings of - * the instance, name of the master group, and the list of sentinel nodes. + * Type of Redis Instance - a sentinel. It encapsulates common settings of the + * instance, name of the master group, and the list of sentinel nodes. */ sealed trait RedisSentinel extends RedisInstance { @@ -99,9 +110,11 @@ sealed trait RedisSentinel extends RedisInstance { override def equals(obj: scala.Any): Boolean = obj match { case that: RedisSentinel => equalsAsInstance(that) && this.sentinels === that.sentinels + case _ => false } + /** to string */ - override def toString = s"Sentinel[${sentinels mkString ","}]" + override def toString: String = s"Sentinel[${sentinels mkString ","}]" } object RedisSentinel { @@ -113,13 +126,12 @@ object RedisSentinel { settings: RedisSettings, username: Option[String] = None, password: Option[String] = None, - database: Option[Int] = None + database: Option[Int] = None, ): RedisSentinel = create(name, masterGroup, username, password, database, sentinels, settings) @inline - private def create(_name: String, _masterGroup: String, _username: Option[String], _password: Option[String], _database: Option[Int], - _sentinels: List[RedisHost], _settings: RedisSettings): RedisSentinel = + private def create(_name: String, _masterGroup: String, _username: Option[String], _password: Option[String], _database: Option[Int], _sentinels: List[RedisHost], _settings: RedisSettings): RedisSentinel = new RedisSentinel with RedisDelegatingSettings { override val name: String = _name override val masterGroup: String = _masterGroup diff --git a/src/main/scala/play/api/cache/redis/configuration/RedisInstanceManager.scala b/src/main/scala/play/api/cache/redis/configuration/RedisInstanceManager.scala index abf8a6e5..0064e760 100644 --- a/src/main/scala/play/api/cache/redis/configuration/RedisInstanceManager.scala +++ b/src/main/scala/play/api/cache/redis/configuration/RedisInstanceManager.scala @@ -1,18 +1,17 @@ package play.api.cache.redis.configuration +import com.typesafe.config.Config import play.api.ConfigLoader import play.api.cache.redis._ -import com.typesafe.config.Config /** - * Cache manager maintains a list of the redis caches in the application. - * It also provides a configuration of the instance based on the name - * of the cache. + * Cache manager maintains a list of the redis caches in the application. It + * also provides a configuration of the instance based on the name of the + * cache. * - * This object should be used only during the configuration phase to - * simplify binding creation and application configuration. While - * the application is running, there should be no need to use this - * manager. + * This object should be used only during the configuration phase to simplify + * binding creation and application configuration. While the application is + * running, there should be no need to use this manager. */ trait RedisInstanceManager extends Iterable[RedisInstanceProvider] { @@ -38,6 +37,7 @@ trait RedisInstanceManager extends Iterable[RedisInstanceProvider] { case _ => false } // $COVERAGE-ON$ + } private[redis] object RedisInstanceManager extends ConfigLoader[RedisInstanceManager] { @@ -52,12 +52,15 @@ private[redis] object RedisInstanceManager extends ConfigLoader[RedisInstanceMan // construct a manager if (hasInstances) new RedisInstanceManagerImpl(config, path) else new RedisInstanceManagerFallback(config, path) } + } /** - * Redis manager reading 'play.cache.redis.instances' tree for cache definitions. + * Redis manager reading 'play.cache.redis.instances' tree for cache + * definitions. */ class RedisInstanceManagerImpl(config: Config, path: String)(implicit defaults: RedisSettings) extends RedisInstanceManager { + import JavaCompatibilityBase._ import RedisConfigLoader._ @@ -75,10 +78,12 @@ class RedisInstanceManagerImpl(config: Config, path: String)(implicit defaults: override def instanceOfOption(name: String): Option[RedisInstanceProvider] = if (config hasPath (path / "instances" / name)) Some(RedisInstanceProvider.load(config, path / "instances" / name, name)) else None + } /** - * Redis manager reading 'play.cache.redis' root for a single fallback default cache. + * Redis manager reading 'play.cache.redis' root for a single fallback default + * cache. */ class RedisInstanceManagerFallback(config: Config, path: String)(implicit defaults: RedisSettings) extends RedisInstanceManager { import RedisConfigLoader._ @@ -93,4 +98,5 @@ class RedisInstanceManagerFallback(config: Config, path: String)(implicit defaul /** returns a configuration of a single named redis instance */ override def instanceOfOption(name: String): Option[RedisInstanceProvider] = if (name === this.name) Some(defaultInstance) else None + } diff --git a/src/main/scala/play/api/cache/redis/configuration/RedisInstanceProvider.scala b/src/main/scala/play/api/cache/redis/configuration/RedisInstanceProvider.scala index c0c78e4e..7d30a779 100644 --- a/src/main/scala/play/api/cache/redis/configuration/RedisInstanceProvider.scala +++ b/src/main/scala/play/api/cache/redis/configuration/RedisInstanceProvider.scala @@ -1,10 +1,9 @@ package play.api.cache.redis.configuration -import java.net.InetAddress - +import com.typesafe.config.Config import play.api.cache.redis._ -import com.typesafe.config.Config +import java.net.InetAddress trait RedisInstanceResolver { def resolve: PartialFunction[String, RedisInstance] @@ -27,7 +26,7 @@ final class ResolvedRedisInstance(val instance: RedisInstance) extends RedisInst override def hashCode(): Int = name.hashCode - override def toString = s"ResolvedRedisInstance($name@$instance)" + override def toString: String = s"ResolvedRedisInstance($name@$instance)" // $COVERAGE-ON$ } @@ -42,7 +41,7 @@ final class UnresolvedRedisInstance(val name: String) extends RedisInstanceProvi override def hashCode(): Int = name.hashCode - override def toString = s"UnresolvedRedisInstance($name)" + override def toString: String = s"UnresolvedRedisInstance($name)" // $COVERAGE-ON$ } @@ -50,7 +49,7 @@ private[configuration] object RedisInstanceProvider extends RedisConfigInstanceL import RedisConfigLoader._ override def load(config: Config, path: String, name: String)(implicit defaults: RedisSettings): RedisInstanceProvider = { - config.getOption(path / "source", _.getString) getOrElse defaults.source match { + config.getOption(path / "source", _.getString).getOrElse(defaults.source) match { // required static configuration of the standalone instance using application.conf case "standalone" => RedisInstanceStandalone // required static configuration of the cluster using application.conf @@ -64,35 +63,36 @@ private[configuration] object RedisInstanceProvider extends RedisConfigInstanceL // supplied custom configuration case "custom" => RedisInstanceCustom // found but unrecognized - case other => invalidConfiguration( - s""" - |Unrecognized configuration provider '$other' in ${config.getValue(path / "source").origin().filename()} - |at ${config.getValue(path / "source").origin().lineNumber()}. - |Expected values are 'standalone', 'cluster', 'connection-string', and 'custom'. - """.stripMargin - ) + case other => + invalidConfiguration( + s""" + |Unrecognized configuration provider '$other' in ${config.getValue(path / "source").origin().filename()} + |at ${config.getValue(path / "source").origin().lineNumber()}. + |Expected values are 'standalone', 'cluster', 'connection-string', and 'custom'. + """.stripMargin, + ) } }.load(config, path, name) + } -/** - * Statically configured single standalone redis instance - */ +/** Statically configured single standalone redis instance */ private[configuration] object RedisInstanceStandalone extends RedisConfigInstanceLoader[RedisInstanceProvider] { + override def load(config: Config, path: String, instanceName: String)(implicit defaults: RedisSettings) = new ResolvedRedisInstance( RedisStandalone.apply( name = instanceName, host = RedisHost.load(config, path), - settings = RedisSettings.withFallback(defaults).load(config, path) - ) + settings = RedisSettings.withFallback(defaults).load(config, path), + ), ) + } -/** - * Statically configured redis cluster - */ +/** Statically configured redis cluster */ private[configuration] object RedisInstanceCluster extends RedisConfigInstanceLoader[RedisInstanceProvider] { + import JavaCompatibilityBase._ import RedisConfigLoader._ @@ -101,14 +101,13 @@ private[configuration] object RedisInstanceCluster extends RedisConfigInstanceLo RedisCluster.apply( name = instanceName, nodes = config.getConfigList(path / "cluster").asScala.map(config => RedisHost.load(config)).toList, - settings = RedisSettings.withFallback(defaults).load(config, path) - ) + settings = RedisSettings.withFallback(defaults).load(config, path), + ), ) + } -/** - * Statically configured redis cluster driven by DNS configuration - */ +/** Statically configured redis cluster driven by DNS configuration */ private[configuration] object RedisInstanceAwsCluster extends RedisConfigInstanceLoader[RedisInstanceProvider] { import RedisConfigLoader._ @@ -117,14 +116,16 @@ private[configuration] object RedisInstanceAwsCluster extends RedisConfigInstanc RedisCluster.apply( name = instanceName, nodes = InetAddress.getAllByName(config.getString(path / "host")).map(address => RedisHost(address.getHostAddress, 6379)).toList, - settings = RedisSettings.withFallback(defaults).load(config, path) - ) + settings = RedisSettings.withFallback(defaults).load(config, path), + ), ) + } /** - * Reads a configuration from the connection string, possibly from an environmental variable. - * This instance configuration is designed to work in PaaS environments such as Heroku. + * Reads a configuration from the connection string, possibly from an + * environmental variable. This instance configuration is designed to work in + * PaaS environments such as Heroku. */ private[configuration] object RedisInstanceEnvironmental extends RedisConfigInstanceLoader[RedisInstanceProvider] { import RedisConfigLoader._ @@ -134,15 +135,15 @@ private[configuration] object RedisInstanceEnvironmental extends RedisConfigInst RedisStandalone.apply( name = instanceName, host = RedisHost.fromConnectionString(config getString path./("connection-string")), - settings = RedisSettings.withFallback(defaults).load(config, path) - ) + settings = RedisSettings.withFallback(defaults).load(config, path), + ), ) + } -/** - * Statically configures redis sentinel - */ +/** Statically configures redis sentinel */ private[configuration] object RedisInstanceSentinel extends RedisConfigInstanceLoader[RedisInstanceProvider] { + import JavaCompatibilityBase._ import RedisConfigLoader._ @@ -154,17 +155,21 @@ private[configuration] object RedisInstanceSentinel extends RedisConfigInstanceL masterGroup = config.getString(path / "master-group"), password = config.getOption(path / "password", _.getString), database = config.getOption(path / "database", _.getInt), - settings = RedisSettings.withFallback(defaults).load(config, path) - ) + settings = RedisSettings.withFallback(defaults).load(config, path), + ), ) + } /** - * This binder indicates that the user provides his own configuration of this named cache. + * This binder indicates that the user provides his own configuration of this + * named cache. */ private[configuration] object RedisInstanceCustom extends RedisConfigInstanceLoader[RedisInstanceProvider] { + override def load(config: Config, path: String, instanceName: String)(implicit defaults: RedisSettings) = new UnresolvedRedisInstance( - name = instanceName + name = instanceName, ) + } diff --git a/src/main/scala/play/api/cache/redis/configuration/RedisSettings.scala b/src/main/scala/play/api/cache/redis/configuration/RedisSettings.scala index 10276d0d..60b3ac17 100644 --- a/src/main/scala/play/api/cache/redis/configuration/RedisSettings.scala +++ b/src/main/scala/play/api/cache/redis/configuration/RedisSettings.scala @@ -1,35 +1,42 @@ package play.api.cache.redis.configuration -import play.api.ConfigLoader - import com.typesafe.config.Config +import play.api.ConfigLoader /** - * Configures non-connection related settings of redis instance, - * e.g., synchronization timeout, Akka dispatcher, and recovery policy. + * Configures non-connection related settings of redis instance, e.g., + * synchronization timeout, Akka dispatcher, and recovery policy. */ trait RedisSettings { + /** the name of the invocation context executing all commands to Redis */ def invocationContext: String + /** the name of the invocation policy used in getOrElse methods */ def invocationPolicy: String + /** timeout configuration */ def timeout: RedisTimeouts + /** recovery policy used with the instance */ def recovery: String + /** configuration source */ def source: String + /** instance prefix */ def prefix: Option[String] // $COVERAGE-OFF$ /** trait-specific equals */ override def equals(obj: scala.Any): Boolean = equalsAsSettings(obj) + /** trait-specific equals, invokable from children */ protected def equalsAsSettings(obj: scala.Any): Boolean = obj match { case that: RedisSettings => Equals.check(this, that)(_.invocationContext, _.invocationPolicy, _.timeout, _.recovery, _.source, _.prefix) case _ => false } // $COVERAGE-ON$ + } object RedisSettings extends ConfigLoader[RedisSettings] { @@ -41,18 +48,19 @@ object RedisSettings extends ConfigLoader[RedisSettings] { recovery = loadRecovery(config, path).get, timeout = loadTimeouts(config, path)(RedisTimeouts.requiredDefault), source = loadSource(config, path).get, - prefix = loadPrefix(config, path) + prefix = loadPrefix(config, path), ) def withFallback(fallback: RedisSettings): ConfigLoader[RedisSettings] = - (config: Config, path: String) => apply( - dispatcher = loadInvocationContext(config, path) getOrElse fallback.invocationContext, - invocationPolicy = loadInvocationPolicy(config, path) getOrElse fallback.invocationPolicy, - recovery = loadRecovery(config, path) getOrElse fallback.recovery, - timeout = loadTimeouts(config, path)(fallback.timeout), - source = loadSource(config, path) getOrElse fallback.source, - prefix = loadPrefix(config, path) orElse fallback.prefix - ) + (config: Config, path: String) => + apply( + dispatcher = loadInvocationContext(config, path) getOrElse fallback.invocationContext, + invocationPolicy = loadInvocationPolicy(config, path) getOrElse fallback.invocationPolicy, + recovery = loadRecovery(config, path) getOrElse fallback.recovery, + timeout = loadTimeouts(config, path)(fallback.timeout), + source = loadSource(config, path) getOrElse fallback.source, + prefix = loadPrefix(config, path) orElse fallback.prefix, + ) def apply(dispatcher: String, invocationPolicy: String, timeout: RedisTimeouts, recovery: String, source: String, prefix: Option[String] = None): RedisSettings = create(dispatcher, invocationPolicy, prefix, timeout, recovery, source) @@ -84,11 +92,10 @@ object RedisSettings extends ConfigLoader[RedisSettings] { private def loadTimeouts(config: Config, path: String)(defaults: RedisTimeouts): RedisTimeouts = RedisTimeouts.load(config, path)(defaults) + } -/** - * A helper trait delegating properties into the inner settings object - */ +/** A helper trait delegating properties into the inner settings object */ trait RedisDelegatingSettings extends RedisSettings { def settings: RedisSettings override def prefix: Option[String] = settings.prefix diff --git a/src/main/scala/play/api/cache/redis/configuration/RedisTimeouts.scala b/src/main/scala/play/api/cache/redis/configuration/RedisTimeouts.scala index 37dd758b..22f34664 100644 --- a/src/main/scala/play/api/cache/redis/configuration/RedisTimeouts.scala +++ b/src/main/scala/play/api/cache/redis/configuration/RedisTimeouts.scala @@ -1,35 +1,45 @@ package play.api.cache.redis.configuration -import java.util.concurrent.TimeUnit -import scala.concurrent.duration.FiniteDuration import com.typesafe.config.Config import play.api.cache.redis._ -/** - * Aggregates the timeout configuration settings - */ +import java.util.concurrent.TimeUnit +import scala.concurrent.duration.FiniteDuration + +/** Aggregates the timeout configuration settings */ trait RedisTimeouts { - /** sync timeout applies in sync API and indicates how long to wait before the future is resolved */ + /** + * sync timeout applies in sync API and indicates how long to wait before the + * future is resolved + */ def sync: FiniteDuration /** redis timeout indicates how long to wait for the response */ def redis: Option[FiniteDuration] - /** connection timeout applies when the connection is not established to fail requests eagerly */ + /** + * connection timeout applies when the connection is not established to fail + * requests eagerly + */ def connection: Option[FiniteDuration] } final case class RedisTimeoutsImpl( - /** sync timeout applies in sync API and indicates how long to wait before the future is resolved */ + /** + * sync timeout applies in sync API and indicates how long to wait before the + * future is resolved + */ sync: FiniteDuration, /** redis timeout indicates how long to wait for the response */ redis: Option[FiniteDuration], - /** fail after timeout applies when the connection is not established to fail requests eagerly */ - connection: Option[FiniteDuration] - + /** + * fail after timeout applies when the connection is not established to fail + * requests eagerly + */ + connection: Option[FiniteDuration], ) extends RedisTimeouts { // $COVERAGE-OFF$ @@ -38,6 +48,7 @@ final case class RedisTimeoutsImpl( case _ => false } // $COVERAGE-ON$ + } object RedisTimeouts { @@ -56,22 +67,20 @@ object RedisTimeouts { def load(config: Config, path: String)(default: RedisTimeouts): RedisTimeouts = RedisTimeouts( sync = loadSyncTimeout(config, path) getOrElse default.sync, redis = loadRedisTimeout(config, path) getOrElse default.redis, - connection = loadConnectionTimeout(config, path) getOrElse default.connection + connection = loadConnectionTimeout(config, path) getOrElse default.connection, ) - private def loadSyncTimeout(config: Config, path: String): Option[FiniteDuration] = { + private def loadSyncTimeout(config: Config, path: String): Option[FiniteDuration] = config.getOption(path / "sync-timeout", _.getDuration).map(duration => FiniteDuration(duration.getSeconds, TimeUnit.SECONDS)) - } - private def loadRedisTimeout(config: Config, path: String): Option[Option[FiniteDuration]] = { + private def loadRedisTimeout(config: Config, path: String): Option[Option[FiniteDuration]] = config.getNullable(path / "redis-timeout", _.getDuration).map { _.map(duration => FiniteDuration(duration.getSeconds, TimeUnit.SECONDS)) } - } - private def loadConnectionTimeout(config: Config, path: String): Option[Option[FiniteDuration]] = { + private def loadConnectionTimeout(config: Config, path: String): Option[Option[FiniteDuration]] = config.getNullable(path / "connection-timeout", _.getDuration).map { _.map(duration => FiniteDuration(duration.getSeconds, TimeUnit.SECONDS) + FiniteDuration(duration.getNano, TimeUnit.NANOSECONDS)) } - } + } diff --git a/src/main/scala/play/api/cache/redis/connector/AkkaSerializer.scala b/src/main/scala/play/api/cache/redis/connector/AkkaSerializer.scala index 4d30bf25..007dd562 100644 --- a/src/main/scala/play/api/cache/redis/connector/AkkaSerializer.scala +++ b/src/main/scala/play/api/cache/redis/connector/AkkaSerializer.scala @@ -1,38 +1,44 @@ package play.api.cache.redis.connector +import akka.actor.ActorSystem +import akka.serialization._ +import play.api.cache.redis._ + import java.util.Base64 import javax.inject._ -import scala.language.implicitConversions import scala.reflect.ClassTag import scala.util._ -import play.api.cache.redis._ -import akka.actor.ActorSystem -import akka.serialization._ /** - * Provides a encode and decode methods to serialize objects into strings - * and vise versa. + * Provides a encode and decode methods to serialize objects into strings and + * vise versa. */ trait AkkaSerializer { /** - * Method accepts a value to be serialized into the string. - * Based on the implementation, it returns a string representing the value or - * provides an exception, if the computation fails. + * Method accepts a value to be serialized into the string. Based on the + * implementation, it returns a string representing the value or provides an + * exception, if the computation fails. * - * @param value value to be serialized - * @return serialized string or exception + * @param value + * value to be serialized + * @return + * serialized string or exception */ def encode(value: Any): Try[String] /** - * Method accepts a valid serialized string and based on the accepted class it deserializes it. - * If the expected class does not match expectations, deserialization fails with an exception. - * Also, if the string is not valid representation, it also fails. + * Method accepts a valid serialized string and based on the accepted class + * it deserializes it. If the expected class does not match expectations, + * deserialization fails with an exception. Also, if the string is not valid + * representation, it also fails. * - * @param value valid serialized entity - * @tparam T expected class - * @return deserialized object or exception + * @param value + * valid serialized entity + * @tparam T + * expected class + * @return + * deserialized object or exception */ def decode[T: ClassTag](value: String): Try[T] } @@ -40,8 +46,8 @@ trait AkkaSerializer { /** * Akka encoder provides implementation of serialization using Akka serializer. * The implementation considers all primitives, nulls, and refs. This enables - * us to use Akka settings to modify serializer mapping and use different serializers - * for different objects. + * us to use Akka settings to modify serializer mapping and use different + * serializers for different objects. */ private[connector] class AkkaEncoder(serializer: Serialization) { @@ -74,13 +80,14 @@ private[connector] class AkkaEncoder(serializer: Serialization) { /** unsafe method converting AnyRef into BASE64 string */ private def anyRefToString(value: AnyRef): String = (anyRefToBinary _ andThen binaryToString)(value) + } /** - * Akka decoder provides implementation of deserialization using Akka serializer. - * The implementation considers all primitives, nulls, and refs. This enables - * us to use Akka settings to modify serializer mapping and use different serializers - * for different objects. + * Akka decoder provides implementation of deserialization using Akka + * serializer. The implementation considers all primitives, nulls, and refs. + * This enables us to use Akka settings to modify serializer mapping and use + * different serializers for different objects. */ private[connector] class AkkaDecoder(serializer: Serialization) { @@ -90,25 +97,31 @@ private[connector] class AkkaDecoder(serializer: Serialization) { private val Nothing = ClassTag(classOf[Nothing]) - /** unsafe method decoding a string into an object. It directly throws exceptions */ + /** + * unsafe method decoding a string into an object. It directly throws + * exceptions + */ def decode[T](value: String)(implicit classTag: ClassTag[T]): T = untypedDecode[T](value).asInstanceOf[T] - /** unsafe method decoding a string into an object. It directly throws exceptions. It does not perform type cast */ + /** + * unsafe method decoding a string into an object. It directly throws + * exceptions. It does not perform type cast + */ private def untypedDecode[T](value: String)(implicit tag: ClassTag[T]): Any = value match { // AnyVal is not supported by default, have to be implemented manually - case "" => null - case _ if tag =~= Nothing => throw new IllegalArgumentException("Type Nothing is not supported. You have probably forgot to specify expected data type.") - case string if tag =~= Java.String => string - case boolean if tag =~= Java.Boolean || tag =~= Scala.Boolean => boolean.toBoolean - case byte if tag =~= Java.Byte || tag =~= Scala.Byte => byte.toByte - case char if tag =~= Java.Char || tag =~= Scala.Char => char.charAt(0) - case short if tag =~= Java.Short || tag =~= Scala.Short => short.toShort - case int if tag =~= Java.Int || tag =~= Scala.Int => int.toInt - case long if tag =~= Java.Long || tag =~= Scala.Long => long.toLong - case float if tag =~= Java.Float || tag =~= Scala.Float => float.toFloat - case double if tag =~= Java.Double || tag =~= Scala.Double => double.toDouble - case anyRef => stringToAnyRef[T](anyRef) + case "" => null + case _ if tag =~= Nothing => throw new IllegalArgumentException("Type Nothing is not supported. You have probably forgot to specify expected data type.") + case string if tag =~= Java.String => string + case boolean if tag =~= Java.Boolean || tag =~= Scala.Boolean => boolean.toBoolean + case byte if tag =~= Java.Byte || tag =~= Scala.Byte => byte.toByte + case char if tag =~= Java.Char || tag =~= Scala.Char => char.charAt(0) + case short if tag =~= Java.Short || tag =~= Scala.Short => short.toShort + case int if tag =~= Java.Int || tag =~= Scala.Int => int.toInt + case long if tag =~= Java.Long || tag =~= Scala.Long => long.toLong + case float if tag =~= Java.Float || tag =~= Scala.Float => float.toFloat + case double if tag =~= Java.Double || tag =~= Scala.Double => double.toDouble + case anyRef => stringToAnyRef[T](anyRef) } /** consumes BASE64 string and returns array of bytes */ @@ -117,19 +130,20 @@ private[connector] class AkkaDecoder(serializer: Serialization) { /** deserializes the binary stream into the object */ private def binaryToAnyRef[T](binary: Array[Byte])(implicit classTag: ClassTag[T]): AnyRef = - serializer.deserialize(binary, classTag.runtimeClass.asInstanceOf[Class[_ <: AnyRef]]).get + serializer.deserialize(binary, classTag.runtimeClass.asInstanceOf[Class[? <: AnyRef]]).get /** converts BASE64 string directly into the object */ private def stringToAnyRef[T: ClassTag](base64: String): AnyRef = (stringToBinary _ andThen binaryToAnyRef[T])(base64) + } @Singleton private[connector] class AkkaSerializerImpl @Inject() (system: ActorSystem) extends AkkaSerializer { /** - * serializer dispatcher used to serialize the objects into bytes; - * the instance is retrieved from Akka based on its configuration + * serializer dispatcher used to serialize the objects into bytes; the + * instance is retrieved from Akka based on its configuration */ protected val serializer: Serialization = SerializationExtension(system) @@ -140,51 +154,63 @@ private[connector] class AkkaSerializerImpl @Inject() (system: ActorSystem) exte private val decoder = new AkkaDecoder(serializer) /** - * Method accepts a value to be serialized into the string. - * Based on the implementation, it returns a string representing the value or - * provides an exception, if the computation fails. + * Method accepts a value to be serialized into the string. Based on the + * implementation, it returns a string representing the value or provides an + * exception, if the computation fails. * - * @param value value to be serialized - * @return serialized string or exception + * @param value + * value to be serialized + * @return + * serialized string or exception */ override def encode(value: Any): Try[String] = Try(encoder.encode(value)) /** - * Method accepts a valid serialized string and based on the accepted class it deserializes it. - * If the expected class does not match expectations, deserialization fails with an exception. - * Also, if the string is not valid representation, it also fails. + * Method accepts a valid serialized string and based on the accepted class + * it deserializes it. If the expected class does not match expectations, + * deserialization fails with an exception. Also, if the string is not valid + * representation, it also fails. * - * @param value valid serialized entity - * @tparam T expected class - * @return deserialized object or exception + * @param value + * valid serialized entity + * @tparam T + * expected class + * @return + * deserialized object or exception */ override def decode[T: ClassTag](value: String): Try[T] = Try(decoder.decode[T](value)) + } -/** - * Registry of known Scala and Java primitives - */ +/** Registry of known Scala and Java primitives */ private[connector] object Primitives { /** primitive types with simplified encoding */ - val primitives: Seq[Class[_]] = Seq( - classOf[Boolean], classOf[java.lang.Boolean], - classOf[Byte], classOf[java.lang.Byte], - classOf[Char], classOf[java.lang.Character], - classOf[Short], classOf[java.lang.Short], - classOf[Int], classOf[java.lang.Integer], - classOf[Long], classOf[java.lang.Long], - classOf[Float], classOf[java.lang.Float], - classOf[Double], classOf[java.lang.Double], - classOf[String] + val primitives: Seq[Class[?]] = Seq( + classOf[Boolean], + classOf[java.lang.Boolean], + classOf[Byte], + classOf[java.lang.Byte], + classOf[Char], + classOf[java.lang.Character], + classOf[Short], + classOf[java.lang.Short], + classOf[Int], + classOf[java.lang.Integer], + classOf[Long], + classOf[java.lang.Long], + classOf[Float], + classOf[java.lang.Float], + classOf[Double], + classOf[java.lang.Double], + classOf[String], ) + } -/** - * Registry of class tags for Java primitives - */ +/** Registry of class tags for Java primitives */ private[connector] object JavaClassTag { val Byte: ClassTag[Byte] = ClassTag(classOf[java.lang.Byte]) diff --git a/src/main/scala/play/api/cache/redis/connector/ExpectedFuture.scala b/src/main/scala/play/api/cache/redis/connector/ExpectedFuture.scala index 7d488ead..92ef09c2 100644 --- a/src/main/scala/play/api/cache/redis/connector/ExpectedFuture.scala +++ b/src/main/scala/play/api/cache/redis/connector/ExpectedFuture.scala @@ -1,13 +1,12 @@ package play.api.cache.redis.connector -import scala.concurrent.{ExecutionContext, Future} -import scala.language.implicitConversions - import play.api.cache.redis._ +import scala.concurrent.{ExecutionContext, Future} + /** - * The extended future implements advanced response handling. - * It unifies maintenance of unexpected responses + * The extended future implements advanced response handling. It unifies + * maintenance of unexpected responses */ private[connector] trait ExpectedFuture[T] { @@ -33,9 +32,9 @@ private[connector] trait ExpectedFuture[T] { } /** handles both expected and unexpected responses and failure recovery */ - def expects[U](expected: PartialFunction[T, U])(implicit context: ExecutionContext): Future[U] = { + def expects[U](expected: PartialFunction[T, U])(implicit context: ExecutionContext): Future[U] = future map (expected orElse onUnexpected) recover onException - } + } private[connector] object ExpectedFuture { @@ -46,8 +45,8 @@ private[connector] object ExpectedFuture { private[connector] class ExpectedFutureWithoutKey[T](protected val future: Future[T], protected val cmd: String) extends ExpectedFuture[T] { - protected def onUnexpected: PartialFunction[Any, Nothing] = { - case _ => unexpected(None, cmd) + protected def onUnexpected: PartialFunction[Any, Nothing] = { case _ => + unexpected(None, cmd) } protected def onFailed(ex: Throwable): Nothing = @@ -57,13 +56,13 @@ private[connector] class ExpectedFutureWithoutKey[T](protected val future: Futur def withKeys(keys: Iterable[String]): ExpectedFutureWithKey[T] = withKey(keys mkString " ") - override def toString = s"ExpectedFuture($cmd)" + override def toString: String = s"ExpectedFuture($cmd)" } private[connector] class ExpectedFutureWithKey[T](protected val future: Future[T], protected val cmd: String, key: String, statement: => String) extends ExpectedFuture[T] { - protected def onUnexpected: PartialFunction[Any, Nothing] = { - case _ => unexpected(Some(key), cmd) + protected def onUnexpected: PartialFunction[Any, Nothing] = { case _ => + unexpected(Some(key), cmd) } protected def onFailed(ex: Throwable): Nothing = @@ -77,11 +76,12 @@ private[connector] class ExpectedFutureWithKey[T](protected val future: Future[T def asCommand(commandOverride: => String) = new ExpectedFutureWithKey(future, cmd, key, s"$cmd $commandOverride") - override def toString = s"ExpectedFuture($statement)" + override def toString: String = s"ExpectedFuture($statement)" } /** - * Constructs expected future from provided parameters, this serves as syntax sugar + * Constructs expected future from provided parameters, this serves as syntax + * sugar */ private[connector] class ExpectedFutureBuilder[T](val future: Future[T]) extends AnyVal { diff --git a/src/main/scala/play/api/cache/redis/connector/RedisCommands.scala b/src/main/scala/play/api/cache/redis/connector/RedisCommands.scala index 1ceded07..22476c27 100644 --- a/src/main/scala/play/api/cache/redis/connector/RedisCommands.scala +++ b/src/main/scala/play/api/cache/redis/connector/RedisCommands.scala @@ -1,13 +1,13 @@ package play.api.cache.redis.connector -import javax.inject._ -import scala.concurrent.Future +import akka.actor.{ActorSystem, Scheduler} import play.api.Logger import play.api.cache.redis.configuration._ import play.api.inject.ApplicationLifecycle -import akka.actor.{ActorSystem, Scheduler} import redis.{RedisClient => RedisStandaloneClient, RedisCluster => RedisClusterClient, _} +import javax.inject._ +import scala.concurrent.Future import scala.concurrent.duration.FiniteDuration /** @@ -21,6 +21,7 @@ private[connector] class RedisCommandsProvider(instance: RedisInstance)(implicit case standalone: RedisStandalone => new RedisCommandsStandalone(standalone).get case sentinel: RedisSentinel => new RedisCommandsSentinel(sentinel).get } + } private[connector] trait AbstractRedisCommands { @@ -50,9 +51,12 @@ private[connector] trait AbstractRedisCommands { /** * Creates a connection to the single instance of redis * - * @param lifecycle application lifecycle to trigger on stop hook - * @param configuration configures clusters - * @param system actor system + * @param lifecycle + * application lifecycle to trigger on stop hook + * @param configuration + * configures clusters + * @param system + * actor system */ private[connector] class RedisCommandsStandalone(configuration: RedisStandalone)(implicit system: ActorSystem, val lifecycle: ApplicationLifecycle) extends Provider[RedisCommands] with AbstractRedisCommands { import configuration._ @@ -62,16 +66,16 @@ private[connector] class RedisCommandsStandalone(configuration: RedisStandalone) port = port, db = database, username = username, - password = password + password = password, ) with FailEagerly with RedisRequestTimeout { protected val connectionTimeout: Option[FiniteDuration] = configuration.timeout.connection protected val timeout: Option[FiniteDuration] = configuration.timeout.redis - protected implicit val scheduler: Scheduler = system.scheduler + implicit protected val scheduler: Scheduler = system.scheduler - override def send[T](redisCommand: RedisCommand[_ <: protocol.RedisReply, T]): Future[T] = super.send(redisCommand) + override def send[T](redisCommand: RedisCommand[? <: protocol.RedisReply, T]): Future[T] = super.send(redisCommand) override def onConnectStatus: Boolean => Unit = (status: Boolean) => connected = status } @@ -79,8 +83,8 @@ private[connector] class RedisCommandsStandalone(configuration: RedisStandalone) // $COVERAGE-OFF$ override def start(): Unit = database.fold { log.info(s"Redis cache actor started. It is connected to $host:$port") - } { - database => log.info(s"Redis cache actor started. It is connected to $host:$port?database=$database") + } { database => + log.info(s"Redis cache actor started. It is connected to $host:$port?database=$database") } override def stop(): Future[Unit] = Future successful { @@ -89,28 +93,34 @@ private[connector] class RedisCommandsStandalone(configuration: RedisStandalone) log.info("Redis cache stopped.") } // $COVERAGE-ON$ + } /** * Creates a connection to the redis cluster. * - * @param lifecycle application lifecycle to trigger on stop hook - * @param configuration configures clusters - * @param system actor system + * @param lifecycle + * application lifecycle to trigger on stop hook + * @param configuration + * configures clusters + * @param system + * actor system */ private[connector] class RedisCommandsCluster(configuration: RedisCluster)(implicit system: ActorSystem, val lifecycle: ApplicationLifecycle) extends Provider[RedisCommands] with AbstractRedisCommands { + import HostnameResolver._ import configuration._ val client: RedisClusterClient = new RedisClusterClient( - nodes.map { - case RedisHost(host, port, database, username, password) => RedisServer(host.resolvedIpAddress, port, username, password, database) - } + nodes.map { case RedisHost(host, port, database, username, password) => + RedisServer(host.resolvedIpAddress, port, username, password, database) + }, ) with RedisRequestTimeout { + protected val timeout: Option[FiniteDuration] = configuration.timeout.redis - protected implicit val scheduler: Scheduler = system.scheduler + implicit protected val scheduler: Scheduler = system.scheduler } // $COVERAGE-OFF$ @@ -129,39 +139,42 @@ private[connector] class RedisCommandsCluster(configuration: RedisCluster)(impli log.info("Redis cluster cache stopped.") } // $COVERAGE-ON$ + } /** * Creates a connection to multiple redis sentinels. * - * @param lifecycle application lifecycle to trigger on stop hook - * @param configuration configures sentinels - * @param system actor system + * @param lifecycle + * application lifecycle to trigger on stop hook + * @param configuration + * configures sentinels + * @param system + * actor system */ private[connector] class RedisCommandsSentinel(configuration: RedisSentinel)(implicit system: ActorSystem, val lifecycle: ApplicationLifecycle) extends Provider[RedisCommands] with AbstractRedisCommands { import HostnameResolver._ val client: SentinelMonitoredRedisClient with RedisRequestTimeout = new SentinelMonitoredRedisClient( - configuration.sentinels.map { - case RedisHost(host, port, _, _, _) => (host.resolvedIpAddress, port) + configuration.sentinels.map { case RedisHost(host, port, _, _, _) => + (host.resolvedIpAddress, port) }, master = configuration.masterGroup, username = configuration.username, password = configuration.password, - db = configuration.database + db = configuration.database, ) with RedisRequestTimeout { protected val timeout: Option[FiniteDuration] = configuration.timeout.redis - protected implicit val scheduler: Scheduler = system.scheduler + implicit protected val scheduler: Scheduler = system.scheduler - override def send[T](redisCommand: RedisCommand[_ <: protocol.RedisReply, T]): Future[T] = super.send(redisCommand) + override def send[T](redisCommand: RedisCommand[? <: protocol.RedisReply, T]): Future[T] = super.send(redisCommand) } // $COVERAGE-OFF$ - override def start(): Unit = { + override def start(): Unit = log.info(s"Redis sentinel cache actor started. It is connected to ${configuration.toString}") - } override def stop(): Future[Unit] = Future successful { log.info("Stopping the redis sentinel cache actor ...") @@ -169,4 +182,5 @@ private[connector] class RedisCommandsSentinel(configuration: RedisSentinel)(imp log.info("Redis sentinel cache stopped.") } // $COVERAGE-ON$ + } diff --git a/src/main/scala/play/api/cache/redis/connector/RedisConnector.scala b/src/main/scala/play/api/cache/redis/connector/RedisConnector.scala index a0d6fffe..44c24f2f 100644 --- a/src/main/scala/play/api/cache/redis/connector/RedisConnector.scala +++ b/src/main/scala/play/api/cache/redis/connector/RedisConnector.scala @@ -9,78 +9,102 @@ import scala.reflect.ClassTag * * Subset of REDIS commands, basic commands. * - * @see https://redis.io/commands + * @see + * https://redis.io/commands */ private[redis] trait CoreCommands { /** * Retrieve a value from the cache. * - * @param key cache storage key - * @return stored record, Some if exists, otherwise None + * @param key + * cache storage key + * @return + * stored record, Some if exists, otherwise None */ def get[T: ClassTag](key: String): Future[Option[T]] /** * Retrieve a values from the cache. * - * @param keys cache storage key - * @return stored record, Some if exists, otherwise None + * @param keys + * cache storage key + * @return + * stored record, Some if exists, otherwise None */ def mGet[T: ClassTag](keys: String*): Future[Seq[Option[T]]] /** * Determines whether value exists in cache. * - * @param key cache storage key - * @return record existence, true if exists, otherwise false + * @param key + * cache storage key + * @return + * record existence, true if exists, otherwise false */ def exists(key: String): Future[Boolean] /** - * Retrieves all keys matching the given pattern. This method invokes KEYS command + * Retrieves all keys matching the given pattern. This method invokes KEYS + * command * * '''Warning:''' complexity is O(n) where n are all keys in the database * - * @param pattern valid KEYS pattern with wildcards - * @return list of matching keys + * @param pattern + * valid KEYS pattern with wildcards + * @return + * list of matching keys */ def matching(pattern: String): Future[Seq[String]] /** - * Set a value into the cache. Expiration time in seconds (0 second means eternity). + * Set a value into the cache. Expiration time in seconds (0 second means + * eternity). * - * @param key cache storage key - * @param value value to store - * @param expiration record duration in seconds - * @param ifNotExists set only if the key does not exist - * @return promise + * @param key + * cache storage key + * @param value + * value to store + * @param expiration + * record duration in seconds + * @param ifNotExists + * set only if the key does not exist + * @return + * promise */ def set(key: String, value: Any, expiration: Duration = Duration.Inf, ifNotExists: Boolean = false): Future[Boolean] /** * Set a value into the cache. Expiration time is the eternity. * - * @param keyValues cache storage key-value pairs to store - * @return promise + * @param keyValues + * cache storage key-value pairs to store + * @return + * promise */ def mSet(keyValues: (String, Any)*): Future[Unit] /** - * Set a value into the cache. Expiration time is the eternity. - * It either set all values or it sets none if any of them already exists. + * Set a value into the cache. Expiration time is the eternity. It either set + * all values or it sets none if any of them already exists. * - * @param keyValues cache storage key-value pairs to store - * @return promise + * @param keyValues + * cache storage key-value pairs to store + * @return + * promise */ def mSetIfNotExist(keyValues: (String, Any)*): Future[Boolean] /** - * refreshes expiration time on a given key, useful, e.g., when we want to refresh session duration + * refreshes expiration time on a given key, useful, e.g., when we want to + * refresh session duration * - * @param key cache storage key - * @param expiration new expiration in seconds - * @return promise + * @param key + * cache storage key + * @param expiration + * new expiration in seconds + * @return + * promise */ def expire(key: String, expiration: Duration): Future[Unit] @@ -88,51 +112,64 @@ private[redis] trait CoreCommands { * returns the remaining time to live of a key that has an expire set, * useful, e.g., when we want to check remaining session duration * - * @param key cache storage key - * @return the remaining time to live of a key in milliseconds + * @param key + * cache storage key + * @return + * the remaining time to live of a key in milliseconds */ def expiresIn(key: String): Future[Option[Duration]] /** - * Removes all keys in arguments. The other remove methods are for syntax sugar + * Removes all keys in arguments. The other remove methods are for syntax + * sugar * - * @param keys cache storage keys - * @return promise + * @param keys + * cache storage keys + * @return + * promise */ def remove(keys: String*): Future[Unit] /** * Remove all keys in cache * - * @return promise + * @return + * promise */ def invalidate(): Future[Unit] /** * Sends PING command to REDIS and expects PONG in return * - * @return promise + * @return + * promise */ def ping(): Future[Unit] /** - * Increments the stored string value representing 10-based signed integer - * by given value. + * Increments the stored string value representing 10-based signed integer by + * given value. * - * @param key cache storage key - * @param by size of increment - * @return the value after the increment + * @param key + * cache storage key + * @param by + * size of increment + * @return + * the value after the increment */ def increment(key: String, by: Long): Future[Long] /** - * If key already exists and is a string, this command appends the value at the - * end of the string. If key does not exist it is created and set as an empty string, - * so APPEND will be similar to SET in this special case. + * If key already exists and is a string, this command appends the value at + * the end of the string. If key does not exist it is created and set as an + * empty string, so APPEND will be similar to SET in this special case. * - * @param key cache storage key - * @param value value to be appended - * @return number of characters of current value + * @param key + * cache storage key + * @param value + * value to be appended + * @return + * number of characters of current value */ def append(key: String, value: String): Future[Long] } @@ -142,19 +179,25 @@ private[redis] trait CoreCommands { * * Subset of REDIS commands, Hash-related commands. * - * @see https://redis.io/commands + * @see + * https://redis.io/commands */ private[redis] trait HashCommands { /** - * Removes the specified fields from the hash stored at key. Specified fields that do not exist within this - * hash are ignored. If key does not exist, it is treated as an empty hash and this command returns 0. + * Removes the specified fields from the hash stored at key. Specified fields + * that do not exist within this hash are ignored. If key does not exist, it + * is treated as an empty hash and this command returns 0. * * Time complexity: O(N) where N is the number of fields to be removed. * - * @param key cache storage key - * @param field fields to be removed - * @return the number of fields that were removed from the hash, not including specified but non existing fields. + * @param key + * cache storage key + * @param field + * fields to be removed + * @return + * the number of fields that were removed from the hash, not including + * specified but non existing fields. */ def hashRemove(key: String, field: String*): Future[Long] @@ -163,9 +206,12 @@ private[redis] trait HashCommands { * * Time complexity: O(1) * - * @param key cache storage key - * @param field tested field name - * @return true if the field exists, false otherwise + * @param key + * cache storage key + * @param field + * tested field name + * @return + * true if the field exists, false otherwise */ def hashExists(key: String, field: String): Future[Boolean] @@ -174,42 +220,57 @@ private[redis] trait HashCommands { * * Time complexity: O(1) * - * @param key cache storage key - * @param field accessed field - * @return Some value if the field exists, otherwise None + * @param key + * cache storage key + * @param field + * accessed field + * @return + * Some value if the field exists, otherwise None */ def hashGet[T: ClassTag](key: String, field: String): Future[Option[T]] /** - * Returns the values associated with fields in the hash stored at given keys. + * Returns the values associated with fields in the hash stored at given + * keys. * * Time complexity: O(n), where n is number of fields * - * @param key cache storage key - * @param fields accessed fields to get - * @return Some value if the field exists, otherwise None + * @param key + * cache storage key + * @param fields + * accessed fields to get + * @return + * Some value if the field exists, otherwise None */ def hashGet[T: ClassTag](key: String, fields: Seq[String]): Future[Seq[Option[T]]] /** - * Returns all fields and values of the hash stored at key. In the returned value, every field name is followed - * by its value, so the length of the reply is twice the size of the hash. + * Returns all fields and values of the hash stored at key. In the returned + * value, every field name is followed by its value, so the length of the + * reply is twice the size of the hash. * * Time complexity: O(N) where N is the size of the hash. * - * @param key cache storage key - * @tparam T expected type of the elements - * @return the stored map + * @param key + * cache storage key + * @tparam T + * expected type of the elements + * @return + * the stored map */ def hashGetAll[T: ClassTag](key: String): Future[Map[String, T]] /** * Increment a value at the given key in the map * - * @param key cache storage key - * @param field key - * @param incrementBy increment by this - * @return value after incrementation + * @param key + * cache storage key + * @param field + * key + * @param incrementBy + * increment by this + * @return + * value after incrementation */ def hashIncrement(key: String, field: String, incrementBy: Long): Future[Long] @@ -218,8 +279,10 @@ private[redis] trait HashCommands { * * Time complexity: O(1) * - * @param key cache storage key - * @return size of the hash + * @param key + * cache storage key + * @return + * size of the hash */ def hashSize(key: String): Future[Long] @@ -228,20 +291,28 @@ private[redis] trait HashCommands { * * Time complexity: O(N) where N is the size of the hash. * - * @param key cache storage key - * @return set of field names + * @param key + * cache storage key + * @return + * set of field names */ def hashKeys(key: String): Future[Set[String]] /** - * Sets field in the hash stored at key to value. If key does not exist, a new key holding a hash is created. If field already exists in the hash, it is overwritten. + * Sets field in the hash stored at key to value. If key does not exist, a + * new key holding a hash is created. If field already exists in the hash, it + * is overwritten. * * Time complexity: O(1) * - * @param key cache storage key - * @param field field to be set - * @param value value to be set - * @return true if the field was newly set, false if was updated + * @param key + * cache storage key + * @param field + * field to be set + * @param value + * value to be set + * @return + * true if the field was newly set, false if was updated */ def hashSet(key: String, field: String, value: Any): Future[Boolean] @@ -250,8 +321,10 @@ private[redis] trait HashCommands { * * Time complexity: O(N) where N is the size of the hash. * - * @param key cache storage key - * @return all values in the hash object + * @param key + * cache storage key + * @return + * all values in the hash object */ def hashValues[T: ClassTag](key: String): Future[Set[T]] } @@ -261,134 +334,181 @@ private[redis] trait HashCommands { * * Subset of REDIS commands, List-related commands. * - * @see https://redis.io/commands + * @see + * https://redis.io/commands */ private[redis] trait ListCommands { /** - * Insert (LPUSH) all the specified values at the head of the list stored at key. - * If key does not exist, it is created as empty list before performing the push operations. - * When key holds a value that is not a list, an error is returned. + * Insert (LPUSH) all the specified values at the head of the list stored at + * key. If key does not exist, it is created as empty list before performing + * the push operations. When key holds a value that is not a list, an error + * is returned. * * Time complexity: O(1) * - * @param key cache storage key - * @param value prepended values - * @return new length of the list + * @param key + * cache storage key + * @param value + * prepended values + * @return + * new length of the list */ def listPrepend(key: String, value: Any*): Future[Long] /** - * Insert (RPUSH) all the specified values at the tail of the list stored at key. If key - * does not exist, it is created as empty list before performing the push operation. - * When key holds a value that is not a list, an error is returned. + * Insert (RPUSH) all the specified values at the tail of the list stored at + * key. If key does not exist, it is created as empty list before performing + * the push operation. When key holds a value that is not a list, an error is + * returned. * * Time complexity: O(1) * - * @param key cache storage key - * @param value appended values - * @return new length of the list + * @param key + * cache storage key + * @param value + * appended values + * @return + * new length of the list */ def listAppend(key: String, value: Any*): Future[Long] /** - * Returns the length of the list stored at key (LLEN). If key does not exist, it is interpreted as an empty - * list and 0 is returned. An error is returned when the value stored at key is not a list. + * Returns the length of the list stored at key (LLEN). If key does not + * exist, it is interpreted as an empty list and 0 is returned. An error is + * returned when the value stored at key is not a list. * * Time complexity: O(1) * - * @param key cache storage key - * @return length of the list + * @param key + * cache storage key + * @return + * length of the list */ def listSize(key: String): Future[Long] /** - * Inserts value in the list stored at key either before or after the reference value pivot. - * When key does not exist, it is considered an empty list and no operation is performed. - * An error is returned when key exists but does not hold a list value. + * Inserts value in the list stored at key either before or after the + * reference value pivot. When key does not exist, it is considered an empty + * list and no operation is performed. An error is returned when key exists + * but does not hold a list value. * - * Time complexity: O(N) where N is the number of elements to traverse before seeing the value pivot. + * Time complexity: O(N) where N is the number of elements to traverse before + * seeing the value pivot. * - * @param key cache storage key - * @param pivot value used as markup - * @param value value to be inserted - * @return the length of the list after the insert operation, or None when the value pivot was not found. + * @param key + * cache storage key + * @param pivot + * value used as markup + * @param value + * value to be inserted + * @return + * the length of the list after the insert operation, or None when the + * value pivot was not found. */ def listInsert(key: String, pivot: Any, value: Any): Future[Option[Long]] /** - * Sets the list element at index to value. For more information on the index argument, see LINDEX. An error is - * returned for out of range indexes. + * Sets the list element at index to value. For more information on the index + * argument, see LINDEX. An error is returned for out of range indexes. * - * Time complexity: O(N) where N is the length of the list. Setting either the first or the last element - * of the list is O(1). + * Time complexity: O(N) where N is the length of the list. Setting either + * the first or the last element of the list is O(1). * - * @param key cache storage key - * @param position position to be overwritten - * @param value value to be set - * @return promise + * @param key + * cache storage key + * @param position + * position to be overwritten + * @param value + * value to be set + * @return + * promise */ - def listSetAt(key: String, position: Int, value: Any): Future[Unit] + def listSetAt(key: String, position: Long, value: Any): Future[Unit] /** * Removes and returns the first element of the list stored at key (LPOP). * * Time complexity: O(1) * - * @param key cache storage key - * @tparam T type of the value - * @return head of the list, if existed + * @param key + * cache storage key + * @tparam T + * type of the value + * @return + * head of the list, if existed */ def listHeadPop[T: ClassTag](key: String): Future[Option[T]] /** - * Returns the specified elements of the list stored at key (LRANGE). The offsets start and stop are zero-based - * indexes, with 0 being the first element of the list (the head of the list), 1 being the next element and so on. - * - * These offsets can also be negative numbers indicating offsets starting at the end of the list. For example, - * -1 is the last element of the list, -2 the penultimate, and so on. - * - * Time complexity: O(S+N) where S is the distance of start offset from HEAD for small lists, from nearest end - * (HEAD or TAIL) for large lists; and N is the number of elements in the specified range. - * - * @param key cache storage key - * @param start initial index of the subset - * @param end last index of the subset (included) - * @tparam T type of the values - * @return subset of existing set - */ - def listSlice[T: ClassTag](key: String, start: Int, end: Int): Future[Seq[T]] - - /** - * Removes (LREM) the first count occurrences of elements equal to value from the list stored at key. The count - * argument influences the operation in the following ways: - * count > 0: Remove elements equal to value moving from head to tail. - * count < 0: Remove elements equal to value moving from tail to head. - * count = 0: Remove all elements equal to value. - * - * @param key cache storage key - * @param value value to be removed - * @param count number of elements to be removed - * @return number of removed elements - */ - def listRemove(key: String, value: Any, count: Int): Future[Long] - - /** - * Trim an existing list so that it will contain only the specified range of elements specified. Both start and stop - * are zero-based indexes, where 0 is the first element of the list (the head), 1 the next element and so on. - * - * For example: LTRIM foobar 0 2 will modify the list stored at foobar so that only the first three elements of - * the list will remain. start and end can also be negative numbers indicating offsets from the end of the list, - * where -1 is the last element of the list, -2 the penultimate element and so on. - * - * Time complexity: O(N) where N is the number of elements to be removed by the operation. - * - * @param key cache storage key - * @param start initial index of preserved subset - * @param end last index of preserved subset (included) - * @return promise - */ - def listTrim(key: String, start: Int, end: Int): Future[Unit] + * Returns the specified elements of the list stored at key (LRANGE). The + * offsets start and stop are zero-based indexes, with 0 being the first + * element of the list (the head of the list), 1 being the next element and + * so on. + * + * These offsets can also be negative numbers indicating offsets starting at + * the end of the list. For example, -1 is the last element of the list, -2 + * the penultimate, and so on. + * + * Time complexity: O(S+N) where S is the distance of start offset from HEAD + * for small lists, from nearest end (HEAD or TAIL) for large lists; and N is + * the number of elements in the specified range. + * + * @param key + * cache storage key + * @param start + * initial index of the subset + * @param end + * last index of the subset (included) + * @tparam T + * type of the values + * @return + * subset of existing set + */ + def listSlice[T: ClassTag](key: String, start: Long, end: Long): Future[Seq[T]] + + /** + * Removes (LREM) the first count occurrences of elements equal to value from + * the list stored at key. The count argument influences the operation in the + * following ways: count > 0: Remove elements equal to value moving from head + * to tail. count < 0: Remove elements equal to value moving from tail to + * head. count = 0: Remove all elements equal to value. + * + * @param key + * cache storage key + * @param value + * value to be removed + * @param count + * number of elements to be removed + * @return + * number of removed elements + */ + def listRemove(key: String, value: Any, count: Long): Future[Long] + + /** + * Trim an existing list so that it will contain only the specified range of + * elements specified. Both start and stop are zero-based indexes, where 0 is + * the first element of the list (the head), 1 the next element and so on. + * + * For example: LTRIM foobar 0 2 will modify the list stored at foobar so + * that only the first three elements of the list will remain. start and end + * can also be negative numbers indicating offsets from the end of the list, + * where -1 is the last element of the list, -2 the penultimate element and + * so on. + * + * Time complexity: O(N) where N is the number of elements to be removed by + * the operation. + * + * @param key + * cache storage key + * @param start + * initial index of preserved subset + * @param end + * last index of preserved subset (included) + * @return + * promise + */ + def listTrim(key: String, start: Long, end: Long): Future[Unit] } /** @@ -396,21 +516,27 @@ private[redis] trait ListCommands { * * Subset of REDIS commands, unordered Set-related commands. * - * @see https://redis.io/commands + * @see + * https://redis.io/commands */ private[redis] trait SetCommands { /** - * Add the specified members to the set stored at key. Specified members that are already a member of this set - * are ignored. If key does not exist, a new set is created before adding the specified members. + * Add the specified members to the set stored at key. Specified members that + * are already a member of this set are ignored. If key does not exist, a new + * set is created before adding the specified members. * * An error is returned when the value stored at key is not a set. * - * @note Time complexity: O(1) for each element added, so O(N) to add N elements when the command is called - * with multiple arguments. - * @param key cache storage key - * @param value values to be added - * @return number of inserted elements ignoring already existing + * @note + * Time complexity: O(1) for each element added, so O(N) to add N elements + * when the command is called with multiple arguments. + * @param key + * cache storage key + * @param value + * values to be added + * @return + * number of inserted elements ignoring already existing */ def setAdd(key: String, value: Any*): Future[Long] @@ -419,8 +545,11 @@ private[redis] trait SetCommands { * * Time complexity: O(1) * - * @param key cache storage key - * @return the cardinality (number of elements) of the set, or 0 if key does not exist. + * @param key + * cache storage key + * @return + * the cardinality (number of elements) of the set, or 0 if key does not + * exist. */ def setSize(key: String): Future[Long] @@ -431,9 +560,12 @@ private[redis] trait SetCommands { * * Time complexity: O(N) where N is the set cardinality. * - * @param key cache storage key - * @tparam T expected type of the elements - * @return the subset + * @param key + * cache storage key + * @tparam T + * expected type of the elements + * @return + * the subset */ def setMembers[T: ClassTag](key: String): Future[Set[T]] @@ -442,23 +574,30 @@ private[redis] trait SetCommands { * * Time complexity: O(1) * - * @param key cache storage key - * @param value tested element - * @return true if the element exists in the set, otherwise false + * @param key + * cache storage key + * @param value + * tested element + * @return + * true if the element exists in the set, otherwise false */ def setIsMember(key: String, value: Any): Future[Boolean] /** - * Remove the specified members from the set stored at key. Specified members that are not a member of this set - * are ignored. If key does not exist, it is treated as an empty set and this command returns 0. + * Remove the specified members from the set stored at key. Specified members + * that are not a member of this set are ignored. If key does not exist, it + * is treated as an empty set and this command returns 0. * * An error is returned when the value stored at key is not a set. * * Time complexity: O(N) where N is the number of members to be removed. * - * @param key cache storage key - * @param value values to be removed - * @return total number of removed values, non existing are ignored + * @param key + * cache storage key + * @param value + * values to be removed + * @return + * total number of removed values, non existing are ignored */ def setRemove(key: String, value: Any*): Future[Long] } @@ -468,60 +607,80 @@ private[redis] trait SetCommands { * * Subset of REDIS commands, sorted set related commands. * - * @see https://redis.io/commands + * @see + * https://redis.io/commands */ private[redis] trait SortedSetCommands { /** - * Adds all the specified members with the specified scores to the sorted set stored at key. - * It is possible to specify multiple score / member pairs. If a specified member is already - * a member of the sorted set, the score is updated and the element reinserted at the right - * position to ensure the correct ordering. + * Adds all the specified members with the specified scores to the sorted set + * stored at key. It is possible to specify multiple score / member pairs. If + * a specified member is already a member of the sorted set, the score is + * updated and the element reinserted at the right position to ensure the + * correct ordering. * - * If key does not exist, a new sorted set with the specified members as sole members is created, - * like if the sorted set was empty. If the key exists but does not hold a sorted set, an error - * is returned. + * If key does not exist, a new sorted set with the specified members as sole + * members is created, like if the sorted set was empty. If the key exists + * but does not hold a sorted set, an error is returned. * - * @note Time complexity: O(log(N)) for each item added, where N is the number of elements in the sorted set. - * @param key cache storage key - * @param scoreValues values and corresponding scores to be added - * @return number of inserted elements ignoring already existing + * @note + * Time complexity: O(log(N)) for each item added, where N is the number of + * elements in the sorted set. + * @param key + * cache storage key + * @param scoreValues + * values and corresponding scores to be added + * @return + * number of inserted elements ignoring already existing */ def sortedSetAdd(key: String, scoreValues: (Double, Any)*): Future[Long] /** - * Returns the sorted set cardinality (number of elements) of the sorted set stored at key. + * Returns the sorted set cardinality (number of elements) of the sorted set + * stored at key. * * Time complexity: O(1) * - * @param key cache storage key - * @return the cardinality (number of elements) of the set, or 0 if key does not exist. + * @param key + * cache storage key + * @return + * the cardinality (number of elements) of the set, or 0 if key does not + * exist. */ def sortedSetSize(key: String): Future[Long] /** * Returns the score of member in the sorted set at key. * - * If member does not exist in the sorted set, or key does not exist, nil is returned. + * If member does not exist in the sorted set, or key does not exist, nil is + * returned. * * Time complexity: O(1) * - * @param key cache storage key - * @param value tested element - * @return the score of member (a double precision floating point number). + * @param key + * cache storage key + * @param value + * tested element + * @return + * the score of member (a double precision floating point number). */ def sortedSetScore(key: String, value: Any): Future[Option[Double]] /** - * Removes the specified members from the sorted set stored at key. Non existing members are ignored. + * Removes the specified members from the sorted set stored at key. Non + * existing members are ignored. * * An error is returned when key exists and does not hold a sorted set. * - * Time complexity: O(M*log(N)) with N being the number of elements in the sorted set and M the number of elements to be removed. + * Time complexity: O(M*log(N)) with N being the number of elements in the + * sorted set and M the number of elements to be removed. * - * @param key cache storage key - * @param value values to be removed - * @return total number of removed values, non existing are ignored + * @param key + * cache storage key + * @param value + * values to be removed + * @return + * total number of removed values, non existing are ignored */ def sortedSetRemove(key: String, value: Any*): Future[Long] @@ -529,25 +688,37 @@ private[redis] trait SortedSetCommands { * Returns the specified range of elements in the sorted set stored at key. * * An error is returned when key exists and does not hold a sorted set. - * @param key cache storage key - * @param start the start index of the range - * @param stop the stop index of the range - * @note The start and stop arguments represent zero-based indexes, where 0 is the first element, - * 1 is the next element, and so on. These arguments specify an inclusive range. - * @return list of elements in the specified range + * @param key + * cache storage key + * @param start + * the start index of the range + * @param stop + * the stop index of the range + * @note + * The start and stop arguments represent zero-based indexes, where 0 is + * the first element, 1 is the next element, and so on. These arguments + * specify an inclusive range. + * @return + * list of elements in the specified range */ def sortedSetRange[T: ClassTag](key: String, start: Long, stop: Long): Future[Seq[T]] /** * Returns the specified range of elements in the sorted set stored at key. - * The elements are considered to be ordered from the highest to the lowest score. - * Descending lexicographical order is used for elements with equal score. - * - * @param key cache storage key - * @param start the start index of the range - * @param stop the stop index of the range - * @note Apart from the reversed ordering, the zrevRange is similar to zrange. - * @return list of elements in the specified range + * The elements are considered to be ordered from the highest to the lowest + * score. Descending lexicographical order is used for elements with equal + * score. + * + * @param key + * cache storage key + * @param start + * the start index of the range + * @param stop + * the stop index of the range + * @note + * Apart from the reversed ordering, the zrevRange is similar to zrange. + * @return + * list of elements in the specified range */ def sortedSetReverseRange[T: ClassTag](key: String, start: Long, stop: Long): Future[Seq[T]] } @@ -555,11 +726,7 @@ private[redis] trait SortedSetCommands { /** * Internal non-blocking Redis API implementing REDIS protocol * - * @see https://redis.io/commands + * @see + * https://redis.io/commands */ -trait RedisConnector extends AnyRef - with CoreCommands - with ListCommands - with SetCommands - with HashCommands - with SortedSetCommands +trait RedisConnector extends AnyRef with CoreCommands with ListCommands with SetCommands with HashCommands with SortedSetCommands diff --git a/src/main/scala/play/api/cache/redis/connector/RedisConnectorImpl.scala b/src/main/scala/play/api/cache/redis/connector/RedisConnectorImpl.scala index a5ce873d..16b1ded1 100644 --- a/src/main/scala/play/api/cache/redis/connector/RedisConnectorImpl.scala +++ b/src/main/scala/play/api/cache/redis/connector/RedisConnectorImpl.scala @@ -1,22 +1,27 @@ package play.api.cache.redis.connector +import play.api.Logger +import play.api.cache.redis._ +import redis._ + import java.util.concurrent.TimeUnit import scala.concurrent.Future import scala.concurrent.duration.Duration import scala.reflect.ClassTag -import play.api.Logger -import play.api.cache.redis._ -import redis._ /** - * The connector directly connects with the REDIS instance, implements protocol commands - * and is supposed to by used internally by another wrappers. The connector does not - * directly implement [[play.api.cache.redis.CacheApi]] but provides fundamental functionality. + * The connector directly connects with the REDIS instance, implements protocol + * commands and is supposed to by used internally by another wrappers. The + * connector does not directly implement [[play.api.cache.redis.CacheApi]] but + * provides fundamental functionality. * - * @param serializer encodes/decodes objects into/from a string - * @param redis implementation of the commands + * @param serializer + * encodes/decodes objects into/from a string + * @param redis + * implementation of the commands */ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: RedisCommands)(implicit runtime: RedisRuntime) extends RedisConnector { + import ExpectedFuture._ import runtime._ @@ -29,7 +34,7 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R case Some(response: String) => log.trace(s"Hit on key '$key'.") Some(decode[T](key, response)) - case None => + case None => log.debug(s"Miss on key '$key'.") None } @@ -37,21 +42,25 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R override def mGet[T: ClassTag](keys: String*): Future[Seq[Option[T]]] = redis.mget[String](keys: _*) executing "MGET" withKeys keys expects { // list is always returned - case list => keys.zip(list).map { - case (key, Some(response: String)) => - log.trace(s"Hit on key '$key'.") - Some(decode[T](key, response)) - case (key, None) => - log.debug(s"Miss on key '$key'.") - None - } + case list => + keys.zip(list).map { + case (key, Some(response: String)) => + log.trace(s"Hit on key '$key'.") + Some(decode[T](key, response)) + case (key, None) => + log.debug(s"Miss on key '$key'.") + None + } } /** decodes the object, reports an exception if fails */ private def decode[T: ClassTag](key: String, encoded: String): T = - serializer.decode[T](encoded).recover { - case ex => serializationFailed(key, "Deserialization failed", ex) - }.get + serializer + .decode[T](encoded) + .recover { case ex => + serializationFailed(key, "Deserialization failed", ex) + } + .get override def set(key: String, value: Any, expiration: Duration, ifNotExists: Boolean): Future[Boolean] = // no value to set @@ -61,18 +70,21 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R /** encodes the object, reports an exception if fails */ private def encode(key: String, value: Any): Future[String] = Future.fromTry { - serializer.encode(value).recover { - case ex => serializationFailed(key, "Serialization failed", ex) + serializer.encode(value).recover { case ex => + serializationFailed(key, "Serialization failed", ex) } } - /** implements the advanced set operation storing already encoded value into the storage */ + /** + * implements the advanced set operation storing already encoded value into + * the storage + */ private def doSet(key: String, value: String, expiration: Duration, ifNotExists: Boolean): Future[Boolean] = redis.set[String]( key, value, pxMilliseconds = if (expiration.isFinite) Some(expiration.toMillis) else None, - NX = ifNotExists + NX = ifNotExists, ) executing "SET" withKey key andParameters s"$value${s" PX $expiration" when expiration.isFinite}${" NX" when ifNotExists}" logging { case true if expiration.isFinite => log.debug(s"Set on key '$key' for ${expiration.toMillis} milliseconds.") case true => log.debug(s"Set on key '$key' for infinite seconds.") @@ -83,7 +95,10 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R override def mSetIfNotExist(keyValues: (String, Any)*): Future[Boolean] = mSetUsing(mSetEternallyIfNotExist, true, keyValues: _*) - /** eternally stores or removes all given values, using the given mSet implementation */ + /** + * eternally stores or removes all given values, using the given mSet + * implementation + */ private def mSetUsing[T](mSet: Seq[(String, String)] => Future[T], default: T, keyValues: (String, Any)*): Future[T] = { val (toBeRemoved, toBeSet) = keyValues.partition(_.isNull) // remove all keys to be removed @@ -96,8 +111,8 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R /** eternally stores already encoded values into the storage */ private def mSetEternally(keyValues: (String, String)*): Future[Unit] = - redis.mset(keyValues.toMap) executing "MSET" withKeys keyValues.map(_._1) asCommand keyValues.map(_.asString).mkString(" ") logging { - case _ => log.debug(s"Set on keys ${keyValues.map(_.key)} for infinite seconds.") + redis.mset(keyValues.toMap) executing "MSET" withKeys keyValues.map(_._1) asCommand keyValues.map(_.asString).mkString(" ") logging { case _ => + log.debug(s"Set on keys ${keyValues.map(_.key)} for infinite seconds.") } /** eternally stores already encoded values into the storage */ @@ -108,17 +123,17 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R } override def expire(key: String, expiration: Duration): Future[Unit] = - redis.expire(key, expiration.toSeconds.toInt) executing "EXPIRE" withKey key andParameter s"$expiration" logging { - case true => log.debug(s"Expiration set on key '$key'.") // expiration was set + redis.expire(key, expiration.toSeconds) executing "EXPIRE" withKey key andParameter s"$expiration" logging { + case true => log.debug(s"Expiration set on key '$key'.") // expiration was set case false => log.debug(s"Expiration set on key '$key' failed. Key does not exist.") // Nothing was removed } override def expiresIn(key: String): Future[Option[Duration]] = redis.pttl(key) executing "PTTL" withKey key expects { - case -2 => + case -2 => log.debug(s"PTTL on key '$key' returns -2, it does not exist.") None - case -1 => + case -1 => log.debug(s"PTTL on key '$key' returns -1, it has no associated expiration.") Some(Duration.Inf) case expiration => @@ -127,8 +142,8 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R } override def matching(pattern: String): Future[Seq[String]] = - redis.keys(pattern) executing "KEYS" withKey pattern logging { - case keys => log.debug(s"KEYS on '$pattern' responded '${keys.mkString(", ")}'.") + redis.keys(pattern) executing "KEYS" withKey pattern logging { case keys => + log.debug(s"KEYS on '$pattern' responded '${keys.mkString(", ")}'.") } // coverage is disabled as testing it would require @@ -136,8 +151,8 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R // the tests are in progress // $COVERAGE-OFF$ override def invalidate(): Future[Unit] = - redis.flushdb() executing "FLUSHDB" logging { - case _ => log.info("Invalidated.") // cache was invalidated + redis.flushdb() executing "FLUSHDB" logging { case _ => + log.info("Invalidated.") // cache was invalidated } // $COVERAGE-ON$ @@ -160,23 +175,23 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R } override def ping(): Future[Unit] = - redis.ping() executing "PING" logging { - case "PONG" => () + redis.ping() executing "PING" logging { case "PONG" => + () } override def increment(key: String, by: Long): Future[Long] = - redis.incrby(key, by) executing "INCRBY" withKey key andParameter s"$by" logging { - case value => log.debug(s"The value at key '$key' was incremented by $by to $value.") + redis.incrby(key, by) executing "INCRBY" withKey key andParameter s"$by" logging { case value => + log.debug(s"The value at key '$key' was incremented by $by to $value.") } override def append(key: String, value: String): Future[Long] = - redis.append(key, value) executing "APPEND" withKey key andParameter value logging { - case length => log.debug(s"The value was appended to key '$key'.") + redis.append(key, value) executing "APPEND" withKey key andParameter value logging { case _ => + log.debug(s"The value was appended to key '$key'.") } override def listPrepend(key: String, values: Any*): Future[Long] = - Future.sequence(values.map(encode(key, _))).flatMap(redis.lpush(key, _: _*)) executing "LPUSH" withKey key andParameters values logging { - case length => log.debug(s"The $length values was prepended to key '$key'.") + Future.sequence(values.map(encode(key, _))).flatMap(redis.lpush(key, _: _*)) executing "LPUSH" withKey key andParameters values logging { case length => + log.debug(s"The $length values was prepended to key '$key'.") } recover { case ExecutionFailedException(_, _, _, ex) if ex.getMessage startsWith "WRONGTYPE" => log.warn(s"Value at '$key' is not a list to be prepended.") @@ -184,8 +199,8 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R } override def listAppend(key: String, values: Any*): Future[Long] = - Future.sequence(values.map(encode(key, _))).flatMap(redis.rpush(key, _: _*)) executing "RPUSH" withKey key andParameters values logging { - case length => log.debug(s"The $length values was appended to key '$key'.") + Future.sequence(values.map(encode(key, _))).flatMap(redis.rpush(key, _: _*)) executing "RPUSH" withKey key andParameters values logging { case length => + log.debug(s"The $length values was appended to key '$key'.") } recover { case ExecutionFailedException(_, _, _, ex) if ex.getMessage startsWith "WRONGTYPE" => log.warn(s"Value at '$key' is not a list to be appended.") @@ -193,17 +208,16 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R } override def listSize(key: String): Future[Long] = - redis.llen(key) executing "LLEN" withKey key logging { - case length => log.debug(s"The collection at '$key' has $length items.") + redis.llen(key) executing "LLEN" withKey key logging { case length => + log.debug(s"The collection at '$key' has $length items.") } - override def listSetAt(key: String, position: Int, value: Any): Future[Unit] = - encode(key, value).flatMap(redis.lset(key, position, _)) executing "LSET" withKey key andParameter value logging { - case _ => log.debug(s"Updated value at $position in '$key' to $value.") - } recover { - case ExecutionFailedException(_, _, _, actors.ReplyErrorException("ERR index out of range")) => - log.debug(s"Update of the value at $position in '$key' failed due to index out of range.") - throw new IndexOutOfBoundsException("Index out of range") + override def listSetAt(key: String, position: Long, value: Any): Future[Unit] = + encode(key, value).flatMap(redis.lset(key, position, _)) executing "LSET" withKey key andParameter value logging { case _ => + log.debug(s"Updated value at $position in '$key' to $value.") + } map (_ => ()) recover { case ExecutionFailedException(_, _, _, actors.ReplyErrorException("ERR index out of range")) => + log.debug(s"Update of the value at $position in '$key' failed due to index out of range.") + throw new IndexOutOfBoundsException("Index out of range") } override def listHeadPop[T: ClassTag](key: String): Future[Option[T]] = @@ -211,52 +225,50 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R case Some(encoded) => log.trace(s"Hit on head in key '$key'.") Some(decode[T](key, encoded)) - case None => + case None => log.trace(s"Miss on head in key '$key'.") None } - override def listSlice[T: ClassTag](key: String, start: Int, end: Int): Future[Seq[T]] = - redis.lrange[String](key, start, end) executing "LRANGE" withKey key andParameters s"$start $end" expects { - case values => - log.debug(s"The range on '$key' from $start to $end included returned ${values.size} values.") - values.map(decode[T](key, _)) + override def listSlice[T: ClassTag](key: String, start: Long, end: Long): Future[Seq[T]] = + redis.lrange[String](key, start, end) executing "LRANGE" withKey key andParameters s"$start $end" expects { case values => + log.debug(s"The range on '$key' from $start to $end included returned ${values.size} values.") + values.map(decode[T](key, _)) } - override def listRemove(key: String, value: Any, count: Int): Future[Long] = - encode(key, value).flatMap(redis.lrem(key, count, _)) executing "LREM" withKey key andParameters s"$value $count" logging { - case removed => log.debug(s"Removed $removed occurrences of $value in '$key'.") + override def listRemove(key: String, value: Any, count: Long): Future[Long] = + encode(key, value).flatMap(redis.lrem(key, count, _)) executing "LREM" withKey key andParameters s"$value $count" logging { case removed => + log.debug(s"Removed $removed occurrences of $value in '$key'.") } - override def listTrim(key: String, start: Int, end: Int): Future[Unit] = - redis.ltrim(key, start, end) executing "LTRIM" withKey key andParameter s"$start $end" logging { - case _ => log.debug(s"Trimmed collection at '$key' to $start:$end ") + override def listTrim(key: String, start: Long, end: Long): Future[Unit] = + redis.ltrim(key, start, end) executing "LTRIM" withKey key andParameter s"$start $end" logging { case _ => + log.debug(s"Trimmed collection at '$key' to $start:$end ") } override def listInsert(key: String, pivot: Any, value: Any): Future[Option[Long]] = for { - pivot <- encode(key, pivot) - value <- encode(key, value) + pivot <- encode(key, pivot) + value <- encode(key, value) result <- redis.linsert(key, api.BEFORE, pivot, value) executing "LINSERT" withKey key andParameter s"$pivot $value" expects { - case -1L | 0L => - log.debug(s"Insert into the list at '$key' failed. Pivot not found.") - None - case length => - log.debug(s"Inserted $value into the list at '$key'. New size is $length.") - Some(length) - } recover[Option[Long]] { - case ExecutionFailedException(_, _, _, ex) if ex.getMessage startsWith "WRONGTYPE" => - log.warn(s"Value at '$key' is not a list.") - throw new IllegalArgumentException(s"Value at '$key' is not a list.") - } + case -1L | 0L => + log.debug(s"Insert into the list at '$key' failed. Pivot not found.") + None + case length => + log.debug(s"Inserted $value into the list at '$key'. New size is $length.") + Some(length) + } recover [Option[Long]] { + case ExecutionFailedException(_, _, _, ex) if ex.getMessage startsWith "WRONGTYPE" => + log.warn(s"Value at '$key' is not a list.") + throw new IllegalArgumentException(s"Value at '$key' is not a list.") + } } yield result override def setAdd(key: String, values: Any*): Future[Long] = { // encodes the value def toEncoded(value: Any) = encode(key, value) - Future.sequence(values map toEncoded).flatMap(redis.sadd(key, _: _*)) executing "SADD" withKey key andParameters values expects { - case inserted => - log.debug(s"Inserted $inserted elements into the set at '$key'.") - inserted + Future.sequence(values map toEncoded).flatMap(redis.sadd(key, _: _*)) executing "SADD" withKey key andParameters values expects { case inserted => + log.debug(s"Inserted $inserted elements into the set at '$key'.") + inserted } recover { case ExecutionFailedException(_, _, _, ex) if ex.getMessage startsWith "WRONGTYPE" => log.warn(s"Value at '$key' is not a set.") @@ -265,15 +277,14 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R } override def setSize(key: String): Future[Long] = - redis.scard(key) executing "SCARD" withKey key logging { - case length => log.debug(s"The collection at '$key' has $length items.") + redis.scard(key) executing "SCARD" withKey key logging { case length => + log.debug(s"The collection at '$key' has $length items.") } override def setMembers[T: ClassTag](key: String): Future[Set[T]] = - redis.smembers[String](key) executing "SMEMBERS" withKey key expects { - case items => - log.debug(s"Returned ${items.size} items from the collection at '$key'.") - items.map(decode[T](key, _)).toSet + redis.smembers[String](key) executing "SMEMBERS" withKey key expects { case items => + log.debug(s"Returned ${items.size} items from the collection at '$key'.") + items.map(decode[T](key, _)).toSet } override def setIsMember(key: String, value: Any): Future[Boolean] = @@ -286,8 +297,8 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R // encodes the value def toEncoded(value: Any): Future[String] = encode(key, value) - Future.sequence(values map toEncoded).flatMap(redis.srem(key, _: _*)) executing "SREM" withKey key andParameters values logging { - case removed => log.debug(s"Removed $removed elements from the collection at '$key'.") + Future.sequence(values map toEncoded).flatMap(redis.srem(key, _: _*)) executing "SREM" withKey key andParameters values logging { case removed => + log.debug(s"Removed $removed elements from the collection at '$key'.") } } @@ -295,10 +306,9 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R // encodes the value def toEncoded(scoreValue: (Double, Any)) = encode(key, scoreValue._2).map((scoreValue._1, _)) - Future.sequence(scoreValues.map(toEncoded)).flatMap(redis.zadd(key, _: _*)) executing "ZADD" withKey key andParameters scoreValues expects { - case inserted => - log.debug(s"Inserted $inserted elements into the zset at '$key'.") - inserted + Future.sequence(scoreValues.map(toEncoded)).flatMap(redis.zadd(key, _: _*)) executing "ZADD" withKey key andParameters scoreValues expects { case inserted => + log.debug(s"Inserted $inserted elements into the zset at '$key'.") + inserted } recover { case ExecutionFailedException(_, _, _, ex) if ex.getMessage startsWith "WRONGTYPE" => log.warn(s"Value at '$key' is not a zset.") @@ -307,50 +317,45 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R } override def sortedSetSize(key: String): Future[Long] = - redis.zcard(key) executing "ZCARD" withKey key logging { - case length => log.debug(s"The zset at '$key' has $length items.") + redis.zcard(key) executing "ZCARD" withKey key logging { case length => + log.debug(s"The zset at '$key' has $length items.") } - override def sortedSetScore(key: String, value: Any): Future[Option[Double]] = { + override def sortedSetScore(key: String, value: Any): Future[Option[Double]] = encode(key, value) flatMap (redis.zscore(key, _)) executing "ZSCORE" withKey key andParameter value logging { case Some(score) => log.debug(s"The score of item: $value is $score in the collection at '$key'.") case None => log.debug(s"Item $value does not exist in the collection at '$key'") } - } override def sortedSetRemove(key: String, values: Any*): Future[Long] = { // encodes the value def toEncoded(value: Any) = encode(key, value) - Future.sequence(values map toEncoded).flatMap(redis.zrem(key, _: _*)) executing "ZREM" withKey key andParameters values logging { - case removed => log.debug(s"Removed $removed elements from the zset at '$key'.") + Future.sequence(values map toEncoded).flatMap(redis.zrem(key, _: _*)) executing "ZREM" withKey key andParameters values logging { case removed => + log.debug(s"Removed $removed elements from the zset at '$key'.") } } - override def sortedSetRange[T: ClassTag](key: String, start: Long, stop: Long): Future[Seq[T]] = { - redis.zrange[String](key, start, stop) executing "ZRANGE" withKey key andParameter s"$start $stop" expects { - case encodedSeq => - log.debug(s"Got range from $start to $stop in the zset at '$key'.") - encodedSeq.map(encoded => decode[T](key, encoded)) + override def sortedSetRange[T: ClassTag](key: String, start: Long, stop: Long): Future[Seq[T]] = + redis.zrange[String](key, start, stop) executing "ZRANGE" withKey key andParameter s"$start $stop" expects { case encodedSeq => + log.debug(s"Got range from $start to $stop in the zset at '$key'.") + encodedSeq.map(encoded => decode[T](key, encoded)) } - } - override def sortedSetReverseRange[T: ClassTag](key: String, start: Long, stop: Long): Future[Seq[T]] = { - redis.zrevrange[String](key, start, stop) executing "ZREVRANGE" withKey key andParameter s"$start $stop" expects { - case encodedSeq => - log.debug(s"Got reverse range from $start to $stop in the zset at '$key'.") - encodedSeq.map(encoded => decode[T](key, encoded)) + override def sortedSetReverseRange[T: ClassTag](key: String, start: Long, stop: Long): Future[Seq[T]] = + redis.zrevrange[String](key, start, stop) executing "ZREVRANGE" withKey key andParameter s"$start $stop" expects { case encodedSeq => + log.debug(s"Got reverse range from $start to $stop in the zset at '$key'.") + encodedSeq.map(encoded => decode[T](key, encoded)) } - } override def hashRemove(key: String, fields: String*): Future[Long] = - redis.hdel(key, fields: _*) executing "HDEL" withKey key andParameters fields logging { - case removed => log.debug(s"Removed $removed elements from the collection at '$key'.") + redis.hdel(key, fields: _*) executing "HDEL" withKey key andParameters fields logging { case removed => + log.debug(s"Removed $removed elements from the collection at '$key'.") } override def hashIncrement(key: String, field: String, incrementBy: Long): Future[Long] = - redis.hincrby(key, field, incrementBy) executing "HINCRBY" withKey key andParameters s"$field $incrementBy" logging { - case value => log.debug(s"Field '$field' in '$key' was incremented to $value.") + redis.hincrby(key, field, incrementBy) executing "HINCRBY" withKey key andParameters s"$field $incrementBy" logging { case value => + log.debug(s"Field '$field' in '$key' was incremented to $value.") } override def hashExists(key: String, field: String): Future[Boolean] = @@ -364,16 +369,15 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R case Some(encoded) => log.debug(s"Item $field exists in the collection at '$key'.") Some(decode[T](key, encoded)) - case None => + case None => log.debug(s"Item $field is not in the collection at '$key'.") None } override def hashGet[T: ClassTag](key: String, fields: Seq[String]): Future[Seq[Option[T]]] = - redis.hmget[String](key, fields: _*) executing "HMGET" withKey key andParameters fields expects { - case encoded => - log.debug(s"Collection at '$key' with fields '$fields' has returned ${encoded.size} items.") - encoded.map(_.map(decode[T](key, _))) + redis.hmget[String](key, fields: _*) executing "HMGET" withKey key andParameters fields expects { case encoded => + log.debug(s"Collection at '$key' with fields '$fields' has returned ${encoded.size} items.") + encoded.map(_.map(decode[T](key, _))) } override def hashGetAll[T: ClassTag](key: String): Future[Map[String, T]] = @@ -381,21 +385,20 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R case empty if empty.isEmpty => log.debug(s"Collection at '$key' is empty.") Map.empty[String, T] - case encoded => + case encoded => log.debug(s"Collection at '$key' has ${encoded.size} items.") encoded.map { case (itemKey, value) => itemKey -> decode[T](itemKey, value) } } override def hashSize(key: String): Future[Long] = - redis.hlen(key) executing "HLEN" withKey key logging { - case length => log.debug(s"The collection at '$key' has $length items.") + redis.hlen(key) executing "HLEN" withKey key logging { case length => + log.debug(s"The collection at '$key' has $length items.") } override def hashKeys(key: String): Future[Set[String]] = - redis.hkeys(key) executing "HKEYS" withKey key expects { - case keys => - log.debug(s"The collection at '$key' defines: ${keys mkString " "}.") - keys.toSet + redis.hkeys(key) executing "HKEYS" withKey key expects { case keys => + log.debug(s"The collection at '$key' defines: ${keys mkString " "}.") + keys.toSet } override def hashSet(key: String, field: String, value: Any): Future[Boolean] = @@ -409,13 +412,12 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R } override def hashValues[T: ClassTag](key: String): Future[Set[T]] = - redis.hvals[String](key) executing "HVALS" withKey key expects { - case values => - log.debug(s"The collection at '$key' contains ${values.size} values.") - values.map(decode[T](key, _)).toSet + redis.hvals[String](key) executing "HVALS" withKey key expects { case values => + log.debug(s"The collection at '$key' contains ${values.size} values.") + values.map(decode[T](key, _)).toSet } // $COVERAGE-OFF$ - override def toString = s"RedisConnector(name=$name)" + override def toString: String = s"RedisConnector(name=$name)" // $COVERAGE-ON$ } diff --git a/src/main/scala/play/api/cache/redis/connector/RedisConnectorProvider.scala b/src/main/scala/play/api/cache/redis/connector/RedisConnectorProvider.scala index 32bc7d60..31dc2984 100644 --- a/src/main/scala/play/api/cache/redis/connector/RedisConnectorProvider.scala +++ b/src/main/scala/play/api/cache/redis/connector/RedisConnectorProvider.scala @@ -1,15 +1,12 @@ package play.api.cache.redis.connector -import javax.inject.Provider - +import akka.actor.ActorSystem import play.api.cache.redis._ import play.api.inject.ApplicationLifecycle -import akka.actor.ActorSystem +import javax.inject.Provider -/** - * Provides an instance of named redis connector - */ +/** Provides an instance of named redis connector */ private[redis] class RedisConnectorProvider(instance: RedisInstance, serializer: AkkaSerializer)(implicit system: ActorSystem, lifecycle: ApplicationLifecycle, runtime: RedisRuntime) extends Provider[RedisConnector] { private[connector] lazy val commands = new RedisCommandsProvider(instance).get diff --git a/src/main/scala/play/api/cache/redis/connector/RequestTimeout.scala b/src/main/scala/play/api/cache/redis/connector/RequestTimeout.scala index b3a3e886..57c500b9 100644 --- a/src/main/scala/play/api/cache/redis/connector/RequestTimeout.scala +++ b/src/main/scala/play/api/cache/redis/connector/RequestTimeout.scala @@ -1,43 +1,39 @@ package play.api.cache.redis.connector -import scala.concurrent.{ExecutionContext, Future} -import scala.concurrent.duration._ - import akka.actor.Scheduler import akka.pattern.after import redis._ +import scala.concurrent.duration._ +import scala.concurrent.{ExecutionContext, Future} + /** - * - * Helper for manipulation with the request to the redis. - * It defines the common variables and methods to avoid - * code duplication + * Helper for manipulation with the request to the redis. It defines the common + * variables and methods to avoid code duplication */ trait RequestTimeout extends Request { - protected implicit val scheduler: Scheduler + implicit protected val scheduler: Scheduler } object RequestTimeout { // fails @inline - def fail(failAfter: FiniteDuration)(implicit scheduler: Scheduler, context: ExecutionContext): Future[Nothing] = { + def fail(failAfter: FiniteDuration)(implicit scheduler: Scheduler, context: ExecutionContext): Future[Nothing] = after(failAfter, scheduler)(Future.failed(redis.actors.NoConnectionException)) - } // first completed @inline - def invokeOrFail[T](continue: => Future[T], failAfter: FiniteDuration)(implicit scheduler: Scheduler, context: ExecutionContext): Future[T] = { + def invokeOrFail[T](continue: => Future[T], failAfter: FiniteDuration)(implicit scheduler: Scheduler, context: ExecutionContext): Future[T] = Future.firstCompletedOf(Seq(continue, fail(failAfter))) - } + } /** - * Actor extension maintaining current connected status. - * The operations are not invoked when the connection - * is not established, the failed future is returned - * instead. + * Actor extension maintaining current connected status. The operations are not + * invoked when the connection is not established, the failed future is + * returned instead. */ trait FailEagerly extends RequestTimeout { import RequestTimeout._ @@ -48,18 +44,19 @@ trait FailEagerly extends RequestTimeout { @inline protected def connectionTimeout: Option[FiniteDuration] - abstract override def send[T](redisCommand: RedisCommand[_ <: protocol.RedisReply, T]): Future[T] = { + abstract override def send[T](redisCommand: RedisCommand[? <: protocol.RedisReply, T]): Future[T] = { // proceed with the command @inline def continue: Future[T] = super.send(redisCommand) // based on connection status if (connected) continue else connectionTimeout.fold(continue)(invokeOrFail(continue, _)) } + } /** - * Actor extension implementing a request timeout, if enabled. - * This is due to no internal timeout provided by - * the redis-scala to avoid never-completed futures. + * Actor extension implementing a request timeout, if enabled. This is due to + * no internal timeout provided by the redis-scala to avoid never-completed + * futures. */ trait RedisRequestTimeout extends RequestTimeout { import RequestTimeout._ @@ -69,7 +66,7 @@ trait RedisRequestTimeout extends RequestTimeout { /** indicates the timeout on the redis request */ protected def timeout: Option[FiniteDuration] - abstract override def send[T](redisCommand: RedisCommand[_ <: protocol.RedisReply, T]): Future[T] = { + abstract override def send[T](redisCommand: RedisCommand[? <: protocol.RedisReply, T]): Future[T] = { // proceed with the command @inline def continue: Future[T] = super.send(redisCommand) // based on connection status diff --git a/src/main/scala/play/api/cache/redis/connector/package.scala b/src/main/scala/play/api/cache/redis/connector/package.scala index 76638403..95d3aba2 100644 --- a/src/main/scala/play/api/cache/redis/connector/package.scala +++ b/src/main/scala/play/api/cache/redis/connector/package.scala @@ -1,13 +1,12 @@ package play.api.cache.redis import scala.concurrent.Future -import scala.language.implicitConversions package object connector { implicit def future2expected[T](future: Future[T]): ExpectedFutureBuilder[T] = new ExpectedFutureBuilder[T](future) - implicit class TupleHelper[+A, +B](val tuple: (A, B)) extends AnyVal { + implicit class TupleHelper[+A, +B](private val tuple: (A, B)) extends AnyVal { @inline def key: A = tuple._1 @inline def value: B = tuple._2 @inline def asString: String = s"$key $value" @@ -17,4 +16,5 @@ package object connector { implicit class StringWhen(private val value: String) extends AnyVal { def when(condition: Boolean): String = if (condition) value else "" } + } diff --git a/src/main/scala/play/api/cache/redis/exceptions.scala b/src/main/scala/play/api/cache/redis/exceptions.scala index 276efacc..7e850a08 100644 --- a/src/main/scala/play/api/cache/redis/exceptions.scala +++ b/src/main/scala/play/api/cache/redis/exceptions.scala @@ -1,38 +1,25 @@ package play.api.cache.redis -/** - * Generic exception produced by the library indicating internal failure - */ +/** Generic exception produced by the library indicating internal failure */ sealed abstract class RedisException(message: String, cause: Throwable) extends RuntimeException(message, cause) { def this(message: String) = this(message, null) } -/** - * Request timeouts -*/ -final case class TimeoutException( - cause: Throwable) extends RedisException("Command execution timed out", cause) +/** Request timeouts */ +final case class TimeoutException(cause: Throwable) extends RedisException("Command execution timed out", cause) -/** - * Command execution failed with exception - */ -final case class ExecutionFailedException( - key: Option[String], command: String, statement: String, cause: Throwable) extends RedisException(s"Execution of '$command'${key.map(key => s" for key '$key'") getOrElse ""} failed", cause) +/** Command execution failed with exception */ +final case class ExecutionFailedException(key: Option[String], command: String, statement: String, cause: Throwable) extends RedisException(s"Execution of '$command'${key.map(key => s" for key '$key'") getOrElse ""} failed", cause) -/** - * Request succeeded but returned unexpected value - */ -final case class UnexpectedResponseException( - key: Option[String], command: String) extends RedisException(s"Command '$command'${key.map(key => s" for key '$key'") getOrElse ""} returned unexpected response") +/** Request succeeded but returned unexpected value */ +final case class UnexpectedResponseException(key: Option[String], command: String) extends RedisException(s"Command '$command'${key.map(key => s" for key '$key'") getOrElse ""} returned unexpected response") -/** - * Value serialization or deserialization failed. - */ -final case class SerializationException( - key: String, message: String, cause: Throwable) extends RedisException(s"$message for $key", cause) +/** Value serialization or deserialization failed. */ +final case class SerializationException(key: String, message: String, cause: Throwable) extends RedisException(s"$message for $key", cause) /** - * Helper trait providing simplified and unified API to exception handling in play-redis + * Helper trait providing simplified and unified API to exception handling in + * play-redis */ trait ExceptionImplicits { @@ -65,4 +52,5 @@ trait ExceptionImplicits { @throws[IllegalStateException] def invalidConfiguration(message: String): Nothing = throw new IllegalStateException(message) + } diff --git a/src/main/scala/play/api/cache/redis/impl/AsyncJavaRedis.scala b/src/main/scala/play/api/cache/redis/impl/AsyncJavaRedis.scala index 873de9d7..951d34a1 100644 --- a/src/main/scala/play/api/cache/redis/impl/AsyncJavaRedis.scala +++ b/src/main/scala/play/api/cache/redis/impl/AsyncJavaRedis.scala @@ -1,56 +1,56 @@ package play.api.cache.redis.impl -import scala.concurrent.duration._ -import scala.concurrent.{ExecutionContext, Future} -import scala.reflect.ClassTag - import play.api.Environment import play.api.cache.redis._ import play.cache.redis._ +import scala.concurrent.duration._ +import scala.concurrent.{ExecutionContext, Future} +import scala.reflect.ClassTag + /** * Implements Play Java version of play.api.CacheApi * - * This acts as an adapter to Play Scala CacheApi, because Java Api is slightly different than Scala Api + * This acts as an adapter to Play Scala CacheApi, because Java Api is slightly + * different than Scala Api */ private[impl] class AsyncJavaRedis(internal: CacheAsyncApi)(implicit environment: Environment, runtime: RedisRuntime) extends play.cache.AsyncCacheApi with play.cache.redis.AsyncCacheApi { import JavaCompatibility._ - def set(key: String, value: scala.Any, expiration: Int): CompletionStage[Done] = { + def set(key: String, value: scala.Any, expiration: Int): CompletionStage[Done] = async { implicit context => set(key, value, expiration.seconds) } - } - def set(key: String, value: scala.Any): CompletionStage[Done] = { + def set(key: String, value: scala.Any): CompletionStage[Done] = async { implicit context => set(key, value, Duration.Inf) } - } - private def set(key: String, value: scala.Any, duration: Duration)(implicit ec: ExecutionContext): Future[Done] = { - Future.from( - // set the value - internal.set(key, value, duration), - // and set its type to be able to read it - internal.set(classTagKey(key), classTagOf(value), duration) - ).asDone - } + private def set(key: String, value: scala.Any, duration: Duration)(implicit ec: ExecutionContext): Future[Done] = + Future + .from( + // set the value + internal.set(key, value, duration), + // and set its type to be able to read it + internal.set(classTagKey(key), classTagOf(value), duration), + ) + .asDone - def remove(key: String): CompletionStage[Done] = { + def remove(key: String): CompletionStage[Done] = async { implicit context => - Future.from( - internal.remove(key), - internal.remove(classTagKey(key)) - ).asDone + Future + .from( + internal.remove(key), + internal.remove(classTagKey(key)), + ) + .asDone } - } - def get[T](key: String): CompletionStage[Optional[T]] = { + def get[T](key: String): CompletionStage[Optional[T]] = async { implicit context => getOrElseOption[T](key, None).map(_.asJava) } - } def getOrElse[T](key: String, block: Callable[T]): CompletionStage[T] = getOrElseUpdate[T](key, (() => Future.successful(block.call()).asJava).asJava) @@ -64,11 +64,10 @@ private[impl] class AsyncJavaRedis(internal: CacheAsyncApi)(implicit environment def getOrElseUpdate[T](key: String, block: Callable[CompletionStage[T]], expiration: Int): CompletionStage[T] = getOrElse[T](key, Some(block), duration = expiration.seconds) - private def getOrElse[T](key: String, callable: Option[Callable[CompletionStage[T]]], duration: Duration = Duration.Inf): CompletionStage[T] = { + private def getOrElse[T](key: String, callable: Option[Callable[CompletionStage[T]]], duration: Duration = Duration.Inf): CompletionStage[T] = async { implicit context => getOrElseOption(key, callable, duration).map[T](play.libs.Scala.orNull) } - } private def getOrElseOption[T](key: String, callable: Option[Callable[CompletionStage[T]]], duration: Duration = Duration.Inf)(implicit context: ExecutionContext): Future[Option[T]] = { // get the tag and decode it @@ -83,8 +82,8 @@ private[impl] class AsyncJavaRedis(internal: CacheAsyncApi)(implicit environment // compute or else and save it into cache def orElse(callable: Callable[CompletionStage[T]]) = callable.call().asScala def saveOrElse(value: T) = set(key, value, duration) - def savedOrElse(callable: Callable[CompletionStage[T]]) = orElse(callable).flatMap { - value => runtime.invocation.invoke(saveOrElse(value), Some(value)) + def savedOrElse(callable: Callable[CompletionStage[T]]) = orElse(callable).flatMap { value => + runtime.invocation.invoke(saveOrElse(value), Some(value)) } getValue.flatMap { @@ -93,125 +92,123 @@ private[impl] class AsyncJavaRedis(internal: CacheAsyncApi)(implicit environment } } - def getAll[T](classTag: Class[T], keys: JavaList[String]): CompletionStage[JavaList[Optional[T]]] = { + def getAll[T](classTag: Class[T], keys: JavaList[String]): CompletionStage[JavaList[Optional[T]]] = async { implicit context => internal.getAll(keys.asScala)(classTag).map(_.map(_.asJava).asJava) } - } def removeAll(): CompletionStage[Done] = internal.invalidate().asJava - def exists(key: String): CompletionStage[java.lang.Boolean] = { + def exists(key: String): CompletionStage[java.lang.Boolean] = async { implicit context => internal.exists(key).map(Boolean.box) } - } - def matching(pattern: String): CompletionStage[JavaList[String]] = { + def matching(pattern: String): CompletionStage[JavaList[String]] = async { implicit context => internal.matching(pattern).map(_.asJava) } - } - def setIfNotExists(key: String, value: Any): CompletionStage[java.lang.Boolean] = { + def setIfNotExists(key: String, value: Any): CompletionStage[java.lang.Boolean] = async { implicit context => - Future.from( - internal.setIfNotExists(key, value).map(Boolean.box), - internal.setIfNotExists(classTagKey(key), classTagOf(value)).map(Boolean.box) - ).map(_.head) + Future + .from( + internal.setIfNotExists(key, value).map(Boolean.box), + internal.setIfNotExists(classTagKey(key), classTagOf(value)).map(Boolean.box), + ) + .map(_.head) } - } - def setIfNotExists(key: String, value: Any, expiration: Int): CompletionStage[java.lang.Boolean] = { + def setIfNotExists(key: String, value: Any, expiration: Int): CompletionStage[java.lang.Boolean] = async { implicit context => - Future.from( - internal.setIfNotExists(key, value, expiration.seconds).map(Boolean.box), - internal.setIfNotExists(classTagKey(key), classTagOf(value), expiration.seconds).map(Boolean.box) - ).map(_.head) + Future + .from( + internal.setIfNotExists(key, value, expiration.seconds).map(Boolean.box), + internal.setIfNotExists(classTagKey(key), classTagOf(value), expiration.seconds).map(Boolean.box), + ) + .map(_.head) } - } - def setAll(keyValues: KeyValue*): CompletionStage[Done] = { - async { implicit context => + def setAll(keyValues: KeyValue*): CompletionStage[Done] = + async { _ => internal.setAll( keyValues.flatMap { kv => Iterable((kv.key, kv.value), (classTagKey(kv.key), classTagOf(kv.value))) - }: _* + }: _*, ) } - } - def setAllIfNotExist(keyValues: KeyValue*): CompletionStage[java.lang.Boolean] = { + def setAllIfNotExist(keyValues: KeyValue*): CompletionStage[java.lang.Boolean] = async { implicit context => - internal.setAllIfNotExist( - keyValues.flatMap(kv => Seq((kv.key, kv.value), (classTagKey(kv.key), classTagOf(kv.value)))): _* - ).map(Boolean.box) + internal + .setAllIfNotExist( + keyValues.flatMap(kv => Seq((kv.key, kv.value), (classTagKey(kv.key), classTagOf(kv.value)))): _*, + ) + .map(Boolean.box) } - } - def append(key: String, value: String): CompletionStage[Done] = { + def append(key: String, value: String): CompletionStage[Done] = async { implicit context => - Future.from( - internal.append(key, value), - internal.setIfNotExists(classTagKey(key), classTagOf(value)) - ).asDone + Future + .from( + internal.append(key, value), + internal.setIfNotExists(classTagKey(key), classTagOf(value)).asDone, + ) + .asDone } - } - def append(key: String, value: String, expiration: Int): CompletionStage[Done] = { + def append(key: String, value: String, expiration: Int): CompletionStage[Done] = async { implicit context => - Future.from( - internal.append(key, value, expiration.seconds), - internal.setIfNotExists(classTagKey(key), classTagOf(value), expiration.seconds) - ).asDone + Future + .from( + internal.append(key, value, expiration.seconds), + internal.setIfNotExists(classTagKey(key), classTagOf(value), expiration.seconds).asDone, + ) + .asDone } - } - def expire(key: String, expiration: Int): CompletionStage[Done] = { + def expire(key: String, expiration: Int): CompletionStage[Done] = async { implicit context => - Future.from( - internal.expire(key, expiration.seconds), - internal.expire(classTagKey(key), expiration.seconds) - ).asDone + Future + .from( + internal.expire(key, expiration.seconds), + internal.expire(classTagKey(key), expiration.seconds), + ) + .asDone } - } - def expiresIn(key: String): CompletionStage[Optional[java.lang.Long]] = { + def expiresIn(key: String): CompletionStage[Optional[java.lang.Long]] = async { implicit context => internal.expiresIn(key).map(_.map(_.toSeconds).map(Long.box).asJava) } - } - def remove(key1: String, key2: String, keys: String*): CompletionStage[Done] = { + def remove(key1: String, key2: String, keys: String*): CompletionStage[Done] = removeAllKeys(Seq(key1, key2) ++ keys: _*) - } - def removeAllKeys(keys: String*): CompletionStage[Done] = { - async { implicit context => + def removeAllKeys(keys: String*): CompletionStage[Done] = + async { _ => internal.removeAll(keys.flatMap(_.withClassTag): _*) } - } - def removeMatching(pattern: String): CompletionStage[Done] = { + def removeMatching(pattern: String): CompletionStage[Done] = async { implicit context => - Future.from( - internal.removeMatching(pattern), - internal.removeMatching(classTagKey(pattern)) - ).asDone + Future + .from( + internal.removeMatching(pattern), + internal.removeMatching(classTagKey(pattern)), + ) + .asDone } - } - def increment(key: String, by: java.lang.Long): CompletionStage[java.lang.Long] = { + def increment(key: String, by: java.lang.Long): CompletionStage[java.lang.Long] = async { implicit context => internal.increment(key, by).map(Long.box) } - } - def decrement(key: String, by: java.lang.Long): CompletionStage[java.lang.Long] = { + def decrement(key: String, by: java.lang.Long): CompletionStage[java.lang.Long] = async { implicit context => internal.decrement(key, by).map(Long.box) } - } def list[T](key: String, classTag: Class[T]): AsyncRedisList[T] = new RedisListJavaImpl(internal.list[T](key)(classTag)) diff --git a/src/main/scala/play/api/cache/redis/impl/AsyncRedisImpl.scala b/src/main/scala/play/api/cache/redis/impl/AsyncRedisImpl.scala index 40f01615..316e9e3d 100644 --- a/src/main/scala/play/api/cache/redis/impl/AsyncRedisImpl.scala +++ b/src/main/scala/play/api/cache/redis/impl/AsyncRedisImpl.scala @@ -1,14 +1,12 @@ package play.api.cache.redis.impl +import play.api.cache.redis._ + import scala.concurrent.Future import scala.concurrent.duration.Duration import scala.reflect.ClassTag -import play.api.cache.redis._ - -/** - * Implementation of **asynchronous** Redis API - */ +/** Implementation of **asynchronous** Redis API */ private[impl] trait AsyncRedis extends play.api.cache.AsyncCacheApi with CacheAsyncApi private[impl] class AsyncRedisImpl(redis: RedisConnector)(implicit runtime: RedisRuntime) extends RedisCache(redis, Builders.AsynchronousBuilder) with AsyncRedis { diff --git a/src/main/scala/play/api/cache/redis/impl/Builders.scala b/src/main/scala/play/api/cache/redis/impl/Builders.scala index 35c62cb6..4a45fc28 100644 --- a/src/main/scala/play/api/cache/redis/impl/Builders.scala +++ b/src/main/scala/play/api/cache/redis/impl/Builders.scala @@ -3,23 +3,30 @@ package play.api.cache.redis.impl import scala.concurrent.Future /** - * Transforms future result produced by redis implementation to the result of the desired type + * Transforms future result produced by redis implementation to the result of + * the desired type */ -object Builders { +private object Builders { + import dsl._ import play.api.cache.redis._ import akka.pattern.AskTimeoutException - trait ResultBuilder[Result[X]] { + trait ResultBuilder[Result[_]] { + /** name of the builder used for internal purposes */ def name: String - /** converts future result produced by Redis to the result of desired type */ + + /** + * converts future result produced by Redis to the result of desired type + */ def toResult[T](run: => Future[T], default: => Future[T])(implicit runtime: RedisRuntime): Result[T] + /** maps the value */ def map[T, U](result: Result[T])(f: T => U)(implicit runtime: RedisRuntime): Result[U] // $COVERAGE-OFF$ /** show the builder name */ - override def toString = s"ResultBuilder($name)" + override def toString: String = s"ResultBuilder($name)" // $COVERAGE-ON$ } @@ -36,6 +43,7 @@ object Builders { override def map[T, U](result: AsynchronousResult[T])(f: T => U)(implicit runtime: RedisRuntime): AsynchronousResult[U] = result.map(f) + } /** converts the future into the value */ @@ -61,5 +69,7 @@ object Builders { override def map[T, U](result: SynchronousResult[T])(f: T => U)(implicit runtime: RedisRuntime): SynchronousResult[U] = f(result) + } + } diff --git a/src/main/scala/play/api/cache/redis/impl/InvocationPolicy.scala b/src/main/scala/play/api/cache/redis/impl/InvocationPolicy.scala index 949d0ace..6bd6837a 100644 --- a/src/main/scala/play/api/cache/redis/impl/InvocationPolicy.scala +++ b/src/main/scala/play/api/cache/redis/impl/InvocationPolicy.scala @@ -3,26 +3,31 @@ package play.api.cache.redis.impl import scala.concurrent.{ExecutionContext, Future} /** - * Invocation policy implements whether to wait for the operation result or not. - * This applies only in the limited number of operations. The best examples are `getOrElse` - * and `getOrFuture`. First, both methods invoke `get`, then, if missed, compute `orElse` clause. - * Finally, there is the invocation of `set`, however, in some scenarios, there is not required to - * wait for the result of `set` operation. The value can be returned earlier. This is the difference - * between `Eager` (not waiting) and `Lazy` (waiting) invocation policies. + * Invocation policy implements whether to wait for the operation result or + * not. This applies only in the limited number of operations. The best + * examples are `getOrElse` and `getOrFuture`. First, both methods invoke + * `get`, then, if missed, compute `orElse` clause. Finally, there is the + * invocation of `set`, however, in some scenarios, there is not required to + * wait for the result of `set` operation. The value can be returned earlier. + * This is the difference between `Eager` (not waiting) and `Lazy` (waiting) + * invocation policies. */ sealed trait InvocationPolicy { def invoke[T](f: => Future[Any], thenReturn: T)(implicit context: ExecutionContext): Future[T] } object EagerInvocation extends InvocationPolicy { + override def invoke[T](f: => Future[Any], thenReturn: T)(implicit context: ExecutionContext): Future[T] = { f: Unit Future successful thenReturn } + } object LazyInvocation extends InvocationPolicy { - override def invoke[T](f: => Future[Any], thenReturn: T)(implicit context: ExecutionContext): Future[T] = { + + override def invoke[T](f: => Future[Any], thenReturn: T)(implicit context: ExecutionContext): Future[T] = f.map(_ => thenReturn) - } + } diff --git a/src/main/scala/play/api/cache/redis/impl/JavaCompatibility.scala b/src/main/scala/play/api/cache/redis/impl/JavaCompatibility.scala index d6b56e5d..39a03032 100644 --- a/src/main/scala/play/api/cache/redis/impl/JavaCompatibility.scala +++ b/src/main/scala/play/api/cache/redis/impl/JavaCompatibility.scala @@ -1,13 +1,11 @@ package play.api.cache.redis.impl -import scala.concurrent.{ExecutionContext, Future} -import scala.language.implicitConversions -import scala.reflect.ClassTag - +import akka.Done import play.api.Environment import play.api.cache.redis._ -import akka.Done +import scala.concurrent.{ExecutionContext, Future} +import scala.reflect.ClassTag private[impl] object JavaCompatibility extends JavaCompatibilityBase { import scala.compat.java8.{FutureConverters, OptionConverters} @@ -20,61 +18,60 @@ private[impl] object JavaCompatibility extends JavaCompatibilityBase { type JavaSet[T] = java.util.Set[T] object JavaList { + def apply[T](values: T*): JavaList[T] = { val list = new java.util.ArrayList[T]() list.addAll(values.asJava): Unit list } + } - implicit class Java8Stage[T](val future: Future[T]) extends AnyVal { + implicit class Java8Stage[T](private val future: Future[T]) extends AnyVal { @inline def asJava: CompletionStage[T] = FutureConverters.toJava(future) @inline def asDone(implicit ec: ExecutionContext): Future[Done] = future.map(_ => Done) } - implicit class Java8Callable[T](val f: () => T) extends AnyVal { + implicit class Java8Callable[T](private val f: () => T) extends AnyVal { @inline def asJava: Callable[T] = () => f() } - implicit class Java8Optional[T](val option: Option[T]) extends AnyVal { + implicit class Java8Optional[T](private val option: Option[T]) extends AnyVal { @inline def asJava: Optional[T] = OptionConverters.toJava(option) } - implicit class ScalaCompatibility[T](val future: CompletionStage[T]) extends AnyVal { + implicit class ScalaCompatibility[T](private val future: CompletionStage[T]) extends AnyVal { @inline def asScala: Future[T] = FutureConverters.toScala(future) } - implicit class RichFuture(val future: Future.type) extends AnyVal { + implicit class RichFuture(private val future: Future.type) extends AnyVal { @inline def from[T](futures: Future[T]*)(implicit ec: ExecutionContext): Future[Seq[T]] = future.sequence(futures) } @inline implicit def class2tag[T](classOf: Class[T]): ClassTag[T] = ClassTag(classOf) - @inline def async[T](doAsync: ExecutionContext => Future[T])(implicit runtime: RedisRuntime): CompletionStage[T] = { + @inline def async[T](doAsync: ExecutionContext => Future[T])(implicit runtime: RedisRuntime): CompletionStage[T] = doAsync { // save the HTTP context if any and restore it later for orElse clause play.core.j.ClassLoaderExecutionContext.fromThread(runtime.context) }.asJava - } @inline def classTagKey(key: String): String = s"classTag::$key" - @inline def classTagOf(value: Any): String = { + @inline def classTagOf(value: Any): String = if (Option(value).isEmpty) "null" else value.getClass.getCanonicalName - } - @inline def classTagFrom[T](tag: String)(implicit environment: Environment): ClassTag[T] = { + @inline def classTagFrom[T](tag: String)(implicit environment: Environment): ClassTag[T] = if (tag === "null") ClassTag.Null.asInstanceOf[ClassTag[T]] else ClassTag(classTagNameToClass(tag, environment)) - } - implicit class CacheKey(val key: String) extends AnyVal { + implicit class CacheKey(private val key: String) extends AnyVal { @inline def withClassTag: Seq[String] = Seq(key, classTagKey(key)) } // $COVERAGE-OFF$ /** java primitives are serialized into their type names instead of classes */ - private def classTagNameToClass(name: String, environment: Environment): Class[_] = name match { + private def classTagNameToClass(name: String, environment: Environment): Class[?] = name match { case "boolean[]" => classOf[Array[java.lang.Boolean]] case "byte[]" => classOf[Array[java.lang.Byte]] case "char[]" => classOf[Array[java.lang.Character]] diff --git a/src/main/scala/play/api/cache/redis/impl/RedisCache.scala b/src/main/scala/play/api/cache/redis/impl/RedisCache.scala index 5aa38560..926e28db 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisCache.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisCache.scala @@ -1,13 +1,15 @@ package play.api.cache.redis.impl +import play.api.cache.redis._ + import scala.concurrent._ import scala.concurrent.duration.Duration -import scala.language.implicitConversions import scala.reflect.ClassTag -import play.api.cache.redis._ - -/**

Implementation of plain API using redis-server cache and Brando connector implementation.

*/ +/** + *

Implementation of plain API using redis-server cache and Brando connector + * implementation.

+ */ private[impl] class RedisCache[Result[_]](redis: RedisConnector, builder: Builders.ResultBuilder[Result])(implicit runtime: RedisRuntime) extends AbstractCacheApi[Result] { // implicit ask timeout and execution context @@ -47,11 +49,14 @@ private[impl] class RedisCache[Result[_]](redis: RedisConnector, builder: Builde override def append(key: String, value: String, expiration: Duration): Result[Done] = key.prefixed { key => - redis.append(key, value).flatMap { result => - // if the new string length is equal to the appended string, it means they should equal - // when the finite duration is required, set it - if (result === value.length && expiration.isFinite) redis.expire(key, expiration) else Future.successful[Unit](()) - }.recoverWithDone + redis + .append(key, value) + .flatMap { result => + // if the new string length is equal to the appended string, it means they should equal + // when the finite duration is required, set it + if (result === value.length.toLong && expiration.isFinite) redis.expire(key, expiration) else Future.successful[Unit](()) + } + .recoverWithDone } override def expire(key: String, expiration: Duration): Result[Done] = @@ -60,14 +65,15 @@ private[impl] class RedisCache[Result[_]](redis: RedisConnector, builder: Builde } /** - * cached implementation of the matching function - * - * - when a prefix is empty, it simply delegates the invocation to the connector - * - when a prefix is defined, it unprefixes the keys when returned - */ + * cached implementation of the matching function + * + * - when a prefix is empty, it simply delegates the invocation to the + * connector + * - when a prefix is defined, it unprefixes the keys when returned + */ private val doMatching = runtime.prefix match { case RedisEmptyPrefix => (pattern: String) => redis.matching(pattern) - case _ => (pattern: String) => redis.matching(pattern).map(_.unprefixed) + case _ => (pattern: String) => redis.matching(pattern).map(_.unprefixed) } override def matching(pattern: String): Result[Seq[String]] = pattern.prefixed { pattern => @@ -79,12 +85,15 @@ private[impl] class RedisCache[Result[_]](redis: RedisConnector, builder: Builde override def getOrFuture[T: ClassTag](key: String, expiration: Duration)(orElse: => Future[T]): Future[T] = key.prefixed { key => - redis.get[T](key).flatMap { - // cache hit, return the unwrapped value - case Some(value) => value.toFuture - // cache miss, compute the value, store it into the cache but do not wait for the result and ignore it, directly return the value - case None => orElse flatMap { value => runtime.invocation.invoke(redis.set(key, value, expiration), thenReturn = value) } - }.recoverWithFuture(orElse) + redis + .get[T](key) + .flatMap { + // cache hit, return the unwrapped value + case Some(value) => value.toFuture + // cache miss, compute the value, store it into the cache but do not wait for the result and ignore it, directly return the value + case None => orElse flatMap { value => runtime.invocation.invoke(redis.set(key, value, expiration), thenReturn = value) } + } + .recoverWithFuture(orElse) } override def remove(key: String): Result[Done] = @@ -149,6 +158,6 @@ private[impl] class RedisCache[Result[_]](redis: RedisConnector, builder: Builde } // $COVERAGE-OFF$ - override def toString = s"RedisCache(name=${runtime.name})" + override def toString: String = s"RedisCache(name=${runtime.name})" // $COVERAGE-ON$ } diff --git a/src/main/scala/play/api/cache/redis/impl/RedisCaches.scala b/src/main/scala/play/api/cache/redis/impl/RedisCaches.scala index 47614789..8ee47be1 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisCaches.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisCaches.scala @@ -1,11 +1,11 @@ package play.api.cache.redis.impl -import javax.inject.Provider +import akka.actor.ActorSystem import play.api.Environment import play.api.cache.redis._ import play.api.inject.ApplicationLifecycle -import akka.actor.ActorSystem -import play.api.cache.{AsyncCacheApi, DefaultSyncCacheApi} + +import javax.inject.Provider /** * Aggregates all available redis APIs into a single handler. This simplifies @@ -24,9 +24,9 @@ trait RedisCaches { private[redis] class RedisCachesProvider(instance: RedisInstance, serializer: connector.AkkaSerializer, environment: Environment)(implicit system: ActorSystem, lifecycle: ApplicationLifecycle, recovery: RecoveryPolicyResolver) extends Provider[RedisCaches] { import RedisRuntime._ - private implicit lazy val runtime: RedisRuntime = RedisRuntime(instance, instance.recovery, instance.invocationPolicy, instance.prefix)(system) + implicit private lazy val runtime: RedisRuntime = RedisRuntime(instance, instance.recovery, instance.invocationPolicy, instance.prefix)(system) - private implicit def implicitEnvironment: Environment = environment + implicit private def implicitEnvironment: Environment = environment lazy val get: RedisCaches = new RedisCaches { lazy val redisConnector: RedisConnector = new connector.RedisConnectorProvider(instance, serializer).get @@ -38,4 +38,5 @@ private[redis] class RedisCachesProvider(instance: RedisInstance, serializer: co lazy val javaAsync: play.cache.redis.AsyncCacheApi = java lazy val javaSync: play.cache.SyncCacheApi = new play.cache.DefaultSyncCacheApi(java) } + } diff --git a/src/main/scala/play/api/cache/redis/impl/RedisListImpl.scala b/src/main/scala/play/api/cache/redis/impl/RedisListImpl.scala index 241b8aa6..8fbe8105 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisListImpl.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisListImpl.scala @@ -1,11 +1,12 @@ package play.api.cache.redis.impl -import scala.language.implicitConversions -import scala.reflect.ClassTag - import play.api.cache.redis._ -/**

Implementation of List API using redis-server cache implementation.

*/ +import scala.reflect.ClassTag + +/** + *

Implementation of List API using redis-server cache implementation.

+ */ private[impl] class RedisListImpl[Elem: ClassTag, Result[_]](key: String, redis: RedisConnector)(implicit builder: Builders.ResultBuilder[Result], runtime: RedisRuntime) extends RedisList[Elem, Result] { // implicit ask timeout and execution context @@ -32,13 +33,16 @@ private[impl] class RedisListImpl[Elem: ClassTag, Result[_]](key: String, redis: private def appendAll(elements: Elem*): Result[This] = redis.listAppend(key, elements: _*).map(_ => This).recoverWithDefault(This) - override def apply(index: Int): Result[Elem] = redis.listSlice[Elem](key, index, index).map { - _.headOption getOrElse (throw new NoSuchElementException(s"Element at index $index is missing.")) - }.recoverWithDefault { - throw new NoSuchElementException(s"Element at index $index is missing.") - } + override def apply(index: Long): Result[Elem] = redis + .listSlice[Elem](key, index, index) + .map { + _.headOption getOrElse (throw new NoSuchElementException(s"Element at index $index is missing.")) + } + .recoverWithDefault { + throw new NoSuchElementException(s"Element at index $index is missing.") + } - override def get(index: Int): Result[Option[Elem]] = + override def get(index: Long): Result[Option[Elem]] = redis.listSlice[Elem](key, index, index).map(_.headOption).recoverWithDefault(None) override def headPop: Result[Option[Elem]] = redis.listHeadPop[Elem](key).recoverWithDefault(None) @@ -48,7 +52,7 @@ private[impl] class RedisListImpl[Elem: ClassTag, Result[_]](key: String, redis: override def insertBefore(pivot: Elem, element: Elem): Result[Option[Long]] = redis.listInsert(key, pivot, element).recoverWithDefault(None) - override def set(position: Int, element: Elem): Result[This] = + override def set(position: Long, element: Elem): Result[This] = redis.listSetAt(key, position, element).map(_ => This).recoverWithDefault(This) override def isEmpty: Result[Boolean] = @@ -60,7 +64,7 @@ private[impl] class RedisListImpl[Elem: ClassTag, Result[_]](key: String, redis: override def view: RedisListView = ListView private object ListView extends RedisListView { - override def slice(start: Int, end: Int): Result[Seq[Elem]] = redis.listSlice[Elem](key, start, end).recoverWithDefault(Seq.empty) + override def slice(start: Long, end: Long): Result[Seq[Elem]] = redis.listSlice[Elem](key, start, end).recoverWithDefault(Seq.empty) } override def modify: RedisListModification = ListModifier @@ -69,22 +73,34 @@ private[impl] class RedisListImpl[Elem: ClassTag, Result[_]](key: String, redis: override def collection: This = This - override def clear(): Result[RedisListModification] = - redis.remove(key).map { - _ => this: RedisListModification - }.recoverWithDefault(this) + override def clear(): Result[RedisListModification] = + redis + .remove(key) + .map { _ => + this: RedisListModification + } + .recoverWithDefault(this) + + override def slice(start: Long, end: Long): Result[RedisListModification] = + redis + .listTrim(key, start, end) + .map { _ => + this: RedisListModification + } + .recoverWithDefault(this) - override def slice(start: Int, end: Int): Result[RedisListModification] = - redis.listTrim(key, start, end).map { - _ => this: RedisListModification - }.recoverWithDefault(this) } - override def remove(element: Elem, count: Int): Result[This] = + override def remove(element: Elem, count: Long): Result[This] = redis.listRemove(key, element, count).map(_ => This).recoverWithDefault(This) - override def removeAt(position: Int): Result[This] = - redis.listSetAt(key, position, "play-redis:DELETED").flatMap { - _ => redis.listRemove(key, "play-redis:DELETED", count = 0) - }.map(_ => This).recoverWithDefault(This) + override def removeAt(position: Long): Result[This] = + redis + .listSetAt(key, position, "play-redis:DELETED") + .flatMap { _ => + redis.listRemove(key, "play-redis:DELETED", count = 0) + } + .map(_ => This) + .recoverWithDefault(This) + } diff --git a/src/main/scala/play/api/cache/redis/impl/RedisListJavaImpl.scala b/src/main/scala/play/api/cache/redis/impl/RedisListJavaImpl.scala index 25d0a7f2..9f66b39d 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisListJavaImpl.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisListJavaImpl.scala @@ -1,10 +1,10 @@ package play.api.cache.redis.impl -import scala.concurrent.Future - import play.api.cache.redis.RedisList import play.cache.redis.AsyncRedisList +import scala.concurrent.Future + class RedisListJavaImpl[Elem](internal: RedisList[Elem, Future])(implicit runtime: RedisRuntime) extends AsyncRedisList[Elem] { import JavaCompatibility._ @@ -13,73 +13,61 @@ class RedisListJavaImpl[Elem](internal: RedisList[Elem, Future])(implicit runtim private lazy val modifier: AsyncRedisList.AsyncRedisListModification[Elem] = newModifier() private lazy val viewer: AsyncRedisList.AsyncRedisListView[Elem] = newViewer() - private def newViewer(): AsyncRedisList.AsyncRedisListView[Elem] = { + private def newViewer(): AsyncRedisList.AsyncRedisListView[Elem] = new AsyncRedisListViewJavaImpl(internal.view) - } - private def newModifier(): AsyncRedisList.AsyncRedisListModification[Elem] = { + private def newModifier(): AsyncRedisList.AsyncRedisListModification[Elem] = new AsyncRedisListModificationJavaImpl(internal.modify) - } - override def prepend(element: Elem): CompletionStage[AsyncRedisList[Elem]] = { + override def prepend(element: Elem): CompletionStage[AsyncRedisList[Elem]] = async { implicit context => internal.prepend(element).map(_ => this) } - } - override def append(element: Elem): CompletionStage[AsyncRedisList[Elem]] = { + override def append(element: Elem): CompletionStage[AsyncRedisList[Elem]] = async { implicit context => internal.append(element).map(_ => this) } - } - override def apply(index: Int): CompletionStage[Elem] = { - async { implicit context => + override def apply(index: Long): CompletionStage[Elem] = + async { _ => internal.apply(index) } - } - override def get(index: Int): CompletionStage[Optional[Elem]] = { + override def get(index: Long): CompletionStage[Optional[Elem]] = async { implicit context => internal.get(index).map(_.asJava) } - } - override def headPop(): CompletionStage[Optional[Elem]] = { + override def headPop(): CompletionStage[Optional[Elem]] = async { implicit context => internal.headPop.map(_.asJava) } - } - override def insertBefore(pivot: Elem, element: Elem): CompletionStage[Optional[java.lang.Long]] = { + override def insertBefore(pivot: Elem, element: Elem): CompletionStage[Optional[java.lang.Long]] = async { implicit context => internal.insertBefore(pivot, element).map(_.map(Long.box).asJava) } - } - override def set(position: Int, element: Elem): CompletionStage[AsyncRedisList[Elem]] = { + override def set(position: Long, element: Elem): CompletionStage[AsyncRedisList[Elem]] = async { implicit context => internal.set(position, element).map(_ => this) } - } - override def remove(element: Elem): CompletionStage[AsyncRedisList[Elem]] = { + override def remove(element: Elem): CompletionStage[AsyncRedisList[Elem]] = async { implicit context => internal.remove(element).map(_ => this) } - } - override def remove(element: Elem, count: Int): CompletionStage[AsyncRedisList[Elem]] = { + override def remove(element: Elem, count: Long): CompletionStage[AsyncRedisList[Elem]] = async { implicit context => internal.remove(element, count).map(_ => this) } - } - override def removeAt(position: Int): CompletionStage[AsyncRedisList[Elem]] = { + override def removeAt(position: Long): CompletionStage[AsyncRedisList[Elem]] = async { implicit context => internal.removeAt(position).map(_ => this) } - } override def view(): AsyncRedisList.AsyncRedisListView[Elem] = viewer @@ -87,28 +75,27 @@ class RedisListJavaImpl[Elem](internal: RedisList[Elem, Future])(implicit runtim private class AsyncRedisListViewJavaImpl(view: internal.RedisListView) extends AsyncRedisList.AsyncRedisListView[Elem] { - override def slice(from: Int, end: Int): CompletionStage[JavaList[Elem]] = { + override def slice(from: Long, end: Long): CompletionStage[JavaList[Elem]] = async { implicit context => view.slice(from, end).map(_.asJava) } - } + } private class AsyncRedisListModificationJavaImpl(modification: internal.RedisListModification) extends AsyncRedisList.AsyncRedisListModification[Elem] { override def collection(): AsyncRedisList[Elem] = This - override def clear(): CompletionStage[AsyncRedisList.AsyncRedisListModification[Elem]] = { + override def clear(): CompletionStage[AsyncRedisList.AsyncRedisListModification[Elem]] = async { implicit context => modification.clear().map(_ => this) } - } - override def slice(from: Int, end: Int): CompletionStage[AsyncRedisList.AsyncRedisListModification[Elem]] = { + override def slice(from: Long, end: Long): CompletionStage[AsyncRedisList.AsyncRedisListModification[Elem]] = async { implicit context => modification.slice(from, end).map(_ => this) } - } + } } diff --git a/src/main/scala/play/api/cache/redis/impl/RedisMapImpl.scala b/src/main/scala/play/api/cache/redis/impl/RedisMapImpl.scala index 7704f71b..c51985a3 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisMapImpl.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisMapImpl.scala @@ -1,10 +1,9 @@ package play.api.cache.redis.impl -import scala.language.implicitConversions -import scala.reflect.ClassTag - import play.api.cache.redis._ +import scala.reflect.ClassTag + /**

Implementation of Set API using redis-server cache implementation.

*/ private[impl] class RedisMapImpl[Elem: ClassTag, Result[_]](key: String, redis: RedisConnector)(implicit builder: Builders.ResultBuilder[Result], runtime: RedisRuntime) extends RedisMap[Elem, Result] { @@ -49,4 +48,5 @@ private[impl] class RedisMapImpl[Elem: ClassTag, Result[_]](key: String, redis: override def nonEmpty: Result[Boolean] = redis.hashSize(key).map(_ > 0).recoverWithDefault(false) + } diff --git a/src/main/scala/play/api/cache/redis/impl/RedisMapJavaImpl.scala b/src/main/scala/play/api/cache/redis/impl/RedisMapJavaImpl.scala index 2dcae4f9..a12d71fa 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisMapJavaImpl.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisMapJavaImpl.scala @@ -1,64 +1,56 @@ package play.api.cache.redis.impl -import scala.concurrent.Future - import play.api.cache.redis.RedisMap import play.cache.redis.AsyncRedisMap +import scala.concurrent.Future + class RedisMapJavaImpl[Elem](internal: RedisMap[Elem, Future])(implicit runtime: RedisRuntime) extends AsyncRedisMap[Elem] { import JavaCompatibility._ - def add(field: String, value: Elem): CompletionStage[AsyncRedisMap[Elem]] = { + def add(field: String, value: Elem): CompletionStage[AsyncRedisMap[Elem]] = async { implicit context => internal.add(field, value).map(_ => this) } - } - def get(field: String): CompletionStage[Optional[Elem]] = { + def get(field: String): CompletionStage[Optional[Elem]] = async { implicit context => internal.get(field).map(_.asJava) } - } - def contains(field: String): CompletionStage[java.lang.Boolean] = { + def contains(field: String): CompletionStage[java.lang.Boolean] = async { implicit context => internal.contains(field).map(Boolean.box) } - } - def remove(field: String*): CompletionStage[AsyncRedisMap[Elem]] = { + def remove(field: String*): CompletionStage[AsyncRedisMap[Elem]] = async { implicit context => internal.remove(field: _*).map(_ => this) } - } - def increment(field: String): CompletionStage[java.lang.Long] = { + def increment(field: String): CompletionStage[java.lang.Long] = async { implicit context => internal.increment(field).map(Long.box) } - } - def increment(field: String, incrementBy: java.lang.Long): CompletionStage[java.lang.Long] = { + def increment(field: String, incrementBy: java.lang.Long): CompletionStage[java.lang.Long] = async { implicit context => internal.increment(field, incrementBy).map(Long.box) } - } - def toMap: CompletionStage[JavaMap[String, Elem]] = { + def toMap: CompletionStage[JavaMap[String, Elem]] = async { implicit context => internal.toMap.map(_.asJava) } - } - def keySet(): CompletionStage[JavaSet[String]] = { + def keySet(): CompletionStage[JavaSet[String]] = async { implicit context => internal.keySet.map(_.asJava) } - } - def values(): CompletionStage[JavaSet[Elem]] = { + def values(): CompletionStage[JavaSet[Elem]] = async { implicit context => internal.values.map(_.asJava) } - } + } diff --git a/src/main/scala/play/api/cache/redis/impl/RedisPrefix.scala b/src/main/scala/play/api/cache/redis/impl/RedisPrefix.scala index 308ccd36..023d71bd 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisPrefix.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisPrefix.scala @@ -1,8 +1,8 @@ package play.api.cache.redis.impl /** - * Each instance can apply its own prefix, e.g., to use multiple instances - * with the same redis database. + * Each instance can apply its own prefix, e.g., to use multiple instances with + * the same redis database. */ sealed trait RedisPrefix extends Any { @inline def prefixed(key: String): String @@ -12,7 +12,7 @@ sealed trait RedisPrefix extends Any { } final class RedisPrefixImpl(val prefix: String) extends AnyVal with RedisPrefix { - @inline override def prefixed(key: String) = s"$prefix:$key" + @inline override def prefixed(key: String): String = s"$prefix:$key" @inline override def unprefixed(key: String): String = key.drop(prefix.length + 1) @inline override def prefixed(keys: Seq[String]): Seq[String] = keys.map(prefixed) @inline override def unprefixed(keys: Seq[String]): Seq[String] = keys.map(unprefixed) diff --git a/src/main/scala/play/api/cache/redis/impl/RedisRuntime.scala b/src/main/scala/play/api/cache/redis/impl/RedisRuntime.scala index 8d91866f..4fa65c0a 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisRuntime.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisRuntime.scala @@ -1,16 +1,14 @@ package play.api.cache.redis.impl -import scala.concurrent.ExecutionContext -import scala.concurrent.duration.FiniteDuration -import scala.language.implicitConversions - +import akka.actor.ActorSystem import play.api.cache.redis._ -import akka.actor.ActorSystem +import scala.concurrent.ExecutionContext +import scala.concurrent.duration.FiniteDuration /** - * Runtime info about the current cache instance. It includes - * a configuration, recovery policy, and the execution context. + * Runtime info about the current cache instance. It includes a configuration, + * recovery policy, and the execution context. */ private[redis] trait RedisRuntime extends connector.RedisRuntime { implicit def policy: RecoveryPolicy @@ -19,13 +17,13 @@ private[redis] trait RedisRuntime extends connector.RedisRuntime { implicit def timeout: akka.util.Timeout } -private[redis] final case class RedisRuntimeImpl( +final private[redis] case class RedisRuntimeImpl( name: String, context: ExecutionContext, policy: RecoveryPolicy, invocation: InvocationPolicy, prefix: RedisPrefix, - timeout: akka.util.Timeout + timeout: akka.util.Timeout, ) extends RedisRuntime private[redis] object RedisRuntime { @@ -39,7 +37,7 @@ private[redis] object RedisRuntime { implicit def string2invocation(invocation: String): InvocationPolicy = invocation.toLowerCase.trim match { case "lazy" => LazyInvocation case "eager" => EagerInvocation - case other => throw new IllegalArgumentException("Illegal invocation policy. Valid values are 'lazy' and 'eager'. See the documentation for more details.") + case _ => throw new IllegalArgumentException("Illegal invocation policy. Valid values are 'lazy' and 'eager'. See the documentation for more details.") } def apply(instance: RedisInstance, recovery: RecoveryPolicy, invocation: InvocationPolicy, prefix: RedisPrefix)(implicit system: ActorSystem): RedisRuntime = @@ -47,4 +45,5 @@ private[redis] object RedisRuntime { def apply(name: String, syncTimeout: FiniteDuration, context: ExecutionContext, recovery: RecoveryPolicy, invocation: InvocationPolicy, prefix: RedisPrefix = RedisEmptyPrefix): RedisRuntime = RedisRuntimeImpl(name, context, recovery, invocation, prefix, akka.util.Timeout(syncTimeout)) + } diff --git a/src/main/scala/play/api/cache/redis/impl/RedisSetImpl.scala b/src/main/scala/play/api/cache/redis/impl/RedisSetImpl.scala index 355270f0..2d9c2ebf 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisSetImpl.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisSetImpl.scala @@ -2,7 +2,6 @@ package play.api.cache.redis.impl import play.api.cache.redis._ -import scala.language.implicitConversions import scala.reflect.ClassTag /**

Implementation of Set API using redis-server cache implementation.

*/ @@ -14,31 +13,25 @@ private[impl] class RedisSetImpl[Elem: ClassTag, Result[_]](key: String, redis: @inline private def This: This = this - override def add(elements: Elem*): Result[RedisSet[Elem, Result]] = { + override def add(elements: Elem*): Result[RedisSet[Elem, Result]] = redis.setAdd(key, elements: _*).map(_ => This).recoverWithDefault(This) - } - override def contains(element: Elem): Result[Boolean] = { + override def contains(element: Elem): Result[Boolean] = redis.setIsMember(key, element).recoverWithDefault(false) - } - override def remove(element: Elem*): Result[RedisSet[Elem, Result]] = { + override def remove(element: Elem*): Result[RedisSet[Elem, Result]] = redis.setRemove(key, element: _*).map(_ => This).recoverWithDefault(This) - } - override def toSet: Result[Set[Elem]] = { + override def toSet: Result[Set[Elem]] = redis.setMembers[Elem](key).recoverWithDefault(Set.empty) - } - override def size: Result[Long] = { + override def size: Result[Long] = redis.setSize(key).recoverWithDefault(0) - } - override def isEmpty: Result[Boolean] = { + override def isEmpty: Result[Boolean] = redis.setSize(key).map(_ === 0).recoverWithDefault(true) - } - override def nonEmpty: Result[Boolean] = { + override def nonEmpty: Result[Boolean] = redis.setSize(key).map(_ > 0).recoverWithDefault(false) - } + } diff --git a/src/main/scala/play/api/cache/redis/impl/RedisSetJavaImpl.scala b/src/main/scala/play/api/cache/redis/impl/RedisSetJavaImpl.scala index f474311e..5f64a5eb 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisSetJavaImpl.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisSetJavaImpl.scala @@ -8,27 +8,24 @@ import scala.concurrent.Future class RedisSetJavaImpl[Elem](internal: RedisSet[Elem, Future])(implicit runtime: RedisRuntime) extends AsyncRedisSet[Elem] { import JavaCompatibility._ - override def add(elements: Elem*): CompletionStage[AsyncRedisSet[Elem]] = { + override def add(elements: Elem*): CompletionStage[AsyncRedisSet[Elem]] = async { implicit context => internal.add(elements: _*).map(_ => this) } - } - override def contains(element: Elem): CompletionStage[java.lang.Boolean] = { + override def contains(element: Elem): CompletionStage[java.lang.Boolean] = async { implicit context => internal.contains(element).map(Boolean.box) } - } - override def remove(elements: Elem*): CompletionStage[AsyncRedisSet[Elem]] = { + override def remove(elements: Elem*): CompletionStage[AsyncRedisSet[Elem]] = async { implicit context => internal.remove(elements: _*).map(_ => this) } - } - override def toSet: CompletionStage[JavaSet[Elem]] = { + override def toSet: CompletionStage[JavaSet[Elem]] = async { implicit context => internal.toSet.map(_.asJava) } - } + } diff --git a/src/main/scala/play/api/cache/redis/impl/RedisSortedSetImpl.scala b/src/main/scala/play/api/cache/redis/impl/RedisSortedSetImpl.scala index b7a068a0..daffd441 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisSortedSetImpl.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisSortedSetImpl.scala @@ -2,16 +2,15 @@ package play.api.cache.redis.impl import play.api.cache.redis._ -import scala.language.implicitConversions import scala.reflect.ClassTag /**

Implementation of Set API using redis-server cache implementation.

*/ private[impl] class RedisSortedSetImpl[Elem: ClassTag, Result[_]]( key: String, - redis: RedisConnector + redis: RedisConnector, )(implicit builder: Builders.ResultBuilder[Result], - runtime: RedisRuntime + runtime: RedisRuntime, ) extends RedisSortedSet[Elem, Result] { // implicit ask timeout and execution context @@ -26,17 +25,15 @@ private[impl] class RedisSortedSetImpl[Elem: ClassTag, Result[_]]( override def contains(element: Elem): Result[Boolean] = redis.sortedSetScore(key, element).map(_.isDefined).recoverWithDefault(false) - override def remove(element: Elem*): Result[RedisSortedSet[Elem, Result]] = { + override def remove(element: Elem*): Result[RedisSortedSet[Elem, Result]] = redis.sortedSetRemove(key, element: _*).map(_ => This).recoverWithDefault(This) - } - override def range(start: Long, stop: Long, isReverse: Boolean = false): Result[Seq[Elem]] = { + override def range(start: Long, stop: Long, isReverse: Boolean = false): Result[Seq[Elem]] = if (isReverse) { redis.sortedSetReverseRange[Elem](key, start, stop).recoverWithDefault(Seq.empty) } else { redis.sortedSetRange[Elem](key, start, stop).recoverWithDefault(Seq.empty) } - } override def size: Result[Long] = redis.sortedSetSize(key).recoverWithDefault(0) @@ -46,4 +43,5 @@ private[impl] class RedisSortedSetImpl[Elem: ClassTag, Result[_]]( override def nonEmpty: Result[Boolean] = builder.map(isEmpty)(x => !x) + } diff --git a/src/main/scala/play/api/cache/redis/impl/SyncRedis.scala b/src/main/scala/play/api/cache/redis/impl/SyncRedis.scala index 84c6c503..808d7266 100644 --- a/src/main/scala/play/api/cache/redis/impl/SyncRedis.scala +++ b/src/main/scala/play/api/cache/redis/impl/SyncRedis.scala @@ -1,12 +1,13 @@ package play.api.cache.redis.impl +import play.api.cache.redis._ + import scala.concurrent.duration.Duration import scala.reflect.ClassTag -import play.api.cache.redis._ - /** - * Implementation of **synchronous** and **blocking** Redis API. It also implements standard Play Scala CacheApi + * Implementation of **synchronous** and **blocking** Redis API. It also + * implements standard Play Scala CacheApi */ private[impl] class SyncRedis(redis: RedisConnector)(implicit runtime: RedisRuntime) extends RedisCache[SynchronousResult](redis, Builders.SynchronousBuilder) with CacheApi { // helpers for dsl @@ -25,4 +26,5 @@ private[impl] class SyncRedis(redis: RedisConnector)(implicit runtime: RedisRunt // try to hit the cache, return on hit, set and return orElse on miss or failure redis.get[T](key).recoverWithDefault(Some(computeAndSet)).getOrElse(computeAndSet) } + } diff --git a/src/main/scala/play/api/cache/redis/impl/dsl.scala b/src/main/scala/play/api/cache/redis/impl/dsl.scala index 9b471a7d..70215186 100644 --- a/src/main/scala/play/api/cache/redis/impl/dsl.scala +++ b/src/main/scala/play/api/cache/redis/impl/dsl.scala @@ -1,13 +1,12 @@ package play.api.cache.redis.impl -import scala.concurrent.{ExecutionContext, Future} -import scala.language.implicitConversions - import play.api.cache.redis._ +import scala.concurrent.{ExecutionContext, Future} + /** - * Implicit helpers used within the redis cache implementation. These - * handful tools simplifies code readability but has no major function. + * Implicit helpers used within the redis cache implementation. These handful + * tools simplifies code readability but has no major function. */ private[impl] object dsl { @@ -15,14 +14,17 @@ private[impl] object dsl { @inline implicit def runtime2prefix(implicit runtime: RedisRuntime): RedisPrefix = runtime.prefix /** enriches any ref by toFuture converting a value to Future.successful */ - implicit class RichFuture[T](val any: T) extends AnyVal { + implicit class RichFuture[T](private val any: T) extends AnyVal { @inline def toFuture: Future[T] = Future.successful(any) } /** helper function enabling us to recover from command execution */ implicit class RecoveryFuture[T](future: => Future[T]) { - /** Transforms the promise into desired builder results, possibly recovers with provided default value */ + /** + * Transforms the promise into desired builder results, possibly recovers + * with provided default value + */ @inline def recoverWithDefault[Result[_]](default: => T)(implicit builder: Builders.ResultBuilder[Result], runtime: RedisRuntime): Result[T] = builder.toResult(future, Future.successful(default)) @@ -32,33 +34,42 @@ private[impl] object dsl { // recover from known exceptions case failure: RedisException => runtime.policy.recoverFrom(future, default, failure) } + } /** helper function enabling us to recover from command execution */ - implicit class RecoveryUnitFuture(val future: Future[Unit]) extends AnyVal { - /** Transforms the promise into desired builder results, possibly recovers with provided default value */ + implicit class RecoveryUnitFuture(private val future: Future[Unit]) extends AnyVal { + + /** + * Transforms the promise into desired builder results, possibly recovers + * with provided default value + */ @inline def recoverWithDone[Result[_]](implicit builder: Builders.ResultBuilder[Result], runtime: RedisRuntime): Result[Done] = builder.toResult(future.map(unitAsDone), Future.successful(Done)) + } /** maps units into akka.Done */ - @inline private def unitAsDone(unit: Unit): Done = Done + @inline private val unitAsDone: Any => Done = _ => Done /** applies prefixer to produce final cache key */ - implicit class CacheKey(val key: String) extends AnyVal { + implicit class CacheKey(private val key: String) extends AnyVal { def prefixed[T](f: String => T)(implicit prefixer: RedisPrefix): T = f(prefixer prefixed key) } /** applies prefixer to produce final cache key */ - implicit class CacheKeys(val keys: Seq[String]) extends AnyVal { + implicit class CacheKeys(private val keys: Seq[String]) extends AnyVal { def prefixed[T](f: Seq[String] => T)(implicit prefixer: RedisPrefix): T = f(prefixer prefixed keys) def unprefixed(implicit prefixer: RedisPrefix): Seq[String] = prefixer unprefixed keys } /** applies prefixer to produce final cache key */ - implicit class CacheKeyValues[X](val keys: Seq[(String, X)]) extends AnyVal { + implicit class CacheKeyValues[X](private val keys: Seq[(String, X)]) extends AnyVal { + def prefixed[T](f: Seq[(String, X)] => T)(implicit prefixer: RedisPrefix): T = f { keys.map { case (key, value) => prefixer.prefixed(key) -> value } } + } + } diff --git a/src/main/scala/play/api/cache/redis/package.scala b/src/main/scala/play/api/cache/redis/package.scala index cc121201..6c33049b 100644 --- a/src/main/scala/play/api/cache/redis/package.scala +++ b/src/main/scala/play/api/cache/redis/package.scala @@ -21,7 +21,8 @@ package object redis extends AnyRef with ExpirationImplicits with ExceptionImpli } @SuppressWarnings(Array("org.wartremover.warts.Equals")) - implicit final class HigherKindedAnyOps[F[_]](private val self: F[_]) extends AnyVal { - def =~=(other: F[_]): Boolean = self == other + implicit final class HigherKindedAnyOps[F[_]](private val self: F[?]) extends AnyVal { + def =~=(other: F[?]): Boolean = self == other } + } diff --git a/src/test/scala/play/api/cache/redis/ExpirationSpec.scala b/src/test/scala/play/api/cache/redis/ExpirationSpec.scala index 51a24a6b..f6602fb5 100644 --- a/src/test/scala/play/api/cache/redis/ExpirationSpec.scala +++ b/src/test/scala/play/api/cache/redis/ExpirationSpec.scala @@ -6,9 +6,7 @@ import java.time.Instant import java.util.Date import scala.concurrent.duration._ -/** - *

This specification tests expiration conversion

- */ +/**

This specification tests expiration conversion

*/ class ExpirationSpec extends UnitSpec { "Expiration" should { @@ -32,4 +30,5 @@ class ExpirationSpec extends UnitSpec { expiration mustBe <=(expirationTo) } } + } diff --git a/src/test/scala/play/api/cache/redis/RecoveryPolicySpec.scala b/src/test/scala/play/api/cache/redis/RecoveryPolicySpec.scala index 907e4eb0..0ae7c979 100644 --- a/src/test/scala/play/api/cache/redis/RecoveryPolicySpec.scala +++ b/src/test/scala/play/api/cache/redis/RecoveryPolicySpec.scala @@ -24,7 +24,7 @@ class RecoveryPolicySpec extends AsyncUnitSpec { "Recovery Policy" should { "log detailed report" in { - val policy = new RecoverWithDefault with DetailedReports { + val policy = new RecoverWithDefault with DetailedReports { override val log: Logger = Logger(getClass) } @@ -52,7 +52,7 @@ class RecoveryPolicySpec extends AsyncUnitSpec { } "fail through" in { - val policy = new FailThrough {} + val policy = new FailThrough {} policy.recoverFrom(rerun, default, ex.any).assertingFailure(ex.any) } @@ -61,4 +61,5 @@ class RecoveryPolicySpec extends AsyncUnitSpec { policy.recoverFrom(rerun, default, ex.any) mustEqual default } } + } diff --git a/src/test/scala/play/api/cache/redis/RedisCacheComponentsSpec.scala b/src/test/scala/play/api/cache/redis/RedisCacheComponentsSpec.scala index 14db7b83..b59c36df 100644 --- a/src/test/scala/play/api/cache/redis/RedisCacheComponentsSpec.scala +++ b/src/test/scala/play/api/cache/redis/RedisCacheComponentsSpec.scala @@ -7,7 +7,7 @@ import play.api.inject.ApplicationLifecycle class RedisCacheComponentsSpec extends IntegrationSpec with RedisStandaloneContainer { - private val prefix = "components-sync" + private val prefix = "components-sync" test("miss on get") { cache => cache.get[String](s"$prefix-test-1") mustEqual None @@ -23,13 +23,15 @@ class RedisCacheComponentsSpec extends IntegrationSpec with RedisStandaloneConta cache.exists(s"$prefix-test-11") mustEqual true } - private def test(name: String)(cache: CacheApi => Assertion): Unit = { + private def test(name: String)(cache: CacheApi => Assertion): Unit = s"should $name" in { val components: TestComponents = new TestComponents { - override lazy val configuration: Configuration = Helpers.configuration.fromHocon( - s"play.cache.redis.port: ${container.mappedPort(defaultPort)}" - ) + override lazy val configuration: Configuration = Helpers + .configuration + .fromHocon( + s"play.cache.redis.port: ${container.mappedPort(defaultPort)}", + ) override lazy val actorSystem: ActorSystem = system override lazy val applicationLifecycle: ApplicationLifecycle = injector.instanceOf[ApplicationLifecycle] override lazy val environment: Environment = injector.instanceOf[Environment] @@ -40,9 +42,9 @@ class RedisCacheComponentsSpec extends IntegrationSpec with RedisStandaloneConta cache(components.syncRedis) } } - } private trait TestComponents extends RedisCacheComponents with FakeApplication { def syncRedis: CacheApi } + } diff --git a/src/test/scala/play/api/cache/redis/RedisCacheModuleSpec.scala b/src/test/scala/play/api/cache/redis/RedisCacheModuleSpec.scala index 468088af..48bce2e8 100644 --- a/src/test/scala/play/api/cache/redis/RedisCacheModuleSpec.scala +++ b/src/test/scala/play/api/cache/redis/RedisCacheModuleSpec.scala @@ -11,12 +11,12 @@ import scala.concurrent.duration.DurationInt import scala.reflect.ClassTag class RedisCacheModuleSpec extends IntegrationSpec with RedisStandaloneContainer { -import Helpers._ + import Helpers._ - private final val defaultCacheName: String = "play" + final private val defaultCacheName: String = "play" test("bind defaults") { - _.bindings(new RedisCacheModule).configure(s"play.cache.redis.port" -> container.mappedPort(defaultPort)) + _.bindings(new RedisCacheModule).configure("play.cache.redis.port" -> container.mappedPort(defaultPort)) } { injector => injector.checkBinding[RedisConnector] injector.checkBinding[CacheApi] @@ -31,7 +31,7 @@ import Helpers._ test("not bind defaults") { _.bindings(new RedisCacheModule) .configure("play.cache.redis.bind-default" -> false) - .configure(s"play.cache.redis.port" -> container.mappedPort(defaultPort)) + .configure("play.cache.redis.port" -> container.mappedPort(defaultPort)) } { injector => // bind named caches injector.checkNamedBinding[CacheApi] @@ -62,24 +62,24 @@ import Helpers._ _.bindings(new RedisCacheModule).configure( configuration.fromHocon( s""" - |play.cache.redis { - | instances { - | play { - | host: ${container.host} - | port: ${container.mappedPort(defaultPort)} - | database: 1 - | } - | other { - | host: ${container.host} - | port: ${container.mappedPort(defaultPort)} - | database: 2 - | password: something - | } - | } - | default-cache: other - |} - """.stripMargin - ) + |play.cache.redis { + | instances { + | play { + | host: ${container.host} + | port: ${container.mappedPort(defaultPort)} + | database: 1 + | } + | other { + | host: ${container.host} + | port: ${container.mappedPort(defaultPort)} + | database: 2 + | password: something + | } + | } + | default-cache: other + |} + """.stripMargin, + ), ) } { injector => val other = "other" @@ -139,33 +139,30 @@ import Helpers._ recovery = "log-and-default", source = "my-instance", prefix = None, - ) + ), ) private def binding[T: ClassTag]: BindingKey[T] = BindingKey(implicitly[ClassTag[T]].runtimeClass.asInstanceOf[Class[T]]) - private implicit class RichBindingKey[T](private val key: BindingKey[T]) { + implicit private class RichBindingKey[T](private val key: BindingKey[T]) { def namedCache(name: String): BindingKey[T] = key.qualifiedWith(new NamedCacheImpl(name)) } - private implicit class InjectorAssertions(private val injector: Injector) { + implicit private class InjectorAssertions(private val injector: Injector) { - def checkBinding[T <: AnyRef : ClassTag]: Assertion = { + def checkBinding[T <: AnyRef: ClassTag]: Assertion = injector.instanceOf(binding[T]) mustBe a[T] - } - def checkNamedBinding[T <: AnyRef : ClassTag]: Assertion = { + def checkNamedBinding[T <: AnyRef: ClassTag]: Assertion = checkNamedBinding(defaultCacheName) - } - def checkNamedBinding[T <: AnyRef : ClassTag](name: String): Assertion = { + def checkNamedBinding[T <: AnyRef: ClassTag](name: String): Assertion = injector.instanceOf(binding[T].namedCache(name)) mustBe a[T] - } - } + } - private def test(name: String)(createBuilder: GuiceApplicationBuilder => GuiceApplicationBuilder)(f: Injector => Assertion): Unit = { + private def test(name: String)(createBuilder: GuiceApplicationBuilder => GuiceApplicationBuilder)(f: Injector => Assertion): Unit = s"should $name" in { val builder = createBuilder(new GuiceApplicationBuilder) @@ -176,5 +173,5 @@ import Helpers._ f(injector) } } - } + } diff --git a/src/test/scala/play/api/cache/redis/configuration/HostnameResolverSpec.scala b/src/test/scala/play/api/cache/redis/configuration/HostnameResolverSpec.scala index 1360552a..1a1b764e 100644 --- a/src/test/scala/play/api/cache/redis/configuration/HostnameResolverSpec.scala +++ b/src/test/scala/play/api/cache/redis/configuration/HostnameResolverSpec.scala @@ -12,4 +12,5 @@ class HostnameResolverSpec extends UnitSpec { "resolving IP address remains an address" in { "127.0.0.1".resolvedIpAddress mustEqual "127.0.0.1" } + } diff --git a/src/test/scala/play/api/cache/redis/configuration/RedisHostSpec.scala b/src/test/scala/play/api/cache/redis/configuration/RedisHostSpec.scala index 501480d4..4158e56c 100644 --- a/src/test/scala/play/api/cache/redis/configuration/RedisHostSpec.scala +++ b/src/test/scala/play/api/cache/redis/configuration/RedisHostSpec.scala @@ -3,11 +3,9 @@ package play.api.cache.redis.configuration import play.api.ConfigLoader import play.api.cache.redis.test._ -import scala.language.implicitConversions - class RedisHostSpec extends UnitSpec with ImplicitOptionMaterialization { - private implicit val loader: ConfigLoader[RedisHost] = RedisHost + implicit private val loader: ConfigLoader[RedisHost] = RedisHost "host with database, username, and password" in { val configuration = Helpers.configuration.fromHocon { @@ -21,7 +19,11 @@ class RedisHostSpec extends UnitSpec with ImplicitOptionMaterialization { """.stripMargin } configuration.get[RedisHost]("play.cache.redis") mustEqual RedisHost( - host = "localhost", port = 6378, database = 1, username = "my-user", password = "something" + host = "localhost", + port = 6378, + database = 1, + username = "my-user", + password = "something", ) } @@ -36,7 +38,11 @@ class RedisHostSpec extends UnitSpec with ImplicitOptionMaterialization { """.stripMargin } configuration.get[RedisHost]("play.cache.redis") mustEqual RedisHost( - host = "localhost", port = 6378, database = 1, username = None, password = "something" + host = "localhost", + port = 6378, + database = 1, + username = None, + password = "something", ) } @@ -59,4 +65,5 @@ class RedisHostSpec extends UnitSpec with ImplicitOptionMaterialization { RedisHost.fromConnectionString("redis:/localhost:6378") } } + } diff --git a/src/test/scala/play/api/cache/redis/configuration/RedisInstanceManagerSpec.scala b/src/test/scala/play/api/cache/redis/configuration/RedisInstanceManagerSpec.scala index bf38cb6f..4d23dca6 100644 --- a/src/test/scala/play/api/cache/redis/configuration/RedisInstanceManagerSpec.scala +++ b/src/test/scala/play/api/cache/redis/configuration/RedisInstanceManagerSpec.scala @@ -4,13 +4,12 @@ import play.api.cache.redis.test._ import play.api.{ConfigLoader, Configuration} import scala.concurrent.duration._ -import scala.language.implicitConversions -class RedisInstanceManagerSpec extends UnitSpec with ImplicitOptionMaterialization{ +class RedisInstanceManagerSpec extends UnitSpec with ImplicitOptionMaterialization { "default configuration" in new TestCase { - protected override def hocon: String = + override protected def hocon: String = """ |play.cache.redis {} """ @@ -19,7 +18,7 @@ class RedisInstanceManagerSpec extends UnitSpec with ImplicitOptionMaterializati RedisStandalone( name = defaultCacheName, host = RedisHost(localhost, defaultPort, database = 0), - settings = defaultsSettings + settings = defaultsSettings, ) manager mustEqual RedisInstanceManagerTest(defaultCacheName)(defaultCache) @@ -32,8 +31,9 @@ class RedisInstanceManagerSpec extends UnitSpec with ImplicitOptionMaterializati } "single default instance" in new TestCase { - protected override def hocon: String = - """ + + override protected def hocon: String = + """ |play.cache.redis { | host: redis.localhost.cz | port: 6378 @@ -50,16 +50,26 @@ class RedisInstanceManagerSpec extends UnitSpec with ImplicitOptionMaterializati | recovery: log-and-fail |} """ - private val settings: RedisSettings = RedisSettingsTest(invocationContext = "my-dispatcher", invocationPolicy = "eager", timeout = RedisTimeouts(5.minutes, 5.seconds, 300.millis), recovery = "log-and-fail", source = "standalone", prefix = "redis.") + + private val settings: RedisSettings = RedisSettingsTest( + invocationContext = "my-dispatcher", + invocationPolicy = "eager", + timeout = RedisTimeouts(5.minutes, 5.seconds, 300.millis), + recovery = "log-and-fail", + source = "standalone", + prefix = "redis.", + ) manager mustEqual RedisInstanceManagerTest(defaultCacheName)( - RedisStandalone(defaultCacheName, RedisHost("redis.localhost.cz", 6378, database = 2, password = "something"), settings) + RedisStandalone(defaultCacheName, RedisHost("redis.localhost.cz", 6378, database = 2, password = "something"), settings), ) + } "named caches" in new TestCase { - protected override def hocon: String = - """ + + override protected def hocon: String = + """ |play.cache.redis { | instances { | @@ -90,8 +100,14 @@ class RedisInstanceManagerSpec extends UnitSpec with ImplicitOptionMaterializati |} """ - private val otherSettings: RedisSettings = RedisSettingsTest( - invocationContext = "my-dispatcher", invocationPolicy = "eager", timeout = RedisTimeouts(5.minutes, 5.seconds, 300.millis), recovery = "log-and-fail", source = "standalone", prefix = "redis.") + private val otherSettings: RedisSettings = RedisSettingsTest( + invocationContext = "my-dispatcher", + invocationPolicy = "eager", + timeout = RedisTimeouts(5.minutes, 5.seconds, 300.millis), + recovery = "log-and-fail", + source = "standalone", + prefix = "redis.", + ) private val defaultCache: RedisInstanceProvider = RedisStandalone(defaultCacheName, RedisHost(localhost, defaultPort, database = 1), otherSettings) private val otherCache: RedisInstanceProvider = RedisStandalone("other", RedisHost("redis.localhost.cz", 6378, database = 2, password = "something"), defaultsSettings) @@ -103,8 +119,9 @@ class RedisInstanceManagerSpec extends UnitSpec with ImplicitOptionMaterializati } "cluster mode" in new TestCase { - protected override def hocon: String = - """ + + override protected def hocon: String = + """ |play.cache.redis { | instances { | play { @@ -119,17 +136,19 @@ class RedisInstanceManagerSpec extends UnitSpec with ImplicitOptionMaterializati | } |} """ - private def node(port: Int) = RedisHost(localhost, port) + + private def node(port: Int) = RedisHost(localhost, port) manager mustEqual RedisInstanceManagerTest(defaultCacheName)( - RedisCluster( - name = defaultCacheName, nodes = node(6380) :: node(6381) :: node(6382) :: node(6383) :: Nil, settings = defaultsSettings.copy(source = "cluster")) + RedisCluster(name = defaultCacheName, nodes = node(6380) :: node(6381) :: node(6382) :: node(6383) :: Nil, settings = defaultsSettings.copy(source = "cluster")), ) + } "AWS cluster mode" in new TestCase { - protected override def hocon: String = - """ + + override protected def hocon: String = + """ |play.cache.redis { | instances { | play { @@ -139,14 +158,16 @@ class RedisInstanceManagerSpec extends UnitSpec with ImplicitOptionMaterializati | } |} """ + private val provider = manager.defaultInstance.asInstanceOf[ResolvedRedisInstance] private val instance = provider.instance.asInstanceOf[RedisCluster] instance.nodes must contain(RedisHost("127.0.0.1", 6379)) } "sentinel mode" in new TestCase { - protected override def hocon: String = - """ + + override protected def hocon: String = + """ |play.cache.redis { | instances { | play { @@ -164,31 +185,41 @@ class RedisInstanceManagerSpec extends UnitSpec with ImplicitOptionMaterializati |} """ - private def node(port: Int) = RedisHost(localhost, port) + private def node(port: Int) = RedisHost(localhost, port) manager mustEqual RedisInstanceManagerTest(defaultCacheName)( RedisSentinel( - name = defaultCacheName, masterGroup = "primary", sentinels = node(6380) :: node(6381) :: node(6382) :: Nil, settings = defaultsSettings.copy(source = "sentinel"), password = "my-password", database = 1) + name = defaultCacheName, + masterGroup = "primary", + sentinels = node(6380) :: node(6381) :: node(6382) :: Nil, + settings = defaultsSettings.copy(source = "sentinel"), + password = "my-password", + database = 1, + ), ) + } "connection string mode" in new TestCase { - protected override def hocon: String = - """ + + override protected def hocon: String = + """ |play.cache.redis { | source: "connection-string" | connection-string: "redis://localhost:6379" |} """ - manager mustEqual RedisInstanceManagerTest(defaultCacheName)( - RedisStandalone(defaultCacheName, RedisHost(localhost, defaultPort), defaultsSettings.copy(source = "connection-string")) + manager mustEqual RedisInstanceManagerTest(defaultCacheName)( + RedisStandalone(defaultCacheName, RedisHost(localhost, defaultPort), defaultsSettings.copy(source = "connection-string")), ) + } "custom mode" in new TestCase { - protected override def hocon: String = - """ + + override protected def hocon: String = + """ |play.cache.redis { | source: custom |} @@ -198,8 +229,9 @@ class RedisInstanceManagerSpec extends UnitSpec with ImplicitOptionMaterializati } "typo in mode with simple syntax" in new TestCase { - protected override def hocon: String = - """ + + override protected def hocon: String = + """ |play.cache.redis { | source: typo |} @@ -209,8 +241,9 @@ class RedisInstanceManagerSpec extends UnitSpec with ImplicitOptionMaterializati } "typo in mode with advanced syntax" in new TestCase { - protected override def hocon: String = - """ + + override protected def hocon: String = + """ |play.cache.redis { | instances { | play { @@ -224,8 +257,9 @@ class RedisInstanceManagerSpec extends UnitSpec with ImplicitOptionMaterializati } "fail when requesting undefined cache" in new TestCase { - protected override def hocon: String = - """ + + override protected def hocon: String = + """ |play.cache.redis { | instances { | play { @@ -237,7 +271,7 @@ class RedisInstanceManagerSpec extends UnitSpec with ImplicitOptionMaterializati |} """ - manager.instanceOfOption(defaultCacheName) mustBe a[Some[_]] + manager.instanceOfOption(defaultCacheName) mustBe a[Some[?]] manager.instanceOfOption("other") mustEqual None the[IllegalArgumentException] thrownBy manager.instanceOf("other") @@ -246,7 +280,7 @@ class RedisInstanceManagerSpec extends UnitSpec with ImplicitOptionMaterializati private trait TestCase { - private implicit val loader: ConfigLoader[RedisInstanceManager] = RedisInstanceManager + implicit private val loader: ConfigLoader[RedisInstanceManager] = RedisInstanceManager private val configuration: Configuration = Helpers.configuration.fromHocon(hocon) @@ -254,16 +288,17 @@ class RedisInstanceManagerSpec extends UnitSpec with ImplicitOptionMaterializati protected def hocon: String - protected implicit def implicitlyInstance2resolved(instance: RedisInstance): RedisInstanceProvider = - new ResolvedRedisInstance(instance) + implicit protected def implicitlyInstance2resolved(instance: RedisInstance): RedisInstanceProvider = + new ResolvedRedisInstance(instance) - protected implicit def implicitlyString2unresolved(name: String): RedisInstanceProvider = + implicit protected def implicitlyString2unresolved(name: String): RedisInstanceProvider = new UnresolvedRedisInstance(name) - protected final case class RedisInstanceManagerTest( - default: String)( - providers: RedisInstanceProvider* - ) extends RedisInstanceManager { + final protected case class RedisInstanceManagerTest( + default: String, + )( + providers: RedisInstanceProvider*, + ) extends RedisInstanceManager { override def caches: Set[String] = providers.map(_.name).toSet @@ -273,20 +308,8 @@ class RedisInstanceManagerSpec extends UnitSpec with ImplicitOptionMaterializati throw new RuntimeException("Default instance is not defined.") } - } - } -} - - - - - - - - - - - - + } + } +} diff --git a/src/test/scala/play/api/cache/redis/configuration/RedisInstanceProviderSpec.scala b/src/test/scala/play/api/cache/redis/configuration/RedisInstanceProviderSpec.scala index c3ff1d67..f02dafb4 100644 --- a/src/test/scala/play/api/cache/redis/configuration/RedisInstanceProviderSpec.scala +++ b/src/test/scala/play/api/cache/redis/configuration/RedisInstanceProviderSpec.scala @@ -2,16 +2,17 @@ package play.api.cache.redis.configuration import play.api.cache.redis.test.UnitSpec -class RedisInstanceProviderSpec extends UnitSpec { +class RedisInstanceProviderSpec extends UnitSpec { private val defaultCache: RedisStandalone = - RedisStandalone( - name = defaultCacheName, host = RedisHost(localhost, defaultPort, database = Some(0)), settings = defaultsSettings) + RedisStandalone(name = defaultCacheName, host = RedisHost(localhost, defaultPort, database = Some(0)), settings = defaultsSettings) -private implicit val resolver: RedisInstanceResolver = new RedisInstanceResolver { - def resolve: PartialFunction[String, RedisStandalone] = { - case `defaultCacheName` => defaultCache + implicit private val resolver: RedisInstanceResolver = new RedisInstanceResolver { + + def resolve: PartialFunction[String, RedisStandalone] = { case `defaultCacheName` => + defaultCache } + } "resolve already resolved" in { @@ -25,4 +26,5 @@ private implicit val resolver: RedisInstanceResolver = new RedisInstanceResolve "fail when not able to resolve" in { the[Exception] thrownBy new UnresolvedRedisInstance("other").resolved } + } diff --git a/src/test/scala/play/api/cache/redis/configuration/RedisTimeoutsSpec.scala b/src/test/scala/play/api/cache/redis/configuration/RedisTimeoutsSpec.scala index c9d752a8..8c3ced8d 100644 --- a/src/test/scala/play/api/cache/redis/configuration/RedisTimeoutsSpec.scala +++ b/src/test/scala/play/api/cache/redis/configuration/RedisTimeoutsSpec.scala @@ -1,10 +1,10 @@ package play.api.cache.redis.configuration -import scala.concurrent.duration._ -import play.api.cache.redis._ import play.api.cache.redis.test.{Helpers, ImplicitOptionMaterialization, UnitSpec} -class RedisTimeoutsSpec extends UnitSpec with ImplicitOptionMaterialization{ +import scala.concurrent.duration._ + +class RedisTimeoutsSpec extends UnitSpec with ImplicitOptionMaterialization { private def orDefault = RedisTimeouts(1.second, None, 500.millis) @@ -63,7 +63,7 @@ class RedisTimeoutsSpec extends UnitSpec with ImplicitOptionMaterialization{ } val expected = RedisTimeouts(sync = 1.second, redis = None, connection = None) val actual = RedisTimeouts.load(configuration.underlying, "play.cache.redis")(orDefault) - actual mustEqual expected + actual mustEqual expected } "load defaults" in { @@ -71,4 +71,5 @@ class RedisTimeoutsSpec extends UnitSpec with ImplicitOptionMaterialization{ RedisTimeouts.requiredDefault.redis mustEqual None RedisTimeouts.requiredDefault.connection mustEqual None } + } diff --git a/src/test/scala/play/api/cache/redis/connector/ExpectedFutureSpec.scala b/src/test/scala/play/api/cache/redis/connector/ExpectedFutureSpec.scala index 00214d1d..e3869e6f 100644 --- a/src/test/scala/play/api/cache/redis/connector/ExpectedFutureSpec.scala +++ b/src/test/scala/play/api/cache/redis/connector/ExpectedFutureSpec.scala @@ -5,7 +5,6 @@ import play.api.cache.redis.test.{AsyncUnitSpec, SimulatedException} import scala.concurrent.{ExecutionContext, Future} - class ExpectedFutureSpec extends AsyncUnitSpec { import ExpectedFutureSpec._ @@ -33,6 +32,7 @@ class ExpectedFutureSpec extends AsyncUnitSpec { Future.failed[String](SimulatedException).asExpected.assertingFailure[ExecutionFailedException] } } + } new TestSuite("Execution without the key")((future: Future[String]) => future.executing(cmd)) @@ -44,8 +44,9 @@ class ExpectedFutureSpec extends AsyncUnitSpec { Future.successful("expected").executing(cmd).withKey("key").andParameters("SET").andParameter(1).toString mustEqual s"ExpectedFuture($cmd key SET 1)" Future.successful("expected").executing(cmd).withKeys(Seq("key1", "key2")).andParameters(Seq("SET", 1)).toString mustEqual s"ExpectedFuture($cmd key1 key2 SET 1)" - Future.successful("expected").executing(cmd).withKey("key").asCommand("other 2").toString mustEqual s"ExpectedFuture(TEST CMD other 2)" + Future.successful("expected").executing(cmd).withKey("key").asCommand("other 2").toString mustEqual "ExpectedFuture(TEST CMD other 2)" } + } object ExpectedFutureSpec { @@ -53,14 +54,15 @@ object ExpectedFutureSpec { private val cmd = "TEST CMD" private val expectation: PartialFunction[Any, String] = { - case "failing" => throw SimulatedException + case "failing" => throw SimulatedException case "expected" => "ok" } private type ExpectationBuilder[T] = Future[T] => ExpectedFuture[String] - private implicit class FutureBuilder[T](private val future: Future[T]) extends AnyVal { + implicit private class FutureBuilder[T](private val future: Future[T]) extends AnyVal { def asExpected(implicit ev: ExpectationBuilder[T], context: ExecutionContext): Future[String] = ev(future).expects(expectation) def asCommand(implicit ev: ExpectationBuilder[T]): String = ev(future).toString } + } diff --git a/src/test/scala/play/api/cache/redis/connector/FailEagerlySpec.scala b/src/test/scala/play/api/cache/redis/connector/FailEagerlySpec.scala index 8f59cb8c..96c7da64 100644 --- a/src/test/scala/play/api/cache/redis/connector/FailEagerlySpec.scala +++ b/src/test/scala/play/api/cache/redis/connector/FailEagerlySpec.scala @@ -34,7 +34,7 @@ class FailEagerlySpec extends AsyncUnitSpec with ImplicitFutureMaterialization { failEagerly.send(cmd).assertTimeout(200.millis) } - def test(name: String)(f: FailEagerlyImpl => Future[Assertion]): Unit = { + def test(name: String)(f: FailEagerlyImpl => Future[Assertion]): Unit = name in { val system = ActorSystem("test", classLoader = Some(getClass.getClassLoader)) val application = StoppableApplication(system) @@ -43,7 +43,7 @@ class FailEagerlySpec extends AsyncUnitSpec with ImplicitFutureMaterialization { f(impl) } } - } + } object FailEagerlySpec { @@ -56,12 +56,12 @@ object FailEagerlySpec { } class FailEagerlyBase(implicit system: ActorSystem) extends RequestTimeout { - protected implicit val scheduler: Scheduler = system.scheduler + implicit protected val scheduler: Scheduler = system.scheduler implicit val executionContext: ExecutionContext = system.dispatcher - def send[T](redisCommand: RedisCommand[_ <: RedisReply, T]): Future[T] = { + def send[T](redisCommand: RedisCommand[? <: RedisReply, T]): Future[T] = redisCommand.asInstanceOf[RedisCommandTest[T]].returning - } + } final class FailEagerlyImpl(implicit system: ActorSystem) extends FailEagerlyBase with FailEagerly { @@ -74,4 +74,5 @@ object FailEagerlySpec { def markDisconnected(): Unit = connected = false } + } diff --git a/src/test/scala/play/api/cache/redis/connector/RedisClusterSpec.scala b/src/test/scala/play/api/cache/redis/connector/RedisClusterSpec.scala index 759d6258..a298c545 100644 --- a/src/test/scala/play/api/cache/redis/connector/RedisClusterSpec.scala +++ b/src/test/scala/play/api/cache/redis/connector/RedisClusterSpec.scala @@ -57,18 +57,21 @@ class RedisClusterSpec extends IntegrationSpec with RedisClusterContainer { } yield Passed } - def test(name: String)(f: RedisConnector => Future[Assertion]): Unit = { + def test(name: String)(f: RedisConnector => Future[Assertion]): Unit = name in { lazy val clusterInstance = RedisCluster( name = "play", - nodes = 0.until(redisMaster).map { i => - RedisHost(container.containerIpAddress, container.mappedPort(initialPort + i)) - }.toList, + nodes = 0 + .until(redisMaster) + .map { i => + RedisHost(container.containerIpAddress, container.mappedPort(initialPort + i)) + } + .toList, settings = RedisSettings.load( config = Helpers.configuration.default.underlying, - path = "play.cache.redis" - ) + path = "play.cache.redis", + ), ) def runTest: Future[Assertion] = { @@ -81,24 +84,23 @@ class RedisClusterSpec extends IntegrationSpec with RedisClusterContainer { for { connector <- Future(new RedisConnectorProvider(clusterInstance, serializer).get) // initialize the connector by flushing the database - keys <- connector.matching("*") - _ <- Future.sequence(keys.map(connector.remove(_))) + keys <- connector.matching("*") + _ <- Future.sequence(keys.map(connector.remove(_))) // run the test - _ <- f(connector) + _ <- f(connector) } yield Passed } } @SuppressWarnings(Array("org.wartremover.warts.Recursion")) - def makeAttempt(id: Int): Future[Assertion] = { + def makeAttempt(id: Int): Future[Assertion] = runTest.recoverWith { case cause: Throwable if id <= 1 => log.error(s"RedisClusterSpec: test '$name', attempt $id failed, will retry", cause) Future.waitFor(1.second).flatMap(_ => makeAttempt(id + 1)) } - } makeAttempt(1) } - } + } diff --git a/src/test/scala/play/api/cache/redis/connector/RedisConnectorFailureSpec.scala b/src/test/scala/play/api/cache/redis/connector/RedisConnectorFailureSpec.scala index a12cfe26..681a4763 100644 --- a/src/test/scala/play/api/cache/redis/connector/RedisConnectorFailureSpec.scala +++ b/src/test/scala/play/api/cache/redis/connector/RedisConnectorFailureSpec.scala @@ -12,11 +12,11 @@ import scala.util.{Failure, Success} class RedisConnectorFailureSpec extends AsyncUnitSpec with ImplicitFutureMaterialization { - private val score = 1D + private val score = 1d private val encodedValue = "encoded" private val disconnected = Future.failed(SimulatedException) - "Serializer fail" when { + "Serializer fail" when { test("serialization fails") { (serializer, _, connector) => for { @@ -39,9 +39,10 @@ class RedisConnectorFailureSpec extends AsyncUnitSpec with ImplicitFutureMateria test("SET returning false") { (serializer, commands, connector) => for { _ <- serializer.encode(cacheValue, encodedValue) - _ = (commands.set[String](_: String, _: String, _: Option[Long], _: Option[Long], _: Boolean, _: Boolean)(_: ByteStringSerializer[String])) - .expects(cacheKey, encodedValue, None, None, false, false, *) - .returns(false) + _ = (commands + .set[String](_: String, _: String, _: Option[Long], _: Option[Long], _: Boolean, _: Boolean)(_: ByteStringSerializer[String])) + .expects(cacheKey, encodedValue, None, None, false, false, *) + .returns(false) _ <- connector.set(cacheKey, cacheValue).assertingEqual(false) } yield Passed } @@ -60,19 +61,22 @@ class RedisConnectorFailureSpec extends AsyncUnitSpec with ImplicitFutureMateria test("failed SET") { (serializer, commands, connector) => for { _ <- serializer.encode(cacheValue, encodedValue) - _ = (commands.set[String](_: String, _: String, _: Option[Long], _: Option[Long], _: Boolean, _: Boolean)(_: ByteStringSerializer[String])) - .expects(cacheKey, encodedValue, None, None, false, false, *) - .returns(disconnected) + _ = (commands + .set[String](_: String, _: String, _: Option[Long], _: Option[Long], _: Boolean, _: Boolean)(_: ByteStringSerializer[String])) + .expects(cacheKey, encodedValue, None, None, false, false, *) + .returns(disconnected) _ <- serializer.encode(cacheValue, encodedValue) - _ = (commands.set[String](_: String, _: String, _: Option[Long], _: Option[Long], _: Boolean, _: Boolean)(_: ByteStringSerializer[String])) - .expects(cacheKey, encodedValue, None, Some(1.minute.toMillis), false, false, *) - .returns(disconnected) + _ = (commands + .set[String](_: String, _: String, _: Option[Long], _: Option[Long], _: Boolean, _: Boolean)(_: ByteStringSerializer[String])) + .expects(cacheKey, encodedValue, None, Some(1.minute.toMillis), false, false, *) + .returns(disconnected) _ <- serializer.encode(cacheValue, encodedValue) - _ = (commands.set[String](_: String, _: String, _: Option[Long], _: Option[Long], _: Boolean, _: Boolean)(_: ByteStringSerializer[String])) - .expects(cacheKey, encodedValue, None, None, true, false, *) - .returns(disconnected) + _ = (commands + .set[String](_: String, _: String, _: Option[Long], _: Option[Long], _: Boolean, _: Boolean)(_: ByteStringSerializer[String])) + .expects(cacheKey, encodedValue, None, None, true, false, *) + .returns(disconnected) _ <- connector.set(cacheKey, cacheValue).assertingFailure[ExecutionFailedException, SimulatedException] _ <- connector.set(cacheKey, cacheValue, 1.minute).assertingFailure[ExecutionFailedException, SimulatedException] @@ -83,9 +87,10 @@ class RedisConnectorFailureSpec extends AsyncUnitSpec with ImplicitFutureMateria test("failed MSET") { (serializer, commands, connector) => for { _ <- serializer.encode(cacheValue, encodedValue) - _ = (commands.mset[String](_: Map[String, String])(_: ByteStringSerializer[String])) - .expects(Map(cacheKey -> encodedValue), *) - .returns(disconnected) + _ = (commands + .mset[String](_: Map[String, String])(_: ByteStringSerializer[String])) + .expects(Map(cacheKey -> encodedValue), *) + .returns(disconnected) _ <- connector.mSet(cacheKey -> cacheValue).assertingFailure[ExecutionFailedException, SimulatedException] } yield Passed } @@ -93,9 +98,10 @@ class RedisConnectorFailureSpec extends AsyncUnitSpec with ImplicitFutureMateria test("failed MSETNX") { (serializer, commands, connector) => for { _ <- serializer.encode(cacheValue, encodedValue) - _ = (commands.msetnx[String](_: Map[String, String])(_: ByteStringSerializer[String])) - .expects(Map(cacheKey -> encodedValue), *) - .returns(disconnected) + _ = (commands + .msetnx[String](_: Map[String, String])(_: ByteStringSerializer[String])) + .expects(Map(cacheKey -> encodedValue), *) + .returns(disconnected) _ <- connector.mSetIfNotExist(cacheKey -> cacheValue).assertingFailure[ExecutionFailedException, SimulatedException] } yield Passed } @@ -116,9 +122,10 @@ class RedisConnectorFailureSpec extends AsyncUnitSpec with ImplicitFutureMateria test("failed LRANGE") { (_, commands, connector) => for { - _ <- (commands.lrange[String](_: String, _: Long, _: Long)(_: ByteStringDeserializer[String])) - .expects(cacheKey, 0, -1, *) - .returns(disconnected) + _ <- (commands + .lrange[String](_: String, _: Long, _: Long)(_: ByteStringDeserializer[String])) + .expects(cacheKey, 0, -1, *) + .returns(disconnected) _ <- connector.listSlice[String](cacheKey, 0, -1).assertingFailure[ExecutionFailedException, SimulatedException] } yield Passed } @@ -126,9 +133,10 @@ class RedisConnectorFailureSpec extends AsyncUnitSpec with ImplicitFutureMateria test("failed LREM") { (serializer, commands, connector) => for { _ <- serializer.encode(cacheValue, encodedValue) - _ = (commands.lrem(_: String, _: Long, _: String)(_: ByteStringSerializer[String])) - .expects(cacheKey, 2L, encodedValue, *) - .returns(disconnected) + _ = (commands + .lrem(_: String, _: Long, _: String)(_: ByteStringSerializer[String])) + .expects(cacheKey, 2L, encodedValue, *) + .returns(disconnected) _ <- connector.listRemove(cacheKey, cacheValue, 2).assertingFailure[ExecutionFailedException, SimulatedException] } yield Passed } @@ -144,9 +152,10 @@ class RedisConnectorFailureSpec extends AsyncUnitSpec with ImplicitFutureMateria for { _ <- serializer.encode("pivot", "encodedPivot") _ <- serializer.encode(cacheValue, encodedValue) - _ = (commands.linsert[String](_: String, _: ListPivot, _: String, _: String)(_: ByteStringSerializer[String])) - .expects(cacheKey, BEFORE, "encodedPivot", encodedValue, *) - .returns(disconnected) + _ = (commands + .linsert[String](_: String, _: ListPivot, _: String, _: String)(_: ByteStringSerializer[String])) + .expects(cacheKey, BEFORE, "encodedPivot", encodedValue, *) + .returns(disconnected) // run the test _ <- connector.listInsert(cacheKey, "pivot", cacheValue).assertingFailure[ExecutionFailedException, SimulatedException] } yield Passed @@ -161,21 +170,23 @@ class RedisConnectorFailureSpec extends AsyncUnitSpec with ImplicitFutureMateria } test("failed HSET") { (serializer, commands, connector) => -for { - _ <- serializer.encode(cacheValue, encodedValue) - _ = (commands.hset[String](_: String, _: String, _: String)(_: ByteStringSerializer[String])) - .expects(cacheKey, "field", encodedValue, *) - .returns(disconnected) - _ <- connector.hashSet(cacheKey, "field", cacheValue).assertingFailure[ExecutionFailedException, SimulatedException] - } yield Passed + for { + _ <- serializer.encode(cacheValue, encodedValue) + _ = (commands + .hset[String](_: String, _: String, _: String)(_: ByteStringSerializer[String])) + .expects(cacheKey, "field", encodedValue, *) + .returns(disconnected) + _ <- connector.hashSet(cacheKey, "field", cacheValue).assertingFailure[ExecutionFailedException, SimulatedException] + } yield Passed } test("failed ZADD") { (serializer, commands, connector) => for { _ <- serializer.encode(cacheValue, encodedValue) - _ = (commands.zaddMock[String](_: String, _: Seq[(Double, String)])(_: ByteStringSerializer[String])) - .expects(cacheKey, Seq((score, encodedValue)), *) - .returns(disconnected) + _ = (commands + .zaddMock[String](_: String, _: Seq[(Double, String)])(_: ByteStringSerializer[String])) + .expects(cacheKey, Seq((score, encodedValue)), *) + .returns(disconnected) _ <- connector.sortedSetAdd(cacheKey, (score, cacheValue)).assertingFailure[ExecutionFailedException, SimulatedException] } yield Passed } @@ -190,9 +201,10 @@ for { test("failed ZSCORE") { (serializer, commands, connector) => for { _ <- serializer.encode(cacheValue, encodedValue) - _ = (commands.zscore[String](_: String, _: String)(_: ByteStringSerializer[String])) - .expects(cacheKey, encodedValue, *) - .returns(disconnected) + _ = (commands + .zscore[String](_: String, _: String)(_: ByteStringSerializer[String])) + .expects(cacheKey, encodedValue, *) + .returns(disconnected) _ <- connector.sortedSetScore(cacheKey, cacheValue).assertingFailure[ExecutionFailedException, SimulatedException] } yield Passed } @@ -200,33 +212,36 @@ for { test("failed ZREM") { (serializer, commands, connector) => for { _ <- serializer.encode(cacheValue, encodedValue) - _ = (commands.zremMock(_: String, _: Seq[String])(_: ByteStringSerializer[String])) - .expects(cacheKey, Seq(encodedValue), *) - .returns(disconnected) + _ = (commands + .zremMock(_: String, _: Seq[String])(_: ByteStringSerializer[String])) + .expects(cacheKey, Seq(encodedValue), *) + .returns(disconnected) _ <- connector.sortedSetRemove(cacheKey, cacheValue).assertingFailure[ExecutionFailedException, SimulatedException] } yield Passed } test("failed ZRANGE") { (_, commands, connector) => for { - _ <- (commands.zrange[String](_: String, _: Long, _: Long)(_: ByteStringDeserializer[String])) - .expects(cacheKey, 1, 5, *) - .returns(disconnected) + _ <- (commands + .zrange[String](_: String, _: Long, _: Long)(_: ByteStringDeserializer[String])) + .expects(cacheKey, 1, 5, *) + .returns(disconnected) _ <- connector.sortedSetRange[String](cacheKey, 1, 5).assertingFailure[ExecutionFailedException, SimulatedException] } yield Passed } test("failed ZREVRANGE") { (_, commands, connector) => for { - _ <- (commands.zrevrange[String](_: String, _: Long, _: Long)(_: ByteStringDeserializer[String])) - .expects(cacheKey, 1, 5, *) - .returns(disconnected) + _ <- (commands + .zrevrange[String](_: String, _: Long, _: Long)(_: ByteStringDeserializer[String])) + .expects(cacheKey, 1, 5, *) + .returns(disconnected) _ <- connector.sortedSetReverseRange[String](cacheKey, 1, 5).assertingFailure[ExecutionFailedException, SimulatedException] } yield Passed } } - private def test(name: String)(f: (SerializerAssertions, RedisCommandsMock, RedisConnector) => Future[Assertion]): Unit = { + private def test(name: String)(f: (SerializerAssertions, RedisCommandsMock, RedisConnector) => Future[Assertion]): Unit = name in { implicit val runtime: RedisRuntime = mock[RedisRuntime] val serializer = mock[AkkaSerializer] @@ -237,27 +252,24 @@ for { f(new SerializerAssertions(serializer), commands, connector) } - } private class SerializerAssertions(mock: AkkaSerializer) { - def failOnEncode[T](value: T): Future[Unit] = { + def failOnEncode[T](value: T): Future[Unit] = Future.successful { (mock.encode(_: Any)).expects(value).returns(Failure(SimulatedException)) } - } - def encode[T](value: T, encoded: String): Future[Unit] = { + def encode[T](value: T, encoded: String): Future[Unit] = Future.successful { (mock.encode(_: Any)).expects(value).returns(Success(encoded)) } - } - def failOnDecode(value: String): Future[Unit] = { + def failOnDecode(value: String): Future[Unit] = Future.successful { (mock.decode(_: String)(_: ClassTag[String])).expects(value, *).returns(Failure(SimulatedException)) } - } + } private trait RedisCommandsMock extends RedisCommands { @@ -267,9 +279,10 @@ for { def zaddMock[V: ByteStringSerializer](key: String, scoreMembers: Seq[(Double, V)]): Future[Long] - override final def zrem[V: ByteStringSerializer](key: String, members: V*): Future[Long] = + final override def zrem[V: ByteStringSerializer](key: String, members: V*): Future[Long] = zremMock(key, members) def zremMock[V: ByteStringSerializer](key: String, members: Seq[V]): Future[Long] } + } diff --git a/src/test/scala/play/api/cache/redis/connector/RedisRequestTimeoutSpec.scala b/src/test/scala/play/api/cache/redis/connector/RedisRequestTimeoutSpec.scala index dfb0c659..8a7a1b24 100644 --- a/src/test/scala/play/api/cache/redis/connector/RedisRequestTimeoutSpec.scala +++ b/src/test/scala/play/api/cache/redis/connector/RedisRequestTimeoutSpec.scala @@ -30,17 +30,19 @@ class RedisRequestTimeoutSpec extends AsyncUnitSpec { } private class RequestTimeoutBase(implicit system: ActorSystem) extends RequestTimeout { - protected implicit val scheduler: Scheduler = system.scheduler + implicit protected val scheduler: Scheduler = system.scheduler implicit val executionContext: ExecutionContextExecutor = system.dispatcher - def send[T](redisCommand: RedisCommand[_ <: RedisReply, T]): Future[T] = { + def send[T](redisCommand: RedisCommand[? <: RedisReply, T]): Future[T] = redisCommand.asInstanceOf[RedisCommandTest[T]].returning - } + } private class RedisRequestTimeoutImpl( - override val timeout: Option[FiniteDuration] - )(implicit - system: ActorSystem - ) extends RequestTimeoutBase with RedisRequestTimeout + override val timeout: Option[FiniteDuration], + )(implicit + system: ActorSystem, + ) extends RequestTimeoutBase + with RedisRequestTimeout + } diff --git a/src/test/scala/play/api/cache/redis/connector/RedisSentinelSpec.scala b/src/test/scala/play/api/cache/redis/connector/RedisSentinelSpec.scala index 6f73d0d7..ae0bfb80 100644 --- a/src/test/scala/play/api/cache/redis/connector/RedisSentinelSpec.scala +++ b/src/test/scala/play/api/cache/redis/connector/RedisSentinelSpec.scala @@ -61,7 +61,7 @@ class RedisSentinelSpec extends IntegrationSpec with RedisSentinelContainer { } yield Passed } - def test(name: String)(f: RedisConnector => Future[Assertion]): Unit = { + def test(name: String)(f: RedisConnector => Future[Assertion]): Unit = name in { implicit val system: ActorSystem = ActorSystem("test", classLoader = Some(getClass.getClassLoader)) implicit val runtime: RedisRuntime = RedisRuntime("sentinel", syncTimeout = 5.seconds, ExecutionContext.global, new LogAndFailPolicy, LazyInvocation) @@ -71,13 +71,16 @@ class RedisSentinelSpec extends IntegrationSpec with RedisSentinelContainer { lazy val sentinelInstance = RedisSentinel( name = "sentinel", masterGroup = master, - sentinels = 0.until(nodes).map { i => - RedisHost(container.containerIpAddress, container.mappedPort(sentinelPort + i)) - }.toList, + sentinels = 0 + .until(nodes) + .map { i => + RedisHost(container.containerIpAddress, container.mappedPort(sentinelPort + i)) + } + .toList, settings = RedisSettings.load( config = Helpers.configuration.default.underlying, - path = "play.cache.redis" - ) + path = "play.cache.redis", + ), ) application.runAsyncInApplication { @@ -85,11 +88,11 @@ class RedisSentinelSpec extends IntegrationSpec with RedisSentinelContainer { for { // initialize the connector by flushing the database keys <- connector.matching("*") - _ <- Future.sequence(keys.map(connector.remove(_))) + _ <- Future.sequence(keys.map(connector.remove(_))) // run the test - _ <- f(connector) + _ <- f(connector) } yield Passed } } - } + } diff --git a/src/test/scala/play/api/cache/redis/connector/RedisStandaloneSpec.scala b/src/test/scala/play/api/cache/redis/connector/RedisStandaloneSpec.scala index b37ee667..2321fd7f 100644 --- a/src/test/scala/play/api/cache/redis/connector/RedisStandaloneSpec.scala +++ b/src/test/scala/play/api/cache/redis/connector/RedisStandaloneSpec.scala @@ -150,9 +150,7 @@ class RedisStandaloneSpec extends IntegrationSpec with RedisStandaloneContainer } test("remove with empty args") { (_, connector) => - for { - _ <- connector.remove(List.empty: _*).assertingSuccess - } yield Passed + connector.remove().assertingSuccess } test("clear with setting null") { (cacheKey, connector) => @@ -185,8 +183,7 @@ class RedisStandaloneSpec extends IntegrationSpec with RedisStandaloneContainer _ <- connector.matching(s"$cacheKey-*A").map(_.toSet).assertingEqual(Set(s"$cacheKey-key-A", s"$cacheKey-note-A")) _ <- connector.matching(s"$cacheKey-key-*").map(_.toSet).assertingEqual(Set(s"$cacheKey-key-A", s"$cacheKey-key-B")) _ <- connector.matching(s"$cacheKey-* A * ").assertingEqual(Seq.empty) - } - yield Passed + } yield Passed } test("remove multiple keys at once") { (cacheKey, connector) => @@ -381,15 +378,15 @@ class RedisStandaloneSpec extends IntegrationSpec with RedisStandaloneContainer } yield Passed } - test("set add") { (cacheKey, connector) => - for { - _ <- connector.setSize(cacheKey).assertingEqual(0) - _ <- connector.setAdd(cacheKey, "A", "B").assertingEqual(2) - _ <- connector.setSize(cacheKey).assertingEqual(2) - _ <- connector.setAdd(cacheKey, "C", "B").assertingEqual(1) - _ <- connector.setSize(cacheKey).assertingEqual(3) - } yield Passed - } + test("set add") { (cacheKey, connector) => + for { + _ <- connector.setSize(cacheKey).assertingEqual(0) + _ <- connector.setAdd(cacheKey, "A", "B").assertingEqual(2) + _ <- connector.setSize(cacheKey).assertingEqual(2) + _ <- connector.setAdd(cacheKey, "C", "B").assertingEqual(1) + _ <- connector.setSize(cacheKey).assertingEqual(3) + } yield Passed + } test("set add into invalid type") { (cacheKey, connector) => for { @@ -399,23 +396,23 @@ class RedisStandaloneSpec extends IntegrationSpec with RedisStandaloneContainer } yield Passed } - test("set rank") { (cacheKey, connector) => - for { - _ <- connector.setSize(cacheKey).assertingEqual(0) - _ <- connector.setAdd(cacheKey, "A", "B").assertingEqual(2) - _ <- connector.setSize(cacheKey).assertingEqual(2) + test("set rank") { (cacheKey, connector) => + for { + _ <- connector.setSize(cacheKey).assertingEqual(0) + _ <- connector.setAdd(cacheKey, "A", "B").assertingEqual(2) + _ <- connector.setSize(cacheKey).assertingEqual(2) - _ <- connector.setIsMember(cacheKey, "A").assertingEqual(true) - _ <- connector.setIsMember(cacheKey, "B").assertingEqual(true) - _ <- connector.setIsMember(cacheKey, "C").assertingEqual(false) + _ <- connector.setIsMember(cacheKey, "A").assertingEqual(true) + _ <- connector.setIsMember(cacheKey, "B").assertingEqual(true) + _ <- connector.setIsMember(cacheKey, "C").assertingEqual(false) - _ <- connector.setAdd(cacheKey, "C", "B").assertingEqual(1) + _ <- connector.setAdd(cacheKey, "C", "B").assertingEqual(1) - _ <- connector.setIsMember(cacheKey, "A").assertingEqual(true) - _ <- connector.setIsMember(cacheKey, "B").assertingEqual(true) - _ <- connector.setIsMember(cacheKey, "C").assertingEqual(true) - } yield Passed - } + _ <- connector.setIsMember(cacheKey, "A").assertingEqual(true) + _ <- connector.setIsMember(cacheKey, "B").assertingEqual(true) + _ <- connector.setIsMember(cacheKey, "C").assertingEqual(true) + } yield Passed + } test("set size") { (cacheKey, connector) => for { @@ -438,41 +435,41 @@ class RedisStandaloneSpec extends IntegrationSpec with RedisStandaloneContainer } yield Passed } - test("set slice") { (cacheKey, connector) => - for { - _ <- connector.setSize(cacheKey).assertingEqual(0) - _ <- connector.setAdd(cacheKey, "A", "B", "C").assertingEqual(3) - _ <- connector.setSize(cacheKey).assertingEqual(3) + test("set slice") { (cacheKey, connector) => + for { + _ <- connector.setSize(cacheKey).assertingEqual(0) + _ <- connector.setAdd(cacheKey, "A", "B", "C").assertingEqual(3) + _ <- connector.setSize(cacheKey).assertingEqual(3) - _ <- connector.setMembers[String](cacheKey).assertingEqual(Set("A", "B", "C")) + _ <- connector.setMembers[String](cacheKey).assertingEqual(Set("A", "B", "C")) - _ <- connector.setSize(cacheKey).assertingEqual(3) - } yield Passed - } + _ <- connector.setSize(cacheKey).assertingEqual(3) + } yield Passed + } test("hash set values") { (cacheKey, connector) => for { _ <- connector.hashSize(cacheKey).assertingEqual(0) - _ <- connector.hashGetAll[String] (cacheKey).assertingEqual(Map.empty) + _ <- connector.hashGetAll[String](cacheKey).assertingEqual(Map.empty) _ <- connector.hashKeys(cacheKey).assertingEqual(Set.empty) - _ <- connector.hashValues[String] (cacheKey).assertingEqual(Set.empty) + _ <- connector.hashValues[String](cacheKey).assertingEqual(Set.empty) - _ <- connector.hashGet[String] (cacheKey, "KA").assertingEqual(None) + _ <- connector.hashGet[String](cacheKey, "KA").assertingEqual(None) _ <- connector.hashSet(cacheKey, "KA", "VA1").assertingEqual(true) - _ <- connector.hashGet[String] (cacheKey, "KA").assertingEqual(Some("VA1")) + _ <- connector.hashGet[String](cacheKey, "KA").assertingEqual(Some("VA1")) _ <- connector.hashSet(cacheKey, "KA", "VA2").assertingEqual(false) - _ <- connector.hashGet[String] (cacheKey, "KA").assertingEqual(Some("VA2")) + _ <- connector.hashGet[String](cacheKey, "KA").assertingEqual(Some("VA2")) _ <- connector.hashSet(cacheKey, "KB", "VB").assertingEqual(true) - _ <- connector.hashGet[String] (cacheKey, Seq("KA", "KB", "KC")).assertingEqual(Seq(Some("VA2"), Some("VB"), None)) + _ <- connector.hashGet[String](cacheKey, Seq("KA", "KB", "KC")).assertingEqual(Seq(Some("VA2"), Some("VB"), None)) _ <- connector.hashExists(cacheKey, "KB").assertingEqual(true) _ <- connector.hashExists(cacheKey, "KC").assertingEqual(false) _ <- connector.hashSize(cacheKey).assertingEqual(2) - _ <- connector.hashGetAll[String] (cacheKey).assertingEqual(Map("KA" -> "VA2", "KB" -> "VB")) + _ <- connector.hashGetAll[String](cacheKey).assertingEqual(Map("KA" -> "VA2", "KB" -> "VB")) _ <- connector.hashKeys(cacheKey).assertingEqual(Set("KA", "KB")) - _ <- connector.hashValues[String] (cacheKey).assertingEqual(Set("VA2", "VB")) + _ <- connector.hashValues[String](cacheKey).assertingEqual(Set("VA2", "VB")) _ <- connector.hashRemove(cacheKey, "KB").assertingEqual(1) _ <- connector.hashRemove(cacheKey, "KC").assertingEqual(0) @@ -480,9 +477,9 @@ class RedisStandaloneSpec extends IntegrationSpec with RedisStandaloneContainer _ <- connector.hashExists(cacheKey, "KA").assertingEqual(true) _ <- connector.hashSize(cacheKey).assertingEqual(1) - _ <- connector.hashGetAll[String] (cacheKey).assertingEqual(Map("KA" -> "VA2")) + _ <- connector.hashGetAll[String](cacheKey).assertingEqual(Map("KA" -> "VA2")) _ <- connector.hashKeys(cacheKey).assertingEqual(Set("KA")) - _ <- connector.hashValues[String] (cacheKey).assertingEqual(Set("VA2")) + _ <- connector.hashValues[String](cacheKey).assertingEqual(Set("VA2")) _ <- connector.hashSet(cacheKey, "KD", 5).assertingEqual(true) _ <- connector.hashIncrement(cacheKey, "KD", 2).assertingEqual(7) @@ -514,25 +511,25 @@ class RedisStandaloneSpec extends IntegrationSpec with RedisStandaloneContainer for { _ <- connector.set(cacheKey, "value").assertingSuccess _ <- connector.get[String](cacheKey).assertingEqual(Some("value")) - _ <- connector.sortedSetAdd(cacheKey, 1D -> "VA1").assertingFailure[IllegalArgumentException] + _ <- connector.sortedSetAdd(cacheKey, 1d -> "VA1").assertingFailure[IllegalArgumentException] } yield Passed } test("sorted set score") { (cacheKey, connector) => for { _ <- connector.sortedSetSize(cacheKey).assertingEqual(0) - _ <- connector.sortedSetAdd(cacheKey, 1D -> "A", 3D -> "B").assertingEqual(2) + _ <- connector.sortedSetAdd(cacheKey, 1d -> "A", 3d -> "B").assertingEqual(2) _ <- connector.sortedSetSize(cacheKey).assertingEqual(2) - _ <- connector.sortedSetScore(cacheKey, "A").assertingEqual(Some(1D)) - _ <- connector.sortedSetScore(cacheKey, "B").assertingEqual(Some(3D)) + _ <- connector.sortedSetScore(cacheKey, "A").assertingEqual(Some(1d)) + _ <- connector.sortedSetScore(cacheKey, "B").assertingEqual(Some(3d)) _ <- connector.sortedSetScore(cacheKey, "C").assertingEqual(None) - _ <- connector.sortedSetAdd(cacheKey, 2D -> "C", 4D -> "B").assertingEqual(1) + _ <- connector.sortedSetAdd(cacheKey, 2d -> "C", 4d -> "B").assertingEqual(1) - _ <- connector.sortedSetScore(cacheKey, "A").assertingEqual(Some(1D)) - _ <- connector.sortedSetScore(cacheKey, "B").assertingEqual(Some(4D)) - _ <- connector.sortedSetScore(cacheKey, "C").assertingEqual(Some(2D)) + _ <- connector.sortedSetScore(cacheKey, "A").assertingEqual(Some(1d)) + _ <- connector.sortedSetScore(cacheKey, "B").assertingEqual(Some(4d)) + _ <- connector.sortedSetScore(cacheKey, "C").assertingEqual(Some(2d)) } yield Passed } @@ -547,7 +544,7 @@ class RedisStandaloneSpec extends IntegrationSpec with RedisStandaloneContainer test("sorted set remove") { (cacheKey, connector) => for { _ <- connector.sortedSetSize(cacheKey).assertingEqual(0) - _ <- connector.sortedSetAdd(cacheKey, 1D -> "A", 2D -> "B", 3D -> "C").assertingEqual(3) + _ <- connector.sortedSetAdd(cacheKey, 1d -> "A", 2d -> "B", 3d -> "C").assertingEqual(3) _ <- connector.sortedSetSize(cacheKey).assertingEqual(3) _ <- connector.sortedSetRemove(cacheKey, "A").assertingEqual(1) @@ -560,7 +557,7 @@ class RedisStandaloneSpec extends IntegrationSpec with RedisStandaloneContainer test("sorted set range") { (cacheKey, connector) => for { _ <- connector.sortedSetSize(cacheKey).assertingEqual(0) - _ <- connector.sortedSetAdd(cacheKey, 1D -> "A", 2D -> "B", 4D -> "C").assertingEqual(3) + _ <- connector.sortedSetAdd(cacheKey, 1d -> "A", 2d -> "B", 4d -> "C").assertingEqual(3) _ <- connector.sortedSetSize(cacheKey).assertingEqual(3) _ <- connector.sortedSetRange[String](cacheKey, 0, 1).assertingEqual(Vector("A", "B")) @@ -574,7 +571,7 @@ class RedisStandaloneSpec extends IntegrationSpec with RedisStandaloneContainer test("sorted set reverse range") { (cacheKey, connector) => for { _ <- connector.sortedSetSize(cacheKey).assertingEqual(0) - _ <- connector.sortedSetAdd(cacheKey, 1D -> "A", 2D -> "B", 4D -> "C").assertingEqual(3) + _ <- connector.sortedSetAdd(cacheKey, 1d -> "A", 2d -> "B", 4d -> "C").assertingEqual(3) _ <- connector.sortedSetSize(cacheKey).assertingEqual(3) _ <- connector.sortedSetReverseRange[String](cacheKey, 0, 1).assertingEqual(Vector("C", "B")) @@ -585,7 +582,7 @@ class RedisStandaloneSpec extends IntegrationSpec with RedisStandaloneContainer } yield Passed } - def test(name: String)(f: (String, RedisConnector) => Future[Assertion]): Unit = { + def test(name: String)(f: (String, RedisConnector) => Future[Assertion]): Unit = name in { implicit val system: ActorSystem = ActorSystem("test", classLoader = Some(getClass.getClassLoader)) implicit val runtime: RedisRuntime = RedisRuntime("standalone", syncTimeout = 5.seconds, ExecutionContext.global, new LogAndFailPolicy, LazyInvocation) @@ -597,22 +594,21 @@ class RedisStandaloneSpec extends IntegrationSpec with RedisStandaloneContainer host = RedisHost(container.containerIpAddress, container.mappedPort(defaultPort)), settings = RedisSettings.load( config = Helpers.configuration.default.underlying, - path = "play.cache.redis" - ) + path = "play.cache.redis", + ), ) - + val cacheKey = name.toLowerCase().replace(" ", "-") application.runAsyncInApplication { for { connector <- Future(new RedisConnectorProvider(instance, serializer).get) // initialize the connector by flushing the database - _ <- connector.invalidate() + _ <- connector.invalidate() // run the test - _ <- f(cacheKey, connector) + _ <- f(cacheKey, connector) } yield Passed } } - } - } +} diff --git a/src/test/scala/play/api/cache/redis/connector/SerializerSpec.scala b/src/test/scala/play/api/cache/redis/connector/SerializerSpec.scala index 838ec754..57d1e015 100644 --- a/src/test/scala/play/api/cache/redis/connector/SerializerSpec.scala +++ b/src/test/scala/play/api/cache/redis/connector/SerializerSpec.scala @@ -15,7 +15,7 @@ class SerializerSpec extends AsyncUnitSpec { "encode" when { test("byte") { implicit serializer => - 0xAB.toByte.encoded mustEqual "-85" + 0xab.toByte.encoded mustEqual "-85" JavaTypes.byteValue.encoded mustEqual "5" } @@ -94,7 +94,7 @@ class SerializerSpec extends AsyncUnitSpec { "decode" when { test("byte") { implicit serializer => - "-85".decoded[Byte] mustEqual 0xAB.toByte + "-85".decoded[Byte] mustEqual 0xab.toByte } test("byte[]") { implicit serializer => @@ -171,31 +171,30 @@ class SerializerSpec extends AsyncUnitSpec { } } - private def test(name: String)(f: AkkaSerializer => Unit): Unit = { + private def test(name: String)(f: AkkaSerializer => Unit): Unit = name in { val system = ActorSystem.apply(s"test-${Random.nextInt()}", classLoader = Some(getClass.getClassLoader)) val serializer: AkkaSerializer = new AkkaSerializerImpl(system) f(serializer) system.terminate().map(_ => Passed) } - } + } object SerializerSpec { - private implicit class ValueEncoder(private val any: Any) extends AnyVal { + implicit private class ValueEncoder(private val any: Any) extends AnyVal { def encoded(implicit s: AkkaSerializer): String = s.encode(any).get } - private implicit class StringDecoder(private val string: String) extends AnyVal { + implicit private class StringDecoder(private val string: String) extends AnyVal { def decoded[T: ClassTag](implicit s: AkkaSerializer): T = s.decode[T](string).get } - private implicit class StringOps(private val string: String) extends AnyVal { + implicit private class StringOps(private val string: String) extends AnyVal { def withoutWhitespaces: String = string.replaceAll("\\s", "") } /** Plain test object to be cached */ - private final case class SimpleObject(key: String, value: Int) + final private case class SimpleObject(key: String, value: Int) } - diff --git a/src/test/scala/play/api/cache/redis/impl/AsyncJavaRedisSpec.scala b/src/test/scala/play/api/cache/redis/impl/AsyncJavaRedisSpec.scala index 32519527..15739b0c 100644 --- a/src/test/scala/play/api/cache/redis/impl/AsyncJavaRedisSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/AsyncJavaRedisSpec.scala @@ -11,7 +11,7 @@ import scala.concurrent.duration._ import scala.jdk.CollectionConverters.IterableHasAsScala class AsyncJavaRedisSpec extends AsyncUnitSpec with AsyncRedisMock with RedisRuntimeMock { -import Helpers._ + import Helpers._ private val expiration = 5.seconds private val expirationLong = expiration.toSeconds @@ -33,26 +33,26 @@ import Helpers._ } yield Passed } - test("get null") { (async, cache) => - for { - _ <- async.expect.getClassTag(cacheKey, Some("null")) + test("get null") { (async, cache) => + for { + _ <- async.expect.getClassTag(cacheKey, Some("null")) _ <- cache.get[String](cacheKey).assertingEqual(Optional.empty) - } yield Passed - } + } yield Passed + } - test("set") { (async, cache) => - for { + test("set") { (async, cache) => + for { _ <- async.expect.set(cacheKey, cacheValue, Duration.Inf) _ <- cache.set(cacheKey, cacheValue).assertingDone - } yield Passed - } + } yield Passed + } - test("set with expiration") { (async, cache) => - for { - _ <- async.expect.set(cacheKey, cacheValue, expiration) - _ <- cache.set(cacheKey, cacheValue, expiration.toSeconds.toInt).assertingDone - } yield Passed - } + test("set with expiration") { (async, cache) => + for { + _ <- async.expect.set(cacheKey, cacheValue, expiration) + _ <- cache.set(cacheKey, cacheValue, expiration.toSeconds.toInt).assertingDone + } yield Passed + } test("set null") { (async, cache) => for { @@ -154,8 +154,8 @@ import Helpers._ for { _ <- async.expect.getAllKeys[String](Iterable(cacheKey, cacheKey, cacheKey), Seq(Some(cacheValue), None, None)) _ <- cache - .getAll(classOf[String], cacheKey, cacheKey, cacheKey) - .asserting(_.asScala.toList mustEqual List(Optional.of(cacheValue), Optional.empty, Optional.empty)) + .getAll(classOf[String], cacheKey, cacheKey, cacheKey) + .asserting(_.asScala.toList mustEqual List(Optional.of(cacheValue), Optional.empty, Optional.empty)) } yield Passed } @@ -164,8 +164,8 @@ import Helpers._ for { _ <- async.expect.getAllKeys[String](Iterable(cacheKey, cacheKey, cacheKey), Seq(Some(cacheValue), None, None)) _ <- cache - .getAll(classOf[String], JavaList(cacheKey, cacheKey, cacheKey)) - .asserting(_.asScala.toList mustEqual List(Optional.of(cacheValue), Optional.empty, Optional.empty)) + .getAll(classOf[String], JavaList(cacheKey, cacheKey, cacheKey)) + .asserting(_.asScala.toList mustEqual List(Optional.of(cacheValue), Optional.empty, Optional.empty)) } yield Passed } @@ -289,12 +289,12 @@ import Helpers._ } yield Passed } - test("remove matching") { (async, cache) => - for { - _ <- async.expect.removeMatching("pattern") - _ <- cache.removeMatching("pattern").assertingDone - } yield Passed - } + test("remove matching") { (async, cache) => + for { + _ <- async.expect.removeMatching("pattern") + _ <- cache.removeMatching("pattern").assertingDone + } yield Passed + } test("exists") { (async, cache) => for { @@ -326,7 +326,7 @@ import Helpers._ val list = mock[RedisListMock] for { _ <- async.expect.list[String](cacheKey, list) - _ <- cache.list(cacheKey, classOf[String]) mustBe a[AsyncRedisList[_]] + _ <- cache.list(cacheKey, classOf[String]) mustBe a[AsyncRedisList[?]] } yield Passed } @@ -335,7 +335,7 @@ import Helpers._ val set = mock[RedisSetMock] for { _ <- async.expect.set[String](cacheKey, set) - _ <- cache.set(cacheKey, classOf[String]) mustBe a[AsyncRedisSet[_]] + _ <- cache.set(cacheKey, classOf[String]) mustBe a[AsyncRedisSet[?]] } yield Passed } @@ -344,11 +344,11 @@ import Helpers._ val map = mock[RedisMapMock] for { _ <- async.expect.map[String](cacheKey, map) - _ <- cache.map(cacheKey, classOf[String]) mustBe a[AsyncRedisMap[_]] + _ <- cache.map(cacheKey, classOf[String]) mustBe a[AsyncRedisMap[?]] } yield Passed } - private def test(name: String)(f: (AsyncRedisMock, play.cache.redis.AsyncCacheApi) => Future[Assertion]): Unit = { + private def test(name: String)(f: (AsyncRedisMock, play.cache.redis.AsyncCacheApi) => Future[Assertion]): Unit = name in { implicit val runtime: RedisRuntime = redisRuntime( invocationPolicy = LazyInvocation, @@ -357,12 +357,12 @@ import Helpers._ implicit val environment: Environment = Environment( rootPath = new java.io.File("."), classLoader = getClass.getClassLoader, - mode = Mode.Test + mode = Mode.Test, ) val async = mock[AsyncRedisMock] val cache: play.cache.redis.AsyncCacheApi = new AsyncJavaRedis(async) f(async, cache) } - } + } diff --git a/src/test/scala/play/api/cache/redis/impl/AsyncRedisMock.scala b/src/test/scala/play/api/cache/redis/impl/AsyncRedisMock.scala index 98fc3df0..6365e883 100644 --- a/src/test/scala/play/api/cache/redis/impl/AsyncRedisMock.scala +++ b/src/test/scala/play/api/cache/redis/impl/AsyncRedisMock.scala @@ -20,22 +20,24 @@ private[impl] trait AsyncRedisMock { this: AsyncMockFactoryBase => final override def getAll[T: ClassTag](keys: Iterable[String]): AsynchronousResult[Seq[Option[T]]] = getAllKeys(keys) - def getAllKeys[T: ClassTag](keys: Iterable[String]): AsynchronousResult[Seq[Option[T]]] + def getAllKeys[T](keys: Iterable[String]): AsynchronousResult[Seq[Option[T]]] } - final protected implicit class AsyncRedisOps(async: AsyncRedisMock) { + implicit final protected class AsyncRedisOps(async: AsyncRedisMock) { + def expect: AsyncRedisExpectation = new AsyncRedisExpectation(async) + } - protected final class AsyncRedisExpectation(async: AsyncRedisMock) { + final protected class AsyncRedisExpectation(async: AsyncRedisMock) { private def classTagKey(key: String): String = s"classTag::$key" private def classTagValue: Any => String = { - case null => "null" + case null => "null" case v if v.getClass =~= classOf[String] => "java.lang.String" - case other => throw new IllegalArgumentException(s"Unexpected value for classTag: ${other.getClass.getSimpleName}") + case other => throw new IllegalArgumentException(s"Unexpected value for classTag: ${other.getClass.getSimpleName}") } def getClassTag(key: String, value: Option[String]): Future[Unit] = @@ -43,58 +45,62 @@ private[impl] trait AsyncRedisMock { this: AsyncMockFactoryBase => def get[T: ClassTag](key: String, value: Option[T]): Future[Unit] = Future.successful { - (async.get(_: String)(_: ClassTag[_])) + (async + .get(_: String)(_: ClassTag[?])) .expects(key, implicitly[ClassTag[T]]) .returning(Future.successful(value)) .once() } - def getAllKeys[T: ClassTag](keys: Iterable[String], values: Seq[Option[T]]): Future[Unit] = + def getAllKeys[T](keys: Iterable[String], values: Seq[Option[T]]): Future[Unit] = Future.successful { - (async.getAllKeys(_: Iterable[String])(_: ClassTag[_])) - .expects(keys, implicitly[ClassTag[T]]) + (async + .getAllKeys[T](_: Iterable[String])) + .expects(keys) .returning(Future.successful(values)) .once() } def setValue[T](key: String, value: T, duration: Duration): Future[Unit] = Future.successful { - (async.set(_: String, _: Any, _: Duration)) + (async + .set(_: String, _: Any, _: Duration)) .expects(key, if (Option(value).isEmpty) * else value, duration) .returning(Future.successful(Done)) .once() } - def setClassTag[T: ClassTag](key: String, value: T, duration: Duration): Future[Unit] = + def setClassTag[T](key: String, value: T, duration: Duration): Future[Unit] = setValue(classTagKey(key), value, duration) - def set[T: ClassTag](key: String, value: T, duration: Duration): Future[Unit] = + def set[T](key: String, value: T, duration: Duration): Future[Unit] = for { _ <- setValue(key, value, duration) _ <- setClassTag(key, classTagValue(value), duration) } yield () - def setValueIfNotExists[T: ClassTag](key: String, value: T, duration: Duration, exists: Boolean): Future[Unit] = + def setValueIfNotExists[T](key: String, value: T, duration: Duration, exists: Boolean): Future[Unit] = Future.successful { - (async.setIfNotExists(_: String, _: Any, _: Duration)) + (async + .setIfNotExists(_: String, _: Any, _: Duration)) .expects(key, if (Option(value).isEmpty) * else value, duration) .returning(Future.successful(exists)) .once() } - def setClassTagIfNotExists[T: ClassTag](key: String, value: T, duration: Duration, exists: Boolean): Future[Unit] = + def setClassTagIfNotExists[T](key: String, value: T, duration: Duration, exists: Boolean): Future[Unit] = setValueIfNotExists(classTagKey(key), classTagValue(value), duration, exists) - def setIfNotExists[T: ClassTag](key: String, value: T, duration: Duration, exists: Boolean): Future[Unit] = + def setIfNotExists[T](key: String, value: T, duration: Duration, exists: Boolean): Future[Unit] = for { _ <- setValueIfNotExists(key, value, duration, exists) _ <- setClassTagIfNotExists(key, value, duration, exists) } yield () - def setAll[T: ClassTag](values: (String, Any)*): Future[Unit] = + def setAll(values: (String, Any)*): Future[Unit] = Future.successful { - val valuesWithClassTags = values.flatMap { - case (k, v) => Seq((k, v), (classTagKey(k), classTagValue(v))) + val valuesWithClassTags = values.flatMap { case (k, v) => + Seq((k, v), (classTagKey(k), classTagValue(v))) } (async.setAll _) .expects(valuesWithClassTags) @@ -102,10 +108,10 @@ private[impl] trait AsyncRedisMock { this: AsyncMockFactoryBase => .once() } - def setAllIfNotExist[T: ClassTag](values: Seq[(String, Any)], exists: Boolean): Future[Unit] = + def setAllIfNotExist(values: Seq[(String, Any)], exists: Boolean): Future[Unit] = Future.successful { - val valuesWithClassTags = values.flatMap { - case (k, v) => Seq((k, v), (classTagKey(k), classTagValue(v))) + val valuesWithClassTags = values.flatMap { case (k, v) => + Seq((k, v), (classTagKey(k), classTagValue(v))) } (async.setAllIfNotExist _) .expects(valuesWithClassTags) @@ -115,11 +121,13 @@ private[impl] trait AsyncRedisMock { this: AsyncMockFactoryBase => def expire(key: String, duration: Duration): Future[Unit] = Future.successful { - (async.expire(_: String, _: Duration)) + (async + .expire(_: String, _: Duration)) .expects(classTagKey(key), duration) .returning(Future.successful(Done)) .once() - (async.expire(_: String, _: Duration)) + (async + .expire(_: String, _: Duration)) .expects(key, duration) .returning(Future.successful(Done)) .once() @@ -127,7 +135,8 @@ private[impl] trait AsyncRedisMock { this: AsyncMockFactoryBase => def expiresIn(key: String, duration: Option[Duration]): Future[Unit] = Future.successful { - (async.expiresIn(_: String)) + (async + .expiresIn(_: String)) .expects(key) .returning(Future.successful(duration)) .once() @@ -135,15 +144,17 @@ private[impl] trait AsyncRedisMock { this: AsyncMockFactoryBase => def matching(pattern: String, keys: Seq[String]): Future[Unit] = Future.successful { - (async.matching(_: String)) + (async + .matching(_: String)) .expects(pattern) .returning(Future.successful(keys)) .once() } def removeMatching(pattern: String): Future[Unit] = { - def removePattern(patternToRemove: String) = - (async.removeMatching(_: String)) + def removePattern(patternToRemove: String): Unit = + (async + .removeMatching(_: String)) .expects(patternToRemove) .returning(Future.successful(Done)) .once() @@ -156,7 +167,8 @@ private[impl] trait AsyncRedisMock { this: AsyncMockFactoryBase => def exists(key: String, exists: Boolean): Future[Unit] = Future.successful { - (async.exists(_: String)) + (async + .exists(_: String)) .expects(key) .returning(Future.successful(exists)) .once() @@ -164,15 +176,17 @@ private[impl] trait AsyncRedisMock { this: AsyncMockFactoryBase => def increment(key: String, by: Long, result: Long): Future[Unit] = Future.successful { - (async.increment(_: String, _: Long)) + (async + .increment(_: String, _: Long)) .expects(key, by) .returning(Future.successful(result)) .once() } - def decrement(key: String, by:Long, result:Long): Future[Unit] = + def decrement(key: String, by: Long, result: Long): Future[Unit] = Future.successful { - (async.decrement(_: String, _:Long)) + (async + .decrement(_: String, _: Long)) .expects(key, by) .returning(Future.successful(result)) .once() @@ -180,11 +194,13 @@ private[impl] trait AsyncRedisMock { this: AsyncMockFactoryBase => def remove(key: String): Future[Unit] = Future.successful { - (async.remove(_: String)) + (async + .remove(_: String)) .expects(classTagKey(key)) .returning(Future.successful(Done)) .once() - (async.remove(_: String)) + (async + .remove(_: String)) .expects(key) .returning(Future.successful(Done)) .once() @@ -192,8 +208,8 @@ private[impl] trait AsyncRedisMock { this: AsyncMockFactoryBase => def removeAll(keys: String*): Future[Unit] = Future.successful { - val keysWithClassTags = keys.flatMap { - key => Seq(key, classTagKey(key)) + val keysWithClassTags = keys.flatMap { key => + Seq(key, classTagKey(key)) } (async.removeAllKeys _) .expects(keysWithClassTags) @@ -203,7 +219,8 @@ private[impl] trait AsyncRedisMock { this: AsyncMockFactoryBase => def append(key: String, value: String, expiration: Duration): Future[Unit] = Future.successful { - (async.append(_: String, _: String, _: Duration)) + (async + .append(_: String, _: String, _: Duration)) .expects(key, value, expiration) .returning(Future.successful(Done)) .once() @@ -219,7 +236,8 @@ private[impl] trait AsyncRedisMock { this: AsyncMockFactoryBase => def list[T: ClassTag](key: String, mock: RedisList[T, AsynchronousResult]): Future[Unit] = Future.successful { - (async.list[T](_: String)(_: ClassTag[T])) + (async + .list[T](_: String)(_: ClassTag[T])) .expects(key, implicitly[ClassTag[T]]) .returning(mock) .once() @@ -227,7 +245,8 @@ private[impl] trait AsyncRedisMock { this: AsyncMockFactoryBase => def set[T: ClassTag](key: String, mock: RedisSet[T, AsynchronousResult]): Future[Unit] = Future.successful { - (async.set[T](_: String)(_: ClassTag[T])) + (async + .set[T](_: String)(_: ClassTag[T])) .expects(key, implicitly[ClassTag[T]]) .returning(mock) .once() @@ -235,10 +254,13 @@ private[impl] trait AsyncRedisMock { this: AsyncMockFactoryBase => def map[T: ClassTag](key: String, mock: RedisMap[T, AsynchronousResult]): Future[Unit] = Future.successful { - (async.map[T](_: String)(_: ClassTag[T])) + (async + .map[T](_: String)(_: ClassTag[T])) .expects(key, implicitly[ClassTag[T]]) .returning(mock) .once() } + } -} \ No newline at end of file + +} diff --git a/src/test/scala/play/api/cache/redis/impl/AsyncRedisSpec.scala b/src/test/scala/play/api/cache/redis/impl/AsyncRedisSpec.scala index d32a6111..94300344 100644 --- a/src/test/scala/play/api/cache/redis/impl/AsyncRedisSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/AsyncRedisSpec.scala @@ -1,11 +1,10 @@ package play.api.cache.redis.impl -import scala.concurrent.duration._ import play.api.cache.redis._ import play.api.cache.redis.test._ import scala.concurrent.Future - +import scala.concurrent.duration._ class AsyncRedisSpec extends AsyncUnitSpec with RedisConnectorMock with RedisRuntimeMock with ImplicitFutureMaterialization { import Helpers._ @@ -59,16 +58,18 @@ class AsyncRedisSpec extends AsyncUnitSpec with RedisConnectorMock with RedisRun _ <- connector.expect.get[String](cacheKey, None) _ <- connector.expect.get[String](cacheKey, None) _ <- connector.expect.set(cacheKey, cacheValue, Duration.Inf, result = true) - orElse = probe.orElse.generic( - Future.failed(SimulatedException.asRedis), - Future.successful(cacheValue), - ) + orElse = probe + .orElse + .generic( + Future.failed(SimulatedException.asRedis), + Future.successful(cacheValue), + ) _ <- cache.getOrElseUpdate[String](cacheKey)(orElse.execute()).assertingEqual(cacheValue) _ = orElse.calls mustEqual 2 } yield Passed } - private def test(name: String, policy: RecoveryPolicy = recoveryPolicy.default)(f: (RedisConnectorMock, AsyncRedis) => Future[Assertion]): Unit = { + private def test(name: String, policy: RecoveryPolicy = recoveryPolicy.default)(f: (RedisConnectorMock, AsyncRedis) => Future[Assertion]): Unit = name in { implicit val runtime: RedisRuntime = redisRuntime( invocationPolicy = LazyInvocation, @@ -79,5 +80,5 @@ class AsyncRedisSpec extends AsyncUnitSpec with RedisConnectorMock with RedisRun f(connector, cache) } - } + } diff --git a/src/test/scala/play/api/cache/redis/impl/BuildersSpec.scala b/src/test/scala/play/api/cache/redis/impl/BuildersSpec.scala index e3725e81..df5b8cf0 100644 --- a/src/test/scala/play/api/cache/redis/impl/BuildersSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/BuildersSpec.scala @@ -11,8 +11,8 @@ class BuildersSpec extends AsyncUnitSpec with RedisRuntimeMock { import Builders._ private case class Task( - response: String, - execution: () => Future[String], + response: String, + execution: () => Future[String], ) extends (() => Future[String]) { def this(response: String)(f: String => Future[String]) = @@ -36,25 +36,25 @@ class BuildersSpec extends AsyncUnitSpec with RedisRuntimeMock { "run regular task" in { implicit val runtime: RedisRuntime = redisRuntime() - AsynchronousBuilder.toResult(Task.regular(), Task.resolved()).assertingEqual(Task.regular.response) + AsynchronousBuilder.toResult(Task.regular(), Task.resolved()).assertingEqual(Task.regular.response) } "run resolved task" in { implicit val runtime: RedisRuntime = redisRuntime() - AsynchronousBuilder.toResult(Task.resolved(),Task.regular()).assertingEqual(Task.resolved.response) + AsynchronousBuilder.toResult(Task.resolved(), Task.regular()).assertingEqual(Task.resolved.response) } "recover with default policy" in { implicit val runtime: RedisRuntime = redisRuntime(recoveryPolicy = recoveryPolicy.default) - AsynchronousBuilder.toResult(Task.failing(),Task.resolved()).assertingEqual(Task.resolved.response) + AsynchronousBuilder.toResult(Task.failing(), Task.resolved()).assertingEqual(Task.resolved.response) } - "recover with fail through policy" in { + "recover with fail through policy" in { implicit val runtime: RedisRuntime = redisRuntime(recoveryPolicy = recoveryPolicy.failThrough) - AsynchronousBuilder.toResult(Task.failing(),Task.resolved()).assertingFailure[TimeoutException] + AsynchronousBuilder.toResult(Task.failing(), Task.resolved()).assertingFailure[TimeoutException] } - "map value" in { + "map value" in { implicit val runtime: RedisRuntime = redisRuntime(recoveryPolicy = recoveryPolicy.failThrough) AsynchronousBuilder.map(Future(5))(_ + 5).assertingEqual(10) } @@ -66,22 +66,22 @@ class BuildersSpec extends AsyncUnitSpec with RedisRuntimeMock { SynchronousBuilder.name mustEqual "SynchronousBuilder" } - "run regular task" in { + "run regular task" in { implicit val runtime: RedisRuntime = redisRuntime() SynchronousBuilder.toResult(Task.regular(), Task.resolved()) mustEqual Task.regular.response } - "run resolved task" in { + "run resolved task" in { implicit val runtime: RedisRuntime = redisRuntime() SynchronousBuilder.toResult(Task.resolved(), Task.regular()) mustEqual Task.resolved.response } - "recover from failure with default policy" in { + "recover from failure with default policy" in { implicit val runtime: RedisRuntime = redisRuntime(recoveryPolicy = recoveryPolicy.default) SynchronousBuilder.toResult(Task.failing(), Task.resolved()) mustEqual Task.resolved.response } - "don't recover from failure with fail through policy" in { + "don't recover from failure with fail through policy" in { implicit val runtime: RedisRuntime = redisRuntime(recoveryPolicy = recoveryPolicy.failThrough) assertThrows[TimeoutException] { SynchronousBuilder.toResult(Task.failing(), Task.resolved()) @@ -91,30 +91,31 @@ class BuildersSpec extends AsyncUnitSpec with RedisRuntimeMock { "don't recover on timeout due to long running task with fail through policy" in { implicit val runtime: RedisRuntime = redisRuntime( recoveryPolicy = recoveryPolicy.failThrough, - timeout = 1.millis + timeout = 1.millis, ) assertThrows[TimeoutException] { SynchronousBuilder.toResult(Task.infinite(), Task.resolved()) } } - "recover from timeout due to long running task with default policy" in { + "recover from timeout due to long running task with default policy" in { implicit val runtime: RedisRuntime = redisRuntime( recoveryPolicy = recoveryPolicy.default, - timeout = 1.millis + timeout = 1.millis, ) SynchronousBuilder.toResult(Task.infinite(), Task.resolved()) mustEqual Task.resolved.response } - "recover from akka ask timeout" in { + "recover from akka ask timeout" in { implicit val runtime: RedisRuntime = redisRuntime(recoveryPolicy = recoveryPolicy.default) val actorFailure = Future.failed(new AskTimeoutException("Simulated actor ask timeout")) SynchronousBuilder.toResult(actorFailure, Task.resolved()) mustEqual Task.resolved.response } - "map value" in { + "map value" in { implicit val runtime: RedisRuntime = redisRuntime() SynchronousBuilder.map(5)(_ + 5) mustEqual 10 } } + } diff --git a/src/test/scala/play/api/cache/redis/impl/InvocationPolicySpec.scala b/src/test/scala/play/api/cache/redis/impl/InvocationPolicySpec.scala index 1595ad5e..aecf706f 100644 --- a/src/test/scala/play/api/cache/redis/impl/InvocationPolicySpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/InvocationPolicySpec.scala @@ -8,7 +8,7 @@ import scala.util.Success class InvocationPolicySpec extends UnitSpec { - private implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.parasitic + implicit private val ec: ExecutionContext = scala.concurrent.ExecutionContext.parasitic private class Probe { private val promise = Promise[Unit]() @@ -31,4 +31,5 @@ class InvocationPolicySpec extends UnitSpec { probe.resolve() outcome.isCompleted mustEqual true } + } diff --git a/src/test/scala/play/api/cache/redis/impl/RedisCacheSpec.scala b/src/test/scala/play/api/cache/redis/impl/RedisCacheSpec.scala index 9c71c56c..02aa431b 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisCacheSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisCacheSpec.scala @@ -6,7 +6,7 @@ import play.api.cache.redis.test._ import scala.concurrent.Future import scala.concurrent.duration.Duration -class RedisCacheSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConnectorMock with ImplicitFutureMaterialization { +class RedisCacheSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConnectorMock with ImplicitFutureMaterialization { import Helpers._ test("get and miss") { (cache, connector) => @@ -51,12 +51,12 @@ class RedisCacheSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConn } yield Passed } - test("set") { (cache, connector) => - for { - _ <- connector.expect.set(cacheKey, cacheValue, result = true) - _ <- cache.set(cacheKey, cacheValue).assertingDone - } yield Passed - } + test("set") { (cache, connector) => + for { + _ <- connector.expect.set(cacheKey, cacheValue, result = true) + _ <- cache.set(cacheKey, cacheValue).assertingDone + } yield Passed + } test("set recover with default") { (cache, connector) => for { @@ -67,7 +67,7 @@ class RedisCacheSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConn test("set if not exists (exists)") { (cache, connector) => for { - _ <- connector.expect.set(cacheKey, cacheValue, setIfNotExists=true,result = false) + _ <- connector.expect.set(cacheKey, cacheValue, setIfNotExists = true, result = false) _ <- cache.setIfNotExists(cacheKey, cacheValue).assertingEqual(false) } yield Passed } @@ -208,7 +208,7 @@ class RedisCacheSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConn test("matching with a prefix", prefix = Some("the-prefix")) { (cache, connector) => for { - _ <- connector.expect.matching(s"the-prefix:pattern", result = Seq(s"the-prefix:$cacheKey")) + _ <- connector.expect.matching("the-prefix:pattern", result = Seq(s"the-prefix:$cacheKey")) _ <- cache.matching("pattern").assertingEqual(Seq(cacheKey)) } yield Passed } @@ -417,8 +417,8 @@ class RedisCacheSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConn policy: RecoveryPolicy = recoveryPolicy.default, prefix: Option[String] = None, )( - f: (RedisCache[AsynchronousResult], RedisConnectorMock) => Future[Assertion] - ): Unit = { + f: (RedisCache[AsynchronousResult], RedisConnectorMock) => Future[Assertion], + ): Unit = name in { implicit val runtime: RedisRuntime = redisRuntime( invocationPolicy = LazyInvocation, @@ -429,5 +429,5 @@ class RedisCacheSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConn val cache: RedisCache[AsynchronousResult] = new RedisCache[AsynchronousResult](connector, Builders.AsynchronousBuilder) f(cache, connector) } - } - } + +} diff --git a/src/test/scala/play/api/cache/redis/impl/RedisConnectorMock.scala b/src/test/scala/play/api/cache/redis/impl/RedisConnectorMock.scala index 6fc2fa34..e0061bc2 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisConnectorMock.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisConnectorMock.scala @@ -17,7 +17,7 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def removeValues(keys: Seq[String]): Future[Unit] - override final def mGet[T: ClassTag](keys: String*): Future[Seq[Option[T]]] = + final override def mGet[T: ClassTag](keys: String*): Future[Seq[Option[T]]] = mGetKeys[T](keys) def mGetKeys[T: ClassTag](keys: Seq[String]): Future[Seq[Option[T]]] @@ -83,16 +83,19 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def hashGetAllValues[T: ClassTag](key: String): Future[Map[String, T]] } - final protected implicit class RedisConnectorExpectationOps(connector: RedisConnectorMock) { + implicit final protected class RedisConnectorExpectationOps(connector: RedisConnectorMock) { + def expect: RedisConnectorExpectation = new RedisConnectorExpectation(connector) + } - protected final class RedisConnectorExpectation(connector: RedisConnectorMock) { + final protected class RedisConnectorExpectation(connector: RedisConnectorMock) { def get[T: ClassTag](key: String, result: Try[Option[T]]): Future[Unit] = Future.successful { - (connector.get(_: String)(_: ClassTag[_])) + (connector + .get(_: String)(_: ClassTag[?])) .expects(key, implicitly[ClassTag[T]]) .returning(Future.fromTry(result)) .once() @@ -106,7 +109,8 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def mGet[T: ClassTag](keys: Seq[String], result: Future[Seq[Option[T]]]): Future[Unit] = Future.successful { - (connector.mGetKeys(_: Seq[String])(_: ClassTag[_])) + (connector + .mGetKeys(_: Seq[String])(_: ClassTag[?])) .expects(keys, implicitly[ClassTag[T]]) .returning(result) .once() @@ -114,15 +118,17 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def set[T](key: String, value: T, duration: Duration = Duration.Inf, setIfNotExists: Boolean = false, result: Future[Boolean]): Future[Unit] = Future.successful { - (connector.set(_: String, _: Any, _: Duration, _: Boolean)) + (connector + .set(_: String, _: Any, _: Duration, _: Boolean)) .expects(key, if (Option(value).isEmpty) * else value, duration, setIfNotExists) .returning(result) .once() } - def mSet(keyValues: Seq[(String, Any)], result: Future[Unit]=Future.unit): Future[Unit] = + def mSet(keyValues: Seq[(String, Any)], result: Future[Unit] = Future.unit): Future[Unit] = Future.successful { - (connector.mSetValues(_: Seq[(String, Any)])) + (connector + .mSetValues(_: Seq[(String, Any)])) .expects(keyValues) .returning(result) .once() @@ -130,7 +136,8 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def mSetIfNotExist(keyValues: Seq[(String, Any)], result: Future[Boolean]): Future[Unit] = Future.successful { - (connector.mSetIfNotExistValues(_: Seq[(String, Any)])) + (connector + .mSetIfNotExistValues(_: Seq[(String, Any)])) .expects(keyValues) .returning(result) .once() @@ -138,7 +145,8 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def expire(key: String, duration: Duration, result: Future[Unit] = Future.unit): Future[Unit] = Future.successful { - (connector.expire(_: String, _: Duration)) + (connector + .expire(_: String, _: Duration)) .expects(key, duration) .returning(result) .once() @@ -146,7 +154,8 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def expiresIn(key: String, result: Future[Option[Duration]]): Future[Unit] = Future.successful { - (connector.expiresIn(_: String)) + (connector + .expiresIn(_: String)) .expects(key) .returning(result) .once() @@ -157,7 +166,8 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def remove(keys: Seq[String], result: Future[Unit]): Future[Unit] = Future.successful { - (connector.removeValues(_: Seq[String])) + (connector + .removeValues(_: Seq[String])) .expects(keys) .returning(result) .once() @@ -173,7 +183,8 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def exists(key: String, result: Future[Boolean]): Future[Unit] = Future.successful { - (connector.exists(_: String)) + (connector + .exists(_: String)) .expects(key) .returning(result) .once() @@ -181,7 +192,8 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def increment(key: String, by: Long, result: Future[Long]): Future[Unit] = Future.successful { - (connector.increment(_: String, _: Long)) + (connector + .increment(_: String, _: Long)) .expects(key, by) .returning(result) .once() @@ -189,7 +201,8 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def append(key: String, value: String, result: Future[Long]): Future[Unit] = Future.successful { - (connector.append(_: String, _: String)) + (connector + .append(_: String, _: String)) .expects(key, value) .returning(result) .once() @@ -197,39 +210,44 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def matching(pattern: String, result: Future[Seq[String]]): Future[Unit] = Future.successful { - (connector.matching(_: String)) + (connector + .matching(_: String)) .expects(pattern) .returning(result) .once() } - def listPrepend(key: String, values: Seq[String], result: Future[Long]= Future.successful(5L)): Future[Unit] = + def listPrepend(key: String, values: Seq[String], result: Future[Long] = Future.successful(5L)): Future[Unit] = Future.successful { - (connector.listPrependValues(_: String, _: Seq[Any])) + (connector + .listPrependValues(_: String, _: Seq[Any])) .expects(key, values) .returning(result) .once() } - def listAppend[T:ClassTag](key: String, values: Seq[T], result: Future[Long] = Future.successful(5L)): Future[Unit] = + def listAppend[T](key: String, values: Seq[T], result: Future[Long] = Future.successful(5L)): Future[Unit] = Future.successful { - (connector.listAppendValues(_: String, _: Seq[Any])) + (connector + .listAppendValues(_: String, _: Seq[Any])) .expects(key, values) .returning(result) .once() } - def listSlice[T: ClassTag](key: String, start: Int, end: Int, result: Future[Seq[T]]): Future[Unit] = + def listSlice[T: ClassTag](key: String, start: Long, end: Long, result: Future[Seq[T]]): Future[Unit] = Future.successful { - (connector.listSlice(_: String, _: Int, _: Int)(_: ClassTag[_])) + (connector + .listSlice(_: String, _: Long, _: Long)(_: ClassTag[?])) .expects(key, start, end, implicitly[ClassTag[T]]) .returning(result) .once() } - def listHeadPop[T:ClassTag](key: String, result: Future[Option[T]]): Future[Unit] = + def listHeadPop[T: ClassTag](key: String, result: Future[Option[T]]): Future[Unit] = Future.successful { - (connector.listHeadPop(_: String)(_: ClassTag[_])) + (connector + .listHeadPop(_: String)(_: ClassTag[?])) .expects(key, implicitly[ClassTag[T]]) .returning(result) .once() @@ -237,7 +255,8 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def listSize(key: String, result: Future[Long]): Future[Unit] = Future.successful { - (connector.listSize(_: String)) + (connector + .listSize(_: String)) .expects(key) .returning(result) .once() @@ -245,43 +264,49 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def listInsert(key: String, pivot: String, value: String, result: Future[Option[Long]]): Future[Unit] = Future.successful { - (connector.listInsert(_: String, _: String, _: Any)) + (connector + .listInsert(_: String, _: String, _: Any)) .expects(key, pivot, value) .returning(result) .once() } - def listSetAt(key: String, index: Int, value: String, result: Future[Unit]): Future[Unit] = + def listSetAt(key: String, index: Long, value: String, result: Future[Unit]): Future[Unit] = Future.successful { - (connector.listSetAt(_: String, _: Int, _: Any)) + (connector + .listSetAt(_: String, _: Long, _: Any)) .expects(key, index, value) .returning(result) .once() } - def listRemove(key: String, value: String, count: Int, result: Future[Long]): Future[Unit] = + def listRemove(key: String, value: String, count: Long, result: Future[Long]): Future[Unit] = Future.successful { - (connector.listRemove(_: String, _:Any, _: Int)) + (connector + .listRemove(_: String, _: Any, _: Long)) .expects(key, value, count) .returning(result) .once() } - def listRemoveAt(key: String, index: Int, result: Future[Long]): Future[Unit] = + def listRemoveAt(key: String, index: Long, result: Future[Long]): Future[Unit] = Future.successful { - (connector.listSetAt(_: String, _: Int, _: Any)) + (connector + .listSetAt(_: String, _: Long, _: Any)) .expects(key, index, "play-redis:DELETED") .returning(Future.unit) .once() - (connector.listRemove(_: String, _: Any, _: Int)) + (connector + .listRemove(_: String, _: Any, _: Long)) .expects(key, "play-redis:DELETED", 0) .returning(result) .once() } - def listTrim(key: String, start: Int, end: Int, result: Future[Unit] = Future.unit): Future[Unit] = + def listTrim(key: String, start: Long, end: Long, result: Future[Unit] = Future.unit): Future[Unit] = Future.successful { - (connector.listTrim(_: String, _: Int, _: Int)) + (connector + .listTrim(_: String, _: Long, _: Long)) .expects(key, start, end) .returning(result) .once() @@ -289,7 +314,8 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def setAdd(key: String, values: Seq[String], result: Future[Long] = Future.successful(5L)): Future[Unit] = Future.successful { - (connector.setAddValues(_: String, _: Seq[Any])) + (connector + .setAddValues(_: String, _: Seq[Any])) .expects(key, values) .returning(result) .once() @@ -297,7 +323,8 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def setIsMember(key: String, value: String, result: Future[Boolean]): Future[Unit] = Future.successful { - (connector.setIsMember(_: String, _: Any)) + (connector + .setIsMember(_: String, _: Any)) .expects(key, value) .returning(result) .once() @@ -305,7 +332,8 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def setRemove(key: String, values: Seq[String], result: Future[Long] = Future.successful(1L)): Future[Unit] = Future.successful { - (connector.setRemoveValues(_: String, _: Seq[Any])) + (connector + .setRemoveValues(_: String, _: Seq[Any])) .expects(key, values) .returning(result) .once() @@ -313,7 +341,8 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def setMembers[T: ClassTag](key: String, result: Future[Set[Any]]): Future[Unit] = Future.successful { - (connector.setMembers(_: String)(_: ClassTag[_])) + (connector + .setMembers(_: String)(_: ClassTag[?])) .expects(key, implicitly[ClassTag[T]]) .returning(result) .once() @@ -321,7 +350,8 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def setSize(key: String, result: Future[Long]): Future[Unit] = Future.successful { - (connector.setSize(_: String)) + (connector + .setSize(_: String)) .expects(key) .returning(result) .once() @@ -329,7 +359,8 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def sortedSetAdd(key: String, values: Seq[(Double, String)], result: Future[Long] = Future.successful(1L)): Future[Unit] = Future.successful { - (connector.sortedSetAddValues(_: String, _: Seq[(Double, Any)])) + (connector + .sortedSetAddValues(_: String, _: Seq[(Double, Any)])) .expects(key, values) .returning(result) .once() @@ -337,7 +368,8 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def sortedSetScore(key: String, value: String, result: Future[Option[Double]]): Future[Unit] = Future.successful { - (connector.sortedSetScore(_: String, _: Any)) + (connector + .sortedSetScore(_: String, _: Any)) .expects(key, value) .returning(result) .once() @@ -345,7 +377,8 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def sortedSetRemove(key: String, values: Seq[String], result: Future[Long] = Future.successful(1L)): Future[Unit] = Future.successful { - (connector.sortedSetRemoveValues(_: String, _: Seq[Any])) + (connector + .sortedSetRemoveValues(_: String, _: Seq[Any])) .expects(key, values) .returning(result) .once() @@ -353,7 +386,8 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def sortedSetRange[T: ClassTag](key: String, start: Long, end: Long, result: Future[Seq[String]]): Future[Unit] = Future.successful { - (connector.sortedSetRange(_: String, _: Long, _: Long)(_: ClassTag[_])) + (connector + .sortedSetRange(_: String, _: Long, _: Long)(_: ClassTag[?])) .expects(key, start, end, implicitly[ClassTag[T]]) .returning(result) .once() @@ -361,7 +395,8 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def sortedSetReverseRange[T: ClassTag](key: String, start: Long, end: Long, result: Future[Seq[String]]): Future[Unit] = Future.successful { - (connector.sortedSetReverseRange(_: String, _: Long, _: Long)(_: ClassTag[_])) + (connector + .sortedSetReverseRange(_: String, _: Long, _: Long)(_: ClassTag[?])) .expects(key, start, end, implicitly[ClassTag[T]]) .returning(result) .once() @@ -369,7 +404,8 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def sortedSetSize(key: String, result: Future[Long]): Future[Unit] = Future.successful { - (connector.sortedSetSize(_: String)) + (connector + .sortedSetSize(_: String)) .expects(key) .returning(result) .once() @@ -377,7 +413,8 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def hashSet(key: String, field: String, value: String, result: Future[Boolean]): Future[Unit] = Future.successful { - (connector.hashSet(_: String, _: String, _: Any)) + (connector + .hashSet(_: String, _: String, _: Any)) .expects(key, field, value) .returning(result) .once() @@ -385,7 +422,8 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def hashGet[T: ClassTag](key: String, field: String, result: Future[Option[T]]): Future[Unit] = Future.successful { - (connector.hashGetField(_: String, _: String)(_: ClassTag[_])) + (connector + .hashGetField(_: String, _: String)(_: ClassTag[?])) .expects(key, field, implicitly[ClassTag[T]]) .returning(result) .once() @@ -393,7 +431,8 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def hashGet[T: ClassTag](key: String, fields: Seq[String], result: Future[Seq[Option[T]]]): Future[Unit] = Future.successful { - (connector.hashGetFields(_: String, _: Seq[String])(_: ClassTag[_])) + (connector + .hashGetFields(_: String, _: Seq[String])(_: ClassTag[?])) .expects(key, fields, implicitly[ClassTag[T]]) .returning(result) .once() @@ -401,7 +440,8 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def hashExists(key: String, field: String, result: Future[Boolean]): Future[Unit] = Future.successful { - (connector.hashExists(_: String, _: String)) + (connector + .hashExists(_: String, _: String)) .expects(key, field) .returning(result) .once() @@ -409,7 +449,8 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def hashRemove(key: String, fields: Seq[String], result: Future[Long] = Future.successful(1L)): Future[Unit] = Future.successful { - (connector.hashRemoveValues(_: String, _: Seq[String])) + (connector + .hashRemoveValues(_: String, _: Seq[String])) .expects(key, fields) .returning(result) .once() @@ -417,7 +458,8 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def hashIncrement(key: String, field: String, by: Long, result: Future[Long]): Future[Unit] = Future.successful { - (connector.hashIncrement(_: String, _: String, _: Long)) + (connector + .hashIncrement(_: String, _: String, _: Long)) .expects(key, field, by) .returning(result) .once() @@ -425,7 +467,8 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def hashGetAll[T: ClassTag](key: String, result: Future[Map[String, T]]): Future[Unit] = Future.successful { - (connector.hashGetAllValues(_: String)(_: ClassTag[_])) + (connector + .hashGetAllValues(_: String)(_: ClassTag[?])) .expects(key, implicitly[ClassTag[T]]) .returning(result) .once() @@ -433,7 +476,8 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def hashKeys(key: String, result: Future[Set[String]]): Future[Unit] = Future.successful { - (connector.hashKeys(_: String)) + (connector + .hashKeys(_: String)) .expects(key) .returning(result) .once() @@ -441,7 +485,8 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def hashValues[T: ClassTag](key: String, result: Future[Set[T]]): Future[Unit] = Future.successful { - (connector.hashValues[T](_: String)(_: ClassTag[T])) + (connector + .hashValues[T](_: String)(_: ClassTag[T])) .expects(key, implicitly[ClassTag[T]]) .returning(result) .once() @@ -449,10 +494,13 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase => def hashSize(key: String, result: Future[Long]): Future[Unit] = Future.successful { - (connector.hashSize(_: String)) + (connector + .hashSize(_: String)) .expects(key) .returning(result) .once() } + } -} \ No newline at end of file + +} diff --git a/src/test/scala/play/api/cache/redis/impl/RedisJavaListSpec.scala b/src/test/scala/play/api/cache/redis/impl/RedisJavaListSpec.scala index 1ad2cb89..aa4f25fe 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisJavaListSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisJavaListSpec.scala @@ -201,10 +201,10 @@ class RedisJavaListSpec extends AsyncUnitSpec with RedisListJavaMock with RedisR private def test( name: String, - policy: RecoveryPolicy = recoveryPolicy.default + policy: RecoveryPolicy = recoveryPolicy.default, )( - f: (AsyncRedisList[String], RedisListMock) => Future[Assertion] - ): Unit = { + f: (AsyncRedisList[String], RedisListMock) => Future[Assertion], + ): Unit = name in { implicit val runtime: RedisRuntime = redisRuntime( invocationPolicy = LazyInvocation, @@ -220,5 +220,5 @@ class RedisJavaListSpec extends AsyncUnitSpec with RedisListJavaMock with RedisR f(list, internal) } - } + } diff --git a/src/test/scala/play/api/cache/redis/impl/RedisJavaMapSpec.scala b/src/test/scala/play/api/cache/redis/impl/RedisJavaMapSpec.scala index c860e31e..a7c60ce2 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisJavaMapSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisJavaMapSpec.scala @@ -75,10 +75,10 @@ class RedisJavaMapSpec extends AsyncUnitSpec with RedisMapJavaMock with RedisRun private def test( name: String, - policy: RecoveryPolicy = recoveryPolicy.default + policy: RecoveryPolicy = recoveryPolicy.default, )( - f: (AsyncRedisMap[String], RedisMapMock) => Future[Assertion] - ): Unit = { + f: (AsyncRedisMap[String], RedisMapMock) => Future[Assertion], + ): Unit = name in { implicit val runtime: RedisRuntime = redisRuntime( invocationPolicy = LazyInvocation, @@ -86,8 +86,8 @@ class RedisJavaMapSpec extends AsyncUnitSpec with RedisMapJavaMock with RedisRun ) val internal: RedisMapMock = mock[RedisMapMock] val map: AsyncRedisMap[String] = new RedisMapJavaImpl(internal) - + f(map, internal) } - } + } diff --git a/src/test/scala/play/api/cache/redis/impl/RedisJavaSetSpec.scala b/src/test/scala/play/api/cache/redis/impl/RedisJavaSetSpec.scala index bc836b44..5569e31a 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisJavaSetSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisJavaSetSpec.scala @@ -2,11 +2,10 @@ package play.api.cache.redis.impl import play.api.cache.redis._ import play.api.cache.redis.test._ -import play.cache.redis.{AsyncRedisList, AsyncRedisSet} +import play.cache.redis.AsyncRedisSet import scala.concurrent.Future import scala.jdk.CollectionConverters._ -import scala.jdk.OptionConverters._ class RedisJavaSetSpec extends AsyncUnitSpec with RedisSetJavaMock with RedisRuntimeMock { @@ -17,7 +16,6 @@ class RedisJavaSetSpec extends AsyncUnitSpec with RedisSetJavaMock with RedisRun } yield Passed } - test("contains") { (set, internal) => for { _ <- internal.expect.contains(cacheKey, result = true) @@ -41,10 +39,10 @@ class RedisJavaSetSpec extends AsyncUnitSpec with RedisSetJavaMock with RedisRun private def test( name: String, - policy: RecoveryPolicy = recoveryPolicy.default + policy: RecoveryPolicy = recoveryPolicy.default, )( - f: (AsyncRedisSet[String], RedisSetMock) => Future[Assertion] - ): Unit = { + f: (AsyncRedisSet[String], RedisSetMock) => Future[Assertion], + ): Unit = name in { implicit val runtime: RedisRuntime = redisRuntime( invocationPolicy = LazyInvocation, @@ -55,5 +53,5 @@ class RedisJavaSetSpec extends AsyncUnitSpec with RedisSetJavaMock with RedisRun f(set, internal) } - } + } diff --git a/src/test/scala/play/api/cache/redis/impl/RedisListJavaMock.scala b/src/test/scala/play/api/cache/redis/impl/RedisListJavaMock.scala index 55de8dc6..a86b5976 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisListJavaMock.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisListJavaMock.scala @@ -9,30 +9,34 @@ private[impl] trait RedisListJavaMock { this: AsyncMockFactoryBase => protected[impl] trait RedisListMock extends RedisList[String, Future] - final protected implicit class RedisListOps(list: RedisListMock) { + implicit final protected class RedisListOps(list: RedisListMock) { + def expect: RedisListExpectation = new RedisListExpectation(list) + } - protected final class RedisListExpectation(list: RedisListMock) { + final protected class RedisListExpectation(list: RedisListMock) { - def apply(index: Int, value: Option[String]): Future[Unit] = + def apply(index: Long, value: Option[String]): Future[Unit] = Future.successful { - (list.apply(_: Int)) + (list + .apply(_: Long)) .expects(index) .returning( value.fold[Future[String]]( - Future.failed(new NoSuchElementException()) + Future.failed(new NoSuchElementException()), )( - Future.successful - ) + Future.successful, + ), ) .once() } - def get(index: Int, value: Option[String]): Future[Unit] = + def get(index: Long, value: Option[String]): Future[Unit] = Future.successful { - (list.get(_: Int)) + (list + .get(_: Long)) .expects(index) .returning(Future.successful(value)) .once() @@ -40,7 +44,8 @@ private[impl] trait RedisListJavaMock { this: AsyncMockFactoryBase => def prepend(value: String): Future[Unit] = Future.successful { - (list.prepend(_: String)) + (list + .prepend(_: String)) .expects(value) .returning(Future.successful(list)) .once() @@ -48,7 +53,8 @@ private[impl] trait RedisListJavaMock { this: AsyncMockFactoryBase => def append(value: String): Future[Unit] = Future.successful { - (list.append(_: String)) + (list + .append(_: String)) .expects(value) .returning(Future.successful(list)) .once() @@ -64,31 +70,35 @@ private[impl] trait RedisListJavaMock { this: AsyncMockFactoryBase => def insertBefore(pivot: String, value: String, newSize: Option[Long]): Future[Unit] = Future.successful { - (list.insertBefore(_: String, _: String)) + (list + .insertBefore(_: String, _: String)) .expects(pivot, value) .returning(Future.successful(newSize)) .once() } - def set(index: Int, value: String): Future[Unit] = + def set(index: Long, value: String): Future[Unit] = Future.successful { - (list.set(_: Int, _: String)) + (list + .set(_: Long, _: String)) .expects(index, value) .returning(Future.successful(list)) .once() } - def remove(value: String, count: Int = 1): Future[Unit] = + def remove(value: String, count: Long = 1): Future[Unit] = Future.successful { - (list.remove(_: String, _: Int)) + (list + .remove(_: String, _: Long)) .expects(value, count) .returning(Future.successful(list)) .once() } - def removeAt(index: Int): Future[Unit] = + def removeAt(index: Long): Future[Unit] = Future.successful { - (list.removeAt(_: Int)) + (list + .removeAt(_: Long)) .expects(index) .returning(Future.successful(list)) .once() @@ -99,18 +109,21 @@ private[impl] trait RedisListJavaMock { this: AsyncMockFactoryBase => def modify: RedisListModificationExpectation = new RedisListModificationExpectation(list) } - protected final class RedisListViewExpectation(list: RedisListMock) { + final protected class RedisListViewExpectation(list: RedisListMock) { - def slice(from: Int, to: Int, value: List[String]): Future[Unit] = + def slice(from: Long, to: Long, value: List[String]): Future[Unit] = Future.successful { - (list.view.slice(_: Int, _: Int)) + (list + .view + .slice(_: Long, _: Long)) .expects(from, to) .returning(Future.successful(value)) .once() } + } - protected final class RedisListModificationExpectation(list: RedisListMock) { + final protected class RedisListModificationExpectation(list: RedisListMock) { def clear(): Future[Unit] = Future.successful { @@ -120,12 +133,16 @@ private[impl] trait RedisListJavaMock { this: AsyncMockFactoryBase => .once() } - def slice(from: Int, to: Int): Future[Unit] = + def slice(from: Long, to: Long): Future[Unit] = Future.successful { - (list.modify.slice(_: Int, _: Int)) + (list + .modify + .slice(_: Long, _: Long)) .expects(from, to) .returning(Future.successful(list.modify)) .once() } + } -} \ No newline at end of file + +} diff --git a/src/test/scala/play/api/cache/redis/impl/RedisListSpec.scala b/src/test/scala/play/api/cache/redis/impl/RedisListSpec.scala index 8e6b4182..dad3e597 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisListSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisListSpec.scala @@ -6,7 +6,7 @@ import play.api.cache.redis.test._ import scala.concurrent.Future -class RedisListSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConnectorMock with ImplicitFutureMaterialization { +class RedisListSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConnectorMock with ImplicitFutureMaterialization { test("prepend (all variants)") { (list, connector) => for { @@ -28,9 +28,9 @@ class RedisListSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConne test("append (all variants)") { (list, connector) => for { - _ <- connector.expect.listAppend(otherKey,Seq( cacheValue)) - _ <- connector.expect.listAppend(otherKey,Seq( cacheValue)) - _ <- connector.expect.listAppend(otherKey,Seq( cacheValue, cacheValue)) + _ <- connector.expect.listAppend(otherKey, Seq(cacheValue)) + _ <- connector.expect.listAppend(otherKey, Seq(cacheValue)) + _ <- connector.expect.listAppend(otherKey, Seq(cacheValue, cacheValue)) _ <- list.append(cacheValue).assertingEqual(list) _ <- (list :+ cacheValue).assertingEqual(list) _ <- (list :++ Seq(cacheValue, cacheValue)).assertingEqual(list) @@ -44,12 +44,12 @@ class RedisListSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConne } yield Passed } - test("get (miss)") { (list, connector) => - for { - _ <- connector.expect.listSlice(otherKey, 5, 5, Seq(cacheValue)) - _ <- list.get(5).assertingEqual(Some(cacheValue)) - } yield Passed - } + test("get (miss)") { (list, connector) => + for { + _ <- connector.expect.listSlice(otherKey, 5, 5, Seq(cacheValue)) + _ <- list.get(5).assertingEqual(Some(cacheValue)) + } yield Passed + } test("get (hit)") { (list, connector) => for { @@ -336,10 +336,10 @@ class RedisListSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConne private def test( name: String, - policy: RecoveryPolicy = recoveryPolicy.default + policy: RecoveryPolicy = recoveryPolicy.default, )( - f: (RedisList[String, AsynchronousResult], RedisConnectorMock) => Future[Assertion] - ): Unit = { + f: (RedisList[String, AsynchronousResult], RedisConnectorMock) => Future[Assertion], + ): Unit = name in { implicit val runtime: RedisRuntime = redisRuntime( invocationPolicy = LazyInvocation, @@ -350,5 +350,5 @@ class RedisListSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConne val list: RedisList[String, AsynchronousResult] = new RedisListImpl[String, AsynchronousResult](otherKey, connector) f(list, connector) } - } + } diff --git a/src/test/scala/play/api/cache/redis/impl/RedisMapJavaMock.scala b/src/test/scala/play/api/cache/redis/impl/RedisMapJavaMock.scala index 4e186387..c15095cc 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisMapJavaMock.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisMapJavaMock.scala @@ -9,22 +9,25 @@ private[impl] trait RedisMapJavaMock { this: AsyncMockFactoryBase => protected[impl] trait RedisMapMock extends RedisMap[String, Future] { - override final def remove(field: String*): Future[RedisMap[String, Future]] = + final override def remove(field: String*): Future[RedisMap[String, Future]] = removeValues(field) def removeValues(field: Seq[String]): Future[RedisMap[String, Future]] } - final protected implicit class RedisMapOps(map: RedisMapMock) { + implicit final protected class RedisMapOps(map: RedisMapMock) { + def expect: RedisMapExpectation = new RedisMapExpectation(map) + } - protected final class RedisMapExpectation(map: RedisMapMock) { + final protected class RedisMapExpectation(map: RedisMapMock) { def add(key: String, value: String): Future[Unit] = Future.successful { - (map.add(_: String, _: String)) + (map + .add(_: String, _: String)) .expects(key, value) .returning(Future.successful(map)) .once() @@ -32,7 +35,8 @@ private[impl] trait RedisMapJavaMock { this: AsyncMockFactoryBase => def get(key: String, value: Option[String]): Future[Unit] = Future.successful { - (map.get(_: String)) + (map + .get(_: String)) .expects(key) .returning(Future.successful(value)) .once() @@ -40,7 +44,8 @@ private[impl] trait RedisMapJavaMock { this: AsyncMockFactoryBase => def contains(key: String, result: Boolean): Future[Unit] = Future.successful { - (map.contains(_: String)) + (map + .contains(_: String)) .expects(key) .returning(Future.successful(result)) .once() @@ -48,7 +53,8 @@ private[impl] trait RedisMapJavaMock { this: AsyncMockFactoryBase => def remove(key: String*): Future[Unit] = Future.successful { - (map.removeValues(_: Seq[String])) + (map + .removeValues(_: Seq[String])) .expects(key) .returning(Future.successful(map)) .once() @@ -56,7 +62,8 @@ private[impl] trait RedisMapJavaMock { this: AsyncMockFactoryBase => def increment(key: String, by: Long, result: Long): Future[Unit] = Future.successful { - (map.increment(_: String, _: Long)) + (map + .increment(_: String, _: Long)) .expects(key, by) .returning(Future.successful(result)) .once() @@ -85,5 +92,7 @@ private[impl] trait RedisMapJavaMock { this: AsyncMockFactoryBase => .returning(Future.successful(values.toSet)) .once() } + } -} \ No newline at end of file + +} diff --git a/src/test/scala/play/api/cache/redis/impl/RedisMapSpec.scala b/src/test/scala/play/api/cache/redis/impl/RedisMapSpec.scala index fdbbc50c..4d658b9e 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisMapSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisMapSpec.scala @@ -6,7 +6,7 @@ import play.api.cache.redis.test._ import scala.concurrent.Future -class RedisMapSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConnectorMock with ImplicitFutureMaterialization { +class RedisMapSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConnectorMock with ImplicitFutureMaterialization { test("set") { (map, connector) => for { @@ -187,10 +187,10 @@ class RedisMapSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConnec private def test( name: String, - policy: RecoveryPolicy = recoveryPolicy.default + policy: RecoveryPolicy = recoveryPolicy.default, )( - f: (RedisMap[String, AsynchronousResult], RedisConnectorMock) => Future[Assertion] - ): Unit = { + f: (RedisMap[String, AsynchronousResult], RedisConnectorMock) => Future[Assertion], + ): Unit = name in { implicit val runtime: RedisRuntime = redisRuntime( invocationPolicy = LazyInvocation, @@ -201,5 +201,5 @@ class RedisMapSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConnec val map: RedisMap[String, AsynchronousResult] = new RedisMapImpl[String, AsynchronousResult](cacheKey, connector) f(map, connector) } - } - } + +} diff --git a/src/test/scala/play/api/cache/redis/impl/RedisPrefixSpec.scala b/src/test/scala/play/api/cache/redis/impl/RedisPrefixSpec.scala index e0ecc5cb..599f54e6 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisPrefixSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisPrefixSpec.scala @@ -29,16 +29,16 @@ class RedisPrefixSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConn } private def test( - name: String, - prefix: RedisPrefix - )( - f: (RedisConnectorMock, AsyncRedis) => Future[Assertion] - ): Unit = { + name: String, + prefix: RedisPrefix, + )( + f: (RedisConnectorMock, AsyncRedis) => Future[Assertion], + ): Unit = name in { implicit val runtime: RedisRuntime = redisRuntime(prefix = prefix) val connector = mock[RedisConnectorMock] val cache: AsyncRedis = new AsyncRedisImpl(connector) f(connector, cache) } - } + } diff --git a/src/test/scala/play/api/cache/redis/impl/RedisRuntimeMock.scala b/src/test/scala/play/api/cache/redis/impl/RedisRuntimeMock.scala index 0bc693e9..4f44ac39 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisRuntimeMock.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisRuntimeMock.scala @@ -4,19 +4,21 @@ import akka.util.Timeout import org.scalamock.scalatest.AsyncMockFactoryBase import play.api.cache.redis.{FailThrough, RecoverWithDefault, RecoveryPolicy, RedisException} -import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.duration._ +import scala.concurrent.{ExecutionContext, Future} private[impl] trait RedisRuntimeMock { outer: AsyncMockFactoryBase => protected object recoveryPolicy { private class RerunPolicy extends RecoveryPolicy { + override def recoverFrom[T]( - rerun: => Future[T], - default: => Future[T], - failure: RedisException - ): Future[T] = rerun + rerun: => Future[T], + default: => Future[T], + failure: RedisException, + ): Future[T] = rerun + } val failThrough: RecoveryPolicy = new FailThrough {} @@ -38,4 +40,5 @@ private[impl] trait RedisRuntimeMock { outer: AsyncMockFactoryBase => (() => runtime.timeout).expects().returns(Timeout(timeout)).anyNumberOfTimes() runtime } + } diff --git a/src/test/scala/play/api/cache/redis/impl/RedisRuntimeSpec.scala b/src/test/scala/play/api/cache/redis/impl/RedisRuntimeSpec.scala index bd2aef9c..8a902088 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisRuntimeSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisRuntimeSpec.scala @@ -9,22 +9,22 @@ import play.api.cache.redis.test.UnitSpec class RedisRuntimeSpec extends UnitSpec { import RedisRuntime._ - private implicit val recoveryResolver: RecoveryPolicyResolver = + implicit private val recoveryResolver: RecoveryPolicyResolver = new RecoveryPolicyResolverImpl - private implicit val system: ActorSystem = ActorSystem("test") + implicit private val system: ActorSystem = ActorSystem("test") "be build from config (A)" in { val instance = RedisStandalone( name = "standalone", host = RedisHost(localhost, defaultPort), - settings = defaultsSettings + settings = defaultsSettings, ) val runtime = RedisRuntime( instance = instance, recovery = "log-and-fail", invocation = "eager", - prefix = None + prefix = None, ) runtime.timeout mustEqual Timeout(instance.timeout.sync) runtime.policy mustBe a[LogAndFailPolicy] @@ -32,36 +32,37 @@ class RedisRuntimeSpec extends UnitSpec { runtime.prefix mustEqual RedisEmptyPrefix } - "be build from config (B)" in { - val instance = RedisStandalone( - name = "standalone", - host = RedisHost(localhost, defaultPort), - settings = defaultsSettings - ) - val runtime = RedisRuntime( + "be build from config (B)" in { + val instance = RedisStandalone( + name = "standalone", + host = RedisHost(localhost, defaultPort), + settings = defaultsSettings, + ) + val runtime = RedisRuntime( + instance = instance, + recovery = "log-and-default", + invocation = "lazy", + prefix = Some("prefix"), + ) + runtime.policy mustBe a[LogAndDefaultPolicy] + runtime.invocation mustEqual LazyInvocation + runtime.prefix mustEqual new RedisPrefixImpl("prefix") + } + + "be build from config (C)" in { + val instance = RedisStandalone( + name = "standalone", + host = RedisHost(localhost, defaultPort), + settings = defaultsSettings, + ) + assertThrows[IllegalArgumentException] { + RedisRuntime( instance = instance, recovery = "log-and-default", - invocation = "lazy", - prefix = Some("prefix") + invocation = "other", + prefix = Some("prefix"), ) - runtime.policy mustBe a[LogAndDefaultPolicy] - runtime.invocation mustEqual LazyInvocation - runtime.prefix mustEqual new RedisPrefixImpl("prefix") } + } - "be build from config (C)" in { - val instance = RedisStandalone( - name = "standalone", - host = RedisHost(localhost, defaultPort), - settings = defaultsSettings - ) - assertThrows[IllegalArgumentException] { - RedisRuntime( - instance = instance, - recovery = "log-and-default", - invocation = "other", - prefix = Some("prefix") - ) - } - } } diff --git a/src/test/scala/play/api/cache/redis/impl/RedisSetJavaMock.scala b/src/test/scala/play/api/cache/redis/impl/RedisSetJavaMock.scala index 728e69aa..e524966d 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisSetJavaMock.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisSetJavaMock.scala @@ -9,27 +9,30 @@ private[impl] trait RedisSetJavaMock { this: AsyncMockFactoryBase => protected[impl] trait RedisSetMock extends RedisSet[String, Future] { - override final def add(values: String*): Future[RedisSet[String, Future]] = + final override def add(values: String*): Future[RedisSet[String, Future]] = addValues(values) def addValues(value: Seq[String]): Future[RedisSet[String, Future]] - override final def remove(values: String*): Future[RedisSet[String, Future]] = + final override def remove(values: String*): Future[RedisSet[String, Future]] = removeValues(values) def removeValues(value: Seq[String]): Future[RedisSet[String, Future]] } - final protected implicit class RedisSetOps(set: RedisSetMock) { + implicit final protected class RedisSetOps(set: RedisSetMock) { + def expect: RedisSetExpectation = new RedisSetExpectation(set) + } - protected final class RedisSetExpectation(set: RedisSetMock) { + final protected class RedisSetExpectation(set: RedisSetMock) { def add(value: String*): Future[Unit] = Future.successful { - (set.addValues(_: Seq[String])) + (set + .addValues(_: Seq[String])) .expects(value) .returning(Future.successful(set)) .once() @@ -37,7 +40,8 @@ private[impl] trait RedisSetJavaMock { this: AsyncMockFactoryBase => def contains(value: String, result: Boolean): Future[Unit] = Future.successful { - (set.contains(_: String)) + (set + .contains(_: String)) .expects(value) .returning(Future.successful(result)) .once() @@ -45,7 +49,8 @@ private[impl] trait RedisSetJavaMock { this: AsyncMockFactoryBase => def remove(value: String*): Future[Unit] = Future.successful { - (set.removeValues(_: Seq[String])) + (set + .removeValues(_: Seq[String])) .expects(value) .returning(Future.successful(set)) .once() @@ -58,5 +63,7 @@ private[impl] trait RedisSetJavaMock { this: AsyncMockFactoryBase => .returning(Future.successful(values.toSet)) .once() } + } -} \ No newline at end of file + +} diff --git a/src/test/scala/play/api/cache/redis/impl/RedisSetSpec.scala b/src/test/scala/play/api/cache/redis/impl/RedisSetSpec.scala index add20ac6..c732141b 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisSetSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisSetSpec.scala @@ -6,7 +6,7 @@ import play.api.cache.redis.test._ import scala.concurrent.Future -class RedisSetSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConnectorMock with ImplicitFutureMaterialization { +class RedisSetSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConnectorMock with ImplicitFutureMaterialization { test("add") { (set, connector) => for { @@ -113,10 +113,10 @@ class RedisSetSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConnec private def test( name: String, - policy: RecoveryPolicy = recoveryPolicy.default + policy: RecoveryPolicy = recoveryPolicy.default, )( - f: (RedisSet[String, AsynchronousResult], RedisConnectorMock) => Future[Assertion] - ): Unit = { + f: (RedisSet[String, AsynchronousResult], RedisConnectorMock) => Future[Assertion], + ): Unit = name in { implicit val runtime: RedisRuntime = redisRuntime( invocationPolicy = LazyInvocation, @@ -127,5 +127,5 @@ class RedisSetSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConnec val set: RedisSet[String, AsynchronousResult] = new RedisSetImpl[String, AsynchronousResult](cacheKey, connector) f(set, connector) } - } + } diff --git a/src/test/scala/play/api/cache/redis/impl/RedisSortedSetSpec.scala b/src/test/scala/play/api/cache/redis/impl/RedisSortedSetSpec.scala index 22cdbdb7..ae0c82e2 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisSortedSetSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisSortedSetSpec.scala @@ -29,7 +29,7 @@ class RedisSortedSetSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisC test("contains (hit)") { (set, connector) => for { - _ <- connector.expect.sortedSetScore(cacheKey, otherValue, result = Some(1D)) + _ <- connector.expect.sortedSetScore(cacheKey, otherValue, result = Some(1d)) _ <- set.contains(otherValue).assertingEqual(true) } yield Passed } @@ -116,10 +116,10 @@ class RedisSortedSetSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisC private def test( name: String, - policy: RecoveryPolicy = recoveryPolicy.default + policy: RecoveryPolicy = recoveryPolicy.default, )( - f: (RedisSortedSet[String, AsynchronousResult], RedisConnectorMock) => Future[Assertion] - ): Unit = { + f: (RedisSortedSet[String, AsynchronousResult], RedisConnectorMock) => Future[Assertion], + ): Unit = name in { implicit val runtime: RedisRuntime = redisRuntime( invocationPolicy = LazyInvocation, @@ -130,5 +130,5 @@ class RedisSortedSetSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisC val set: RedisSortedSet[String, AsynchronousResult] = new RedisSortedSetImpl[String, AsynchronousResult](cacheKey, connector) f(set, connector) } - } - } + +} diff --git a/src/test/scala/play/api/cache/redis/impl/SyncRedisSpec.scala b/src/test/scala/play/api/cache/redis/impl/SyncRedisSpec.scala index abc7109b..c103ec7f 100644 --- a/src/test/scala/play/api/cache/redis/impl/SyncRedisSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/SyncRedisSpec.scala @@ -5,15 +5,15 @@ import play.api.cache.redis.test._ import scala.concurrent.Future -class SyncRedisSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConnectorMock with ImplicitFutureMaterialization { +class SyncRedisSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConnectorMock with ImplicitFutureMaterialization { import Helpers._ test("get or else (hit)") { (cache, connector) => for { _ <- connector.expect.get[String](cacheKey, result = Some(cacheValue)) orElse = probe.orElse.const(otherValue) - _ = cache.getOrElse(cacheKey)(orElse.execute()) mustEqual cacheValue - _ = orElse.calls mustEqual 0 + _ = cache.getOrElse(cacheKey)(orElse.execute()) mustEqual cacheValue + _ = orElse.calls mustEqual 0 } yield Passed } @@ -22,8 +22,8 @@ class SyncRedisSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConne _ <- connector.expect.get[String](cacheKey, result = None) _ <- connector.expect.set(cacheKey, cacheValue, result = true) orElse = probe.orElse.const(cacheValue) - _ = cache.getOrElse(cacheKey)(orElse.execute()) mustEqual cacheValue - _ = orElse.calls mustEqual 1 + _ = cache.getOrElse(cacheKey)(orElse.execute()) mustEqual cacheValue + _ = orElse.calls mustEqual 1 } yield Passed } @@ -32,8 +32,8 @@ class SyncRedisSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConne _ <- connector.expect.get[String](cacheKey, result = failure) _ <- connector.expect.set(cacheKey, cacheValue, result = true) orElse = probe.orElse.const(cacheValue) - _ = cache.getOrElse(cacheKey)(orElse.execute()) mustEqual cacheValue - _ = orElse.calls mustEqual 1 + _ = cache.getOrElse(cacheKey)(orElse.execute()) mustEqual cacheValue + _ = orElse.calls mustEqual 1 } yield Passed } @@ -42,8 +42,8 @@ class SyncRedisSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConne _ <- connector.expect.get[String](cacheKey, result = None) _ <- connector.expect.set(cacheKey, cacheValue, result = failure) orElse = probe.orElse.const(cacheValue) - _ = cache.getOrElse(cacheKey)(orElse.execute()) mustEqual cacheValue - _ = orElse.calls mustEqual 1 + _ = cache.getOrElse(cacheKey)(orElse.execute()) mustEqual cacheValue + _ = orElse.calls mustEqual 1 } yield Passed } @@ -52,8 +52,8 @@ class SyncRedisSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConne _ <- connector.expect.get[String](cacheKey, result = failure) _ <- connector.expect.set(cacheKey, cacheValue, result = true) orElse = probe.orElse.const(cacheValue) - _ = cache.getOrElse(cacheKey)(orElse.execute()) mustEqual cacheValue - _ = orElse.calls mustEqual 1 + _ = cache.getOrElse(cacheKey)(orElse.execute()) mustEqual cacheValue + _ = orElse.calls mustEqual 1 } yield Passed } @@ -62,8 +62,8 @@ class SyncRedisSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConne _ <- connector.expect.get[String](s"the-prefix:$cacheKey", result = None) _ <- connector.expect.set(s"the-prefix:$cacheKey", cacheValue, result = true) orElse = probe.orElse.const(cacheValue) - _ = cache.getOrElse(cacheKey)(orElse.execute()) mustEqual cacheValue - _ = orElse.calls mustEqual 1 + _ = cache.getOrElse(cacheKey)(orElse.execute()) mustEqual cacheValue + _ = orElse.calls mustEqual 1 } yield Passed } @@ -72,8 +72,8 @@ class SyncRedisSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConne policy: RecoveryPolicy = recoveryPolicy.default, prefix: Option[String] = None, )( - f: (RedisCache[SynchronousResult], RedisConnectorMock) => Future[Assertion] - ): Unit = { + f: (RedisCache[SynchronousResult], RedisConnectorMock) => Future[Assertion], + ): Unit = name in { implicit val runtime: RedisRuntime = redisRuntime( invocationPolicy = LazyInvocation, @@ -84,5 +84,5 @@ class SyncRedisSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConne val cache: RedisCache[SynchronousResult] = new SyncRedis(connector) f(cache, connector) } - } - } + +} diff --git a/src/test/scala/play/api/cache/redis/test/BaseSpec.scala b/src/test/scala/play/api/cache/redis/test/BaseSpec.scala index aef52b13..00d7e189 100644 --- a/src/test/scala/play/api/cache/redis/test/BaseSpec.scala +++ b/src/test/scala/play/api/cache/redis/test/BaseSpec.scala @@ -3,53 +3,52 @@ package play.api.cache.redis.test import akka.Done import org.scalactic.source.Position import org.scalamock.scalatest.AsyncMockFactory +import org.scalatest._ import org.scalatest.matchers.must.Matchers import org.scalatest.wordspec.{AnyWordSpecLike, AsyncWordSpecLike} -import org.scalatest._ import play.api.cache.redis.RedisException import play.api.cache.redis.configuration._ import java.util.concurrent.{CompletionStage, TimeoutException} import scala.concurrent.duration._ import scala.concurrent.{Await, ExecutionContext, Future} -import scala.language.implicitConversions import scala.reflect.ClassTag import scala.util.{Failure, Success, Try} trait DefaultValues { - protected final val defaultCacheName: String = "play" - protected final val localhost = "localhost" - protected final val defaultPort: Int = 6379 - - val defaultsSettings: RedisSettingsTest = - RedisSettingsTest( - invocationContext = "akka.actor.default-dispatcher", - invocationPolicy = "lazy", - timeout = RedisTimeouts(1.second, None, Some(500.millis)), - recovery = "log-and-default", - source = "standalone" - ) + final protected val defaultCacheName: String = "play" + final protected val localhost = "localhost" + final protected val defaultPort: Int = 6379 + + val defaultsSettings: RedisSettingsTest = + RedisSettingsTest( + invocationContext = "akka.actor.default-dispatcher", + invocationPolicy = "lazy", + timeout = RedisTimeouts(1.second, None, Some(500.millis)), + recovery = "log-and-default", + source = "standalone", + ) - protected final val cacheKey: String = "cache-key" - protected final val cacheValue: String = "cache-value" - protected final val otherKey: String = "other-key" - protected final val otherValue: String = "other-value" - protected final val field: String = "field" + final protected val cacheKey: String = "cache-key" + final protected val cacheValue: String = "cache-value" + final protected val otherKey: String = "other-key" + final protected val otherValue: String = "other-value" + final protected val field: String = "field" - protected final val cacheExpiration: FiniteDuration = 1.minute + final protected val cacheExpiration: FiniteDuration = 1.minute - protected final val failure: RedisException = SimulatedException.asRedis + final protected val failure: RedisException = SimulatedException.asRedis } trait ImplicitOptionMaterialization { - protected implicit def implicitlyAny2Some[T](value: T): Option[T] = Some(value) + implicit protected def implicitlyAny2Some[T](value: T): Option[T] = Some(value) } trait ImplicitFutureMaterialization { - protected implicit def implicitlyThrowable2Future[T](cause: Throwable): Future[T] = Future.failed(cause) - protected implicit def implicitlyAny2Future[T](value: T): Future[T] = Future.successful(value) + implicit protected def implicitlyThrowable2Future[T](cause: Throwable): Future[T] = Future.failed(cause) + implicit protected def implicitlyAny2Future[T](value: T): Future[T] = Future.successful(value) } trait TimeLimitedSpec extends AsyncTestSuiteMixin with AsyncUtilities { @@ -72,25 +71,27 @@ trait TimeLimitedSpec extends AsyncTestSuiteMixin with AsyncUtilities { val result: Future[Outcome] = Future.firstCompletedOf( Seq( Future.after(testTimeout, ()).map(_ => fail(s"Test didn't finish within $testTimeout.")), - test.toFuture - ) + test.toFuture, + ), ) new FutureOutcome(result) } - abstract override def withFixture(test: NoArgAsyncTest): FutureOutcome = { + abstract override def withFixture(test: NoArgAsyncTest): FutureOutcome = super.withFixture(new TimeLimitedTest(test)) - } + } trait AsyncUtilities { this: AsyncTestSuite => implicit class FutureAsyncUtilities(future: Future.type) { + def after[T](duration: FiniteDuration, value: => T): Future[T] = Future(Await.result(Future.never, duration)).recover(_ => value) def waitFor(duration: FiniteDuration): Future[Unit] = after(duration, ()) + } } @@ -118,10 +119,10 @@ trait FutureAssertions extends AsyncUtilities { this: BaseSpec => def assertingTry(f: Try[T] => Assertion): Future[Assertion] = future.map(Success.apply).recover { case ex => Failure(ex) }.map(f) - def assertingFailure[Cause <: Throwable : ClassTag]: Future[Assertion] = + def assertingFailure[Cause <: Throwable: ClassTag]: Future[Assertion] = future.map(value => fail(s"Expected exception but got $value")).recover { case ex => ex mustBe a[Cause] } - def assertingFailure[Cause <: Throwable : ClassTag, InnerCause <: Throwable : ClassTag]: Future[Assertion] = + def assertingFailure[Cause <: Throwable: ClassTag, InnerCause <: Throwable: ClassTag]: Future[Assertion] = future.map(value => fail(s"Expected exception but got $value")).recover { case ex => ex mustBe a[Cause] ex.getCause mustBe a[InnerCause] @@ -131,21 +132,24 @@ trait FutureAssertions extends AsyncUtilities { this: BaseSpec => future.map(value => fail(s"Expected exception but got $value")).recover { case ex => ex mustEqual cause } def assertingSuccess: Future[Assertion] = - future.recover(cause => fail(s"Got unexpected exception", cause)).map(_ => Passed) - - def assertTimeout(timeout: FiniteDuration): Future[Assertion] = { - Future.firstCompletedOf( - Seq( - Future.after(timeout, throw new TimeoutException(s"Expected timeout after $timeout")), - future.map(value => fail(s"Expected timeout but got $value")) + future.recover(cause => fail("Got unexpected exception", cause)).map(_ => Passed) + + def assertTimeout(timeout: FiniteDuration): Future[Assertion] = + Future + .firstCompletedOf( + Seq( + Future.after(timeout, throw new TimeoutException(s"Expected timeout after $timeout")), + future.map(value => fail(s"Expected timeout but got $value")), + ), ) - ).assertingFailure[TimeoutException] - } + .assertingFailure[TimeoutException] + } implicit class FutureAssertionDoneOps(future: Future[Done]) { def assertingDone: Future[Assertion] = future.assertingEqual(Done) } + } trait BaseSpec extends Matchers with AsyncMockFactory { @@ -153,7 +157,7 @@ trait BaseSpec extends Matchers with AsyncMockFactory { protected type Assertion = org.scalatest.Assertion protected val Passed: Assertion = org.scalatest.Succeeded - override implicit def executionContext: ExecutionContext = ExecutionContext.global + implicit override def executionContext: ExecutionContext = ExecutionContext.global } trait AsyncBaseSpec extends BaseSpec with AsyncWordSpecLike with FutureAssertions with AsyncUtilities with TimeLimitedSpec diff --git a/src/test/scala/play/api/cache/redis/test/FakeApplication.scala b/src/test/scala/play/api/cache/redis/test/FakeApplication.scala index f496134c..7836ce72 100644 --- a/src/test/scala/play/api/cache/redis/test/FakeApplication.scala +++ b/src/test/scala/play/api/cache/redis/test/FakeApplication.scala @@ -4,6 +4,7 @@ import akka.actor.ActorSystem import play.api.inject.Injector trait FakeApplication extends StoppableApplication { + import play.api.Application import play.api.inject.guice.GuiceApplicationBuilder diff --git a/src/test/scala/play/api/cache/redis/test/ForAllTestContainer.scala b/src/test/scala/play/api/cache/redis/test/ForAllTestContainer.scala index eddb85e9..1a92f9cc 100644 --- a/src/test/scala/play/api/cache/redis/test/ForAllTestContainer.scala +++ b/src/test/scala/play/api/cache/redis/test/ForAllTestContainer.scala @@ -3,17 +3,16 @@ package play.api.cache.redis.test import com.dimafeng.testcontainers.SingleContainer import org.scalatest.{BeforeAndAfterAll, Suite} -trait ForAllTestContainer extends BeforeAndAfterAll {this: Suite => +trait ForAllTestContainer extends BeforeAndAfterAll { this: Suite => - protected def newContainer: SingleContainer[_] + protected def newContainer: SingleContainer[?] final protected lazy val container = newContainer - override def beforeAll(): Unit = { + override def beforeAll(): Unit = container.start() - } - override def afterAll(): Unit = { + override def afterAll(): Unit = container.stop() - } + } diff --git a/src/test/scala/play/api/cache/redis/test/Helpers.scala b/src/test/scala/play/api/cache/redis/test/Helpers.scala index 6d78fb82..d6623cc5 100644 --- a/src/test/scala/play/api/cache/redis/test/Helpers.scala +++ b/src/test/scala/play/api/cache/redis/test/Helpers.scala @@ -3,22 +3,24 @@ package play.api.cache.redis.test object Helpers { object configuration { + import com.typesafe.config.ConfigFactory import play.api.Configuration - def default: Configuration = { + def default: Configuration = Configuration(ConfigFactory.load()) - } def fromHocon(hocon: String): Configuration = { val reference = ConfigFactory.load() val local = ConfigFactory.parseString(hocon.stripMargin) Configuration(local.withFallback(reference)) } + } object probe { val orElse: OrElseProbe.type = OrElseProbe } + } diff --git a/src/test/scala/play/api/cache/redis/test/OrElseProbe.scala b/src/test/scala/play/api/cache/redis/test/OrElseProbe.scala index cfe9c490..0522a2d6 100644 --- a/src/test/scala/play/api/cache/redis/test/OrElseProbe.scala +++ b/src/test/scala/play/api/cache/redis/test/OrElseProbe.scala @@ -4,7 +4,6 @@ import java.util.concurrent.CompletionStage import scala.concurrent.Future import scala.jdk.FutureConverters.FutureOps - final class OrElseProbe[T](queue: LazyList[T]) { private var called: Int = 0 @@ -18,6 +17,7 @@ final class OrElseProbe[T](queue: LazyList[T]) { next = next.tail result } + } object OrElseProbe { @@ -36,4 +36,5 @@ object OrElseProbe { def generic[T](values: T*): OrElseProbe[T] = new OrElseProbe(LazyList(values: _*)) + } diff --git a/src/test/scala/play/api/cache/redis/test/RedisClusterContainer.scala b/src/test/scala/play/api/cache/redis/test/RedisClusterContainer.scala index bdc62aee..6b6276ff 100644 --- a/src/test/scala/play/api/cache/redis/test/RedisClusterContainer.scala +++ b/src/test/scala/play/api/cache/redis/test/RedisClusterContainer.scala @@ -7,13 +7,13 @@ import scala.concurrent.duration._ trait RedisClusterContainer extends RedisContainer { this: Suite => - protected val log = Logger("play.api.cache.redis.test") + protected val log: Logger = Logger("play.api.cache.redis.test") protected def redisMaster = 4 protected def redisSlaves = 1 - protected final def initialPort = 7000 + final protected def initialPort = 7000 private val waitForStart = 6.seconds @@ -23,9 +23,9 @@ trait RedisClusterContainer extends RedisContainer { this: Suite => redisMappedPorts = Seq.empty, redisFixedPorts = 0.until(redisMaster * (redisSlaves + 1)).map(initialPort + _), redisEnvironment = Map( - "IP" -> "0.0.0.0", - "INITIAL_PORT" -> initialPort.toString, - "MASTERS" -> redisMaster.toString, + "IP" -> "0.0.0.0", + "INITIAL_PORT" -> initialPort.toString, + "MASTERS" -> redisMaster.toString, "SLAVES_PER_MASTER" -> redisSlaves.toString, ), ) @@ -37,4 +37,5 @@ trait RedisClusterContainer extends RedisContainer { this: Suite => Thread.sleep(waitForStart.toMillis) log.info(s"Finished waiting for Redis Cluster to start on ${container.containerIpAddress}") } + } diff --git a/src/test/scala/play/api/cache/redis/test/RedisContainer.scala b/src/test/scala/play/api/cache/redis/test/RedisContainer.scala index 322d93bf..2ef26b98 100644 --- a/src/test/scala/play/api/cache/redis/test/RedisContainer.scala +++ b/src/test/scala/play/api/cache/redis/test/RedisContainer.scala @@ -15,13 +15,13 @@ trait RedisContainer extends ForAllTestContainer { this: Suite => @nowarn("cat=deprecation") @SuppressWarnings(Array("org.wartremover.warts.ForeachEntry")) - protected override final val newContainer: GenericContainer = { - val container: FixedHostPortGenericContainer[_] = new FixedHostPortGenericContainer(config.redisDockerImage) + final override protected val newContainer: GenericContainer = { + val container: FixedHostPortGenericContainer[?] = new FixedHostPortGenericContainer(config.redisDockerImage) container.withExposedPorts(config.redisMappedPorts.map(int2Integer): _*) config.redisEnvironment.foreach { case (k, v) => container.withEnv(k, v) } container.waitingFor(Wait.forListeningPorts(config.redisMappedPorts ++ config.redisFixedPorts: _*)) - config.redisFixedPorts.foreach { port => container.withFixedExposedPort(port, port) } + config.redisFixedPorts.foreach(port => container.withFixedExposedPort(port, port)) new GenericContainer(container) } -} +} diff --git a/src/test/scala/play/api/cache/redis/test/RedisLogger.scala b/src/test/scala/play/api/cache/redis/test/RedisLogger.scala index 536b794f..1af106da 100644 --- a/src/test/scala/play/api/cache/redis/test/RedisLogger.scala +++ b/src/test/scala/play/api/cache/redis/test/RedisLogger.scala @@ -9,11 +9,11 @@ import akka.event.slf4j.Slf4jLogger */ class RedisLogger extends Slf4jLogger { - private val doReceive: PartialFunction[Any, Unit] = { - case InitializeLogger(_) => sender() ! LoggerInitialized + private val doReceive: PartialFunction[Any, Unit] = { case InitializeLogger(_) => + sender() ! LoggerInitialized } - override def receive: PartialFunction[Any, Unit] = { + override def receive: PartialFunction[Any, Unit] = doReceive.orElse(super.receive) - } + } diff --git a/src/test/scala/play/api/cache/redis/test/RedisSentinelContainer.scala b/src/test/scala/play/api/cache/redis/test/RedisSentinelContainer.scala index e7a422db..fd65e74e 100644 --- a/src/test/scala/play/api/cache/redis/test/RedisSentinelContainer.scala +++ b/src/test/scala/play/api/cache/redis/test/RedisSentinelContainer.scala @@ -12,9 +12,9 @@ trait RedisSentinelContainer extends RedisContainer { protected def nodes: Int = 3 - protected final def initialPort: Int = 7000 + final protected def initialPort: Int = 7000 - protected final def sentinelPort: Int = initialPort - 2000 + final protected def sentinelPort: Int = initialPort - 2000 protected def master: String = s"sentinel$initialPort" @@ -23,12 +23,12 @@ trait RedisSentinelContainer extends RedisContainer { override protected lazy val redisConfig: RedisContainerConfig = RedisContainerConfig( redisDockerImage = "grokzen/redis-cluster:7.0.10", - redisMappedPorts = Seq.empty, + redisMappedPorts = Seq.empty, redisFixedPorts = 0.until(nodes).flatMap(i => Seq(initialPort + i, sentinelPort + i)), redisEnvironment = Map( - "IP" -> "0.0.0.0", + "IP" -> "0.0.0.0", "INITIAL_PORT" -> initialPort.toString, - "SENTINEL" -> "true" + "SENTINEL" -> "true", ), ) @@ -39,4 +39,5 @@ trait RedisSentinelContainer extends RedisContainer { Thread.sleep(waitForStart.toMillis) log.info(s"Finished waiting for Redis Sentinel to start on ${container.containerIpAddress}") } + } diff --git a/src/test/scala/play/api/cache/redis/test/RedisSettingsTest.scala b/src/test/scala/play/api/cache/redis/test/RedisSettingsTest.scala index 7e46d76a..556370f9 100644 --- a/src/test/scala/play/api/cache/redis/test/RedisSettingsTest.scala +++ b/src/test/scala/play/api/cache/redis/test/RedisSettingsTest.scala @@ -8,6 +8,5 @@ final case class RedisSettingsTest( timeout: RedisTimeouts, recovery: String, source: String, - prefix: Option[String] = None - + prefix: Option[String] = None, ) extends RedisSettings diff --git a/src/test/scala/play/api/cache/redis/test/RedisStandaloneContainer.scala b/src/test/scala/play/api/cache/redis/test/RedisStandaloneContainer.scala index e09ea24f..010680b9 100644 --- a/src/test/scala/play/api/cache/redis/test/RedisStandaloneContainer.scala +++ b/src/test/scala/play/api/cache/redis/test/RedisStandaloneContainer.scala @@ -11,6 +11,7 @@ trait RedisStandaloneContainer extends RedisContainer { this: Suite => redisDockerImage = "redis:latest", redisMappedPorts = Seq(defaultPort), redisFixedPorts = Seq.empty, - redisEnvironment = Map.empty + redisEnvironment = Map.empty, ) + } diff --git a/src/test/scala/play/api/cache/redis/test/StoppableApplication.scala b/src/test/scala/play/api/cache/redis/test/StoppableApplication.scala index 6c2c392b..5fbd2f58 100644 --- a/src/test/scala/play/api/cache/redis/test/StoppableApplication.scala +++ b/src/test/scala/play/api/cache/redis/test/StoppableApplication.scala @@ -10,29 +10,29 @@ import scala.util.{Failure, Success} trait StoppableApplication extends ApplicationLifecycle { - private var hooks: List[() => Future[_]] = Nil + private var hooks: List[() => Future[?]] = Nil protected def system: ActorSystem def shutdownAsync(): Future[Done] = CoordinatedShutdown(system).run(CoordinatedShutdown.UnknownReason) - def runAsyncInApplication(block: => Future[Assertion])(implicit ec: ExecutionContext): Future[Assertion] = { - block.map(Success(_)).recover(Failure(_)) + def runAsyncInApplication(block: => Future[Assertion])(implicit ec: ExecutionContext): Future[Assertion] = + block + .map(Success(_)) + .recover(Failure(_)) .flatMap(result => Future.sequence(hooks.map(_.apply())).map(_ => result)) .flatMap(result => shutdownAsync().map(_ => result)) .flatMap(result => system.terminate().map(_ => result)) .flatMap(Future.fromTry) - } - final def runInApplication(block: => Assertion)(implicit ec: ExecutionContext): Future[Assertion] = { + final def runInApplication(block: => Assertion)(implicit ec: ExecutionContext): Future[Assertion] = runAsyncInApplication(Future(block)) - } - final override def addStopHook(hook: () => Future[_]): Unit = + final override def addStopHook(hook: () => Future[?]): Unit = hooks = hook :: hooks - final override def stop(): Future[_] = Future.unit + final override def stop(): Future[?] = Future.unit } @@ -42,4 +42,5 @@ object StoppableApplication { new StoppableApplication { override protected def system: ActorSystem = actorSystem } + } From 0a70d558f2e18d3b0fc26f367a4629af5b638ec7 Mon Sep 17 00:00:00 2001 From: Karel Cemus Date: Sun, 4 Feb 2024 23:49:03 +0100 Subject: [PATCH 4/5] Documentation updated to version 3.0.0-M2 (#276) --- CHANGELOG.md | 2 ++ README.md | 40 ++++++++++++++++++++-------------------- doc/10-integration.md | 2 +- doc/20-configuration.md | 2 +- doc/40-migration.md | 4 ++-- 5 files changed, 26 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a34f7a0e..90dcfe1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ for cluster are using fixed port mapping and tests for sentinel are disabled sin that sentinel implementation is not fully reliable, therefore sentinel is not officially supported at this moment. [#273](https://github.com/KarelCemus/play-redis/pull/273) +Installed linters and formatters - scalafmt, scalafix, wartremover. [#275](https://github.com/KarelCemus/play-redis/pull/275) + ### [:link: 2.7.0](https://github.com/KarelCemus/play-redis/tree/2.7.0) SET command supports milliseconds, previous versions used seconds [#247](https://github.com/KarelCemus/play-redis/issues/247) diff --git a/README.md b/README.md index 24f10bdc..96b20fe0 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,22 @@ as well as on your premise. - [synchronous and asynchronous APIs](#provided-apis) - [implements standard APIs defined by Play's `cacheApi` project](#provided-apis) -- support of [named caches](https://github.com/KarelCemus/play-redis/blob/3.0.0-M1/doc/20-configuration.md#named-caches) -- [works with Guice](https://github.com/KarelCemus/play-redis/blob/3.0.0-M1/doc/40-migration.md#runtime-time-dependency-injection) as well as [compile-time DI](https://github.com/KarelCemus/play-redis/blob/3.0.0-M1/doc/40-migration.md#compile-time-dependency-injection) -- [getOrElse and getOrFuture operations](https://github.com/KarelCemus/play-redis/blob/3.0.0-M1/doc/30-how-to-use.md#use-of-cacheapi) easing the use -- [wildcards in remove operation](https://github.com/KarelCemus/play-redis/blob/3.0.0-M1/doc/30-how-to-use.md#use-of-cacheapi) -- support of collections: [sets](https://github.com/KarelCemus/play-redis/blob/3.0.0-M1/doc/30-how-to-use.md#use-of-sets), [lists](https://github.com/KarelCemus/play-redis/blob/3.0.0-M1/doc/30-how-to-use.md#use-of-lists), and [maps](https://github.com/KarelCemus/play-redis/blob/3.0.0-M1/doc/30-how-to-use.md#use-of-maps) -- [increment and decrement operations](https://github.com/KarelCemus/play-redis/blob/3.0.0-M1/doc/30-how-to-use.md#use-of-cacheapi) -- [eager and lazy invocation policies](https://github.com/KarelCemus/play-redis/blob/3.0.0-M1/doc/20-configuration.md#eager-and-lazy-invocation) waiting or not waiting for the result -- several [recovery policies](https://github.com/KarelCemus/play-redis/blob/3.0.0-M1/doc/20-configuration.md#recovery-policy) and possibility of further customization -- support of [several configuration sources](https://github.com/KarelCemus/play-redis/blob/3.0.0-M1/doc/20-configuration.md#running-in-different-environments) +- support of [named caches](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/20-configuration.md#named-caches) +- [works with Guice](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/40-migration.md#runtime-time-dependency-injection) as well as [compile-time DI](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/40-migration.md#compile-time-dependency-injection) +- [getOrElse and getOrFuture operations](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/30-how-to-use.md#use-of-cacheapi) easing the use +- [wildcards in remove operation](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/30-how-to-use.md#use-of-cacheapi) +- support of collections: [sets](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/30-how-to-use.md#use-of-sets), [lists](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/30-how-to-use.md#use-of-lists), and [maps](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/30-how-to-use.md#use-of-maps) +- [increment and decrement operations](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/30-how-to-use.md#use-of-cacheapi) +- [eager and lazy invocation policies](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/20-configuration.md#eager-and-lazy-invocation) waiting or not waiting for the result +- several [recovery policies](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/20-configuration.md#recovery-policy) and possibility of further customization +- support of [several configuration sources](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/20-configuration.md#running-in-different-environments) - static in the configuration file - from the connection string optionally in the environmental variable - custom implementation of the configuration provider -- support of [standalone, cluster,](https://github.com/KarelCemus/play-redis/blob/3.0.0-M1/doc/20-configuration.md#standalone-vs-cluster) - [aws-cluster,](https://github.com/KarelCemus/play-redis/blob/3.0.0-M1/doc/20-configuration.md#aws-cluster) - and [sentinel modes](https://github.com/KarelCemus/play-redis/blob/3.0.0-M1/doc/20-configuration.md#sentinel) -- build on the top of Akka actors and serializers, [agnostic to the serialization mechanism](https://github.com/KarelCemus/play-redis/blob/3.0.0-M1/doc/20-configuration.md#limitation-of-data-serialization) +- support of [standalone, cluster,](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/20-configuration.md#standalone-vs-cluster) + [aws-cluster,](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/20-configuration.md#aws-cluster) + and [sentinel modes](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/20-configuration.md#sentinel) +- build on the top of Akka actors and serializers, [agnostic to the serialization mechanism](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/20-configuration.md#limitation-of-data-serialization) - for simplicity, it uses deprecated Java serialization by default - it is recommended to use [Kryo library](https://github.com/romix/akka-kryo-serialization) or any other mechanism @@ -92,11 +92,11 @@ or you can use shortcuts in the table below. To use this module: -1. [Add this library into your project](https://github.com/KarelCemus/play-redis/blob/3.0.0-M1/doc/10-integration.md) and expose APIs -1. See the [configuration options](https://github.com/KarelCemus/play-redis/blob/3.0.0-M1/doc/20-configuration.md) -1. [Browse examples of use](https://github.com/KarelCemus/play-redis/blob/3.0.0-M1/doc/30-how-to-use.md) +1. [Add this library into your project](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/10-integration.md) and expose APIs +1. See the [configuration options](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/20-configuration.md) +1. [Browse examples of use](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/30-how-to-use.md) -If you come from older version, you might check the [Migration Guide](https://github.com/KarelCemus/play-redis/blob/3.0.0-M1/doc/40-migration.md) +If you come from older version, you might check the [Migration Guide](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/40-migration.md) ## Samples @@ -124,7 +124,7 @@ To your SBT `build.sbt` add the following lines: // enable Play cache API (based on your Play version) libraryDependencies += play.sbt.PlayImport.cacheApi // include play-redis library -libraryDependencies += "com.github.karelcemus" %% "play-redis" % "3.0.0-M1" +libraryDependencies += "com.github.karelcemus" %% "play-redis" % "3.0.0-M2" ``` @@ -132,7 +132,7 @@ libraryDependencies += "com.github.karelcemus" %% "play-redis" % "3.0.0-M1" | play framework | play-redis | documentation | |----------------|---------------------------------:|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------:| -| 2.9.x | 3.0.0-M1 | [see here](https://github.com/KarelCemus/play-redis/blob/3.0.0-M1/README.md) | +| 2.9.x | 3.0.0-M2 | [see here](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/README.md) | | 2.8.x | 2.7.0 | [see here](https://github.com/KarelCemus/play-redis/blob/2.7.0/README.md) | | 2.7.x | 2.5.1 | [see here](https://github.com/KarelCemus/play-redis/blob/2.5.1/README.md) | | 2.6.x | 2.3.0 | [see here](https://github.com/KarelCemus/play-redis/blob/2.3.0/README.md) ([Migration Guide](https://github.com/KarelCemus/play-redis/blob/2.3.0/doc/40-migration.md)) | @@ -150,7 +150,7 @@ like this library, please feel free to report it or contact me. ## Changelog For the list of changes and migration guide please see -[the Changelog](https://github.com/KarelCemus/play-redis/blob/3.0.0-M1/CHANGELOG.md). +[the Changelog](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/CHANGELOG.md). ## Caveat diff --git a/doc/10-integration.md b/doc/10-integration.md index 5051aed6..1e9d410a 100644 --- a/doc/10-integration.md +++ b/doc/10-integration.md @@ -9,7 +9,7 @@ Although the use of runtime-time injection is preferred, both options are equal // enable Play cache API (based on your Play version) libraryDependencies += play.sbt.PlayImport.cacheApi // include play-redis library -libraryDependencies += "com.github.karelcemus" %% "play-redis" % "3.0.0-M1" +libraryDependencies += "com.github.karelcemus" %% "play-redis" % "3.0.0-M2" ``` diff --git a/doc/20-configuration.md b/doc/20-configuration.md index c288e985..0760efd4 100644 --- a/doc/20-configuration.md +++ b/doc/20-configuration.md @@ -1,6 +1,6 @@ # Configuration -Default configuration and very detailed manual is available in [reference.conf](https://github.com/KarelCemus/play-redis/blob/3.0.0-M1/src/main/resources/reference.conf). It can be overwritten in your `conf/application.conf` file. +Default configuration and very detailed manual is available in [reference.conf](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/src/main/resources/reference.conf). It can be overwritten in your `conf/application.conf` file. There are several features supported in the configuration, they are discussed below. However, by default, there is no need for any further configuration. Default settings are set to the standalone instance running on `localhost:6379?db=0`, which is default for redis server. This instance is named `play` but is also exposed as a default implementation. diff --git a/doc/40-migration.md b/doc/40-migration.md index 3c66f90d..78c0269a 100644 --- a/doc/40-migration.md +++ b/doc/40-migration.md @@ -25,7 +25,7 @@ changes in public API, which needs your code to be updated. The invocation policy in `2.0.x` was used as an implicit parameter. Since `2.1.x` it is a static configurable property inside the instance configuration. -See the [updated documentation for more details](https://github.com/KarelCemus/play-redis/blob/3.0.0-M1/doc/20-configuration.md#eager-and-lazy-invocation). +See the [updated documentation for more details](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/20-configuration.md#eager-and-lazy-invocation). ### Named caches uses @NamedCache instead of @Named @@ -39,7 +39,7 @@ Since `2.1.0`, there is a new `redis-timeout` property. To avoid ambiguity, the original `timeout` property was renamed to `sync-redis`. The `timeout` property was deprecated any will be removed in `2.2.0`. -See the updated [documentation for more details](https://github.com/KarelCemus/play-redis/blob/3.0.0-M1/doc/20-configuration.md#eager-and-lazy-invocation). +See the updated [documentation for more details](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/20-configuration.md#eager-and-lazy-invocation). ## Migration from 1.6.x to 2.0.x From 4c09acb8220db775bfb1ee28f042b122a16778fa Mon Sep 17 00:00:00 2001 From: Karel Cemus Date: Mon, 5 Feb 2024 00:02:38 +0100 Subject: [PATCH 5/5] Documentation updated to version 3.0.0-M3 (#277) --- README.md | 40 +++++++++---------- doc/10-integration.md | 2 +- doc/20-configuration.md | 2 +- doc/40-migration.md | 4 +- .../redis/configuration/RedisTimeouts.scala | 11 ----- 5 files changed, 24 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 96b20fe0..f87a42c6 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,22 @@ as well as on your premise. - [synchronous and asynchronous APIs](#provided-apis) - [implements standard APIs defined by Play's `cacheApi` project](#provided-apis) -- support of [named caches](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/20-configuration.md#named-caches) -- [works with Guice](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/40-migration.md#runtime-time-dependency-injection) as well as [compile-time DI](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/40-migration.md#compile-time-dependency-injection) -- [getOrElse and getOrFuture operations](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/30-how-to-use.md#use-of-cacheapi) easing the use -- [wildcards in remove operation](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/30-how-to-use.md#use-of-cacheapi) -- support of collections: [sets](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/30-how-to-use.md#use-of-sets), [lists](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/30-how-to-use.md#use-of-lists), and [maps](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/30-how-to-use.md#use-of-maps) -- [increment and decrement operations](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/30-how-to-use.md#use-of-cacheapi) -- [eager and lazy invocation policies](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/20-configuration.md#eager-and-lazy-invocation) waiting or not waiting for the result -- several [recovery policies](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/20-configuration.md#recovery-policy) and possibility of further customization -- support of [several configuration sources](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/20-configuration.md#running-in-different-environments) +- support of [named caches](https://github.com/KarelCemus/play-redis/blob/3.0.0-M3/doc/20-configuration.md#named-caches) +- [works with Guice](https://github.com/KarelCemus/play-redis/blob/3.0.0-M3/doc/40-migration.md#runtime-time-dependency-injection) as well as [compile-time DI](https://github.com/KarelCemus/play-redis/blob/3.0.0-M3/doc/40-migration.md#compile-time-dependency-injection) +- [getOrElse and getOrFuture operations](https://github.com/KarelCemus/play-redis/blob/3.0.0-M3/doc/30-how-to-use.md#use-of-cacheapi) easing the use +- [wildcards in remove operation](https://github.com/KarelCemus/play-redis/blob/3.0.0-M3/doc/30-how-to-use.md#use-of-cacheapi) +- support of collections: [sets](https://github.com/KarelCemus/play-redis/blob/3.0.0-M3/doc/30-how-to-use.md#use-of-sets), [lists](https://github.com/KarelCemus/play-redis/blob/3.0.0-M3/doc/30-how-to-use.md#use-of-lists), and [maps](https://github.com/KarelCemus/play-redis/blob/3.0.0-M3/doc/30-how-to-use.md#use-of-maps) +- [increment and decrement operations](https://github.com/KarelCemus/play-redis/blob/3.0.0-M3/doc/30-how-to-use.md#use-of-cacheapi) +- [eager and lazy invocation policies](https://github.com/KarelCemus/play-redis/blob/3.0.0-M3/doc/20-configuration.md#eager-and-lazy-invocation) waiting or not waiting for the result +- several [recovery policies](https://github.com/KarelCemus/play-redis/blob/3.0.0-M3/doc/20-configuration.md#recovery-policy) and possibility of further customization +- support of [several configuration sources](https://github.com/KarelCemus/play-redis/blob/3.0.0-M3/doc/20-configuration.md#running-in-different-environments) - static in the configuration file - from the connection string optionally in the environmental variable - custom implementation of the configuration provider -- support of [standalone, cluster,](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/20-configuration.md#standalone-vs-cluster) - [aws-cluster,](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/20-configuration.md#aws-cluster) - and [sentinel modes](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/20-configuration.md#sentinel) -- build on the top of Akka actors and serializers, [agnostic to the serialization mechanism](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/20-configuration.md#limitation-of-data-serialization) +- support of [standalone, cluster,](https://github.com/KarelCemus/play-redis/blob/3.0.0-M3/doc/20-configuration.md#standalone-vs-cluster) + [aws-cluster,](https://github.com/KarelCemus/play-redis/blob/3.0.0-M3/doc/20-configuration.md#aws-cluster) + and [sentinel modes](https://github.com/KarelCemus/play-redis/blob/3.0.0-M3/doc/20-configuration.md#sentinel) +- build on the top of Akka actors and serializers, [agnostic to the serialization mechanism](https://github.com/KarelCemus/play-redis/blob/3.0.0-M3/doc/20-configuration.md#limitation-of-data-serialization) - for simplicity, it uses deprecated Java serialization by default - it is recommended to use [Kryo library](https://github.com/romix/akka-kryo-serialization) or any other mechanism @@ -92,11 +92,11 @@ or you can use shortcuts in the table below. To use this module: -1. [Add this library into your project](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/10-integration.md) and expose APIs -1. See the [configuration options](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/20-configuration.md) -1. [Browse examples of use](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/30-how-to-use.md) +1. [Add this library into your project](https://github.com/KarelCemus/play-redis/blob/3.0.0-M3/doc/10-integration.md) and expose APIs +1. See the [configuration options](https://github.com/KarelCemus/play-redis/blob/3.0.0-M3/doc/20-configuration.md) +1. [Browse examples of use](https://github.com/KarelCemus/play-redis/blob/3.0.0-M3/doc/30-how-to-use.md) -If you come from older version, you might check the [Migration Guide](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/40-migration.md) +If you come from older version, you might check the [Migration Guide](https://github.com/KarelCemus/play-redis/blob/3.0.0-M3/doc/40-migration.md) ## Samples @@ -124,7 +124,7 @@ To your SBT `build.sbt` add the following lines: // enable Play cache API (based on your Play version) libraryDependencies += play.sbt.PlayImport.cacheApi // include play-redis library -libraryDependencies += "com.github.karelcemus" %% "play-redis" % "3.0.0-M2" +libraryDependencies += "com.github.karelcemus" %% "play-redis" % "3.0.0-M3" ``` @@ -132,7 +132,7 @@ libraryDependencies += "com.github.karelcemus" %% "play-redis" % "3.0.0-M2" | play framework | play-redis | documentation | |----------------|---------------------------------:|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------:| -| 2.9.x | 3.0.0-M2 | [see here](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/README.md) | +| 2.9.x | 3.0.0-M3 | [see here](https://github.com/KarelCemus/play-redis/blob/3.0.0-M3/README.md) | | 2.8.x | 2.7.0 | [see here](https://github.com/KarelCemus/play-redis/blob/2.7.0/README.md) | | 2.7.x | 2.5.1 | [see here](https://github.com/KarelCemus/play-redis/blob/2.5.1/README.md) | | 2.6.x | 2.3.0 | [see here](https://github.com/KarelCemus/play-redis/blob/2.3.0/README.md) ([Migration Guide](https://github.com/KarelCemus/play-redis/blob/2.3.0/doc/40-migration.md)) | @@ -150,7 +150,7 @@ like this library, please feel free to report it or contact me. ## Changelog For the list of changes and migration guide please see -[the Changelog](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/CHANGELOG.md). +[the Changelog](https://github.com/KarelCemus/play-redis/blob/3.0.0-M3/CHANGELOG.md). ## Caveat diff --git a/doc/10-integration.md b/doc/10-integration.md index 1e9d410a..182b8890 100644 --- a/doc/10-integration.md +++ b/doc/10-integration.md @@ -9,7 +9,7 @@ Although the use of runtime-time injection is preferred, both options are equal // enable Play cache API (based on your Play version) libraryDependencies += play.sbt.PlayImport.cacheApi // include play-redis library -libraryDependencies += "com.github.karelcemus" %% "play-redis" % "3.0.0-M2" +libraryDependencies += "com.github.karelcemus" %% "play-redis" % "3.0.0-M3" ``` diff --git a/doc/20-configuration.md b/doc/20-configuration.md index 0760efd4..c22a76aa 100644 --- a/doc/20-configuration.md +++ b/doc/20-configuration.md @@ -1,6 +1,6 @@ # Configuration -Default configuration and very detailed manual is available in [reference.conf](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/src/main/resources/reference.conf). It can be overwritten in your `conf/application.conf` file. +Default configuration and very detailed manual is available in [reference.conf](https://github.com/KarelCemus/play-redis/blob/3.0.0-M3/src/main/resources/reference.conf). It can be overwritten in your `conf/application.conf` file. There are several features supported in the configuration, they are discussed below. However, by default, there is no need for any further configuration. Default settings are set to the standalone instance running on `localhost:6379?db=0`, which is default for redis server. This instance is named `play` but is also exposed as a default implementation. diff --git a/doc/40-migration.md b/doc/40-migration.md index 78c0269a..19ea4024 100644 --- a/doc/40-migration.md +++ b/doc/40-migration.md @@ -25,7 +25,7 @@ changes in public API, which needs your code to be updated. The invocation policy in `2.0.x` was used as an implicit parameter. Since `2.1.x` it is a static configurable property inside the instance configuration. -See the [updated documentation for more details](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/20-configuration.md#eager-and-lazy-invocation). +See the [updated documentation for more details](https://github.com/KarelCemus/play-redis/blob/3.0.0-M3/doc/20-configuration.md#eager-and-lazy-invocation). ### Named caches uses @NamedCache instead of @Named @@ -39,7 +39,7 @@ Since `2.1.0`, there is a new `redis-timeout` property. To avoid ambiguity, the original `timeout` property was renamed to `sync-redis`. The `timeout` property was deprecated any will be removed in `2.2.0`. -See the updated [documentation for more details](https://github.com/KarelCemus/play-redis/blob/3.0.0-M2/doc/20-configuration.md#eager-and-lazy-invocation). +See the updated [documentation for more details](https://github.com/KarelCemus/play-redis/blob/3.0.0-M3/doc/20-configuration.md#eager-and-lazy-invocation). ## Migration from 1.6.x to 2.0.x diff --git a/src/main/scala/play/api/cache/redis/configuration/RedisTimeouts.scala b/src/main/scala/play/api/cache/redis/configuration/RedisTimeouts.scala index 22f34664..76603e61 100644 --- a/src/main/scala/play/api/cache/redis/configuration/RedisTimeouts.scala +++ b/src/main/scala/play/api/cache/redis/configuration/RedisTimeouts.scala @@ -26,19 +26,8 @@ trait RedisTimeouts { } final case class RedisTimeoutsImpl( - /** - * sync timeout applies in sync API and indicates how long to wait before the - * future is resolved - */ sync: FiniteDuration, - - /** redis timeout indicates how long to wait for the response */ redis: Option[FiniteDuration], - - /** - * fail after timeout applies when the connection is not established to fail - * requests eagerly - */ connection: Option[FiniteDuration], ) extends RedisTimeouts {