import dataclasses import functools import inspect import os import shutil import tempfile from argparse import Namespace from unittest.mock import patch import pytest import typer import gpt_engineer.applications.cli.main as main from gpt_engineer.applications.cli.main import load_prompt from gpt_engineer.core.default.disk_memory import DiskMemory from gpt_engineer.core.prompt import Prompt @functools.wraps(dataclasses.make_dataclass) def dcommand(typer_f, **kwargs): required = True def field_desc(name, param): nonlocal required t = param.annotation or "typing.Any" if param.default.default is not ...: required = False return name, t, dataclasses.field(default=param.default.default) if not required: raise ValueError("Required value after optional") return name, t kwargs.setdefault("cls_name", typer_f.__name__) params = inspect.signature(typer_f).parameters kwargs["fields"] = [field_desc(k, v) for k, v in params.items()] @functools.wraps(typer_f) def dcommand_decorator(function_or_class): assert callable(function_or_class) ka = dict(kwargs) ns = Namespace(**(ka.pop("namespace", None) or {})) if isinstance(function_or_class, type): ka["bases"] = *ka.get("bases", ()), function_or_class else: ns.__call__ = function_or_class ka["namespace"] = vars(ns) return dataclasses.make_dataclass(**ka) return dcommand_decorator @dcommand(main.main) class DefaultArgumentsMain: def __call__(self): attribute_dict = vars(self) main.main(**attribute_dict) def input_generator(): yield "y" # First response while True: yield "n" # Subsequent responses prompt_text = "Make a python program that writes 'hello' to a file called 'output.txt'" class TestMain: # Runs gpt-engineer cli interface for many parameter configurations, BUT DOES NOT CODEGEN! Only testing cli. def test_default_settings_generate_project(self, tmp_path, monkeypatch): p = tmp_path / "projects/example" p.mkdir(parents=True) (p / "prompt").write_text(prompt_text) args = DefaultArgumentsMain(str(p), llm_via_clipboard=True, no_execution=True) args() # Runs gpt-engineer with improve mode and improves an existing project in the specified path. def test_improve_existing_project(self, tmp_path, monkeypatch): p = tmp_path / "projects/example" p.mkdir(parents=True) (p / "prompt").write_text(prompt_text) args = DefaultArgumentsMain( str(p), improve_mode=True, llm_via_clipboard=True, no_execution=True ) args() # def improve_generator(): # yield "y" # while True: # yield "n" # Subsequent responses # # gen = improve_generator() # monkeypatch.setattr("builtins.input", lambda _: next(gen)) # p = tmp_path / "projects/example" # p.mkdir(parents=True) # (p / "prompt").write_text(prompt_text) # (p / "main.py").write_text("The program will be written in this file") # meta_p = p / META_DATA_REL_PATH # meta_p.mkdir(parents=True) # (meta_p / "file_selection.toml").write_text( # """ # [files] # "main.py" = "selected" # """ # ) # os.environ["GPTE_TEST_MODE"] = "True" # simplified_main(str(p), "improve") # DiskExecutionEnv(path=p) # del os.environ["GPTE_TEST_MODE"] # Runs gpt-engineer with lite mode and generates a project with only the main prompt. def test_lite_mode_generate_project(self, tmp_path, monkeypatch): p = tmp_path / "projects/example" p.mkdir(parents=True) (p / "prompt").write_text(prompt_text) args = DefaultArgumentsMain( str(p), lite_mode=True, llm_via_clipboard=True, no_execution=True ) args() # Runs gpt-engineer with clarify mode and generates a project after discussing the specification with the AI. def test_clarify_mode_generate_project(self, tmp_path, monkeypatch): p = tmp_path / "projects/example" p.mkdir(parents=True) (p / "prompt").write_text(prompt_text) args = DefaultArgumentsMain( str(p), clarify_mode=True, llm_via_clipboard=True, no_execution=True ) args() # Runs gpt-engineer with self-heal mode and generates a project after discussing the specification with the AI and self-healing the code. def test_self_heal_mode_generate_project(self, tmp_path, monkeypatch): p = tmp_path / "projects/example" p.mkdir(parents=True) (p / "prompt").write_text(prompt_text) args = DefaultArgumentsMain( str(p), self_heal_mode=True, llm_via_clipboard=True, no_execution=True ) args() def test_clarify_lite_improve_mode_generate_project(self, tmp_path, monkeypatch): p = tmp_path / "projects/example" p.mkdir(parents=True) (p / "prompt").write_text(prompt_text) args = DefaultArgumentsMain( str(p), improve_mode=True, lite_mode=True, clarify_mode=True, llm_via_clipboard=True, no_execution=True, ) pytest.raises(typer.Exit, args) # Tests the creation of a log file in improve mode. class TestLoadPrompt: # Load prompt from existing file in input_repo def test_load_prompt_existing_file(self): with tempfile.TemporaryDirectory() as tmp_dir: input_repo = DiskMemory(tmp_dir) prompt_file = "prompt.txt" prompt_content = "This is the prompt" input_repo[prompt_file] = prompt_content improve_mode = False image_directory = "" result = load_prompt(input_repo, improve_mode, prompt_file, image_directory) assert isinstance(result, Prompt) assert result.text == prompt_content assert result.image_urls is None # Prompt file does not exist in input_repo, and improve_mode is False def test_load_prompt_no_file_improve_mode_false(self): with tempfile.TemporaryDirectory() as tmp_dir: input_repo = DiskMemory(tmp_dir) prompt_file = "prompt.txt" improve_mode = False image_directory = "" with patch( "builtins.input", return_value="What application do you want gpt-engineer to generate?", ): result = load_prompt( input_repo, improve_mode, prompt_file, image_directory ) assert isinstance(result, Prompt) assert ( result.text == "What application do you want gpt-engineer to generate?" ) assert result.image_urls is None # Prompt file is a directory def test_load_prompt_directory_file(self): with tempfile.TemporaryDirectory() as tmp_dir: input_repo = DiskMemory(tmp_dir) prompt_file = os.path.join(tmp_dir, "prompt") os.makedirs(os.path.join(tmp_dir, prompt_file)) improve_mode = False image_directory = "" with pytest.raises(ValueError): load_prompt(input_repo, improve_mode, prompt_file, image_directory) # Prompt file is empty def test_load_prompt_empty_file(self): with tempfile.TemporaryDirectory() as tmp_dir: input_repo = DiskMemory(tmp_dir) prompt_file = "prompt.txt" input_repo[prompt_file] = "" improve_mode = False image_directory = "" with patch( "builtins.input", return_value="What application do you want gpt-engineer to generate?", ): result = load_prompt( input_repo, improve_mode, prompt_file, image_directory ) assert isinstance(result, Prompt) assert ( result.text == "What application do you want gpt-engineer to generate?" ) assert result.image_urls is None # image_directory does not exist in input_repo def test_load_prompt_no_image_directory(self): with tempfile.TemporaryDirectory() as tmp_dir: input_repo = DiskMemory(tmp_dir) prompt_file = "prompt.txt" prompt_content = "This is the prompt" input_repo[prompt_file] = prompt_content improve_mode = False image_directory = "tests/test_data" shutil.copytree(image_directory, os.path.join(tmp_dir, image_directory)) result = load_prompt(input_repo, improve_mode, prompt_file, image_directory) assert isinstance(result, Prompt) assert result.text == prompt_content assert "mona_lisa.jpg" in result.image_urls # def test_log_creation_in_improve_mode(self, tmp_path, monkeypatch): # def improve_generator(): # yield "y" # while True: # yield "n" # Subsequent responses # # gen = improve_generator() # monkeypatch.setattr("builtins.input", lambda _: next(gen)) # p = tmp_path / "projects/example" # p.mkdir(parents=True) # (p / "prompt").write_text(prompt_text) # (p / "main.py").write_text("The program will be written in this file") # meta_p = p / META_DATA_REL_PATH # meta_p.mkdir(parents=True) # (meta_p / "file_selection.toml").write_text( # """ # [files] # "main.py" = "selected" # """ # ) # os.environ["GPTE_TEST_MODE"] = "True" # simplified_main(str(p), "improve") # DiskExecutionEnv(path=p) # assert ( # (p / f".gpteng/memory/{DEBUG_LOG_FILE}").read_text().strip() # == """UPLOADED FILES: # ``` # File: main.py # 1 The program will be written in this file # # ``` # PROMPT: # Make a python program that writes 'hello' to a file called 'output.txt' # CONSOLE OUTPUT:""" # ) # del os.environ["GPTE_TEST_MODE"] # # def test_log_creation_in_improve_mode_with_failing_diff( # self, tmp_path, monkeypatch # ): # def improve_generator(): # yield "y" # while True: # yield "n" # Subsequent responses # # def mock_salvage_correct_hunks( # messages: List, files_dict: FilesDict, error_message: List # ) -> FilesDict: # # create a falling diff # messages[ # -1 # ].content = """To create a Python program that writes 'hello' to a file called 'output.txt', we will need to perform the following steps: # # 1. Open the file 'output.txt' in write mode. # 2. Write the string 'hello' to the file. # 3. Close the file to ensure the data is written and the file is not left open. # # Here is the implementation of the program in the `main.py` file: # # ```diff # --- main.py # +++ main.py # @@ -0,0 +1,9 @@ # -create falling diff # ``` # # This concludes a fully working implementation.""" # # Call the original function with modified messages or define your own logic # return salvage_correct_hunks(messages, files_dict, error_message) # # gen = improve_generator() # monkeypatch.setattr("builtins.input", lambda _: next(gen)) # monkeypatch.setattr( # "gpt_engineer.core.default.steps.salvage_correct_hunks", # mock_salvage_correct_hunks, # ) # p = tmp_path / "projects/example" # p.mkdir(parents=True) # (p / "prompt").write_text(prompt_text) # (p / "main.py").write_text("The program will be written in this file") # meta_p = p / META_DATA_REL_PATH # meta_p.mkdir(parents=True) # (meta_p / "file_selection.toml").write_text( # """ # [files] # "main.py" = "selected" # """ # ) # os.environ["GPTE_TEST_MODE"] = "True" # simplified_main(str(p), "improve") # DiskExecutionEnv(path=p) # assert ( # (p / f".gpteng/memory/{DEBUG_LOG_FILE}").read_text().strip() # == """UPLOADED FILES: # ``` # File: main.py # 1 The program will be written in this file # # ``` # PROMPT: # Make a python program that writes 'hello' to a file called 'output.txt' # CONSOLE OUTPUT: # Invalid hunk: @@ -0,0 +1,9 @@ # -create falling diff # # Invalid hunk: @@ -0,0 +1,9 @@ # -create falling diff""" # ) # del os.environ["GPTE_TEST_MODE"] # # def test_log_creation_in_improve_mode_with_unexpected_exceptions( # self, tmp_path, monkeypatch # ): # def improve_generator(): # yield "y" # while True: # yield "n" # Subsequent responses # # def mock_salvage_correct_hunks( # messages: List, files_dict: FilesDict, error_message: List # ) -> FilesDict: # raise Exception("Mock exception in salvage_correct_hunks") # # gen = improve_generator() # monkeypatch.setattr("builtins.input", lambda _: next(gen)) # monkeypatch.setattr( # "gpt_engineer.core.default.steps.salvage_correct_hunks", # mock_salvage_correct_hunks, # ) # p = tmp_path / "projects/example" # p.mkdir(parents=True) # (p / "prompt").write_text(prompt_text) # (p / "main.py").write_text("The program will be written in this file") # meta_p = p / META_DATA_REL_PATH # meta_p.mkdir(parents=True) # (meta_p / "file_selection.toml").write_text( # """ # [files] # "main.py" = "selected" # """ # ) # os.environ["GPTE_TEST_MODE"] = "True" # simplified_main(str(p), "improve") # DiskExecutionEnv(path=p) # assert ( # (p / f".gpteng/memory/{DEBUG_LOG_FILE}").read_text().strip() # == """UPLOADED FILES: # ``` # File: main.py # 1 The program will be written in this file # # ``` # PROMPT: # Make a python program that writes 'hello' to a file called 'output.txt' # CONSOLE OUTPUT: # Error while improving the project: Mock exception in salvage_correct_hunks""" # ) # del os.environ["GPTE_TEST_MODE"]