UNPKG

agentsqripts

Version:

Comprehensive static code analysis toolkit for identifying technical debt, security vulnerabilities, performance issues, and code quality problems

442 lines (367 loc) 14.6 kB
/** * @file Unit tests for performance analysis core functionality * @description Tests performance issue detection, optimization recommendations, and scoring */ const { analyzeFilePerformance, analyzeProjectPerformance, calculatePerformanceScore, generateOptimizationRecommendations } = require('./analyzePerformance'); const qtests = require('qtests'); const fs = require('fs'); const path = require('path'); /** * Test runner for performance analysis */ async function runTests() { console.log('=== Testing Performance Analysis Core Functionality ==='); const results = { total: 0, passed: 0 }; // Test O(n²) algorithm detection results.total++; try { const tempFile = path.join(__dirname, 'temp-on2-test.js'); const on2Code = ` function findDuplicates(array) { const duplicates = []; // O(n²) nested loop pattern for (let i = 0; i < array.length; i++) { for (let j = i + 1; j < array.length; j++) { if (array[i] === array[j]) { duplicates.push(array[i]); } } } return duplicates; } function bubbleSort(arr) { // Another O(n²) pattern for (let i = 0; i < arr.length; i++) { for (let j = 0; j < arr.length - i - 1; j++) { if (arr[j] > arr[j + 1]) { [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]; } } } return arr; } `; fs.writeFileSync(tempFile, on2Code); const analysis = await analyzeFilePerformance(tempFile); qtests.assert(typeof analysis === 'object', 'analyzeFilePerformance should return object'); qtests.assert(Array.isArray(analysis.issues), 'Analysis should include issues array'); qtests.assert(analysis.issues.length > 0, 'Should detect O(n²) performance issues'); qtests.assert(analysis.issues.some(issue => issue.type.includes('o_n_squared') || issue.category === 'Algorithm' ), 'Should identify algorithmic complexity issues'); fs.unlinkSync(tempFile); console.log('✓ analyzeFilePerformance correctly detects O(n²) algorithms'); results.passed++; } catch (error) { console.log(`✗ analyzeFilePerformance O(n²) test failed: ${error.message}`); } // Test synchronous I/O detection results.total++; try { const tempFile = path.join(__dirname, 'temp-sync-io-test.js'); const syncIOCode = ` const fs = require('fs'); function loadConfig() { // Synchronous file operations - performance issue const config = JSON.parse(fs.readFileSync('./config.json', 'utf8')); const secrets = JSON.parse(fs.readFileSync('./secrets.json', 'utf8')); const settings = JSON.parse(fs.readFileSync('./settings.json', 'utf8')); return { ...config, ...secrets, ...settings }; } function processFiles(filePaths) { return filePaths.map(filePath => { // Sync operation in loop - major performance issue return fs.readFileSync(filePath, 'utf8'); }); } `; fs.writeFileSync(tempFile, syncIOCode); const analysis = await analyzeFilePerformance(tempFile); qtests.assert(analysis.issues.some(issue => issue.type.includes('sync_io') || issue.category === 'I/O' ), 'Should identify synchronous I/O performance issues'); fs.unlinkSync(tempFile); console.log('✓ analyzeFilePerformance correctly detects synchronous I/O operations'); results.passed++; } catch (error) { console.log(`✗ analyzeFilePerformance sync I/O test failed: ${error.message}`); } // Test string concatenation in loops results.total++; try { const tempFile = path.join(__dirname, 'temp-string-concat-test.js'); const stringConcatCode = ` function buildLargeString(items) { let result = ''; // String concatenation in loop - performance issue for (let i = 0; i < items.length; i++) { result += items[i] + '\\n'; } return result; } function processTemplates(templates, data) { let html = ''; templates.forEach(template => { // String concatenation in forEach - performance issue html += template.render(data); }); return html; } `; fs.writeFileSync(tempFile, stringConcatCode); const analysis = await analyzeFilePerformance(tempFile); qtests.assert(analysis.issues.some(issue => issue.type.includes('string_concat') || issue.category === 'String' ), 'Should identify string concatenation performance issues'); fs.unlinkSync(tempFile); console.log('✓ analyzeFilePerformance correctly detects string concatenation in loops'); results.passed++; } catch (error) { console.log(`✗ analyzeFilePerformance string concatenation test failed: ${error.message}`); } // Test DOM query optimization opportunities results.total++; try { const tempFile = path.join(__dirname, 'temp-dom-test.js'); const domCode = ` function updateElements() { // Repeated DOM queries - performance issue document.getElementById('header').style.color = 'red'; document.getElementById('header').style.fontSize = '20px'; document.getElementById('header').className = 'highlight'; // Query in loop - major performance issue const items = document.querySelectorAll('.item'); for (let i = 0; i < 100; i++) { const element = document.querySelector('.dynamic-' + i); if (element) { element.textContent = 'Updated'; } } } function processListItems() { // Inefficient DOM manipulation for (let i = 0; i < 1000; i++) { document.body.appendChild(document.createElement('div')); } } `; fs.writeFileSync(tempFile, domCode); const analysis = await analyzeFilePerformance(tempFile); qtests.assert(analysis.issues.some(issue => issue.type.includes('dom') || issue.category === 'DOM' ), 'Should identify DOM query performance issues'); fs.unlinkSync(tempFile); console.log('✓ analyzeFilePerformance correctly detects DOM query optimization opportunities'); results.passed++; } catch (error) { console.log(`✗ analyzeFilePerformance DOM optimization test failed: ${error.message}`); } // Test React rendering optimization results.total++; try { const tempFile = path.join(__dirname, 'temp-react-test.jsx'); const reactCode = ` import React from 'react'; function IneffientComponent({ items, filter }) { // Inline object creation - causes re-renders const style = { color: 'red', fontSize: '16px' }; return ( <div> {/* Inline function - performance issue */} {items.map((item, index) => ( <div key={index} onClick={() => handleClick(item)} style={style} > {item.name} </div> ))} {/* Expensive operation in render */} {items.filter(item => item.category === filter).map(item => ( <span key={item.id}>{item.name}</span> ))} </div> ); } function AnotherComponent({ data }) { // Missing React.memo optimization opportunity const processedData = data.map(item => ({ ...item, processed: true })); return <div>{processedData.length} items</div>; } `; fs.writeFileSync(tempFile, reactCode); const analysis = await analyzeFilePerformance(tempFile); qtests.assert(analysis.issues.some(issue => issue.type.includes('react') || issue.category === 'React' ), 'Should identify React rendering performance issues'); fs.unlinkSync(tempFile); console.log('✓ analyzeFilePerformance correctly detects React rendering optimization opportunities'); results.passed++; } catch (error) { console.log(`✗ analyzeFilePerformance React optimization test failed: ${error.message}`); } // Test performance score calculation results.total++; try { const testIssues = [ { severity: 'HIGH', impact: 9, category: 'Algorithm' }, { severity: 'HIGH', impact: 8, category: 'I/O' }, { severity: 'MEDIUM', impact: 6, category: 'DOM' }, { severity: 'MEDIUM', impact: 5, category: 'String' }, { severity: 'LOW', impact: 3, category: 'React' } ]; const score = calculatePerformanceScore(testIssues, 100); // 100 lines of code qtests.assert(typeof score === 'number', 'calculatePerformanceScore should return number'); qtests.assert(score >= 0 && score <= 100, 'Performance score should be between 0 and 100'); qtests.assert(score < 90, 'Score should be reduced for high-impact issues'); // Test with no issues const perfectScore = calculatePerformanceScore([], 100); qtests.assert(perfectScore === 100, 'Perfect score should be 100 with no issues'); console.log('✓ calculatePerformanceScore correctly calculates performance scores'); results.passed++; } catch (error) { console.log(`✗ calculatePerformanceScore test failed: ${error.message}`); } // Test optimization recommendations results.total++; try { const testIssues = [ { type: 'o_n_squared', severity: 'HIGH', impact: 9, effort: 4, category: 'Algorithm', line: 15, pattern: 'nested loops' }, { type: 'sync_io', severity: 'HIGH', impact: 8, effort: 2, category: 'I/O', line: 25, pattern: 'fs.readFileSync' }, { type: 'string_concat', severity: 'MEDIUM', impact: 6, effort: 1, category: 'String', line: 35, pattern: 'string concatenation in loop' } ]; const recommendations = generateOptimizationRecommendations(testIssues); qtests.assert(Array.isArray(recommendations), 'generateOptimizationRecommendations should return array'); qtests.assert(recommendations.length > 0, 'Should generate recommendations for performance issues'); qtests.assert(recommendations.every(rec => typeof rec.priority === 'string'), 'Each recommendation should have priority'); qtests.assert(recommendations.every(rec => typeof rec.optimization === 'string'), 'Each recommendation should have optimization strategy'); qtests.assert(recommendations.every(rec => typeof rec.expectedImpact === 'string'), 'Each recommendation should have expected impact'); console.log('✓ generateOptimizationRecommendations correctly generates actionable recommendations'); results.passed++; } catch (error) { console.log(`✗ generateOptimizationRecommendations test failed: ${error.message}`); } // Test project-level performance analysis results.total++; try { const tempDir = path.join(__dirname, 'temp-perf-project'); fs.mkdirSync(tempDir, { recursive: true }); // Create multiple files with different performance issues const file1 = path.join(tempDir, 'algorithms.js'); fs.writeFileSync(file1, ` function inefficientSort(arr) { for (let i = 0; i < arr.length; i++) { for (let j = 0; j < arr.length; j++) { if (arr[i] < arr[j]) { [arr[i], arr[j]] = [arr[j], arr[i]]; } } } } `); const file2 = path.join(tempDir, 'io.js'); fs.writeFileSync(file2, ` const fs = require('fs'); function loadData() { return fs.readFileSync('./data.json', 'utf8'); } `); const projectAnalysis = await analyzeProjectPerformance(tempDir, { extensions: ['.js'] }); qtests.assert(typeof projectAnalysis === 'object', 'analyzeProjectPerformance should return object'); qtests.assert(projectAnalysis.summary.totalFiles >= 2, 'Should analyze multiple files'); qtests.assert(projectAnalysis.summary.totalIssues > 0, 'Should detect performance issues across project'); // Clean up fs.rmSync(tempDir, { recursive: true, force: true }); console.log('✓ analyzeProjectPerformance correctly analyzes entire projects'); results.passed++; } catch (error) { console.log(`✗ analyzeProjectPerformance test failed: ${error.message}`); } // Test clean, optimized code results.total++; try { const tempFile = path.join(__dirname, 'temp-optimized-test.js'); const optimizedCode = ` // Optimized algorithms and patterns function efficientFindDuplicates(array) { const seen = new Set(); const duplicates = new Set(); for (const item of array) { if (seen.has(item)) { duplicates.add(item); } else { seen.add(item); } } return Array.from(duplicates); } async function loadConfigOptimized() { const [config, secrets, settings] = await Promise.all([ fs.promises.readFile('./config.json', 'utf8').then(JSON.parse), fs.promises.readFile('./secrets.json', 'utf8').then(JSON.parse), fs.promises.readFile('./settings.json', 'utf8').then(JSON.parse) ]); return { ...config, ...secrets, ...settings }; } function buildStringEfficiently(items) { return items.join('\\n'); } `; fs.writeFileSync(tempFile, optimizedCode); const analysis = await analyzeFilePerformance(tempFile); qtests.assert(analysis.summary.totalIssues === 0 || analysis.summary.totalIssues < 2, 'Optimized code should have minimal performance issues'); fs.unlinkSync(tempFile); console.log('✓ analyzeFilePerformance correctly identifies optimized code'); results.passed++; } catch (error) { console.log(`✗ analyzeFilePerformance optimized code test failed: ${error.message}`); } console.log(`=== Performance Analysis Test Results ===`); console.log(`Tests passed: ${results.passed}/${results.total}`); console.log(`Success rate: ${((results.passed / results.total) * 100).toFixed(1)}%`); return results; } module.exports = { runTests };