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
This commit is contained in:
parent
257416023d
commit
b0564c1d54
@ -22,16 +22,30 @@
|
||||
#
|
||||
###########################################################################
|
||||
#
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from typing import Optional
|
||||
import sys, os
|
||||
|
||||
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):
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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',
|
||||
])
|
||||
|
||||
@ -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:
|
||||
|
||||
49
tests/http/testenv/ports.py
Normal file
49
tests/http/testenv/ports.py
Normal file
@ -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
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user