Files
Flash5/flash/src/test/java/dev/relism/flash/http2/NghttpInteropTest.java
T
Zakaria El OrcheandClaude Opus 5 e0ad83eb2f test(core): read bound ports back from the app instead of guessing free ones
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 <noreply@anthropic.com>
2026-09-09 11:05:06 +00:00

97 lines
3.5 KiB
Java

package dev.relism.flash.http2;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import dev.relism.flash.extension.FlashApp;
import dev.relism.flash.extension.FlashConfiguration;
import dev.relism.flash.tls.TestKeystores;
import dev.relism.flash.tls.TlsConfig;
import java.net.ServerSocket;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
import org.junit.jupiter.api.io.TempDir;
@EnabledIfSystemProperty(named = "nghttp.executable", matches = ".+")
class NghttpInteropTest {
private FlashApp app;
@AfterEach
void stop() {
if (app != null) app.stop().join();
}
@Test
void verboseFrameTraceIsCorrectForTlsAndCleartext(@TempDir Path directory) throws Exception {
exercise(directory, true);
stop();
app = null;
exercise(directory, false);
}
private void exercise(Path directory, boolean tls) throws Exception {
FlashConfiguration.FlashConfigurationBuilder builder =
FlashConfiguration.builder().host("127.0.0.1").port(0);
if (tls) {
Path keystore =
TestKeystores.build(
directory,
"nghttp.p12",
"changeit",
TestKeystores.Entry.of("server", "localhost", "localhost"));
builder.tls(TlsConfig.keystore(keystore, "changeit")).http2Enabled(true);
} else {
builder.http2CleartextEnabled(true);
}
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));
app.start();
String origin = (tls ? "https://localhost:" : "http://127.0.0.1:") + port;
Path upload = directory.resolve("nghttp-upload.bin");
Files.write(upload, large);
assertTrace(run(origin + "/get", tls));
assertTrace(run(origin + "/post", tls, "-d", upload.toString()));
assertTrace(run(origin + "/large", tls, "-n"));
}
private static String run(String uri, boolean tls, String... extra) throws Exception {
List<String> command = new ArrayList<>();
command.add(System.getProperty("nghttp.executable"));
command.add("-v");
command.add("-t");
command.add("30s");
if (tls) command.add("-y");
command.addAll(List.of(extra));
command.add(uri);
ProcessBuilder builder = new ProcessBuilder(command).redirectErrorStream(true);
String libraryPath = System.getProperty("nghttp.library.path");
if (libraryPath != null) builder.environment().put("LD_LIBRARY_PATH", libraryPath);
Process process = builder.start();
assertTrue(process.waitFor(Duration.ofSeconds(40).toMillis(), TimeUnit.MILLISECONDS));
String output = new String(process.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
assertEquals(0, process.exitValue(), output);
return output;
}
private static void assertTrace(String trace) {
assertTrue(trace.contains("recv SETTINGS frame"), trace);
assertTrue(trace.contains("recv HEADERS frame"), trace);
assertTrue(trace.contains(":status: 200"), trace);
assertTrue(trace.contains("recv DATA frame"), trace);
}
}