1"""Support code for distutils test cases."""
2import os
3import sys
4import shutil
5import tempfile
6import unittest
7import sysconfig
8from copy import deepcopy
9from test.support import os_helper
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            os_helper.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