1 """Support code for distutils test cases."""
2 import os
3 import sys
4 import shutil
5 import tempfile
6 import unittest
7 import sysconfig
8 from copy import deepcopy
9 from test.support import os_helper
10 
11 from distutils import log
12 from distutils.log import DEBUG, INFO, WARN, ERROR, FATAL
13 from distutils.core import Distribution
14 
15 
16 class 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 
49 class 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 
110 class 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 
121 class 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 
139 def 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 
158 def _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 
175 def 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