diff --git a/src/paths.py b/src/paths.py index 416ca9ed..e2e4ff8f 100644 --- a/src/paths.py +++ b/src/paths.py @@ -83,8 +83,9 @@ def get_global_presets_path() -> Path: """ [C: src/presets.py:PresetManager.__init__, src/presets.py:PresetManager.delete_preset, src/presets.py:PresetManager.get_preset_scope] """ - root_dir = Path(__file__).resolve().parent.parent - return Path(os.environ.get("SLOP_GLOBAL_PRESETS", root_dir / "presets.toml")) + if "presets" not in _RESOLVED: + _RESOLVED["presets"] = _resolve_path("SLOP_GLOBAL_PRESETS", "presets", "presets.toml") + return _RESOLVED["presets"] def get_project_presets_path(project_root: Path) -> Path: """ @@ -96,8 +97,9 @@ def get_global_tool_presets_path() -> Path: """ [C: src/tool_presets.py:ToolPresetManager._get_path, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets] """ - root_dir = Path(__file__).resolve().parent.parent - return Path(os.environ.get("SLOP_GLOBAL_TOOL_PRESETS", root_dir / "tool_presets.toml")) + if "tool_presets" not in _RESOLVED: + _RESOLVED["tool_presets"] = _resolve_path("SLOP_GLOBAL_TOOL_PRESETS", "tool_presets", "tool_presets.toml") + return _RESOLVED["tool_presets"] def get_project_tool_presets_path(project_root: Path) -> Path: """ @@ -109,8 +111,9 @@ def get_global_personas_path() -> Path: """ [C: src/personas.py:PersonaManager._get_path, src/personas.py:PersonaManager.get_persona_scope, src/personas.py:PersonaManager.load_all] """ - root_dir = Path(__file__).resolve().parent.parent - return Path(os.environ.get("SLOP_GLOBAL_PERSONAS", root_dir / "personas.toml")) + if "personas" not in _RESOLVED: + _RESOLVED["personas"] = _resolve_path("SLOP_GLOBAL_PERSONAS", "personas", "personas.toml") + return _RESOLVED["personas"] def get_project_personas_path(project_root: Path) -> Path: """ @@ -122,8 +125,9 @@ def get_global_themes_path() -> Path: """ [C: src/theme_2.py:load_themes_from_disk] """ - root_dir = Path(__file__).resolve().parent.parent - return Path(os.environ.get("SLOP_GLOBAL_THEMES", root_dir / "themes")) + if "themes" not in _RESOLVED: + _RESOLVED["themes"] = _resolve_path("SLOP_GLOBAL_THEMES", "themes", "themes") + return _RESOLVED["themes"] def get_project_themes_path(project_root: Path) -> Path: """ @@ -135,8 +139,9 @@ def get_global_workspace_profiles_path() -> Path: """ [C: src/workspace_manager.py:WorkspaceManager._get_path, src/workspace_manager.py:WorkspaceManager.load_all_profiles] """ - root_dir = Path(__file__).resolve().parent.parent - return Path(os.environ.get("SLOP_GLOBAL_WORKSPACE_PROFILES", root_dir / "workspace_profiles.toml")) + if "workspace_profiles" not in _RESOLVED: + _RESOLVED["workspace_profiles"] = _resolve_path("SLOP_GLOBAL_WORKSPACE_PROFILES", "workspace_profiles", "workspace_profiles.toml") + return _RESOLVED["workspace_profiles"] def get_project_workspace_profiles_path(project_root: Path) -> Path: """ @@ -148,8 +153,9 @@ def get_credentials_path() -> Path: """ [C: src/mcp_client.py:_is_allowed] """ - root_dir = Path(__file__).resolve().parent.parent - return Path(os.environ.get("SLOP_CREDENTIALS", str(root_dir / "credentials.toml"))) + if "credentials" not in _RESOLVED: + _RESOLVED["credentials"] = _resolve_path("SLOP_CREDENTIALS", "credentials", "credentials.toml") + return _RESOLVED["credentials"] def _resolve_path(env_var: str, config_key: str, default: str) -> Path: root_dir = Path(__file__).resolve().parent.parent diff --git a/tests/conftest.py b/tests/conftest.py index 5dc36326..75e622fc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -376,12 +376,17 @@ def _enforce_test_sandbox() -> Generator[None, None, None]: def isolate_workspace(monkeypatch) -> Generator[None, None, None]: """ Autouse fixture to isolate tests from the active user workspace. - Workspace lives under tests/artifacts/_isolation_workspace_/ per - conductor/code_styleguides/workspace_paths.md (replaces the historical - tmp_path_factory.mktemp pattern). Writes placeholder TOMLs so src/ code - that reads these files at startup does not crash. - [C: tests/conftest.py:_ISOLATION_WORKSPACE, src/paths.py:set_config_override] + Writes config_overrides.toml with a [paths] section that overrides every + path getter in src/paths.py to point inside this test's workspace + (tests/artifacts/_isolation_workspace_/). Also writes placeholder + TOML files for the redirected paths. NO SLOP_* env vars are set; + src/paths.py reads the overrides from config.toml [paths] (with env var + as fallback if needed). + [C: tests/conftest.py:_ISOLATION_WORKSPACE, src/paths.py:_resolve_path] """ + from src import paths as _paths + _paths.reset_resolved() + test_workspace = _ISOLATION_WORKSPACE config_path = test_workspace / "config_overrides.toml" @@ -391,24 +396,26 @@ def isolate_workspace(monkeypatch) -> Generator[None, None, None]: tomli_w.dump({ 'ai': {'provider': 'gemini', 'model': 'gemini-2.5-flash-lite'}, 'projects': {'paths': [], 'active': ''}, - 'gui': {'show_windows': {}} + 'gui': {'show_windows': {}}, + 'paths': { + 'presets': str(test_workspace / "presets.toml"), + 'tool_presets': str(test_workspace / "tool_presets.toml"), + 'personas': str(test_workspace / "personas.toml"), + 'themes': str(test_workspace / "themes"), + 'workspace_profiles': str(test_workspace / "workspace_profiles.toml"), + 'credentials': str(test_workspace / "credentials.toml"), + 'logs_dir': str(test_workspace / "logs"), + 'scripts_dir': str(test_workspace / "scripts"), + }, }, f) + (test_workspace / "themes").mkdir(exist_ok=True) for name in ( "presets.toml", "tool_presets.toml", "personas.toml", "workspace_profiles.toml", "credentials.toml", "mcp_env.toml", ): (test_workspace / name).touch() - monkeypatch.setenv("SLOP_GLOBAL_PRESETS", str(test_workspace / "presets.toml")) - monkeypatch.setenv("SLOP_GLOBAL_TOOL_PRESETS", str(test_workspace / "tool_presets.toml")) - monkeypatch.setenv("SLOP_GLOBAL_PERSONAS", str(test_workspace / "personas.toml")) - monkeypatch.setenv("SLOP_GLOBAL_WORKSPACE_PROFILES", str(test_workspace / "workspace_profiles.toml")) - monkeypatch.setenv("SLOP_CREDENTIALS", str(test_workspace / "credentials.toml")) - monkeypatch.setenv("SLOP_MCP_ENV", str(test_workspace / "mcp_env.toml")) - monkeypatch.setenv("SLOP_LOGS_DIR", str(test_workspace / "logs")) - monkeypatch.setenv("SLOP_SCRIPTS_DIR", str(test_workspace / "scripts")) - yield @pytest.fixture(autouse=True) @@ -739,17 +746,13 @@ def live_gui(request) -> Generator["_LiveGuiHandle", None, None]: log_file_name = Path(gui_script).name.replace('.', '_') log_file = open(f"tests/logs/{log_file_name}_test.log", "w", encoding="utf-8") - # Use environment variable to point to temp config if App supports it, - # or just run from that CWD. + # The sloppy.py subprocess reads path overrides from --config (which + # points at a config_overrides.toml inside temp_workspace that has the + # [paths] table). No SLOP_* env vars needed; sloppy.py itself reads + # paths from the config file. env = os.environ.copy() env["PYTHONPATH"] = str(project_root.absolute()) - if cred_file.exists(): - env["SLOP_CREDENTIALS"] = str(cred_file.absolute()) - if mcp_file.exists(): - env["SLOP_MCP_ENV"] = str(mcp_file.absolute()) - env["SLOP_GLOBAL_PRESETS"] = str((temp_workspace / "presets.toml").absolute()) - env["SLOP_GLOBAL_TOOL_PRESETS"] = str((temp_workspace / "tool_presets.toml").absolute()) - + gui_args = ["uv", "run", "python", "-u", gui_script, "--enable-test-hooks"] if config_file.exists(): gui_args.append(f"--config={config_file.absolute()}")