Skip to content

Fix autest compatibility with Fedora 43 / Python 3.14#12857

Open
bryancall wants to merge 5 commits intoapache:masterfrom
bryancall:fix-autest-fedora43
Open

Fix autest compatibility with Fedora 43 / Python 3.14#12857
bryancall wants to merge 5 commits intoapache:masterfrom
bryancall:fix-autest-fedora43

Conversation

@bryancall
Copy link
Contributor

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

  • 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 Fixes

  • 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 + Fedora crypto-policies disable TLSv1.0/TLSv1.1 by default
  • Detection uses actual TLS connection test to handle crypto-policy restrictions

Timing Improvements

  • Increase sleep duration in polite_hook_wait.cc from 200ms to 500ms to account for variable scheduling latency

Test Plan

Tested on:

  • Fedora 43 (Server Edition)
  • Python 3.14.2
  • GCC 15.2.1
  • OpenSSL 3.x with DEFAULT crypto-policy

Results:

  • TLS client_versions tests now properly skip instead of failing
  • Core autest functionality works correctly
  • No regressions observed

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.
@bryancall bryancall self-assigned this Feb 4, 2026
@bryancall bryancall added this to the 10.2.0 milestone Feb 4, 2026
@bryancall bryancall requested a review from Copilot February 4, 2026 18:36
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  • PortOpen catches OSError before socket.timeout, but socket.timeout is a subclass of OSError, so the except socket.timeout block will never run. Swap the order (catch socket.timeout first) 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.

Comment on lines +55 to +95
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
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +55 to +89
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
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
# 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")
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
Test.SkipUnless(Condition.HasLegacyTLSSupport(), "This test requires TLSv1.0/TLSv1.1 support which is disabled on modern systems")
Test.SkipUnless(Condition.HasLegacyTLSSupport())

Copilot uses AI. Check for mistakes.
# 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")
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
Test.SkipUnless(Condition.HasLegacyTLSSupport(), "This test requires TLSv1.0/TLSv1.1 support which is disabled on modern systems")
Test.SkipUnless(Condition.HasLegacyTLSSupport())

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant