From cd5ccdbf3e0b91e9bdbe206cbe4b82cc570c0e82 Mon Sep 17 00:00:00 2001 From: Andrea Peruffo Date: Mon, 29 Nov 2021 14:20:50 +0000 Subject: [PATCH] [KEYCLOAK-19311] Add testcontainers to Dist.X Integration Tests (#8946) * Supporting running tests against the server image using test containers --- .github/workflows/ci.yml | 6 + quarkus/tests/integration/pom.xml | 23 ++- .../main/java/org/junit/rules/TestRule.java | 6 + .../it/junit5/extension/CLITestExtension.java | 24 ++- .../it/utils/DockerKeycloakDistribution.java | 143 ++++++++++++++++++ 5 files changed, 196 insertions(+), 6 deletions(-) create mode 100644 quarkus/tests/integration/src/main/java/org/junit/rules/TestRule.java create mode 100644 quarkus/tests/integration/src/main/java/org/keycloak/it/utils/DockerKeycloakDistribution.java diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a1c600ac149..bf0cf659a81 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -412,6 +412,12 @@ jobs: - name: Prepare the local distribution archives run: mvn clean install -DskipTests -Pdistribution + - name: Run Quarkus Tests in Docker + run: | + mvn clean install -nsu -B -f quarkus/tests/pom.xml -Dkc.quarkus.tests.dist=docker | misc/log/trimmer.sh + TEST_RESULT=${PIPESTATUS[0]} + exit $TEST_RESULT + - name: Run Quarkus Integration Tests run: | mvn clean install -nsu -B -f quarkus/tests/pom.xml | misc/log/trimmer.sh diff --git a/quarkus/tests/integration/pom.xml b/quarkus/tests/integration/pom.xml index 0fb7984c2ae..bf3626fcbc0 100644 --- a/quarkus/tests/integration/pom.xml +++ b/quarkus/tests/integration/pom.xml @@ -59,7 +59,28 @@ io.rest-assured rest-assured - test + + + org.testcontainers + junit-jupiter + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + kc.quarkus.tests.dist + ${kc.quarkus.tests.dist} + + + + + + + diff --git a/quarkus/tests/integration/src/main/java/org/junit/rules/TestRule.java b/quarkus/tests/integration/src/main/java/org/junit/rules/TestRule.java new file mode 100644 index 00000000000..cd0b3843711 --- /dev/null +++ b/quarkus/tests/integration/src/main/java/org/junit/rules/TestRule.java @@ -0,0 +1,6 @@ +package org.junit.rules; + +// WORKAROUND: https://github.com/testcontainers/testcontainers-java/issues/970#issuecomment-625044008 +@SuppressWarnings("unused") +public interface TestRule { +} diff --git a/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/CLITestExtension.java b/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/CLITestExtension.java index 6f34b560d40..420c48b9e73 100644 --- a/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/CLITestExtension.java +++ b/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/CLITestExtension.java @@ -27,6 +27,7 @@ import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.keycloak.it.utils.KeycloakDistribution; +import org.keycloak.it.utils.DockerKeycloakDistribution; import org.keycloak.it.utils.RawKeycloakDistribution; import org.keycloak.quarkus.runtime.Environment; import org.keycloak.quarkus.runtime.cli.command.Start; @@ -82,11 +83,24 @@ public class CLITestExtension extends QuarkusMainTestExtension { } private KeycloakDistribution createDistribution(DistributionTest config) { - KeycloakDistribution distribution = new RawKeycloakDistribution( - config.debug(), - config.keepAlive(), - !DistributionTest.ReInstall.NEVER.equals(config.reInstall()) - ); + KeycloakDistribution distribution = null; + + switch (System.getProperty("kc.quarkus.tests.dist", "raw")) { + case "docker": + distribution = new DockerKeycloakDistribution( + config.debug(), + config.keepAlive(), + !DistributionTest.ReInstall.NEVER.equals(config.reInstall()) + ); + break; + case "raw": + default: + distribution = new RawKeycloakDistribution( + config.debug(), + config.keepAlive(), + !DistributionTest.ReInstall.NEVER.equals(config.reInstall()) + ); + } return distribution; } diff --git a/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/DockerKeycloakDistribution.java b/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/DockerKeycloakDistribution.java new file mode 100644 index 00000000000..33e60fe0f19 --- /dev/null +++ b/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/DockerKeycloakDistribution.java @@ -0,0 +1,143 @@ +package org.keycloak.it.utils; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.jboss.logging.Logger; +import org.keycloak.common.Version; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.output.OutputFrame; +import org.testcontainers.containers.output.ToStringConsumer; +import org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.containers.wait.strategy.WaitStrategy; +import org.testcontainers.images.builder.ImageFromDockerfile; + +import java.io.File; +import java.net.URL; +import java.time.Duration; +import java.util.List; + +public final class DockerKeycloakDistribution implements KeycloakDistribution { + + private static final Logger LOGGER = Logger.getLogger(DockerKeycloakDistribution.class); + + private boolean debug; + private boolean manualStop; + private int exitCode = -1; + + private List stdout = List.of(); + private List stderr = List.of(); + private ToStringConsumer backupConsumer = new ToStringConsumer(); + + private File distributionFile = new File("../../../distribution/server-x-dist/target/keycloak.x-" + Version.VERSION_KEYCLOAK + ".tar.gz"); + + private GenericContainer keycloakContainer = null; + + private GenericContainer runKeycloakContainer() { + if (!distributionFile.exists()) { + throw new RuntimeException("Distribution archive " + distributionFile.getAbsolutePath() +" doesn't exists"); + } + File dockerFile = null; + try { + dockerFile = File.createTempFile("keycloakx", "Dockerfile"); + FileUtils.copyURLToFile(new URL("https://raw.githubusercontent.com/keycloak/keycloak-containers/main/server-x/Dockerfile"), dockerFile); + } catch (Exception cause) { + throw new RuntimeException("Cannot download upstream Dockerfile", cause); + } + return new GenericContainer( + new ImageFromDockerfile() + .withFileFromFile("keycloakx.tar.gz", distributionFile) + .withFileFromFile("Dockerfile", dockerFile) + .withBuildArg("KEYCLOAK_DIST", "keycloakx.tar.gz") + ) + .withExposedPorts(8080) + .withStartupTimeout(Duration.ofSeconds(40)) + .withStartupAttempts(1) + .waitingFor(Wait.forHttp("/").forStatusCode(200).withReadTimeout(Duration.ofSeconds(2))); + } + + public DockerKeycloakDistribution(boolean debug, boolean manualStop, boolean reCreate) { + this.debug = debug; + this.manualStop = manualStop; + } + + @Override + public void start(List arguments) { + try { + this.exitCode = -1; + this.stdout = List.of(); + this.stderr = List.of(); + this.backupConsumer = new ToStringConsumer(); + + keycloakContainer = runKeycloakContainer(); + + keycloakContainer + .withLogConsumer(backupConsumer) + .withCommand(arguments.toArray(new String[0])) + .start(); + + // TODO: this is based on a lot of assumptions + io.restassured.RestAssured.port = keycloakContainer.getMappedPort(8080); + } catch (Exception cause) { + this.exitCode = -1; + this.stdout = List.of(backupConsumer.toUtf8String()); + this.stderr = List.of(backupConsumer.toUtf8String()); + keycloakContainer = null; + LOGGER.warn("Failed to start Keycloak container", cause); + } + } + + @Override + public void stop() { + try { + if (keycloakContainer != null) { + this.stdout = getOutputStream(); + this.stderr = getErrorStream(); + + keycloakContainer.stop(); + keycloakContainer = null; + this.exitCode = 0; + } + } catch (Exception cause) { + this.exitCode = -1; + throw new RuntimeException("Failed to stop the server", cause); + } + } + + @Override + public List getOutputStream() { + if (keycloakContainer != null && keycloakContainer.isRunning()) { + return List.of(keycloakContainer.getLogs(OutputFrame.OutputType.STDOUT)); + } else if (this.stdout.isEmpty()) { + return List.of(backupConsumer.toUtf8String()); + } else { + return this.stdout; + } + } + + @Override + public List getErrorStream() { + if (keycloakContainer != null && keycloakContainer.isRunning()) { + return List.of(keycloakContainer.getLogs(OutputFrame.OutputType.STDERR)); + } else if (this.stderr.isEmpty()) { + return List.of(backupConsumer.toUtf8String()); + } else { + return this.stderr; + } + } + + @Override + public int getExitCode() { + return this.exitCode; + } + + @Override + public boolean isDebug() { + return this.debug; + } + + @Override + public boolean isManualStop() { + return this.manualStop; + } +}