mirror of
https://github.com/meta-llama/llama-stack.git
synced 2025-07-26 06:07:43 +00:00
418 lines
16 KiB
Python
418 lines
16 KiB
Python
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
# All rights reserved.
|
|
#
|
|
# This source code is licensed under the terms described in the LICENSE file in
|
|
# the root directory of this source tree.
|
|
|
|
import os
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
|
|
# Import after we set up environment to avoid module-level imports affecting tests
|
|
class TestConfigDirs(unittest.TestCase):
|
|
"""Test the config_dirs module with XDG compliance and backwards compatibility."""
|
|
|
|
def setUp(self):
|
|
"""Set up test environment."""
|
|
# Store original environment variables
|
|
self.original_env = {}
|
|
self.env_vars = [
|
|
"XDG_CONFIG_HOME",
|
|
"XDG_DATA_HOME",
|
|
"XDG_STATE_HOME",
|
|
"XDG_CACHE_HOME",
|
|
"LLAMA_STACK_CONFIG_DIR",
|
|
"SQLITE_STORE_DIR",
|
|
"FILES_STORAGE_DIR",
|
|
]
|
|
|
|
for key in self.env_vars:
|
|
self.original_env[key] = os.environ.get(key)
|
|
|
|
def tearDown(self):
|
|
"""Clean up test environment."""
|
|
# Restore original environment variables
|
|
for key, value in self.original_env.items():
|
|
if value is None:
|
|
os.environ.pop(key, None)
|
|
else:
|
|
os.environ[key] = value
|
|
|
|
# Clear module cache to ensure fresh imports
|
|
import sys
|
|
|
|
modules_to_clear = ["llama_stack.distribution.utils.config_dirs", "llama_stack.distribution.utils.xdg_utils"]
|
|
for module in modules_to_clear:
|
|
if module in sys.modules:
|
|
del sys.modules[module]
|
|
|
|
def clear_env_vars(self):
|
|
"""Clear all relevant environment variables."""
|
|
for key in self.env_vars:
|
|
os.environ.pop(key, None)
|
|
|
|
def test_config_dirs_xdg_defaults(self):
|
|
"""Test config_dirs with XDG default paths."""
|
|
self.clear_env_vars()
|
|
|
|
# Mock that no legacy directory exists
|
|
with patch("llama_stack.distribution.utils.xdg_utils.Path.home") as mock_home:
|
|
mock_home.return_value = Path("/home/testuser")
|
|
with patch("llama_stack.distribution.utils.xdg_utils.Path.exists") as mock_exists:
|
|
mock_exists.return_value = False
|
|
|
|
# Import after setting up mocks
|
|
from llama_stack.distribution.utils.config_dirs import (
|
|
DEFAULT_CHECKPOINT_DIR,
|
|
DISTRIBS_BASE_DIR,
|
|
EXTERNAL_PROVIDERS_DIR,
|
|
LLAMA_STACK_CONFIG_DIR,
|
|
RUNTIME_BASE_DIR,
|
|
)
|
|
|
|
# Verify XDG-compliant paths
|
|
self.assertEqual(LLAMA_STACK_CONFIG_DIR, Path("/home/testuser/.config/llama-stack"))
|
|
self.assertEqual(DEFAULT_CHECKPOINT_DIR, Path("/home/testuser/.local/share/llama-stack/checkpoints"))
|
|
self.assertEqual(RUNTIME_BASE_DIR, Path("/home/testuser/.local/state/llama-stack/runtime"))
|
|
self.assertEqual(EXTERNAL_PROVIDERS_DIR, Path("/home/testuser/.config/llama-stack/providers.d"))
|
|
self.assertEqual(DISTRIBS_BASE_DIR, Path("/home/testuser/.config/llama-stack/distributions"))
|
|
|
|
def test_config_dirs_custom_xdg_paths(self):
|
|
"""Test config_dirs with custom XDG paths."""
|
|
self.clear_env_vars()
|
|
|
|
# Set custom XDG paths
|
|
os.environ["XDG_CONFIG_HOME"] = "/custom/config"
|
|
os.environ["XDG_DATA_HOME"] = "/custom/data"
|
|
os.environ["XDG_STATE_HOME"] = "/custom/state"
|
|
|
|
# Mock that no legacy directory exists
|
|
with patch("llama_stack.distribution.utils.xdg_utils.Path.exists") as mock_exists:
|
|
mock_exists.return_value = False
|
|
|
|
from llama_stack.distribution.utils.config_dirs import (
|
|
DEFAULT_CHECKPOINT_DIR,
|
|
DISTRIBS_BASE_DIR,
|
|
EXTERNAL_PROVIDERS_DIR,
|
|
LLAMA_STACK_CONFIG_DIR,
|
|
RUNTIME_BASE_DIR,
|
|
)
|
|
|
|
# Verify custom XDG paths are used
|
|
self.assertEqual(LLAMA_STACK_CONFIG_DIR, Path("/custom/config/llama-stack"))
|
|
self.assertEqual(DEFAULT_CHECKPOINT_DIR, Path("/custom/data/llama-stack/checkpoints"))
|
|
self.assertEqual(RUNTIME_BASE_DIR, Path("/custom/state/llama-stack/runtime"))
|
|
self.assertEqual(EXTERNAL_PROVIDERS_DIR, Path("/custom/config/llama-stack/providers.d"))
|
|
self.assertEqual(DISTRIBS_BASE_DIR, Path("/custom/config/llama-stack/distributions"))
|
|
|
|
def test_config_dirs_legacy_environment_variable(self):
|
|
"""Test config_dirs with legacy LLAMA_STACK_CONFIG_DIR."""
|
|
self.clear_env_vars()
|
|
|
|
# Set legacy environment variable
|
|
os.environ["LLAMA_STACK_CONFIG_DIR"] = "/legacy/llama"
|
|
|
|
from llama_stack.distribution.utils.config_dirs import (
|
|
DEFAULT_CHECKPOINT_DIR,
|
|
DISTRIBS_BASE_DIR,
|
|
EXTERNAL_PROVIDERS_DIR,
|
|
LLAMA_STACK_CONFIG_DIR,
|
|
RUNTIME_BASE_DIR,
|
|
)
|
|
|
|
# All paths should use the legacy base
|
|
legacy_base = Path("/legacy/llama")
|
|
self.assertEqual(LLAMA_STACK_CONFIG_DIR, legacy_base)
|
|
self.assertEqual(DEFAULT_CHECKPOINT_DIR, legacy_base / "checkpoints")
|
|
self.assertEqual(RUNTIME_BASE_DIR, legacy_base / "runtime")
|
|
self.assertEqual(EXTERNAL_PROVIDERS_DIR, legacy_base / "providers.d")
|
|
self.assertEqual(DISTRIBS_BASE_DIR, legacy_base / "distributions")
|
|
|
|
def test_config_dirs_legacy_directory_exists(self):
|
|
"""Test config_dirs when legacy ~/.llama directory exists."""
|
|
self.clear_env_vars()
|
|
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
home_dir = Path(temp_dir)
|
|
legacy_dir = home_dir / ".llama"
|
|
legacy_dir.mkdir()
|
|
(legacy_dir / "test_file").touch() # Add content
|
|
|
|
with patch("llama_stack.distribution.utils.xdg_utils.Path.home") as mock_home:
|
|
mock_home.return_value = home_dir
|
|
|
|
from llama_stack.distribution.utils.config_dirs import (
|
|
DEFAULT_CHECKPOINT_DIR,
|
|
DISTRIBS_BASE_DIR,
|
|
EXTERNAL_PROVIDERS_DIR,
|
|
LLAMA_STACK_CONFIG_DIR,
|
|
RUNTIME_BASE_DIR,
|
|
)
|
|
|
|
# Should use legacy directory
|
|
self.assertEqual(LLAMA_STACK_CONFIG_DIR, legacy_dir)
|
|
self.assertEqual(DEFAULT_CHECKPOINT_DIR, legacy_dir / "checkpoints")
|
|
self.assertEqual(RUNTIME_BASE_DIR, legacy_dir / "runtime")
|
|
self.assertEqual(EXTERNAL_PROVIDERS_DIR, legacy_dir / "providers.d")
|
|
self.assertEqual(DISTRIBS_BASE_DIR, legacy_dir / "distributions")
|
|
|
|
def test_config_dirs_precedence_order(self):
|
|
"""Test precedence order: LLAMA_STACK_CONFIG_DIR > legacy directory > XDG."""
|
|
self.clear_env_vars()
|
|
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
home_dir = Path(temp_dir)
|
|
legacy_dir = home_dir / ".llama"
|
|
legacy_dir.mkdir()
|
|
(legacy_dir / "test_file").touch()
|
|
|
|
# Set both legacy env var and XDG vars
|
|
os.environ["LLAMA_STACK_CONFIG_DIR"] = "/priority/path"
|
|
os.environ["XDG_CONFIG_HOME"] = "/custom/config"
|
|
|
|
with patch("llama_stack.distribution.utils.xdg_utils.Path.home") as mock_home:
|
|
mock_home.return_value = home_dir
|
|
|
|
from llama_stack.distribution.utils.config_dirs import LLAMA_STACK_CONFIG_DIR
|
|
|
|
# Legacy env var should take precedence
|
|
self.assertEqual(LLAMA_STACK_CONFIG_DIR, Path("/priority/path"))
|
|
|
|
def test_config_dirs_all_path_types(self):
|
|
"""Test that all path objects are of correct type and absolute."""
|
|
self.clear_env_vars()
|
|
|
|
from llama_stack.distribution.utils.config_dirs import (
|
|
DEFAULT_CHECKPOINT_DIR,
|
|
DISTRIBS_BASE_DIR,
|
|
EXTERNAL_PROVIDERS_DIR,
|
|
LLAMA_STACK_CONFIG_DIR,
|
|
RUNTIME_BASE_DIR,
|
|
)
|
|
|
|
# All should be Path objects
|
|
paths = [
|
|
LLAMA_STACK_CONFIG_DIR,
|
|
DEFAULT_CHECKPOINT_DIR,
|
|
RUNTIME_BASE_DIR,
|
|
EXTERNAL_PROVIDERS_DIR,
|
|
DISTRIBS_BASE_DIR,
|
|
]
|
|
|
|
for path in paths:
|
|
self.assertIsInstance(path, Path, f"Path {path} should be Path object")
|
|
self.assertTrue(path.is_absolute(), f"Path {path} should be absolute")
|
|
|
|
def test_config_dirs_directory_relationships(self):
|
|
"""Test relationships between different directory paths."""
|
|
self.clear_env_vars()
|
|
|
|
from llama_stack.distribution.utils.config_dirs import (
|
|
DISTRIBS_BASE_DIR,
|
|
EXTERNAL_PROVIDERS_DIR,
|
|
LLAMA_STACK_CONFIG_DIR,
|
|
)
|
|
|
|
# Test parent-child relationships
|
|
self.assertEqual(EXTERNAL_PROVIDERS_DIR.parent, LLAMA_STACK_CONFIG_DIR)
|
|
self.assertEqual(DISTRIBS_BASE_DIR.parent, LLAMA_STACK_CONFIG_DIR)
|
|
|
|
# Test expected subdirectory names
|
|
self.assertEqual(EXTERNAL_PROVIDERS_DIR.name, "providers.d")
|
|
self.assertEqual(DISTRIBS_BASE_DIR.name, "distributions")
|
|
|
|
def test_config_dirs_environment_isolation(self):
|
|
"""Test that config_dirs is properly isolated between tests."""
|
|
self.clear_env_vars()
|
|
|
|
# First import with one set of environment variables
|
|
os.environ["LLAMA_STACK_CONFIG_DIR"] = "/first/path"
|
|
|
|
# Clear module cache
|
|
import sys
|
|
|
|
if "llama_stack.distribution.utils.config_dirs" in sys.modules:
|
|
del sys.modules["llama_stack.distribution.utils.config_dirs"]
|
|
|
|
from llama_stack.distribution.utils.config_dirs import LLAMA_STACK_CONFIG_DIR as FIRST_CONFIG
|
|
|
|
# Change environment and re-import
|
|
os.environ["LLAMA_STACK_CONFIG_DIR"] = "/second/path"
|
|
|
|
# Clear module cache again
|
|
if "llama_stack.distribution.utils.config_dirs" in sys.modules:
|
|
del sys.modules["llama_stack.distribution.utils.config_dirs"]
|
|
|
|
from llama_stack.distribution.utils.config_dirs import LLAMA_STACK_CONFIG_DIR as SECOND_CONFIG
|
|
|
|
# Should get different paths
|
|
self.assertEqual(FIRST_CONFIG, Path("/first/path"))
|
|
self.assertEqual(SECOND_CONFIG, Path("/second/path"))
|
|
|
|
def test_config_dirs_with_tilde_expansion(self):
|
|
"""Test config_dirs with tilde in paths."""
|
|
self.clear_env_vars()
|
|
|
|
os.environ["LLAMA_STACK_CONFIG_DIR"] = "~/custom_llama"
|
|
|
|
from llama_stack.distribution.utils.config_dirs import LLAMA_STACK_CONFIG_DIR
|
|
|
|
# Should expand tilde
|
|
expected = Path.home() / "custom_llama"
|
|
self.assertEqual(LLAMA_STACK_CONFIG_DIR, expected)
|
|
|
|
def test_config_dirs_empty_environment_variables(self):
|
|
"""Test config_dirs with empty environment variables."""
|
|
self.clear_env_vars()
|
|
|
|
# Set empty values
|
|
os.environ["XDG_CONFIG_HOME"] = ""
|
|
os.environ["XDG_DATA_HOME"] = ""
|
|
|
|
# Mock no legacy directory
|
|
with patch("llama_stack.distribution.utils.xdg_utils.Path.home") as mock_home:
|
|
mock_home.return_value = Path("/home/testuser")
|
|
with patch("llama_stack.distribution.utils.xdg_utils.Path.exists") as mock_exists:
|
|
mock_exists.return_value = False
|
|
|
|
from llama_stack.distribution.utils.config_dirs import (
|
|
DEFAULT_CHECKPOINT_DIR,
|
|
LLAMA_STACK_CONFIG_DIR,
|
|
)
|
|
|
|
# Should fall back to defaults
|
|
self.assertEqual(LLAMA_STACK_CONFIG_DIR, Path("/home/testuser/.config/llama-stack"))
|
|
self.assertEqual(DEFAULT_CHECKPOINT_DIR, Path("/home/testuser/.local/share/llama-stack/checkpoints"))
|
|
|
|
def test_config_dirs_relative_paths(self):
|
|
"""Test config_dirs with relative paths in environment variables."""
|
|
self.clear_env_vars()
|
|
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
os.chdir(temp_dir)
|
|
|
|
# Use relative path
|
|
os.environ["LLAMA_STACK_CONFIG_DIR"] = "relative/config"
|
|
|
|
from llama_stack.distribution.utils.config_dirs import LLAMA_STACK_CONFIG_DIR
|
|
|
|
# Should be resolved to absolute path
|
|
self.assertTrue(LLAMA_STACK_CONFIG_DIR.is_absolute())
|
|
self.assertTrue(str(LLAMA_STACK_CONFIG_DIR).endswith("relative/config"))
|
|
|
|
def test_config_dirs_with_spaces_in_paths(self):
|
|
"""Test config_dirs with spaces in directory paths."""
|
|
self.clear_env_vars()
|
|
|
|
path_with_spaces = "/path with spaces/llama config"
|
|
os.environ["LLAMA_STACK_CONFIG_DIR"] = path_with_spaces
|
|
|
|
from llama_stack.distribution.utils.config_dirs import LLAMA_STACK_CONFIG_DIR
|
|
|
|
self.assertEqual(LLAMA_STACK_CONFIG_DIR, Path(path_with_spaces))
|
|
|
|
def test_config_dirs_unicode_paths(self):
|
|
"""Test config_dirs with unicode characters in paths."""
|
|
self.clear_env_vars()
|
|
|
|
unicode_path = "/配置/llama-stack"
|
|
os.environ["LLAMA_STACK_CONFIG_DIR"] = unicode_path
|
|
|
|
from llama_stack.distribution.utils.config_dirs import LLAMA_STACK_CONFIG_DIR
|
|
|
|
self.assertEqual(LLAMA_STACK_CONFIG_DIR, Path(unicode_path))
|
|
|
|
def test_config_dirs_compatibility_import(self):
|
|
"""Test that config_dirs can be imported without errors in various scenarios."""
|
|
self.clear_env_vars()
|
|
|
|
# Test import with no environment variables
|
|
try:
|
|
# If we get here without exception, the import succeeded
|
|
self.assertTrue(True)
|
|
except Exception as e:
|
|
self.fail(f"Import failed: {e}")
|
|
|
|
def test_config_dirs_multiple_imports(self):
|
|
"""Test that multiple imports of config_dirs return consistent results."""
|
|
self.clear_env_vars()
|
|
|
|
os.environ["LLAMA_STACK_CONFIG_DIR"] = "/consistent/path"
|
|
|
|
# First import
|
|
from llama_stack.distribution.utils.config_dirs import LLAMA_STACK_CONFIG_DIR as FIRST_IMPORT
|
|
|
|
# Second import (should get cached result)
|
|
from llama_stack.distribution.utils.config_dirs import LLAMA_STACK_CONFIG_DIR as SECOND_IMPORT
|
|
|
|
self.assertEqual(FIRST_IMPORT, SECOND_IMPORT)
|
|
self.assertIs(FIRST_IMPORT, SECOND_IMPORT) # Should be the same object
|
|
|
|
|
|
class TestConfigDirsIntegration(unittest.TestCase):
|
|
"""Integration tests for config_dirs with other modules."""
|
|
|
|
def setUp(self):
|
|
"""Set up test environment."""
|
|
self.original_env = {}
|
|
for key in ["XDG_CONFIG_HOME", "XDG_DATA_HOME", "XDG_STATE_HOME", "LLAMA_STACK_CONFIG_DIR"]:
|
|
self.original_env[key] = os.environ.get(key)
|
|
|
|
def tearDown(self):
|
|
"""Clean up test environment."""
|
|
for key, value in self.original_env.items():
|
|
if value is None:
|
|
os.environ.pop(key, None)
|
|
else:
|
|
os.environ[key] = value
|
|
|
|
# Clear module cache
|
|
import sys
|
|
|
|
modules_to_clear = ["llama_stack.distribution.utils.config_dirs", "llama_stack.distribution.utils.xdg_utils"]
|
|
for module in modules_to_clear:
|
|
if module in sys.modules:
|
|
del sys.modules[module]
|
|
|
|
def test_config_dirs_with_model_utils(self):
|
|
"""Test that config_dirs works correctly with model_utils."""
|
|
for key in self.original_env:
|
|
os.environ.pop(key, None)
|
|
|
|
from llama_stack.distribution.utils.config_dirs import DEFAULT_CHECKPOINT_DIR
|
|
from llama_stack.distribution.utils.model_utils import model_local_dir
|
|
|
|
# Test that model_local_dir uses the correct base directory
|
|
model_descriptor = "meta-llama/Llama-3.2-1B-Instruct"
|
|
expected_path = str(DEFAULT_CHECKPOINT_DIR / model_descriptor.replace(":", "-"))
|
|
actual_path = model_local_dir(model_descriptor)
|
|
|
|
self.assertEqual(actual_path, expected_path)
|
|
|
|
def test_config_dirs_consistency_across_modules(self):
|
|
"""Test that all modules use consistent directory paths."""
|
|
for key in self.original_env:
|
|
os.environ.pop(key, None)
|
|
|
|
from llama_stack.distribution.utils.config_dirs import (
|
|
DEFAULT_CHECKPOINT_DIR,
|
|
LLAMA_STACK_CONFIG_DIR,
|
|
RUNTIME_BASE_DIR,
|
|
)
|
|
from llama_stack.distribution.utils.xdg_utils import (
|
|
get_llama_stack_config_dir,
|
|
get_llama_stack_data_dir,
|
|
get_llama_stack_state_dir,
|
|
)
|
|
|
|
# Paths should be consistent between modules
|
|
self.assertEqual(LLAMA_STACK_CONFIG_DIR, get_llama_stack_config_dir())
|
|
self.assertEqual(DEFAULT_CHECKPOINT_DIR.parent, get_llama_stack_data_dir())
|
|
self.assertEqual(RUNTIME_BASE_DIR.parent, get_llama_stack_state_dir())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|