From e3af0610e21961c1e8d074a223f9fffee0e56dde Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Tue, 9 Aug 2022 04:18:25 -0700 Subject: [PATCH] Support running base testsuite on Windows Closes #12648 Co-authored-by: Dominik Guhr --- .../keycloak/quarkus/runtime/Environment.java | 4 +- .../mappers/CachingPropertyMappers.java | 3 +- testsuite/integration-arquillian/pom.xml | 2 +- .../auth-server/quarkus/ant/configure.xml | 5 + .../src/main/content/conf/keycloak.conf | 2 +- .../integration-arquillian/tests/base/pom.xml | 9 +- ...cloakQuarkusServerDeployableContainer.java | 101 ++++++++++++++++-- 7 files changed, 109 insertions(+), 17 deletions(-) diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/Environment.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/Environment.java index 1f43f517ef..19e852e207 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/Environment.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/Environment.java @@ -41,8 +41,8 @@ public final class Environment { public static final String IMPORT_EXPORT_MODE = "import_export"; public static final String PROFILE ="kc.profile"; public static final String ENV_PROFILE ="KC_PROFILE"; - public static final String DATA_PATH = "/data"; - public static final String DEFAULT_THEMES_PATH = "/themes"; + public static final String DATA_PATH = File.separator + "data"; + public static final String DEFAULT_THEMES_PATH = File.separator + "themes"; public static final String DEV_PROFILE_VALUE = "dev"; public static final String PROD_PROFILE_VALUE = "prod"; public static final String LAUNCH_MODE = "kc.launch.mode"; diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/CachingPropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/CachingPropertyMappers.java index 86536ffe5a..a6de02c2b8 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/CachingPropertyMappers.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/CachingPropertyMappers.java @@ -8,6 +8,7 @@ import io.smallrye.config.ConfigSourceInterceptorContext; import static java.util.Optional.of; import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption; +import java.io.File; import java.util.Optional; final class CachingPropertyMappers { @@ -46,7 +47,7 @@ final class CachingPropertyMappers { if (homeDir == null) { pathPrefix = ""; } else { - pathPrefix = homeDir + "/conf/"; + pathPrefix = homeDir + File.separator + "conf" + File.separator; } return of(pathPrefix + value.get()); diff --git a/testsuite/integration-arquillian/pom.xml b/testsuite/integration-arquillian/pom.xml index 21f489c984..3237efe6a4 100644 --- a/testsuite/integration-arquillian/pom.xml +++ b/testsuite/integration-arquillian/pom.xml @@ -46,7 +46,7 @@ 3.1.4 3.14.0 2.5.5 - 2.5.4 + 3.0.0-alpha.3 3.0.1.Final 1.0.1.Final 1.0.0.CR3 diff --git a/testsuite/integration-arquillian/servers/auth-server/quarkus/ant/configure.xml b/testsuite/integration-arquillian/servers/auth-server/quarkus/ant/configure.xml index afc9fbe556..da6d362f07 100644 --- a/testsuite/integration-arquillian/servers/auth-server/quarkus/ant/configure.xml +++ b/testsuite/integration-arquillian/servers/auth-server/quarkus/ant/configure.xml @@ -8,6 +8,11 @@ + + + + + diff --git a/testsuite/integration-arquillian/servers/auth-server/quarkus/src/main/content/conf/keycloak.conf b/testsuite/integration-arquillian/servers/auth-server/quarkus/src/main/content/conf/keycloak.conf index cf04ad3251..8757aa13a6 100644 --- a/testsuite/integration-arquillian/servers/auth-server/quarkus/src/main/content/conf/keycloak.conf +++ b/testsuite/integration-arquillian/servers/auth-server/quarkus/src/main/content/conf/keycloak.conf @@ -46,4 +46,4 @@ spi-login-protocol-saml-known-protocols=http=8180,https=8543 # File-Based Vault vault=file -vault-dir=${kc.home.dir}secrets +vault-dir=${kc.home.dir}/secrets diff --git a/testsuite/integration-arquillian/tests/base/pom.xml b/testsuite/integration-arquillian/tests/base/pom.xml index 16f71cf736..1d9340c17d 100644 --- a/testsuite/integration-arquillian/tests/base/pom.xml +++ b/testsuite/integration-arquillian/tests/base/pom.xml @@ -44,6 +44,10 @@ 2.4.0.Final 1.19.0 ${basedir}/../../servers/auth-server/jboss/common + 3.8.1 + 11 + 11 + 11 @@ -365,7 +369,10 @@ - + + maven-compiler-plugin + ${maven-compiler-plugin.version} + diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakQuarkusServerDeployableContainer.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakQuarkusServerDeployableContainer.java index bb75baf191..774f37e1a1 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakQuarkusServerDeployableContainer.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakQuarkusServerDeployableContainer.java @@ -14,21 +14,26 @@ import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; +import java.nio.file.FileVisitResult; import java.nio.file.Files; +import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; import org.apache.commons.exec.StreamPumper; -import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.SystemUtils; import org.jboss.arquillian.container.spi.client.container.DeployableContainer; import org.jboss.arquillian.container.spi.client.container.DeploymentException; @@ -48,6 +53,7 @@ import org.keycloak.testsuite.arquillian.SuiteContext; */ public class KeycloakQuarkusServerDeployableContainer implements DeployableContainer { + private static final int DEFAULT_SHUTDOWN_TIMEOUT_SECONDS = 10; private static final String AUTH_SERVER_QUARKUS_MAP_STORAGE_PROFILE = "auth.server.quarkus.mapStorage.profile"; private static final Logger log = Logger.getLogger(KeycloakQuarkusServerDeployableContainer.class); @@ -87,11 +93,15 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta @Override public void stop() throws LifecycleException { - container.destroy(); - try { - container.waitFor(10, TimeUnit.SECONDS); - } catch (InterruptedException e) { - container.destroyForcibly(); + if (container.isAlive()) { + try { + destroyDescendantsOnWindows(container, false); + container.destroy(); + container.waitFor(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + destroyDescendantsOnWindows(container, true); + container.destroyForcibly(); + } } } @@ -118,6 +128,10 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta public void undeploy(Archive archive) throws DeploymentException { File wrkDir = configuration.getProvidersPath().resolve("providers").toFile(); try { + if (isWindows()) { + // stop before updating providers to avoid file locking issues on Windows + stop(); + } Files.deleteIfExists(wrkDir.toPath().resolve(archive.getName())); restartServer(); } catch (Exception e) { @@ -172,7 +186,7 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta builder.environment().put("KEYCLOAK_ADMIN_PASSWORD", "admin"); if (restart.compareAndSet(false, true)) { - FileUtils.deleteDirectory(configuration.getProvidersPath().resolve("data").toFile()); + deleteDirectory(configuration.getProvidersPath().resolve("data")); } return builder.start(); @@ -346,7 +360,11 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta additionalBuildArgs = Collections.emptyList(); } - private void deployArchiveToServer(Archive archive) throws IOException { + private void deployArchiveToServer(Archive archive) throws IOException, LifecycleException { + if (isWindows()) { + // stop before updating providers to avoid file locking issues on Windows + stop(); + } File providersDir = configuration.getProvidersPath().resolve("providers").toFile(); InputStream zipStream = archive.as(ZipExporter.class).exportAsInputStream(); Files.copy(zipStream, providersDir.toPath().resolve(archive.getName()), StandardCopyOption.REPLACE_EXISTING); @@ -357,9 +375,9 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta start(); } - private static String getCommand() { - if (SystemUtils.IS_OS_WINDOWS) { - return "kc.bat"; + private String getCommand() { + if (isWindows()) { + return configuration.getProvidersPath().resolve("bin").resolve("kc.bat").toString(); } return "./kc.sh"; } @@ -371,4 +389,65 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta public void setAdditionalBuildArgs(List newArgs) { additionalBuildArgs = newArgs; } + + private void destroyDescendantsOnWindows(Process parent, boolean force) { + if (!isWindows()) { + return; + } + + CompletableFuture allProcesses = CompletableFuture.completedFuture(null); + + for (ProcessHandle process : parent.descendants().collect(Collectors.toList())) { + if (force) { + process.destroyForcibly(); + } else { + process.destroy(); + } + + allProcesses = CompletableFuture.allOf(allProcesses, process.onExit()); + } + + try { + allProcesses.get(DEFAULT_SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } catch (Exception cause) { + throw new RuntimeException("Failed to terminate descendants processes", cause); + } + + try { + // TODO: remove this. do not ask why, but on Windows we are here even though the process was previously terminated + // without this pause, tests re-installing dist before tests should fail + // looks like pausing the current thread let windows to cleanup processes? + // more likely it is env dependent + Thread.sleep(500); + } catch (InterruptedException e) { + } + } + + private static boolean isWindows() { + return SystemUtils.IS_OS_WINDOWS; + } + + public static void deleteDirectory(final Path directory) throws IOException { + if (Files.isDirectory(directory, new LinkOption[0])) { + Files.walkFileTree(directory, new SimpleFileVisitor() { + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + try { + Files.delete(file); + } catch (IOException var4) { + } + + return FileVisitResult.CONTINUE; + } + + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + try { + Files.delete(dir); + } catch (IOException var4) { + } + + return FileVisitResult.CONTINUE; + } + }); + } + } }