Feature keep file 1135 (#1139)
* Added gitignorefile * Fixed gitignorefile for os.PathLike paths * --keep now follows .gitignore rules * Fixed ruff QA error * Added support for .osxphotos_keep file * Added reference to .osxphotos_keep * Added tests for .osxphotos_keep * Updated help text for --cleanup, --keep
This commit is contained in:
137
tests/test_gitignorefile_cache.py
Normal file
137
tests/test_gitignorefile_cache.py
Normal file
@@ -0,0 +1,137 @@
|
||||
import io
|
||||
import itertools
|
||||
import os
|
||||
import stat
|
||||
import tempfile
|
||||
import unittest
|
||||
import unittest.mock
|
||||
|
||||
import osxphotos.gitignorefile
|
||||
|
||||
|
||||
class TestCache(unittest.TestCase):
|
||||
def test_simple(self):
|
||||
def normalize_path(path):
|
||||
return os.path.abspath(path).replace(os.sep, "/")
|
||||
|
||||
class StatResult:
|
||||
def __init__(self, is_file=False):
|
||||
self.st_ino = id(self)
|
||||
self.st_dev = 0
|
||||
self.st_mode = stat.S_IFREG if is_file else stat.S_IFDIR
|
||||
|
||||
def isdir(self):
|
||||
return self.st_mode == stat.S_IFDIR
|
||||
|
||||
def isfile(self):
|
||||
return self.st_mode == stat.S_IFREG
|
||||
|
||||
class Stat:
|
||||
def __init__(self, directories, files):
|
||||
self.__filesystem = {}
|
||||
for path in directories:
|
||||
self.__filesystem[normalize_path(path)] = StatResult()
|
||||
for path in files:
|
||||
self.__filesystem[normalize_path(path)] = StatResult(True)
|
||||
|
||||
def __call__(self, path):
|
||||
try:
|
||||
return self.__filesystem[normalize_path(path)]
|
||||
|
||||
except KeyError:
|
||||
raise FileNotFoundError()
|
||||
|
||||
for ignore_file_name in (".gitignore", ".mylovelytoolignore"):
|
||||
with self.subTest(ignore_file_name=ignore_file_name):
|
||||
my_stat = Stat(
|
||||
[
|
||||
"/home/vladimir/project/directory/subdirectory",
|
||||
"/home/vladimir/project/directory",
|
||||
"/home/vladimir/project",
|
||||
"/home/vladimir",
|
||||
"/home",
|
||||
"/",
|
||||
],
|
||||
[
|
||||
"/home/vladimir/project/directory/subdirectory/subdirectory/file.txt",
|
||||
"/home/vladimir/project/directory/subdirectory/subdirectory/file2.txt",
|
||||
"/home/vladimir/project/directory/subdirectory/subdirectory/file3.txt",
|
||||
"/home/vladimir/project/directory/subdirectory/file.txt",
|
||||
"/home/vladimir/project/directory/subdirectory/file2.txt",
|
||||
"/home/vladimir/project/directory/%s" % ignore_file_name,
|
||||
"/home/vladimir/project/directory/file.txt",
|
||||
"/home/vladimir/project/directory/file2.txt",
|
||||
"/home/vladimir/project/file.txt",
|
||||
"/home/vladimir/project/%s" % ignore_file_name,
|
||||
"/home/vladimir/file.txt",
|
||||
],
|
||||
)
|
||||
|
||||
def mock_open(path):
|
||||
data = {
|
||||
normalize_path(
|
||||
"/home/vladimir/project/directory/%s" % ignore_file_name
|
||||
): ["file.txt"],
|
||||
normalize_path(
|
||||
"/home/vladimir/project/%s" % ignore_file_name
|
||||
): ["file2.txt"],
|
||||
}
|
||||
|
||||
statistics["open"] += 1
|
||||
try:
|
||||
return io.StringIO("\n".join(data[normalize_path(path)]))
|
||||
|
||||
except KeyError:
|
||||
raise FileNotFoundError()
|
||||
|
||||
def mock_isdir(path):
|
||||
statistics["isdir"] += 1
|
||||
try:
|
||||
return my_stat(path).isdir()
|
||||
except FileNotFoundError:
|
||||
return False
|
||||
|
||||
def mock_isfile(path):
|
||||
statistics["isfile"] += 1
|
||||
try:
|
||||
return my_stat(path).isfile()
|
||||
except FileNotFoundError:
|
||||
return False
|
||||
|
||||
data = {
|
||||
"/home/vladimir/project/directory/subdirectory/file.txt": True,
|
||||
"/home/vladimir/project/directory/subdirectory/file2.txt": True,
|
||||
"/home/vladimir/project/directory/subdirectory/subdirectory/file.txt": True,
|
||||
"/home/vladimir/project/directory/subdirectory/subdirectory/file2.txt": True,
|
||||
"/home/vladimir/project/directory/subdirectory/subdirectory/file3.txt": False,
|
||||
"/home/vladimir/project/directory/file.txt": True,
|
||||
"/home/vladimir/project/directory/file2.txt": True,
|
||||
"/home/vladimir/project/file.txt": False,
|
||||
"/home/vladimir/file.txt": False, # No rules and no `isdir` calls for this file.
|
||||
}
|
||||
|
||||
# 9! == 362880 combinations.
|
||||
for permutation in itertools.islice(
|
||||
itertools.permutations(data.items()), 0, None, 6 * 8
|
||||
):
|
||||
statistics = {"open": 0, "isdir": 0, "isfile": 0}
|
||||
|
||||
with unittest.mock.patch("builtins.open", mock_open):
|
||||
with unittest.mock.patch("os.path.isdir", mock_isdir):
|
||||
with unittest.mock.patch("os.path.isfile", mock_isfile):
|
||||
matches = osxphotos.gitignorefile.Cache(
|
||||
ignore_names=[ignore_file_name]
|
||||
)
|
||||
for path, expected in permutation:
|
||||
self.assertEqual(matches(path), expected)
|
||||
|
||||
self.assertEqual(statistics["open"], 2)
|
||||
self.assertEqual(statistics["isdir"], len(data) - 1)
|
||||
self.assertEqual(statistics["isfile"], 7) # Unique path fragments.
|
||||
|
||||
def test_wrong_symlink(self):
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
matches = osxphotos.gitignorefile.Cache()
|
||||
os.makedirs(f"{d}/.venv/bin")
|
||||
os.symlink(f"/nonexistent-path-{id(self)}", f"{d}/.venv/bin/python")
|
||||
self.assertFalse(matches(f"{d}/.venv/bin/python"))
|
||||
Reference in New Issue
Block a user