Skip to content

Commit ce8ae02

Browse files
committed
improve test coverage
1 parent 6798f21 commit ce8ae02

File tree

1 file changed

+80
-41
lines changed

1 file changed

+80
-41
lines changed

Lib/test/test_ctypes/test_dlerror.py

Lines changed: 80 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
import _ctypes
2+
13
import os
24
import platform
3-
import re
4-
import sys
5+
import subprocess
6+
import tempfile
57
import test.support
68
import unittest
9+
from ctypes import CDLL, c_int
710

811

912
FOO_C = r"""
@@ -28,8 +31,16 @@
2831
"""
2932

3033

31-
@unittest.skipUnless(sys.platform.startswith('linux'),
32-
'Test only valid for Linux')
34+
def has_gcc():
35+
return subprocess.call(["gcc", "--version"],
36+
stdout=subprocess.DEVNULL,
37+
stderr=subprocess.DEVNULL) == 0
38+
39+
40+
@unittest.skipUnless(hasattr(_ctypes, 'dlopen'),
41+
'test requires _ctypes.dlopen()')
42+
@unittest.skipUnless(hasattr(_ctypes, 'dlsym'),
43+
'test requires _ctypes.dlsym()')
3344
class TestNullDlsym(unittest.TestCase):
3445
"""GH-126554: Ensure that we catch NULL dlsym return values
3546
@@ -53,21 +64,7 @@ class TestNullDlsym(unittest.TestCase):
5364
"""
5465

5566
def test_null_dlsym(self):
56-
import subprocess
57-
import tempfile
58-
59-
# To avoid ImportErrors on Windows, where _ctypes does not have
60-
# dlopen and dlsym,
61-
# import here, i.e., inside the test function.
62-
# The skipUnless('linux') decorator ensures that we're on linux
63-
# if we're executing these statements.
64-
from ctypes import CDLL, c_int
65-
from _ctypes import dlopen, dlsym
66-
67-
retcode = subprocess.call(["gcc", "--version"],
68-
stdout=subprocess.DEVNULL,
69-
stderr=subprocess.DEVNULL)
70-
if retcode != 0:
67+
if not has_gcc():
7168
self.skipTest("gcc is missing")
7269

7370
pipe_r, pipe_w = os.pipe()
@@ -114,35 +111,77 @@ def test_null_dlsym(self):
114111
self.assertEqual(os.read(pipe_r, 2), b'OK')
115112

116113
# Case #3: Test 'py_dl_sym' from Modules/_ctypes/callproc.c
117-
L = dlopen(dstname)
114+
L = _ctypes.dlopen(dstname)
118115
with self.assertRaisesRegex(OSError, "symbol 'foo' not found"):
119-
dlsym(L, "foo")
120-
121-
# Assert that the IFUNC was called
122-
self.assertEqual(os.read(pipe_r, 2), b'OK')
116+
_ctypes.dlsym(L, "foo")
123117

124118

125-
@unittest.skipUnless(sys.platform.startswith('linux'),
126-
'test requires _ctypes.dlopen()')
127119
class TestLinuxLocalization(unittest.TestCase):
128120

129-
@test.support.run_with_locale(
130-
'LC_ALL',
131-
'fr_FR.iso88591', 'ja_JP.sjis', 'zh_CN.gbk',
132-
'',
133-
)
134-
def test_localized_error(self):
135-
# An ImportError would be propagated and would be unexpected on Linux.
136-
from _ctypes import dlopen
137-
121+
@staticmethod
122+
def configure_locales(func):
123+
return test.support.run_with_locale(
124+
'LC_ALL',
125+
'fr_FR.iso88591', 'ja_JP.sjis', 'zh_CN.gbk',
126+
'fr_FR.utf8', 'en_US.utf8',
127+
'',
128+
)(func)
129+
130+
@classmethod
131+
def setUpClass(cls):
132+
if not has_gcc():
133+
raise unittest.SkipTest("gcc is missing")
134+
135+
def make_libfoo(self, outdir, source, so_libname):
136+
srcname = os.path.join(outdir, 'source.c')
137+
dstname = os.path.join(outdir, so_libname)
138+
with open(srcname, 'w') as f:
139+
f.write(source)
140+
args = ['gcc', '-fPIC', '-shared', '-o', dstname, srcname]
141+
p = subprocess.run(args, capture_output=True)
142+
p.check_returncode()
143+
return dstname
144+
145+
@configure_locales
146+
def test_localized_error_from_dll(self):
147+
with tempfile.TemporaryDirectory() as outdir:
148+
dstname = self.make_libfoo(outdir, 'int x = 0;', 'test_in_dll.so')
149+
dll = CDLL(dstname)
150+
with self.assertRaisesRegex(AttributeError, r'test_in_dll\.so:.+'):
151+
dll.foo
152+
153+
@configure_locales
154+
def test_localized_error_in_dll(self):
155+
with tempfile.TemporaryDirectory() as outdir:
156+
dstname = self.make_libfoo(outdir, 'int x = 0;', 'test_in_dll.so')
157+
dll = CDLL(dstname)
158+
with self.assertRaisesRegex(ValueError, r'test_in_dll\.so:.+'):
159+
c_int.in_dll(dll, 'foo')
160+
161+
@unittest.skipUnless(hasattr(_ctypes, 'dlopen'),
162+
'test requires _ctypes.dlopen()')
163+
@configure_locales
164+
def test_localized_error_dlopen(self):
138165
missing_filename = b'missing\xff.so'
139-
# Depending whether the locale is ISO-88591 or not, we may either
140-
# encode '\xff' as '\udcff' or 'ÿ', but we are only interested in
141-
# avoiding a UnicodeDecodeError when reporting the dlerror() error
142-
# message which contains the localized filename.
143-
filename_pattern = r'missing[ÿ|\udcff].so:.+'
166+
# Depending whether the locale, we may encode '\xff' differently
167+
# but we are only interested in avoiding a UnicodeDecodeError
168+
# when reporting the dlerror() error message which contains
169+
# the localized filename.
170+
filename_pattern = r'missing.*?\.so:.+'
144171
with self.assertRaisesRegex(OSError, filename_pattern):
145-
dlopen(missing_filename, 2)
172+
_ctypes.dlopen(missing_filename, 2)
173+
174+
@unittest.skipUnless(hasattr(_ctypes, 'dlopen'),
175+
'test requires _ctypes.dlopen()')
176+
@unittest.skipUnless(hasattr(_ctypes, 'dlsym'),
177+
'test requires _ctypes.dlsym()')
178+
@configure_locales
179+
def test_localized_error_dlsym(self):
180+
with tempfile.TemporaryDirectory() as outdir:
181+
dstname = self.make_libfoo(outdir, 'int x = 0;', 'test_dlsym.so')
182+
dll = _ctypes.dlopen(dstname)
183+
with self.assertRaisesRegex(OSError, r'test_dlsym\.so:.+'):
184+
_ctypes.dlsym(dll, 'foo')
146185

147186

148187
if __name__ == "__main__":

0 commit comments

Comments
 (0)