Skip to main content
Glama

documcp

by tosin2013
performance.test.ts23.7 kB
import { PerformanceBenchmarker } from "../../src/benchmarks/performance"; import * as fs from "fs/promises"; import * as path from "path"; import * as os from "os"; describe("Performance Benchmarking System", () => { let benchmarker: PerformanceBenchmarker; let tempDir: string; beforeEach(async () => { benchmarker = new PerformanceBenchmarker(); tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "perf-test-")); }); afterEach(async () => { try { await fs.rm(tempDir, { recursive: true, force: true }); } catch (error) { // Ignore cleanup errors } }); // Helper function to create test repositories async function createTestRepo( name: string, fileCount: number, ): Promise<string> { const repoPath = path.join(tempDir, name); await fs.mkdir(repoPath, { recursive: true }); // Create package.json to make it look like a real project await fs.writeFile( path.join(repoPath, "package.json"), JSON.stringify({ name, version: "1.0.0" }, null, 2), ); // Create additional files to reach the target count for (let i = 1; i < fileCount; i++) { const fileName = `file${i}.js`; await fs.writeFile( path.join(repoPath, fileName), `// Test file ${i}\nconsole.log('Hello from file ${i}');\n`, ); } return repoPath; } describe("Repository Size Categorization", () => { it("should categorize small repositories correctly", async () => { const smallRepoPath = await createTestRepo("small-repo", 25); const result = await benchmarker.benchmarkRepository(smallRepoPath); expect(result.repoSize).toBe("small"); expect(result.fileCount).toBe(25); }); it("should categorize medium repositories correctly", async () => { const mediumRepoPath = await createTestRepo("medium-repo", 250); const result = await benchmarker.benchmarkRepository(mediumRepoPath); expect(result.repoSize).toBe("medium"); expect(result.fileCount).toBe(250); }); it("should categorize large repositories correctly", async () => { const largeRepoPath = await createTestRepo("large-repo", 1200); const result = await benchmarker.benchmarkRepository(largeRepoPath); expect(result.repoSize).toBe("large"); expect(result.fileCount).toBe(1200); }); }); describe("Performance Measurement", () => { it("should measure execution time accurately", async () => { const testRepoPath = await createTestRepo("timing-test", 10); const result = await benchmarker.benchmarkRepository(testRepoPath); expect(result.executionTime).toBeGreaterThanOrEqual(0); expect(typeof result.executionTime).toBe("number"); }); it("should calculate performance ratios correctly", async () => { const testRepoPath = await createTestRepo("ratio-test", 50); const result = await benchmarker.benchmarkRepository(testRepoPath); expect(result.performanceRatio).toBeGreaterThanOrEqual(0); expect(result.performanceRatio).toBeLessThanOrEqual(100); }); it("should track memory usage", async () => { const testRepoPath = await createTestRepo("memory-test", 30); const result = await benchmarker.benchmarkRepository(testRepoPath); expect(result.details.memoryUsage).toBeDefined(); // Memory delta can be negative due to GC, just verify it's tracked expect(typeof result.details.memoryUsage.heapUsed).toBe("number"); expect(result.details.memoryUsage.rss).toBeDefined(); expect(result.details.memoryUsage.heapTotal).toBeDefined(); }); }); describe("PERF-001 Compliance", () => { it("should pass for small repositories under 1 second", async () => { const testRepoPath = await createTestRepo("perf-test", 10); const result = await benchmarker.benchmarkRepository(testRepoPath); expect(result.passed).toBe(true); expect(result.executionTime).toBeLessThan(1000); }); it("should have correct performance targets", async () => { const smallRepo = await createTestRepo("small-perf", 50); const mediumRepo = await createTestRepo("medium-perf", 500); const largeRepo = await createTestRepo("large-perf", 1500); const smallResult = await benchmarker.benchmarkRepository(smallRepo); const mediumResult = await benchmarker.benchmarkRepository(mediumRepo); const largeResult = await benchmarker.benchmarkRepository(largeRepo); expect(smallResult.targetTime).toBe(1000); // 1 second for small expect(mediumResult.targetTime).toBe(10000); // 10 seconds for medium expect(largeResult.targetTime).toBe(60000); // 60 seconds for large }); }); describe("Benchmark Suite", () => { it("should run multiple repository benchmarks", async () => { const testRepos = [ { path: await createTestRepo("suite-test-1", 25), name: "Suite Test 1", }, { path: await createTestRepo("suite-test-2", 75), name: "Suite Test 2", }, ]; const suite = await benchmarker.runBenchmarkSuite(testRepos); expect(suite.results.length).toBe(2); expect(suite.testName).toBeDefined(); expect(suite.overallPassed).toBeDefined(); }); it("should generate accurate summaries", async () => { const testRepos = [ { path: await createTestRepo("small-repo", 25), name: "Small Repo" }, { path: await createTestRepo("medium-repo", 250), name: "Medium Repo" }, ]; const suite = await benchmarker.runBenchmarkSuite(testRepos); expect(suite.summary).toBeDefined(); const totalRepos = suite.summary.smallRepos.count + suite.summary.mediumRepos.count + suite.summary.largeRepos.count; expect(totalRepos).toBe(2); const totalPassed = suite.summary.smallRepos.passed + suite.summary.mediumRepos.passed + suite.summary.largeRepos.passed; expect(totalPassed).toBeGreaterThanOrEqual(0); }); }); describe("Result Export", () => { it("should export benchmark results to JSON", async () => { const testRepos = [ { path: await createTestRepo("export-test", 20), name: "Export Test" }, ]; const suite = await benchmarker.runBenchmarkSuite(testRepos); const exportPath = path.join(tempDir, "benchmark-results.json"); await benchmarker.exportResults(suite, exportPath); const exportedContent = await fs.readFile(exportPath, "utf-8"); const exportedData = JSON.parse(exportedContent); expect(exportedData.suite).toBeDefined(); expect(exportedData.systemInfo).toBeDefined(); expect(exportedData.performanceTargets).toBeDefined(); expect(exportedData.timestamp).toBeDefined(); }); }); describe("Error Handling", () => { it("should handle non-existent repository paths gracefully", async () => { const nonExistentPath = path.join(tempDir, "does-not-exist"); const result = await benchmarker.benchmarkRepository(nonExistentPath); // Should handle gracefully with 0 files expect(result.fileCount).toBe(0); expect(result.repoSize).toBe("small"); expect(result.executionTime).toBeGreaterThanOrEqual(0); expect(result.passed).toBe(true); // Fast execution passes performance target }); it("should handle permission denied scenarios gracefully", async () => { if (process.platform === "win32") { // Skip on Windows as permission handling is different return; } const restrictedPath = path.join(tempDir, "restricted"); await fs.mkdir(restrictedPath, { recursive: true }); try { await fs.chmod(restrictedPath, 0o000); const result = await benchmarker.benchmarkRepository(restrictedPath); // Should handle gracefully with 0 files expect(result.fileCount).toBe(0); expect(result.repoSize).toBe("small"); expect(result.executionTime).toBeGreaterThanOrEqual(0); } finally { // Restore permissions for cleanup await fs.chmod(restrictedPath, 0o755); } }); it("should handle empty repositories", async () => { const emptyRepoPath = path.join(tempDir, "empty-repo"); await fs.mkdir(emptyRepoPath, { recursive: true }); const result = await benchmarker.benchmarkRepository(emptyRepoPath); expect(result.fileCount).toBe(0); expect(result.repoSize).toBe("small"); expect(result.executionTime).toBeGreaterThanOrEqual(0); }); it("should handle suite with all valid repositories", async () => { const validRepo1 = await createTestRepo("valid-repo-1", 10); const validRepo2 = await createTestRepo("valid-repo-2", 20); const testRepos = [ { path: validRepo1, name: "Valid Repo 1" }, { path: validRepo2, name: "Valid Repo 2" }, ]; const suite = await benchmarker.runBenchmarkSuite(testRepos); expect(suite.results.length).toBe(2); expect(suite.overallPassed).toBeDefined(); expect(typeof suite.averagePerformance).toBe("number"); }); it("should handle benchmark execution errors in try-catch", async () => { // Test the error handling path by mocking analyzeRepository to throw const originalAnalyze = require("../../src/tools/analyze-repository").analyzeRepository; const mockAnalyze = jest.fn().mockRejectedValue(new Error("Mock error")); // Replace the function temporarily require("../../src/tools/analyze-repository").analyzeRepository = mockAnalyze; try { const testRepoPath = await createTestRepo("error-test", 10); await expect( benchmarker.benchmarkRepository(testRepoPath), ).rejects.toThrow("Mock error"); // Should still record the failed benchmark const results = benchmarker.getResults(); expect(results.length).toBe(1); expect(results[0].passed).toBe(false); } finally { // Restore original function require("../../src/tools/analyze-repository").analyzeRepository = originalAnalyze; } }); }); describe("Utility Methods", () => { it("should reset benchmark results", async () => { const testRepoPath = await createTestRepo("reset-test", 10); await benchmarker.benchmarkRepository(testRepoPath); expect(benchmarker.getResults().length).toBe(1); benchmarker.reset(); expect(benchmarker.getResults().length).toBe(0); }); it("should return copy of results array", async () => { const testRepoPath = await createTestRepo("copy-test", 15); await benchmarker.benchmarkRepository(testRepoPath); const results1 = benchmarker.getResults(); const results2 = benchmarker.getResults(); expect(results1).toEqual(results2); expect(results1).not.toBe(results2); // Different array instances }); it("should handle different analysis depths", async () => { const testRepoPath = await createTestRepo("depth-test", 20); // Test with quick analysis const quickResult = await benchmarker.benchmarkRepository( testRepoPath, "quick", ); expect(quickResult.executionTime).toBeGreaterThanOrEqual(0); // Test with deep analysis const deepResult = await benchmarker.benchmarkRepository( testRepoPath, "deep", ); expect(deepResult.executionTime).toBeGreaterThanOrEqual(0); }); }); describe("Report Generation", () => { it("should generate detailed reports without errors", async () => { const testRepos = [ await createTestRepo("report-small", 25), await createTestRepo("report-medium", 250), await createTestRepo("report-large", 1200), ]; const results: any[] = []; for (const repoPath of testRepos) { const result = await benchmarker.benchmarkRepository(repoPath); results.push(result); } const suite = benchmarker.generateSuite("Report Test", results); // Capture console output const originalLog = console.log; const logOutput: string[] = []; console.log = (...args) => { logOutput.push(args.join(" ")); }; try { benchmarker.printDetailedReport(suite); expect(logOutput.length).toBeGreaterThan(0); expect( logOutput.some((line) => line.includes("Performance Benchmark Report"), ), ).toBe(true); } finally { console.log = originalLog; } }); it("should handle empty suite reports", async () => { const emptySuite = benchmarker.generateSuite("Empty Suite", []); // Should not throw when generating report for empty suite expect(() => benchmarker.printDetailedReport(emptySuite)).not.toThrow(); }); it("should calculate correct averages for mixed results", async () => { const repo1 = await createTestRepo("avg-test-1", 10); const repo2 = await createTestRepo("avg-test-2", 20); const repo3 = await createTestRepo("avg-test-3", 30); const results = [ await benchmarker.benchmarkRepository(repo1), await benchmarker.benchmarkRepository(repo2), await benchmarker.benchmarkRepository(repo3), ]; const suite = benchmarker.generateSuite("Average Test", results); expect(suite.averagePerformance).toBeGreaterThanOrEqual(0); expect(suite.averagePerformance).toBeLessThanOrEqual(100); expect(typeof suite.averagePerformance).toBe("number"); }); }); describe("Memory Usage Tracking", () => { it("should track memory usage differences", async () => { const testRepoPath = await createTestRepo("memory-tracking", 100); const result = await benchmarker.benchmarkRepository(testRepoPath); expect(result.details.memoryUsage).toBeDefined(); // Memory differences can be negative due to garbage collection expect(typeof result.details.memoryUsage.heapUsed).toBe("number"); expect(typeof result.details.memoryUsage.heapTotal).toBe("number"); expect(typeof result.details.memoryUsage.rss).toBe("number"); }); it("should handle memory tracking in error scenarios", async () => { const emptyRepoPath = path.join(tempDir, "empty-memory-test"); await fs.mkdir(emptyRepoPath, { recursive: true }); const result = await benchmarker.benchmarkRepository(emptyRepoPath); // Even in error scenarios, memory tracking should work expect(result.details.memoryUsage).toBeDefined(); expect(typeof result.details.memoryUsage.heapUsed).toBe("number"); }); }); describe("Edge Cases", () => { it("should handle repositories with special characters in paths", async () => { const specialCharRepo = path.join(tempDir, "repo with spaces & symbols!"); await fs.mkdir(specialCharRepo, { recursive: true }); await fs.writeFile( path.join(specialCharRepo, "test.js"), 'console.log("test");', ); const result = await benchmarker.benchmarkRepository(specialCharRepo); expect(result.fileCount).toBe(1); expect(result.executionTime).toBeGreaterThanOrEqual(0); }); it("should handle very deep directory structures", async () => { const deepRepoPath = path.join(tempDir, "deep-repo"); let currentPath = deepRepoPath; // Create a deep directory structure for (let i = 0; i < 10; i++) { currentPath = path.join(currentPath, `level-${i}`); await fs.mkdir(currentPath, { recursive: true }); await fs.writeFile( path.join(currentPath, `file-${i}.js`), `// Level ${i}`, ); } const result = await benchmarker.benchmarkRepository(deepRepoPath); expect(result.fileCount).toBe(10); expect(result.executionTime).toBeGreaterThanOrEqual(0); }); it("should handle concurrent benchmarking", async () => { const repo1 = await createTestRepo("concurrent-1", 15); const repo2 = await createTestRepo("concurrent-2", 25); const repo3 = await createTestRepo("concurrent-3", 35); // Run benchmarks concurrently const promises = [ benchmarker.benchmarkRepository(repo1), benchmarker.benchmarkRepository(repo2), benchmarker.benchmarkRepository(repo3), ]; const results = await Promise.all(promises); expect(results.length).toBe(3); results.forEach((result) => { expect(result.executionTime).toBeGreaterThanOrEqual(0); expect(result.fileCount).toBeGreaterThan(0); }); }); it("should handle extremely deep recursion limit", async () => { const deepRepoPath = path.join(tempDir, "extremely-deep"); let currentPath = deepRepoPath; // Create a structure deeper than the 10-level limit for (let i = 0; i < 15; i++) { currentPath = path.join(currentPath, `level-${i}`); await fs.mkdir(currentPath, { recursive: true }); await fs.writeFile( path.join(currentPath, `file-${i}.js`), `// Level ${i}`, ); } const result = await benchmarker.benchmarkRepository(deepRepoPath); // Should stop at recursion limit, so fewer than 15 files expect(result.fileCount).toBeLessThanOrEqual(10); expect(result.executionTime).toBeGreaterThanOrEqual(0); }); it("should skip node_modules and vendor directories", async () => { const repoPath = await createTestRepo("skip-dirs", 5); // Add node_modules and vendor directories const nodeModulesPath = path.join(repoPath, "node_modules"); const vendorPath = path.join(repoPath, "vendor"); await fs.mkdir(nodeModulesPath, { recursive: true }); await fs.mkdir(vendorPath, { recursive: true }); // Add files that should be skipped await fs.writeFile( path.join(nodeModulesPath, "package.js"), "module.exports = {};", ); await fs.writeFile(path.join(vendorPath, "library.js"), "var lib = {};"); const result = await benchmarker.benchmarkRepository(repoPath); // Should only count the original 5 files, not the ones in node_modules/vendor expect(result.fileCount).toBe(5); }); it("should skip hidden files except .github", async () => { const repoPath = await createTestRepo("hidden-files", 3); // Add hidden files and .github directory await fs.writeFile(path.join(repoPath, ".hidden"), "hidden content"); await fs.writeFile(path.join(repoPath, ".env"), "SECRET=value"); const githubPath = path.join(repoPath, ".github"); await fs.mkdir(githubPath, { recursive: true }); await fs.writeFile(path.join(githubPath, "workflow.yml"), "name: CI"); const result = await benchmarker.benchmarkRepository(repoPath); // Should count original 3 files + 1 .github file, but not other hidden files expect(result.fileCount).toBe(4); }); }); describe("Factory Function", () => { it("should create benchmarker instance via factory", () => { const { createBenchmarker } = require("../../src/benchmarks/performance"); const factoryBenchmarker = createBenchmarker(); expect(factoryBenchmarker).toBeInstanceOf(PerformanceBenchmarker); expect(factoryBenchmarker.getResults()).toEqual([]); }); }); describe("Export Results Error Handling", () => { it("should handle export to invalid path gracefully", async () => { const testRepos = [ { path: await createTestRepo("export-error-test", 10), name: "Export Error Test", }, ]; const suite = await benchmarker.runBenchmarkSuite(testRepos); const invalidPath = path.join( "/invalid/nonexistent/path", "results.json", ); await expect( benchmarker.exportResults(suite, invalidPath), ).rejects.toThrow(); }); it("should export complete system information", async () => { const testRepos = [ { path: await createTestRepo("system-info-test", 5), name: "System Info Test", }, ]; const suite = await benchmarker.runBenchmarkSuite(testRepos); const exportPath = path.join(tempDir, "system-info-results.json"); await benchmarker.exportResults(suite, exportPath); const exportedContent = await fs.readFile(exportPath, "utf-8"); const exportedData = JSON.parse(exportedContent); expect(exportedData.systemInfo.node).toBe(process.version); expect(exportedData.systemInfo.platform).toBe(process.platform); expect(exportedData.systemInfo.arch).toBe(process.arch); expect(exportedData.systemInfo.memoryUsage).toBeDefined(); expect(exportedData.performanceTargets).toEqual({ small: 1000, medium: 10000, large: 60000, }); }); }); describe("Detailed Report Coverage", () => { it("should print detailed reports with all categories", async () => { // Create repos of all sizes to test all report sections const smallRepo = await createTestRepo("report-small", 25); const mediumRepo = await createTestRepo("report-medium", 250); const largeRepo = await createTestRepo("report-large", 1200); const results = [ await benchmarker.benchmarkRepository(smallRepo), await benchmarker.benchmarkRepository(mediumRepo), await benchmarker.benchmarkRepository(largeRepo), ]; const suite = benchmarker.generateSuite("Complete Report Test", results); // Capture console output const originalLog = console.log; const logOutput: string[] = []; console.log = (...args) => { logOutput.push(args.join(" ")); }; try { benchmarker.printDetailedReport(suite); // Verify all report sections are present const fullOutput = logOutput.join("\n"); expect(fullOutput).toContain("Performance Benchmark Report"); expect(fullOutput).toContain("Overall Status:"); expect(fullOutput).toContain("Average Performance:"); expect(fullOutput).toContain("Small (<100 files)"); expect(fullOutput).toContain("Medium (100-1000 files)"); expect(fullOutput).toContain("Large (1000+ files)"); expect(fullOutput).toContain("Detailed Results:"); expect(fullOutput).toContain("Memory:"); } finally { console.log = originalLog; } }); it("should handle report generation with no results in some categories", async () => { // Only create small repos to test empty category handling const results = [ await benchmarker.benchmarkRepository( await createTestRepo("small-only-1", 10), ), await benchmarker.benchmarkRepository( await createTestRepo("small-only-2", 20), ), ]; const suite = benchmarker.generateSuite("Small Only Test", results); const originalLog = console.log; const logOutput: string[] = []; console.log = (...args) => { logOutput.push(args.join(" ")); }; try { benchmarker.printDetailedReport(suite); const fullOutput = logOutput.join("\n"); expect(fullOutput).toContain("Small (<100 files)"); // Medium and Large categories should not appear since count is 0 expect(fullOutput).not.toContain("Medium (100-1000 files):"); expect(fullOutput).not.toContain("Large (1000+ files):"); } finally { console.log = originalLog; } }); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/tosin2013/documcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server