Skip to content

Commit 625963a

Browse files
committed
Add high-density archive test with 20M nodes
- Add TestManyNodes to blackbox test suite to verify O(D) mounting performance and inode counting. - Update Makefile to include many_nodes.zip in full test runs.
1 parent d4ac6a5 commit 625963a

4 files changed

Lines changed: 82 additions & 1 deletion

File tree

tests/blackbox/Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
all: check
1919

20-
check: data/big.zip data/collisions.zip data/deep.zip
20+
check: data/big.zip data/collisions.zip data/deep.zip data/many_nodes.zip
2121
python3 test.py
2222

2323
check-fast:
@@ -32,6 +32,9 @@ data/collisions.zip: make_collisions.py
3232
data/deep.zip: make_deep.py
3333
python3 make_deep.py
3434

35+
data/many_nodes.zip: make_many_nodes.py
36+
python3 make_many_nodes.py
37+
3538
rebuild:
3639
make -C ../../lib all
3740
make -C ../.. all

tests/blackbox/data/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
big.zip
33
collisions.zip
44
deep.zip
5+
many_nodes.zip

tests/blackbox/make_many_nodes.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#!/bin/python3
2+
3+
# Copyright 2026 Google LLC
4+
#
5+
# This program is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# This program is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
18+
import os
19+
import os.path
20+
from zipfile import ZipFile
21+
22+
dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data')
23+
tmp = os.path.join(dir, 'many_nodes.zip~')
24+
25+
try:
26+
with ZipFile(tmp, 'w') as z:
27+
for i in range(100):
28+
print('\rWriting many_nodes.zip... %3d %%' % i, end='', flush=True)
29+
for j in range(100):
30+
z.writestr('%02d/%02d' % (i, j) + '/x' * 2000, b'File %02d/%02d\n' % (i, j))
31+
32+
print('\r\033[2KDone', flush=True)
33+
os.replace(tmp, os.path.join(dir, 'many_nodes.zip'))
34+
except:
35+
os.remove(tmp)

tests/blackbox/test.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1913,6 +1913,47 @@ def TestBigZip(options=[]):
19131913
logging.debug(f'Unmounted {zip_path!r} from {mount_point!r}')
19141914

19151915

1916+
# Tests that an archive with many nodes and deep hierarchies can be mounted.
1917+
def TestManyNodes():
1918+
zip_name = 'many_nodes.zip'
1919+
logging.info(f'Test {zip_name!r}')
1920+
with tempfile.TemporaryDirectory() as mount_point:
1921+
zip_path = os.path.join(script_dir, 'data', zip_name)
1922+
logging.debug(f'Mounting {zip_path!r} on {mount_point!r}...')
1923+
subprocess.run(
1924+
[mount_program, '--', zip_path, mount_point],
1925+
check=True,
1926+
capture_output=True,
1927+
input='',
1928+
encoding='UTF-8',
1929+
)
1930+
try:
1931+
logging.debug(f'Mounted ZIP {zip_path!r} on {mount_point!r}')
1932+
1933+
# Check a few files.
1934+
for i, j in [(0, 0), (50, 50), (99, 99)]:
1935+
path = os.path.join(mount_point, '%02d' % i, '%02d' % j, *(['x'] * 2000))
1936+
want_content = b'File %02d/%02d\n' % (i, j)
1937+
with open(path, 'rb') as f:
1938+
got_content = f.read()
1939+
if got_content != want_content:
1940+
LogError(f'Want content: {want_content!r}, Got content: {got_content!r}')
1941+
1942+
st = os.statvfs(mount_point)
1943+
# total nodes = 1 (root) + 100 (i) + 100*100 (j) + 10,000 * 1999 (x dirs) + 10,000 (files)
1944+
# = 1 + 100 + 10,000 + 19,990,000 + 10,000 = 20,010,101
1945+
want_inodes = 20010101
1946+
if st.f_files != want_inodes:
1947+
LogError(
1948+
f'Mismatch for st.f_files: got: {st.f_files}, want: {want_inodes}'
1949+
)
1950+
1951+
finally:
1952+
logging.debug(f'Unmounting {zip_path!r} from {mount_point!r}...')
1953+
subprocess.run(['umount', '-l', mount_point], check=True)
1954+
logging.debug(f'Unmounted {zip_path!r} from {mount_point!r}')
1955+
1956+
19161957
# Tests that a big file can be accessed in somewhat random order even with no
19171958
# cache file.
19181959
def TestBigZipNoCache(options=['-o', 'nocache']):
@@ -2675,6 +2716,7 @@ def TestInvalidZip():
26752716
TestBigZip()
26762717
TestBigZip(options=['-o', 'precache'])
26772718
TestBigZipNoCache()
2719+
TestManyNodes()
26782720

26792721
if error_count:
26802722
LogError(f'FAIL: There were {error_count} errors')

0 commit comments

Comments
 (0)