feat: use XDG directory standards

Signed-off-by: Mustafa Elbehery <melbeher@redhat.com>
This commit is contained in:
Mustafa Elbehery 2025-07-03 18:48:53 +02:00
parent 9736f096f6
commit 407c3e3bad
50 changed files with 5611 additions and 508 deletions

View 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()