]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
tests: use dynamic ports numbers in pytest suite
authorStefan Eissing <stefan@eissing.org>
Mon, 6 Mar 2023 15:11:11 +0000 (16:11 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Mon, 6 Mar 2023 22:44:45 +0000 (23:44 +0100)
- necessary ports are bound at start of test suite and then
  given to server fixtures for use.
- this make parallel use of pytest (in separate directories),
  practically safe for use as OS tend to not reuse such port numbers
  for a while

Closes #10692

tests/conftest.py
tests/http/config.ini.in
tests/http/conftest.py
tests/http/test_05_errors.py
tests/http/testenv/env.py
tests/http/testenv/ports.py [new file with mode: 0644]

index 3e0599c5f1691dabcdd410870bc03fb4a1e8d598..443b2a4433032e06cd2949e17245fabde40b1d32 100644 (file)
 #
 ###########################################################################
 #
-import logging
-import os
-import sys
-from typing import Optional
+import sys, os
 
-import pytest
+sys.path.append(os.path.join(os.path.dirname(__file__), 'http'))
 
+import pytest
+from testenv import Env
 
 def pytest_report_header(config, startdir):
-    return f"curl tests"
+    # Env inits its base properties only once, we can report them here
+    env = Env()
+    report = [
+        f'Testing curl {env.curl_version()}',
+        f'  httpd: {env.httpd_version()}, http:{env.http_port} https:{env.https_port}',
+        f'  httpd-proxy: {env.httpd_version()}, http:{env.proxy_port} https:{env.proxys_port}'
+    ]
+    if env.have_h3_server():
+        report.extend([
+            f'  nghttpx: {env.nghttpx_version()}, h3:{env.https_port}'
+        ])
+    if env.has_caddy():
+        report.extend([
+            f'  Caddy: {env.caddy_version()}, http:{env.caddy_http_port} https:{env.caddy_https_port}'
+        ])
+    return '\n'.join(report)
 
 
 def pytest_addoption(parser):
index 68ab0d360f5c15ee07b25a67a88615f777a8759c..361292874e7b1807f162c9acc9aa38844802f6c9 100644 (file)
@@ -30,17 +30,8 @@ apxs = @APXS@
 httpd = @HTTPD@
 apachectl = @APACHECTL@
 
-[test]
-http_port = 5001
-https_port = 5002
-h3_port = 5002
-proxy_port = 5004
-proxys_port = 5005
-
 [nghttpx]
 nghttpx = @HTTPD_NGHTTPX@
 
 [caddy]
 caddy = @CADDY@
-http_port = 5010
-https_port = 5011
index 903c6c87b12129d317883f45207c78fa14cf360c..1ce89844d57c2b87aa65294ffae7db97139f7869 100644 (file)
@@ -34,10 +34,6 @@ sys.path.append(os.path.join(os.path.dirname(__file__), '.'))
 from testenv import Env, Nghttpx, Httpd
 
 
-def pytest_report_header(config, startdir):
-    return f"curl http tests"
-
-
 @pytest.fixture(scope="package")
 def env(pytestconfig) -> Env:
     env = Env(pytestconfig=pytestconfig)
@@ -46,6 +42,10 @@ def env(pytestconfig) -> Env:
     env.setup()
     return env
 
+@pytest.fixture(scope="package", autouse=True)
+def log_global_env_facts(record_testsuite_property, env):
+    record_testsuite_property("http-port", env.http_port)
+
 
 @pytest.fixture(scope='package')
 def httpd(env) -> Httpd:
index a95e25536e7d47de818877eb908eed631e874a62..f917cd7377d1fa9b8eef578568c8870abb872670 100644 (file)
@@ -79,7 +79,7 @@ class TestErrors:
         curl = CurlClient(env=env)
         urln = f'https://{env.authority_for(env.domain1, proto)}' \
                f'/curltest/tweak?id=[0-{count - 1}]'\
-               '&chunks=3&chunk_size=16000&body_error=reset'
+               '&chunks=5&chunk_size=16000&body_error=reset'
         r = curl.http_download(urls=[urln], alpn_proto=proto, extra_args=[
             '--retry', '0', '--parallel',
         ])
index 8389119a7dfee8afdf64a958c1ed7dead566f698..8b5b5b647823c428115f720ee84f9b96de6525ac 100644 (file)
 import logging
 import os
 import re
+import socket
 import subprocess
+import sys
 from configparser import ConfigParser, ExtendedInterpolation
 from typing import Optional
 
 from .certs import CertificateSpec, TestCA, Credentials
+from .ports import alloc_ports
+
 
 log = logging.getLogger(__name__)
 
@@ -95,11 +99,14 @@ class EnvConfig:
         self.nghttpx_with_h3 = re.match(r'.* nghttp3/.*', p.stdout.strip())
         log.debug(f'nghttpx -v: {p.stdout}')
 
-        self.http_port = self.config['test']['http_port']
-        self.https_port = self.config['test']['https_port']
-        self.proxy_port = self.config['test']['proxy_port']
-        self.proxys_port = self.config['test']['proxys_port']
-        self.h3_port = self.config['test']['h3_port']
+        self.ports = alloc_ports(port_specs={
+            'http': socket.SOCK_STREAM,
+            'https': socket.SOCK_STREAM,
+            'proxy': socket.SOCK_STREAM,
+            'proxys': socket.SOCK_STREAM,
+            'caddy': socket.SOCK_STREAM,
+            'caddys': socket.SOCK_STREAM,
+        })
         self.httpd = self.config['httpd']['httpd']
         self.apachectl = self.config['httpd']['apachectl']
         self.apxs = self.config['httpd']['apxs']
@@ -126,6 +133,7 @@ class EnvConfig:
         ]
 
         self.nghttpx = self.config['nghttpx']['nghttpx']
+        self._nghttpx_version = None
         self.nghttpx_with_h3 = False
         if len(self.nghttpx) == 0:
             self.nghttpx = 'nghttpx'
@@ -136,10 +144,12 @@ class EnvConfig:
                 # not a working nghttpx
                 self.nghttpx = None
             else:
+                self._nghttpx_version = re.sub(r'^nghttpx\s*', '', p.stdout.strip())
                 self.nghttpx_with_h3 = re.match(r'.* nghttp3/.*', p.stdout.strip()) is not None
                 log.debug(f'nghttpx -v: {p.stdout}')
 
         self.caddy = self.config['caddy']['caddy']
+        self._caddy_version = None
         if len(self.caddy.strip()) == 0:
             self.caddy = None
         if self.caddy is not None:
@@ -149,10 +159,9 @@ class EnvConfig:
                 if p.returncode != 0:
                     # not a working caddy
                     self.caddy = None
+                self._caddy_version = re.sub(r' .*', '', p.stdout.strip())
             except:
                 self.caddy = None
-        self.caddy_http_port = self.config['caddy']['http_port']
-        self.caddy_https_port = self.config['caddy']['https_port']
 
     @property
     def httpd_version(self):
@@ -189,6 +198,14 @@ class EnvConfig:
             return f"apxs ({self.apxs}) not found"
         return None
 
+    @property
+    def nghttpx_version(self):
+        return self._nghttpx_version
+
+    @property
+    def caddy_version(self):
+        return self._caddy_version
+
 
 class Env:
 
@@ -242,6 +259,14 @@ class Env:
     def httpd_version() -> str:
         return Env.CONFIG.httpd_version
 
+    @staticmethod
+    def nghttpx_version() -> str:
+        return Env.CONFIG.nghttpx_version
+
+    @staticmethod
+    def caddy_version() -> str:
+        return Env.CONFIG.caddy_version
+
     @staticmethod
     def httpd_is_at_least(minv) -> bool:
         return Env.CONFIG.httpd_is_at_least(minv)
@@ -303,36 +328,36 @@ class Env:
         return self.CONFIG.proxy_domain
 
     @property
-    def http_port(self) -> str:
-        return self.CONFIG.http_port
+    def http_port(self) -> int:
+        return self.CONFIG.ports['http']
 
     @property
-    def https_port(self) -> str:
-        return self.CONFIG.https_port
+    def https_port(self) -> int:
+        return self.CONFIG.ports['https']
 
     @property
-    def h3_port(self) -> str:
-        return self.CONFIG.h3_port
+    def h3_port(self) -> int:
+        return self.https_port
 
     @property
     def proxy_port(self) -> str:
-        return self.CONFIG.proxy_port
+        return self.CONFIG.ports['proxy']
 
     @property
     def proxys_port(self) -> str:
-        return self.CONFIG.proxys_port
+        return self.CONFIG.ports['proxys']
 
     @property
     def caddy(self) -> str:
         return self.CONFIG.caddy
 
     @property
-    def caddy_https_port(self) -> str:
-        return self.CONFIG.caddy_https_port
+    def caddy_https_port(self) -> int:
+        return self.CONFIG.ports['caddys']
 
     @property
-    def caddy_http_port(self) -> str:
-        return self.CONFIG.caddy_http_port
+    def caddy_http_port(self) -> int:
+        return self.CONFIG.ports['caddy']
 
     @property
     def curl(self) -> str:
diff --git a/tests/http/testenv/ports.py b/tests/http/testenv/ports.py
new file mode 100644 (file)
index 0000000..c040dcb
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#***************************************************************************
+#                                  _   _ ____  _
+#  Project                     ___| | | |  _ \| |
+#                             / __| | | | |_) | |
+#                            | (__| |_| |  _ <| |___
+#                             \___|\___/|_| \_\_____|
+#
+# Copyright (C) 2008 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at https://curl.se/docs/copyright.html.
+#
+# You may opt to use, copy, modify, merge, publish, distribute and/or sell
+# copies of the Software, and permit persons to whom the Software is
+# furnished to do so, under the terms of the COPYING file.
+#
+# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+# KIND, either express or implied.
+#
+# SPDX-License-Identifier: curl
+#
+###########################################################################
+#
+import logging
+import socket
+from typing import Dict
+
+log = logging.getLogger(__name__)
+
+
+def alloc_ports(port_specs: Dict[str, int]) -> Dict[str, int]:
+    ports = {}
+    socks = []
+    for name, ptype in port_specs.items():
+        try:
+            s = socket.socket(type=ptype)
+            s.bind(('', 0))
+            ports[name] = s.getsockname()[1]
+            socks.append(s)
+        except Exception as e:
+            raise e
+    for s in socks:
+        s.close()
+    return ports
+
+