ยปCore Development>Code coverage>Lib/distutils/tests/test_build_ext.py

Python code coverage for Lib/distutils/tests/test_build_ext.py

#countcontent
1n/aimport sys
2n/aimport os
3n/afrom io import StringIO
4n/aimport textwrap
5n/a
6n/afrom distutils.core import Distribution
7n/afrom distutils.command.build_ext import build_ext
8n/afrom distutils import sysconfig
9n/afrom distutils.tests.support import (TempdirManager, LoggingSilencer,
10n/a copy_xxmodule_c, fixup_build_ext)
11n/afrom distutils.extension import Extension
12n/afrom distutils.errors import (
13n/a CompileError, DistutilsPlatformError, DistutilsSetupError,
14n/a UnknownFileError)
15n/a
16n/aimport unittest
17n/afrom test import support
18n/a
19n/a# http://bugs.python.org/issue4373
20n/a# Don't load the xx module more than once.
21n/aALREADY_TESTED = False
22n/a
23n/a
24n/aclass BuildExtTestCase(TempdirManager,
25n/a LoggingSilencer,
26n/a unittest.TestCase):
27n/a def setUp(self):
28n/a # Create a simple test environment
29n/a # Note that we're making changes to sys.path
30n/a super(BuildExtTestCase, self).setUp()
31n/a self.tmp_dir = self.mkdtemp()
32n/a self.sys_path = sys.path, sys.path[:]
33n/a sys.path.append(self.tmp_dir)
34n/a import site
35n/a self.old_user_base = site.USER_BASE
36n/a site.USER_BASE = self.mkdtemp()
37n/a from distutils.command import build_ext
38n/a build_ext.USER_BASE = site.USER_BASE
39n/a
40n/a def build_ext(self, *args, **kwargs):
41n/a return build_ext(*args, **kwargs)
42n/a
43n/a def test_build_ext(self):
44n/a cmd = support.missing_compiler_executable()
45n/a if cmd is not None:
46n/a self.skipTest('The %r command is not found' % cmd)
47n/a global ALREADY_TESTED
48n/a copy_xxmodule_c(self.tmp_dir)
49n/a xx_c = os.path.join(self.tmp_dir, 'xxmodule.c')
50n/a xx_ext = Extension('xx', [xx_c])
51n/a dist = Distribution({'name': 'xx', 'ext_modules': [xx_ext]})
52n/a dist.package_dir = self.tmp_dir
53n/a cmd = self.build_ext(dist)
54n/a fixup_build_ext(cmd)
55n/a cmd.build_lib = self.tmp_dir
56n/a cmd.build_temp = self.tmp_dir
57n/a
58n/a old_stdout = sys.stdout
59n/a if not support.verbose:
60n/a # silence compiler output
61n/a sys.stdout = StringIO()
62n/a try:
63n/a cmd.ensure_finalized()
64n/a cmd.run()
65n/a finally:
66n/a sys.stdout = old_stdout
67n/a
68n/a if ALREADY_TESTED:
69n/a self.skipTest('Already tested in %s' % ALREADY_TESTED)
70n/a else:
71n/a ALREADY_TESTED = type(self).__name__
72n/a
73n/a import xx
74n/a
75n/a for attr in ('error', 'foo', 'new', 'roj'):
76n/a self.assertTrue(hasattr(xx, attr))
77n/a
78n/a self.assertEqual(xx.foo(2, 5), 7)
79n/a self.assertEqual(xx.foo(13,15), 28)
80n/a self.assertEqual(xx.new().demo(), None)
81n/a if support.HAVE_DOCSTRINGS:
82n/a doc = 'This is a template module just for instruction.'
83n/a self.assertEqual(xx.__doc__, doc)
84n/a self.assertIsInstance(xx.Null(), xx.Null)
85n/a self.assertIsInstance(xx.Str(), xx.Str)
86n/a
87n/a def tearDown(self):
88n/a # Get everything back to normal
89n/a support.unload('xx')
90n/a sys.path = self.sys_path[0]
91n/a sys.path[:] = self.sys_path[1]
92n/a import site
93n/a site.USER_BASE = self.old_user_base
94n/a from distutils.command import build_ext
95n/a build_ext.USER_BASE = self.old_user_base
96n/a super(BuildExtTestCase, self).tearDown()
97n/a
98n/a def test_solaris_enable_shared(self):
99n/a dist = Distribution({'name': 'xx'})
100n/a cmd = self.build_ext(dist)
101n/a old = sys.platform
102n/a
103n/a sys.platform = 'sunos' # fooling finalize_options
104n/a from distutils.sysconfig import _config_vars
105n/a old_var = _config_vars.get('Py_ENABLE_SHARED')
106n/a _config_vars['Py_ENABLE_SHARED'] = 1
107n/a try:
108n/a cmd.ensure_finalized()
109n/a finally:
110n/a sys.platform = old
111n/a if old_var is None:
112n/a del _config_vars['Py_ENABLE_SHARED']
113n/a else:
114n/a _config_vars['Py_ENABLE_SHARED'] = old_var
115n/a
116n/a # make sure we get some library dirs under solaris
117n/a self.assertGreater(len(cmd.library_dirs), 0)
118n/a
119n/a def test_user_site(self):
120n/a import site
121n/a dist = Distribution({'name': 'xx'})
122n/a cmd = self.build_ext(dist)
123n/a
124n/a # making sure the user option is there
125n/a options = [name for name, short, lable in
126n/a cmd.user_options]
127n/a self.assertIn('user', options)
128n/a
129n/a # setting a value
130n/a cmd.user = 1
131n/a
132n/a # setting user based lib and include
133n/a lib = os.path.join(site.USER_BASE, 'lib')
134n/a incl = os.path.join(site.USER_BASE, 'include')
135n/a os.mkdir(lib)
136n/a os.mkdir(incl)
137n/a
138n/a # let's run finalize
139n/a cmd.ensure_finalized()
140n/a
141n/a # see if include_dirs and library_dirs
142n/a # were set
143n/a self.assertIn(lib, cmd.library_dirs)
144n/a self.assertIn(lib, cmd.rpath)
145n/a self.assertIn(incl, cmd.include_dirs)
146n/a
147n/a def test_optional_extension(self):
148n/a
149n/a # this extension will fail, but let's ignore this failure
150n/a # with the optional argument.
151n/a modules = [Extension('foo', ['xxx'], optional=False)]
152n/a dist = Distribution({'name': 'xx', 'ext_modules': modules})
153n/a cmd = self.build_ext(dist)
154n/a cmd.ensure_finalized()
155n/a self.assertRaises((UnknownFileError, CompileError),
156n/a cmd.run) # should raise an error
157n/a
158n/a modules = [Extension('foo', ['xxx'], optional=True)]
159n/a dist = Distribution({'name': 'xx', 'ext_modules': modules})
160n/a cmd = self.build_ext(dist)
161n/a cmd.ensure_finalized()
162n/a cmd.run() # should pass
163n/a
164n/a def test_finalize_options(self):
165n/a # Make sure Python's include directories (for Python.h, pyconfig.h,
166n/a # etc.) are in the include search path.
167n/a modules = [Extension('foo', ['xxx'], optional=False)]
168n/a dist = Distribution({'name': 'xx', 'ext_modules': modules})
169n/a cmd = self.build_ext(dist)
170n/a cmd.finalize_options()
171n/a
172n/a py_include = sysconfig.get_python_inc()
173n/a self.assertIn(py_include, cmd.include_dirs)
174n/a
175n/a plat_py_include = sysconfig.get_python_inc(plat_specific=1)
176n/a self.assertIn(plat_py_include, cmd.include_dirs)
177n/a
178n/a # make sure cmd.libraries is turned into a list
179n/a # if it's a string
180n/a cmd = self.build_ext(dist)
181n/a cmd.libraries = 'my_lib, other_lib lastlib'
182n/a cmd.finalize_options()
183n/a self.assertEqual(cmd.libraries, ['my_lib', 'other_lib', 'lastlib'])
184n/a
185n/a # make sure cmd.library_dirs is turned into a list
186n/a # if it's a string
187n/a cmd = self.build_ext(dist)
188n/a cmd.library_dirs = 'my_lib_dir%sother_lib_dir' % os.pathsep
189n/a cmd.finalize_options()
190n/a self.assertIn('my_lib_dir', cmd.library_dirs)
191n/a self.assertIn('other_lib_dir', cmd.library_dirs)
192n/a
193n/a # make sure rpath is turned into a list
194n/a # if it's a string
195n/a cmd = self.build_ext(dist)
196n/a cmd.rpath = 'one%stwo' % os.pathsep
197n/a cmd.finalize_options()
198n/a self.assertEqual(cmd.rpath, ['one', 'two'])
199n/a
200n/a # make sure cmd.link_objects is turned into a list
201n/a # if it's a string
202n/a cmd = build_ext(dist)
203n/a cmd.link_objects = 'one two,three'
204n/a cmd.finalize_options()
205n/a self.assertEqual(cmd.link_objects, ['one', 'two', 'three'])
206n/a
207n/a # XXX more tests to perform for win32
208n/a
209n/a # make sure define is turned into 2-tuples
210n/a # strings if they are ','-separated strings
211n/a cmd = self.build_ext(dist)
212n/a cmd.define = 'one,two'
213n/a cmd.finalize_options()
214n/a self.assertEqual(cmd.define, [('one', '1'), ('two', '1')])
215n/a
216n/a # make sure undef is turned into a list of
217n/a # strings if they are ','-separated strings
218n/a cmd = self.build_ext(dist)
219n/a cmd.undef = 'one,two'
220n/a cmd.finalize_options()
221n/a self.assertEqual(cmd.undef, ['one', 'two'])
222n/a
223n/a # make sure swig_opts is turned into a list
224n/a cmd = self.build_ext(dist)
225n/a cmd.swig_opts = None
226n/a cmd.finalize_options()
227n/a self.assertEqual(cmd.swig_opts, [])
228n/a
229n/a cmd = self.build_ext(dist)
230n/a cmd.swig_opts = '1 2'
231n/a cmd.finalize_options()
232n/a self.assertEqual(cmd.swig_opts, ['1', '2'])
233n/a
234n/a def test_check_extensions_list(self):
235n/a dist = Distribution()
236n/a cmd = self.build_ext(dist)
237n/a cmd.finalize_options()
238n/a
239n/a #'extensions' option must be a list of Extension instances
240n/a self.assertRaises(DistutilsSetupError,
241n/a cmd.check_extensions_list, 'foo')
242n/a
243n/a # each element of 'ext_modules' option must be an
244n/a # Extension instance or 2-tuple
245n/a exts = [('bar', 'foo', 'bar'), 'foo']
246n/a self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, exts)
247n/a
248n/a # first element of each tuple in 'ext_modules'
249n/a # must be the extension name (a string) and match
250n/a # a python dotted-separated name
251n/a exts = [('foo-bar', '')]
252n/a self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, exts)
253n/a
254n/a # second element of each tuple in 'ext_modules'
255n/a # must be a dictionary (build info)
256n/a exts = [('foo.bar', '')]
257n/a self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, exts)
258n/a
259n/a # ok this one should pass
260n/a exts = [('foo.bar', {'sources': [''], 'libraries': 'foo',
261n/a 'some': 'bar'})]
262n/a cmd.check_extensions_list(exts)
263n/a ext = exts[0]
264n/a self.assertIsInstance(ext, Extension)
265n/a
266n/a # check_extensions_list adds in ext the values passed
267n/a # when they are in ('include_dirs', 'library_dirs', 'libraries'
268n/a # 'extra_objects', 'extra_compile_args', 'extra_link_args')
269n/a self.assertEqual(ext.libraries, 'foo')
270n/a self.assertFalse(hasattr(ext, 'some'))
271n/a
272n/a # 'macros' element of build info dict must be 1- or 2-tuple
273n/a exts = [('foo.bar', {'sources': [''], 'libraries': 'foo',
274n/a 'some': 'bar', 'macros': [('1', '2', '3'), 'foo']})]
275n/a self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, exts)
276n/a
277n/a exts[0][1]['macros'] = [('1', '2'), ('3',)]
278n/a cmd.check_extensions_list(exts)
279n/a self.assertEqual(exts[0].undef_macros, ['3'])
280n/a self.assertEqual(exts[0].define_macros, [('1', '2')])
281n/a
282n/a def test_get_source_files(self):
283n/a modules = [Extension('foo', ['xxx'], optional=False)]
284n/a dist = Distribution({'name': 'xx', 'ext_modules': modules})
285n/a cmd = self.build_ext(dist)
286n/a cmd.ensure_finalized()
287n/a self.assertEqual(cmd.get_source_files(), ['xxx'])
288n/a
289n/a def test_compiler_option(self):
290n/a # cmd.compiler is an option and
291n/a # should not be overridden by a compiler instance
292n/a # when the command is run
293n/a dist = Distribution()
294n/a cmd = self.build_ext(dist)
295n/a cmd.compiler = 'unix'
296n/a cmd.ensure_finalized()
297n/a cmd.run()
298n/a self.assertEqual(cmd.compiler, 'unix')
299n/a
300n/a def test_get_outputs(self):
301n/a cmd = support.missing_compiler_executable()
302n/a if cmd is not None:
303n/a self.skipTest('The %r command is not found' % cmd)
304n/a tmp_dir = self.mkdtemp()
305n/a c_file = os.path.join(tmp_dir, 'foo.c')
306n/a self.write_file(c_file, 'void PyInit_foo(void) {}\n')
307n/a ext = Extension('foo', [c_file], optional=False)
308n/a dist = Distribution({'name': 'xx',
309n/a 'ext_modules': [ext]})
310n/a cmd = self.build_ext(dist)
311n/a fixup_build_ext(cmd)
312n/a cmd.ensure_finalized()
313n/a self.assertEqual(len(cmd.get_outputs()), 1)
314n/a
315n/a cmd.build_lib = os.path.join(self.tmp_dir, 'build')
316n/a cmd.build_temp = os.path.join(self.tmp_dir, 'tempt')
317n/a
318n/a # issue #5977 : distutils build_ext.get_outputs
319n/a # returns wrong result with --inplace
320n/a other_tmp_dir = os.path.realpath(self.mkdtemp())
321n/a old_wd = os.getcwd()
322n/a os.chdir(other_tmp_dir)
323n/a try:
324n/a cmd.inplace = 1
325n/a cmd.run()
326n/a so_file = cmd.get_outputs()[0]
327n/a finally:
328n/a os.chdir(old_wd)
329n/a self.assertTrue(os.path.exists(so_file))
330n/a ext_suffix = sysconfig.get_config_var('EXT_SUFFIX')
331n/a self.assertTrue(so_file.endswith(ext_suffix))
332n/a so_dir = os.path.dirname(so_file)
333n/a self.assertEqual(so_dir, other_tmp_dir)
334n/a
335n/a cmd.inplace = 0
336n/a cmd.compiler = None
337n/a cmd.run()
338n/a so_file = cmd.get_outputs()[0]
339n/a self.assertTrue(os.path.exists(so_file))
340n/a self.assertTrue(so_file.endswith(ext_suffix))
341n/a so_dir = os.path.dirname(so_file)
342n/a self.assertEqual(so_dir, cmd.build_lib)
343n/a
344n/a # inplace = 0, cmd.package = 'bar'
345n/a build_py = cmd.get_finalized_command('build_py')
346n/a build_py.package_dir = {'': 'bar'}
347n/a path = cmd.get_ext_fullpath('foo')
348n/a # checking that the last directory is the build_dir
349n/a path = os.path.split(path)[0]
350n/a self.assertEqual(path, cmd.build_lib)
351n/a
352n/a # inplace = 1, cmd.package = 'bar'
353n/a cmd.inplace = 1
354n/a other_tmp_dir = os.path.realpath(self.mkdtemp())
355n/a old_wd = os.getcwd()
356n/a os.chdir(other_tmp_dir)
357n/a try:
358n/a path = cmd.get_ext_fullpath('foo')
359n/a finally:
360n/a os.chdir(old_wd)
361n/a # checking that the last directory is bar
362n/a path = os.path.split(path)[0]
363n/a lastdir = os.path.split(path)[-1]
364n/a self.assertEqual(lastdir, 'bar')
365n/a
366n/a def test_ext_fullpath(self):
367n/a ext = sysconfig.get_config_var('EXT_SUFFIX')
368n/a # building lxml.etree inplace
369n/a #etree_c = os.path.join(self.tmp_dir, 'lxml.etree.c')
370n/a #etree_ext = Extension('lxml.etree', [etree_c])
371n/a #dist = Distribution({'name': 'lxml', 'ext_modules': [etree_ext]})
372n/a dist = Distribution()
373n/a cmd = self.build_ext(dist)
374n/a cmd.inplace = 1
375n/a cmd.distribution.package_dir = {'': 'src'}
376n/a cmd.distribution.packages = ['lxml', 'lxml.html']
377n/a curdir = os.getcwd()
378n/a wanted = os.path.join(curdir, 'src', 'lxml', 'etree' + ext)
379n/a path = cmd.get_ext_fullpath('lxml.etree')
380n/a self.assertEqual(wanted, path)
381n/a
382n/a # building lxml.etree not inplace
383n/a cmd.inplace = 0
384n/a cmd.build_lib = os.path.join(curdir, 'tmpdir')
385n/a wanted = os.path.join(curdir, 'tmpdir', 'lxml', 'etree' + ext)
386n/a path = cmd.get_ext_fullpath('lxml.etree')
387n/a self.assertEqual(wanted, path)
388n/a
389n/a # building twisted.runner.portmap not inplace
390n/a build_py = cmd.get_finalized_command('build_py')
391n/a build_py.package_dir = {}
392n/a cmd.distribution.packages = ['twisted', 'twisted.runner.portmap']
393n/a path = cmd.get_ext_fullpath('twisted.runner.portmap')
394n/a wanted = os.path.join(curdir, 'tmpdir', 'twisted', 'runner',
395n/a 'portmap' + ext)
396n/a self.assertEqual(wanted, path)
397n/a
398n/a # building twisted.runner.portmap inplace
399n/a cmd.inplace = 1
400n/a path = cmd.get_ext_fullpath('twisted.runner.portmap')
401n/a wanted = os.path.join(curdir, 'twisted', 'runner', 'portmap' + ext)
402n/a self.assertEqual(wanted, path)
403n/a
404n/a
405n/a @unittest.skipUnless(sys.platform == 'darwin', 'test only relevant for MacOSX')
406n/a def test_deployment_target_default(self):
407n/a # Issue 9516: Test that, in the absence of the environment variable,
408n/a # an extension module is compiled with the same deployment target as
409n/a # the interpreter.
410n/a self._try_compile_deployment_target('==', None)
411n/a
412n/a @unittest.skipUnless(sys.platform == 'darwin', 'test only relevant for MacOSX')
413n/a def test_deployment_target_too_low(self):
414n/a # Issue 9516: Test that an extension module is not allowed to be
415n/a # compiled with a deployment target less than that of the interpreter.
416n/a self.assertRaises(DistutilsPlatformError,
417n/a self._try_compile_deployment_target, '>', '10.1')
418n/a
419n/a @unittest.skipUnless(sys.platform == 'darwin', 'test only relevant for MacOSX')
420n/a def test_deployment_target_higher_ok(self):
421n/a # Issue 9516: Test that an extension module can be compiled with a
422n/a # deployment target higher than that of the interpreter: the ext
423n/a # module may depend on some newer OS feature.
424n/a deptarget = sysconfig.get_config_var('MACOSX_DEPLOYMENT_TARGET')
425n/a if deptarget:
426n/a # increment the minor version number (i.e. 10.6 -> 10.7)
427n/a deptarget = [int(x) for x in deptarget.split('.')]
428n/a deptarget[-1] += 1
429n/a deptarget = '.'.join(str(i) for i in deptarget)
430n/a self._try_compile_deployment_target('<', deptarget)
431n/a
432n/a def _try_compile_deployment_target(self, operator, target):
433n/a orig_environ = os.environ
434n/a os.environ = orig_environ.copy()
435n/a self.addCleanup(setattr, os, 'environ', orig_environ)
436n/a
437n/a if target is None:
438n/a if os.environ.get('MACOSX_DEPLOYMENT_TARGET'):
439n/a del os.environ['MACOSX_DEPLOYMENT_TARGET']
440n/a else:
441n/a os.environ['MACOSX_DEPLOYMENT_TARGET'] = target
442n/a
443n/a deptarget_c = os.path.join(self.tmp_dir, 'deptargetmodule.c')
444n/a
445n/a with open(deptarget_c, 'w') as fp:
446n/a fp.write(textwrap.dedent('''\
447n/a #include <AvailabilityMacros.h>
448n/a
449n/a int dummy;
450n/a
451n/a #if TARGET %s MAC_OS_X_VERSION_MIN_REQUIRED
452n/a #else
453n/a #error "Unexpected target"
454n/a #endif
455n/a
456n/a ''' % operator))
457n/a
458n/a # get the deployment target that the interpreter was built with
459n/a target = sysconfig.get_config_var('MACOSX_DEPLOYMENT_TARGET')
460n/a target = tuple(map(int, target.split('.')[0:2]))
461n/a # format the target value as defined in the Apple
462n/a # Availability Macros. We can't use the macro names since
463n/a # at least one value we test with will not exist yet.
464n/a if target[1] < 10:
465n/a # for 10.1 through 10.9.x -> "10n0"
466n/a target = '%02d%01d0' % target
467n/a else:
468n/a # for 10.10 and beyond -> "10nn00"
469n/a target = '%02d%02d00' % target
470n/a deptarget_ext = Extension(
471n/a 'deptarget',
472n/a [deptarget_c],
473n/a extra_compile_args=['-DTARGET=%s'%(target,)],
474n/a )
475n/a dist = Distribution({
476n/a 'name': 'deptarget',
477n/a 'ext_modules': [deptarget_ext]
478n/a })
479n/a dist.package_dir = self.tmp_dir
480n/a cmd = self.build_ext(dist)
481n/a cmd.build_lib = self.tmp_dir
482n/a cmd.build_temp = self.tmp_dir
483n/a
484n/a try:
485n/a old_stdout = sys.stdout
486n/a if not support.verbose:
487n/a # silence compiler output
488n/a sys.stdout = StringIO()
489n/a try:
490n/a cmd.ensure_finalized()
491n/a cmd.run()
492n/a finally:
493n/a sys.stdout = old_stdout
494n/a
495n/a except CompileError:
496n/a self.fail("Wrong deployment target during compilation")
497n/a
498n/a
499n/aclass ParallelBuildExtTestCase(BuildExtTestCase):
500n/a
501n/a def build_ext(self, *args, **kwargs):
502n/a build_ext = super().build_ext(*args, **kwargs)
503n/a build_ext.parallel = True
504n/a return build_ext
505n/a
506n/a
507n/adef test_suite():
508n/a suite = unittest.TestSuite()
509n/a suite.addTest(unittest.makeSuite(BuildExtTestCase))
510n/a suite.addTest(unittest.makeSuite(ParallelBuildExtTestCase))
511n/a return suite
512n/a
513n/aif __name__ == '__main__':
514n/a support.run_unittest(__name__)