From 997c3ceb54222f88f3638864d9b9746d06a10d19 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Wed, 6 Oct 2021 18:09:14 +0100 Subject: [PATCH] Introduce integration tests to cover the production classloader --- .github/quarkus-bot.yml | 2 + integration-tests/pom.xml | 1 + integration-tests/production-mode/README.md | 7 ++ .../production-mode/disable-native-profile | 1 + integration-tests/production-mode/pom.xml | 81 +++++++++++++++++++ .../it/prodmode/ForkJoinPoolAssertions.java | 65 +++++++++++++++ .../prodmode/ProductionModeTestsEndpoint.java | 48 +++++++++++ .../io/quarkus/it/prodmode/ProdModeTest.java | 50 ++++++++++++ 8 files changed, 255 insertions(+) create mode 100644 integration-tests/production-mode/README.md create mode 100644 integration-tests/production-mode/disable-native-profile create mode 100644 integration-tests/production-mode/pom.xml create mode 100644 integration-tests/production-mode/src/main/java/io/quarkus/it/prodmode/ForkJoinPoolAssertions.java create mode 100644 integration-tests/production-mode/src/main/java/io/quarkus/it/prodmode/ProductionModeTestsEndpoint.java create mode 100644 integration-tests/production-mode/src/test/java/io/quarkus/it/prodmode/ProdModeTest.java diff --git a/.github/quarkus-bot.yml b/.github/quarkus-bot.yml index 7c3a0ee9fca450..90ca4d514c15b7 100644 --- a/.github/quarkus-bot.yml +++ b/.github/quarkus-bot.yml @@ -328,8 +328,10 @@ triage: - core/deployment/src/main/java/io/quarkus/deployment/configuration/ - core/runtime/src/main/java/io/quarkus/runtime/configuration/ - labels: [area/core] + notify: [aloubyansky, gsmet, geoand, radcortez, Sanne, stuartwdouglas] directories: - core/ + - integration-tests/production-mode - labels: [area/dependencies] directories: - .github/dependabot.yml diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 9591af5495a034..f7f8cc9e84d54f 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -113,6 +113,7 @@ + production-mode avro-reload awt bouncycastle diff --git a/integration-tests/production-mode/README.md b/integration-tests/production-mode/README.md new file mode 100644 index 00000000000000..16f6ac6e93cafb --- /dev/null +++ b/integration-tests/production-mode/README.md @@ -0,0 +1,7 @@ +The purpose of this module is to run integration tests +on the core state assumptions of a Quarkus application +booted in production mode (on JVM). + +These same tests are not expected to be run in other modes, +and the same assumptions are not expected to hold in +native-image mode either. diff --git a/integration-tests/production-mode/disable-native-profile b/integration-tests/production-mode/disable-native-profile new file mode 100644 index 00000000000000..011a7cc4571d5e --- /dev/null +++ b/integration-tests/production-mode/disable-native-profile @@ -0,0 +1 @@ +This file disables the native profile in the parent pom.xml of this module. \ No newline at end of file diff --git a/integration-tests/production-mode/pom.xml b/integration-tests/production-mode/pom.xml new file mode 100644 index 00000000000000..65d9973cbc4e85 --- /dev/null +++ b/integration-tests/production-mode/pom.xml @@ -0,0 +1,81 @@ + + + + quarkus-integration-tests-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + production-mode + Quarkus - Integration Tests - Quarkus Production Mode + Integration tests specifically requiring to run Quarkus in prod mode + + + + io.quarkus + quarkus-resteasy + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + io.quarkus + quarkus-junit5-internal + test + + + + + io.quarkus + quarkus-resteasy-deployment + ${project.version} + pom + test + + + * + * + + + + + + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + maven-failsafe-plugin + + + + integration-test + verify + + + + + + + + + diff --git a/integration-tests/production-mode/src/main/java/io/quarkus/it/prodmode/ForkJoinPoolAssertions.java b/integration-tests/production-mode/src/main/java/io/quarkus/it/prodmode/ForkJoinPoolAssertions.java new file mode 100644 index 00000000000000..fbfc8913c0a3e5 --- /dev/null +++ b/integration-tests/production-mode/src/main/java/io/quarkus/it/prodmode/ForkJoinPoolAssertions.java @@ -0,0 +1,65 @@ +package io.quarkus.it.prodmode; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Helper to collect assertions related to the JDK ForkJoinPool + * state. + */ +public class ForkJoinPoolAssertions { + + /** + * We test which Classloader is being used by each thread + * in the common pool of ForkJoinPool. + * It is expected that a Quarkus application running in production mode + * will have set the io.quarkus.bootstrap.runner.RunnerClassLoader as + * context classloader on each of them, to prevent problems at runtime. + * + * @return true only if all checks are successful. + */ + static boolean isEachFJThreadUsingQuarkusClassloader() { + AtomicInteger testedOk = new AtomicInteger(); + final int poolParallelism = ForkJoinPool.getCommonPoolParallelism(); + CountDownLatch allDone = new CountDownLatch(poolParallelism); + CountDownLatch taskRelease = new CountDownLatch(1); + if (poolParallelism < 1) { + System.err + .println("Can't test this when ForkJoinPool.getCommonPoolParallelism() has been forced to less than one."); + return false; + } + for (int i = 0; i < poolParallelism; ++i) { + ForkJoinPool.commonPool().execute(new Runnable() { + @Override + public void run() { + final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + final String classLoaderImplementationName = contextClassLoader.getClass().getName(); + if (classLoaderImplementationName.equals(io.quarkus.bootstrap.runner.RunnerClassLoader.class.getName())) { + testedOk.incrementAndGet(); + } else { + System.out.println("Unexpected classloader name used in production: " + classLoaderImplementationName); + } + allDone.countDown(); + try { + taskRelease.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }); + } + try { + if (!allDone.await(10, TimeUnit.SECONDS)) { + throw new IllegalStateException("Timed out while trying to scale up the fork join pool"); + } + } catch (InterruptedException e) { + e.printStackTrace(); + return false; + } finally { + taskRelease.countDown(); + } + return testedOk.get() == poolParallelism; + } +} diff --git a/integration-tests/production-mode/src/main/java/io/quarkus/it/prodmode/ProductionModeTestsEndpoint.java b/integration-tests/production-mode/src/main/java/io/quarkus/it/prodmode/ProductionModeTestsEndpoint.java new file mode 100644 index 00000000000000..e1118032374b77 --- /dev/null +++ b/integration-tests/production-mode/src/main/java/io/quarkus/it/prodmode/ProductionModeTestsEndpoint.java @@ -0,0 +1,48 @@ +package io.quarkus.it.prodmode; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import io.quarkus.runtime.Quarkus; + +@Path("/production-mode-tests") +public class ProductionModeTestsEndpoint { + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String hello() { + return "hello"; + } + + @GET + @Path("areExpectedSystemPropertiesSet") + @Produces(MediaType.TEXT_PLAIN) + public String areExpectedSystemPropertiesSet() { + if (!"org.jboss.logmanager.LogManager".equals(System.getProperty("java.util.logging.manager"))) { + return "no"; + } + if (!"io.quarkus.bootstrap.forkjoin.QuarkusForkJoinWorkerThreadFactory" + .equals(System.getProperty("java.util.concurrent.ForkJoinPool.common.threadFactory"))) { + return "no"; + } + return "yes"; + } + + @GET + @Path("isForkJoinPoolUsingExpectedClassloader") + @Produces(MediaType.TEXT_PLAIN) + public String isForkJoinPoolUsingExpectedClassloader() { + return ForkJoinPoolAssertions.isEachFJThreadUsingQuarkusClassloader() ? "yes" : "no"; + } + + @GET + @Path("shutdown") + @Produces(MediaType.TEXT_PLAIN) + public String shutdown() { + System.out.println("Terminating Quarkus app!"); + Quarkus.asyncExit(); + return "quitting"; + } +} diff --git a/integration-tests/production-mode/src/test/java/io/quarkus/it/prodmode/ProdModeTest.java b/integration-tests/production-mode/src/test/java/io/quarkus/it/prodmode/ProdModeTest.java new file mode 100644 index 00000000000000..8478397565c94a --- /dev/null +++ b/integration-tests/production-mode/src/test/java/io/quarkus/it/prodmode/ProdModeTest.java @@ -0,0 +1,50 @@ +package io.quarkus.it.prodmode; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.is; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusProdModeTest; + +public class ProdModeTest { + + @RegisterExtension + static final QuarkusProdModeTest config = new QuarkusProdModeTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(ProductionModeTestsEndpoint.class, ForkJoinPoolAssertions.class)) + .setApplicationName("prod-mode-quarkus") + .setApplicationVersion("0.1-SNAPSHOT") + .setRun(true); + + @Test + public void basicApplicationAliveTest() { + given() + .when().get("/production-mode-tests") + .then() + .statusCode(200) + .body(is("hello")); + } + + @Test + public void areExpectedSystemPropertiesSet() { + given() + .when().get("/production-mode-tests/areExpectedSystemPropertiesSet") + .then() + .statusCode(200) + .body(is("yes")); + } + + @Test + public void isForkJoinPoolUsingExpectedClassloader() { + given() + .when().get("/production-mode-tests/isForkJoinPoolUsingExpectedClassloader") + .then() + .statusCode(200) + .body(is("yes")); + } + +}