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,489 @@
# 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.
# All rights reserved.
#
# This source code is licensed under the terms described in the LICENSE file in
# the root directory of this source tree.
# 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 argparse
import os
import tempfile
import unittest
from io import StringIO
from pathlib import Path
from unittest.mock import patch
from llama_stack.cli.migrate_xdg import MigrateXDG, migrate_to_xdg
class TestMigrateXDGCLI(unittest.TestCase):
"""Test the MigrateXDG CLI command."""
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)
os.environ.pop(key, None)
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
def create_parser_with_migrate_cmd(self):
"""Create parser with migrate-xdg command."""
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest="command")
migrate_cmd = MigrateXDG.create(subparsers)
return parser, migrate_cmd
def test_migrate_xdg_command_creation(self):
"""Test that MigrateXDG command can be created."""
parser, migrate_cmd = self.create_parser_with_migrate_cmd()
self.assertIsInstance(migrate_cmd, MigrateXDG)
self.assertEqual(migrate_cmd.parser.prog, "llama migrate-xdg")
self.assertEqual(migrate_cmd.parser.description, "Migrate from legacy ~/.llama to XDG-compliant directories")
def test_migrate_xdg_argument_parsing(self):
"""Test argument parsing for migrate-xdg command."""
parser, _ = self.create_parser_with_migrate_cmd()
# Test with dry-run flag
args = parser.parse_args(["migrate-xdg", "--dry-run"])
self.assertEqual(args.command, "migrate-xdg")
self.assertTrue(args.dry_run)
# Test without dry-run flag
args = parser.parse_args(["migrate-xdg"])
self.assertEqual(args.command, "migrate-xdg")
self.assertFalse(args.dry_run)
def test_migrate_xdg_help_text(self):
"""Test help text for migrate-xdg command."""
parser, _ = self.create_parser_with_migrate_cmd()
# Capture help output
with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
with patch("sys.exit"):
try:
parser.parse_args(["migrate-xdg", "--help"])
except SystemExit:
pass
help_text = mock_stdout.getvalue()
self.assertIn("migrate-xdg", help_text)
self.assertIn("XDG-compliant directories", help_text)
self.assertIn("--dry-run", help_text)
def test_migrate_xdg_command_execution_no_legacy(self):
"""Test command execution when no legacy directory exists."""
parser, migrate_cmd = self.create_parser_with_migrate_cmd()
with tempfile.TemporaryDirectory() as temp_dir:
with patch("llama_stack.distribution.utils.xdg_utils.Path.home") as mock_home:
mock_home.return_value = Path(temp_dir)
args = parser.parse_args(["migrate-xdg"])
with patch("builtins.print") as mock_print:
result = migrate_cmd._run_migrate_xdg_cmd(args)
# Should succeed when no migration needed
self.assertEqual(result, 0)
# Should print appropriate message
print_calls = [call[0][0] for call in mock_print.call_args_list]
self.assertTrue(any("No legacy directory found" in call for call in print_calls))
def test_migrate_xdg_command_execution_with_legacy(self):
"""Test command execution when legacy directory exists."""
parser, migrate_cmd = self.create_parser_with_migrate_cmd()
with tempfile.TemporaryDirectory() as temp_dir:
base_dir = Path(temp_dir)
legacy_dir = base_dir / ".llama"
legacy_dir.mkdir()
(legacy_dir / "test_file").touch()
with patch("llama_stack.distribution.utils.xdg_utils.Path.home") as mock_home:
mock_home.return_value = base_dir
args = parser.parse_args(["migrate-xdg"])
with patch("builtins.print") as mock_print:
result = migrate_cmd._run_migrate_xdg_cmd(args)
# Should succeed
self.assertEqual(result, 0)
# Should print migration information
print_calls = [call[0][0] for call in mock_print.call_args_list]
self.assertTrue(any("Found legacy directory" in call for call in print_calls))
def test_migrate_xdg_command_execution_dry_run(self):
"""Test command execution with dry-run flag."""
parser, migrate_cmd = self.create_parser_with_migrate_cmd()
with tempfile.TemporaryDirectory() as temp_dir:
base_dir = Path(temp_dir)
legacy_dir = base_dir / ".llama"
legacy_dir.mkdir()
(legacy_dir / "test_file").touch()
with patch("llama_stack.distribution.utils.xdg_utils.Path.home") as mock_home:
mock_home.return_value = base_dir
args = parser.parse_args(["migrate-xdg", "--dry-run"])
with patch("builtins.print") as mock_print:
result = migrate_cmd._run_migrate_xdg_cmd(args)
# Should succeed
self.assertEqual(result, 0)
# Should print dry-run information
print_calls = [call[0][0] for call in mock_print.call_args_list]
self.assertTrue(any("Dry run mode" in call for call in print_calls))
def test_migrate_xdg_command_execution_error_handling(self):
"""Test command execution with error handling."""
parser, migrate_cmd = self.create_parser_with_migrate_cmd()
args = parser.parse_args(["migrate-xdg"])
# Mock migrate_to_xdg to raise an exception
with patch("llama_stack.cli.migrate_xdg.migrate_to_xdg") as mock_migrate:
mock_migrate.side_effect = Exception("Test error")
with patch("builtins.print") as mock_print:
result = migrate_cmd._run_migrate_xdg_cmd(args)
# Should return error code
self.assertEqual(result, 1)
# Should print error message
print_calls = [call[0][0] for call in mock_print.call_args_list]
self.assertTrue(any("Error during migration" in call for call in print_calls))
def test_migrate_xdg_command_integration(self):
"""Test full integration of migrate-xdg command."""
from llama_stack.cli.llama import LlamaCLIParser
# Create main parser
main_parser = LlamaCLIParser()
# Test that migrate-xdg is in the subcommands
with patch("sys.argv", ["llama", "migrate-xdg", "--help"]):
with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
with patch("sys.exit"):
try:
main_parser.parse_args()
except SystemExit:
pass
help_text = mock_stdout.getvalue()
self.assertIn("migrate-xdg", help_text)
class TestMigrateXDGFunction(unittest.TestCase):
"""Test the migrate_to_xdg function directly."""
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)
os.environ.pop(key, None)
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
def create_legacy_structure(self, base_dir: Path) -> Path:
"""Create a test legacy directory structure."""
legacy_dir = base_dir / ".llama"
legacy_dir.mkdir()
# Create distributions
(legacy_dir / "distributions").mkdir()
(legacy_dir / "distributions" / "ollama").mkdir()
(legacy_dir / "distributions" / "ollama" / "run.yaml").write_text("version: 2\n")
# Create checkpoints
(legacy_dir / "checkpoints").mkdir()
(legacy_dir / "checkpoints" / "model.bin").write_text("fake model")
# Create providers.d
(legacy_dir / "providers.d").mkdir()
(legacy_dir / "providers.d" / "provider.yaml").write_text("provider: test\n")
# Create runtime
(legacy_dir / "runtime").mkdir()
(legacy_dir / "runtime" / "trace.db").write_text("fake database")
return legacy_dir
def test_migrate_to_xdg_no_legacy_directory(self):
"""Test migrate_to_xdg when no legacy directory exists."""
with tempfile.TemporaryDirectory() as temp_dir:
with patch("llama_stack.distribution.utils.xdg_utils.Path.home") as mock_home:
mock_home.return_value = Path(temp_dir)
result = migrate_to_xdg(dry_run=False)
self.assertTrue(result)
def test_migrate_to_xdg_dry_run(self):
"""Test migrate_to_xdg with dry_run=True."""
with tempfile.TemporaryDirectory() as temp_dir:
base_dir = Path(temp_dir)
legacy_dir = self.create_legacy_structure(base_dir)
with patch("llama_stack.distribution.utils.xdg_utils.Path.home") as mock_home:
mock_home.return_value = base_dir
with patch("builtins.print") as mock_print:
result = migrate_to_xdg(dry_run=True)
self.assertTrue(result)
# Should print dry run information
print_calls = [call[0][0] for call in mock_print.call_args_list]
self.assertTrue(any("Dry run mode" in call for call in print_calls))
# Legacy directory should still exist
self.assertTrue(legacy_dir.exists())
def test_migrate_to_xdg_user_confirms(self):
"""Test migrate_to_xdg when user confirms migration."""
with tempfile.TemporaryDirectory() as temp_dir:
base_dir = Path(temp_dir)
legacy_dir = self.create_legacy_structure(base_dir)
with patch("llama_stack.distribution.utils.xdg_utils.Path.home") as mock_home:
mock_home.return_value = base_dir
with patch("builtins.input") as mock_input:
mock_input.side_effect = ["y", "y"] # Confirm migration and cleanup
result = migrate_to_xdg(dry_run=False)
self.assertTrue(result)
# Legacy directory should be removed
self.assertFalse(legacy_dir.exists())
# XDG directories should be created
self.assertTrue((base_dir / ".config" / "llama-stack").exists())
self.assertTrue((base_dir / ".local" / "share" / "llama-stack").exists())
def test_migrate_to_xdg_user_cancels(self):
"""Test migrate_to_xdg when user cancels migration."""
with tempfile.TemporaryDirectory() as temp_dir:
base_dir = Path(temp_dir)
legacy_dir = self.create_legacy_structure(base_dir)
with patch("llama_stack.distribution.utils.xdg_utils.Path.home") as mock_home:
mock_home.return_value = base_dir
with patch("builtins.input") as mock_input:
mock_input.return_value = "n" # Cancel migration
result = migrate_to_xdg(dry_run=False)
self.assertFalse(result)
# Legacy directory should still exist
self.assertTrue(legacy_dir.exists())
def test_migrate_to_xdg_partial_migration(self):
"""Test migrate_to_xdg with partial migration (some files fail)."""
with tempfile.TemporaryDirectory() as temp_dir:
base_dir = Path(temp_dir)
legacy_dir = self.create_legacy_structure(base_dir)
self.assertFalse(legacy_dir.exists())
with patch("llama_stack.distribution.utils.xdg_utils.Path.home") as mock_home:
mock_home.return_value = base_dir
# Create target directory with conflicting file
config_dir = base_dir / ".config" / "llama-stack"
config_dir.mkdir(parents=True)
(config_dir / "distributions").mkdir()
(config_dir / "distributions" / "existing.yaml").write_text("existing")
with patch("builtins.input") as mock_input:
mock_input.side_effect = ["y", "n"] # Confirm migration, don't cleanup
with patch("builtins.print") as mock_print:
result = migrate_to_xdg(dry_run=False)
self.assertTrue(result)
# Should print warning about conflicts
print_calls = [call[0][0] for call in mock_print.call_args_list]
self.assertTrue(any("Warning: Target already exists" in call for call in print_calls))
def test_migrate_to_xdg_permission_error(self):
"""Test migrate_to_xdg with permission errors."""
with tempfile.TemporaryDirectory() as temp_dir:
base_dir = Path(temp_dir)
legacy_dir = self.create_legacy_structure(base_dir)
self.assertFalse(legacy_dir.exists())
with patch("llama_stack.distribution.utils.xdg_utils.Path.home") as mock_home:
mock_home.return_value = base_dir
# Create readonly target directory
config_dir = base_dir / ".config" / "llama-stack"
config_dir.mkdir(parents=True)
config_dir.chmod(0o444) # Read-only
try:
with patch("builtins.input") as mock_input:
mock_input.side_effect = ["y", "n"] # Confirm migration, don't cleanup
with patch("builtins.print") as mock_print:
result = migrate_to_xdg(dry_run=False)
self.assertFalse(result)
# Should handle permission errors gracefully
print_calls = [call[0][0] for call in mock_print.call_args_list]
# Should contain some error or warning message
self.assertTrue(len(print_calls) > 0)
finally:
# Restore permissions for cleanup
config_dir.chmod(0o755)
def test_migrate_to_xdg_empty_legacy_directory(self):
"""Test migrate_to_xdg with empty legacy directory."""
with tempfile.TemporaryDirectory() as temp_dir:
base_dir = Path(temp_dir)
legacy_dir = base_dir / ".llama"
legacy_dir.mkdir() # Empty directory
with patch("llama_stack.distribution.utils.xdg_utils.Path.home") as mock_home:
mock_home.return_value = base_dir
result = migrate_to_xdg(dry_run=False)
self.assertTrue(result)
def test_migrate_to_xdg_preserves_file_content(self):
"""Test that migrate_to_xdg preserves file content correctly."""
with tempfile.TemporaryDirectory() as temp_dir:
base_dir = Path(temp_dir)
legacy_dir = self.create_legacy_structure(base_dir)
# Add specific content to test
test_content = "test configuration content"
(legacy_dir / "distributions" / "ollama" / "run.yaml").write_text(test_content)
with patch("llama_stack.distribution.utils.xdg_utils.Path.home") as mock_home:
mock_home.return_value = base_dir
with patch("builtins.input") as mock_input:
mock_input.side_effect = ["y", "y"] # Confirm migration and cleanup
result = migrate_to_xdg(dry_run=False)
self.assertTrue(result)
# Check content was preserved
migrated_file = base_dir / ".config" / "llama-stack" / "distributions" / "ollama" / "run.yaml"
self.assertTrue(migrated_file.exists())
self.assertEqual(migrated_file.read_text(), test_content)
def test_migrate_to_xdg_with_symlinks(self):
"""Test migrate_to_xdg with symbolic links."""
with tempfile.TemporaryDirectory() as temp_dir:
base_dir = Path(temp_dir)
legacy_dir = self.create_legacy_structure(base_dir)
# Create symlink
actual_file = legacy_dir / "actual_config.yaml"
actual_file.write_text("actual config")
symlink_file = legacy_dir / "distributions" / "symlinked.yaml"
symlink_file.symlink_to(actual_file)
with patch("llama_stack.distribution.utils.xdg_utils.Path.home") as mock_home:
mock_home.return_value = base_dir
with patch("builtins.input") as mock_input:
mock_input.side_effect = ["y", "y"] # Confirm migration and cleanup
result = migrate_to_xdg(dry_run=False)
self.assertTrue(result)
# Check symlink was preserved
migrated_symlink = base_dir / ".config" / "llama-stack" / "distributions" / "symlinked.yaml"
self.assertTrue(migrated_symlink.exists())
self.assertTrue(migrated_symlink.is_symlink())
def test_migrate_to_xdg_nested_directory_structure(self):
"""Test migrate_to_xdg with nested directory structures."""
with tempfile.TemporaryDirectory() as temp_dir:
base_dir = Path(temp_dir)
legacy_dir = self.create_legacy_structure(base_dir)
# Create nested structure
nested_dir = legacy_dir / "checkpoints" / "org" / "model" / "variant"
nested_dir.mkdir(parents=True)
(nested_dir / "model.bin").write_text("nested model")
with patch("llama_stack.distribution.utils.xdg_utils.Path.home") as mock_home:
mock_home.return_value = base_dir
with patch("builtins.input") as mock_input:
mock_input.side_effect = ["y", "y"] # Confirm migration and cleanup
result = migrate_to_xdg(dry_run=False)
self.assertTrue(result)
# Check nested structure was preserved
migrated_nested = (
base_dir / ".local" / "share" / "llama-stack" / "checkpoints" / "org" / "model" / "variant"
)
self.assertTrue(migrated_nested.exists())
self.assertTrue((migrated_nested / "model.bin").exists())
def test_migrate_to_xdg_user_input_variations(self):
"""Test migrate_to_xdg with various user input variations."""
with tempfile.TemporaryDirectory() as temp_dir:
base_dir = Path(temp_dir)
legacy_dir = self.create_legacy_structure(base_dir)
with patch("llama_stack.distribution.utils.xdg_utils.Path.home") as mock_home:
mock_home.return_value = base_dir
# Test various forms of "yes"
for yes_input in ["y", "Y", "yes", "Yes", "YES"]:
# Recreate legacy directory for each test
if legacy_dir.exists():
import shutil
shutil.rmtree(legacy_dir)
self.create_legacy_structure(base_dir)
with patch("builtins.input") as mock_input:
mock_input.side_effect = [yes_input, "n"] # Confirm migration, don't cleanup
result = migrate_to_xdg(dry_run=False)
self.assertTrue(result, f"Failed with input: {yes_input}")
if __name__ == "__main__":
unittest.main()

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

View file

@ -0,0 +1,422 @@
# 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 unittest
from pathlib import Path
from unittest.mock import MagicMock, patch
import yaml
# Template imports will be tested through file system access
class TestTemplateXDGPaths(unittest.TestCase):
"""Test that templates use XDG-compliant paths correctly."""
def setUp(self):
"""Set up test environment."""
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."""
for key, value in self.original_env.items():
if value is None:
os.environ.pop(key, None)
else:
os.environ[key] = value
def clear_env_vars(self):
"""Clear all relevant environment variables."""
for key in self.env_vars:
os.environ.pop(key, None)
def test_ollama_template_run_yaml_xdg_paths(self):
"""Test that ollama template's run.yaml uses XDG environment variables."""
template_path = Path(__file__).parent.parent.parent / "llama_stack" / "templates" / "ollama" / "run.yaml"
if not template_path.exists():
self.skipTest("Ollama template not found")
content = template_path.read_text()
# Check for XDG-compliant environment variable references
self.assertIn("${env.XDG_STATE_HOME:-~/.local/state}", content)
self.assertIn("${env.XDG_DATA_HOME:-~/.local/share}", content)
# Check that paths use llama-stack directory
self.assertIn("llama-stack", content)
# Check specific path patterns
self.assertIn("${env.XDG_STATE_HOME:-~/.local/state}/llama-stack/distributions/ollama", content)
self.assertIn("${env.XDG_DATA_HOME:-~/.local/share}/llama-stack/distributions/ollama", content)
def test_ollama_template_run_yaml_parsing(self):
"""Test that ollama template's run.yaml can be parsed correctly."""
template_path = Path(__file__).parent.parent.parent / "llama_stack" / "templates" / "ollama" / "run.yaml"
if not template_path.exists():
self.skipTest("Ollama template not found")
content = template_path.read_text()
# Replace environment variables with test values for parsing
test_content = (
content.replace("${env.XDG_STATE_HOME:-~/.local/state}", "/test/state")
.replace("${env.XDG_DATA_HOME:-~/.local/share}", "/test/data")
.replace(
"${env.SQLITE_STORE_DIR:=${env.XDG_STATE_HOME:-~/.local/state}/llama-stack/distributions/ollama}",
"/test/state/llama-stack/distributions/ollama",
)
)
# Should be valid YAML
try:
yaml.safe_load(test_content)
except yaml.YAMLError as e:
self.fail(f"Template YAML is invalid: {e}")
def test_template_environment_variable_expansion(self):
"""Test environment variable expansion in templates."""
self.clear_env_vars()
# Set XDG variables
os.environ["XDG_STATE_HOME"] = "/custom/state"
os.environ["XDG_DATA_HOME"] = "/custom/data"
# Test pattern that should expand
pattern = "${env.XDG_STATE_HOME:-~/.local/state}/llama-stack/test"
expected = "/custom/state/llama-stack/test"
# Mock environment variable expansion (this would normally be done by the shell)
expanded = pattern.replace("${env.XDG_STATE_HOME:-~/.local/state}", os.environ["XDG_STATE_HOME"])
self.assertEqual(expanded, expected)
def test_template_fallback_values(self):
"""Test that templates have correct fallback values."""
self.clear_env_vars()
# Test fallback pattern
pattern = "${env.XDG_STATE_HOME:-~/.local/state}/llama-stack/test"
# When environment variable is not set, should use fallback
if "XDG_STATE_HOME" not in os.environ:
# This is what the shell would do
expanded = pattern.replace("${env.XDG_STATE_HOME:-~/.local/state}", "~/.local/state")
expected = "~/.local/state/llama-stack/test"
self.assertEqual(expanded, expected)
def test_ollama_template_python_config_xdg(self):
"""Test that ollama template's Python config uses XDG-compliant paths."""
template_path = Path(__file__).parent.parent.parent / "llama_stack" / "templates" / "ollama" / "ollama.py"
if not template_path.exists():
self.skipTest("Ollama template Python file not found")
content = template_path.read_text()
# Check for XDG-compliant environment variable references
self.assertIn("${env.XDG_STATE_HOME:-~/.local/state}", content)
self.assertIn("${env.XDG_DATA_HOME:-~/.local/share}", content)
# Check that paths use llama-stack directory
self.assertIn("llama-stack", content)
def test_template_path_consistency(self):
"""Test that template paths are consistent across different files."""
ollama_yaml_path = Path(__file__).parent.parent.parent / "llama_stack" / "templates" / "ollama" / "run.yaml"
ollama_py_path = Path(__file__).parent.parent.parent / "llama_stack" / "templates" / "ollama" / "ollama.py"
if not ollama_yaml_path.exists() or not ollama_py_path.exists():
self.skipTest("Ollama template files not found")
yaml_content = ollama_yaml_path.read_text()
py_content = ollama_py_path.read_text()
# Both should use the same XDG environment variable patterns
xdg_patterns = ["${env.XDG_STATE_HOME:-~/.local/state}", "${env.XDG_DATA_HOME:-~/.local/share}", "llama-stack"]
for pattern in xdg_patterns:
self.assertIn(pattern, yaml_content, f"Pattern {pattern} not found in YAML")
self.assertIn(pattern, py_content, f"Pattern {pattern} not found in Python")
def test_template_no_hardcoded_legacy_paths(self):
"""Test that templates don't contain hardcoded legacy paths."""
template_dir = Path(__file__).parent.parent.parent / "llama_stack" / "templates"
if not template_dir.exists():
self.skipTest("Templates directory not found")
# Check various template files
for template_path in template_dir.rglob("*.yaml"):
content = template_path.read_text()
# Should not contain hardcoded ~/.llama paths
self.assertNotIn("~/.llama", content, f"Found hardcoded ~/.llama in {template_path}")
# Should not contain hardcoded /tmp paths for persistent data
if "db_path" in content or "storage_dir" in content:
self.assertNotIn("/tmp", content, f"Found hardcoded /tmp in {template_path}")
def test_template_environment_variable_format(self):
"""Test that templates use correct environment variable format."""
template_dir = Path(__file__).parent.parent.parent / "llama_stack" / "templates"
if not template_dir.exists():
self.skipTest("Templates directory not found")
# Pattern for XDG environment variables with fallbacks
xdg_patterns = [
"${env.XDG_CONFIG_HOME:-~/.config}",
"${env.XDG_DATA_HOME:-~/.local/share}",
"${env.XDG_STATE_HOME:-~/.local/state}",
"${env.XDG_CACHE_HOME:-~/.cache}",
]
for template_path in template_dir.rglob("*.yaml"):
content = template_path.read_text()
# If XDG variables are used, they should have proper fallbacks
for pattern in xdg_patterns:
base_var = pattern.split(":-")[0] + "}"
if base_var in content:
self.assertIn(pattern, content, f"XDG variable without fallback in {template_path}")
def test_template_sqlite_store_dir_xdg(self):
"""Test that SQLITE_STORE_DIR uses XDG-compliant fallback."""
template_dir = Path(__file__).parent.parent.parent / "llama_stack" / "templates"
if not template_dir.exists():
self.skipTest("Templates directory not found")
for template_path in template_dir.rglob("*.yaml"):
content = template_path.read_text()
if "SQLITE_STORE_DIR" in content:
# Should use XDG fallback pattern
self.assertIn("${env.XDG_STATE_HOME:-~/.local/state}", content)
self.assertIn("llama-stack", content)
def test_template_files_storage_dir_xdg(self):
"""Test that FILES_STORAGE_DIR uses XDG-compliant fallback."""
template_dir = Path(__file__).parent.parent.parent / "llama_stack" / "templates"
if not template_dir.exists():
self.skipTest("Templates directory not found")
for template_path in template_dir.rglob("*.yaml"):
content = template_path.read_text()
if "FILES_STORAGE_DIR" in content:
# Should use XDG fallback pattern
self.assertIn("${env.XDG_DATA_HOME:-~/.local/share}", content)
self.assertIn("llama-stack", content)
class TestTemplateCodeGeneration(unittest.TestCase):
"""Test template code generation with XDG paths."""
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
def test_provider_codegen_xdg_paths(self):
"""Test that provider code generation uses XDG-compliant paths."""
codegen_path = Path(__file__).parent.parent.parent / "scripts" / "provider_codegen.py"
if not codegen_path.exists():
self.skipTest("Provider codegen script not found")
content = codegen_path.read_text()
# Should use XDG-compliant path in documentation
self.assertIn("${env.XDG_DATA_HOME:-~/.local/share}/llama-stack", content)
# Should not use hardcoded ~/.llama paths
self.assertNotIn("~/.llama/dummy", content)
def test_template_sample_config_paths(self):
"""Test that template sample configs use XDG-compliant paths."""
# This test checks that when templates generate sample configs,
# they use XDG-compliant paths
# Mock a template that generates sample config
with patch("llama_stack.templates.template.Template") as mock_template:
mock_instance = MagicMock()
mock_template.return_value = mock_instance
# Mock sample config generation
def mock_sample_config(distro_dir):
# Should use XDG-compliant path structure
self.assertIn("llama-stack", distro_dir)
return {"config": "test"}
mock_instance.sample_run_config = mock_sample_config
# Test sample config generation
template = mock_template()
template.sample_run_config("${env.XDG_DATA_HOME:-~/.local/share}/llama-stack/test")
def test_template_path_substitution(self):
"""Test that template path substitution works correctly."""
# Test path substitution in template generation
original_path = "~/.llama/distributions/test"
# Should be converted to XDG-compliant path
xdg_path = original_path.replace("~/.llama", "${env.XDG_DATA_HOME:-~/.local/share}/llama-stack")
expected = "${env.XDG_DATA_HOME:-~/.local/share}/llama-stack/distributions/test"
self.assertEqual(xdg_path, expected)
def test_template_environment_variable_precedence(self):
"""Test environment variable precedence in templates."""
# Test that custom XDG variables take precedence over defaults
test_cases = [
{
"env": {"XDG_STATE_HOME": "/custom/state"},
"pattern": "${env.XDG_STATE_HOME:-~/.local/state}/llama-stack/test",
"expected": "/custom/state/llama-stack/test",
},
{
"env": {}, # No XDG variable set
"pattern": "${env.XDG_STATE_HOME:-~/.local/state}/llama-stack/test",
"expected": "~/.local/state/llama-stack/test",
},
]
for case in test_cases:
# Clear environment
for key in ["XDG_STATE_HOME", "XDG_DATA_HOME", "XDG_CONFIG_HOME"]:
os.environ.pop(key, None)
# Set test environment
for key, value in case["env"].items():
os.environ[key] = value
# Simulate shell variable expansion
pattern = case["pattern"]
for key, value in case["env"].items():
var_pattern = f"${{env.{key}:-"
if var_pattern in pattern:
# Replace with actual value
pattern = pattern.replace(f"${{env.{key}:-~/.local/state}}", value)
# If no replacement happened, use fallback
if "${env.XDG_STATE_HOME:-~/.local/state}" in pattern:
pattern = pattern.replace("${env.XDG_STATE_HOME:-~/.local/state}", "~/.local/state")
self.assertEqual(pattern, case["expected"])
class TestTemplateIntegration(unittest.TestCase):
"""Integration tests for templates with XDG compliance."""
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
def test_template_with_xdg_environment(self):
"""Test template behavior with XDG environment variables set."""
# Clear environment
for key in self.original_env:
os.environ.pop(key, None)
# Set custom XDG variables
os.environ["XDG_CONFIG_HOME"] = "/custom/config"
os.environ["XDG_DATA_HOME"] = "/custom/data"
os.environ["XDG_STATE_HOME"] = "/custom/state"
# Test that template paths would resolve correctly
# (This is a conceptual test since actual shell expansion happens at runtime)
template_pattern = "${env.XDG_STATE_HOME:-~/.local/state}/llama-stack/test"
# In a real shell, this would expand to:
# Verify the pattern structure is correct
self.assertIn("XDG_STATE_HOME", template_pattern)
self.assertIn("llama-stack", template_pattern)
self.assertIn("~/.local/state", template_pattern) # fallback
def test_template_with_no_xdg_environment(self):
"""Test template behavior with no XDG environment variables."""
# Clear all XDG environment variables
for key in ["XDG_CONFIG_HOME", "XDG_DATA_HOME", "XDG_STATE_HOME", "XDG_CACHE_HOME"]:
os.environ.pop(key, None)
# Test that templates would use fallback values
template_pattern = "${env.XDG_STATE_HOME:-~/.local/state}/llama-stack/test"
# In a real shell with no XDG_STATE_HOME, this would expand to:
# Verify the pattern structure includes fallback
self.assertIn(":-~/.local/state", template_pattern)
def test_template_consistency_across_providers(self):
"""Test that all template providers use consistent XDG patterns."""
templates_dir = Path(__file__).parent.parent.parent / "llama_stack" / "templates"
if not templates_dir.exists():
self.skipTest("Templates directory not found")
# Expected XDG patterns that should be consistent across templates
# Check a few different provider templates
provider_templates = []
for provider_dir in templates_dir.iterdir():
if provider_dir.is_dir() and not provider_dir.name.startswith("."):
run_yaml = provider_dir / "run.yaml"
if run_yaml.exists():
provider_templates.append(run_yaml)
if not provider_templates:
self.skipTest("No provider templates found")
# Check that templates use consistent patterns
for template_path in provider_templates[:3]: # Check first 3 templates
content = template_path.read_text()
# Should use llama-stack in paths
if any(xdg_var in content for xdg_var in ["XDG_CONFIG_HOME", "XDG_DATA_HOME", "XDG_STATE_HOME"]):
self.assertIn("llama-stack", content, f"Template {template_path} uses XDG but not llama-stack")
if __name__ == "__main__":
unittest.main()

View file

@ -0,0 +1,610 @@
# 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
from llama_stack.distribution.utils.xdg_utils import (
ensure_directory_exists,
get_llama_stack_cache_dir,
get_llama_stack_config_dir,
get_llama_stack_data_dir,
get_llama_stack_state_dir,
get_xdg_cache_home,
get_xdg_compliant_path,
get_xdg_config_home,
get_xdg_data_home,
get_xdg_state_home,
migrate_legacy_directory,
)
class TestXDGCompliance(unittest.TestCase):
"""Comprehensive test suite for XDG Base Directory Specification compliance."""
def setUp(self):
"""Set up test environment."""
# Store original environment variables
self.original_env = {}
self.xdg_vars = ["XDG_CONFIG_HOME", "XDG_DATA_HOME", "XDG_CACHE_HOME", "XDG_STATE_HOME"]
self.llama_vars = ["LLAMA_STACK_CONFIG_DIR", "SQLITE_STORE_DIR", "FILES_STORAGE_DIR"]
for key in self.xdg_vars + self.llama_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
def clear_env_vars(self, vars_to_clear=None):
"""Helper to clear environment variables."""
if vars_to_clear is None:
vars_to_clear = self.xdg_vars + self.llama_vars
for key in vars_to_clear:
os.environ.pop(key, None)
def test_xdg_defaults(self):
"""Test that XDG directories use correct defaults when no env vars are set."""
self.clear_env_vars()
home = Path.home()
self.assertEqual(get_xdg_config_home(), home / ".config")
self.assertEqual(get_xdg_data_home(), home / ".local" / "share")
self.assertEqual(get_xdg_cache_home(), home / ".cache")
self.assertEqual(get_xdg_state_home(), home / ".local" / "state")
def test_xdg_custom_paths(self):
"""Test that custom XDG paths are respected."""
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir)
os.environ["XDG_CONFIG_HOME"] = str(temp_path / "config")
os.environ["XDG_DATA_HOME"] = str(temp_path / "data")
os.environ["XDG_CACHE_HOME"] = str(temp_path / "cache")
os.environ["XDG_STATE_HOME"] = str(temp_path / "state")
self.assertEqual(get_xdg_config_home(), temp_path / "config")
self.assertEqual(get_xdg_data_home(), temp_path / "data")
self.assertEqual(get_xdg_cache_home(), temp_path / "cache")
self.assertEqual(get_xdg_state_home(), temp_path / "state")
def test_xdg_paths_with_tilde(self):
"""Test XDG paths that use tilde expansion."""
os.environ["XDG_CONFIG_HOME"] = "~/custom_config"
os.environ["XDG_DATA_HOME"] = "~/custom_data"
home = Path.home()
self.assertEqual(get_xdg_config_home(), home / "custom_config")
self.assertEqual(get_xdg_data_home(), home / "custom_data")
def test_xdg_paths_relative(self):
"""Test XDG paths with relative paths get resolved."""
with tempfile.TemporaryDirectory() as temp_dir:
os.chdir(temp_dir)
os.environ["XDG_CONFIG_HOME"] = "relative_config"
# Should resolve relative to current directory
result = get_xdg_config_home()
self.assertTrue(result.is_absolute())
self.assertTrue(str(result).endswith("relative_config"))
def test_llama_stack_directories_new_installation(self):
"""Test llama-stack directories for new installations (no legacy directory)."""
self.clear_env_vars()
home = Path.home()
# Mock that ~/.llama doesn't exist
with patch("llama_stack.distribution.utils.xdg_utils.Path.home") as mock_home:
mock_home.return_value = home
with patch("llama_stack.distribution.utils.xdg_utils.Path.exists") as mock_exists:
mock_exists.return_value = False
self.assertEqual(get_llama_stack_config_dir(), home / ".config" / "llama-stack")
self.assertEqual(get_llama_stack_data_dir(), home / ".local" / "share" / "llama-stack")
self.assertEqual(get_llama_stack_state_dir(), home / ".local" / "state" / "llama-stack")
self.assertEqual(get_llama_stack_cache_dir(), home / ".cache" / "llama-stack")
def test_llama_stack_directories_with_custom_xdg(self):
"""Test llama-stack directories with custom XDG paths."""
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir)
os.environ["XDG_CONFIG_HOME"] = str(temp_path / "config")
os.environ["XDG_DATA_HOME"] = str(temp_path / "data")
os.environ["XDG_STATE_HOME"] = str(temp_path / "state")
os.environ["XDG_CACHE_HOME"] = str(temp_path / "cache")
# Mock that ~/.llama doesn't exist
with patch("llama_stack.distribution.utils.xdg_utils.Path.exists") as mock_exists:
mock_exists.return_value = False
self.assertEqual(get_llama_stack_config_dir(), temp_path / "config" / "llama-stack")
self.assertEqual(get_llama_stack_data_dir(), temp_path / "data" / "llama-stack")
self.assertEqual(get_llama_stack_state_dir(), temp_path / "state" / "llama-stack")
self.assertEqual(get_llama_stack_cache_dir(), temp_path / "cache" / "llama-stack")
def test_legacy_environment_variable_precedence(self):
"""Test that LLAMA_STACK_CONFIG_DIR takes highest precedence."""
with tempfile.TemporaryDirectory() as temp_dir:
legacy_path = Path(temp_dir) / "legacy"
xdg_path = Path(temp_dir) / "xdg"
# Set both legacy and XDG variables
os.environ["LLAMA_STACK_CONFIG_DIR"] = str(legacy_path)
os.environ["XDG_CONFIG_HOME"] = str(xdg_path / "config")
os.environ["XDG_DATA_HOME"] = str(xdg_path / "data")
os.environ["XDG_STATE_HOME"] = str(xdg_path / "state")
# Legacy should take precedence for all directory types
self.assertEqual(get_llama_stack_config_dir(), legacy_path)
self.assertEqual(get_llama_stack_data_dir(), legacy_path)
self.assertEqual(get_llama_stack_state_dir(), legacy_path)
self.assertEqual(get_llama_stack_cache_dir(), legacy_path)
def test_legacy_directory_exists_and_has_content(self):
"""Test that existing ~/.llama directory with content is used."""
with tempfile.TemporaryDirectory() as temp_dir:
home = Path(temp_dir)
legacy_llama = home / ".llama"
legacy_llama.mkdir()
# Create some content to simulate existing data
(legacy_llama / "test_file").touch()
(legacy_llama / "distributions").mkdir()
# Clear environment variables
self.clear_env_vars()
with patch("llama_stack.distribution.utils.xdg_utils.Path.home") as mock_home:
mock_home.return_value = home
self.assertEqual(get_llama_stack_config_dir(), legacy_llama)
self.assertEqual(get_llama_stack_data_dir(), legacy_llama)
self.assertEqual(get_llama_stack_state_dir(), legacy_llama)
def test_legacy_directory_exists_but_empty(self):
"""Test that empty ~/.llama directory is ignored in favor of XDG."""
with tempfile.TemporaryDirectory() as temp_dir:
home = Path(temp_dir)
legacy_llama = home / ".llama"
legacy_llama.mkdir()
# Don't add any content - directory is empty
self.clear_env_vars()
with patch("llama_stack.distribution.utils.xdg_utils.Path.home") as mock_home:
mock_home.return_value = home
# Should use XDG paths since legacy directory is empty
self.assertEqual(get_llama_stack_config_dir(), home / ".config" / "llama-stack")
self.assertEqual(get_llama_stack_data_dir(), home / ".local" / "share" / "llama-stack")
self.assertEqual(get_llama_stack_state_dir(), home / ".local" / "state" / "llama-stack")
def test_xdg_compliant_path_function(self):
"""Test the get_xdg_compliant_path utility function."""
self.clear_env_vars()
home = Path.home()
# Mock that ~/.llama doesn't exist
with patch("llama_stack.distribution.utils.xdg_utils.Path.home") as mock_home:
mock_home.return_value = home
with patch("llama_stack.distribution.utils.xdg_utils.Path.exists") as mock_exists:
mock_exists.return_value = False
self.assertEqual(get_xdg_compliant_path("config"), home / ".config" / "llama-stack")
self.assertEqual(
get_xdg_compliant_path("data", "models"), home / ".local" / "share" / "llama-stack" / "models"
)
self.assertEqual(
get_xdg_compliant_path("state", "runtime"), home / ".local" / "state" / "llama-stack" / "runtime"
)
self.assertEqual(get_xdg_compliant_path("cache", "temp"), home / ".cache" / "llama-stack" / "temp")
def test_xdg_compliant_path_invalid_type(self):
"""Test that invalid path types raise ValueError."""
with self.assertRaises(ValueError) as context:
get_xdg_compliant_path("invalid_type")
self.assertIn("Unknown path type", str(context.exception))
self.assertIn("invalid_type", str(context.exception))
def test_xdg_compliant_path_with_subdirectory(self):
"""Test get_xdg_compliant_path with various subdirectories."""
self.clear_env_vars()
home = Path.home()
with patch("llama_stack.distribution.utils.xdg_utils.Path.home") as mock_home:
mock_home.return_value = home
with patch("llama_stack.distribution.utils.xdg_utils.Path.exists") as mock_exists:
mock_exists.return_value = False
# Test nested subdirectories
self.assertEqual(
get_xdg_compliant_path("data", "models/checkpoints"),
home / ".local" / "share" / "llama-stack" / "models/checkpoints",
)
# Test with Path object
self.assertEqual(
get_xdg_compliant_path("config", str(Path("distributions") / "ollama")),
home / ".config" / "llama-stack" / "distributions" / "ollama",
)
def test_ensure_directory_exists(self):
"""Test the ensure_directory_exists utility function."""
with tempfile.TemporaryDirectory() as temp_dir:
test_path = Path(temp_dir) / "nested" / "directory" / "structure"
# Directory shouldn't exist initially
self.assertFalse(test_path.exists())
# Create it
ensure_directory_exists(test_path)
# Should exist now
self.assertTrue(test_path.exists())
self.assertTrue(test_path.is_dir())
def test_ensure_directory_exists_already_exists(self):
"""Test ensure_directory_exists when directory already exists."""
with tempfile.TemporaryDirectory() as temp_dir:
test_path = Path(temp_dir) / "existing"
test_path.mkdir()
# Should not raise an error
ensure_directory_exists(test_path)
self.assertTrue(test_path.exists())
def test_config_dirs_import_and_types(self):
"""Test that the config_dirs module imports correctly and has proper types."""
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
self.assertIsInstance(LLAMA_STACK_CONFIG_DIR, Path)
self.assertIsInstance(DEFAULT_CHECKPOINT_DIR, Path)
self.assertIsInstance(RUNTIME_BASE_DIR, Path)
self.assertIsInstance(EXTERNAL_PROVIDERS_DIR, Path)
self.assertIsInstance(DISTRIBS_BASE_DIR, Path)
# All should be absolute paths
self.assertTrue(LLAMA_STACK_CONFIG_DIR.is_absolute())
self.assertTrue(DEFAULT_CHECKPOINT_DIR.is_absolute())
self.assertTrue(RUNTIME_BASE_DIR.is_absolute())
self.assertTrue(EXTERNAL_PROVIDERS_DIR.is_absolute())
self.assertTrue(DISTRIBS_BASE_DIR.is_absolute())
def test_config_dirs_proper_structure(self):
"""Test that config_dirs uses proper XDG structure."""
from llama_stack.distribution.utils.config_dirs import (
DISTRIBS_BASE_DIR,
EXTERNAL_PROVIDERS_DIR,
LLAMA_STACK_CONFIG_DIR,
)
# Check that paths contain expected components
config_str = str(LLAMA_STACK_CONFIG_DIR)
self.assertTrue(
"llama-stack" in config_str or ".llama" in config_str,
f"Config dir should contain 'llama-stack' or '.llama': {config_str}",
)
# Test relationships between directories
self.assertEqual(DISTRIBS_BASE_DIR, LLAMA_STACK_CONFIG_DIR / "distributions")
self.assertEqual(EXTERNAL_PROVIDERS_DIR, LLAMA_STACK_CONFIG_DIR / "providers.d")
def test_environment_variable_combinations(self):
"""Test various combinations of environment variables."""
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir)
# Test partial XDG variables
os.environ["XDG_CONFIG_HOME"] = str(temp_path / "config")
# Leave others as default
self.clear_env_vars(["XDG_DATA_HOME", "XDG_STATE_HOME", "XDG_CACHE_HOME"])
home = Path.home()
with patch("llama_stack.distribution.utils.xdg_utils.Path.exists") as mock_exists:
mock_exists.return_value = False
self.assertEqual(get_llama_stack_config_dir(), temp_path / "config" / "llama-stack")
self.assertEqual(get_llama_stack_data_dir(), home / ".local" / "share" / "llama-stack")
self.assertEqual(get_llama_stack_state_dir(), home / ".local" / "state" / "llama-stack")
def test_migrate_legacy_directory_no_legacy(self):
"""Test migration when no legacy directory exists."""
with patch("llama_stack.distribution.utils.xdg_utils.Path.home") as mock_home:
mock_home.return_value = Path("/fake/home")
with patch("llama_stack.distribution.utils.xdg_utils.Path.exists") as mock_exists:
mock_exists.return_value = False
# Should return True (success) when no migration needed
result = migrate_legacy_directory()
self.assertTrue(result)
def test_migrate_legacy_directory_exists(self):
"""Test migration message when legacy directory exists."""
with tempfile.TemporaryDirectory() as temp_dir:
home = Path(temp_dir)
legacy_llama = home / ".llama"
legacy_llama.mkdir()
(legacy_llama / "test_file").touch()
with patch("llama_stack.distribution.utils.xdg_utils.Path.home") as mock_home:
mock_home.return_value = home
with patch("builtins.print") as mock_print:
result = migrate_legacy_directory()
self.assertTrue(result)
# Check that migration information was printed
print_calls = [call[0][0] for call in mock_print.call_args_list]
self.assertTrue(any("Found legacy directory" in call for call in print_calls))
self.assertTrue(any("Consider migrating" in call for call in print_calls))
def test_path_consistency_across_functions(self):
"""Test that all path functions return consistent results."""
self.clear_env_vars()
with tempfile.TemporaryDirectory() as temp_dir:
home = Path(temp_dir)
with patch("llama_stack.distribution.utils.xdg_utils.Path.home") as mock_home:
mock_home.return_value = home
with patch("llama_stack.distribution.utils.xdg_utils.Path.exists") as mock_exists:
mock_exists.return_value = False
# All config-related functions should return the same base
config_dir = get_llama_stack_config_dir()
config_path = get_xdg_compliant_path("config")
self.assertEqual(config_dir, config_path)
# All data-related functions should return the same base
data_dir = get_llama_stack_data_dir()
data_path = get_xdg_compliant_path("data")
self.assertEqual(data_dir, data_path)
def test_unicode_and_special_characters(self):
"""Test XDG paths with unicode and special characters."""
with tempfile.TemporaryDirectory() as temp_dir:
# Test with unicode characters
unicode_path = Path(temp_dir) / "配置" / "llama-stack"
os.environ["XDG_CONFIG_HOME"] = str(unicode_path.parent)
result = get_xdg_config_home()
self.assertEqual(result, unicode_path.parent)
# Test spaces in paths
space_path = Path(temp_dir) / "my config"
os.environ["XDG_CONFIG_HOME"] = str(space_path)
result = get_xdg_config_home()
self.assertEqual(result, space_path)
def test_concurrent_access_safety(self):
"""Test that XDG functions are safe for concurrent access."""
import threading
import time
results = []
errors = []
def worker():
try:
# Simulate concurrent access
config_dir = get_llama_stack_config_dir()
time.sleep(0.01) # Small delay to increase chance of race conditions
data_dir = get_llama_stack_data_dir()
results.append((config_dir, data_dir))
except Exception as e:
errors.append(e)
# Start multiple threads
threads = []
for _ in range(10):
t = threading.Thread(target=worker)
threads.append(t)
t.start()
# Wait for all threads
for t in threads:
t.join()
# Check results
self.assertEqual(len(errors), 0, f"Concurrent access errors: {errors}")
self.assertEqual(len(results), 10)
# All results should be identical
first_result = results[0]
for result in results[1:]:
self.assertEqual(result, first_result)
def test_symlink_handling(self):
"""Test XDG path handling with symbolic links."""
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir)
# Create actual directory
actual_dir = temp_path / "actual_config"
actual_dir.mkdir()
# Create symlink
symlink_dir = temp_path / "symlinked_config"
symlink_dir.symlink_to(actual_dir)
os.environ["XDG_CONFIG_HOME"] = str(symlink_dir)
result = get_xdg_config_home()
self.assertEqual(result, symlink_dir)
# Should resolve to actual path when needed
self.assertTrue(result.exists())
def test_readonly_directory_handling(self):
"""Test behavior when XDG directories are read-only."""
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir)
readonly_dir = temp_path / "readonly"
readonly_dir.mkdir()
# Make directory read-only
readonly_dir.chmod(0o444)
try:
os.environ["XDG_CONFIG_HOME"] = str(readonly_dir)
# Should still return the path even if read-only
result = get_xdg_config_home()
self.assertEqual(result, readonly_dir)
finally:
# Restore permissions for cleanup
readonly_dir.chmod(0o755)
def test_nonexistent_parent_directory(self):
"""Test XDG paths with non-existent parent directories."""
with tempfile.TemporaryDirectory() as temp_dir:
# Use a path with non-existent parents
nonexistent_path = Path(temp_dir) / "does" / "not" / "exist" / "config"
os.environ["XDG_CONFIG_HOME"] = str(nonexistent_path)
# Should return the path even if it doesn't exist
result = get_xdg_config_home()
self.assertEqual(result, nonexistent_path)
def test_env_var_expansion(self):
"""Test environment variable expansion in XDG paths."""
with tempfile.TemporaryDirectory() as temp_dir:
os.environ["TEST_BASE"] = temp_dir
os.environ["XDG_CONFIG_HOME"] = "$TEST_BASE/config"
# Path expansion should work
result = get_xdg_config_home()
expected = Path(temp_dir) / "config"
self.assertEqual(result, expected)
class TestXDGEdgeCases(unittest.TestCase):
"""Test edge cases and error conditions for XDG compliance."""
def setUp(self):
"""Set up test environment."""
self.original_env = {}
for key in ["XDG_CONFIG_HOME", "XDG_DATA_HOME", "XDG_CACHE_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
def test_empty_environment_variables(self):
"""Test behavior with empty environment variables."""
# Set empty values
os.environ["XDG_CONFIG_HOME"] = ""
os.environ["XDG_DATA_HOME"] = ""
# Should fall back to defaults
home = Path.home()
self.assertEqual(get_xdg_config_home(), home / ".config")
self.assertEqual(get_xdg_data_home(), home / ".local" / "share")
def test_whitespace_only_environment_variables(self):
"""Test behavior with whitespace-only environment variables."""
os.environ["XDG_CONFIG_HOME"] = " "
os.environ["XDG_DATA_HOME"] = "\t\n"
# Should handle whitespace gracefully
result_config = get_xdg_config_home()
result_data = get_xdg_data_home()
# Results should be valid Path objects
self.assertIsInstance(result_config, Path)
self.assertIsInstance(result_data, Path)
def test_very_long_paths(self):
"""Test behavior with very long directory paths."""
with tempfile.TemporaryDirectory() as temp_dir:
# Create a very long path
long_path_parts = ["very_long_directory_name_" + str(i) for i in range(20)]
long_path = Path(temp_dir)
for part in long_path_parts:
long_path = long_path / part
os.environ["XDG_CONFIG_HOME"] = str(long_path)
result = get_xdg_config_home()
self.assertEqual(result, long_path)
def test_circular_symlinks(self):
"""Test handling of circular symbolic links."""
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir)
# Create circular symlinks
link1 = temp_path / "link1"
link2 = temp_path / "link2"
try:
link1.symlink_to(link2)
link2.symlink_to(link1)
os.environ["XDG_CONFIG_HOME"] = str(link1)
# Should handle circular symlinks gracefully
result = get_xdg_config_home()
self.assertEqual(result, link1)
except (OSError, NotImplementedError):
# Some systems don't support circular symlinks
self.skipTest("System doesn't support circular symlinks")
def test_permission_denied_scenarios(self):
"""Test scenarios where permission is denied."""
# This test is platform-specific and may not work on all systems
try:
# Try to use a system directory that typically requires root
os.environ["XDG_CONFIG_HOME"] = "/root/.config"
# Should still return the path even if we can't access it
result = get_xdg_config_home()
self.assertEqual(result, Path("/root/.config"))
except Exception:
# If this fails, it's not critical for the XDG implementation
pass
def test_network_paths(self):
"""Test XDG paths with network/UNC paths (Windows-style)."""
# Test UNC path (though this may not work on non-Windows systems)
network_path = "//server/share/config"
os.environ["XDG_CONFIG_HOME"] = network_path
result = get_xdg_config_home()
# Should handle network paths gracefully
self.assertIsInstance(result, Path)
if __name__ == "__main__":
unittest.main()

View file

@ -0,0 +1,522 @@
# 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 platform
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
from llama_stack.distribution.utils.xdg_utils import (
ensure_directory_exists,
get_llama_stack_config_dir,
get_xdg_config_home,
migrate_legacy_directory,
)
class TestXDGEdgeCases(unittest.TestCase):
"""Test edge cases and error conditions for XDG compliance."""
def setUp(self):
"""Set up test environment."""
self.original_env = {}
for key in ["XDG_CONFIG_HOME", "XDG_DATA_HOME", "XDG_STATE_HOME", "XDG_CACHE_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
def clear_env_vars(self):
"""Clear all XDG environment variables."""
for key in self.original_env:
os.environ.pop(key, None)
def test_very_long_paths(self):
"""Test XDG functions with very long directory paths."""
self.clear_env_vars()
# Create a very long path (close to filesystem limits)
long_components = ["very_long_directory_name_" + str(i) for i in range(50)]
long_path = "/tmp/" + "/".join(long_components)
# Test with very long XDG paths
os.environ["XDG_CONFIG_HOME"] = long_path
result = get_xdg_config_home()
self.assertEqual(result, Path(long_path))
# Should handle long paths in llama-stack functions
with patch("llama_stack.distribution.utils.xdg_utils.Path.exists") as mock_exists:
mock_exists.return_value = False
config_dir = get_llama_stack_config_dir()
self.assertEqual(config_dir, Path(long_path) / "llama-stack")
def test_paths_with_special_characters(self):
"""Test XDG functions with special characters in paths."""
self.clear_env_vars()
# Test various special characters
special_chars = [
"path with spaces",
"path-with-hyphens",
"path_with_underscores",
"path.with.dots",
"path@with@symbols",
"path+with+plus",
"path&with&ampersand",
"path(with)parentheses",
]
for special_path in special_chars:
with self.subTest(path=special_path):
test_path = f"/tmp/{special_path}"
os.environ["XDG_CONFIG_HOME"] = test_path
result = get_xdg_config_home()
self.assertEqual(result, Path(test_path))
def test_unicode_paths(self):
"""Test XDG functions with unicode characters in paths."""
self.clear_env_vars()
unicode_paths = [
"/配置/llama-stack", # Chinese
"/конфигурация/llama-stack", # Russian
"/構成/llama-stack", # Japanese
"/구성/llama-stack", # Korean
"/تكوين/llama-stack", # Arabic
"/configuración/llama-stack", # Spanish with accents
"/配置📁/llama-stack", # With emoji
]
for unicode_path in unicode_paths:
with self.subTest(path=unicode_path):
os.environ["XDG_CONFIG_HOME"] = unicode_path
result = get_xdg_config_home()
self.assertEqual(result, Path(unicode_path))
def test_network_paths(self):
"""Test XDG functions with network/UNC paths."""
self.clear_env_vars()
if platform.system() == "Windows":
# Test Windows UNC paths
unc_paths = [
"\\\\server\\share\\config",
"\\\\server.domain.com\\share\\config",
"\\\\192.168.1.100\\config",
]
for unc_path in unc_paths:
with self.subTest(path=unc_path):
os.environ["XDG_CONFIG_HOME"] = unc_path
result = get_xdg_config_home()
self.assertEqual(result, Path(unc_path))
else:
# Test network mount paths on Unix-like systems
network_paths = [
"/mnt/nfs/config",
"/net/server/config",
"/media/network/config",
]
for network_path in network_paths:
with self.subTest(path=network_path):
os.environ["XDG_CONFIG_HOME"] = network_path
result = get_xdg_config_home()
self.assertEqual(result, Path(network_path))
def test_nonexistent_paths(self):
"""Test XDG functions with non-existent paths."""
self.clear_env_vars()
nonexistent_path = "/this/path/does/not/exist/config"
os.environ["XDG_CONFIG_HOME"] = nonexistent_path
# Should return the path even if it doesn't exist
result = get_xdg_config_home()
self.assertEqual(result, Path(nonexistent_path))
# Should work with llama-stack functions too
with patch("llama_stack.distribution.utils.xdg_utils.Path.exists") as mock_exists:
mock_exists.return_value = False
config_dir = get_llama_stack_config_dir()
self.assertEqual(config_dir, Path(nonexistent_path) / "llama-stack")
def test_circular_symlinks(self):
"""Test XDG functions with circular symbolic links."""
self.clear_env_vars()
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir)
# Create circular symlinks
link1 = temp_path / "link1"
link2 = temp_path / "link2"
try:
link1.symlink_to(link2)
link2.symlink_to(link1)
os.environ["XDG_CONFIG_HOME"] = str(link1)
# Should handle circular symlinks gracefully
result = get_xdg_config_home()
self.assertEqual(result, link1)
except (OSError, NotImplementedError):
# Some systems don't support circular symlinks
self.skipTest("System doesn't support circular symlinks")
def test_broken_symlinks(self):
"""Test XDG functions with broken symbolic links."""
self.clear_env_vars()
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir)
# Create broken symlink
target = temp_path / "nonexistent_target"
link = temp_path / "broken_link"
try:
link.symlink_to(target)
os.environ["XDG_CONFIG_HOME"] = str(link)
# Should handle broken symlinks gracefully
result = get_xdg_config_home()
self.assertEqual(result, link)
except (OSError, NotImplementedError):
# Some systems might not support this
self.skipTest("System doesn't support broken symlinks")
def test_readonly_directories(self):
"""Test XDG functions with read-only directories."""
self.clear_env_vars()
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir)
readonly_dir = temp_path / "readonly"
readonly_dir.mkdir()
# Make directory read-only
readonly_dir.chmod(0o444)
try:
os.environ["XDG_CONFIG_HOME"] = str(readonly_dir)
# Should still return the path
result = get_xdg_config_home()
self.assertEqual(result, readonly_dir)
finally:
# Restore permissions for cleanup
readonly_dir.chmod(0o755)
def test_permission_denied_access(self):
"""Test XDG functions when permission is denied."""
self.clear_env_vars()
# This test is platform-specific
if platform.system() != "Windows":
# Try to use a system directory that typically requires root
restricted_paths = [
"/root/.config",
"/etc/config",
"/var/root/config",
]
for restricted_path in restricted_paths:
with self.subTest(path=restricted_path):
os.environ["XDG_CONFIG_HOME"] = restricted_path
# Should still return the path even if we can't access it
result = get_xdg_config_home()
self.assertEqual(result, Path(restricted_path))
def test_environment_variable_injection(self):
"""Test XDG functions with environment variable injection attempts."""
self.clear_env_vars()
# Test potential injection attempts
injection_attempts = [
"/tmp/config; rm -rf /",
"/tmp/config && echo 'injected'",
"/tmp/config | cat /etc/passwd",
"/tmp/config`whoami`",
"/tmp/config$(whoami)",
"/tmp/config\necho 'newline'",
]
for injection_attempt in injection_attempts:
with self.subTest(attempt=injection_attempt):
os.environ["XDG_CONFIG_HOME"] = injection_attempt
# Should treat as literal path, not execute
result = get_xdg_config_home()
self.assertEqual(result, Path(injection_attempt))
def test_extremely_nested_paths(self):
"""Test XDG functions with extremely nested directory structures."""
self.clear_env_vars()
# Create deeply nested path
nested_parts = ["level" + str(i) for i in range(100)]
nested_path = "/tmp/" + "/".join(nested_parts)
os.environ["XDG_CONFIG_HOME"] = nested_path
result = get_xdg_config_home()
self.assertEqual(result, Path(nested_path))
def test_empty_and_whitespace_paths(self):
"""Test XDG functions with empty and whitespace-only paths."""
self.clear_env_vars()
empty_values = [
"",
" ",
"\t",
"\n",
"\r\n",
" \t \n ",
]
for empty_value in empty_values:
with self.subTest(value=repr(empty_value)):
os.environ["XDG_CONFIG_HOME"] = empty_value
# Should fall back to default
result = get_xdg_config_home()
self.assertEqual(result, Path.home() / ".config")
def test_path_with_null_bytes(self):
"""Test XDG functions with null bytes in paths."""
self.clear_env_vars()
# Test path with null byte
null_path = "/tmp/config\x00/test"
os.environ["XDG_CONFIG_HOME"] = null_path
# Should handle null bytes (Path will likely raise an error, which is expected)
try:
result = get_xdg_config_home()
# If it doesn't raise an error, check the result
self.assertIsInstance(result, Path)
except (ValueError, OSError):
# This is expected behavior for null bytes
pass
def test_concurrent_access_safety(self):
"""Test that XDG functions are thread-safe."""
import threading
import time
self.clear_env_vars()
results = []
errors = []
def worker(thread_id):
try:
# Each thread sets a different XDG path
os.environ["XDG_CONFIG_HOME"] = f"/tmp/thread_{thread_id}"
# Small delay to increase chance of race conditions
time.sleep(0.01)
config_dir = get_llama_stack_config_dir()
results.append((thread_id, config_dir))
except Exception as e:
errors.append((thread_id, e))
# Start multiple threads
threads = []
for i in range(20):
t = threading.Thread(target=worker, args=(i,))
threads.append(t)
t.start()
# Wait for all threads
for t in threads:
t.join()
# Check for errors
if errors:
self.fail(f"Thread errors: {errors}")
# Check that we got results from all threads
self.assertEqual(len(results), 20)
def test_filesystem_limits(self):
"""Test XDG functions approaching filesystem limits."""
self.clear_env_vars()
# Test with very long filename (close to 255 char limit)
long_filename = "a" * 240
long_path = f"/tmp/{long_filename}"
os.environ["XDG_CONFIG_HOME"] = long_path
result = get_xdg_config_home()
self.assertEqual(result, Path(long_path))
def test_case_sensitivity(self):
"""Test XDG functions with case sensitivity edge cases."""
self.clear_env_vars()
# Test case variations
case_variations = [
"/tmp/Config",
"/tmp/CONFIG",
"/tmp/config",
"/tmp/Config/MixedCase",
]
for case_path in case_variations:
with self.subTest(path=case_path):
os.environ["XDG_CONFIG_HOME"] = case_path
result = get_xdg_config_home()
self.assertEqual(result, Path(case_path))
def test_ensure_directory_exists_edge_cases(self):
"""Test ensure_directory_exists with edge cases."""
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir)
# Test with file that exists but is not a directory
file_path = temp_path / "file_not_dir"
file_path.touch()
with self.assertRaises(FileExistsError):
ensure_directory_exists(file_path)
# Test with permission denied
if platform.system() != "Windows":
readonly_parent = temp_path / "readonly_parent"
readonly_parent.mkdir()
readonly_parent.chmod(0o444)
try:
nested_path = readonly_parent / "nested"
with self.assertRaises(PermissionError):
ensure_directory_exists(nested_path)
finally:
# Restore permissions for cleanup
readonly_parent.chmod(0o755)
def test_migrate_legacy_directory_edge_cases(self):
"""Test migrate_legacy_directory with edge cases."""
with tempfile.TemporaryDirectory() as temp_dir:
home_dir = Path(temp_dir)
with patch("llama_stack.distribution.utils.xdg_utils.Path.home") as mock_home:
mock_home.return_value = home_dir
# Test with legacy directory but no write permissions
legacy_dir = home_dir / ".llama"
legacy_dir.mkdir()
(legacy_dir / "test_file").touch()
# Make home directory read-only
home_dir.chmod(0o444)
try:
# Should handle permission errors gracefully
with patch("builtins.print") as mock_print:
migrate_legacy_directory()
# Should print some information
self.assertTrue(mock_print.called)
finally:
# Restore permissions for cleanup
home_dir.chmod(0o755)
legacy_dir.chmod(0o755)
def test_path_traversal_attempts(self):
"""Test XDG functions with path traversal attempts."""
self.clear_env_vars()
traversal_attempts = [
"/tmp/config/../../../etc/passwd",
"/tmp/config/../../root/.ssh",
"/tmp/config/../../../../../etc/shadow",
"/tmp/config/./../../root",
]
for traversal_attempt in traversal_attempts:
with self.subTest(attempt=traversal_attempt):
os.environ["XDG_CONFIG_HOME"] = traversal_attempt
# Should handle path traversal attempts by treating as literal paths
result = get_xdg_config_home()
self.assertEqual(result, Path(traversal_attempt))
def test_environment_variable_precedence_edge_cases(self):
"""Test environment variable precedence with edge cases."""
self.clear_env_vars()
# Test with both old and new env vars set
os.environ["LLAMA_STACK_CONFIG_DIR"] = "/legacy/path"
os.environ["XDG_CONFIG_HOME"] = "/xdg/path"
# Create fake legacy directory
with tempfile.TemporaryDirectory() as temp_dir:
fake_home = Path(temp_dir)
fake_legacy = fake_home / ".llama"
fake_legacy.mkdir()
(fake_legacy / "test_file").touch()
with patch("llama_stack.distribution.utils.xdg_utils.Path.home") as mock_home:
mock_home.return_value = fake_home
# LLAMA_STACK_CONFIG_DIR should take precedence
config_dir = get_llama_stack_config_dir()
self.assertEqual(config_dir, Path("/legacy/path"))
def test_malformed_environment_variables(self):
"""Test XDG functions with malformed environment variables."""
self.clear_env_vars()
malformed_values = [
"not_an_absolute_path",
"~/tilde_not_expanded",
"$HOME/variable_not_expanded",
"relative/path/config",
"./relative/path",
"../parent/path",
]
for malformed_value in malformed_values:
with self.subTest(value=malformed_value):
os.environ["XDG_CONFIG_HOME"] = malformed_value
# Should handle malformed values gracefully
result = get_xdg_config_home()
self.assertIsInstance(result, Path)
if __name__ == "__main__":
unittest.main()