/Lib/distutils/tests/test_build_ext.py

http://unladen-swallow.googlecode.com/ · Python · 401 lines · 296 code · 55 blank · 50 comment · 22 complexity · d22cf60c913dfc168a3e1133a3d78683 MD5 · raw file

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