From e0ad83eb2f47fe75c2c1c95c23888a40e09a612a Mon Sep 17 00:00:00 2001 From: Zakaria El Orche Date: Wed, 9 Sep 2026 11:05:06 +0000 Subject: [PATCH] test(core): read bound ports back from the app instead of guessing free ones MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every integration test picked a port by opening ServerSocket(0), closing it and reusing the number, which races anything else on the machine between the close and the rebind. FlashApp.port() reports the port the listener actually bound, so the guess is gone: 18 freePort() helpers deleted, 40 call sites now pass port(0) and read the result back. Two ServerSocket(0) uses remain and are correct. ConnectionRunnerTest accepts on its socket rather than using it to pick a number. H2LoadMeasurementTest hands its port to an external nghttpd process, which has no equivalent of port() to read back; that one is now commented to say so. HttpServerTlsTest's two-listener case reads both back through ports(). HttpServerTest and HttpServerConcurrencyTest also move from @BeforeEach to @BeforeAll — every test in them is read-only against the same routes, so 11 and 3 boots respectively become 1. HttpServerConcurrencyTest's lazy-compile test keeps building its own app, since a freshly compiled router is the thing it tests. HttpServerTest now runs 11 tests in 0.06s. The http2 interop suites (curl, nghttp, grpcurl, h2spec), the load measurement and the soak test are skipped without their external binaries or system property, so those edits are compile-verified here and exercised in CI. Co-Authored-By: Claude Opus 5 --- .../flash/HttpServerConcurrencyTest.java | 38 ++++++-------- .../java/dev/relism/flash/HttpServerTest.java | 27 +++++----- .../relism/flash/HttpServerTimeoutTest.java | 27 ++++------ .../dev/relism/flash/HttpServerTlsTest.java | 51 +++++++++---------- .../relism/flash/HttpServerWebSocketTest.java | 8 +-- .../extension/FlashAppWebSocketTest.java | 8 +-- .../relism/flash/http2/CurlInteropTest.java | 13 ++--- .../relism/flash/http2/GrpcInteropTest.java | 9 +--- .../flash/http2/H2LoadMeasurementTest.java | 8 ++- .../flash/http2/H2SpecComplianceTest.java | 13 ++--- .../flash/http2/H2cPriorKnowledgeTest.java | 13 ++--- .../relism/flash/http2/Http2AbuseTest.java | 9 +--- .../flash/http2/Http2ConcurrencyTest.java | 9 +--- .../relism/flash/http2/Http2ConnectTest.java | 9 +--- .../http2/Http2ConnectionIntegrationTest.java | 45 ++++++++-------- .../http2/Http2MisdirectedRequestTest.java | 9 +--- .../http2/Http2RegressionCorpusTest.java | 9 +--- .../dev/relism/flash/http2/Http2SoakTest.java | 9 +--- .../relism/flash/http2/Http2TrailersTest.java | 13 ++--- .../relism/flash/http2/NghttpInteropTest.java | 9 +--- .../flash/http2/WebSocketOverH2Test.java | 9 +--- .../flash/http2/WebSocketParityTest.java | 9 +--- .../ServerLifecycleGracefulShutdownTest.java | 13 ++--- 23 files changed, 132 insertions(+), 235 deletions(-) diff --git a/flash/src/test/java/dev/relism/flash/HttpServerConcurrencyTest.java b/flash/src/test/java/dev/relism/flash/HttpServerConcurrencyTest.java index ea4368e..942a1d7 100644 --- a/flash/src/test/java/dev/relism/flash/HttpServerConcurrencyTest.java +++ b/flash/src/test/java/dev/relism/flash/HttpServerConcurrencyTest.java @@ -4,11 +4,10 @@ import dev.relism.flash.extension.FlashApp; import dev.relism.flash.extension.FlashConfiguration; import dev.relism.flash.http.ContentType; import dev.relism.flash.models.Response; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import java.net.ServerSocket; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; @@ -25,20 +24,17 @@ import static org.junit.jupiter.api.Assertions.*; class HttpServerConcurrencyTest { - private FlashApp app; - private int port; - private HttpClient httpClient; - - @BeforeEach - void setUp() throws Exception { - try (ServerSocket s = new ServerSocket(0)) { - port = s.getLocalPort(); - } + private static FlashApp app; + private static int port; + private static HttpClient httpClient; + @BeforeAll + static void setUp() { app = FlashApp.create(FlashConfiguration.builder() - .port(port) + .port(0) .host("127.0.0.1") .build()); + port = app.port(); app.get("/ping", (req, res) -> "pong"); app.post("/echo", (req, res) -> { @@ -53,9 +49,9 @@ class HttpServerConcurrencyTest { .build(); } - @AfterEach - void tearDown() { - if (app != null) app.stop(); + @AfterAll + static void tearDown() { + if (app != null) app.stop().join(); } // --- helpers --- @@ -173,15 +169,13 @@ class HttpServerConcurrencyTest { */ @Test void concurrent_lazyCompile_noRaceCondition() throws Exception { - int freshPort; - try (ServerSocket s = new ServerSocket(0)) { - freshPort = s.getLocalPort(); - } - + // Deliberately a brand-new app: this test is about compiling routes lazily on first + // use, so it must not share the class-scoped server. FlashApp freshApp = FlashApp.create(FlashConfiguration.builder() - .port(freshPort) + .port(0) .host("127.0.0.1") .build()); + int freshPort = freshApp.port(); for (int i = 0; i < 10; i++) { final int idx = i; diff --git a/flash/src/test/java/dev/relism/flash/HttpServerTest.java b/flash/src/test/java/dev/relism/flash/HttpServerTest.java index ff4705f..3f7ab21 100644 --- a/flash/src/test/java/dev/relism/flash/HttpServerTest.java +++ b/flash/src/test/java/dev/relism/flash/HttpServerTest.java @@ -2,15 +2,14 @@ package dev.relism.flash; import dev.relism.flash.extension.FlashApp; import dev.relism.flash.extension.FlashConfiguration; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.OutputStream; -import java.net.ServerSocket; import java.net.Socket; import java.nio.charset.StandardCharsets; @@ -18,19 +17,17 @@ import static org.junit.jupiter.api.Assertions.*; class HttpServerTest { - private FlashApp app; - private int port; - - @BeforeEach - void setUp() throws Exception { - try (ServerSocket s = new ServerSocket(0)) { - port = s.getLocalPort(); - } + // Every test here is read-only against the same routes, so one boot for the class. + private static FlashApp app; + private static int port; + @BeforeAll + static void setUp() { app = FlashApp.create(FlashConfiguration.builder() - .port(port) + .port(0) .host("127.0.0.1") .build()); + port = app.port(); app.get("/api/ping", (req, res) -> "pong"); @@ -61,9 +58,9 @@ class HttpServerTest { app.start(); } - @AfterEach - void tearDown() { - if (app != null) app.stop(); + @AfterAll + static void tearDown() { + if (app != null) app.stop().join(); } // --- helpers --- diff --git a/flash/src/test/java/dev/relism/flash/HttpServerTimeoutTest.java b/flash/src/test/java/dev/relism/flash/HttpServerTimeoutTest.java index a354a90..471ef5a 100644 --- a/flash/src/test/java/dev/relism/flash/HttpServerTimeoutTest.java +++ b/flash/src/test/java/dev/relism/flash/HttpServerTimeoutTest.java @@ -9,7 +9,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import java.io.OutputStream; -import java.net.ServerSocket; import java.net.Socket; import java.nio.charset.StandardCharsets; import java.nio.file.Path; @@ -30,21 +29,15 @@ class HttpServerTimeoutTest { if (app != null) app.stop(); } - private int freePort() throws Exception { - try (ServerSocket s = new ServerSocket(0)) { - return s.getLocalPort(); - } - } - @Test void slowlorisHeaderDribble_disconnectedWithinHeaderReadTimeout() throws Exception { int headerTimeoutMs = 300; - int port = freePort(); app = FlashApp.create(FlashConfiguration.builder() - .port(port).host("127.0.0.1") + .port(0).host("127.0.0.1") .headerReadTimeoutMs(headerTimeoutMs) .idleKeepAliveTimeoutMs(60_000) .build()); + int port = app.port(); app.get("/", (req, res) -> "ok"); app.start(); @@ -71,12 +64,12 @@ class HttpServerTimeoutTest { @Test void idleKeepAliveConnection_disconnectedWithinIdleTimeout() throws Exception { int idleTimeoutMs = 300; - int port = freePort(); app = FlashApp.create(FlashConfiguration.builder() - .port(port).host("127.0.0.1") + .port(0).host("127.0.0.1") .headerReadTimeoutMs(10_000) .idleKeepAliveTimeoutMs(idleTimeoutMs) .build()); + int port = app.port(); app.get("/", (req, res) -> "ok"); app.start(); @@ -96,13 +89,13 @@ class HttpServerTimeoutTest { @Test void slowBodyDribble_disconnectedWithinBodyReadTimeout() throws Exception { int bodyTimeoutMs = 300; - int port = freePort(); app = FlashApp.create(FlashConfiguration.builder() - .port(port).host("127.0.0.1") + .port(0).host("127.0.0.1") .headerReadTimeoutMs(10_000) .idleKeepAliveTimeoutMs(10_000) .bodyReadTimeoutMs(bodyTimeoutMs) .build()); + int port = app.port(); app.post("/echo", (req, res) -> req.body().bytes()); app.start(); @@ -130,12 +123,12 @@ class HttpServerTimeoutTest { int headerTimeoutMs = 300; Path ks = TestKeystores.build(dir, "timeout.p12", "changeit", TestKeystores.Entry.of("only", "timeout.test")); - int port = freePort(); app = FlashApp.create(FlashConfiguration.builder() - .port(port).host("127.0.0.1") + .port(0).host("127.0.0.1") .tls(TlsConfig.keystore(ks, "changeit")) .headerReadTimeoutMs(headerTimeoutMs) .build()); + int port = app.port(); app.get("/", (req, res) -> "ok"); app.start(); @@ -157,13 +150,13 @@ class HttpServerTimeoutTest { @Test void wellBehavedRequest_wellWithinTimeouts_unaffected() throws Exception { - int port = freePort(); app = FlashApp.create(FlashConfiguration.builder() - .port(port).host("127.0.0.1") + .port(0).host("127.0.0.1") .headerReadTimeoutMs(300) .idleKeepAliveTimeoutMs(300) .bodyReadTimeoutMs(300) .build()); + int port = app.port(); app.get("/ping", (req, res) -> "pong"); app.start(); diff --git a/flash/src/test/java/dev/relism/flash/HttpServerTlsTest.java b/flash/src/test/java/dev/relism/flash/HttpServerTlsTest.java index d1c55d3..635ba36 100644 --- a/flash/src/test/java/dev/relism/flash/HttpServerTlsTest.java +++ b/flash/src/test/java/dev/relism/flash/HttpServerTlsTest.java @@ -60,9 +60,6 @@ class HttpServerTlsTest { if (app != null) app.stop(); } - private static int freePort() throws IOException { - try (ServerSocket s = new ServerSocket(0)) { return s.getLocalPort(); } - } private static String httpGet(SSLSocket socket, String path) throws IOException { socket.setSoTimeout(SOCKET_TIMEOUT_MS); @@ -79,11 +76,11 @@ class HttpServerTlsTest { @Test void httpsRequest_servedOverModernTls(@TempDir java.nio.file.Path dir) throws Exception { var ks = TestKeystores.build(dir, "id.p12", "changeit", TestKeystores.Entry.of("srv", "localhost")); - int port = freePort(); app = FlashApp.create(FlashConfiguration.builder() - .port(port).host("127.0.0.1") + .port(0).host("127.0.0.1") .tls(TlsConfig.keystore(ks, "changeit")) .build()); + int port = app.port(); app.get("/ping", (req, res) -> "pong"); app.start(); @@ -103,11 +100,11 @@ class HttpServerTlsTest { var ks = TestKeystores.build(dir, "sni.p12", "changeit", TestKeystores.Entry.of("a", "a.test"), TestKeystores.Entry.of("b", "b.test")); - int port = freePort(); app = FlashApp.create(FlashConfiguration.builder() - .port(port).host("127.0.0.1") + .port(0).host("127.0.0.1") .tls(TlsConfig.keystore(ks, "changeit")) .build()); + int port = app.port(); app.get("/ping", (req, res) -> "pong"); app.start(); @@ -143,11 +140,11 @@ class HttpServerTlsTest { @Test void mTls_requireRejectsClientWithNoCertificate(@TempDir java.nio.file.Path dir) throws Exception { var ks = TestKeystores.build(dir, "id.p12", "changeit", TestKeystores.Entry.of("srv", "localhost")); - int port = freePort(); app = FlashApp.create(FlashConfiguration.builder() - .port(port).host("127.0.0.1") + .port(0).host("127.0.0.1") .tls(TlsConfig.keystore(ks, "changeit").clientAuth(ClientAuth.REQUIRE)) .build()); + int port = app.port(); app.get("/ping", (req, res) -> "pong"); app.start(); @@ -180,11 +177,11 @@ class HttpServerTlsTest { SSLContext serverCtx = SSLContext.getInstance("TLS"); serverCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); - int port = freePort(); app = FlashApp.create(FlashConfiguration.builder() - .port(port).host("127.0.0.1") + .port(0).host("127.0.0.1") .tls(TlsConfig.ofContext(serverCtx).clientAuth(ClientAuth.REQUIRE)) .build()); + int port = app.port(); app.get("/ping", (req, res) -> "pong"); app.start(); @@ -219,12 +216,12 @@ class HttpServerTlsTest { @Test void multipleListeners_plainAndTlsServeTheSameApp(@TempDir java.nio.file.Path dir) throws Exception { var ks = TestKeystores.build(dir, "id.p12", "changeit", TestKeystores.Entry.of("srv", "localhost")); - int plainPort = freePort(); - int tlsPort = freePort(); app = FlashApp.create(FlashConfiguration.builder() - .listener(new FlashConfiguration.Listener(plainPort, "127.0.0.1", null)) - .listener(new FlashConfiguration.Listener(tlsPort, "127.0.0.1", TlsConfig.keystore(ks, "changeit"))) + .listener(new FlashConfiguration.Listener(0, "127.0.0.1", null)) + .listener(new FlashConfiguration.Listener(0, "127.0.0.1", TlsConfig.keystore(ks, "changeit"))) .build()); + int plainPort = app.ports().get(0); + int tlsPort = app.ports().get(1); app.get("/ping", (req, res) -> "pong"); app.start(); @@ -287,11 +284,11 @@ class HttpServerTlsTest { SSLContext ctx = SSLContext.getInstance("TLS"); ctx.init(managers, null, null); - int port = freePort(); app = FlashApp.create(FlashConfiguration.builder() - .port(port).host("127.0.0.1") + .port(0).host("127.0.0.1") .tls(TlsConfig.ofContext(ctx)) .build()); + int port = app.port(); app.get("/ping", (req, res) -> "pong"); app.start(); @@ -364,11 +361,11 @@ class HttpServerTlsTest { SSLContext[] boxedCtx = new SSLContext[1]; RecordingKeyManager recorder = buildRecordingContext(ks, boxedCtx); - int port = freePort(); app = FlashApp.create(FlashConfiguration.builder() - .port(port).host("127.0.0.1") + .port(0).host("127.0.0.1") .tls(TlsConfig.ofContext(boxedCtx[0]).applicationProtocols("acme-tls/1", "http/1.1")) .build()); + int port = app.port(); app.get("/ping", (req, res) -> "pong"); app.start(); @@ -422,11 +419,11 @@ class HttpServerTlsTest { @Test void request_isSecureAndSessionAvailableOverTls(@TempDir java.nio.file.Path dir) throws Exception { var ks = TestKeystores.build(dir, "id.p12", "changeit", TestKeystores.Entry.of("srv", "localhost")); - int port = freePort(); app = FlashApp.create(FlashConfiguration.builder() - .port(port).host("127.0.0.1") + .port(0).host("127.0.0.1") .tls(TlsConfig.keystore(ks, "changeit")) .build()); + int port = app.port(); app.get("/secure-info", (req, res) -> { SSLSession session = req.sslSession(); return req.isSecure() + ":" + (session != null) + ":" + (session != null ? session.getCipherSuite() : ""); @@ -456,13 +453,13 @@ class HttpServerTlsTest { SSLContext serverCtx = SSLContext.getInstance("TLS"); serverCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); - int port = freePort(); app = FlashApp.create(FlashConfiguration.builder() - .port(port).host("127.0.0.1") + .port(0).host("127.0.0.1") // OPTIONAL, not REQUIRE: proves getPeerCertificates() works without also // re-testing the REQUIRE-rejection path already covered elsewhere in this file. .tls(TlsConfig.ofContext(serverCtx).clientAuth(ClientAuth.OPTIONAL)) .build()); + int port = app.port(); app.get("/secure-info", (req, res) -> { try { X509Certificate peer = (X509Certificate) req.sslSession().getPeerCertificates()[0]; @@ -488,8 +485,8 @@ class HttpServerTlsTest { @Test void request_isNotSecureAndSessionIsNullOnPlainListener() throws Exception { - int port = freePort(); - app = FlashApp.create(FlashConfiguration.builder().port(port).host("127.0.0.1").build()); + app = FlashApp.create(FlashConfiguration.builder().port(0).host("127.0.0.1").build()); + int port = app.port(); app.get("/secure-info", (req, res) -> req.isSecure() + ":" + (req.sslSession() == null)); app.start(); @@ -524,11 +521,11 @@ class HttpServerTlsTest { @Test void wss_sessionIsSecureAndExposesSslSession(@TempDir java.nio.file.Path dir) throws Exception { var ks = TestKeystores.build(dir, "id.p12", "changeit", TestKeystores.Entry.of("srv", "localhost")); - int port = freePort(); app = FlashApp.create(FlashConfiguration.builder() - .port(port).host("127.0.0.1") + .port(0).host("127.0.0.1") .tls(TlsConfig.keystore(ks, "changeit")) .build()); + int port = app.port(); AtomicReference observedSecure = new AtomicReference<>(); AtomicReference observedSession = new AtomicReference<>(); diff --git a/flash/src/test/java/dev/relism/flash/HttpServerWebSocketTest.java b/flash/src/test/java/dev/relism/flash/HttpServerWebSocketTest.java index d5eddd4..9d5e8d2 100644 --- a/flash/src/test/java/dev/relism/flash/HttpServerWebSocketTest.java +++ b/flash/src/test/java/dev/relism/flash/HttpServerWebSocketTest.java @@ -13,7 +13,6 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.OutputStream; -import java.net.ServerSocket; import java.net.Socket; import java.nio.charset.StandardCharsets; import java.util.Base64; @@ -29,14 +28,11 @@ class HttpServerWebSocketTest { @BeforeEach void setUp() throws Exception { - try (ServerSocket s = new ServerSocket(0)) { - port = s.getLocalPort(); - } - app = FlashApp.create(FlashConfiguration.builder() - .port(port) + .port(0) .host("127.0.0.1") .build()); + port = app.port(); app.ws("/chat", new WebSocketHandler() { @Override diff --git a/flash/src/test/java/dev/relism/flash/extension/FlashAppWebSocketTest.java b/flash/src/test/java/dev/relism/flash/extension/FlashAppWebSocketTest.java index ffa059a..9589e9b 100644 --- a/flash/src/test/java/dev/relism/flash/extension/FlashAppWebSocketTest.java +++ b/flash/src/test/java/dev/relism/flash/extension/FlashAppWebSocketTest.java @@ -9,7 +9,6 @@ import org.junit.jupiter.api.Test; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.net.ServerSocket; import static org.junit.jupiter.api.Assertions.*; @@ -20,14 +19,11 @@ class FlashAppWebSocketTest { @BeforeEach void setUp() throws Exception { - try (ServerSocket s = new ServerSocket(0)) { - port = s.getLocalPort(); - } - app = FlashApp.create(FlashConfiguration.builder() - .port(port) + .port(0) .host("127.0.0.1") .build()); + port = app.port(); } @AfterEach diff --git a/flash/src/test/java/dev/relism/flash/http2/CurlInteropTest.java b/flash/src/test/java/dev/relism/flash/http2/CurlInteropTest.java index d269a74..ece753a 100644 --- a/flash/src/test/java/dev/relism/flash/http2/CurlInteropTest.java +++ b/flash/src/test/java/dev/relism/flash/http2/CurlInteropTest.java @@ -32,7 +32,6 @@ class CurlInteropTest { @Test void tlsGetPostLargeUploadAndLargeDownload(@TempDir Path directory) throws Exception { - int port = freePort(); Path keystore = TestKeystores.build( directory, @@ -43,23 +42,24 @@ class CurlInteropTest { FlashApp.create( FlashConfiguration.builder() .host("127.0.0.1") - .port(port) + .port(0) .tls(TlsConfig.keystore(keystore, "changeit")) .http2Enabled(true) .build()); + int port = app.port(); exercise(directory, "https://localhost:" + port, "--http2", "--insecure"); } @Test void cleartextGetPostLargeUploadAndLargeDownload(@TempDir Path directory) throws Exception { - int port = freePort(); app = FlashApp.create( FlashConfiguration.builder() .host("127.0.0.1") - .port(port) + .port(0) .http2CleartextEnabled(true) .build()); + int port = app.port(); exercise(directory, "http://127.0.0.1:" + port, "--http2-prior-knowledge"); } @@ -112,9 +112,4 @@ class CurlInteropTest { return result; } - private static int freePort() throws Exception { - try (ServerSocket socket = new ServerSocket(0)) { - return socket.getLocalPort(); - } - } } diff --git a/flash/src/test/java/dev/relism/flash/http2/GrpcInteropTest.java b/flash/src/test/java/dev/relism/flash/http2/GrpcInteropTest.java index 1d06280..71692ec 100644 --- a/flash/src/test/java/dev/relism/flash/http2/GrpcInteropTest.java +++ b/flash/src/test/java/dev/relism/flash/http2/GrpcInteropTest.java @@ -28,14 +28,14 @@ class GrpcInteropTest { @Test void grpcurlCompletesUnaryStreamingAndErrorCalls(@TempDir Path directory) throws Exception { - int port = freePort(); app = FlashApp.create( FlashConfiguration.builder() .host("127.0.0.1") - .port(port) + .port(0) .http2CleartextEnabled(true) .build()); + int port = app.port(); app.post("/flash.test.Echo/Unary", (request, response) -> response.type("application/grpc") .body(request.body().bytes()) @@ -156,11 +156,6 @@ class GrpcInteropTest { return count; } - private static int freePort() throws Exception { - try (ServerSocket socket = new ServerSocket(0)) { - return socket.getLocalPort(); - } - } private record Result(int exitCode, String output) {} } diff --git a/flash/src/test/java/dev/relism/flash/http2/H2LoadMeasurementTest.java b/flash/src/test/java/dev/relism/flash/http2/H2LoadMeasurementTest.java index f0dee81..7b73b3e 100644 --- a/flash/src/test/java/dev/relism/flash/http2/H2LoadMeasurementTest.java +++ b/flash/src/test/java/dev/relism/flash/http2/H2LoadMeasurementTest.java @@ -42,16 +42,16 @@ class H2LoadMeasurementTest { @Test void measureFlashAndNghttpdMatrix(@TempDir Path directory) throws Exception { - int flashPort = freePort(); app = FlashApp.create( FlashConfiguration.builder() .host("127.0.0.1") - .port(flashPort) + .port(0) .http2CleartextEnabled(true) .h2MaxStreamsCreatedPerInterval(Integer.MAX_VALUE) .h2MaxStreamsPerConnection(0) .build()); + int flashPort = app.port(); app.get("/index.html", (request, response) -> "flash-load"); app.start(); @@ -133,6 +133,10 @@ class H2LoadMeasurementTest { if (path != null) builder.environment().put("LD_LIBRARY_PATH", path); } + /** + * Still needed here: this port is handed to an external nghttpd process, which has no + * equivalent of {@code FlashApp.port()} to read an OS-assigned port back from. + */ private static int freePort() throws Exception { try (ServerSocket socket = new ServerSocket(0)) { return socket.getLocalPort(); diff --git a/flash/src/test/java/dev/relism/flash/http2/H2SpecComplianceTest.java b/flash/src/test/java/dev/relism/flash/http2/H2SpecComplianceTest.java index bbd29cb..40a7a9d 100644 --- a/flash/src/test/java/dev/relism/flash/http2/H2SpecComplianceTest.java +++ b/flash/src/test/java/dev/relism/flash/http2/H2SpecComplianceTest.java @@ -36,14 +36,14 @@ class H2SpecComplianceTest { @Test void cleartextSuiteHasNoFailuresOrSkips(@TempDir Path directory) throws Exception { - int port = freePort(); app = FlashApp.create( FlashConfiguration.builder() .host("127.0.0.1") - .port(port) + .port(0) .http2CleartextEnabled(true) .build()); + int port = app.port(); registerProbeRoutes(); app.start(); @@ -52,7 +52,6 @@ class H2SpecComplianceTest { @Test void tlsSuiteHasNoFailuresOrSkips(@TempDir Path directory) throws Exception { - int port = freePort(); Path keystore = TestKeystores.build( directory, @@ -63,10 +62,11 @@ class H2SpecComplianceTest { FlashApp.create( FlashConfiguration.builder() .host("127.0.0.1") - .port(port) + .port(0) .tls(TlsConfig.keystore(keystore, "changeit")) .http2Enabled(true) .build()); + int port = app.port(); registerProbeRoutes(); app.start(); @@ -145,11 +145,6 @@ class H2SpecComplianceTest { assertEquals(0, skipped.getLength(), output); } - private static int freePort() throws Exception { - try (ServerSocket socket = new ServerSocket(0)) { - return socket.getLocalPort(); - } - } private record ProcessResult(int exitCode, String output) {} } diff --git a/flash/src/test/java/dev/relism/flash/http2/H2cPriorKnowledgeTest.java b/flash/src/test/java/dev/relism/flash/http2/H2cPriorKnowledgeTest.java index fdd6186..a043eb5 100644 --- a/flash/src/test/java/dev/relism/flash/http2/H2cPriorKnowledgeTest.java +++ b/flash/src/test/java/dev/relism/flash/http2/H2cPriorKnowledgeTest.java @@ -28,14 +28,14 @@ class H2cPriorKnowledgeTest { @Test void priorKnowledgeRequiresItsIndependentOptIn() throws Exception { - int disabledPort = freePort(); app = FlashApp.create( FlashConfiguration.builder() .host("127.0.0.1") - .port(disabledPort) + .port(0) .http2Enabled(true) .build()); + int disabledPort = app.port(); app.get("/", (request, response) -> "wrong protocol"); app.start(); @@ -47,14 +47,14 @@ class H2cPriorKnowledgeTest { } app.stop().join(); - int enabledPort = freePort(); app = FlashApp.create( FlashConfiguration.builder() .host("127.0.0.1") - .port(enabledPort) + .port(0) .http2CleartextEnabled(true) .build()); + int enabledPort = app.port(); app.get("/", (request, response) -> "h2c"); app.start(); @@ -125,9 +125,4 @@ class H2cPriorKnowledgeTest { payload); } - private static int freePort() throws Exception { - try (ServerSocket socket = new ServerSocket(0)) { - return socket.getLocalPort(); - } - } } diff --git a/flash/src/test/java/dev/relism/flash/http2/Http2AbuseTest.java b/flash/src/test/java/dev/relism/flash/http2/Http2AbuseTest.java index c44a589..1c6bf96 100644 --- a/flash/src/test/java/dev/relism/flash/http2/Http2AbuseTest.java +++ b/flash/src/test/java/dev/relism/flash/http2/Http2AbuseTest.java @@ -183,15 +183,15 @@ class Http2AbuseTest { @Test void idleOpenStreamIsCancelledWithinConfiguredDeadline() throws Exception { - int port = freePort(); FlashApp app = FlashApp.create( FlashConfiguration.builder() .host("127.0.0.1") - .port(port) + .port(0) .http2CleartextEnabled(true) .h2StreamIdleTimeoutMs(20) .build()); + int port = app.port(); app.post("/idle", (request, response) -> request.body().bytes()); app.start(); ByteWriter headers = new ByteWriter(64); @@ -295,11 +295,6 @@ class Http2AbuseTest { throw new AssertionError("missing " + expected); } - private static int freePort() throws Exception { - try (ServerSocket socket = new ServerSocket(0)) { - return socket.getLocalPort(); - } - } private record Run(List frames) { int lastGoAwayError() { diff --git a/flash/src/test/java/dev/relism/flash/http2/Http2ConcurrencyTest.java b/flash/src/test/java/dev/relism/flash/http2/Http2ConcurrencyTest.java index 9793bf2..84c7096 100644 --- a/flash/src/test/java/dev/relism/flash/http2/Http2ConcurrencyTest.java +++ b/flash/src/test/java/dev/relism/flash/http2/Http2ConcurrencyTest.java @@ -29,16 +29,16 @@ class Http2ConcurrencyTest { @Test void thousandMultiplexedStreamsCompleteOnOneConnection() throws Exception { - int port = freePort(); AtomicInteger handled = new AtomicInteger(); app = FlashApp.create( FlashConfiguration.builder() .host("127.0.0.1") - .port(port) + .port(0) .http2CleartextEnabled(true) .h2MaxStreamsCreatedPerInterval(2_000) .build()); + int port = app.port(); app.get("/work", (request, response) -> Integer.toString(handled.incrementAndGet())); app.start(); @@ -110,9 +110,4 @@ class Http2ConcurrencyTest { payload); } - private static int freePort() throws Exception { - try (ServerSocket socket = new ServerSocket(0)) { - return socket.getLocalPort(); - } - } } diff --git a/flash/src/test/java/dev/relism/flash/http2/Http2ConnectTest.java b/flash/src/test/java/dev/relism/flash/http2/Http2ConnectTest.java index 733a9fb..0cfa99f 100644 --- a/flash/src/test/java/dev/relism/flash/http2/Http2ConnectTest.java +++ b/flash/src/test/java/dev/relism/flash/http2/Http2ConnectTest.java @@ -28,14 +28,14 @@ class Http2ConnectTest { @Test void connectTunnelCanExchangeDataBeforeEitherSideCloses() throws Exception { - int port = freePort(); app = FlashApp.create( FlashConfiguration.builder() .host("127.0.0.1") - .port(port) + .port(0) .http2CleartextEnabled(true) .build()); + int port = app.port(); app.connect("tunnel", (request, response) -> response.type(ContentType.NONE).streaming(output -> { byte[] bytes = new byte[16]; @@ -100,9 +100,4 @@ class Http2ConnectTest { return text.getBytes(StandardCharsets.US_ASCII); } - private static int freePort() throws Exception { - try (ServerSocket socket = new ServerSocket(0)) { - return socket.getLocalPort(); - } - } } diff --git a/flash/src/test/java/dev/relism/flash/http2/Http2ConnectionIntegrationTest.java b/flash/src/test/java/dev/relism/flash/http2/Http2ConnectionIntegrationTest.java index febbb76..13acd2b 100644 --- a/flash/src/test/java/dev/relism/flash/http2/Http2ConnectionIntegrationTest.java +++ b/flash/src/test/java/dev/relism/flash/http2/Http2ConnectionIntegrationTest.java @@ -48,7 +48,6 @@ class Http2ConnectionIntegrationTest { @Test void javaHttpClientUsesHttp2AgainstAnExistingParameterizedRoute(@TempDir Path directory) throws Exception { - int port = freePort(); Path keystore = TestKeystores.build( directory, @@ -58,11 +57,12 @@ class Http2ConnectionIntegrationTest { app = FlashApp.create( FlashConfiguration.builder() - .port(port) + .port(0) .host("127.0.0.1") .tls(TlsConfig.keystore(keystore, "changeit")) .http2Enabled(true) .build()); + int port = app.port(); app.get( "/users/{id}", (request, response) -> request.param("id") + ":" + request.header("host")); app.start(); @@ -87,7 +87,6 @@ class Http2ConnectionIntegrationTest { @Test void javaHttpClientUploadsAndDownloadsFlowControlledBodies(@TempDir Path directory) throws Exception { - int port = freePort(); Path keystore = TestKeystores.build( directory, @@ -102,11 +101,12 @@ class Http2ConnectionIntegrationTest { app = FlashApp.create( FlashConfiguration.builder() - .port(port) + .port(0) .host("127.0.0.1") .tls(TlsConfig.keystore(keystore, "changeit")) .http2Enabled(true) .build()); + int port = app.port(); app.post("/echo", (request, response) -> request.body().bytes()); app.get("/fixed", (request, response) -> response.body(download)); app.get( @@ -143,7 +143,6 @@ class Http2ConnectionIntegrationTest { @Test void pushStreamingAppliesBackpressureAcrossMultipleWindows(@TempDir Path directory) throws Exception { - int port = freePort(); int length = 2 * 1024 * 1024 + 31; Path keystore = TestKeystores.build( @@ -154,11 +153,12 @@ class Http2ConnectionIntegrationTest { app = FlashApp.create( FlashConfiguration.builder() - .port(port) + .port(0) .host("127.0.0.1") .tls(TlsConfig.keystore(keystore, "changeit")) .http2Enabled(true) .build()); + int port = app.port(); app.get( "/push", (request, response) -> @@ -197,7 +197,6 @@ class Http2ConnectionIntegrationTest { @Test void hundredMegabyteUploadAndDownloadRemainStreaming(@TempDir Path directory) throws Exception { - int port = freePort(); long length = 100L * 1024 * 1024; Path keystore = TestKeystores.build( @@ -208,11 +207,12 @@ class Http2ConnectionIntegrationTest { app = FlashApp.create( FlashConfiguration.builder() - .port(port) + .port(0) .host("127.0.0.1") .tls(TlsConfig.keystore(keystore, "changeit")) .http2Enabled(true) .build()); + int port = app.port(); app.post( "/upload", (request, response) -> { @@ -250,14 +250,14 @@ class Http2ConnectionIntegrationTest { @Test void bodylessGetRunsExistingRouteAndReturnsHeadersAndData() throws Exception { - int port = freePort(); app = FlashApp.create( FlashConfiguration.builder() - .port(port) + .port(0) .host("127.0.0.1") .http2CleartextEnabled(true) .build()); + int port = app.port(); app.get("/api/ping", (request, response) -> "pong"); app.start(); @@ -314,15 +314,15 @@ class Http2ConnectionIntegrationTest { @Test void resetQueuedStreamCannotReleaseOrReuseItBeforeDispatchObservesCancellation() throws Exception { - int port = freePort(); AtomicInteger calls = new AtomicInteger(); app = FlashApp.create( FlashConfiguration.builder() - .port(port) + .port(0) .host("127.0.0.1") .http2CleartextEnabled(true) .build()); + int port = app.port(); app.get( "/queued", (request, response) -> { @@ -384,15 +384,15 @@ class Http2ConnectionIntegrationTest { @Test void h2cTransportDispatchesControlFramesWithoutRunningApplicationWork() throws Exception { - int port = freePort(); AtomicBoolean handlerEntered = new AtomicBoolean(); app = FlashApp.create( FlashConfiguration.builder() - .port(port) + .port(0) .host("127.0.0.1") .http2CleartextEnabled(true) .build()); + int port = app.port(); app.get( "/", (request, response) -> { @@ -442,15 +442,15 @@ class Http2ConnectionIntegrationTest { @Test void serverStopInitiatesTwoStageGoAwayOnIdleConnection() throws Exception { - int port = freePort(); app = FlashApp.create( FlashConfiguration.builder() - .port(port) + .port(0) .host("127.0.0.1") .http2CleartextEnabled(true) .shutdownDrainTimeoutMs(5_000) .build()); + int port = app.port(); app.start(); try (Socket socket = new Socket("127.0.0.1", port)) { @@ -485,14 +485,14 @@ class Http2ConnectionIntegrationTest { @Test void protocolStateDoesNotLeakAcrossConsecutiveConnections() throws Exception { - int port = freePort(); app = FlashApp.create( FlashConfiguration.builder() - .port(port) + .port(0) .host("127.0.0.1") .http2CleartextEnabled(true) .build()); + int port = app.port(); app.start(); try (Socket first = new Socket("127.0.0.1", port)) { @@ -537,7 +537,6 @@ class Http2ConnectionIntegrationTest { @Test void tlsListenerOffersAndNegotiatesH2WhenHttp2IsEnabled(@TempDir Path directory) throws Exception { - int port = freePort(); Path keystore = TestKeystores.build( directory, @@ -547,11 +546,12 @@ class Http2ConnectionIntegrationTest { app = FlashApp.create( FlashConfiguration.builder() - .port(port) + .port(0) .host("127.0.0.1") .tls(TlsConfig.keystore(keystore, "changeit")) .http2Enabled(true) .build()); + int port = app.port(); app.start(); try (SSLSocket socket = @@ -630,11 +630,6 @@ class Http2ConnectionIntegrationTest { payload); } - private static int freePort() throws Exception { - try (ServerSocket socket = new ServerSocket(0)) { - return socket.getLocalPort(); - } - } private static String ascii(dev.relism.fpr.core.ByteView view) { byte[] bytes = new byte[view.length()]; diff --git a/flash/src/test/java/dev/relism/flash/http2/Http2MisdirectedRequestTest.java b/flash/src/test/java/dev/relism/flash/http2/Http2MisdirectedRequestTest.java index e8f04c2..0b8b9c3 100644 --- a/flash/src/test/java/dev/relism/flash/http2/Http2MisdirectedRequestTest.java +++ b/flash/src/test/java/dev/relism/flash/http2/Http2MisdirectedRequestTest.java @@ -31,7 +31,6 @@ class Http2MisdirectedRequestTest { @Test void authorityOutsideSelectedCertificateReceives421(@TempDir Path directory) throws Exception { - int port = freePort(); Path keystore = TestKeystores.build( directory, @@ -42,10 +41,11 @@ class Http2MisdirectedRequestTest { FlashApp.create( FlashConfiguration.builder() .host("127.0.0.1") - .port(port) + .port(0) .tls(TlsConfig.keystore(keystore, "changeit")) .http2Enabled(true) .build()); + int port = app.port(); app.get("/", (request, response) -> "must not run"); app.start(); @@ -114,9 +114,4 @@ class Http2MisdirectedRequestTest { } } - private static int freePort() throws Exception { - try (ServerSocket socket = new ServerSocket(0)) { - return socket.getLocalPort(); - } - } } diff --git a/flash/src/test/java/dev/relism/flash/http2/Http2RegressionCorpusTest.java b/flash/src/test/java/dev/relism/flash/http2/Http2RegressionCorpusTest.java index 9cfcce0..0445ecd 100644 --- a/flash/src/test/java/dev/relism/flash/http2/Http2RegressionCorpusTest.java +++ b/flash/src/test/java/dev/relism/flash/http2/Http2RegressionCorpusTest.java @@ -47,14 +47,14 @@ class Http2RegressionCorpusTest { @Test void headersOnHalfClosedRemoteStreamUseStreamErrorBeforeDispatch() throws Exception { - int port = freePort(); app = FlashApp.create( FlashConfiguration.builder() .host("127.0.0.1") - .port(port) + .port(0) .http2CleartextEnabled(true) .build()); + int port = app.port(); app.get("/", (request, response) -> "ok"); app.start(); @@ -101,9 +101,4 @@ class Http2RegressionCorpusTest { payload); } - private static int freePort() throws Exception { - try (ServerSocket socket = new ServerSocket(0)) { - return socket.getLocalPort(); - } - } } diff --git a/flash/src/test/java/dev/relism/flash/http2/Http2SoakTest.java b/flash/src/test/java/dev/relism/flash/http2/Http2SoakTest.java index cb8f827..8714f65 100644 --- a/flash/src/test/java/dev/relism/flash/http2/Http2SoakTest.java +++ b/flash/src/test/java/dev/relism/flash/http2/Http2SoakTest.java @@ -35,18 +35,18 @@ class Http2SoakTest { @Test void sustainedMixedTrafficRetainsBoundedHeapAndCompletesRequests() throws Exception { long seconds = Long.getLong("flash.http2.soak.seconds", 600L); - int port = freePort(); byte[] streamBody = new byte[8 * 1024]; Arrays.fill(streamBody, (byte) 's'); app = FlashApp.create( FlashConfiguration.builder() .host("127.0.0.1") - .port(port) + .port(0) .http2CleartextEnabled(true) .h2MaxStreamsCreatedPerInterval(100_000) .h2MaxStreamsPerConnection(0) .build()); + int port = app.port(); app.get("/get", (request, response) -> "get"); app.post("/post", (request, response) -> request.body().bytes()); app.get("/stream", (request, response) -> response.chunked(new ByteArrayInputStream(streamBody))); @@ -182,9 +182,4 @@ class Http2SoakTest { payload); } - private static int freePort() throws Exception { - try (ServerSocket socket = new ServerSocket(0)) { - return socket.getLocalPort(); - } - } } diff --git a/flash/src/test/java/dev/relism/flash/http2/Http2TrailersTest.java b/flash/src/test/java/dev/relism/flash/http2/Http2TrailersTest.java index 880c2ae..dcf3c1e 100644 --- a/flash/src/test/java/dev/relism/flash/http2/Http2TrailersTest.java +++ b/flash/src/test/java/dev/relism/flash/http2/Http2TrailersTest.java @@ -27,14 +27,14 @@ class Http2TrailersTest { @Test void requestTrailersReachHandlerAfterBodyEof() throws Exception { - int port = freePort(); app = FlashApp.create( FlashConfiguration.builder() .host("127.0.0.1") - .port(port) + .port(0) .http2CleartextEnabled(true) .build()); + int port = app.port(); app.post("/trailers", (request, response) -> { assertEquals("abc", new String(request.body().bytes(), StandardCharsets.US_ASCII)); return request.trailers().first("grpc-status"); @@ -99,14 +99,14 @@ class Http2TrailersTest { } private int startBlockingRoute() throws Exception { - int port = freePort(); app = FlashApp.create( FlashConfiguration.builder() .host("127.0.0.1") - .port(port) + .port(0) .http2CleartextEnabled(true) .build()); + int port = app.port(); app.post("/trailers", (request, response) -> request.body().bytes()); app.start(); return port; @@ -152,9 +152,4 @@ class Http2TrailersTest { payload); } - private static int freePort() throws Exception { - try (ServerSocket socket = new ServerSocket(0)) { - return socket.getLocalPort(); - } - } } diff --git a/flash/src/test/java/dev/relism/flash/http2/NghttpInteropTest.java b/flash/src/test/java/dev/relism/flash/http2/NghttpInteropTest.java index 46fc5d7..0b542f2 100644 --- a/flash/src/test/java/dev/relism/flash/http2/NghttpInteropTest.java +++ b/flash/src/test/java/dev/relism/flash/http2/NghttpInteropTest.java @@ -38,9 +38,8 @@ class NghttpInteropTest { } private void exercise(Path directory, boolean tls) throws Exception { - int port = freePort(); FlashConfiguration.FlashConfigurationBuilder builder = - FlashConfiguration.builder().host("127.0.0.1").port(port); + FlashConfiguration.builder().host("127.0.0.1").port(0); if (tls) { Path keystore = TestKeystores.build( @@ -54,6 +53,7 @@ class NghttpInteropTest { } byte[] large = new byte[2 * 1024 * 1024 + 29]; app = FlashApp.create(builder.build()); + int port = app.port(); app.get("/get", (request, response) -> "nghttp-get"); app.post("/post", (request, response) -> "uploaded-" + request.body().bytes().length); app.get("/large", (request, response) -> response.body(large)); @@ -93,9 +93,4 @@ class NghttpInteropTest { assertTrue(trace.contains("recv DATA frame"), trace); } - private static int freePort() throws Exception { - try (ServerSocket socket = new ServerSocket(0)) { - return socket.getLocalPort(); - } - } } diff --git a/flash/src/test/java/dev/relism/flash/http2/WebSocketOverH2Test.java b/flash/src/test/java/dev/relism/flash/http2/WebSocketOverH2Test.java index d269f51..8f1df4c 100644 --- a/flash/src/test/java/dev/relism/flash/http2/WebSocketOverH2Test.java +++ b/flash/src/test/java/dev/relism/flash/http2/WebSocketOverH2Test.java @@ -23,15 +23,15 @@ class WebSocketOverH2Test { @Test void opensEchoesFragmentsAndCarriesAMessageLargerThanTheFlowWindow() throws Exception { - int port = freePort(); app = FlashApp.create( FlashConfiguration.builder() .host("127.0.0.1") - .port(port) + .port(0) .http2CleartextEnabled(true) .wsFrameBufferSize(2 * 1024 * 1024) .build()); + int port = app.port(); app.ws( "/chat", new WebSocketHandler() { @@ -67,9 +67,4 @@ class WebSocketOverH2Test { } } - private static int freePort() throws Exception { - try (ServerSocket socket = new ServerSocket(0)) { - return socket.getLocalPort(); - } - } } diff --git a/flash/src/test/java/dev/relism/flash/http2/WebSocketParityTest.java b/flash/src/test/java/dev/relism/flash/http2/WebSocketParityTest.java index fb120ae..c73d838 100644 --- a/flash/src/test/java/dev/relism/flash/http2/WebSocketParityTest.java +++ b/flash/src/test/java/dev/relism/flash/http2/WebSocketParityTest.java @@ -28,14 +28,14 @@ class WebSocketParityTest { @Test void oneRouteAndHandlerEchoTheSameMessageOverHttp1AndHttp2() throws Exception { - int port = freePort(); app = FlashApp.create( FlashConfiguration.builder() .host("127.0.0.1") - .port(port) + .port(0) .http2CleartextEnabled(true) .build()); + int port = app.port(); app.ws( "/parity", new WebSocketHandler() { @@ -135,9 +135,4 @@ class WebSocketParityTest { return input.readNBytes(length); } - private static int freePort() throws Exception { - try (ServerSocket socket = new ServerSocket(0)) { - return socket.getLocalPort(); - } - } } diff --git a/flash/src/test/java/dev/relism/flash/transport/ServerLifecycleGracefulShutdownTest.java b/flash/src/test/java/dev/relism/flash/transport/ServerLifecycleGracefulShutdownTest.java index e148835..b114284 100644 --- a/flash/src/test/java/dev/relism/flash/transport/ServerLifecycleGracefulShutdownTest.java +++ b/flash/src/test/java/dev/relism/flash/transport/ServerLifecycleGracefulShutdownTest.java @@ -28,22 +28,17 @@ class ServerLifecycleGracefulShutdownTest { if (app != null) app.stop(); } - private static int freePort() throws Exception { - try (ServerSocket s = new ServerSocket(0)) { - return s.getLocalPort(); - } - } @Test void inFlightRequest_completesWithConnectionClose_duringShutdown() throws Exception { - int port = freePort(); CountDownLatch handlerStarted = new CountDownLatch(1); CountDownLatch releaseHandler = new CountDownLatch(1); app = FlashApp.create(FlashConfiguration.builder() - .port(port).host("127.0.0.1") + .port(0).host("127.0.0.1") .shutdownDrainTimeoutMs(5_000) .build()); + int port = app.port(); app.get("/slow", (req, res) -> { handlerStarted.countDown(); assertTrue(releaseHandler.await(5, TimeUnit.SECONDS)); @@ -80,11 +75,11 @@ class ServerLifecycleGracefulShutdownTest { @Test void stop_closesListener_soNewConnectionsAreRefused() throws Exception { - int port = freePort(); app = FlashApp.create(FlashConfiguration.builder() - .port(port).host("127.0.0.1") + .port(0).host("127.0.0.1") .shutdownDrainTimeoutMs(500) .build()); + int port = app.port(); app.get("/ping", (req, res) -> "pong"); app.start();