mirror of
https://github.com/meta-llama/llama-stack.git
synced 2025-07-27 06:28:50 +00:00
feat: use XDG directory standards
Signed-off-by: Mustafa Elbehery <melbeher@redhat.com>
This commit is contained in:
parent
9736f096f6
commit
407c3e3bad
50 changed files with 5611 additions and 508 deletions
418
tests/unit/test_config_dirs.py
Normal file
418
tests/unit/test_config_dirs.py
Normal file
|
@ -0,0 +1,418 @@
|
|||
# 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()
|
Loading…
Add table
Add a link
Reference in a new issue