Skip to content

Commit 60fad7a

Browse files
author
Jack O'Connor
committed
Update zipfile to 3.13.5
Notes: - I have to skip some brand new tests due to shift_jis encoding not being supported - `test_write_filtered_python_package` marked as `expectedFailure` with "AttributeError: module 'os' has no attribute 'supports_effective_ids'" - I didn't want to do a partial or full update to os module in this PR
1 parent 0533474 commit 60fad7a

File tree

15 files changed

+2712
-825
lines changed

15 files changed

+2712
-825
lines changed

Lib/test/test_zipfile/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import os
2+
from test.support import load_package_tests
3+
4+
def load_tests(*args):
5+
return load_package_tests(os.path.dirname(__file__), *args)

Lib/test/test_zipfile/__main__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import unittest
2+
3+
from . import load_tests # noqa: F401
4+
5+
6+
if __name__ == "__main__":
7+
unittest.main()

Lib/test/test_zipfile/_path/__init__.py

Whitespace-only changes.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import functools
2+
3+
4+
# from jaraco.functools 3.5.2
5+
def compose(*funcs):
6+
def compose_two(f1, f2):
7+
return lambda *args, **kwargs: f1(f2(*args, **kwargs))
8+
9+
return functools.reduce(compose_two, funcs)
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import itertools
2+
from collections import deque
3+
from itertools import islice
4+
5+
6+
# from jaraco.itertools 6.3.0
7+
class Counter:
8+
"""
9+
Wrap an iterable in an object that stores the count of items
10+
that pass through it.
11+
12+
>>> items = Counter(range(20))
13+
>>> items.count
14+
0
15+
>>> values = list(items)
16+
>>> items.count
17+
20
18+
"""
19+
20+
def __init__(self, i):
21+
self.count = 0
22+
self.iter = zip(itertools.count(1), i)
23+
24+
def __iter__(self):
25+
return self
26+
27+
def __next__(self):
28+
self.count, result = next(self.iter)
29+
return result
30+
31+
32+
# from more_itertools v8.13.0
33+
def always_iterable(obj, base_type=(str, bytes)):
34+
if obj is None:
35+
return iter(())
36+
37+
if (base_type is not None) and isinstance(obj, base_type):
38+
return iter((obj,))
39+
40+
try:
41+
return iter(obj)
42+
except TypeError:
43+
return iter((obj,))
44+
45+
46+
# from more_itertools v9.0.0
47+
def consume(iterator, n=None):
48+
"""Advance *iterable* by *n* steps. If *n* is ``None``, consume it
49+
entirely.
50+
Efficiently exhausts an iterator without returning values. Defaults to
51+
consuming the whole iterator, but an optional second argument may be
52+
provided to limit consumption.
53+
>>> i = (x for x in range(10))
54+
>>> next(i)
55+
0
56+
>>> consume(i, 3)
57+
>>> next(i)
58+
4
59+
>>> consume(i)
60+
>>> next(i)
61+
Traceback (most recent call last):
62+
File "<stdin>", line 1, in <module>
63+
StopIteration
64+
If the iterator has fewer items remaining than the provided limit, the
65+
whole iterator will be consumed.
66+
>>> i = (x for x in range(3))
67+
>>> consume(i, 5)
68+
>>> next(i)
69+
Traceback (most recent call last):
70+
File "<stdin>", line 1, in <module>
71+
StopIteration
72+
"""
73+
# Use functions that consume iterators at C speed.
74+
if n is None:
75+
# feed the entire iterator into a zero-length deque
76+
deque(iterator, maxlen=0)
77+
else:
78+
# advance to the empty slice starting at position n
79+
next(islice(iterator, n, n), None)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import importlib
2+
import unittest
3+
4+
5+
def import_or_skip(name):
6+
try:
7+
return importlib.import_module(name)
8+
except ImportError: # pragma: no cover
9+
raise unittest.SkipTest(f'Unable to import {name}')
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import functools
2+
import types
3+
4+
from ._itertools import always_iterable
5+
6+
7+
def parameterize(names, value_groups):
8+
"""
9+
Decorate a test method to run it as a set of subtests.
10+
11+
Modeled after pytest.parametrize.
12+
"""
13+
14+
def decorator(func):
15+
@functools.wraps(func)
16+
def wrapped(self):
17+
for values in value_groups:
18+
resolved = map(Invoked.eval, always_iterable(values))
19+
params = dict(zip(always_iterable(names), resolved))
20+
with self.subTest(**params):
21+
func(self, **params)
22+
23+
return wrapped
24+
25+
return decorator
26+
27+
28+
class Invoked(types.SimpleNamespace):
29+
"""
30+
Wrap a function to be invoked for each usage.
31+
"""
32+
33+
@classmethod
34+
def wrap(cls, func):
35+
return cls(func=func)
36+
37+
@classmethod
38+
def eval(cls, cand):
39+
return cand.func() if isinstance(cand, cls) else cand
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import io
2+
import itertools
3+
import math
4+
import re
5+
import string
6+
import unittest
7+
import zipfile
8+
9+
from ._functools import compose
10+
from ._itertools import consume
11+
from ._support import import_or_skip
12+
13+
big_o = import_or_skip('big_o')
14+
pytest = import_or_skip('pytest')
15+
16+
17+
class TestComplexity(unittest.TestCase):
18+
@pytest.mark.flaky
19+
def test_implied_dirs_performance(self):
20+
best, others = big_o.big_o(
21+
compose(consume, zipfile._path.CompleteDirs._implied_dirs),
22+
lambda size: [
23+
'/'.join(string.ascii_lowercase + str(n)) for n in range(size)
24+
],
25+
max_n=1000,
26+
min_n=1,
27+
)
28+
assert best <= big_o.complexities.Linear
29+
30+
def make_zip_path(self, depth=1, width=1) -> zipfile.Path:
31+
"""
32+
Construct a Path with width files at every level of depth.
33+
"""
34+
zf = zipfile.ZipFile(io.BytesIO(), mode='w')
35+
pairs = itertools.product(self.make_deep_paths(depth), self.make_names(width))
36+
for path, name in pairs:
37+
zf.writestr(f"{path}{name}.txt", b'')
38+
zf.filename = "big un.zip"
39+
return zipfile.Path(zf)
40+
41+
@classmethod
42+
def make_names(cls, width, letters=string.ascii_lowercase):
43+
"""
44+
>>> list(TestComplexity.make_names(1))
45+
['a']
46+
>>> list(TestComplexity.make_names(2))
47+
['a', 'b']
48+
>>> list(TestComplexity.make_names(30))
49+
['aa', 'ab', ..., 'bd']
50+
>>> list(TestComplexity.make_names(17124))
51+
['aaa', 'aab', ..., 'zip']
52+
"""
53+
# determine how many products are needed to produce width
54+
n_products = max(1, math.ceil(math.log(width, len(letters))))
55+
inputs = (letters,) * n_products
56+
combinations = itertools.product(*inputs)
57+
names = map(''.join, combinations)
58+
return itertools.islice(names, width)
59+
60+
@classmethod
61+
def make_deep_paths(cls, depth):
62+
return map(cls.make_deep_path, range(depth))
63+
64+
@classmethod
65+
def make_deep_path(cls, depth):
66+
return ''.join(('d/',) * depth)
67+
68+
def test_baseline_regex_complexity(self):
69+
best, others = big_o.big_o(
70+
lambda path: re.fullmatch(r'[^/]*\\.txt', path),
71+
self.make_deep_path,
72+
max_n=100,
73+
min_n=1,
74+
)
75+
assert best <= big_o.complexities.Constant
76+
77+
@pytest.mark.flaky
78+
def test_glob_depth(self):
79+
best, others = big_o.big_o(
80+
lambda path: consume(path.glob('*.txt')),
81+
self.make_zip_path,
82+
max_n=100,
83+
min_n=1,
84+
)
85+
assert best <= big_o.complexities.Linear
86+
87+
@pytest.mark.flaky
88+
def test_glob_width(self):
89+
best, others = big_o.big_o(
90+
lambda path: consume(path.glob('*.txt')),
91+
lambda size: self.make_zip_path(width=size),
92+
max_n=100,
93+
min_n=1,
94+
)
95+
assert best <= big_o.complexities.Linear
96+
97+
@pytest.mark.flaky
98+
def test_glob_width_and_depth(self):
99+
best, others = big_o.big_o(
100+
lambda path: consume(path.glob('*.txt')),
101+
lambda size: self.make_zip_path(depth=size, width=size),
102+
max_n=10,
103+
min_n=1,
104+
)
105+
assert best <= big_o.complexities.Linear

0 commit comments

Comments
 (0)