Skip to content

Commit 83817f4

Browse files
committed
test: make core suite Windows-safe
1 parent fdc1839 commit 83817f4

3 files changed

Lines changed: 73 additions & 86 deletions

File tree

.github/workflows/ci.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@ on:
88

99
jobs:
1010
test:
11-
runs-on: ubuntu-latest
11+
runs-on: ${{ matrix.os }}
1212
strategy:
13+
fail-fast: false
1314
matrix:
15+
os: [ubuntu-latest, windows-latest]
1416
python-version: ["3.10", "3.11", "3.12", "3.13"]
1517

1618
steps:
17-
- uses: actions/checkout@v4
19+
- uses: actions/checkout@v6
1820

1921
- name: Set up Python ${{ matrix.python-version }}
20-
uses: actions/setup-python@v5
22+
uses: actions/setup-python@v6
2123
with:
2224
python-version: ${{ matrix.python-version }}
2325

.github/workflows/publish.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ jobs:
1212
runs-on: ubuntu-latest
1313
environment: pypi
1414
steps:
15-
- uses: actions/checkout@v4
15+
- uses: actions/checkout@v6
1616

1717
- name: Set up Python
18-
uses: actions/setup-python@v5
18+
uses: actions/setup-python@v6
1919
with:
2020
python-version: "3.12"
2121

tests/test_core.py

Lines changed: 66 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Tests for core modules: config, tools, session, context."""
22

33
import os
4-
import tempfile
4+
import sys
55

66
from anycoder import __version__, Agent, LLMClient, Config
77
from anycoder.config import MODEL_ALIASES
@@ -56,15 +56,13 @@ def test_tool_schemas():
5656
assert "description" in s["function"]
5757

5858

59-
def test_read_file():
59+
def test_read_file(tmp_path):
6060
tool = TOOL_MAP["read_file"]
61-
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
62-
f.write("line1\nline2\nline3\n")
63-
f.flush()
64-
result = tool.execute(file_path=f.name)
65-
assert "line1" in result
66-
assert "3 lines total" in result
67-
os.unlink(f.name)
61+
path = tmp_path / "sample.txt"
62+
path.write_text("line1\nline2\nline3\n", encoding="utf-8")
63+
result = tool.execute(file_path=str(path))
64+
assert "line1" in result
65+
assert "3 lines total" in result
6866

6967

7068
def test_read_file_not_found():
@@ -73,38 +71,35 @@ def test_read_file_not_found():
7371
assert "[error]" in result
7472

7573

76-
def test_write_file():
74+
def test_write_file(tmp_path):
7775
tool = TOOL_MAP["write_file"]
78-
path = tempfile.mktemp(suffix=".txt")
79-
result = tool.execute(file_path=path, content="hello\nworld\n")
76+
path = tmp_path / "write.txt"
77+
result = tool.execute(file_path=str(path), content="hello\nworld\n")
8078
assert "Wrote" in result
81-
with open(path) as f:
82-
assert f.read() == "hello\nworld\n"
83-
os.unlink(path)
79+
assert path.read_text(encoding="utf-8") == "hello\nworld\n"
8480

8581

86-
def test_edit_file():
82+
def test_edit_file(tmp_path):
8783
tool = TOOL_MAP["edit_file"]
88-
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
89-
f.write("foo = 1\nbar = 2\n")
90-
f.flush()
91-
result = tool.execute(file_path=f.name, old_string="foo = 1", new_string="foo = 42")
92-
assert "Edited" in result
93-
with open(f.name) as rf:
94-
assert "foo = 42" in rf.read()
95-
os.unlink(f.name)
84+
path = tmp_path / "sample.py"
85+
path.write_text("foo = 1\nbar = 2\n", encoding="utf-8")
86+
result = tool.execute(file_path=str(path), old_string="foo = 1", new_string="foo = 42")
87+
assert "Edited" in result
88+
assert "foo = 42" in path.read_text(encoding="utf-8")
9689

9790

98-
def test_edit_file_diff_output():
91+
def test_edit_file_diff_output(tmp_path):
9992
tool = TOOL_MAP["edit_file"]
100-
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
101-
f.write("hello = 'world'\n")
102-
f.flush()
103-
result = tool.execute(file_path=f.name, old_string="hello = 'world'", new_string="hello = 'universe'")
104-
assert "---" in result and "+++" in result # unified diff markers
105-
assert "-hello = 'world'" in result
106-
assert "+hello = 'universe'" in result
107-
os.unlink(f.name)
93+
path = tmp_path / "diff.py"
94+
path.write_text("hello = 'world'\n", encoding="utf-8")
95+
result = tool.execute(
96+
file_path=str(path),
97+
old_string="hello = 'world'",
98+
new_string="hello = 'universe'",
99+
)
100+
assert "---" in result and "+++" in result # unified diff markers
101+
assert "-hello = 'world'" in result
102+
assert "+hello = 'universe'" in result
108103

109104

110105
def test_edit_file_not_found():
@@ -113,14 +108,12 @@ def test_edit_file_not_found():
113108
assert "[error]" in result
114109

115110

116-
def test_edit_file_unique_check():
111+
def test_edit_file_unique_check(tmp_path):
117112
tool = TOOL_MAP["edit_file"]
118-
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
119-
f.write("x = 1\nx = 1\n")
120-
f.flush()
121-
result = tool.execute(file_path=f.name, old_string="x = 1", new_string="x = 2")
122-
assert "appears 2 times" in result
123-
os.unlink(f.name)
113+
path = tmp_path / "dupe.py"
114+
path.write_text("x = 1\nx = 1\n", encoding="utf-8")
115+
result = tool.execute(file_path=str(path), old_string="x = 1", new_string="x = 2")
116+
assert "appears 2 times" in result
124117

125118

126119
def test_glob_tool():
@@ -143,33 +136,30 @@ def test_bash_tool():
143136

144137
def test_bash_timeout():
145138
tool = TOOL_MAP["bash"]
146-
result = tool.execute(command="sleep 10", timeout=1)
139+
result = tool.execute(command=f'"{sys.executable}" -c "import time; time.sleep(10)"', timeout=1)
147140
assert "timed out" in result.lower()
148141

149142

150143
# --- File change tracking ---
151144

152-
def test_edit_tracks_changes():
145+
def test_edit_tracks_changes(tmp_path):
153146
from anycoder.tools.edit_file import _changed_files
154147
_changed_files.clear()
155148
tool = TOOL_MAP["edit_file"]
156-
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
157-
f.write("aaa\nbbb\n")
158-
f.flush()
159-
tool.execute(file_path=f.name, old_string="aaa", new_string="zzz")
160-
assert len(_changed_files) > 0
161-
os.unlink(f.name)
149+
path = tmp_path / "tracked.py"
150+
path.write_text("aaa\nbbb\n", encoding="utf-8")
151+
tool.execute(file_path=str(path), old_string="aaa", new_string="zzz")
152+
assert len(_changed_files) > 0
162153
_changed_files.clear()
163154

164155

165-
def test_write_tracks_changes():
156+
def test_write_tracks_changes(tmp_path):
166157
from anycoder.tools.edit_file import _changed_files
167158
_changed_files.clear()
168159
tool = TOOL_MAP["write_file"]
169-
path = tempfile.mktemp(suffix=".txt")
170-
tool.execute(file_path=path, content="tracked\n")
160+
path = tmp_path / "tracked.txt"
161+
tool.execute(file_path=str(path), content="tracked\n")
171162
assert len(_changed_files) > 0
172-
os.unlink(path)
173163
_changed_files.clear()
174164

175165

@@ -217,40 +207,38 @@ def test_safe_command_not_blocked():
217207
assert "Blocked" not in result
218208

219209

220-
def test_cd_tracking():
210+
def test_cd_tracking(tmp_path):
221211
import anycoder.tools.bash as bash_mod
222212
old_cwd = bash_mod._cwd
223213
bash_mod._cwd = None # reset
224214

225215
tool = TOOL_MAP["bash"]
226-
# cd to /tmp should work
227-
tool.execute(command="cd /tmp && pwd")
228-
assert bash_mod._cwd == "/tmp" or bash_mod._cwd == "/private/tmp" # macOS /tmp -> /private/tmp
216+
command = f'cd "{tmp_path}" && ' + ("cd" if os.name == "nt" else "pwd")
217+
tool.execute(command=command)
218+
assert os.path.normcase(os.path.abspath(bash_mod._cwd or "")) == os.path.normcase(
219+
os.path.abspath(tmp_path)
220+
)
229221

230222
bash_mod._cwd = old_cwd # restore
231223

232224

233225
# --- Binary file detection ---
234226

235-
def test_binary_file_rejected():
227+
def test_binary_file_rejected(tmp_path):
236228
tool = TOOL_MAP["read_file"]
237-
with tempfile.NamedTemporaryFile(suffix=".bin", delete=False) as f:
238-
f.write(b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR")
239-
f.flush()
240-
result = tool.execute(file_path=f.name)
241-
assert "Binary file" in result
242-
os.unlink(f.name)
229+
path = tmp_path / "image.bin"
230+
path.write_bytes(b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR")
231+
result = tool.execute(file_path=str(path))
232+
assert "Binary file" in result
243233

244234

245-
def test_text_file_not_rejected():
235+
def test_text_file_not_rejected(tmp_path):
246236
tool = TOOL_MAP["read_file"]
247-
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
248-
f.write("just plain text\n")
249-
f.flush()
250-
result = tool.execute(file_path=f.name)
251-
assert "Binary" not in result
252-
assert "just plain text" in result
253-
os.unlink(f.name)
237+
path = tmp_path / "plain.txt"
238+
path.write_text("just plain text\n", encoding="utf-8")
239+
result = tool.execute(file_path=str(path))
240+
assert "Binary" not in result
241+
assert "just plain text" in result
254242

255243

256244
# --- Context compression ---
@@ -323,17 +311,14 @@ def count_tokens(self, messages):
323311

324312
# --- read_file offset/limit ---
325313

326-
def test_read_file_with_offset_limit():
314+
def test_read_file_with_offset_limit(tmp_path):
327315
tool = TOOL_MAP["read_file"]
328-
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
329-
for i in range(100):
330-
f.write(f"line {i}\n")
331-
f.flush()
332-
result = tool.execute(file_path=f.name, offset=10, limit=5)
333-
assert "showing lines 11-15" in result
334-
assert "line 10" in result # 0-indexed line 10 = "line 10"
335-
assert "line 0" not in result
336-
os.unlink(f.name)
316+
path = tmp_path / "long.txt"
317+
path.write_text("".join(f"line {i}\n" for i in range(100)), encoding="utf-8")
318+
result = tool.execute(file_path=str(path), offset=10, limit=5)
319+
assert "showing lines 11-15" in result
320+
assert "line 10" in result # 0-indexed line 10 = "line 10"
321+
assert "line 0" not in result
337322

338323

339324
# --- grep skips junk dirs ---

0 commit comments

Comments
 (0)