Global variable leak detection to ensure test isolation and prevent memory leaks. This system monitors the global scope for unexpected variable additions that could indicate memory leaks or improper test isolation.
Detects global variable leaks by comparing the global scope before and after test execution.
/**
* Detects global variable leaks during test execution
* @param customGlobals - Array of global variable names to ignore
* @param options - Leak detection configuration options
* @returns Promise resolving to leak detection report
*/
leaks.detect(customGlobals?: string[], options?: LeakOptions): Promise<LeakReport>;Usage Examples:
const Lab = require('@hapi/lab');
// Basic leak detection
const leakReport = await Lab.leaks.detect();
// Ignore specific globals
const report = await Lab.leaks.detect([
'myGlobalVar',
'SOME_CONSTANT',
'Symbol(special)'
]);
// With options
const detailedReport = await Lab.leaks.detect(['myGlobal'], {
timeout: 5000
});Configuration options for controlling leak detection behavior.
interface LeakOptions {
/** Maximum time to wait for leak detection in milliseconds */
timeout?: number;
/** Whether to enable leak detection (default: true) */
leaks?: boolean;
/** Additional global variables to ignore during detection */
globals?: string[];
}Detailed report of detected global variable leaks.
interface LeakReport {
/** Whether any leaks were detected */
hasLeaks: boolean;
/** Array of detected leaked global variables */
leaks: GlobalLeak[];
/** Total number of detected leaks */
count: number;
/** Baseline global count before tests */
baseline: number;
/** Final global count after tests */
final: number;
/** Execution time for leak detection */
duration: number;
}
interface GlobalLeak {
/** Name of the leaked global variable */
name: string;
/** Type of the leaked variable */
type: string;
/** String representation of the variable value */
value?: string;
/** Whether this is a symbol */
isSymbol: boolean;
/** Stack trace of where the leak was introduced (if available) */
stack?: string;
}Leak detection integrates automatically with the test execution system when enabled.
Usage Examples:
// Enable leak detection in test execution
const result = await Lab.report(script, {
leaks: true,
globals: ['myExpectedGlobal', 'CONFIGURATION']
});
if (result.leaks && result.leaks.hasLeaks) {
console.log(`Detected ${result.leaks.count} global leaks:`);
result.leaks.leaks.forEach(leak => {
console.log(`- ${leak.name} (${leak.type}): ${leak.value}`);
});
}Leak detection works seamlessly with the command-line interface.
Command Line Usage:
# Enable leak detection
lab --leaks
# Disable leak detection
lab --no-leaks
# Ignore specific globals
lab --globals myGlobal,anotherGlobal
# Ignore symbols
lab --globals "Symbol(special),myVar"Common sources of global variable leaks in Node.js applications:
varletconstsetTimeoutsetIntervalprocessBest practices for preventing global leaks in tests:
// ❌ Bad: Creates global leak
test('bad example', () => {
undeclaredVar = 'this creates a global';
expect(undeclaredVar).to.equal('this creates a global');
});
// ✅ Good: Properly scoped variable
test('good example', () => {
const localVar = 'this is properly scoped';
expect(localVar).to.equal('this is properly scoped');
});
// ✅ Good: Clean up global modifications
test('global modification with cleanup', (flags) => {
global.temporaryValue = 'test data';
flags.onCleanup = () => {
delete global.temporaryValue;
};
expect(global.temporaryValue).to.equal('test data');
});For applications that legitimately add global variables, configure a whitelist:
// Test configuration with expected globals
const result = await Lab.report(script, {
leaks: true,
globals: [
// Application globals
'APP_CONFIG',
'DEBUG_MODE',
// Third-party library globals
'_', // lodash
'$', // jQuery (if in browser-like environment)
'Buffer', // Node.js Buffer (if not standard)
// Symbols
'Symbol(react.element)',
'Symbol(react.portal)'
]
});Use leak detection programmatically for custom test flows:
const Lab = require('@hapi/lab');
const customTestRunner = async () => {
// Take baseline measurement
const baselineGlobals = Object.keys(global).length;
// Run your tests
await runMyTests();
// Check for leaks
const leakReport = await Lab.leaks.detect([
'expectedGlobal1',
'expectedGlobal2'
]);
if (leakReport.hasLeaks) {
console.error('Memory leaks detected:');
leakReport.leaks.forEach(leak => {
console.error(` ${leak.name}: ${leak.value}`);
});
throw new Error(`${leakReport.count} memory leaks detected`);
}
console.log('No memory leaks detected');
};Special handling for Symbol leaks, which can be harder to track:
// Symbols must be represented as strings in the globals array
const result = await Lab.report(script, {
leaks: true,
globals: [
'Symbol(react.element)',
'Symbol(my.custom.symbol)',
'Symbol(iterator)'
]
});When leaks are detected, the report provides detailed information for debugging:
const leakReport = await Lab.leaks.detect();
if (leakReport.hasLeaks) {
leakReport.leaks.forEach(leak => {
console.log(`Leak: ${leak.name}`);
console.log(`Type: ${leak.type}`);
console.log(`Value: ${leak.value}`);
if (leak.stack) {
console.log(`Stack trace:\n${leak.stack}`);
}
if (leak.isSymbol) {
console.log('This is a Symbol leak');
}
});
}Leak detection adds minimal overhead to test execution:
The system is optimized for production test environments and can handle large test suites efficiently.
Example for continuous integration with fail-on-leak behavior:
const runTestsWithLeakDetection = async () => {
const result = await Lab.report(script, {
leaks: true,
globals: process.env.ALLOWED_GLOBALS?.split(',') || []
});
if (result.leaks && result.leaks.hasLeaks) {
console.error(`❌ ${result.leaks.count} memory leaks detected`);
result.leaks.leaks.forEach(leak => {
console.error(` ${leak.name} (${leak.type}): ${leak.value}`);
});
process.exit(1);
}
console.log('✅ No memory leaks detected');
return result;
};