Just a whole lot of crap
This commit is contained in:
526
tests/tui/performance/memoryManagement.test.js
Normal file
526
tests/tui/performance/memoryManagement.test.js
Normal file
@@ -0,0 +1,526 @@
|
||||
const React = require("react");
|
||||
const { render } = require("ink-testing-library");
|
||||
const {
|
||||
useEventListener,
|
||||
useInterval,
|
||||
useTimeout,
|
||||
useAsyncOperation,
|
||||
useMemoryMonitor,
|
||||
useWeakRef,
|
||||
useCleanup,
|
||||
useResourcePool,
|
||||
} = require("../../../src/tui/hooks/useMemoryManagement.js");
|
||||
const {
|
||||
withMemoryManagement,
|
||||
MemoryOptimizedContainer,
|
||||
MemoryEfficientList,
|
||||
AutoCleanupComponent,
|
||||
} = require("../../../src/tui/components/common/MemoryOptimizedComponent.jsx");
|
||||
|
||||
/**
|
||||
* Memory management tests for TUI components
|
||||
* Requirements: 4.2, 4.5
|
||||
*/
|
||||
|
||||
describe("Memory Management Hooks", () => {
|
||||
describe("useCleanup", () => {
|
||||
test("should execute cleanup functions on unmount", () => {
|
||||
const cleanupFn1 = jest.fn();
|
||||
const cleanupFn2 = jest.fn();
|
||||
|
||||
const TestComponent = () => {
|
||||
const { addCleanup } = useCleanup();
|
||||
|
||||
React.useEffect(() => {
|
||||
addCleanup(cleanupFn1);
|
||||
addCleanup(cleanupFn2);
|
||||
}, [addCleanup]);
|
||||
|
||||
return React.createElement("div", null, "Test");
|
||||
};
|
||||
|
||||
const { unmount } = render(React.createElement(TestComponent));
|
||||
|
||||
expect(cleanupFn1).not.toHaveBeenCalled();
|
||||
expect(cleanupFn2).not.toHaveBeenCalled();
|
||||
|
||||
unmount();
|
||||
|
||||
expect(cleanupFn1).toHaveBeenCalledTimes(1);
|
||||
expect(cleanupFn2).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test("should handle cleanup function errors gracefully", () => {
|
||||
const consoleSpy = jest
|
||||
.spyOn(console, "error")
|
||||
.mockImplementation(() => {});
|
||||
const goodCleanup = jest.fn();
|
||||
const badCleanup = jest.fn(() => {
|
||||
throw new Error("Cleanup error");
|
||||
});
|
||||
|
||||
const TestComponent = () => {
|
||||
const { addCleanup } = useCleanup();
|
||||
|
||||
React.useEffect(() => {
|
||||
addCleanup(badCleanup);
|
||||
addCleanup(goodCleanup);
|
||||
}, [addCleanup]);
|
||||
|
||||
return React.createElement("div", null, "Test");
|
||||
};
|
||||
|
||||
const { unmount } = render(React.createElement(TestComponent));
|
||||
unmount();
|
||||
|
||||
expect(badCleanup).toHaveBeenCalled();
|
||||
expect(goodCleanup).toHaveBeenCalled();
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
"Error during cleanup:",
|
||||
expect.any(Error)
|
||||
);
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe("useAsyncOperation", () => {
|
||||
test("should cancel operations on unmount", async () => {
|
||||
let operationCancelled = false;
|
||||
const asyncOperation = () =>
|
||||
new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
if (operationCancelled) {
|
||||
reject(new Error("Operation cancelled"));
|
||||
} else {
|
||||
resolve("success");
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
|
||||
const TestComponent = () => {
|
||||
const { executeAsync, cancelAllOperations } = useAsyncOperation();
|
||||
|
||||
React.useEffect(() => {
|
||||
executeAsync(asyncOperation).catch((error) => {
|
||||
if (error.message === "Operation cancelled") {
|
||||
operationCancelled = true;
|
||||
}
|
||||
});
|
||||
}, [executeAsync]);
|
||||
|
||||
return React.createElement("div", null, "Test");
|
||||
};
|
||||
|
||||
const { unmount } = render(React.createElement(TestComponent));
|
||||
|
||||
// Unmount before operation completes
|
||||
setTimeout(() => unmount(), 50);
|
||||
|
||||
// Wait for operation to complete or be cancelled
|
||||
await new Promise((resolve) => setTimeout(resolve, 150));
|
||||
|
||||
expect(operationCancelled).toBe(true);
|
||||
});
|
||||
|
||||
test("should not execute callbacks after unmount", async () => {
|
||||
const onSuccess = jest.fn();
|
||||
const onError = jest.fn();
|
||||
|
||||
const TestComponent = () => {
|
||||
const { executeAsync } = useAsyncOperation();
|
||||
|
||||
React.useEffect(() => {
|
||||
const asyncOp = () => Promise.resolve("success");
|
||||
executeAsync(asyncOp, onSuccess, onError);
|
||||
}, [executeAsync]);
|
||||
|
||||
return React.createElement("div", null, "Test");
|
||||
};
|
||||
|
||||
const { unmount } = render(React.createElement(TestComponent));
|
||||
|
||||
// Unmount immediately
|
||||
unmount();
|
||||
|
||||
// Wait for async operation
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
expect(onSuccess).not.toHaveBeenCalled();
|
||||
expect(onError).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("useInterval", () => {
|
||||
test("should clear interval on unmount", () => {
|
||||
jest.useFakeTimers();
|
||||
const callback = jest.fn();
|
||||
|
||||
const TestComponent = () => {
|
||||
useInterval(callback, 1000);
|
||||
return React.createElement("div", null, "Test");
|
||||
};
|
||||
|
||||
const { unmount } = render(React.createElement(TestComponent));
|
||||
|
||||
// Fast-forward time
|
||||
jest.advanceTimersByTime(2000);
|
||||
expect(callback).toHaveBeenCalledTimes(2);
|
||||
|
||||
unmount();
|
||||
|
||||
// Fast-forward more time after unmount
|
||||
jest.advanceTimersByTime(2000);
|
||||
expect(callback).toHaveBeenCalledTimes(2); // Should not increase
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
test("should provide manual control over interval", () => {
|
||||
jest.useFakeTimers();
|
||||
const callback = jest.fn();
|
||||
|
||||
const TestComponent = () => {
|
||||
const { start, stop, restart } = useInterval(callback, 1000);
|
||||
|
||||
React.useEffect(() => {
|
||||
// Test manual control
|
||||
setTimeout(() => stop(), 1500);
|
||||
setTimeout(() => start(), 2500);
|
||||
setTimeout(() => restart(), 3500);
|
||||
}, [start, stop, restart]);
|
||||
|
||||
return React.createElement("div", null, "Test");
|
||||
};
|
||||
|
||||
render(React.createElement(TestComponent));
|
||||
|
||||
jest.advanceTimersByTime(1000);
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
|
||||
jest.advanceTimersByTime(1000); // 2000ms total, stopped at 1500ms
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
|
||||
jest.advanceTimersByTime(1000); // 3000ms total, restarted at 2500ms
|
||||
expect(callback).toHaveBeenCalledTimes(2);
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
});
|
||||
|
||||
describe("useTimeout", () => {
|
||||
test("should clear timeout on unmount", () => {
|
||||
jest.useFakeTimers();
|
||||
const callback = jest.fn();
|
||||
|
||||
const TestComponent = () => {
|
||||
useTimeout(callback, 1000);
|
||||
return React.createElement("div", null, "Test");
|
||||
};
|
||||
|
||||
const { unmount } = render(React.createElement(TestComponent));
|
||||
|
||||
// Unmount before timeout
|
||||
unmount();
|
||||
|
||||
// Fast-forward past timeout
|
||||
jest.advanceTimersByTime(1500);
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
});
|
||||
|
||||
describe("useMemoryMonitor", () => {
|
||||
test("should track render count", () => {
|
||||
let renderCount = 0;
|
||||
|
||||
const TestComponent = ({ value }) => {
|
||||
const { renderCount: currentRenderCount } =
|
||||
useMemoryMonitor("TestComponent");
|
||||
renderCount = currentRenderCount;
|
||||
return React.createElement("div", null, value);
|
||||
};
|
||||
|
||||
const { rerender } = render(
|
||||
React.createElement(TestComponent, { value: 1 })
|
||||
);
|
||||
expect(renderCount).toBe(1);
|
||||
|
||||
rerender(React.createElement(TestComponent, { value: 2 }));
|
||||
expect(renderCount).toBe(2);
|
||||
|
||||
rerender(React.createElement(TestComponent, { value: 3 }));
|
||||
expect(renderCount).toBe(3);
|
||||
});
|
||||
|
||||
test("should provide memory statistics", () => {
|
||||
let memoryStats = null;
|
||||
|
||||
const TestComponent = () => {
|
||||
const { getMemoryStats } = useMemoryMonitor("TestComponent");
|
||||
|
||||
React.useEffect(() => {
|
||||
// Simulate some memory usage
|
||||
setTimeout(() => {
|
||||
memoryStats = getMemoryStats();
|
||||
}, 100);
|
||||
}, [getMemoryStats]);
|
||||
|
||||
return React.createElement("div", null, "Test");
|
||||
};
|
||||
|
||||
render(React.createElement(TestComponent));
|
||||
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
// Memory stats might be null in test environment
|
||||
// but the function should exist
|
||||
expect(typeof memoryStats).toBeDefined();
|
||||
resolve();
|
||||
}, 150);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("useWeakRef", () => {
|
||||
test("should store and retrieve values using weak references", () => {
|
||||
let getValue, setValue;
|
||||
|
||||
const TestComponent = () => {
|
||||
[getValue, setValue] = useWeakRef();
|
||||
return React.createElement("div", null, "Test");
|
||||
};
|
||||
|
||||
render(React.createElement(TestComponent));
|
||||
|
||||
const testObject = { data: "test" };
|
||||
setValue(testObject);
|
||||
|
||||
expect(getValue()).toBe(testObject);
|
||||
|
||||
setValue(null);
|
||||
expect(getValue()).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("useResourcePool", () => {
|
||||
test("should manage resource pool efficiently", () => {
|
||||
let resourcePool;
|
||||
const createResource = jest.fn(() => ({ id: Math.random() }));
|
||||
const resetResource = jest.fn();
|
||||
|
||||
const TestComponent = () => {
|
||||
resourcePool = useResourcePool(createResource, resetResource, 3);
|
||||
return React.createElement("div", null, "Test");
|
||||
};
|
||||
|
||||
render(React.createElement(TestComponent));
|
||||
|
||||
// Acquire resources
|
||||
const resource1 = resourcePool.acquire();
|
||||
const resource2 = resourcePool.acquire();
|
||||
|
||||
expect(createResource).toHaveBeenCalledTimes(2);
|
||||
expect(resourcePool.activeCount).toBe(2);
|
||||
expect(resourcePool.poolSize).toBe(0);
|
||||
|
||||
// Release resources
|
||||
resourcePool.release(resource1);
|
||||
expect(resourcePool.activeCount).toBe(1);
|
||||
expect(resourcePool.poolSize).toBe(1);
|
||||
|
||||
// Acquire again (should reuse)
|
||||
const resource3 = resourcePool.acquire();
|
||||
expect(createResource).toHaveBeenCalledTimes(2); // No new creation
|
||||
expect(resetResource).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Memory Optimized Components", () => {
|
||||
describe("withMemoryManagement HOC", () => {
|
||||
test("should provide memory management props to wrapped component", () => {
|
||||
let receivedProps = {};
|
||||
|
||||
const TestComponent = (props) => {
|
||||
receivedProps = props;
|
||||
return React.createElement("div", null, "Test");
|
||||
};
|
||||
|
||||
const MemoryManagedComponent = withMemoryManagement(TestComponent, {
|
||||
componentName: "TestComponent",
|
||||
});
|
||||
|
||||
render(
|
||||
React.createElement(MemoryManagedComponent, { testProp: "value" })
|
||||
);
|
||||
|
||||
expect(receivedProps.testProp).toBe("value");
|
||||
expect(typeof receivedProps.addCleanup).toBe("function");
|
||||
expect(typeof receivedProps.executeAsync).toBe("function");
|
||||
expect(typeof receivedProps.getMemoryStats).toBe("function");
|
||||
expect(typeof receivedProps.renderCount).toBe("number");
|
||||
});
|
||||
});
|
||||
|
||||
describe("MemoryOptimizedContainer", () => {
|
||||
test("should display memory warnings when threshold is exceeded", () => {
|
||||
// Mock process.memoryUsage to return high memory usage
|
||||
const originalMemoryUsage = process.memoryUsage;
|
||||
process.memoryUsage = jest.fn(() => ({
|
||||
heapUsed: 200 * 1024 * 1024, // 200MB
|
||||
heapTotal: 250 * 1024 * 1024,
|
||||
external: 10 * 1024 * 1024,
|
||||
rss: 300 * 1024 * 1024,
|
||||
}));
|
||||
|
||||
const onMemoryWarning = jest.fn();
|
||||
|
||||
const { lastFrame } = render(
|
||||
React.createElement(
|
||||
MemoryOptimizedContainer,
|
||||
{
|
||||
memoryThreshold: 100 * 1024 * 1024, // 100MB threshold
|
||||
memoryCheckInterval: 100, // Fast check for testing
|
||||
onMemoryWarning,
|
||||
},
|
||||
"Test content"
|
||||
)
|
||||
);
|
||||
|
||||
// Wait for memory check
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const output = lastFrame();
|
||||
expect(output).toContain("Memory Warning");
|
||||
process.memoryUsage = originalMemoryUsage;
|
||||
resolve();
|
||||
}, 150);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("MemoryEfficientList", () => {
|
||||
test("should limit cached items to prevent memory bloat", () => {
|
||||
const items = Array.from({ length: 200 }, (_, i) => `Item ${i}`);
|
||||
const renderItem = (item, index) =>
|
||||
React.createElement("div", { key: index }, item);
|
||||
|
||||
const { lastFrame } = render(
|
||||
React.createElement(MemoryEfficientList, {
|
||||
items,
|
||||
renderItem,
|
||||
maxCachedItems: 50,
|
||||
})
|
||||
);
|
||||
|
||||
const output = lastFrame();
|
||||
expect(output).toContain("Cached:");
|
||||
expect(output).toContain("50"); // Should show cache limit
|
||||
});
|
||||
});
|
||||
|
||||
describe("AutoCleanupComponent", () => {
|
||||
test("should cleanup old resources automatically", () => {
|
||||
jest.useFakeTimers();
|
||||
let resourceUtils = {};
|
||||
|
||||
const TestComponent = (utils) => {
|
||||
resourceUtils = utils;
|
||||
return React.createElement("div", null, "Test");
|
||||
};
|
||||
|
||||
render(
|
||||
React.createElement(
|
||||
AutoCleanupComponent,
|
||||
{
|
||||
cleanupInterval: 1000,
|
||||
maxAge: 2000,
|
||||
},
|
||||
TestComponent
|
||||
)
|
||||
);
|
||||
|
||||
// Add a resource
|
||||
const mockResource = {
|
||||
cleanup: jest.fn(),
|
||||
};
|
||||
resourceUtils.addResource("test", mockResource);
|
||||
expect(resourceUtils.resourceCount).toBe(1);
|
||||
|
||||
// Fast-forward past maxAge
|
||||
jest.advanceTimersByTime(3000);
|
||||
|
||||
expect(mockResource.cleanup).toHaveBeenCalled();
|
||||
expect(resourceUtils.resourceCount).toBe(0);
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Memory Leak Detection", () => {
|
||||
test("should detect potential memory leaks in component lifecycle", async () => {
|
||||
const components = [];
|
||||
|
||||
// Create multiple components with potential leaks
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const TestComponent = () => {
|
||||
const [data, setData] = React.useState([]);
|
||||
|
||||
React.useEffect(() => {
|
||||
// Simulate memory leak by accumulating data
|
||||
const interval = setInterval(() => {
|
||||
setData((prev) => [...prev, new Array(1000).fill(i)]);
|
||||
}, 10);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
return React.createElement("div", null, `Component ${i}`);
|
||||
};
|
||||
|
||||
const { unmount } = render(React.createElement(TestComponent));
|
||||
components.push(unmount);
|
||||
}
|
||||
|
||||
// Let components run for a bit
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
// Unmount all components
|
||||
components.forEach((unmount) => unmount());
|
||||
|
||||
// Wait for cleanup
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
// In a real scenario, we would check memory usage here
|
||||
// For testing, we just verify that components were created and destroyed
|
||||
expect(components).toHaveLength(10);
|
||||
});
|
||||
|
||||
test("should properly cleanup event listeners", () => {
|
||||
const mockAddEventListener = jest.fn();
|
||||
const mockRemoveEventListener = jest.fn();
|
||||
|
||||
// Mock DOM element
|
||||
const mockElement = {
|
||||
addEventListener: mockAddEventListener,
|
||||
removeEventListener: mockRemoveEventListener,
|
||||
};
|
||||
|
||||
const TestComponent = () => {
|
||||
useEventListener("click", () => {}, mockElement);
|
||||
return React.createElement("div", null, "Test");
|
||||
};
|
||||
|
||||
const { unmount } = render(React.createElement(TestComponent));
|
||||
|
||||
expect(mockAddEventListener).toHaveBeenCalledTimes(1);
|
||||
expect(mockRemoveEventListener).not.toHaveBeenCalled();
|
||||
|
||||
unmount();
|
||||
|
||||
expect(mockRemoveEventListener).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user