• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Support code for distutils test cases."""
2import os
3import sys
4import shutil
5import tempfile
6import unittest
7import sysconfig
8from copy import deepcopy
9import test.support
10
11from distutils import log
12from distutils.log import DEBUG, INFO, WARN, ERROR, FATAL
13from distutils.core import Distribution
14
15
16class LoggingSilencer(object):
17
18    def setUp(self):
19        super().setUp()
20        self.threshold = log.set_threshold(log.FATAL)
21        # catching warnings
22        # when log will be replaced by logging
23        # we won't need such monkey-patch anymore
24        self._old_log = log.Log._log
25        log.Log._log = self._log
26        self.logs = []
27
28    def tearDown(self):
29        log.set_threshold(self.threshold)
30        log.Log._log = self._old_log
31        super().tearDown()
32
33    def _log(self, level, msg, args):
34        if level not in (DEBUG, INFO, WARN, ERROR, FATAL):
35            raise ValueError('%s wrong log level' % str(level))
36        if not isinstance(msg, str):
37            raise TypeError("msg should be str, not '%.200s'"
38                            % (type(msg).__name__))
39        self.logs.append((level, msg, args))
40
41    def get_logs(self, *levels):
42        return [msg % args for level, msg, args
43                in self.logs if level in levels]
44
45    def clear_logs(self):
46        self.logs = []
47
48
49class TempdirManager(object):
50    """Mix-in class that handles temporary directories for test cases.
51
52    This is intended to be used with unittest.TestCase.
53    """
54
55    def setUp(self):
56        super().setUp()
57        self.old_cwd = os.getcwd()
58        self.tempdirs = []
59
60    def tearDown(self):
61        # Restore working dir, for Solaris and derivatives, where rmdir()
62        # on the current directory fails.
63        os.chdir(self.old_cwd)
64        super().tearDown()
65        while self.tempdirs:
66            tmpdir = self.tempdirs.pop()
67            test.support.rmtree(tmpdir)
68
69    def mkdtemp(self):
70        """Create a temporary directory that will be cleaned up.
71
72        Returns the path of the directory.
73        """
74        d = tempfile.mkdtemp()
75        self.tempdirs.append(d)
76        return d
77
78    def write_file(self, path, content='xxx'):
79        """Writes a file in the given path.
80
81
82        path can be a string or a sequence.
83        """
84        if isinstance(path, (list, tuple)):
85            path = os.path.join(*path)
86        f = open(path, 'w')
87        try:
88            f.write(content)
89        finally:
90            f.close()
91
92    def create_dist(self, pkg_name='foo', **kw):
93        """Will generate a test environment.
94
95        This function creates:
96         - a Distribution instance using keywords
97         - a temporary directory with a package structure
98
99        It returns the package directory and the distribution
100        instance.
101        """
102        tmp_dir = self.mkdtemp()
103        pkg_dir = os.path.join(tmp_dir, pkg_name)
104        os.mkdir(pkg_dir)
105        dist = Distribution(attrs=kw)
106
107        return pkg_dir, dist
108
109
110class DummyCommand:
111    """Class to store options for retrieval via set_undefined_options()."""
112
113    def __init__(self, **kwargs):
114        for kw, val in kwargs.items():
115            setattr(self, kw, val)
116
117    def ensure_finalized(self):
118        pass
119
120
121class EnvironGuard(object):
122
123    def setUp(self):
124        super(EnvironGuard, self).setUp()
125        self.old_environ = deepcopy(os.environ)
126
127    def tearDown(self):
128        for key, value in self.old_environ.items():
129            if os.environ.get(key) != value:
130                os.environ[key] = value
131
132        for key in tuple(os.environ.keys()):
133            if key not in self.old_environ:
134                del os.environ[key]
135
136        super(EnvironGuard, self).tearDown()
137
138
139def copy_xxmodule_c(directory):
140    """Helper for tests that need the xxmodule.c source file.
141
142    Example use:
143
144        def test_compile(self):
145            copy_xxmodule_c(self.tmpdir)
146            self.assertIn('xxmodule.c', os.listdir(self.tmpdir))
147
148    If the source file can be found, it will be copied to *directory*.  If not,
149    the test will be skipped.  Errors during copy are not caught.
150    """
151    filename = _get_xxmodule_path()
152    if filename is None:
153        raise unittest.SkipTest('cannot find xxmodule.c (test must run in '
154                                'the python build dir)')
155    shutil.copy(filename, directory)
156
157
158def _get_xxmodule_path():
159    srcdir = sysconfig.get_config_var('srcdir')
160    candidates = [
161        # use installed copy if available
162        os.path.join(os.path.dirname(__file__), 'xxmodule.c'),
163        # otherwise try using copy from build directory
164        os.path.join(srcdir, 'Modules', 'xxmodule.c'),
165        # srcdir mysteriously can be $srcdir/Lib/distutils/tests when
166        # this file is run from its parent directory, so walk up the
167        # tree to find the real srcdir
168        os.path.join(srcdir, '..', '..', '..', 'Modules', 'xxmodule.c'),
169    ]
170    for path in candidates:
171        if os.path.exists(path):
172            return path
173
174
175def fixup_build_ext(cmd):
176    """Function needed to make build_ext tests pass.
177
178    When Python was built with --enable-shared on Unix, -L. is not enough to
179    find libpython<blah>.so, because regrtest runs in a tempdir, not in the
180    source directory where the .so lives.
181
182    When Python was built with in debug mode on Windows, build_ext commands
183    need their debug attribute set, and it is not done automatically for
184    some reason.
185
186    This function handles both of these things.  Example use:
187
188        cmd = build_ext(dist)
189        support.fixup_build_ext(cmd)
190        cmd.ensure_finalized()
191
192    Unlike most other Unix platforms, Mac OS X embeds absolute paths
193    to shared libraries into executables, so the fixup is not needed there.
194    """
195    if os.name == 'nt':
196        cmd.debug = sys.executable.endswith('_d.exe')
197    elif sysconfig.get_config_var('Py_ENABLE_SHARED'):
198        # To further add to the shared builds fun on Unix, we can't just add
199        # library_dirs to the Extension() instance because that doesn't get
200        # plumbed through to the final compiler command.
201        runshared = sysconfig.get_config_var('RUNSHARED')
202        if runshared is None:
203            cmd.library_dirs = ['.']
204        else:
205            if sys.platform == 'darwin':
206                cmd.library_dirs = []
207            else:
208                name, equals, value = runshared.partition('=')
209                cmd.library_dirs = [d for d in value.split(os.pathsep) if d]
210