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