[KEYCLOAK-19311] Add testcontainers to Dist.X Integration Tests (#8946)
* Supporting running tests against the server image using test containers
This commit is contained in:
parent
79931fd607
commit
cd5ccdbf3e
5 changed files with 196 additions and 6 deletions
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
|
@ -412,6 +412,12 @@ jobs:
|
||||||
- name: Prepare the local distribution archives
|
- name: Prepare the local distribution archives
|
||||||
run: mvn clean install -DskipTests -Pdistribution
|
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
|
- name: Run Quarkus Integration Tests
|
||||||
run: |
|
run: |
|
||||||
mvn clean install -nsu -B -f quarkus/tests/pom.xml | misc/log/trimmer.sh
|
mvn clean install -nsu -B -f quarkus/tests/pom.xml | misc/log/trimmer.sh
|
||||||
|
|
|
@ -59,7 +59,28 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.rest-assured</groupId>
|
<groupId>io.rest-assured</groupId>
|
||||||
<artifactId>rest-assured</artifactId>
|
<artifactId>rest-assured</artifactId>
|
||||||
<scope>test</scope>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testcontainers</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<systemProperties>
|
||||||
|
<property>
|
||||||
|
<name>kc.quarkus.tests.dist</name>
|
||||||
|
<value>${kc.quarkus.tests.dist}</value>
|
||||||
|
</property>
|
||||||
|
</systemProperties>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
package org.junit.rules;
|
||||||
|
|
||||||
|
// WORKAROUND: https://github.com/testcontainers/testcontainers-java/issues/970#issuecomment-625044008
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public interface TestRule {
|
||||||
|
}
|
|
@ -27,6 +27,7 @@ import org.junit.jupiter.api.extension.ExtensionContext;
|
||||||
import org.junit.jupiter.api.extension.ParameterContext;
|
import org.junit.jupiter.api.extension.ParameterContext;
|
||||||
import org.junit.jupiter.api.extension.ParameterResolutionException;
|
import org.junit.jupiter.api.extension.ParameterResolutionException;
|
||||||
import org.keycloak.it.utils.KeycloakDistribution;
|
import org.keycloak.it.utils.KeycloakDistribution;
|
||||||
|
import org.keycloak.it.utils.DockerKeycloakDistribution;
|
||||||
import org.keycloak.it.utils.RawKeycloakDistribution;
|
import org.keycloak.it.utils.RawKeycloakDistribution;
|
||||||
import org.keycloak.quarkus.runtime.Environment;
|
import org.keycloak.quarkus.runtime.Environment;
|
||||||
import org.keycloak.quarkus.runtime.cli.command.Start;
|
import org.keycloak.quarkus.runtime.cli.command.Start;
|
||||||
|
@ -82,11 +83,24 @@ public class CLITestExtension extends QuarkusMainTestExtension {
|
||||||
}
|
}
|
||||||
|
|
||||||
private KeycloakDistribution createDistribution(DistributionTest config) {
|
private KeycloakDistribution createDistribution(DistributionTest config) {
|
||||||
KeycloakDistribution distribution = new RawKeycloakDistribution(
|
KeycloakDistribution distribution = null;
|
||||||
|
|
||||||
|
switch (System.getProperty("kc.quarkus.tests.dist", "raw")) {
|
||||||
|
case "docker":
|
||||||
|
distribution = new DockerKeycloakDistribution(
|
||||||
config.debug(),
|
config.debug(),
|
||||||
config.keepAlive(),
|
config.keepAlive(),
|
||||||
!DistributionTest.ReInstall.NEVER.equals(config.reInstall())
|
!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;
|
return distribution;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<String> stdout = List.of();
|
||||||
|
private List<String> 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 <T> DockerKeycloakDistribution(boolean debug, boolean manualStop, boolean reCreate) {
|
||||||
|
this.debug = debug;
|
||||||
|
this.manualStop = manualStop;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(List<String> 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<String> 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<String> 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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue