Skip to content

Commit

Permalink
Deprecated remaining older extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
sksamuel committed Jun 18, 2023
1 parent a9aa6fd commit 18ddf70
Show file tree
Hide file tree
Showing 11 changed files with 300 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,12 @@ import org.testcontainers.containers.GenericContainer
class ContainerExtension<T : GenericContainer<T>>(
private val container: T,
private val mode: ContainerLifecycleMode = ContainerLifecycleMode.Project,
private val beforeStart: (T) -> Unit = {},
private val afterStart: (T) -> Unit = {},
private val beforeTest: suspend (TestCase, T) -> Unit = { _, _ -> },
private val afterTest: suspend (TestCase, T) -> Unit = { _, _ -> },
private val beforeSpec: suspend (Spec, T) -> Unit = { _, _ -> },
private val afterSpec: suspend (Spec, T) -> Unit = { _, _ -> },
private val beforeStart: (T) -> Unit = {},
private val afterStart: (T) -> Unit = {},
private val beforeShutdown: (T) -> Unit = {},
private val afterShutdown: (T) -> Unit = {},
) : MountableExtension<T, T>,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.kotest.extensions.testcontainers

/**
* Determines the lifetime of a test container installed in a Kotest extension.
*/
enum class ContainerLifecycleMode {

/**
* The TestContainer is started only when first installed and stopped after the spec where it was
* installed completes.
*
* Use this when you need the test container to shut down as soon as the spec does - usually
* because you are using a separate test container per spec and waiting until the test suite
* completes to shut them all down will take too much memory.
*/
Spec,

/**
* The TestContainer is started only when first installed and stopped after the entire test suite.
* This mode is the default choice for test containers. This mode can be used with
* multiple test containers by using separate instances of the container extensions.
*/
Project,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package io.kotest.extensions.testcontainers

import io.kotest.core.extensions.MountableExtension
import io.kotest.core.listeners.AfterProjectListener
import io.kotest.core.listeners.AfterSpecListener
import io.kotest.core.listeners.AfterTestListener
import io.kotest.core.listeners.BeforeSpecListener
import io.kotest.core.listeners.BeforeTestListener
import io.kotest.core.spec.Spec
import io.kotest.core.test.TestCase
import io.kotest.core.test.TestResult
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.testcontainers.containers.DockerComposeContainer
import org.testcontainers.containers.GenericContainer

/**
* A Kotest [MountableExtension] for [GenericContainer]s that are started the first time they are
* installed in a spec.
*
* If no spec is executed that installs a particular container,
* then that container is never started.
*
* Containers can be shared between specs using the default [mode].
*
* @param container the test container instance
*
* @param mode determines if the container is shutdown after the test suite (project) or after the installed spec
* The default is after the test suite.
*
* @param beforeSpec a beforeSpec callback
* @param afterSpec an afterSpec callback
* @param beforeTest a beforeTest callback
* @param afterTest a afterTest callback
*
* @param beforeStart a callback that is invoked only once, just before the container is started.
* If the container is never started, this callback will not be invoked.
* This callback can be useful instead of the installation callback as it will only
* be executed once, regardless of how many times this container is installed.
*
* @param afterStart a callback that is invoked only once, just after the container is started.
* If the container is never started, this callback will not be invoked.
*
* @param beforeShutdown a callback that is invoked only once, just before the container is stopped.
* If the container is never started, this callback will not be invoked.
*
* @param afterShutdown a callback that is invoked only once, just after the container is stopped.
* If the container is never started, this callback will not be invoked.
*/
class DockerComposeContainerExtension<T : DockerComposeContainer<T>>(
private val container: T,
private val mode: ContainerLifecycleMode = ContainerLifecycleMode.Project,
private val beforeStart: (T) -> Unit = {},
private val afterStart: (T) -> Unit = {},
private val beforeTest: suspend (TestCase, T) -> Unit = { _, _ -> },
private val afterTest: suspend (TestCase, T) -> Unit = { _, _ -> },
private val beforeSpec: suspend (Spec, T) -> Unit = { _, _ -> },
private val afterSpec: suspend (Spec, T) -> Unit = { _, _ -> },
private val beforeShutdown: (T) -> Unit = {},
private val afterShutdown: (T) -> Unit = {},
) : MountableExtension<T, T>,
AfterProjectListener,
BeforeTestListener,
BeforeSpecListener,
AfterTestListener,
AfterSpecListener {

private var started: Boolean = false

/**
* Mounts the container, starting it if necessary. The [configure] block will be invoked
* every time the container is mounted, and after the container has started.
*/
override fun mount(configure: T.() -> Unit): T {
if (!started) {
beforeStart(container)
container.start()
started = true
afterStart(container)
}
container.configure()
return container
}

override suspend fun beforeTest(testCase: TestCase) {
beforeTest(testCase, container)
}

override suspend fun afterTest(testCase: TestCase, result: TestResult) {
afterTest(testCase, container)
}

override suspend fun beforeSpec(spec: Spec) {
beforeSpec(spec, container)
}

override suspend fun afterSpec(spec: Spec) {
afterSpec(spec, container)
if (mode == ContainerLifecycleMode.Spec && started) close()
}

override suspend fun afterProject() {
if (started) close()
}

private suspend fun close() {
withContext(Dispatchers.IO) {
beforeShutdown(container)
container.stop()
started = false
afterShutdown(container)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,29 @@ package io.kotest.extensions.testcontainers
import io.kotest.core.TestConfiguration
import org.testcontainers.lifecycle.Startable

@Deprecated("use ContainerExtension")
fun <T : Startable> T.perTest(): StartablePerTestListener<T> = StartablePerTestListener<T>(this)

@Deprecated("use ContainerExtension")
fun <T : Startable> T.perSpec(): StartablePerSpecListener<T> = StartablePerSpecListener<T>(this)
fun <T : Startable> T.perProject(containerName: String): StartablePerProjectListener<T> = StartablePerProjectListener<T>(this, containerName)

@Deprecated("use ContainerExtension")
fun <T : Startable> T.perProject(containerName: String): StartablePerProjectListener<T> =
StartablePerProjectListener<T>(this, containerName)

@Deprecated("use ContainerExtension")
fun <T : Startable> TestConfiguration.configurePerTest(startable: T): T {
listener(StartablePerTestListener(startable))
return startable
}

@Deprecated("use ContainerExtension")
fun <T : Startable> TestConfiguration.configurePerSpec(startable: T): T {
listener(StartablePerSpecListener(startable))
return startable
}

@Deprecated("use ContainerExtension")
fun <T : Startable> TestConfiguration.configurePerProject(startable: T, containerName: String): T {
listener(StartablePerProjectListener(startable, containerName))
return startable
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package io.kotest.extensions.testcontainers

import com.zaxxer.hikari.HikariDataSource
import io.kotest.core.extensions.MountableExtension
import io.kotest.core.listeners.AfterProjectListener
import io.kotest.core.listeners.AfterSpecListener
import io.kotest.core.listeners.AfterTestListener
import io.kotest.core.listeners.BeforeSpecListener
import io.kotest.core.listeners.BeforeTestListener
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.testcontainers.containers.JdbcDatabaseContainer

/**
* A Kotest [MountableExtension] for [JdbcDatabaseContainer]s that are started the first time they are
* installed in a spec.
*
* If no spec is executed that installs a particular container,
* then that container is never started.
*
* Once mounted in a spec, the return value from the installation point is a configured HikariDataSource.
*
* @param container the specific database test container type
* @param beforeSpec a beforeSpec callback
* @param afterSpec an afterSpec callback
* @param beforeTest a beforeTest callback
* @param afterTest a afterTest callback
*
* @param beforeStart a callback that is invoked only once, just before the container is started.
* If the container is never started, this callback will not be invoked.
* This callback can be useful instead of the installation callback as it will only
* be executed once, regardless of how many times this container is installed.
*
* @param afterStart a callback that is invoked only once, just after the container is started.
* If the container is never started, this callback will not be invoked.
*
* @param beforeShutdown a callback that is invoked only once, just before the container is stopped.
* If the container is never started, this callback will not be invoked.
*
* @param afterShutdown a callback that is invoked only once, just after the container is stopped.
* If the container is never started, this callback will not be invoked.
*/
class JdbcDatabaseContainerExtension(
private val container: JdbcDatabaseContainer<*>,
private val beforeStart: (JdbcDatabaseContainer<*>) -> Unit = {},
private val afterStart: (JdbcDatabaseContainer<*>) -> Unit = {},
private val beforeShutdown: (JdbcDatabaseContainer<*>) -> Unit = {},
private val beforeTest: suspend (HikariDataSource) -> Unit = {},
private val afterTest: suspend (HikariDataSource) -> Unit = {},
private val beforeSpec: suspend (HikariDataSource) -> Unit = {},
private val afterSpec: suspend (HikariDataSource) -> Unit = {},
private val afterShutdown: (HikariDataSource) -> Unit = {},
) : MountableExtension<TestContainerHikariConfig, HikariDataSource>,
AfterProjectListener,
BeforeTestListener,
BeforeSpecListener,
AfterTestListener,
AfterSpecListener {

/**
* Mounts the container, starting it if necessary. The [configure] block will be invoked
* every time the container is mounted, and after the container has started.
*/
override fun mount(configure: TestContainerHikariConfig.() -> Unit): HikariDataSource {
if (!container.isRunning) {
beforeStart(container)
container.start()
afterStart(container)
}
return createDataSource(configure)
}

override suspend fun afterProject() {
if (container.isRunning) withContext(Dispatchers.IO) {
beforeShutdown(container)
container.stop()
}
}

private fun createDataSource(configure: TestContainerHikariConfig.() -> Unit): HikariDataSource {
val config = TestContainerHikariConfig()
config.jdbcUrl = container.jdbcUrl
config.username = container.username
config.password = container.password
config.configure()
// runInitScripts(ds.connection, config.dbInitScripts)
return HikariDataSource(config)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ import io.kotest.core.test.isRootTest
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.testcontainers.containers.JdbcDatabaseContainer
import java.io.PrintWriter
import java.sql.Connection
import java.util.logging.Logger
import javax.sql.DataSource

/**
Expand All @@ -33,6 +31,7 @@ import javax.sql.DataSource
*
* @since 1.1.0
*/
@Deprecated("use JdbcDatabaseContainerExtension")
class JdbcTestContainerExtension(
private val container: JdbcDatabaseContainer<Nothing>,
private val lifecycleMode: LifecycleMode = LifecycleMode.Spec,
Expand Down Expand Up @@ -117,49 +116,3 @@ class JdbcTestContainerExtension(
}
}

class SettableDataSource(private var ds: HikariDataSource?) : DataSource {

private fun getDs(): DataSource = ds ?: error("DataSource is not ready")

fun setDataSource(ds: HikariDataSource?) {
this.ds?.close()
this.ds = ds
}

override fun getLogWriter(): PrintWriter {
return getDs().logWriter
}

override fun setLogWriter(out: PrintWriter?) {
getDs().logWriter = out
}

override fun setLoginTimeout(seconds: Int) {
getDs().loginTimeout = seconds
}

override fun getLoginTimeout(): Int {
return getDs().loginTimeout
}

override fun getParentLogger(): Logger {
return getDs().parentLogger
}

override fun <T : Any?> unwrap(iface: Class<T>?): T {
return getDs().unwrap(iface)
}

override fun isWrapperFor(iface: Class<*>?): Boolean {
return getDs().isWrapperFor(iface)
}

override fun getConnection(): Connection {
return getDs().connection
}

override fun getConnection(username: String?, password: String?): Connection {
return getDs().getConnection(username, password)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,3 @@ enum class LifecycleMode {
Spec, EveryTest, Leaf, Root
}

/**
* Determines the lifetime of a test container installed in a Kotest extension.
*/
enum class ContainerLifecycleMode {

/**
* The TestContainer is started only when first installed and stopped after the spec where it was
* installed completes.
*
* Use this when you need the test container to shut down as soon as the spec does - usually
* because you are using a separate test container per spec and waiting until the test suite
* completes to shut them all down will take too much memory.
*/
Spec,

/**
* The TestContainer is started only when first installed and stopped after the entire test suite.
* This mode is the default choice for test containers. This mode can be used with
* multiple test containers by using separate instances of the container extensions.
*/
Project,
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,25 @@ import kotlin.io.path.isDirectory
import kotlin.io.path.isRegularFile
import kotlin.io.path.name


@Deprecated("use Flyway or another db migration tool")
sealed interface Resource {
data class Classpath(val resource: String) : Resource
data class File(val path: Path) : Resource
}

@Deprecated("use Flyway or another db migration tool")
fun Resource.endsWith(name: String): Boolean = when (this) {
is Resource.Classpath -> this.resource.endsWith(name)
is Resource.File -> this.path.name.endsWith(name)
}

@Deprecated("use Flyway or another db migration tool")
fun Resource.loadToReader(): BufferedReader = when (this) {
is Resource.Classpath -> javaClass.getResourceAsStream(this.resource)?.bufferedReader() ?: error("$this was not found on classpath")
is Resource.File -> Files.newBufferedReader(this.path)
}

@Deprecated("use Flyway or another db migration tool")
class ResourceLoader {

private fun Path.getDirContentsOrItself(): List<Path> {
Expand Down
Loading

0 comments on commit 18ddf70

Please sign in to comment.