pytest: use dante-server in CI

- add startup check for 'danted' to avoid fails on low cpu
- rename 'sockd' to 'danted' everywhere to clarify what we use
- add proper defaults for 'danted' for debian
- install 'dante-server' in pytest ci runs

Closes #18075
This commit is contained in:
Stefan Eissing 2025-07-29 10:15:43 +02:00 committed by Daniel Stenberg
parent cd586149d5
commit 6b70e8a838
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
11 changed files with 85 additions and 68 deletions

View file

@ -52,11 +52,11 @@ if(NOT HTTPD_NGHTTPX)
endif()
mark_as_advanced(HTTPD_NGHTTPX)
find_program(SOCKD "sockd")
if(NOT SOCKD)
set(SOCKD "")
find_program(DANTED "danted")
if(NOT DANTED)
set(DANTED "")
endif()
mark_as_advanced(SOCKD)
mark_as_advanced(DANTED)
# Consumed variables: APXS, CADDY, HTTPD, HTTPD_NGHTTPX, SOCKD, VSFTPD
# Consumed variables: APXS, CADDY, HTTPD, HTTPD_NGHTTPX, DANTED, VSFTPD
configure_file("config.ini.in" "${CMAKE_CURRENT_BINARY_DIR}/config.ini" @ONLY)

View file

@ -38,5 +38,5 @@ caddy = @CADDY@
[vsftpd]
vsftpd = @VSFTPD@
[sockd]
sockd = @SOCKD@
[danted]
danted = @DANTED@

View file

@ -35,15 +35,15 @@ from testenv import Env, CurlClient, Dante
log = logging.getLogger(__name__)
@pytest.mark.skipif(condition=not Env.has_sockd(), reason="missing sockd")
@pytest.mark.skipif(condition=not Env.has_danted(), reason="missing danted")
class TestSocks:
@pytest.fixture(scope='class')
def sockd(self, env: Env) -> Generator[Dante, None, None]:
sockd = Dante(env=env)
assert sockd.initial_start()
yield sockd
sockd.stop()
def danted(self, env: Env) -> Generator[Dante, None, None]:
danted = Dante(env=env)
assert danted.initial_start()
yield danted
danted.stop()
@pytest.fixture(autouse=True, scope='class')
def _class_scope(self, env, httpd):
@ -52,9 +52,9 @@ class TestSocks:
env.make_data_file(indir=env.gen_dir, fname="data-10m", fsize=10*1024*1024)
@pytest.mark.parametrize("sproto", ['socks4', 'socks5'])
def test_40_01_socks_http(self, env: Env, sproto, sockd: Dante, httpd):
def test_40_01_socks_http(self, env: Env, sproto, danted: Dante, httpd):
curl = CurlClient(env=env, socks_args=[
f'--{sproto}', f'127.0.0.1:{sockd.port}'
f'--{sproto}', f'127.0.0.1:{danted.port}'
])
url = f'http://{env.domain1}:{env.http_port}/data.json'
r = curl.http_get(url=url)
@ -62,11 +62,11 @@ class TestSocks:
@pytest.mark.parametrize("sproto", ['socks4', 'socks5'])
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
def test_40_02_socks_https(self, env: Env, sproto, proto, sockd: Dante, httpd):
def test_40_02_socks_https(self, env: Env, sproto, proto, danted: Dante, httpd):
if proto == 'h3' and not env.have_h3():
pytest.skip("h3 not supported")
curl = CurlClient(env=env, socks_args=[
f'--{sproto}', f'127.0.0.1:{sockd.port}'
f'--{sproto}', f'127.0.0.1:{danted.port}'
])
url = f'https://{env.authority_for(env.domain1, proto)}/data.json'
r = curl.http_get(url=url, alpn_proto=proto)
@ -77,22 +77,22 @@ class TestSocks:
@pytest.mark.parametrize("sproto", ['socks4', 'socks5'])
@pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
def test_40_03_dl_serial(self, env: Env, httpd, sockd, proto, sproto):
def test_40_03_dl_serial(self, env: Env, httpd, danted, proto, sproto):
count = 3
urln = f'https://{env.authority_for(env.domain1, proto)}/data-10m?[0-{count-1}]'
curl = CurlClient(env=env, socks_args=[
f'--{sproto}', f'127.0.0.1:{sockd.port}'
f'--{sproto}', f'127.0.0.1:{danted.port}'
])
r = curl.http_download(urls=[urln], alpn_proto=proto)
r.check_response(count=count, http_status=200)
@pytest.mark.parametrize("sproto", ['socks4', 'socks5'])
@pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
def test_40_04_ul_serial(self, env: Env, httpd, sockd, proto, sproto):
def test_40_04_ul_serial(self, env: Env, httpd, danted, proto, sproto):
fdata = os.path.join(env.gen_dir, 'data-10m')
count = 2
curl = CurlClient(env=env, socks_args=[
f'--{sproto}', f'127.0.0.1:{sockd.port}'
f'--{sproto}', f'127.0.0.1:{danted.port}'
])
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]'
r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto)

View file

@ -28,9 +28,12 @@ import logging
import os
import socket
import subprocess
import time
from datetime import timedelta, datetime
from typing import Dict
from . import CurlClient
from .env import Env
from .ports import alloc_ports_and_do
@ -41,12 +44,12 @@ class Dante:
def __init__(self, env: Env):
self.env = env
self._cmd = env.sockd
self._cmd = env.danted
self._port = 0
self.name = 'sockd'
self._port_skey = 'sockd'
self.name = 'danted'
self._port_skey = 'danted'
self._port_specs = {
'sockd': socket.SOCK_STREAM,
'danted': socket.SOCK_STREAM,
}
self._dante_dir = os.path.join(env.gen_dir, self.name)
self._run_dir = os.path.join(self._dante_dir, 'run')
@ -124,7 +127,21 @@ class Dante:
self._process = subprocess.Popen(args=args, stderr=procerr)
if self._process.returncode is not None:
return False
return True
return self.wait_live(timeout=timedelta(seconds=Env.SERVER_TIMEOUT))
def wait_live(self, timeout: timedelta):
curl = CurlClient(env=self.env, run_dir=self._tmp_dir,
timeout=timeout.total_seconds(), socks_args=[
'--socks5', f'127.0.0.1:{self._port}'
])
try_until = datetime.now() + timeout
while datetime.now() < try_until:
r = curl.http_get(url=f'http://{self.env.domain1}:{self.env.http_port}/')
if r.exit_code == 0:
return True
time.sleep(.1)
log.error(f"Server still not responding after {timeout}")
return False
def _rmf(self, path):
if os.path.exists(path):

View file

@ -256,28 +256,28 @@ class EnvConfig:
except Exception:
self.vsftpd = None
self.sockd = self.config['sockd']['sockd']
if self.sockd == '':
self.sockd = None
self._sockd_version = None
if self.sockd is not None:
self.danted = self.config['danted']['danted']
if self.danted == '':
self.danted = None
self._danted_version = None
if self.danted is not None:
try:
p = subprocess.run(args=[self.sockd, '-v'],
p = subprocess.run(args=[self.danted, '-v'],
capture_output=True, text=True)
assert p.returncode == 0
if p.returncode != 0:
# not a working vsftpd
self.sockd = None
self.danted = None
m = re.match(r'^Dante v(\d+\.\d+\.\d+).*', p.stdout)
if not m:
m = re.match(r'^Dante v(\d+\.\d+\.\d+).*', p.stderr)
if m:
self._sockd_version = m.group(1)
self._danted_version = m.group(1)
else:
self.sockd = None
raise Exception(f'Unable to determine sockd version from: {p.stderr}')
self.danted = None
raise Exception(f'Unable to determine danted version from: {p.stderr}')
except Exception:
self.sockd = None
self.danted = None
self._tcpdump = shutil.which('tcpdump')
@ -508,8 +508,8 @@ class Env:
return Env.CONFIG.vsftpd_version
@staticmethod
def has_sockd() -> bool:
return Env.CONFIG.sockd is not None
def has_danted() -> bool:
return Env.CONFIG.danted is not None
@staticmethod
def tcpdump() -> Optional[str]:
@ -673,8 +673,8 @@ class Env:
return self.CONFIG.ports['caddy']
@property
def sockd(self) -> str:
return self.CONFIG.sockd
def danted(self) -> str:
return self.CONFIG.danted
@property
def vsftpd(self) -> str: