mirror of
https://github.com/curl/curl.git
synced 2026-06-02 03:14:15 +03:00
pytest: fixes and tidy-ups to h3-proxy tests
- merge tests into a single class. For shorter names, to fix sort order by test number, and to align with other tests. - fix preconditions to make `test_60_04_guard_proxy_http3_unsupported` actually run. - replace local precondition with constant of the same effect. - drop redundant non-`ngtcp2` requirement for `test_60_04_guard_proxy_http3_unsupported`. (seemed relevant for no longer supported openssl-quic builds.) - drop unused `NGTCP2_ONLY_MSG` constant. Follow-up toe4139a73c8#21798 - avoid creating unnecessary test data blobs, and minimize their scopes. Follow-up to91facd7bb3#21791 Follow-up toe78b1b3ecc#21153 Closes #21811
This commit is contained in:
parent
872c313d76
commit
d806323ffd
1 changed files with 56 additions and 101 deletions
|
|
@ -55,14 +55,6 @@ MARK_NEEDS_NGHTTPX = pytest.mark.skipif(
|
|||
condition=not Env.have_nghttpx(), reason="no nghttpx available"
|
||||
)
|
||||
|
||||
H3_PROXY_COMMON_MARKS = [
|
||||
MARK_NEEDS_HTTPS_PROXY,
|
||||
MARK_NEEDS_HTTP3,
|
||||
MARK_NEEDS_PROXY_HTTP3,
|
||||
MARK_NEEDS_NGHTTP3,
|
||||
]
|
||||
|
||||
NGTCP2_ONLY_MSG = "only supported with the ngtcp2 quic stack"
|
||||
UNSUPPORTED_OPT_MSG = "does not support this"
|
||||
H2O_HELLO_MSG = '"message": "Hello from h2o HTTP/3 server"'
|
||||
|
||||
|
|
@ -145,11 +137,15 @@ def _h2o_proxy_args(
|
|||
return xargs
|
||||
|
||||
|
||||
class TestH3ProxySuccess:
|
||||
"""Success matrix for HTTP/3 proxy CONNECT / CONNECT-UDP."""
|
||||
@MARK_NEEDS_HTTPS_PROXY
|
||||
@MARK_NEEDS_HTTP3
|
||||
@MARK_NEEDS_NGHTTP3
|
||||
class TestH3Proxy:
|
||||
|
||||
pytestmark = H3_PROXY_COMMON_MARKS + [MARK_NEEDS_H2O]
|
||||
# Success matrix for HTTP/3 proxy CONNECT / CONNECT-UDP.
|
||||
|
||||
@MARK_NEEDS_PROXY_HTTP3
|
||||
@MARK_NEEDS_H2O
|
||||
@pytest.mark.parametrize(
|
||||
["alpn_proto", "proxy_proto"],
|
||||
[
|
||||
|
|
@ -192,12 +188,10 @@ class TestH3ProxySuccess:
|
|||
r.check_response(count=1, http_status=200)
|
||||
_check_download_message(curl, H2O_HELLO_MSG)
|
||||
|
||||
# Failure matrix when proxy side does not support requested mode.
|
||||
|
||||
class TestH3ProxyFailure:
|
||||
"""Failure matrix when proxy side does not support requested mode."""
|
||||
|
||||
pytestmark = H3_PROXY_COMMON_MARKS + [MARK_NEEDS_NGHTTPX]
|
||||
|
||||
@MARK_NEEDS_PROXY_HTTP3
|
||||
@MARK_NEEDS_NGHTTPX
|
||||
@pytest.mark.parametrize(
|
||||
["alpn_proto", "proxy_proto", "exp_err"],
|
||||
[
|
||||
|
|
@ -260,12 +254,10 @@ class TestH3ProxyFailure:
|
|||
f"Expected protocol/proxy error but got: {r.dump_logs()}"
|
||||
)
|
||||
|
||||
# Behavior checks for tunnel vs non-tunnel proxy mode selection.
|
||||
|
||||
class TestH3ProxyModeSelection:
|
||||
"""Behavior checks for tunnel vs non-tunnel proxy mode selection."""
|
||||
|
||||
pytestmark = H3_PROXY_COMMON_MARKS + [MARK_NEEDS_NGHTTPX]
|
||||
|
||||
@MARK_NEEDS_PROXY_HTTP3
|
||||
@MARK_NEEDS_NGHTTPX
|
||||
@pytest.mark.parametrize(
|
||||
["proxy_proto"],
|
||||
[
|
||||
|
|
@ -301,22 +293,8 @@ class TestH3ProxyModeSelection:
|
|||
f"expected CONNECT-UDP attempt in output, got: {r.dump_logs()}"
|
||||
)
|
||||
|
||||
# Guard checks for unsupported HTTP/3 proxy options.
|
||||
|
||||
class TestH3ProxyRuntimeGuards:
|
||||
"""Guard checks for unsupported HTTP/3 proxy options."""
|
||||
|
||||
pytestmark = [
|
||||
MARK_NEEDS_HTTPS_PROXY,
|
||||
MARK_NEEDS_PROXY_HTTP3,
|
||||
pytest.mark.skipif(
|
||||
condition=Env.curl_uses_lib("ngtcp2"),
|
||||
reason="guard only applies to non-ngtcp2 builds",
|
||||
),
|
||||
]
|
||||
|
||||
@pytest.mark.skipif(
|
||||
condition=not Env.curl_has_feature("HTTP3"), reason="curl lacks HTTP/3 support"
|
||||
)
|
||||
@pytest.mark.skipif(
|
||||
condition=Env.curl_has_feature("proxy-HTTP3"), reason="curl has h3 proxy support"
|
||||
)
|
||||
|
|
@ -340,23 +318,11 @@ class TestH3ProxyRuntimeGuards:
|
|||
f"Expected unsupported option failure but got: {r.stderr}"
|
||||
)
|
||||
|
||||
# Robustness checks for shutdown and proxy loss during transfer.
|
||||
|
||||
class TestH3ProxyRobustness:
|
||||
"""Robustness checks for shutdown and proxy loss during transfer."""
|
||||
|
||||
pytestmark = H3_PROXY_COMMON_MARKS + [MARK_NEEDS_H2O]
|
||||
|
||||
@pytest.fixture(autouse=True, scope="class")
|
||||
def _class_scope(self, env, h2o_server):
|
||||
if not env.have_h2o():
|
||||
pytest.skip("h2o not available")
|
||||
env.make_data_file(
|
||||
indir=h2o_server.docs_dir, fname="proxy-drop-20m", fsize=20 * 1024 * 1024
|
||||
)
|
||||
|
||||
def test_60_05_graceful_shutdown(
|
||||
self, env: Env, h2o_server, h2o_proxy
|
||||
):
|
||||
@MARK_NEEDS_PROXY_HTTP3
|
||||
@MARK_NEEDS_H2O
|
||||
def test_60_05_graceful_shutdown(self, env: Env, h2o_server, h2o_proxy):
|
||||
if not env.curl_is_debug():
|
||||
pytest.skip("needs debug curl for shutdown trace lines")
|
||||
if not env.curl_is_verbose():
|
||||
|
|
@ -380,9 +346,12 @@ class TestH3ProxyRobustness:
|
|||
]
|
||||
assert shutdown_lines, f"No shutdown trace lines found:\n{r.stderr}"
|
||||
|
||||
@MARK_NEEDS_PROXY_HTTP3
|
||||
@MARK_NEEDS_H2O
|
||||
def test_60_06_proxy_drop_mid_transfer(self, env: Env, h2o_server, h2o_proxy):
|
||||
_require_available(h2o_server=h2o_server, h2o_proxy=h2o_proxy)
|
||||
|
||||
env.make_data_file(indir=h2o_server.docs_dir, fname="proxy-drop-20m", fsize=20 * 1024 * 1024)
|
||||
proxy_port = h2o_proxy.port
|
||||
url = f"https://localhost:{h2o_server.port}/proxy-drop-20m"
|
||||
out_path = os.path.join(env.gen_dir, "proxy-drop.out")
|
||||
|
|
@ -423,22 +392,13 @@ class TestH3ProxyRobustness:
|
|||
proc.wait(timeout=5)
|
||||
assert h2o_proxy.start(), "failed to restart h2o proxy"
|
||||
|
||||
# Large file transfers and multiplexing through HTTP/3 proxy.
|
||||
|
||||
class TestH3ProxyDataTransfer:
|
||||
"""Large file transfers and multiplexing through HTTP/3 proxy."""
|
||||
|
||||
pytestmark = H3_PROXY_COMMON_MARKS + [MARK_NEEDS_H2O]
|
||||
|
||||
@pytest.fixture(autouse=True, scope="class")
|
||||
def _class_scope(self, env, h2o_server):
|
||||
if not env.have_h2o():
|
||||
pytest.skip("h2o not available")
|
||||
env.make_data_file(indir=h2o_server.docs_dir, fname="download-1m", fsize=1 * 1024 * 1024)
|
||||
env.make_data_file(indir=h2o_server.docs_dir, fname="download-10m", fsize=10 * 1024 * 1024)
|
||||
env.make_data_file(indir=env.gen_dir, fname="upload-2m", fsize=2 * 1024 * 1024)
|
||||
|
||||
@MARK_NEEDS_PROXY_HTTP3
|
||||
@MARK_NEEDS_H2O
|
||||
def test_60_07_large_download(self, env: Env, h2o_server, h2o_proxy):
|
||||
_require_available(h2o_server=h2o_server, h2o_proxy=h2o_proxy)
|
||||
env.make_data_file(indir=h2o_server.docs_dir, fname="download-10m", fsize=10 * 1024 * 1024)
|
||||
curl = CurlClient(env=env)
|
||||
url = f"https://localhost:{h2o_server.port}/download-10m"
|
||||
proxy_args = _h2o_proxy_args(env, h2o_proxy, "h3", tunnel=True)
|
||||
|
|
@ -448,8 +408,11 @@ class TestH3ProxyDataTransfer:
|
|||
r.check_response(count=1, http_status=200)
|
||||
_check_download_size(curl, 10 * 1024 * 1024)
|
||||
|
||||
@MARK_NEEDS_PROXY_HTTP3
|
||||
@MARK_NEEDS_H2O
|
||||
def test_60_08_large_upload(self, env: Env, httpd, h2o_server, h2o_proxy):
|
||||
_require_available(h2o_proxy=h2o_proxy)
|
||||
env.make_data_file(indir=env.gen_dir, fname="upload-2m", fsize=2 * 1024 * 1024)
|
||||
fdata = os.path.join(env.gen_dir, "upload-2m")
|
||||
curl = CurlClient(env=env)
|
||||
url = f"https://localhost:{httpd.ports['https']}/curltest/echo?id=[0-0]"
|
||||
|
|
@ -463,8 +426,11 @@ class TestH3ProxyDataTransfer:
|
|||
)
|
||||
r.check_response(count=1, http_status=200)
|
||||
|
||||
@MARK_NEEDS_PROXY_HTTP3
|
||||
@MARK_NEEDS_H2O
|
||||
def test_60_09_parallel_downloads(self, env: Env, h2o_server, h2o_proxy):
|
||||
_require_available(h2o_server=h2o_server, h2o_proxy=h2o_proxy)
|
||||
env.make_data_file(indir=h2o_server.docs_dir, fname="download-1m", fsize=1 * 1024 * 1024)
|
||||
count = 5
|
||||
curl = CurlClient(env=env)
|
||||
urln = f"https://localhost:{h2o_server.port}/download-1m?[0-{count - 1}]"
|
||||
|
|
@ -475,12 +441,8 @@ class TestH3ProxyDataTransfer:
|
|||
)
|
||||
r.check_response(count=count, http_status=200)
|
||||
|
||||
|
||||
class TestH3ProxyConnectionManagement:
|
||||
"""Proxy authentication, connection reuse, and session resumption."""
|
||||
|
||||
pytestmark = H3_PROXY_COMMON_MARKS + [MARK_NEEDS_H2O]
|
||||
|
||||
@MARK_NEEDS_PROXY_HTTP3
|
||||
@MARK_NEEDS_H2O
|
||||
def test_60_10_proxy_basic_auth(self, env: Env, h2o_server, h2o_proxy):
|
||||
_require_available(h2o_server=h2o_server, h2o_proxy=h2o_proxy)
|
||||
curl = CurlClient(env=env)
|
||||
|
|
@ -493,6 +455,8 @@ class TestH3ProxyConnectionManagement:
|
|||
r.check_response(count=1, http_status=200)
|
||||
_check_download_message(curl, H2O_HELLO_MSG)
|
||||
|
||||
@MARK_NEEDS_PROXY_HTTP3
|
||||
@MARK_NEEDS_H2O
|
||||
def test_60_11_connection_reuse(self, env: Env, h2o_server, h2o_proxy):
|
||||
_require_available(h2o_server=h2o_server, h2o_proxy=h2o_proxy)
|
||||
curl = CurlClient(env=env)
|
||||
|
|
@ -506,6 +470,8 @@ class TestH3ProxyConnectionManagement:
|
|||
f"expected proxy connection reuse, got {r.total_connects} connects"
|
||||
)
|
||||
|
||||
@MARK_NEEDS_PROXY_HTTP3
|
||||
@MARK_NEEDS_H2O
|
||||
@pytest.mark.skipif(condition=not Env.curl_has_feature('SSLS-EXPORT'),
|
||||
reason='curl lacks SSL session export support')
|
||||
def test_60_12_quic_session_resumption(self, env: Env, h2o_server, h2o_proxy):
|
||||
|
|
@ -530,20 +496,9 @@ class TestH3ProxyConnectionManagement:
|
|||
reuses = [line for line in r2.trace_lines if '[SSLS] took session for proxy.http.curl.se' in line]
|
||||
assert len(reuses), f'{r2.dump_logs()}'
|
||||
|
||||
# CONNECT-UDP tunnel payload size and capsule-protocol tests.
|
||||
|
||||
class TestH3ProxyUdpTunnel:
|
||||
"""CONNECT-UDP tunnel payload size and capsule-protocol tests."""
|
||||
|
||||
pytestmark = H3_PROXY_COMMON_MARKS
|
||||
|
||||
@pytest.fixture(autouse=True, scope="class")
|
||||
def _class_scope(self, env, h2o_server):
|
||||
if not env.have_h2o():
|
||||
return
|
||||
env.make_data_file(indir=h2o_server.docs_dir, fname="download-1400", fsize=1400)
|
||||
env.make_data_file(indir=h2o_server.docs_dir, fname="download-1m", fsize=1 * 1024 * 1024)
|
||||
env.make_data_file(indir=h2o_server.docs_dir, fname="download-10m", fsize=10 * 1024 * 1024)
|
||||
|
||||
@MARK_NEEDS_PROXY_HTTP3
|
||||
@MARK_NEEDS_H2O
|
||||
@pytest.mark.parametrize(
|
||||
"fname,fsize",
|
||||
|
|
@ -557,6 +512,7 @@ class TestH3ProxyUdpTunnel:
|
|||
self, env: Env, h2o_server, h2o_proxy, fname, fsize
|
||||
):
|
||||
_require_available(h2o_server=h2o_server, h2o_proxy=h2o_proxy)
|
||||
env.make_data_file(indir=h2o_server.docs_dir, fname=fname, fsize=fsize)
|
||||
curl = CurlClient(env=env)
|
||||
url = f"https://localhost:{h2o_server.port}/{fname}"
|
||||
proxy_args = _h2o_proxy_args(env, h2o_proxy, "h3", tunnel=True)
|
||||
|
|
@ -566,6 +522,7 @@ class TestH3ProxyUdpTunnel:
|
|||
r.check_response(count=1, http_status=200)
|
||||
_check_download_size(curl, fsize)
|
||||
|
||||
@MARK_NEEDS_PROXY_HTTP3
|
||||
@MARK_NEEDS_NGHTTPX
|
||||
def test_60_14_udp_tunnel_capsule_absent(
|
||||
self, env: Env, httpd, nghttpx, nghttpx_fwd
|
||||
|
|
@ -585,12 +542,10 @@ class TestH3ProxyUdpTunnel:
|
|||
"expected failure: nghttpx does not support CONNECT-UDP / Capsule-Protocol"
|
||||
)
|
||||
|
||||
# Timeout and protocol-mismatch edge cases.
|
||||
|
||||
class TestH3ProxyEdgeCases:
|
||||
"""Timeout and protocol-mismatch edge cases."""
|
||||
|
||||
pytestmark = H3_PROXY_COMMON_MARKS + [MARK_NEEDS_H2O]
|
||||
|
||||
#@MARK_NEEDS_PROXY_HTTP3
|
||||
#@MARK_NEEDS_H2O
|
||||
#def test_60_15_connect_timeout(self, env: Env, h2o_proxy):
|
||||
# _require_available(h2o_proxy=h2o_proxy)
|
||||
# curl = CurlClient(env=env, timeout=15)
|
||||
|
|
@ -610,6 +565,8 @@ class TestH3ProxyEdgeCases:
|
|||
# f"timeout not respected: took {r.duration.total_seconds():.1f}s"
|
||||
# )
|
||||
|
||||
@MARK_NEEDS_PROXY_HTTP3
|
||||
@MARK_NEEDS_H2O
|
||||
@MARK_NEEDS_NGHTTP2
|
||||
def test_60_16_h2_uses_connect_tcp_not_udp(self, env: Env, httpd, h2o_proxy):
|
||||
_require_available(httpd=httpd, h2o_proxy=h2o_proxy)
|
||||
|
|
@ -624,18 +581,14 @@ class TestH3ProxyEdgeCases:
|
|||
)
|
||||
r.check_response(count=1, http_status=200)
|
||||
|
||||
# Verify that happy eyeballs is active for HTTP/3 proxy connections.
|
||||
#
|
||||
# With the H3-PROXY filter sitting above HAPPY-EYEBALLS -> UDP, address
|
||||
# family selection to the proxy is done by happy eyeballs.
|
||||
|
||||
class TestH3ProxyHappyEyeballs:
|
||||
"""
|
||||
Verify that happy eyeballs is active for HTTP/3 proxy connections.
|
||||
|
||||
With the H3-PROXY filter sitting above HAPPY-EYEBALLS -> UDP, address
|
||||
family selection to the proxy is done by happy eyeballs.
|
||||
"""
|
||||
|
||||
pytestmark = H3_PROXY_COMMON_MARKS + [MARK_NEEDS_H2O]
|
||||
|
||||
def test_60_17_h3_proxy_happy_eyeballs_filter_present(self, env: Env, h2o_server, h2o_proxy):
|
||||
@MARK_NEEDS_PROXY_HTTP3
|
||||
@MARK_NEEDS_H2O
|
||||
def test_60_17_happy_eyeballs_filter_present(self, env: Env, h2o_server, h2o_proxy):
|
||||
"""Verbose trace confirms HAPPY-EYEBALLS filter is in the H3 proxy chain."""
|
||||
if not env.curl_is_debug():
|
||||
pytest.skip("needs debug curl for filter trace")
|
||||
|
|
@ -651,8 +604,10 @@ class TestH3ProxyHappyEyeballs:
|
|||
f"expected HAPPY-EYEBALLS trace for H3 proxy, got: {r.stderr}"
|
||||
)
|
||||
|
||||
@MARK_NEEDS_PROXY_HTTP3
|
||||
@MARK_NEEDS_H2O
|
||||
@MARK_NEEDS_NGHTTP2
|
||||
def test_60_18_h3_proxy_ipv4_all_proto(self, env: Env, h2o_server, h2o_proxy):
|
||||
def test_60_18_happy_eyeballs_ipv4_all_proto(self, env: Env, h2o_server, h2o_proxy):
|
||||
"""IPv4-forced H3 proxy works for h1/h2/h3 inner protocols."""
|
||||
_require_available(h2o_server=h2o_server, h2o_proxy=h2o_proxy)
|
||||
for alpn_proto in ["http/1.1", "h2", "h3"]:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue