UNPKG

forge-mutation-tester

Version:

Mutation testing tool for Solidity smart contracts using Gambit

251 lines β€’ 14 kB
"use strict"; 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