| import pytest
|
| import yaml
|
| import os
|
| import sys
|
| from unittest.mock import Mock, patch, mock_open
|
|
|
| from utils.extra_config import load_extra_path_config
|
| import folder_paths
|
|
|
|
|
| @pytest.fixture()
|
| def clear_folder_paths():
|
|
|
| original = folder_paths.folder_names_and_paths.copy()
|
| folder_paths.folder_names_and_paths.clear()
|
| yield
|
| folder_paths.folder_names_and_paths = original
|
|
|
|
|
| @pytest.fixture
|
| def mock_yaml_content():
|
| return {
|
| 'test_config': {
|
| 'base_path': '~/App/',
|
| 'checkpoints': 'subfolder1',
|
| }
|
| }
|
|
|
|
|
| @pytest.fixture
|
| def mock_expanded_home():
|
| return '/home/user'
|
|
|
|
|
| @pytest.fixture
|
| def yaml_config_with_appdata():
|
| return """
|
| test_config:
|
| base_path: '%APPDATA%/ComfyUI'
|
| checkpoints: 'models/checkpoints'
|
| """
|
|
|
|
|
| @pytest.fixture
|
| def mock_yaml_content_appdata(yaml_config_with_appdata):
|
| return yaml.safe_load(yaml_config_with_appdata)
|
|
|
|
|
| @pytest.fixture
|
| def mock_expandvars_appdata():
|
| mock = Mock()
|
|
|
| def expandvars(path):
|
| if '%APPDATA%' in path:
|
| if sys.platform == 'win32':
|
| return path.replace('%APPDATA%', 'C:/Users/TestUser/AppData/Roaming')
|
| else:
|
| return path.replace('%APPDATA%', '/Users/TestUser/AppData/Roaming')
|
| return path
|
|
|
| mock.side_effect = expandvars
|
| return mock
|
|
|
|
|
| @pytest.fixture
|
| def mock_add_model_folder_path():
|
| return Mock()
|
|
|
|
|
| @pytest.fixture
|
| def mock_expanduser(mock_expanded_home):
|
| def _expanduser(path):
|
| if path.startswith('~/'):
|
| return os.path.join(mock_expanded_home, path[2:])
|
| return path
|
| return _expanduser
|
|
|
|
|
| @pytest.fixture
|
| def mock_yaml_safe_load(mock_yaml_content):
|
| return Mock(return_value=mock_yaml_content)
|
|
|
|
|
| @patch('builtins.open', new_callable=mock_open, read_data="dummy file content")
|
| def test_load_extra_model_paths_expands_userpath(
|
| mock_file,
|
| monkeypatch,
|
| mock_add_model_folder_path,
|
| mock_expanduser,
|
| mock_yaml_safe_load,
|
| mock_expanded_home
|
| ):
|
|
|
| monkeypatch.setattr(folder_paths, 'add_model_folder_path', mock_add_model_folder_path)
|
| monkeypatch.setattr(os.path, 'expanduser', mock_expanduser)
|
| monkeypatch.setattr(yaml, 'safe_load', mock_yaml_safe_load)
|
|
|
| dummy_yaml_file_name = 'dummy_path.yaml'
|
| load_extra_path_config(dummy_yaml_file_name)
|
|
|
| expected_calls = [
|
| ('checkpoints', os.path.join(mock_expanded_home, 'App', 'subfolder1'), False),
|
| ]
|
|
|
| assert mock_add_model_folder_path.call_count == len(expected_calls)
|
|
|
|
|
| for actual_call, expected_call in zip(mock_add_model_folder_path.call_args_list, expected_calls):
|
| assert actual_call.args[0] == expected_call[0]
|
| assert os.path.normpath(actual_call.args[1]) == os.path.normpath(expected_call[1])
|
| assert actual_call.args[2] == expected_call[2]
|
|
|
|
|
| mock_yaml_safe_load.assert_called_once()
|
|
|
|
|
| mock_file.assert_called_once_with(dummy_yaml_file_name, 'r', encoding='utf-8')
|
|
|
|
|
| @patch('builtins.open', new_callable=mock_open)
|
| def test_load_extra_model_paths_expands_appdata(
|
| mock_file,
|
| monkeypatch,
|
| mock_add_model_folder_path,
|
| mock_expandvars_appdata,
|
| yaml_config_with_appdata,
|
| mock_yaml_content_appdata
|
| ):
|
|
|
| mock_file.return_value.read.return_value = yaml_config_with_appdata
|
|
|
|
|
| monkeypatch.setattr(folder_paths, 'add_model_folder_path', mock_add_model_folder_path)
|
| monkeypatch.setattr(os.path, 'expandvars', mock_expandvars_appdata)
|
| monkeypatch.setattr(yaml, 'safe_load', Mock(return_value=mock_yaml_content_appdata))
|
|
|
|
|
| monkeypatch.setattr(os.path, 'expanduser', lambda x: x)
|
|
|
| dummy_yaml_file_name = 'dummy_path.yaml'
|
| load_extra_path_config(dummy_yaml_file_name)
|
|
|
| if sys.platform == "win32":
|
| expected_base_path = 'C:/Users/TestUser/AppData/Roaming/ComfyUI'
|
| else:
|
| expected_base_path = '/Users/TestUser/AppData/Roaming/ComfyUI'
|
| expected_calls = [
|
| ('checkpoints', os.path.normpath(os.path.join(expected_base_path, 'models/checkpoints')), False),
|
| ]
|
|
|
| assert mock_add_model_folder_path.call_count == len(expected_calls)
|
|
|
|
|
| for actual_call, expected_call in zip(mock_add_model_folder_path.call_args_list, expected_calls):
|
| assert actual_call.args == expected_call
|
|
|
|
|
| assert mock_expandvars_appdata.called
|
|
|
|
|
| @patch("builtins.open", new_callable=mock_open, read_data="dummy yaml content")
|
| @patch("yaml.safe_load")
|
| def test_load_extra_path_config_relative_base_path(
|
| mock_yaml_load, _mock_file, clear_folder_paths, monkeypatch, tmp_path
|
| ):
|
| """
|
| Test that when 'base_path' is a relative path in the YAML, it is joined to the YAML file directory, and then
|
| the items in the config are correctly converted to absolute paths.
|
| """
|
| sub_folder = "./my_rel_base"
|
| config_data = {
|
| "some_model_folder": {
|
| "base_path": sub_folder,
|
| "is_default": True,
|
| "checkpoints": "checkpoints",
|
| "some_key": "some_value"
|
| }
|
| }
|
| mock_yaml_load.return_value = config_data
|
|
|
| dummy_yaml_name = "dummy_file.yaml"
|
|
|
| def fake_abspath(path):
|
| if path == dummy_yaml_name:
|
|
|
| return os.path.join(str(tmp_path), dummy_yaml_name)
|
| return os.path.join(str(tmp_path), path)
|
|
|
| def fake_dirname(path):
|
|
|
| if path.endswith(dummy_yaml_name):
|
| return str(tmp_path)
|
| return os.path.dirname(path)
|
|
|
| monkeypatch.setattr(os.path, "abspath", fake_abspath)
|
| monkeypatch.setattr(os.path, "dirname", fake_dirname)
|
|
|
| load_extra_path_config(dummy_yaml_name)
|
|
|
| expected_checkpoints = os.path.abspath(os.path.join(str(tmp_path), "my_rel_base", "checkpoints"))
|
| expected_some_value = os.path.abspath(os.path.join(str(tmp_path), "my_rel_base", "some_value"))
|
|
|
| actual_paths = folder_paths.folder_names_and_paths["checkpoints"][0]
|
| assert len(actual_paths) == 1, "Should have one path added for 'checkpoints'."
|
| assert actual_paths[0] == expected_checkpoints
|
|
|
| actual_paths = folder_paths.folder_names_and_paths["some_key"][0]
|
| assert len(actual_paths) == 1, "Should have one path added for 'some_key'."
|
| assert actual_paths[0] == expected_some_value
|
|
|
|
|
| @patch("builtins.open", new_callable=mock_open, read_data="dummy yaml content")
|
| @patch("yaml.safe_load")
|
| def test_load_extra_path_config_absolute_base_path(
|
| mock_yaml_load, _mock_file, clear_folder_paths, monkeypatch, tmp_path
|
| ):
|
| """
|
| Test that when 'base_path' is an absolute path, each subdirectory is joined with that absolute path,
|
| rather than being relative to the YAML's directory.
|
| """
|
| abs_base = os.path.join(str(tmp_path), "abs_base")
|
| config_data = {
|
| "some_absolute_folder": {
|
| "base_path": abs_base,
|
| "is_default": True,
|
| "loras": "loras_folder",
|
| "embeddings": "embeddings_folder"
|
| }
|
| }
|
| mock_yaml_load.return_value = config_data
|
|
|
| dummy_yaml_name = "dummy_abs.yaml"
|
|
|
| def fake_abspath(path):
|
| if path == dummy_yaml_name:
|
|
|
| return os.path.join(str(tmp_path), dummy_yaml_name)
|
| return path
|
|
|
| def fake_dirname(path):
|
| return str(tmp_path) if path.endswith(dummy_yaml_name) else os.path.dirname(path)
|
|
|
| monkeypatch.setattr(os.path, "abspath", fake_abspath)
|
| monkeypatch.setattr(os.path, "dirname", fake_dirname)
|
|
|
| load_extra_path_config(dummy_yaml_name)
|
|
|
|
|
| expected_loras = os.path.join(abs_base, "loras_folder")
|
| expected_embeddings = os.path.join(abs_base, "embeddings_folder")
|
|
|
| actual_loras = folder_paths.folder_names_and_paths["loras"][0]
|
| assert len(actual_loras) == 1, "Should have one path for 'loras'."
|
| assert actual_loras[0] == os.path.abspath(expected_loras)
|
|
|
| actual_embeddings = folder_paths.folder_names_and_paths["embeddings"][0]
|
| assert len(actual_embeddings) == 1, "Should have one path for 'embeddings'."
|
| assert actual_embeddings[0] == os.path.abspath(expected_embeddings)
|
|
|
|
|
| @patch("builtins.open", new_callable=mock_open, read_data="dummy yaml content")
|
| @patch("yaml.safe_load")
|
| def test_load_extra_path_config_no_base_path(
|
| mock_yaml_load, _mock_file, clear_folder_paths, monkeypatch, tmp_path
|
| ):
|
| """
|
| Test that if 'base_path' is not present, each path is joined
|
| with the directory of the YAML file (unless it's already absolute).
|
| """
|
| config_data = {
|
| "some_folder_without_base": {
|
| "is_default": True,
|
| "text_encoders": "clip",
|
| "diffusion_models": "unet"
|
| }
|
| }
|
| mock_yaml_load.return_value = config_data
|
|
|
| dummy_yaml_name = "dummy_no_base.yaml"
|
|
|
| def fake_abspath(path):
|
| if path == dummy_yaml_name:
|
| return os.path.join(str(tmp_path), dummy_yaml_name)
|
| return os.path.join(str(tmp_path), path)
|
|
|
| def fake_dirname(path):
|
| return str(tmp_path) if path.endswith(dummy_yaml_name) else os.path.dirname(path)
|
|
|
| monkeypatch.setattr(os.path, "abspath", fake_abspath)
|
| monkeypatch.setattr(os.path, "dirname", fake_dirname)
|
|
|
| load_extra_path_config(dummy_yaml_name)
|
|
|
| expected_clip = os.path.join(str(tmp_path), "clip")
|
| expected_unet = os.path.join(str(tmp_path), "unet")
|
|
|
| actual_text_encoders = folder_paths.folder_names_and_paths["text_encoders"][0]
|
| assert len(actual_text_encoders) == 1, "Should have one path for 'text_encoders'."
|
| assert actual_text_encoders[0] == os.path.abspath(expected_clip)
|
|
|
| actual_diffusion = folder_paths.folder_names_and_paths["diffusion_models"][0]
|
| assert len(actual_diffusion) == 1, "Should have one path for 'diffusion_models'."
|
| assert actual_diffusion[0] == os.path.abspath(expected_unet)
|
|
|