xref: /third_party/python/Lib/test/test_zipapp.py (revision 7db96d56)
17db96d56Sopenharmony_ci"""Test harness for the zipapp module."""
27db96d56Sopenharmony_ci
37db96d56Sopenharmony_ciimport io
47db96d56Sopenharmony_ciimport pathlib
57db96d56Sopenharmony_ciimport stat
67db96d56Sopenharmony_ciimport sys
77db96d56Sopenharmony_ciimport tempfile
87db96d56Sopenharmony_ciimport unittest
97db96d56Sopenharmony_ciimport zipapp
107db96d56Sopenharmony_ciimport zipfile
117db96d56Sopenharmony_cifrom test.support import requires_zlib
127db96d56Sopenharmony_cifrom test.support import os_helper
137db96d56Sopenharmony_ci
147db96d56Sopenharmony_cifrom unittest.mock import patch
157db96d56Sopenharmony_ci
167db96d56Sopenharmony_ciclass ZipAppTest(unittest.TestCase):
177db96d56Sopenharmony_ci
187db96d56Sopenharmony_ci    """Test zipapp module functionality."""
197db96d56Sopenharmony_ci
207db96d56Sopenharmony_ci    def setUp(self):
217db96d56Sopenharmony_ci        tmpdir = tempfile.TemporaryDirectory()
227db96d56Sopenharmony_ci        self.addCleanup(tmpdir.cleanup)
237db96d56Sopenharmony_ci        self.tmpdir = pathlib.Path(tmpdir.name)
247db96d56Sopenharmony_ci
257db96d56Sopenharmony_ci    def test_create_archive(self):
267db96d56Sopenharmony_ci        # Test packing a directory.
277db96d56Sopenharmony_ci        source = self.tmpdir / 'source'
287db96d56Sopenharmony_ci        source.mkdir()
297db96d56Sopenharmony_ci        (source / '__main__.py').touch()
307db96d56Sopenharmony_ci        target = self.tmpdir / 'source.pyz'
317db96d56Sopenharmony_ci        zipapp.create_archive(str(source), str(target))
327db96d56Sopenharmony_ci        self.assertTrue(target.is_file())
337db96d56Sopenharmony_ci
347db96d56Sopenharmony_ci    def test_create_archive_with_pathlib(self):
357db96d56Sopenharmony_ci        # Test packing a directory using Path objects for source and target.
367db96d56Sopenharmony_ci        source = self.tmpdir / 'source'
377db96d56Sopenharmony_ci        source.mkdir()
387db96d56Sopenharmony_ci        (source / '__main__.py').touch()
397db96d56Sopenharmony_ci        target = self.tmpdir / 'source.pyz'
407db96d56Sopenharmony_ci        zipapp.create_archive(source, target)
417db96d56Sopenharmony_ci        self.assertTrue(target.is_file())
427db96d56Sopenharmony_ci
437db96d56Sopenharmony_ci    def test_create_archive_with_subdirs(self):
447db96d56Sopenharmony_ci        # Test packing a directory includes entries for subdirectories.
457db96d56Sopenharmony_ci        source = self.tmpdir / 'source'
467db96d56Sopenharmony_ci        source.mkdir()
477db96d56Sopenharmony_ci        (source / '__main__.py').touch()
487db96d56Sopenharmony_ci        (source / 'foo').mkdir()
497db96d56Sopenharmony_ci        (source / 'bar').mkdir()
507db96d56Sopenharmony_ci        (source / 'foo' / '__init__.py').touch()
517db96d56Sopenharmony_ci        target = io.BytesIO()
527db96d56Sopenharmony_ci        zipapp.create_archive(str(source), target)
537db96d56Sopenharmony_ci        target.seek(0)
547db96d56Sopenharmony_ci        with zipfile.ZipFile(target, 'r') as z:
557db96d56Sopenharmony_ci            self.assertIn('foo/', z.namelist())
567db96d56Sopenharmony_ci            self.assertIn('bar/', z.namelist())
577db96d56Sopenharmony_ci
587db96d56Sopenharmony_ci    def test_create_archive_with_filter(self):
597db96d56Sopenharmony_ci        # Test packing a directory and using filter to specify
607db96d56Sopenharmony_ci        # which files to include.
617db96d56Sopenharmony_ci        def skip_pyc_files(path):
627db96d56Sopenharmony_ci            return path.suffix != '.pyc'
637db96d56Sopenharmony_ci        source = self.tmpdir / 'source'
647db96d56Sopenharmony_ci        source.mkdir()
657db96d56Sopenharmony_ci        (source / '__main__.py').touch()
667db96d56Sopenharmony_ci        (source / 'test.py').touch()
677db96d56Sopenharmony_ci        (source / 'test.pyc').touch()
687db96d56Sopenharmony_ci        target = self.tmpdir / 'source.pyz'
697db96d56Sopenharmony_ci
707db96d56Sopenharmony_ci        zipapp.create_archive(source, target, filter=skip_pyc_files)
717db96d56Sopenharmony_ci        with zipfile.ZipFile(target, 'r') as z:
727db96d56Sopenharmony_ci            self.assertIn('__main__.py', z.namelist())
737db96d56Sopenharmony_ci            self.assertIn('test.py', z.namelist())
747db96d56Sopenharmony_ci            self.assertNotIn('test.pyc', z.namelist())
757db96d56Sopenharmony_ci
767db96d56Sopenharmony_ci    def test_create_archive_filter_exclude_dir(self):
777db96d56Sopenharmony_ci        # Test packing a directory and using a filter to exclude a
787db96d56Sopenharmony_ci        # subdirectory (ensures that the path supplied to include
797db96d56Sopenharmony_ci        # is relative to the source location, as expected).
807db96d56Sopenharmony_ci        def skip_dummy_dir(path):
817db96d56Sopenharmony_ci            return path.parts[0] != 'dummy'
827db96d56Sopenharmony_ci        source = self.tmpdir / 'source'
837db96d56Sopenharmony_ci        source.mkdir()
847db96d56Sopenharmony_ci        (source / '__main__.py').touch()
857db96d56Sopenharmony_ci        (source / 'test.py').touch()
867db96d56Sopenharmony_ci        (source / 'dummy').mkdir()
877db96d56Sopenharmony_ci        (source / 'dummy' / 'test2.py').touch()
887db96d56Sopenharmony_ci        target = self.tmpdir / 'source.pyz'
897db96d56Sopenharmony_ci
907db96d56Sopenharmony_ci        zipapp.create_archive(source, target, filter=skip_dummy_dir)
917db96d56Sopenharmony_ci        with zipfile.ZipFile(target, 'r') as z:
927db96d56Sopenharmony_ci            self.assertEqual(len(z.namelist()), 2)
937db96d56Sopenharmony_ci            self.assertIn('__main__.py', z.namelist())
947db96d56Sopenharmony_ci            self.assertIn('test.py', z.namelist())
957db96d56Sopenharmony_ci
967db96d56Sopenharmony_ci    def test_create_archive_default_target(self):
977db96d56Sopenharmony_ci        # Test packing a directory to the default name.
987db96d56Sopenharmony_ci        source = self.tmpdir / 'source'
997db96d56Sopenharmony_ci        source.mkdir()
1007db96d56Sopenharmony_ci        (source / '__main__.py').touch()
1017db96d56Sopenharmony_ci        zipapp.create_archive(str(source))
1027db96d56Sopenharmony_ci        expected_target = self.tmpdir / 'source.pyz'
1037db96d56Sopenharmony_ci        self.assertTrue(expected_target.is_file())
1047db96d56Sopenharmony_ci
1057db96d56Sopenharmony_ci    @requires_zlib()
1067db96d56Sopenharmony_ci    def test_create_archive_with_compression(self):
1077db96d56Sopenharmony_ci        # Test packing a directory into a compressed archive.
1087db96d56Sopenharmony_ci        source = self.tmpdir / 'source'
1097db96d56Sopenharmony_ci        source.mkdir()
1107db96d56Sopenharmony_ci        (source / '__main__.py').touch()
1117db96d56Sopenharmony_ci        (source / 'test.py').touch()
1127db96d56Sopenharmony_ci        target = self.tmpdir / 'source.pyz'
1137db96d56Sopenharmony_ci
1147db96d56Sopenharmony_ci        zipapp.create_archive(source, target, compressed=True)
1157db96d56Sopenharmony_ci        with zipfile.ZipFile(target, 'r') as z:
1167db96d56Sopenharmony_ci            for name in ('__main__.py', 'test.py'):
1177db96d56Sopenharmony_ci                self.assertEqual(z.getinfo(name).compress_type,
1187db96d56Sopenharmony_ci                                 zipfile.ZIP_DEFLATED)
1197db96d56Sopenharmony_ci
1207db96d56Sopenharmony_ci    def test_no_main(self):
1217db96d56Sopenharmony_ci        # Test that packing a directory with no __main__.py fails.
1227db96d56Sopenharmony_ci        source = self.tmpdir / 'source'
1237db96d56Sopenharmony_ci        source.mkdir()
1247db96d56Sopenharmony_ci        (source / 'foo.py').touch()
1257db96d56Sopenharmony_ci        target = self.tmpdir / 'source.pyz'
1267db96d56Sopenharmony_ci        with self.assertRaises(zipapp.ZipAppError):
1277db96d56Sopenharmony_ci            zipapp.create_archive(str(source), str(target))
1287db96d56Sopenharmony_ci
1297db96d56Sopenharmony_ci    def test_main_and_main_py(self):
1307db96d56Sopenharmony_ci        # Test that supplying a main argument with __main__.py fails.
1317db96d56Sopenharmony_ci        source = self.tmpdir / 'source'
1327db96d56Sopenharmony_ci        source.mkdir()
1337db96d56Sopenharmony_ci        (source / '__main__.py').touch()
1347db96d56Sopenharmony_ci        target = self.tmpdir / 'source.pyz'
1357db96d56Sopenharmony_ci        with self.assertRaises(zipapp.ZipAppError):
1367db96d56Sopenharmony_ci            zipapp.create_archive(str(source), str(target), main='pkg.mod:fn')
1377db96d56Sopenharmony_ci
1387db96d56Sopenharmony_ci    def test_main_written(self):
1397db96d56Sopenharmony_ci        # Test that the __main__.py is written correctly.
1407db96d56Sopenharmony_ci        source = self.tmpdir / 'source'
1417db96d56Sopenharmony_ci        source.mkdir()
1427db96d56Sopenharmony_ci        (source / 'foo.py').touch()
1437db96d56Sopenharmony_ci        target = self.tmpdir / 'source.pyz'
1447db96d56Sopenharmony_ci        zipapp.create_archive(str(source), str(target), main='pkg.mod:fn')
1457db96d56Sopenharmony_ci        with zipfile.ZipFile(str(target), 'r') as z:
1467db96d56Sopenharmony_ci            self.assertIn('__main__.py', z.namelist())
1477db96d56Sopenharmony_ci            self.assertIn(b'pkg.mod.fn()', z.read('__main__.py'))
1487db96d56Sopenharmony_ci
1497db96d56Sopenharmony_ci    def test_main_only_written_once(self):
1507db96d56Sopenharmony_ci        # Test that we don't write multiple __main__.py files.
1517db96d56Sopenharmony_ci        # The initial implementation had this bug; zip files allow
1527db96d56Sopenharmony_ci        # multiple entries with the same name
1537db96d56Sopenharmony_ci        source = self.tmpdir / 'source'
1547db96d56Sopenharmony_ci        source.mkdir()
1557db96d56Sopenharmony_ci        # Write 2 files, as the original bug wrote __main__.py
1567db96d56Sopenharmony_ci        # once for each file written :-(
1577db96d56Sopenharmony_ci        # See http://bugs.python.org/review/23491/diff/13982/Lib/zipapp.py#newcode67Lib/zipapp.py:67
1587db96d56Sopenharmony_ci        # (line 67)
1597db96d56Sopenharmony_ci        (source / 'foo.py').touch()
1607db96d56Sopenharmony_ci        (source / 'bar.py').touch()
1617db96d56Sopenharmony_ci        target = self.tmpdir / 'source.pyz'
1627db96d56Sopenharmony_ci        zipapp.create_archive(str(source), str(target), main='pkg.mod:fn')
1637db96d56Sopenharmony_ci        with zipfile.ZipFile(str(target), 'r') as z:
1647db96d56Sopenharmony_ci            self.assertEqual(1, z.namelist().count('__main__.py'))
1657db96d56Sopenharmony_ci
1667db96d56Sopenharmony_ci    def test_main_validation(self):
1677db96d56Sopenharmony_ci        # Test that invalid values for main are rejected.
1687db96d56Sopenharmony_ci        source = self.tmpdir / 'source'
1697db96d56Sopenharmony_ci        source.mkdir()
1707db96d56Sopenharmony_ci        target = self.tmpdir / 'source.pyz'
1717db96d56Sopenharmony_ci        problems = [
1727db96d56Sopenharmony_ci            '', 'foo', 'foo:', ':bar', '12:bar', 'a.b.c.:d',
1737db96d56Sopenharmony_ci            '.a:b', 'a:b.', 'a:.b', 'a:silly name'
1747db96d56Sopenharmony_ci        ]
1757db96d56Sopenharmony_ci        for main in problems:
1767db96d56Sopenharmony_ci            with self.subTest(main=main):
1777db96d56Sopenharmony_ci                with self.assertRaises(zipapp.ZipAppError):
1787db96d56Sopenharmony_ci                    zipapp.create_archive(str(source), str(target), main=main)
1797db96d56Sopenharmony_ci
1807db96d56Sopenharmony_ci    def test_default_no_shebang(self):
1817db96d56Sopenharmony_ci        # Test that no shebang line is written to the target by default.
1827db96d56Sopenharmony_ci        source = self.tmpdir / 'source'
1837db96d56Sopenharmony_ci        source.mkdir()
1847db96d56Sopenharmony_ci        (source / '__main__.py').touch()
1857db96d56Sopenharmony_ci        target = self.tmpdir / 'source.pyz'
1867db96d56Sopenharmony_ci        zipapp.create_archive(str(source), str(target))
1877db96d56Sopenharmony_ci        with target.open('rb') as f:
1887db96d56Sopenharmony_ci            self.assertNotEqual(f.read(2), b'#!')
1897db96d56Sopenharmony_ci
1907db96d56Sopenharmony_ci    def test_custom_interpreter(self):
1917db96d56Sopenharmony_ci        # Test that a shebang line with a custom interpreter is written
1927db96d56Sopenharmony_ci        # correctly.
1937db96d56Sopenharmony_ci        source = self.tmpdir / 'source'
1947db96d56Sopenharmony_ci        source.mkdir()
1957db96d56Sopenharmony_ci        (source / '__main__.py').touch()
1967db96d56Sopenharmony_ci        target = self.tmpdir / 'source.pyz'
1977db96d56Sopenharmony_ci        zipapp.create_archive(str(source), str(target), interpreter='python')
1987db96d56Sopenharmony_ci        with target.open('rb') as f:
1997db96d56Sopenharmony_ci            self.assertEqual(f.read(2), b'#!')
2007db96d56Sopenharmony_ci            self.assertEqual(b'python\n', f.readline())
2017db96d56Sopenharmony_ci
2027db96d56Sopenharmony_ci    def test_pack_to_fileobj(self):
2037db96d56Sopenharmony_ci        # Test that we can pack to a file object.
2047db96d56Sopenharmony_ci        source = self.tmpdir / 'source'
2057db96d56Sopenharmony_ci        source.mkdir()
2067db96d56Sopenharmony_ci        (source / '__main__.py').touch()
2077db96d56Sopenharmony_ci        target = io.BytesIO()
2087db96d56Sopenharmony_ci        zipapp.create_archive(str(source), target, interpreter='python')
2097db96d56Sopenharmony_ci        self.assertTrue(target.getvalue().startswith(b'#!python\n'))
2107db96d56Sopenharmony_ci
2117db96d56Sopenharmony_ci    def test_read_shebang(self):
2127db96d56Sopenharmony_ci        # Test that we can read the shebang line correctly.
2137db96d56Sopenharmony_ci        source = self.tmpdir / 'source'
2147db96d56Sopenharmony_ci        source.mkdir()
2157db96d56Sopenharmony_ci        (source / '__main__.py').touch()
2167db96d56Sopenharmony_ci        target = self.tmpdir / 'source.pyz'
2177db96d56Sopenharmony_ci        zipapp.create_archive(str(source), str(target), interpreter='python')
2187db96d56Sopenharmony_ci        self.assertEqual(zipapp.get_interpreter(str(target)), 'python')
2197db96d56Sopenharmony_ci
2207db96d56Sopenharmony_ci    def test_read_missing_shebang(self):
2217db96d56Sopenharmony_ci        # Test that reading the shebang line of a file without one returns None.
2227db96d56Sopenharmony_ci        source = self.tmpdir / 'source'
2237db96d56Sopenharmony_ci        source.mkdir()
2247db96d56Sopenharmony_ci        (source / '__main__.py').touch()
2257db96d56Sopenharmony_ci        target = self.tmpdir / 'source.pyz'
2267db96d56Sopenharmony_ci        zipapp.create_archive(str(source), str(target))
2277db96d56Sopenharmony_ci        self.assertEqual(zipapp.get_interpreter(str(target)), None)
2287db96d56Sopenharmony_ci
2297db96d56Sopenharmony_ci    def test_modify_shebang(self):
2307db96d56Sopenharmony_ci        # Test that we can change the shebang of a file.
2317db96d56Sopenharmony_ci        source = self.tmpdir / 'source'
2327db96d56Sopenharmony_ci        source.mkdir()
2337db96d56Sopenharmony_ci        (source / '__main__.py').touch()
2347db96d56Sopenharmony_ci        target = self.tmpdir / 'source.pyz'
2357db96d56Sopenharmony_ci        zipapp.create_archive(str(source), str(target), interpreter='python')
2367db96d56Sopenharmony_ci        new_target = self.tmpdir / 'changed.pyz'
2377db96d56Sopenharmony_ci        zipapp.create_archive(str(target), str(new_target), interpreter='python2.7')
2387db96d56Sopenharmony_ci        self.assertEqual(zipapp.get_interpreter(str(new_target)), 'python2.7')
2397db96d56Sopenharmony_ci
2407db96d56Sopenharmony_ci    def test_write_shebang_to_fileobj(self):
2417db96d56Sopenharmony_ci        # Test that we can change the shebang of a file, writing the result to a
2427db96d56Sopenharmony_ci        # file object.
2437db96d56Sopenharmony_ci        source = self.tmpdir / 'source'
2447db96d56Sopenharmony_ci        source.mkdir()
2457db96d56Sopenharmony_ci        (source / '__main__.py').touch()
2467db96d56Sopenharmony_ci        target = self.tmpdir / 'source.pyz'
2477db96d56Sopenharmony_ci        zipapp.create_archive(str(source), str(target), interpreter='python')
2487db96d56Sopenharmony_ci        new_target = io.BytesIO()
2497db96d56Sopenharmony_ci        zipapp.create_archive(str(target), new_target, interpreter='python2.7')
2507db96d56Sopenharmony_ci        self.assertTrue(new_target.getvalue().startswith(b'#!python2.7\n'))
2517db96d56Sopenharmony_ci
2527db96d56Sopenharmony_ci    def test_read_from_pathobj(self):
2537db96d56Sopenharmony_ci        # Test that we can copy an archive using a pathlib.Path object
2547db96d56Sopenharmony_ci        # for the source.
2557db96d56Sopenharmony_ci        source = self.tmpdir / 'source'
2567db96d56Sopenharmony_ci        source.mkdir()
2577db96d56Sopenharmony_ci        (source / '__main__.py').touch()
2587db96d56Sopenharmony_ci        target1 = self.tmpdir / 'target1.pyz'
2597db96d56Sopenharmony_ci        target2 = self.tmpdir / 'target2.pyz'
2607db96d56Sopenharmony_ci        zipapp.create_archive(source, target1, interpreter='python')
2617db96d56Sopenharmony_ci        zipapp.create_archive(target1, target2, interpreter='python2.7')
2627db96d56Sopenharmony_ci        self.assertEqual(zipapp.get_interpreter(target2), 'python2.7')
2637db96d56Sopenharmony_ci
2647db96d56Sopenharmony_ci    def test_read_from_fileobj(self):
2657db96d56Sopenharmony_ci        # Test that we can copy an archive using an open file object.
2667db96d56Sopenharmony_ci        source = self.tmpdir / 'source'
2677db96d56Sopenharmony_ci        source.mkdir()
2687db96d56Sopenharmony_ci        (source / '__main__.py').touch()
2697db96d56Sopenharmony_ci        target = self.tmpdir / 'source.pyz'
2707db96d56Sopenharmony_ci        temp_archive = io.BytesIO()
2717db96d56Sopenharmony_ci        zipapp.create_archive(str(source), temp_archive, interpreter='python')
2727db96d56Sopenharmony_ci        new_target = io.BytesIO()
2737db96d56Sopenharmony_ci        temp_archive.seek(0)
2747db96d56Sopenharmony_ci        zipapp.create_archive(temp_archive, new_target, interpreter='python2.7')
2757db96d56Sopenharmony_ci        self.assertTrue(new_target.getvalue().startswith(b'#!python2.7\n'))
2767db96d56Sopenharmony_ci
2777db96d56Sopenharmony_ci    def test_remove_shebang(self):
2787db96d56Sopenharmony_ci        # Test that we can remove the shebang from a file.
2797db96d56Sopenharmony_ci        source = self.tmpdir / 'source'
2807db96d56Sopenharmony_ci        source.mkdir()
2817db96d56Sopenharmony_ci        (source / '__main__.py').touch()
2827db96d56Sopenharmony_ci        target = self.tmpdir / 'source.pyz'
2837db96d56Sopenharmony_ci        zipapp.create_archive(str(source), str(target), interpreter='python')
2847db96d56Sopenharmony_ci        new_target = self.tmpdir / 'changed.pyz'
2857db96d56Sopenharmony_ci        zipapp.create_archive(str(target), str(new_target), interpreter=None)
2867db96d56Sopenharmony_ci        self.assertEqual(zipapp.get_interpreter(str(new_target)), None)
2877db96d56Sopenharmony_ci
2887db96d56Sopenharmony_ci    def test_content_of_copied_archive(self):
2897db96d56Sopenharmony_ci        # Test that copying an archive doesn't corrupt it.
2907db96d56Sopenharmony_ci        source = self.tmpdir / 'source'
2917db96d56Sopenharmony_ci        source.mkdir()
2927db96d56Sopenharmony_ci        (source / '__main__.py').touch()
2937db96d56Sopenharmony_ci        target = io.BytesIO()
2947db96d56Sopenharmony_ci        zipapp.create_archive(str(source), target, interpreter='python')
2957db96d56Sopenharmony_ci        new_target = io.BytesIO()
2967db96d56Sopenharmony_ci        target.seek(0)
2977db96d56Sopenharmony_ci        zipapp.create_archive(target, new_target, interpreter=None)
2987db96d56Sopenharmony_ci        new_target.seek(0)
2997db96d56Sopenharmony_ci        with zipfile.ZipFile(new_target, 'r') as z:
3007db96d56Sopenharmony_ci            self.assertEqual(set(z.namelist()), {'__main__.py'})
3017db96d56Sopenharmony_ci
3027db96d56Sopenharmony_ci    # (Unix only) tests that archives with shebang lines are made executable
3037db96d56Sopenharmony_ci    @unittest.skipIf(sys.platform == 'win32',
3047db96d56Sopenharmony_ci                     'Windows does not support an executable bit')
3057db96d56Sopenharmony_ci    @os_helper.skip_unless_working_chmod
3067db96d56Sopenharmony_ci    def test_shebang_is_executable(self):
3077db96d56Sopenharmony_ci        # Test that an archive with a shebang line is made executable.
3087db96d56Sopenharmony_ci        source = self.tmpdir / 'source'
3097db96d56Sopenharmony_ci        source.mkdir()
3107db96d56Sopenharmony_ci        (source / '__main__.py').touch()
3117db96d56Sopenharmony_ci        target = self.tmpdir / 'source.pyz'
3127db96d56Sopenharmony_ci        zipapp.create_archive(str(source), str(target), interpreter='python')
3137db96d56Sopenharmony_ci        self.assertTrue(target.stat().st_mode & stat.S_IEXEC)
3147db96d56Sopenharmony_ci
3157db96d56Sopenharmony_ci    @unittest.skipIf(sys.platform == 'win32',
3167db96d56Sopenharmony_ci                     'Windows does not support an executable bit')
3177db96d56Sopenharmony_ci    def test_no_shebang_is_not_executable(self):
3187db96d56Sopenharmony_ci        # Test that an archive with no shebang line is not made executable.
3197db96d56Sopenharmony_ci        source = self.tmpdir / 'source'
3207db96d56Sopenharmony_ci        source.mkdir()
3217db96d56Sopenharmony_ci        (source / '__main__.py').touch()
3227db96d56Sopenharmony_ci        target = self.tmpdir / 'source.pyz'
3237db96d56Sopenharmony_ci        zipapp.create_archive(str(source), str(target), interpreter=None)
3247db96d56Sopenharmony_ci        self.assertFalse(target.stat().st_mode & stat.S_IEXEC)
3257db96d56Sopenharmony_ci
3267db96d56Sopenharmony_ci
3277db96d56Sopenharmony_ciclass ZipAppCmdlineTest(unittest.TestCase):
3287db96d56Sopenharmony_ci
3297db96d56Sopenharmony_ci    """Test zipapp module command line API."""
3307db96d56Sopenharmony_ci
3317db96d56Sopenharmony_ci    def setUp(self):
3327db96d56Sopenharmony_ci        tmpdir = tempfile.TemporaryDirectory()
3337db96d56Sopenharmony_ci        self.addCleanup(tmpdir.cleanup)
3347db96d56Sopenharmony_ci        self.tmpdir = pathlib.Path(tmpdir.name)
3357db96d56Sopenharmony_ci
3367db96d56Sopenharmony_ci    def make_archive(self):
3377db96d56Sopenharmony_ci        # Test that an archive with no shebang line is not made executable.
3387db96d56Sopenharmony_ci        source = self.tmpdir / 'source'
3397db96d56Sopenharmony_ci        source.mkdir()
3407db96d56Sopenharmony_ci        (source / '__main__.py').touch()
3417db96d56Sopenharmony_ci        target = self.tmpdir / 'source.pyz'
3427db96d56Sopenharmony_ci        zipapp.create_archive(source, target)
3437db96d56Sopenharmony_ci        return target
3447db96d56Sopenharmony_ci
3457db96d56Sopenharmony_ci    def test_cmdline_create(self):
3467db96d56Sopenharmony_ci        # Test the basic command line API.
3477db96d56Sopenharmony_ci        source = self.tmpdir / 'source'
3487db96d56Sopenharmony_ci        source.mkdir()
3497db96d56Sopenharmony_ci        (source / '__main__.py').touch()
3507db96d56Sopenharmony_ci        args = [str(source)]
3517db96d56Sopenharmony_ci        zipapp.main(args)
3527db96d56Sopenharmony_ci        target = source.with_suffix('.pyz')
3537db96d56Sopenharmony_ci        self.assertTrue(target.is_file())
3547db96d56Sopenharmony_ci
3557db96d56Sopenharmony_ci    def test_cmdline_copy(self):
3567db96d56Sopenharmony_ci        # Test copying an archive.
3577db96d56Sopenharmony_ci        original = self.make_archive()
3587db96d56Sopenharmony_ci        target = self.tmpdir / 'target.pyz'
3597db96d56Sopenharmony_ci        args = [str(original), '-o', str(target)]
3607db96d56Sopenharmony_ci        zipapp.main(args)
3617db96d56Sopenharmony_ci        self.assertTrue(target.is_file())
3627db96d56Sopenharmony_ci
3637db96d56Sopenharmony_ci    def test_cmdline_copy_inplace(self):
3647db96d56Sopenharmony_ci        # Test copying an archive in place fails.
3657db96d56Sopenharmony_ci        original = self.make_archive()
3667db96d56Sopenharmony_ci        target = self.tmpdir / 'target.pyz'
3677db96d56Sopenharmony_ci        args = [str(original), '-o', str(original)]
3687db96d56Sopenharmony_ci        with self.assertRaises(SystemExit) as cm:
3697db96d56Sopenharmony_ci            zipapp.main(args)
3707db96d56Sopenharmony_ci        # Program should exit with a non-zero return code.
3717db96d56Sopenharmony_ci        self.assertTrue(cm.exception.code)
3727db96d56Sopenharmony_ci
3737db96d56Sopenharmony_ci    def test_cmdline_copy_change_main(self):
3747db96d56Sopenharmony_ci        # Test copying an archive doesn't allow changing __main__.py.
3757db96d56Sopenharmony_ci        original = self.make_archive()
3767db96d56Sopenharmony_ci        target = self.tmpdir / 'target.pyz'
3777db96d56Sopenharmony_ci        args = [str(original), '-o', str(target), '-m', 'foo:bar']
3787db96d56Sopenharmony_ci        with self.assertRaises(SystemExit) as cm:
3797db96d56Sopenharmony_ci            zipapp.main(args)
3807db96d56Sopenharmony_ci        # Program should exit with a non-zero return code.
3817db96d56Sopenharmony_ci        self.assertTrue(cm.exception.code)
3827db96d56Sopenharmony_ci
3837db96d56Sopenharmony_ci    @patch('sys.stdout', new_callable=io.StringIO)
3847db96d56Sopenharmony_ci    def test_info_command(self, mock_stdout):
3857db96d56Sopenharmony_ci        # Test the output of the info command.
3867db96d56Sopenharmony_ci        target = self.make_archive()
3877db96d56Sopenharmony_ci        args = [str(target), '--info']
3887db96d56Sopenharmony_ci        with self.assertRaises(SystemExit) as cm:
3897db96d56Sopenharmony_ci            zipapp.main(args)
3907db96d56Sopenharmony_ci        # Program should exit with a zero return code.
3917db96d56Sopenharmony_ci        self.assertEqual(cm.exception.code, 0)
3927db96d56Sopenharmony_ci        self.assertEqual(mock_stdout.getvalue(), "Interpreter: <none>\n")
3937db96d56Sopenharmony_ci
3947db96d56Sopenharmony_ci    def test_info_error(self):
3957db96d56Sopenharmony_ci        # Test the info command fails when the archive does not exist.
3967db96d56Sopenharmony_ci        target = self.tmpdir / 'dummy.pyz'
3977db96d56Sopenharmony_ci        args = [str(target), '--info']
3987db96d56Sopenharmony_ci        with self.assertRaises(SystemExit) as cm:
3997db96d56Sopenharmony_ci            zipapp.main(args)
4007db96d56Sopenharmony_ci        # Program should exit with a non-zero return code.
4017db96d56Sopenharmony_ci        self.assertTrue(cm.exception.code)
4027db96d56Sopenharmony_ci
4037db96d56Sopenharmony_ci
4047db96d56Sopenharmony_ciif __name__ == "__main__":
4057db96d56Sopenharmony_ci    unittest.main()
406