From: Ardis Lu Date: Tue, 15 Jul 2025 05:07:56 +0000 (-0700) Subject: Add initial tests for memory leaks X-Git-Tag: v0.1.5~17 X-Git-Url: https://git.ardis.lu/?a=commitdiff_plain;h=794e8954218ec9fda54a28c0da9af2ac8dd5e0d5;p=csvio Add initial tests for memory leaks Future tests should be dedicated for finding memory leaks only, but as an initial step it makes sense to add a simple sanity check to the large file tests. --- diff --git a/package.json b/package.json index 83d22a5..8f01a9a 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "scripts": { "types": "tsc", "test": "node --test --test-skip-pattern=\"/^large:/\"", - "test:large": "node --test --test-name-pattern=\"/^large:/\"", + "test:large": "node --test --test-name-pattern=\"/^large:/\" --expose-gc", "test:coverage": "npm run test -- --experimental-test-coverage", "bench": "node ./benchmark/core.bench.js", "prepack": "node ./npm/prepack.js", diff --git a/test/large.test.js b/test/large.test.js index cb35b66..3d2252b 100644 --- a/test/large.test.js +++ b/test/large.test.js @@ -1,22 +1,36 @@ import { suite, test, before, after } from 'node:test'; import { ok, deepStrictEqual } from 'node:assert/strict'; import { unlink, stat } from 'node:fs/promises'; +import { memoryUsage } from 'node:process'; import { createRandomCSV, createTempFile } from './utils.js'; import { CSVReader, CSVTransformer, CSVWriter } from '../src/core.js'; +/** Maximum variance allowed for memory leak testing. */ +const MEMORY_THRESHOLD = 10 * 1024 * 1024; // 10 MB, or 10% of the CSV file size + suite('large: 100000 row x 100 column CSV (~100 MB)', { concurrency: 1 }, async () => { let temp; before(async () => { temp = await createTempFile() }); after(async () => await unlink(temp)); test('CSVWriter writes file', async () => { + global.gc?.(); + const memoryBefore = memoryUsage().heapUsed; + await createRandomCSV(100000, 100, 1).pipeTo(new CSVWriter(temp)); const { size } = await stat(temp, { bigint: true }); ok(size > 100_000_000n); + + global.gc?.(); + const memoryAfter = memoryUsage().heapUsed; + ok(memoryAfter - memoryBefore < MEMORY_THRESHOLD); }); test('CSVReader reads file', async () => { + global.gc?.(); + const memoryBefore = memoryUsage().heapUsed; + const reader = new CSVReader(temp); let rowCount = 0; for await (const row of reader) { @@ -38,9 +52,16 @@ suite('large: 100000 row x 100 column CSV (~100 MB)', { concurrency: 1 }, async } rowCount++; } + + global.gc?.(); + const memoryAfter = memoryUsage().heapUsed; + ok(memoryAfter - memoryBefore < MEMORY_THRESHOLD); }); test('CSVTransformer transforms file', async () => { + global.gc?.(); + const memoryBefore = memoryUsage().heapUsed; + const reader = new CSVReader(temp) .pipeThrough(new CSVTransformer(r => r.map(f => Number(f) + 1), { handleHeaders: true })); let rowCount = 0; @@ -63,6 +84,10 @@ suite('large: 100000 row x 100 column CSV (~100 MB)', { concurrency: 1 }, async } rowCount++; } + + global.gc?.(); + const memoryAfter = memoryUsage().heapUsed; + ok(memoryAfter - memoryBefore < MEMORY_THRESHOLD); }); }); @@ -72,12 +97,22 @@ suite('large: 100 row x 100000 column CSV (~100 MB)', { concurrency: 1 }, async after(async () => await unlink(temp)); test('CSVWriter writes file', async () => { + global.gc?.(); + const memoryBefore = memoryUsage().heapUsed; + await createRandomCSV(100, 100000, 1).pipeTo(new CSVWriter(temp)); const { size } = await stat(temp, { bigint: true }); ok(size > 100_000_000n); + + global.gc?.(); + const memoryAfter = memoryUsage().heapUsed; + ok(memoryAfter - memoryBefore < MEMORY_THRESHOLD); }); test('CSVReader reads file', async () => { + global.gc?.(); + const memoryBefore = memoryUsage().heapUsed; + const reader = new CSVReader(temp); let rowCount = 0; for await (const row of reader) { @@ -99,9 +134,16 @@ suite('large: 100 row x 100000 column CSV (~100 MB)', { concurrency: 1 }, async } rowCount++; } + + global.gc?.(); + const memoryAfter = memoryUsage().heapUsed; + ok(memoryAfter - memoryBefore < MEMORY_THRESHOLD); }); test('CSVTransformer transforms file', async () => { + global.gc?.(); + const memoryBefore = memoryUsage().heapUsed; + const reader = new CSVReader(temp) .pipeThrough(new CSVTransformer(r => r.map(f => Number(f) + 1), { handleHeaders: true })); let rowCount = 0; @@ -124,5 +166,9 @@ suite('large: 100 row x 100000 column CSV (~100 MB)', { concurrency: 1 }, async } rowCount++; } + + global.gc?.(); + const memoryAfter = memoryUsage().heapUsed; + ok(memoryAfter - memoryBefore < MEMORY_THRESHOLD); }); }); \ No newline at end of file