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