Skip to content

Commit f0f601c

Browse files
committed
Merge remote-tracking branch 'johbo/fix-xfail-tests-2' into v0.92.0
2 parents eceda28 + 562b8c2 commit f0f601c

5 files changed

Lines changed: 65 additions & 90 deletions

File tree

cecli/coders/base_coder.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -753,9 +753,16 @@ def show_pretty(self):
753753
return True
754754

755755
def get_abs_fnames_content(self):
756+
# Remove deleted files from abs_fnames
757+
deleted_fnames = [f for f in self.abs_fnames if not os.path.exists(f)]
758+
for fname in deleted_fnames:
759+
relative_fname = self.get_rel_fname(fname)
760+
self.io.tool_warning(f"Dropping {relative_fname} from the chat (file was deleted).")
761+
self.abs_fnames.remove(fname)
762+
756763
# Sort files by last modified time (earliest first, latest last)
757764
sorted_fnames = sorted(
758-
list(filter(lambda f: os.path.exists(f), self.abs_fnames)),
765+
list(self.abs_fnames),
759766
key=lambda fname: os.path.getmtime(fname),
760767
)
761768

cecli/io.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -941,6 +941,7 @@ def get_continuation(width, line_number, is_soft_wrap):
941941

942942
if coder:
943943
await coder.commands.do_run("exit", "")
944+
return ""
944945
else:
945946
raise SystemExit
946947

@@ -1157,7 +1158,7 @@ async def confirm_ask(
11571158
self.confirmation_in_progress_event.clear() # Confirmation is in progress
11581159

11591160
try:
1160-
return await asyncio.create_task(self._confirm_ask(*args, **kwargs))
1161+
return await self._confirm_ask(*args, **kwargs)
11611162
except KeyboardInterrupt:
11621163
# Re-raise KeyboardInterrupt to allow it to propagate
11631164
raise
@@ -1236,16 +1237,9 @@ async def _confirm_ask(
12361237
while True:
12371238
try:
12381239
if self.prompt_session:
1239-
await self.recreate_input()
1240-
1241-
if coroutines.is_active(self.input_task):
1242-
self.prompt_session.message = question
1243-
self.prompt_session.app.invalidate()
1244-
else:
1245-
await asyncio.sleep(0)
1246-
1247-
res = await self.input_task
1248-
await asyncio.sleep(0)
1240+
# Call prompt_async directly instead of using input_task
1241+
# This allows KeyboardInterrupt to propagate properly
1242+
res = await self.prompt_session.prompt_async(question)
12491243
else:
12501244
res = await asyncio.get_event_loop().run_in_executor(
12511245
None, input, question

tests/basic/test_coder.py

Lines changed: 49 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,6 @@ async def test_allowed_to_edit(self):
5454

5555
assert not coder.need_commit_before_edits
5656

57-
@pytest.mark.xfail(
58-
reason="Bug in io.py:970 - UnboundLocalError when exceptions occur before line assigned"
59-
)
6057
async def test_allowed_to_edit_no(self):
6158
with GitTemporaryDirectory():
6259
repo = git.Repo()
@@ -71,8 +68,8 @@ async def test_allowed_to_edit_no(self):
7168

7269
repo.git.commit("-m", "init")
7370

74-
# say NO
7571
io = InputOutput(yes=False)
72+
io.confirm_ask = AsyncMock(return_value=False)
7673

7774
coder = await Coder.create(self.GPT35, None, io, fnames=["added.txt"])
7875

@@ -126,14 +123,12 @@ async def test_get_files_content(self):
126123
assert "file1.txt" in all_file_names
127124
assert "file2.txt" in all_file_names
128125

129-
@pytest.mark.xfail(
130-
reason="Bug in io.py:970 - UnboundLocalError when exceptions occur before line assigned"
131-
)
132126
async def test_check_for_filename_mentions(self):
133127
with GitTemporaryDirectory():
134128
repo = git.Repo()
135129

136130
mock_io = MagicMock()
131+
mock_io.confirm_ask = AsyncMock(return_value=True)
137132

138133
fname1 = Path("file1.txt")
139134
fname2 = Path("file2.py")
@@ -145,13 +140,11 @@ async def test_check_for_filename_mentions(self):
145140
repo.git.add(str(fname2))
146141
repo.git.commit("-m", "new")
147142

148-
# Initialize the Coder object with the mocked IO and mocked repo
149-
coder = await Coder.create(self.GPT35, None, mock_io)
143+
mock_args = MagicMock(tui=False)
144+
coder = await Coder.create(self.GPT35, None, mock_io, args=mock_args)
150145

151-
# Call the check_for_file_mentions method
152-
coder.check_for_file_mentions("Please check file1.txt and file2.py")
146+
await coder.check_for_file_mentions("Please check file1.txt and file2.py")
153147

154-
# Check if coder.abs_fnames contains both files
155148
expected_files = set(
156149
[
157150
str(Path(coder.root) / fname1),
@@ -161,13 +154,11 @@ async def test_check_for_filename_mentions(self):
161154

162155
assert coder.abs_fnames == expected_files
163156

164-
@pytest.mark.xfail(
165-
reason="Bug in io.py:970 - UnboundLocalError when exceptions occur before line assigned"
166-
)
167157
async def test_check_for_ambiguous_filename_mentions_of_longer_paths(self):
168158
with GitTemporaryDirectory():
169159
io = InputOutput(pretty=False, yes=True)
170-
coder = await Coder.create(self.GPT35, None, io)
160+
mock_args = MagicMock(tui=False)
161+
coder = await Coder.create(self.GPT35, None, io, args=mock_args)
171162

172163
fname = Path("file1.txt")
173164
fname.touch()
@@ -180,8 +171,7 @@ async def test_check_for_ambiguous_filename_mentions_of_longer_paths(self):
180171
mock.return_value = set([str(fname), str(other_fname)])
181172
coder.repo.get_tracked_files = mock
182173

183-
# Call the check_for_file_mentions method
184-
coder.check_for_file_mentions(f"Please check {fname}!")
174+
await coder.check_for_file_mentions(f"Please check {fname}!")
185175

186176
assert coder.abs_fnames == {str(fname.resolve())}
187177

@@ -216,83 +206,54 @@ async def test_skip_duplicate_basename_mentions(self):
216206
mentioned = coder.get_file_mentions(f"Check {fname1} and {fname3}")
217207
assert mentioned == {str(fname3)}
218208

219-
@pytest.mark.xfail(
220-
reason="Bug in io.py:970 - UnboundLocalError when exceptions occur before line assigned"
221-
)
222209
async def test_check_for_file_mentions_read_only(self):
223210
with GitTemporaryDirectory():
224-
io = InputOutput(
225-
pretty=False,
226-
yes=True,
227-
)
211+
io = InputOutput(pretty=False, yes=True)
228212
coder = await Coder.create(self.GPT35, None, io)
229213

230214
fname = Path("readonly_file.txt")
231215
fname.touch()
232216

233217
coder.abs_read_only_fnames.add(str(fname.resolve()))
234218

235-
# Mock the get_tracked_files method
236219
mock = MagicMock()
237220
mock.return_value = set([str(fname)])
238221
coder.repo.get_tracked_files = mock
239222

240-
# Call the check_for_file_mentions method
241-
result = coder.check_for_file_mentions(f"Please check {fname}!")
223+
result = await coder.check_for_file_mentions(f"Please check {fname}!")
242224

243-
# Assert that the method returns None (user not asked to add the file)
244225
assert result is None
245-
246-
# Assert that abs_fnames is still empty (file not added)
247226
assert coder.abs_fnames == set()
248227

249-
@pytest.mark.xfail(
250-
reason="Bug in io.py:970 - UnboundLocalError when exceptions occur before line assigned"
251-
)
252228
async def test_check_for_file_mentions_with_mocked_confirm(self):
253229
with GitTemporaryDirectory():
254230
io = InputOutput(pretty=False)
255-
coder = await Coder.create(self.GPT35, None, io)
231+
io.confirm_ask = AsyncMock(side_effect=[False, True, True])
232+
mock_args = MagicMock(tui=False)
233+
coder = await Coder.create(self.GPT35, None, io, args=mock_args)
256234

257-
# Mock get_file_mentions to return two file names
258235
coder.get_file_mentions = MagicMock(return_value=set(["file1.txt", "file2.txt"]))
259236

260-
# Mock confirm_ask to return False for the first call and True for the second
261-
io.confirm_ask = AsyncMock(side_effect=[False, True, True])
262-
263-
# First call to check_for_file_mentions
264237
await coder.check_for_file_mentions("Please check file1.txt for the info")
265238

266-
# Assert that confirm_ask was called twice
267239
assert io.confirm_ask.call_count == 2
268-
269-
# Assert that only file2.txt was added to abs_fnames
270240
assert len(coder.abs_fnames) == 1
271241
assert "file2.txt" in str(coder.abs_fnames)
272242

273-
# Reset the mock
274243
io.confirm_ask.reset_mock()
275244

276-
# Second call to check_for_file_mentions
277245
await coder.check_for_file_mentions("Please check file1.txt and file2.txt again")
278246

279-
# Assert that confirm_ask was called only once (for file1.txt)
280247
assert io.confirm_ask.call_count == 1
281-
282-
# Assert that abs_fnames still contains only file2.txt
283248
assert len(coder.abs_fnames) == 1
284249
assert "file2.txt" in str(coder.abs_fnames)
285-
286-
# Assert that file1.txt is in ignore_mentions
287250
assert "file1.txt" in coder.ignore_mentions
288251

289-
@pytest.mark.xfail(
290-
reason="Bug in io.py:970 - UnboundLocalError when exceptions occur before line assigned"
291-
)
292252
async def test_check_for_subdir_mention(self):
293253
with GitTemporaryDirectory():
294254
io = InputOutput(pretty=False, yes=True)
295-
coder = await Coder.create(self.GPT35, None, io)
255+
mock_args = MagicMock(tui=False)
256+
coder = await Coder.create(self.GPT35, None, io, args=mock_args)
296257

297258
fname = Path("other") / "file1.txt"
298259
fname.parent.mkdir(parents=True, exist_ok=True)
@@ -302,8 +263,7 @@ async def test_check_for_subdir_mention(self):
302263
mock.return_value = set([str(fname)])
303264
coder.repo.get_tracked_files = mock
304265

305-
# Call the check_for_file_mentions method
306-
coder.check_for_file_mentions(f"Please check `{fname}`")
266+
await coder.check_for_file_mentions(f"Please check `{fname}`")
307267

308268
assert coder.abs_fnames == {str(fname.resolve())}
309269

@@ -460,12 +420,6 @@ async def test_get_file_mentions_path_formats(self):
460420
mentioned_files == expected_files
461421
), f"Failed for content: {content}, addable_files: {addable_files}"
462422

463-
@pytest.mark.xfail(
464-
reason=(
465-
"Behavior change: deleted files are filtered out during processing but not removed from"
466-
" abs_fnames"
467-
)
468-
)
469423
async def test_run_with_file_deletion(self):
470424
# Create a few temporary files
471425

@@ -896,12 +850,20 @@ async def test_skip_gitignored_files_on_init(self):
896850
f"Skipping {ignored_file.name} that matches gitignore spec."
897851
)
898852

899-
@pytest.mark.xfail(reason="Commands.cmd_web method not implemented")
900853
async def test_check_for_urls(self):
901854
io = InputOutput(yes=True)
902-
coder = await Coder.create(self.GPT35, None, io=io)
903-
coder.commands.scraper = MagicMock()
904-
coder.commands.scraper.scrape = MagicMock(return_value="some content")
855+
mock_args = MagicMock()
856+
mock_args.yes_always_commands = False
857+
mock_args.disable_scraping = False
858+
coder = await Coder.create(self.GPT35, None, io=io, args=mock_args)
859+
860+
# Mock the do_run command to return scraped content
861+
async def mock_do_run(cmd_name, url, **kwargs):
862+
if cmd_name == "web" and kwargs.get("return_content"):
863+
return f"Scraped content from {url}"
864+
return None
865+
866+
coder.commands.do_run = mock_do_run
905867

906868
# Test various URL formats
907869
test_cases = [
@@ -1051,18 +1013,34 @@ async def test_no_suggest_shell_commands(self):
10511013
coder = await Coder.create(self.GPT35, "diff", io=io, suggest_shell_commands=False)
10521014
assert not coder.suggest_shell_commands
10531015

1054-
@pytest.mark.xfail(reason="Commands.cmd_web method not implemented")
10551016
async def test_detect_urls_enabled(self):
10561017
with GitTemporaryDirectory():
10571018
io = InputOutput(yes=True)
1058-
coder = await Coder.create(self.GPT35, "diff", io=io, detect_urls=True)
1059-
coder.commands.scraper = MagicMock()
1060-
coder.commands.scraper.scrape = MagicMock(return_value="some content")
1019+
mock_args = MagicMock()
1020+
mock_args.yes_always_commands = False
1021+
mock_args.disable_scraping = False
1022+
coder = await Coder.create(self.GPT35, "diff", io=io, detect_urls=True, args=mock_args)
1023+
1024+
# Track calls to do_run
1025+
do_run_calls = []
1026+
1027+
async def mock_do_run(cmd_name, url, **kwargs):
1028+
do_run_calls.append((cmd_name, url, kwargs))
1029+
if cmd_name == "web" and kwargs.get("return_content"):
1030+
return f"Scraped content from {url}"
1031+
return None
1032+
1033+
coder.commands.do_run = mock_do_run
10611034

10621035
# Test with a message containing a URL
10631036
message = "Check out https://example.com"
10641037
await coder.check_for_urls(message)
1065-
coder.commands.scraper.scrape.assert_called_once_with("https://example.com")
1038+
1039+
# Verify do_run was called with the web command and correct URL
1040+
assert len(do_run_calls) == 1
1041+
assert do_run_calls[0][0] == "web"
1042+
assert do_run_calls[0][1] == "https://example.com"
1043+
assert do_run_calls[0][2].get("return_content") is True
10661044

10671045
async def test_detect_urls_disabled(self):
10681046
with GitTemporaryDirectory():

tests/basic/test_io.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -393,11 +393,6 @@ def test_tool_message_unicode_fallback(self):
393393
# The invalid Unicode should be replaced with '?'
394394
assert converted_message == "Hello ?World"
395395

396-
# TODO: Fix underlying bug in io.py:970 (UnboundLocalError)
397-
# This test will pass once the bug is fixed in the production code
398-
@pytest.mark.xfail(
399-
reason="Bug: confirm_ask doesn't propagate KeyboardInterrupt - revealed by pytest migration"
400-
)
401396
async def test_multiline_mode_restored_after_interrupt(self):
402397
"""Test that multiline mode is restored after KeyboardInterrupt"""
403398
io = InputOutput(fancy_input=True)

tests/basic/test_wholefile.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import shutil
33
import tempfile
44
from pathlib import Path
5-
from unittest.mock import MagicMock
5+
from unittest.mock import AsyncMock, MagicMock
66

77
import pytest
88

@@ -40,7 +40,8 @@ async def test_no_files(self):
4040
coder.render_incremental_response(True)
4141

4242
async def test_no_files_new_file_should_ask(self):
43-
io = InputOutput(yes=False) # <- yes=FALSE
43+
io = InputOutput(yes=False)
44+
io.confirm_ask = AsyncMock(return_value=False)
4445
coder = WholeFileCoder(main_model=self.GPT35, io=io, fnames=[])
4546
coder.partial_response_content = (
4647
'To print "Hello, World!" in most programming languages, you can use the following'

0 commit comments

Comments
 (0)