You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ds...@apache.org on 2026/02/27 11:30:48 UTC
(tomcat) 04/18: Add httpd integration testing infrastructure
This is an automated email from the ASF dual-hosted git repository.
dsoumis pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 509a8b42238ccfedfcf51570df2ac053447dd1f7
Author: Dimitris Soumis <ji...@gmail.com>
AuthorDate: Tue Feb 17 16:34:44 2026 +0200
Add httpd integration testing infrastructure
---
.../httpd/HttpdIntegrationBaseTest.java | 154 +++++++++++++++++++++
.../tomcat/integration/httpd/TesterHttpd.java | 149 ++++++++++++++++++++
.../tomcat/integration/httpd/httpd-binary.lock | 0
3 files changed, 303 insertions(+)
diff --git a/test/org/apache/tomcat/integration/httpd/HttpdIntegrationBaseTest.java b/test/org/apache/tomcat/integration/httpd/HttpdIntegrationBaseTest.java
new file mode 100644
index 0000000000..17930db98d
--- /dev/null
+++ b/test/org/apache/tomcat/integration/httpd/HttpdIntegrationBaseTest.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tomcat.integration.httpd;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.ServerSocket;
+import java.nio.channels.FileLock;
+import java.util.List;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.Valve;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.tomcat.util.compat.JrePlatform;
+
+/**
+ * Base class for httpd integration tests.
+ * Manages httpd and Tomcat process lifecycle.
+ */
+public abstract class HttpdIntegrationBaseTest extends TomcatBaseTest {
+
+ private static final File lockFile = new File("test/org/apache/tomcat/integration/httpd/httpd-binary.lock");
+ private static FileLock lock = null;
+
+ private TesterHttpd httpd;
+ private int httpdPort;
+ protected File httpdConfDir;
+
+ private int tomcatPort;
+
+ protected abstract String getHttpdConfig();
+ protected abstract List<Valve> getValveConfig();
+
+ @BeforeClass
+ public static void obtainHttpdBinaryLock() throws IOException {
+ @SuppressWarnings("resource")
+ FileOutputStream fos = new FileOutputStream(lockFile);
+ lock = fos.getChannel().lock();
+ }
+
+ @AfterClass
+ public static void releaseHttpdBinaryLock() throws IOException {
+ // Should not be null be in case obtaining the lock fails, avoid a second error.
+ if (lock != null) {
+ lock.release();
+ }
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ setUpTomcat();
+ setUpHttpd();
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ if (httpd != null) {
+ httpd.stop();
+ httpd = null;
+ }
+ super.tearDown();
+ }
+
+ private void setUpTomcat() throws LifecycleException {
+ Tomcat tomcat = getTomcatInstance();
+ Context ctx = getProgrammaticRootContext();
+ for (Valve valve : getValveConfig()) {
+ ctx.getPipeline().addValve(valve);
+ }
+ Tomcat.addServlet(ctx, "snoop", new SnoopServlet());
+ ctx.addServletMappingDecoded("/snoop", "snoop");
+ tomcat.start();
+ tomcatPort = getPort();
+ }
+
+ private void setUpHttpd() throws IOException {
+ httpdPort = findFreePort();
+ httpdConfDir = getTemporaryDirectory();
+ generateHttpdConfig(getHttpdConfig());
+
+ httpd = new TesterHttpd(httpdConfDir, httpdPort);
+ try {
+ httpd.start();
+ } catch (IOException | InterruptedException ioe) {
+ httpd = null;
+ }
+ }
+
+ private static int findFreePort() throws IOException {
+ try (ServerSocket socket = new ServerSocket(0)) {
+ return socket.getLocalPort();
+ }
+ }
+
+ public void generateHttpdConfig(String httpdConf) throws IOException {
+
+ httpdConf = getPlatformHttpdConfig() + httpdConf;
+
+ httpdConf = httpdConf.replace("%{HTTPD_PORT}", Integer.toString(httpdPort))
+ .replace("%{TOMCAT_PORT}", Integer.toString(tomcatPort))
+ .replace("%{CONF_DIR}", httpdConfDir.getAbsolutePath());
+
+
+ try (PrintWriter writer = new PrintWriter(new File(httpdConfDir, "httpd.conf"))) {
+ writer.write(httpdConf);
+ }
+
+ }
+
+ private String getPlatformHttpdConfig() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Listen %{HTTPD_PORT}\n");
+ sb.append("PidFile %{CONF_DIR}/httpd.pid\n");
+ sb.append("LoadModule authz_core_module modules/mod_authz_core.so\n");
+ if (JrePlatform.IS_WINDOWS) {
+ sb.append("LoadModule mpm_winnt_module modules/mod_mpm_winnt.so\n");
+ sb.append("ErrorLog \"|C:/Windows/System32/more.com\"\n");
+ } else {
+ sb.append("LoadModule unixd_module modules/mod_unixd.so\n");
+ sb.append("LoadModule mpm_event_module modules/mod_mpm_event.so\n");
+ sb.append("ErrorLog /dev/stderr\n");
+ }
+ sb.append("LogLevel warn\n");
+ sb.append("ServerName localhost:%{HTTPD_PORT}\n");
+ return sb.toString();
+ }
+
+ public int getHttpdPort() {
+ return httpdPort;
+ }
+}
diff --git a/test/org/apache/tomcat/integration/httpd/TesterHttpd.java b/test/org/apache/tomcat/integration/httpd/TesterHttpd.java
new file mode 100644
index 0000000000..7502af76ad
--- /dev/null
+++ b/test/org/apache/tomcat/integration/httpd/TesterHttpd.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tomcat.integration.httpd;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.Reader;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Assert;
+
+
+public class TesterHttpd {
+
+ private final File httpdConfDir;
+ private final int httpdPort;
+
+ private static final String HTTPD_PATH = "tomcat.test.httpd.path";
+
+ private Process p;
+
+ public TesterHttpd(File httpdConfDir, int httpdPort) {
+ this.httpdConfDir = httpdConfDir;
+ this.httpdPort = httpdPort;
+ }
+
+ public void start() throws IOException, InterruptedException {
+ start(false);
+ }
+
+ public void start(boolean swallowOutput) throws IOException, InterruptedException {
+ if (p != null) {
+ throw new IllegalStateException("Already started");
+ }
+
+ String httpdPath = System.getProperty(HTTPD_PATH);
+ if (httpdPath == null) {
+ httpdPath = "httpd";
+ }
+
+ File httpdConfFile = new File(httpdConfDir, "httpd.conf");
+ validateHttpdConfig(httpdPath, httpdConfFile.getAbsolutePath());
+
+ List<String> cmd = new ArrayList<>(4);
+ cmd.add(httpdPath);
+ cmd.add("-f");
+ cmd.add(httpdConfFile.getAbsolutePath());
+ cmd.add("-X");
+
+ ProcessBuilder pb = new ProcessBuilder(cmd.toArray(new String[0]));
+
+ p = pb.start();
+
+ redirect(p.inputReader(), System.out, swallowOutput);
+ redirect(p.errorReader(), System.err, swallowOutput);
+
+ Assert.assertTrue(p.isAlive() && isHttpdReady());
+ }
+
+ public void stop() {
+ if (p == null) {
+ throw new IllegalStateException("Not started");
+ }
+ p.destroy();
+
+ try {
+ if (!p.waitFor(30, TimeUnit.SECONDS)) {
+ throw new IllegalStateException("Failed to stop");
+ }
+ } catch (InterruptedException e) {
+ throw new IllegalStateException("Interrupted while waiting to stop", e);
+ }
+ }
+
+ private void redirect(final Reader r, final PrintStream os, final boolean swallow) {
+ /*
+ * InputStream will close when process ends. Thread will exit once stream closes.
+ */
+ new Thread( () -> {
+ char[] cbuf = new char[1024];
+ try {
+ int read;
+ while ((read = r.read(cbuf)) > 0) {
+ if (!swallow) {
+ os.print(new String(cbuf, 0, read));
+ }
+ }
+ } catch (IOException ignore) {
+ // Ignore
+ }
+
+ }).start();
+ }
+
+ private static void validateHttpdConfig(final String httpdPath, final String httpdConfPath) throws IOException, InterruptedException {
+ List<String> cmd = new ArrayList<>(4);
+
+ cmd.add(httpdPath);
+ cmd.add("-t");
+ cmd.add("-f");
+ cmd.add(httpdConfPath);
+
+ ProcessBuilder pb = new ProcessBuilder(cmd.toArray(new String[0]));
+ pb.redirectErrorStream(true);
+
+ Process p = pb.start();
+
+ String output = new String(p.getInputStream().readAllBytes());
+ int exitCode = p.waitFor();
+
+ if (exitCode != 0) {
+ throw new IllegalStateException("Httpd configuration invalid. Output: " + output);
+ }
+ }
+
+ @SuppressWarnings("BusyWait")
+ private boolean isHttpdReady() throws InterruptedException {
+ long deadline = System.currentTimeMillis() + 1000;
+ while (System.currentTimeMillis() < deadline) {
+ try (Socket ignored = new Socket("localhost", this.httpdPort)) {
+ return true;
+ } catch (IOException e) {
+ Thread.sleep(100);
+ }
+ }
+ throw new IllegalStateException("Httpd has not been started.");
+ }
+
+
+}
diff --git a/test/org/apache/tomcat/integration/httpd/httpd-binary.lock b/test/org/apache/tomcat/integration/httpd/httpd-binary.lock
new file mode 100644
index 0000000000..e69de29bb2
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org