test_build_ext.py revision fbf50b8c7c6c1201f6060d0e01bc601c79e57e32
1import sys 2import os 3import tempfile 4import shutil 5from StringIO import StringIO 6 7from distutils.core import Extension, Distribution 8from distutils.command.build_ext import build_ext 9from distutils import sysconfig 10from distutils.tests import support 11from distutils.errors import DistutilsSetupError 12 13import unittest 14from test import test_support 15 16# http://bugs.python.org/issue4373 17# Don't load the xx module more than once. 18ALREADY_TESTED = False 19 20def _get_source_filename(): 21 srcdir = sysconfig.get_config_var('srcdir') 22 xxmodule = os.path.join(srcdir, 'Modules', 'xxmodule.c') 23 if not os.path.exists(xxmodule): 24 # local fallback 25 xxmodule = os.path.join(os.path.dirname(__file__), 'xxmodule.c') 26 return xxmodule 27 28class BuildExtTestCase(support.TempdirManager, 29 support.LoggingSilencer, 30 unittest.TestCase): 31 def setUp(self): 32 # Create a simple test environment 33 # Note that we're making changes to sys.path 34 super(BuildExtTestCase, self).setUp() 35 self.tmp_dir = tempfile.mkdtemp(prefix="pythontest_") 36 self.sys_path = sys.path[:] 37 sys.path.append(self.tmp_dir) 38 shutil.copy(_get_source_filename(), self.tmp_dir) 39 40 def test_build_ext(self): 41 global ALREADY_TESTED 42 xx_c = os.path.join(self.tmp_dir, 'xxmodule.c') 43 xx_ext = Extension('xx', [xx_c]) 44 dist = Distribution({'name': 'xx', 'ext_modules': [xx_ext]}) 45 dist.package_dir = self.tmp_dir 46 cmd = build_ext(dist) 47 if os.name == "nt": 48 # On Windows, we must build a debug version iff running 49 # a debug build of Python 50 cmd.debug = sys.executable.endswith("_d.exe") 51 cmd.build_lib = self.tmp_dir 52 cmd.build_temp = self.tmp_dir 53 54 old_stdout = sys.stdout 55 if not test_support.verbose: 56 # silence compiler output 57 sys.stdout = StringIO() 58 try: 59 cmd.ensure_finalized() 60 cmd.run() 61 finally: 62 sys.stdout = old_stdout 63 64 if ALREADY_TESTED: 65 return 66 else: 67 ALREADY_TESTED = True 68 69 import xx 70 71 for attr in ('error', 'foo', 'new', 'roj'): 72 self.assert_(hasattr(xx, attr)) 73 74 self.assertEquals(xx.foo(2, 5), 7) 75 self.assertEquals(xx.foo(13,15), 28) 76 self.assertEquals(xx.new().demo(), None) 77 doc = 'This is a template module just for instruction.' 78 self.assertEquals(xx.__doc__, doc) 79 self.assert_(isinstance(xx.Null(), xx.Null)) 80 self.assert_(isinstance(xx.Str(), xx.Str)) 81 82 def tearDown(self): 83 # Get everything back to normal 84 test_support.unload('xx') 85 sys.path = self.sys_path 86 # XXX on Windows the test leaves a directory with xx module in TEMP 87 shutil.rmtree(self.tmp_dir, os.name == 'nt' or sys.platform == 'cygwin') 88 super(BuildExtTestCase, self).tearDown() 89 90 def test_solaris_enable_shared(self): 91 dist = Distribution({'name': 'xx'}) 92 cmd = build_ext(dist) 93 old = sys.platform 94 95 sys.platform = 'sunos' # fooling finalize_options 96 from distutils.sysconfig import _config_vars 97 old_var = _config_vars.get('Py_ENABLE_SHARED') 98 _config_vars['Py_ENABLE_SHARED'] = 1 99 try: 100 cmd.ensure_finalized() 101 finally: 102 sys.platform = old 103 if old_var is None: 104 del _config_vars['Py_ENABLE_SHARED'] 105 else: 106 _config_vars['Py_ENABLE_SHARED'] = old_var 107 108 # make sure we get some library dirs under solaris 109 self.assert_(len(cmd.library_dirs) > 0) 110 111 def test_finalize_options(self): 112 # Make sure Python's include directories (for Python.h, pyconfig.h, 113 # etc.) are in the include search path. 114 modules = [Extension('foo', ['xxx'])] 115 dist = Distribution({'name': 'xx', 'ext_modules': modules}) 116 cmd = build_ext(dist) 117 cmd.finalize_options() 118 119 from distutils import sysconfig 120 py_include = sysconfig.get_python_inc() 121 self.assert_(py_include in cmd.include_dirs) 122 123 plat_py_include = sysconfig.get_python_inc(plat_specific=1) 124 self.assert_(plat_py_include in cmd.include_dirs) 125 126 # make sure cmd.libraries is turned into a list 127 # if it's a string 128 cmd = build_ext(dist) 129 cmd.libraries = 'my_lib' 130 cmd.finalize_options() 131 self.assertEquals(cmd.libraries, ['my_lib']) 132 133 # make sure cmd.library_dirs is turned into a list 134 # if it's a string 135 cmd = build_ext(dist) 136 cmd.library_dirs = 'my_lib_dir' 137 cmd.finalize_options() 138 self.assert_('my_lib_dir' in cmd.library_dirs) 139 140 # make sure rpath is turned into a list 141 # if it's a list of os.pathsep's paths 142 cmd = build_ext(dist) 143 cmd.rpath = os.pathsep.join(['one', 'two']) 144 cmd.finalize_options() 145 self.assertEquals(cmd.rpath, ['one', 'two']) 146 147 # XXX more tests to perform for win32 148 149 # make sure define is turned into 2-tuples 150 # strings if they are ','-separated strings 151 cmd = build_ext(dist) 152 cmd.define = 'one,two' 153 cmd.finalize_options() 154 self.assertEquals(cmd.define, [('one', '1'), ('two', '1')]) 155 156 # make sure undef is turned into a list of 157 # strings if they are ','-separated strings 158 cmd = build_ext(dist) 159 cmd.undef = 'one,two' 160 cmd.finalize_options() 161 self.assertEquals(cmd.undef, ['one', 'two']) 162 163 # make sure swig_opts is turned into a list 164 cmd = build_ext(dist) 165 cmd.swig_opts = None 166 cmd.finalize_options() 167 self.assertEquals(cmd.swig_opts, []) 168 169 cmd = build_ext(dist) 170 cmd.swig_opts = '1 2' 171 cmd.finalize_options() 172 self.assertEquals(cmd.swig_opts, ['1', '2']) 173 174 def test_check_extensions_list(self): 175 dist = Distribution() 176 cmd = build_ext(dist) 177 cmd.finalize_options() 178 179 #'extensions' option must be a list of Extension instances 180 self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, 'foo') 181 182 # each element of 'ext_modules' option must be an 183 # Extension instance or 2-tuple 184 exts = [('bar', 'foo', 'bar'), 'foo'] 185 self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, exts) 186 187 # first element of each tuple in 'ext_modules' 188 # must be the extension name (a string) and match 189 # a python dotted-separated name 190 exts = [('foo-bar', '')] 191 self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, exts) 192 193 # second element of each tuple in 'ext_modules' 194 # must be a ary (build info) 195 exts = [('foo.bar', '')] 196 self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, exts) 197 198 # ok this one should pass 199 exts = [('foo.bar', {'sources': [''], 'libraries': 'foo', 200 'some': 'bar'})] 201 cmd.check_extensions_list(exts) 202 ext = exts[0] 203 self.assert_(isinstance(ext, Extension)) 204 205 # check_extensions_list adds in ext the values passed 206 # when they are in ('include_dirs', 'library_dirs', 'libraries' 207 # 'extra_objects', 'extra_compile_args', 'extra_link_args') 208 self.assertEquals(ext.libraries, 'foo') 209 self.assert_(not hasattr(ext, 'some')) 210 211 # 'macros' element of build info dict must be 1- or 2-tuple 212 exts = [('foo.bar', {'sources': [''], 'libraries': 'foo', 213 'some': 'bar', 'macros': [('1', '2', '3'), 'foo']})] 214 self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, exts) 215 216 exts[0][1]['macros'] = [('1', '2'), ('3',)] 217 cmd.check_extensions_list(exts) 218 self.assertEquals(exts[0].undef_macros, ['3']) 219 self.assertEquals(exts[0].define_macros, [('1', '2')]) 220 221 def test_get_source_files(self): 222 modules = [Extension('foo', ['xxx'])] 223 dist = Distribution({'name': 'xx', 'ext_modules': modules}) 224 cmd = build_ext(dist) 225 cmd.ensure_finalized() 226 self.assertEquals(cmd.get_source_files(), ['xxx']) 227 228 def test_compiler_option(self): 229 # cmd.compiler is an option and 230 # should not be overriden by a compiler instance 231 # when the command is run 232 dist = Distribution() 233 cmd = build_ext(dist) 234 cmd.compiler = 'unix' 235 cmd.ensure_finalized() 236 cmd.run() 237 self.assertEquals(cmd.compiler, 'unix') 238 239 def test_get_outputs(self): 240 tmp_dir = self.mkdtemp() 241 c_file = os.path.join(tmp_dir, 'foo.c') 242 self.write_file(c_file, 'void initfoo(void) {};\n') 243 ext = Extension('foo', [c_file]) 244 dist = Distribution({'name': 'xx', 245 'ext_modules': [ext]}) 246 cmd = build_ext(dist) 247 cmd.ensure_finalized() 248 self.assertEquals(len(cmd.get_outputs()), 1) 249 250 if os.name == "nt": 251 cmd.debug = sys.executable.endswith("_d.exe") 252 253 cmd.build_lib = os.path.join(self.tmp_dir, 'build') 254 cmd.build_temp = os.path.join(self.tmp_dir, 'tempt') 255 256 # issue #5977 : distutils build_ext.get_outputs 257 # returns wrong result with --inplace 258 other_tmp_dir = os.path.realpath(self.mkdtemp()) 259 old_wd = os.getcwd() 260 os.chdir(other_tmp_dir) 261 try: 262 cmd.inplace = 1 263 cmd.run() 264 so_file = cmd.get_outputs()[0] 265 finally: 266 os.chdir(old_wd) 267 self.assert_(os.path.exists(so_file)) 268 self.assertEquals(os.path.splitext(so_file)[-1], 269 sysconfig.get_config_var('SO')) 270 so_dir = os.path.dirname(so_file) 271 self.assertEquals(so_dir, other_tmp_dir) 272 cmd.compiler = None 273 cmd.inplace = 0 274 cmd.run() 275 so_file = cmd.get_outputs()[0] 276 self.assert_(os.path.exists(so_file)) 277 self.assertEquals(os.path.splitext(so_file)[-1], 278 sysconfig.get_config_var('SO')) 279 so_dir = os.path.dirname(so_file) 280 self.assertEquals(so_dir, cmd.build_lib) 281 282 # inplace = 0, cmd.package = 'bar' 283 build_py = cmd.get_finalized_command('build_py') 284 build_py.package_dir = {'': 'bar'} 285 path = cmd.get_ext_fullpath('foo') 286 # checking that the last directory is the build_dir 287 path = os.path.split(path)[0] 288 self.assertEquals(path, cmd.build_lib) 289 290 # inplace = 1, cmd.package = 'bar' 291 cmd.inplace = 1 292 other_tmp_dir = os.path.realpath(self.mkdtemp()) 293 old_wd = os.getcwd() 294 os.chdir(other_tmp_dir) 295 try: 296 path = cmd.get_ext_fullpath('foo') 297 finally: 298 os.chdir(old_wd) 299 # checking that the last directory is bar 300 path = os.path.split(path)[0] 301 lastdir = os.path.split(path)[-1] 302 self.assertEquals(lastdir, 'bar') 303 304 def test_ext_fullpath(self): 305 ext = sysconfig.get_config_vars()['SO'] 306 dist = Distribution() 307 cmd = build_ext(dist) 308 cmd.inplace = 1 309 cmd.distribution.package_dir = {'': 'src'} 310 cmd.distribution.packages = ['lxml', 'lxml.html'] 311 curdir = os.getcwd() 312 wanted = os.path.join(curdir, 'src', 'lxml', 'etree' + ext) 313 path = cmd.get_ext_fullpath('lxml.etree') 314 self.assertEquals(wanted, path) 315 316 # building lxml.etree not inplace 317 cmd.inplace = 0 318 cmd.build_lib = os.path.join(curdir, 'tmpdir') 319 wanted = os.path.join(curdir, 'tmpdir', 'lxml', 'etree' + ext) 320 path = cmd.get_ext_fullpath('lxml.etree') 321 self.assertEquals(wanted, path) 322 323 # building twisted.runner.portmap not inplace 324 build_py = cmd.get_finalized_command('build_py') 325 build_py.package_dir = {} 326 cmd.distribution.packages = ['twisted', 'twisted.runner.portmap'] 327 path = cmd.get_ext_fullpath('twisted.runner.portmap') 328 wanted = os.path.join(curdir, 'tmpdir', 'twisted', 'runner', 329 'portmap' + ext) 330 self.assertEquals(wanted, path) 331 332 # building twisted.runner.portmap inplace 333 cmd.inplace = 1 334 path = cmd.get_ext_fullpath('twisted.runner.portmap') 335 wanted = os.path.join(curdir, 'twisted', 'runner', 'portmap' + ext) 336 self.assertEquals(wanted, path) 337 338 def test_build_ext_inplace(self): 339 etree_c = os.path.join(self.tmp_dir, 'lxml.etree.c') 340 etree_ext = Extension('lxml.etree', [etree_c]) 341 dist = Distribution({'name': 'lxml', 'ext_modules': [etree_ext]}) 342 cmd = build_ext(dist) 343 cmd.ensure_finalized() 344 cmd.inplace = 1 345 cmd.distribution.package_dir = {'': 'src'} 346 cmd.distribution.packages = ['lxml', 'lxml.html'] 347 curdir = os.getcwd() 348 ext = sysconfig.get_config_var("SO") 349 wanted = os.path.join(curdir, 'src', 'lxml', 'etree' + ext) 350 path = cmd.get_ext_fullpath('lxml.etree') 351 self.assertEquals(wanted, path) 352 353 def test_setuptools_compat(self): 354 from setuptools_build_ext import build_ext as setuptools_build_ext 355 from setuptools_extension import Extension 356 357 etree_c = os.path.join(self.tmp_dir, 'lxml.etree.c') 358 etree_ext = Extension('lxml.etree', [etree_c]) 359 dist = Distribution({'name': 'lxml', 'ext_modules': [etree_ext]}) 360 cmd = setuptools_build_ext(dist) 361 cmd.ensure_finalized() 362 cmd.inplace = 1 363 cmd.distribution.package_dir = {'': 'src'} 364 cmd.distribution.packages = ['lxml', 'lxml.html'] 365 curdir = os.getcwd() 366 ext = sysconfig.get_config_var("SO") 367 wanted = os.path.join(curdir, 'src', 'lxml', 'etree' + ext) 368 path = cmd.get_ext_fullpath('lxml.etree') 369 self.assertEquals(wanted, path) 370 371 def test_build_ext_path_with_os_sep(self): 372 dist = Distribution({'name': 'UpdateManager'}) 373 cmd = build_ext(dist) 374 cmd.ensure_finalized() 375 ext = sysconfig.get_config_var("SO") 376 ext_name = os.path.join('UpdateManager', 'fdsend') 377 ext_path = cmd.get_ext_fullpath(ext_name) 378 wanted = os.path.join(cmd.build_lib, 'UpdateManager', 'fdsend' + ext) 379 self.assertEquals(ext_path, wanted) 380 381 def test_build_ext_path_cross_platform(self): 382 if sys.platform != 'win32': 383 return 384 dist = Distribution({'name': 'UpdateManager'}) 385 cmd = build_ext(dist) 386 cmd.ensure_finalized() 387 ext = sysconfig.get_config_var("SO") 388 # this needs to work even under win32 389 ext_name = 'UpdateManager/fdsend' 390 ext_path = cmd.get_ext_fullpath(ext_name) 391 wanted = os.path.join(cmd.build_lib, 'UpdateManager', 'fdsend' + ext) 392 self.assertEquals(ext_path, wanted) 393 394def test_suite(): 395 src = _get_source_filename() 396 if not os.path.exists(src): 397 if test_support.verbose: 398 print ('test_build_ext: Cannot find source code (test' 399 ' must run in python build dir)') 400 return unittest.TestSuite() 401 else: return unittest.makeSuite(BuildExtTestCase) 402 403if __name__ == '__main__': 404 test_support.run_unittest(test_suite()) 405