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:
Stefan Eissing 2023-03-06 16:11:11 +01:00 committed by Daniel Stenberg
parent 257416023d
commit b0564c1d54
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
6 changed files with 118 additions and 39 deletions

View File

@ -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):

View 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

View 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:

View 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',
])

View File

@ -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:

View 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