agentsqripts
Version:
Comprehensive static code analysis toolkit for identifying technical debt, security vulnerabilities, performance issues, and code quality problems
483 lines (398 loc) • 14.7 kB
JavaScript
/**
* @file Unit tests for static bug analysis core functionality
* @description Tests bug pattern detection, logic error identification, and fix recommendations
*/
const {
analyzeFileStaticBugs,
analyzeProjectStaticBugs,
detectBugPatterns,
generateBugRecommendations
} = require('./analyzeStaticBugs');
const qtests = require('qtests');
const fs = require('fs');
const path = require('path');
/**
* Test runner for static bug analysis
*/
async function runTests() {
console.log('=== Testing Static Bug Analysis Core Functionality ===');
const results = {
total: 0,
passed: 0
};
// Test null reference detection
results.total++;
try {
const tempFile = path.join(__dirname, 'temp-null-ref-test.js');
const nullRefCode = `
function processUser(user) {
// Potential null reference - no null check
return user.name.toUpperCase();
}
function getEmail(person) {
// Chain of property access without null checks
return person.contact.email.toLowerCase();
}
function formatData(data) {
// Accessing property on potentially undefined object
const items = data.items;
return items.map(item => item.name);
}
// Safe null checking (should not trigger)
function safeProcessUser(user) {
if (user && user.name) {
return user.name.toUpperCase();
}
return '';
}
`;
fs.writeFileSync(tempFile, nullRefCode);
const analysis = await analyzeFileStaticBugs(tempFile);
qtests.assert(typeof analysis === 'object', 'analyzeFileStaticBugs should return object');
qtests.assert(Array.isArray(analysis.bugs), 'Analysis should include bugs array');
qtests.assert(analysis.bugs.length > 0, 'Should detect null reference bugs');
qtests.assert(analysis.bugs.some(bug =>
bug.type.includes('null_reference') ||
bug.category === 'Null Reference'
), 'Should identify null reference patterns');
fs.unlinkSync(tempFile);
console.log('✓ analyzeFileStaticBugs correctly detects null reference bugs');
results.passed++;
} catch (error) {
console.log(`✗ analyzeFileStaticBugs null reference test failed: ${error.message}`);
}
// Test undefined variable detection
results.total++;
try {
const tempFile = path.join(__dirname, 'temp-undefined-test.js');
const undefinedCode = `
function calculate() {
// Using undefined variable
return total * 1.1;
}
function processArray() {
// Typo in variable name
const items = [1, 2, 3];
return item.map(x => x * 2); // 'item' instead of 'items'
}
function handleData() {
let result;
if (Math.random() > 0.5) {
result = 'success';
}
// result might be undefined here
return result.toUpperCase();
}
// Proper variable declaration (should not trigger)
function properCalculate() {
const total = 100;
return total * 1.1;
}
`;
fs.writeFileSync(tempFile, undefinedCode);
const analysis = await analyzeFileStaticBugs(tempFile);
qtests.assert(analysis.bugs.some(bug =>
bug.type.includes('undefined_variable') ||
bug.category === 'Undefined Variable'
), 'Should identify undefined variable bugs');
fs.unlinkSync(tempFile);
console.log('✓ analyzeFileStaticBugs correctly detects undefined variable bugs');
results.passed++;
} catch (error) {
console.log(`✗ analyzeFileStaticBugs undefined variable test failed: ${error.message}`);
}
// Test logical error detection
results.total++;
try {
const tempFile = path.join(__dirname, 'temp-logic-test.js');
const logicCode = `
function isEven(num) {
// Logic error - should use modulo
return num / 2 === Math.floor(num / 2);
}
function findMax(arr) {
let max = 0; // Logic error - should initialize with first element or -Infinity
for (let i = 0; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
return max;
}
function validateAge(age) {
// Logic error - age validation
if (age > 0 && age < 150) {
return true;
}
return false; // Missing handling for edge case: age = 0
}
function processItems(items) {
// Off-by-one error
for (let i = 1; i <= items.length; i++) {
console.log(items[i]); // Will be undefined on last iteration
}
}
`;
fs.writeFileSync(tempFile, logicCode);
const analysis = await analyzeFileStaticBugs(tempFile);
qtests.assert(analysis.bugs.some(bug =>
bug.type.includes('logic_error') ||
bug.type.includes('off_by_one') ||
bug.category === 'Logic Error'
), 'Should identify logical error bugs');
fs.unlinkSync(tempFile);
console.log('✓ analyzeFileStaticBugs correctly detects logical error bugs');
results.passed++;
} catch (error) {
console.log(`✗ analyzeFileStaticBugs logic error test failed: ${error.message}`);
}
// Test type mismatch detection
results.total++;
try {
const tempFile = path.join(__dirname, 'temp-type-test.js');
const typeCode = `
function addNumbers(a, b) {
// Type confusion - string concatenation instead of addition
return a + b; // Could be '12' instead of 3 if a='1', b='2'
}
function processData(data) {
// Assuming array but could be object
return data.map(item => item.name); // Will fail if data is not array
}
function compareValues(x, y) {
// Type coercion issues
if (x == y) { // Should use strict equality
return true;
}
return false;
}
function formatNumber(num) {
// Type assumption without validation
return num.toFixed(2); // Will fail if num is not a number
}
// Type safe implementation (should not trigger)
function safeAdd(a, b) {
if (typeof a === 'number' && typeof b === 'number') {
return a + b;
}
throw new Error('Both arguments must be numbers');
}
`;
fs.writeFileSync(tempFile, typeCode);
const analysis = await analyzeFileStaticBugs(tempFile);
qtests.assert(analysis.bugs.some(bug =>
bug.type.includes('type_mismatch') ||
bug.type.includes('type_coercion') ||
bug.category === 'Type Error'
), 'Should identify type mismatch bugs');
fs.unlinkSync(tempFile);
console.log('✓ analyzeFileStaticBugs correctly detects type mismatch bugs');
results.passed++;
} catch (error) {
console.log(`✗ analyzeFileStaticBugs type mismatch test failed: ${error.message}`);
}
// Test error handling bugs
results.total++;
try {
const tempFile = path.join(__dirname, 'temp-error-handling-test.js');
const errorCode = `
async function fetchData(url) {
// Missing error handling
const response = await fetch(url);
const data = await response.json();
return data;
}
function parseJSON(jsonString) {
// No try-catch for JSON.parse
return JSON.parse(jsonString);
}
function divide(a, b) {
// No check for division by zero
return a / b;
}
function processFile(filename) {
// File operations without error handling
const content = fs.readFileSync(filename);
return content.toString();
}
// Proper error handling (should not trigger)
async function safeFetchData(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching data:', error);
return null;
}
}
`;
fs.writeFileSync(tempFile, errorCode);
const analysis = await analyzeFileStaticBugs(tempFile);
qtests.assert(analysis.bugs.some(bug =>
bug.type.includes('missing_error_handling') ||
bug.type.includes('unhandled_exception') ||
bug.category === 'Error Handling'
), 'Should identify error handling bugs');
fs.unlinkSync(tempFile);
console.log('✓ analyzeFileStaticBugs correctly detects error handling bugs');
results.passed++;
} catch (error) {
console.log(`✗ analyzeFileStaticBugs error handling test failed: ${error.message}`);
}
// Test async/await pattern bugs
results.total++;
try {
const tempFile = path.join(__dirname, 'temp-async-test.js');
const asyncCode = `
function fetchUserData(userId) {
// Missing await keyword
return fetch('/api/users/' + userId);
}
async function processUsers() {
const users = [1, 2, 3];
const results = [];
// Sequential processing in loop (performance issue)
for (const userId of users) {
const user = await fetchUser(userId);
results.push(user);
}
return results;
}
async function handleRequests() {
// Forgotten await
const promise1 = fetchData('/api/data1');
const promise2 = fetchData('/api/data2');
// Using promises incorrectly
return promise1 + promise2;
}
// Proper async implementation (should not trigger)
async function properFetchUserData(userId) {
try {
const response = await fetch('/api/users/' + userId);
return await response.json();
} catch (error) {
console.error('Error fetching user:', error);
return null;
}
}
`;
fs.writeFileSync(tempFile, asyncCode);
const analysis = await analyzeFileStaticBugs(tempFile);
qtests.assert(analysis.bugs.some(bug =>
bug.type.includes('missing_await') ||
bug.type.includes('async_pattern') ||
bug.category === 'Async/Await'
), 'Should identify async/await pattern bugs');
fs.unlinkSync(tempFile);
console.log('✓ analyzeFileStaticBugs correctly detects async/await bugs');
results.passed++;
} catch (error) {
console.log(`✗ analyzeFileStaticBugs async/await test failed: ${error.message}`);
}
// Test bug recommendations generation
results.total++;
try {
const testBugs = [
{
type: 'null_reference',
severity: 'HIGH',
line: 15,
variable: 'user',
pattern: 'property access without null check'
},
{
type: 'undefined_variable',
severity: 'HIGH',
line: 25,
variable: 'total',
pattern: 'variable used before declaration'
},
{
type: 'type_mismatch',
severity: 'MEDIUM',
line: 35,
expected: 'number',
actual: 'string',
pattern: 'type coercion in comparison'
}
];
const recommendations = generateBugRecommendations(testBugs);
qtests.assert(Array.isArray(recommendations), 'generateBugRecommendations should return array');
qtests.assert(recommendations.length > 0, 'Should generate recommendations for bugs');
qtests.assert(recommendations.every(rec => typeof rec.priority === 'string'), 'Each recommendation should have priority');
qtests.assert(recommendations.every(rec => typeof rec.fix === 'string'), 'Each recommendation should have fix suggestion');
qtests.assert(recommendations.every(rec => typeof rec.explanation === 'string'), 'Each recommendation should have explanation');
console.log('✓ generateBugRecommendations correctly generates actionable recommendations');
results.passed++;
} catch (error) {
console.log(`✗ generateBugRecommendations test failed: ${error.message}`);
}
// Test clean, bug-free code
results.total++;
try {
const tempFile = path.join(__dirname, 'temp-clean-test.js');
const cleanCode = `
// Well-written, bug-free code
function safeProcessUser(user) {
if (!user || typeof user !== 'object') {
throw new Error('Invalid user object');
}
if (!user.name || typeof user.name !== 'string') {
throw new Error('User name is required and must be a string');
}
return user.name.toUpperCase();
}
function safeAdd(a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new Error('Both arguments must be numbers');
}
return a + b;
}
async function safeFetchData(url) {
try {
if (typeof url !== 'string' || !url.trim()) {
throw new Error('Valid URL is required');
}
const response = await fetch(url);
if (!response.ok) {
throw new Error(\`HTTP error! status: \${response.status}\`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching data:', error);
throw error;
}
}
function findMax(arr) {
if (!Array.isArray(arr) || arr.length === 0) {
throw new Error('Array must be non-empty');
}
let max = arr[0];
for (let i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
return max;
}
`;
fs.writeFileSync(tempFile, cleanCode);
const analysis = await analyzeFileStaticBugs(tempFile);
qtests.assert(analysis.bugs.length === 0 || analysis.bugs.length < 2, 'Clean code should have minimal static bugs');
fs.unlinkSync(tempFile);
console.log('✓ analyzeFileStaticBugs correctly identifies clean, bug-free code');
results.passed++;
} catch (error) {
console.log(`✗ analyzeFileStaticBugs clean code test failed: ${error.message}`);
}
console.log(`=== Static Bug 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 };