blob: be1149a87faef18049651a3f3e09904e449b3d57 [file] [log] [blame]
Brett Cannonbefb14f2009-02-10 02:10:16 +00001import compileall
Lumír 'Frenzy' Balhare77d4282020-05-14 16:17:22 +02002import contextlib
3import filecmp
Brett Cannon7822e122013-06-14 23:04:02 -04004import importlib.util
Lumír 'Frenzy' Balhare77d4282020-05-14 16:17:22 +02005import io
6import itertools
Brett Cannonbefb14f2009-02-10 02:10:16 +00007import os
Brett Cannon65ed7502015-10-09 15:09:43 -07008import pathlib
Brett Cannonbefb14f2009-02-10 02:10:16 +00009import py_compile
10import shutil
11import struct
Lumír 'Frenzy' Balhare77d4282020-05-14 16:17:22 +020012import sys
Brett Cannonbefb14f2009-02-10 02:10:16 +000013import tempfile
Lumír 'Frenzy' Balhare77d4282020-05-14 16:17:22 +020014import test.test_importlib.util
R. David Murray650f1472010-11-20 21:18:51 +000015import time
Brett Cannonbefb14f2009-02-10 02:10:16 +000016import unittest
17
Brett Cannonf1a8df02014-09-12 10:39:48 -040018from unittest import mock, skipUnless
19try:
20 from concurrent.futures import ProcessPoolExecutor
21 _have_multiprocessing = True
22except ImportError:
23 _have_multiprocessing = False
24
Berker Peksagce643912015-05-06 06:33:17 +030025from test import support
Hai Shi883bc632020-07-06 17:12:49 +080026from test.support import os_helper
Berker Peksagce643912015-05-06 06:33:17 +030027from test.support import script_helper
Brett Cannonbefb14f2009-02-10 02:10:16 +000028
Elvis Pranskevichusa6b3ec52018-10-10 12:43:14 -040029from .test_py_compile import without_source_date_epoch
30from .test_py_compile import SourceDateEpochTestMeta
31
32
Lumír 'Frenzy' Balhare77d4282020-05-14 16:17:22 +020033def get_pyc(script, opt):
34 if not opt:
35 # Replace None and 0 with ''
36 opt = ''
37 return importlib.util.cache_from_source(script, optimization=opt)
38
39
40def get_pycs(script):
41 return [get_pyc(script, opt) for opt in (0, 1, 2)]
42
43
44def is_hardlink(filename1, filename2):
45 """Returns True if two files have the same inode (hardlink)"""
46 inode1 = os.stat(filename1).st_ino
47 inode2 = os.stat(filename2).st_ino
48 return inode1 == inode2
49
50
Elvis Pranskevichusa6b3ec52018-10-10 12:43:14 -040051class CompileallTestsBase:
Brett Cannonbefb14f2009-02-10 02:10:16 +000052
53 def setUp(self):
54 self.directory = tempfile.mkdtemp()
55 self.source_path = os.path.join(self.directory, '_test.py')
Brett Cannon7822e122013-06-14 23:04:02 -040056 self.bc_path = importlib.util.cache_from_source(self.source_path)
Brett Cannonbefb14f2009-02-10 02:10:16 +000057 with open(self.source_path, 'w') as file:
58 file.write('x = 123\n')
Matthias Klosec33b9022010-03-16 00:36:26 +000059 self.source_path2 = os.path.join(self.directory, '_test2.py')
Brett Cannon7822e122013-06-14 23:04:02 -040060 self.bc_path2 = importlib.util.cache_from_source(self.source_path2)
Matthias Klosec33b9022010-03-16 00:36:26 +000061 shutil.copyfile(self.source_path, self.source_path2)
Georg Brandl45438462011-02-07 12:36:54 +000062 self.subdirectory = os.path.join(self.directory, '_subdir')
63 os.mkdir(self.subdirectory)
64 self.source_path3 = os.path.join(self.subdirectory, '_test3.py')
65 shutil.copyfile(self.source_path, self.source_path3)
Brett Cannonbefb14f2009-02-10 02:10:16 +000066
67 def tearDown(self):
68 shutil.rmtree(self.directory)
69
Brett Cannon1e3c3e92015-12-27 13:17:04 -080070 def add_bad_source_file(self):
71 self.bad_source_path = os.path.join(self.directory, '_test_bad.py')
72 with open(self.bad_source_path, 'w') as file:
73 file.write('x (\n')
74
Elvis Pranskevichusa6b3ec52018-10-10 12:43:14 -040075 def timestamp_metadata(self):
Brett Cannonbefb14f2009-02-10 02:10:16 +000076 with open(self.bc_path, 'rb') as file:
Benjamin Peterson42aa93b2017-12-09 10:26:52 -080077 data = file.read(12)
Brett Cannonbefb14f2009-02-10 02:10:16 +000078 mtime = int(os.stat(self.source_path).st_mtime)
Benjamin Peterson42aa93b2017-12-09 10:26:52 -080079 compare = struct.pack('<4sll', importlib.util.MAGIC_NUMBER, 0, mtime)
Brett Cannonbefb14f2009-02-10 02:10:16 +000080 return data, compare
81
82 def recreation_check(self, metadata):
83 """Check that compileall recreates bytecode when the new metadata is
84 used."""
Elvis Pranskevichusa6b3ec52018-10-10 12:43:14 -040085 if os.environ.get('SOURCE_DATE_EPOCH'):
86 raise unittest.SkipTest('SOURCE_DATE_EPOCH is set')
Brett Cannonbefb14f2009-02-10 02:10:16 +000087 py_compile.compile(self.source_path)
Elvis Pranskevichusa6b3ec52018-10-10 12:43:14 -040088 self.assertEqual(*self.timestamp_metadata())
Brett Cannonbefb14f2009-02-10 02:10:16 +000089 with open(self.bc_path, 'rb') as file:
90 bc = file.read()[len(metadata):]
91 with open(self.bc_path, 'wb') as file:
92 file.write(metadata)
93 file.write(bc)
Elvis Pranskevichusa6b3ec52018-10-10 12:43:14 -040094 self.assertNotEqual(*self.timestamp_metadata())
Brett Cannonbefb14f2009-02-10 02:10:16 +000095 compileall.compile_dir(self.directory, force=False, quiet=True)
Elvis Pranskevichusa6b3ec52018-10-10 12:43:14 -040096 self.assertTrue(*self.timestamp_metadata())
Brett Cannonbefb14f2009-02-10 02:10:16 +000097
98 def test_mtime(self):
99 # Test a change in mtime leads to a new .pyc.
Benjamin Peterson42aa93b2017-12-09 10:26:52 -0800100 self.recreation_check(struct.pack('<4sll', importlib.util.MAGIC_NUMBER,
101 0, 1))
Brett Cannonbefb14f2009-02-10 02:10:16 +0000102
103 def test_magic_number(self):
104 # Test a change in mtime leads to a new .pyc.
105 self.recreation_check(b'\0\0\0\0')
106
Matthias Klosec33b9022010-03-16 00:36:26 +0000107 def test_compile_files(self):
108 # Test compiling a single file, and complete directory
109 for fn in (self.bc_path, self.bc_path2):
110 try:
111 os.unlink(fn)
112 except:
113 pass
Brett Cannon1e3c3e92015-12-27 13:17:04 -0800114 self.assertTrue(compileall.compile_file(self.source_path,
115 force=False, quiet=True))
Barry Warsaw28a691b2010-04-17 00:19:56 +0000116 self.assertTrue(os.path.isfile(self.bc_path) and
117 not os.path.isfile(self.bc_path2))
Matthias Klosec33b9022010-03-16 00:36:26 +0000118 os.unlink(self.bc_path)
Brett Cannon1e3c3e92015-12-27 13:17:04 -0800119 self.assertTrue(compileall.compile_dir(self.directory, force=False,
120 quiet=True))
Barry Warsaw28a691b2010-04-17 00:19:56 +0000121 self.assertTrue(os.path.isfile(self.bc_path) and
122 os.path.isfile(self.bc_path2))
Matthias Klosec33b9022010-03-16 00:36:26 +0000123 os.unlink(self.bc_path)
124 os.unlink(self.bc_path2)
Brett Cannon1e3c3e92015-12-27 13:17:04 -0800125 # Test against bad files
126 self.add_bad_source_file()
127 self.assertFalse(compileall.compile_file(self.bad_source_path,
128 force=False, quiet=2))
129 self.assertFalse(compileall.compile_dir(self.directory,
130 force=False, quiet=2))
131
Berker Peksag812a2b62016-10-01 00:54:18 +0300132 def test_compile_file_pathlike(self):
133 self.assertFalse(os.path.isfile(self.bc_path))
134 # we should also test the output
135 with support.captured_stdout() as stdout:
136 self.assertTrue(compileall.compile_file(pathlib.Path(self.source_path)))
Berker Peksagd8e97132016-10-01 02:44:37 +0300137 self.assertRegex(stdout.getvalue(), r'Compiling ([^WindowsPath|PosixPath].*)')
Berker Peksag812a2b62016-10-01 00:54:18 +0300138 self.assertTrue(os.path.isfile(self.bc_path))
139
140 def test_compile_file_pathlike_ddir(self):
141 self.assertFalse(os.path.isfile(self.bc_path))
142 self.assertTrue(compileall.compile_file(pathlib.Path(self.source_path),
143 ddir=pathlib.Path('ddir_path'),
144 quiet=2))
145 self.assertTrue(os.path.isfile(self.bc_path))
146
Brett Cannon1e3c3e92015-12-27 13:17:04 -0800147 def test_compile_path(self):
Berker Peksag408b78c2016-09-28 17:38:53 +0300148 with test.test_importlib.util.import_state(path=[self.directory]):
149 self.assertTrue(compileall.compile_path(quiet=2))
Brett Cannon1e3c3e92015-12-27 13:17:04 -0800150
151 with test.test_importlib.util.import_state(path=[self.directory]):
152 self.add_bad_source_file()
153 self.assertFalse(compileall.compile_path(skip_curdir=False,
154 force=True, quiet=2))
Brett Cannonbefb14f2009-02-10 02:10:16 +0000155
Barry Warsawc8a99de2010-04-29 18:43:10 +0000156 def test_no_pycache_in_non_package(self):
157 # Bug 8563 reported that __pycache__ directories got created by
158 # compile_file() for non-.py files.
159 data_dir = os.path.join(self.directory, 'data')
160 data_file = os.path.join(data_dir, 'file')
161 os.mkdir(data_dir)
162 # touch data/file
163 with open(data_file, 'w'):
164 pass
165 compileall.compile_file(data_file)
166 self.assertFalse(os.path.exists(os.path.join(data_dir, '__pycache__')))
167
Georg Brandl8334fd92010-12-04 10:26:46 +0000168 def test_optimize(self):
169 # make sure compiling with different optimization settings than the
170 # interpreter's creates the correct file names
Brett Cannonf299abd2015-04-13 14:21:02 -0400171 optimize, opt = (1, 1) if __debug__ else (0, '')
Georg Brandl8334fd92010-12-04 10:26:46 +0000172 compileall.compile_dir(self.directory, quiet=True, optimize=optimize)
Brett Cannon7822e122013-06-14 23:04:02 -0400173 cached = importlib.util.cache_from_source(self.source_path,
Brett Cannonf299abd2015-04-13 14:21:02 -0400174 optimization=opt)
Georg Brandl8334fd92010-12-04 10:26:46 +0000175 self.assertTrue(os.path.isfile(cached))
Brett Cannon7822e122013-06-14 23:04:02 -0400176 cached2 = importlib.util.cache_from_source(self.source_path2,
Brett Cannonf299abd2015-04-13 14:21:02 -0400177 optimization=opt)
Georg Brandl45438462011-02-07 12:36:54 +0000178 self.assertTrue(os.path.isfile(cached2))
Brett Cannon7822e122013-06-14 23:04:02 -0400179 cached3 = importlib.util.cache_from_source(self.source_path3,
Brett Cannonf299abd2015-04-13 14:21:02 -0400180 optimization=opt)
Georg Brandl45438462011-02-07 12:36:54 +0000181 self.assertTrue(os.path.isfile(cached3))
Georg Brandl8334fd92010-12-04 10:26:46 +0000182
Berker Peksag812a2b62016-10-01 00:54:18 +0300183 def test_compile_dir_pathlike(self):
184 self.assertFalse(os.path.isfile(self.bc_path))
185 with support.captured_stdout() as stdout:
186 compileall.compile_dir(pathlib.Path(self.directory))
Berker Peksagd8e97132016-10-01 02:44:37 +0300187 line = stdout.getvalue().splitlines()[0]
188 self.assertRegex(line, r'Listing ([^WindowsPath|PosixPath].*)')
Berker Peksag812a2b62016-10-01 00:54:18 +0300189 self.assertTrue(os.path.isfile(self.bc_path))
190
Dustin Spicuzza1d817e42018-11-23 12:06:55 -0500191 @mock.patch('concurrent.futures.ProcessPoolExecutor')
Brett Cannonf1a8df02014-09-12 10:39:48 -0400192 def test_compile_pool_called(self, pool_mock):
193 compileall.compile_dir(self.directory, quiet=True, workers=5)
194 self.assertTrue(pool_mock.called)
195
196 def test_compile_workers_non_positive(self):
197 with self.assertRaisesRegex(ValueError,
198 "workers must be greater or equal to 0"):
199 compileall.compile_dir(self.directory, workers=-1)
200
Dustin Spicuzza1d817e42018-11-23 12:06:55 -0500201 @mock.patch('concurrent.futures.ProcessPoolExecutor')
Brett Cannonf1a8df02014-09-12 10:39:48 -0400202 def test_compile_workers_cpu_count(self, pool_mock):
203 compileall.compile_dir(self.directory, quiet=True, workers=0)
204 self.assertEqual(pool_mock.call_args[1]['max_workers'], None)
205
Dustin Spicuzza1d817e42018-11-23 12:06:55 -0500206 @mock.patch('concurrent.futures.ProcessPoolExecutor')
Brett Cannonf1a8df02014-09-12 10:39:48 -0400207 @mock.patch('compileall.compile_file')
208 def test_compile_one_worker(self, compile_file_mock, pool_mock):
209 compileall.compile_dir(self.directory, quiet=True)
210 self.assertFalse(pool_mock.called)
211 self.assertTrue(compile_file_mock.called)
212
Dustin Spicuzza1d817e42018-11-23 12:06:55 -0500213 @mock.patch('concurrent.futures.ProcessPoolExecutor', new=None)
Berker Peksagd86ef052015-04-22 09:39:19 +0300214 @mock.patch('compileall.compile_file')
215 def test_compile_missing_multiprocessing(self, compile_file_mock):
216 compileall.compile_dir(self.directory, quiet=True, workers=5)
217 self.assertTrue(compile_file_mock.called)
Barry Warsaw28a691b2010-04-17 00:19:56 +0000218
Petr Viktorin4267c982019-09-26 11:53:51 +0200219 def test_compile_dir_maxlevels(self):
Victor Stinnereb1dda22019-10-15 11:26:13 +0200220 # Test the actual impact of maxlevels parameter
221 depth = 3
222 path = self.directory
223 for i in range(1, depth + 1):
224 path = os.path.join(path, f"dir_{i}")
225 source = os.path.join(path, 'script.py')
226 os.mkdir(path)
227 shutil.copyfile(self.source_path, source)
228 pyc_filename = importlib.util.cache_from_source(source)
229
230 compileall.compile_dir(self.directory, quiet=True, maxlevels=depth - 1)
231 self.assertFalse(os.path.isfile(pyc_filename))
232
233 compileall.compile_dir(self.directory, quiet=True, maxlevels=depth)
234 self.assertTrue(os.path.isfile(pyc_filename))
Lumír 'Frenzy' Balhar8e7bb992019-09-26 08:28:26 +0200235
Gregory P. Smith02673352020-02-28 17:28:37 -0800236 def _test_ddir_only(self, *, ddir, parallel=True):
237 """Recursive compile_dir ddir must contain package paths; bpo39769."""
238 fullpath = ["test", "foo"]
239 path = self.directory
240 mods = []
241 for subdir in fullpath:
242 path = os.path.join(path, subdir)
243 os.mkdir(path)
244 script_helper.make_script(path, "__init__", "")
245 mods.append(script_helper.make_script(path, "mod",
246 "def fn(): 1/0\nfn()\n"))
247 compileall.compile_dir(
248 self.directory, quiet=True, ddir=ddir,
249 workers=2 if parallel else 1)
250 self.assertTrue(mods)
251 for mod in mods:
252 self.assertTrue(mod.startswith(self.directory), mod)
253 modcode = importlib.util.cache_from_source(mod)
254 modpath = mod[len(self.directory+os.sep):]
255 _, _, err = script_helper.assert_python_failure(modcode)
256 expected_in = os.path.join(ddir, modpath)
257 mod_code_obj = test.test_importlib.util.get_code_from_pyc(modcode)
258 self.assertEqual(mod_code_obj.co_filename, expected_in)
259 self.assertIn(f'"{expected_in}"', os.fsdecode(err))
260
261 def test_ddir_only_one_worker(self):
262 """Recursive compile_dir ddir= contains package paths; bpo39769."""
263 return self._test_ddir_only(ddir="<a prefix>", parallel=False)
264
265 def test_ddir_multiple_workers(self):
266 """Recursive compile_dir ddir= contains package paths; bpo39769."""
267 return self._test_ddir_only(ddir="<a prefix>", parallel=True)
268
269 def test_ddir_empty_only_one_worker(self):
270 """Recursive compile_dir ddir='' contains package paths; bpo39769."""
271 return self._test_ddir_only(ddir="", parallel=False)
272
273 def test_ddir_empty_multiple_workers(self):
274 """Recursive compile_dir ddir='' contains package paths; bpo39769."""
275 return self._test_ddir_only(ddir="", parallel=True)
276
Lumír 'Frenzy' Balhar8e7bb992019-09-26 08:28:26 +0200277 def test_strip_only(self):
278 fullpath = ["test", "build", "real", "path"]
279 path = os.path.join(self.directory, *fullpath)
280 os.makedirs(path)
281 script = script_helper.make_script(path, "test", "1 / 0")
282 bc = importlib.util.cache_from_source(script)
283 stripdir = os.path.join(self.directory, *fullpath[:2])
284 compileall.compile_dir(path, quiet=True, stripdir=stripdir)
285 rc, out, err = script_helper.assert_python_failure(bc)
286 expected_in = os.path.join(*fullpath[2:])
287 self.assertIn(
288 expected_in,
289 str(err, encoding=sys.getdefaultencoding())
290 )
291 self.assertNotIn(
292 stripdir,
293 str(err, encoding=sys.getdefaultencoding())
294 )
295
296 def test_prepend_only(self):
297 fullpath = ["test", "build", "real", "path"]
298 path = os.path.join(self.directory, *fullpath)
299 os.makedirs(path)
300 script = script_helper.make_script(path, "test", "1 / 0")
301 bc = importlib.util.cache_from_source(script)
302 prependdir = "/foo"
303 compileall.compile_dir(path, quiet=True, prependdir=prependdir)
304 rc, out, err = script_helper.assert_python_failure(bc)
305 expected_in = os.path.join(prependdir, self.directory, *fullpath)
306 self.assertIn(
307 expected_in,
308 str(err, encoding=sys.getdefaultencoding())
309 )
310
311 def test_strip_and_prepend(self):
312 fullpath = ["test", "build", "real", "path"]
313 path = os.path.join(self.directory, *fullpath)
314 os.makedirs(path)
315 script = script_helper.make_script(path, "test", "1 / 0")
316 bc = importlib.util.cache_from_source(script)
317 stripdir = os.path.join(self.directory, *fullpath[:2])
318 prependdir = "/foo"
319 compileall.compile_dir(path, quiet=True,
320 stripdir=stripdir, prependdir=prependdir)
321 rc, out, err = script_helper.assert_python_failure(bc)
322 expected_in = os.path.join(prependdir, *fullpath[2:])
323 self.assertIn(
324 expected_in,
325 str(err, encoding=sys.getdefaultencoding())
326 )
327 self.assertNotIn(
328 stripdir,
329 str(err, encoding=sys.getdefaultencoding())
330 )
331
332 def test_strip_prepend_and_ddir(self):
333 fullpath = ["test", "build", "real", "path", "ddir"]
334 path = os.path.join(self.directory, *fullpath)
335 os.makedirs(path)
336 script_helper.make_script(path, "test", "1 / 0")
337 with self.assertRaises(ValueError):
338 compileall.compile_dir(path, quiet=True, ddir="/bar",
339 stripdir="/foo", prependdir="/bar")
340
341 def test_multiple_optimization_levels(self):
342 script = script_helper.make_script(self.directory,
343 "test_optimization",
344 "a = 0")
345 bc = []
346 for opt_level in "", 1, 2, 3:
347 bc.append(importlib.util.cache_from_source(script,
348 optimization=opt_level))
349 test_combinations = [[0, 1], [1, 2], [0, 2], [0, 1, 2]]
350 for opt_combination in test_combinations:
351 compileall.compile_file(script, quiet=True,
352 optimize=opt_combination)
353 for opt_level in opt_combination:
354 self.assertTrue(os.path.isfile(bc[opt_level]))
355 try:
356 os.unlink(bc[opt_level])
357 except Exception:
358 pass
359
Hai Shi883bc632020-07-06 17:12:49 +0800360 @os_helper.skip_unless_symlink
Lumír 'Frenzy' Balhar8e7bb992019-09-26 08:28:26 +0200361 def test_ignore_symlink_destination(self):
362 # Create folders for allowed files, symlinks and prohibited area
363 allowed_path = os.path.join(self.directory, "test", "dir", "allowed")
364 symlinks_path = os.path.join(self.directory, "test", "dir", "symlinks")
365 prohibited_path = os.path.join(self.directory, "test", "dir", "prohibited")
366 os.makedirs(allowed_path)
367 os.makedirs(symlinks_path)
368 os.makedirs(prohibited_path)
369
370 # Create scripts and symlinks and remember their byte-compiled versions
371 allowed_script = script_helper.make_script(allowed_path, "test_allowed", "a = 0")
372 prohibited_script = script_helper.make_script(prohibited_path, "test_prohibited", "a = 0")
373 allowed_symlink = os.path.join(symlinks_path, "test_allowed.py")
374 prohibited_symlink = os.path.join(symlinks_path, "test_prohibited.py")
375 os.symlink(allowed_script, allowed_symlink)
376 os.symlink(prohibited_script, prohibited_symlink)
377 allowed_bc = importlib.util.cache_from_source(allowed_symlink)
378 prohibited_bc = importlib.util.cache_from_source(prohibited_symlink)
379
380 compileall.compile_dir(symlinks_path, quiet=True, limit_sl_dest=allowed_path)
381
382 self.assertTrue(os.path.isfile(allowed_bc))
383 self.assertFalse(os.path.isfile(prohibited_bc))
384
Elvis Pranskevichusa6b3ec52018-10-10 12:43:14 -0400385
386class CompileallTestsWithSourceEpoch(CompileallTestsBase,
387 unittest.TestCase,
388 metaclass=SourceDateEpochTestMeta,
389 source_date_epoch=True):
390 pass
391
392
393class CompileallTestsWithoutSourceEpoch(CompileallTestsBase,
394 unittest.TestCase,
395 metaclass=SourceDateEpochTestMeta,
396 source_date_epoch=False):
397 pass
398
399
Martin v. Löwis4b003072010-03-16 13:19:21 +0000400class EncodingTest(unittest.TestCase):
Barry Warsaw28a691b2010-04-17 00:19:56 +0000401 """Issue 6716: compileall should escape source code when printing errors
402 to stdout."""
Martin v. Löwis4b003072010-03-16 13:19:21 +0000403
404 def setUp(self):
405 self.directory = tempfile.mkdtemp()
406 self.source_path = os.path.join(self.directory, '_test.py')
407 with open(self.source_path, 'w', encoding='utf-8') as file:
408 file.write('# -*- coding: utf-8 -*-\n')
409 file.write('print u"\u20ac"\n')
410
411 def tearDown(self):
412 shutil.rmtree(self.directory)
413
414 def test_error(self):
415 try:
416 orig_stdout = sys.stdout
417 sys.stdout = io.TextIOWrapper(io.BytesIO(),encoding='ascii')
418 compileall.compile_dir(self.directory)
419 finally:
420 sys.stdout = orig_stdout
421
Barry Warsawc8a99de2010-04-29 18:43:10 +0000422
Elvis Pranskevichusa6b3ec52018-10-10 12:43:14 -0400423class CommandLineTestsBase:
R. David Murray650f1472010-11-20 21:18:51 +0000424 """Test compileall's CLI."""
Barry Warsaw28a691b2010-04-17 00:19:56 +0000425
Brett Cannon65ed7502015-10-09 15:09:43 -0700426 @classmethod
427 def setUpClass(cls):
428 for path in filter(os.path.isdir, sys.path):
429 directory_created = False
430 directory = pathlib.Path(path) / '__pycache__'
431 path = directory / 'test.try'
432 try:
433 if not directory.is_dir():
434 directory.mkdir()
435 directory_created = True
436 with path.open('w') as file:
437 file.write('# for test_compileall')
438 except OSError:
439 sys_path_writable = False
440 break
441 finally:
Hai Shi883bc632020-07-06 17:12:49 +0800442 os_helper.unlink(str(path))
Brett Cannon65ed7502015-10-09 15:09:43 -0700443 if directory_created:
444 directory.rmdir()
445 else:
446 sys_path_writable = True
447 cls._sys_path_writable = sys_path_writable
448
449 def _skip_if_sys_path_not_writable(self):
450 if not self._sys_path_writable:
451 raise unittest.SkipTest('not all entries on sys.path are writable')
452
Benjamin Petersona820c7c2012-09-25 11:42:35 -0400453 def _get_run_args(self, args):
Victor Stinner9def2842016-01-18 12:15:08 +0100454 return [*support.optim_args_from_interpreter_flags(),
455 '-S', '-m', 'compileall',
456 *args]
Benjamin Petersona820c7c2012-09-25 11:42:35 -0400457
R. David Murray5317e9c2010-12-16 19:08:51 +0000458 def assertRunOK(self, *args, **env_vars):
459 rc, out, err = script_helper.assert_python_ok(
Serhiy Storchaka700cfa82020-06-25 17:56:31 +0300460 *self._get_run_args(args), **env_vars,
461 PYTHONIOENCODING='utf-8')
R. David Murray95333e32010-12-14 22:32:50 +0000462 self.assertEqual(b'', err)
463 return out
464
R. David Murray5317e9c2010-12-16 19:08:51 +0000465 def assertRunNotOK(self, *args, **env_vars):
R. David Murray95333e32010-12-14 22:32:50 +0000466 rc, out, err = script_helper.assert_python_failure(
Serhiy Storchaka700cfa82020-06-25 17:56:31 +0300467 *self._get_run_args(args), **env_vars,
468 PYTHONIOENCODING='utf-8')
R. David Murray95333e32010-12-14 22:32:50 +0000469 return rc, out, err
470
471 def assertCompiled(self, fn):
Brett Cannon7822e122013-06-14 23:04:02 -0400472 path = importlib.util.cache_from_source(fn)
473 self.assertTrue(os.path.exists(path))
R. David Murray95333e32010-12-14 22:32:50 +0000474
475 def assertNotCompiled(self, fn):
Brett Cannon7822e122013-06-14 23:04:02 -0400476 path = importlib.util.cache_from_source(fn)
477 self.assertFalse(os.path.exists(path))
R. David Murray95333e32010-12-14 22:32:50 +0000478
Barry Warsaw28a691b2010-04-17 00:19:56 +0000479 def setUp(self):
Barry Warsaw28a691b2010-04-17 00:19:56 +0000480 self.directory = tempfile.mkdtemp()
Hai Shi883bc632020-07-06 17:12:49 +0800481 self.addCleanup(os_helper.rmtree, self.directory)
Barry Warsaw28a691b2010-04-17 00:19:56 +0000482 self.pkgdir = os.path.join(self.directory, 'foo')
483 os.mkdir(self.pkgdir)
R. David Murray95333e32010-12-14 22:32:50 +0000484 self.pkgdir_cachedir = os.path.join(self.pkgdir, '__pycache__')
485 # Create the __init__.py and a package module.
486 self.initfn = script_helper.make_script(self.pkgdir, '__init__', '')
487 self.barfn = script_helper.make_script(self.pkgdir, 'bar', '')
Barry Warsaw28a691b2010-04-17 00:19:56 +0000488
R. David Murray5317e9c2010-12-16 19:08:51 +0000489 def test_no_args_compiles_path(self):
490 # Note that -l is implied for the no args case.
Brett Cannon65ed7502015-10-09 15:09:43 -0700491 self._skip_if_sys_path_not_writable()
R. David Murray5317e9c2010-12-16 19:08:51 +0000492 bazfn = script_helper.make_script(self.directory, 'baz', '')
493 self.assertRunOK(PYTHONPATH=self.directory)
494 self.assertCompiled(bazfn)
495 self.assertNotCompiled(self.initfn)
496 self.assertNotCompiled(self.barfn)
Barry Warsaw28a691b2010-04-17 00:19:56 +0000497
Elvis Pranskevichusa6b3ec52018-10-10 12:43:14 -0400498 @without_source_date_epoch # timestamp invalidation test
R David Murray8a1d1e62013-12-15 20:49:38 -0500499 def test_no_args_respects_force_flag(self):
Brett Cannon65ed7502015-10-09 15:09:43 -0700500 self._skip_if_sys_path_not_writable()
R David Murray8a1d1e62013-12-15 20:49:38 -0500501 bazfn = script_helper.make_script(self.directory, 'baz', '')
502 self.assertRunOK(PYTHONPATH=self.directory)
R David Murray755d5ea2013-12-15 20:56:00 -0500503 pycpath = importlib.util.cache_from_source(bazfn)
R David Murray8a1d1e62013-12-15 20:49:38 -0500504 # Set atime/mtime backward to avoid file timestamp resolution issues
505 os.utime(pycpath, (time.time()-60,)*2)
506 mtime = os.stat(pycpath).st_mtime
507 # Without force, no recompilation
508 self.assertRunOK(PYTHONPATH=self.directory)
509 mtime2 = os.stat(pycpath).st_mtime
510 self.assertEqual(mtime, mtime2)
511 # Now force it.
512 self.assertRunOK('-f', PYTHONPATH=self.directory)
513 mtime2 = os.stat(pycpath).st_mtime
514 self.assertNotEqual(mtime, mtime2)
515
516 def test_no_args_respects_quiet_flag(self):
Brett Cannon65ed7502015-10-09 15:09:43 -0700517 self._skip_if_sys_path_not_writable()
R David Murray8a1d1e62013-12-15 20:49:38 -0500518 script_helper.make_script(self.directory, 'baz', '')
519 noisy = self.assertRunOK(PYTHONPATH=self.directory)
520 self.assertIn(b'Listing ', noisy)
521 quiet = self.assertRunOK('-q', PYTHONPATH=self.directory)
522 self.assertNotIn(b'Listing ', quiet)
523
Georg Brandl1463a3f2010-10-14 07:42:27 +0000524 # Ensure that the default behavior of compileall's CLI is to create
Brett Cannonf299abd2015-04-13 14:21:02 -0400525 # PEP 3147/PEP 488 pyc files.
Georg Brandl1463a3f2010-10-14 07:42:27 +0000526 for name, ext, switch in [
527 ('normal', 'pyc', []),
Brett Cannonf299abd2015-04-13 14:21:02 -0400528 ('optimize', 'opt-1.pyc', ['-O']),
529 ('doubleoptimize', 'opt-2.pyc', ['-OO']),
Georg Brandl1463a3f2010-10-14 07:42:27 +0000530 ]:
531 def f(self, ext=ext, switch=switch):
R. David Murray95333e32010-12-14 22:32:50 +0000532 script_helper.assert_python_ok(*(switch +
533 ['-m', 'compileall', '-q', self.pkgdir]))
Georg Brandl1463a3f2010-10-14 07:42:27 +0000534 # Verify the __pycache__ directory contents.
R. David Murray95333e32010-12-14 22:32:50 +0000535 self.assertTrue(os.path.exists(self.pkgdir_cachedir))
Brett Cannon7822e122013-06-14 23:04:02 -0400536 expected = sorted(base.format(sys.implementation.cache_tag, ext)
537 for base in ('__init__.{}.{}', 'bar.{}.{}'))
R. David Murray95333e32010-12-14 22:32:50 +0000538 self.assertEqual(sorted(os.listdir(self.pkgdir_cachedir)), expected)
Georg Brandl1463a3f2010-10-14 07:42:27 +0000539 # Make sure there are no .pyc files in the source directory.
R. David Murray95333e32010-12-14 22:32:50 +0000540 self.assertFalse([fn for fn in os.listdir(self.pkgdir)
541 if fn.endswith(ext)])
Georg Brandl1463a3f2010-10-14 07:42:27 +0000542 locals()['test_pep3147_paths_' + name] = f
Barry Warsaw28a691b2010-04-17 00:19:56 +0000543
544 def test_legacy_paths(self):
545 # Ensure that with the proper switch, compileall leaves legacy
Brett Cannonf299abd2015-04-13 14:21:02 -0400546 # pyc files, and no __pycache__ directory.
R. David Murray95333e32010-12-14 22:32:50 +0000547 self.assertRunOK('-b', '-q', self.pkgdir)
Barry Warsaw28a691b2010-04-17 00:19:56 +0000548 # Verify the __pycache__ directory contents.
R. David Murray95333e32010-12-14 22:32:50 +0000549 self.assertFalse(os.path.exists(self.pkgdir_cachedir))
Brett Cannonf299abd2015-04-13 14:21:02 -0400550 expected = sorted(['__init__.py', '__init__.pyc', 'bar.py',
551 'bar.pyc'])
Barry Warsaw28a691b2010-04-17 00:19:56 +0000552 self.assertEqual(sorted(os.listdir(self.pkgdir)), expected)
553
Barry Warsawc04317f2010-04-26 15:59:03 +0000554 def test_multiple_runs(self):
555 # Bug 8527 reported that multiple calls produced empty
556 # __pycache__/__pycache__ directories.
R. David Murray95333e32010-12-14 22:32:50 +0000557 self.assertRunOK('-q', self.pkgdir)
Barry Warsawc04317f2010-04-26 15:59:03 +0000558 # Verify the __pycache__ directory contents.
R. David Murray95333e32010-12-14 22:32:50 +0000559 self.assertTrue(os.path.exists(self.pkgdir_cachedir))
560 cachecachedir = os.path.join(self.pkgdir_cachedir, '__pycache__')
Barry Warsawc04317f2010-04-26 15:59:03 +0000561 self.assertFalse(os.path.exists(cachecachedir))
562 # Call compileall again.
R. David Murray95333e32010-12-14 22:32:50 +0000563 self.assertRunOK('-q', self.pkgdir)
564 self.assertTrue(os.path.exists(self.pkgdir_cachedir))
Barry Warsawc04317f2010-04-26 15:59:03 +0000565 self.assertFalse(os.path.exists(cachecachedir))
566
Elvis Pranskevichusa6b3ec52018-10-10 12:43:14 -0400567 @without_source_date_epoch # timestamp invalidation test
R. David Murray650f1472010-11-20 21:18:51 +0000568 def test_force(self):
R. David Murray95333e32010-12-14 22:32:50 +0000569 self.assertRunOK('-q', self.pkgdir)
Brett Cannon7822e122013-06-14 23:04:02 -0400570 pycpath = importlib.util.cache_from_source(self.barfn)
R. David Murray650f1472010-11-20 21:18:51 +0000571 # set atime/mtime backward to avoid file timestamp resolution issues
572 os.utime(pycpath, (time.time()-60,)*2)
R. David Murray95333e32010-12-14 22:32:50 +0000573 mtime = os.stat(pycpath).st_mtime
574 # without force, no recompilation
575 self.assertRunOK('-q', self.pkgdir)
576 mtime2 = os.stat(pycpath).st_mtime
577 self.assertEqual(mtime, mtime2)
578 # now force it.
579 self.assertRunOK('-q', '-f', self.pkgdir)
580 mtime2 = os.stat(pycpath).st_mtime
581 self.assertNotEqual(mtime, mtime2)
R. David Murray650f1472010-11-20 21:18:51 +0000582
R. David Murray95333e32010-12-14 22:32:50 +0000583 def test_recursion_control(self):
584 subpackage = os.path.join(self.pkgdir, 'spam')
585 os.mkdir(subpackage)
586 subinitfn = script_helper.make_script(subpackage, '__init__', '')
587 hamfn = script_helper.make_script(subpackage, 'ham', '')
588 self.assertRunOK('-q', '-l', self.pkgdir)
589 self.assertNotCompiled(subinitfn)
590 self.assertFalse(os.path.exists(os.path.join(subpackage, '__pycache__')))
591 self.assertRunOK('-q', self.pkgdir)
592 self.assertCompiled(subinitfn)
593 self.assertCompiled(hamfn)
R. David Murray650f1472010-11-20 21:18:51 +0000594
Benjamin Peterson344ff4a2014-08-19 16:13:26 -0500595 def test_recursion_limit(self):
596 subpackage = os.path.join(self.pkgdir, 'spam')
597 subpackage2 = os.path.join(subpackage, 'ham')
598 subpackage3 = os.path.join(subpackage2, 'eggs')
599 for pkg in (subpackage, subpackage2, subpackage3):
600 script_helper.make_pkg(pkg)
601
602 subinitfn = os.path.join(subpackage, '__init__.py')
603 hamfn = script_helper.make_script(subpackage, 'ham', '')
604 spamfn = script_helper.make_script(subpackage2, 'spam', '')
605 eggfn = script_helper.make_script(subpackage3, 'egg', '')
606
607 self.assertRunOK('-q', '-r 0', self.pkgdir)
608 self.assertNotCompiled(subinitfn)
609 self.assertFalse(
610 os.path.exists(os.path.join(subpackage, '__pycache__')))
611
612 self.assertRunOK('-q', '-r 1', self.pkgdir)
613 self.assertCompiled(subinitfn)
614 self.assertCompiled(hamfn)
615 self.assertNotCompiled(spamfn)
616
617 self.assertRunOK('-q', '-r 2', self.pkgdir)
618 self.assertCompiled(subinitfn)
619 self.assertCompiled(hamfn)
620 self.assertCompiled(spamfn)
621 self.assertNotCompiled(eggfn)
622
623 self.assertRunOK('-q', '-r 5', self.pkgdir)
624 self.assertCompiled(subinitfn)
625 self.assertCompiled(hamfn)
626 self.assertCompiled(spamfn)
627 self.assertCompiled(eggfn)
628
Hai Shi883bc632020-07-06 17:12:49 +0800629 @os_helper.skip_unless_symlink
Lumír 'Frenzy' Balhar8e7bb992019-09-26 08:28:26 +0200630 def test_symlink_loop(self):
631 # Currently, compileall ignores symlinks to directories.
632 # If that limitation is ever lifted, it should protect against
633 # recursion in symlink loops.
634 pkg = os.path.join(self.pkgdir, 'spam')
635 script_helper.make_pkg(pkg)
636 os.symlink('.', os.path.join(pkg, 'evil'))
637 os.symlink('.', os.path.join(pkg, 'evil2'))
638 self.assertRunOK('-q', self.pkgdir)
639 self.assertCompiled(os.path.join(
640 self.pkgdir, 'spam', 'evil', 'evil2', '__init__.py'
641 ))
642
R. David Murray650f1472010-11-20 21:18:51 +0000643 def test_quiet(self):
R. David Murray95333e32010-12-14 22:32:50 +0000644 noisy = self.assertRunOK(self.pkgdir)
645 quiet = self.assertRunOK('-q', self.pkgdir)
646 self.assertNotEqual(b'', noisy)
647 self.assertEqual(b'', quiet)
R. David Murray650f1472010-11-20 21:18:51 +0000648
Berker Peksag6554b862014-10-15 11:10:57 +0300649 def test_silent(self):
650 script_helper.make_script(self.pkgdir, 'crunchyfrog', 'bad(syntax')
651 _, quiet, _ = self.assertRunNotOK('-q', self.pkgdir)
652 _, silent, _ = self.assertRunNotOK('-qq', self.pkgdir)
653 self.assertNotEqual(b'', quiet)
654 self.assertEqual(b'', silent)
655
R. David Murray650f1472010-11-20 21:18:51 +0000656 def test_regexp(self):
R David Murrayee1a7cb2011-07-01 14:55:43 -0400657 self.assertRunOK('-q', '-x', r'ba[^\\/]*$', self.pkgdir)
R. David Murray95333e32010-12-14 22:32:50 +0000658 self.assertNotCompiled(self.barfn)
659 self.assertCompiled(self.initfn)
R. David Murray650f1472010-11-20 21:18:51 +0000660
R. David Murray95333e32010-12-14 22:32:50 +0000661 def test_multiple_dirs(self):
662 pkgdir2 = os.path.join(self.directory, 'foo2')
663 os.mkdir(pkgdir2)
664 init2fn = script_helper.make_script(pkgdir2, '__init__', '')
665 bar2fn = script_helper.make_script(pkgdir2, 'bar2', '')
666 self.assertRunOK('-q', self.pkgdir, pkgdir2)
667 self.assertCompiled(self.initfn)
668 self.assertCompiled(self.barfn)
669 self.assertCompiled(init2fn)
670 self.assertCompiled(bar2fn)
671
R. David Murray95333e32010-12-14 22:32:50 +0000672 def test_d_compile_error(self):
673 script_helper.make_script(self.pkgdir, 'crunchyfrog', 'bad(syntax')
674 rc, out, err = self.assertRunNotOK('-q', '-d', 'dinsdale', self.pkgdir)
675 self.assertRegex(out, b'File "dinsdale')
676
677 def test_d_runtime_error(self):
678 bazfn = script_helper.make_script(self.pkgdir, 'baz', 'raise Exception')
679 self.assertRunOK('-q', '-d', 'dinsdale', self.pkgdir)
680 fn = script_helper.make_script(self.pkgdir, 'bing', 'import baz')
Brett Cannon7822e122013-06-14 23:04:02 -0400681 pyc = importlib.util.cache_from_source(bazfn)
R. David Murray95333e32010-12-14 22:32:50 +0000682 os.rename(pyc, os.path.join(self.pkgdir, 'baz.pyc'))
683 os.remove(bazfn)
Victor Stinnere8785ff2013-10-12 14:44:01 +0200684 rc, out, err = script_helper.assert_python_failure(fn, __isolated=False)
R. David Murray95333e32010-12-14 22:32:50 +0000685 self.assertRegex(err, b'File "dinsdale')
686
687 def test_include_bad_file(self):
688 rc, out, err = self.assertRunNotOK(
689 '-i', os.path.join(self.directory, 'nosuchfile'), self.pkgdir)
690 self.assertRegex(out, b'rror.*nosuchfile')
691 self.assertNotRegex(err, b'Traceback')
Brett Cannon7822e122013-06-14 23:04:02 -0400692 self.assertFalse(os.path.exists(importlib.util.cache_from_source(
R. David Murray95333e32010-12-14 22:32:50 +0000693 self.pkgdir_cachedir)))
694
695 def test_include_file_with_arg(self):
696 f1 = script_helper.make_script(self.pkgdir, 'f1', '')
697 f2 = script_helper.make_script(self.pkgdir, 'f2', '')
698 f3 = script_helper.make_script(self.pkgdir, 'f3', '')
699 f4 = script_helper.make_script(self.pkgdir, 'f4', '')
700 with open(os.path.join(self.directory, 'l1'), 'w') as l1:
701 l1.write(os.path.join(self.pkgdir, 'f1.py')+os.linesep)
702 l1.write(os.path.join(self.pkgdir, 'f2.py')+os.linesep)
703 self.assertRunOK('-i', os.path.join(self.directory, 'l1'), f4)
704 self.assertCompiled(f1)
705 self.assertCompiled(f2)
706 self.assertNotCompiled(f3)
707 self.assertCompiled(f4)
708
709 def test_include_file_no_arg(self):
710 f1 = script_helper.make_script(self.pkgdir, 'f1', '')
711 f2 = script_helper.make_script(self.pkgdir, 'f2', '')
712 f3 = script_helper.make_script(self.pkgdir, 'f3', '')
713 f4 = script_helper.make_script(self.pkgdir, 'f4', '')
714 with open(os.path.join(self.directory, 'l1'), 'w') as l1:
715 l1.write(os.path.join(self.pkgdir, 'f2.py')+os.linesep)
716 self.assertRunOK('-i', os.path.join(self.directory, 'l1'))
717 self.assertNotCompiled(f1)
718 self.assertCompiled(f2)
719 self.assertNotCompiled(f3)
720 self.assertNotCompiled(f4)
721
722 def test_include_on_stdin(self):
723 f1 = script_helper.make_script(self.pkgdir, 'f1', '')
724 f2 = script_helper.make_script(self.pkgdir, 'f2', '')
725 f3 = script_helper.make_script(self.pkgdir, 'f3', '')
726 f4 = script_helper.make_script(self.pkgdir, 'f4', '')
Benjamin Petersona820c7c2012-09-25 11:42:35 -0400727 p = script_helper.spawn_python(*(self._get_run_args(()) + ['-i', '-']))
R. David Murray95333e32010-12-14 22:32:50 +0000728 p.stdin.write((f3+os.linesep).encode('ascii'))
729 script_helper.kill_python(p)
730 self.assertNotCompiled(f1)
731 self.assertNotCompiled(f2)
732 self.assertCompiled(f3)
733 self.assertNotCompiled(f4)
734
735 def test_compiles_as_much_as_possible(self):
736 bingfn = script_helper.make_script(self.pkgdir, 'bing', 'syntax(error')
737 rc, out, err = self.assertRunNotOK('nosuchfile', self.initfn,
738 bingfn, self.barfn)
R. David Murray5317e9c2010-12-16 19:08:51 +0000739 self.assertRegex(out, b'rror')
R. David Murray95333e32010-12-14 22:32:50 +0000740 self.assertNotCompiled(bingfn)
741 self.assertCompiled(self.initfn)
742 self.assertCompiled(self.barfn)
743
R. David Murray5317e9c2010-12-16 19:08:51 +0000744 def test_invalid_arg_produces_message(self):
745 out = self.assertRunOK('badfilename')
Victor Stinner53071262011-05-11 00:36:28 +0200746 self.assertRegex(out, b"Can't list 'badfilename'")
R. David Murray650f1472010-11-20 21:18:51 +0000747
Benjamin Peterson42aa93b2017-12-09 10:26:52 -0800748 def test_pyc_invalidation_mode(self):
749 script_helper.make_script(self.pkgdir, 'f1', '')
750 pyc = importlib.util.cache_from_source(
751 os.path.join(self.pkgdir, 'f1.py'))
752 self.assertRunOK('--invalidation-mode=checked-hash', self.pkgdir)
753 with open(pyc, 'rb') as fp:
754 data = fp.read()
755 self.assertEqual(int.from_bytes(data[4:8], 'little'), 0b11)
756 self.assertRunOK('--invalidation-mode=unchecked-hash', self.pkgdir)
757 with open(pyc, 'rb') as fp:
758 data = fp.read()
759 self.assertEqual(int.from_bytes(data[4:8], 'little'), 0b01)
760
Brett Cannonf1a8df02014-09-12 10:39:48 -0400761 @skipUnless(_have_multiprocessing, "requires multiprocessing")
762 def test_workers(self):
763 bar2fn = script_helper.make_script(self.directory, 'bar2', '')
764 files = []
765 for suffix in range(5):
766 pkgdir = os.path.join(self.directory, 'foo{}'.format(suffix))
767 os.mkdir(pkgdir)
768 fn = script_helper.make_script(pkgdir, '__init__', '')
769 files.append(script_helper.make_script(pkgdir, 'bar2', ''))
770
771 self.assertRunOK(self.directory, '-j', '0')
772 self.assertCompiled(bar2fn)
773 for file in files:
774 self.assertCompiled(file)
775
776 @mock.patch('compileall.compile_dir')
777 def test_workers_available_cores(self, compile_dir):
778 with mock.patch("sys.argv",
779 new=[sys.executable, self.directory, "-j0"]):
780 compileall.main()
781 self.assertTrue(compile_dir.called)
Antoine Pitrou1a2dd822019-05-15 23:45:18 +0200782 self.assertEqual(compile_dir.call_args[-1]['workers'], 0)
Brett Cannonf1a8df02014-09-12 10:39:48 -0400783
Lumír 'Frenzy' Balhar8e7bb992019-09-26 08:28:26 +0200784 def test_strip_and_prepend(self):
785 fullpath = ["test", "build", "real", "path"]
786 path = os.path.join(self.directory, *fullpath)
787 os.makedirs(path)
788 script = script_helper.make_script(path, "test", "1 / 0")
789 bc = importlib.util.cache_from_source(script)
790 stripdir = os.path.join(self.directory, *fullpath[:2])
791 prependdir = "/foo"
792 self.assertRunOK("-s", stripdir, "-p", prependdir, path)
793 rc, out, err = script_helper.assert_python_failure(bc)
794 expected_in = os.path.join(prependdir, *fullpath[2:])
795 self.assertIn(
796 expected_in,
797 str(err, encoding=sys.getdefaultencoding())
798 )
799 self.assertNotIn(
800 stripdir,
801 str(err, encoding=sys.getdefaultencoding())
802 )
803
804 def test_multiple_optimization_levels(self):
805 path = os.path.join(self.directory, "optimizations")
806 os.makedirs(path)
807 script = script_helper.make_script(path,
808 "test_optimization",
809 "a = 0")
810 bc = []
811 for opt_level in "", 1, 2, 3:
812 bc.append(importlib.util.cache_from_source(script,
813 optimization=opt_level))
814 test_combinations = [["0", "1"],
815 ["1", "2"],
816 ["0", "2"],
817 ["0", "1", "2"]]
818 for opt_combination in test_combinations:
819 self.assertRunOK(path, *("-o" + str(n) for n in opt_combination))
820 for opt_level in opt_combination:
821 self.assertTrue(os.path.isfile(bc[int(opt_level)]))
822 try:
823 os.unlink(bc[opt_level])
824 except Exception:
825 pass
826
Hai Shi883bc632020-07-06 17:12:49 +0800827 @os_helper.skip_unless_symlink
Lumír 'Frenzy' Balhar8e7bb992019-09-26 08:28:26 +0200828 def test_ignore_symlink_destination(self):
829 # Create folders for allowed files, symlinks and prohibited area
830 allowed_path = os.path.join(self.directory, "test", "dir", "allowed")
831 symlinks_path = os.path.join(self.directory, "test", "dir", "symlinks")
832 prohibited_path = os.path.join(self.directory, "test", "dir", "prohibited")
833 os.makedirs(allowed_path)
834 os.makedirs(symlinks_path)
835 os.makedirs(prohibited_path)
836
837 # Create scripts and symlinks and remember their byte-compiled versions
838 allowed_script = script_helper.make_script(allowed_path, "test_allowed", "a = 0")
839 prohibited_script = script_helper.make_script(prohibited_path, "test_prohibited", "a = 0")
840 allowed_symlink = os.path.join(symlinks_path, "test_allowed.py")
841 prohibited_symlink = os.path.join(symlinks_path, "test_prohibited.py")
842 os.symlink(allowed_script, allowed_symlink)
843 os.symlink(prohibited_script, prohibited_symlink)
844 allowed_bc = importlib.util.cache_from_source(allowed_symlink)
845 prohibited_bc = importlib.util.cache_from_source(prohibited_symlink)
846
847 self.assertRunOK(symlinks_path, "-e", allowed_path)
848
849 self.assertTrue(os.path.isfile(allowed_bc))
850 self.assertFalse(os.path.isfile(prohibited_bc))
851
Lumír 'Frenzy' Balhare77d4282020-05-14 16:17:22 +0200852 def test_hardlink_bad_args(self):
853 # Bad arguments combination, hardlink deduplication make sense
854 # only for more than one optimization level
855 self.assertRunNotOK(self.directory, "-o 1", "--hardlink-dupes")
856
857 def test_hardlink(self):
858 # 'a = 0' code produces the same bytecode for the 3 optimization
859 # levels. All three .pyc files must have the same inode (hardlinks).
860 #
861 # If deduplication is disabled, all pyc files must have different
862 # inodes.
863 for dedup in (True, False):
864 with tempfile.TemporaryDirectory() as path:
865 with self.subTest(dedup=dedup):
866 script = script_helper.make_script(path, "script", "a = 0")
867 pycs = get_pycs(script)
868
869 args = ["-q", "-o 0", "-o 1", "-o 2"]
870 if dedup:
871 args.append("--hardlink-dupes")
872 self.assertRunOK(path, *args)
873
874 self.assertEqual(is_hardlink(pycs[0], pycs[1]), dedup)
875 self.assertEqual(is_hardlink(pycs[1], pycs[2]), dedup)
876 self.assertEqual(is_hardlink(pycs[0], pycs[2]), dedup)
877
Barry Warsaw28a691b2010-04-17 00:19:56 +0000878
Min ho Kimc4cacc82019-07-31 08:16:13 +1000879class CommandLineTestsWithSourceEpoch(CommandLineTestsBase,
Elvis Pranskevichusa6b3ec52018-10-10 12:43:14 -0400880 unittest.TestCase,
881 metaclass=SourceDateEpochTestMeta,
882 source_date_epoch=True):
883 pass
884
885
Min ho Kimc4cacc82019-07-31 08:16:13 +1000886class CommandLineTestsNoSourceEpoch(CommandLineTestsBase,
Elvis Pranskevichusa6b3ec52018-10-10 12:43:14 -0400887 unittest.TestCase,
888 metaclass=SourceDateEpochTestMeta,
889 source_date_epoch=False):
890 pass
891
892
893
Lumír 'Frenzy' Balhare77d4282020-05-14 16:17:22 +0200894class HardlinkDedupTestsBase:
895 # Test hardlink_dupes parameter of compileall.compile_dir()
896
897 def setUp(self):
898 self.path = None
899
900 @contextlib.contextmanager
901 def temporary_directory(self):
902 with tempfile.TemporaryDirectory() as path:
903 self.path = path
904 yield path
905 self.path = None
906
907 def make_script(self, code, name="script"):
908 return script_helper.make_script(self.path, name, code)
909
910 def compile_dir(self, *, dedup=True, optimize=(0, 1, 2), force=False):
911 compileall.compile_dir(self.path, quiet=True, optimize=optimize,
912 hardlink_dupes=dedup, force=force)
913
914 def test_bad_args(self):
915 # Bad arguments combination, hardlink deduplication make sense
916 # only for more than one optimization level
917 with self.temporary_directory():
918 self.make_script("pass")
919 with self.assertRaises(ValueError):
920 compileall.compile_dir(self.path, quiet=True, optimize=0,
921 hardlink_dupes=True)
922 with self.assertRaises(ValueError):
923 # same optimization level specified twice:
924 # compile_dir() removes duplicates
925 compileall.compile_dir(self.path, quiet=True, optimize=[0, 0],
926 hardlink_dupes=True)
927
928 def create_code(self, docstring=False, assertion=False):
929 lines = []
930 if docstring:
931 lines.append("'module docstring'")
932 lines.append('x = 1')
933 if assertion:
934 lines.append("assert x == 1")
935 return '\n'.join(lines)
936
937 def iter_codes(self):
938 for docstring in (False, True):
939 for assertion in (False, True):
940 code = self.create_code(docstring=docstring, assertion=assertion)
941 yield (code, docstring, assertion)
942
943 def test_disabled(self):
944 # Deduplication disabled, no hardlinks
945 for code, docstring, assertion in self.iter_codes():
946 with self.subTest(docstring=docstring, assertion=assertion):
947 with self.temporary_directory():
948 script = self.make_script(code)
949 pycs = get_pycs(script)
950 self.compile_dir(dedup=False)
951 self.assertFalse(is_hardlink(pycs[0], pycs[1]))
952 self.assertFalse(is_hardlink(pycs[0], pycs[2]))
953 self.assertFalse(is_hardlink(pycs[1], pycs[2]))
954
955 def check_hardlinks(self, script, docstring=False, assertion=False):
956 pycs = get_pycs(script)
957 self.assertEqual(is_hardlink(pycs[0], pycs[1]),
958 not assertion)
959 self.assertEqual(is_hardlink(pycs[0], pycs[2]),
960 not assertion and not docstring)
961 self.assertEqual(is_hardlink(pycs[1], pycs[2]),
962 not docstring)
963
964 def test_hardlink(self):
965 # Test deduplication on all combinations
966 for code, docstring, assertion in self.iter_codes():
967 with self.subTest(docstring=docstring, assertion=assertion):
968 with self.temporary_directory():
969 script = self.make_script(code)
970 self.compile_dir()
971 self.check_hardlinks(script, docstring, assertion)
972
973 def test_only_two_levels(self):
974 # Don't build the 3 optimization levels, but only 2
975 for opts in ((0, 1), (1, 2), (0, 2)):
976 with self.subTest(opts=opts):
977 with self.temporary_directory():
978 # code with no dostring and no assertion:
979 # same bytecode for all optimization levels
980 script = self.make_script(self.create_code())
981 self.compile_dir(optimize=opts)
982 pyc1 = get_pyc(script, opts[0])
983 pyc2 = get_pyc(script, opts[1])
984 self.assertTrue(is_hardlink(pyc1, pyc2))
985
986 def test_duplicated_levels(self):
987 # compile_dir() must not fail if optimize contains duplicated
988 # optimization levels and/or if optimization levels are not sorted.
989 with self.temporary_directory():
990 # code with no dostring and no assertion:
991 # same bytecode for all optimization levels
992 script = self.make_script(self.create_code())
993 self.compile_dir(optimize=[1, 0, 1, 0])
994 pyc1 = get_pyc(script, 0)
995 pyc2 = get_pyc(script, 1)
996 self.assertTrue(is_hardlink(pyc1, pyc2))
997
998 def test_recompilation(self):
999 # Test compile_dir() when pyc files already exists and the script
1000 # content changed
1001 with self.temporary_directory():
1002 script = self.make_script("a = 0")
1003 self.compile_dir()
1004 # All three levels have the same inode
1005 self.check_hardlinks(script)
1006
1007 pycs = get_pycs(script)
1008 inode = os.stat(pycs[0]).st_ino
1009
1010 # Change of the module content
1011 script = self.make_script("print(0)")
1012
1013 # Recompilation without -o 1
1014 self.compile_dir(optimize=[0, 2], force=True)
1015
1016 # opt-1.pyc should have the same inode as before and others should not
1017 self.assertEqual(inode, os.stat(pycs[1]).st_ino)
1018 self.assertTrue(is_hardlink(pycs[0], pycs[2]))
1019 self.assertNotEqual(inode, os.stat(pycs[2]).st_ino)
1020 # opt-1.pyc and opt-2.pyc have different content
1021 self.assertFalse(filecmp.cmp(pycs[1], pycs[2], shallow=True))
1022
1023 def test_import(self):
1024 # Test that import updates a single pyc file when pyc files already
1025 # exists and the script content changed
1026 with self.temporary_directory():
1027 script = self.make_script(self.create_code(), name="module")
1028 self.compile_dir()
1029 # All three levels have the same inode
1030 self.check_hardlinks(script)
1031
1032 pycs = get_pycs(script)
1033 inode = os.stat(pycs[0]).st_ino
1034
1035 # Change of the module content
1036 script = self.make_script("print(0)", name="module")
1037
1038 # Import the module in Python with -O (optimization level 1)
1039 script_helper.assert_python_ok(
1040 "-O", "-c", "import module", __isolated=False, PYTHONPATH=self.path
1041 )
1042
1043 # Only opt-1.pyc is changed
1044 self.assertEqual(inode, os.stat(pycs[0]).st_ino)
1045 self.assertEqual(inode, os.stat(pycs[2]).st_ino)
1046 self.assertFalse(is_hardlink(pycs[1], pycs[2]))
1047 # opt-1.pyc and opt-2.pyc have different content
1048 self.assertFalse(filecmp.cmp(pycs[1], pycs[2], shallow=True))
1049
1050
1051class HardlinkDedupTestsWithSourceEpoch(HardlinkDedupTestsBase,
1052 unittest.TestCase,
1053 metaclass=SourceDateEpochTestMeta,
1054 source_date_epoch=True):
1055 pass
1056
1057
1058class HardlinkDedupTestsNoSourceEpoch(HardlinkDedupTestsBase,
1059 unittest.TestCase,
1060 metaclass=SourceDateEpochTestMeta,
1061 source_date_epoch=False):
1062 pass
1063
1064
Brett Cannonbefb14f2009-02-10 02:10:16 +00001065if __name__ == "__main__":
Brett Cannon7822e122013-06-14 23:04:02 -04001066 unittest.main()