Skip to content

Commit

Permalink
Bump docker testkit. Remove scala 2.10 support. Add scala 3 support (d…
Browse files Browse the repository at this point in the history
…ebasishg#296)

* WIP

* make tests more reliable. Fix the hanging docker containers.

* Include results of `for i in 2.11.12 2.12.14 2.13.7 3.0.2; do sbt ++$i test | tee tests-$i ; done`

* Remove test results for pr

* fix dep version
  • Loading branch information
hughsimpson authored Dec 19, 2021
1 parent c84848a commit f4c9e9f
Show file tree
Hide file tree
Showing 13 changed files with 87 additions and 93 deletions.
31 changes: 10 additions & 21 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -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" ),

Expand All @@ -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/"
Expand Down
2 changes: 1 addition & 1 deletion src/test/scala/com/redis/PatternsSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class PatternsSpec extends AnyFunSpec
client => client.flushdb
}

override def afterAll() = {
override def beforeStop() = {
clients.withClient{ client =>
client.flushall
client.disconnect
Expand Down
2 changes: 1 addition & 1 deletion src/test/scala/com/redis/PipelineSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand Down
4 changes: 1 addition & 3 deletions src/test/scala/com/redis/PoolSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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._

Expand All @@ -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]) = {
Expand Down
2 changes: 1 addition & 1 deletion src/test/scala/com/redis/PubSubSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
3 changes: 0 additions & 3 deletions src/test/scala/com/redis/RedisClientSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
11 changes: 6 additions & 5 deletions src/test/scala/com/redis/SSLSpec.scala
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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()
Expand All @@ -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 =>
Expand Down
3 changes: 1 addition & 2 deletions src/test/scala/com/redis/WatchSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -22,7 +21,7 @@ class WatchSpec extends AnyFunSpec
p.get("key")
p.get("key1")
}
}
}
}

val p2: Future[Boolean] = Future {
Expand Down
8 changes: 4 additions & 4 deletions src/test/scala/com/redis/cluster/CommonRedisClusterSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 14 additions & 15 deletions src/test/scala/com/redis/cluster/ReconnectableSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}

}
9 changes: 3 additions & 6 deletions src/test/scala/com/redis/common/IntClusterSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 1 addition & 4 deletions src/test/scala/com/redis/common/IntSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
71 changes: 44 additions & 27 deletions src/test/scala/com/redis/common/RedisDocker.scala
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)))
}
}

0 comments on commit f4c9e9f

Please sign in to comment.