forked from jsturtevant/copilot-codeact-plugin
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_tools_config.py
More file actions
144 lines (117 loc) · 6.4 KB
/
Copy pathtest_tools_config.py
File metadata and controls
144 lines (117 loc) · 6.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#!/usr/bin/env python3
"""test_tools_config.py — Unit tests for tool enable/disable + custom tool loading.
Exercises both backend codeact.py modules (monty + hyperlight) without needing
the actual sandbox runtimes — only the tool-discovery + filtering layer.
Run:
python3 plugins/codeact/tests/test_tools_config.py
"""
from __future__ import annotations
import importlib.util
import os
import shutil
import sys
import tempfile
import unittest
from pathlib import Path
PLUGIN_DIR = Path(__file__).resolve().parents[2]
FIXTURES = Path(__file__).resolve().parents[1] / "fixtures" / "user-tools"
def _load_backend(name: str):
"""Load a backend's codeact.py as a module, isolated from sys.modules."""
path = PLUGIN_DIR / "skills" / f"{name}-codeact" / "scripts" / "codeact.py"
spec = importlib.util.spec_from_file_location(f"_codeact_{name}", path)
assert spec and spec.loader, f"could not load {path}"
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod
class ToolConfigTests(unittest.TestCase):
def setUp(self) -> None:
self.tmp = Path(tempfile.mkdtemp(prefix="codeact-test-"))
self.cfg_dir = self.tmp / "codeact"
self.cfg_dir.mkdir()
os.environ["CODEACT_CONFIG_DIR"] = str(self.cfg_dir)
# Clean any leftover env from other tests
for var in ("CODEACT_TOOLS", "CODEACT_DISABLE"):
os.environ.pop(var, None)
def tearDown(self) -> None:
shutil.rmtree(self.tmp, ignore_errors=True)
for var in ("CODEACT_CONFIG_DIR", "CODEACT_TOOLS", "CODEACT_DISABLE"):
os.environ.pop(var, None)
# ---- shared assertions, parameterised over backend ----
def _check_baseline(self, backend_name: str) -> None:
be = _load_backend(backend_name)
tools = be.apply_user_config(be.discover_tools())
names = {t["name"] for t in tools}
# Built-ins always present (no filesystem deps required)
for required in ("view", "create", "edit", "glob", "bash", "sql"):
self.assertIn(required, names,
f"{backend_name}: missing builtin {required}")
def _check_denylist_env(self, backend_name: str) -> None:
os.environ["CODEACT_DISABLE"] = "bash, sql"
be = _load_backend(backend_name)
names = {t["name"] for t in be.apply_user_config(be.discover_tools())}
self.assertNotIn("bash", names, f"{backend_name}: bash should be disabled")
self.assertNotIn("sql", names, f"{backend_name}: sql should be disabled")
self.assertIn("view", names)
def _check_allowlist_env(self, backend_name: str) -> None:
os.environ["CODEACT_TOOLS"] = "view,glob"
be = _load_backend(backend_name)
names = {t["name"] for t in be.apply_user_config(be.discover_tools())}
self.assertEqual(names, {"view", "glob"},
f"{backend_name}: allowlist not honored, got {names}")
def _check_config_file(self, backend_name: str) -> None:
(self.cfg_dir / "config.json").write_text(
'{"disabled": ["bash", "sql", "github_api", "web_fetch", "grep"]}')
be = _load_backend(backend_name)
names = {t["name"] for t in be.apply_user_config(be.discover_tools())}
self.assertNotIn("bash", names)
self.assertNotIn("sql", names)
self.assertIn("view", names)
def _check_custom_tool(self, backend_name: str) -> None:
tools_dir = self.cfg_dir / "tools"
tools_dir.mkdir()
shutil.copy(FIXTURES / "shout.py", tools_dir / "shout.py")
be = _load_backend(backend_name)
tools = be.apply_user_config(be.discover_tools())
shout = next((t for t in tools if t["name"] == "shout"), None)
self.assertIsNotNone(shout, f"{backend_name}: custom tool not registered")
self.assertEqual(shout["implementation"]["type"], "user")
# Build handler and call it — must work for both backends
handler = be._make_handler(shout)
self.assertEqual(handler(text="hello"), "HELLO",
f"{backend_name}: handler returned wrong value")
def _check_custom_tool_denylist(self, backend_name: str) -> None:
tools_dir = self.cfg_dir / "tools"
tools_dir.mkdir()
shutil.copy(FIXTURES / "shout.py", tools_dir / "shout.py")
os.environ["CODEACT_DISABLE"] = "shout"
be = _load_backend(backend_name)
names = {t["name"] for t in be.apply_user_config(be.discover_tools())}
self.assertNotIn("shout", names,
f"{backend_name}: custom tool not filtered by denylist")
def _check_bad_custom_tool_skipped(self, backend_name: str) -> None:
tools_dir = self.cfg_dir / "tools"
tools_dir.mkdir()
(tools_dir / "broken.py").write_text("raise RuntimeError('boom')\n")
be = _load_backend(backend_name)
# Should not raise — bad tools are logged + skipped
tools = be.apply_user_config(be.discover_tools())
names = {t["name"] for t in tools}
self.assertNotIn("broken", names)
self.assertIn("view", names) # baseline still works
# ---- per-backend test methods (so unittest reports them separately) ----
def test_monty_baseline(self): self._check_baseline("monty")
def test_hyperlight_baseline(self): self._check_baseline("hyperlight")
def test_monty_denylist_env(self): self._check_denylist_env("monty")
def test_hyperlight_denylist_env(self): self._check_denylist_env("hyperlight")
def test_monty_allowlist_env(self): self._check_allowlist_env("monty")
def test_hyperlight_allowlist_env(self): self._check_allowlist_env("hyperlight")
def test_monty_config_file(self): self._check_config_file("monty")
def test_hyperlight_config_file(self): self._check_config_file("hyperlight")
def test_monty_custom_tool(self): self._check_custom_tool("monty")
def test_hyperlight_custom_tool(self): self._check_custom_tool("hyperlight")
def test_monty_custom_denylist(self): self._check_custom_tool_denylist("monty")
def test_hyperlight_custom_denylist(self): self._check_custom_tool_denylist("hyperlight")
def test_monty_bad_tool_skipped(self): self._check_bad_custom_tool_skipped("monty")
def test_hyperlight_bad_tool_skipped(self): self._check_bad_custom_tool_skipped("hyperlight")
if __name__ == "__main__":
sys.exit(0 if unittest.main(exit=False, verbosity=2).result.wasSuccessful() else 1)