Skip to content

Commit 2e17bbf

Browse files
committed
gh-117829 : added tests
1 parent 59e2089 commit 2e17bbf

File tree

2 files changed

+136
-2
lines changed

2 files changed

+136
-2
lines changed

Doc/library/zipapp.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,12 @@ The following options are understood:
9696

9797
.. option:: --include
9898

99-
Accept glob-like filtering for files to be allowed in output archive. This will run
99+
Accept glob-patterns filtering for files to be allowed in output archive. This will run
100100
first if :option:`--exclude` is also used.
101101

102102
.. option:: --exclude
103103

104-
Accept glob-like filtering files to be denied inclusion in output archive. This will
104+
Accept glob-patterns filtering files to be denied inclusion in output archive. This will
105105
run second if :option:`--include` is also used.
106106

107107

Lib/test/test_zipapp.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,12 @@ def make_archive(self):
395395
target = self.tmpdir / 'source.pyz'
396396
zipapp.create_archive(source, target)
397397
return target
398+
399+
def _make_tree(self, root: pathlib.Path, files: list[str]) -> None:
400+
for rel in files:
401+
p = root / rel
402+
p.parent.mkdir(parents=True, exist_ok=True)
403+
p.touch()
398404

399405
def test_cmdline_create(self):
400406
# Test the basic command line API.
@@ -454,6 +460,134 @@ def test_info_error(self):
454460
# Program should exit with a non-zero return code.
455461
self.assertTrue(cm.exception.code)
456462

463+
def test_cmdline_include_then_exclude(self):
464+
source = self.tmpdir / 'source'
465+
source.mkdir()
466+
self._make_tree(source, [
467+
'__main__.py',
468+
'foo/a.py',
469+
'foo/b.pyc',
470+
'bar/c.txt',
471+
])
472+
473+
# Include 'foo' (directory implies subtree), then exclude *.pyc
474+
args = [
475+
str(source),
476+
'--include', '*.py',
477+
'--include', 'foo',
478+
'--exclude', '**/*.pyc']
479+
zipapp.main(args)
480+
481+
target = source.with_suffix('.pyz')
482+
with zipfile.ZipFile(target, 'r') as z:
483+
names = set(z.namelist())
484+
# Always contains __main__.py unless overridden by -m
485+
self.assertIn('__main__.py', names)
486+
self.assertIn('foo/', names)
487+
self.assertIn('foo/a.py', names)
488+
# Excluded by pattern
489+
self.assertNotIn('foo/b.pyc', names)
490+
# Not included at all since include restricted to 'foo'
491+
self.assertNotIn('bar/', names)
492+
self.assertNotIn('bar/c.txt', names)
493+
494+
def test_cmdline_multiple_includes_commas_and_extend(self):
495+
source = self.tmpdir / 'src'
496+
source.mkdir()
497+
self._make_tree(source, [
498+
'__main__.py',
499+
'pkg/x.py',
500+
'pkg/y.txt',
501+
'data/readme.txt',
502+
'data/keep.bin',
503+
])
504+
505+
args = [
506+
str(source),
507+
'--include', 'pkg,data/*.txt',
508+
'--include', 'data/keep.bin',
509+
]
510+
zipapp.main(args)
511+
512+
target = source.with_suffix('.pyz')
513+
with zipfile.ZipFile(target, 'r') as z:
514+
names = set(z.namelist())
515+
# did not include root files
516+
self.assertNotIn('__main__.py', names)
517+
# from "pkg"
518+
self.assertIn('pkg/', names)
519+
self.assertIn('pkg/x.py', names)
520+
self.assertIn('pkg/y.txt', names)
521+
# from "data/*.txt"
522+
self.assertIn('data/readme.txt', names)
523+
# from the second --include
524+
self.assertIn('data/keep.bin', names)
525+
526+
def test_cmdline_exclude_directory_over_included_files(self):
527+
source = self.tmpdir / 'tree'
528+
source.mkdir()
529+
self._make_tree(source, [
530+
'__main__.py',
531+
'foo/a.py',
532+
'foo/b.py',
533+
'bar/c.py',
534+
])
535+
536+
# Include all *.py, but exclude 'foo/**' entirely
537+
args = [
538+
str(source),
539+
'--include', '*.py',
540+
'--exclude', 'foo',
541+
]
542+
zipapp.main(args)
543+
544+
target = source.with_suffix('.pyz')
545+
with zipfile.ZipFile(target, 'r') as z:
546+
names = set(z.namelist())
547+
self.assertIn('__main__.py', names)
548+
# foo is excluded even though files match *.py
549+
self.assertNotIn('foo/', names)
550+
self.assertNotIn('foo/a.py', names)
551+
self.assertNotIn('foo/b.py', names)
552+
# bar/c.py remains
553+
self.assertIn('bar/c.py', names)
554+
555+
def test_cmdline_normalization_and_dir_implies_subtree(self):
556+
source = self.tmpdir / 'norm'
557+
source.mkdir()
558+
self._make_tree(source, [
559+
'__main__.py',
560+
'a/b/c.txt',
561+
'a/d/e.py',
562+
'x/y/z.py',
563+
])
564+
565+
# Use Windows-style backslashes and a leading './'
566+
# 'a\\b' should imply both 'a/b' and 'a/b/**'
567+
args = [
568+
str(source),
569+
'--include', r'.\a\b',
570+
'--include', r'a\d', # also directory → subtree
571+
'--exclude', '**/*.py', # exclude all *.py after include
572+
]
573+
zipapp.main(args)
574+
575+
target = source.with_suffix('.pyz')
576+
with zipfile.ZipFile(target, 'r') as z:
577+
names = set(z.namelist())
578+
# did not include root files
579+
self.assertNotIn('__main__.py', names)
580+
# from a/b subtree, c.txt should be present
581+
self.assertIn('a/b/', names)
582+
self.assertIn('a/b/c.txt', names)
583+
# from a/d subtree, e.py would match include but then be excluded by **/*.py
584+
self.assertIn('a/d/', names)
585+
self.assertNotIn('a/d/e.py', names)
586+
# x/y/z.py not included at all (not in includes)
587+
self.assertNotIn('x/', names)
588+
self.assertNotIn('x/y/', names)
589+
self.assertNotIn('x/y/z.py', names)
590+
457591

458592
if __name__ == "__main__":
459593
unittest.main()

0 commit comments

Comments
 (0)