python (3.12.0)
1 import functools
2 import importlib.util
3 import os
4 import py_compile
5 import shutil
6 import stat
7 import subprocess
8 import sys
9 import tempfile
10 import unittest
11
12 from test import support
13 from test.support import os_helper, script_helper
14
15
16 def without_source_date_epoch(fxn):
17 """Runs function with SOURCE_DATE_EPOCH unset."""
18 @functools.wraps(fxn)
19 def wrapper(*args, **kwargs):
20 with os_helper.EnvironmentVarGuard() as env:
21 env.unset('SOURCE_DATE_EPOCH')
22 return fxn(*args, **kwargs)
23 return wrapper
24
25
26 def with_source_date_epoch(fxn):
27 """Runs function with SOURCE_DATE_EPOCH set."""
28 @functools.wraps(fxn)
29 def wrapper(*args, **kwargs):
30 with os_helper.EnvironmentVarGuard() as env:
31 env['SOURCE_DATE_EPOCH'] = '123456789'
32 return fxn(*args, **kwargs)
33 return wrapper
34
35
36 # Run tests with SOURCE_DATE_EPOCH set or unset explicitly.
37 class ESC[4;38;5;81mSourceDateEpochTestMeta(ESC[4;38;5;149mtype(unittest.TestCase)):
38 def __new__(mcls, name, bases, dct, *, source_date_epoch):
39 cls = super().__new__(mcls, name, bases, dct)
40
41 for attr in dir(cls):
42 if attr.startswith('test_'):
43 meth = getattr(cls, attr)
44 if source_date_epoch:
45 wrapper = with_source_date_epoch(meth)
46 else:
47 wrapper = without_source_date_epoch(meth)
48 setattr(cls, attr, wrapper)
49
50 return cls
51
52
53 class ESC[4;38;5;81mPyCompileTestsBase:
54
55 def setUp(self):
56 self.directory = tempfile.mkdtemp(dir=os.getcwd())
57 self.source_path = os.path.join(self.directory, '_test.py')
58 self.pyc_path = self.source_path + 'c'
59 self.cache_path = importlib.util.cache_from_source(self.source_path)
60 self.cwd_drive = os.path.splitdrive(os.getcwd())[0]
61 # In these tests we compute relative paths. When using Windows, the
62 # current working directory path and the 'self.source_path' might be
63 # on different drives. Therefore we need to switch to the drive where
64 # the temporary source file lives.
65 drive = os.path.splitdrive(self.source_path)[0]
66 if drive:
67 os.chdir(drive)
68 with open(self.source_path, 'w') as file:
69 file.write('x = 123\n')
70
71 def tearDown(self):
72 shutil.rmtree(self.directory)
73 if self.cwd_drive:
74 os.chdir(self.cwd_drive)
75
76 def test_absolute_path(self):
77 py_compile.compile(self.source_path, self.pyc_path)
78 self.assertTrue(os.path.exists(self.pyc_path))
79 self.assertFalse(os.path.exists(self.cache_path))
80
81 def test_do_not_overwrite_symlinks(self):
82 # In the face of a cfile argument being a symlink, bail out.
83 # Issue #17222
84 try:
85 os.symlink(self.pyc_path + '.actual', self.pyc_path)
86 except (NotImplementedError, OSError):
87 self.skipTest('need to be able to create a symlink for a file')
88 else:
89 assert os.path.islink(self.pyc_path)
90 with self.assertRaises(FileExistsError):
91 py_compile.compile(self.source_path, self.pyc_path)
92
93 @unittest.skipIf(not os.path.exists(os.devnull) or os.path.isfile(os.devnull),
94 'requires os.devnull and for it to be a non-regular file')
95 def test_do_not_overwrite_nonregular_files(self):
96 # In the face of a cfile argument being a non-regular file, bail out.
97 # Issue #17222
98 with self.assertRaises(FileExistsError):
99 py_compile.compile(self.source_path, os.devnull)
100
101 def test_cache_path(self):
102 py_compile.compile(self.source_path)
103 self.assertTrue(os.path.exists(self.cache_path))
104
105 def test_cwd(self):
106 with os_helper.change_cwd(self.directory):
107 py_compile.compile(os.path.basename(self.source_path),
108 os.path.basename(self.pyc_path))
109 self.assertTrue(os.path.exists(self.pyc_path))
110 self.assertFalse(os.path.exists(self.cache_path))
111
112 def test_relative_path(self):
113 py_compile.compile(os.path.relpath(self.source_path),
114 os.path.relpath(self.pyc_path))
115 self.assertTrue(os.path.exists(self.pyc_path))
116 self.assertFalse(os.path.exists(self.cache_path))
117
118 @os_helper.skip_if_dac_override
119 @unittest.skipIf(os.name == 'nt',
120 'cannot control directory permissions on Windows')
121 @os_helper.skip_unless_working_chmod
122 def test_exceptions_propagate(self):
123 # Make sure that exceptions raised thanks to issues with writing
124 # bytecode.
125 # http://bugs.python.org/issue17244
126 mode = os.stat(self.directory)
127 os.chmod(self.directory, stat.S_IREAD)
128 try:
129 with self.assertRaises(IOError):
130 py_compile.compile(self.source_path, self.pyc_path)
131 finally:
132 os.chmod(self.directory, mode.st_mode)
133
134 def test_bad_coding(self):
135 bad_coding = os.path.join(os.path.dirname(__file__), 'bad_coding2.py')
136 with support.captured_stderr():
137 self.assertIsNone(py_compile.compile(bad_coding, doraise=False))
138 self.assertFalse(os.path.exists(
139 importlib.util.cache_from_source(bad_coding)))
140
141 def test_source_date_epoch(self):
142 py_compile.compile(self.source_path, self.pyc_path)
143 self.assertTrue(os.path.exists(self.pyc_path))
144 self.assertFalse(os.path.exists(self.cache_path))
145 with open(self.pyc_path, 'rb') as fp:
146 flags = importlib._bootstrap_external._classify_pyc(
147 fp.read(), 'test', {})
148 if os.environ.get('SOURCE_DATE_EPOCH'):
149 expected_flags = 0b11
150 else:
151 expected_flags = 0b00
152
153 self.assertEqual(flags, expected_flags)
154
155 @unittest.skipIf(sys.flags.optimize > 0, 'test does not work with -O')
156 def test_double_dot_no_clobber(self):
157 # http://bugs.python.org/issue22966
158 # py_compile foo.bar.py -> __pycache__/foo.cpython-34.pyc
159 weird_path = os.path.join(self.directory, 'foo.bar.py')
160 cache_path = importlib.util.cache_from_source(weird_path)
161 pyc_path = weird_path + 'c'
162 head, tail = os.path.split(cache_path)
163 penultimate_tail = os.path.basename(head)
164 self.assertEqual(
165 os.path.join(penultimate_tail, tail),
166 os.path.join(
167 '__pycache__',
168 'foo.bar.{}.pyc'.format(sys.implementation.cache_tag)))
169 with open(weird_path, 'w') as file:
170 file.write('x = 123\n')
171 py_compile.compile(weird_path)
172 self.assertTrue(os.path.exists(cache_path))
173 self.assertFalse(os.path.exists(pyc_path))
174
175 def test_optimization_path(self):
176 # Specifying optimized bytecode should lead to a path reflecting that.
177 self.assertIn('opt-2', py_compile.compile(self.source_path, optimize=2))
178
179 def test_invalidation_mode(self):
180 py_compile.compile(
181 self.source_path,
182 invalidation_mode=py_compile.PycInvalidationMode.CHECKED_HASH,
183 )
184 with open(self.cache_path, 'rb') as fp:
185 flags = importlib._bootstrap_external._classify_pyc(
186 fp.read(), 'test', {})
187 self.assertEqual(flags, 0b11)
188 py_compile.compile(
189 self.source_path,
190 invalidation_mode=py_compile.PycInvalidationMode.UNCHECKED_HASH,
191 )
192 with open(self.cache_path, 'rb') as fp:
193 flags = importlib._bootstrap_external._classify_pyc(
194 fp.read(), 'test', {})
195 self.assertEqual(flags, 0b1)
196
197 def test_quiet(self):
198 bad_coding = os.path.join(os.path.dirname(__file__), 'bad_coding2.py')
199 with support.captured_stderr() as stderr:
200 self.assertIsNone(py_compile.compile(bad_coding, doraise=False, quiet=2))
201 self.assertIsNone(py_compile.compile(bad_coding, doraise=True, quiet=2))
202 self.assertEqual(stderr.getvalue(), '')
203 with self.assertRaises(py_compile.PyCompileError):
204 py_compile.compile(bad_coding, doraise=True, quiet=1)
205
206
207 class ESC[4;38;5;81mPyCompileTestsWithSourceEpoch(ESC[4;38;5;149mPyCompileTestsBase,
208 ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase,
209 metaclass=ESC[4;38;5;149mSourceDateEpochTestMeta,
210 source_date_epoch=ESC[4;38;5;149mTrue):
211 pass
212
213
214 class ESC[4;38;5;81mPyCompileTestsWithoutSourceEpoch(ESC[4;38;5;149mPyCompileTestsBase,
215 ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase,
216 metaclass=ESC[4;38;5;149mSourceDateEpochTestMeta,
217 source_date_epoch=ESC[4;38;5;149mFalse):
218 pass
219
220
221 class ESC[4;38;5;81mPyCompileCLITestCase(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
222
223 def setUp(self):
224 self.directory = tempfile.mkdtemp()
225 self.source_path = os.path.join(self.directory, '_test.py')
226 self.cache_path = importlib.util.cache_from_source(self.source_path)
227 with open(self.source_path, 'w') as file:
228 file.write('x = 123\n')
229
230 def tearDown(self):
231 os_helper.rmtree(self.directory)
232
233 @support.requires_subprocess()
234 def pycompilecmd(self, *args, **kwargs):
235 # assert_python_* helpers don't return proc object. We'll just use
236 # subprocess.run() instead of spawn_python() and its friends to test
237 # stdin support of the CLI.
238 opts = '-m' if __debug__ else '-Om'
239 if args and args[0] == '-' and 'input' in kwargs:
240 return subprocess.run([sys.executable, opts, 'py_compile', '-'],
241 input=kwargs['input'].encode(),
242 capture_output=True)
243 return script_helper.assert_python_ok(opts, 'py_compile', *args, **kwargs)
244
245 def pycompilecmd_failure(self, *args):
246 return script_helper.assert_python_failure('-m', 'py_compile', *args)
247
248 def test_stdin(self):
249 result = self.pycompilecmd('-', input=self.source_path)
250 self.assertEqual(result.returncode, 0)
251 self.assertEqual(result.stdout, b'')
252 self.assertEqual(result.stderr, b'')
253 self.assertTrue(os.path.exists(self.cache_path))
254
255 def test_with_files(self):
256 rc, stdout, stderr = self.pycompilecmd(self.source_path, self.source_path)
257 self.assertEqual(rc, 0)
258 self.assertEqual(stdout, b'')
259 self.assertEqual(stderr, b'')
260 self.assertTrue(os.path.exists(self.cache_path))
261
262 def test_bad_syntax(self):
263 bad_syntax = os.path.join(os.path.dirname(__file__), 'badsyntax_3131.py')
264 rc, stdout, stderr = self.pycompilecmd_failure(bad_syntax)
265 self.assertEqual(rc, 1)
266 self.assertEqual(stdout, b'')
267 self.assertIn(b'SyntaxError', stderr)
268
269 def test_bad_syntax_with_quiet(self):
270 bad_syntax = os.path.join(os.path.dirname(__file__), 'badsyntax_3131.py')
271 rc, stdout, stderr = self.pycompilecmd_failure('-q', bad_syntax)
272 self.assertEqual(rc, 1)
273 self.assertEqual(stdout, b'')
274 self.assertEqual(stderr, b'')
275
276 def test_file_not_exists(self):
277 should_not_exists = os.path.join(os.path.dirname(__file__), 'should_not_exists.py')
278 rc, stdout, stderr = self.pycompilecmd_failure(self.source_path, should_not_exists)
279 self.assertEqual(rc, 1)
280 self.assertEqual(stdout, b'')
281 self.assertIn(b'no such file or directory', stderr.lower())
282
283 def test_file_not_exists_with_quiet(self):
284 should_not_exists = os.path.join(os.path.dirname(__file__), 'should_not_exists.py')
285 rc, stdout, stderr = self.pycompilecmd_failure('-q', self.source_path, should_not_exists)
286 self.assertEqual(rc, 1)
287 self.assertEqual(stdout, b'')
288 self.assertEqual(stderr, b'')
289
290
291 if __name__ == "__main__":
292 unittest.main()