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.
This commit is contained in:
Will Miao
2026-10-12 11:35:33 +08:00
parent 85f79283b6
commit c16de68640
13 changed files with 162 additions and 91 deletions
+76 -55
View File
@@ -1,12 +1,33 @@
import logging
from typing import Any, Dict, Iterable, List
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"
@@ -94,11 +115,11 @@ def test_save_paths_renames_default_library(monkeypatch: pytest.MonkeyPatch, tmp
key: [path.replace("\\", "/") for path in paths]
for key, paths in folder_paths.items()
}
assert payload["folder_paths"] == expected_folder_paths
assert payload["default_lora_root"] == 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 payload["metadata"] == {"display_name": "ComfyUI", "source": "comfyui"}
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
@@ -175,9 +196,9 @@ def test_save_paths_repairs_empty_default_roots(monkeypatch: pytest.MonkeyPatch,
config_module.Config()
assert fake_settings.name == "comfyui"
assert fake_settings.payload["default_lora_root"] == folder_paths["loras"][0].replace("\\", "/")
assert fake_settings.payload["default_checkpoint_root"] == folder_paths["checkpoints"][0].replace("\\", "/")
assert fake_settings.payload["default_embedding_root"] == folder_paths["embeddings"][0].replace("\\", "/")
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):
@@ -214,9 +235,9 @@ def test_save_paths_repairs_stale_default_roots(monkeypatch: pytest.MonkeyPatch,
config_module.Config()
assert fake_settings.name == "comfyui"
assert fake_settings.payload["default_lora_root"] == folder_paths["loras"][0].replace("\\", "/")
assert fake_settings.payload["default_checkpoint_root"] == folder_paths["checkpoints"][0].replace("\\", "/")
assert fake_settings.payload["default_embedding_root"] == folder_paths["embeddings"][0].replace("\\", "/")
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):
@@ -253,9 +274,9 @@ def test_save_paths_keeps_valid_default_roots(monkeypatch: pytest.MonkeyPatch, t
config_module.Config()
assert fake_settings.name == "comfyui"
assert fake_settings.payload["default_lora_root"] == folder_paths["loras"][0].replace("\\", "/")
assert fake_settings.payload["default_checkpoint_root"] == folder_paths["checkpoints"][0].replace("\\", "/")
assert fake_settings.payload["default_embedding_root"] == folder_paths["embeddings"][0].replace("\\", "/")
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):
@@ -338,8 +359,8 @@ def test_save_paths_removes_template_default_library(monkeypatch, tmp_path):
key: [path.replace("\\", "/") for path in paths]
for key, paths in folder_paths.items()
}
assert payload["folder_paths"] == expected_folder_paths
assert payload["default_lora_root"] == folder_paths["loras"][0].replace("\\", "/")
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("\\", "/")
@@ -348,7 +369,7 @@ def test_save_paths_removes_template_default_library(monkeypatch, tmp_path):
payload["default_embedding_root"]
== folder_paths["embeddings"][0].replace("\\", "/")
)
assert payload["metadata"] == {"display_name": "ComfyUI", "source": "comfyui"}
assert _identity(payload["metadata"]) == _identity({"display_name": "ComfyUI", "source": "comfyui"})
assert payload["activate"] is True
@@ -397,16 +418,16 @@ def test_save_paths_keeps_default_roots_in_extra_paths(monkeypatch: pytest.Monke
config_module.Config()
assert fake_settings.name == "comfyui"
assert fake_settings.payload["extra_folder_paths"]["loras"] == [str(extra_lora_dir).replace("\\", "/")]
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 fake_settings.payload["default_lora_root"] == str(extra_lora_dir).replace("\\", "/")
assert fake_settings.payload["default_checkpoint_root"] == str(extra_checkpoint_dir).replace("\\", "/")
assert fake_settings.payload["default_embedding_root"] == 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
@@ -451,8 +472,8 @@ def test_save_paths_keeps_default_roots_in_extra_paths_with_windows_slash_mismat
config_module.Config()
assert fake_settings.name == "comfyui"
assert fake_settings.payload["default_lora_root"] == "U:/Lora7/Loras"
assert fake_settings.payload["default_checkpoint_root"] == "U:/Lora7/Models"
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(
@@ -505,7 +526,7 @@ def test_save_paths_repairs_empty_default_roots_to_extra_paths_when_primary_miss
config_module.Config()
assert fake_settings.name == "comfyui"
assert fake_settings.payload["default_lora_root"] == str(extra_lora_dir).replace("\\", "/")
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(
@@ -585,12 +606,12 @@ def test_apply_library_settings_merges_extra_paths(monkeypatch, tmp_path):
config_instance.apply_library_settings(library_config)
assert str(loras_dir) in config_instance.loras_roots
assert str(extra_loras_dir) in config_instance.extra_loras_roots
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 str(extra_checkpoints_dir) in config_instance.extra_checkpoints_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 str(extra_embeddings_dir) in config_instance.extra_embeddings_roots
assert _identity(str(extra_embeddings_dir)) in _identity(config_instance.extra_embeddings_roots)
def test_apply_library_settings_without_extra_paths(monkeypatch, tmp_path):
@@ -617,12 +638,12 @@ def test_apply_library_settings_without_extra_paths(monkeypatch, tmp_path):
config_instance.apply_library_settings(library_config)
assert str(loras_dir) in config_instance.loras_roots
assert config_instance.extra_loras_roots == []
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 config_instance.extra_checkpoints_roots == []
assert _identity(config_instance.extra_checkpoints_roots) == _identity([])
assert str(embeddings_dir) in (config_instance.embeddings_roots or [])
assert config_instance.extra_embeddings_roots == []
assert _identity(config_instance.extra_embeddings_roots) == _identity([])
def test_extra_paths_deduplication(monkeypatch, tmp_path):
@@ -654,8 +675,8 @@ def test_extra_paths_deduplication(monkeypatch, tmp_path):
config_instance.apply_library_settings(library_config)
assert config_instance.loras_roots == [str(loras_dir)]
assert config_instance.extra_loras_roots == [str(extra_loras_dir)]
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(
@@ -687,8 +708,8 @@ def test_apply_library_settings_ignores_extra_lora_path_overlapping_primary_syml
with caplog.at_level("WARNING", logger=config_module.logger.name):
config_instance.apply_library_settings(library_config)
assert config_instance.loras_roots == [str(loras_link)]
assert config_instance.extra_loras_roots == []
assert _identity(config_instance.loras_roots) == _identity([str(loras_link)])
assert _identity(config_instance.extra_loras_roots) == _identity([])
warning_messages = [
record.message
@@ -764,8 +785,8 @@ def test_apply_library_settings_detects_overlap_case_insensitively(
with caplog.at_level("WARNING", logger=config_module.logger.name):
config_instance.apply_library_settings(library_config)
assert config_instance.loras_roots == [primary_path]
assert config_instance.extra_loras_roots == []
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)
@@ -794,8 +815,8 @@ def test_apply_library_settings_ignores_missing_extra_lora_paths(monkeypatch, tm
with caplog.at_level("WARNING", logger=config_module.logger.name):
config_instance.apply_library_settings(library_config)
assert config_instance.loras_roots == [str(loras_dir)]
assert config_instance.extra_loras_roots == []
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)
@@ -829,8 +850,8 @@ def test_apply_library_settings_ignores_extra_lora_path_overlapping_primary_root
with caplog.at_level("WARNING", logger=config_module.logger.name):
config_instance.apply_library_settings(library_config)
assert config_instance.loras_roots == [str(loras_dir)]
assert config_instance.extra_loras_roots == []
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
@@ -960,14 +981,14 @@ def test_save_paths_keeps_configured_root_of_a_switched_off_drive(
persisted = fake_settings.payload["folder_paths"]["loras"]
# The switched-off drive keeps its configured path...
assert switched_off.replace("\\", "/") in persisted
assert readable.replace("\\", "/") in persisted
assert _identity(switched_off.replace("\\", "/")) in _identity(persisted)
assert _identity(readable.replace("\\", "/")) in _identity(persisted)
# ...the path the host really dropped is gone...
assert removed_by_user.replace("\\", "/") not in persisted
assert _identity(removed_by_user.replace("\\", "/")) not in _identity(persisted)
# ...and the live list still only holds what is readable right now.
assert config_instance.loras_roots == [readable.replace("\\", "/")]
assert _identity(config_instance.loras_roots) == _identity([readable.replace("\\", "/")])
# The user's default root survives because it is merely unavailable.
assert fake_settings.payload["default_lora_root"] == switched_off.replace("\\", "/")
assert _identity(fake_settings.payload["default_lora_root"]) == _identity(switched_off.replace("\\", "/"))
# --- roots the host registered but never created (issue #1146) --------------
@@ -1067,9 +1088,9 @@ def test_save_paths_drops_registered_directories_that_never_existed(
("unet", output_unet),
("embeddings", output_embeddings),
):
assert never_created.replace("\\", "/") not in persisted[key], key
assert _identity(never_created.replace("\\", "/")) not in _identity(persisted[key]), key
# The real root of that key is untouched.
assert folder_paths[key][0].replace("\\", "/") in persisted[key], key
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",
@@ -1102,8 +1123,8 @@ def test_save_paths_keeps_an_unavailable_root_that_has_cached_models(
config_module.Config()
persisted = fake_settings.payload["folder_paths"]["loras"]
assert switched_off.replace("\\", "/") in persisted
assert readable.replace("\\", "/") in persisted
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(
@@ -1129,7 +1150,7 @@ def test_save_paths_keeps_every_root_when_the_cache_cannot_be_read(
config_module.Config()
persisted = fake_settings.payload["folder_paths"]["loras"]
assert unknown.replace("\\", "/") in persisted
assert _identity(unknown.replace("\\", "/")) in _identity(persisted)
def test_save_paths_keeps_the_root_a_default_setting_points_at(
@@ -1160,5 +1181,5 @@ def test_save_paths_keeps_the_root_a_default_setting_points_at(
config_module.Config()
persisted = fake_settings.payload["folder_paths"]["unet"]
assert default_target.replace("\\", "/") in persisted
assert str(unet_dir).replace("\\", "/") in persisted
assert _identity(default_target.replace("\\", "/")) in _identity(persisted)
assert _identity(str(unet_dir).replace("\\", "/")) in _identity(persisted)