tree-ast-grep-mcp
Version:
Simple, direct ast-grep wrapper for AI coding agents. Zero abstractions, maximum performance.
488 lines (402 loc) • 16.2 kB
JavaScript
/**
* Performance and Stress Tests
* Tests for high-load scenarios, performance limits, and resource management
*/
import { SearchTool } from '../../build/tools/search.js';
import { ReplaceTool } from '../../build/tools/replace.js';
import { AstGrepBinaryManager } from '../../build/core/binary-manager.js';
import { WorkspaceManager } from '../../build/core/workspace-manager.js';
import { TestSuite, TestAssert, withTimeout } from '../utils/test-helpers.js';
import fs from 'fs/promises';
import path from 'path';
import { tmpdir } from 'os';
export default async function runPerformanceStressTests() {
const suite = new TestSuite('Performance and Stress Tests');
let binaryManager;
let workspaceManager;
let searchTool;
let replaceTool;
let tempDir;
suite.beforeAll(async () => {
binaryManager = new AstGrepBinaryManager({ useSystem: true });
workspaceManager = new WorkspaceManager();
searchTool = new SearchTool(binaryManager, workspaceManager);
replaceTool = new ReplaceTool(binaryManager, workspaceManager);
try {
await binaryManager.initialize();
} catch (error) {
console.log(' ⚠️ Binary manager initialization failed:', error.message);
}
tempDir = await fs.mkdtemp(path.join(tmpdir(), 'ast-grep-stress-test-'));
});
suite.afterAll(async () => {
try {
await fs.rm(tempDir, { recursive: true, force: true });
} catch (error) {
console.log(' ⚠️ Failed to clean up temp directory');
}
});
// =============================================================================
// LARGE CODEBASE STRESS TESTS
// =============================================================================
suite.test('should handle massive code files efficiently', async () => {
// Generate a very large JavaScript file
const lines = [];
for (let i = 0; i < 5000; i++) {
lines.push(`function func${i}(param1, param2) {`);
lines.push(` console.log("Processing function ${i} with", param1, param2);`);
lines.push(` if (param1 > ${i}) {`);
lines.push(` return param1 + param2 + ${i};`);
lines.push(` }`);
lines.push(` return ${i};`);
lines.push(`}`);
lines.push('');
}
const massiveCode = lines.join('\n');
console.log(` 📊 Testing with ${lines.length} lines of code (${Math.round(massiveCode.length / 1024)}KB)`);
const params = {
pattern: 'console.log($MSG)',
language: 'javascript',
code: massiveCode,
maxMatches: 100, // Limit results to avoid overwhelming output
timeoutMs: 30000 // 30 second timeout
};
try {
const startTime = Date.now();
const result = await withTimeout(searchTool.execute(params), 35000);
const endTime = Date.now();
TestAssert.assertTrue(typeof result === 'object');
TestAssert.assertTrue('matches' in result);
TestAssert.assertTrue(result.matches.length <= 100);
const executionTime = endTime - startTime;
console.log(` ⏱️ Execution time: ${executionTime}ms`);
// Should complete within reasonable time (30 seconds)
TestAssert.assertTrue(executionTime < 30000);
} catch (error) {
if (error.message.includes('ast-grep not found')) {
console.log(' ⚠️ Skipping massive file test - ast-grep not available');
return;
}
if (error.message.includes('timeout')) {
console.log(' ⚠️ Large file test timed out - acceptable for stress test');
return;
}
throw error;
}
});
suite.test('should handle deep directory structures with many files', async () => {
console.log(' 📁 Creating deep directory structure...');
// Create deep nested structure
const maxDepth = 10;
const filesPerDir = 5;
async function createDeepStructure(currentPath, depth) {
if (depth >= maxDepth) return;
for (let i = 0; i < filesPerDir; i++) {
const fileName = `file_${depth}_${i}.js`;
const filePath = path.join(currentPath, fileName);
const content = `
// File at depth ${depth}, index ${i}
function processData${depth}_${i}(data) {
console.log("Processing at depth ${depth}, index ${i}", data);
if (data && data.length > 0) {
return data.map(item => item * ${depth + i});
}
return [];
}
class Handler${depth}_${i} {
constructor() {
console.log("Handler created at depth ${depth}, index ${i}");
}
process() {
console.log("Processing in handler ${depth}_${i}");
}
}
`;
await fs.writeFile(filePath, content);
}
// Create subdirectory and recurse
const nextDir = path.join(currentPath, `level_${depth + 1}`);
await fs.mkdir(nextDir, { recursive: true });
await createDeepStructure(nextDir, depth + 1);
}
await createDeepStructure(tempDir, 0);
const params = {
pattern: 'console.log($MSG)',
language: 'javascript',
paths: [tempDir],
maxMatches: 50,
timeoutMs: 25000
};
try {
const startTime = Date.now();
const result = await withTimeout(searchTool.execute(params), 30000);
const endTime = Date.now();
TestAssert.assertTrue(typeof result === 'object');
TestAssert.assertTrue('matches' in result);
const executionTime = endTime - startTime;
console.log(` ⏱️ Directory traversal time: ${executionTime}ms`);
console.log(` 🔍 Found ${result.matches.length} matches`);
// Should find multiple matches across the directory structure
TestAssert.assertTrue(result.matches.length > 0);
} catch (error) {
if (error.message.includes('ast-grep not found')) {
console.log(' ⚠️ Skipping directory structure test - ast-grep not available');
return;
}
if (error.message.includes('timeout')) {
console.log(' ⚠️ Directory structure test timed out - acceptable for stress test');
return;
}
throw error;
}
});
// =============================================================================
// CONCURRENT OPERATIONS STRESS TESTS
// =============================================================================
suite.test('should handle high concurrent load', async () => {
const concurrencyLevel = 20;
console.log(` 🔄 Testing ${concurrencyLevel} concurrent operations...`);
const operations = [];
for (let i = 0; i < concurrencyLevel; i++) {
const operation = searchTool.execute({
pattern: 'console.log($ARG)',
language: 'javascript',
code: `
function test${i}() {
console.log("Test function ${i}");
console.log("Additional log ${i}");
return ${i};
}
`,
timeoutMs: 10000
});
operations.push(operation);
}
try {
const startTime = Date.now();
const results = await Promise.allSettled(operations);
const endTime = Date.now();
const successful = results.filter(r => r.status === 'fulfilled').length;
const failed = results.filter(r => r.status === 'rejected').length;
console.log(` ✅ Successful operations: ${successful}`);
console.log(` ❌ Failed operations: ${failed}`);
console.log(` ⏱️ Total time: ${endTime - startTime}ms`);
// Most operations should succeed
TestAssert.assertTrue(successful > failed);
// All operations should complete
TestAssert.assertEqual(successful + failed, concurrencyLevel);
// Check that successful operations returned valid results
results.filter(r => r.status === 'fulfilled').forEach(result => {
TestAssert.assertTrue(typeof result.value === 'object');
TestAssert.assertTrue('matches' in result.value);
});
} catch (error) {
if (error.message.includes('ast-grep not found')) {
console.log(' ⚠️ Skipping concurrent test - ast-grep not available');
return;
}
throw error;
}
});
suite.test('should handle rapid sequential operations', async () => {
const operationCount = 50;
console.log(` 🏃 Testing ${operationCount} rapid sequential operations...`);
try {
const startTime = Date.now();
let successCount = 0;
let errorCount = 0;
for (let i = 0; i < operationCount; i++) {
try {
const result = await searchTool.execute({
pattern: 'function $NAME() { $$$BODY }',
language: 'javascript',
code: `function rapidTest${i}() { return ${i}; }`,
timeoutMs: 5000
});
TestAssert.assertTrue(typeof result === 'object');
successCount++;
} catch (error) {
errorCount++;
if (!error.message.includes('ast-grep not found')) {
// Allow some failures in stress test, but not all
TestAssert.assertTrue(errorCount < operationCount / 2);
}
}
}
const endTime = Date.now();
const totalTime = endTime - startTime;
const avgTime = totalTime / operationCount;
console.log(` ✅ Successful: ${successCount}`);
console.log(` ❌ Failed: ${errorCount}`);
console.log(` ⏱️ Total time: ${totalTime}ms`);
console.log(` 📊 Average per operation: ${avgTime.toFixed(2)}ms`);
// Most operations should succeed
TestAssert.assertTrue(successCount > errorCount);
} catch (error) {
if (error.message.includes('ast-grep not found')) {
console.log(' ⚠️ Skipping sequential test - ast-grep not available');
return;
}
throw error;
}
});
// =============================================================================
// MEMORY PRESSURE TESTS
// =============================================================================
suite.test('should handle memory-intensive operations', async () => {
console.log(' 🧠 Testing memory-intensive operations...');
// Create operations that might use significant memory
const memoryIntensiveOperations = [];
for (let i = 0; i < 10; i++) {
// Large code with complex patterns
const largeCode = Array(1000).fill().map((_, j) => `
class LargeClass${i}_${j} {
constructor(data${i}_${j}) {
this.data = data${i}_${j};
console.log("Creating large class ${i}_${j} with", data${i}_${j});
}
process() {
console.log("Processing in large class ${i}_${j}");
return this.data.map(item => ({
...item,
processed: true,
timestamp: Date.now(),
index: ${i * 1000 + j}
}));
}
}
`).join('\n');
const operation = searchTool.execute({
pattern: 'class $NAME { $$$BODY }',
language: 'javascript',
code: largeCode,
maxMatches: 20,
timeoutMs: 15000
});
memoryIntensiveOperations.push(operation);
}
try {
const startTime = Date.now();
const results = await Promise.allSettled(memoryIntensiveOperations);
const endTime = Date.now();
const successful = results.filter(r => r.status === 'fulfilled').length;
const failed = results.filter(r => r.status === 'rejected').length;
console.log(` ✅ Memory operations successful: ${successful}`);
console.log(` ❌ Memory operations failed: ${failed}`);
console.log(` ⏱️ Memory test time: ${endTime - startTime}ms`);
// Should handle memory pressure gracefully
TestAssert.assertTrue(successful + failed === 10);
// At least some should succeed
TestAssert.assertTrue(successful > 0);
} catch (error) {
if (error.message.includes('ast-grep not found')) {
console.log(' ⚠️ Skipping memory test - ast-grep not available');
return;
}
throw error;
}
});
// =============================================================================
// TIMEOUT AND RESOURCE LIMIT TESTS
// =============================================================================
suite.test('should respect various timeout settings', async () => {
const timeoutTests = [
{ timeout: 1000, description: 'short timeout' },
{ timeout: 5000, description: 'medium timeout' },
{ timeout: 15000, description: 'long timeout' }
];
for (const timeoutTest of timeoutTests) {
console.log(` ⏰ Testing ${timeoutTest.description} (${timeoutTest.timeout}ms)...`);
try {
const startTime = Date.now();
const result = await searchTool.execute({
pattern: 'function $NAME($ARGS) { $$$BODY }',
language: 'javascript',
code: Array(500).fill().map((_, i) =>
`function timeoutTest${i}(param) { console.log("Function ${i}"); return param * ${i}; }`
).join('\n'),
timeoutMs: timeoutTest.timeout
});
const endTime = Date.now();
const actualTime = endTime - startTime;
console.log(` ✅ Completed in ${actualTime}ms`);
TestAssert.assertTrue(typeof result === 'object');
TestAssert.assertTrue('matches' in result);
// Should complete within the timeout
TestAssert.assertTrue(actualTime <= timeoutTest.timeout + 1000); // Allow 1s buffer
} catch (error) {
if (error.message.includes('ast-grep not found')) {
console.log(` ⚠️ Skipping timeout test - ast-grep not available`);
continue;
}
if (error.message.includes('timeout')) {
console.log(` ⏰ Timeout occurred as expected for ${timeoutTest.description}`);
TestAssert.assertTrue(true, 'Timeout handled correctly');
} else {
throw error;
}
}
}
});
// =============================================================================
// PERFORMANCE REGRESSION TESTS
// =============================================================================
suite.test('should maintain reasonable performance baselines', async () => {
console.log(' 📈 Running performance baseline tests...');
const performanceTests = [
{
name: 'simple pattern search',
params: {
pattern: 'console.log($ARG)',
language: 'javascript',
code: Array(100).fill('console.log("test");').join('\n')
},
expectedMaxTime: 5000
},
{
name: 'complex pattern search',
params: {
pattern: 'function $NAME($ARGS) { $$$BODY }',
language: 'javascript',
code: Array(100).fill().map((_, i) =>
`function test${i}(a, b) { return a + b + ${i}; }`
).join('\n')
},
expectedMaxTime: 8000
},
{
name: 'class pattern search',
params: {
pattern: 'class $NAME { $$$BODY }',
language: 'javascript',
code: Array(50).fill().map((_, i) =>
`class Test${i} { constructor() { this.id = ${i}; } }`
).join('\n')
},
expectedMaxTime: 6000
}
];
for (const perfTest of performanceTests) {
try {
const startTime = Date.now();
const result = await searchTool.execute(perfTest.params);
const endTime = Date.now();
const executionTime = endTime - startTime;
console.log(` 📊 ${perfTest.name}: ${executionTime}ms`);
TestAssert.assertTrue(typeof result === 'object');
TestAssert.assertTrue('matches' in result);
// Performance check (lenient for stress test environment)
if (executionTime > perfTest.expectedMaxTime) {
console.log(` ⚠️ Performance warning: ${perfTest.name} took ${executionTime}ms (expected < ${perfTest.expectedMaxTime}ms)`);
}
} catch (error) {
if (error.message.includes('ast-grep not found')) {
console.log(` ⚠️ Skipping ${perfTest.name} - ast-grep not available`);
continue;
}
throw error;
}
}
});
return suite.run();
}