moved tests/ to aider/tests/

This commit is contained in:
Paul Gauthier 2024-06-10 15:33:49 -07:00
parent 470b8d0251
commit ce46ea8d62
13 changed files with 7 additions and 1 deletions

View file

View file

@ -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()

View file

@ -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))

View file

@ -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()

View file

@ -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()

View file

@ -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)

View file

@ -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()

View file

@ -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)])

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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}"

View file

@ -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()