Skip to content

Commit 488a1b4

Browse files
authored
Merge pull request #2 from JamesVorder/sandbox-dir
Sandbox Directory
2 parents d149230 + c418843 commit 488a1b4

File tree

2 files changed

+71
-48
lines changed

2 files changed

+71
-48
lines changed

TestRunner/GenericTestRunner.py

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import pytest
22
import subprocess
3+
from lib.utils import CodeGenSandbox
34
from abc import ABCMeta, abstractmethod
45
from pytest_plugins import ResultsCollector, SessionStartPlugin
56

@@ -10,35 +11,38 @@ def run(self, *args, **kwargs) -> (int, str): pass
1011

1112

1213
class SubProcessTestRunner(GenericTestRunner):
13-
code_dir: str
14-
test_dir: str
14+
sandbox: CodeGenSandbox
1515

16-
def __init__(self, _code, _test) -> None:
17-
self.code_dir = _code
18-
self.test_dir = _test
16+
def __init__(self, sandbox: CodeGenSandbox) -> None:
17+
self.sandbox = sandbox
1918

2019
def run(self, *args, **kwargs) -> (int, str):
21-
# TODO: check that code_dir and test_dir exist
22-
proc = subprocess.run(["pytest", self.test_dir], capture_output=True, universal_newlines=True)
20+
proc = subprocess.run(
21+
["pytest", self.sandbox.test_path],
22+
cwd=self.sandbox.get_sandboxed_project_path(),
23+
capture_output=True,
24+
universal_newlines=True
25+
)
2326
return proc.returncode, proc.stdout
2427

2528

26-
class InlineTestRunner(GenericTestRunner):
27-
28-
def __init__(self, _code, _test) -> None:
29-
self.code_dir = _code
30-
self.test_dir = _test
31-
32-
def run(self, *args, **kwargs) -> (int, str):
33-
collector = ResultsCollector()
34-
setup = SessionStartPlugin()
35-
pytest.main(args=["-k", "ExampleClass"], plugins=[collector, setup])
36-
_out = ""
37-
38-
if collector.exitcode > 0:
39-
for report in collector.reports:
40-
_out += f"{report.outcome.upper()} {report.nodeid} ... Outcome: - {report.longrepr.reprcrash.message}"
41-
_out += "\n"
42-
_out += report.longreprtext
43-
_out += "\n"
44-
return collector.exitcode, _out
29+
# TODO: This implementation is currently defunct
30+
# class InlineTestRunner(GenericTestRunner):
31+
#
32+
# def __init__(self, sandbox) -> None:
33+
# self.test_dir = sandbox.get_sandboxed_test_path()
34+
#
35+
# def run(self, *args, **kwargs) -> (int, str):
36+
# collector = ResultsCollector()
37+
# setup = SessionStartPlugin()
38+
# # TODO: Remove the ExampleClass reference here.
39+
# pytest.main(args=["-k", "ExampleClass"], plugins=[collector, setup])
40+
# _out = ""
41+
#
42+
# if collector.exitcode > 0:
43+
# for report in collector.reports:
44+
# _out += f"{report.outcome.upper()} {report.nodeid} ... Outcome: - {report.longrepr.reprcrash.message}"
45+
# _out += "\n"
46+
# _out += report.longreprtext
47+
# _out += "\n"
48+
# return collector.exitcode, _out

main.py

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
import os
2+
from lib.utils import CodeGenSandbox
23
import typer
34

45
import langroid as lr
56
from langroid.utils.configuration import set_global, Settings
67
from langroid.utils.logging import setup_colored_logging
78

8-
from TestRunner.GenericTestRunner import GenericTestRunner, InlineTestRunner, SubProcessTestRunner
9+
from TestRunner.GenericTestRunner import GenericTestRunner, SubProcessTestRunner
910

1011
app = typer.Typer()
1112
setup_colored_logging()
1213

1314

14-
def generate_first_attempt(class_skeleton: str) -> None:
15-
with open(class_skeleton, "r") as f:
15+
def generate_first_attempt(sandbox: CodeGenSandbox) -> None:
16+
with open(sandbox.get_sandboxed_class_path(), "r") as f:
1617
class_skeleton = f.read()
1718

1819
cfg = lr.ChatAgentConfig(
@@ -30,11 +31,11 @@ def generate_first_attempt(class_skeleton: str) -> None:
3031
f"Do not say 'here is the python code'"
3132
f"Your output MUST be valid, runnable python code and NOTHING else."
3233
f"{class_skeleton}")
33-
with open(os.path.join(".", "generated", "test_class.py"), "w+") as _out:
34+
with open(sandbox.get_sandboxed_class_path(), "w+") as _out:
3435
_out.write(response.content)
3536

3637

37-
def generate_next_attempt(test_results: str, test_results_insights: str) -> None:
38+
def generate_next_attempt(sandbox: CodeGenSandbox, test_results: str, test_results_insights: str) -> None:
3839
cfg = lr.ChatAgentConfig(
3940
llm=lr.language_models.OpenAIGPTConfig(
4041
chat_model="ollama/llama3:latest",
@@ -43,7 +44,7 @@ def generate_next_attempt(test_results: str, test_results_insights: str) -> None
4344
vecdb=None
4445
)
4546
agent = lr.ChatAgent(cfg)
46-
with open(os.path.join(".", "generated", "test_class.py"), "r") as f:
47+
with open(sandbox.get_sandboxed_class_path(), "r") as f:
4748
code_snippet = f.read()
4849

4950
prompt = f"""
@@ -65,7 +66,7 @@ def generate_next_attempt(test_results: str, test_results_insights: str) -> None
6566
Your output MUST be valid, runnable python code and NOTHING else.
6667
"""
6768
response = agent.llm_response(prompt)
68-
with open(os.path.join(".", "generated", "test_class.py"), "w") as _out:
69+
with open(sandbox.get_sandboxed_class_path(), "w") as _out:
6970
_out.write(response.content)
7071

7172

@@ -101,8 +102,8 @@ def teardown() -> None:
101102
generated_file.truncate(0)
102103

103104

104-
def chat(class_skeleton: str, test_dir: str, test_runner: GenericTestRunner, max_epochs: int=5) -> None:
105-
generate_first_attempt(class_skeleton)
105+
def chat(sandbox: CodeGenSandbox, test_runner: GenericTestRunner, max_epochs: int=5) -> None:
106+
generate_first_attempt(sandbox)
106107
solved = False
107108
for _ in range(max_epochs):
108109
# test_exit_code, test_results = get_test_results()
@@ -114,22 +115,44 @@ def chat(class_skeleton: str, test_dir: str, test_runner: GenericTestRunner, max
114115
break
115116
elif test_exit_code == 1:
116117
results_insights = interpret_test_results(test_results)
117-
generate_next_attempt(test_results, results_insights)
118+
generate_next_attempt(sandbox, test_results, results_insights)
118119
else:
119120
solved = True
120121
print("There is some problem with the test suite itself.")
121122
break
122-
teardown()
123+
# teardown()
123124
if not solved:
124125
print(f"Reached the end of epoch {max_epochs} without finding a solution :(")
125126

127+
126128
@app.command()
127129
def main(
128130
debug: bool = typer.Option(False, "--debug", "-d", help="debug mode"),
129131
no_stream: bool = typer.Option(False, "--nostream", "-ns", help="no streaming"),
130132
nocache: bool = typer.Option(False, "--nocache", "-nc", help="don't use cache"),
131-
class_skeleton: str = typer.Option(None, "--class-skeleton", "-c", help="You must provide a class skeleton."),
132-
test_dir: str = typer.Option(os.path.join(".", "test"), "--test-dir", "-t", help=""),
133+
project_dir: str = typer.Argument(
134+
default=".",
135+
help="The project directory that contains your tests and class skeleton. "
136+
"This directory may also have other contents. "
137+
"The directory you give here will be cloned into a 'sandbox' for the code generator to operate in."
138+
),
139+
class_skeleton_path: str = typer.Argument(
140+
default=os.path.join("assets", "test_class.py"),
141+
help="Path to the class skeleton file, relative to project_dir."
142+
),
143+
test_path: str = typer.Argument(
144+
default=os.path.join(".", "test"),
145+
help="Path to the test file or directory, relative to project_dir."
146+
),
147+
sandbox_path: str = typer.Option(
148+
"./build", "--sandbox-path", "-s",
149+
help="You may optionally specify a location for the sandbox in which the code generator operates."
150+
"Default: ./build"
151+
),
152+
max_epochs: int = typer.Option(
153+
5, "--max-epochs", "-n", help="The maximum number of times to let the code generator try"
154+
"before giving up."
155+
)
133156
) -> None:
134157
set_global(
135158
Settings(
@@ -138,15 +161,11 @@ def main(
138161
stream=not no_stream,
139162
)
140163
)
141-
assert os.path.isfile(class_skeleton), f"The class skeleton file provided does not exist! Got {class_skeleton}"
142-
assert os.path.exists(test_dir), f"The test-dir provided does not exist! Got {test_dir}"
143-
144-
tr: GenericTestRunner = SubProcessTestRunner("", test_dir)
145-
chat(
146-
class_skeleton=class_skeleton,
147-
test_dir=test_dir,
148-
test_runner=tr
149-
)
164+
165+
sandbox = CodeGenSandbox(project_dir, class_skeleton_path, test_path, sandbox_path)
166+
sandbox.init_sandbox()
167+
tr: GenericTestRunner = SubProcessTestRunner(sandbox)
168+
chat(sandbox, tr, max_epochs=max_epochs)
150169

151170

152171
if __name__ == "__main__":

0 commit comments

Comments
 (0)