Skip to content

Commit

Permalink
fix(tool): update the test-runner tool
Browse files Browse the repository at this point in the history
  • Loading branch information
sshivaditya committed Jan 20, 2025
1 parent c3b6fd4 commit 744cee3
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 131 deletions.
21 changes: 12 additions & 9 deletions src/adapters/openai/helpers/completions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,6 @@ To use tools, you can include one or more tool calls in your response. Each tool
"path": "/absolute/path/to/file/or/directory"
// For testRunner:
"mode": "run" | "generate",
"functionCode": "code to test (for generate mode)",
"testDescription": "what to test (for generate mode)",
"projectPath": "path to project root (optional)"
}
}
Expand Down Expand Up @@ -164,20 +161,26 @@ Available Tools:
- metadata: execution details
### TestRunner Tool ###
- Purpose: Generate and run tests using TDD principles
- Method: execute(args: { mode: "run" | "generate", functionCode?: string, testDescription?: string, projectPath?: string })
- Purpose: Run tests and analyze results
- Method: execute(args: { projectPath?: string })
- Returns: ToolResult<TestRunnerResult> containing:
- success: boolean
- data: {
success: boolean,
testOutput?: string,
failedTests?: string[],
passedTests?: string[],
suggestions?: string[]
passedTests?: string[]
}
- error?: string
- metadata: execution details
Test-Driven Development (TDD) Process:
1. Generate test code using the completion model following Jest patterns
2. Use writeFile tool to write the test file to the appropriate location
3. Use testRunner tool to run the tests and verify they fail initially
4. Implement the solution
5. Use testRunner tool again to verify tests pass
Note: All file paths must be absolute paths. For example, if you want to write to "src/file.ts", you must specify the full path starting with "/". Relative paths are not supported.
Rules and Best Practices:
Expand Down Expand Up @@ -249,7 +252,7 @@ export class Completions extends SuperOpenAi {
searchFiles: new SearchFiles(),
createPr: new CreatePr(context),
analyzeCode: new AnalyzeCode(),
testRunner: new TestRunner(this.client, context),
testRunner: new TestRunner(context),
};
}

Expand Down Expand Up @@ -657,7 +660,7 @@ Return only the fixed JSON without any explanation.`;
this.tools.createPr = new CreatePr(this.context, workingDir);
this.tools.searchFiles = new SearchFiles(workingDir);
this.tools.analyzeCode = new AnalyzeCode(workingDir);
this.tools.testRunner = new TestRunner(this.client, this.context, workingDir);
this.tools.testRunner = new TestRunner(this.context, workingDir);

let isSolved = false;
let finalResponse: OpenAI.Chat.Completions.ChatCompletion | null = null;
Expand Down
18 changes: 12 additions & 6 deletions src/handlers/front-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,18 @@ ${fileTree}
Follow TDD Process:
1. First, read all files you require from the directory using the tree structure.
2. Write a failing test for the issue, and run the test to verify it fails.
3. Write a solution to make the test pass.
4. Run the test again to verify it passes.
5. Modify the solution until all tests pass.
Use the testRunner tool with mode: "generate" to create tests, and mode: "run" to execute them.`;
2. Generate a test that verifies the fix for the issue, following Jest patterns.
3. Use the writeFile tool to write the test file to the appropriate location.
4. Use testRunner to run the test and verify it fails (as expected).
5. Write the solution using the writeFile tool.
6. Run the test again using testRunner to verify it passes.
7. Refactor if needed while keeping tests passing.
Remember:
- Generate test code that follows Jest patterns and best practices
- Use writeFile tool to write both test and implementation files
- Use testRunner to verify test results
- Follow existing project conventions for test file naming and location`;

// Get the solution with retries and verification
const solution = await context.adapters.openai.completions.createCompletion(prompt, "deepseek/deepseek-r1", workingDir);
Expand Down
113 changes: 12 additions & 101 deletions src/tools/test-runner/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import { detectTestConfiguration } from "../../helpers/test-config";
import { Context } from "../../types/context";
import { exec } from "child_process";
import { promisify } from "util";
import { join } from "path";
import OpenAI from "openai";

const execAsync = promisify(exec);

Expand All @@ -18,136 +16,49 @@ export interface TestRunnerResult {

export class TestRunner implements Tool<TestRunnerResult> {
readonly name = "testRunner";
readonly description = "Generate and run tests using TDD principles";
readonly description = "Run tests and analyze results";
readonly parameters: JSONSchemaDefinition = {
type: "object",
properties: {
mode: {
type: "string",
enum: ["run", "generate"],
description: "Whether to run existing tests or generate new ones",
},
functionCode: {
type: "string",
description: "The function code to generate tests for",
},
testDescription: {
type: "string",
description: "Description of what the test should verify",
},
projectPath: {
type: "string",
description: "Path to the project root",
},
},
required: ["mode"],
required: [],
};

private workingDir: string;
private context: Context;
private client: OpenAI;

constructor(client: OpenAI, context: Context, workingDir: string = process.cwd()) {
constructor(context: Context, workingDir: string = process.cwd()) {
this.workingDir = workingDir;
this.client = client;
this.context = context;
}

async generateTestCase(functionCode: string, description: string): Promise<string> {
const prompt = `Given this TypeScript function:
${functionCode}
Generate a test case that ${description}. The test should:
1. Follow Jest testing patterns
2. Include proper assertions
3. Handle async operations if present
4. Follow TDD principles by testing expected behavior
Return only the test code without any explanation.`;

const completion = await this.client.chat.completions.create({
model: "gpt-4",
messages: [
{
role: "user",
content: prompt,
},
],
});

return completion.choices[0]?.message?.content || "";
}

async analyzeTestOutput(output: string): Promise<{
failedTests: string[];
passedTests: string[];
suggestions: string[];
}> {
const prompt = `Analyze this test output and provide:
1. List of failed tests
2. List of passed tests
3. Suggestions for fixing failed tests
${output}
Format response as JSON with properties: failedTests (array), passedTests (array), suggestions (array)`;

const completion = await this.client.chat.completions.create({
model: "gpt-4",
messages: [
{
role: "user",
content: prompt,
},
],
});

const response = completion.choices[0]?.message?.content || "{}";
return JSON.parse(response);
}

async execute(args: {
mode: "run" | "generate";
functionCode?: string;
testDescription?: string;
projectPath?: string;
}): Promise<ToolResult<TestRunnerResult>> {
async execute(args: { projectPath?: string }): Promise<ToolResult<TestRunnerResult>> {
try {
const projectPath = args.projectPath || this.workingDir;

if (args.mode === "generate" && args.functionCode && args.testDescription) {
const testCode = await this.generateTestCase(args.functionCode, args.testDescription);
return {
success: true,
data: {
success: true,
testOutput: testCode,
},
metadata: {
timestamp: Date.now(),
toolName: this.name,
},
};
}

// Run tests
const config = await detectTestConfiguration(projectPath);
const { stdout, stderr } = await execAsync(config.command, {
cwd: projectPath,
});

const testOutput = stdout + stderr;
const analysis = await this.analyzeTestOutput(testOutput);
const failedTestsMatch = testOutput.match(/Tests:\s+(\d+)\s+failed/i);
const passedTestsMatch = testOutput.match(/Tests:\s+(\d+)\s+passed/i);

const failedTests = failedTestsMatch ? Array(parseInt(failedTestsMatch[1])).fill("Test failed") : [];
const passedTests = passedTestsMatch ? Array(parseInt(passedTestsMatch[1])).fill("Test passed") : [];

return {
success: true,
data: {
success: analysis.failedTests.length === 0,
success: failedTests.length === 0,
testOutput,
failedTests: analysis.failedTests,
passedTests: analysis.passedTests,
suggestions: analysis.suggestions,
failedTests,
passedTests,
},
metadata: {
timestamp: Date.now(),
Expand Down
17 changes: 2 additions & 15 deletions src/types/tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,29 +220,16 @@ export const toolFunctions: Record<string, ToolFunction> = {
type: "function",
function: {
name: "testRunner",
description: "Generate and run tests using TDD principles",
description: "Run tests and analyze results",
parameters: {
type: "object",
properties: {
mode: {
type: "string",
enum: ["run", "generate"],
description: "Whether to run existing tests or generate new ones",
},
functionCode: {
type: "string",
description: "The function code to generate tests for",
},
testDescription: {
type: "string",
description: "Description of what the test should verify",
},
projectPath: {
type: "string",
description: "Path to the project root",
},
},
required: ["mode"],
required: [],
},
},
},
Expand Down

0 comments on commit 744cee3

Please sign in to comment.