fix(tests): don't test for case sensitivity on windows

This commit is contained in:
Travis Abendshien
2026-09-15 22:08:47 -07:00
parent 9319944863
commit 4576e69cba
5 changed files with 36 additions and 25 deletions
@@ -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."""
+3 -4
View File
@@ -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)}))
+1 -7
View File
@@ -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)
+3 -3
View File
@@ -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]
+25 -5
View File
@@ -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()