import os
import stat
import unittest
import mozunit
import mozpack.path as mozpath
from mozpack.copier import FileCopier, FileRegistry, FileRegistrySubtree, Jarrer
from mozpack.errors import ErrorMessage
from mozpack.files import ExistingFile, GeneratedFile
from mozpack.mozjar import JarReader
from mozpack.test.test_files import MatchTestTemplate, MockDest, TestWithTmpDir
class BaseTestFileRegistry(MatchTestTemplate):
def add(self, path):
self.registry.add(path, GeneratedFile(path))
def do_check(self, pattern, result):
self.checked = True
if result:
self.assertTrue(self.registry.contains(pattern))
else:
self.assertFalse(self.registry.contains(pattern))
self.assertEqual(self.registry.match(pattern), result)
def do_test_file_registry(self, registry):
self.registry = registry
self.registry.add("foo", GeneratedFile(b"foo"))
bar = GeneratedFile(b"bar")
self.registry.add("bar", bar)
self.assertEqual(self.registry.paths(), ["foo", "bar"])
self.assertEqual(self.registry["bar"], bar)
self.assertRaises(
ErrorMessage, self.registry.add, "foo", GeneratedFile(b"foo2")
)
self.assertRaises(ErrorMessage, self.registry.remove, "qux")
self.assertRaises(
ErrorMessage, self.registry.add, "foo/bar", GeneratedFile(b"foobar")
)
self.assertRaises(
ErrorMessage, self.registry.add, "foo/bar/baz", GeneratedFile(b"foobar")
)
self.assertEqual(self.registry.paths(), ["foo", "bar"])
self.registry.remove("foo")
self.assertEqual(self.registry.paths(), ["bar"])
self.registry.remove("bar")
self.assertEqual(self.registry.paths(), [])
self.prepare_match_test()
self.do_match_test()
self.assertTrue(self.checked)
self.assertEqual(
self.registry.paths(),
[
"bar",
"foo/bar",
"foo/baz",
"foo/qux/1",
"foo/qux/bar",
"foo/qux/2/test",
"foo/qux/2/test2",
],
)
self.registry.remove("foo/qux")
self.assertEqual(self.registry.paths(), ["bar", "foo/bar", "foo/baz"])
self.registry.add("foo/qux", GeneratedFile(b"fooqux"))
self.assertEqual(
self.registry.paths(), ["bar", "foo/bar", "foo/baz", "foo/qux"]
)
self.registry.remove("foo/b*")
self.assertEqual(self.registry.paths(), ["bar", "foo/qux"])
self.assertEqual([f for f, c in self.registry], ["bar", "foo/qux"])
self.assertEqual(len(self.registry), 2)
self.add("foo/.foo")
self.assertTrue(self.registry.contains("foo/.foo"))
def do_test_registry_paths(self, registry):
self.registry = registry
self.registry.add("foo", GeneratedFile(b"foo"))
self.assertRaises(
ErrorMessage, self.registry.add, "foo/bar", GeneratedFile(b"foobar")
)
self.registry.add("bar/baz", GeneratedFile(b"barbaz"))
self.assertRaises(ErrorMessage, self.registry.add, "bar", GeneratedFile(b"bar"))
self.registry.add("bar/zot", GeneratedFile(b"barzot"))
self.assertRaises(ErrorMessage, self.registry.add, "bar", GeneratedFile(b"bar"))
self.registry.remove("bar/baz")
self.assertRaises(ErrorMessage, self.registry.add, "bar", GeneratedFile(b"bar"))
self.registry.remove("bar/zot")
self.registry.add("bar/zot", GeneratedFile(b"barzot"))
class TestFileRegistry(BaseTestFileRegistry, unittest.TestCase):
def test_partial_paths(self):
cases = {
"foo/bar/baz/zot": ["foo/bar/baz", "foo/bar", "foo"],
"foo/bar": ["foo"],
"bar": [],
}
reg = FileRegistry()
for path, parts in cases.items():
self.assertEqual(reg._partial_paths(path), parts)
def test_file_registry(self):
self.do_test_file_registry(FileRegistry())
def test_registry_paths(self):
self.do_test_registry_paths(FileRegistry())
def test_required_directories(self):
self.registry = FileRegistry()
self.registry.add("foo", GeneratedFile(b"foo"))
self.assertEqual(self.registry.required_directories(), set())
self.registry.add("bar/baz", GeneratedFile(b"barbaz"))
self.assertEqual(self.registry.required_directories(), {"bar"})
self.registry.add("bar/zot", GeneratedFile(b"barzot"))
self.assertEqual(self.registry.required_directories(), {"bar"})
self.registry.add("bar/zap/zot", GeneratedFile(b"barzapzot"))
self.assertEqual(self.registry.required_directories(), {"bar", "bar/zap"})
self.registry.remove("bar/zap/zot")
self.assertEqual(self.registry.required_directories(), {"bar"})
self.registry.remove("bar/baz")
self.assertEqual(self.registry.required_directories(), {"bar"})
self.registry.remove("bar/zot")
self.assertEqual(self.registry.required_directories(), set())
self.registry.add("x/y/z", GeneratedFile(b"xyz"))
self.assertEqual(self.registry.required_directories(), {"x", "x/y"})
class TestFileRegistrySubtree(BaseTestFileRegistry, unittest.TestCase):
def test_file_registry_subtree_base(self):
registry = FileRegistry()
self.assertEqual(registry, FileRegistrySubtree("", registry))
self.assertNotEqual(registry, FileRegistrySubtree("base", registry))
def create_registry(self):
registry = FileRegistry()
registry.add("foo/bar", GeneratedFile(b"foo/bar"))
registry.add("baz/qux", GeneratedFile(b"baz/qux"))
return FileRegistrySubtree("base/root", registry)
def test_file_registry_subtree(self):
self.do_test_file_registry(self.create_registry())
def test_registry_paths_subtree(self):
FileRegistry()
self.do_test_registry_paths(self.create_registry())
class TestFileCopier(TestWithTmpDir):
def all_dirs(self, base):
all_dirs = set()
for root, dirs, files in os.walk(base):
if not dirs:
all_dirs.add(mozpath.relpath(root, base))
return all_dirs
def all_files(self, base):
all_files = set()
for root, dirs, files in os.walk(base):
for f in files:
all_files.add(mozpath.join(mozpath.relpath(root, base), f))
return all_files
def test_file_copier(self):
copier = FileCopier()
copier.add("foo/bar", GeneratedFile(b"foobar"))
copier.add("foo/qux", GeneratedFile(b"fooqux"))
copier.add("foo/deep/nested/directory/file", GeneratedFile(b"fooz"))
copier.add("bar", GeneratedFile(b"bar"))
copier.add("qux/foo", GeneratedFile(b"quxfoo"))
copier.add("qux/bar", GeneratedFile(b""))
result = copier.copy(self.tmpdir)
self.assertEqual(self.all_files(self.tmpdir), set(copier.paths()))
self.assertEqual(
self.all_dirs(self.tmpdir), set(["foo/deep/nested/directory", "qux"])
)
self.assertEqual(
result.updated_files,
set(self.tmppath(p) for p in self.all_files(self.tmpdir)),
)
self.assertEqual(result.existing_files, set())
self.assertEqual(result.removed_files, set())
self.assertEqual(result.removed_directories, set())
copier.remove("foo")
copier.add("test", GeneratedFile(b"test"))
result = copier.copy(self.tmpdir)
self.assertEqual(self.all_files(self.tmpdir), set(copier.paths()))
self.assertEqual(self.all_dirs(self.tmpdir), set(["qux"]))
self.assertEqual(
result.removed_files,
set(
self.tmppath(p)
for p in ("foo/bar", "foo/qux", "foo/deep/nested/directory/file")
),
)
def test_symlink_directory_replaced(self):
if not self.symlink_supported:
return
dest = self.tmppath("dest")
copier = FileCopier()
copier.add("foo/bar/baz", GeneratedFile(b"foobarbaz"))
os.makedirs(self.tmppath("dest/foo"))
dummy = self.tmppath("dummy")
os.mkdir(dummy)
link = self.tmppath("dest/foo/bar")
os.symlink(dummy, link)
result = copier.copy(dest)
st = os.lstat(link)
self.assertFalse(stat.S_ISLNK(st.st_mode))
self.assertTrue(stat.S_ISDIR(st.st_mode))
self.assertEqual(self.all_files(dest), set(copier.paths()))
self.assertEqual(result.removed_directories, set())
self.assertEqual(len(result.updated_files), 1)
def test_remove_unaccounted_directory_symlinks(self):
if not self.symlink_supported:
return
dest = self.tmppath("dest")
copier = FileCopier()
copier.add("foo/bar/baz", GeneratedFile(b"foobarbaz"))
os.makedirs(self.tmppath("dest/foo"))
dummy = self.tmppath("dummy")
os.mkdir(dummy)
os.mkdir(self.tmppath("dest/zot"))
link = self.tmppath("dest/zot/zap")
os.symlink(dummy, link)
result = copier.copy(
dest,
remove_unaccounted=False,
remove_empty_directories=True,
remove_all_directory_symlinks=False,
)
st = os.lstat(link)
self.assertTrue(stat.S_ISLNK(st.st_mode))
self.assertFalse(stat.S_ISDIR(st.st_mode))
self.assertEqual(self.all_files(dest), set(copier.paths()))
self.assertEqual(self.all_dirs(dest), set(["foo/bar"]))
self.assertEqual(result.removed_directories, set())
self.assertEqual(len(result.updated_files), 1)
result = copier.copy(
dest,
remove_unaccounted=True,
remove_empty_directories=False,
remove_all_directory_symlinks=False,
)
st = os.lstat(self.tmppath("dest/zot"))
self.assertFalse(stat.S_ISLNK(st.st_mode))
self.assertTrue(stat.S_ISDIR(st.st_mode))
self.assertEqual(result.removed_files, set([link]))
self.assertEqual(result.removed_directories, set())
self.assertEqual(self.all_files(dest), set(copier.paths()))
self.assertEqual(self.all_dirs(dest), set(["foo/bar", "zot"]))
link = self.tmppath("dest/zot/zap")
os.symlink(dummy, link)
result = copier.copy(
dest,
remove_unaccounted=True,
remove_empty_directories=True,
remove_all_directory_symlinks=False,
)
self.assertEqual(result.removed_files, set([link]))
self.assertEqual(result.removed_directories, set([self.tmppath("dest/zot")]))
self.assertEqual(self.all_files(dest), set(copier.paths()))
self.assertEqual(self.all_dirs(dest), set(["foo/bar"]))
def test_permissions(self):
with open(self.tmppath("dummy"), "a"):
pass
p = self.tmppath("no_perms")
with open(p, "a"):
pass
os.chmod(p, 0o400)
os.chmod(self.tmpdir, 0o400)
copier = FileCopier()
copier.add("dummy", GeneratedFile(b"content"))
result = copier.copy(self.tmpdir)
self.assertEqual(result.removed_files_count, 1)
self.assertFalse(os.path.exists(p))
def test_no_remove(self):
copier = FileCopier()
copier.add("foo", GeneratedFile(b"foo"))
with open(self.tmppath("bar"), "a"):
pass
os.mkdir(self.tmppath("emptydir"))
d = self.tmppath("populateddir")
os.mkdir(d)
with open(self.tmppath("populateddir/foo"), "a"):
pass
result = copier.copy(self.tmpdir, remove_unaccounted=False)
self.assertEqual(
self.all_files(self.tmpdir), set(["foo", "bar", "populateddir/foo"])
)
self.assertEqual(self.all_dirs(self.tmpdir), set(["populateddir"]))
self.assertEqual(result.removed_files, set())
self.assertEqual(result.removed_directories, set([self.tmppath("emptydir")]))
def test_no_remove_empty_directories(self):
copier = FileCopier()
copier.add("foo", GeneratedFile(b"foo"))
with open(self.tmppath("bar"), "a"):
pass
os.mkdir(self.tmppath("emptydir"))
d = self.tmppath("populateddir")
os.mkdir(d)
with open(self.tmppath("populateddir/foo"), "a"):
pass
result = copier.copy(
self.tmpdir, remove_unaccounted=False, remove_empty_directories=False
)
self.assertEqual(
self.all_files(self.tmpdir), set(["foo", "bar", "populateddir/foo"])
)
self.assertEqual(self.all_dirs(self.tmpdir), set(["emptydir", "populateddir"]))
self.assertEqual(result.removed_files, set())
self.assertEqual(result.removed_directories, set())
def test_optional_exists_creates_unneeded_directory(self):
dest = self.tmppath("dest")
copier = FileCopier()
copier.add("foo/bar", ExistingFile(required=False))
result = copier.copy(dest)
st = os.lstat(self.tmppath("dest/foo"))
self.assertFalse(stat.S_ISLNK(st.st_mode))
self.assertTrue(stat.S_ISDIR(st.st_mode))
self.assertEqual(len(result.updated_files), 0)
self.assertIn(self.tmppath("dest/foo/bar"), result.existing_files)
def test_remove_unaccounted_file_registry(self):
dest = self.tmppath("dest")
copier = FileCopier()
copier.add("foo/bar/baz", GeneratedFile(b"foobarbaz"))
copier.add("foo/bar/qux", GeneratedFile(b"foobarqux"))
copier.add("foo/hoge/fuga", GeneratedFile(b"foohogefuga"))
copier.add("foo/toto/tata", GeneratedFile(b"footototata"))
os.makedirs(os.path.join(dest, "bar"))
with open(os.path.join(dest, "bar", "bar"), "w") as fh:
fh.write("barbar")
os.makedirs(os.path.join(dest, "foo", "toto"))
with open(os.path.join(dest, "foo", "toto", "toto"), "w") as fh:
fh.write("foototototo")
result = copier.copy(dest, remove_unaccounted=False)
self.assertEqual(
self.all_files(dest), set(copier.paths()) | {"foo/toto/toto", "bar/bar"}
)
self.assertEqual(
self.all_dirs(dest), {"foo/bar", "foo/hoge", "foo/toto", "bar"}
)
copier2 = FileCopier()
copier2.add("foo/hoge/fuga", GeneratedFile(b"foohogefuga"))
result = copier2.copy(dest, remove_unaccounted=copier)
self.assertEqual(
self.all_files(dest), set(copier2.paths()) | {"foo/toto/toto", "bar/bar"}
)
self.assertEqual(self.all_dirs(dest), {"foo/hoge", "foo/toto", "bar"})
self.assertEqual(result.updated_files, {self.tmppath("dest/foo/hoge/fuga")})
self.assertEqual(result.existing_files, set())
self.assertEqual(
result.removed_files,
{
self.tmppath(p)
for p in ("dest/foo/bar/baz", "dest/foo/bar/qux", "dest/foo/toto/tata")
},
)
self.assertEqual(result.removed_directories, {self.tmppath("dest/foo/bar")})
class TestJarrer(unittest.TestCase):
def check_jar(self, dest, copier):
jar = JarReader(fileobj=dest)
self.assertEqual([f.filename for f in jar], copier.paths())
for f in jar:
self.assertEqual(f.uncompressed_data.read(), copier[f.filename].content)
def test_jarrer(self):
copier = Jarrer()
copier.add("foo/bar", GeneratedFile(b"foobar"))
copier.add("foo/qux", GeneratedFile(b"fooqux"))
copier.add("foo/deep/nested/directory/file", GeneratedFile(b"fooz"))
copier.add("bar", GeneratedFile(b"bar"))
copier.add("qux/foo", GeneratedFile(b"quxfoo"))
copier.add("qux/bar", GeneratedFile(b""))
dest = MockDest()
copier.copy(dest)
self.check_jar(dest, copier)
copier.remove("foo")
copier.add("test", GeneratedFile(b"test"))
copier.copy(dest)
self.check_jar(dest, copier)
copier.remove("test")
copier.add("test", GeneratedFile(b"replaced-content"))
copier.copy(dest)
self.check_jar(dest, copier)
copier.copy(dest)
self.check_jar(dest, copier)
preloaded = ["qux/bar", "bar"]
copier.preload(preloaded)
copier.copy(dest)
dest.seek(0)
jar = JarReader(fileobj=dest)
self.assertEqual(
[f.filename for f in jar],
preloaded + [p for p in copier.paths() if p not in preloaded],
)
self.assertEqual(jar.last_preloaded, preloaded[-1])
def test_jarrer_compress(self):
copier = Jarrer()
copier.add("foo/bar", GeneratedFile(b"ffffff"))
copier.add("foo/qux", GeneratedFile(b"ffffff"), compress=False)
dest = MockDest()
copier.copy(dest)
self.check_jar(dest, copier)
dest.seek(0)
jar = JarReader(fileobj=dest)
self.assertTrue(jar["foo/bar"].compressed)
self.assertFalse(jar["foo/qux"].compressed)
if __name__ == "__main__":
mozunit.main()