blob: 48db969d0319a7646331aba47fec4aab602b2aa7 [file] [edit]
#!/usr/bin/env vpython3
# Copyright 2013 The LUCI Authors. All rights reserved.
# Use of this source code is governed under the Apache License, Version 2.0
# that can be found in the LICENSE file.
import io
import os
import subprocess
import sys
import tempfile
import unittest
import zipfile
# Mutates sys.path.
import test_env
from utils import file_path
from utils import zip_package
def check_output(*args, **kwargs):
return subprocess.check_output(
*args, stderr=subprocess.STDOUT, **kwargs
).decode("utf-8")
class ZipPackageTest(unittest.TestCase):
def setUp(self):
super(ZipPackageTest, self).setUp()
self.temp_dir = tempfile.mkdtemp(prefix="zip_package_test")
def tearDown(self):
try:
file_path.rmtree(self.temp_dir)
finally:
super(ZipPackageTest, self).tearDown()
def stage_files(self, files):
"""Populates temp directory with given files specified as a list or dict."""
if not isinstance(files, dict):
files = dict((p, b"") for p in files)
for path, content in files.items():
abs_path = os.path.join(self.temp_dir, path.replace("/", os.sep))
dir_path = os.path.dirname(abs_path)
if not os.path.exists(dir_path):
os.makedirs(dir_path)
with open(abs_path, "wb") as f:
f.write(content)
@staticmethod
def read_zip(stream):
"""Given some stream with zip data, reads and decompresses it into dict."""
zip_file = zipfile.ZipFile(stream, "r")
try:
return dict((i.filename, zip_file.read(i)) for i in zip_file.infolist())
finally:
zip_file.close()
def test_require_absolute_root(self):
# Absolute path is ok.
zip_package.ZipPackage(self.temp_dir)
# Relative path is not ok.
with self.assertRaises(AssertionError):
zip_package.ZipPackage(".")
def test_require_absolute_file_paths(self):
# Add some files to temp_dir.
self.stage_files(
[
"a.txt",
"b.py",
"c/c.txt",
]
)
# Item to add -> method used to add it.
cases = [
("a.txt", zip_package.ZipPackage.add_file),
("b.py", zip_package.ZipPackage.add_python_file),
("c", zip_package.ZipPackage.add_directory),
]
for path, method in cases:
pkg = zip_package.ZipPackage(self.temp_dir)
# Absolute path is ok.
method(pkg, os.path.join(self.temp_dir, path))
# Relative path is not ok.
with self.assertRaises(AssertionError):
method(pkg, path)
def test_added_files_are_under_root(self):
# Add some files to temp_dir.
self.stage_files(
[
"a.txt",
"p.py",
"pkg/1.txt",
"some_dir/2.txt",
]
)
# Adding using |archive_path| should work.
pkg = zip_package.ZipPackage(os.path.join(self.temp_dir, "pkg"))
pkg.add_file(os.path.join(self.temp_dir, "a.txt"), "_a.txt")
pkg.add_python_file(os.path.join(self.temp_dir, "p.py"), "_p.py")
pkg.add_directory(os.path.join(self.temp_dir, "pkg"), "_pkg")
# Adding without |archive_path| should fail.
with self.assertRaises(zip_package.ZipPackageError):
pkg.add_file(os.path.join(self.temp_dir, "a.txt"))
with self.assertRaises(zip_package.ZipPackageError):
pkg.add_python_file(os.path.join(self.temp_dir, "p.py"))
with self.assertRaises(zip_package.ZipPackageError):
pkg.add_directory(os.path.join(self.temp_dir, "a.txt"))
def test_adding_missing_files(self):
pkg = zip_package.ZipPackage(self.temp_dir)
with self.assertRaises(zip_package.ZipPackageError):
pkg.add_file(os.path.join(self.temp_dir, "im_not_here"))
with self.assertRaises(zip_package.ZipPackageError):
pkg.add_python_file(os.path.join(self.temp_dir, "im_not_here.py"))
with self.assertRaises(zip_package.ZipPackageError):
pkg.add_directory(os.path.join(self.temp_dir, "im_not_here_dir"))
def test_adding_dir_as_file(self):
# Create 'dir'.
self.stage_files(["dir/keep"])
# Try to add it as file, not a directory.
pkg = zip_package.ZipPackage(self.temp_dir)
with self.assertRaises(zip_package.ZipPackageError):
pkg.add_file(os.path.join(self.temp_dir, "dir"))
# Adding as directory works.
pkg.add_directory(os.path.join(self.temp_dir, "dir"))
def test_adding_non_python_as_python(self):
self.stage_files(["file.sh"])
pkg = zip_package.ZipPackage(self.temp_dir)
with self.assertRaises(zip_package.ZipPackageError):
pkg.add_python_file(os.path.join(self.temp_dir, "file.sh"))
def test_adding_py_instead_of_pyc(self):
self.stage_files(
[
"file.py",
"file.pyo",
"file.pyc",
]
)
for alternative in ("file.pyc", "file.pyo"):
pkg = zip_package.ZipPackage(self.temp_dir)
pkg.add_python_file(os.path.join(self.temp_dir, alternative))
self.assertIn("file.py", pkg.files)
def test_adding_same_file_twice(self):
self.stage_files(["file"])
pkg = zip_package.ZipPackage(self.temp_dir)
pkg.add_file(os.path.join(self.temp_dir, "file"))
with self.assertRaises(zip_package.ZipPackageError):
pkg.add_file(os.path.join(self.temp_dir, "file"))
def test_add_directory(self):
should_add = [
"script.py",
"a/1.txt",
"a/2.txt",
"a/b/3.txt",
"a/script.py",
]
should_ignore = [
"script.pyc",
"a/script.pyo",
".git/stuff",
".svn/stuff",
"a/.svn/stuff",
"a/b/.svn/stuff",
]
# Add a whole set and verify only files from |should_add| were added.
self.stage_files(should_add + should_ignore)
pkg = zip_package.ZipPackage(self.temp_dir)
pkg.add_directory(self.temp_dir)
self.assertEqual(set(pkg.files), set(should_add))
def test_archive_path_is_respected(self):
self.stage_files(["a", "b.py", "dir/c"])
pkg = zip_package.ZipPackage(self.temp_dir)
pkg.add_file(os.path.join(self.temp_dir, "a"), "d1/a")
pkg.add_python_file(os.path.join(self.temp_dir, "b.py"), "d2/b.py")
pkg.add_directory(os.path.join(self.temp_dir, "dir"), "d3")
self.assertEqual(set(pkg.files), set(["d1/a", "d2/b.py", "d3/c"]))
def test_add_buffer(self):
pkg = zip_package.ZipPackage(self.temp_dir)
pkg.add_buffer("buf1", b"")
self.assertEqual(list(pkg.files), ["buf1"])
# No text types.
with self.assertRaises(AssertionError):
pkg.add_buffer("buf2", "unicode")
def test_zipping(self):
data = {"a": b"123", "b/c": b"456"}
self.stage_files(data)
pkg = zip_package.ZipPackage(self.temp_dir)
pkg.add_directory(self.temp_dir)
# Test zip_into_buffer produces readable zip with same content.
for compress in (True, False):
buf = pkg.zip_into_buffer(compress=compress)
self.assertEqual(data, self.read_zip(io.BytesIO(buf)))
# Test zip_into_file produces readable zip with same content.
for compress in (True, False):
path = os.path.join(self.temp_dir, "pkg.zip")
pkg.zip_into_file(path, compress=compress)
with open(path, "rb") as f:
self.assertEqual(data, self.read_zip(f))
def test_repeatable_content(self):
content = []
for _ in range(2):
# Build temp dir content from scratch.
assert not os.listdir(self.temp_dir)
self.stage_files({"a": b"123", "b": b"456", "c": b"789"})
# Zip it.
pkg = zip_package.ZipPackage(self.temp_dir)
pkg.add_directory(self.temp_dir)
content.append(pkg.zip_into_buffer())
# Clear everything.
for name in os.listdir(self.temp_dir):
os.remove(os.path.join(self.temp_dir, name))
# Contents of both runs should match exactly.
self.assertEqual(content[0], content[1])
def test_running_from_zip(self):
# Test assumes that it runs from a normal checkout, not a zip.
self.assertFalse(zip_package.is_zipped_module(sys.modules[__name__]))
self.assertIsNone(zip_package.get_module_zip_archive(sys.modules[__name__]))
self.assertTrue(
os.path.abspath(zip_package.get_main_script_path()).startswith(
test_env.CLIENT_DIR
)
)
# Build executable zip that calls same functions.
pkg = zip_package.ZipPackage(self.temp_dir)
pkg.add_directory(os.path.join(test_env.CLIENT_DIR, "utils"), "utils")
pkg.add_buffer(
"__main__.py",
b"\n".join(
[
b"import sys",
b"",
b"from utils import zip_package",
b"",
b"print(zip_package.is_zipped_module(sys.modules[__name__]))",
b"print(zip_package.get_module_zip_archive(sys.modules[__name__]))",
b"print(zip_package.get_main_script_path())",
]
),
)
zip_file = os.path.join(self.temp_dir, "out.zip")
pkg.zip_into_file(zip_file)
# Run the zip, validate results.
actual = check_output([sys.executable, zip_file]).strip().splitlines()
self.assertEqual(["True", zip_file, zip_file], actual)
def test_extract_resource(self):
pkg = zip_package.ZipPackage(self.temp_dir)
pkg.add_directory(os.path.join(test_env.CLIENT_DIR, "utils"), "utils")
pkg.add_buffer("cert.pem", b"Certificate\n")
pkg.add_buffer(
"__main__.py",
b"\n".join(
[
b"import sys",
b"from utils import zip_package",
b"print(zip_package.extract_resource(",
b" sys.modules[__name__], 'cert.pem'))",
]
),
)
zip_file = os.path.join(self.temp_dir, "out.zip")
pkg.zip_into_file(zip_file)
actual = check_output([sys.executable, zip_file]).strip()
self.assertEqual(tempfile.gettempdir(), os.path.dirname(actual))
basename = os.path.basename(actual)
self.assertTrue(basename.startswith(".zip_pkg-"), actual)
self.assertTrue(basename.endswith("-cert.pem"), actual)
def test_extract_resource_temp_dir(self):
pkg = zip_package.ZipPackage(self.temp_dir)
pkg.add_directory(os.path.join(test_env.CLIENT_DIR, "utils"), "utils")
pkg.add_buffer("cert.pem", b"Certificate\n")
pkg.add_buffer(
"__main__.py",
b"\n".join(
[
b"import sys",
b"from utils import zip_package",
b"print(zip_package.extract_resource(",
b" sys.modules[__name__], 'cert.pem', %r))" % self.temp_dir,
]
),
)
zip_file = os.path.join(self.temp_dir, "out.zip")
pkg.zip_into_file(zip_file)
actual = check_output([sys.executable, zip_file]).strip()
expected = os.path.join(
self.temp_dir,
"e47a41780d9cb4a1234e4915b14443bdaa8fae9f821b00f0f2fed719661572f6-cert."
"pem",
)
self.assertEqual(expected, actual)
if __name__ == "__main__":
test_env.main()