From: Stefan Eissing Date: Mon, 6 Mar 2023 15:11:11 +0000 (+0100) Subject: tests: use dynamic ports numbers in pytest suite X-Git-Tag: curl-8_0_0~90 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=b0564c1d545d74da0c95665357d69d2bd2da04ae;p=thirdparty%2Fcurl.git tests: use dynamic ports numbers in pytest suite - 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 --- diff --git a/tests/conftest.py b/tests/conftest.py index 3e0599c5f1..443b2a4433 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -22,16 +22,30 @@ # ########################################################################### # -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): diff --git a/tests/http/config.ini.in b/tests/http/config.ini.in index 68ab0d360f..361292874e 100644 --- a/tests/http/config.ini.in +++ b/tests/http/config.ini.in @@ -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 diff --git a/tests/http/conftest.py b/tests/http/conftest.py index 903c6c87b1..1ce89844d5 100644 --- a/tests/http/conftest.py +++ b/tests/http/conftest.py @@ -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: diff --git a/tests/http/test_05_errors.py b/tests/http/test_05_errors.py index a95e25536e..f917cd7377 100644 --- a/tests/http/test_05_errors.py +++ b/tests/http/test_05_errors.py @@ -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', ]) diff --git a/tests/http/testenv/env.py b/tests/http/testenv/env.py index 8389119a7d..8b5b5b6478 100644 --- a/tests/http/testenv/env.py +++ b/tests/http/testenv/env.py @@ -27,11 +27,15 @@ 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 index 0000000000..c040dcb05c --- /dev/null +++ b/tests/http/testenv/ports.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +#*************************************************************************** +# _ _ ____ _ +# Project ___| | | | _ \| | +# / __| | | | |_) | | +# | (__| |_| | _ <| |___ +# \___|\___/|_| \_\_____| +# +# Copyright (C) 2008 - 2022, Daniel Stenberg, , 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 + +