Skip to content

Commit c20f532

Browse files
Increase test coverage (#308)
* Added tests and moved to latest claude. * Adding test for AgentCompletionABC. * Adding comments to tests. * Added more comments. * Adding final comments. * Fixing inadvertent agent removal of code.
1 parent 0a69e33 commit c20f532

15 files changed

+774
-24
lines changed

cover_agent/UnitTestValidator.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,9 @@ def validate_test(self, generated_test: dict):
482482
)
483483
stdout, stderr, exit_code, time_of_test_command = (
484484
Runner.run_command(
485-
command=self.test_command, cwd=self.test_command_dir, max_run_time=self.max_run_time
485+
command=self.test_command,
486+
cwd=self.test_command_dir,
487+
max_run_time=self.max_run_time,
486488
)
487489
)
488490
if exit_code != 0:

cover_agent/version.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.3.5
1+
0.3.6

tests/test_AICaller.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,22 @@
66

77

88
class TestAICaller:
9+
"""
10+
Test suite for the AICaller class.
11+
"""
12+
913
@pytest.fixture
1014
def ai_caller(self):
15+
"""
16+
Fixture to create an instance of AICaller for testing.
17+
"""
1118
return AICaller("test-model", "test-api", enable_retry=False)
1219

1320
@patch("cover_agent.AICaller.AICaller.call_model")
1421
def test_call_model_simplified(self, mock_call_model):
22+
"""
23+
Test the call_model method with a simplified scenario.
24+
"""
1525
# Set up the mock to return a predefined response
1626
mock_call_model.return_value = ("Hello world!", 2, 10)
1727
prompt = {"system": "", "user": "Hello, world!"}
@@ -30,6 +40,9 @@ def test_call_model_simplified(self, mock_call_model):
3040

3141
@patch("cover_agent.AICaller.litellm.completion")
3242
def test_call_model_with_error(self, mock_completion, ai_caller):
43+
"""
44+
Test the call_model method when an exception is raised.
45+
"""
3346
# Set up mock to raise an exception
3447
mock_completion.side_effect = Exception("Test exception")
3548
prompt = {"system": "", "user": "Hello, world!"}
@@ -41,6 +54,9 @@ def test_call_model_with_error(self, mock_completion, ai_caller):
4154

4255
@patch("cover_agent.AICaller.litellm.completion")
4356
def test_call_model_error_streaming(self, mock_completion, ai_caller):
57+
"""
58+
Test the call_model method when an exception is raised during streaming.
59+
"""
4460
# Set up mock to raise an exception
4561
mock_completion.side_effect = ["results"]
4662
prompt = {"system": "", "user": "Hello, world!"}
@@ -57,6 +73,9 @@ def test_call_model_error_streaming(self, mock_completion, ai_caller):
5773
@patch.dict(os.environ, {"WANDB_API_KEY": "test_key"})
5874
@patch("cover_agent.AICaller.Trace.log")
5975
def test_call_model_wandb_logging(self, mock_log, mock_completion, ai_caller):
76+
"""
77+
Test the call_model method with W&B logging enabled.
78+
"""
6079
mock_completion.return_value = [
6180
{"choices": [{"delta": {"content": "response"}}]}
6281
]
@@ -74,6 +93,9 @@ def test_call_model_wandb_logging(self, mock_log, mock_completion, ai_caller):
7493

7594
@patch("cover_agent.AICaller.litellm.completion")
7695
def test_call_model_api_base(self, mock_completion, ai_caller):
96+
"""
97+
Test the call_model method with a different API base.
98+
"""
7799
mock_completion.return_value = [
78100
{"choices": [{"delta": {"content": "response"}}]}
79101
]
@@ -91,6 +113,9 @@ def test_call_model_api_base(self, mock_completion, ai_caller):
91113

92114
@patch("cover_agent.AICaller.litellm.completion")
93115
def test_call_model_with_system_key(self, mock_completion, ai_caller):
116+
"""
117+
Test the call_model method with a system key in the prompt.
118+
"""
94119
mock_completion.return_value = [
95120
{"choices": [{"delta": {"content": "response"}}]}
96121
]
@@ -106,6 +131,9 @@ def test_call_model_with_system_key(self, mock_completion, ai_caller):
106131
assert response_tokens == 10
107132

108133
def test_call_model_missing_keys(self, ai_caller):
134+
"""
135+
Test the call_model method when the prompt is missing required keys.
136+
"""
109137
prompt = {"user": "Hello, world!"}
110138
with pytest.raises(KeyError) as exc_info:
111139
ai_caller.call_model(prompt)
@@ -116,6 +144,9 @@ def test_call_model_missing_keys(self, ai_caller):
116144

117145
@patch("cover_agent.AICaller.litellm.completion")
118146
def test_call_model_o1_preview(self, mock_completion, ai_caller):
147+
"""
148+
Test the call_model method with the 'o1-preview' model.
149+
"""
119150
ai_caller.model = "o1-preview"
120151
prompt = {"system": "System message", "user": "Hello, world!"}
121152
# Mock the response
@@ -133,6 +164,9 @@ def test_call_model_o1_preview(self, mock_completion, ai_caller):
133164

134165
@patch("cover_agent.AICaller.litellm.completion")
135166
def test_call_model_streaming_response(self, mock_completion, ai_caller):
167+
"""
168+
Test the call_model method with a streaming response.
169+
"""
136170
prompt = {"system": "", "user": "Hello, world!"}
137171
# Mock the response to be an iterable of chunks
138172
mock_chunk = Mock()
@@ -155,6 +189,9 @@ def test_call_model_streaming_response(self, mock_completion, ai_caller):
155189
def test_call_model_wandb_logging_exception(
156190
self, mock_log, mock_completion, ai_caller
157191
):
192+
"""
193+
Test the call_model method with W&B logging and handle logging exceptions.
194+
"""
158195
mock_completion.return_value = [
159196
{"choices": [{"delta": {"content": "response"}}]}
160197
]

tests/test_AgentCompletionABC.py

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
import pytest
2+
from cover_agent.AgentCompletionABC import AgentCompletionABC
3+
4+
5+
# Dummy subclass that calls the parent's abstract method (executing "pass") then returns dummy values.
6+
class DummyAgent(AgentCompletionABC):
7+
"""
8+
A dummy implementation of AgentCompletionABC for testing purposes.
9+
"""
10+
11+
def generate_tests(
12+
self,
13+
source_file_name: str,
14+
max_tests: int,
15+
source_file_numbered: str,
16+
code_coverage_report: str,
17+
language: str,
18+
test_file: str,
19+
test_file_name: str,
20+
testing_framework: str,
21+
additional_instructions_text: str = None,
22+
additional_includes_section: str = None,
23+
failed_tests_section: str = None,
24+
) -> tuple:
25+
"""
26+
Generate tests for the given source file.
27+
28+
:param source_file_name: Name of the source file.
29+
:param max_tests: Maximum number of tests to generate.
30+
:param source_file_numbered: Numbered source file content.
31+
:param code_coverage_report: Code coverage report.
32+
:param language: Programming language of the source file.
33+
:param test_file: Content of the test file.
34+
:param test_file_name: Name of the test file.
35+
:param testing_framework: Testing framework to use.
36+
:param additional_instructions_text: Additional instructions text.
37+
:param additional_includes_section: Additional includes section.
38+
:param failed_tests_section: Failed tests section.
39+
:return: A tuple containing generated tests information.
40+
"""
41+
# Call the abstract method to execute the "pass"
42+
super().generate_tests(
43+
source_file_name,
44+
max_tests,
45+
source_file_numbered,
46+
code_coverage_report,
47+
language,
48+
test_file,
49+
test_file_name,
50+
testing_framework,
51+
additional_instructions_text,
52+
additional_includes_section,
53+
failed_tests_section,
54+
)
55+
return ("generated_tests", 10, 20, "final_prompt")
56+
57+
def analyze_test_failure(
58+
self,
59+
source_file_name: str,
60+
source_file: str,
61+
processed_test_file: str,
62+
stdout: str,
63+
stderr: str,
64+
test_file_name: str,
65+
) -> tuple:
66+
"""
67+
Analyze the failure of a test.
68+
69+
:param source_file_name: Name of the source file.
70+
:param source_file: Content of the source file.
71+
:param processed_test_file: Processed test file content.
72+
:param stdout: Standard output from the test run.
73+
:param stderr: Standard error from the test run.
74+
:param test_file_name: Name of the test file.
75+
:return: A tuple containing analyzed failure information.
76+
"""
77+
super().analyze_test_failure(
78+
source_file_name,
79+
source_file,
80+
processed_test_file,
81+
stdout,
82+
stderr,
83+
test_file_name,
84+
)
85+
return ("analyzed_failure", 30, 40, "failure_prompt")
86+
87+
def analyze_test_insert_line(
88+
self,
89+
language: str,
90+
test_file_numbered: str,
91+
test_file_name: str,
92+
additional_instructions_text: str = None,
93+
) -> tuple:
94+
"""
95+
Analyze the insertion line for a test.
96+
97+
:param language: Programming language of the test file.
98+
:param test_file_numbered: Numbered test file content.
99+
:param test_file_name: Name of the test file.
100+
:param additional_instructions_text: Additional instructions text.
101+
:return: A tuple containing insert line analysis information.
102+
"""
103+
super().analyze_test_insert_line(
104+
language, test_file_numbered, test_file_name, additional_instructions_text
105+
)
106+
return ("insert_line_instruction", 50, 60, "insert_prompt")
107+
108+
def analyze_test_against_context(
109+
self,
110+
language: str,
111+
test_file_content: str,
112+
test_file_name_rel: str,
113+
context_files_names_rel: str,
114+
) -> tuple:
115+
"""
116+
Analyze the test against the given context.
117+
118+
:param language: Programming language of the test file.
119+
:param test_file_content: Content of the test file.
120+
:param test_file_name_rel: Relative name of the test file.
121+
:param context_files_names_rel: Relative names of the context files.
122+
:return: A tuple containing context analysis information.
123+
"""
124+
super().analyze_test_against_context(
125+
language, test_file_content, test_file_name_rel, context_files_names_rel
126+
)
127+
return ("context_analysis", 70, 80, "context_prompt")
128+
129+
def analyze_suite_test_headers_indentation(
130+
self,
131+
language: str,
132+
test_file_name: str,
133+
test_file: str,
134+
) -> tuple:
135+
"""
136+
Analyze the headers indentation of a test suite.
137+
138+
:param language: Programming language of the test file.
139+
:param test_file_name: Name of the test file.
140+
:param test_file: Content of the test file.
141+
:return: A tuple containing suite analysis information.
142+
"""
143+
super().analyze_suite_test_headers_indentation(
144+
language, test_file_name, test_file
145+
)
146+
return ("suite_analysis", 90, 100, "suite_prompt")
147+
148+
def adapt_test_command_for_a_single_test_via_ai(
149+
self,
150+
test_file_relative_path: str,
151+
test_command: str,
152+
project_root_dir: str,
153+
) -> tuple:
154+
"""
155+
Adapt the test command for a single test using AI.
156+
157+
:param test_file_relative_path: Relative path of the test file.
158+
:param test_command: Original test command.
159+
:param project_root_dir: Root directory of the project.
160+
:return: A tuple containing adapted command information.
161+
"""
162+
super().adapt_test_command_for_a_single_test_via_ai(
163+
test_file_relative_path, test_command, project_root_dir
164+
)
165+
return ("adapted_command", 110, 120, "adapt_prompt")
166+
167+
168+
class TestAgentCompletionABC:
169+
"""
170+
Test cases for the AgentCompletionABC class.
171+
"""
172+
173+
def check_output_format(self, result):
174+
"""
175+
Check the format of the output result.
176+
177+
:param result: The result to check.
178+
"""
179+
assert isinstance(result, tuple), "Result is not a tuple"
180+
assert len(result) == 4, "Tuple does not have four elements"
181+
assert isinstance(
182+
result[0], str
183+
), "First element is not a string (AI-generated text)"
184+
assert isinstance(
185+
result[1], int
186+
), "Second element is not an integer (input token count)"
187+
assert isinstance(
188+
result[2], int
189+
), "Third element is not an integer (output token count)"
190+
assert isinstance(
191+
result[3], str
192+
), "Fourth element is not a string (final prompt)"
193+
194+
def test_instantiation_of_abstract_class(self):
195+
"""
196+
Test that instantiating the abstract class raises a TypeError.
197+
"""
198+
with pytest.raises(TypeError):
199+
AgentCompletionABC()
200+
201+
def test_generate_tests(self):
202+
"""
203+
Test the generate_tests method of DummyAgent.
204+
"""
205+
agent = DummyAgent()
206+
result = agent.generate_tests(
207+
"source.py",
208+
5,
209+
"numbered_source",
210+
"coverage",
211+
"python",
212+
"test_file_content",
213+
"test_file.py",
214+
"pytest",
215+
)
216+
self.check_output_format(result)
217+
218+
def test_analyze_test_failure(self):
219+
"""
220+
Test the analyze_test_failure method of DummyAgent.
221+
"""
222+
agent = DummyAgent()
223+
result = agent.analyze_test_failure(
224+
"source.py",
225+
"source_code",
226+
"processed_test",
227+
"stdout",
228+
"stderr",
229+
"test_file.py",
230+
)
231+
self.check_output_format(result)
232+
233+
def test_analyze_test_insert_line(self):
234+
"""
235+
Test the analyze_test_insert_line method of DummyAgent.
236+
"""
237+
agent = DummyAgent()
238+
result = agent.analyze_test_insert_line(
239+
"python", "numbered_test_file", "test_file.py"
240+
)
241+
self.check_output_format(result)
242+
243+
def test_analyze_test_against_context(self):
244+
"""
245+
Test the analyze_test_against_context method of DummyAgent.
246+
"""
247+
agent = DummyAgent()
248+
result = agent.analyze_test_against_context(
249+
"python", "test_file_content", "test_file.py", "context1.py, context2.py"
250+
)
251+
self.check_output_format(result)
252+
253+
def test_analyze_suite_test_headers_indentation(self):
254+
"""
255+
Test the analyze_suite_test_headers_indentation method of DummyAgent.
256+
"""
257+
agent = DummyAgent()
258+
result = agent.analyze_suite_test_headers_indentation(
259+
"python", "test_file.py", "test_file_content"
260+
)
261+
self.check_output_format(result)
262+
263+
def test_adapt_test_command_for_a_single_test_via_ai(self):
264+
"""
265+
Test the adapt_test_command_for_a_single_test_via_ai method of DummyAgent.
266+
"""
267+
agent = DummyAgent()
268+
result = agent.adapt_test_command_for_a_single_test_via_ai(
269+
"relative/path/test_file.py", "pytest --maxfail=1", "/project/root"
270+
)
271+
self.check_output_format(result)

0 commit comments

Comments
 (0)