ยปCore Development>Code coverage>Lib/packaging/tests/support.py

Python code coverage for Lib/packaging/tests/support.py

#countcontent
1n/a"""Support code for packaging test cases.
2n/a
3n/a*This module should not be considered public: its content and API may
4n/achange in incompatible ways.*
5n/a
6n/aA few helper classes are provided: LoggingCatcher, TempdirManager and
7n/aEnvironRestorer. They are written to be used as mixins::
8n/a
9n/a from packaging.tests import unittest
10n/a from packaging.tests.support import LoggingCatcher
11n/a
12n/a class SomeTestCase(LoggingCatcher, unittest.TestCase):
13n/a ...
14n/a
15n/aIf you need to define a setUp method on your test class, you have to
16n/acall the mixin class' setUp method or it won't work (same thing for
17n/atearDown):
18n/a
19n/a def setUp(self):
20n/a super(SomeTestCase, self).setUp()
21n/a ... # other setup code
22n/a
23n/aAlso provided is a DummyCommand class, useful to mock commands in the
24n/atests of another command that needs them, for example to fake
25n/acompilation in build_ext (this requires that the mock build_ext command
26n/abe injected into the distribution object's command_obj dictionary).
27n/a
28n/aFor tests that need to compile an extension module, use the
29n/acopy_xxmodule_c and fixup_build_ext functions.
30n/a
31n/aEach class or function has a docstring to explain its purpose and usage.
32n/aExisting tests should also be used as examples.
33n/a"""
34n/a
35n/aimport os
36n/aimport sys
37n/aimport shutil
38n/aimport logging
39n/aimport weakref
40n/aimport tempfile
41n/aimport sysconfig
42n/a
43n/afrom packaging.dist import Distribution
44n/afrom packaging.util import resolve_name
45n/afrom packaging.command import set_command, _COMMANDS
46n/a
47n/afrom packaging.tests import unittest
48n/afrom test.support import requires_zlib, unlink
49n/a
50n/a# define __all__ to make pydoc more useful
51n/a__all__ = [
52n/a # TestCase mixins
53n/a 'LoggingCatcher', 'TempdirManager', 'EnvironRestorer',
54n/a # mocks
55n/a 'DummyCommand', 'TestDistribution', 'Inputs',
56n/a # misc. functions and decorators
57n/a 'fake_dec', 'create_distribution', 'use_command',
58n/a 'copy_xxmodule_c', 'fixup_build_ext',
59n/a 'skip_2to3_optimize',
60n/a # imported from this module for backport purposes
61n/a 'unittest', 'requires_zlib', 'skip_unless_symlink',
62n/a]
63n/a
64n/a
65n/alogger = logging.getLogger('packaging')
66n/alogger2to3 = logging.getLogger('RefactoringTool')
67n/a
68n/a
69n/aclass _TestHandler(logging.handlers.BufferingHandler):
70n/a # stolen and adapted from test.support
71n/a
72n/a def __init__(self):
73n/a super(_TestHandler, self).__init__(0)
74n/a self.setLevel(logging.DEBUG)
75n/a
76n/a def shouldFlush(self):
77n/a return False
78n/a
79n/a def emit(self, record):
80n/a self.buffer.append(record)
81n/a
82n/a
83n/aclass LoggingCatcher:
84n/a """TestCase-compatible mixin to receive logging calls.
85n/a
86n/a Upon setUp, instances of this classes get a BufferingHandler that's
87n/a configured to record all messages logged to the 'packaging' logger.
88n/a
89n/a Use get_logs to retrieve messages and self.loghandler.flush to discard
90n/a them. get_logs automatically flushes the logs, unless you pass
91n/a *flush=False*, for example to make multiple calls to the method with
92n/a different level arguments. If your test calls some code that generates
93n/a logging message and then you don't call get_logs, you will need to flush
94n/a manually before testing other code in the same test_* method, otherwise
95n/a get_logs in the next lines will see messages from the previous lines.
96n/a See example in test_command_check.
97n/a """
98n/a
99n/a def setUp(self):
100n/a super(LoggingCatcher, self).setUp()
101n/a self.loghandler = handler = _TestHandler()
102n/a self._old_levels = logger.level, logger2to3.level
103n/a logger.addHandler(handler)
104n/a logger.setLevel(logging.DEBUG) # we want all messages
105n/a logger2to3.setLevel(logging.CRITICAL) # we don't want 2to3 messages
106n/a
107n/a def tearDown(self):
108n/a handler = self.loghandler
109n/a # All this is necessary to properly shut down the logging system and
110n/a # avoid a regrtest complaint. Thanks to Vinay Sajip for the help.
111n/a handler.close()
112n/a logger.removeHandler(handler)
113n/a for ref in weakref.getweakrefs(handler):
114n/a logging._removeHandlerRef(ref)
115n/a del self.loghandler
116n/a logger.setLevel(self._old_levels[0])
117n/a logger2to3.setLevel(self._old_levels[1])
118n/a super(LoggingCatcher, self).tearDown()
119n/a
120n/a def get_logs(self, level=logging.WARNING, flush=True):
121n/a """Return all log messages with given level.
122n/a
123n/a *level* defaults to logging.WARNING.
124n/a
125n/a For log calls with arguments (i.e. logger.info('bla bla %r', arg)),
126n/a the messages will be formatted before being returned (e.g. "bla bla
127n/a 'thing'").
128n/a
129n/a Returns a list. Automatically flushes the loghandler after being
130n/a called, unless *flush* is False (this is useful to get e.g. all
131n/a warnings then all info messages).
132n/a """
133n/a messages = [log.getMessage() for log in self.loghandler.buffer
134n/a if log.levelno == level]
135n/a if flush:
136n/a self.loghandler.flush()
137n/a return messages
138n/a
139n/a
140n/aclass TempdirManager:
141n/a """TestCase-compatible mixin to create temporary directories and files.
142n/a
143n/a Directories and files created in a test_* method will be removed after it
144n/a has run.
145n/a """
146n/a
147n/a def setUp(self):
148n/a super(TempdirManager, self).setUp()
149n/a self._olddir = os.getcwd()
150n/a self._basetempdir = tempfile.mkdtemp()
151n/a self._files = []
152n/a
153n/a def tearDown(self):
154n/a for handle, name in self._files:
155n/a handle.close()
156n/a unlink(name)
157n/a
158n/a os.chdir(self._olddir)
159n/a shutil.rmtree(self._basetempdir)
160n/a super(TempdirManager, self).tearDown()
161n/a
162n/a def mktempfile(self):
163n/a """Create a read-write temporary file and return it."""
164n/a fd, fn = tempfile.mkstemp(dir=self._basetempdir)
165n/a os.close(fd)
166n/a fp = open(fn, 'w+')
167n/a self._files.append((fp, fn))
168n/a return fp
169n/a
170n/a def mkdtemp(self):
171n/a """Create a temporary directory and return its path."""
172n/a d = tempfile.mkdtemp(dir=self._basetempdir)
173n/a return d
174n/a
175n/a def write_file(self, path, content='xxx', encoding=None):
176n/a """Write a file at the given path.
177n/a
178n/a path can be a string, a tuple or a list; if it's a tuple or list,
179n/a os.path.join will be used to produce a path.
180n/a """
181n/a if isinstance(path, (list, tuple)):
182n/a path = os.path.join(*path)
183n/a with open(path, 'w', encoding=encoding) as f:
184n/a f.write(content)
185n/a
186n/a def create_dist(self, **kw):
187n/a """Create a stub distribution object and files.
188n/a
189n/a This function creates a Distribution instance (use keyword arguments
190n/a to customize it) and a temporary directory with a project structure
191n/a (currently an empty directory).
192n/a
193n/a It returns the path to the directory and the Distribution instance.
194n/a You can use self.write_file to write any file in that
195n/a directory, e.g. setup scripts or Python modules.
196n/a """
197n/a if 'name' not in kw:
198n/a kw['name'] = 'foo'
199n/a tmp_dir = self.mkdtemp()
200n/a project_dir = os.path.join(tmp_dir, kw['name'])
201n/a os.mkdir(project_dir)
202n/a dist = Distribution(attrs=kw)
203n/a return project_dir, dist
204n/a
205n/a def assertIsFile(self, *args):
206n/a path = os.path.join(*args)
207n/a dirname = os.path.dirname(path)
208n/a file = os.path.basename(path)
209n/a if os.path.isdir(dirname):
210n/a files = os.listdir(dirname)
211n/a msg = "%s not found in %s: %s" % (file, dirname, files)
212n/a assert os.path.isfile(path), msg
213n/a else:
214n/a raise AssertionError(
215n/a '%s not found. %s does not exist' % (file, dirname))
216n/a
217n/a def assertIsNotFile(self, *args):
218n/a path = os.path.join(*args)
219n/a self.assertFalse(os.path.isfile(path), "%r exists" % path)
220n/a
221n/a
222n/aclass EnvironRestorer:
223n/a """TestCase-compatible mixin to restore or delete environment variables.
224n/a
225n/a The variables to restore (or delete if they were not originally present)
226n/a must be explicitly listed in self.restore_environ. It's better to be
227n/a aware of what we're modifying instead of saving and restoring the whole
228n/a environment.
229n/a """
230n/a
231n/a def setUp(self):
232n/a super(EnvironRestorer, self).setUp()
233n/a self._saved = []
234n/a self._added = []
235n/a for key in self.restore_environ:
236n/a if key in os.environ:
237n/a self._saved.append((key, os.environ[key]))
238n/a else:
239n/a self._added.append(key)
240n/a
241n/a def tearDown(self):
242n/a for key, value in self._saved:
243n/a os.environ[key] = value
244n/a for key in self._added:
245n/a os.environ.pop(key, None)
246n/a super(EnvironRestorer, self).tearDown()
247n/a
248n/a
249n/aclass DummyCommand:
250n/a """Class to store options for retrieval via set_undefined_options().
251n/a
252n/a Useful for mocking one dependency command in the tests for another
253n/a command, see e.g. the dummy build command in test_build_scripts.
254n/a """
255n/a # XXX does not work with dist.reinitialize_command, which typechecks
256n/a # and wants a finalized attribute
257n/a
258n/a def __init__(self, **kwargs):
259n/a for kw, val in kwargs.items():
260n/a setattr(self, kw, val)
261n/a
262n/a def ensure_finalized(self):
263n/a pass
264n/a
265n/a
266n/aclass TestDistribution(Distribution):
267n/a """Distribution subclasses that avoids the default search for
268n/a configuration files.
269n/a
270n/a The ._config_files attribute must be set before
271n/a .parse_config_files() is called.
272n/a """
273n/a
274n/a def find_config_files(self):
275n/a return self._config_files
276n/a
277n/a
278n/aclass Inputs:
279n/a """Fakes user inputs."""
280n/a # TODO document usage
281n/a # TODO use context manager or something for auto cleanup
282n/a
283n/a def __init__(self, *answers):
284n/a self.answers = answers
285n/a self.index = 0
286n/a
287n/a def __call__(self, prompt=''):
288n/a try:
289n/a return self.answers[self.index]
290n/a finally:
291n/a self.index += 1
292n/a
293n/a
294n/adef create_distribution(configfiles=()):
295n/a """Prepares a distribution with given config files parsed."""
296n/a d = TestDistribution()
297n/a d.config.find_config_files = d.find_config_files
298n/a d._config_files = configfiles
299n/a d.parse_config_files()
300n/a d.parse_command_line()
301n/a return d
302n/a
303n/a
304n/adef use_command(testcase, fullname):
305n/a """Register command at *fullname* for the duration of a test."""
306n/a set_command(fullname)
307n/a # XXX maybe set_command should return the class object
308n/a name = resolve_name(fullname).get_command_name()
309n/a # XXX maybe we need a public API to remove commands
310n/a testcase.addCleanup(_COMMANDS.__delitem__, name)
311n/a
312n/a
313n/adef fake_dec(*args, **kw):
314n/a """Fake decorator"""
315n/a def _wrap(func):
316n/a def __wrap(*args, **kw):
317n/a return func(*args, **kw)
318n/a return __wrap
319n/a return _wrap
320n/a
321n/a
322n/adef copy_xxmodule_c(directory):
323n/a """Helper for tests that need the xxmodule.c source file.
324n/a
325n/a Example use:
326n/a
327n/a def test_compile(self):
328n/a copy_xxmodule_c(self.tmpdir)
329n/a self.assertIn('xxmodule.c', os.listdir(self.tmpdir))
330n/a
331n/a If the source file can be found, it will be copied to *directory*. If not,
332n/a the test will be skipped. Errors during copy are not caught.
333n/a """
334n/a filename = _get_xxmodule_path()
335n/a if filename is None:
336n/a raise unittest.SkipTest('cannot find xxmodule.c')
337n/a shutil.copy(filename, directory)
338n/a
339n/a
340n/adef _get_xxmodule_path():
341n/a if sysconfig.is_python_build():
342n/a srcdir = sysconfig.get_config_var('projectbase')
343n/a path = os.path.join(os.getcwd(), srcdir, 'Modules', 'xxmodule.c')
344n/a else:
345n/a path = os.path.join(os.path.dirname(__file__), 'xxmodule.c')
346n/a if os.path.exists(path):
347n/a return path
348n/a
349n/a
350n/adef fixup_build_ext(cmd):
351n/a """Function needed to make build_ext tests pass.
352n/a
353n/a When Python was built with --enable-shared on Unix, -L. is not enough to
354n/a find libpython<blah>.so, because regrtest runs in a tempdir, not in the
355n/a source directory where the .so lives. (Mac OS X embeds absolute paths
356n/a to shared libraries into executables, so the fixup is a no-op on that
357n/a platform.)
358n/a
359n/a When Python was built with in debug mode on Windows, build_ext commands
360n/a need their debug attribute set, and it is not done automatically for
361n/a some reason.
362n/a
363n/a This function handles both of these things, and also fixes
364n/a cmd.distribution.include_dirs if the running Python is an uninstalled
365n/a build. Example use:
366n/a
367n/a cmd = build_ext(dist)
368n/a support.fixup_build_ext(cmd)
369n/a cmd.ensure_finalized()
370n/a """
371n/a if os.name == 'nt':
372n/a cmd.debug = sys.executable.endswith('_d.exe')
373n/a elif sysconfig.get_config_var('Py_ENABLE_SHARED'):
374n/a # To further add to the shared builds fun on Unix, we can't just add
375n/a # library_dirs to the Extension() instance because that doesn't get
376n/a # plumbed through to the final compiler command.
377n/a runshared = sysconfig.get_config_var('RUNSHARED')
378n/a if runshared is None:
379n/a cmd.library_dirs = ['.']
380n/a else:
381n/a if sys.platform == 'darwin':
382n/a cmd.library_dirs = []
383n/a else:
384n/a name, equals, value = runshared.partition('=')
385n/a cmd.library_dirs = value.split(os.pathsep)
386n/a
387n/a # Allow tests to run with an uninstalled Python
388n/a if sysconfig.is_python_build():
389n/a pysrcdir = sysconfig.get_config_var('projectbase')
390n/a cmd.distribution.include_dirs.append(os.path.join(pysrcdir, 'Include'))
391n/a
392n/a
393n/atry:
394n/a from test.support import skip_unless_symlink
395n/aexcept ImportError:
396n/a skip_unless_symlink = unittest.skip(
397n/a 'requires test.support.skip_unless_symlink')
398n/a
399n/askip_2to3_optimize = unittest.skipIf(sys.flags.optimize,
400n/a "2to3 doesn't work under -O")