diff --git a/build.sbt b/build.sbt index 3308a04..94c7865 100644 --- a/build.sbt +++ b/build.sbt @@ -6,7 +6,7 @@ lazy val commonSettings: Seq[Setting[_]] = Seq( organization := "net.debasishg", version := "3.42", scalaVersion := "2.13.7", - crossScalaVersions := Seq("2.13.7", "2.12.14", "2.11.12", "2.10.7"), + crossScalaVersions := Seq("2.13.7", "2.12.14", "2.11.12", "3.0.2"), Compile / scalacOptions ++= Seq( "-unchecked", "-feature", "-language:postfixOps", "-deprecation" ), @@ -15,29 +15,18 @@ lazy val commonSettings: Seq[Setting[_]] = Seq( ) ) -def dockerTestKit(version: String): Seq[ModuleID] = { - Seq( - "com.whisk" %% "docker-testkit-scalatest" % version % Test, - "com.whisk" %% "docker-testkit-impl-docker-java" % version % Test - ) :+ - // https://github.com/eclipse-ee4j/jaxb-ri/issues/1222 - "javax.xml.bind" % "jaxb-api" % "2.3.1" % Test -} - lazy val coreSettings = commonSettings ++ Seq( name := "RedisClient", libraryDependencies ++= Seq( - "org.apache.commons" % "commons-pool2" % "2.8.0", - "org.slf4j" % "slf4j-api" % "1.7.32", - "org.slf4j" % "slf4j-log4j12" % "1.7.32" % "provided", - "log4j" % "log4j" % "1.2.17" % "provided", - "org.scalatest" %% "scalatest" % "3.2.9" % Test - ) ++ - (scalaBinaryVersion.value match { - case "2.10" => dockerTestKit("0.9.8") - case _ => dockerTestKit("0.9.9") - }) - , + "org.apache.commons" % "commons-pool2" % "2.8.0", + "org.slf4j" % "slf4j-api" % "1.7.32", + "org.slf4j" % "slf4j-log4j12" % "1.7.32" % "provided", + "log4j" % "log4j" % "1.2.17" % "provided", + "org.scalatest" %% "scalatest" % "3.2.9" % Test, + "com.whisk" %% "docker-testkit-scalatest" % "0.11.0" % Test, + // https://github.com/eclipse-ee4j/jaxb-ri/issues/1222 + "javax.xml.bind" % "jaxb-api" % "2.3.1" % Test + ), publishTo := version { (v: String) => val nexus = "https://oss.sonatype.org/" diff --git a/src/test/scala/com/redis/PatternsSpec.scala b/src/test/scala/com/redis/PatternsSpec.scala index 5e895fc..3d351af 100644 --- a/src/test/scala/com/redis/PatternsSpec.scala +++ b/src/test/scala/com/redis/PatternsSpec.scala @@ -17,7 +17,7 @@ class PatternsSpec extends AnyFunSpec client => client.flushdb } - override def afterAll() = { + override def beforeStop() = { clients.withClient{ client => client.flushall client.disconnect diff --git a/src/test/scala/com/redis/PipelineSpec.scala b/src/test/scala/com/redis/PipelineSpec.scala index b8a4c87..e2303ea 100644 --- a/src/test/scala/com/redis/PipelineSpec.scala +++ b/src/test/scala/com/redis/PipelineSpec.scala @@ -178,7 +178,7 @@ class PipelineSpec extends AnyFunSpec val client = new RedisClient(redisContainerHost, redisContainerPort, batch = RedisClient.BATCH) import com.redis.serialization._ import Parse.Implicits.parseInt - implicit val parseString = Parse[String](new String(_).toInt.toBinaryString) + implicit val parseString: Parse[String] = Parse[String](new String(_).toInt.toBinaryString) val res = client.batchedPipeline( List( () => client.hmset("hash", Map("field1" -> "1", "field2" -> 2)), diff --git a/src/test/scala/com/redis/PoolSpec.scala b/src/test/scala/com/redis/PoolSpec.scala index fa51190..b00d3df 100644 --- a/src/test/scala/com/redis/PoolSpec.scala +++ b/src/test/scala/com/redis/PoolSpec.scala @@ -4,7 +4,6 @@ import com.redis.common.RedisDocker import org.scalatest.BeforeAndAfterEach import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers - import scala.concurrent._ import scala.concurrent.duration._ @@ -27,10 +26,9 @@ class PoolSpec extends AnyFunSpec super.afterEach() } - override def afterAll(): Unit = { + override def beforeStop(): Unit = { clients.withClient{ client => client.disconnect } clients.close() - super.afterAll() } def lp(msgs: List[String]) = { diff --git a/src/test/scala/com/redis/PubSubSpec.scala b/src/test/scala/com/redis/PubSubSpec.scala index 031f320..ff0fd82 100644 --- a/src/test/scala/com/redis/PubSubSpec.scala +++ b/src/test/scala/com/redis/PubSubSpec.scala @@ -11,7 +11,7 @@ class PubSubSpec extends AnyFunSpec lazy val r = new RedisClient(redisContainerHost, redisContainerPort) lazy val t = new RedisClient(redisContainerHost, redisContainerPort) - override def afterAll(): Unit = { + override def beforeStop(): Unit = { r.close() t.close() } diff --git a/src/test/scala/com/redis/RedisClientSpec.scala b/src/test/scala/com/redis/RedisClientSpec.scala index 8dc3bc7..37694e6 100644 --- a/src/test/scala/com/redis/RedisClientSpec.scala +++ b/src/test/scala/com/redis/RedisClientSpec.scala @@ -2,10 +2,7 @@ package com.redis import java.net.{ServerSocket, URI} -import com.github.dockerjava.core.DefaultDockerClientConfig import com.redis.api.ApiSpec -import com.whisk.docker.DockerContainerManager -import com.whisk.docker.impl.dockerjava.Docker import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers diff --git a/src/test/scala/com/redis/SSLSpec.scala b/src/test/scala/com/redis/SSLSpec.scala index 0b9e91e..12cd994 100644 --- a/src/test/scala/com/redis/SSLSpec.scala +++ b/src/test/scala/com/redis/SSLSpec.scala @@ -1,8 +1,9 @@ package com.redis import java.security.cert.X509Certificate -import org.apache.http.ssl.{SSLContexts, TrustStrategy} +import javax.net.ssl.SSLContext +import org.apache.http.ssl.{SSLContexts, TrustStrategy} import com.redis.common.RedisDockerSSL import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers @@ -12,19 +13,19 @@ class SSLSpec extends AnyFunSpec with Matchers with RedisDockerSSL { // Our certificate on the test server is self-signed, which will be // rejected by the default SSLContext. This SSLContext is therefore // specifically configured to trust all certificates. - private val sslContext = SSLContexts + override val sslContext: Option[SSLContext] = Some(SSLContexts .custom() .loadTrustMaterial(null, new TrustStrategy() { def isTrusted(arg0: Array[X509Certificate], arg1: String) = true }) - .build() + .build()) describe("ssl connections") { it("should be established for a RedisClient with a valid SSLContext") { val secureClient: RedisClient = new RedisClient( redisContainerHost, redisContainerPort, - sslContext = Some(sslContext) + sslContext = sslContext ) secureClient.ping shouldEqual Some("PONG") secureClient.close() @@ -34,7 +35,7 @@ class SSLSpec extends AnyFunSpec with Matchers with RedisDockerSSL { val clients = new RedisClientPool( redisContainerHost, redisContainerPort, - sslContext = Some(sslContext) + sslContext = sslContext ) clients.withClient(_.ping) shouldEqual Some("PONG") clients.withClient { client => diff --git a/src/test/scala/com/redis/WatchSpec.scala b/src/test/scala/com/redis/WatchSpec.scala index c04b4ed..fe4a03a 100644 --- a/src/test/scala/com/redis/WatchSpec.scala +++ b/src/test/scala/com/redis/WatchSpec.scala @@ -3,7 +3,6 @@ package com.redis import com.redis.common.RedisDocker import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers - import scala.concurrent.Future class WatchSpec extends AnyFunSpec @@ -22,7 +21,7 @@ class WatchSpec extends AnyFunSpec p.get("key") p.get("key1") } - } + } } val p2: Future[Boolean] = Future { diff --git a/src/test/scala/com/redis/cluster/CommonRedisClusterSpec.scala b/src/test/scala/com/redis/cluster/CommonRedisClusterSpec.scala index e29a25e..99d287e 100644 --- a/src/test/scala/com/redis/cluster/CommonRedisClusterSpec.scala +++ b/src/test/scala/com/redis/cluster/CommonRedisClusterSpec.scala @@ -121,14 +121,14 @@ import org.scalatest.matchers.should.Matchers //simulate the same value is duplicated to slave //for test, don't set to master, just to make sure the expected value is loaded from slave - val redisClient = new RedisClient(redisContainerHost, redisContainerPort(dockerContainers.head)) + val redisClient = new RedisClient(redisContainerHost, redisContainerPort(managedContainers.containers.head)) redisClient.set("testkey1", "testvalue1") //replaced master with slave on the same node - r.replaceServer(ClusterNode(nodename, redisContainerHost, redisContainerPort(dockerContainers.head))) - r.nodeForKey("testkey1").port should equal(redisContainerPort(dockerContainers.head)) + r.replaceServer(ClusterNode(nodename, redisContainerHost, redisContainerPort(managedContainers.containers.head))) + r.nodeForKey("testkey1").port should equal(redisContainerPort(managedContainers.containers.head)) - r.hr.cluster.find(_.node.nodename.equals(nodename)).get.port should equal(redisContainerPort(dockerContainers.head)) + r.hr.cluster.find(_.node.nodename.equals(nodename)).get.port should equal(redisContainerPort(managedContainers.containers.head)) r.get("testkey1") should equal(Some("testvalue1")) //switch back to master. the old value is loaded diff --git a/src/test/scala/com/redis/cluster/ReconnectableSpec.scala b/src/test/scala/com/redis/cluster/ReconnectableSpec.scala index d616dd4..1779cb3 100644 --- a/src/test/scala/com/redis/cluster/ReconnectableSpec.scala +++ b/src/test/scala/com/redis/cluster/ReconnectableSpec.scala @@ -2,13 +2,12 @@ package com.redis.cluster import com.redis.cluster.KeyTag.NoOpKeyTag import com.redis.common.IntClusterSpec -import com.whisk.docker.DockerContainer +import com.whisk.docker.testkit.{BaseContainer, Container} import org.scalatest.{GivenWhenThen, Suite} import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers import org.slf4j.LoggerFactory - -import scala.concurrent.{Future, Promise} +import scala.concurrent.{ExecutionContext, Future, Promise} import scala.util.Try class ReconnectableSpec extends AnyFunSpec with GivenWhenThen @@ -82,35 +81,35 @@ class ReconnectableSpec extends AnyFunSpec with GivenWhenThen p.failure(new Throwable("Did not reach expected state")).future } - override def afterAll(): Unit = { + override def beforeStop(): Unit = { + Try(stopContainer0()) r.close() - Try(dockerExecutor.remove(container0Name).futureValue) - containerNames.foreach(i => Try(dockerExecutor.remove(i).futureValue)) - super.afterAll() } } trait ControlledDockerRedisCluster extends IntClusterSpec with Matchers { that: Suite => + implicit lazy val executionContext: ExecutionContext = scala.concurrent.ExecutionContext.global + private val logger = LoggerFactory.getLogger(getClass) - protected lazy val container0: DockerContainer = runningContainers.head - protected lazy val container0Name: String = container0.name.get - protected lazy val container0Ports: Map[Int, Int] = container0.getPorts().futureValue - protected lazy val newContainer0: DockerContainer = createContainer(Some(container0Name), container0Ports) + protected lazy val container0: BaseContainer = managedContainers.containers.head + protected lazy val container0Name: String = container0.spec.name.orNull + protected lazy val container0Ports: Map[Int, Int] = container0.mappedPorts() + protected lazy val newContainer0: Container = new Container(createContainer(Some(container0Name), container0Ports)) - protected lazy val containerNames: List[String] = runningContainers.flatMap(_.name) + protected lazy val containerNames: Seq[String] = managedContainers.containers.flatMap(_.spec.name) protected def startContainer0(): Unit = { logger.info(s"Manually starting node [$container0Name], [$container0Ports]") - val new0Id = dockerExecutor.createContainer(newContainer0).futureValue - dockerExecutor.startContainer(new0Id).futureValue + val new0Id = dockerExecutor.createContainer(newContainer0.spec).futureValue + dockerExecutor.startContainer(new0Id.id()).futureValue } protected def stopContainer0(): Unit = { logger.info(s"Manually removing node [$container0Name], [$container0Ports]") - dockerExecutor.remove(container0Name).futureValue + dockerExecutor.remove(container0Name, force = true, removeVolumes = true).futureValue } } diff --git a/src/test/scala/com/redis/common/IntClusterSpec.scala b/src/test/scala/com/redis/common/IntClusterSpec.scala index 3d9695c..e3d61c3 100644 --- a/src/test/scala/com/redis/common/IntClusterSpec.scala +++ b/src/test/scala/com/redis/common/IntClusterSpec.scala @@ -12,18 +12,15 @@ trait IntClusterSpec extends BeforeAndAfterEach with RedisDockerCluster { protected val nodeNamePrefix = "node" protected lazy val nodes: List[ClusterNode] = - runningContainers.zipWithIndex.map { case (c, i) => + managedContainers.containers.zipWithIndex.map { case (c, i) => ClusterNode(s"$nodeNamePrefix$i", redisContainerHost, redisContainerPort(c)) - } + }.toList def formattedKey(key: Any)(implicit format: Format): Array[Byte] = { format(key) } - override def afterAll(): Unit = { - r.close() - super.afterAll() - } + override def beforeStop(): Unit = r.close() override def afterEach(): Unit = { r.flushall diff --git a/src/test/scala/com/redis/common/IntSpec.scala b/src/test/scala/com/redis/common/IntSpec.scala index 772ab90..056633b 100644 --- a/src/test/scala/com/redis/common/IntSpec.scala +++ b/src/test/scala/com/redis/common/IntSpec.scala @@ -8,10 +8,7 @@ trait IntSpec extends BeforeAndAfterEach with RedisDocker { protected def r: BaseApi with AutoCloseable - override def afterAll(): Unit = { - r.close() - super.afterAll() - } + override def beforeStop(): Unit = r.close() override def afterEach(): Unit = { r.flushall diff --git a/src/test/scala/com/redis/common/RedisDocker.scala b/src/test/scala/com/redis/common/RedisDocker.scala index e03df81..ee1f0b3 100644 --- a/src/test/scala/com/redis/common/RedisDocker.scala +++ b/src/test/scala/com/redis/common/RedisDocker.scala @@ -1,10 +1,16 @@ package com.redis.common import java.io.File +import javax.net.ssl.SSLContext -import com.whisk.docker.impl.dockerjava.DockerKitDockerJava -import com.whisk.docker.scalatest.DockerTestKit -import com.whisk.docker.{DockerContainer, DockerKit, DockerReadyChecker, VolumeMapping} +import scala.concurrent.{ExecutionContext, Future} +import scala.concurrent.duration.DurationInt + +import com.redis.RedisClient +import com.spotify.docker.client.messages.HostConfig.Bind +import com.spotify.docker.client.messages.PortBinding +import com.whisk.docker.testkit.{BaseContainer, Container, ContainerCommandExecutor, ContainerGroup, ContainerSpec, DockerReadyChecker, SingleContainer} +import com.whisk.docker.testkit.scalatest.DockerTestKitForAll import org.apache.commons.lang.RandomStringUtils import org.scalatest.Suite import org.scalatest.concurrent.ScalaFutures @@ -14,58 +20,69 @@ import org.scalatest.time.{Milliseconds, Seconds, Span} trait RedisDockerCluster extends RedisContainer { that: Suite => - protected def redisContainerPort(container: DockerContainer): Int = container.getPorts().futureValue.apply(redisPort) + protected def redisContainerPort(container: BaseContainer): Int = container.mappedPort(redisPort) - protected lazy val runningContainers: List[DockerContainer] = (0 until 4) - .map(_ => createContainer()) + protected def make4Containers: List[Container] = (0 until 4) + .map(_ => new Container(createContainer())) .toList - abstract override def dockerContainers: List[DockerContainer] = - runningContainers ++ super.dockerContainers - + override val managedContainers: ContainerGroup = ContainerGroup(make4Containers) } trait RedisDocker extends RedisContainer { that: Suite => - protected lazy val redisContainerPort: Int = runningContainer.getPorts().futureValue.apply(redisPort) + implicit lazy val executionContext: ExecutionContext = scala.concurrent.ExecutionContext.global - private lazy val runningContainer = createContainer() - - abstract override def dockerContainers: List[DockerContainer] = - runningContainer :: super.dockerContainers + protected def redisContainerPort: Int = managedContainers.container.mappedPort(redisPort) + override val managedContainers: SingleContainer = SingleContainer(new Container(createContainer())) } trait RedisDockerSSL extends RedisDocker { that: Suite => - private val certsPath = new File("src/test/resources/certs").getAbsolutePath + private lazy val certsPath = new File("src/test/resources/certs").getAbsolutePath - override protected def baseContainer(name: Option[String]) = - DockerContainer("madflojo/redis-tls:latest", name = name) - .withVolumes(Seq(VolumeMapping(certsPath, "/certs"))) + override protected def baseContainer(name: Option[String]): ContainerSpec = + name.foldLeft(ContainerSpec("madflojo/redis-tls:latest") + .withVolumeBindings(Bind.from(certsPath).to("/certs").build()))(_ withName _) } -trait RedisContainer extends DockerKit with DockerTestKit with DockerKitDockerJava with ScalaFutures { +trait RedisContainer extends DockerTestKitForAll with ScalaFutures { that: Suite => + def sslContext: Option[SSLContext] = None + implicit val pc: PatienceConfig = PatienceConfig(Span(30, Seconds), Span(100, Milliseconds)) protected val redisContainerHost: String = "localhost" protected val redisPort: Int = 6379 - protected def baseContainer(name: Option[String]) = DockerContainer("redis:latest", name=name) - + protected def baseContainer(name: Option[String]): ContainerSpec = + name.foldLeft(ContainerSpec("redis:latest"))(_ withName _) + + private def ping: DockerReadyChecker = new DockerReadyChecker { + override def apply(container: BaseContainer)(implicit docker: ContainerCommandExecutor, ec: ExecutionContext): Future[Unit] = { + Future.traverse(container.mappedPorts().values) { p => + Future{ + val cli = new RedisClient(redisContainerHost, p, sslContext = sslContext) + try cli.ping finally cli.close() + } + }.map(_ => ()) + } + } protected def createContainer(name: Option[String] = Some(RandomStringUtils.randomAlphabetic(10)), - ports: Map[Int, Int] = Map.empty): DockerContainer = { - val containerPorts: Seq[(Int, Option[Int])] = if (ports.isEmpty) { - Seq((redisPort -> None)) + ports: Map[Int, Int] = Map.empty): ContainerSpec = { + val containerPorts: Seq[(Int, PortBinding)] = if (ports.isEmpty) { + Seq((redisPort -> PortBinding.randomPort("0.0.0.0"))) } else { - ports.mapValues(i => Some(i)).toSeq + ports.mapValues(i => PortBinding.of(redisContainerHost, i)).toSeq } - baseContainer(name).withPorts(containerPorts: _*) - .withReadyChecker(DockerReadyChecker.LogLineContains("Ready to accept connections")) + baseContainer(name).withPortBindings(containerPorts: _*) + .withReadyChecker(DockerReadyChecker.And( + DockerReadyChecker.LogLineContains("Ready to accept connections"), + ping.looped(1000, 10.milli))) } }