Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tr/making the tests great #50

Merged
merged 15 commits into from
May 28, 2024
3 changes: 3 additions & 0 deletions cover_agent/PromptBuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ def __init__(
self.source_file_numbered = "\n".join(
[f"{i+1} {line}" for i, line in enumerate(self.source_file.split("\n"))]
)
self.test_fule_numbered = "\n".join(
[f"{i+1} {line}" for i, line in enumerate(self.test_file.split("\n"))])

# Conditionally fill in optional sections
self.included_files = (
Expand Down Expand Up @@ -125,6 +127,7 @@ def build_prompt(self) -> dict:
"source_file_name": self.source_file_name,
"test_file_name": self.test_file_name,
"source_file_numbered": self.source_file_numbered,
"test_file_numbered": self.test_fule_numbered,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling error: fule but should be file. Need to refactor.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

"source_file": self.source_file,
"test_file": self.test_file,
"code_coverage_report": self.code_coverage_report,
Expand Down
273 changes: 132 additions & 141 deletions cover_agent/UnitTestGenerator.py

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions cover_agent/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,14 +160,14 @@ def main():
logger.info(f"Desired Coverage: {test_gen.desired_coverage}%")

# Generate tests by making a call to the LLM
generated_tests = test_gen.generate_tests(max_tokens=4096)
generated_tests_dict = test_gen.generate_tests(max_tokens=4096)

# Write test_gen.prompt to a debug markdown file
write_prompt_to_file(GENERATED_PROMPT_NAME, test_gen.prompt)

# Validate each test and append the results to the test results list
for generated_test in generated_tests:
test_result = test_gen.validate_test(generated_test)
for generated_test in generated_tests_dict['tests']:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion from PR Agent

Suggested change
for generated_test in generated_tests_dict['tests']:
for generated_test in generated_tests_dict.get('tests', []):

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

test_result = test_gen.validate_test(generated_test, generated_tests_dict)
test_results_list.append(test_result)

# Increment the iteration counter
Expand Down
18 changes: 12 additions & 6 deletions cover_agent/settings/test_generation_prompt.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ Additional guidelines:
- Carefully analyze the provided code. Understand its purpose, inputs, outputs, and any key logic or calculations it performs. Spend significant time considering all different scenarios, including boundary values, invalid inputs, extreme conditions, and concurrency issues like race conditions and deadlocks, that need to be tested.
- Brainstorm a list of test cases you think will be necessary to fully validate the correctness of the code and achieve 100% code coverage.
- After each individual test has been added, review all tests to ensure they cover the full range of scenarios, including how to handle exceptions or errors.
- Each new test should be a single self-contained independent {{ language }} function, without relying on any other tests, or setup code. \
Each new test should be seen as appended at the end of original test file, and it should use the same testing framework and conventions as the existing tests.
- If the original test file contained a test suite, assume that each generated test will be a part of the same suite. Ensure that the new tests are consistent with the existing test suite in terms of style, naming conventions, and structure.


## Source File
Expand All @@ -25,9 +24,9 @@ This numbers are not part of the original code.


## Test File
Here is the file that contains the existing tests, called `{{ test_file_name }}`:
Here is the file that contains the existing tests, called `{{ test_file_name }}`. Again, the line numbers are added manually, and are not part of the original code.
=========
{{ test_file|trim }}
{{ test_file_numbered|trim }}
=========


Expand Down Expand Up @@ -63,17 +62,22 @@ class SingleTest(BaseModel):
{%- else %}
test_name: str = Field(description=" A short unique test name, that should reflect the test objective")
{%- endif %}
test_code: str = Field(description="A single self-contained test function, that tests the behavior described in 'test_behavior'. Do not add new dependencies.")
test_code: str = Field(description="A single test function, that tests the behavior described in 'test_behavior'. Do not add new dependencies.")
new_imports_code: str = Field(description="New imports that are required for the new test function, and are not already present in the test file. Give an empty string if no new imports are required.")
test_tags: str = Field(description="A single label that best describes the test, out of: ['happy path', 'edge case','other']")

class NewTests(BaseModel):
language: str = Field(description="The programming language of the source code")
tests: List[SingleTest] = Field(min_items=1, max_items={{ max_tests }}, description="A list of new test functions to append to the existing test file, aiming to increase the code coverage.")
relevant_line_to_insert_after: int = Field(description="The line number in the test file, **after which** the new tests should be inserted, so they will be a part of the existing test suite. Choose the last line possible")
needed_indent: int = Field(description="The number of spaces currently used to indent existing test suite functions (usually 0, 2, 4 etc.)")
tests: List[SingleTest] = Field(min_items=1, max_items={{ max_tests }}, description="A list of new test functions to append to the existing test suite, aiming to increase the code coverage.")
=====

Example output:
```yaml
language: {{ language }}
relevant_line_to_insert_after: 10
needed_indent: ...
tests:
- test_behavior: |
Test that the function returns the correct output for a single element list
Expand All @@ -90,6 +94,8 @@ tests:
{%- else %}
...
{%- endif %}
new_imports_code: |
""
test_tags: happy path
...
```
Expand Down
3 changes: 3 additions & 0 deletions templated_tests/js_vanilla/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules/
package-lock.json
coverage/
3 changes: 3 additions & 0 deletions templated_tests/js_vanilla/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# js-vanilla-example

A simple vanilla js project to find github users and their repositories.
22 changes: 22 additions & 0 deletions templated_tests/js_vanilla/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// search input
const github = new Github();
const ui = new UI();
const searchUser = document.getElementById('userName');

searchUser.addEventListener('keyup', (e) => {
const userText = e.target.value;
if (userText !== '') {
github.getUser(userText).then((data) => {
if (data.profile.message === 'Not Found') {
//show alert
ui.showAlert('Profile Not Found', 'alert alert-danger');
} else {
ui.showProfile(data.profile);
ui.showRepos(data.repos);
}
});
} else {
// clear profile
ui.clearProfile();
}
});
12 changes: 12 additions & 0 deletions templated_tests/js_vanilla/bootstrap.min.css

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions templated_tests/js_vanilla/github.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
class Github {
constructor() {
this.client_id = 'ab7e3c6ac10d3714249a';
this.client_secret = 'f315c3cc4bca8b4b922fc04af1b31b02cb1d143d';
this.repos_count = 5;
this.repos_sort = 'created: asc';
}

async getUser(user) {
const profileResponse = await fetch(
`https://api.github.com/users/${user}?client_id=${this.client_id}&client_secret=${this.client_secret}`
);
const reposResponse = await fetch(
`https://api.github.com/users/${user}/repos?per_page=${this.repos_count}&sort=${this.repos_sort}&client_id=${this.client_id}&client_secret=${this.client_secret}`
);

const profile = await profileResponse.json();
const repos = await reposResponse.json();

return {
profile: profile,
repos: repos,
};
}
}
33 changes: 33 additions & 0 deletions templated_tests/js_vanilla/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GitHub User Finder</title>
<link rel="stylesheet" href="bootstrap.min.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<nav class="navbar navbar-dark bg-primary mb-3">
<div class="container">
<a href="" class="navbar-brand">Github User Finder</a>
</div>
</nav>
<div class="container searchContainer">
<div class="search card card-body">
<h2>Search github user</h2>
<p class="lead">Enter username to see user profile and reps </p>
<input type="text" id="userName" class="form-control" placeholder="Enter Github username">
</div>
<br>
<div id="profile">
<footer class="mt-5 p-3 text-center bg-light">
Github user finder &copy;
</footer>
</div>

<script src="github.js"></script>
<script src="ui.js"></script>
<script src="app.js"></script>
</body>
</html>
20 changes: 20 additions & 0 deletions templated_tests/js_vanilla/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "js-vanilla-example",
"version": "1.0.0",
"description": "A simple vanilla js project to find github users and their repositories.",
"main": "app.js",
"type": "module",
"scripts": {
"test": "vitest",
"test:coverage": "vitest run --coverage"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@vitest/coverage-v8": "^1.6.0",
"vitest": "^1.6.0"
},
"dependencies": {
"jsdom": "^24.1.0"
}
}
Empty file.
82 changes: 82 additions & 0 deletions templated_tests/js_vanilla/ui.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
export default class UI {
constructor() {
this.profile = document.getElementById('profile');
}
showProfile(user) {
this.profile.innerHTML = `
<div class="card card-body mb-3">
<div class="row">
<div class="col-md-3">
<img src="${user.avatar_url}" alt="" class="img-fluid mb-2">
<a href="${user.html_url}" target="_blank" class="btn btn-primary btn-block"> View Profile</a>
</div>
<div class="col-md-9">
<span class="badge badge-primary"> Public Repos: ${user.public_repos}</span>
<span class="badge badge-secondary"> Public Gists: ${user.public_gists}</span>
<span class="badge badge-success"> Followers: ${user.followers}</span>
<span class="badge badge-info"> Following: ${user.following}</span>
<br> <br>
<ul class="list-group">
<li class="list-group-item">Company : ${user.company}</li>
<li class="list-group-item">Website : ${user.blog}</li>
<li class="list-group-item">Location : ${user.location}</li>
<li class="list-group-item">Member Since : ${user.created_at}</li>
</ul>
</div>
</div>
</div>
<h3 class="page-heading mb-3"> Latest Repos</h3>
<div id="repos"></div>
</div>
`;
}

showRepos(repos) {
let output = '';

repos.forEach(function (repo) {
output += `
<div class="card card-body mb-2">
<div class="row">
<div class="col-sm-6">
<a href="${repo.html_url}" target="_blank">${repo.name}</a>
<p class="pt-2">${repo.description}</p>
</div>
<div class="col-sm-6">
<span class="badge badge-primary"> Starts: ${repo.stargazers_count}</span>
<span class="badge badge-info"> Watchers: ${repo.watchers_count}</span>
<span class="badge badge-light"> Forks: ${repo.forms_count}</span>
</div>
</div>
</div>
`;
});

document.getElementById('repos').innerHTML = output;
}

showAlert(message, className) {
this.clearAlert();
const div = document.createElement('div');
div.className = className;
div.appendChild(document.createTextNode(message));
const container = document.querySelector('.searchContainer');
const search = document.querySelector('.search');
container.insertBefore(div, search);

setTimeout(() => {
this.clearAlert();
}, 2000);
}

clearAlert() {
const currentAlert = document.querySelector('.alert');
if (currentAlert) {
currentAlert.remove();
}
}

clearProfile() {
this.profile.innerHTML = '';
}
}
29 changes: 29 additions & 0 deletions templated_tests/js_vanilla/ui.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// ui.test.js
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { JSDOM } from 'jsdom';
import UI from './ui';

describe('UI Class', () => {
let ui;
let document;
let profileElement;

beforeEach(() => {
const dom = new JSDOM(`
<div id="profile"></div>
<div class="searchContainer">
<div class="search"></div>
</div>
`);
document = dom.window.document;
global.document = document;
global.window = dom.window;

profileElement = document.getElementById('profile');
ui = new UI();
});

it('should instantiate the UI class', () => {
expect(ui).toBeDefined();
});
});
13 changes: 13 additions & 0 deletions templated_tests/js_vanilla/vitest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { defineConfig } from 'vite';

export default defineConfig({
test: {
globals: true,
environment: 'jsdom',
coverage: {
provider: 'v8',
reporter: ['text', 'html', 'cobertura'],
// Add any other coverage options here
},
},
});
Loading