|
3 | 3 | import io |
4 | 4 | import pathlib |
5 | 5 | import stat |
| 6 | +import subprocess |
6 | 7 | import sys |
7 | 8 | import tempfile |
8 | 9 | import unittest |
9 | 10 | import zipapp |
10 | 11 | import zipfile |
11 | | -from test.support import requires_zlib |
| 12 | +from test.support import requires_zlib, requires_subprocess |
12 | 13 | from test.support import os_helper |
13 | 14 |
|
14 | 15 | from unittest.mock import patch |
@@ -455,5 +456,128 @@ def test_info_error(self): |
455 | 456 | self.assertTrue(cm.exception.code) |
456 | 457 |
|
457 | 458 |
|
| 459 | +@requires_subprocess() |
| 460 | +class CommandLineTest(unittest.TestCase): |
| 461 | + """Test the ``python -m zipapp`` command-line interface via subprocess.""" |
| 462 | + |
| 463 | + def setUp(self): |
| 464 | + tmpdir = tempfile.TemporaryDirectory() |
| 465 | + self.addCleanup(tmpdir.cleanup) |
| 466 | + self.tmpdir = pathlib.Path(tmpdir.name) |
| 467 | + |
| 468 | + def _run(self, *args): |
| 469 | + return subprocess.run( |
| 470 | + [sys.executable, '-m', 'zipapp', *args], |
| 471 | + capture_output=True, |
| 472 | + ) |
| 473 | + |
| 474 | + def _make_source(self, *, main_content=b''): |
| 475 | + source = self.tmpdir / 'source' |
| 476 | + source.mkdir() |
| 477 | + (source / '__main__.py').write_bytes(main_content) |
| 478 | + return source |
| 479 | + |
| 480 | + def _make_archive(self, *, interpreter=None): |
| 481 | + source = self._make_source() |
| 482 | + target = self.tmpdir / 'source.pyz' |
| 483 | + zipapp.create_archive(source, target, interpreter=interpreter) |
| 484 | + return target |
| 485 | + |
| 486 | + def test_help(self): |
| 487 | + result = self._run('--help') |
| 488 | + self.assertEqual(result.returncode, 0) |
| 489 | + self.assertIn(b'usage:', result.stdout) |
| 490 | + |
| 491 | + def test_create_archive(self): |
| 492 | + source = self._make_source() |
| 493 | + result = self._run(str(source)) |
| 494 | + self.assertEqual(result.returncode, 0, result.stderr) |
| 495 | + target = source.with_suffix('.pyz') |
| 496 | + self.assertTrue(target.is_file()) |
| 497 | + |
| 498 | + def test_create_archive_with_output(self): |
| 499 | + source = self._make_source() |
| 500 | + target = self.tmpdir / 'out.pyz' |
| 501 | + result = self._run(str(source), '-o', str(target)) |
| 502 | + self.assertEqual(result.returncode, 0, result.stderr) |
| 503 | + self.assertTrue(target.is_file()) |
| 504 | + |
| 505 | + def test_create_archive_with_interpreter(self): |
| 506 | + source = self._make_source() |
| 507 | + target = self.tmpdir / 'out.pyz' |
| 508 | + result = self._run(str(source), '-o', str(target), '-p', '/usr/bin/env python3') |
| 509 | + self.assertEqual(result.returncode, 0, result.stderr) |
| 510 | + self.assertTrue(target.is_file()) |
| 511 | + self.assertEqual(zipapp.get_interpreter(target), '/usr/bin/env python3') |
| 512 | + |
| 513 | + def test_create_archive_with_main(self): |
| 514 | + source = self.tmpdir / 'pkg' |
| 515 | + source.mkdir() |
| 516 | + (source / 'mod.py').write_text('def fn(): pass\n', encoding='utf-8') |
| 517 | + target = self.tmpdir / 'out.pyz' |
| 518 | + result = self._run(str(source), '-o', str(target), '-m', 'mod:fn') |
| 519 | + self.assertEqual(result.returncode, 0, result.stderr) |
| 520 | + self.assertTrue(target.is_file()) |
| 521 | + with zipfile.ZipFile(target, 'r') as z: |
| 522 | + self.assertIn('__main__.py', z.namelist()) |
| 523 | + |
| 524 | + @requires_zlib() |
| 525 | + def test_create_archive_with_compress(self): |
| 526 | + source = self._make_source(main_content=b'print("hello")\n') |
| 527 | + target = self.tmpdir / 'out.pyz' |
| 528 | + result = self._run(str(source), '-o', str(target), '-c') |
| 529 | + self.assertEqual(result.returncode, 0, result.stderr) |
| 530 | + with zipfile.ZipFile(target, 'r') as z: |
| 531 | + self.assertEqual(z.getinfo('__main__.py').compress_type, |
| 532 | + zipfile.ZIP_DEFLATED) |
| 533 | + |
| 534 | + def test_info_command(self): |
| 535 | + target = self._make_archive(interpreter='python3') |
| 536 | + result = self._run(str(target), '--info') |
| 537 | + self.assertEqual(result.returncode, 0, result.stderr) |
| 538 | + self.assertIn(b'Interpreter: python3', result.stdout) |
| 539 | + |
| 540 | + def test_info_no_interpreter(self): |
| 541 | + target = self._make_archive() |
| 542 | + result = self._run(str(target), '--info') |
| 543 | + self.assertEqual(result.returncode, 0, result.stderr) |
| 544 | + self.assertIn(b'Interpreter: <none>', result.stdout) |
| 545 | + |
| 546 | + def test_info_nonexistent_file(self): |
| 547 | + target = self.tmpdir / 'nonexistent.pyz' |
| 548 | + result = self._run(str(target), '--info') |
| 549 | + self.assertNotEqual(result.returncode, 0) |
| 550 | + |
| 551 | + def test_copy_archive(self): |
| 552 | + original = self._make_archive() |
| 553 | + target = self.tmpdir / 'copy.pyz' |
| 554 | + result = self._run(str(original), '-o', str(target)) |
| 555 | + self.assertEqual(result.returncode, 0, result.stderr) |
| 556 | + self.assertTrue(target.is_file()) |
| 557 | + |
| 558 | + def test_copy_archive_inplace_fails(self): |
| 559 | + original = self._make_archive() |
| 560 | + result = self._run(str(original), '-o', str(original)) |
| 561 | + self.assertNotEqual(result.returncode, 0) |
| 562 | + |
| 563 | + def test_copy_archive_change_main_fails(self): |
| 564 | + original = self._make_archive() |
| 565 | + target = self.tmpdir / 'copy.pyz' |
| 566 | + result = self._run(str(original), '-o', str(target), '-m', 'foo:bar') |
| 567 | + self.assertNotEqual(result.returncode, 0) |
| 568 | + |
| 569 | + def test_no_source_argument(self): |
| 570 | + result = self._run() |
| 571 | + self.assertNotEqual(result.returncode, 0) |
| 572 | + self.assertIn(b'usage:', result.stderr) |
| 573 | + |
| 574 | + def test_no_main_in_source(self): |
| 575 | + source = self.tmpdir / 'empty' |
| 576 | + source.mkdir() |
| 577 | + (source / 'foo.py').touch() |
| 578 | + result = self._run(str(source)) |
| 579 | + self.assertNotEqual(result.returncode, 0) |
| 580 | + |
| 581 | + |
458 | 582 | if __name__ == "__main__": |
459 | 583 | unittest.main() |
0 commit comments