Skip to content

Commit a5b95bf

Browse files
committed
ci: fix locale, urllib and GDB tests on Leap
Regenerate configure and pyconfig.h.in before the full build so locale coercion is enabled. Install glibc-locale-base for the UTF-32 converters required by GDB. Replace the public HTTP endpoint in test_issue16464 with a mocked transport while retaining request and Content-Length checks. Include full GDB command, status and output in failures, with regression coverage for the diagnostics.
1 parent be37077 commit a5b95bf

4 files changed

Lines changed: 80 additions & 26 deletions

File tree

‎.github/posix-deps-zypp.sh‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ zypper --non-interactive refresh
88
# Install build tools explicitly: build patterns pull in a full base system,
99
# including packages that conflict with the container's busybox replacements.
1010
# Python 3.6 needs OpenSSL 1.1 headers rather than Leap's default OpenSSL 3.
11+
# GDB needs the UTF-32 converters supplied by glibc-locale-base.
1112
zypper --non-interactive install --auto-agree-with-licenses \
1213
--allow-downgrade --no-recommends \
1314
autoconf automake gcc gcc-c++ make patch pkg-config python3 \
14-
binutils diffutils findutils gdb gzip libtool perl tar which \
15+
binutils diffutils findutils gdb glibc-locale-base gzip libtool perl tar which \
1516
libabigail-tools xorg-x11-server-Xvfb xvfb-run \
1617
cantarell-fonts google-droid-fonts google-inconsolata-fonts dejavu-fonts \
1718
libffi-devel \

‎.github/workflows/build.yml‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,10 @@ jobs:
111111

112112
- name: Configure CPython
113113
if: matrix.task == 'build-and-test'
114-
run: ./configure --with-pydebug --with-openssl=$PWD/multissl/openssl/$OPENSSL_VER
114+
run: |
115+
autoconf -Wall
116+
autoheader -Wall
117+
./configure --with-pydebug --with-openssl=$PWD/multissl/openssl/$OPENSSL_VER
115118
116119
- name: Build CPython
117120
if: matrix.task == 'build-and-test'

‎Lib/test/test_gdb.py‎

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import sysconfig
1313
import textwrap
1414
import unittest
15+
from unittest import mock
1516

1617
# Is this Python configured to support threads?
1718
try:
@@ -85,7 +86,8 @@ def cet_protection():
8586
def run_gdb(*args, **env_vars):
8687
"""Runs gdb in --batch mode with the additional arguments given by *args.
8788
88-
Returns its (stdout, stderr) decoded from utf-8 using the replace handler.
89+
Returns a CompletedProcess with stdout and stderr decoded from utf-8
90+
using the replace handler.
8991
"""
9092
if env_vars:
9193
env = os.environ.copy()
@@ -97,7 +99,8 @@ def run_gdb(*args, **env_vars):
9799
base_cmd = ('gdb', '--batch', '-nx')
98100
if (gdb_major_version, gdb_minor_version) >= (7, 4):
99101
base_cmd += ('-iex', 'add-auto-load-safe-path ' + checkout_hook_path)
100-
proc = subprocess.Popen(base_cmd + args,
102+
command = base_cmd + args
103+
proc = subprocess.Popen(command,
101104
# Redirect stdin to prevent GDB from messing with
102105
# the terminal settings
103106
stdin=subprocess.PIPE,
@@ -106,23 +109,25 @@ def run_gdb(*args, **env_vars):
106109
env=env)
107110
with proc:
108111
out, err = proc.communicate()
109-
return out.decode('utf-8', 'replace'), err.decode('utf-8', 'replace')
112+
return subprocess.CompletedProcess(command, proc.returncode,
113+
out.decode('utf-8', 'replace'),
114+
err.decode('utf-8', 'replace'))
110115

111116
# Verify that "gdb" was built with the embedded python support enabled:
112-
gdbpy_version, _ = run_gdb("--eval-command=python import sys; print(sys.version_info)")
117+
gdbpy_version = run_gdb("--eval-command=python import sys; print(sys.version_info)").stdout
113118
if not gdbpy_version:
114119
raise unittest.SkipTest("gdb not built with embedded python support")
115120

116121
# Verify that "gdb" can load our custom hooks, as OS security settings may
117122
# disallow this without a customized .gdbinit.
118-
_, gdbpy_errors = run_gdb('--args', sys.executable)
123+
gdbpy_errors = run_gdb('--args', sys.executable).stderr
119124
if "auto-loading has been declined" in gdbpy_errors:
120125
msg = "gdb security settings prevent use of custom hooks: "
121126
raise unittest.SkipTest(msg + gdbpy_errors.rstrip())
122127

123128
def gdb_has_frame_select():
124129
# Does this build of gdb have gdb.Frame.select ?
125-
stdout, _ = run_gdb("--eval-command=python print(dir(gdb.Frame))")
130+
stdout = run_gdb("--eval-command=python print(dir(gdb.Frame))").stdout
126131
m = re.match(r'.*\[(.*)\].*', stdout)
127132
if not m:
128133
raise unittest.SkipTest("Unable to parse output from gdb.Frame.select test")
@@ -221,7 +226,8 @@ def get_stack_trace(self, source=None, script=None,
221226
# print (' '.join(args))
222227

223228
# Use "args" to invoke gdb, capturing stdout, stderr:
224-
out, err = run_gdb(*args, PYTHONHASHSEED=PYTHONHASHSEED)
229+
result = run_gdb(*args, PYTHONHASHSEED=PYTHONHASHSEED)
230+
out, err = result.stdout, result.stderr
225231

226232
errlines = err.splitlines()
227233
unexpected_errlines = []
@@ -252,8 +258,11 @@ def get_stack_trace(self, source=None, script=None,
252258
if not line.startswith(ignore_patterns):
253259
unexpected_errlines.append(line)
254260

255-
# Ensure no unexpected error messages:
256-
self.assertEqual(unexpected_errlines, [])
261+
# Keep the full diagnostic, including warning lines filtered above.
262+
diagnostic = ('GDB command: {!r}\nGDB exit status: {}\n'
263+
'GDB stdout:\n{}\nGDB stderr:\n{}').format(
264+
result.args, result.returncode, out, err)
265+
self.assertEqual(unexpected_errlines, [], diagnostic)
257266
return out
258267

259268
def get_gdb_repr(self, source,
@@ -964,12 +973,43 @@ def test_locals_after_up(self):
964973
self.assertMultilineMatches(bt,
965974
r".*\na = 1\nb = 2\nc = 3\n.*")
966975

976+
class GdbInvocationTests(unittest.TestCase):
977+
def test_run_gdb_preserves_process_details(self):
978+
with mock.patch.object(subprocess, 'Popen') as popen:
979+
proc = popen.return_value
980+
proc.communicate.return_value = (b'output\xff', b'warning: detail\xfe')
981+
proc.returncode = 7
982+
result = run_gdb('--eval-command=quit')
983+
self.assertEqual(result.args, popen.call_args[0][0])
984+
self.assertIn('--eval-command=quit', result.args)
985+
self.assertEqual(result.returncode, 7)
986+
self.assertEqual(result.stdout, 'output\ufffd')
987+
self.assertEqual(result.stderr, 'warning: detail\ufffd')
988+
989+
def test_failure_includes_unfiltered_output(self):
990+
stderr = ('warning: original diagnostic\n'
991+
'This normally should not happen, please file a bug report.\n')
992+
for returncode in (0, 7):
993+
with self.subTest(returncode=returncode):
994+
result = subprocess.CompletedProcess(
995+
('gdb', '--batch'), returncode, 'raw backtrace\n', stderr)
996+
with mock.patch(__name__ + '.run_gdb', return_value=result):
997+
with self.assertRaises(AssertionError) as cm:
998+
DebuggerTests().get_stack_trace(source='id(42)')
999+
message = str(cm.exception)
1000+
self.assertIn('GDB command: {!r}'.format(result.args), message)
1001+
self.assertIn('GDB exit status: {}'.format(returncode), message)
1002+
self.assertIn('GDB stdout:\n' + result.stdout, message)
1003+
self.assertIn('GDB stderr:\n' + stderr, message)
1004+
1005+
9671006
def test_main():
9681007
if support.verbose:
9691008
print("GDB version %s.%s:" % (gdb_major_version, gdb_minor_version))
9701009
for line in gdb_version.splitlines():
9711010
print(" " * 4 + line)
972-
run_unittest(PrettyPrintTests,
1011+
run_unittest(GdbInvocationTests,
1012+
PrettyPrintTests,
9731013
PyListTests,
9741014
StackNavigationTests,
9751015
PyBtTests,

‎Lib/test/test_urllib2.py‎

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1772,21 +1772,31 @@ class MyOtherHTTPHandler(urllib.request.HTTPHandler):
17721772
self.opener_has_handler(o, MyHTTPHandler)
17731773
self.opener_has_handler(o, MyOtherHTTPHandler)
17741774

1775-
@unittest.skipUnless(support.is_resource_enabled('network'),
1776-
'test requires network access')
17771775
def test_issue16464(self):
1778-
with support.transient_internet("http://www.example.com/"):
1779-
opener = urllib.request.build_opener()
1780-
request = urllib.request.Request("http://www.example.com/")
1781-
self.assertEqual(None, request.data)
1782-
1783-
opener.open(request, "1".encode("us-ascii"))
1784-
self.assertEqual(b"1", request.data)
1785-
self.assertEqual("1", request.get_header("Content-length"))
1786-
1787-
opener.open(request, "1234567890".encode("us-ascii"))
1788-
self.assertEqual(b"1234567890", request.data)
1789-
self.assertEqual("10", request.get_header("Content-length"))
1776+
# Reusing a Request must update its body and Content-Length. Keep the
1777+
# real opener and request processing, but replace the HTTP transport.
1778+
connection = MockHTTPClass()
1779+
1780+
class TestHTTPHandler(urllib.request.HTTPHandler):
1781+
def http_open(self, request):
1782+
return self.do_open(connection, request)
1783+
1784+
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}),
1785+
TestHTTPHandler())
1786+
request = urllib.request.Request("http://www.example.com/")
1787+
self.assertIsNone(request.data)
1788+
for data in (b"1", b"1234567890"):
1789+
with self.subTest(data=data):
1790+
connection.req_headers = []
1791+
with opener.open(request, data) as response:
1792+
self.assertEqual(response.code, 200)
1793+
self.assertEqual(request.data, data)
1794+
self.assertEqual(request.get_header("Content-length"),
1795+
str(len(data)))
1796+
self.assertEqual(connection.method, "POST")
1797+
self.assertEqual(connection.data, data)
1798+
self.assertEqual(dict(connection.req_headers)["Content-Length"],
1799+
str(len(data)))
17901800

17911801
def test_HTTPError_interface(self):
17921802
"""

0 commit comments

Comments
 (0)