Files
ComfyUI-Lora-Manager/tests/config/test_config_save_paths.py
T
Will Miao c16de68640 test: make the backend suite pass on Windows
The new informational Windows job surfaced 91 failures: four real bugs (fixed in
the previous two commits) and a long tail of POSIX assumptions in the tests
themselves. None of them assert behaviour that differs on Windows, so they are
fixed rather than skipped wherever the behaviour is the same:

* Path spelling: expectations built with `str(tmp_path)` are compared through an
  identity helper (`normalize_root_identity`) or the platform's separator, since
  the code hands out forward-slashed business paths while `str()` is native.
* Root labels: `_root_display_labels` prefixes the drive on Windows ("C: loras"),
  so label assertions compare the final path component or derive the expected
  label from the same helper.
* `~` expansion reads USERPROFILE (ntpath), not HOME; timer granularity rounds
  0.1s down; a case-only rename counts as "already named" via `os.path.normcase`.
* Genuinely POSIX-only strategies are skipped with a reason instead of failing:
  the headless clipboard fallback, dbus file-manager selection, xdg-open, and
  the case-fold index test that simulates a case-sensitive filesystem.
2026-10-12 11:35:33 +08:00

1186 lines
44 KiB
Python

import logging
from typing import Any, Dict, Iterable, List, Mapping
import pytest
from py import config as config_module
from py.utils.root_paths import normalize_root_identity
from py.services import settings_manager as settings_manager_module
def _identity(value):
"""Fold path spelling so an assertion is about paths, not separators.
LM keeps the host's own spelling for the paths it is configured with
(backslashes on Windows) while the ones it derives itself are forward-slashed,
so two values naming the same directory can differ as strings. Compare
identities wherever a test asserts *which* paths survived.
"""
if isinstance(value, str):
return normalize_root_identity(value)
if isinstance(value, Mapping):
return {key: _identity(item) for key, item in value.items()}
if isinstance(value, (list, tuple)):
return [_identity(item) for item in value]
if isinstance(value, set):
return {_identity(item) for item in value}
return value
def _setup_config_environment(monkeypatch: pytest.MonkeyPatch, tmp_path) -> Dict[str, List[str]]:
loras_dir = tmp_path / "loras"
checkpoints_dir = tmp_path / "checkpoints"
embeddings_dir = tmp_path / "embeddings"
for directory in (loras_dir, checkpoints_dir, embeddings_dir):
directory.mkdir()
folder_paths: Dict[str, List[str]] = {
"loras": [str(loras_dir)],
"checkpoints": [str(checkpoints_dir)],
"unet": [],
"embeddings": [str(embeddings_dir)],
}
def fake_get_folder_paths(kind: str) -> Iterable[str]:
return folder_paths.get(kind, [])
monkeypatch.setattr(config_module.folder_paths, "get_folder_paths", fake_get_folder_paths)
monkeypatch.setattr(config_module, "standalone_mode", False)
monkeypatch.setattr(
config_module,
"ensure_settings_file",
lambda logger=None: str(tmp_path / "settings.json"),
)
return folder_paths
def test_save_paths_renames_default_library(monkeypatch: pytest.MonkeyPatch, tmp_path):
folder_paths = _setup_config_environment(monkeypatch, tmp_path)
class FakeSettingsService:
def __init__(self, default_paths: Dict[str, List[str]]):
self._default_paths = default_paths
self.rename_calls = []
self.delete_calls = []
self.upsert_calls = []
self._renamed = False
self.active_library = "default"
def get_libraries(self):
if self._renamed:
return {"comfyui": {}}
return {
"default": {
"folder_paths": {key: list(value) for key, value in self._default_paths.items()},
"default_lora_root": "",
"default_checkpoint_root": "",
"default_embedding_root": "",
}
}
def rename_library(self, old_name: str, new_name: str):
self.rename_calls.append((old_name, new_name))
self._renamed = True
if self.active_library == old_name:
self.active_library = new_name
def get_active_library_name(self):
return self.active_library
def delete_library(self, name: str): # pragma: no cover - defensive guard
self.delete_calls.append(name)
raise AssertionError("delete_library should not be invoked in this scenario")
def upsert_library(self, name: str, **payload):
self.upsert_calls.append((name, payload))
fake_settings = FakeSettingsService(folder_paths)
monkeypatch.setattr(settings_manager_module, "settings", fake_settings)
config_instance = config_module.Config()
assert isinstance(config_instance, config_module.Config)
assert fake_settings.rename_calls == [("default", "comfyui")]
assert len(fake_settings.upsert_calls) == 1
name, payload = fake_settings.upsert_calls[0]
assert name == "comfyui"
# The Config class normalizes paths to use forward slashes for cross-platform compatibility
# Convert expected paths to the same format for comparison
expected_folder_paths = {
key: [path.replace("\\", "/") for path in paths]
for key, paths in folder_paths.items()
}
assert _identity(payload["folder_paths"]) == _identity(expected_folder_paths)
assert _identity(payload["default_lora_root"]) == _identity(folder_paths["loras"][0].replace("\\", "/"))
assert _identity(payload["default_checkpoint_root"]) == _identity(folder_paths["checkpoints"][0].replace("\\", "/"))
assert _identity(payload["default_embedding_root"]) == _identity(folder_paths["embeddings"][0].replace("\\", "/"))
assert _identity(payload["metadata"]) == _identity({"display_name": "ComfyUI", "source": "comfyui"})
assert payload["activate"] is True
def test_save_paths_logs_warning_when_upsert_fails(
monkeypatch: pytest.MonkeyPatch, tmp_path, caplog
):
folder_paths = _setup_config_environment(monkeypatch, tmp_path)
class RaisingSettingsService:
def __init__(self):
self.upsert_attempts = []
self.active_library = "comfyui"
def get_libraries(self):
return {
"comfyui": {
"folder_paths": {key: list(value) for key, value in folder_paths.items()},
"default_lora_root": "existing",
}
}
def rename_library(self, *_):
raise AssertionError("rename_library should not be invoked")
def get_active_library_name(self):
return self.active_library
def upsert_library(self, name: str, **payload):
self.upsert_attempts.append((name, payload))
raise RuntimeError("boom")
fake_settings = RaisingSettingsService()
monkeypatch.setattr(settings_manager_module, "settings", fake_settings)
with caplog.at_level(logging.WARNING, logger=config_module.logger.name):
config_instance = config_module.Config()
assert isinstance(config_instance, config_module.Config)
assert fake_settings.upsert_attempts and fake_settings.upsert_attempts[0][0] == "comfyui"
assert "Failed to save folder paths: boom" in caplog.text
def test_save_paths_repairs_empty_default_roots(monkeypatch: pytest.MonkeyPatch, tmp_path):
folder_paths = _setup_config_environment(monkeypatch, tmp_path)
class FakeSettingsService:
active_library = "comfyui"
name: str = ""
payload: Dict[str, Any] = {}
def get_libraries(self):
return {
"comfyui": {
"folder_paths": {key: list(value) for key, value in folder_paths.items()},
"default_lora_root": "",
"default_checkpoint_root": "",
"default_embedding_root": "",
}
}
def rename_library(self, *_):
raise AssertionError("rename_library should not be invoked")
def get_active_library_name(self):
return self.active_library
def upsert_library(self, name: str, **payload):
self.name = name
self.payload = payload
fake_settings = FakeSettingsService()
monkeypatch.setattr(settings_manager_module, "settings", fake_settings)
config_module.Config()
assert fake_settings.name == "comfyui"
assert _identity(fake_settings.payload["default_lora_root"]) == _identity(folder_paths["loras"][0].replace("\\", "/"))
assert _identity(fake_settings.payload["default_checkpoint_root"]) == _identity(folder_paths["checkpoints"][0].replace("\\", "/"))
assert _identity(fake_settings.payload["default_embedding_root"]) == _identity(folder_paths["embeddings"][0].replace("\\", "/"))
def test_save_paths_repairs_stale_default_roots(monkeypatch: pytest.MonkeyPatch, tmp_path):
folder_paths = _setup_config_environment(monkeypatch, tmp_path)
class FakeSettingsService:
active_library = "comfyui"
name: str = ""
payload: Dict[str, Any] = {}
def get_libraries(self):
return {
"comfyui": {
"folder_paths": {key: list(value) for key, value in folder_paths.items()},
"default_lora_root": "/stale/loras",
"default_checkpoint_root": "/stale/checkpoints",
"default_embedding_root": "/stale/embeddings",
}
}
def rename_library(self, *_):
raise AssertionError("rename_library should not be invoked")
def get_active_library_name(self):
return self.active_library
def upsert_library(self, name: str, **payload):
self.name = name
self.payload = payload
fake_settings = FakeSettingsService()
monkeypatch.setattr(settings_manager_module, "settings", fake_settings)
config_module.Config()
assert fake_settings.name == "comfyui"
assert _identity(fake_settings.payload["default_lora_root"]) == _identity(folder_paths["loras"][0].replace("\\", "/"))
assert _identity(fake_settings.payload["default_checkpoint_root"]) == _identity(folder_paths["checkpoints"][0].replace("\\", "/"))
assert _identity(fake_settings.payload["default_embedding_root"]) == _identity(folder_paths["embeddings"][0].replace("\\", "/"))
def test_save_paths_keeps_valid_default_roots(monkeypatch: pytest.MonkeyPatch, tmp_path):
folder_paths = _setup_config_environment(monkeypatch, tmp_path)
class FakeSettingsService:
active_library = "comfyui"
name: str = ""
payload: Dict[str, Any] = {}
def get_libraries(self):
return {
"comfyui": {
"folder_paths": {key: list(value) for key, value in folder_paths.items()},
"default_lora_root": folder_paths["loras"][0],
"default_checkpoint_root": folder_paths["checkpoints"][0],
"default_embedding_root": folder_paths["embeddings"][0],
}
}
def rename_library(self, *_):
raise AssertionError("rename_library should not be invoked")
def get_active_library_name(self):
return self.active_library
def upsert_library(self, name: str, **payload):
self.name = name
self.payload = payload
fake_settings = FakeSettingsService()
monkeypatch.setattr(settings_manager_module, "settings", fake_settings)
config_module.Config()
assert fake_settings.name == "comfyui"
assert _identity(fake_settings.payload["default_lora_root"]) == _identity(folder_paths["loras"][0].replace("\\", "/"))
assert _identity(fake_settings.payload["default_checkpoint_root"]) == _identity(folder_paths["checkpoints"][0].replace("\\", "/"))
assert _identity(fake_settings.payload["default_embedding_root"]) == _identity(folder_paths["embeddings"][0].replace("\\", "/"))
def test_save_paths_removes_template_default_library(monkeypatch, tmp_path):
folder_paths = _setup_config_environment(monkeypatch, tmp_path)
placeholder_paths = {
"loras": [
"C:/path/to/your/loras_folder",
"C:/path/to/another/loras_folder",
],
"checkpoints": [
"C:/path/to/your/checkpoints_folder",
"C:/path/to/another/checkpoints_folder",
],
"embeddings": [
"C:/path/to/your/embeddings_folder",
"C:/path/to/another/embeddings_folder",
],
}
class FakeSettingsService:
def __init__(self):
self.libraries = {
"default": {
"folder_paths": placeholder_paths,
"default_lora_root": "",
"default_checkpoint_root": "",
"default_embedding_root": "",
}
}
self.rename_calls = []
self.delete_calls = []
self.upsert_calls = []
self.active_library = "default"
def get_libraries(self):
return self.libraries
def rename_library(self, old_name: str, new_name: str):
self.rename_calls.append((old_name, new_name))
self.libraries[new_name] = self.libraries.pop(old_name)
if self.active_library == old_name:
self.active_library = new_name
def delete_library(self, name: str):
self.delete_calls.append(name)
self.libraries.pop(name, None)
def upsert_library(self, name: str, **payload):
self.upsert_calls.append((name, payload))
self.libraries[name] = {**payload}
if payload.get("activate"):
self.active_library = name
def get_active_library_name(self):
return self.active_library
fake_settings = FakeSettingsService()
monkeypatch.setattr(settings_manager_module, "settings", fake_settings)
monkeypatch.setattr(
config_module,
"load_settings_template",
lambda: {"folder_paths": placeholder_paths},
)
config_instance = config_module.Config()
assert isinstance(config_instance, config_module.Config)
assert fake_settings.rename_calls == [("default", "comfyui")]
assert not fake_settings.delete_calls
assert len(fake_settings.upsert_calls) == 1
assert "default" not in fake_settings.libraries
assert set(fake_settings.libraries.keys()) == {"comfyui"}
name, payload = fake_settings.upsert_calls[0]
assert name == "comfyui"
expected_folder_paths = {
key: [path.replace("\\", "/") for path in paths]
for key, paths in folder_paths.items()
}
assert _identity(payload["folder_paths"]) == _identity(expected_folder_paths)
assert _identity(payload["default_lora_root"]) == _identity(folder_paths["loras"][0].replace("\\", "/"))
assert (
payload["default_checkpoint_root"]
== folder_paths["checkpoints"][0].replace("\\", "/")
)
assert (
payload["default_embedding_root"]
== folder_paths["embeddings"][0].replace("\\", "/")
)
assert _identity(payload["metadata"]) == _identity({"display_name": "ComfyUI", "source": "comfyui"})
assert payload["activate"] is True
def test_save_paths_keeps_default_roots_in_extra_paths(monkeypatch: pytest.MonkeyPatch, tmp_path):
folder_paths = _setup_config_environment(monkeypatch, tmp_path)
extra_lora_dir = tmp_path / "extra_loras"
extra_checkpoint_dir = tmp_path / "extra_checkpoints"
extra_embedding_dir = tmp_path / "extra_embeddings"
for directory in (extra_lora_dir, extra_checkpoint_dir, extra_embedding_dir):
directory.mkdir()
class FakeSettingsService:
active_library = "comfyui"
name: str = ""
payload: Dict[str, Any] = {}
def get_libraries(self):
return {
"comfyui": {
"folder_paths": {key: list(value) for key, value in folder_paths.items()},
"extra_folder_paths": {
"loras": [str(extra_lora_dir)],
"checkpoints": [str(extra_checkpoint_dir)],
"embeddings": [str(extra_embedding_dir)],
},
"default_lora_root": str(extra_lora_dir),
"default_checkpoint_root": str(extra_checkpoint_dir),
"default_embedding_root": str(extra_embedding_dir),
}
}
def rename_library(self, *_):
raise AssertionError("rename_library should not be invoked")
def get_active_library_name(self):
return self.active_library
def upsert_library(self, name: str, **payload):
self.name = name
self.payload = payload
fake_settings = FakeSettingsService()
monkeypatch.setattr(settings_manager_module, "settings", fake_settings)
config_module.Config()
assert fake_settings.name == "comfyui"
assert _identity(fake_settings.payload["extra_folder_paths"]["loras"]) == _identity([str(extra_lora_dir).replace("\\", "/")])
assert fake_settings.payload["extra_folder_paths"]["checkpoints"] == [
str(extra_checkpoint_dir).replace("\\", "/")
]
assert fake_settings.payload["extra_folder_paths"]["embeddings"] == [
str(extra_embedding_dir).replace("\\", "/")
]
assert _identity(fake_settings.payload["default_lora_root"]) == _identity(str(extra_lora_dir).replace("\\", "/"))
assert _identity(fake_settings.payload["default_checkpoint_root"]) == _identity(str(extra_checkpoint_dir).replace("\\", "/"))
assert _identity(fake_settings.payload["default_embedding_root"]) == _identity(str(extra_embedding_dir).replace("\\", "/"))
assert fake_settings.payload["activate"] is True
def test_save_paths_keeps_default_roots_in_extra_paths_with_windows_slash_mismatch(
monkeypatch: pytest.MonkeyPatch, tmp_path
):
folder_paths = _setup_config_environment(monkeypatch, tmp_path)
class FakeSettingsService:
active_library = "comfyui"
name: str = ""
payload: Dict[str, Any] = {}
def get_libraries(self):
return {
"comfyui": {
"folder_paths": {key: list(value) for key, value in folder_paths.items()},
"extra_folder_paths": {
"loras": ["U:\\Lora7\\Loras"],
"checkpoints": ["U:\\Lora7\\Models"],
"embeddings": [],
},
"default_lora_root": "U:/Lora7/Loras",
"default_checkpoint_root": "U:/Lora7/Models",
"default_embedding_root": folder_paths["embeddings"][0],
}
}
def rename_library(self, *_):
raise AssertionError("rename_library should not be invoked")
def get_active_library_name(self):
return self.active_library
def upsert_library(self, name: str, **payload):
self.name = name
self.payload = payload
fake_settings = FakeSettingsService()
monkeypatch.setattr(settings_manager_module, "settings", fake_settings)
config_module.Config()
assert fake_settings.name == "comfyui"
assert _identity(fake_settings.payload["default_lora_root"]) == _identity("U:/Lora7/Loras")
assert _identity(fake_settings.payload["default_checkpoint_root"]) == _identity("U:/Lora7/Models")
def test_save_paths_repairs_empty_default_roots_to_extra_paths_when_primary_missing(
monkeypatch: pytest.MonkeyPatch, tmp_path
):
_setup_config_environment(monkeypatch, tmp_path)
extra_lora_dir = tmp_path / "extra_loras"
extra_lora_dir.mkdir()
monkeypatch.setattr(
config_module.folder_paths,
"get_folder_paths",
lambda kind: [] if kind == "loras" else [],
)
class FakeSettingsService:
active_library = "comfyui"
name: str = ""
payload: Dict[str, Any] = {}
def get_libraries(self):
return {
"comfyui": {
"folder_paths": {
"loras": [],
"checkpoints": [],
"unet": [],
"embeddings": [],
},
"extra_folder_paths": {
"loras": [str(extra_lora_dir)],
},
"default_lora_root": "",
}
}
def rename_library(self, *_):
raise AssertionError("rename_library should not be invoked")
def get_active_library_name(self):
return self.active_library
def upsert_library(self, name: str, **payload):
self.name = name
self.payload = payload
fake_settings = FakeSettingsService()
monkeypatch.setattr(settings_manager_module, "settings", fake_settings)
config_module.Config()
assert fake_settings.name == "comfyui"
assert _identity(fake_settings.payload["default_lora_root"]) == _identity(str(extra_lora_dir).replace("\\", "/"))
def test_save_paths_does_not_activate_comfyui_library_when_another_library_is_active(
monkeypatch: pytest.MonkeyPatch, tmp_path
):
folder_paths = _setup_config_environment(monkeypatch, tmp_path)
class FakeSettingsService:
def __init__(self):
self.active_library = "studio"
self.upsert_calls = []
def get_libraries(self):
return {
"studio": {
"folder_paths": {"loras": ["/studio/loras"]},
},
"comfyui": {
"folder_paths": {key: list(value) for key, value in folder_paths.items()},
"default_lora_root": folder_paths["loras"][0],
"default_checkpoint_root": folder_paths["checkpoints"][0],
"default_embedding_root": folder_paths["embeddings"][0],
},
}
def rename_library(self, *_):
raise AssertionError("rename_library should not be invoked")
def get_active_library_name(self):
return self.active_library
def upsert_library(self, name: str, **payload):
self.upsert_calls.append((name, payload))
fake_settings = FakeSettingsService()
monkeypatch.setattr(settings_manager_module, "settings", fake_settings)
config_module.Config()
assert len(fake_settings.upsert_calls) == 1
name, payload = fake_settings.upsert_calls[0]
assert name == "comfyui"
assert payload["activate"] is False
def test_apply_library_settings_merges_extra_paths(monkeypatch, tmp_path):
"""Test that apply_library_settings correctly merges folder_paths with extra_folder_paths."""
loras_dir = tmp_path / "loras"
extra_loras_dir = tmp_path / "extra_loras"
checkpoints_dir = tmp_path / "checkpoints"
extra_checkpoints_dir = tmp_path / "extra_checkpoints"
embeddings_dir = tmp_path / "embeddings"
extra_embeddings_dir = tmp_path / "extra_embeddings"
for directory in (loras_dir, extra_loras_dir, checkpoints_dir, extra_checkpoints_dir, embeddings_dir, extra_embeddings_dir):
directory.mkdir()
config_instance = config_module.Config()
folder_paths = {
"loras": [str(loras_dir)],
"checkpoints": [str(checkpoints_dir)],
"unet": [],
"embeddings": [str(embeddings_dir)],
}
extra_folder_paths = {
"loras": [str(extra_loras_dir)],
"checkpoints": [str(extra_checkpoints_dir)],
"unet": [],
"embeddings": [str(extra_embeddings_dir)],
}
library_config = {
"folder_paths": folder_paths,
"extra_folder_paths": extra_folder_paths,
}
config_instance.apply_library_settings(library_config)
assert _identity(str(loras_dir)) in _identity(config_instance.loras_roots)
assert _identity(str(extra_loras_dir)) in _identity(config_instance.extra_loras_roots)
assert str(checkpoints_dir) in config_instance.base_models_roots
assert _identity(str(extra_checkpoints_dir)) in _identity(config_instance.extra_checkpoints_roots)
assert str(embeddings_dir) in (config_instance.embeddings_roots or [])
assert _identity(str(extra_embeddings_dir)) in _identity(config_instance.extra_embeddings_roots)
def test_apply_library_settings_without_extra_paths(monkeypatch, tmp_path):
"""Test that apply_library_settings works when extra_folder_paths is not provided."""
loras_dir = tmp_path / "loras"
checkpoints_dir = tmp_path / "checkpoints"
embeddings_dir = tmp_path / "embeddings"
for directory in (loras_dir, checkpoints_dir, embeddings_dir):
directory.mkdir()
config_instance = config_module.Config()
folder_paths = {
"loras": [str(loras_dir)],
"checkpoints": [str(checkpoints_dir)],
"unet": [],
"embeddings": [str(embeddings_dir)],
}
library_config = {
"folder_paths": folder_paths,
}
config_instance.apply_library_settings(library_config)
assert _identity(str(loras_dir)) in _identity(config_instance.loras_roots)
assert _identity(config_instance.extra_loras_roots) == _identity([])
assert str(checkpoints_dir) in config_instance.base_models_roots
assert _identity(config_instance.extra_checkpoints_roots) == _identity([])
assert str(embeddings_dir) in (config_instance.embeddings_roots or [])
assert _identity(config_instance.extra_embeddings_roots) == _identity([])
def test_extra_paths_deduplication(monkeypatch, tmp_path):
"""Test that extra paths are stored separately from main paths in Config."""
loras_dir = tmp_path / "loras"
extra_loras_dir = tmp_path / "extra_loras"
loras_dir.mkdir()
extra_loras_dir.mkdir()
config_instance = config_module.Config()
folder_paths = {
"loras": [str(loras_dir)],
"checkpoints": [],
"unet": [],
"embeddings": [],
}
extra_folder_paths = {
"loras": [str(extra_loras_dir)],
"checkpoints": [],
"unet": [],
"embeddings": [],
}
library_config = {
"folder_paths": folder_paths,
"extra_folder_paths": extra_folder_paths,
}
config_instance.apply_library_settings(library_config)
assert _identity(config_instance.loras_roots) == _identity([str(loras_dir)])
assert _identity(config_instance.extra_loras_roots) == _identity([str(extra_loras_dir)])
def test_apply_library_settings_ignores_extra_lora_path_overlapping_primary_symlink(
monkeypatch, tmp_path, caplog
):
"""Extra LoRA paths should be ignored when they resolve to the same target as a primary root."""
real_loras_dir = tmp_path / "loras_real"
real_loras_dir.mkdir()
loras_link = tmp_path / "loras_link"
loras_link.symlink_to(real_loras_dir, target_is_directory=True)
config_instance = config_module.Config()
library_config = {
"folder_paths": {
"loras": [str(loras_link)],
"checkpoints": [],
"unet": [],
"embeddings": [],
},
"extra_folder_paths": {
"loras": [str(real_loras_dir)],
"checkpoints": [],
"unet": [],
"embeddings": [],
},
}
with caplog.at_level("WARNING", logger=config_module.logger.name):
config_instance.apply_library_settings(library_config)
assert _identity(config_instance.loras_roots) == _identity([str(loras_link)])
assert _identity(config_instance.extra_loras_roots) == _identity([])
warning_messages = [
record.message
for record in caplog.records
if record.levelname == "WARNING"
and "same lora folder" in record.message.lower()
]
assert len(warning_messages) == 1
assert "comfyui model paths" in warning_messages[0].lower()
assert "extra folder paths" in warning_messages[0].lower()
assert "duplicate items" in warning_messages[0].lower()
def test_apply_library_settings_detects_overlap_case_insensitively(
monkeypatch, tmp_path, caplog
):
"""Overlap detection should use case-insensitive comparison on Windows-like paths."""
real_loras_dir = tmp_path / "loras_real"
real_loras_dir.mkdir()
loras_link = tmp_path / "loras_link"
loras_link.symlink_to(real_loras_dir, target_is_directory=True)
original_exists = config_module.os.path.exists
original_realpath = config_module.os.path.realpath
original_normcase = config_module.os.path.normcase
def fake_exists(path):
if isinstance(path, str) and path.lower() in {
str(loras_link).lower(),
str(real_loras_dir).lower(),
str(loras_link).upper().lower(),
str(real_loras_dir).upper().lower(),
}:
return True
return original_exists(path)
def fake_realpath(path, *args, **kwargs):
if isinstance(path, str):
lowered = path.lower()
if lowered == str(loras_link).lower():
return str(real_loras_dir)
if lowered == str(real_loras_dir).lower():
return str(real_loras_dir)
return original_realpath(path, *args, **kwargs)
monkeypatch.setattr(config_module.os.path, "exists", fake_exists)
monkeypatch.setattr(config_module.os.path, "realpath", fake_realpath)
monkeypatch.setattr(
config_module.os.path,
"normcase",
lambda value: original_normcase(value).lower(),
)
config_instance = config_module.Config()
primary_path = str(loras_link).replace("loras_link", "LORAS_LINK")
extra_path = str(real_loras_dir).replace("loras_real", "loras_real")
library_config = {
"folder_paths": {
"loras": [primary_path],
"checkpoints": [],
"unet": [],
"embeddings": [],
},
"extra_folder_paths": {
"loras": [extra_path.upper()],
"checkpoints": [],
"unet": [],
"embeddings": [],
},
}
with caplog.at_level("WARNING", logger=config_module.logger.name):
config_instance.apply_library_settings(library_config)
assert _identity(config_instance.loras_roots) == _identity([primary_path])
assert _identity(config_instance.extra_loras_roots) == _identity([])
assert any("same lora folder" in record.message.lower() for record in caplog.records)
def test_apply_library_settings_ignores_missing_extra_lora_paths(monkeypatch, tmp_path, caplog):
"""Missing extra paths should be ignored without overlap warnings."""
loras_dir = tmp_path / "loras"
loras_dir.mkdir()
missing_extra = tmp_path / "missing_loras"
config_instance = config_module.Config()
library_config = {
"folder_paths": {
"loras": [str(loras_dir)],
"checkpoints": [],
"unet": [],
"embeddings": [],
},
"extra_folder_paths": {
"loras": [str(missing_extra)],
"checkpoints": [],
"unet": [],
"embeddings": [],
},
}
with caplog.at_level("WARNING", logger=config_module.logger.name):
config_instance.apply_library_settings(library_config)
assert _identity(config_instance.loras_roots) == _identity([str(loras_dir)])
assert _identity(config_instance.extra_loras_roots) == _identity([])
assert not any("same lora folder" in record.message.lower() for record in caplog.records)
def test_apply_library_settings_ignores_extra_lora_path_overlapping_primary_root_symlink(
tmp_path, caplog
):
"""Extra LoRA paths should be ignored when already reachable via a first-level symlink under the primary root."""
loras_dir = tmp_path / "loras"
loras_dir.mkdir()
external_dir = tmp_path / "external_loras"
external_dir.mkdir()
link_dir = loras_dir / "link"
link_dir.symlink_to(external_dir, target_is_directory=True)
config_instance = config_module.Config()
library_config = {
"folder_paths": {
"loras": [str(loras_dir)],
"checkpoints": [],
"unet": [],
"embeddings": [],
},
"extra_folder_paths": {
"loras": [str(external_dir)],
"checkpoints": [],
"unet": [],
"embeddings": [],
},
}
with caplog.at_level("WARNING", logger=config_module.logger.name):
config_instance.apply_library_settings(library_config)
assert _identity(config_instance.loras_roots) == _identity([str(loras_dir)])
assert _identity(config_instance.extra_loras_roots) == _identity([])
assert any(
"same lora folder" in record.message.lower()
for record in caplog.records
)
def test_save_paths_removes_stale_empty_default_when_comfyui_exists(
monkeypatch: pytest.MonkeyPatch, tmp_path,
):
"""When an empty-shell 'default' library coexists with 'comfyui', the
stale 'default' entry should be removed and 'comfyui' activated."""
folder_paths = _setup_config_environment(monkeypatch, tmp_path)
class FakeSettingsService:
def __init__(self):
# Replicate the user's settings.json: empty default + populated comfyui
self.libraries = {
"default": {
"folder_paths": {},
"extra_folder_paths": {},
"default_lora_root": "",
"default_checkpoint_root": "",
"default_unet_root": "",
"default_embedding_root": "",
"recipes_path": "",
},
"comfyui": {
"folder_paths": {
key: list(value) for key, value in folder_paths.items()
},
"default_lora_root": folder_paths["loras"][0],
"default_checkpoint_root": folder_paths["checkpoints"][0],
"default_embedding_root": folder_paths["embeddings"][0],
},
}
# No active_library key — get_active_library_name() falls back to
# dict order, returning "default".
self.active_library = "default"
self.delete_calls: list[str] = []
self.upsert_calls: list[tuple[str, dict[str, Any]]] = []
def get_libraries(self):
return dict(self.libraries)
def delete_library(self, name: str):
self.delete_calls.append(name)
self.libraries.pop(name, None)
def rename_library(self, *_):
raise AssertionError("rename_library should not be invoked")
def get_active_library_name(self):
return self.active_library
def upsert_library(self, name: str, **payload):
self.upsert_calls.append((name, payload))
self.libraries[name] = {**payload}
if payload.get("activate"):
self.active_library = name
fake_settings = FakeSettingsService()
monkeypatch.setattr(settings_manager_module, "settings", fake_settings)
config_module.Config()
assert fake_settings.delete_calls == ["default"]
assert "default" not in fake_settings.libraries
assert set(fake_settings.libraries.keys()) == {"comfyui"}
assert len(fake_settings.upsert_calls) == 1
name, payload = fake_settings.upsert_calls[0]
assert name == "comfyui"
assert payload["activate"] is True
assert fake_settings.active_library == "comfyui"
def test_save_paths_keeps_configured_root_of_a_switched_off_drive(
monkeypatch: pytest.MonkeyPatch, tmp_path
):
"""A root whose directory is missing right now must stay in the library.
`upsert_library(folder_paths=...)` replaces the stored paths, so persisting
the existence-filtered live list would erase a drive that happened to be
switched off when ComfyUI started. A path the host no longer configures must
still be dropped.
"""
folder_paths = _setup_config_environment(monkeypatch, tmp_path)
readable = folder_paths["loras"][0]
switched_off = str(tmp_path / "drive-Z" / "loras")
removed_by_user = str(tmp_path / "old-location" / "loras")
# The host still configures the switched-off drive; it no longer configures
# the old location (that one only survives in the stored library paths).
folder_paths["loras"] = [switched_off, readable]
class FakeSettingsService:
active_library = "comfyui"
name: str = ""
payload: Dict[str, Any] = {}
def get_libraries(self):
return {
"comfyui": {
"folder_paths": {
"loras": [removed_by_user, readable],
"checkpoints": [],
"unet": [],
"embeddings": [],
},
"default_lora_root": switched_off,
}
}
def rename_library(self, *_):
raise AssertionError("rename_library should not be invoked")
def get_active_library_name(self):
return self.active_library
def upsert_library(self, name: str, **payload):
self.name = name
self.payload = payload
fake_settings = FakeSettingsService()
monkeypatch.setattr(settings_manager_module, "settings", fake_settings)
config_instance = config_module.Config()
persisted = fake_settings.payload["folder_paths"]["loras"]
# The switched-off drive keeps its configured path...
assert _identity(switched_off.replace("\\", "/")) in _identity(persisted)
assert _identity(readable.replace("\\", "/")) in _identity(persisted)
# ...the path the host really dropped is gone...
assert _identity(removed_by_user.replace("\\", "/")) not in _identity(persisted)
# ...and the live list still only holds what is readable right now.
assert _identity(config_instance.loras_roots) == _identity([readable.replace("\\", "/")])
# The user's default root survives because it is merely unavailable.
assert _identity(fake_settings.payload["default_lora_root"]) == _identity(switched_off.replace("\\", "/"))
# --- roots the host registered but never created (issue #1146) --------------
class _FakePersistentCache:
"""Stand-in for PersistentModelCache in the startup sync."""
def __init__(self, cached_roots=None, error: Exception | None = None):
self._cached_roots = cached_roots or {}
self._error = error
self.queries: List[tuple] = []
def roots_with_cached_entries(self, model_type: str, roots):
self.queries.append((model_type, list(roots)))
if self._error is not None:
raise self._error
known = {path.replace("\\", "/") for path in self._cached_roots.get(model_type, [])}
return {
root for root in roots if root.replace("\\", "/") in known
}
def _stub_persistent_cache(monkeypatch: pytest.MonkeyPatch, cache) -> None:
from py.services import persistent_model_cache as persistent_model_cache_module
monkeypatch.setattr(
persistent_model_cache_module, "get_persistent_cache", lambda: cache
)
class _LibrarySettingsService:
"""Minimal settings service recording what the startup sync persists."""
active_library = "comfyui"
def __init__(self, library: Dict[str, Any]):
self._library = library
self.payload: Dict[str, Any] = {}
def get_libraries(self):
return {"comfyui": self._library}
def rename_library(self, *_): # pragma: no cover - defensive guard
raise AssertionError("rename_library should not be invoked")
def delete_library(self, *_): # pragma: no cover - defensive guard
raise AssertionError("delete_library should not be invoked")
def get_active_library_name(self):
return self.active_library
def upsert_library(self, name: str, **payload):
self.payload = payload
def test_save_paths_drops_registered_directories_that_never_existed(
monkeypatch: pytest.MonkeyPatch, tmp_path
):
"""ComfyUI registers `<output>/<type>` for its save nodes unconditionally.
Those directories are not library paths when they do not exist and hold no
cached models: persisting them brings them back as "unavailable" roots on
every boot.
"""
folder_paths = _setup_config_environment(monkeypatch, tmp_path)
unet_dir = tmp_path / "unet"
unet_dir.mkdir()
folder_paths["unet"] = [str(unet_dir)]
output_loras = str(tmp_path / "output" / "loras")
output_checkpoints = str(tmp_path / "output" / "checkpoints")
output_unet = str(tmp_path / "output" / "diffusion_models")
output_embeddings = str(tmp_path / "output" / "embeddings")
folder_paths["loras"].append(output_loras)
folder_paths["checkpoints"].append(output_checkpoints)
folder_paths["unet"].append(output_unet)
folder_paths["embeddings"].append(output_embeddings)
fake_settings = _LibrarySettingsService(
{
"folder_paths": {key: list(value) for key, value in folder_paths.items()},
"default_lora_root": folder_paths["loras"][0],
"default_checkpoint_root": folder_paths["checkpoints"][0],
"default_embedding_root": folder_paths["embeddings"][0],
}
)
monkeypatch.setattr(settings_manager_module, "settings", fake_settings)
cache = _FakePersistentCache()
_stub_persistent_cache(monkeypatch, cache)
config_module.Config()
persisted = fake_settings.payload["folder_paths"]
for key, never_created in (
("loras", output_loras),
("checkpoints", output_checkpoints),
("unet", output_unet),
("embeddings", output_embeddings),
):
assert _identity(never_created.replace("\\", "/")) not in _identity(persisted[key]), key
# The real root of that key is untouched.
assert _identity(folder_paths[key][0].replace("\\", "/")) in _identity(persisted[key]), key
# Lora and checkpoint roots are looked up in their own model type.
assert [model_type for model_type, _roots in cache.queries] == [
"lora",
"checkpoint",
"checkpoint",
"embedding",
]
def test_save_paths_keeps_an_unavailable_root_that_has_cached_models(
monkeypatch: pytest.MonkeyPatch, tmp_path
):
"""A switched-off drive keeps its path: its models are still cached."""
folder_paths = _setup_config_environment(monkeypatch, tmp_path)
readable = folder_paths["loras"][0]
switched_off = str(tmp_path / "drive-Z" / "loras")
folder_paths["loras"] = [readable, switched_off]
fake_settings = _LibrarySettingsService(
{
"folder_paths": {"loras": [readable]},
"default_lora_root": readable,
}
)
monkeypatch.setattr(settings_manager_module, "settings", fake_settings)
_stub_persistent_cache(
monkeypatch, _FakePersistentCache({"lora": [switched_off]})
)
config_module.Config()
persisted = fake_settings.payload["folder_paths"]["loras"]
assert _identity(switched_off.replace("\\", "/")) in _identity(persisted)
assert _identity(readable.replace("\\", "/")) in _identity(persisted)
def test_save_paths_keeps_every_root_when_the_cache_cannot_be_read(
monkeypatch: pytest.MonkeyPatch, tmp_path
):
"""No answer from the cache means no root is dropped."""
folder_paths = _setup_config_environment(monkeypatch, tmp_path)
readable = folder_paths["loras"][0]
unknown = str(tmp_path / "drive-Z" / "loras")
folder_paths["loras"] = [readable, unknown]
fake_settings = _LibrarySettingsService(
{
"folder_paths": {"loras": [readable]},
"default_lora_root": readable,
}
)
monkeypatch.setattr(settings_manager_module, "settings", fake_settings)
_stub_persistent_cache(
monkeypatch, _FakePersistentCache(error=FileNotFoundError("no cache"))
)
config_module.Config()
persisted = fake_settings.payload["folder_paths"]["loras"]
assert _identity(unknown.replace("\\", "/")) in _identity(persisted)
def test_save_paths_keeps_the_root_a_default_setting_points_at(
monkeypatch: pytest.MonkeyPatch, tmp_path
):
"""An empty unavailable default root must not be dropped.
Dropping it would let the next boot "repair" the default onto another root and
silently retarget downloads. ``default_unet_root`` is only known from the
stored library (the sync resolves the lora/checkpoint/embedding ones itself),
so this covers the fallback to the stored value.
"""
folder_paths = _setup_config_environment(monkeypatch, tmp_path)
unet_dir = tmp_path / "unet"
unet_dir.mkdir()
default_target = str(tmp_path / "drive-Z" / "unet")
folder_paths["unet"] = [str(unet_dir), default_target]
fake_settings = _LibrarySettingsService(
{
"folder_paths": {"unet": [str(unet_dir)]},
"default_unet_root": default_target,
}
)
monkeypatch.setattr(settings_manager_module, "settings", fake_settings)
_stub_persistent_cache(monkeypatch, _FakePersistentCache())
config_module.Config()
persisted = fake_settings.payload["folder_paths"]["unet"]
assert _identity(default_target.replace("\\", "/")) in _identity(persisted)
assert _identity(str(unet_dir).replace("\\", "/")) in _identity(persisted)