From 249ac33a7fc7225ff78749de029d814ead949fd9 Mon Sep 17 00:00:00 2001 From: Andrei Nevedomskii Date: Fri, 20 Oct 2023 18:52:10 +0200 Subject: [PATCH] fix: add a workaround for testcontainers/testcontainers-java#6441 (#152) Adds a workaround for testcontainers/testcontainers-java#6441 issue. The issue happens when Docker was unavailable during the first attempt to start a container. All future attempts will fail, since `testcontainers` caches the connection attempt result in a static variable, so the state will persist while the class is loaded. The workaround is to reset the state through reflection each time before we start the container. --- .../monosoul/jooq/artifact/GradleContainer.kt | 20 ++++++------ .../artifact/JooqCodegenLoggingLevelsTest.kt | 6 ++-- .../jooq/artifact/PluginWorksArtifactTest.kt | 3 +- ...rksAfterFailingToFindDockerArtifactTest.kt | 31 ++++++++++++++++++ .../src/test/resources/gradle_run.sh | 8 +++++ .../container/GenericDatabaseContainer.kt | 32 ++++++++++++++++--- 6 files changed, 79 insertions(+), 21 deletions(-) create mode 100644 artifact-tests/src/test/kotlin/dev/monosoul/jooq/artifact/WorksAfterFailingToFindDockerArtifactTest.kt create mode 100755 artifact-tests/src/test/resources/gradle_run.sh diff --git a/artifact-tests/src/test/kotlin/dev/monosoul/jooq/artifact/GradleContainer.kt b/artifact-tests/src/test/kotlin/dev/monosoul/jooq/artifact/GradleContainer.kt index 7f31a16..9eb569f 100644 --- a/artifact-tests/src/test/kotlin/dev/monosoul/jooq/artifact/GradleContainer.kt +++ b/artifact-tests/src/test/kotlin/dev/monosoul/jooq/artifact/GradleContainer.kt @@ -1,30 +1,28 @@ package dev.monosoul.jooq.artifact -import ch.qos.logback.classic.Logger -import ch.qos.logback.classic.spi.ILoggingEvent -import ch.qos.logback.core.read.ListAppender import org.slf4j.LoggerFactory import org.testcontainers.containers.BindMode.READ_ONLY import org.testcontainers.containers.GenericContainer import org.testcontainers.containers.SelinuxContext.SHARED import org.testcontainers.containers.output.Slf4jLogConsumer +import org.testcontainers.containers.output.ToStringConsumer import org.testcontainers.containers.startupcheck.IndefiniteWaitOneShotStartupCheckStrategy -class GradleContainer : GenericContainer("gradle:jdk17-alpine") { +class GradleContainer( + dockerSocketPath: String = "/var/run/docker.sock", +) : GenericContainer("gradle:jdk17-alpine") { - private val listAppender = ListAppender().also { it.start() } - private val logger = (LoggerFactory.getLogger("GradleContainer[$dockerImageName]") as Logger).also { - it.addAppender(listAppender) - } - val output: List get() = ArrayList(listAppender.list).map { it.formattedMessage } + private val toStringLogConsumer = ToStringConsumer() + val output: String get() = toStringLogConsumer.toUtf8String() val projectPath = "/home/gradle/project" init { withLogConsumer( - Slf4jLogConsumer(logger) + Slf4jLogConsumer(LoggerFactory.getLogger("GradleContainer[$dockerImageName]")) ) + withLogConsumer(toStringLogConsumer) addFsBind("build/local-repository", "$projectPath/local-repository") - addFsBind("/var/run/docker.sock", "/var/run/docker.sock") + addFsBind("/var/run/docker.sock", dockerSocketPath) withWorkingDirectory(projectPath) withStartupCheckStrategy( diff --git a/artifact-tests/src/test/kotlin/dev/monosoul/jooq/artifact/JooqCodegenLoggingLevelsTest.kt b/artifact-tests/src/test/kotlin/dev/monosoul/jooq/artifact/JooqCodegenLoggingLevelsTest.kt index 639195b..67766a1 100644 --- a/artifact-tests/src/test/kotlin/dev/monosoul/jooq/artifact/JooqCodegenLoggingLevelsTest.kt +++ b/artifact-tests/src/test/kotlin/dev/monosoul/jooq/artifact/JooqCodegenLoggingLevelsTest.kt @@ -31,8 +31,7 @@ class JooqCodegenLoggingLevelsTest { gradleContainer.stop() }.isSuccess() - val output = gradleContainer.output.joinToString("\n") - that(output).apply { + that(gradleContainer.output).apply { not().contains("Database version is older than what dialect POSTGRES supports") } } @@ -54,8 +53,7 @@ class JooqCodegenLoggingLevelsTest { gradleContainer.stop() }.isSuccess() - val output = gradleContainer.output.joinToString("\n") - that(output).apply { + that(gradleContainer.output).apply { contains("Database version is older than what dialect POSTGRES supports") } } diff --git a/artifact-tests/src/test/kotlin/dev/monosoul/jooq/artifact/PluginWorksArtifactTest.kt b/artifact-tests/src/test/kotlin/dev/monosoul/jooq/artifact/PluginWorksArtifactTest.kt index 79dfa6e..2513aa3 100644 --- a/artifact-tests/src/test/kotlin/dev/monosoul/jooq/artifact/PluginWorksArtifactTest.kt +++ b/artifact-tests/src/test/kotlin/dev/monosoul/jooq/artifact/PluginWorksArtifactTest.kt @@ -38,8 +38,7 @@ class PluginWorksArtifactTest { gradleContainer.stop() }.isSuccess() - val output = gradleContainer.output.joinToString("\n") - that(output).contains("BUILD SUCCESSFUL in ") + that(gradleContainer.output).contains("BUILD SUCCESSFUL in ") } } } diff --git a/artifact-tests/src/test/kotlin/dev/monosoul/jooq/artifact/WorksAfterFailingToFindDockerArtifactTest.kt b/artifact-tests/src/test/kotlin/dev/monosoul/jooq/artifact/WorksAfterFailingToFindDockerArtifactTest.kt new file mode 100644 index 0000000..2ea0ee0 --- /dev/null +++ b/artifact-tests/src/test/kotlin/dev/monosoul/jooq/artifact/WorksAfterFailingToFindDockerArtifactTest.kt @@ -0,0 +1,31 @@ +package dev.monosoul.jooq.artifact + +import org.junit.jupiter.api.Test +import org.testcontainers.utility.MountableFile.forClasspathResource +import strikt.api.expect +import strikt.assertions.contains +import strikt.assertions.isSuccess + +class WorksAfterFailingToFindDockerArtifactTest { + + @Test + fun `should be possible to generate jooq classes even after it failed on first attempt`() { + // given + val gradleContainer = GradleContainer(dockerSocketPath = "/var/run/docker-alt.sock").apply { + withEnv("TESTCONTAINERS_RYUK_DISABLED", "true") + withCopyToContainer(forClasspathResource("/testproject"), projectPath) + withCopyToContainer(forClasspathResource("/gradle_run.sh"), "/gradle_run.sh") + withCommand("/gradle_run.sh") + } + + // when & then + expect { + catching { + gradleContainer.start() + gradleContainer.stop() + }.isSuccess() + + that(gradleContainer.output).contains("BUILD SUCCESSFUL in ") + } + } +} diff --git a/artifact-tests/src/test/resources/gradle_run.sh b/artifact-tests/src/test/resources/gradle_run.sh new file mode 100755 index 0000000..c069c86 --- /dev/null +++ b/artifact-tests/src/test/resources/gradle_run.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +gradle classes --info --stacktrace + +export DOCKER_HOST=unix:///var/run/docker-alt.sock +export TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/var/run/docker-alt.sock + +gradle classes --info --stacktrace \ No newline at end of file diff --git a/src/main/kotlin/dev/monosoul/jooq/container/GenericDatabaseContainer.kt b/src/main/kotlin/dev/monosoul/jooq/container/GenericDatabaseContainer.kt index a4fe727..76e6a3e 100644 --- a/src/main/kotlin/dev/monosoul/jooq/container/GenericDatabaseContainer.kt +++ b/src/main/kotlin/dev/monosoul/jooq/container/GenericDatabaseContainer.kt @@ -6,16 +6,26 @@ import org.slf4j.LoggerFactory import org.testcontainers.containers.JdbcDatabaseContainer import org.testcontainers.containers.output.Slf4jLogConsumer import org.testcontainers.containers.wait.strategy.HostPortWaitStrategy +import org.testcontainers.dockerclient.DockerClientProviderStrategy import org.testcontainers.utility.DockerImageName import java.sql.Driver +import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock +import kotlin.reflect.KCallable +import kotlin.reflect.full.declaredMembers +import kotlin.reflect.jvm.isAccessible class GenericDatabaseContainer( - private val image: Image, - private val database: Database.Internal, - private val jdbcAwareClassLoader: ClassLoader, -) : JdbcDatabaseContainer(DockerImageName.parse(image.name)) { + private val image: Image, + private val database: Database.Internal, + private val jdbcAwareClassLoader: ClassLoader, +) : JdbcDatabaseContainer( + image.let { + failFastAlways.set(false) + DockerImageName.parse(it.name) + } +) { private val driverLoadLock = ReentrantLock() private var driver: Driver? = null @@ -67,4 +77,18 @@ class GenericDatabaseContainer( else -> throw e } } + + private companion object { + /** + * Workaround for https://github.com/testcontainers/testcontainers-java/issues/6441 + */ + val failFastAlways = DockerClientProviderStrategy::class.declaredMembers + .single { it.name == "FAIL_FAST_ALWAYS" } + .apply { isAccessible = true } + .let { + @Suppress("UNCHECKED_CAST") + it as KCallable + } + .call() + } }