]> git.ipfire.org Git - thirdparty/apache/httpd.git/commitdiff
*) tests: load the top-level mod_aptest in all test suites.
authorStefan Eissing <icing@apache.org>
Wed, 21 Sep 2022 11:03:44 +0000 (11:03 +0000)
committerStefan Eissing <icing@apache.org>
Wed, 21 Sep 2022 11:03:44 +0000 (11:03 +0000)
     - aptest logs the test name from the header 'AP-Test-Name' with
       the request line. LogLevel aptest:info is switched on.
     - pytest sets 'AP-Test-Name' for all curl/nghttp invocation
       against the server.

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1904188 13f79535-47bb-0310-9956-ffa450edef68

test/conftest.py
test/modules/http2/test_200_header_invalid.py
test/modules/http2/test_700_load_get.py
test/modules/http2/test_710_load_post_static.py
test/modules/http2/test_711_load_post_cgi.py
test/pyhttpd/env.py
test/pyhttpd/mod_aptest/mod_aptest.c [new file with mode: 0644]
test/pyhttpd/nghttp.py

index 3472e969f2c132c458c8ffbfeb6e57929674b115..2ae35f34ed9b555ebd141c709cc1bc1a1ba5aad2 100644 (file)
@@ -1,6 +1,8 @@
 import sys
 import os
 
+import pytest
+
 sys.path.append(os.path.join(os.path.dirname(__file__), '.'))
 
 from pyhttpd.env import HttpdTestEnv
@@ -21,4 +23,9 @@ def pytest_generate_tests(metafunc):
         metafunc.fixturenames.append('tmp_ct')
         metafunc.parametrize('repeat', range(count))
 
+@pytest.fixture(autouse=True, scope="function")
+def _function_scope(env, request):
+    env.set_current_test_name(request.node.name)
+    yield
+    env.set_current_test_name(None)
 
index 5f8c976808fc7642e0d3de2d2100f68e75d107f0..44ad69bc6be2fc7ca6a8c698272706b741b754f8 100644 (file)
@@ -88,7 +88,8 @@ class TestInvalidHeaders:
     def test_h2_200_12(self, env):
         url = env.mkurl("https", "cgi", "/")
         opt = []
-        for i in range(98):  # curl sends 2 headers itself (user-agent and accept)
+        # curl sends 3 headers itself (user-agent, accept, and our AP-Test-Name)
+        for i in range(97):
             opt += ["-H", "x: 1"]
         r = env.curl_get(url, options=opt)
         assert r.response["status"] == 200
@@ -100,8 +101,9 @@ class TestInvalidHeaders:
     def test_h2_200_13(self, env):
         url = env.mkurl("https", "cgi", "/")
         opt = []
-        for i in range(98):  # curl sends 2 headers itself (user-agent and accept)
-            opt += ["-H", "x{0}: 1".format(i)]
+        # curl sends 3 headers itself (user-agent, accept, and our AP-Test-Name)
+        for i in range(97):
+            opt += ["-H", f"x{i}: 1"]
         r = env.curl_get(url, options=opt)
         assert r.response["status"] == 200
         r = env.curl_get(url, options=(opt + ["-H", "y: 2"]))
index d1121a6065c0bea86b96397e3a8d560c1d6fb77a..9ee8898dfd5910278ddc8a2d3be41d1a951d7177 100644 (file)
@@ -30,6 +30,7 @@ class TestLoadGet:
         1000, 80000
     ])
     def test_h2_700_10(self, env, start):
+        assert env.is_live()
         text = "X"
         chunk = 32
         for n in range(0, 5):
@@ -47,6 +48,7 @@ class TestLoadGet:
         1, 2, 16, 32
     ])
     def test_h2_700_11(self, env, conns):
+        assert env.is_live()
         text = "X"
         start = 1200
         chunk = 64
index fd1b5abe80d865fc384ce0d2bf242279d4f00b5f..ad8ae96aefd07d4d00e96822338b5f73ec9ce41f 100644 (file)
@@ -26,6 +26,7 @@ class TestLoadPostStatic:
     
     # test POST on static file, slurped in by server
     def test_h2_710_00(self, env, repeat):
+        assert env.is_live()
         url = env.mkurl("https", "test1", "/index.html")
         n = 10
         m = 1
@@ -38,6 +39,7 @@ class TestLoadPostStatic:
         self.check_h2load_ok(env, r, n)
 
     def test_h2_710_01(self, env, repeat):
+        assert env.is_live()
         url = env.mkurl("https", "test1", "/index.html")
         n = 1000
         m = 100
@@ -50,6 +52,7 @@ class TestLoadPostStatic:
         self.check_h2load_ok(env, r, n)
 
     def test_h2_710_02(self, env, repeat):
+        assert env.is_live()
         url = env.mkurl("https", "test1", "/index.html")
         n = 100
         m = 50
index 94c16002a75e4822b168d9ea98638cbf5001919b..82529d17644bfc4a44301536b050f2455be030e1 100644 (file)
@@ -26,6 +26,7 @@ class TestLoadCgi:
     
     # test POST on cgi, where input is read
     def test_h2_711_10(self, env, repeat):
+        assert env.is_live()
         url = env.mkurl("https", "test1", "/echo.py")
         n = 100
         m = 5
@@ -41,6 +42,7 @@ class TestLoadCgi:
 
     # test POST on cgi via http/1.1 proxy, where input is read
     def test_h2_711_11(self, env, repeat):
+        assert env.is_live()
         url = env.mkurl("https", "test1", "/proxy/echo.py")
         n = 100
         m = 5
@@ -56,6 +58,7 @@ class TestLoadCgi:
 
     # test POST on cgi via h2proxy, where input is read
     def test_h2_711_12(self, env, repeat):
+        assert env.is_live()
         url = env.mkurl("https", "test1", "/h2proxy/echo.py")
         n = 100
         m = 5
index 45f6d2f066035e18972761917fbc134b4cc88560..991ead9e113da1d481abc8b0523327011295a01c 100644 (file)
@@ -90,6 +90,7 @@ class HttpdTestSetup:
             self.add_modules([self.env.ssl_module])
         self._make_modules_conf()
         self._make_htdocs()
+        self._add_aptest()
         self.env.clear_curl_headerfiles()
 
     def _make_dirs(self):
@@ -179,6 +180,21 @@ class HttpdTestSetup:
                     st = os.stat(py_file)
                     os.chmod(py_file, st.st_mode | stat.S_IEXEC)
 
+    def _add_aptest(self):
+        local_dir = os.path.dirname(inspect.getfile(HttpdTestSetup))
+        p = subprocess.run([self.env.apxs, '-c', 'mod_aptest.c'],
+                           capture_output=True,
+                           cwd=os.path.join(local_dir, 'mod_aptest'))
+        rv = p.returncode
+        if rv != 0:
+            log.error(f"compiling mod_aptest failed: {p.stderr}")
+            raise Exception(f"compiling mod_aptest failed: {p.stderr}")
+
+        modules_conf = os.path.join(self.env.server_dir, 'conf/modules.conf')
+        with open(modules_conf, 'a') as fd:
+            # load our test module which is not installed
+            fd.write(f"LoadModule aptest_module   \"{local_dir}/mod_aptest/.libs/mod_aptest.so\"\n")
+
 
 class HttpdTestEnv:
 
@@ -255,7 +271,7 @@ class HttpdTestEnv:
         self._verbosity = pytestconfig.option.verbose if pytestconfig is not None else 0
         self._test_conf = os.path.join(self._server_conf_dir, "test.conf")
         self._httpd_base_conf = []
-        self._httpd_log_modules = []
+        self._httpd_log_modules = ['aptest']
         self._log_interesting = None
         self._setup = None
 
@@ -269,6 +285,8 @@ class HttpdTestEnv:
 
         self._verify_certs = False
         self._curl_headerfiles_n = 0
+        self._h2load_version = None
+        self._current_test = None
 
     def add_httpd_conf(self, lines: List[str]):
         self._httpd_base_conf.extend(lines)
@@ -402,6 +420,13 @@ class HttpdTestEnv:
     def ca(self) -> Credentials:
         return self._ca
 
+    @property
+    def current_test_name(self) -> str:
+        return self._current_test
+
+    def set_current_test_name(self, val) -> None:
+        self._current_test = val
+
     @property
     def apachectl_stderr(self):
         return self._apachectl_stderr
@@ -416,6 +441,7 @@ class HttpdTestEnv:
         return []
 
     def _versiontuple(self, v):
+        v = re.sub(r'(\d+\.\d+(\.\d+)?)(-\S+)?', r'\1', v)
         return tuple(map(int, v.split('.')))
 
     def httpd_is_at_least(self, minv):
@@ -428,14 +454,16 @@ class HttpdTestEnv:
     def h2load_is_at_least(self, minv):
         if not self.has_h2load():
             return False
-        p = subprocess.run([self._h2load, '--version'], capture_output=True, text=True)
-        if p.returncode != 0:
-            return False
-        s = p.stdout.strip()
-        m = re.match(r'h2load nghttp2/(\S+)', s)
-        if m:
-            hv = self._versiontuple(m.group(1))
-            return hv >= self._versiontuple(minv)
+        if self._h2load_version is None:
+            p = subprocess.run([self._h2load, '--version'], capture_output=True, text=True)
+            if p.returncode != 0:
+                return False
+            s = p.stdout.strip()
+            m = re.match(r'h2load nghttp2/(\S+)', s)
+            if m:
+                self._h2load_version = self._versiontuple(m.group(1))
+        if self._h2load_version is not None:
+            return self._h2load_version >= self._versiontuple(minv)
         return False
 
     def has_nghttp(self):
@@ -627,6 +655,9 @@ class HttpdTestEnv:
             if ca_pem:
                 args.extend(["--cacert", ca_pem])
 
+        if self._current_test is not None:
+            args.extend(["-H", f'AP-Test-Name: {self._current_test}'])
+
         if force_resolve and u.hostname and u.hostname != 'localhost' \
                 and u.hostname != self._httpd_addr \
                 and not re.match(r'^(\d+|\[|:).*', u.hostname):
@@ -739,7 +770,8 @@ class HttpdTestEnv:
         return -1
         
     def nghttp(self):
-        return Nghttp(self._nghttp, connect_addr=self._httpd_addr, tmp_dir=self.gen_dir)
+        return Nghttp(self._nghttp, connect_addr=self._httpd_addr,
+                      tmp_dir=self.gen_dir, test_name=self._current_test)
 
     def h2load_status(self, run: ExecResult):
         stats = {}
diff --git a/test/pyhttpd/mod_aptest/mod_aptest.c b/test/pyhttpd/mod_aptest/mod_aptest.c
new file mode 100644 (file)
index 0000000..d1a8e05
--- /dev/null
@@ -0,0 +1,66 @@
+/* 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.
+ */
+
+#include <apr_optional.h>
+#include <apr_optional_hooks.h>
+#include <apr_strings.h>
+#include <apr_cstr.h>
+#include <apr_want.h>
+
+#include <httpd.h>
+#include <http_protocol.h>
+#include <http_request.h>
+#include <http_log.h>
+
+static void aptest_hooks(apr_pool_t *pool);
+
+AP_DECLARE_MODULE(aptest) = {
+    STANDARD20_MODULE_STUFF,
+    NULL, /* func to create per dir config */
+    NULL,  /* func to merge per dir config */
+    NULL, /* func to create per server config */
+    NULL,  /* func to merge per server config */
+    NULL,              /* command handlers */
+    aptest_hooks,
+#if defined(AP_MODULE_FLAG_NONE)
+    AP_MODULE_FLAG_ALWAYS_MERGE
+#endif
+};
+
+
+static int aptest_post_read_request(request_rec *r)
+{
+    const char *test_name = apr_table_get(r->headers_in, "AP-Test-Name");
+    if (test_name) {
+        ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "test[%s]: %s",
+                      test_name, r->the_request);
+    }
+    return DECLINED;
+}
+
+/* Install this module into the apache2 infrastructure.
+ */
+static void aptest_hooks(apr_pool_t *pool)
+{
+    ap_log_perror(APLOG_MARK, APLOG_TRACE1, 0, pool,
+                  "installing hooks and handlers");
+
+    /* test case monitoring */
+    ap_hook_post_read_request(aptest_post_read_request, NULL,
+                              NULL, APR_HOOK_MIDDLE);
+
+}
+
index 84b8f20c6f4a4467885a6e56c53e479083e0fcf5..6dea97b55c477cc8becf5356ca363f34c4965260 100644 (file)
@@ -15,10 +15,12 @@ def _get_path(x):
 
 class Nghttp:
 
-    def __init__(self, path, connect_addr=None, tmp_dir="/tmp"):
+    def __init__(self, path, connect_addr=None, tmp_dir="/tmp",
+                 test_name: str = None):
         self.NGHTTP = path
         self.CONNECT_ADDR = connect_addr
         self.TMP_DIR = tmp_dir
+        self._test_name = test_name
 
     @staticmethod
     def get_stream(streams, sid):
@@ -104,7 +106,7 @@ class Nghttp:
                 body += m.group(1)
                 s = self.get_stream(streams, m.group(2))
                 if s:
-                    print("stream %d: recv %d header" % (s["id"], len(s["header"]))) 
+                    print("stream %d: recv %d header" % (s["id"], len(s["header"])))
                     response = s["response"]
                     hkey = "header"
                     if "header" in response:
@@ -194,9 +196,11 @@ class Nghttp:
             output["response"] = streams[main_stream]["response"]
             output["paddings"] = streams[main_stream]["paddings"]
         return output
-    
+
     def _raw(self, url, timeout, options):
         args = ["-v"]
+        if self._test_name is not None:
+            args.append(f'--header=AP-Test-Name: {self._test_name}')
         if options:
             args.extend(options)
         r = self._baserun(url, timeout, args)