mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
ci: instrument being able to run leaksanitizer (#21142)
tests not in `test/no-validate-leaksan.txt` will run with leaksanitizer in CI leaks documented in `test/leaksan.supp` will not cause a test failure -- notes about leaksanitizer - will not catch garbage collected objects accumulated during long-running processes - will not catch js objects (eg a strong held to a promise) - will catch native calls to `malloc` not `free`d - will catch allocations made in C, Zig, C++, libc, dependencies, dlopen'd --------- Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
This commit is contained in:
@@ -298,7 +298,7 @@ function getTestExpectations() {
|
||||
return expectations;
|
||||
}
|
||||
|
||||
const skipArray = (() => {
|
||||
const skipsForExceptionValidation = (() => {
|
||||
const path = join(cwd, "test/no-validate-exceptions.txt");
|
||||
if (!existsSync(path)) {
|
||||
return [];
|
||||
@@ -309,13 +309,32 @@ const skipArray = (() => {
|
||||
.filter(line => !line.startsWith("#") && line.length > 0);
|
||||
})();
|
||||
|
||||
const skipsForLeaksan = (() => {
|
||||
const path = join(cwd, "test/no-validate-leaksan.txt");
|
||||
if (!existsSync(path)) {
|
||||
return [];
|
||||
}
|
||||
return readFileSync(path, "utf-8")
|
||||
.split("\n")
|
||||
.filter(line => !line.startsWith("#") && line.length > 0);
|
||||
})();
|
||||
|
||||
/**
|
||||
* Returns whether we should validate exception checks running the given test
|
||||
* @param {string} test
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const shouldValidateExceptions = test => {
|
||||
return !(skipArray.includes(test) || skipArray.includes("test/" + test));
|
||||
return !(skipsForExceptionValidation.includes(test) || skipsForExceptionValidation.includes("test/" + test));
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether we should validate exception checks running the given test
|
||||
* @param {string} test
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const shouldValidateLeakSan = test => {
|
||||
return !(skipsForLeaksan.includes(test) || skipsForLeaksan.includes("test/" + test));
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -400,7 +419,9 @@ async function runTests() {
|
||||
|
||||
const okResults = [];
|
||||
const flakyResults = [];
|
||||
const flakyResultsTitles = [];
|
||||
const failedResults = [];
|
||||
const failedResultsTitles = [];
|
||||
const maxAttempts = 1 + (parseInt(options["retries"]) || 0);
|
||||
|
||||
const parallelism = options["parallel"] ? availableParallelism() : 1;
|
||||
@@ -436,6 +457,7 @@ async function runTests() {
|
||||
if (ok) {
|
||||
if (failure) {
|
||||
flakyResults.push(failure);
|
||||
flakyResultsTitles.push(title);
|
||||
} else {
|
||||
okResults.push(result);
|
||||
}
|
||||
@@ -455,6 +477,7 @@ async function runTests() {
|
||||
if (attempt >= maxAttempts || isAlwaysFailure(error)) {
|
||||
flaky = false;
|
||||
failedResults.push(failure);
|
||||
failedResultsTitles.push(title);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -567,6 +590,12 @@ async function runTests() {
|
||||
env.BUN_JSC_validateExceptionChecks = "1";
|
||||
env.BUN_JSC_dumpSimulatedThrows = "1";
|
||||
}
|
||||
if ((basename(execPath).includes("asan") || !isCI) && shouldValidateLeakSan(testPath)) {
|
||||
env.BUN_DESTRUCT_VM_ON_EXIT = "1";
|
||||
env.ASAN_OPTIONS = "allow_user_segv_handler=1:disable_coredump=0:detect_leaks=1";
|
||||
// prettier-ignore
|
||||
env.LSAN_OPTIONS = `malloc_context_size=100:print_suppressions=0:suppressions=${process.cwd()}/test/leaksan.supp`;
|
||||
}
|
||||
return runTest(title, async () => {
|
||||
const { ok, error, stdout, crashes } = await spawnBun(execPath, {
|
||||
cwd: cwd,
|
||||
@@ -809,14 +838,14 @@ async function runTests() {
|
||||
|
||||
if (failedResults.length) {
|
||||
console.log(`${getAnsi("red")}Failing Tests:${getAnsi("reset")}`);
|
||||
for (const { testPath } of failedResults) {
|
||||
for (const testPath of failedResultsTitles) {
|
||||
console.log(`${getAnsi("red")}- ${testPath}${getAnsi("reset")}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (flakyResults.length) {
|
||||
console.log(`${getAnsi("yellow")}Flaky Tests:${getAnsi("reset")}`);
|
||||
for (const { testPath } of flakyResults) {
|
||||
for (const testPath of flakyResultsTitles) {
|
||||
console.log(`${getAnsi("yellow")}- ${testPath}${getAnsi("reset")}`);
|
||||
}
|
||||
}
|
||||
@@ -1094,7 +1123,7 @@ async function spawnBun(execPath, { args, cwd, timeout, env, stdout, stderr }) {
|
||||
: { BUN_ENABLE_CRASH_REPORTING: "0" }),
|
||||
};
|
||||
|
||||
if (basename(execPath).includes("asan")) {
|
||||
if (basename(execPath).includes("asan") && bunEnv.ASAN_OPTIONS === undefined) {
|
||||
bunEnv.ASAN_OPTIONS = "allow_user_segv_handler=1:disable_coredump=0";
|
||||
}
|
||||
|
||||
@@ -1250,17 +1279,17 @@ async function spawnBun(execPath, { args, cwd, timeout, env, stdout, stderr }) {
|
||||
*
|
||||
* @param {string} execPath
|
||||
* @param {string} testPath
|
||||
* @param {object} [options]
|
||||
* @param {string} [options.cwd]
|
||||
* @param {string[]} [options.args]
|
||||
* @param {object} [opts]
|
||||
* @param {string} [opts.cwd]
|
||||
* @param {string[]} [opts.args]
|
||||
* @returns {Promise<TestResult>}
|
||||
*/
|
||||
async function spawnBunTest(execPath, testPath, options = { cwd }) {
|
||||
async function spawnBunTest(execPath, testPath, opts = { cwd }) {
|
||||
const timeout = getTestTimeout(testPath);
|
||||
const perTestTimeout = Math.ceil(timeout / 2);
|
||||
const absPath = join(options["cwd"], testPath);
|
||||
const absPath = join(opts["cwd"], testPath);
|
||||
const isReallyTest = isTestStrict(testPath) || absPath.includes("vendor");
|
||||
const args = options["args"] ?? [];
|
||||
const args = opts["args"] ?? [];
|
||||
|
||||
const testArgs = ["test", ...args, `--timeout=${perTestTimeout}`];
|
||||
|
||||
@@ -1291,10 +1320,16 @@ async function spawnBunTest(execPath, testPath, options = { cwd }) {
|
||||
env.BUN_JSC_validateExceptionChecks = "1";
|
||||
env.BUN_JSC_dumpSimulatedThrows = "1";
|
||||
}
|
||||
if ((basename(execPath).includes("asan") || !isCI) && shouldValidateLeakSan(relative(cwd, absPath))) {
|
||||
env.BUN_DESTRUCT_VM_ON_EXIT = "1";
|
||||
env.ASAN_OPTIONS = "allow_user_segv_handler=1:disable_coredump=0:detect_leaks=1";
|
||||
// prettier-ignore
|
||||
env.LSAN_OPTIONS = `malloc_context_size=100:print_suppressions=0:suppressions=${process.cwd()}/test/leaksan.supp`;
|
||||
}
|
||||
|
||||
const { ok, error, stdout, crashes } = await spawnBun(execPath, {
|
||||
args: isReallyTest ? testArgs : [...args, absPath],
|
||||
cwd: options["cwd"],
|
||||
cwd: opts["cwd"],
|
||||
timeout: isReallyTest ? timeout : 30_000,
|
||||
env,
|
||||
stdout: options.stdout,
|
||||
@@ -1528,7 +1563,11 @@ function isNodeTest(path) {
|
||||
return false;
|
||||
}
|
||||
const unixPath = path.replaceAll(sep, "/");
|
||||
return unixPath.includes("js/node/test/parallel/") || unixPath.includes("js/node/test/sequential/");
|
||||
return (
|
||||
unixPath.includes("js/node/test/parallel/") ||
|
||||
unixPath.includes("js/node/test/sequential/") ||
|
||||
unixPath.includes("js/bun/test/parallel/")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user