Skip to content

Commit 6b05eb5

Browse files
authored
Feature-329: Refactor run_tests_all.py to work within pytest framework (#330)
* Minor initial changes * Add initial pytest stuff * Add README.md for configuration.toml file * Fix pytest.ini * Improving Docker test script * Fix cover-agent-full-repo * Adjust CI pipeline for E2E tests * Some code reformatting
1 parent 33c2e58 commit 6b05eb5

21 files changed

+459
-110
lines changed

.github/workflows/ci_pipeline.yml

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,13 +153,72 @@ jobs:
153153
name: cover-agent-${{ matrix.os }}
154154
path: dist/cover-agent*
155155

156+
e2e-test:
157+
# This job will run after building binary because it needs the binary to run the E2E tests in Docker
158+
needs: build
159+
runs-on: ubuntu-latest
160+
steps:
161+
- uses: actions/checkout@v2
162+
- name: Set up Python
163+
uses: actions/setup-python@v2
164+
with:
165+
python-version: '3.12'
166+
167+
# Download the Ubuntu binary
168+
- name: Download Ubuntu executable
169+
uses: actions/download-artifact@v4
170+
with:
171+
name: cover-agent-ubuntu-22.04
172+
path: dist
173+
174+
# Make the binary executable
175+
- name: Make binary executable
176+
run: chmod +x dist/cover-agent
177+
178+
# Cache and install dependencies
179+
- name: Cache Poetry dependencies
180+
uses: actions/cache@v4
181+
with:
182+
path: |
183+
~/.cache/pypoetry
184+
~/.cache/pip
185+
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
186+
restore-keys: |
187+
${{ runner.os }}-poetry-
188+
189+
- name: Install Poetry
190+
run: pip install poetry
191+
- name: Install dependencies using Poetry
192+
run: poetry install
193+
194+
# Run the E2E Docker tests using the Makefile target `e2e-test`
195+
- name: Run E2E Docker tests
196+
env:
197+
OPENAI_API_KEY: "This_is_a_fake_API_key"
198+
run: |
199+
make e2e-test 2>&1 | tee e2e_test_full.log
200+
201+
# Upload E2E test reports
202+
- name: Upload E2E Docker test report
203+
uses: actions/upload-artifact@v4
204+
if: always()
205+
with:
206+
name: e2e-test-reports
207+
path: |
208+
testLog_e2e.xml
209+
e2e_test_full.log
210+
156211
release:
157212
# This job will run only if the following conditions are met:
158213
# - The event is a 'push' to the 'main' branch
159214
# - The 'version.txt' file has changed
160-
# - There are relevant changes outside of 'README.md' and the 'docs/' folder
161-
needs: [build, changes]
162-
if: github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.changes.outputs.version_changed == 'true' && needs.changes.outputs.relevant_changes == 'true'
215+
# - There are relevant changes outside 'README.md' and the 'docs/' folder
216+
needs: [build, changes, e2e-test]
217+
if: >
218+
github.event_name == 'push' &&
219+
github.ref == 'refs/heads/main' &&
220+
needs.changes.outputs.version_changed == 'true' &&
221+
needs.changes.outputs.relevant_changes == 'true'
163222
runs-on: ubuntu-latest
164223
steps:
165224
- uses: actions/checkout@v2

Makefile

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,21 @@ TOML_FILES=$(shell find cover_agent/settings -name "*.toml" | sed 's/.*/-\-add-d
77

88
# Run unit tests with Pytest
99
test:
10-
poetry run pytest --junitxml=testLog.xml --cov=cover_agent --cov-report=xml:cobertura.xml --cov-report=term --cov-fail-under=65 --log-cli-level=INFO
10+
poetry run pytest \
11+
-m "not e2e_docker" \
12+
--junitxml=testLog.xml \
13+
--cov=cover_agent \
14+
--cov-report=xml:cobertura.xml \
15+
--cov-report=term \
16+
--cov-fail-under=65
17+
--log-cli-level=INFO
18+
19+
e2e-test:
20+
poetry run pytest \
21+
-m "e2e_docker" \
22+
--capture=no \
23+
--junitxml=testLog_e2e.xml \
24+
--log-cli-level=INFO
1125

1226
# Use Python Black to format python files
1327
format:

cover_agent/CoverAgent.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,21 @@
22
import os
33
import shutil
44
import sys
5-
import wandb
65

76
from typing import Optional
87

9-
from cover_agent.CustomLogger import CustomLogger
10-
from cover_agent.UnitTestGenerator import UnitTestGenerator
11-
from cover_agent.UnitTestValidator import UnitTestValidator
12-
from cover_agent.UnitTestDB import UnitTestDB
13-
from cover_agent.AICaller import AICaller
8+
import wandb
9+
1410
from cover_agent.AgentCompletionABC import AgentCompletionABC
15-
from cover_agent.DefaultAgentCompletion import DefaultAgentCompletion
1611
from cover_agent.ai_caller_replay import AICallerReplay
12+
from cover_agent.AICaller import AICaller
13+
from cover_agent.CustomLogger import CustomLogger
14+
from cover_agent.DefaultAgentCompletion import DefaultAgentCompletion
1715
from cover_agent.record_replay_manager import RecordReplayManager
1816
from cover_agent.settings.config_schema import CoverAgentConfig
17+
from cover_agent.UnitTestDB import UnitTestDB
18+
from cover_agent.UnitTestGenerator import UnitTestGenerator
19+
from cover_agent.UnitTestValidator import UnitTestValidator
1920

2021

2122
class CoverAgent:
@@ -381,6 +382,7 @@ def run(self):
381382
failed_test_runs, language, test_framework, coverage_report = self.init()
382383

383384
while iteration_count < self.config.max_iterations:
385+
self.logger.info(f"Iteration {iteration_count + 1} of {self.config.max_iterations}.")
384386
self.generate_and_validate_tests(failed_test_runs, language, test_framework, coverage_report)
385387

386388
failed_test_runs, language, test_framework, coverage_report, target_reached = self.check_iteration_progress()

cover_agent/UnitTestValidator.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -198,9 +198,9 @@ def initial_test_suite_analysis(self):
198198
None
199199
"""
200200
try:
201+
settings = get_settings().get("default")
201202
test_headers_indentation = None
202-
# TODO: Move to configuration.toml?
203-
allowed_attempts = 3
203+
allowed_attempts = settings.get("test_headers_indentation_attempts", 3)
204204
counter_attempts = 0
205205
while (
206206
test_headers_indentation is None and counter_attempts < allowed_attempts
@@ -233,8 +233,6 @@ def initial_test_suite_analysis(self):
233233

234234
relevant_line_number_to_insert_tests_after = None
235235
relevant_line_number_to_insert_imports_after = None
236-
# TODO: Move to configuration.toml?
237-
allowed_attempts = 3
238236
counter_attempts = 0
239237
while (
240238
not relevant_line_number_to_insert_tests_after

cover_agent/__main__.py

Lines changed: 0 additions & 4 deletions
This file was deleted.

cover_agent/main_full_repo.py

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,39 @@
11
import asyncio
22
import copy
3-
import os
3+
44
from cover_agent.AICaller import AICaller
5-
from cover_agent.utils import parse_args_full_repo, find_test_files
65
from cover_agent.CoverAgent import CoverAgent
76
from cover_agent.lsp_logic.ContextHelper import ContextHelper
7+
from cover_agent.settings.config_loader import get_settings
8+
from cover_agent.settings.config_schema import CoverAgentConfig
9+
from cover_agent.utils import find_test_files, parse_args_full_repo
810

911

1012
async def run():
11-
args = parse_args_full_repo()
13+
settings = get_settings().get("default")
14+
args = parse_args_full_repo(settings)
1215

1316
if args.project_language == "python":
1417
context_helper = ContextHelper(args)
1518
else:
16-
raise NotImplementedError(
17-
"Unsupported language: {}".format(args.project_language)
18-
)
19+
raise NotImplementedError("Unsupported language: {}".format(args.project_language))
1920

2021
# scan the project directory for test files
2122
test_files = find_test_files(args)
22-
print(
23-
"============\nTest files to be extended:\n"
24-
+ "".join(f"{f}\n============\n" for f in test_files)
25-
)
23+
print("============\nTest files to be extended:\n" + "".join(f"{f}\n============\n" for f in test_files))
2624

2725
# start the language server
2826
async with context_helper.start_server():
2927
print("LSP server initialized.")
3028

31-
ai_caller = AICaller(model=args.model)
29+
generate_log_files = not args.suppress_log_files
30+
ai_caller = AICaller(model=args.model, generate_log_files=generate_log_files)
3231

3332
# main loop for analyzing test files
3433
for test_file in test_files:
3534
# Find the context files for the test file
3635
context_files = await context_helper.find_test_file_context(test_file)
37-
print(
38-
"Context files for test file '{}':\n{}".format(
39-
test_file, "".join(f"{f}\n" for f in context_files)
40-
)
41-
)
36+
print("Context files for test file '{}':\n{}".format(test_file, "".join(f"{f}\n" for f in context_files)))
4237

4338
# Analyze the test file against the context files
4439
print("\nAnalyzing test file against context files...")
@@ -54,7 +49,9 @@ async def run():
5449
args_copy.test_command_dir = args.project_root
5550
args_copy.test_file_path = test_file
5651
args_copy.included_files = context_files_include
57-
agent = CoverAgent(args_copy)
52+
53+
config = CoverAgentConfig.from_cli_args_with_defaults(args_copy)
54+
agent = CoverAgent(config)
5855
agent.run()
5956
except Exception as e:
6057
print(f"Error running CoverAgent for test file '{test_file}': {e}")

cover_agent/settings/README.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# [DRAFT] Configuration Parameters
2+
3+
Explanation of parameters from the `configuration.toml` file.
4+
5+
## [default]
6+
7+
### LLM Configuration
8+
- `model`: The LLM model to use for test generation (default: `gpt-4o-2024-11-20`)
9+
- `api_base`: Base URL for API calls, used for local LLM servers (default: `http://localhost:11434`)
10+
- `model_retries`: Number of retry attempts for failed LLM calls (default: `3`)
11+
12+
### Coverage Settings
13+
- `desired_coverage`: Target code coverage percentage to achieve (default: `70`)
14+
- `coverage_type`: Type of coverage report to generate (default: `cobertura`)
15+
16+
### Execution Limits
17+
- `max_iterations`: Maximum number of test generation iterations (default: `3`)
18+
- `max_run_time_sec`: Maximum runtime in seconds for each test generation attempt (default: `30`)
19+
- `max_tests_per_run`: Maximum number of tests to generate per run (default: `4`)
20+
- `allowed_initial_test_analysis_attempts`: Number of attempts for initial test analysis (default: `3`)
21+
- `run_tests_multiple_times`: Number of times to run each test for consistency (default: `1`)
22+
23+
### File Paths
24+
- `log_file_path`: Path to the main log file and its name (default: `run.log`)
25+
- `log_db_path`: Path to the SQLite database for logging and its name (default: `cover_agent_unit_test_runs.db`)
26+
- `report_filepath`: Path to the HTML test results report and its name (default: `test_results.html`)
27+
- `responses_folder`: Directory for storing LLM responses (default: `stored_responses`)
28+
29+
### Docker Settings
30+
- `cover_agent_host_folder`: Host machine folder for cover-agent (default: `dist/cover-agent`)
31+
- `cover_agent_container_folder`: Container folder for cover-agent (default: `/usr/local/bin/cover-agent`)
32+
- `docker_hash_display_length`: Length of displayed Docker hash (default:`12`)
33+
- `record_replay_hash_display_length`: Length of displayed record/replay hash (default: `12`)
34+
35+
### Git Settings
36+
- `branch`: Git branch to use (default: `main`)
37+
38+
### Fuzzy Lookup Settings
39+
- `fuzzy_lookup_threshold`: Threshold for fuzzy matching (default:`95`)
40+
- `fuzzy_lookup_prefix_length`: Prefix length for fuzzy lookup (default: `1000`)
41+
- `fuzzy_lookup_best_ratio`: Best ratio for fuzzy matching (default: `0`)
42+
43+
## [include_files]
44+
- `limit_tokens`: Whether to limit tokens for included files (default: `true`)
45+
- `max_tokens`: Maximum tokens allowed for included files (default: `20000`)
46+
47+
## [tests]
48+
- `max_allowed_runtime_seconds`: Maximum allowed runtime for tests in seconds (default: `30`)

cover_agent/settings/config_schema.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class CoverageType(Enum):
1717
COBERTURA: Represents the Cobertura coverage report format.
1818
JACOCO: Represents the JaCoCo coverage report format.
1919
"""
20+
2021
LCOV = "lcov"
2122
COBERTURA = "cobertura"
2223
JACOCO = "jacoco"
@@ -57,6 +58,7 @@ class CoverAgentConfig:
5758
record_mode (bool): Enable LLM responses record mode for tests.
5859
test_command_original (str): Original test command before any modifications.
5960
"""
61+
6062
source_file_path: str
6163
test_file_path: str
6264
project_root: str
@@ -82,6 +84,9 @@ class CoverAgentConfig:
8284
run_each_test_separately: bool
8385
record_mode: bool
8486
suppress_log_files: bool
87+
max_test_files_allowed_to_analyze: int
88+
look_for_oldest_unchanged_test_file: bool
89+
project_language: str
8590
test_command_original: Optional[str] = None
8691

8792
@classmethod
@@ -122,6 +127,9 @@ def from_cli_args(cls, args: argparse.Namespace) -> "CoverAgentConfig":
122127
run_each_test_separately=args.run_each_test_separately,
123128
record_mode=args.record_mode,
124129
suppress_log_files=args.suppress_log_files,
130+
max_test_files_allowed_to_analyze=args.max_test_files_allowed_to_analyze,
131+
look_for_oldest_unchanged_test_file=args.look_for_oldest_unchanged_test_file,
132+
project_language=args.project_language,
125133
)
126134

127135
@classmethod
@@ -165,6 +173,9 @@ def from_cli_args_with_defaults(cls, args: argparse.Namespace) -> "CoverAgentCon
165173
"run_each_test_separately": default_config.get("run_each_test_separately"),
166174
"record_mode": default_config.get("record_mode"),
167175
"suppress_log_files": default_config.get("suppress_log_files"),
176+
"max_test_files_allowed_to_analyze": default_config.get("max_test_files_allowed_to_analyze"),
177+
"look_for_oldest_unchanged_test_file": default_config.get("look_for_oldest_unchanged_test_file"),
178+
"project_language": default_config.get("project_language"),
168179
}
169180

170181
# CLI overrides default settings

cover_agent/settings/configuration.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
[default]
22
model = "gpt-4o-2024-11-20"
3+
model_full_repo = "bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0"
34
desired_coverage = 70
5+
desired_coverage_full_repo = 100
46
max_iterations = 3
7+
max_test_files_allowed_to_analyze = 20
58
api_base = "http://localhost:11434"
69
max_run_time_sec = 30
710
max_tests_per_run = 4
11+
allowed_initial_test_analysis_attempts = 3
812
model_retries = 3
913
run_tests_multiple_times = 1
1014
branch = "main"
15+
project_language = "python"
1116
coverage_type = "cobertura"
1217
log_file_path = "run.log"
1318
log_db_path = "cover_agent_unit_test_runs.db"

0 commit comments

Comments
 (0)