Just a whole lot of crap

This commit is contained in:
2025-08-14 16:36:12 -05:00
parent 66b7e42275
commit 62f6d6f279
144 changed files with 41421 additions and 2458 deletions

View File

@@ -0,0 +1,482 @@
const React = require("react");
const { render } = require("ink-testing-library");
const {
MemoryLeakDetector,
getGlobalDetector,
useMemoryLeakDetection,
MemoryLeakUtils,
} = require("../../../src/tui/utils/memoryLeakDetector.js");
/**
* Memory leak detection tests
* Requirements: 4.2, 4.5
*/
describe("MemoryLeakDetector", () => {
let detector;
beforeEach(() => {
detector = new MemoryLeakDetector({
checkInterval: 100, // Fast interval for testing
sampleSize: 5,
growthThreshold: 1024 * 1024, // 1MB
verbose: false,
});
});
afterEach(() => {
detector.stop();
});
describe("Basic Functionality", () => {
test("should start and stop monitoring", () => {
expect(detector.isMonitoring).toBe(false);
detector.start();
expect(detector.isMonitoring).toBe(true);
detector.stop();
expect(detector.isMonitoring).toBe(false);
});
test("should take memory samples", () => {
// Mock process.memoryUsage
const mockMemoryUsage = jest.fn(() => ({
heapUsed: 50 * 1024 * 1024,
heapTotal: 60 * 1024 * 1024,
external: 5 * 1024 * 1024,
rss: 70 * 1024 * 1024,
}));
const originalMemoryUsage = process.memoryUsage;
process.memoryUsage = mockMemoryUsage;
detector.takeSample();
expect(detector.samples).toHaveLength(1);
expect(detector.samples[0]).toMatchObject({
heapUsed: 50 * 1024 * 1024,
heapTotal: 60 * 1024 * 1024,
});
process.memoryUsage = originalMemoryUsage;
});
test("should limit sample size", () => {
const mockMemoryUsage = jest.fn(() => ({
heapUsed: Math.random() * 100 * 1024 * 1024,
heapTotal: 100 * 1024 * 1024,
external: 5 * 1024 * 1024,
rss: 120 * 1024 * 1024,
}));
const originalMemoryUsage = process.memoryUsage;
process.memoryUsage = mockMemoryUsage;
// Take more samples than the limit
for (let i = 0; i < 10; i++) {
detector.takeSample();
}
expect(detector.samples).toHaveLength(5); // Should be limited to sampleSize
process.memoryUsage = originalMemoryUsage;
});
});
describe("Component Registration", () => {
test("should register and unregister components", () => {
detector.registerComponent("TestComponent", 1);
expect(detector.componentRegistry.has("TestComponent")).toBe(true);
expect(detector.componentRegistry.get("TestComponent").instances).toBe(1);
detector.registerComponent("TestComponent", 1);
expect(detector.componentRegistry.get("TestComponent").instances).toBe(2);
detector.unregisterComponent("TestComponent");
expect(detector.componentRegistry.get("TestComponent").instances).toBe(1);
detector.unregisterComponent("TestComponent");
expect(detector.componentRegistry.has("TestComponent")).toBe(false);
});
test("should detect suspicious components", () => {
detector.registerComponent("LeakyComponent", 1);
// Register many instances
for (let i = 0; i < 10; i++) {
detector.registerComponent("LeakyComponent", 1);
}
const suspicious = detector.getSuspiciousComponents();
expect(suspicious).toHaveLength(1);
expect(suspicious[0].name).toBe("LeakyComponent");
expect(suspicious[0].instances).toBe(11);
expect(suspicious[0].ratio).toBeGreaterThan(2);
});
});
describe("Leak Detection", () => {
test("should detect steady memory growth", () => {
const mockMemoryUsage = jest.fn();
const originalMemoryUsage = process.memoryUsage;
process.memoryUsage = mockMemoryUsage;
// Simulate steady growth
const baseMemory = 50 * 1024 * 1024;
const growthPerSample = 5 * 1024 * 1024;
for (let i = 0; i < 5; i++) {
mockMemoryUsage.mockReturnValue({
heapUsed: baseMemory + i * growthPerSample,
heapTotal: 100 * 1024 * 1024,
external: 5 * 1024 * 1024,
rss: 120 * 1024 * 1024,
});
detector.takeSample();
}
const analysis = detector.analyzeLeaks();
expect(analysis.hasLeak).toBe(true);
expect(analysis.analysis.steadyGrowth).toBe(true);
process.memoryUsage = originalMemoryUsage;
});
test("should detect rapid memory growth", () => {
const mockMemoryUsage = jest.fn();
const originalMemoryUsage = process.memoryUsage;
process.memoryUsage = mockMemoryUsage;
// Simulate rapid growth
mockMemoryUsage.mockReturnValueOnce({
heapUsed: 50 * 1024 * 1024,
heapTotal: 100 * 1024 * 1024,
external: 5 * 1024 * 1024,
rss: 120 * 1024 * 1024,
});
detector.takeSample();
// Wait a bit then add large growth
setTimeout(() => {
mockMemoryUsage.mockReturnValueOnce({
heapUsed: 60 * 1024 * 1024, // 10MB growth
heapTotal: 100 * 1024 * 1024,
external: 5 * 1024 * 1024,
rss: 130 * 1024 * 1024,
});
detector.takeSample();
mockMemoryUsage.mockReturnValueOnce({
heapUsed: 70 * 1024 * 1024, // Another 10MB growth
heapTotal: 100 * 1024 * 1024,
external: 5 * 1024 * 1024,
rss: 140 * 1024 * 1024,
});
detector.takeSample();
const analysis = detector.analyzeLeaks();
expect(analysis.hasLeak).toBe(true);
expect(analysis.analysis.rapidGrowth).toBe(true);
process.memoryUsage = originalMemoryUsage;
}, 10);
});
test("should generate appropriate recommendations", () => {
const mockMemoryUsage = jest.fn();
const originalMemoryUsage = process.memoryUsage;
process.memoryUsage = mockMemoryUsage;
// Simulate component leak
detector.registerComponent("LeakyComponent", 1);
for (let i = 0; i < 5; i++) {
detector.registerComponent("LeakyComponent", 1);
}
// Simulate memory growth
for (let i = 0; i < 3; i++) {
mockMemoryUsage.mockReturnValue({
heapUsed: 50 * 1024 * 1024 + i * 2 * 1024 * 1024,
heapTotal: 100 * 1024 * 1024,
external: 5 * 1024 * 1024,
rss: 120 * 1024 * 1024,
});
detector.takeSample();
}
const analysis = detector.analyzeLeaks();
expect(analysis.analysis.recommendations).toBeDefined();
expect(analysis.analysis.recommendations.length).toBeGreaterThan(0);
const hasComponentRecommendation = analysis.analysis.recommendations.some(
(rec) => rec.type === "component-leak"
);
expect(hasComponentRecommendation).toBe(true);
process.memoryUsage = originalMemoryUsage;
});
});
describe("Event Listeners", () => {
test("should notify listeners of events", () => {
const listener = jest.fn();
detector.addListener(listener);
const mockMemoryUsage = jest.fn(() => ({
heapUsed: 50 * 1024 * 1024,
heapTotal: 100 * 1024 * 1024,
external: 5 * 1024 * 1024,
rss: 120 * 1024 * 1024,
}));
const originalMemoryUsage = process.memoryUsage;
process.memoryUsage = mockMemoryUsage;
detector.takeSample();
expect(listener).toHaveBeenCalledWith("sample", expect.any(Object));
detector.removeListener(listener);
detector.takeSample();
expect(listener).toHaveBeenCalledTimes(1); // Should not be called again
process.memoryUsage = originalMemoryUsage;
});
test("should handle listener errors gracefully", () => {
const consoleSpy = jest
.spyOn(console, "error")
.mockImplementation(() => {});
const badListener = jest.fn(() => {
throw new Error("Listener error");
});
const goodListener = jest.fn();
detector.addListener(badListener);
detector.addListener(goodListener);
const mockMemoryUsage = jest.fn(() => ({
heapUsed: 50 * 1024 * 1024,
heapTotal: 100 * 1024 * 1024,
external: 5 * 1024 * 1024,
rss: 120 * 1024 * 1024,
}));
const originalMemoryUsage = process.memoryUsage;
process.memoryUsage = mockMemoryUsage;
detector.takeSample();
expect(badListener).toHaveBeenCalled();
expect(goodListener).toHaveBeenCalled();
expect(consoleSpy).toHaveBeenCalledWith(
"[MemoryLeakDetector] Error in listener:",
expect.any(Error)
);
consoleSpy.mockRestore();
process.memoryUsage = originalMemoryUsage;
});
});
describe("Statistics and Reporting", () => {
test("should provide memory statistics", () => {
const mockMemoryUsage = jest.fn();
const originalMemoryUsage = process.memoryUsage;
process.memoryUsage = mockMemoryUsage;
// Take a few samples
for (let i = 0; i < 3; i++) {
mockMemoryUsage.mockReturnValue({
heapUsed: 50 * 1024 * 1024 + i * 1024 * 1024,
heapTotal: 100 * 1024 * 1024,
external: 5 * 1024 * 1024,
rss: 120 * 1024 * 1024,
});
detector.takeSample();
}
const stats = detector.getStatistics();
expect(stats).toBeDefined();
expect(stats.current).toBeDefined();
expect(stats.growth).toBeDefined();
expect(stats.trend).toBeDefined();
expect(stats.samples).toBe(3);
process.memoryUsage = originalMemoryUsage;
});
test("should generate comprehensive report", () => {
detector.registerComponent("TestComponent", 1);
const mockMemoryUsage = jest.fn(() => ({
heapUsed: 50 * 1024 * 1024,
heapTotal: 100 * 1024 * 1024,
external: 5 * 1024 * 1024,
rss: 120 * 1024 * 1024,
}));
const originalMemoryUsage = process.memoryUsage;
process.memoryUsage = mockMemoryUsage;
detector.takeSample();
const report = detector.generateReport();
expect(report).toMatchObject({
timestamp: expect.any(Number),
monitoring: expect.any(Boolean),
statistics: expect.any(Object),
components: expect.any(Array),
recommendations: expect.any(Array),
});
expect(report.components).toHaveLength(1);
expect(report.components[0].name).toBe("TestComponent");
process.memoryUsage = originalMemoryUsage;
});
});
});
describe("useMemoryLeakDetection Hook", () => {
test("should register and unregister component on mount/unmount", () => {
const TestComponent = () => {
useMemoryLeakDetection("TestComponent");
return React.createElement("div", null, "Test");
};
const detector = getGlobalDetector();
expect(detector.componentRegistry.has("TestComponent")).toBe(false);
const { unmount } = render(React.createElement(TestComponent));
expect(detector.componentRegistry.has("TestComponent")).toBe(true);
unmount();
expect(detector.componentRegistry.has("TestComponent")).toBe(false);
});
test("should provide detector utilities", () => {
let detectorUtils = {};
const TestComponent = () => {
detectorUtils = useMemoryLeakDetection("TestComponent");
return React.createElement("div", null, "Test");
};
render(React.createElement(TestComponent));
expect(typeof detectorUtils.detector).toBe("object");
expect(typeof detectorUtils.forceGC).toBe("function");
expect(typeof detectorUtils.getReport).toBe("function");
expect(typeof detectorUtils.getStats).toBe("function");
});
});
describe("MemoryLeakUtils", () => {
describe("checkObjectForLeaks", () => {
test("should detect circular references", () => {
const obj = { name: "test" };
obj.self = obj; // Create circular reference
const issues = MemoryLeakUtils.checkObjectForLeaks(obj, "testObj");
const circularIssue = issues.find(
(issue) => issue.type === "circular-reference"
);
expect(circularIssue).toBeDefined();
expect(circularIssue.message).toContain("Circular reference");
});
test("should detect large arrays", () => {
const largeArray = new Array(15000).fill("item");
const issues = MemoryLeakUtils.checkObjectForLeaks(
largeArray,
"largeArray"
);
const arrayIssue = issues.find((issue) => issue.type === "large-array");
expect(arrayIssue).toBeDefined();
expect(arrayIssue.length).toBe(15000);
});
test("should detect objects with many properties", () => {
const obj = {};
for (let i = 0; i < 1500; i++) {
obj[`prop${i}`] = i;
}
const issues = MemoryLeakUtils.checkObjectForLeaks(obj, "manyPropsObj");
const propsIssue = issues.find(
(issue) => issue.type === "many-properties"
);
expect(propsIssue).toBeDefined();
expect(propsIssue.count).toBe(1500);
});
test("should handle null and undefined objects", () => {
expect(MemoryLeakUtils.checkObjectForLeaks(null)).toEqual([]);
expect(MemoryLeakUtils.checkObjectForLeaks(undefined)).toEqual([]);
});
});
describe("checkDOMNodeForLeaks", () => {
test("should detect excessive event listeners", () => {
const mockNode = {
_events: {},
};
// Add many event listeners
for (let i = 0; i < 60; i++) {
mockNode._events[`event${i}`] = () => {};
}
const issues = MemoryLeakUtils.checkDOMNodeForLeaks(mockNode);
const listenerIssue = issues.find(
(issue) => issue.type === "excessive-listeners"
);
expect(listenerIssue).toBeDefined();
expect(listenerIssue.count).toBe(60);
});
test("should detect detached DOM nodes", () => {
const mockNode = {
parentNode: null,
};
const issues = MemoryLeakUtils.checkDOMNodeForLeaks(mockNode);
const detachedIssue = issues.find(
(issue) => issue.type === "detached-node"
);
expect(detachedIssue).toBeDefined();
});
test("should handle invalid nodes", () => {
expect(MemoryLeakUtils.checkDOMNodeForLeaks(null)).toEqual([]);
expect(MemoryLeakUtils.checkDOMNodeForLeaks("not an object")).toEqual([]);
});
});
});
describe("Global Detector", () => {
test("should return the same instance", () => {
const detector1 = getGlobalDetector();
const detector2 = getGlobalDetector();
expect(detector1).toBe(detector2);
});
test("should accept options on first call", () => {
// Reset global detector
const MemoryLeakDetectorModule = require("../../../src/tui/utils/memoryLeakDetector.js");
MemoryLeakDetectorModule.globalDetector = null;
const detector = getGlobalDetector({
checkInterval: 5000,
verbose: true,
});
expect(detector.options.checkInterval).toBe(5000);
expect(detector.options.verbose).toBe(true);
});
});