mirror of
https://github.com/Aider-AI/aider.git
synced 2025-05-31 17:55:01 +00:00
moved tests/ to aider/tests/
This commit is contained in:
parent
470b8d0251
commit
ce46ea8d62
13 changed files with 7 additions and 1 deletions
|
@ -1,612 +0,0 @@
|
|||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import git
|
||||
import openai
|
||||
|
||||
from aider.coders import Coder
|
||||
from aider.dump import dump # noqa: F401
|
||||
from aider.io import InputOutput
|
||||
from aider.models import Model
|
||||
from aider.utils import ChdirTemporaryDirectory, GitTemporaryDirectory
|
||||
|
||||
|
||||
class TestCoder(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.GPT35 = Model("gpt-3.5-turbo")
|
||||
|
||||
def test_allowed_to_edit(self):
|
||||
with GitTemporaryDirectory():
|
||||
repo = git.Repo()
|
||||
|
||||
fname = Path("added.txt")
|
||||
fname.touch()
|
||||
repo.git.add(str(fname))
|
||||
|
||||
fname = Path("repo.txt")
|
||||
fname.touch()
|
||||
repo.git.add(str(fname))
|
||||
|
||||
repo.git.commit("-m", "init")
|
||||
|
||||
# YES!
|
||||
io = InputOutput(yes=True)
|
||||
coder = Coder.create(self.GPT35, None, io, fnames=["added.txt"])
|
||||
|
||||
self.assertTrue(coder.allowed_to_edit("added.txt"))
|
||||
self.assertTrue(coder.allowed_to_edit("repo.txt"))
|
||||
self.assertTrue(coder.allowed_to_edit("new.txt"))
|
||||
|
||||
self.assertIn("repo.txt", str(coder.abs_fnames))
|
||||
self.assertIn("new.txt", str(coder.abs_fnames))
|
||||
|
||||
self.assertFalse(coder.need_commit_before_edits)
|
||||
|
||||
def test_allowed_to_edit_no(self):
|
||||
with GitTemporaryDirectory():
|
||||
repo = git.Repo()
|
||||
|
||||
fname = Path("added.txt")
|
||||
fname.touch()
|
||||
repo.git.add(str(fname))
|
||||
|
||||
fname = Path("repo.txt")
|
||||
fname.touch()
|
||||
repo.git.add(str(fname))
|
||||
|
||||
repo.git.commit("-m", "init")
|
||||
|
||||
# say NO
|
||||
io = InputOutput(yes=False)
|
||||
|
||||
coder = Coder.create(self.GPT35, None, io, fnames=["added.txt"])
|
||||
|
||||
self.assertTrue(coder.allowed_to_edit("added.txt"))
|
||||
self.assertFalse(coder.allowed_to_edit("repo.txt"))
|
||||
self.assertFalse(coder.allowed_to_edit("new.txt"))
|
||||
|
||||
self.assertNotIn("repo.txt", str(coder.abs_fnames))
|
||||
self.assertNotIn("new.txt", str(coder.abs_fnames))
|
||||
|
||||
self.assertFalse(coder.need_commit_before_edits)
|
||||
|
||||
def test_allowed_to_edit_dirty(self):
|
||||
with GitTemporaryDirectory():
|
||||
repo = git.Repo()
|
||||
|
||||
fname = Path("added.txt")
|
||||
fname.touch()
|
||||
repo.git.add(str(fname))
|
||||
|
||||
repo.git.commit("-m", "init")
|
||||
|
||||
# say NO
|
||||
io = InputOutput(yes=False)
|
||||
|
||||
coder = Coder.create(self.GPT35, None, io, fnames=["added.txt"])
|
||||
|
||||
self.assertTrue(coder.allowed_to_edit("added.txt"))
|
||||
self.assertFalse(coder.need_commit_before_edits)
|
||||
|
||||
fname.write_text("dirty!")
|
||||
self.assertTrue(coder.allowed_to_edit("added.txt"))
|
||||
self.assertTrue(coder.need_commit_before_edits)
|
||||
|
||||
def test_get_last_modified(self):
|
||||
# Mock the IO object
|
||||
mock_io = MagicMock()
|
||||
|
||||
with GitTemporaryDirectory():
|
||||
repo = git.Repo(Path.cwd())
|
||||
fname = Path("new.txt")
|
||||
fname.touch()
|
||||
repo.git.add(str(fname))
|
||||
repo.git.commit("-m", "new")
|
||||
|
||||
# Initialize the Coder object with the mocked IO and mocked repo
|
||||
coder = Coder.create(self.GPT35, None, mock_io)
|
||||
|
||||
mod = coder.get_last_modified()
|
||||
|
||||
fname.write_text("hi")
|
||||
mod_newer = coder.get_last_modified()
|
||||
self.assertLess(mod, mod_newer)
|
||||
|
||||
fname.unlink()
|
||||
self.assertEqual(coder.get_last_modified(), 0)
|
||||
|
||||
def test_get_files_content(self):
|
||||
tempdir = Path(tempfile.mkdtemp())
|
||||
|
||||
file1 = tempdir / "file1.txt"
|
||||
file2 = tempdir / "file2.txt"
|
||||
|
||||
file1.touch()
|
||||
file2.touch()
|
||||
|
||||
files = [file1, file2]
|
||||
|
||||
# Initialize the Coder object with the mocked IO and mocked repo
|
||||
coder = Coder.create(self.GPT35, None, io=InputOutput(), fnames=files)
|
||||
|
||||
content = coder.get_files_content().splitlines()
|
||||
self.assertIn("file1.txt", content)
|
||||
self.assertIn("file2.txt", content)
|
||||
|
||||
def test_check_for_filename_mentions(self):
|
||||
with GitTemporaryDirectory():
|
||||
repo = git.Repo()
|
||||
|
||||
mock_io = MagicMock()
|
||||
|
||||
fname1 = Path("file1.txt")
|
||||
fname2 = Path("file2.py")
|
||||
|
||||
fname1.write_text("one\n")
|
||||
fname2.write_text("two\n")
|
||||
|
||||
repo.git.add(str(fname1))
|
||||
repo.git.add(str(fname2))
|
||||
repo.git.commit("-m", "new")
|
||||
|
||||
# Initialize the Coder object with the mocked IO and mocked repo
|
||||
coder = Coder.create(self.GPT35, None, mock_io)
|
||||
|
||||
# Call the check_for_file_mentions method
|
||||
coder.check_for_file_mentions("Please check file1.txt and file2.py")
|
||||
|
||||
# Check if coder.abs_fnames contains both files
|
||||
expected_files = set(
|
||||
[
|
||||
str(Path(coder.root) / fname1),
|
||||
str(Path(coder.root) / fname2),
|
||||
]
|
||||
)
|
||||
|
||||
self.assertEqual(coder.abs_fnames, expected_files)
|
||||
|
||||
def test_check_for_ambiguous_filename_mentions_of_longer_paths(self):
|
||||
with GitTemporaryDirectory():
|
||||
io = InputOutput(pretty=False, yes=True)
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
|
||||
fname = Path("file1.txt")
|
||||
fname.touch()
|
||||
|
||||
other_fname = Path("other") / "file1.txt"
|
||||
other_fname.parent.mkdir(parents=True, exist_ok=True)
|
||||
other_fname.touch()
|
||||
|
||||
mock = MagicMock()
|
||||
mock.return_value = set([str(fname), str(other_fname)])
|
||||
coder.repo.get_tracked_files = mock
|
||||
|
||||
# Call the check_for_file_mentions method
|
||||
coder.check_for_file_mentions(f"Please check {fname}!")
|
||||
|
||||
self.assertEqual(coder.abs_fnames, set([str(fname.resolve())]))
|
||||
|
||||
def test_check_for_subdir_mention(self):
|
||||
with GitTemporaryDirectory():
|
||||
io = InputOutput(pretty=False, yes=True)
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
|
||||
fname = Path("other") / "file1.txt"
|
||||
fname.parent.mkdir(parents=True, exist_ok=True)
|
||||
fname.touch()
|
||||
|
||||
mock = MagicMock()
|
||||
mock.return_value = set([str(fname)])
|
||||
coder.repo.get_tracked_files = mock
|
||||
|
||||
# Call the check_for_file_mentions method
|
||||
coder.check_for_file_mentions(f"Please check `{fname}`")
|
||||
|
||||
self.assertEqual(coder.abs_fnames, set([str(fname.resolve())]))
|
||||
|
||||
def test_run_with_file_deletion(self):
|
||||
# Create a few temporary files
|
||||
|
||||
tempdir = Path(tempfile.mkdtemp())
|
||||
|
||||
file1 = tempdir / "file1.txt"
|
||||
file2 = tempdir / "file2.txt"
|
||||
|
||||
file1.touch()
|
||||
file2.touch()
|
||||
|
||||
files = [file1, file2]
|
||||
|
||||
# Initialize the Coder object with the mocked IO and mocked repo
|
||||
coder = Coder.create(self.GPT35, None, io=InputOutput(), fnames=files)
|
||||
|
||||
def mock_send(*args, **kwargs):
|
||||
coder.partial_response_content = "ok"
|
||||
coder.partial_response_function_call = dict()
|
||||
return []
|
||||
|
||||
coder.send = mock_send
|
||||
|
||||
# Call the run method with a message
|
||||
coder.run(with_message="hi")
|
||||
self.assertEqual(len(coder.abs_fnames), 2)
|
||||
|
||||
file1.unlink()
|
||||
|
||||
# Call the run method again with a message
|
||||
coder.run(with_message="hi")
|
||||
self.assertEqual(len(coder.abs_fnames), 1)
|
||||
|
||||
def test_run_with_file_unicode_error(self):
|
||||
# Create a few temporary files
|
||||
_, file1 = tempfile.mkstemp()
|
||||
_, file2 = tempfile.mkstemp()
|
||||
|
||||
files = [file1, file2]
|
||||
|
||||
# Initialize the Coder object with the mocked IO and mocked repo
|
||||
coder = Coder.create(self.GPT35, None, io=InputOutput(), fnames=files)
|
||||
|
||||
def mock_send(*args, **kwargs):
|
||||
coder.partial_response_content = "ok"
|
||||
coder.partial_response_function_call = dict()
|
||||
return []
|
||||
|
||||
coder.send = mock_send
|
||||
|
||||
# Call the run method with a message
|
||||
coder.run(with_message="hi")
|
||||
self.assertEqual(len(coder.abs_fnames), 2)
|
||||
|
||||
# Write some non-UTF8 text into the file
|
||||
with open(file1, "wb") as f:
|
||||
f.write(b"\x80abc")
|
||||
|
||||
# Call the run method again with a message
|
||||
coder.run(with_message="hi")
|
||||
self.assertEqual(len(coder.abs_fnames), 1)
|
||||
|
||||
def test_choose_fence(self):
|
||||
# Create a few temporary files
|
||||
_, file1 = tempfile.mkstemp()
|
||||
|
||||
with open(file1, "wb") as f:
|
||||
f.write(b"this contains ``` backticks")
|
||||
|
||||
files = [file1]
|
||||
|
||||
# Initialize the Coder object with the mocked IO and mocked repo
|
||||
coder = Coder.create(self.GPT35, None, io=InputOutput(), fnames=files)
|
||||
|
||||
def mock_send(*args, **kwargs):
|
||||
coder.partial_response_content = "ok"
|
||||
coder.partial_response_function_call = dict()
|
||||
return []
|
||||
|
||||
coder.send = mock_send
|
||||
|
||||
# Call the run method with a message
|
||||
coder.run(with_message="hi")
|
||||
|
||||
self.assertNotEqual(coder.fence[0], "```")
|
||||
|
||||
def test_run_with_file_utf_unicode_error(self):
|
||||
"make sure that we honor InputOutput(encoding) and don't just assume utf-8"
|
||||
# Create a few temporary files
|
||||
_, file1 = tempfile.mkstemp()
|
||||
_, file2 = tempfile.mkstemp()
|
||||
|
||||
files = [file1, file2]
|
||||
|
||||
encoding = "utf-16"
|
||||
|
||||
# Initialize the Coder object with the mocked IO and mocked repo
|
||||
coder = Coder.create(
|
||||
self.GPT35,
|
||||
None,
|
||||
io=InputOutput(encoding=encoding),
|
||||
fnames=files,
|
||||
)
|
||||
|
||||
def mock_send(*args, **kwargs):
|
||||
coder.partial_response_content = "ok"
|
||||
coder.partial_response_function_call = dict()
|
||||
return []
|
||||
|
||||
coder.send = mock_send
|
||||
|
||||
# Call the run method with a message
|
||||
coder.run(with_message="hi")
|
||||
self.assertEqual(len(coder.abs_fnames), 2)
|
||||
|
||||
some_content_which_will_error_if_read_with_encoding_utf8 = "ÅÍÎÏ".encode(encoding)
|
||||
with open(file1, "wb") as f:
|
||||
f.write(some_content_which_will_error_if_read_with_encoding_utf8)
|
||||
|
||||
coder.run(with_message="hi")
|
||||
|
||||
# both files should still be here
|
||||
self.assertEqual(len(coder.abs_fnames), 2)
|
||||
|
||||
def test_run_with_invalid_request_error(self):
|
||||
with ChdirTemporaryDirectory():
|
||||
# Mock the IO object
|
||||
mock_io = MagicMock()
|
||||
|
||||
# Initialize the Coder object with the mocked IO and mocked repo
|
||||
coder = Coder.create(self.GPT35, None, mock_io)
|
||||
|
||||
# Call the run method and assert that InvalidRequestError is raised
|
||||
with self.assertRaises(openai.BadRequestError):
|
||||
with patch("litellm.completion") as Mock:
|
||||
Mock.side_effect = openai.BadRequestError(
|
||||
message="Invalid request",
|
||||
response=MagicMock(),
|
||||
body=None,
|
||||
)
|
||||
|
||||
coder.run(with_message="hi")
|
||||
|
||||
def test_new_file_edit_one_commit(self):
|
||||
"""A new file shouldn't get pre-committed before the GPT edit commit"""
|
||||
with GitTemporaryDirectory():
|
||||
repo = git.Repo()
|
||||
|
||||
fname = Path("file.txt")
|
||||
|
||||
io = InputOutput(yes=True)
|
||||
coder = Coder.create(self.GPT35, "diff", io=io, fnames=[str(fname)])
|
||||
|
||||
self.assertTrue(fname.exists())
|
||||
|
||||
# make sure it was not committed
|
||||
with self.assertRaises(git.exc.GitCommandError):
|
||||
list(repo.iter_commits(repo.active_branch.name))
|
||||
|
||||
def mock_send(*args, **kwargs):
|
||||
coder.partial_response_content = f"""
|
||||
Do this:
|
||||
|
||||
{str(fname)}
|
||||
<<<<<<< SEARCH
|
||||
=======
|
||||
new
|
||||
>>>>>>> REPLACE
|
||||
|
||||
"""
|
||||
coder.partial_response_function_call = dict()
|
||||
return []
|
||||
|
||||
coder.send = mock_send
|
||||
coder.repo.get_commit_message = MagicMock()
|
||||
coder.repo.get_commit_message.return_value = "commit message"
|
||||
|
||||
coder.run(with_message="hi")
|
||||
|
||||
content = fname.read_text()
|
||||
self.assertEqual(content, "new\n")
|
||||
|
||||
num_commits = len(list(repo.iter_commits(repo.active_branch.name)))
|
||||
self.assertEqual(num_commits, 1)
|
||||
|
||||
def test_only_commit_gpt_edited_file(self):
|
||||
"""
|
||||
Only commit file that gpt edits, not other dirty files.
|
||||
Also ensure commit msg only depends on diffs from the GPT edited file.
|
||||
"""
|
||||
|
||||
with GitTemporaryDirectory():
|
||||
repo = git.Repo()
|
||||
|
||||
fname1 = Path("file1.txt")
|
||||
fname2 = Path("file2.txt")
|
||||
|
||||
fname1.write_text("one\n")
|
||||
fname2.write_text("two\n")
|
||||
|
||||
repo.git.add(str(fname1))
|
||||
repo.git.add(str(fname2))
|
||||
repo.git.commit("-m", "new")
|
||||
|
||||
# DIRTY!
|
||||
fname1.write_text("ONE\n")
|
||||
|
||||
io = InputOutput(yes=True)
|
||||
coder = Coder.create(self.GPT35, "diff", io=io, fnames=[str(fname1), str(fname2)])
|
||||
|
||||
def mock_send(*args, **kwargs):
|
||||
coder.partial_response_content = f"""
|
||||
Do this:
|
||||
|
||||
{str(fname2)}
|
||||
<<<<<<< SEARCH
|
||||
two
|
||||
=======
|
||||
TWO
|
||||
>>>>>>> REPLACE
|
||||
|
||||
"""
|
||||
coder.partial_response_function_call = dict()
|
||||
return []
|
||||
|
||||
def mock_get_commit_message(diffs, context):
|
||||
self.assertNotIn("one", diffs)
|
||||
self.assertNotIn("ONE", diffs)
|
||||
return "commit message"
|
||||
|
||||
coder.send = mock_send
|
||||
coder.repo.get_commit_message = MagicMock(side_effect=mock_get_commit_message)
|
||||
|
||||
coder.run(with_message="hi")
|
||||
|
||||
content = fname2.read_text()
|
||||
self.assertEqual(content, "TWO\n")
|
||||
|
||||
self.assertTrue(repo.is_dirty(path=str(fname1)))
|
||||
|
||||
def test_gpt_edit_to_dirty_file(self):
|
||||
"""A dirty file should be committed before the GPT edits are committed"""
|
||||
|
||||
with GitTemporaryDirectory():
|
||||
repo = git.Repo()
|
||||
|
||||
fname = Path("file.txt")
|
||||
fname.write_text("one\n")
|
||||
repo.git.add(str(fname))
|
||||
|
||||
fname2 = Path("other.txt")
|
||||
fname2.write_text("other\n")
|
||||
repo.git.add(str(fname2))
|
||||
|
||||
repo.git.commit("-m", "new")
|
||||
|
||||
# dirty
|
||||
fname.write_text("two\n")
|
||||
fname2.write_text("OTHER\n")
|
||||
|
||||
io = InputOutput(yes=True)
|
||||
coder = Coder.create(self.GPT35, "diff", io=io, fnames=[str(fname)])
|
||||
|
||||
def mock_send(*args, **kwargs):
|
||||
coder.partial_response_content = f"""
|
||||
Do this:
|
||||
|
||||
{str(fname)}
|
||||
<<<<<<< SEARCH
|
||||
two
|
||||
=======
|
||||
three
|
||||
>>>>>>> REPLACE
|
||||
|
||||
"""
|
||||
coder.partial_response_function_call = dict()
|
||||
return []
|
||||
|
||||
saved_diffs = []
|
||||
|
||||
def mock_get_commit_message(diffs, context):
|
||||
saved_diffs.append(diffs)
|
||||
return "commit message"
|
||||
|
||||
coder.repo.get_commit_message = MagicMock(side_effect=mock_get_commit_message)
|
||||
coder.send = mock_send
|
||||
|
||||
coder.run(with_message="hi")
|
||||
|
||||
content = fname.read_text()
|
||||
self.assertEqual(content, "three\n")
|
||||
|
||||
num_commits = len(list(repo.iter_commits(repo.active_branch.name)))
|
||||
self.assertEqual(num_commits, 3)
|
||||
|
||||
diff = repo.git.diff(["HEAD~2", "HEAD~1"])
|
||||
self.assertIn("one", diff)
|
||||
self.assertIn("two", diff)
|
||||
self.assertNotIn("three", diff)
|
||||
self.assertNotIn("other", diff)
|
||||
self.assertNotIn("OTHER", diff)
|
||||
|
||||
diff = saved_diffs[0]
|
||||
self.assertIn("one", diff)
|
||||
self.assertIn("two", diff)
|
||||
self.assertNotIn("three", diff)
|
||||
self.assertNotIn("other", diff)
|
||||
self.assertNotIn("OTHER", diff)
|
||||
|
||||
diff = repo.git.diff(["HEAD~1", "HEAD"])
|
||||
self.assertNotIn("one", diff)
|
||||
self.assertIn("two", diff)
|
||||
self.assertIn("three", diff)
|
||||
self.assertNotIn("other", diff)
|
||||
self.assertNotIn("OTHER", diff)
|
||||
|
||||
diff = saved_diffs[1]
|
||||
self.assertNotIn("one", diff)
|
||||
self.assertIn("two", diff)
|
||||
self.assertIn("three", diff)
|
||||
self.assertNotIn("other", diff)
|
||||
self.assertNotIn("OTHER", diff)
|
||||
|
||||
self.assertEqual(len(saved_diffs), 2)
|
||||
|
||||
def test_gpt_edit_to_existing_file_not_in_repo(self):
|
||||
with GitTemporaryDirectory():
|
||||
repo = git.Repo()
|
||||
|
||||
fname = Path("file.txt")
|
||||
fname.write_text("one\n")
|
||||
|
||||
fname2 = Path("other.txt")
|
||||
fname2.write_text("other\n")
|
||||
repo.git.add(str(fname2))
|
||||
|
||||
repo.git.commit("-m", "initial")
|
||||
|
||||
io = InputOutput(yes=True)
|
||||
coder = Coder.create(self.GPT35, "diff", io=io, fnames=[str(fname)])
|
||||
|
||||
def mock_send(*args, **kwargs):
|
||||
coder.partial_response_content = f"""
|
||||
Do this:
|
||||
|
||||
{str(fname)}
|
||||
<<<<<<< SEARCH
|
||||
one
|
||||
=======
|
||||
two
|
||||
>>>>>>> REPLACE
|
||||
|
||||
"""
|
||||
coder.partial_response_function_call = dict()
|
||||
return []
|
||||
|
||||
saved_diffs = []
|
||||
|
||||
def mock_get_commit_message(diffs, context):
|
||||
saved_diffs.append(diffs)
|
||||
return "commit message"
|
||||
|
||||
coder.repo.get_commit_message = MagicMock(side_effect=mock_get_commit_message)
|
||||
coder.send = mock_send
|
||||
|
||||
coder.run(with_message="hi")
|
||||
|
||||
content = fname.read_text()
|
||||
self.assertEqual(content, "two\n")
|
||||
|
||||
diff = saved_diffs[0]
|
||||
self.assertIn("file.txt", diff)
|
||||
|
||||
def test_skip_aiderignored_files(self):
|
||||
with GitTemporaryDirectory():
|
||||
repo = git.Repo()
|
||||
|
||||
fname1 = "ignoreme1.txt"
|
||||
fname2 = "ignoreme2.txt"
|
||||
fname3 = "dir/ignoreme3.txt"
|
||||
|
||||
Path(fname2).touch()
|
||||
repo.git.add(str(fname2))
|
||||
repo.git.commit("-m", "initial")
|
||||
|
||||
aignore = Path(".aiderignore")
|
||||
aignore.write_text(f"{fname1}\n{fname2}\ndir\n")
|
||||
|
||||
io = InputOutput(yes=True)
|
||||
coder = Coder.create(
|
||||
self.GPT35,
|
||||
None,
|
||||
io,
|
||||
fnames=[fname1, fname2, fname3],
|
||||
aider_ignore_file=str(aignore),
|
||||
)
|
||||
|
||||
self.assertNotIn(fname1, str(coder.abs_fnames))
|
||||
self.assertNotIn(fname2, str(coder.abs_fnames))
|
||||
self.assertNotIn(fname3, str(coder.abs_fnames))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
|
@ -1,588 +0,0 @@
|
|||
import codecs
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
from io import StringIO
|
||||
from pathlib import Path
|
||||
from unittest import TestCase
|
||||
|
||||
import git
|
||||
|
||||
from aider.coders import Coder
|
||||
from aider.commands import Commands
|
||||
from aider.dump import dump # noqa: F401
|
||||
from aider.io import InputOutput
|
||||
from aider.models import Model
|
||||
from aider.utils import ChdirTemporaryDirectory, GitTemporaryDirectory, make_repo
|
||||
|
||||
|
||||
class TestCommands(TestCase):
|
||||
def setUp(self):
|
||||
self.original_cwd = os.getcwd()
|
||||
self.tempdir = tempfile.mkdtemp()
|
||||
os.chdir(self.tempdir)
|
||||
|
||||
self.GPT35 = Model("gpt-3.5-turbo")
|
||||
|
||||
def tearDown(self):
|
||||
os.chdir(self.original_cwd)
|
||||
shutil.rmtree(self.tempdir, ignore_errors=True)
|
||||
|
||||
def test_cmd_add(self):
|
||||
# Initialize the Commands and InputOutput objects
|
||||
io = InputOutput(pretty=False, yes=True)
|
||||
from aider.coders import Coder
|
||||
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
commands = Commands(io, coder)
|
||||
|
||||
# Call the cmd_add method with 'foo.txt' and 'bar.txt' as a single string
|
||||
commands.cmd_add("foo.txt bar.txt")
|
||||
|
||||
# Check if both files have been created in the temporary directory
|
||||
self.assertTrue(os.path.exists("foo.txt"))
|
||||
self.assertTrue(os.path.exists("bar.txt"))
|
||||
|
||||
def test_cmd_add_bad_glob(self):
|
||||
# https://github.com/paul-gauthier/aider/issues/293
|
||||
|
||||
io = InputOutput(pretty=False, yes=False)
|
||||
from aider.coders import Coder
|
||||
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
commands = Commands(io, coder)
|
||||
|
||||
commands.cmd_add("**.txt")
|
||||
|
||||
def test_cmd_add_with_glob_patterns(self):
|
||||
# Initialize the Commands and InputOutput objects
|
||||
io = InputOutput(pretty=False, yes=True)
|
||||
from aider.coders import Coder
|
||||
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
commands = Commands(io, coder)
|
||||
|
||||
# Create some test files
|
||||
with open("test1.py", "w") as f:
|
||||
f.write("print('test1')")
|
||||
with open("test2.py", "w") as f:
|
||||
f.write("print('test2')")
|
||||
with open("test.txt", "w") as f:
|
||||
f.write("test")
|
||||
|
||||
# Call the cmd_add method with a glob pattern
|
||||
commands.cmd_add("*.py")
|
||||
|
||||
# Check if the Python files have been added to the chat session
|
||||
self.assertIn(str(Path("test1.py").resolve()), coder.abs_fnames)
|
||||
self.assertIn(str(Path("test2.py").resolve()), coder.abs_fnames)
|
||||
|
||||
# Check if the text file has not been added to the chat session
|
||||
self.assertNotIn(str(Path("test.txt").resolve()), coder.abs_fnames)
|
||||
|
||||
def test_cmd_add_no_match(self):
|
||||
# yes=False means we will *not* create the file when it is not found
|
||||
io = InputOutput(pretty=False, yes=False)
|
||||
from aider.coders import Coder
|
||||
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
commands = Commands(io, coder)
|
||||
|
||||
# Call the cmd_add method with a non-existent file pattern
|
||||
commands.cmd_add("*.nonexistent")
|
||||
|
||||
# Check if no files have been added to the chat session
|
||||
self.assertEqual(len(coder.abs_fnames), 0)
|
||||
|
||||
def test_cmd_add_no_match_but_make_it(self):
|
||||
# yes=True means we *will* create the file when it is not found
|
||||
io = InputOutput(pretty=False, yes=True)
|
||||
from aider.coders import Coder
|
||||
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
commands = Commands(io, coder)
|
||||
|
||||
fname = Path("[abc].nonexistent")
|
||||
|
||||
# Call the cmd_add method with a non-existent file pattern
|
||||
commands.cmd_add(str(fname))
|
||||
|
||||
# Check if no files have been added to the chat session
|
||||
self.assertEqual(len(coder.abs_fnames), 1)
|
||||
self.assertTrue(fname.exists())
|
||||
|
||||
def test_cmd_add_drop_directory(self):
|
||||
# Initialize the Commands and InputOutput objects
|
||||
io = InputOutput(pretty=False, yes=False)
|
||||
from aider.coders import Coder
|
||||
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
commands = Commands(io, coder)
|
||||
|
||||
# Create a directory and add files to it using pathlib
|
||||
Path("test_dir").mkdir()
|
||||
Path("test_dir/another_dir").mkdir()
|
||||
Path("test_dir/test_file1.txt").write_text("Test file 1")
|
||||
Path("test_dir/test_file2.txt").write_text("Test file 2")
|
||||
Path("test_dir/another_dir/test_file.txt").write_text("Test file 3")
|
||||
|
||||
# Call the cmd_add method with a directory
|
||||
commands.cmd_add("test_dir test_dir/test_file2.txt")
|
||||
|
||||
# Check if the files have been added to the chat session
|
||||
self.assertIn(str(Path("test_dir/test_file1.txt").resolve()), coder.abs_fnames)
|
||||
self.assertIn(str(Path("test_dir/test_file2.txt").resolve()), coder.abs_fnames)
|
||||
self.assertIn(str(Path("test_dir/another_dir/test_file.txt").resolve()), coder.abs_fnames)
|
||||
|
||||
commands.cmd_drop("test_dir/another_dir")
|
||||
self.assertIn(str(Path("test_dir/test_file1.txt").resolve()), coder.abs_fnames)
|
||||
self.assertIn(str(Path("test_dir/test_file2.txt").resolve()), coder.abs_fnames)
|
||||
self.assertNotIn(
|
||||
str(Path("test_dir/another_dir/test_file.txt").resolve()), coder.abs_fnames
|
||||
)
|
||||
|
||||
# Issue #139 /add problems when cwd != git_root
|
||||
|
||||
# remember the proper abs path to this file
|
||||
abs_fname = str(Path("test_dir/another_dir/test_file.txt").resolve())
|
||||
|
||||
# chdir to someplace other than git_root
|
||||
Path("side_dir").mkdir()
|
||||
os.chdir("side_dir")
|
||||
|
||||
# add it via it's git_root referenced name
|
||||
commands.cmd_add("test_dir/another_dir/test_file.txt")
|
||||
|
||||
# it should be there, but was not in v0.10.0
|
||||
self.assertIn(abs_fname, coder.abs_fnames)
|
||||
|
||||
# drop it via it's git_root referenced name
|
||||
commands.cmd_drop("test_dir/another_dir/test_file.txt")
|
||||
|
||||
# it should be there, but was not in v0.10.0
|
||||
self.assertNotIn(abs_fname, coder.abs_fnames)
|
||||
|
||||
def test_cmd_drop_with_glob_patterns(self):
|
||||
# Initialize the Commands and InputOutput objects
|
||||
io = InputOutput(pretty=False, yes=True)
|
||||
from aider.coders import Coder
|
||||
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
commands = Commands(io, coder)
|
||||
|
||||
subdir = Path("subdir")
|
||||
subdir.mkdir()
|
||||
(subdir / "subtest1.py").touch()
|
||||
(subdir / "subtest2.py").touch()
|
||||
|
||||
Path("test1.py").touch()
|
||||
Path("test2.py").touch()
|
||||
|
||||
# Add some files to the chat session
|
||||
commands.cmd_add("*.py")
|
||||
|
||||
self.assertEqual(len(coder.abs_fnames), 2)
|
||||
|
||||
# Call the cmd_drop method with a glob pattern
|
||||
commands.cmd_drop("*2.py")
|
||||
|
||||
self.assertIn(str(Path("test1.py").resolve()), coder.abs_fnames)
|
||||
self.assertNotIn(str(Path("test2.py").resolve()), coder.abs_fnames)
|
||||
|
||||
def test_cmd_add_bad_encoding(self):
|
||||
# Initialize the Commands and InputOutput objects
|
||||
io = InputOutput(pretty=False, yes=True)
|
||||
from aider.coders import Coder
|
||||
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
commands = Commands(io, coder)
|
||||
|
||||
# Create a new file foo.bad which will fail to decode as utf-8
|
||||
with codecs.open("foo.bad", "w", encoding="iso-8859-15") as f:
|
||||
f.write("ÆØÅ") # Characters not present in utf-8
|
||||
|
||||
commands.cmd_add("foo.bad")
|
||||
|
||||
self.assertEqual(coder.abs_fnames, set())
|
||||
|
||||
def test_cmd_git(self):
|
||||
# Initialize the Commands and InputOutput objects
|
||||
io = InputOutput(pretty=False, yes=True)
|
||||
|
||||
with GitTemporaryDirectory() as tempdir:
|
||||
# Create a file in the temporary directory
|
||||
with open(f"{tempdir}/test.txt", "w") as f:
|
||||
f.write("test")
|
||||
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
commands = Commands(io, coder)
|
||||
|
||||
# Run the cmd_git method with the arguments "commit -a -m msg"
|
||||
commands.cmd_git("add test.txt")
|
||||
commands.cmd_git("commit -a -m msg")
|
||||
|
||||
# Check if the file has been committed to the repository
|
||||
repo = git.Repo(tempdir)
|
||||
files_in_repo = repo.git.ls_files()
|
||||
self.assertIn("test.txt", files_in_repo)
|
||||
|
||||
def test_cmd_tokens(self):
|
||||
# Initialize the Commands and InputOutput objects
|
||||
io = InputOutput(pretty=False, yes=True)
|
||||
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
commands = Commands(io, coder)
|
||||
|
||||
commands.cmd_add("foo.txt bar.txt")
|
||||
|
||||
# Redirect the standard output to an instance of io.StringIO
|
||||
stdout = StringIO()
|
||||
sys.stdout = stdout
|
||||
|
||||
commands.cmd_tokens("")
|
||||
|
||||
# Reset the standard output
|
||||
sys.stdout = sys.__stdout__
|
||||
|
||||
# Get the console output
|
||||
console_output = stdout.getvalue()
|
||||
|
||||
self.assertIn("foo.txt", console_output)
|
||||
self.assertIn("bar.txt", console_output)
|
||||
|
||||
def test_cmd_add_from_subdir(self):
|
||||
repo = git.Repo.init()
|
||||
repo.config_writer().set_value("user", "name", "Test User").release()
|
||||
repo.config_writer().set_value("user", "email", "testuser@example.com").release()
|
||||
|
||||
# Create three empty files and add them to the git repository
|
||||
filenames = ["one.py", Path("subdir") / "two.py", Path("anotherdir") / "three.py"]
|
||||
for filename in filenames:
|
||||
file_path = Path(filename)
|
||||
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
file_path.touch()
|
||||
repo.git.add(str(file_path))
|
||||
repo.git.commit("-m", "added")
|
||||
|
||||
filenames = [str(Path(fn).resolve()) for fn in filenames]
|
||||
|
||||
###
|
||||
|
||||
os.chdir("subdir")
|
||||
|
||||
io = InputOutput(pretty=False, yes=True)
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
commands = Commands(io, coder)
|
||||
|
||||
# this should get added
|
||||
commands.cmd_add(str(Path("anotherdir") / "three.py"))
|
||||
|
||||
# this should add one.py
|
||||
commands.cmd_add("*.py")
|
||||
|
||||
self.assertIn(filenames[0], coder.abs_fnames)
|
||||
self.assertNotIn(filenames[1], coder.abs_fnames)
|
||||
self.assertIn(filenames[2], coder.abs_fnames)
|
||||
|
||||
def test_cmd_add_from_subdir_again(self):
|
||||
with GitTemporaryDirectory():
|
||||
io = InputOutput(pretty=False, yes=False)
|
||||
from aider.coders import Coder
|
||||
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
commands = Commands(io, coder)
|
||||
|
||||
Path("side_dir").mkdir()
|
||||
os.chdir("side_dir")
|
||||
|
||||
# add a file that is in the side_dir
|
||||
with open("temp.txt", "w"):
|
||||
pass
|
||||
|
||||
# this was blowing up with GitCommandError, per:
|
||||
# https://github.com/paul-gauthier/aider/issues/201
|
||||
commands.cmd_add("temp.txt")
|
||||
|
||||
def test_cmd_commit(self):
|
||||
with GitTemporaryDirectory():
|
||||
fname = "test.txt"
|
||||
with open(fname, "w") as f:
|
||||
f.write("test")
|
||||
repo = git.Repo()
|
||||
repo.git.add(fname)
|
||||
repo.git.commit("-m", "initial")
|
||||
|
||||
io = InputOutput(pretty=False, yes=True)
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
commands = Commands(io, coder)
|
||||
|
||||
self.assertFalse(repo.is_dirty())
|
||||
with open(fname, "w") as f:
|
||||
f.write("new")
|
||||
self.assertTrue(repo.is_dirty())
|
||||
|
||||
commit_message = "Test commit message"
|
||||
commands.cmd_commit(commit_message)
|
||||
self.assertFalse(repo.is_dirty())
|
||||
|
||||
def test_cmd_add_from_outside_root(self):
|
||||
with ChdirTemporaryDirectory() as tmp_dname:
|
||||
root = Path("root")
|
||||
root.mkdir()
|
||||
os.chdir(str(root))
|
||||
|
||||
io = InputOutput(pretty=False, yes=False)
|
||||
from aider.coders import Coder
|
||||
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
commands = Commands(io, coder)
|
||||
|
||||
outside_file = Path(tmp_dname) / "outside.txt"
|
||||
outside_file.touch()
|
||||
|
||||
# This should not be allowed!
|
||||
# https://github.com/paul-gauthier/aider/issues/178
|
||||
commands.cmd_add("../outside.txt")
|
||||
|
||||
self.assertEqual(len(coder.abs_fnames), 0)
|
||||
|
||||
def test_cmd_add_from_outside_git(self):
|
||||
with ChdirTemporaryDirectory() as tmp_dname:
|
||||
root = Path("root")
|
||||
root.mkdir()
|
||||
os.chdir(str(root))
|
||||
|
||||
make_repo()
|
||||
|
||||
io = InputOutput(pretty=False, yes=False)
|
||||
from aider.coders import Coder
|
||||
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
commands = Commands(io, coder)
|
||||
|
||||
outside_file = Path(tmp_dname) / "outside.txt"
|
||||
outside_file.touch()
|
||||
|
||||
# This should not be allowed!
|
||||
# It was blowing up with GitCommandError, per:
|
||||
# https://github.com/paul-gauthier/aider/issues/178
|
||||
commands.cmd_add("../outside.txt")
|
||||
|
||||
self.assertEqual(len(coder.abs_fnames), 0)
|
||||
|
||||
def test_cmd_add_filename_with_special_chars(self):
|
||||
with ChdirTemporaryDirectory():
|
||||
io = InputOutput(pretty=False, yes=False)
|
||||
from aider.coders import Coder
|
||||
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
commands = Commands(io, coder)
|
||||
|
||||
fname = Path("with[brackets].txt")
|
||||
fname.touch()
|
||||
|
||||
commands.cmd_add(str(fname))
|
||||
|
||||
self.assertIn(str(fname.resolve()), coder.abs_fnames)
|
||||
|
||||
def test_cmd_add_dirname_with_special_chars(self):
|
||||
with ChdirTemporaryDirectory():
|
||||
io = InputOutput(pretty=False, yes=False)
|
||||
from aider.coders import Coder
|
||||
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
commands = Commands(io, coder)
|
||||
|
||||
dname = Path("with[brackets]")
|
||||
dname.mkdir()
|
||||
fname = dname / "filename.txt"
|
||||
fname.touch()
|
||||
|
||||
commands.cmd_add(str(dname))
|
||||
|
||||
self.assertIn(str(fname.resolve()), coder.abs_fnames)
|
||||
|
||||
def test_cmd_add_abs_filename(self):
|
||||
with ChdirTemporaryDirectory():
|
||||
io = InputOutput(pretty=False, yes=False)
|
||||
from aider.coders import Coder
|
||||
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
commands = Commands(io, coder)
|
||||
|
||||
fname = Path("file.txt")
|
||||
fname.touch()
|
||||
|
||||
commands.cmd_add(str(fname.resolve()))
|
||||
|
||||
self.assertIn(str(fname.resolve()), coder.abs_fnames)
|
||||
|
||||
def test_cmd_add_quoted_filename(self):
|
||||
with ChdirTemporaryDirectory():
|
||||
io = InputOutput(pretty=False, yes=False)
|
||||
from aider.coders import Coder
|
||||
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
commands = Commands(io, coder)
|
||||
|
||||
fname = Path("file with spaces.txt")
|
||||
fname.touch()
|
||||
|
||||
commands.cmd_add(f'"{fname}"')
|
||||
|
||||
self.assertIn(str(fname.resolve()), coder.abs_fnames)
|
||||
|
||||
def test_cmd_add_existing_with_dirty_repo(self):
|
||||
with GitTemporaryDirectory():
|
||||
repo = git.Repo()
|
||||
|
||||
files = ["one.txt", "two.txt"]
|
||||
for fname in files:
|
||||
Path(fname).touch()
|
||||
repo.git.add(fname)
|
||||
repo.git.commit("-m", "initial")
|
||||
|
||||
commit = repo.head.commit.hexsha
|
||||
|
||||
# leave a dirty `git rm`
|
||||
repo.git.rm("one.txt")
|
||||
|
||||
io = InputOutput(pretty=False, yes=True)
|
||||
from aider.coders import Coder
|
||||
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
commands = Commands(io, coder)
|
||||
|
||||
# There's no reason this /add should trigger a commit
|
||||
commands.cmd_add("two.txt")
|
||||
|
||||
self.assertEqual(commit, repo.head.commit.hexsha)
|
||||
|
||||
# Windows is throwing:
|
||||
# PermissionError: [WinError 32] The process cannot access
|
||||
# the file because it is being used by another process
|
||||
|
||||
repo.git.commit("-m", "cleanup")
|
||||
|
||||
del coder
|
||||
del commands
|
||||
del repo
|
||||
|
||||
def test_cmd_add_unicode_error(self):
|
||||
# Initialize the Commands and InputOutput objects
|
||||
io = InputOutput(pretty=False, yes=True)
|
||||
from aider.coders import Coder
|
||||
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
commands = Commands(io, coder)
|
||||
|
||||
fname = "file.txt"
|
||||
encoding = "utf-16"
|
||||
some_content_which_will_error_if_read_with_encoding_utf8 = "ÅÍÎÏ".encode(encoding)
|
||||
with open(fname, "wb") as f:
|
||||
f.write(some_content_which_will_error_if_read_with_encoding_utf8)
|
||||
|
||||
commands.cmd_add("file.txt")
|
||||
self.assertEqual(coder.abs_fnames, set())
|
||||
|
||||
def test_cmd_add_drop_untracked_files(self):
|
||||
with GitTemporaryDirectory():
|
||||
repo = git.Repo()
|
||||
|
||||
io = InputOutput(pretty=False, yes=False)
|
||||
from aider.coders import Coder
|
||||
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
commands = Commands(io, coder)
|
||||
|
||||
fname = Path("test.txt")
|
||||
fname.touch()
|
||||
|
||||
self.assertEqual(len(coder.abs_fnames), 0)
|
||||
|
||||
commands.cmd_add(str(fname))
|
||||
|
||||
files_in_repo = repo.git.ls_files()
|
||||
self.assertNotIn(str(fname), files_in_repo)
|
||||
|
||||
self.assertEqual(len(coder.abs_fnames), 1)
|
||||
|
||||
commands.cmd_drop(str(fname))
|
||||
|
||||
self.assertEqual(len(coder.abs_fnames), 0)
|
||||
|
||||
def test_cmd_undo_with_dirty_files_not_in_last_commit(self):
|
||||
with GitTemporaryDirectory() as repo_dir:
|
||||
repo = git.Repo(repo_dir)
|
||||
io = InputOutput(pretty=False, yes=True)
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
commands = Commands(io, coder)
|
||||
|
||||
other_path = Path(repo_dir) / "other_file.txt"
|
||||
other_path.write_text("other content")
|
||||
repo.git.add(str(other_path))
|
||||
|
||||
# Create and commit a file
|
||||
filename = "test_file.txt"
|
||||
file_path = Path(repo_dir) / filename
|
||||
file_path.write_text("first content")
|
||||
repo.git.add(filename)
|
||||
repo.git.commit("-m", "aider: first commit")
|
||||
|
||||
file_path.write_text("second content")
|
||||
repo.git.add(filename)
|
||||
repo.git.commit("-m", "aider: second commit")
|
||||
|
||||
# Store the commit hash
|
||||
last_commit_hash = repo.head.commit.hexsha[:7]
|
||||
coder.last_aider_commit_hash = last_commit_hash
|
||||
|
||||
file_path.write_text("dirty content")
|
||||
|
||||
# Attempt to undo the last commit
|
||||
commands.cmd_undo("")
|
||||
|
||||
# Check that the last commit is still present
|
||||
self.assertEqual(last_commit_hash, repo.head.commit.hexsha[:7])
|
||||
|
||||
# Put back the initial content (so it's not dirty now)
|
||||
file_path.write_text("second content")
|
||||
other_path.write_text("dirty content")
|
||||
|
||||
commands.cmd_undo("")
|
||||
self.assertNotEqual(last_commit_hash, repo.head.commit.hexsha[:7])
|
||||
|
||||
self.assertEqual(file_path.read_text(), "first content")
|
||||
self.assertEqual(other_path.read_text(), "dirty content")
|
||||
|
||||
del coder
|
||||
del commands
|
||||
del repo
|
||||
|
||||
def test_cmd_add_aiderignored_file(self):
|
||||
with GitTemporaryDirectory():
|
||||
repo = git.Repo()
|
||||
|
||||
fname1 = "ignoreme1.txt"
|
||||
fname2 = "ignoreme2.txt"
|
||||
fname3 = "dir/ignoreme3.txt"
|
||||
|
||||
Path(fname2).touch()
|
||||
repo.git.add(str(fname2))
|
||||
repo.git.commit("-m", "initial")
|
||||
|
||||
aignore = Path(".aiderignore")
|
||||
aignore.write_text(f"{fname1}\n{fname2}\ndir\n")
|
||||
|
||||
io = InputOutput(yes=True)
|
||||
coder = Coder.create(
|
||||
self.GPT35, None, io, fnames=[fname1, fname2], aider_ignore_file=str(aignore)
|
||||
)
|
||||
commands = Commands(io, coder)
|
||||
|
||||
commands.cmd_add(f"{fname1} {fname2} {fname3}")
|
||||
|
||||
self.assertNotIn(fname1, str(coder.abs_fnames))
|
||||
self.assertNotIn(fname2, str(coder.abs_fnames))
|
||||
self.assertNotIn(fname3, str(coder.abs_fnames))
|
|
@ -1,403 +0,0 @@
|
|||
# flake8: noqa: E501
|
||||
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from aider.coders import Coder
|
||||
from aider.coders import editblock_coder as eb
|
||||
from aider.dump import dump # noqa: F401
|
||||
from aider.io import InputOutput
|
||||
from aider.models import Model
|
||||
|
||||
|
||||
class TestUtils(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.GPT35 = Model("gpt-3.5-turbo")
|
||||
|
||||
# fuzzy logic disabled v0.11.2-dev
|
||||
def __test_replace_most_similar_chunk(self):
|
||||
whole = "This is a sample text.\nAnother line of text.\nYet another line.\n"
|
||||
part = "This is a sample text\n"
|
||||
replace = "This is a replaced text.\n"
|
||||
expected_output = "This is a replaced text.\nAnother line of text.\nYet another line.\n"
|
||||
|
||||
result = eb.replace_most_similar_chunk(whole, part, replace)
|
||||
self.assertEqual(result, expected_output)
|
||||
|
||||
# fuzzy logic disabled v0.11.2-dev
|
||||
def __test_replace_most_similar_chunk_not_perfect_match(self):
|
||||
whole = "This is a sample text.\nAnother line of text.\nYet another line.\n"
|
||||
part = "This was a sample text.\nAnother line of txt\n"
|
||||
replace = "This is a replaced text.\nModified line of text.\n"
|
||||
expected_output = "This is a replaced text.\nModified line of text.\nYet another line.\n"
|
||||
|
||||
result = eb.replace_most_similar_chunk(whole, part, replace)
|
||||
self.assertEqual(result, expected_output)
|
||||
|
||||
def test_strip_quoted_wrapping(self):
|
||||
input_text = (
|
||||
"filename.ext\n```\nWe just want this content\nNot the filename and triple quotes\n```"
|
||||
)
|
||||
expected_output = "We just want this content\nNot the filename and triple quotes\n"
|
||||
result = eb.strip_quoted_wrapping(input_text, "filename.ext")
|
||||
self.assertEqual(result, expected_output)
|
||||
|
||||
def test_strip_quoted_wrapping_no_filename(self):
|
||||
input_text = "```\nWe just want this content\nNot the triple quotes\n```"
|
||||
expected_output = "We just want this content\nNot the triple quotes\n"
|
||||
result = eb.strip_quoted_wrapping(input_text)
|
||||
self.assertEqual(result, expected_output)
|
||||
|
||||
def test_strip_quoted_wrapping_no_wrapping(self):
|
||||
input_text = "We just want this content\nNot the triple quotes\n"
|
||||
expected_output = "We just want this content\nNot the triple quotes\n"
|
||||
result = eb.strip_quoted_wrapping(input_text)
|
||||
self.assertEqual(result, expected_output)
|
||||
|
||||
def test_find_original_update_blocks(self):
|
||||
edit = """
|
||||
Here's the change:
|
||||
|
||||
```text
|
||||
foo.txt
|
||||
<<<<<<< SEARCH
|
||||
Two
|
||||
=======
|
||||
Tooooo
|
||||
>>>>>>> REPLACE
|
||||
```
|
||||
|
||||
Hope you like it!
|
||||
"""
|
||||
|
||||
edits = list(eb.find_original_update_blocks(edit))
|
||||
self.assertEqual(edits, [("foo.txt", "Two\n", "Tooooo\n")])
|
||||
|
||||
def test_find_original_update_blocks_mangled_filename_w_source_tag(self):
|
||||
source = "source"
|
||||
|
||||
edit = """
|
||||
Here's the change:
|
||||
|
||||
<%s>foo.txt
|
||||
<<<<<<< SEARCH
|
||||
One
|
||||
=======
|
||||
Two
|
||||
>>>>>>> REPLACE
|
||||
</%s>
|
||||
|
||||
Hope you like it!
|
||||
""" % (source, source)
|
||||
|
||||
fence = ("<%s>" % source, "</%s>" % source)
|
||||
|
||||
with self.assertRaises(ValueError) as cm:
|
||||
_edits = list(eb.find_original_update_blocks(edit, fence))
|
||||
self.assertIn("missing filename", str(cm.exception))
|
||||
|
||||
def test_find_original_update_blocks_quote_below_filename(self):
|
||||
edit = """
|
||||
Here's the change:
|
||||
|
||||
foo.txt
|
||||
```text
|
||||
<<<<<<< SEARCH
|
||||
Two
|
||||
=======
|
||||
Tooooo
|
||||
>>>>>>> REPLACE
|
||||
```
|
||||
|
||||
Hope you like it!
|
||||
"""
|
||||
|
||||
edits = list(eb.find_original_update_blocks(edit))
|
||||
self.assertEqual(edits, [("foo.txt", "Two\n", "Tooooo\n")])
|
||||
|
||||
def test_find_original_update_blocks_unclosed(self):
|
||||
edit = """
|
||||
Here's the change:
|
||||
|
||||
```text
|
||||
foo.txt
|
||||
<<<<<<< SEARCH
|
||||
Two
|
||||
=======
|
||||
Tooooo
|
||||
|
||||
|
||||
oops!
|
||||
"""
|
||||
|
||||
with self.assertRaises(ValueError) as cm:
|
||||
list(eb.find_original_update_blocks(edit))
|
||||
self.assertIn("Incomplete", str(cm.exception))
|
||||
|
||||
def test_find_original_update_blocks_missing_filename(self):
|
||||
edit = """
|
||||
Here's the change:
|
||||
|
||||
```text
|
||||
<<<<<<< SEARCH
|
||||
Two
|
||||
=======
|
||||
Tooooo
|
||||
|
||||
|
||||
oops!
|
||||
"""
|
||||
|
||||
with self.assertRaises(ValueError) as cm:
|
||||
list(eb.find_original_update_blocks(edit))
|
||||
self.assertIn("filename", str(cm.exception))
|
||||
|
||||
def test_find_original_update_blocks_no_final_newline(self):
|
||||
edit = """
|
||||
aider/coder.py
|
||||
<<<<<<< SEARCH
|
||||
self.console.print("[red]^C again to quit")
|
||||
=======
|
||||
self.io.tool_error("^C again to quit")
|
||||
>>>>>>> REPLACE
|
||||
|
||||
aider/coder.py
|
||||
<<<<<<< SEARCH
|
||||
self.io.tool_error("Malformed ORIGINAL/UPDATE blocks, retrying...")
|
||||
self.io.tool_error(err)
|
||||
=======
|
||||
self.io.tool_error("Malformed ORIGINAL/UPDATE blocks, retrying...")
|
||||
self.io.tool_error(str(err))
|
||||
>>>>>>> REPLACE
|
||||
|
||||
aider/coder.py
|
||||
<<<<<<< SEARCH
|
||||
self.console.print("[red]Unable to get commit message from gpt-3.5-turbo. Use /commit to try again.\n")
|
||||
=======
|
||||
self.io.tool_error("Unable to get commit message from gpt-3.5-turbo. Use /commit to try again.")
|
||||
>>>>>>> REPLACE
|
||||
|
||||
aider/coder.py
|
||||
<<<<<<< SEARCH
|
||||
self.console.print("[red]Skipped commmit.")
|
||||
=======
|
||||
self.io.tool_error("Skipped commmit.")
|
||||
>>>>>>> REPLACE"""
|
||||
|
||||
# Should not raise a ValueError
|
||||
list(eb.find_original_update_blocks(edit))
|
||||
|
||||
def test_incomplete_edit_block_missing_filename(self):
|
||||
edit = """
|
||||
No problem! Here are the changes to patch `subprocess.check_output` instead of `subprocess.run` in both tests:
|
||||
|
||||
```python
|
||||
tests/test_repomap.py
|
||||
<<<<<<< SEARCH
|
||||
def test_check_for_ctags_failure(self):
|
||||
with patch("subprocess.run") as mock_run:
|
||||
mock_run.side_effect = Exception("ctags not found")
|
||||
=======
|
||||
def test_check_for_ctags_failure(self):
|
||||
with patch("subprocess.check_output") as mock_check_output:
|
||||
mock_check_output.side_effect = Exception("ctags not found")
|
||||
>>>>>>> REPLACE
|
||||
|
||||
<<<<<<< SEARCH
|
||||
def test_check_for_ctags_success(self):
|
||||
with patch("subprocess.run") as mock_run:
|
||||
mock_run.return_value = CompletedProcess(args=["ctags", "--version"], returncode=0, stdout='''{
|
||||
"_type": "tag",
|
||||
"name": "status",
|
||||
"path": "aider/main.py",
|
||||
"pattern": "/^ status = main()$/",
|
||||
"kind": "variable"
|
||||
}''')
|
||||
=======
|
||||
def test_check_for_ctags_success(self):
|
||||
with patch("subprocess.check_output") as mock_check_output:
|
||||
mock_check_output.return_value = '''{
|
||||
"_type": "tag",
|
||||
"name": "status",
|
||||
"path": "aider/main.py",
|
||||
"pattern": "/^ status = main()$/",
|
||||
"kind": "variable"
|
||||
}'''
|
||||
>>>>>>> REPLACE
|
||||
```
|
||||
|
||||
These changes replace the `subprocess.run` patches with `subprocess.check_output` patches in both `test_check_for_ctags_failure` and `test_check_for_ctags_success` tests.
|
||||
"""
|
||||
edit_blocks = list(eb.find_original_update_blocks(edit))
|
||||
self.assertEqual(len(edit_blocks), 2) # 2 edits
|
||||
self.assertEqual(edit_blocks[0][0], "tests/test_repomap.py")
|
||||
self.assertEqual(edit_blocks[1][0], "tests/test_repomap.py")
|
||||
|
||||
def test_replace_part_with_missing_varied_leading_whitespace(self):
|
||||
whole = """
|
||||
line1
|
||||
line2
|
||||
line3
|
||||
line4
|
||||
"""
|
||||
|
||||
part = "line2\n line3\n"
|
||||
replace = "new_line2\n new_line3\n"
|
||||
expected_output = """
|
||||
line1
|
||||
new_line2
|
||||
new_line3
|
||||
line4
|
||||
"""
|
||||
|
||||
result = eb.replace_most_similar_chunk(whole, part, replace)
|
||||
self.assertEqual(result, expected_output)
|
||||
|
||||
def test_replace_part_with_missing_leading_whitespace(self):
|
||||
whole = " line1\n line2\n line3\n"
|
||||
part = "line1\nline2\n"
|
||||
replace = "new_line1\nnew_line2\n"
|
||||
expected_output = " new_line1\n new_line2\n line3\n"
|
||||
|
||||
result = eb.replace_most_similar_chunk(whole, part, replace)
|
||||
self.assertEqual(result, expected_output)
|
||||
|
||||
def test_replace_part_with_just_some_missing_leading_whitespace(self):
|
||||
whole = " line1\n line2\n line3\n"
|
||||
part = " line1\n line2\n"
|
||||
replace = " new_line1\n new_line2\n"
|
||||
expected_output = " new_line1\n new_line2\n line3\n"
|
||||
|
||||
result = eb.replace_most_similar_chunk(whole, part, replace)
|
||||
self.assertEqual(result, expected_output)
|
||||
|
||||
def test_replace_part_with_missing_leading_whitespace_including_blank_line(self):
|
||||
"""
|
||||
The part has leading whitespace on all lines, so should be ignored.
|
||||
But it has a *blank* line with no whitespace at all, which was causing a
|
||||
bug per issue #25. Test case to repro and confirm fix.
|
||||
"""
|
||||
whole = " line1\n line2\n line3\n"
|
||||
part = "\n line1\n line2\n"
|
||||
replace = " new_line1\n new_line2\n"
|
||||
expected_output = " new_line1\n new_line2\n line3\n"
|
||||
|
||||
result = eb.replace_most_similar_chunk(whole, part, replace)
|
||||
self.assertEqual(result, expected_output)
|
||||
|
||||
def test_full_edit(self):
|
||||
# Create a few temporary files
|
||||
_, file1 = tempfile.mkstemp()
|
||||
|
||||
with open(file1, "w", encoding="utf-8") as f:
|
||||
f.write("one\ntwo\nthree\n")
|
||||
|
||||
files = [file1]
|
||||
|
||||
# Initialize the Coder object with the mocked IO and mocked repo
|
||||
coder = Coder.create(self.GPT35, "diff", io=InputOutput(), fnames=files)
|
||||
|
||||
def mock_send(*args, **kwargs):
|
||||
coder.partial_response_content = f"""
|
||||
Do this:
|
||||
|
||||
{Path(file1).name}
|
||||
<<<<<<< SEARCH
|
||||
two
|
||||
=======
|
||||
new
|
||||
>>>>>>> REPLACE
|
||||
|
||||
"""
|
||||
coder.partial_response_function_call = dict()
|
||||
return []
|
||||
|
||||
coder.send = mock_send
|
||||
|
||||
# Call the run method with a message
|
||||
coder.run(with_message="hi")
|
||||
|
||||
content = Path(file1).read_text(encoding="utf-8")
|
||||
self.assertEqual(content, "one\nnew\nthree\n")
|
||||
|
||||
def test_full_edit_dry_run(self):
|
||||
# Create a few temporary files
|
||||
_, file1 = tempfile.mkstemp()
|
||||
|
||||
orig_content = "one\ntwo\nthree\n"
|
||||
|
||||
with open(file1, "w", encoding="utf-8") as f:
|
||||
f.write(orig_content)
|
||||
|
||||
files = [file1]
|
||||
|
||||
# Initialize the Coder object with the mocked IO and mocked repo
|
||||
coder = Coder.create(
|
||||
self.GPT35,
|
||||
"diff",
|
||||
io=InputOutput(dry_run=True),
|
||||
fnames=files,
|
||||
dry_run=True,
|
||||
)
|
||||
|
||||
def mock_send(*args, **kwargs):
|
||||
coder.partial_response_content = f"""
|
||||
Do this:
|
||||
|
||||
{Path(file1).name}
|
||||
<<<<<<< SEARCH
|
||||
two
|
||||
=======
|
||||
new
|
||||
>>>>>>> REPLACE
|
||||
|
||||
"""
|
||||
coder.partial_response_function_call = dict()
|
||||
return []
|
||||
|
||||
coder.send = mock_send
|
||||
|
||||
# Call the run method with a message
|
||||
coder.run(with_message="hi")
|
||||
|
||||
content = Path(file1).read_text(encoding="utf-8")
|
||||
self.assertEqual(content, orig_content)
|
||||
|
||||
def test_find_original_update_blocks_mupltiple_same_file(self):
|
||||
edit = """
|
||||
Here's the change:
|
||||
|
||||
```text
|
||||
foo.txt
|
||||
<<<<<<< SEARCH
|
||||
one
|
||||
=======
|
||||
two
|
||||
>>>>>>> REPLACE
|
||||
|
||||
...
|
||||
|
||||
<<<<<<< SEARCH
|
||||
three
|
||||
=======
|
||||
four
|
||||
>>>>>>> REPLACE
|
||||
```
|
||||
|
||||
Hope you like it!
|
||||
"""
|
||||
|
||||
edits = list(eb.find_original_update_blocks(edit))
|
||||
self.assertEqual(
|
||||
edits,
|
||||
[
|
||||
("foo.txt", "one\n", "two\n"),
|
||||
("foo.txt", "three\n", "four\n"),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
|
@ -1,65 +0,0 @@
|
|||
import os
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from aider.io import AutoCompleter, InputOutput
|
||||
from aider.utils import ChdirTemporaryDirectory
|
||||
|
||||
|
||||
class TestInputOutput(unittest.TestCase):
|
||||
def test_no_color_environment_variable(self):
|
||||
with patch.dict(os.environ, {"NO_COLOR": "1"}):
|
||||
io = InputOutput()
|
||||
self.assertFalse(io.pretty)
|
||||
|
||||
def test_autocompleter_with_non_existent_file(self):
|
||||
root = ""
|
||||
rel_fnames = ["non_existent_file.txt"]
|
||||
addable_rel_fnames = []
|
||||
commands = None
|
||||
autocompleter = AutoCompleter(root, rel_fnames, addable_rel_fnames, commands, "utf-8")
|
||||
self.assertEqual(autocompleter.words, set(rel_fnames))
|
||||
|
||||
def test_autocompleter_with_unicode_file(self):
|
||||
with ChdirTemporaryDirectory():
|
||||
root = ""
|
||||
fname = "file.py"
|
||||
rel_fnames = [fname]
|
||||
addable_rel_fnames = []
|
||||
commands = None
|
||||
autocompleter = AutoCompleter(root, rel_fnames, addable_rel_fnames, commands, "utf-8")
|
||||
self.assertEqual(autocompleter.words, set(rel_fnames))
|
||||
|
||||
Path(fname).write_text("def hello(): pass\n")
|
||||
autocompleter = AutoCompleter(root, rel_fnames, addable_rel_fnames, commands, "utf-8")
|
||||
self.assertEqual(autocompleter.words, set(rel_fnames + ["hello"]))
|
||||
|
||||
encoding = "utf-16"
|
||||
some_content_which_will_error_if_read_with_encoding_utf8 = "ÅÍÎÏ".encode(encoding)
|
||||
with open(fname, "wb") as f:
|
||||
f.write(some_content_which_will_error_if_read_with_encoding_utf8)
|
||||
|
||||
autocompleter = AutoCompleter(root, rel_fnames, addable_rel_fnames, commands, "utf-8")
|
||||
self.assertEqual(autocompleter.words, set(rel_fnames))
|
||||
|
||||
@patch("aider.io.PromptSession")
|
||||
def test_get_input_is_a_directory_error(self, MockPromptSession):
|
||||
# Mock the PromptSession to simulate user input
|
||||
mock_session = MockPromptSession.return_value
|
||||
mock_session.prompt.return_value = "test input"
|
||||
|
||||
io = InputOutput(pretty=False) # Windows tests throw UnicodeDecodeError
|
||||
root = "/"
|
||||
rel_fnames = ["existing_file.txt"]
|
||||
addable_rel_fnames = ["new_file.txt"]
|
||||
commands = MagicMock()
|
||||
|
||||
# Simulate IsADirectoryError
|
||||
with patch("aider.io.open", side_effect=IsADirectoryError):
|
||||
result = io.get_input(root, rel_fnames, addable_rel_fnames, commands)
|
||||
self.assertEqual(result, "test input")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
|
@ -1,239 +0,0 @@
|
|||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from unittest import TestCase
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import git
|
||||
from prompt_toolkit.input import DummyInput
|
||||
from prompt_toolkit.output import DummyOutput
|
||||
|
||||
from aider.dump import dump # noqa: F401
|
||||
from aider.io import InputOutput
|
||||
from aider.main import check_gitignore, main, setup_git
|
||||
from aider.utils import GitTemporaryDirectory, make_repo
|
||||
|
||||
|
||||
class TestMain(TestCase):
|
||||
def setUp(self):
|
||||
os.environ["OPENAI_API_KEY"] = "deadbeef"
|
||||
self.original_cwd = os.getcwd()
|
||||
self.tempdir = tempfile.mkdtemp()
|
||||
os.chdir(self.tempdir)
|
||||
|
||||
def tearDown(self):
|
||||
os.chdir(self.original_cwd)
|
||||
shutil.rmtree(self.tempdir, ignore_errors=True)
|
||||
|
||||
def test_main_with_empty_dir_no_files_on_command(self):
|
||||
main(["--no-git"], input=DummyInput(), output=DummyOutput())
|
||||
|
||||
def test_main_with_empty_dir_new_file(self):
|
||||
main(["foo.txt", "--yes", "--no-git"], input=DummyInput(), output=DummyOutput())
|
||||
self.assertTrue(os.path.exists("foo.txt"))
|
||||
|
||||
@patch("aider.repo.GitRepo.get_commit_message", return_value="mock commit message")
|
||||
def test_main_with_empty_git_dir_new_file(self, _):
|
||||
make_repo()
|
||||
main(["--yes", "foo.txt"], input=DummyInput(), output=DummyOutput())
|
||||
self.assertTrue(os.path.exists("foo.txt"))
|
||||
|
||||
@patch("aider.repo.GitRepo.get_commit_message", return_value="mock commit message")
|
||||
def test_main_with_empty_git_dir_new_files(self, _):
|
||||
make_repo()
|
||||
main(["--yes", "foo.txt", "bar.txt"], input=DummyInput(), output=DummyOutput())
|
||||
self.assertTrue(os.path.exists("foo.txt"))
|
||||
self.assertTrue(os.path.exists("bar.txt"))
|
||||
|
||||
def test_main_with_dname_and_fname(self):
|
||||
subdir = Path("subdir")
|
||||
subdir.mkdir()
|
||||
make_repo(str(subdir))
|
||||
res = main(["subdir", "foo.txt"], input=DummyInput(), output=DummyOutput())
|
||||
self.assertNotEqual(res, None)
|
||||
|
||||
@patch("aider.repo.GitRepo.get_commit_message", return_value="mock commit message")
|
||||
def test_main_with_subdir_repo_fnames(self, _):
|
||||
subdir = Path("subdir")
|
||||
subdir.mkdir()
|
||||
make_repo(str(subdir))
|
||||
main(
|
||||
["--yes", str(subdir / "foo.txt"), str(subdir / "bar.txt")],
|
||||
input=DummyInput(),
|
||||
output=DummyOutput(),
|
||||
)
|
||||
self.assertTrue((subdir / "foo.txt").exists())
|
||||
self.assertTrue((subdir / "bar.txt").exists())
|
||||
|
||||
def test_main_with_git_config_yml(self):
|
||||
make_repo()
|
||||
|
||||
Path(".aider.conf.yml").write_text("auto-commits: false\n")
|
||||
with patch("aider.main.Coder.create") as MockCoder:
|
||||
main(["--yes"], input=DummyInput(), output=DummyOutput())
|
||||
_, kwargs = MockCoder.call_args
|
||||
assert kwargs["auto_commits"] is False
|
||||
|
||||
Path(".aider.conf.yml").write_text("auto-commits: true\n")
|
||||
with patch("aider.main.Coder.create") as MockCoder:
|
||||
main([], input=DummyInput(), output=DummyOutput())
|
||||
_, kwargs = MockCoder.call_args
|
||||
assert kwargs["auto_commits"] is True
|
||||
|
||||
def test_main_with_empty_git_dir_new_subdir_file(self):
|
||||
make_repo()
|
||||
subdir = Path("subdir")
|
||||
subdir.mkdir()
|
||||
fname = subdir / "foo.txt"
|
||||
fname.touch()
|
||||
subprocess.run(["git", "add", str(subdir)])
|
||||
subprocess.run(["git", "commit", "-m", "added"])
|
||||
|
||||
# This will throw a git error on windows if get_tracked_files doesn't
|
||||
# properly convert git/posix/paths to git\posix\paths.
|
||||
# Because aider will try and `git add` a file that's already in the repo.
|
||||
main(["--yes", str(fname)], input=DummyInput(), output=DummyOutput())
|
||||
|
||||
def test_setup_git(self):
|
||||
io = InputOutput(pretty=False, yes=True)
|
||||
git_root = setup_git(None, io)
|
||||
git_root = Path(git_root).resolve()
|
||||
self.assertEqual(git_root, Path(self.tempdir).resolve())
|
||||
|
||||
self.assertTrue(git.Repo(self.tempdir))
|
||||
|
||||
gitignore = Path.cwd() / ".gitignore"
|
||||
self.assertTrue(gitignore.exists())
|
||||
self.assertEqual(".aider*", gitignore.read_text().splitlines()[0])
|
||||
|
||||
def test_check_gitignore(self):
|
||||
with GitTemporaryDirectory():
|
||||
os.environ["GIT_CONFIG_GLOBAL"] = "globalgitconfig"
|
||||
|
||||
io = InputOutput(pretty=False, yes=True)
|
||||
cwd = Path.cwd()
|
||||
gitignore = cwd / ".gitignore"
|
||||
|
||||
self.assertFalse(gitignore.exists())
|
||||
check_gitignore(cwd, io)
|
||||
self.assertTrue(gitignore.exists())
|
||||
|
||||
self.assertEqual(".aider*", gitignore.read_text().splitlines()[0])
|
||||
|
||||
gitignore.write_text("one\ntwo\n")
|
||||
check_gitignore(cwd, io)
|
||||
self.assertEqual("one\ntwo\n.aider*\n", gitignore.read_text())
|
||||
del os.environ["GIT_CONFIG_GLOBAL"]
|
||||
|
||||
def test_main_git_ignore(self):
|
||||
cwd = Path().cwd()
|
||||
self.assertFalse((cwd / ".git").exists())
|
||||
self.assertFalse((cwd / ".gitignore").exists())
|
||||
|
||||
with patch("aider.main.Coder.create"):
|
||||
main(["--yes"], input=DummyInput())
|
||||
|
||||
self.assertTrue((cwd / ".git").exists())
|
||||
self.assertTrue((cwd / ".gitignore").exists())
|
||||
|
||||
def test_main_args(self):
|
||||
with patch("aider.main.Coder.create") as MockCoder:
|
||||
# --yes will just ok the git repo without blocking on input
|
||||
# following calls to main will see the new repo already
|
||||
main(["--no-auto-commits", "--yes"], input=DummyInput())
|
||||
_, kwargs = MockCoder.call_args
|
||||
assert kwargs["auto_commits"] is False
|
||||
|
||||
with patch("aider.main.Coder.create") as MockCoder:
|
||||
main(["--auto-commits"], input=DummyInput())
|
||||
_, kwargs = MockCoder.call_args
|
||||
assert kwargs["auto_commits"] is True
|
||||
|
||||
with patch("aider.main.Coder.create") as MockCoder:
|
||||
main([], input=DummyInput())
|
||||
_, kwargs = MockCoder.call_args
|
||||
assert kwargs["dirty_commits"] is True
|
||||
assert kwargs["auto_commits"] is True
|
||||
assert kwargs["pretty"] is True
|
||||
|
||||
with patch("aider.main.Coder.create") as MockCoder:
|
||||
main(["--no-pretty"], input=DummyInput())
|
||||
_, kwargs = MockCoder.call_args
|
||||
assert kwargs["pretty"] is False
|
||||
|
||||
with patch("aider.main.Coder.create") as MockCoder:
|
||||
main(["--pretty"], input=DummyInput())
|
||||
_, kwargs = MockCoder.call_args
|
||||
assert kwargs["pretty"] is True
|
||||
|
||||
with patch("aider.main.Coder.create") as MockCoder:
|
||||
main(["--no-dirty-commits"], input=DummyInput())
|
||||
_, kwargs = MockCoder.call_args
|
||||
assert kwargs["dirty_commits"] is False
|
||||
|
||||
with patch("aider.main.Coder.create") as MockCoder:
|
||||
main(["--dirty-commits"], input=DummyInput())
|
||||
_, kwargs = MockCoder.call_args
|
||||
assert kwargs["dirty_commits"] is True
|
||||
|
||||
def test_message_file_flag(self):
|
||||
message_file_content = "This is a test message from a file."
|
||||
message_file_path = tempfile.mktemp()
|
||||
with open(message_file_path, "w", encoding="utf-8") as message_file:
|
||||
message_file.write(message_file_content)
|
||||
|
||||
with patch("aider.main.Coder.create") as MockCoder:
|
||||
MockCoder.return_value.run = MagicMock()
|
||||
main(
|
||||
["--yes", "--message-file", message_file_path],
|
||||
input=DummyInput(),
|
||||
output=DummyOutput(),
|
||||
)
|
||||
MockCoder.return_value.run.assert_called_once_with(with_message=message_file_content)
|
||||
|
||||
os.remove(message_file_path)
|
||||
|
||||
def test_encodings_arg(self):
|
||||
fname = "foo.py"
|
||||
|
||||
with GitTemporaryDirectory():
|
||||
with patch("aider.main.Coder.create") as MockCoder: # noqa: F841
|
||||
with patch("aider.main.InputOutput") as MockSend:
|
||||
|
||||
def side_effect(*args, **kwargs):
|
||||
self.assertEqual(kwargs["encoding"], "iso-8859-15")
|
||||
return MagicMock()
|
||||
|
||||
MockSend.side_effect = side_effect
|
||||
|
||||
main(["--yes", fname, "--encoding", "iso-8859-15"])
|
||||
|
||||
@patch("aider.main.InputOutput")
|
||||
@patch("aider.coders.base_coder.Coder.run")
|
||||
def test_main_message_adds_to_input_history(self, mock_run, MockInputOutput):
|
||||
test_message = "test message"
|
||||
mock_io_instance = MockInputOutput.return_value
|
||||
|
||||
main(["--message", test_message], input=DummyInput(), output=DummyOutput())
|
||||
|
||||
mock_io_instance.add_to_input_history.assert_called_once_with(test_message)
|
||||
|
||||
@patch("aider.main.InputOutput")
|
||||
@patch("aider.coders.base_coder.Coder.run")
|
||||
def test_yes(self, mock_run, MockInputOutput):
|
||||
test_message = "test message"
|
||||
|
||||
main(["--yes", "--message", test_message])
|
||||
args, kwargs = MockInputOutput.call_args
|
||||
self.assertTrue(args[1])
|
||||
|
||||
@patch("aider.main.InputOutput")
|
||||
@patch("aider.coders.base_coder.Coder.run")
|
||||
def test_default_yes(self, mock_run, MockInputOutput):
|
||||
test_message = "test message"
|
||||
|
||||
main(["--message", test_message])
|
||||
args, kwargs = MockInputOutput.call_args
|
||||
self.assertEqual(args[1], None)
|
|
@ -1,28 +0,0 @@
|
|||
import unittest
|
||||
|
||||
from aider.models import Model
|
||||
|
||||
|
||||
class TestModels(unittest.TestCase):
|
||||
def test_max_context_tokens(self):
|
||||
model = Model("gpt-3.5-turbo")
|
||||
self.assertEqual(model.info["max_input_tokens"], 16385)
|
||||
|
||||
model = Model("gpt-3.5-turbo-16k")
|
||||
self.assertEqual(model.info["max_input_tokens"], 16385)
|
||||
|
||||
model = Model("gpt-3.5-turbo-1106")
|
||||
self.assertEqual(model.info["max_input_tokens"], 16385)
|
||||
|
||||
model = Model("gpt-4")
|
||||
self.assertEqual(model.info["max_input_tokens"], 8 * 1024)
|
||||
|
||||
model = Model("gpt-4-32k")
|
||||
self.assertEqual(model.info["max_input_tokens"], 32 * 1024)
|
||||
|
||||
model = Model("gpt-4-0613")
|
||||
self.assertEqual(model.info["max_input_tokens"], 8 * 1024)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
|
@ -1,296 +0,0 @@
|
|||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import git
|
||||
|
||||
from aider.dump import dump # noqa: F401
|
||||
from aider.io import InputOutput
|
||||
from aider.models import Model
|
||||
from aider.repo import GitRepo
|
||||
from aider.utils import GitTemporaryDirectory
|
||||
|
||||
|
||||
class TestRepo(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.GPT35 = Model("gpt-3.5-turbo")
|
||||
|
||||
def test_diffs_empty_repo(self):
|
||||
with GitTemporaryDirectory():
|
||||
repo = git.Repo()
|
||||
|
||||
# Add a change to the index
|
||||
fname = Path("foo.txt")
|
||||
fname.write_text("index\n")
|
||||
repo.git.add(str(fname))
|
||||
|
||||
# Make a change in the working dir
|
||||
fname.write_text("workingdir\n")
|
||||
|
||||
git_repo = GitRepo(InputOutput(), None, ".")
|
||||
diffs = git_repo.get_diffs()
|
||||
self.assertIn("index", diffs)
|
||||
self.assertIn("workingdir", diffs)
|
||||
|
||||
def test_diffs_nonempty_repo(self):
|
||||
with GitTemporaryDirectory():
|
||||
repo = git.Repo()
|
||||
fname = Path("foo.txt")
|
||||
fname.touch()
|
||||
repo.git.add(str(fname))
|
||||
|
||||
fname2 = Path("bar.txt")
|
||||
fname2.touch()
|
||||
repo.git.add(str(fname2))
|
||||
|
||||
repo.git.commit("-m", "initial")
|
||||
|
||||
fname.write_text("index\n")
|
||||
repo.git.add(str(fname))
|
||||
|
||||
fname2.write_text("workingdir\n")
|
||||
|
||||
git_repo = GitRepo(InputOutput(), None, ".")
|
||||
diffs = git_repo.get_diffs()
|
||||
self.assertIn("index", diffs)
|
||||
self.assertIn("workingdir", diffs)
|
||||
|
||||
def test_diffs_detached_head(self):
|
||||
with GitTemporaryDirectory():
|
||||
repo = git.Repo()
|
||||
fname = Path("foo.txt")
|
||||
fname.touch()
|
||||
repo.git.add(str(fname))
|
||||
repo.git.commit("-m", "foo")
|
||||
|
||||
fname2 = Path("bar.txt")
|
||||
fname2.touch()
|
||||
repo.git.add(str(fname2))
|
||||
repo.git.commit("-m", "bar")
|
||||
|
||||
fname3 = Path("baz.txt")
|
||||
fname3.touch()
|
||||
repo.git.add(str(fname3))
|
||||
repo.git.commit("-m", "baz")
|
||||
|
||||
repo.git.checkout("HEAD^")
|
||||
|
||||
fname.write_text("index\n")
|
||||
repo.git.add(str(fname))
|
||||
|
||||
fname2.write_text("workingdir\n")
|
||||
|
||||
git_repo = GitRepo(InputOutput(), None, ".")
|
||||
diffs = git_repo.get_diffs()
|
||||
self.assertIn("index", diffs)
|
||||
self.assertIn("workingdir", diffs)
|
||||
|
||||
def test_diffs_between_commits(self):
|
||||
with GitTemporaryDirectory():
|
||||
repo = git.Repo()
|
||||
fname = Path("foo.txt")
|
||||
|
||||
fname.write_text("one\n")
|
||||
repo.git.add(str(fname))
|
||||
repo.git.commit("-m", "initial")
|
||||
|
||||
fname.write_text("two\n")
|
||||
repo.git.add(str(fname))
|
||||
repo.git.commit("-m", "second")
|
||||
|
||||
git_repo = GitRepo(InputOutput(), None, ".")
|
||||
diffs = git_repo.diff_commits(False, "HEAD~1", "HEAD")
|
||||
self.assertIn("two", diffs)
|
||||
|
||||
@patch("aider.repo.simple_send_with_retries")
|
||||
def test_get_commit_message(self, mock_send):
|
||||
mock_send.return_value = "a good commit message"
|
||||
|
||||
repo = GitRepo(InputOutput(), None, None, models=[self.GPT35])
|
||||
# Call the get_commit_message method with dummy diff and context
|
||||
result = repo.get_commit_message("dummy diff", "dummy context")
|
||||
|
||||
# Assert that the returned message is the expected one
|
||||
self.assertEqual(result, "a good commit message")
|
||||
|
||||
@patch("aider.repo.simple_send_with_retries")
|
||||
def test_get_commit_message_strip_quotes(self, mock_send):
|
||||
mock_send.return_value = '"a good commit message"'
|
||||
|
||||
repo = GitRepo(InputOutput(), None, None, models=[self.GPT35])
|
||||
# Call the get_commit_message method with dummy diff and context
|
||||
result = repo.get_commit_message("dummy diff", "dummy context")
|
||||
|
||||
# Assert that the returned message is the expected one
|
||||
self.assertEqual(result, "a good commit message")
|
||||
|
||||
@patch("aider.repo.simple_send_with_retries")
|
||||
def test_get_commit_message_no_strip_unmatched_quotes(self, mock_send):
|
||||
mock_send.return_value = 'a good "commit message"'
|
||||
|
||||
repo = GitRepo(InputOutput(), None, None, models=[self.GPT35])
|
||||
# Call the get_commit_message method with dummy diff and context
|
||||
result = repo.get_commit_message("dummy diff", "dummy context")
|
||||
|
||||
# Assert that the returned message is the expected one
|
||||
self.assertEqual(result, 'a good "commit message"')
|
||||
|
||||
def test_get_tracked_files(self):
|
||||
# Create a temporary directory
|
||||
tempdir = Path(tempfile.mkdtemp())
|
||||
|
||||
# Initialize a git repository in the temporary directory and set user name and email
|
||||
repo = git.Repo.init(tempdir)
|
||||
repo.config_writer().set_value("user", "name", "Test User").release()
|
||||
repo.config_writer().set_value("user", "email", "testuser@example.com").release()
|
||||
|
||||
# Create three empty files and add them to the git repository
|
||||
filenames = ["README.md", "subdir/fänny.md", "systemüber/blick.md", 'file"with"quotes.txt']
|
||||
created_files = []
|
||||
for filename in filenames:
|
||||
file_path = tempdir / filename
|
||||
try:
|
||||
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
file_path.touch()
|
||||
repo.git.add(str(file_path))
|
||||
created_files.append(Path(filename))
|
||||
except OSError:
|
||||
# windows won't allow files with quotes, that's ok
|
||||
self.assertIn('"', filename)
|
||||
self.assertEqual(os.name, "nt")
|
||||
|
||||
self.assertTrue(len(created_files) >= 3)
|
||||
|
||||
repo.git.commit("-m", "added")
|
||||
|
||||
tracked_files = GitRepo(InputOutput(), [tempdir], None).get_tracked_files()
|
||||
|
||||
# On windows, paths will come back \like\this, so normalize them back to Paths
|
||||
tracked_files = [Path(fn) for fn in tracked_files]
|
||||
|
||||
# Assert that coder.get_tracked_files() returns the three filenames
|
||||
self.assertEqual(set(tracked_files), set(created_files))
|
||||
|
||||
def test_get_tracked_files_with_new_staged_file(self):
|
||||
with GitTemporaryDirectory():
|
||||
# new repo
|
||||
raw_repo = git.Repo()
|
||||
|
||||
# add it, but no commits at all in the raw_repo yet
|
||||
fname = Path("new.txt")
|
||||
fname.touch()
|
||||
raw_repo.git.add(str(fname))
|
||||
|
||||
git_repo = GitRepo(InputOutput(), None, None)
|
||||
|
||||
# better be there
|
||||
fnames = git_repo.get_tracked_files()
|
||||
self.assertIn(str(fname), fnames)
|
||||
|
||||
# commit it, better still be there
|
||||
raw_repo.git.commit("-m", "new")
|
||||
fnames = git_repo.get_tracked_files()
|
||||
self.assertIn(str(fname), fnames)
|
||||
|
||||
# new file, added but not committed
|
||||
fname2 = Path("new2.txt")
|
||||
fname2.touch()
|
||||
raw_repo.git.add(str(fname2))
|
||||
|
||||
# both should be there
|
||||
fnames = git_repo.get_tracked_files()
|
||||
self.assertIn(str(fname), fnames)
|
||||
self.assertIn(str(fname2), fnames)
|
||||
|
||||
def test_get_tracked_files_with_aiderignore(self):
|
||||
with GitTemporaryDirectory():
|
||||
# new repo
|
||||
raw_repo = git.Repo()
|
||||
|
||||
# add it, but no commits at all in the raw_repo yet
|
||||
fname = Path("new.txt")
|
||||
fname.touch()
|
||||
raw_repo.git.add(str(fname))
|
||||
|
||||
aiderignore = Path(".aiderignore")
|
||||
git_repo = GitRepo(InputOutput(), None, None, str(aiderignore))
|
||||
|
||||
# better be there
|
||||
fnames = git_repo.get_tracked_files()
|
||||
self.assertIn(str(fname), fnames)
|
||||
|
||||
# commit it, better still be there
|
||||
raw_repo.git.commit("-m", "new")
|
||||
fnames = git_repo.get_tracked_files()
|
||||
self.assertIn(str(fname), fnames)
|
||||
|
||||
# new file, added but not committed
|
||||
fname2 = Path("new2.txt")
|
||||
fname2.touch()
|
||||
raw_repo.git.add(str(fname2))
|
||||
|
||||
# both should be there
|
||||
fnames = git_repo.get_tracked_files()
|
||||
self.assertIn(str(fname), fnames)
|
||||
self.assertIn(str(fname2), fnames)
|
||||
|
||||
aiderignore.write_text("new.txt\n")
|
||||
|
||||
# new.txt should be gone!
|
||||
fnames = git_repo.get_tracked_files()
|
||||
self.assertNotIn(str(fname), fnames)
|
||||
self.assertIn(str(fname2), fnames)
|
||||
|
||||
# This does not work in github actions?!
|
||||
# The mtime doesn't change, even if I time.sleep(1)
|
||||
# Before doing this write_text()!?
|
||||
#
|
||||
# aiderignore.write_text("new2.txt\n")
|
||||
# new2.txt should be gone!
|
||||
# fnames = git_repo.get_tracked_files()
|
||||
# self.assertIn(str(fname), fnames)
|
||||
# self.assertNotIn(str(fname2), fnames)
|
||||
|
||||
def test_get_tracked_files_from_subdir(self):
|
||||
with GitTemporaryDirectory():
|
||||
# new repo
|
||||
raw_repo = git.Repo()
|
||||
|
||||
# add it, but no commits at all in the raw_repo yet
|
||||
fname = Path("subdir/new.txt")
|
||||
fname.parent.mkdir()
|
||||
fname.touch()
|
||||
raw_repo.git.add(str(fname))
|
||||
|
||||
os.chdir(fname.parent)
|
||||
|
||||
git_repo = GitRepo(InputOutput(), None, None)
|
||||
|
||||
# better be there
|
||||
fnames = git_repo.get_tracked_files()
|
||||
self.assertIn(str(fname), fnames)
|
||||
|
||||
# commit it, better still be there
|
||||
raw_repo.git.commit("-m", "new")
|
||||
fnames = git_repo.get_tracked_files()
|
||||
self.assertIn(str(fname), fnames)
|
||||
|
||||
@patch("aider.repo.simple_send_with_retries")
|
||||
def test_noop_commit(self, mock_send):
|
||||
mock_send.return_value = '"a good commit message"'
|
||||
|
||||
with GitTemporaryDirectory():
|
||||
# new repo
|
||||
raw_repo = git.Repo()
|
||||
|
||||
# add it, but no commits at all in the raw_repo yet
|
||||
fname = Path("file.txt")
|
||||
fname.touch()
|
||||
raw_repo.git.add(str(fname))
|
||||
raw_repo.git.commit("-m", "new")
|
||||
|
||||
git_repo = GitRepo(InputOutput(), None, None)
|
||||
|
||||
git_repo.commit(fnames=[str(fname)])
|
|
@ -1,217 +0,0 @@
|
|||
import os
|
||||
import unittest
|
||||
|
||||
from aider.dump import dump # noqa: F401
|
||||
from aider.io import InputOutput
|
||||
from aider.models import Model
|
||||
from aider.repomap import RepoMap
|
||||
from aider.utils import IgnorantTemporaryDirectory
|
||||
|
||||
|
||||
class TestRepoMap(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.GPT35 = Model("gpt-3.5-turbo")
|
||||
|
||||
def test_get_repo_map(self):
|
||||
# Create a temporary directory with sample files for testing
|
||||
test_files = [
|
||||
"test_file1.py",
|
||||
"test_file2.py",
|
||||
"test_file3.md",
|
||||
"test_file4.json",
|
||||
]
|
||||
|
||||
with IgnorantTemporaryDirectory() as temp_dir:
|
||||
for file in test_files:
|
||||
with open(os.path.join(temp_dir, file), "w") as f:
|
||||
f.write("")
|
||||
|
||||
io = InputOutput()
|
||||
repo_map = RepoMap(main_model=self.GPT35, root=temp_dir, io=io)
|
||||
other_files = [os.path.join(temp_dir, file) for file in test_files]
|
||||
result = repo_map.get_repo_map([], other_files)
|
||||
|
||||
# Check if the result contains the expected tags map
|
||||
self.assertIn("test_file1.py", result)
|
||||
self.assertIn("test_file2.py", result)
|
||||
self.assertIn("test_file3.md", result)
|
||||
self.assertIn("test_file4.json", result)
|
||||
|
||||
# close the open cache files, so Windows won't error
|
||||
del repo_map
|
||||
|
||||
def test_get_repo_map_with_identifiers(self):
|
||||
# Create a temporary directory with a sample Python file containing identifiers
|
||||
test_file1 = "test_file_with_identifiers.py"
|
||||
file_content1 = """\
|
||||
class MyClass:
|
||||
def my_method(self, arg1, arg2):
|
||||
return arg1 + arg2
|
||||
|
||||
def my_function(arg1, arg2):
|
||||
return arg1 * arg2
|
||||
"""
|
||||
|
||||
test_file2 = "test_file_import.py"
|
||||
file_content2 = """\
|
||||
from test_file_with_identifiers import MyClass
|
||||
|
||||
obj = MyClass()
|
||||
print(obj.my_method(1, 2))
|
||||
print(my_function(3, 4))
|
||||
"""
|
||||
|
||||
test_file3 = "test_file_pass.py"
|
||||
file_content3 = "pass"
|
||||
|
||||
with IgnorantTemporaryDirectory() as temp_dir:
|
||||
with open(os.path.join(temp_dir, test_file1), "w") as f:
|
||||
f.write(file_content1)
|
||||
|
||||
with open(os.path.join(temp_dir, test_file2), "w") as f:
|
||||
f.write(file_content2)
|
||||
|
||||
with open(os.path.join(temp_dir, test_file3), "w") as f:
|
||||
f.write(file_content3)
|
||||
|
||||
io = InputOutput()
|
||||
repo_map = RepoMap(main_model=self.GPT35, root=temp_dir, io=io)
|
||||
other_files = [
|
||||
os.path.join(temp_dir, test_file1),
|
||||
os.path.join(temp_dir, test_file2),
|
||||
os.path.join(temp_dir, test_file3),
|
||||
]
|
||||
result = repo_map.get_repo_map([], other_files)
|
||||
|
||||
# Check if the result contains the expected tags map with identifiers
|
||||
self.assertIn("test_file_with_identifiers.py", result)
|
||||
self.assertIn("MyClass", result)
|
||||
self.assertIn("my_method", result)
|
||||
self.assertIn("my_function", result)
|
||||
self.assertIn("test_file_pass.py", result)
|
||||
|
||||
# close the open cache files, so Windows won't error
|
||||
del repo_map
|
||||
|
||||
def test_get_repo_map_all_files(self):
|
||||
test_files = [
|
||||
"test_file0.py",
|
||||
"test_file1.txt",
|
||||
"test_file2.md",
|
||||
"test_file3.json",
|
||||
"test_file4.html",
|
||||
"test_file5.css",
|
||||
"test_file6.js",
|
||||
]
|
||||
|
||||
with IgnorantTemporaryDirectory() as temp_dir:
|
||||
for file in test_files:
|
||||
with open(os.path.join(temp_dir, file), "w") as f:
|
||||
f.write("")
|
||||
|
||||
repo_map = RepoMap(main_model=self.GPT35, root=temp_dir, io=InputOutput())
|
||||
|
||||
other_files = [os.path.join(temp_dir, file) for file in test_files]
|
||||
result = repo_map.get_repo_map([], other_files)
|
||||
dump(other_files)
|
||||
dump(repr(result))
|
||||
|
||||
# Check if the result contains each specific file in the expected tags map without ctags
|
||||
for file in test_files:
|
||||
self.assertIn(file, result)
|
||||
|
||||
# close the open cache files, so Windows won't error
|
||||
del repo_map
|
||||
|
||||
def test_get_repo_map_excludes_added_files(self):
|
||||
# Create a temporary directory with sample files for testing
|
||||
test_files = [
|
||||
"test_file1.py",
|
||||
"test_file2.py",
|
||||
"test_file3.md",
|
||||
"test_file4.json",
|
||||
]
|
||||
|
||||
with IgnorantTemporaryDirectory() as temp_dir:
|
||||
for file in test_files:
|
||||
with open(os.path.join(temp_dir, file), "w") as f:
|
||||
f.write("def foo(): pass\n")
|
||||
|
||||
io = InputOutput()
|
||||
repo_map = RepoMap(main_model=self.GPT35, root=temp_dir, io=io)
|
||||
test_files = [os.path.join(temp_dir, file) for file in test_files]
|
||||
result = repo_map.get_repo_map(test_files[:2], test_files[2:])
|
||||
|
||||
dump(result)
|
||||
|
||||
# Check if the result contains the expected tags map
|
||||
self.assertNotIn("test_file1.py", result)
|
||||
self.assertNotIn("test_file2.py", result)
|
||||
self.assertIn("test_file3.md", result)
|
||||
self.assertIn("test_file4.json", result)
|
||||
|
||||
# close the open cache files, so Windows won't error
|
||||
del repo_map
|
||||
|
||||
|
||||
class TestRepoMapTypescript(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.GPT35 = Model("gpt-3.5-turbo")
|
||||
|
||||
def test_get_repo_map_typescript(self):
|
||||
# Create a temporary directory with a sample TypeScript file
|
||||
test_file_ts = "test_file.ts"
|
||||
file_content_ts = """\
|
||||
interface IMyInterface {
|
||||
someMethod(): void;
|
||||
}
|
||||
|
||||
type ExampleType = {
|
||||
key: string;
|
||||
value: number;
|
||||
};
|
||||
|
||||
enum Status {
|
||||
New,
|
||||
InProgress,
|
||||
Completed,
|
||||
}
|
||||
|
||||
export class MyClass {
|
||||
constructor(public value: number) {}
|
||||
|
||||
add(input: number): number {
|
||||
return this.value + input;
|
||||
return this.value + input;
|
||||
}
|
||||
}
|
||||
|
||||
export function myFunction(input: number): number {
|
||||
return input * 2;
|
||||
}
|
||||
"""
|
||||
|
||||
with IgnorantTemporaryDirectory() as temp_dir:
|
||||
with open(os.path.join(temp_dir, test_file_ts), "w") as f:
|
||||
f.write(file_content_ts)
|
||||
|
||||
io = InputOutput()
|
||||
repo_map = RepoMap(main_model=self.GPT35, root=temp_dir, io=io)
|
||||
other_files = [os.path.join(temp_dir, test_file_ts)]
|
||||
result = repo_map.get_repo_map([], other_files)
|
||||
|
||||
# Check if the result contains the expected tags map with TypeScript identifiers
|
||||
self.assertIn("test_file.ts", result)
|
||||
self.assertIn("IMyInterface", result)
|
||||
self.assertIn("ExampleType", result)
|
||||
self.assertIn("Status", result)
|
||||
self.assertIn("MyClass", result)
|
||||
self.assertIn("add", result)
|
||||
self.assertIn("myFunction", result)
|
||||
|
||||
# close the open cache files, so Windows won't error
|
||||
del repo_map
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
|
@ -1,47 +0,0 @@
|
|||
import unittest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import httpx
|
||||
|
||||
from aider.litellm import litellm
|
||||
from aider.sendchat import send_with_retries
|
||||
|
||||
|
||||
class PrintCalled(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class TestSendChat(unittest.TestCase):
|
||||
@patch("litellm.completion")
|
||||
@patch("builtins.print")
|
||||
def test_send_with_retries_rate_limit_error(self, mock_print, mock_completion):
|
||||
mock = MagicMock()
|
||||
mock.status_code = 500
|
||||
|
||||
# Set up the mock to raise
|
||||
mock_completion.side_effect = [
|
||||
litellm.exceptions.RateLimitError(
|
||||
"rate limit exceeded",
|
||||
response=mock,
|
||||
llm_provider="llm_provider",
|
||||
model="model",
|
||||
),
|
||||
None,
|
||||
]
|
||||
|
||||
# Call the send_with_retries method
|
||||
send_with_retries("model", ["message"], None, False)
|
||||
mock_print.assert_called_once()
|
||||
|
||||
@patch("litellm.completion")
|
||||
@patch("builtins.print")
|
||||
def test_send_with_retries_connection_error(self, mock_print, mock_completion):
|
||||
# Set up the mock to raise
|
||||
mock_completion.side_effect = [
|
||||
httpx.ConnectError("Connection error"),
|
||||
None,
|
||||
]
|
||||
|
||||
# Call the send_with_retries method
|
||||
send_with_retries("model", ["message"], None, False)
|
||||
mock_print.assert_called_once()
|
|
@ -1,119 +0,0 @@
|
|||
import unittest
|
||||
|
||||
from aider.coders.udiff_coder import find_diffs
|
||||
from aider.dump import dump # noqa: F401
|
||||
|
||||
|
||||
class TestUnifiedDiffCoder(unittest.TestCase):
|
||||
def test_find_diffs_single_hunk(self):
|
||||
# Test find_diffs with a single hunk
|
||||
content = """
|
||||
Some text...
|
||||
|
||||
```diff
|
||||
--- file.txt
|
||||
+++ file.txt
|
||||
@@ ... @@
|
||||
-Original
|
||||
+Modified
|
||||
```
|
||||
"""
|
||||
edits = find_diffs(content)
|
||||
dump(edits)
|
||||
self.assertEqual(len(edits), 1)
|
||||
|
||||
edit = edits[0]
|
||||
self.assertEqual(edit[0], "file.txt")
|
||||
self.assertEqual(edit[1], ["-Original\n", "+Modified\n"])
|
||||
|
||||
def test_find_diffs_dev_null(self):
|
||||
# Test find_diffs with a single hunk
|
||||
content = """
|
||||
Some text...
|
||||
|
||||
```diff
|
||||
--- /dev/null
|
||||
+++ file.txt
|
||||
@@ ... @@
|
||||
-Original
|
||||
+Modified
|
||||
```
|
||||
"""
|
||||
edits = find_diffs(content)
|
||||
dump(edits)
|
||||
self.assertEqual(len(edits), 1)
|
||||
|
||||
edit = edits[0]
|
||||
self.assertEqual(edit[0], "file.txt")
|
||||
self.assertEqual(edit[1], ["-Original\n", "+Modified\n"])
|
||||
|
||||
def test_find_diffs_dirname_with_spaces(self):
|
||||
# Test find_diffs with a single hunk
|
||||
content = """
|
||||
Some text...
|
||||
|
||||
```diff
|
||||
--- dir name with spaces/file.txt
|
||||
+++ dir name with spaces/file.txt
|
||||
@@ ... @@
|
||||
-Original
|
||||
+Modified
|
||||
```
|
||||
"""
|
||||
edits = find_diffs(content)
|
||||
dump(edits)
|
||||
self.assertEqual(len(edits), 1)
|
||||
|
||||
edit = edits[0]
|
||||
self.assertEqual(edit[0], "dir name with spaces/file.txt")
|
||||
self.assertEqual(edit[1], ["-Original\n", "+Modified\n"])
|
||||
|
||||
def test_find_multi_diffs(self):
|
||||
content = """
|
||||
To implement the `--check-update` option, I will make the following changes:
|
||||
|
||||
1. Add the `--check-update` argument to the argument parser in `aider/main.py`.
|
||||
2. Modify the `check_version` function in `aider/versioncheck.py` to return a boolean indicating whether an update is available.
|
||||
3. Use the returned value from `check_version` in `aider/main.py` to set the exit status code when `--check-update` is used.
|
||||
|
||||
Here are the diffs for those changes:
|
||||
|
||||
```diff
|
||||
--- aider/versioncheck.py
|
||||
+++ aider/versioncheck.py
|
||||
@@ ... @@
|
||||
except Exception as err:
|
||||
print_cmd(f"Error checking pypi for new version: {err}")
|
||||
+ return False
|
||||
|
||||
--- aider/main.py
|
||||
+++ aider/main.py
|
||||
@@ ... @@
|
||||
other_group.add_argument(
|
||||
"--version",
|
||||
action="version",
|
||||
version=f"%(prog)s {__version__}",
|
||||
help="Show the version number and exit",
|
||||
)
|
||||
+ other_group.add_argument(
|
||||
+ "--check-update",
|
||||
+ action="store_true",
|
||||
+ help="Check for updates and return status in the exit code",
|
||||
+ default=False,
|
||||
+ )
|
||||
other_group.add_argument(
|
||||
"--apply",
|
||||
metavar="FILE",
|
||||
```
|
||||
|
||||
These changes will add the `--check-update` option to the command-line interface and use the `check_version` function to determine if an update is available, exiting with status code `0` if no update is available and `1` if an update is available.
|
||||
""" # noqa: E501
|
||||
|
||||
edits = find_diffs(content)
|
||||
dump(edits)
|
||||
self.assertEqual(len(edits), 2)
|
||||
self.assertEqual(len(edits[0][1]), 3)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
|
@ -1,9 +0,0 @@
|
|||
import requests
|
||||
from aider import urls
|
||||
|
||||
def test_urls():
|
||||
url_attributes = [attr for attr in dir(urls) if not callable(getattr(urls, attr)) and not attr.startswith("__")]
|
||||
for attr in url_attributes:
|
||||
url = getattr(urls, attr)
|
||||
response = requests.get(url)
|
||||
assert response.status_code == 200, f"URL {url} returned status code {response.status_code}"
|
|
@ -1,321 +0,0 @@
|
|||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from aider.coders import Coder
|
||||
from aider.coders.wholefile_coder import WholeFileCoder
|
||||
from aider.dump import dump # noqa: F401
|
||||
from aider.io import InputOutput
|
||||
from aider.models import Model
|
||||
|
||||
|
||||
class TestWholeFileCoder(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.original_cwd = os.getcwd()
|
||||
self.tempdir = tempfile.mkdtemp()
|
||||
os.chdir(self.tempdir)
|
||||
|
||||
self.GPT35 = Model("gpt-3.5-turbo")
|
||||
|
||||
def tearDown(self):
|
||||
os.chdir(self.original_cwd)
|
||||
shutil.rmtree(self.tempdir, ignore_errors=True)
|
||||
|
||||
def test_no_files(self):
|
||||
# Initialize WholeFileCoder with the temporary directory
|
||||
io = InputOutput(yes=True)
|
||||
|
||||
coder = WholeFileCoder(main_model=self.GPT35, io=io, fnames=[])
|
||||
coder.partial_response_content = (
|
||||
'To print "Hello, World!" in most programming languages, you can use the following'
|
||||
' code:\n\n```python\nprint("Hello, World!")\n```\n\nThis code will output "Hello,'
|
||||
' World!" to the console.'
|
||||
)
|
||||
|
||||
# This is throwing ValueError!
|
||||
coder.render_incremental_response(True)
|
||||
|
||||
def test_no_files_new_file_should_ask(self):
|
||||
io = InputOutput(yes=False) # <- yes=FALSE
|
||||
coder = WholeFileCoder(main_model=self.GPT35, io=io, fnames=[])
|
||||
coder.partial_response_content = (
|
||||
'To print "Hello, World!" in most programming languages, you can use the following'
|
||||
' code:\n\nfoo.js\n```python\nprint("Hello, World!")\n```\n\nThis code will output'
|
||||
' "Hello, World!" to the console.'
|
||||
)
|
||||
coder.update_files()
|
||||
self.assertFalse(Path("foo.js").exists())
|
||||
|
||||
def test_update_files(self):
|
||||
# Create a sample file in the temporary directory
|
||||
sample_file = "sample.txt"
|
||||
with open(sample_file, "w") as f:
|
||||
f.write("Original content\n")
|
||||
|
||||
# Initialize WholeFileCoder with the temporary directory
|
||||
io = InputOutput(yes=True)
|
||||
coder = WholeFileCoder(main_model=self.GPT35, io=io, fnames=[sample_file])
|
||||
|
||||
# Set the partial response content with the updated content
|
||||
coder.partial_response_content = f"{sample_file}\n```\nUpdated content\n```"
|
||||
|
||||
# Call update_files method
|
||||
edited_files = coder.update_files()
|
||||
|
||||
# Check if the sample file was updated
|
||||
self.assertIn("sample.txt", edited_files)
|
||||
|
||||
# Check if the content of the sample file was updated
|
||||
with open(sample_file, "r") as f:
|
||||
updated_content = f.read()
|
||||
self.assertEqual(updated_content, "Updated content\n")
|
||||
|
||||
def test_update_files_live_diff(self):
|
||||
# Create a sample file in the temporary directory
|
||||
sample_file = "sample.txt"
|
||||
with open(sample_file, "w") as f:
|
||||
f.write("\n".join(map(str, range(0, 100))))
|
||||
|
||||
# Initialize WholeFileCoder with the temporary directory
|
||||
io = InputOutput(yes=True)
|
||||
coder = WholeFileCoder(main_model=self.GPT35, io=io, fnames=[sample_file])
|
||||
|
||||
# Set the partial response content with the updated content
|
||||
coder.partial_response_content = f"{sample_file}\n```\n0\n\1\n2\n"
|
||||
|
||||
lines = coder.get_edits(mode="diff").splitlines()
|
||||
|
||||
# the live diff should be concise, since we haven't changed anything yet
|
||||
self.assertLess(len(lines), 20)
|
||||
|
||||
def test_update_files_with_existing_fence(self):
|
||||
# Create a sample file in the temporary directory
|
||||
sample_file = "sample.txt"
|
||||
original_content = """
|
||||
Here is some quoted text:
|
||||
```
|
||||
Quote!
|
||||
```
|
||||
"""
|
||||
with open(sample_file, "w") as f:
|
||||
f.write(original_content)
|
||||
|
||||
# Initialize WholeFileCoder with the temporary directory
|
||||
io = InputOutput(yes=True)
|
||||
coder = WholeFileCoder(main_model=self.GPT35, io=io, fnames=[sample_file])
|
||||
|
||||
coder.choose_fence()
|
||||
|
||||
self.assertNotEqual(coder.fence[0], "```")
|
||||
|
||||
# Set the partial response content with the updated content
|
||||
coder.partial_response_content = (
|
||||
f"{sample_file}\n{coder.fence[0]}\nUpdated content\n{coder.fence[1]}"
|
||||
)
|
||||
|
||||
# Call update_files method
|
||||
edited_files = coder.update_files()
|
||||
|
||||
# Check if the sample file was updated
|
||||
self.assertIn("sample.txt", edited_files)
|
||||
|
||||
# Check if the content of the sample file was updated
|
||||
with open(sample_file, "r") as f:
|
||||
updated_content = f.read()
|
||||
self.assertEqual(updated_content, "Updated content\n")
|
||||
|
||||
def test_update_files_bogus_path_prefix(self):
|
||||
# Create a sample file in the temporary directory
|
||||
sample_file = "sample.txt"
|
||||
with open(sample_file, "w") as f:
|
||||
f.write("Original content\n")
|
||||
|
||||
# Initialize WholeFileCoder with the temporary directory
|
||||
io = InputOutput(yes=True)
|
||||
coder = WholeFileCoder(main_model=self.GPT35, io=io, fnames=[sample_file])
|
||||
|
||||
# Set the partial response content with the updated content
|
||||
# With path/to/ prepended onto the filename
|
||||
coder.partial_response_content = f"path/to/{sample_file}\n```\nUpdated content\n```"
|
||||
|
||||
# Call update_files method
|
||||
edited_files = coder.update_files()
|
||||
|
||||
# Check if the sample file was updated
|
||||
self.assertIn("sample.txt", edited_files)
|
||||
|
||||
# Check if the content of the sample file was updated
|
||||
with open(sample_file, "r") as f:
|
||||
updated_content = f.read()
|
||||
self.assertEqual(updated_content, "Updated content\n")
|
||||
|
||||
def test_update_files_not_in_chat(self):
|
||||
# Create a sample file in the temporary directory
|
||||
sample_file = "sample.txt"
|
||||
with open(sample_file, "w") as f:
|
||||
f.write("Original content\n")
|
||||
|
||||
# Initialize WholeFileCoder with the temporary directory
|
||||
io = InputOutput(yes=True)
|
||||
coder = WholeFileCoder(main_model=self.GPT35, io=io)
|
||||
|
||||
# Set the partial response content with the updated content
|
||||
coder.partial_response_content = f"{sample_file}\n```\nUpdated content\n```"
|
||||
|
||||
# Call update_files method
|
||||
edited_files = coder.update_files()
|
||||
|
||||
# Check if the sample file was updated
|
||||
self.assertIn("sample.txt", edited_files)
|
||||
|
||||
# Check if the content of the sample file was updated
|
||||
with open(sample_file, "r") as f:
|
||||
updated_content = f.read()
|
||||
self.assertEqual(updated_content, "Updated content\n")
|
||||
|
||||
def test_update_files_no_filename_single_file_in_chat(self):
|
||||
sample_file = "accumulate.py"
|
||||
content = (
|
||||
"def accumulate(collection, operation):\n return [operation(x) for x in"
|
||||
" collection]\n"
|
||||
)
|
||||
|
||||
with open(sample_file, "w") as f:
|
||||
f.write("Original content\n")
|
||||
|
||||
# Initialize WholeFileCoder with the temporary directory
|
||||
io = InputOutput(yes=True)
|
||||
coder = WholeFileCoder(main_model=self.GPT35, io=io, fnames=[sample_file])
|
||||
|
||||
# Set the partial response content with the updated content
|
||||
coder.partial_response_content = (
|
||||
f"Here's the modified `{sample_file}` file that implements the `accumulate`"
|
||||
f" function as per the given instructions:\n\n```\n{content}```\n\nThis"
|
||||
" implementation uses a list comprehension to apply the `operation` function to"
|
||||
" each element of the `collection` and returns the resulting list."
|
||||
)
|
||||
|
||||
# Call update_files method
|
||||
edited_files = coder.update_files()
|
||||
|
||||
# Check if the sample file was updated
|
||||
self.assertIn(sample_file, edited_files)
|
||||
|
||||
# Check if the content of the sample file was updated
|
||||
with open(sample_file, "r") as f:
|
||||
updated_content = f.read()
|
||||
self.assertEqual(updated_content, content)
|
||||
|
||||
def test_update_files_earlier_filename(self):
|
||||
fname_a = Path("a.txt")
|
||||
fname_b = Path("b.txt")
|
||||
|
||||
fname_a.write_text("before a\n")
|
||||
fname_b.write_text("before b\n")
|
||||
|
||||
response = """
|
||||
Here is a new version of `a.txt` for you to consider:
|
||||
|
||||
```
|
||||
after a
|
||||
```
|
||||
|
||||
And here is `b.txt`:
|
||||
|
||||
```
|
||||
after b
|
||||
```
|
||||
"""
|
||||
# Initialize WholeFileCoder with the temporary directory
|
||||
io = InputOutput(yes=True)
|
||||
coder = WholeFileCoder(main_model=self.GPT35, io=io, fnames=[fname_a, fname_b])
|
||||
|
||||
# Set the partial response content with the updated content
|
||||
coder.partial_response_content = response
|
||||
|
||||
# Call update_files method
|
||||
edited_files = coder.update_files()
|
||||
|
||||
# Check if the sample file was updated
|
||||
self.assertIn(str(fname_a), edited_files)
|
||||
self.assertIn(str(fname_b), edited_files)
|
||||
|
||||
self.assertEqual(fname_a.read_text(), "after a\n")
|
||||
self.assertEqual(fname_b.read_text(), "after b\n")
|
||||
|
||||
def test_update_named_file_but_extra_unnamed_code_block(self):
|
||||
sample_file = "hello.py"
|
||||
new_content = "new\ncontent\ngoes\nhere\n"
|
||||
|
||||
with open(sample_file, "w") as f:
|
||||
f.write("Original content\n")
|
||||
|
||||
# Initialize WholeFileCoder with the temporary directory
|
||||
io = InputOutput(yes=True)
|
||||
coder = WholeFileCoder(main_model=self.GPT35, io=io, fnames=[sample_file])
|
||||
|
||||
# Set the partial response content with the updated content
|
||||
coder.partial_response_content = (
|
||||
f"Here's the modified `{sample_file}` file that implements the `accumulate`"
|
||||
f" function as per the given instructions:\n\n```\n{new_content}```\n\nThis"
|
||||
" implementation uses a list comprehension to apply the `operation` function to"
|
||||
" each element of the `collection` and returns the resulting list.\n"
|
||||
"Run it like this:\n\n"
|
||||
"```\npython {sample_file}\n```\n\n"
|
||||
)
|
||||
|
||||
# Call update_files method
|
||||
edited_files = coder.update_files()
|
||||
|
||||
# Check if the sample file was updated
|
||||
self.assertIn(sample_file, edited_files)
|
||||
|
||||
# Check if the content of the sample file was updated
|
||||
with open(sample_file, "r") as f:
|
||||
updated_content = f.read()
|
||||
self.assertEqual(updated_content, new_content)
|
||||
|
||||
def test_full_edit(self):
|
||||
# Create a few temporary files
|
||||
_, file1 = tempfile.mkstemp()
|
||||
|
||||
with open(file1, "w", encoding="utf-8") as f:
|
||||
f.write("one\ntwo\nthree\n")
|
||||
|
||||
files = [file1]
|
||||
|
||||
# Initialize the Coder object with the mocked IO and mocked repo
|
||||
coder = Coder.create(self.GPT35, "whole", io=InputOutput(), fnames=files)
|
||||
|
||||
# no trailing newline so the response content below doesn't add ANOTHER newline
|
||||
new_content = "new\ntwo\nthree"
|
||||
|
||||
def mock_send(*args, **kwargs):
|
||||
coder.partial_response_content = f"""
|
||||
Do this:
|
||||
|
||||
{Path(file1).name}
|
||||
```
|
||||
{new_content}
|
||||
```
|
||||
|
||||
"""
|
||||
coder.partial_response_function_call = dict()
|
||||
return []
|
||||
|
||||
coder.send = MagicMock(side_effect=mock_send)
|
||||
|
||||
# Call the run method with a message
|
||||
coder.run(with_message="hi")
|
||||
|
||||
content = Path(file1).read_text(encoding="utf-8")
|
||||
|
||||
# check for one trailing newline
|
||||
self.assertEqual(content, new_content + "\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
Loading…
Add table
Add a link
Reference in a new issue