diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml
index 215b0d6..eac2e3f 100644
--- a/.github/workflows/build-test.yml
+++ b/.github/workflows/build-test.yml
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-22.04
timeout-minutes: 15
env:
- SBT_OPTS: -Dfile.encoding=UTF-8 -Duser.timezone=UTC
+ SBT_OPTS: "-Dfile.encoding=UTF-8 -Duser.timezone=UTC -Xmx2g"
steps:
- name: Checkout code
uses: actions/checkout@v4
@@ -31,8 +31,8 @@ jobs:
jvm: adoptium:1.11
- name: Build
- timeout-minutes: 10
+ timeout-minutes: 15
env:
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
- sbt --client "+clean; +compile; +Test/compile; lint; +coverage; +test; +coverageReport; +coveralls;"
+ sbt -v "+clean; +compile; +Test/compile; lint; +coverage; +test; +coverageReport; +coveralls;"
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 9807381..01c2dc1 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -13,7 +13,7 @@ jobs:
timeout-minutes: 15
environment: "Generally Available"
env:
- SBT_OPTS: -Dfile.encoding=UTF-8 -Duser.timezone=UTC
+ SBT_OPTS: "-Dfile.encoding=UTF-8 -Duser.timezone=UTC -Xmx2g"
steps:
- name: Checkout code
uses: actions/checkout@v4
@@ -36,10 +36,10 @@ jobs:
echo $PGP_SECRET | base64 --decode | gpg --batch --import
- name: Test and Publish JARs
- timeout-minutes: 10
+ timeout-minutes: 15
env:
PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }}
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
run: |
- sbt --client "+test; +publishSigned; sonatypeBundleRelease;"
+ sbt -v "+test; +publishSigned; sonatypeBundleRelease;"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 529fe8c..83c251f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,8 @@
Migration to Pekko and Play 3.0, thanks to @TomJKing for help in [#272](https://github.com/KarelCemus/play-redis/pull/272),
finished in [#278](https://github.com/KarelCemus/play-redis/pull/278)
+Added support to Scala 3 in [#264](https://github.com/KarelCemus/play-redis/issues/264)
+
### [:link: 3.0.0](https://github.com/KarelCemus/play-redis/tree/3.0.0-M1)
Updated to Play `2.9.0` and dropped `Scala 2.12` since it was discontinued from the Play framework.
diff --git a/README.md b/README.md
index 55c38e8..77d9ee8 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
# Redis Cache module for Play framework
- **This version supports Play framework 3.0.x with JDK 11 and Scala 2.13. Scala 3 on the roadmap.**
+ **This version supports Play framework 3.0.x with JDK 11 and Scala 2.13 and Scala 3.**
**For previous versions see older releases.**
[![Travis CI: Status](https://travis-ci.org/KarelCemus/play-redis.svg?branch=master)](https://travis-ci.org/KarelCemus/play-redis)
diff --git a/build.sbt b/build.sbt
index da353ca..d8e4e14 100644
--- a/build.sbt
+++ b/build.sbt
@@ -11,11 +11,11 @@ description := "Redis cache plugin for the Play framework 2"
organization := "com.github.karelcemus"
-crossScalaVersions := Seq("2.13.12") //, "3.3.0"
+crossScalaVersions := Seq("2.13.12", "3.3.1")
scalaVersion := crossScalaVersions.value.head
-playVersion := "3.0.0"
+playVersion := "3.0.1"
libraryDependencies ++= Seq(
// play framework cache API
@@ -23,8 +23,8 @@ libraryDependencies ++= Seq(
// redis connector
"io.github.rediscala" %% "rediscala" % "1.14.0-pekko",
// test framework with mockito extension
- "org.scalatest" %% "scalatest" % "3.2.17" % Test,
- "org.scalamock" %% "scalamock" % "5.2.0" % Test,
+ "org.scalatest" %% "scalatest" % "3.2.18" % Test,
+ "org.scalamock" %% "scalamock" % "6.0.0-M1" % Test,
// test module for play framework
"org.playframework" %% "play-test" % playVersion.value % Test,
// to run integration tests
@@ -37,7 +37,11 @@ resolvers ++= Seq(
javacOptions ++= Seq("-Xlint:unchecked", "-encoding", "UTF-8")
-scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked", "-Ywarn-unused")
+scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked")
+
+scalacOptions ++= {
+ if (scalaVersion.value.startsWith("2.")) Seq("-Ywarn-unused") else Seq.empty
+}
enablePlugins(CustomReleasePlugin)
@@ -47,7 +51,6 @@ 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)
@@ -57,6 +60,7 @@ wartremoverWarnings ++= Warts.allBut(
Wart.AsInstanceOf,
Wart.AutoUnboxing,
Wart.DefaultArguments,
+ Wart.FinalVal,
Wart.GlobalExecutionContext,
Wart.ImplicitConversion,
Wart.ImplicitParameter,
diff --git a/src/main/scala/play/api/cache/redis/impl/RedisSetJavaImpl.scala b/src/main/scala-2.13/RedisSetJavaImpl.scala
similarity index 100%
rename from src/main/scala/play/api/cache/redis/impl/RedisSetJavaImpl.scala
rename to src/main/scala-2.13/RedisSetJavaImpl.scala
diff --git a/src/main/scala-3/play/api/cache/redis/impl/RedisSetJavaImpl.scala b/src/main/scala-3/play/api/cache/redis/impl/RedisSetJavaImpl.scala
new file mode 100644
index 0000000..2730576
--- /dev/null
+++ b/src/main/scala-3/play/api/cache/redis/impl/RedisSetJavaImpl.scala
@@ -0,0 +1,31 @@
+package play.api.cache.redis.impl
+
+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.*
+
+ override def add(elements: Array[? <: Elem]): CompletionStage[AsyncRedisSet[Elem]] =
+ async { implicit context =>
+ internal.add(elements.toSeq: _*).map(_ => this)
+ }
+
+ override def contains(element: Elem): CompletionStage[java.lang.Boolean] =
+ async { implicit context =>
+ internal.contains(element).map(Boolean.box)
+ }
+
+ override def remove(elements: Array[? <: Elem]): CompletionStage[AsyncRedisSet[Elem]] =
+ async { implicit context =>
+ internal.remove(elements.toSeq: _*).map(_ => this)
+ }
+
+ override def toSet: CompletionStage[JavaSet[Elem]] =
+ async { implicit context =>
+ internal.toSet.map(_.asJava)
+ }
+
+}
diff --git a/src/main/scala/play/api/cache/redis/connector/PekkoSerializer.scala b/src/main/scala/play/api/cache/redis/connector/PekkoSerializer.scala
index 54c7c70..37ddc94 100644
--- a/src/main/scala/play/api/cache/redis/connector/PekkoSerializer.scala
+++ b/src/main/scala/play/api/cache/redis/connector/PekkoSerializer.scala
@@ -129,6 +129,7 @@ private[connector] class PekkoDecoder(serializer: Serialization) {
Base64.getDecoder.decode(base64)
/** deserializes the binary stream into the object */
+ @SuppressWarnings(Array("org.wartremover.warts.RedundantAsInstanceOf"))
private def binaryToAnyRef[T](binary: Array[Byte])(implicit classTag: ClassTag[T]): AnyRef =
serializer.deserialize(binary, classTag.runtimeClass.asInstanceOf[Class[? <: AnyRef]]).get
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 85e6dd9..0444d8d 100644
--- a/src/main/scala/play/api/cache/redis/connector/RedisConnectorImpl.scala
+++ b/src/main/scala/play/api/cache/redis/connector/RedisConnectorImpl.scala
@@ -256,7 +256,7 @@ private[connector] class RedisConnectorImpl(serializer: PekkoSerializer, redis:
case length =>
log.debug(s"Inserted $value into the list at '$key'. New size is $length.")
Some(length)
- } recover [Option[Long]] {
+ } recover {
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.")
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 6bd6837..0c36538 100644
--- a/src/main/scala/play/api/cache/redis/impl/InvocationPolicy.scala
+++ b/src/main/scala/play/api/cache/redis/impl/InvocationPolicy.scala
@@ -19,7 +19,7 @@ sealed trait InvocationPolicy {
object EagerInvocation extends InvocationPolicy {
override def invoke[T](f: => Future[Any], thenReturn: T)(implicit context: ExecutionContext): Future[T] = {
- f: Unit
+ val _ = f
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 5a3c9dc..7b64c93 100644
--- a/src/main/scala/play/api/cache/redis/impl/JavaCompatibility.scala
+++ b/src/main/scala/play/api/cache/redis/impl/JavaCompatibility.scala
@@ -21,7 +21,7 @@ private[impl] object JavaCompatibility extends JavaCompatibilityBase {
def apply[T](values: T*): JavaList[T] = {
val list = new java.util.ArrayList[T]()
- list.addAll(values.asJava): Unit
+ val _ = list.addAll(values.asJava)
list
}
diff --git a/src/main/scala/play/api/cache/redis/package.scala b/src/main/scala/play/api/cache/redis/package.scala
index ec73bbe..c9b2e40 100644
--- a/src/main/scala/play/api/cache/redis/package.scala
+++ b/src/main/scala/play/api/cache/redis/package.scala
@@ -21,8 +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[_], A](private val self: F[A]) extends AnyVal {
+ def =~=[T](other: F[T]): Boolean = self == other
}
}
diff --git a/src/test/scala-2.13/play/api/cache/redis/connector/RedisCommandsMock.scala b/src/test/scala-2.13/play/api/cache/redis/connector/RedisCommandsMock.scala
new file mode 100644
index 0000000..d83e201
--- /dev/null
+++ b/src/test/scala-2.13/play/api/cache/redis/connector/RedisCommandsMock.scala
@@ -0,0 +1,28 @@
+package play.api.cache.redis.connector
+
+import org.scalamock.scalatest.AsyncMockFactory
+import redis.{ByteStringSerializer, RedisCommands}
+
+import scala.concurrent.Future
+
+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]
+
+ 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]
+}
+
+private object RedisCommandsMock {
+
+ def mock(factory: AsyncMockFactory): (RedisCommands, RedisCommandsMock) = {
+ val mock = factory.mock[RedisCommandsMock](factory)
+ (mock, mock)
+ }
+
+}
diff --git a/src/test/scala-2.13/play/api/cache/redis/impl/AsyncRedisMock.scala b/src/test/scala-2.13/play/api/cache/redis/impl/AsyncRedisMock.scala
new file mode 100644
index 0000000..2c3dfbc
--- /dev/null
+++ b/src/test/scala-2.13/play/api/cache/redis/impl/AsyncRedisMock.scala
@@ -0,0 +1,28 @@
+package play.api.cache.redis.impl
+
+import org.scalamock.scalatest.AsyncMockFactory
+import play.api.cache.redis.{AsynchronousResult, Done}
+
+import scala.reflect.ClassTag
+
+private 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](keys: Iterable[String]): AsynchronousResult[Seq[Option[T]]]
+}
+
+private object AsyncRedisMock {
+
+ def mock(factory: AsyncMockFactory): (AsyncRedis, AsyncRedisMock) = {
+ val mock = factory.mock[AsyncRedisMock](factory)
+ (mock, mock)
+ }
+
+}
diff --git a/src/test/scala-3/play/api/cache/redis/connector/RedisCommandsMock.scala b/src/test/scala-3/play/api/cache/redis/connector/RedisCommandsMock.scala
new file mode 100644
index 0000000..acd8439
--- /dev/null
+++ b/src/test/scala-3/play/api/cache/redis/connector/RedisCommandsMock.scala
@@ -0,0 +1,118 @@
+package play.api.cache.redis.connector
+
+import org.scalamock.scalatest.AsyncMockFactory
+import redis.*
+import redis.api.ListPivot
+import redis.protocol.RedisReply
+
+import scala.concurrent.{ExecutionContext, Future}
+
+// this is implemented due to a bug in ScalaMock 6.0.0-M1 and reported as https://github.com/paulbutcher/ScalaMock/issues/503
+private trait RedisCommandsMock {
+
+ def get[R: ByteStringDeserializer](key: String): Future[Option[R]]
+
+ def set[V: ByteStringSerializer](key: String, value: V, exSeconds: Option[Long], pxMilliseconds: Option[Long], NX: Boolean, XX: Boolean): Future[Boolean]
+
+ def expire(key: String, seconds: Long): Future[Boolean]
+
+ def mset[V: ByteStringSerializer](keysValues: Map[String, V]): Future[Boolean]
+
+ def msetnx[V: ByteStringSerializer](keysValues: Map[String, V]): Future[Boolean]
+
+ def incrby(key: String, increment: Long): Future[Long]
+
+ def lrange[R: ByteStringDeserializer](key: String, start: Long, stop: Long): Future[Seq[R]]
+
+ def lrem[V: ByteStringSerializer](key: String, count: Long, value: V): Future[Long]
+
+ def ltrim(key: String, start: Long, stop: Long): Future[Boolean]
+
+ def linsert[V: ByteStringSerializer](key: String, beforeAfter: ListPivot, pivot: String, value: V): Future[Long]
+
+ def hincrby(key: String, field: String, increment: Long): Future[Long]
+
+ def hset[V: ByteStringSerializer](key: String, field: String, value: V): Future[Boolean]
+
+ def zcard(key: String): Future[Long]
+
+ def zscore[V: ByteStringSerializer](key: String, member: V): Future[Option[Double]]
+
+ def zrange[V: ByteStringDeserializer](key: String, start: Long, stop: Long): Future[Seq[V]]
+
+ def zrevrange[V: ByteStringDeserializer](key: String, start: Long, stop: Long): Future[Seq[V]]
+
+ def zaddMock[V: ByteStringSerializer](key: String, scoreMembers: Seq[(Double, V)]): Future[Long]
+
+ def zremMock[V: ByteStringSerializer](key: String, members: Seq[V]): Future[Long]
+}
+
+private object RedisCommandsMock {
+
+ def mock(factory: AsyncMockFactory)(implicit ec: ExecutionContext): (RedisCommands, RedisCommandsMock) = {
+ val mock = factory.mock[RedisCommandsMock](factory)
+ (new RedisCommandsAdapter(mock), mock)
+ }
+
+}
+
+private class RedisCommandsAdapter(inner: RedisCommandsMock)(implicit override val executionContext: ExecutionContext) extends RedisCommands {
+
+ override def send[T](redisCommand: RedisCommand[? <: RedisReply, T]): Future[T] =
+ throw new IllegalStateException(s"Uncaught call to mock: $redisCommand")
+
+ final override def get[R: ByteStringDeserializer](key: String): Future[Option[R]] =
+ inner.get(key)
+
+ final override def set[V: ByteStringSerializer](key: String, value: V, exSeconds: Option[Long], pxMilliseconds: Option[Long], NX: Boolean, XX: Boolean): Future[Boolean] =
+ inner.set(key, value, exSeconds, pxMilliseconds, NX, XX)
+
+ final override def expire(key: String, seconds: Long): Future[Boolean] =
+ inner.expire(key, seconds)
+
+ final override def mset[V: ByteStringSerializer](keysValues: Map[String, V]): Future[Boolean] =
+ inner.mset(keysValues)
+
+ final override def msetnx[V: ByteStringSerializer](keysValues: Map[String, V]): Future[Boolean] =
+ inner.msetnx(keysValues)
+
+ final override def incrby(key: String, increment: Long): Future[Long] =
+ inner.incrby(key, increment)
+
+ final override def lrange[R: ByteStringDeserializer](key: String, start: Long, stop: Long): Future[Seq[R]] =
+ inner.lrange(key, start, stop)
+
+ final override def lrem[V: ByteStringSerializer](key: String, count: Long, value: V): Future[Long] =
+ inner.lrem(key, count, value)
+
+ final override def ltrim(key: String, start: Long, stop: Long): Future[Boolean] =
+ inner.ltrim(key, start, stop)
+
+ final override def linsert[V: ByteStringSerializer](key: String, beforeAfter: ListPivot, pivot: String, value: V): Future[Long] =
+ inner.linsert(key, beforeAfter, pivot, value)
+
+ final override def hincrby(key: String, field: String, increment: Long): Future[Long] =
+ inner.hincrby(key, field, increment)
+
+ final override def hset[V: ByteStringSerializer](key: String, field: String, value: V): Future[Boolean] =
+ inner.hset(key, field, value)
+
+ final override def zcard(key: String): Future[Long] =
+ inner.zcard(key)
+
+ final override def zscore[V: ByteStringSerializer](key: String, member: V): Future[Option[Double]] =
+ inner.zscore(key, member)
+
+ final override def zrange[V: ByteStringDeserializer](key: String, start: Long, stop: Long): Future[Seq[V]] =
+ inner.zrange(key, start, stop)
+
+ final override def zrevrange[V: ByteStringDeserializer](key: String, start: Long, stop: Long): Future[Seq[V]] =
+ inner.zrevrange(key, start, stop)
+
+ final override def zadd[V: ByteStringSerializer](key: String, scoreMembers: (Double, V)*): Future[Long] =
+ inner.zaddMock(key, scoreMembers)
+
+ final override def zrem[V: ByteStringSerializer](key: String, members: V*): Future[Long] =
+ inner.zremMock(key, members)
+
+}
diff --git a/src/test/scala-3/play/api/cache/redis/impl/AsyncRedisMock.scala b/src/test/scala-3/play/api/cache/redis/impl/AsyncRedisMock.scala
new file mode 100644
index 0000000..0f4233b
--- /dev/null
+++ b/src/test/scala-3/play/api/cache/redis/impl/AsyncRedisMock.scala
@@ -0,0 +1,156 @@
+package play.api.cache.redis.impl
+
+import org.apache.pekko.Done
+import org.scalamock.scalatest.AsyncMockFactory
+import play.api.cache.redis.*
+
+import scala.concurrent.duration.Duration
+import scala.concurrent.{ExecutionContext, Future}
+import scala.reflect.ClassTag
+
+// this is implemented due to a bug in ScalaMock 6.0.0-M1 and reported as https://github.com/paulbutcher/ScalaMock/issues/503
+private trait AsyncRedisMock {
+
+ def get[A: ClassTag](key: String): Future[Option[A]]
+
+ def getOrElseUpdate[A: ClassTag](key: String, expiration: Duration)(orElse: => Future[A]): Future[A]
+
+ def removeAll(): Future[Done]
+
+ def getAllKeys[T](keys: Iterable[String]): Future[Seq[Option[T]]]
+
+ def getOrElse[T: ClassTag](key: String, expiration: Duration)(orElse: => T): Future[T]
+
+ def getOrFuture[T: ClassTag](key: String, expiration: Duration)(orElse: => Future[T]): Future[T]
+
+ def exists(key: String): Future[Boolean]
+
+ def matching(pattern: String): Future[Seq[String]]
+
+ def set(key: String, value: Any, expiration: Duration): Future[Done]
+
+ def setIfNotExists(key: String, value: Any, expiration: Duration): Future[Boolean]
+
+ def setAll(keyValues: (String, Any)*): Future[Done]
+
+ def setAllIfNotExist(keyValues: (String, Any)*): Future[Boolean]
+
+ def append(key: String, value: String, expiration: Duration): Future[Done]
+
+ def expire(key: String, expiration: Duration): Future[Done]
+
+ def expiresIn(key: String): Future[Option[Duration]]
+
+ def remove(key: String): Future[Done]
+
+ def remove(key1: String, key2: String, keys: String*): Future[Done]
+
+ def removeAllKeys(keys: Seq[String]): Future[Done]
+
+ def removeMatching(pattern: String): Future[Done]
+
+ def invalidate(): Future[Done]
+
+ def increment(key: String, by: Long = 1): Future[Long]
+
+ def decrement(key: String, by: Long = 1): Future[Long]
+
+ def list[T: ClassTag](key: String): RedisList[T, Future]
+
+ def set[T: ClassTag](key: String): RedisSet[T, Future]
+
+ def map[T: ClassTag](key: String): RedisMap[T, Future]
+
+ def zset[T: ClassTag](key: String): RedisSortedSet[T, Future]
+}
+
+private object AsyncRedisMock {
+
+ def mock(factory: AsyncMockFactory)(implicit ec: ExecutionContext): (AsyncRedis, AsyncRedisMock) = {
+ val mock = factory.mock[AsyncRedisMock](factory)
+ (new AsyncRedisAdapter(mock), mock)
+ }
+
+}
+
+private class AsyncRedisAdapter(inner: AsyncRedisMock) extends AsyncRedis {
+
+ override def get[A: ClassTag](key: String): Future[Option[A]] =
+ inner.get(key)
+
+ override def getOrElseUpdate[A: ClassTag](key: String, expiration: Duration)(orElse: => Future[A]): Future[A] =
+ inner.getOrElseUpdate(key, expiration)(orElse)
+
+ override def removeAll(): Future[Done] =
+ inner.removeAll()
+
+ override def getAll[T: ClassTag](keys: Iterable[String]): Future[Seq[Option[T]]] =
+ inner.getAllKeys(keys)
+
+ override def getOrElse[T: ClassTag](key: String, expiration: Duration)(orElse: => T): Future[T] =
+ inner.getOrElse(key, expiration)(orElse)
+
+ override def getOrFuture[T: ClassTag](key: String, expiration: Duration)(orElse: => Future[T]): Future[T] =
+ inner.getOrFuture(key, expiration)(orElse)
+
+ override def exists(key: String): Future[Boolean] =
+ inner.exists(key)
+
+ override def matching(pattern: String): Future[Seq[String]] =
+ inner.matching(pattern)
+
+ override def set(key: String, value: Any, expiration: Duration): Future[Done] =
+ inner.set(key, value, expiration)
+
+ override def setIfNotExists(key: String, value: Any, expiration: Duration): Future[Boolean] =
+ inner.setIfNotExists(key, value, expiration)
+
+ override def setAll(keyValues: (String, Any)*): Future[Done] =
+ inner.setAll(keyValues: _*)
+
+ override def setAllIfNotExist(keyValues: (String, Any)*): Future[Boolean] =
+ inner.setAllIfNotExist(keyValues: _*)
+
+ override def append(key: String, value: String, expiration: Duration): Future[Done] =
+ inner.append(key, value, expiration)
+
+ override def expire(key: String, expiration: Duration): Future[Done] =
+ inner.expire(key, expiration)
+
+ override def expiresIn(key: String): Future[Option[Duration]] =
+ inner.expiresIn(key)
+
+ override def remove(key: String): Future[Done] =
+ inner.remove(key)
+
+ override def remove(key1: String, key2: String, keys: String*): Future[Done] =
+ inner.remove(key1, key2, keys: _*)
+
+ override def removeAll(keys: String*): Future[Done] =
+ inner.removeAllKeys(keys)
+
+ override def removeMatching(pattern: String): Future[Done] =
+ inner.removeMatching(pattern)
+
+ override def invalidate(): Future[Done] =
+ inner.invalidate()
+
+ override def increment(key: String, by: Long = 1): Future[Long] =
+ inner.increment(key, by)
+
+ override def decrement(key: String, by: Long = 1): Future[Long] =
+ inner.decrement(key, by)
+
+ override def list[T: ClassTag](key: String): RedisList[T, Future] =
+ inner.list(key)
+
+ override def set[T: ClassTag](key: String): RedisSet[T, Future] =
+ inner.set(key)
+
+ override def map[T: ClassTag](key: String): RedisMap[T, Future] =
+ inner.map(key)
+
+ override def zset[T: ClassTag](key: String): RedisSortedSet[T, Future] =
+ inner.zset(key)
+
+}
diff --git a/src/test/scala/play/api/cache/redis/RedisCacheComponentsSpec.scala b/src/test/scala/play/api/cache/redis/RedisCacheComponentsSpec.scala
index f662aeb..96b4b37 100644
--- a/src/test/scala/play/api/cache/redis/RedisCacheComponentsSpec.scala
+++ b/src/test/scala/play/api/cache/redis/RedisCacheComponentsSpec.scala
@@ -5,8 +5,12 @@ import play.api._
import play.api.cache.redis.test._
import play.api.inject.ApplicationLifecycle
+import scala.concurrent.duration._
+
class RedisCacheComponentsSpec extends IntegrationSpec with RedisStandaloneContainer {
+ override protected def testTimeout: FiniteDuration = 3.seconds
+
private val prefix = "components-sync"
test("miss on get") { cache =>
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 466d95e..d752db9 100644
--- a/src/test/scala/play/api/cache/redis/connector/RedisConnectorFailureSpec.scala
+++ b/src/test/scala/play/api/cache/redis/connector/RedisConnectorFailureSpec.scala
@@ -244,13 +244,13 @@ class RedisConnectorFailureSpec extends AsyncUnitSpec with ImplicitFutureMateria
private def test(name: String)(f: (SerializerAssertions, RedisCommandsMock, RedisConnector) => Future[Assertion]): Unit =
name in {
implicit val runtime: RedisRuntime = mock[RedisRuntime]
- val serializer = mock[PekkoSerializer]
- val commands = mock[RedisCommandsMock]
+ val serializer: PekkoSerializer = mock[PekkoSerializer]
+ val (commands: RedisCommands, mockedCommands: RedisCommandsMock) = RedisCommandsMock.mock(this)
val connector: RedisConnector = new RedisConnectorImpl(serializer, commands)
(() => runtime.context).expects().returns(ExecutionContext.global).anyNumberOfTimes()
- f(new SerializerAssertions(serializer), commands, connector)
+ f(new SerializerAssertions(serializer), mockedCommands, connector)
}
private class SerializerAssertions(mock: PekkoSerializer) {
@@ -272,17 +272,4 @@ class RedisConnectorFailureSpec extends AsyncUnitSpec with ImplicitFutureMateria
}
- 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]
-
- 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 b8053a9..3e94fac 100644
--- a/src/test/scala/play/api/cache/redis/connector/RedisRequestTimeoutSpec.scala
+++ b/src/test/scala/play/api/cache/redis/connector/RedisRequestTimeoutSpec.scala
@@ -13,7 +13,7 @@ class RedisRequestTimeoutSpec extends AsyncUnitSpec {
override protected def testTimeout: FiniteDuration = 3.seconds
"fail long running requests when connected but timeout defined" in {
- implicit val system: ActorSystem = ActorSystem("test")
+ implicit val system: ActorSystem = ActorSystem("test", classLoader = Some(getClass.getClassLoader))
val application = StoppableApplication(system)
application.runAsyncInApplication {
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 58ae80e..f91a3b7 100644
--- a/src/test/scala/play/api/cache/redis/connector/SerializerSpec.scala
+++ b/src/test/scala/play/api/cache/redis/connector/SerializerSpec.scala
@@ -196,5 +196,7 @@ object SerializerSpec {
}
/** Plain test object to be cached */
+ @SerialVersionUID(3363306882840417725L)
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 15739b0..dc57abf 100644
--- a/src/test/scala/play/api/cache/redis/impl/AsyncJavaRedisSpec.scala
+++ b/src/test/scala/play/api/cache/redis/impl/AsyncJavaRedisSpec.scala
@@ -10,7 +10,7 @@ import scala.concurrent.Future
import scala.concurrent.duration._
import scala.jdk.CollectionConverters.IterableHasAsScala
-class AsyncJavaRedisSpec extends AsyncUnitSpec with AsyncRedisMock with RedisRuntimeMock {
+class AsyncJavaRedisSpec extends AsyncUnitSpec with MockedAsyncRedis with RedisRuntimeMock {
import Helpers._
private val expiration = 5.seconds
@@ -359,10 +359,10 @@ class AsyncJavaRedisSpec extends AsyncUnitSpec with AsyncRedisMock with RedisRun
classLoader = getClass.getClassLoader,
mode = Mode.Test,
)
- val async = mock[AsyncRedisMock]
+ val (async: AsyncRedis, asyncMock: AsyncRedisMock) = AsyncRedisMock.mock(this)
val cache: play.cache.redis.AsyncCacheApi = new AsyncJavaRedis(async)
- f(async, cache)
+ f(asyncMock, cache)
}
}
diff --git a/src/test/scala/play/api/cache/redis/impl/AsyncRedisMock.scala b/src/test/scala/play/api/cache/redis/impl/MockedAsyncRedis.scala
similarity index 93%
rename from src/test/scala/play/api/cache/redis/impl/AsyncRedisMock.scala
rename to src/test/scala/play/api/cache/redis/impl/MockedAsyncRedis.scala
index ae66047..e09ccba 100644
--- a/src/test/scala/play/api/cache/redis/impl/AsyncRedisMock.scala
+++ b/src/test/scala/play/api/cache/redis/impl/MockedAsyncRedis.scala
@@ -2,25 +2,13 @@ package play.api.cache.redis.impl
import org.scalamock.scalatest.AsyncMockFactoryBase
import play.api.cache.redis._
+import play.api.cache.redis.test.ImplicitOptionMaterialization
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](keys: Iterable[String]): AsynchronousResult[Seq[Option[T]]]
- }
+private[impl] trait MockedAsyncRedis { this: AsyncMockFactoryBase =>
implicit final protected class AsyncRedisOps(async: AsyncRedisMock) {
@@ -29,7 +17,7 @@ private[impl] trait AsyncRedisMock { this: AsyncMockFactoryBase =>
}
- final protected class AsyncRedisExpectation(async: AsyncRedisMock) {
+ final protected class AsyncRedisExpectation(async: AsyncRedisMock) extends ImplicitOptionMaterialization {
private def classTagKey(key: String): String = s"classTag::$key"
@@ -45,7 +33,7 @@ private[impl] trait AsyncRedisMock { this: AsyncMockFactoryBase =>
def get[T: ClassTag](key: String, value: Option[T]): Future[Unit] =
Future.successful {
(async
- .get(_: String)(_: ClassTag[?]))
+ .get[T](_: String)(_: ClassTag[T]))
.expects(key, implicitly[ClassTag[T]])
.returning(Future.successful(value))
.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 e0061bc..c0dd485 100644
--- a/src/test/scala/play/api/cache/redis/impl/RedisConnectorMock.scala
+++ b/src/test/scala/play/api/cache/redis/impl/RedisConnectorMock.scala
@@ -2,6 +2,7 @@ package play.api.cache.redis.impl
import org.scalamock.scalatest.AsyncMockFactoryBase
import play.api.cache.redis._
+import play.api.cache.redis.test.ImplicitOptionMaterialization
import scala.concurrent.Future
import scala.concurrent.duration.Duration
@@ -90,12 +91,12 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase =>
}
- final protected class RedisConnectorExpectation(connector: RedisConnectorMock) {
+ final protected class RedisConnectorExpectation(connector: RedisConnectorMock) extends ImplicitOptionMaterialization {
def get[T: ClassTag](key: String, result: Try[Option[T]]): Future[Unit] =
Future.successful {
(connector
- .get(_: String)(_: ClassTag[?]))
+ .get(_: String)(_: ClassTag[T]))
.expects(key, implicitly[ClassTag[T]])
.returning(Future.fromTry(result))
.once()
@@ -110,7 +111,7 @@ 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[?]))
+ .mGetKeys(_: Seq[String])(_: ClassTag[T]))
.expects(keys, implicitly[ClassTag[T]])
.returning(result)
.once()
@@ -238,7 +239,7 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase =>
def listSlice[T: ClassTag](key: String, start: Long, end: Long, result: Future[Seq[T]]): Future[Unit] =
Future.successful {
(connector
- .listSlice(_: String, _: Long, _: Long)(_: ClassTag[?]))
+ .listSlice(_: String, _: Long, _: Long)(_: ClassTag[T]))
.expects(key, start, end, implicitly[ClassTag[T]])
.returning(result)
.once()
@@ -247,7 +248,7 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase =>
def listHeadPop[T: ClassTag](key: String, result: Future[Option[T]]): Future[Unit] =
Future.successful {
(connector
- .listHeadPop(_: String)(_: ClassTag[?]))
+ .listHeadPop(_: String)(_: ClassTag[T]))
.expects(key, implicitly[ClassTag[T]])
.returning(result)
.once()
@@ -339,10 +340,10 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase =>
.once()
}
- def setMembers[T: ClassTag](key: String, result: Future[Set[Any]]): Future[Unit] =
+ def setMembers[T: ClassTag](key: String, result: Future[Set[T]]): Future[Unit] =
Future.successful {
(connector
- .setMembers(_: String)(_: ClassTag[?]))
+ .setMembers(_: String)(_: ClassTag[T]))
.expects(key, implicitly[ClassTag[T]])
.returning(result)
.once()
@@ -384,19 +385,19 @@ private[impl] trait RedisConnectorMock { this: AsyncMockFactoryBase =>
.once()
}
- def sortedSetRange[T: ClassTag](key: String, start: Long, end: Long, result: Future[Seq[String]]): Future[Unit] =
+ def sortedSetRange[T: ClassTag](key: String, start: Long, end: Long, result: Future[Seq[T]]): Future[Unit] =
Future.successful {
(connector
- .sortedSetRange(_: String, _: Long, _: Long)(_: ClassTag[?]))
+ .sortedSetRange(_: String, _: Long, _: Long)(_: ClassTag[T]))
.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] =
+ def sortedSetReverseRange[T: ClassTag](key: String, start: Long, end: Long, result: Future[Seq[T]]): Future[Unit] =
Future.successful {
(connector
- .sortedSetReverseRange(_: String, _: Long, _: Long)(_: ClassTag[?]))
+ .sortedSetReverseRange(_: String, _: Long, _: Long)(_: ClassTag[T]))
.expects(key, start, end, implicitly[ClassTag[T]])
.returning(result)
.once()
@@ -423,7 +424,7 @@ 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[?]))
+ .hashGetField(_: String, _: String)(_: ClassTag[T]))
.expects(key, field, implicitly[ClassTag[T]])
.returning(result)
.once()
@@ -432,7 +433,7 @@ 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[?]))
+ .hashGetFields(_: String, _: Seq[String])(_: ClassTag[T]))
.expects(key, fields, implicitly[ClassTag[T]])
.returning(result)
.once()
@@ -468,7 +469,7 @@ 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[?]))
+ .hashGetAllValues(_: String)(_: ClassTag[T]))
.expects(key, implicitly[ClassTag[T]])
.returning(result)
.once()
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 c732141..5d4a348 100644
--- a/src/test/scala/play/api/cache/redis/impl/RedisSetSpec.scala
+++ b/src/test/scala/play/api/cache/redis/impl/RedisSetSpec.scala
@@ -58,7 +58,7 @@ class RedisSetSpec extends AsyncUnitSpec with RedisRuntimeMock with RedisConnect
test("toSet") { (set, connector) =>
for {
- _ <- connector.expect.setMembers[String](cacheKey, result = Set[Any](cacheValue, otherValue))
+ _ <- connector.expect.setMembers[String](cacheKey, result = Set(cacheValue, otherValue))
_ <- set.toSet.assertingEqual(Set(cacheValue, otherValue))
} yield Passed
}
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 cac81cb..3dd549f 100644
--- a/src/test/scala/play/api/cache/redis/test/BaseSpec.scala
+++ b/src/test/scala/play/api/cache/redis/test/BaseSpec.scala
@@ -96,7 +96,7 @@ trait AsyncUtilities { this: AsyncTestSuite =>
}
-trait FutureAssertions extends AsyncUtilities { this: BaseSpec =>
+trait FutureAssertions extends AsyncUtilities { this: AsyncBaseSpec =>
import scala.jdk.FutureConverters._
implicit def completionStageToFutureOps[T](future: CompletionStage[T]): FutureAssertionOps[T] =
@@ -152,16 +152,17 @@ trait FutureAssertions extends AsyncUtilities { this: BaseSpec =>
}
-trait BaseSpec extends Matchers with AsyncMockFactory {
+trait BaseSpec extends Matchers {
protected type Assertion = org.scalatest.Assertion
protected val Passed: Assertion = org.scalatest.Succeeded
+}
+
+trait AsyncBaseSpec extends BaseSpec with AsyncWordSpecLike with AsyncMockFactory with FutureAssertions with AsyncUtilities with TimeLimitedSpec {
implicit override 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