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
JavaScript
/**
* @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 };