summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
Diffstat (limited to 'Lib')
-rw-r--r--Lib/test/test_zipfile/_functools.py9
-rw-r--r--Lib/test/test_zipfile/_itertools.py12
-rw-r--r--Lib/test/test_zipfile/_test_params.py39
-rw-r--r--Lib/test/test_zipfile/test_path.py50
-rw-r--r--Lib/zipfile/_path.py20
5 files changed, 108 insertions, 22 deletions
diff --git a/Lib/test/test_zipfile/_functools.py b/Lib/test/test_zipfile/_functools.py
new file mode 100644
index 0000000..75f2b20
--- /dev/null
+++ b/Lib/test/test_zipfile/_functools.py
@@ -0,0 +1,9 @@
+import functools
+
+
+# from jaraco.functools 3.5.2
+def compose(*funcs):
+ def compose_two(f1, f2):
+ return lambda *args, **kwargs: f1(f2(*args, **kwargs))
+
+ return functools.reduce(compose_two, funcs)
diff --git a/Lib/test/test_zipfile/_itertools.py b/Lib/test/test_zipfile/_itertools.py
new file mode 100644
index 0000000..559f3f1
--- /dev/null
+++ b/Lib/test/test_zipfile/_itertools.py
@@ -0,0 +1,12 @@
+# from more_itertools v8.13.0
+def always_iterable(obj, base_type=(str, bytes)):
+ if obj is None:
+ return iter(())
+
+ if (base_type is not None) and isinstance(obj, base_type):
+ return iter((obj,))
+
+ try:
+ return iter(obj)
+ except TypeError:
+ return iter((obj,))
diff --git a/Lib/test/test_zipfile/_test_params.py b/Lib/test/test_zipfile/_test_params.py
new file mode 100644
index 0000000..bc95b4e
--- /dev/null
+++ b/Lib/test/test_zipfile/_test_params.py
@@ -0,0 +1,39 @@
+import types
+import functools
+
+from ._itertools import always_iterable
+
+
+def parameterize(names, value_groups):
+ """
+ Decorate a test method to run it as a set of subtests.
+
+ Modeled after pytest.parametrize.
+ """
+
+ def decorator(func):
+ @functools.wraps(func)
+ def wrapped(self):
+ for values in value_groups:
+ resolved = map(Invoked.eval, always_iterable(values))
+ params = dict(zip(always_iterable(names), resolved))
+ with self.subTest(**params):
+ func(self, **params)
+
+ return wrapped
+
+ return decorator
+
+
+class Invoked(types.SimpleNamespace):
+ """
+ Wrap a function to be invoked for each usage.
+ """
+
+ @classmethod
+ def wrap(cls, func):
+ return cls(func=func)
+
+ @classmethod
+ def eval(cls, cand):
+ return cand.func() if isinstance(cand, cls) else cand
diff --git a/Lib/test/test_zipfile/test_path.py b/Lib/test/test_zipfile/test_path.py
index 3c62e9a..02253c5 100644
--- a/Lib/test/test_zipfile/test_path.py
+++ b/Lib/test/test_zipfile/test_path.py
@@ -4,7 +4,12 @@ import contextlib
import pathlib
import unittest
import string
-import functools
+import pickle
+import itertools
+
+from ._test_params import parameterize, Invoked
+from ._functools import compose
+
from test.support.os_helper import temp_dir
@@ -76,18 +81,12 @@ def build_alpharep_fixture():
return zf
-def pass_alpharep(meth):
- """
- Given a method, wrap it in a for loop that invokes method
- with each subtest.
- """
-
- @functools.wraps(meth)
- def wrapper(self):
- for alpharep in self.zipfile_alpharep():
- meth(self, alpharep=alpharep)
+alpharep_generators = [
+ Invoked.wrap(build_alpharep_fixture),
+ Invoked.wrap(compose(add_dirs, build_alpharep_fixture)),
+]
- return wrapper
+pass_alpharep = parameterize(['alpharep'], alpharep_generators)
class TestPath(unittest.TestCase):
@@ -95,12 +94,6 @@ class TestPath(unittest.TestCase):
self.fixtures = contextlib.ExitStack()
self.addCleanup(self.fixtures.close)
- def zipfile_alpharep(self):
- with self.subTest():
- yield build_alpharep_fixture()
- with self.subTest():
- yield add_dirs(build_alpharep_fixture())
-
def zipfile_ondisk(self, alpharep):
tmpdir = pathlib.Path(self.fixtures.enter_context(temp_dir()))
buffer = alpharep.fp
@@ -418,6 +411,21 @@ class TestPath(unittest.TestCase):
@pass_alpharep
def test_inheritance(self, alpharep):
cls = type('PathChild', (zipfile.Path,), {})
- for alpharep in self.zipfile_alpharep():
- file = cls(alpharep).joinpath('some dir').parent
- assert isinstance(file, cls)
+ file = cls(alpharep).joinpath('some dir').parent
+ assert isinstance(file, cls)
+
+ @parameterize(
+ ['alpharep', 'path_type', 'subpath'],
+ itertools.product(
+ alpharep_generators,
+ [str, pathlib.Path],
+ ['', 'b/'],
+ ),
+ )
+ def test_pickle(self, alpharep, path_type, subpath):
+ zipfile_ondisk = path_type(self.zipfile_ondisk(alpharep))
+
+ saved_1 = pickle.dumps(zipfile.Path(zipfile_ondisk, at=subpath))
+ restored_1 = pickle.loads(saved_1)
+ first, *rest = restored_1.iterdir()
+ assert first.read_text().startswith('content of ')
diff --git a/Lib/zipfile/_path.py b/Lib/zipfile/_path.py
index 67ef07a..aea17b6 100644
--- a/Lib/zipfile/_path.py
+++ b/Lib/zipfile/_path.py
@@ -62,7 +62,25 @@ def _difference(minuend, subtrahend):
return itertools.filterfalse(set(subtrahend).__contains__, minuend)
-class CompleteDirs(zipfile.ZipFile):
+class InitializedState:
+ """
+ Mix-in to save the initialization state for pickling.
+ """
+
+ def __init__(self, *args, **kwargs):
+ self.__args = args
+ self.__kwargs = kwargs
+ super().__init__(*args, **kwargs)
+
+ def __getstate__(self):
+ return self.__args, self.__kwargs
+
+ def __setstate__(self, state):
+ args, kwargs = state
+ super().__init__(*args, **kwargs)
+
+
+class CompleteDirs(InitializedState, zipfile.ZipFile):
"""
A ZipFile subclass that ensures that implied directories
are always included in the namelist.