test_PR349 / tests /test_profile_paths.py
alozowski's picture
alozowski HF Staff
Sync from GitHub via hub-sync
8c424b3 verified
import shutil
import zipfile
import subprocess
from pathlib import Path, PurePosixPath
import pytest
import reachy_mini_conversation_app.config as config_mod
import reachy_mini_conversation_app.prompts as prompts_mod
import reachy_mini_conversation_app.headless_personality as headless_mod
from reachy_mini_conversation_app.config import DEFAULT_PROFILES_DIRECTORY, config
from reachy_mini_conversation_app.gradio_personality import PersonalityUI
from reachy_mini_conversation_app.headless_personality import (
DEFAULT_OPTION,
read_tools_for,
resolve_profile_dir,
read_instructions_for,
)
# Path characters budget computation
# ─────────────────
# Windows MAX_PATH limit: 259 usable characters (failures start at 260)
#
# Project files (WINDOWS_PATH_BUDGET = 130):
# C:\Users\<username(20)>
# \.cache\huggingface\hub
# \spaces--pollen-robotics--reachy_mini_conversation_app
# \snapshots\<commit_hash(40)>\
# = 158 characters => 101 remaining to 259.
# The project root folder is not cloned in the snapshot, so we add it
# back to the budget: 101 + len("reachy_mini_conversation_app\") (29) = 130.
#
# Wheel files (WINDOWS_WHEEL_PATH_BUDGET = 71):
# C:\Users\<username(20)>
# \.cache\huggingface\hub
# \spaces--pollen-robotics--reachy_mini_conversation_app
# \snapshots\<commit_hash(40)>
# \build\bdist.win-amd64\wheel\
# = 186 characters => 73 remaining to 259.
# In practice the copy fails at 257 because of an intermediate \.\
# folder, bringing the real budget down to 71.
WINDOWS_PATH_BUDGET = 130
WINDOWS_WHEEL_PATH_BUDGET = 71
def _git_tracked_files(project_root: Path) -> list[Path]:
"""Return git-tracked files that still exist in the working tree."""
try:
result = subprocess.run(
["git", "ls-files"],
cwd=project_root,
check=True,
capture_output=True,
text=True,
)
except (OSError, subprocess.CalledProcessError) as exc:
pytest.skip(f"git-tracked file listing unavailable: {exc}")
tracked_files = [project_root / relative_path for relative_path in result.stdout.splitlines() if relative_path]
return [path for path in tracked_files if path.is_file()]
def test_profile_name_resolves_directly_to_storage_dir() -> None:
"""Built-in profile names should map directly to their on-disk directory."""
profile_dir = resolve_profile_dir("mad_scientist_assistant")
assert profile_dir.name == "mad_scientist_assistant"
assert (profile_dir / "instructions.txt").is_file()
def test_prompts_load_from_compact_builtin_profile(monkeypatch: pytest.MonkeyPatch) -> None:
"""Prompt loading should read compact built-in profile instructions directly."""
monkeypatch.setattr(config, "REACHY_MINI_CUSTOM_PROFILE", "mad_scientist_assistant")
monkeypatch.setattr(config, "PROFILES_DIRECTORY", DEFAULT_PROFILES_DIRECTORY)
expected = (
(DEFAULT_PROFILES_DIRECTORY / "mad_scientist_assistant" / "instructions.txt")
.read_text(encoding="utf-8")
.strip()
)
assert prompts_mod.get_session_instructions() == expected
assert read_instructions_for("mad_scientist_assistant") == expected
def test_builtin_default_profile_tools_load_for_ui() -> None:
"""The UI should read built-in default tools from the packaged default profile."""
expected = (DEFAULT_PROFILES_DIRECTORY / "default" / "tools.txt").read_text(encoding="utf-8")
assert read_tools_for(DEFAULT_OPTION) == expected
def test_gradio_personality_ui_prefills_builtin_default_tools(monkeypatch: pytest.MonkeyPatch) -> None:
"""Gradio should show the built-in default profile tools on first render."""
monkeypatch.setattr(config, "REACHY_MINI_CUSTOM_PROFILE", None)
ui = PersonalityUI()
ui.create_components()
expected_tools = read_tools_for(ui.DEFAULT_OPTION)
expected_enabled = [
line.strip() for line in expected_tools.splitlines() if line.strip() and not line.strip().startswith("#")
]
assert ui.tools_txt_ta.value == expected_tools
assert sorted(ui.available_tools_cg.value) == sorted(expected_enabled)
def test_session_voice_defaults_follow_selected_backend(monkeypatch: pytest.MonkeyPatch) -> None:
"""Session voice should fall back to the active backend default."""
monkeypatch.setattr(config, "BACKEND_PROVIDER", "gemini")
monkeypatch.setattr(config, "MODEL_NAME", "gemini-3.1-flash-live-preview")
monkeypatch.setattr(config, "REACHY_MINI_CUSTOM_PROFILE", None)
assert prompts_mod.get_session_voice() == "Kore"
def test_headless_profile_write_defaults_voice_at_call_time(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""New headless profiles should use the currently selected backend default voice."""
monkeypatch.setattr(config, "BACKEND_PROVIDER", "gemini")
monkeypatch.setattr(config, "MODEL_NAME", "gemini-3.1-flash-live-preview")
monkeypatch.setattr(headless_mod, "_profiles_root", lambda: tmp_path)
headless_mod._write_profile("runtime_voice_default", "test instructions", "")
voice_file = tmp_path / "user_personalities" / "runtime_voice_default" / "voice.txt"
assert voice_file.read_text(encoding="utf-8") == "Kore\n"
def test_packaged_profiles_win_outside_source_checkout(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""Installed builds should use packaged profiles, not an unrelated sibling folder."""
unrelated_profiles = tmp_path / "profiles"
unrelated_profiles.mkdir()
packaged_profiles = tmp_path / "package_data" / "profiles"
packaged_profiles.mkdir(parents=True)
monkeypatch.setattr(config_mod, "PROJECT_ROOT", tmp_path)
monkeypatch.setattr(config_mod, "_packaged_profiles_directory", lambda: packaged_profiles)
assert config_mod._resolve_default_profiles_directory() == packaged_profiles
def test_project_file_paths_stay_within_windows_budget() -> None:
"""Git-tracked project file paths should stay below the agreed Windows budget."""
project_root = Path(__file__).parents[1].resolve()
project_files = _git_tracked_files(project_root)
violations = []
for path in project_files:
relative = str(Path(project_root.name) / path.relative_to(project_root))
length = len(relative)
if length > WINDOWS_PATH_BUDGET:
violations.append(
f"Windows path budget exceeded ({WINDOWS_PATH_BUDGET}): {relative} is {length} characters long"
)
assert not violations, "\n".join(violations)
def test_wheel_file_paths_stay_within_windows_budget(tmp_path: Path) -> None:
"""Built wheel paths should stay below the agreed Windows budget."""
project_root = Path(__file__).parents[1].resolve()
source_checkout = tmp_path / "checkout"
dist_dir = tmp_path / "dist"
for source_file in _git_tracked_files(project_root):
target_file = source_checkout / source_file.relative_to(project_root)
target_file.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(source_file, target_file)
try:
subprocess.run(
["uv", "build", "--wheel", "--out-dir", str(dist_dir)],
cwd=source_checkout,
check=True,
capture_output=True,
text=True,
)
except (OSError, subprocess.CalledProcessError) as exc:
details = exc.stderr if isinstance(exc, subprocess.CalledProcessError) and exc.stderr else str(exc)
pytest.fail(f"Wheel build failed while checking Windows path budget: {details}")
wheel_files = list(dist_dir.glob("*.whl"))
assert len(wheel_files) == 1, f"Expected exactly one built wheel in {dist_dir}, found: {wheel_files}"
with zipfile.ZipFile(wheel_files[0]) as archive:
archived_paths = [PurePosixPath(info.filename) for info in archive.infolist() if not info.is_dir()]
violations = []
for path in archived_paths:
length = len(path.as_posix())
if length > WINDOWS_WHEEL_PATH_BUDGET:
violations.append(
f"Windows wheel path budget exceeded ({WINDOWS_WHEEL_PATH_BUDGET}): "
f"{path.as_posix()} is {length} characters long"
)
assert not violations, "\n".join(violations)