mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-28 08:28:53 -03:00
fix(paths): deduplicate LoRA path overlap (#871)
This commit is contained in:
@@ -73,6 +73,15 @@ class DummyScanner(ModelScanner):
|
||||
}
|
||||
|
||||
|
||||
class MultiRootDummyScanner(DummyScanner):
|
||||
def __init__(self, roots: List[Path]):
|
||||
self._roots = [str(root) for root in roots]
|
||||
super().__init__(roots[0])
|
||||
|
||||
def get_model_roots(self) -> List[str]:
|
||||
return list(self._roots)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def reset_model_scanner_singletons():
|
||||
ModelScanner._instances.clear()
|
||||
@@ -559,3 +568,59 @@ async def test_count_model_files_handles_symlink_loops(tmp_path: Path):
|
||||
count = scanner._count_model_files()
|
||||
|
||||
assert count == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_initialize_cache_dedupes_files_reachable_via_primary_symlink_and_extra_root(
|
||||
tmp_path: Path,
|
||||
):
|
||||
loras_root = tmp_path / "loras"
|
||||
loras_root.mkdir()
|
||||
extra_root = tmp_path / "extra"
|
||||
extra_root.mkdir()
|
||||
(extra_root / "one.txt").write_text("one", encoding="utf-8")
|
||||
(loras_root / "link").symlink_to(extra_root, target_is_directory=True)
|
||||
|
||||
scanner = MultiRootDummyScanner([loras_root, extra_root])
|
||||
|
||||
await scanner._initialize_cache()
|
||||
cache = await scanner.get_cached_data()
|
||||
|
||||
assert len(cache.raw_data) == 1
|
||||
assert cache.raw_data[0]["file_path"] == _normalize_path(loras_root / "link" / "one.txt")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_reconcile_cache_removes_duplicate_alias_when_same_real_file_seen_once(
|
||||
tmp_path: Path,
|
||||
):
|
||||
loras_root = tmp_path / "loras"
|
||||
loras_root.mkdir()
|
||||
extra_root = tmp_path / "extra"
|
||||
extra_root.mkdir()
|
||||
real_file = extra_root / "one.txt"
|
||||
real_file.write_text("one", encoding="utf-8")
|
||||
(loras_root / "link").symlink_to(extra_root, target_is_directory=True)
|
||||
|
||||
scanner = MultiRootDummyScanner([loras_root, extra_root])
|
||||
await scanner._initialize_cache()
|
||||
|
||||
duplicate_entry = {
|
||||
"file_path": _normalize_path(extra_root / "one.txt"),
|
||||
"folder": "",
|
||||
"sha256": "hash-one",
|
||||
"tags": ["alpha"],
|
||||
"model_name": "one",
|
||||
"size": 1,
|
||||
"modified": 1.0,
|
||||
"license_flags": DEFAULT_LICENSE_FLAGS,
|
||||
}
|
||||
scanner._cache.raw_data.append(duplicate_entry)
|
||||
scanner._cache.add_to_version_index(duplicate_entry)
|
||||
scanner._hash_index.add_entry("hash-one", duplicate_entry["file_path"])
|
||||
|
||||
await scanner._reconcile_cache()
|
||||
|
||||
cache = await scanner.get_cached_data()
|
||||
cached_paths = {item["file_path"] for item in cache.raw_data}
|
||||
assert cached_paths == {_normalize_path(loras_root / "link" / "one.txt")}
|
||||
|
||||
@@ -590,6 +590,97 @@ def test_extra_paths_validation_no_overlap_with_other_libraries(manager, tmp_pat
|
||||
manager.update_extra_folder_paths({"loras": [str(lora_dir1)]})
|
||||
|
||||
|
||||
def test_extra_paths_validation_no_overlap_with_active_primary_lora_root(manager, tmp_path):
|
||||
"""Test that extra LoRA paths cannot overlap the active library primary LoRA roots."""
|
||||
real_lora_dir = tmp_path / "loras_real"
|
||||
real_lora_dir.mkdir()
|
||||
lora_link = tmp_path / "loras_link"
|
||||
lora_link.symlink_to(real_lora_dir, target_is_directory=True)
|
||||
|
||||
manager.create_library(
|
||||
"library1",
|
||||
folder_paths={"loras": [str(lora_link)]},
|
||||
activate=True,
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match="overlap with the active library's primary LoRA roots"):
|
||||
manager.update_extra_folder_paths({"loras": [str(real_lora_dir)]})
|
||||
|
||||
|
||||
def test_extra_paths_validation_no_overlap_with_active_primary_lora_root_case_insensitive(
|
||||
manager, monkeypatch, tmp_path
|
||||
):
|
||||
"""Overlap validation should treat differently-cased Windows-like paths as the same path."""
|
||||
real_lora_dir = tmp_path / "loras_real"
|
||||
real_lora_dir.mkdir()
|
||||
lora_link = tmp_path / "loras_link"
|
||||
lora_link.symlink_to(real_lora_dir, target_is_directory=True)
|
||||
|
||||
manager.create_library(
|
||||
"library1",
|
||||
folder_paths={"loras": [str(lora_link)]},
|
||||
activate=True,
|
||||
)
|
||||
|
||||
original_exists = settings_manager_module.os.path.exists
|
||||
original_realpath = settings_manager_module.os.path.realpath
|
||||
original_normcase = settings_manager_module.os.path.normcase
|
||||
|
||||
def fake_exists(path):
|
||||
if isinstance(path, str) and path.lower() in {str(lora_link).lower(), str(real_lora_dir).lower()}:
|
||||
return True
|
||||
return original_exists(path)
|
||||
|
||||
def fake_realpath(path):
|
||||
if isinstance(path, str) and path.lower() == str(lora_link).lower():
|
||||
return str(real_lora_dir)
|
||||
return original_realpath(path)
|
||||
|
||||
monkeypatch.setattr(settings_manager_module.os.path, "exists", fake_exists)
|
||||
monkeypatch.setattr(settings_manager_module.os.path, "realpath", fake_realpath)
|
||||
monkeypatch.setattr(settings_manager_module.os.path, "normcase", lambda value: original_normcase(value).lower())
|
||||
|
||||
with pytest.raises(ValueError, match="overlap with the active library's primary LoRA roots"):
|
||||
manager.update_extra_folder_paths({"loras": [str(real_lora_dir).upper()]})
|
||||
|
||||
|
||||
def test_extra_paths_validation_allows_missing_non_overlapping_lora_root(manager, tmp_path):
|
||||
"""Missing non-overlapping extra LoRA paths should not be rejected."""
|
||||
lora_dir = tmp_path / "loras"
|
||||
lora_dir.mkdir()
|
||||
missing_extra = tmp_path / "missing_loras"
|
||||
|
||||
manager.create_library(
|
||||
"library1",
|
||||
folder_paths={"loras": [str(lora_dir)]},
|
||||
activate=True,
|
||||
)
|
||||
|
||||
manager.update_extra_folder_paths({"loras": [str(missing_extra)]})
|
||||
|
||||
extra_paths = manager.get_extra_folder_paths()
|
||||
assert extra_paths["loras"] == [str(missing_extra)]
|
||||
|
||||
|
||||
def test_extra_paths_validation_rejects_primary_root_first_level_symlink_target(manager, tmp_path):
|
||||
"""Extra LoRA paths should be rejected when already reachable via a first-level symlink under the primary root."""
|
||||
lora_dir = tmp_path / "loras"
|
||||
lora_dir.mkdir()
|
||||
external_dir = tmp_path / "external_loras"
|
||||
external_dir.mkdir()
|
||||
link_dir = lora_dir / "link"
|
||||
link_dir.symlink_to(external_dir, target_is_directory=True)
|
||||
|
||||
manager.create_library(
|
||||
"library1",
|
||||
folder_paths={"loras": [str(lora_dir)]},
|
||||
activate=True,
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match="overlap with the active library's primary LoRA roots"):
|
||||
manager.update_extra_folder_paths({"loras": [str(external_dir)]})
|
||||
|
||||
|
||||
def test_delete_library_switches_active(manager, tmp_path):
|
||||
other_dir = tmp_path / "other"
|
||||
other_dir.mkdir()
|
||||
|
||||
Reference in New Issue
Block a user