tenseleyflow/loader / 33e7384

Browse files

Stabilize Loader test collection and package imports

Authored by espadonne
SHA
33e738432deffa233df587e66a3fb86a11c47a57
Parents
ef74134
Tree
1795f1c

8 changed files

StatusFile+-
M pyproject.toml 7 0
M src/loader/agent/__init__.py 15 1
M src/loader/agent/recovery.py 0 1
M src/loader/llm/__init__.py 12 1
M tests/conftest.py 13 3
M tests/test_parsing.py 1 2
M tests/test_recovery.py 4 5
M tests/test_tools.py 9 3
pyproject.tomlmodified
@@ -50,10 +50,17 @@ packages = ["src/loader"]
5050
 [tool.ruff]
5151
 line-length = 100
5252
 target-version = "py311"
53
+extend-exclude = ["refs"]
5354
 
5455
 [tool.ruff.lint]
5556
 select = ["E", "F", "I", "N", "W", "UP"]
5657
 
58
+[tool.pytest.ini_options]
59
+testpaths = ["tests"]
60
+asyncio_mode = "strict"
61
+asyncio_default_fixture_loop_scope = "function"
62
+
5763
 [tool.mypy]
5864
 python_version = "3.11"
5965
 strict = true
66
+exclude = ["^refs/"]
src/loader/agent/__init__.pymodified
@@ -1,5 +1,19 @@
11
 """Agent system."""
22
 
3
-from .loop import Agent, AgentConfig
3
+from typing import Any
44
 
55
 __all__ = ["Agent", "AgentConfig"]
6
+
7
+
8
+def __getattr__(name: str) -> Any:
9
+    """Lazily import the heavy agent loop on demand."""
10
+
11
+    if name in {"Agent", "AgentConfig"}:
12
+        from .loop import Agent, AgentConfig
13
+
14
+        exports = {
15
+            "Agent": Agent,
16
+            "AgentConfig": AgentConfig,
17
+        }
18
+        return exports[name]
19
+    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
src/loader/agent/recovery.pymodified
@@ -218,7 +218,6 @@ def categorize_error(error_message: str) -> ErrorCategory:
218218
     if any(x in error_lower for x in [
219219
         "unauthorized", "403 forbidden", "401 unauthorized",
220220
         "authentication failed", "invalid credentials",
221
-        "access denied", "permission denied to",
222221
         "invalid token", "token expired",
223222
     ]):
224223
         return ErrorCategory.AUTH_ERROR
src/loader/llm/__init__.pymodified
@@ -1,6 +1,17 @@
11
 """LLM backend abstraction layer."""
22
 
3
+from typing import Any
4
+
35
 from .base import LLMBackend, Message, ToolCall, ToolResult
4
-from .ollama import OllamaBackend
56
 
67
 __all__ = ["LLMBackend", "Message", "ToolCall", "ToolResult", "OllamaBackend"]
8
+
9
+
10
+def __getattr__(name: str) -> Any:
11
+    """Avoid importing the Ollama backend until a caller actually needs it."""
12
+
13
+    if name == "OllamaBackend":
14
+        from .ollama import OllamaBackend
15
+
16
+        return OllamaBackend
17
+    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
tests/conftest.pymodified
@@ -1,9 +1,17 @@
11
 """Pytest configuration and fixtures."""
22
 
3
-import pytest
3
+import sys
44
 import tempfile
55
 from pathlib import Path
66
 
7
+import pytest
8
+
9
+ROOT = Path(__file__).resolve().parents[1]
10
+SRC = ROOT / "src"
11
+
12
+if str(SRC) not in sys.path:
13
+    sys.path.insert(0, str(SRC))
14
+
715
 
816
 @pytest.fixture
917
 def temp_dir():
@@ -24,7 +32,8 @@ def sample_file(temp_dir):
2432
 def sample_python_file(temp_dir):
2533
     """Create a sample Python file for testing."""
2634
     file_path = temp_dir / "sample.py"
27
-    file_path.write_text('''"""Sample module."""
35
+    file_path.write_text(
36
+        '''"""Sample module."""
2837
 
2938
 def hello():
3039
     """Say hello."""
@@ -33,5 +42,6 @@ def hello():
3342
 def add(a, b):
3443
     """Add two numbers."""
3544
     return a + b
36
-''')
45
+'''
46
+    )
3747
     return file_path
tests/test_parsing.pymodified
@@ -1,7 +1,6 @@
11
 """Tests for the ReAct parsing module."""
22
 
3
-import pytest
4
-from loader.agent.parsing import parse_tool_calls, format_tool_result
3
+from loader.agent.parsing import format_tool_result, parse_tool_calls
54
 
65
 
76
 class TestParseToolCalls:
tests/test_recovery.pymodified
@@ -1,10 +1,8 @@
11
 """Tests for the error recovery system."""
22
 
3
-import pytest
43
 from loader.agent.recovery import (
54
     ErrorCategory,
65
     RecoveryContext,
7
-    ToolAttempt,
86
     categorize_error,
97
     format_failure_message,
108
     format_recovery_prompt,
@@ -43,7 +41,7 @@ class TestCategorizeError:
4341
 
4442
     def test_network_error(self):
4543
         assert categorize_error("Network unreachable") == ErrorCategory.NETWORK_ERROR
46
-        assert categorize_error("Connection refused") == ErrorCategory.NETWORK_ERROR
44
+        assert categorize_error("Connection refused") == ErrorCategory.CONNECTION_REFUSED
4745
 
4846
     def test_unknown(self):
4947
         assert categorize_error("Something weird happened") == ErrorCategory.UNKNOWN
@@ -136,10 +134,11 @@ class TestFormatRecoveryPrompt:
136134
         ctx.add_attempt("read", {"path": "test.py"}, "No such file")
137135
 
138136
         prompt = format_recovery_prompt(ctx, "read", {"path": "test.py"}, "No such file")
139
-        assert "Tool: read" in prompt
137
+        assert "Failed Command" in prompt
138
+        assert "read(path='test.py')" in prompt
140139
         assert "No such file" in prompt
141140
         assert "1/3" in prompt
142
-        assert "Do NOT repeat" in prompt
141
+        assert "retry the same command with slight variations" in prompt
143142
 
144143
 
145144
 class TestFormatFailureMessage:
tests/test_tools.pymodified
@@ -1,11 +1,17 @@
11
 """Tests for tool implementations."""
22
 
33
 import pytest
4
+
45
 from loader.tools import (
5
-    ReadTool, WriteTool, EditTool, GlobTool,
6
-    BashTool, GrepTool, ConfirmationRequired,
6
+    BashTool,
7
+    ConfirmationRequired,
8
+    EditTool,
9
+    GlobTool,
10
+    GrepTool,
11
+    ReadTool,
12
+    WriteTool,
713
 )
8
-from loader.tools.base import ToolRegistry, create_default_registry
14
+from loader.tools.base import create_default_registry
915
 
1016
 
1117
 class TestReadTool: