diff --git a/package-lock.json b/package-lock.json index 85676cc..f02f6d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "11.5.3", "license": "BlueOak-1.0.0", "devDependencies": { + "@tapjs/clock": "^3.3.10", "benchmark": "^2.1.4", "esbuild": "^0.28.0", "marked": "^4.2.12", @@ -1465,6 +1466,23 @@ "@tapjs/core": "4.5.8" } }, + "node_modules/@tapjs/clock": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/@tapjs/clock/-/clock-3.3.10.tgz", + "integrity": "sha512-BO0qHEbEX0oR5wK0JH8LFc0I00I2M4nyQPNq529EBJks7dyfFzcS6VeBV5YRnlHv2FdSXiqI4OzdJhpCbeUuIA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@tapjs/after": "3.3.10", + "clock-mock": "^2.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "peerDependencies": { + "@tapjs/core": "4.5.8" + } + }, "node_modules/@tapjs/config": { "version": "5.6.4", "resolved": "https://registry.npmjs.org/@tapjs/config/-/config-5.6.4.tgz", @@ -2543,6 +2561,16 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/clock-mock": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/clock-mock/-/clock-mock-2.0.4.tgz", + "integrity": "sha512-y54bFfYHLME1IF/rwPa7uF0+oYmozUXu9AKyAc9HsS3I4uQp8cyi7AxNQABxuniDnCg1hyGk+0KXORPooombGg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16.17" + } + }, "node_modules/code-excerpt": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", diff --git a/package.json b/package.json index 6f54392..5b7add1 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "scripts": { "build": "npm run prepare", "prepare": "tshy && bash scripts/build.sh", - "pretest": "npm run prepare", + "pretest": "npm run prepare && tap build", "presnap": "npm run prepare", "test": "tap", "snap": "tap", @@ -87,6 +87,7 @@ "url": "git+ssh://git@github.com/isaacs/node-lru-cache.git" }, "devDependencies": { + "@tapjs/clock": "^3.3.10", "benchmark": "^2.1.4", "esbuild": "^0.28.0", "marked": "^4.2.12", diff --git a/test/ttl-autopurge-rearm.ts b/test/ttl-autopurge-rearm.ts new file mode 100644 index 0000000..a3bfd88 --- /dev/null +++ b/test/ttl-autopurge-rearm.ts @@ -0,0 +1,27 @@ +import { LRUCache } from 'lru-cache' +import t from 'tap' + +// If the autopurge timer fires before the entry is stale (timer drift, +// coarse clocks), it must be re-armed instead of dropped. Use a perf +// clock that only moves when the test says so, so this does not depend +// on platform timer timing. +t.test( + 'autopurge timer re-arms when the entry is not stale yet', + async t => { + let now = 1000 + const cache = new LRUCache({ + max: 10, + ttl: 10, + ttlAutopurge: true, + ttlResolution: 0, + perf: { now: () => now }, + }) + cache.set('a', 1) + // timer fires after ~11ms of real time, but the clock has not moved + await new Promise(r => setTimeout(r, 50)) + t.equal(cache.size, 1, 'not purged while not stale') + now += 1000 + await new Promise(r => setTimeout(r, 50)) + t.equal(cache.size, 0, 'purged by the re-armed timer once stale') + }, +)