forge-mutation-tester
Version:
Mutation testing tool for Solidity smart contracts using Gambit
251 lines β’ 14 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.runCoverageAnalysis = runCoverageAnalysis;
const fs_1 = require("fs");
const path_1 = __importDefault(require("path"));
const chalk_1 = __importDefault(require("chalk"));
const child_process_1 = require("child_process");
const util_1 = require("util");
const git_service_1 = require("../services/git.service");
const ai_service_1 = require("../services/ai.service");
const readline = __importStar(require("readline"));
const execAsync = (0, util_1.promisify)(child_process_1.exec);
async function runCoverageAnalysis(options) {
console.log(chalk_1.default.bold.blue('\nπ Forge Coverage Analyzer & Test Generator\n'));
const gitService = new git_service_1.GitService();
const aiService = new ai_service_1.AIService(options.openaiKey, options.model);
let repoPath = null;
let isLocalMode = false;
try {
// Step 1: Clone repository OR use local path
if (options.localPath) {
console.log(chalk_1.default.bold('Step 1: Using local repository...'));
repoPath = path_1.default.resolve(options.localPath);
isLocalMode = true;
// Verify the path exists
try {
await fs_1.promises.access(repoPath);
console.log(chalk_1.default.green(`β Using local repository at: ${repoPath}`));
}
catch {
throw new Error(`Local repository path does not exist: ${repoPath}`);
}
}
else if (options.repo) {
console.log(chalk_1.default.bold('Step 1: Cloning repository...'));
const tempDir = path_1.default.join(process.cwd(), '.coverage-analysis-temp');
await fs_1.promises.mkdir(tempDir, { recursive: true });
repoPath = await gitService.cloneRepository(options.repo, tempDir, options.branch, options.token);
}
else {
throw new Error('Either repo URL or local path must be provided');
}
// Step 2: Check if already set up or needs setup
const needsSetup = !isLocalMode || !(await isProjectSetup(repoPath));
if (needsSetup) {
console.log(chalk_1.default.bold('\nπ Step 2: Project Setup Required'));
await displayCoverageSetupInstructions(repoPath);
const isReady = await waitForUserConfirmation();
if (!isReady) {
console.log(chalk_1.default.yellow('\nβΈοΈ Setup cancelled. Please run the command again when your project is ready.'));
return;
}
}
else {
console.log(chalk_1.default.bold('\nβ
Step 2: Project already set up'));
}
// Step 3: Analyze current coverage
console.log(chalk_1.default.bold('\nStep 3: Analyzing current test coverage...'));
const coverageReport = await analyzeCoverage(repoPath);
console.log(chalk_1.default.cyan('\nπ Current Coverage Summary:'));
console.log(` β’ Overall Coverage: ${coverageReport.overallCoverage.toFixed(2)}%`);
console.log(` β’ Target Coverage: ${options.targetCoverage}%`);
console.log(` β’ Uncovered Lines: ${coverageReport.uncoveredLines.length}`);
console.log(` β’ Uncovered Functions: ${coverageReport.uncoveredFunctions.length}`);
if (coverageReport.overallCoverage >= options.targetCoverage) {
console.log(chalk_1.default.green(`\nβ
Excellent! Coverage is already at ${coverageReport.overallCoverage.toFixed(2)}%, which meets your target of ${options.targetCoverage}%!`));
return;
}
const coverageGap = options.targetCoverage - coverageReport.overallCoverage;
console.log(chalk_1.default.yellow(`\nβ οΈ Coverage gap: ${coverageGap.toFixed(2)}% to reach target`));
// Step 4: Generate tests for uncovered code
console.log(chalk_1.default.bold('\nStep 4: Generating tests to increase coverage...'));
const prioritizedUncovered = await prioritizeUncoveredCode(coverageReport);
const generatedTests = await aiService.generateCoverageTests(prioritizedUncovered, repoPath);
// Step 5: Save generated tests
console.log(chalk_1.default.bold('\nStep 5: Saving generated tests...'));
await saveGeneratedTests(generatedTests, options.output);
// Step 6: Generate coverage summary
console.log(chalk_1.default.bold('\nStep 6: Generating coverage report...'));
const summary = await aiService.generateCoverageSummary(coverageReport, generatedTests, options.targetCoverage);
await saveCoverageSummary(summary, options.output);
// Display results
console.log(chalk_1.default.bold.green('\nβ
Coverage analysis completed successfully!\n'));
console.log(chalk_1.default.cyan('Results:'));
console.log(` β’ Current Coverage: ${coverageReport.overallCoverage.toFixed(2)}%`);
console.log(` β’ Target Coverage: ${options.targetCoverage}%`);
console.log(` β’ Estimated New Coverage: ${(coverageReport.overallCoverage + (generatedTests.length * 2)).toFixed(2)}%`);
console.log(` β’ Generated test files: ${generatedTests.length}`);
console.log(` β’ Uncovered lines addressed: ${prioritizedUncovered.lines.length}`);
console.log(`\nOutput saved to: ${chalk_1.default.underline(options.output)}`);
console.log(chalk_1.default.bold('\nπ Next Steps:'));
console.log(` 1. Review generated tests in ${options.output}`);
console.log(` 2. Add tests to your test suite`);
console.log(` 3. Run coverage analysis again to verify improvements`);
}
catch (error) {
console.error(chalk_1.default.red('\nβ Error:'), error);
throw error;
}
finally {
// Cleanup only if not local mode and cleanup is enabled
if (repoPath && !isLocalMode && options.cleanup) {
console.log(chalk_1.default.dim('\nCleaning up...'));
await gitService.cleanup(repoPath);
}
}
}
async function analyzeCoverage(projectPath) {
const CoverageService = (await Promise.resolve().then(() => __importStar(require('../services/coverage.service')))).CoverageService;
const coverageService = new CoverageService();
return await coverageService.analyzeCoverage(projectPath);
}
async function prioritizeUncoveredCode(coverageReport) {
const CoverageService = (await Promise.resolve().then(() => __importStar(require('../services/coverage.service')))).CoverageService;
const coverageService = new CoverageService();
return await coverageService.prioritizeUncoveredCode(coverageReport);
}
// Removed generateCoverageTests and generateCoverageSummary - now using AI service methods
async function displayCoverageSetupInstructions(repoPath) {
console.log(chalk_1.default.cyan('\nπ Repository cloned to:'), chalk_1.default.underline(repoPath));
console.log(chalk_1.default.bold('\nπ§ Please complete the following setup steps:\n'));
// Check project type
const foundryTomlExists = await fs_1.promises.access(path_1.default.join(repoPath, 'foundry.toml')).then(() => true).catch(() => false);
const hardhatConfigExists = await fs_1.promises.access(path_1.default.join(repoPath, 'hardhat.config.js')).then(() => true).catch(() => fs_1.promises.access(path_1.default.join(repoPath, 'hardhat.config.ts')).then(() => true).catch(() => false));
if (foundryTomlExists) {
console.log(chalk_1.default.green('π¨ Detected Forge/Foundry project\n'));
console.log(chalk_1.default.yellow('Run these commands in your terminal:\n'));
console.log(chalk_1.default.cyan(`cd ${repoPath}`));
console.log(chalk_1.default.cyan('forge install'));
console.log(chalk_1.default.cyan('forge build'));
console.log(chalk_1.default.cyan('forge test'));
console.log(chalk_1.default.cyan('forge coverage'));
console.log(chalk_1.default.yellow('\nπ Coverage Analysis Requirements:'));
console.log(' β’ All contracts compile successfully');
console.log(' β’ All existing tests pass');
console.log(' β’ Coverage tools are working (forge coverage runs)');
}
else if (hardhatConfigExists) {
console.log(chalk_1.default.green('βοΈ Detected Hardhat project\n'));
console.log(chalk_1.default.yellow('Run these commands in your terminal:\n'));
console.log(chalk_1.default.cyan(`cd ${repoPath}`));
console.log(chalk_1.default.cyan('npm install'));
console.log(chalk_1.default.cyan('npx hardhat compile'));
console.log(chalk_1.default.cyan('npx hardhat test'));
console.log(chalk_1.default.cyan('npm install --save-dev solidity-coverage'));
console.log(chalk_1.default.cyan('npx hardhat coverage'));
console.log(chalk_1.default.yellow('\nπ Coverage Analysis Requirements:'));
console.log(' β’ All contracts compile successfully');
console.log(' β’ All existing tests pass');
console.log(' β’ Coverage tools are installed and working');
}
else {
console.log(chalk_1.default.yellow('β Unknown project type detected\n'));
console.log(chalk_1.default.yellow('Please ensure your project is set up with:\n'));
console.log(chalk_1.default.cyan(`cd ${repoPath}`));
console.log(chalk_1.default.cyan('# Install dependencies'));
console.log(chalk_1.default.cyan('# Compile contracts'));
console.log(chalk_1.default.cyan('# Run tests to ensure they pass'));
console.log(chalk_1.default.cyan('# Set up coverage tools'));
}
console.log(chalk_1.default.bold('\nβ
Requirements:'));
console.log(' β’ All dependencies installed');
console.log(' β’ All contracts compile successfully');
console.log(' β’ All existing tests pass');
console.log(' β’ Coverage analysis tools working');
console.log(chalk_1.default.bold('\nπ― What this tool will do:'));
console.log(' β’ Analyze current test coverage');
console.log(' β’ Identify uncovered code (lines, functions, branches)');
console.log(' β’ Generate AI-powered tests to increase coverage');
console.log(' β’ Prioritize high-impact coverage improvements');
}
async function waitForUserConfirmation() {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
return new Promise((resolve) => {
console.log(chalk_1.default.bold('\nβ Is your project ready for coverage analysis?'));
rl.question(chalk_1.default.cyan('Type "yes" or "y" to continue, anything else to cancel: '), (answer) => {
rl.close();
const normalizedAnswer = answer.toLowerCase().trim();
resolve(normalizedAnswer === 'yes' || normalizedAnswer === 'y');
});
});
}
async function saveGeneratedTests(tests, outputDir) {
await fs_1.promises.mkdir(outputDir, { recursive: true });
for (const test of tests) {
const testPath = path_1.default.join(outputDir, test.fileName);
await fs_1.promises.writeFile(testPath, test.content);
console.log(chalk_1.default.green(` β Saved ${test.fileName}`));
}
}
async function saveCoverageSummary(summary, outputDir) {
const summaryPath = path_1.default.join(outputDir, 'coverage-analysis-summary.md');
await fs_1.promises.writeFile(summaryPath, summary);
console.log(chalk_1.default.green(` β Saved coverage analysis report`));
}
async function isProjectSetup(projectPath) {
// Check if project appears to be already set up
try {
// Check for common build artifacts
const foundryOut = path_1.default.join(projectPath, 'out');
const hardhatArtifacts = path_1.default.join(projectPath, 'artifacts');
const nodeModules = path_1.default.join(projectPath, 'node_modules');
const foundryExists = await fs_1.promises.access(foundryOut).then(() => true).catch(() => false);
const hardhatExists = await fs_1.promises.access(hardhatArtifacts).then(() => true).catch(() => false);
const nodeModulesExists = await fs_1.promises.access(nodeModules).then(() => true).catch(() => false);
return foundryExists || (hardhatExists && nodeModulesExists);
}
catch {
return false;
}
}
//# sourceMappingURL=coverage.js.map