1+ import _ctypes
2+
13import os
24import platform
3- import re
4- import sys
5+ import subprocess
6+ import tempfile
57import test .support
68import unittest
9+ from ctypes import CDLL , c_int
710
811
912FOO_C = r"""
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()' )
3344class 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()' )
127119class 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
148187if __name__ == "__main__" :
0 commit comments