mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-09-23 02:42:39 +02:00
fix(tests): don't test for case sensitivity on windows
This commit is contained in:
@@ -93,6 +93,7 @@ from tagstudio.core.library.alchemy.models import (
|
||||
from tagstudio.core.library.alchemy.visitors import SQLBoolExpressionBuilder
|
||||
from tagstudio.core.library.ignore import migrate_ext_list
|
||||
from tagstudio.core.library.json.library import Library as JsonLibrary
|
||||
from tagstudio.core.utils.filesystem import is_fs_case_sensitive
|
||||
from tagstudio.core.utils.normalization import norm_path
|
||||
from tagstudio.core.utils.stat import get_date_created, get_date_modified, get_file_size
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
@@ -219,13 +220,14 @@ class Library:
|
||||
engine: Engine | None = None
|
||||
path_cache: dict[Path, int] | None = None
|
||||
duplicate_path_entry_ids: list[int] | None = None
|
||||
is_case_sensitive_fs: bool | None = None
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.dupe_entries_count: int = -1 # NOTE: For internal management.
|
||||
self.dupe_files_count: int = -1
|
||||
self.ignored_entries_count: int = -1
|
||||
self.unlinked_entries_count: int = -1
|
||||
# TODO: Make this dependant on the filesystem(s) of the library root directories.
|
||||
self.is_fs_case_sensitive = is_fs_case_sensitive()
|
||||
|
||||
def close(self):
|
||||
if self.engine:
|
||||
@@ -234,7 +236,6 @@ class Library:
|
||||
self.folder = None
|
||||
self.path_cache = None
|
||||
self.duplicate_path_entry_ids = None
|
||||
self.is_case_sensitive_fs = None
|
||||
|
||||
self.dupe_entries_count = -1
|
||||
self.dupe_files_count = -1
|
||||
@@ -819,10 +820,7 @@ class Library:
|
||||
return False
|
||||
|
||||
def _path_cache_key(self, path: Path) -> Path:
|
||||
case_sensitive = (
|
||||
self.is_case_sensitive_fs if self.is_case_sensitive_fs is not None else True
|
||||
)
|
||||
return norm_path(path, case_sensitive=case_sensitive)
|
||||
return norm_path(path, case_sensitive=self.is_fs_case_sensitive)
|
||||
|
||||
def _cache_add_path(self, entry_id: int, path: Path) -> None:
|
||||
"""Keep the path cache consistent with a newly-added or relinked entry."""
|
||||
|
||||
@@ -101,7 +101,7 @@ def _scan_with_internal_scanner(scan_dir: Path, ignore_patterns: list[str]) -> I
|
||||
logger.info("[Scanners] Using internal scanner for scanning", path=scan_dir)
|
||||
matcher = glob.compile(patterns=ignore_to_glob(ignore_patterns), flags=PATH_GLOB_FLAGS)
|
||||
|
||||
def walk(dir_path: Path, ancestors: frozenset[tuple[int, int]]) -> Iterator[Path]:
|
||||
def walk(dir_path: Path, ancestors: frozenset[str]) -> Iterator[Path]:
|
||||
try:
|
||||
dir_items = list(os.scandir(dir_path))
|
||||
except OSError as e:
|
||||
@@ -119,11 +119,10 @@ def _scan_with_internal_scanner(scan_dir: Path, ignore_patterns: list[str]) -> I
|
||||
|
||||
# Check for and handle cyclical symlinks
|
||||
if stat.S_ISDIR(item_stat.st_mode):
|
||||
key = (item_stat.st_dev, item_stat.st_ino)
|
||||
key = os.path.realpath(item.path)
|
||||
if key not in ancestors:
|
||||
yield from walk(Path(item.path), ancestors | {key})
|
||||
else:
|
||||
yield rel
|
||||
|
||||
root_stat = scan_dir.stat()
|
||||
yield from walk(scan_dir, frozenset({(root_stat.st_dev, root_stat.st_ino)}))
|
||||
yield from walk(scan_dir, frozenset({os.path.realpath(scan_dir)}))
|
||||
|
||||
@@ -14,7 +14,6 @@ from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.library.alchemy.models import Entry
|
||||
from tagstudio.core.library.ignore import Ignore
|
||||
from tagstudio.core.library.scanners import scan_paths
|
||||
from tagstudio.core.utils.filesystem import is_fs_case_sensitive
|
||||
from tagstudio.core.utils.normalization import norm_path
|
||||
from tagstudio.core.utils.stat import get_date_created, get_date_modified, get_file_size
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
@@ -60,11 +59,6 @@ class LibrarySyncEngine:
|
||||
self.unlinked_entries = []
|
||||
self.relinked_entries = []
|
||||
|
||||
def _get_case_sensitivity(self) -> bool:
|
||||
if self.library.is_case_sensitive_fs is None:
|
||||
self.library.is_case_sensitive_fs = is_fs_case_sensitive()
|
||||
return self.library.is_case_sensitive_fs
|
||||
|
||||
def sync_dir(
|
||||
self, library_dir: Path, force_internal_scanner: bool = False
|
||||
) -> Iterator[tuple[int, int]]:
|
||||
@@ -85,7 +79,7 @@ class LibrarySyncEngine:
|
||||
self.reset()
|
||||
self.cancelled = False
|
||||
|
||||
case_sensitive = self._get_case_sensitivity()
|
||||
case_sensitive = self.library.is_fs_case_sensitive
|
||||
cache = self.library.get_or_build_path_cache()
|
||||
unvisited = set(cache.keys())
|
||||
ignore_patterns = Ignore.get_patterns(library_dir)
|
||||
|
||||
@@ -95,7 +95,7 @@ def test_path_cache_untouched_when_not_yet_built(library: Library):
|
||||
|
||||
def test_path_cache_self_maintained_by_add_entries(library: Library):
|
||||
"""`add_entries()` must keep an already-built path cache up to date on its own."""
|
||||
library.is_case_sensitive_fs = True
|
||||
library.is_fs_case_sensitive = True
|
||||
cache = library.get_or_build_path_cache()
|
||||
assert Path("added_directly.txt") not in cache
|
||||
|
||||
@@ -105,7 +105,7 @@ def test_path_cache_self_maintained_by_add_entries(library: Library):
|
||||
|
||||
|
||||
def test_path_cache_self_maintained_by_remove_entries(library: Library):
|
||||
library.is_case_sensitive_fs = True
|
||||
library.is_fs_case_sensitive = True
|
||||
cache = library.get_or_build_path_cache()
|
||||
entry = Entry(path=Path("to_remove.txt"), fields=[])
|
||||
entry_id = library.add_entries([entry])[0]
|
||||
@@ -116,7 +116,7 @@ def test_path_cache_self_maintained_by_remove_entries(library: Library):
|
||||
|
||||
|
||||
def test_path_cache_self_maintained_by_update_entry_path(library: Library):
|
||||
library.is_case_sensitive_fs = True
|
||||
library.is_fs_case_sensitive = True
|
||||
cache = library.get_or_build_path_cache()
|
||||
entry = Entry(path=Path("old_location.txt"), fields=[])
|
||||
entry_id = library.add_entries([entry])[0]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
# pyright: reportPrivateUsage=false
|
||||
|
||||
import os
|
||||
import platform
|
||||
import unicodedata
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
@@ -123,7 +124,7 @@ def test_sync_duplicate_case_collision_merged(library: Library):
|
||||
"""A duplicate entry displaced by a case-insensitive collision must be merged automatically."""
|
||||
library_dir = unwrap(library.library_dir)
|
||||
engine = LibrarySyncEngine(library=library)
|
||||
library.is_case_sensitive_fs = False # Force a case-insensitive collision
|
||||
library.is_fs_case_sensitive = False # Force a case-insensitive collision
|
||||
|
||||
(library_dir / "Dupe").mkdir()
|
||||
(library_dir / "Dupe" / "photo.jpg").touch()
|
||||
@@ -198,9 +199,14 @@ def test_sync_auto_relink_merges_nfc_nfd_duplicate(library: Library):
|
||||
assert unwrap(library.get_entry_full(kept_id)).id == kept_id
|
||||
|
||||
|
||||
@pytest.mark.skipif(platform.system() == "Windows", reason="Windows is treated as case-insensitive")
|
||||
@pytest.mark.parametrize("library", [TemporaryDirectory()], indirect=True)
|
||||
def test_sync_auto_relink_respects_case_sensitivity(library: Library):
|
||||
"""The filename-only fallback pass must respect the library's case-sensitivity setting."""
|
||||
"""The filename-only fallback pass must not relink across a case difference when sensitive.
|
||||
|
||||
|
||||
Currently applies broadly to any non-Windows system.
|
||||
"""
|
||||
library_dir = unwrap(library.library_dir)
|
||||
engine = LibrarySyncEngine(library=library)
|
||||
|
||||
@@ -208,12 +214,26 @@ def test_sync_auto_relink_respects_case_sensitivity(library: Library):
|
||||
(library_dir / "Other" / "name.txt").touch()
|
||||
library.add_entries([Entry(path=Path("Folder/Name.txt"), fields=[])])
|
||||
|
||||
library.is_case_sensitive_fs = True
|
||||
library.is_fs_case_sensitive = True
|
||||
list(engine.sync_dir(library_dir, force_internal_scanner=True))
|
||||
assert engine.relinked_entries_count == 0
|
||||
assert Path("Folder/Name.txt") in {e.path for e in engine.unlinked_entries}
|
||||
|
||||
library.is_case_sensitive_fs = False
|
||||
|
||||
@pytest.mark.parametrize("library", [TemporaryDirectory()], indirect=True)
|
||||
def test_sync_auto_relink_respects_case_sensitivity_false(library: Library):
|
||||
"""The filename-only fallback pass must relink across a case difference when insensitive.
|
||||
|
||||
Currently only applies to Windows, but the test can run on any system.
|
||||
"""
|
||||
library_dir = unwrap(library.library_dir)
|
||||
engine = LibrarySyncEngine(library=library)
|
||||
|
||||
(library_dir / "Other").mkdir()
|
||||
(library_dir / "Other" / "name.txt").touch()
|
||||
library.add_entries([Entry(path=Path("Folder/Name.txt"), fields=[])])
|
||||
|
||||
library.is_fs_case_sensitive = False
|
||||
list(engine.sync_dir(library_dir, force_internal_scanner=True))
|
||||
assert engine.relinked_entries_count == 1
|
||||
assert engine.relinked_entries[0].path == Path("Folder/Name.txt")
|
||||
@@ -680,7 +700,7 @@ def test_sync_cancel_skips_duplicate_merge(library: Library):
|
||||
"""Cancelling right after the scan must stop duplicate merging before it starts."""
|
||||
library_dir = unwrap(library.library_dir)
|
||||
engine = LibrarySyncEngine(library=library)
|
||||
library.is_case_sensitive_fs = False # Force a collision using a case difference
|
||||
library.is_fs_case_sensitive = False # Force a collision using a case difference
|
||||
|
||||
(library_dir / "Dupe").mkdir()
|
||||
(library_dir / "Dupe" / "photo.jpg").touch()
|
||||
|
||||
Reference in New Issue
Block a user