Fix autest compatibility with Fedora 43 / Python 3.14#12857
Fix autest compatibility with Fedora 43 / Python 3.14#12857bryancall wants to merge 5 commits intoapache:masterfrom
Conversation
This commit addresses multiple compatibility issues that cause autests to fail on modern systems like Fedora 43 with Python 3.14 and OpenSSL 3.x: Python 3.14 compatibility: - Fix kwargs.iteritems() -> kwargs.items() in microserver.test.ext - Add missing 'os' import in microserver.test.ext and conditions.test.ext - Add explicit UTF-8 encoding to socket decode() calls - Replace deprecated socket.error with OSError in ports.py - Add socket timeout to prevent recv() from hanging indefinitely TLS protocol deprecation: - Add HasLegacyTLSSupport() condition to detect TLSv1.0/TLSv1.1 support - Skip tls_client_versions tests on systems without legacy TLS support - Modern OpenSSL 3.x disables TLSv1.0/TLSv1.1 by default Timing improvements: - Increase sleep duration in polite_hook_wait.cc from 200ms to 500ms to account for variable scheduling latency on different systems
On Fedora/RHEL systems with crypto-policies, TLSv1.0/TLSv1.1 may be disabled at the system level even though OpenSSL accepts the -tls1 flag. The previous check would incorrectly report TLSv1 as supported because 'openssl ciphers -v -tls1' returns ciphers (TLSv1.2/TLSv1.3) even when TLSv1.0 is disabled. This fix improves detection by parsing the cipher list output and checking if any cipher is specifically marked as TLSv1 or SSLv3 in the version column, rather than just checking if the command succeeds.
The previous check looked for TLSv1 ciphers in 'openssl ciphers' output,
but on Fedora with crypto-policies, TLSv1 ciphers are still listed even
though the protocol is disabled at runtime ("no protocols available").
This fix tests actual TLSv1 protocol availability by attempting an
s_client connection and checking for the "no protocols available" error
that indicates the protocol is blocked by system crypto-policy.
Testing TLSv1 against localhost:1 fails at TCP layer before reaching TLS, so "no protocols available" error is never shown. Need to connect to a real HTTPS server to properly detect crypto-policy restrictions.
There was a problem hiding this comment.
Pull request overview
This PR updates the autest gold-test infrastructure to better tolerate modern platforms (Fedora 43 / Python 3.14 / OpenSSL 3.x), primarily by addressing Python API deprecations, preventing socket hangs, and conditionally skipping legacy-TLS-dependent tests.
Changes:
- Add a new
HasLegacyTLSSupport()condition and use it to gate TLSv1.0/TLSv1.1 client-version tests. - Improve Python 3.14 compatibility in autest extensions (e.g.,
iteritems()removal, missing imports) and harden microserver socket reads (timeout + explicit UTF-8 decode). - Reduce timing flakiness in the polite hook wait plugin test by increasing the sleep delay.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/gold_tests/tls/tls_client_versions.test.py | Skip legacy-TLS tests when legacy TLS is unavailable. |
| tests/gold_tests/tls/tls_client_versions_minmax.test.py | Skip legacy-TLS tests when legacy TLS is unavailable. |
| tests/gold_tests/autest-site/conditions.test.ext | Add HasLegacyTLSSupport() condition for runtime legacy TLS detection. |
| tests/gold_tests/autest-site/microserver.test.ext | Python 3.14 fixes; add socket recv timeout and explicit UTF-8 decode. |
| tests/gold_tests/autest-site/ports.py | Update exception type from deprecated socket.error to OSError. |
| tests/gold_tests/pluginTest/polite_hook_wait/polite_hook_wait.cc | Increase sleep duration to reduce scheduling-related flakes. |
Comments suppressed due to low confidence (1)
tests/gold_tests/autest-site/ports.py:84
PortOpencatchesOSErrorbeforesocket.timeout, butsocket.timeoutis a subclass ofOSError, so theexcept socket.timeoutblock will never run. Swap the order (catchsocket.timeoutfirst) or drop the second handler to avoid dead code and ensure timeout-specific logging works.
except OSError:
host.WriteDebug(
'PortOpen', f"socket error for port {port}, port is closed, "
"and therefore a future connection can use it")
except socket.timeout:
host.WriteDebug(
'PortOpen', f"Timeout error for port {port}, port is closed, "
"and therefore a future connection can use it")
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def HasLegacyTLSSupport(self): | ||
| """Check if the system supports legacy TLS protocols (TLSv1.0 and TLSv1.1). | ||
|
|
||
| Modern OpenSSL 3.x installations often disable these protocols entirely, | ||
| even if the openssl binary still accepts the -tls1 flag and lists TLSv1 ciphers. | ||
|
|
||
| On Fedora/RHEL systems, the crypto-policies framework may disable legacy | ||
| TLS at runtime even when OpenSSL is compiled with support for it. This | ||
| causes 'openssl ciphers -v -tls1' to still list TLSv1 ciphers, but actual | ||
| TLS 1.0 connections will fail with "no protocols available". | ||
|
|
||
| This check attempts to create an actual TLSv1 connection to a known HTTPS | ||
| server to detect if the protocol is truly available at runtime. | ||
| """ | ||
|
|
||
| def check_tls1_support(): | ||
| try: | ||
| # Try to actually use TLSv1 against a real HTTPS server | ||
| # This catches crypto-policy restrictions that aren't visible in cipher listings | ||
| # We use a well-known server that's likely to be reachable | ||
| result = subprocess.run( | ||
| ['openssl', 's_client', '-tls1', '-connect', 'www.google.com:443'], | ||
| capture_output=True, | ||
| text=True, | ||
| timeout=10, | ||
| input='' # Don't wait for input | ||
| ) | ||
| # Combine stdout and stderr for checking | ||
| output = result.stdout + result.stderr | ||
| # If we get "no protocols available", TLSv1 is disabled by policy | ||
| if 'no protocols available' in output: | ||
| return False | ||
| # If we get a successful connection or a server-side TLS version mismatch, | ||
| # it means TLSv1 is enabled on this client | ||
| return True | ||
| except subprocess.TimeoutExpired: | ||
| # If network is slow but TLSv1 was attempted, assume it's available | ||
| return True | ||
| except Exception: | ||
| # If we can't determine, assume TLSv1 is not available (safer) | ||
| return False |
There was a problem hiding this comment.
HasLegacyTLSSupport shells out to openssl s_client against www.google.com:443, which makes the test outcome depend on external network/DNS availability and the remote server’s configuration. In offline/locked-down CI this can lead to long timeouts or incorrect results (especially since TimeoutExpired currently returns True). Prefer an offline/local capability check (e.g., attempt to create a TLSv1/TLSv1.1 context with the ssl module, or run openssl s_client -tls1 against localhost and treat connection-refused as "protocol available" but policy-disabled errors as unsupported), and treat timeouts as "unsupported" to avoid false positives.
| def HasLegacyTLSSupport(self): | ||
| """Check if the system supports legacy TLS protocols (TLSv1.0 and TLSv1.1). | ||
|
|
||
| Modern OpenSSL 3.x installations often disable these protocols entirely, | ||
| even if the openssl binary still accepts the -tls1 flag and lists TLSv1 ciphers. | ||
|
|
||
| On Fedora/RHEL systems, the crypto-policies framework may disable legacy | ||
| TLS at runtime even when OpenSSL is compiled with support for it. This | ||
| causes 'openssl ciphers -v -tls1' to still list TLSv1 ciphers, but actual | ||
| TLS 1.0 connections will fail with "no protocols available". | ||
|
|
||
| This check attempts to create an actual TLSv1 connection to a known HTTPS | ||
| server to detect if the protocol is truly available at runtime. | ||
| """ | ||
|
|
||
| def check_tls1_support(): | ||
| try: | ||
| # Try to actually use TLSv1 against a real HTTPS server | ||
| # This catches crypto-policy restrictions that aren't visible in cipher listings | ||
| # We use a well-known server that's likely to be reachable | ||
| result = subprocess.run( | ||
| ['openssl', 's_client', '-tls1', '-connect', 'www.google.com:443'], | ||
| capture_output=True, | ||
| text=True, | ||
| timeout=10, | ||
| input='' # Don't wait for input | ||
| ) | ||
| # Combine stdout and stderr for checking | ||
| output = result.stdout + result.stderr | ||
| # If we get "no protocols available", TLSv1 is disabled by policy | ||
| if 'no protocols available' in output: | ||
| return False | ||
| # If we get a successful connection or a server-side TLS version mismatch, | ||
| # it means TLSv1 is enabled on this client | ||
| return True |
There was a problem hiding this comment.
This condition claims to check TLSv1.0 and TLSv1.1 support, but it only probes -tls1. Either also probe -tls1_1 (and require both, if that’s what the tests need) or rename/update the messaging so it matches what is actually being detected.
| # for special domain foo.com only offer TLSv1 and TLSv1_1 | ||
|
|
||
| Test.SkipUnless(Condition.HasOpenSSLVersion("1.1.1")) | ||
| Test.SkipUnless(Condition.HasLegacyTLSSupport(), "This test requires TLSv1.0/TLSv1.1 support which is disabled on modern systems") |
There was a problem hiding this comment.
This SkipUnless call passes a second positional string argument. In this repository, Test.SkipUnless(...) is only used with Condition objects (optionally multiple), not a separate reason string (see e.g. tests/README.md:328-331). If SkipUnless doesn’t accept a reason parameter, this will raise at runtime or be treated as an invalid condition. Consider relying on Condition.HasLegacyTLSSupport()’s own failure message (or extend the condition message) instead of passing a raw string here.
| Test.SkipUnless(Condition.HasLegacyTLSSupport(), "This test requires TLSv1.0/TLSv1.1 support which is disabled on modern systems") | |
| Test.SkipUnless(Condition.HasLegacyTLSSupport()) |
| # for special domain foo.com only offer TLSv1 and TLSv1_1 | ||
|
|
||
| Test.SkipUnless(Condition.HasOpenSSLVersion("1.1.1")) | ||
| Test.SkipUnless(Condition.HasLegacyTLSSupport(), "This test requires TLSv1.0/TLSv1.1 support which is disabled on modern systems") |
There was a problem hiding this comment.
This SkipUnless call passes a second positional string argument. In this repository, Test.SkipUnless(...) is only used with Condition objects (optionally multiple), not a separate reason string (see e.g. tests/README.md:328-331). If SkipUnless doesn’t accept a reason parameter, this will raise at runtime or be treated as an invalid condition. Consider relying on Condition.HasLegacyTLSSupport()’s own failure message (or extend the condition message) instead of passing a raw string here.
| Test.SkipUnless(Condition.HasLegacyTLSSupport(), "This test requires TLSv1.0/TLSv1.1 support which is disabled on modern systems") | |
| Test.SkipUnless(Condition.HasLegacyTLSSupport()) |
Summary
This PR fixes multiple autest compatibility issues that cause test failures on modern systems like Fedora 43 with Python 3.14 and OpenSSL 3.x.
Python 3.14 Compatibility Fixes
kwargs.iteritems()→kwargs.items()inmicroserver.test.extosimport inmicroserver.test.extandconditions.test.extdecode()callssocket.errorwithOSErrorinports.pyrecv()from hanging indefinitelyTLS Protocol Deprecation Fixes
HasLegacyTLSSupport()condition to detect TLSv1.0/TLSv1.1 supporttls_client_versionstests on systems without legacy TLS supportTiming Improvements
polite_hook_wait.ccfrom 200ms to 500ms to account for variable scheduling latencyTest Plan
Tested on:
Results: