From eb6c935744d800d9d21cabc601d38960359464e0 Mon Sep 17 00:00:00 2001 From: Steve Hawkins Date: Thu, 7 May 2026 15:31:13 -0400 Subject: [PATCH] fix: simplifying the management of the raw distribution also optimized ClusterConfigDistTest closes: #48754 Signed-off-by: Steve Hawkins --- .../configuration/PersistedConfigSource.java | 4 +- .../it/cli/dist/AbstractPathDistTest.java | 48 +++------ .../it/cli/dist/ClusterConfigDistTest.java | 59 ++++++----- .../it/cli/dist/ImportAtStartupDistTest.java | 8 +- .../it/cli/dist/LiquibaseDistTest.java | 9 +- .../keycloak/it/cli/dist/LoggingDistTest.java | 11 +-- .../it/cli/dist/PathWithSpacesDistTest.java | 2 +- .../dist/PathWithSpecialCharsDistTest.java | 2 +- .../cli/dist/QuarkusPropertiesDistTest.java | 20 +--- .../it/cli/dist/StartCommandDistTest.java | 3 + .../it/junit5/extension/CLITestExtension.java | 58 ++++++----- .../it/junit5/extension/DistributionTest.java | 26 ++--- .../it/junit5/extension/DistributionType.java | 2 +- .../RawDistributionLifecycleManager.java | 35 +++++++ .../it/utils/RawKeycloakDistribution.java | 97 +++++++++++++++---- .../resources/KeycloakApplication.java | 8 ++ 16 files changed, 229 insertions(+), 163 deletions(-) create mode 100644 quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/RawDistributionLifecycleManager.java diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/PersistedConfigSource.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/PersistedConfigSource.java index 025fda607734..40aec092f804 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/PersistedConfigSource.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/PersistedConfigSource.java @@ -173,7 +173,7 @@ public T runWithDisabled(Supplier execution) { } public void saveDryRunProperties() throws FileNotFoundException, IOException { - Path path = Environment.getHomePath().orElseThrow().resolve("lib").resolve("dryRun.properties"); + Path path = Environment.getHomePath().orElseThrow().resolve("lib").resolve("quarkus").resolve("dryRun.properties"); var properties = Picocli.getNonPersistedBuildTimeOptions(); try (FileOutputStream fos = new FileOutputStream(path.toFile())) { properties.store(fos, null); @@ -181,7 +181,7 @@ public void saveDryRunProperties() throws FileNotFoundException, IOException { } public void useDryRunProperties() { - Path path = Environment.getHomePath().orElseThrow().resolve("lib").resolve("dryRun.properties"); + Path path = Environment.getHomePath().orElseThrow().resolve("lib").resolve("quarkus").resolve("dryRun.properties"); if (Files.exists(path)) { Properties properties = new Properties(); try (FileInputStream fis = new FileInputStream(path.toFile())) { diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/AbstractPathDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/AbstractPathDistTest.java index ecd77e602cca..21067eb74c1d 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/AbstractPathDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/AbstractPathDistTest.java @@ -1,6 +1,7 @@ package org.keycloak.it.cli.dist; -import java.io.File; +import java.io.IOException; +import java.nio.file.Path; import java.util.Map; import java.util.function.Consumer; @@ -12,15 +13,11 @@ import org.keycloak.it.utils.RawKeycloakDistribution; import org.keycloak.quarkus.runtime.cli.command.StartDev; -import io.quarkus.test.junit.main.Launch; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; -import static org.junit.jupiter.api.Assertions.assertTrue; /** * Runs test with distribution placed in a directory that has special characters in name. @@ -30,42 +27,23 @@ @TestInstance(TestInstance.Lifecycle.PER_CLASS) abstract class AbstractPathDistTest { - /** - * Switches temporary directory used by the {@link RawKeycloakDistribution}. - */ - private static final String JAVA_IO_TMPDIR = "java.io.tmpdir"; - private String originalTmpDir = null; - protected abstract String getSubPath(); - @BeforeAll - void changeTmpDir() { - originalTmpDir = System.getProperty(JAVA_IO_TMPDIR); - System.setProperty(JAVA_IO_TMPDIR, originalTmpDir + File.separator + getSubPath()); - } - - @AfterAll - void switchBackToOriginalTmpDir() { - if (originalTmpDir != null) { - System.setProperty(JAVA_IO_TMPDIR, originalTmpDir); - originalTmpDir = null; - } - } - @BeforeStartDistribution(AddCustomScriptsProvider.class) @RawDistOnly(reason = "Testing installation path handling") @Test - @Launch(StartDev.NAME) - void testApplicationBuildAndStart(CLIResult result, KeycloakDistribution dist) { - assertDistPath(dist); - result.assertStartedDevMode(); - assertThat(result.getOutput(), containsString("Updating the configuration and installing your custom providers, if any. Please wait.")); - } - - private void assertDistPath(KeycloakDistribution dist) { + void testApplicationBuildAndStart(KeycloakDistribution dist) throws IOException { RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class); - String distPath = rawDist.getDistPath().toString(); - assertTrue(distPath.contains(getSubPath()), "Test must run with '" + getSubPath() + "' in path. Actual path: " + distPath); + Path distPath = rawDist.getDistPath(); + Path newPath = distPath.getParent().resolve(getSubPath()).resolve(distPath.getFileName()); + rawDist.setDistPath(newPath); + try { + CLIResult result = dist.run(StartDev.NAME); + result.assertStartedDevMode(); + assertThat(result.getOutput(), containsString("Updating the configuration and installing your custom providers, if any. Please wait.")); + } finally { + rawDist.setDistPath(distPath); + } } public static final class AddCustomScriptsProvider implements Consumer { diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ClusterConfigDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ClusterConfigDistTest.java index 2b55202defe7..2dd00f16278f 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ClusterConfigDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ClusterConfigDistTest.java @@ -34,22 +34,27 @@ import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.condition.EnabledOnOs; import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.api.io.TempDir; import org.testcontainers.shaded.com.google.common.io.Files; -@DistributionTest(reInstall = DistributionTest.ReInstall.BEFORE_TEST) +@DistributionTest(defaultOptions = {"--db=dev-file", "--http-enabled=true", "--hostname-strict=false"}) @RawDistOnly(reason = "Not possible to mount files using docker.") @Storage(defaultLocalCache = false) @Tag(DistributionTest.SMOKE) @Tag(DistributionTest.SLOW) +@TestMethodOrder(OrderAnnotation.class) public class ClusterConfigDistTest { private static final String WARN_DEFAULT_CACHE_MUTATIONS = "Modifying the default cache configuration in the config file"; + @Order(1) // run the dev specific tests together to prevent later reaugmentations @Test @Launch({ "start-dev", "--cache=ispn" }) void changeClusterSetting(CLIResult result) { @@ -58,7 +63,7 @@ void changeClusterSetting(CLIResult result) { } @Test - @Launch({ "start-dev", "--cache=ispn", "--cache-embedded-network-bind-address=127.0.0.2","--cache-embedded-network-bind-port=7802"}) + @Launch({ "start", "--cache-embedded-network-bind-address=127.0.0.2","--cache-embedded-network-bind-port=7802"}) void changeBindAddressAndPort(CLIResult result) { result.assertClusteredCache(); result.assertMessage("physical addresses are `[127.0.0.2:7802]`"); @@ -66,7 +71,7 @@ void changeBindAddressAndPort(CLIResult result) { } @Test - @Launch({ "start-dev", "--cache=ispn", "--cache-embedded-network-bind-address=127.0.0.1","--cache-embedded-network-bind-port=7801", "--cache-embedded-network-external-address=127.0.0.2", "--cache-embedded-network-external-port=7802"}) + @Launch({ "start", "--cache-embedded-network-bind-address=127.0.0.1","--cache-embedded-network-bind-port=7801", "--cache-embedded-network-external-address=127.0.0.2", "--cache-embedded-network-external-port=7802"}) void changeBindAndExternalAddress(CLIResult result) { result.assertClusteredCache(); result.assertMessage("physical addresses are `[127.0.0.2:7802]`"); @@ -74,7 +79,7 @@ void changeBindAndExternalAddress(CLIResult result) { } @Test - @Launch({ "start-dev", "--cache=ispn", "--cache-embedded-network-bind-address=127.0.0.1", "-Djgroups.bind.address=127.0.0.2", "-Djgroups.bind_addr=127.0.0.3"}) + @Launch({ "start", "--cache-embedded-network-bind-address=127.0.0.1", "-Djgroups.bind.address=127.0.0.2", "-Djgroups.bind_addr=127.0.0.3"}) void testJGroupsBindAddressPropertyAlsoExists(CLIResult result) { result.assertClusteredCache(); result.assertMessage("Conflicting system property 'jgroups.bind.address' and CLI arg 'cache-embedded-network-bind-address' set, utilising CLI value '127.0.0.1'"); @@ -84,7 +89,7 @@ void testJGroupsBindAddressPropertyAlsoExists(CLIResult result) { } @Test - @Launch({ "start-dev", "--cache=ispn", "-Djgroups.bind.address=127.0.0.2", "-Djgroups.bind.port=7801"}) + @Launch({ "start", "-Djgroups.bind.address=127.0.0.2", "-Djgroups.bind.port=7801"}) void testJGroupsBindAddressProperty(CLIResult result) { result.assertClusteredCache(); result.assertMessage("physical addresses are `[127.0.0.2:7801]`"); @@ -92,7 +97,7 @@ void testJGroupsBindAddressProperty(CLIResult result) { } @Test - @Launch({ "start-dev", "--cache=ispn", "--cache-embedded-network-bind-address=match-address:127.0.0.*"}) + @Launch({ "start", "--cache-embedded-network-bind-address=match-address:127.0.0.*"}) void testBindSiteMatches(CLIResult result) { result.assertClusteredCache(); result.assertMessage("physical addresses are `[127.0.0."); @@ -100,52 +105,52 @@ void testBindSiteMatches(CLIResult result) { } @Test - @Launch({ "start-dev", "--cache=ispn", "--cache-embedded-network-bind-address=SITE_LOCAL"}) + @Launch({ "start", "--cache-embedded-network-bind-address=SITE_LOCAL"}) void testBindSiteLocal(CLIResult result) { result.assertClusteredCache(); result.assertMessage("ISPN000078: Starting JGroups channel `ISPN` with stack `jdbc-ping`"); } @Test - @Launch({ "start-dev", "--cache=ispn", "--cache-stack=jdbc-ping-udp"}) + @Launch({ "start", "--cache-stack=jdbc-ping-udp"}) void testJdbcPingTCP(CLIResult result) { result.assertClusteredCache(); result.assertMessage("ISPN000078: Starting JGroups channel `ISPN` with stack `jdbc-ping-udp`"); } @Test - @Launch({ "start-dev", "--cache=ispn", "--cache-stack=azure" }) + @Launch({ "start", "--cache-stack=azure" }) void warnDeprecatedCloudStack(CLIResult result) { result.assertMessage("Stack 'azure' is deprecated. We recommend to use 'jdbc-ping' instead"); } @Test - @Launch({ "start-dev", "--cache-config-file=invalid" }) + @Launch({ "start", "--cache-config-file=invalid" }) void failInvalidClusterConfig(CLIResult result) { result.assertError("Cache config file 'invalid' does not exist in the conf directory"); } @Test - @Launch({ "start-dev", "--cache=ispn", "--cache-stack=kubernetes" }) + @Launch({ "start", "--cache-stack=kubernetes" }) void failMisConfiguredClusterStack(CLIResult result) { result.assertMessage("ERROR: dns_query can not be null or empty"); } @Test - @Launch({ "start-dev", "--cache=ispn", "--cache-stack=invalid" }) + @Launch({ "start", "--cache-stack=invalid" }) void failInvalidClusterStack(CLIResult result) { result.assertMessage("No such JGroups stack 'invalid'"); } @Test - @Launch({ "start-dev", "--cache-config-file=cache-ispn.xml" }) + @Launch({ "start", "--cache-config-file=cache-ispn.xml" }) void testExplicitCacheConfigFile(CLIResult result) { - result.assertStartedDevMode(); + result.assertStarted(); result.assertClusteredCache(); } @Test - @Launch({ "start", "--cache=ispn", "--log-level=info,org.keycloak.connections.infinispan:debug", "--http-enabled=true", "--hostname-strict=false"}) + @Launch({ "start", "--log-level=info,org.keycloak.connections.infinispan:debug"}) void testPrintCacheConfigurationsDebug(CLIResult result) { result.assertStarted(); result.assertMessage("Infinispan configuration"); @@ -153,7 +158,7 @@ void testPrintCacheConfigurationsDebug(CLIResult result) { @Test @EnabledOnOs(value = { OS.LINUX, OS.MAC }, disabledReason = "different shell escaping behaviour on Windows.") - @Launch({ "start", "--db=dev-file", "--log-level=info,org.infinispan.remoting.transport.jgroups.JGroupsTransport:debug","--http-enabled=true", "--hostname-strict=false" }) + @Launch({ "start", "--log-level=info,org.infinispan.remoting.transport.jgroups.JGroupsTransport:debug"}) void testStartDefaultsToClustering(CLIResult result) { result.assertStarted(); result.assertClusteredCache(); @@ -162,13 +167,14 @@ void testStartDefaultsToClustering(CLIResult result) { @Test @EnabledOnOs(value = { OS.WINDOWS }, disabledReason = "different shell behaviour on Windows.") - @Launch({ "start", "--db=dev-file", "--log-level=\"info,org.infinispan.remoting.transport.jgroups.JGroupsTransport:debug\"","--http-enabled=true", "--hostname-strict=false" }) + @Launch({ "start", "--log-level=\"info,org.infinispan.remoting.transport.jgroups.JGroupsTransport:debug\"" }) void testWinStartDefaultsToClustering(CLIResult result) { result.assertStarted(); result.assertClusteredCache(); result.assertMessage("JGroups protocol stack: TCP"); } + @Order(2) @Test @Launch({ "start-dev" }) void testStartDevDefaultsToLocalCaches(CLIResult result) { @@ -179,6 +185,7 @@ void testStartDevDefaultsToLocalCaches(CLIResult result) { result.assertNoMessage("Starting JGroups certificate reload manager"); } + @Order(3) @Test @BeforeStartDistribution(ConfigureCacheUsingAsyncEncryption.class) @Launch({ "start-dev", "--cache-config-file=cache-ispn-asym-enc.xml" }) @@ -188,14 +195,14 @@ void testCustomCacheStackInConfigFile(CLIResult result) { @Test @BeforeStartDistribution(ConfigureCacheUsingAsyncEncryption.class) - @Launch({"start", "--cache-config-file=cache-ispn-asym-enc.xml", "--http-enabled=true", "--hostname-strict=false", "--cache-embedded-mtls-enabled=false"}) + @Launch({"start", "--cache-config-file=cache-ispn-asym-enc.xml", "--cache-embedded-mtls-enabled=false"}) void testCustomCacheStackInConfigFileNotDev(CLIResult result) { result.assertMessage("ISPN000078: Starting JGroups channel `ISPN` with stack `encrypt-udp`"); } @Test @BeforeStartDistribution(ConfigureCustomCache.class) - @Launch({ "start-dev", "--cache-config-file=cache-ispn-custom-cache.xml" }) + @Launch({ "start", "--cache-config-file=cache-ispn-custom-cache.xml" }) void testCustomCacheConfigurationWarning(CLIResult result) { result.assertMessage(WARN_DEFAULT_CACHE_MUTATIONS); } @@ -208,32 +215,32 @@ void testAbsoluteCacheFile(KeycloakDistribution dist, @TempDir Path tempDir) thr Files.write(is.readAllBytes(), customCacheFile); } - LaunchResult result = dist.run("start-dev", "--cache-config-file=" + customCacheFile.getAbsolutePath()); + LaunchResult result = dist.run("start", "--cache-config-file=" + customCacheFile.getAbsolutePath()); Assertions.assertEquals(0, result.exitCode()); MatcherAssert.assertThat(result.getOutput(), Matchers.containsString(WARN_DEFAULT_CACHE_MUTATIONS)); String absolutePath = missingCacheFile.getAbsolutePath(); - result = dist.run("start-dev", "--cache-config-file=" + absolutePath); + result = dist.run("start", "--cache-config-file=" + absolutePath); Assertions.assertEquals(2, result.exitCode()); MatcherAssert.assertThat(result.getErrorOutput(), Matchers.matchesRegex("Cache config file '" + absolutePath + "' does not exist")); } @Test @BeforeStartDistribution(ConfigureCustomCache.class) - @Launch({ "start-dev", "--cache-config-file=cache-ispn-custom-cache.xml", "--cache-config-mutate=true" }) + @Launch({ "start", "--cache-config-file=cache-ispn-custom-cache.xml", "--cache-config-mutate=true" }) void testCustomCacheConfigurationNoWarning(CLIResult result) { result.assertNoMessage(WARN_DEFAULT_CACHE_MUTATIONS); } @Test @BeforeStartDistribution(ConfigureCustomCache.class) - @Launch({ "start-dev", "--cache-config-file=cache-ispn-custom-user-cache.xml"}) + @Launch({ "start", "--cache-config-file=cache-ispn-custom-user-cache.xml"}) void testCustomUserCacheConfigurationNoWarning(CLIResult result) { result.assertNoMessage(WARN_DEFAULT_CACHE_MUTATIONS); } @Test - @Launch({ "start", "--cache=local", "--http-enabled=true", "--hostname-strict=false"}) + @Launch({ "start", "--cache=local"}) void testNotClustered(CLIResult result) { result.assertStarted(); result.assertLocalCache(); @@ -244,13 +251,13 @@ void testNotClustered(CLIResult result) { } @Test - @Launch({ "start-dev", "--cache-embedded-users-max-count=-1" }) + @Launch({ "start", "--cache-embedded-users-max-count=-1" }) void testNegativeMaxCountIgnoredForBoundedCache(CLIResult result) { result.assertMessage("Ignoring unbounded max-count for cache 'users'"); } @Test - @Launch({ "start-dev", "--cache-embedded-sessions-max-count=-1", "--features-disabled=persistent-user-sessions" }) + @Launch({ "start", "--cache-embedded-sessions-max-count=-1", "--features-disabled=persistent-user-sessions" }) void testNegativeMaxCountAllowedForVolatileCache(CLIResult result) { result.assertNoMessage("Ignoring unbounded max-count for cache 'sessions'"); } diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ImportAtStartupDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ImportAtStartupDistTest.java index 004ec0d37d93..4b4ed1f4a4fe 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ImportAtStartupDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ImportAtStartupDistTest.java @@ -84,8 +84,7 @@ void testImportFromFileCreatedByExportAllRealms(KeycloakDistribution dist) throw dist.run("start-dev", "--import-realm"); dist.run("--profile=dev", "export", "--file=../data/import/realm.json", "--verbose"); - RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class); - FileUtil.deleteDirectory(rawDist.getDistPath().resolve("data").resolve("h2").toAbsolutePath()); + dist.unwrap(RawKeycloakDistribution.class).resetH2Dir(); CLIResult result = dist.run("start-dev", "--import-realm"); result.assertMessage("Realm 'quickstart-realm' imported"); @@ -99,8 +98,7 @@ void testImportFromFileCreatedByExportSingleRealm(KeycloakDistribution dist) thr dist.run("start-dev", "--import-realm"); dist.run("--profile=dev", "export", "--realm=quickstart-realm", "--file=../data/import/realm.json"); - RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class); - FileUtil.deleteDirectory(rawDist.getDistPath().resolve("data").resolve("h2").toAbsolutePath()); + dist.unwrap(RawKeycloakDistribution.class).resetH2Dir(); CLIResult result = dist.run("start-dev", "--import-realm"); result.assertMessage("Realm 'quickstart-realm' imported"); @@ -115,7 +113,7 @@ void testImportFromDirCreatedByExport(KeycloakDistribution dist) throws IOExcept FileUtil.deleteDirectory(rawDist.getDistPath().resolve("data").resolve("import").toAbsolutePath()); dist.run("--profile=dev", "export", "--dir=../data/import"); - FileUtil.deleteDirectory(rawDist.getDistPath().resolve("data").resolve("h2").toAbsolutePath()); + dist.unwrap(RawKeycloakDistribution.class).resetH2Dir(); CLIResult result = dist.run("start-dev", "--import-realm"); result.assertMessage("Realm 'quickstart-realm' imported"); diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/LiquibaseDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/LiquibaseDistTest.java index 195d95bad663..799b6a5fd157 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/LiquibaseDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/LiquibaseDistTest.java @@ -1,9 +1,13 @@ package org.keycloak.it.cli.dist; +import java.io.IOException; + import org.keycloak.it.junit5.extension.DistributionTest; import org.keycloak.it.junit5.extension.RawDistOnly; import org.keycloak.it.utils.KeycloakDistribution; +import org.keycloak.it.utils.RawKeycloakDistribution; +import io.quarkus.deployment.util.FileUtil; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @@ -13,7 +17,10 @@ public class LiquibaseDistTest { @Test - public void dbLockMultipleExecution(KeycloakDistribution distribution) { + public void dbLockMultipleExecution(KeycloakDistribution distribution) throws IOException { + // force a full db initialization + RawKeycloakDistribution rawDist = distribution.unwrap(RawKeycloakDistribution.class); + FileUtil.deleteDirectory(rawDist.getDistPath().resolve("data").resolve("h2").toAbsolutePath()); var result = distribution.run("start-dev", "--log-level=org.keycloak.connections.jpa.updater.liquibase.lock.CustomLockService:trace"); result.assertMessage("Initialize Database Lock Table, current locks []"); result.assertMessage("Initialized record in the database lock table"); diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/LoggingDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/LoggingDistTest.java index 80e24140b897..fff96c44440e 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/LoggingDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/LoggingDistTest.java @@ -38,7 +38,6 @@ import org.keycloak.it.utils.RawDistRootPath; import org.keycloak.it.utils.RawKeycloakDistribution; -import io.quarkus.deployment.util.FileUtil; import io.quarkus.test.junit.main.Launch; import org.apache.commons.io.FileUtils; import org.apache.http.HttpHeaders; @@ -109,23 +108,21 @@ void testSetLogFormat(CLIResult cliResult) { @Test void testJsonFormatApplied(KeycloakDistribution dist) throws IOException { - RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class); - FileUtil.deleteDirectory(rawDist.getDistPath().resolve("data").resolve("h2").toAbsolutePath()); + dist.unwrap(RawKeycloakDistribution.class).resetH2Dir(); CLIResult cliResult = dist.run("start-dev", "--log-console-output=json"); cliResult.assertJsonLogDefaultsApplied(); cliResult.assertStartedDevMode(); - assertFalse(cliResult.getOutput().contains("UPDATE SUMMARY")); + assertFalse(cliResult.getOutput().contains("\"loggerName\":\"liquibase.servicelocator\",\"level\":\"FINE\"")); } @Test void testLogLevelSettingsAppliedWhenJsonEnabled(KeycloakDistribution dist) throws IOException { - RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class); - FileUtil.deleteDirectory(rawDist.getDistPath().resolve("data").resolve("h2").toAbsolutePath()); + dist.unwrap(RawKeycloakDistribution.class).resetH2Dir(); CLIResult cliResult = dist.run("start-dev", "--log-level=off,org.keycloak:debug,liquibase:debug", "--log-console-output=json"); assertFalse(cliResult.getOutput().contains("\"loggerName\":\"io.quarkus\",\"level\":\"INFO\")")); assertTrue(cliResult.getOutput().contains("\"loggerName\":\"org.keycloak.services.resources.KeycloakApplication\",\"level\":\"DEBUG\"")); assertTrue(cliResult.getOutput().contains("\"loggerName\":\"liquibase.servicelocator\",\"level\":\"FINE\"")); - assertTrue(cliResult.getOutput().contains("UPDATE SUMMARY")); + cliResult.assertStartedDevMode(); } @Test diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/PathWithSpacesDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/PathWithSpacesDistTest.java index bf8b66d10457..0910c6c83107 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/PathWithSpacesDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/PathWithSpacesDistTest.java @@ -2,7 +2,7 @@ import org.keycloak.it.junit5.extension.DistributionTest; -@DistributionTest(reInstall = DistributionTest.ReInstall.BEFORE_TEST) +@DistributionTest class PathWithSpacesDistTest extends AbstractPathDistTest { @Override diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/PathWithSpecialCharsDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/PathWithSpecialCharsDistTest.java index 645298492709..f9f17c4fabe1 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/PathWithSpecialCharsDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/PathWithSpecialCharsDistTest.java @@ -2,7 +2,7 @@ import org.keycloak.it.junit5.extension.DistributionTest; -@DistributionTest(reInstall = DistributionTest.ReInstall.BEFORE_TEST) +@DistributionTest class PathWithSpecialCharsDistTest extends AbstractPathDistTest { @Override diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesDistTest.java index c725460181a0..fa605f632a5c 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesDistTest.java @@ -46,7 +46,7 @@ import static org.hamcrest.Matchers.containsString; import static org.junit.jupiter.api.Assertions.assertTrue; -@DistributionTest(reInstall = DistributionTest.ReInstall.NEVER, defaultOptions = "--db=dev-file") +@DistributionTest(defaultOptions = "--db=dev-file") @WithEnvVars({"KC_CACHE", "local"}) // avoid flakey port conflicts @RawDistOnly(reason = "Containers are immutable") @Tag(DistributionTest.WIN) @@ -56,14 +56,6 @@ public class QuarkusPropertiesDistTest { private static final String QUARKUS_BUILDTIME_HIBERNATE_METRICS_KEY = "quarkus.datasource.metrics.enabled"; private static final String QUARKUS_RUNTIME_CONSOLE_HANDLER_ENABLED_KEY = "quarkus.log.handler.console.\"console-2\".enable"; - @DryRun - @Test - @Launch({"build"}) - @Order(1) - void testBuildWithPropertyFromQuarkusProperties(CLIResult cliResult) { - cliResult.assertBuild(); - } - @Test @BeforeStartDistribution(QuarkusPropertiesDistTest.AddConsoleHandlerFromQuarkusProps.class) @Launch({"start", "--http-enabled=true", "--hostname-strict=false"}) @@ -102,6 +94,7 @@ void buildFirstWithUnknownQuarkusBuildProperty(CLIResult cliResult) { } @Test + @BeforeStartDistribution(UpdateHibernateMetricsFromQuarkusProps.class) @KeepServerAlive @Launch({ "start", "--http-enabled=true", "--hostname-strict=false", "--metrics-enabled=true"}) @Order(8) @@ -125,15 +118,6 @@ void testMissingSmallRyeKeyStorePasswordProperty(CLIResult cliResult) { () -> "The Error Output:\n " + cliResult.getErrorOutput() + " doesn't warn about the missing password"); } - @Disabled("Ensuring config-keystore is used only at runtime removes proactive validation of the path when only the keystore is used") - @Test - @Launch({ "start", "--http-enabled=true", "--hostname-strict=false", "--config-keystore-password=secret" }) - @Order(10) - void testMissingSmallRyeKeyStorePathProperty(CLIResult cliResult) { - cliResult.assertBuild(); - cliResult.assertError("config-keystore must be specified"); - } - @Test @Launch({ "start", "--http-enabled=true", "--hostname-strict=false", "--config-keystore=/invalid/path", "--config-keystore-password=secret" }) diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/StartCommandDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/StartCommandDistTest.java index 565b47f32ace..9c44b191fa56 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/StartCommandDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/StartCommandDistTest.java @@ -23,7 +23,9 @@ import org.keycloak.it.junit5.extension.RawDistOnly; import org.keycloak.it.junit5.extension.WithEnvVars; import org.keycloak.it.utils.KeycloakDistribution; +import org.keycloak.it.utils.RawDistributionLifecycleManager; +import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.main.Launch; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; @@ -43,6 +45,7 @@ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) @DistributionTest @Tag(DistributionTest.WIN) +@QuarkusTestResource(RawDistributionLifecycleManager.class) public class StartCommandDistTest { @DryRun diff --git a/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/CLITestExtension.java b/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/CLITestExtension.java index 4460ba934fe6..e23679762685 100644 --- a/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/CLITestExtension.java +++ b/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/CLITestExtension.java @@ -17,7 +17,6 @@ package org.keycloak.it.junit5.extension; -import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.Arrays; @@ -34,8 +33,8 @@ import org.keycloak.quarkus.runtime.cli.command.Start; import org.keycloak.quarkus.runtime.cli.command.StartDev; -import io.quarkus.deployment.util.FileUtil; import io.quarkus.runtime.configuration.QuarkusConfigFactory; +import io.quarkus.test.common.TestResourceManager; import io.quarkus.test.junit.QuarkusMainTestExtension; import io.quarkus.test.junit.main.Launch; import io.quarkus.test.junit.main.LaunchResult; @@ -46,7 +45,6 @@ import static java.lang.System.setProperty; -import static org.keycloak.it.junit5.extension.DistributionTest.ReInstall.BEFORE_ALL; import static org.keycloak.it.junit5.extension.DistributionType.RAW; import static org.keycloak.quarkus.runtime.Environment.forceTestLaunchMode; @@ -56,7 +54,8 @@ public class CLITestExtension extends QuarkusMainTestExtension { private DatabaseContainer databaseContainer; private InfinispanContainer infinispanContainer; private CLIResult result; - + private boolean beforeAll; + @Override public void beforeEach(ExtensionContext context) throws Exception { DistributionTest distConfig = getDistributionConfig(context); @@ -74,6 +73,15 @@ public void beforeEach(ExtensionContext context) throws Exception { } }); } + + if (isRaw() && distConfig != null && dist != null) { + try { + dist.unwrap(RawKeycloakDistribution.class).reset(beforeAll); + beforeAll = false; + } catch (Exception cause) { + throw new RuntimeException("Failed to partially reset", cause); + } + } configureDatabase(context); infinispanContainer = configureExternalInfinispan(context); @@ -171,10 +179,6 @@ public void afterEach(ExtensionContext context) throws Exception { onKeepServerAlive(context.getRequiredTestMethod().getAnnotation(KeepServerAlive.class), false); dist.stop(); dist.clearEnv(); - - if (distConfig != null && DistributionTest.ReInstall.BEFORE_TEST.equals(distConfig.reInstall())) { - dist = null; - } } super.afterEach(context); @@ -191,30 +195,34 @@ private void reset(DistributionTest distConfig, ExtensionContext context) { infinispanContainer.stop(); } result = null; - if (isRaw()) { - if (distConfig != null && !DistributionTest.ReInstall.NEVER.equals(distConfig.reInstall()) && dist != null) { - try { - FileUtil.deleteDirectory(getDistPath().getDistRootPath().resolve("conf")); - getDistPath().getDistRootPath().resolve("conf").toFile().mkdirs(); - FileUtil.deleteDirectory(getDistPath().getDistRootPath().resolve("providers")); - getDistPath().getDistRootPath().resolve("providers").toFile().mkdirs(); - FileUtil.deleteDirectory(getDistPath().getDistRootPath().resolve("data")); - getDistPath().getDistRootPath().resolve("data").toFile().mkdirs(); - } catch (IOException e) { - throw new RuntimeException("Failed to delete conf directory"); - } - } - } } @Override public void beforeAll(ExtensionContext context) throws Exception { + // taken from QuarkusUnitTest - QuarkusMainTestExtension does not do resource management + ExtensionContext.Store store = context.getRoot().getStore(ExtensionContext.Namespace.GLOBAL); + TestResourceManager testResourceManager = (TestResourceManager) store.get(TestResourceManager.class.getName()); + if (testResourceManager == null) { + testResourceManager = new TestResourceManager(context.getRequiredTestClass()); + testResourceManager.init(null); + testResourceManager.start(); + TestResourceManager tm = testResourceManager; + store.put(TestResourceManager.class.getName(), testResourceManager); + store.put(TestResourceManager.CLOSEABLE_NAME, new AutoCloseable() { + + @Override + public void close() throws Exception { + tm.close(); + } + }); + } + + beforeAll = true; + DistributionTest distConfig = getDistributionConfig(context); if (distConfig != null) { - if (BEFORE_ALL.equals(distConfig.reInstall())) { - dist = createDistribution(distConfig, getStoreConfig(context), getDatabaseConfig(context)); - } + dist = createDistribution(distConfig, getStoreConfig(context), getDatabaseConfig(context)); } else { forceTestLaunchMode(); } diff --git a/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/DistributionTest.java b/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/DistributionTest.java index 5b8bde3b7fa8..726d5bb580f5 100644 --- a/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/DistributionTest.java +++ b/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/DistributionTest.java @@ -24,6 +24,12 @@ import org.junit.jupiter.api.extension.ExtendWith; +/** + * A Test that runs against a Keycloak distribution, which can be from a Docker container or zip (raw). + *
+ * Note with the raw distribution test methods are not completely isolated for performance reasons. + * Only a single distribution will be installed and it will only have its augmentation state reset before each test class. + */ @Target(ElementType.TYPE) @ExtendWith({ CLITestExtension.class }) @Retention(RetentionPolicy.RUNTIME) @@ -41,26 +47,6 @@ boolean keepAlive() default false; boolean enableTls() default false; - enum ReInstall { - - /** - * Install the distribution only once before running a test class. - */ - BEFORE_ALL, - - /** - * Re-install the distribution before running a test method. - */ - BEFORE_TEST, - - /** - * Does not reset the distribution such as removing data, providers, and conf directories. - */ - NEVER; - } - - ReInstall reInstall() default ReInstall.BEFORE_ALL; - /** * If any build option must be unset after the running the build command. */ diff --git a/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/DistributionType.java b/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/DistributionType.java index d4d786f747f3..9e7b932e2ebb 100644 --- a/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/DistributionType.java +++ b/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/DistributionType.java @@ -43,7 +43,7 @@ private static KeycloakDistribution createRawDistribution(DistributionTest confi config.debug(), config.keepAlive(), config.enableTls(), - !DistributionTest.ReInstall.NEVER.equals(config.reInstall()), + false, config.removeBuildOptionsAfterBuild(), config.requestPort()); } diff --git a/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/RawDistributionLifecycleManager.java b/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/RawDistributionLifecycleManager.java new file mode 100644 index 000000000000..9351eb8b61ac --- /dev/null +++ b/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/RawDistributionLifecycleManager.java @@ -0,0 +1,35 @@ +package org.keycloak.it.utils; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.Map; + +import io.quarkus.deployment.util.FileUtil; +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; + +public class RawDistributionLifecycleManager implements QuarkusTestResourceLifecycleManager { + + RawKeycloakDistribution dist; + + @Override + public Map start() { + dist = new RawKeycloakDistribution( + false, + false, + false, + true, + false, + 8080); + return null; + } + + @Override + public void stop() { + try { + FileUtil.deleteDirectory(dist.getDistPath()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + +} diff --git a/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java b/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java index c46e2d6cd698..2c4745e17780 100644 --- a/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java +++ b/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java @@ -67,8 +67,10 @@ import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers; import io.quarkus.deployment.util.FileUtil; +import io.quarkus.deployment.util.IoUtil; import io.quarkus.fs.util.ZipUtils; import io.restassured.RestAssured; +import org.apache.commons.io.FileUtils; import org.awaitility.Awaitility; import org.jboss.logging.Logger; import org.jboss.shrinkwrap.api.ShrinkWrap; @@ -86,7 +88,7 @@ public final class RawKeycloakDistribution implements KeycloakDistribution { private static final Logger LOG = Logger.getLogger(RawKeycloakDistribution.class); private Process keycloak; private int exitCode = -1; - private final Path distPath; + private Path distPath; private boolean manualStop; private String relativePath; private int httpPort; @@ -97,7 +99,6 @@ public final class RawKeycloakDistribution implements KeycloakDistribution { private final boolean removeBuildOptionsAfterBuild; private final int requestPort; private ExecutorService outputExecutor; - private boolean inited = false; private final Map envVars = new HashMap<>(); private final OutputConsumer outputConsumer; private long startTimeout = TimeUnit.SECONDS.toMillis(Long.getLong("keycloak.distribution.start.timeout", 120L)); @@ -105,10 +106,6 @@ public final class RawKeycloakDistribution implements KeycloakDistribution { private boolean threadDump = true; public RawKeycloakDistribution(boolean debug, boolean manualStop, boolean enableTls, boolean reCreate, boolean removeBuildOptionsAfterBuild, int requestPort) { - this(debug, manualStop, enableTls, reCreate, removeBuildOptionsAfterBuild, requestPort, new DefaultOutputConsumer()); - } - - public RawKeycloakDistribution(boolean debug, boolean manualStop, boolean enableTls, boolean reCreate, boolean removeBuildOptionsAfterBuild, int requestPort, OutputConsumer outputConsumer) { this.debug = debug; this.manualStop = manualStop; this.enableTls = enableTls; @@ -116,7 +113,7 @@ public RawKeycloakDistribution(boolean debug, boolean manualStop, boolean enable this.removeBuildOptionsAfterBuild = removeBuildOptionsAfterBuild; this.requestPort = requestPort; this.distPath = prepareDistribution(); - this.outputConsumer = outputConsumer; + this.outputConsumer = new DefaultOutputConsumer(); } public RawKeycloakDistribution withThrowErrorIfFailedToStart(boolean throwErrorIfFailedToStart) { @@ -542,14 +539,13 @@ private Path prepareDistribution() { Path dPath = distRootPath.resolve(inDistZipDirectory(distFile)); - if (!inited || (reCreate || !dPath.toFile().exists())) { + if (reCreate || !dPath.toFile().exists()) { FileUtil.deleteDirectory(dPath); ZipUtils.unzip(distFile.toPath(), distRootPath); - - if (System.getProperty("product") != null) { - // JDBC drivers might be excluded if running as a product build - copyProvider(dPath, "com.microsoft.sqlserver", "mssql-jdbc"); - } + FileUtils.copyDirectory(dPath.resolve("conf").toFile(), dPath.resolve("conf-bak").toFile()); + FileUtils.copyDirectory(dPath.resolve("lib").resolve("quarkus").toFile(), dPath.resolve("quarkus-bak").toFile()); + preInitH2(dPath); + postInit(dPath); } // make sure script is executable @@ -560,14 +556,43 @@ private Path prepareDistribution() { throw new RuntimeException("Cannot set " + SCRIPT_KCADM_CMD + " executable"); } - inited = true; - return dPath; } catch (Exception cause) { throw new RuntimeException("Failed to prepare distribution", cause); } } + private void preInitH2(Path dPath) throws IOException { + LOG.info("Creating pre-initialized database for reuse"); + ProcessHandle.current().info().command().ifPresent(command -> { + ProcessBuilder pb = new ProcessBuilder(List.of(command, "-Dinit_db_only=true", + "-Dkc.home.dir=" + dPath.toFile().getAbsolutePath(), "-Dkc.config.built=true", + "-Dkc.db=dev-file", "-jar", dPath.resolve("lib").resolve("quarkus-run.jar").toFile().getAbsolutePath(), "start-dev")); + try { + Process dbInit = pb.start(); + if (!dbInit.waitFor(30, TimeUnit.SECONDS)) { + throw new RuntimeException("Pre-initialized db process did not exit"); + } + if (dbInit.exitValue() != 0) { + throw new RuntimeException("Could not create pre-initialized db, exit code %s: %s".formatted(dbInit.exitValue(), new String(IoUtil.readBytes(dbInit.getErrorStream())))); + } + } catch (IOException e) { + throw new RuntimeException(e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } + }); + FileUtils.copyDirectory(dPath.resolve("data").resolve("h2").toFile(), dPath.resolve("h2-bak").toFile()); + } + + private void postInit(Path dPath) { + if (System.getProperty("product") != null) { + // JDBC drivers might be excluded if running as a product build + copyProvider(dPath, "com.microsoft.sqlserver", "mssql-jdbc"); + } + } + private void readOutput() { readOutput(keycloak, outputConsumer); } @@ -657,12 +682,12 @@ public void deleteQuarkusProperties() { @Override public void copyOrReplaceFileFromClasspath(String file, Path targetFile) { - File targetDir = distPath.resolve(targetFile).toFile(); + Path path = distPath.resolve(targetFile); - targetDir.mkdirs(); + path.getParent().toFile().mkdirs(); try { - Files.copy(getClass().getResourceAsStream(file), targetDir.toPath(), StandardCopyOption.REPLACE_EXISTING); + Files.copy(getClass().getResourceAsStream(file), path, StandardCopyOption.REPLACE_EXISTING); } catch (IOException cause) { throw new RuntimeException("Failed to copy file", cause); } @@ -674,12 +699,12 @@ public void copyOrReplaceFile(Path file, Path targetFile) { return; } - File targetDir = distPath.resolve(targetFile).toFile(); + Path path = distPath.resolve(targetFile); - targetDir.mkdirs(); + path.getParent().toFile().mkdirs(); try { - Files.copy(file, targetDir.toPath(), StandardCopyOption.REPLACE_EXISTING); + Files.copy(file, path, StandardCopyOption.REPLACE_EXISTING); } catch (IOException cause) { throw new RuntimeException("Failed to copy file", cause); } @@ -820,4 +845,34 @@ public List getStdOut() { return stdOut; } } + + public void resetH2Dir() throws IOException { + FileUtil.deleteDirectory(getDistPath().resolve("data").resolve("h2")); + FileUtils.copyDirectory(getDistPath().resolve("h2-bak").toFile(), getDistPath().resolve("data").resolve("h2").toFile()); + } + + /** + * Reset the distribution back to its install state. + * @param resetAugmentation if true the lib/quarkus directory will be reset + */ + public void reset(boolean resetAugmentation) throws IOException { + LOG.infof("Resetting the distribution for the next test%s %s", resetAugmentation ? " including augmentation" : "", distPath); + FileUtil.deleteDirectory(getDistPath().resolve("conf")); + FileUtils.copyDirectory(getDistPath().resolve("conf-bak").toFile(), getDistPath().resolve("conf").toFile()); + FileUtil.deleteDirectory(getDistPath().resolve("providers")); + getDistPath().resolve("providers").toFile().mkdirs(); + FileUtil.deleteDirectory(getDistPath().resolve("data")); + getDistPath().resolve("data").toFile().mkdirs(); + resetH2Dir(); + if (resetAugmentation) { + FileUtil.deleteDirectory(getDistPath().resolve("lib").resolve("quarkus")); + FileUtils.copyDirectory(getDistPath().resolve("quarkus-bak").toFile(), getDistPath().resolve("lib").resolve("quarkus").toFile()); + } + postInit(getDistPath()); + } + + public void setDistPath(Path newPath) throws IOException { + FileUtils.moveDirectory(distPath.toFile(), newPath.toFile()); + this.distPath = newPath; + } } diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java index 0505bb21d0f0..f5da995552f3 100644 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -38,6 +38,7 @@ import org.keycloak.services.DefaultKeycloakSessionFactory; import org.keycloak.services.managers.ApplianceBootstrap; +import io.quarkus.runtime.Quarkus; import org.jboss.logging.Logger; /** @@ -110,6 +111,13 @@ private synchronized void runBootstrap(DefaultKeycloakSessionFactory keycloakSes var startTime = System.nanoTime(); keycloakSessionFactory.init(); + + // for testing, allow the quarkus application to exit early + if (io.quarkus.runtime.Application.currentApplication() != null && Boolean.getBoolean("init_db_only")) { + Quarkus.asyncExit(0); + return; + } + setTransactionTimeout(keycloakSessionFactory); var exportImportManager = KeycloakModelUtils.runJobInTransactionWithResult(keycloakSessionFactory, session -> { DBLockManager dbLockManager = new DBLockManager(session);