diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index f94f501e..04fa2212 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -28,18 +28,13 @@ jobs: - name: Setup JDK uses: coursier/setup-action@v1.3.4 with: - jvm: adoptium:1.8 - - - name: Test Code Style - run: | - sbt --client "+scalariformFormat; +test:scalariformFormat" - git diff --exit-code || ( - echo "ERROR: Scalariform check failed, see differences above." - echo "To fix, format your sources using sbt scalariformFormat test:scalariformFormat before submitting a pull request." - false - ) + jvm: adoptium:1.17 - name: Build timeout-minutes: 10 run: | - sbt --client "+clean; +compile; +Test/compile; +test;" + [[ "$GITHUB_REF" == "refs/heads/master" ]] && SBT_CMD="+coverageOn +test +coverageOff +publish" || SBT_CMD="+coverage +test" + echo "Github ref: $GITHUB_REF" + SBT_CMD="+clean; +compile; +Test/compile; $SBT_CMD; +coverageReport; +coveralls;" + echo "SBT command: $SBT_CMD" + sbt --client "$SBT_CMD" diff --git a/.scalariform.conf b/.scalariform.conf deleted file mode 100644 index adbb97d4..00000000 --- a/.scalariform.conf +++ /dev/null @@ -1,30 +0,0 @@ -#alignArguments=false -#alignParameters=false -alignSingleLineCaseStatements=true -#alignSingleLineCaseStatements.maxArrowIndent=40 -#allowParamGroupsOnNewlines=false -#compactControlReadability=false -#compactStringConcatenation=false -danglingCloseParenthesis=Force -#doubleIndentClassDeclaration=false -#doubleIndentConstructorArguments=false -doubleIndentMethodDeclaration=true -#firstArgumentOnNewline=Force -#firstParameterOnNewline=Force -#formatXml=true -#indentLocalDefs=false -#indentPackageBlocks=true -#indentSpaces=2 -#indentWithTabs=false -#multilineScaladocCommentsStartOnFirstLine=false -newlineAtEndOfFile=true -placeScaladocAsterisksBeneathSecondAsterisk=true -#preserveSpaceBeforeArguments=false -#rewriteArrowSymbols=false -#singleCasePatternOnNewline=true -#spaceBeforeColon=false -#spaceBeforeContextColon=false -#spaceInsideBrackets=false -#spaceInsideParentheses=false -spacesAroundMultiImports=false -#spacesWithinPatternBinders=true diff --git a/bin/config b/bin/config deleted file mode 100644 index dbc113f3..00000000 --- a/bin/config +++ /dev/null @@ -1,23 +0,0 @@ -#! /bin/bash - -# config for CI scripts - -set -o pipefail - -# Travis uses a detached HEAD during builds -# CURRENT_BRANCH is used when updating Whitesource to determine the correct project name -export CURRENT_BRANCH=${TRAVIS_BRANCH} - -# Command to run tests based on current branch -[[ "$TRAVIS_BRANCH" == "master" && "$TRAVIS_PULL_REQUEST" == false ]] && SBT_CMD="coverageOn test coverageOff publish" || SBT_CMD="coverage test" -export SBT_CMD - -runSbt() { - sbt "$@" -} - -doAfterSuccess() { - if [ $1 -eq 0 ]; then - sbt ++$2 coverageReport coveralls - fi -} diff --git a/bin/test b/bin/test deleted file mode 100755 index ac166e2d..00000000 --- a/bin/test +++ /dev/null @@ -1,13 +0,0 @@ -#! /bin/bash - -. "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/config" - -SCALA_VERSION="$1" - -runSbt ++$SCALA_VERSION $SBT_CMD - -result=$? - -doAfterSuccess $result $SCALA_VERSION - -exit $result diff --git a/bin/test-code-style b/bin/test-code-style deleted file mode 100755 index b2a0debf..00000000 --- a/bin/test-code-style +++ /dev/null @@ -1,12 +0,0 @@ -#! /bin/bash - -. "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/config" - -runSbt +scalariformFormat \ - +test:scalariformFormat - -git diff --exit-code || ( - echo "ERROR: Scalariform check failed, see differences above." - echo "To fix, format your sources using sbt scalariformFormat test:scalariformFormat before submitting a pull request." - false -) diff --git a/build.sbt b/build.sbt index 3f67bd71..d25a5030 100644 --- a/build.sbt +++ b/build.sbt @@ -9,21 +9,23 @@ description := "Redis cache plugin for the Play framework 2" organization := "com.github.karelcemus" -scalaVersion := "2.13.8" +crossScalaVersions := Seq("2.13.12") //, "3.3.0" -crossScalaVersions := Seq("2.12.15", scalaVersion.value) +scalaVersion := crossScalaVersions.value.head -playVersion := "2.8.13" +playVersion := "2.9.0" libraryDependencies ++= Seq( // play framework cache API "com.typesafe.play" %% "play-cache" % playVersion.value % Provided, // redis connector - "com.github.karelcemus" %% "rediscala" % "1.9.1", + "io.github.rediscala" %% "rediscala" % "1.14.0-akka", // test framework with mockito extension - "org.specs2" %% "specs2-mock" % "4.13.2" % Test, + "org.specs2" %% "specs2-mock" % "4.20.3" % Test, // test module for play framework - "com.typesafe.play" %% "play-specs2" % playVersion.value % Test + "com.typesafe.play" %% "play-test" % playVersion.value % Test, + // to run integration tests + "com.dimafeng" %% "testcontainers-scala-core" % "0.41.0" % Test ) resolvers ++= Seq( @@ -34,10 +36,6 @@ javacOptions ++= Seq("-Xlint:unchecked", "-encoding", "UTF-8") scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked") -homepage := Some(url("https://github.com/karelcemus/play-redis")) - -licenses := Seq("Apache 2" -> url("https://www.apache.org/licenses/LICENSE-2.0")) - enablePlugins(CustomReleasePlugin) // exclude from tests coverage diff --git a/project/CustomReleasePlugin.scala b/project/CustomReleasePlugin.scala index 0c077d08..d73b395e 100644 --- a/project/CustomReleasePlugin.scala +++ b/project/CustomReleasePlugin.scala @@ -32,7 +32,9 @@ object CustomReleasePlugin extends AutoPlugin { pomIncludeRepository := { _ => false }, // customized release process 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( ScmInfo( url("https://github.com/KarelCemus/play-i18n.git"), diff --git a/project/plugins.sbt b/project/plugins.sbt index 0ebf6af2..1a57ca14 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,15 +1,12 @@ resolvers += Resolver.url("scoverage-bintray", url("https://dl.bintray.com/sksamuel/sbt-plugins/"))(Resolver.ivyStylePatterns) // checks for updates -addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.1") +addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.4") // code coverage and uploader of the coverage results into the coveralls.io addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.8.2") addSbtPlugin("org.scoverage" % "sbt-coveralls" % "1.3.1") -// code linter -addSbtPlugin("org.scalariform" % "sbt-scalariform" % "1.8.3") - // library release addSbtPlugin("com.github.sbt" % "sbt-git" % "2.0.1") addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.21") diff --git a/src/main/scala-2.12/play/api/cache/redis/JavaCompatibilityBase.scala b/src/main/scala-2.12/play/api/cache/redis/JavaCompatibilityBase.scala deleted file mode 100644 index c333601f..00000000 --- a/src/main/scala-2.12/play/api/cache/redis/JavaCompatibilityBase.scala +++ /dev/null @@ -1,8 +0,0 @@ -package play.api.cache.redis - -import scala.collection.convert.{DecorateAsJava, DecorateAsScala} - -private[redis] trait JavaCompatibilityBase extends DecorateAsScala with DecorateAsJava - -private[redis] object JavaCompatibilityBase extends JavaCompatibilityBase - diff --git a/src/main/scala-2.13/play/api/cache/redis/JavaCompatibilityBase.scala b/src/main/scala/play/api/cache/redis/JavaCompatibilityBase.scala similarity index 100% rename from src/main/scala-2.13/play/api/cache/redis/JavaCompatibilityBase.scala rename to src/main/scala/play/api/cache/redis/JavaCompatibilityBase.scala 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 60928212..dfde44e9 100644 --- a/src/main/scala/play/api/cache/redis/configuration/RedisHost.scala +++ b/src/main/scala/play/api/cache/redis/configuration/RedisHost.scala @@ -14,6 +14,8 @@ trait RedisHost { 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$ @@ -21,13 +23,13 @@ trait RedisHost { override def equals(obj: scala.Any) = equalsAsHost(obj) /** trait-specific equals, invokable from children */ protected def equalsAsHost(obj: scala.Any) = obj match { - case that: RedisHost => Equals.check(this, that)(_.host, _.port, _.database, _.password) + case that: RedisHost => Equals.check(this, that)(_.host, _.port, _.username, _.database, _.password) case _ => false } /** to string */ - override def toString = (password, database) match { - case (Some(password), Some(database)) => s"redis://redis:$password@$host:$port?db=$database" - case (Some(password), None) => s"redis://redis:$password@$host:$port" + override def toString: String = (password, database) match { + case (Some(password), Some(database)) => s"redis://${username.getOrElse("redis")}:$password@$host:$port?db=$database" + case (Some(password), None) => s"redis://${username.getOrElse("redis")}:$password@$host:$port" case (None, Some(database)) => s"redis://$host:$port?db=$database" case (None, None) => s"redis://$host:$port" } @@ -38,7 +40,7 @@ object RedisHost extends ConfigLoader[RedisHost] { import RedisConfigLoader._ /** expected format of the environment variable */ - private val ConnectionString = "redis://((?[^:]+):(?[^@]+)@)?(?[^:]+):(?[0-9]+)".r("auth", "user", "password", "host", "port") + private val ConnectionString = "redis://((?[^:]+):(?[^@]+)@)?(?[^:]+):(?[0-9]+)".r("auth", "username", "password", "host", "port") def load(config: Config, path: String): RedisHost = apply( host = config.getString(path / "host"), @@ -51,29 +53,31 @@ object RedisHost extends ConfigLoader[RedisHost] { 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 { - val host = matcher.group("host") - val port = matcher.group("port").toInt - val database = None - val password = Option(matcher.group("password")) + 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'.") } - def apply(host: String, port: Int, database: Option[Int] = None, password: Option[String] = None): RedisHost = - create(host, port, database, password) + def apply(host: String, port: Int, database: Option[Int] = None, username: Option[String] = None, password: Option[String] = None): RedisHost = + create(host, port, database, username, password) /** hackish method to preserve nice names of parameters in apply */ - @inline private def create(_host: String, _port: Int, _database: Option[Int], _password: Option[String]) = new RedisHost { - val host = _host - val port = _port - val database = _database - val password = _password + @inline private def create(_host: String, _port: Int, _database: Option[Int], _username: Option[String], _password: Option[String]) = new RedisHost { + override val host: String = _host + override val port: Int = _port + override val database: Option[Int] = _database + override val username: Option[String] = _username + override val password: Option[String] = _password } // $COVERAGE-OFF$ - def unapply(host: RedisHost): Option[(String, Int, Option[Int], Option[String])] = { - Some((host.host, host.port, host.database, host.password)) + def unapply(host: RedisHost): Option[(String, Int, Option[Int],Option[String], Option[String])] = { + Some((host.host, host.port, host.database, host.username, host.password)) } // $COVERAGE-ON$ } @@ -84,8 +88,9 @@ object RedisHost extends ConfigLoader[RedisHost] { */ trait RedisDelegatingHost extends RedisHost { def innerHost: RedisHost - def host = innerHost.host - def port = innerHost.port - def database = innerHost.database - def password = innerHost.password + override def host: String = innerHost.host + override def port: Int = innerHost.port + override def database: Option[Int] = innerHost.database + override def username: Option[String] = innerHost.username + override def password: Option[String] = innerHost.password } 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 0df7c272..03c2f334 100644 --- a/src/main/scala/play/api/cache/redis/configuration/RedisInstance.scala +++ b/src/main/scala/play/api/cache/redis/configuration/RedisInstance.scala @@ -91,6 +91,7 @@ trait RedisSentinel extends RedisInstance { def sentinels: List[RedisHost] def masterGroup: String + def username: Option[String] def password: Option[String] def database: Option[Int] @@ -103,23 +104,28 @@ trait RedisSentinel extends RedisInstance { object RedisSentinel { - def apply(name: String, masterGroup: String, - sentinels: List[RedisHost], - settings: RedisSettings, - password: Option[String] = None, - database: Option[Int] = None): RedisSentinel with RedisDelegatingSettings = - create(name, masterGroup, password, database, sentinels, settings) + def apply( + name: String, + masterGroup: String, + sentinels: List[RedisHost], + settings: RedisSettings, + username: Option[String] = None, + password: Option[String] = None, + database: Option[Int] = None + ): RedisSentinel with RedisDelegatingSettings = + create(name, masterGroup, username, password, database, sentinels, settings) @inline - private def create(_name: String, _masterGroup: String, _password: Option[String], _database: Option[Int], + private def create(_name: String, _masterGroup: String, _username: Option[String], _password: Option[String], _database: Option[Int], _sentinels: List[RedisHost], _settings: RedisSettings) = new RedisSentinel with RedisDelegatingSettings { - val name = _name - val masterGroup = _masterGroup - val password = _password - val database = _database - val sentinels = _sentinels - val settings = _settings + override val name: String = _name + override val masterGroup: String = _masterGroup + override val username: Option[String] = _username + override val password: Option[String] = _password + override val database: Option[Int] = _database + override val sentinels: List[RedisHost] = _sentinels + override val settings: RedisSettings = _settings } } 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 eaad4a90..4c2c0bc5 100644 --- a/src/main/scala/play/api/cache/redis/connector/RedisCommands.scala +++ b/src/main/scala/play/api/cache/redis/connector/RedisCommands.scala @@ -62,6 +62,7 @@ private[connector] class RedisCommandsStandalone(configuration: RedisStandalone) host = host, port = port, db = database, + username = username, password = password ) with FailEagerly with RedisRequestTimeout { @@ -105,7 +106,7 @@ private[connector] class RedisCommandsCluster(configuration: RedisCluster)(impli val client: RedisClusterClient = new RedisClusterClient( nodes.map { - case RedisHost(host, port, database, password) => RedisServer(host.resolvedIpAddress, port, password, database) + case RedisHost(host, port, database, username, password) => RedisServer(host.resolvedIpAddress, port, username, password, database) } ) with RedisRequestTimeout { protected val timeout = configuration.timeout.redis @@ -116,8 +117,8 @@ private[connector] class RedisCommandsCluster(configuration: RedisCluster)(impli // $COVERAGE-OFF$ def start() = { def servers = nodes.map { - case RedisHost(host, port, Some(database), _) => s" $host:$port?database=$database" - case RedisHost(host, port, None, _) => s" $host:$port" + case RedisHost(host, port, Some(database), _, _) => s" $host:$port?database=$database" + case RedisHost(host, port, None, _, _) => s" $host:$port" } log.info(s"Redis cluster cache actor started. It is connected to ${servers mkString ", "}") @@ -143,9 +144,10 @@ private[connector] class RedisCommandsSentinel(configuration: RedisSentinel)(imp val client: SentinelMonitoredRedisClient with RedisRequestTimeout = new SentinelMonitoredRedisClient( configuration.sentinels.map { - case RedisHost(host, port, _, _) => (host.resolvedIpAddress, port) + case RedisHost(host, port, _, _, _) => (host.resolvedIpAddress, port) }, master = configuration.masterGroup, + username = configuration.username, password = configuration.password, db = configuration.database ) with RedisRequestTimeout { 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 ca1b9ce4..e1989f9b 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisCaches.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisCaches.scala @@ -1,12 +1,11 @@ package play.api.cache.redis.impl import javax.inject.Provider - import play.api.Environment import play.api.cache.redis._ import play.api.inject.ApplicationLifecycle - import akka.actor.ActorSystem +import play.api.cache.{AsyncCacheApi, DefaultSyncCacheApi} /** * Aggregates all available redis APIs into a single handler. This simplifies @@ -27,16 +26,16 @@ private[redis] class RedisCachesProvider(instance: RedisInstance, serializer: co private implicit lazy val runtime: RedisRuntime = RedisRuntime(instance, instance.recovery, instance.invocationPolicy, instance.prefix)(system) - private implicit def implicitEnvironment = environment + private implicit def implicitEnvironment: Environment = environment lazy val get = new RedisCaches { - lazy val redisConnector = new connector.RedisConnectorProvider(instance, serializer).get - lazy val async = new AsyncRedisImpl(redisConnector) - lazy val sync = new SyncRedis(redisConnector) - lazy val scalaSync = new play.api.cache.DefaultSyncCacheApi(async) - lazy val scalaAsync = async - lazy val java = new AsyncJavaRedis(async) - lazy val javaAsync = java - lazy val javaSync = new play.cache.DefaultSyncCacheApi(java) + lazy val redisConnector: RedisConnector = new connector.RedisConnectorProvider(instance, serializer).get + lazy val async: AsyncRedis = new AsyncRedisImpl(redisConnector) + lazy val sync: CacheApi = new SyncRedis(redisConnector) + lazy val scalaSync: play.api.cache.SyncCacheApi = new play.api.cache.DefaultSyncCacheApi(async) + lazy val scalaAsync: play.api.cache.AsyncCacheApi = async + lazy val java: AsyncJavaRedis = new AsyncJavaRedis(async) + 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/RedisListJavaImpl.scala b/src/main/scala/play/api/cache/redis/impl/RedisListJavaImpl.scala index dab7f8bd..f4a9b68a 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisListJavaImpl.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisListJavaImpl.scala @@ -78,7 +78,7 @@ class RedisListJavaImpl[Elem](internal: RedisList[Elem, Future])(implicit runtim new AsyncRedisListModificationJavaImpl(internal.modify) } - class AsyncRedisListViewJavaImpl(view: internal.RedisListView) extends AsyncRedisList.AsyncRedisListView[Elem] { + private class AsyncRedisListViewJavaImpl(view: internal.RedisListView) extends AsyncRedisList.AsyncRedisListView[Elem] { def slice(from: Int, end: Int): CompletionStage[JavaList[Elem]] = { async { implicit context => @@ -87,7 +87,7 @@ class RedisListJavaImpl[Elem](internal: RedisList[Elem, Future])(implicit runtim } } - class AsyncRedisListModificationJavaImpl(modification: internal.RedisListModification) extends AsyncRedisList.AsyncRedisListModification[Elem] { + private class AsyncRedisListModificationJavaImpl(modification: internal.RedisListModification) extends AsyncRedisList.AsyncRedisListModification[Elem] { def collection(): AsyncRedisList[Elem] = This 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 7304bb1c..f474311e 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisSetJavaImpl.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisSetJavaImpl.scala @@ -1,32 +1,32 @@ package play.api.cache.redis.impl -import scala.concurrent.Future - import play.api.cache.redis.RedisSet import play.cache.redis.AsyncRedisSet +import scala.concurrent.Future + class RedisSetJavaImpl[Elem](internal: RedisSet[Elem, Future])(implicit runtime: RedisRuntime) extends AsyncRedisSet[Elem] { import JavaCompatibility._ - def add(element: Elem*): CompletionStage[AsyncRedisSet[Elem]] = { + override def add(elements: Elem*): CompletionStage[AsyncRedisSet[Elem]] = { async { implicit context => - internal.add(element: _*).map(_ => this) + internal.add(elements: _*).map(_ => this) } } - 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) } } - def remove(element: Elem*): CompletionStage[AsyncRedisSet[Elem]] = { + override def remove(elements: Elem*): CompletionStage[AsyncRedisSet[Elem]] = { async { implicit context => - internal.remove(element: _*).map(_ => this) + internal.remove(elements: _*).map(_ => this) } } - def toSet: CompletionStage[JavaSet[Elem]] = { + override def toSet: CompletionStage[JavaSet[Elem]] = { async { implicit context => internal.toSet.map(_.asJava) } diff --git a/src/test/scala-2.11/play/api/cache/redis/connector/ScalaSpecificSerializerSpec.scala b/src/test/scala-2.11/play/api/cache/redis/connector/ScalaSpecificSerializerSpec.scala deleted file mode 100644 index 949012dc..00000000 --- a/src/test/scala-2.11/play/api/cache/redis/connector/ScalaSpecificSerializerSpec.scala +++ /dev/null @@ -1,58 +0,0 @@ -package play.api.cache.redis.connector - -import java.util.Date - -import scala.reflect.ClassTag - -import play.api.inject.guice.GuiceApplicationBuilder -import play.api.cache.redis._ - -import org.joda.time.{DateTime, DateTimeZone} -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 - |cyRTaW1wbGVPYmplY3TbTGkeqUDNdwIAAkkABXZhbHVlTAADa2V5dAASTGphdmEvbGFuZy9TdHJp - |bmc7eHAAAAADdAABQg== - """.stripMargin.removeAllWhitespaces - } - - "list" in { - List("A", "B", "C").encoded mustEqual """ - |rO0ABXNyADJzY2FsYS5jb2xsZWN0aW9uLmltbXV0YWJsZS5MaXN0JFNlcmlhbGl6YXRpb25Qcm94 - |eQAAAAAAAAABAwAAeHB0AAFBdAABQnQAAUNzcgAsc2NhbGEuY29sbGVjdGlvbi5pbW11dGFibGUu - |TGlzdFNlcmlhbGl6ZUVuZCSKXGNb91MLbQIAAHhweA== - """.stripMargin.removeAllWhitespaces - } - } - - "AkkaDecoder" should "decode" >> { - - "custom classes" in { - """ - |rO0ABXNyAD9wbGF5LmFwaS5jYWNoZS5yZWRpcy5jb25uZWN0b3IuU2VyaWFsaXplckltcGxpY2l0 - |cyRTaW1wbGVPYmplY3TbTGkeqUDNdwIAAkkABXZhbHVlTAADa2V5dAASTGphdmEvbGFuZy9TdHJp - |bmc7eHAAAAADdAABQg== - """.stripMargin.removeAllWhitespaces.decoded[SimpleObject] mustEqual SimpleObject("B", 3) - } - - "list" in { - """ - |rO0ABXNyADJzY2FsYS5jb2xsZWN0aW9uLmltbXV0YWJsZS5MaXN0JFNlcmlhbGl6YXRpb25Qcm94 - |eQAAAAAAAAABAwAAeHB0AAFBdAABQnQAAUNzcgAsc2NhbGEuY29sbGVjdGlvbi5pbW11dGFibGUu - |TGlzdFNlcmlhbGl6ZUVuZCSKXGNb91MLbQIAAHhweA== - """.stripMargin.removeAllWhitespaces.decoded[List[String]] mustEqual List("A", "B", "C") - } - } -} diff --git a/src/test/scala-2.12/play/api/cache/redis/connector/ScalaSpecificSerializerSpec.scala b/src/test/scala-2.12/play/api/cache/redis/connector/ScalaSpecificSerializerSpec.scala deleted file mode 100644 index 0671b42c..00000000 --- a/src/test/scala-2.12/play/api/cache/redis/connector/ScalaSpecificSerializerSpec.scala +++ /dev/null @@ -1,58 +0,0 @@ -package play.api.cache.redis.connector - -import java.util.Date - -import scala.reflect.ClassTag - -import play.api.inject.guice.GuiceApplicationBuilder -import play.api.cache.redis._ - -import org.joda.time.{DateTime, DateTimeZone} -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 """ - |rO0ABXNyAD9wbGF5LmFwaS5jYWNoZS5yZWRpcy5jb25uZWN0b3IuU2VyaWFsaXplckltcGxpY2l0c - |yRTaW1wbGVPYmplY3TbTGkeqUDNdwIAAkkABXZhbHVlTAADa2V5dAASTGphdmEvbGFuZy9TdHJpbm - |c7eHAAAAADdAABQg== - """.stripMargin.removeAllWhitespaces - } - - "list" in { - List("A", "B", "C").encoded mustEqual """ - |rO0ABXNyADJzY2FsYS5jb2xsZWN0aW9uLmltbXV0YWJsZS5MaXN0JFNlcmlhbGl6YXRpb25Qcm94 - |eQAAAAAAAAABAwAAeHB0AAFBdAABQnQAAUNzcgAsc2NhbGEuY29sbGVjdGlvbi5pbW11dGFibGUu - |TGlzdFNlcmlhbGl6ZUVuZCSKXGNb91MLbQIAAHhweA== - """.stripMargin.removeAllWhitespaces - } - } - - "AkkaDecoder" should "decode" >> { - - "custom classes" in { - """ - |rO0ABXNyAD9wbGF5LmFwaS5jYWNoZS5yZWRpcy5jb25uZWN0b3IuU2VyaWFsaXplckltcGxpY2l0c - |yRTaW1wbGVPYmplY3TbTGkeqUDNdwIAAkkABXZhbHVlTAADa2V5dAASTGphdmEvbGFuZy9TdHJpbm - |c7eHAAAAADdAABQg== - """.stripMargin.removeAllWhitespaces.decoded[SimpleObject] mustEqual SimpleObject("B", 3) - } - - "list" in { - """ - |rO0ABXNyADJzY2FsYS5jb2xsZWN0aW9uLmltbXV0YWJsZS5MaXN0JFNlcmlhbGl6YXRpb25Qcm94 - |eQAAAAAAAAABAwAAeHB0AAFBdAABQnQAAUNzcgAsc2NhbGEuY29sbGVjdGlvbi5pbW11dGFibGUu - |TGlzdFNlcmlhbGl6ZUVuZCSKXGNb91MLbQIAAHhweA== - """.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 new file mode 100644 index 00000000..da359612 --- /dev/null +++ b/src/test/scala/play/api/cache/redis/ClusterRedisContainer.scala @@ -0,0 +1,20 @@ +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 b546c6f1..8f160d79 100644 --- a/src/test/scala/play/api/cache/redis/ExpirationSpec.scala +++ b/src/test/scala/play/api/cache/redis/ExpirationSpec.scala @@ -1,12 +1,11 @@ package play.api.cache.redis -import java.util.Date +import org.specs2.mutable.Specification +import java.time.Instant +import java.util.Date import scala.concurrent.duration._ -import org.joda.time._ -import org.specs2.mutable.Specification - /** *

This specification tests expiration conversion

*/ @@ -14,19 +13,19 @@ class ExpirationSpec extends Specification { "Expiration" should { - def expireAt = DateTime.now().plusMinutes(5).plusSeconds(30) + val expireAt: Instant = Instant.now().plusSeconds(5.minutes.toSeconds).plusSeconds(30) val expiration = 5.minutes + 30.seconds val expirationFrom = expiration - 2.second val expirationTo = expiration + 1.second "from java.util.Date" in { - new Date(expireAt.getMillis).asExpiration must beBetween(expirationFrom, expirationTo) + new Date(expireAt.toEpochMilli).asExpiration must beBetween(expirationFrom, expirationTo) } "from java.time.LocalDateTime" in { import java.time._ - LocalDateTime.ofInstant(expireAt.toInstant.toDate.toInstant, ZoneId.systemDefault()).asExpiration must beBetween(expirationFrom, expirationTo) + LocalDateTime.ofInstant(expireAt, ZoneId.systemDefault()).asExpiration must beBetween(expirationFrom, expirationTo) } } } diff --git a/src/test/scala/play/api/cache/redis/ForAllTestContainer.scala b/src/test/scala/play/api/cache/redis/ForAllTestContainer.scala new file mode 100644 index 00000000..224f0a7c --- /dev/null +++ b/src/test/scala/play/api/cache/redis/ForAllTestContainer.scala @@ -0,0 +1,19 @@ +package play.api.cache.redis + +import com.dimafeng.testcontainers.SingleContainer +import org.specs2.specification.BeforeAfterAll + +trait ForAllTestContainer extends BeforeAfterAll { + + def newContainer: SingleContainer[_] + + final protected lazy val container = newContainer + + override def beforeAll(): Unit = { + container.start() + } + + override def afterAll(): Unit = { + container.stop() + } +} diff --git a/src/test/scala/play/api/cache/redis/Implicits.scala b/src/test/scala/play/api/cache/redis/Implicits.scala index 671c37b3..abd74bf5 100644 --- a/src/test/scala/play/api/cache/redis/Implicits.scala +++ b/src/test/scala/play/api/cache/redis/Implicits.scala @@ -42,7 +42,7 @@ object Implicits { implicit def implicitlyAny2failure(ex: Throwable): Try[Nothing] = Failure(ex) implicit class FutureAwait[T](val future: Future[T]) extends AnyVal { - def await = Await.result(future, 2.minutes) + def awaitForFuture = Await.result(future, 2.minutes) } implicit def implicitlyAny2Callable[T](f: => T): Callable[T] = new Callable[T] { @@ -73,9 +73,12 @@ trait ReducedMockito extends MocksCreation with MockitoStubs with CapturedArgument // with MockitoMatchers - with ArgThat + // with ArgThat with Expectations - with MockitoFunctions + with MockitoFunctions { + + override def argThat[T, U <: T](m: org.specs2.matcher.Matcher[U]): T = super.argThat(m) +} object MockitoImplicits extends ReducedMockito @@ -85,13 +88,13 @@ trait WithApplication { protected def builder = new GuiceApplicationBuilder() - private val theBuilder = builder + private lazy val theBuilder = builder - protected val injector = theBuilder.injector + protected lazy val injector = theBuilder.injector() - protected val application: Application = injector.instanceOf[Application] + protected lazy val application: Application = injector.instanceOf[Application] - implicit protected val system = injector.instanceOf[ActorSystem] + implicit protected lazy val system: ActorSystem = injector.instanceOf[ActorSystem] } trait WithHocon { @@ -101,13 +104,13 @@ trait WithHocon { protected def hocon: String - protected val config = { + protected lazy val config = { val reference = ConfigFactory.load() val local = ConfigFactory.parseString(hocon.stripMargin) local.withFallback(reference) } - protected val configuration = Configuration(config) + protected lazy val configuration = Configuration(config) } abstract class WithConfiguration(val hocon: String) extends WithHocon with Around with Scope { diff --git a/src/test/scala/play/api/cache/redis/RecoveryPolicySpec.scala b/src/test/scala/play/api/cache/redis/RecoveryPolicySpec.scala index cd748a9a..172f089a 100644 --- a/src/test/scala/play/api/cache/redis/RecoveryPolicySpec.scala +++ b/src/test/scala/play/api/cache/redis/RecoveryPolicySpec.scala @@ -5,10 +5,9 @@ import scala.concurrent.Future import play.api.Logger import org.specs2.concurrent.ExecutionEnv -import org.specs2.mock.Mockito import org.specs2.mutable.Specification -class RecoveryPolicySpec(implicit ee: ExecutionEnv) extends Specification with Mockito { +class RecoveryPolicySpec(implicit ee: ExecutionEnv) extends Specification with ReducedMockito { class BasicPolicy extends RecoveryPolicy { def recoverFrom[T](rerun: => Future[T], default: => Future[T], failure: RedisException) = default @@ -25,7 +24,7 @@ class RecoveryPolicySpec(implicit ee: ExecutionEnv) extends Specification with M val failedKey = ExecutionFailedException(Some("key"), "TEST-CMD", "TEST-CMD key value", internal) val timeout = TimeoutException(internal) val serialization = SerializationException("some key", "TEST-CMD", internal) - def any = unexpectedAny + def any: UnexpectedResponseException = unexpectedAny } "Recovery Policy" should { diff --git a/src/test/scala/play/api/cache/redis/RedisCacheComponentsSpec.scala b/src/test/scala/play/api/cache/redis/RedisCacheComponentsSpec.scala index 07258ed1..f8ca9019 100644 --- a/src/test/scala/play/api/cache/redis/RedisCacheComponentsSpec.scala +++ b/src/test/scala/play/api/cache/redis/RedisCacheComponentsSpec.scala @@ -2,23 +2,23 @@ package play.api.cache.redis import play.api._ import play.api.inject.ApplicationLifecycle - import org.specs2.mutable.Specification -import org.specs2.specification.AfterAll -class RedisCacheComponentsSpec extends Specification with WithApplication with AfterAll { +class RedisCacheComponentsSpec extends Specification with WithApplication with StandaloneRedisContainer { + import Implicits._ - object components extends RedisCacheComponents { + object components extends RedisCacheComponents with WithHocon { def actorSystem = system def applicationLifecycle = injector.instanceOf[ApplicationLifecycle] def environment = injector.instanceOf[Environment] - def configuration = injector.instanceOf[Configuration] - val syncRedis = cacheApi("play").sync + 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 - private val cache = components.syncRedis + private lazy val cache = components.syncRedis val prefix = "components-sync" @@ -42,7 +42,8 @@ class RedisCacheComponentsSpec extends Specification with WithApplication with A } } - def afterAll() = { - Shutdown.run + override def afterAll() = { + Shutdown.run.awaitForFuture + super.afterAll() } } diff --git a/src/test/scala/play/api/cache/redis/RedisCacheModuleSpec.scala b/src/test/scala/play/api/cache/redis/RedisCacheModuleSpec.scala index 13db7d7a..8735aa78 100644 --- a/src/test/scala/play/api/cache/redis/RedisCacheModuleSpec.scala +++ b/src/test/scala/play/api/cache/redis/RedisCacheModuleSpec.scala @@ -163,16 +163,17 @@ object RedisCacheModuleSpec { object MyRedisInstance extends RedisStandalone { - def name = defaultCacheName - def invocationContext = "akka.actor.default-dispatcher" - def invocationPolicy = "lazy" - def timeout = RedisTimeouts(1.second) - def recovery = "log-and-default" - def source = "my-instance" - def prefix = None - def host = localhost - def port = defaultPort - def database = None - def password = None + 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 } } diff --git a/src/test/scala/play/api/cache/redis/RedisContainer.scala b/src/test/scala/play/api/cache/redis/RedisContainer.scala new file mode 100644 index 00000000..b0682d46 --- /dev/null +++ b/src/test/scala/play/api/cache/redis/RedisContainer.scala @@ -0,0 +1,19 @@ +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/RedisContainerConfig.scala b/src/test/scala/play/api/cache/redis/RedisContainerConfig.scala new file mode 100644 index 00000000..f5cdb544 --- /dev/null +++ b/src/test/scala/play/api/cache/redis/RedisContainerConfig.scala @@ -0,0 +1,7 @@ +package play.api.cache.redis + +final case class RedisContainerConfig( + redisDockerImage: String, + redisPorts: Seq[Int], + redisEnvironment: Map[String, String], +) diff --git a/src/test/scala/play/api/cache/redis/StandaloneRedisContainer.scala b/src/test/scala/play/api/cache/redis/StandaloneRedisContainer.scala new file mode 100644 index 00000000..505e0276 --- /dev/null +++ b/src/test/scala/play/api/cache/redis/StandaloneRedisContainer.scala @@ -0,0 +1,11 @@ +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/RedisHostSpec.scala b/src/test/scala/play/api/cache/redis/configuration/RedisHostSpec.scala index eebca540..312f7120 100644 --- a/src/test/scala/play/api/cache/redis/configuration/RedisHostSpec.scala +++ b/src/test/scala/play/api/cache/redis/configuration/RedisHostSpec.scala @@ -1,13 +1,13 @@ package play.api.cache.redis.configuration import play.api.cache.redis._ - import org.specs2.mutable.Specification +import play.api.ConfigLoader class RedisHostSpec extends Specification { import Implicits._ - private implicit val loader = RedisHost + private implicit val loader: ConfigLoader[RedisHost] = RedisHost "host with database and password" in new WithConfiguration( """ @@ -34,7 +34,7 @@ class RedisHostSpec extends Specification { } "host from connection string" in { - RedisHost.fromConnectionString("redis://redis:something@localhost:6378") mustEqual RedisHost("localhost", 6378, password = "something") + 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] diff --git a/src/test/scala/play/api/cache/redis/configuration/RedisInstanceManagerTest.scala b/src/test/scala/play/api/cache/redis/configuration/RedisInstanceManagerTest.scala index 8a6956ad..7c81c3dd 100644 --- a/src/test/scala/play/api/cache/redis/configuration/RedisInstanceManagerTest.scala +++ b/src/test/scala/play/api/cache/redis/configuration/RedisInstanceManagerTest.scala @@ -1,5 +1,6 @@ package play.api.cache.redis.configuration +import play.api.ConfigLoader import play.api.cache.redis._ /** @@ -18,7 +19,7 @@ case class RedisInstanceManagerTest(default: String)(providers: RedisInstancePro abstract class WithRedisInstanceManager(hocon: String) extends WithConfiguration(hocon) { - private implicit val loader = RedisInstanceManager + 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 71a47652..0a9e016e 100644 --- a/src/test/scala/play/api/cache/redis/configuration/RedisInstanceProviderSpec.scala +++ b/src/test/scala/play/api/cache/redis/configuration/RedisInstanceProviderSpec.scala @@ -7,7 +7,7 @@ class RedisInstanceProviderSpec extends Specification { val defaultCache = RedisStandalone(defaultCacheName, RedisHost(localhost, defaultPort, database = 0), defaults) - implicit val resolver = new RedisInstanceResolver { + implicit val resolver: RedisInstanceResolver = new RedisInstanceResolver { def resolve = { case `defaultCacheName` => defaultCache } 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 066afa59..3c3b8f79 100644 --- a/src/test/scala/play/api/cache/redis/connector/RedisClusterSpec.scala +++ b/src/test/scala/play/api/cache/redis/connector/RedisClusterSpec.scala @@ -15,19 +15,31 @@ import org.specs2.specification.{AfterAll, BeforeAll} /** *

Specification of the low level connector implementing basic commands

*/ -class RedisClusterSpec(implicit ee: ExecutionEnv) extends Specification with BeforeAll with AfterAll with WithApplication { +class RedisClusterSpec(implicit ee: ExecutionEnv) extends Specification with WithApplication with ClusterRedisContainer { + + args(skipAll=true) import Implicits._ - implicit private val lifecycle = application.injector.instanceOf[ApplicationLifecycle] + implicit private val lifecycle: ApplicationLifecycle = application.injector.instanceOf[ApplicationLifecycle] - implicit private val runtime = RedisRuntime("cluster", syncTimeout = 5.seconds, ExecutionContext.global, new LogAndFailPolicy, LazyInvocation) + implicit private val runtime: RedisRuntime = RedisRuntime("cluster", syncTimeout = 5.seconds, ExecutionContext.global, new LogAndFailPolicy, LazyInvocation) private val serializer = new AkkaSerializerImpl(system) - private val clusterInstance = RedisCluster(defaultCacheName, nodes = RedisHost(dockerIp, 7000) :: RedisHost(dockerIp, 7001) :: RedisHost(dockerIp, 7002) :: RedisHost(dockerIp, 7003) :: Nil, defaults) + 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 val connector: RedisConnector = new RedisConnectorProvider(clusterInstance, serializer).get + private lazy val connector: RedisConnector = new RedisConnectorProvider(clusterInstance, serializer).get val prefix = "cluster-test" @@ -71,12 +83,16 @@ class RedisClusterSpec(implicit ee: ExecutionEnv) extends Specification with Bef } } - def beforeAll() = { + override def beforeAll() = { + super.beforeAll() // initialize the connector by flushing the database - connector.matching(s"$prefix-*").flatMap(connector.remove).await + connector.matching(s"$prefix-*").flatMap { + keys => Future.sequence(keys.map(connector.remove(_))) + }.awaitForFuture } - def afterAll() = { - Shutdown.run + override def afterAll() = { + Shutdown.run.awaitForFuture + super.afterAll() } } 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 a1c10fa4..6a2221d0 100644 --- a/src/test/scala/play/api/cache/redis/connector/RedisConnectorFailureSpec.scala +++ b/src/test/scala/play/api/cache/redis/connector/RedisConnectorFailureSpec.scala @@ -40,7 +40,7 @@ class RedisConnectorFailureSpec(implicit ee: ExecutionEnv) extends Specification } "fail when decoder fails" in new MockedConnector { - serializer.decode(anyString)(any[ClassTag[_]]) returns simulatedFailure + serializer.decode(anyString)(any()) returns simulatedFailure commands.get[String](key) returns someValue // run the test connector.get[String](key) must throwA[SerializationException].await diff --git a/src/test/scala/play/api/cache/redis/connector/RedisConnectorSpec.scala b/src/test/scala/play/api/cache/redis/connector/RedisConnectorSpec.scala index 48f2be72..dcbfc156 100644 --- a/src/test/scala/play/api/cache/redis/connector/RedisConnectorSpec.scala +++ b/src/test/scala/play/api/cache/redis/connector/RedisConnectorSpec.scala @@ -1,29 +1,31 @@ package play.api.cache.redis.connector -import scala.concurrent.duration._ -import scala.concurrent.{ExecutionContext, Future} - +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 org.specs2.concurrent.ExecutionEnv -import org.specs2.mutable.Specification -import org.specs2.specification.{AfterAll, BeforeAll} +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 BeforeAll with AfterAll with WithApplication { +class RedisConnectorSpec(implicit ee: ExecutionEnv) extends Specification with WithApplication with StandaloneRedisContainer { import Implicits._ - implicit private val lifecycle = application.injector.instanceOf[ApplicationLifecycle] + implicit private val lifecycle: ApplicationLifecycle = application.injector.instanceOf[ApplicationLifecycle] - implicit private val runtime = RedisRuntime("connector", syncTimeout = 5.seconds, ExecutionContext.global, new LogAndFailPolicy, LazyInvocation) + implicit private val runtime: RedisRuntime = RedisRuntime("connector", syncTimeout = 5.seconds, ExecutionContext.global, new LogAndFailPolicy, LazyInvocation) private val serializer = new AkkaSerializerImpl(system) - private val connector: RedisConnector = new RedisConnectorProvider(defaultInstance, serializer).get + private lazy val connector: RedisConnector = new RedisConnectorProvider( + RedisStandalone(defaultCacheName, RedisHost(container.containerIpAddress, container.mappedPort(defaultPort)), defaults), + serializer + ).get val prefix = "connector-test" @@ -67,11 +69,11 @@ class RedisConnectorSpec(implicit ee: ExecutionEnv) extends Specification with B } "hit after mset" in new TestCase { - connector.mSet(s"$prefix-mset-$idx-1" -> "value-1", s"$prefix-mset-$idx-2" -> "value-2").await + 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).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).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 } @@ -84,7 +86,7 @@ class RedisConnectorSpec(implicit ee: ExecutionEnv) extends Specification with B "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).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 @@ -174,7 +176,7 @@ class RedisConnectorSpec(implicit ee: ExecutionEnv) extends Specification with B 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").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 @@ -187,7 +189,7 @@ class RedisConnectorSpec(implicit ee: ExecutionEnv) extends Specification with B 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").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 @@ -235,14 +237,14 @@ class RedisConnectorSpec(implicit ee: ExecutionEnv) extends Specification with B "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").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").await + connector.append(s"$prefix-append-to-some", " value").awaitForFuture connector.get[String](s"$prefix-append-to-some") must beSome("some value").await } @@ -266,7 +268,7 @@ class RedisConnectorSpec(implicit ee: ExecutionEnv) extends Specification with B "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").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 } @@ -297,7 +299,7 @@ class RedisConnectorSpec(implicit ee: ExecutionEnv) extends Specification with B "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).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 } @@ -380,7 +382,7 @@ class RedisConnectorSpec(implicit ee: ExecutionEnv) extends Specification with B val key = s"$prefix-hash-set" connector.hashSize(key) must beEqualTo(0).await - connector.hashGetAll(key) must beEqualTo(Map.empty).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 @@ -496,12 +498,14 @@ class RedisConnectorSpec(implicit ee: ExecutionEnv) extends Specification with B } } - def beforeAll() = { + override def beforeAll(): Unit = { + super.beforeAll() // initialize the connector by flushing the database - connector.matching(s"$prefix-*").flatMap(connector.remove).await + connector.matching(s"$prefix-*").flatMap(connector.remove).awaitForFuture } - def afterAll() = { - Shutdown.run + override def afterAll(): Unit = { + Shutdown.run.awaitForFuture + super.afterAll() } } 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 92471e05..6532f101 100644 --- a/src/test/scala/play/api/cache/redis/connector/RedisSentinelSpec.scala +++ b/src/test/scala/play/api/cache/redis/connector/RedisSentinelSpec.scala @@ -16,17 +16,19 @@ import scala.concurrent.{ExecutionContext, Future} */ class RedisSentinelSpec(implicit ee: ExecutionEnv) extends Specification with BeforeAll with AfterAll with WithApplication { + args(skipAll=true) + import Implicits._ - implicit private val lifecycle = application.injector.instanceOf[ApplicationLifecycle] + implicit private val lifecycle: ApplicationLifecycle = application.injector.instanceOf[ApplicationLifecycle] - implicit private val runtime = RedisRuntime("sentinel", syncTimeout = 5.seconds, ExecutionContext.global, new LogAndFailPolicy, LazyInvocation) + implicit private val runtime: RedisRuntime = RedisRuntime("sentinel", syncTimeout = 5.seconds, ExecutionContext.global, new LogAndFailPolicy, LazyInvocation) private val serializer = new AkkaSerializerImpl(system) - private val sentinelInstance = RedisSentinel(defaultCacheName, masterGroup = "sentinel5000", sentinels = RedisHost(dockerIp, 5000) :: RedisHost(dockerIp, 5001) :: RedisHost(dockerIp, 5002) :: Nil, defaults) + private lazy val sentinelInstance = RedisSentinel(defaultCacheName, masterGroup = "sentinel5000", sentinels = RedisHost(dockerIp, 5000) :: RedisHost(dockerIp, 5001) :: RedisHost(dockerIp, 5002) :: Nil, defaults) - private val connector: RedisConnector = new RedisConnectorProvider(sentinelInstance, serializer).get + private lazy val connector: RedisConnector = new RedisConnectorProvider(sentinelInstance, serializer).get val prefix = "sentinel-test" @@ -72,7 +74,7 @@ class RedisSentinelSpec(implicit ee: ExecutionEnv) extends Specification with Be def beforeAll(): Unit = { // initialize the connector by flushing the database - connector.matching(s"$prefix-*").flatMap(connector.remove).await + connector.matching(s"$prefix-*").flatMap(connector.remove).awaitForFuture } def afterAll(): Unit = { 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 77cd047b..a148cf35 100644 --- a/src/test/scala/play/api/cache/redis/connector/SerializerSpec.scala +++ b/src/test/scala/play/api/cache/redis/connector/SerializerSpec.scala @@ -2,12 +2,9 @@ package play.api.cache.redis.connector import java.util.Date -import scala.reflect.ClassTag - import play.api.inject.guice.GuiceApplicationBuilder import play.api.cache.redis._ -import org.joda.time.{DateTime, DateTimeZone} import org.specs2.mock.Mockito import org.specs2.mutable.Specification @@ -67,16 +64,6 @@ class SerializerSpec extends Specification with Mockito { new Date(123).encoded mustEqual "rO0ABXNyAA5qYXZhLnV0aWwuRGF0ZWhqgQFLWXQZAwAAeHB3CAAAAAAAAAB7eA==" } - "datetime" in { - new DateTime(123456L, DateTimeZone.forID("UTC")).encoded mustEqual """ - |rO0ABXNyABZvcmcuam9kYS50aW1lLkRhdGVUaW1luDx4ZGpb3fkCAAB4cgAfb3JnLmpvZGEudGlt - |ZS5iYXNlLkJhc2VEYXRlVGltZf//+eFPXS6jAgACSgAHaU1pbGxpc0wAC2lDaHJvbm9sb2d5dAAa - |TG9yZy9qb2RhL3RpbWUvQ2hyb25vbG9neTt4cAAAAAAAAeJAc3IAJ29yZy5qb2RhLnRpbWUuY2hy - |b25vLklTT0Nocm9ub2xvZ3kkU3R1YqnIEWZxN1AnAwAAeHBzcgAfb3JnLmpvZGEudGltZS5EYXRl - |VGltZVpvbmUkU3R1YqYvAZp8MhrjAwAAeHB3BQADVVRDeHg= - """.stripMargin.removeAllWhitespaces - } - "null" in { new ValueEncoder(null).encoded must throwA[UnsupportedOperationException] } @@ -134,18 +121,8 @@ class SerializerSpec extends Specification with Mockito { "rO0ABXNyAA5qYXZhLnV0aWwuRGF0ZWhqgQFLWXQZAwAAeHB3CAAAAAAAAAB7eA==".decoded[Date] mustEqual new Date(123) } - "datetime" in { - """ - |rO0ABXNyABZvcmcuam9kYS50aW1lLkRhdGVUaW1luDx4ZGpb3fkCAAB4cgAfb3JnLmpvZGEudGlt - |ZS5iYXNlLkJhc2VEYXRlVGltZf//+eFPXS6jAgACSgAHaU1pbGxpc0wAC2lDaHJvbm9sb2d5dAAa - |TG9yZy9qb2RhL3RpbWUvQ2hyb25vbG9neTt4cAAAAAAAAeJAc3IAJ29yZy5qb2RhLnRpbWUuY2hy - |b25vLklTT0Nocm9ub2xvZ3kkU3R1YqnIEWZxN1AnAwAAeHBzcgAfb3JnLmpvZGEudGltZS5EYXRl - |VGltZVpvbmUkU3R1YqYvAZp8MhrjAwAAeHB3BQADVVRDeHg= - """.stripMargin.removeAllWhitespaces.decoded[DateTime] mustEqual new DateTime(123456L, DateTimeZone.forID("UTC")) - } - - "forgotten type" in { - def decoded: String = "something".decoded + "invalid type" in { + def decoded: Date = "something".decoded[Date] decoded must throwA[IllegalArgumentException] } } 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 add0174b..d98d4311 100644 --- a/src/test/scala/play/api/cache/redis/impl/AsyncRedisSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/AsyncRedisSpec.scala @@ -42,7 +42,7 @@ class AsyncRedisSpec(implicit ee: ExecutionEnv) extends Specification with Reduc "getOrElseUpdate (failing orElse)" in new MockedAsyncRedis with OrElse { connector.get[String](anyString)(anyClassTag) returns None - cache.getOrElseUpdate(key)(failedFuture) must throwA[TimeoutException].await + cache.getOrElseUpdate[String](key)(failedFuture) must throwA[TimeoutException].await orElse mustEqual 2 } 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 829439c9..7ea241d5 100644 --- a/src/test/scala/play/api/cache/redis/impl/BuildersSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/BuildersSpec.scala @@ -11,7 +11,7 @@ import org.specs2.mock.Mockito import org.specs2.mutable.Specification import org.specs2.specification._ -class BuildersSpec(implicit ee: ExecutionEnv) extends Specification with Mockito with WithApplication { +class BuildersSpec(implicit ee: ExecutionEnv) extends Specification with ReducedMockito with WithApplication { import Builders._ import BuildersSpec._ diff --git a/src/test/scala/play/api/cache/redis/impl/RedisCacheImplicits.scala b/src/test/scala/play/api/cache/redis/impl/RedisCacheImplicits.scala index fccf66aa..7db3b3c6 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisCacheImplicits.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisCacheImplicits.scala @@ -46,7 +46,7 @@ object RedisCacheImplicits { protected def policy: RecoveryPolicy = new RecoverWithDefault {} - protected implicit val runtime = mock[RedisRuntime] + protected implicit val runtime: RedisRuntime = mock[RedisRuntime] runtime.context returns ExecutionContext.global runtime.invocation returns invocation runtime.prefix returns RedisEmptyPrefix @@ -104,7 +104,7 @@ object RedisCacheImplicits { protected val classTagKey = s"classTag::$key" protected val classTagOther = s"classTag::$other" - protected implicit val environment = mock[Environment] + protected implicit val environment: Environment = mock[Environment] protected val async = mock[AsyncRedis] protected val cache: play.cache.redis.AsyncCacheApi = new AsyncJavaRedis(async) 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 18bab084..2a6a0ac7 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisCacheSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisCacheSpec.scala @@ -13,8 +13,6 @@ class RedisCacheSpec(implicit ee: ExecutionEnv) extends Specification with Reduc import org.mockito.ArgumentMatchers._ - val key = "key" - val value = "value" val expiration = 1.second "Redis Cache" should { @@ -213,7 +211,7 @@ class RedisCacheSpec(implicit ee: ExecutionEnv) extends Specification with Reduc "get or future (failing orElse)" in new MockedCache with OrElse { connector.get[String](anyString)(anyClassTag) returns None - cache.getOrFuture(key)(failedFuture) must throwA[TimeoutException].await + cache.getOrFuture[String](key)(failedFuture) must throwA[TimeoutException].await orElse mustEqual 2 } 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 31891f64..96390493 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisRuntimeSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisRuntimeSpec.scala @@ -8,7 +8,7 @@ class RedisRuntimeSpec extends Specification with WithApplication { import Implicits._ - implicit val recoveryResolver = new RecoveryPolicyResolverImpl + implicit val recoveryResolver: RecoveryPolicyResolver = new RecoveryPolicyResolverImpl "RedisRuntime" should { import RedisRuntime._