diff --git a/.eslintrc.js b/.eslintrc.js index 2e0a7e4..e243b9f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -16,10 +16,6 @@ module.exports = { rules: { "@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/explicit-module-boundary-types": "off", - "@typescript-eslint/member-delimiter-style": [ - "warn", - { multiline: { delimiter: "none" } }, - ], "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-var-requires": "off", "@typescript-eslint/await-thenable": ["error"], diff --git a/.ncurc.json b/.ncurc.json index 1c378c6..3de3741 100644 --- a/.ncurc.json +++ b/.ncurc.json @@ -1,6 +1,9 @@ { "reject": [ "@types/chai", + "@types/chai-as-promised", + "chai-as-promised", + "chai-as-promised why: v8 went to ESM", "chai", "eslint", "rimraf", diff --git a/.vscode/settings.json b/.vscode/settings.json index 6af3ca4..45e960a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,6 +8,7 @@ "rngseed" ], "cSpell.words": [ + "sinonjs", "zombification" ] } diff --git a/package.json b/package.json index 38ebfcb..d46e6de 100644 --- a/package.json +++ b/package.json @@ -49,16 +49,18 @@ "author": "Matthew McEachen ", "license": "MIT", "devDependencies": { + "@sinonjs/fake-timers": "^13.0.3", "@types/chai": "^4.3.11", - "@types/chai-as-promised": "^8.0.1", + "@types/chai-as-promised": "^7", "@types/chai-string": "^1.4.5", "@types/chai-subset": "^1.3.5", "@types/mocha": "^10.0.9", "@types/node": "^22.7.7", + "@types/sinonjs__fake-timers": "^8.1.5", "@typescript-eslint/eslint-plugin": "^8.10.0", "@typescript-eslint/parser": "^8.10.0", "chai": "^4.3.10", - "chai-as-promised": "^8.0.0", + "chai-as-promised": "^7.1.2", "chai-string": "^1.5.0", "chai-subset": "^1.6.0", "chai-withintoleranceof": "^1.0.1", @@ -73,7 +75,6 @@ "serve": "^14.2.4", "source-map-support": "^0.5.21", "split2": "^4.2.0", - "timekeeper": "^2.3.1", "typedoc": "^0.26.10", "typescript": "~5.6.3" } diff --git a/src/BatchCluster.spec.ts b/src/BatchCluster.spec.ts index f3144e5..6031671 100644 --- a/src/BatchCluster.spec.ts +++ b/src/BatchCluster.spec.ts @@ -1,14 +1,5 @@ +import FakeTimers from "@sinonjs/fake-timers" import process from "node:process" -import { filterInPlace } from "./Array" -import { delay, until } from "./Async" -import { BatchCluster } from "./BatchCluster" -import { secondMs } from "./BatchClusterOptions" -import { DefaultTestOptions } from "./DefaultTestOptions.spec" -import { map, omit, orElse } from "./Object" -import { isWin } from "./Platform" -import { toS } from "./String" -import { Task } from "./Task" -import { thenOrTimeout } from "./Timeout" import { currentTestPids, expect, @@ -24,9 +15,18 @@ import { times, unhandledRejections, } from "./_chai.spec" +import { filterInPlace } from "./Array" +import { delay, until } from "./Async" +import { BatchCluster } from "./BatchCluster" +import { secondMs } from "./BatchClusterOptions" +import { DefaultTestOptions } from "./DefaultTestOptions.spec" +import { map, omit, orElse } from "./Object" +import { isWin } from "./Platform" +import { toS } from "./String" +import { Task } from "./Task" +import { thenOrTimeout } from "./Timeout" const isCI = process.env.CI === "1" -const tk = require("timekeeper") function arrayEqualish(a: T[], b: T[], maxAcceptableDiffs: number) { const common = a.filter((ea) => b.includes(ea)) @@ -901,11 +901,20 @@ describe("BatchCluster", function () { describe("maxProcAgeMillis (recycling procs)", () => { let bc: BatchCluster + let clock: FakeTimers.InstalledClock + + beforeEach(() => { + clock = FakeTimers.install({ + shouldClearNativeTimers: true, + shouldAdvanceTime: true, + }) + }) afterEach(() => { - tk.reset() + clock.uninstall() return shutdown(bc) }) + for (const { maxProcAgeMillis, ctx, exp } of [ { maxProcAgeMillis: 0, @@ -929,8 +938,6 @@ describe("BatchCluster", function () { it("(" + maxProcAgeMillis + "): " + ctx, async function () { // TODO: look into why this fails in CI on windows if (isWin && isCI) return this.skip() - const start = Date.now() - tk.freeze(start) setFailratePct(0) bc = listen( @@ -944,7 +951,7 @@ describe("BatchCluster", function () { ) assertExpectedResults(await Promise.all(runTasks(bc, 2))) const pidsBefore = bc.pids() - tk.freeze(start + 7000) + clock.tick(7000) assertExpectedResults(await Promise.all(runTasks(bc, 2))) const pidsAfter = bc.pids() console.dir({ maxProcAgeMillis, pidsBefore, pidsAfter }) diff --git a/src/BatchClusterEmitter.ts b/src/BatchClusterEmitter.ts index ec186ee..b12e7b6 100644 --- a/src/BatchClusterEmitter.ts +++ b/src/BatchClusterEmitter.ts @@ -23,7 +23,7 @@ export interface TypedEventEmitter { ): this emit(eventName: E, ...args: Args): boolean - // eslint-disable-next-line @typescript-eslint/ban-types + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type listeners(event: E): Function[] removeAllListeners(eventName?: keyof T): this diff --git a/src/BatchProcess.ts b/src/BatchProcess.ts index a6d7eb0..50239de 100644 --- a/src/BatchProcess.ts +++ b/src/BatchProcess.ts @@ -365,7 +365,7 @@ export class BatchProcess { }) return true } - } catch (err) { + } catch { // child process went away. We should too. this.end(false, "stdin.error") return false diff --git a/src/Error.ts b/src/Error.ts index 9100aa8..7bce9bd 100644 --- a/src/Error.ts +++ b/src/Error.ts @@ -8,7 +8,7 @@ export function tryEach(arr: (() => void)[]): void { for (const f of arr) { try { f() - } catch (_) { + } catch { // } } diff --git a/src/Rate.spec.ts b/src/Rate.spec.ts index 82f7b71..c2c6c1a 100644 --- a/src/Rate.spec.ts +++ b/src/Rate.spec.ts @@ -1,20 +1,22 @@ +import FakeTimers from "@sinonjs/fake-timers" import { minuteMs } from "./BatchClusterOptions" import { Rate } from "./Rate" import { expect, times } from "./_chai.spec" -const tk = require("timekeeper") - describe("Rate", () => { const now = Date.now() const r = new Rate() + let clock: FakeTimers.InstalledClock beforeEach(() => { - tk.freeze(now) - // clear() must be called _after_ freezing time + clock = FakeTimers.install({ now: now }) + // clear() must be called _after_ setting up fake timers r.clear() }) - after(() => tk.reset()) + afterEach(() => { + clock.uninstall() + }) function expectRate(rate: Rate, epm: number, tol = 0.1) { expect(rate.eventsPerMs).to.be.withinToleranceOf(epm, tol) @@ -27,7 +29,7 @@ describe("Rate", () => { }) it("maintains a rate of 0 after time with no events", () => { - tk.freeze(now + minuteMs) + clock.tick(minuteMs) expectRate(r, 0) }) @@ -37,38 +39,39 @@ describe("Rate", () => { () => { times(cnt, () => r.onEvent()) expectRate(r, 0) - tk.freeze(now + 100) + clock.tick(100) expectRate(r, 0) - tk.freeze(now + r.warmupMs + 1) + clock.tick(r.warmupMs - 100 + 1) expectRate(r, cnt / r.warmupMs) - tk.freeze(now + 2 * r.warmupMs) + clock.tick(r.warmupMs) expectRate(r, cnt / (2 * r.warmupMs)) - tk.freeze(now + 3 * r.warmupMs) + clock.tick(r.warmupMs) expectRate(r, cnt / (3 * r.warmupMs)) - tk.freeze(now + r.periodMs) + clock.tick(r.periodMs - 3 * r.warmupMs) expectRate(r, 0) - expect(r.msSinceLastEvent).to.eql(minuteMs) + expect(r.msSinceLastEvent).to.be.closeTo(r.periodMs, 5) }, ) } - for (const events of [5, 10, 100, 1000]) { + for (const events of [4, 32, 256, 1024]) { it( "calculates average rate for " + events + " events, and then decays", () => { const period = r.periodMs - times(events, (i) => { - tk.freeze(now + (period * i) / events) + times(events, () => { + clock.tick(r.periodMs / events) r.onEvent() }) + const tickMs = r.periodMs / 4 expectRate(r, events / period, 0.3) - tk.freeze(now + 1.25 * r.periodMs) + clock.tick(tickMs) expectRate(r, 0.75 * (events / period), 0.3) - tk.freeze(now + 1.5 * r.periodMs) + clock.tick(tickMs) expectRate(r, 0.5 * (events / period), 0.3) - tk.freeze(now + 1.75 * r.periodMs) - expectRate(r, 0.25 * (events / period), 0.3) - tk.freeze(now + 2 * r.periodMs) + clock.tick(tickMs) + expectRate(r, 0.25 * (events / period), 0.5) + clock.tick(tickMs) expectRate(r, 0) }, ) diff --git a/src/_chai.spec.ts b/src/_chai.spec.ts index e761fce..de29a8d 100644 --- a/src/_chai.spec.ts +++ b/src/_chai.spec.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-require-imports */ try { require("source-map-support").install() } catch { diff --git a/src/test.ts b/src/test.ts index fe985e9..afee866 100644 --- a/src/test.ts +++ b/src/test.ts @@ -40,7 +40,8 @@ function toF(s: string | undefined) { const failrate = toF(process.env.failrate) ?? 0 const rng = process.env.rngseed != null - ? require("seedrandom")(process.env.rngseed) + ? // eslint-disable-next-line @typescript-eslint/no-require-imports + require("seedrandom")(process.env.rngseed) : Math.random async function onLine(line: string): Promise { @@ -148,5 +149,6 @@ async function onLine(line: string): Promise { const m = new Mutex() process.stdin + // eslint-disable-next-line @typescript-eslint/no-require-imports .pipe(require("split2")()) .on("data", (ea: string) => m.serial(() => onLine(ea)))