From 5ea9b52b6090a8cbb217b17dd340a5ce0aa72205 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Wed, 15 May 2024 10:39:41 +0200 Subject: [PATCH] Fix QuarkusProdModeTest mistaking Hibernate ORM logs for a proof of application startup Without this, QuarkusProdModeTest runs the tests too early because it mistakes this log line for the "Installed features: ..." line that Quarkus usually outputs on startup: > 2024-05-15 09:11:26,199 WARN [org.hib.dia.Dialect] (main) HHH000511: The -9999.-9999.-9999 version for [org.hibernate.dialect.PostgreSQLDialect] is no longer supported, hence certain features may not work properly. The minimum supported version is 12.0.0. Check the community dialects project for available legacy versions. Note how the line contains the word "features". See https://quarkusio.zulipchat.com/#narrow/stream/187038-dev/topic/Hibernate.206.2E5/near/438739167 --- .../io/quarkus/test/QuarkusProdModeTest.java | 2 +- .../QuarkusProdModeTestConfusingLogTest.java | 110 ++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 test-framework/junit5-internal/src/test/java/io/quarkus/test/QuarkusProdModeTestConfusingLogTest.java diff --git a/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusProdModeTest.java b/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusProdModeTest.java index bb75fc407f5ff..f2f9de9fd5b8f 100644 --- a/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusProdModeTest.java +++ b/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusProdModeTest.java @@ -74,7 +74,7 @@ public class QuarkusProdModeTest implements BeforeAllCallback, AfterAllCallback, BeforeEachCallback, TestWatcher, InvocationInterceptor { - private static final String EXPECTED_OUTPUT_FROM_SUCCESSFULLY_STARTED = "features"; + private static final String EXPECTED_OUTPUT_FROM_SUCCESSFULLY_STARTED = "Installed features"; private static final int DEFAULT_HTTP_PORT_INT = 8081; private static final String DEFAULT_HTTP_PORT = "" + DEFAULT_HTTP_PORT_INT; private static final String QUARKUS_HTTP_PORT_PROPERTY = "quarkus.http.port"; diff --git a/test-framework/junit5-internal/src/test/java/io/quarkus/test/QuarkusProdModeTestConfusingLogTest.java b/test-framework/junit5-internal/src/test/java/io/quarkus/test/QuarkusProdModeTestConfusingLogTest.java new file mode 100644 index 0000000000000..2450c61632824 --- /dev/null +++ b/test-framework/junit5-internal/src/test/java/io/quarkus/test/QuarkusProdModeTestConfusingLogTest.java @@ -0,0 +1,110 @@ +package io.quarkus.test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import com.sun.net.httpserver.HttpServer; + +import io.quarkus.runtime.Quarkus; +import io.quarkus.runtime.annotations.QuarkusMain; + +public class QuarkusProdModeTestConfusingLogTest { + + @RegisterExtension + static final QuarkusProdModeTest simpleApp = new QuarkusProdModeTest() + .withApplicationRoot(jar -> jar.addClass(Main.class)) + .setApplicationName("simple-app") + .setApplicationVersion("0.1-SNAPSHOT") + .setRun(true); + + static HttpClient client; + + @BeforeAll + static void setUp() { + // No tear down, because there's no way to shut down the client explicitly before Java 21 :( + // We'll just hope no connection is left hanging. + client = HttpClient.newBuilder() + .connectTimeout(Duration.ofMillis(100)) + .build(); + } + + @Test + public void shouldWaitForAppActuallyStarted() { + thenAppIsRunning(); + + whenStopApp(); + thenAppIsNotRunning(); + + whenStartApp(); + thenAppIsRunning(); + } + + private void whenStopApp() { + simpleApp.stop(); + } + + private void whenStartApp() { + simpleApp.start(); + } + + private void thenAppIsNotRunning() { + assertNotNull(simpleApp.getExitCode(), "App is running"); + assertThrows(IOException.class, this::tryReachApp, "App's HTTP server is still running"); + } + + private void thenAppIsRunning() { + assertNull(simpleApp.getExitCode(), "App is not running"); + assertDoesNotThrow(this::tryReachApp, "App's HTTP server is not reachable"); + } + + private void tryReachApp() throws IOException, InterruptedException { + String response = client.send(HttpRequest.newBuilder().uri(URI.create("http://localhost:8081/test")).GET().build(), + HttpResponse.BodyHandlers.ofString()) + .body(); + // If the app is reachable, this is the expected response. + assertEquals("OK", response, "App returned unexpected response"); + } + + @QuarkusMain + public static class Main { + public static void main(String[] args) { + // Use an unrelated log to trick QuarkusProdModeTest into thinking the app started + System.out.println( + "HHH000511: The -9999.-9999.-9999 version for [org.hibernate.dialect.PostgreSQLDialect] is no longer supported, hence certain features may not work properly. The minimum supported version is 12.0.0. Check the community dialects project for available legacy versions."); + try { + // Delay the actual app start so there's a decent chance of QuarkusProdModeTest + // being ahead of the app -- otherwise we wouldn't reproduce the bug. + Thread.sleep(500); + // Expose an endpoint proving the app is up + HttpServer server = HttpServer.create(new InetSocketAddress(8081), 0); + server.createContext("/test", exchange -> { + String response = "OK"; + exchange.sendResponseHeaders(200, response.length()); + OutputStream os = exchange.getResponseBody(); + os.write(response.getBytes()); + os.close(); + }); + server.start(); + Quarkus.run(args); + } catch (InterruptedException | IOException e) { + throw new RuntimeException(e); + } + } + } +}