diff --git a/TODO.md b/TODO.md index 854ff37..e4da039 100644 --- a/TODO.md +++ b/TODO.md @@ -26,6 +26,14 @@ declares one. # Completed Steps +- 2026-11-10: The test suite passes on a host under heavy load + (https://git.eeqj.de/sneak/quak/issues/233). vitest runs at most four workers + instead of one per CPU core. `test/cli/bin.test.ts` compiles the sources + `bin/quak.ts` imports and starts libsodium once, before its tests run. The + fake accounts of `test/ente-server.ts` and + `test/thumbnails/thumbnails.test.ts` stretch their passwords at libsodium's + lowest Argon2id cost, since every login stretches the password again. + - 2026-10-26: On an empty cache, `quak helper fix-missing-thumbnails --file` stops fetching collections' files once it finds every file it names (https://git.eeqj.de/sneak/quak/issues/223). It used to wait for a refresh, diff --git a/test/cli/bin.test.ts b/test/cli/bin.test.ts index c24726c..6e5b8be 100644 --- a/test/cli/bin.test.ts +++ b/test/cli/bin.test.ts @@ -7,6 +7,11 @@ * Each test runs `bin/quak.ts`, as the smoke test does, with its session loader * replaced by one that records the options it is given and reports no session, * so the command stops before it makes any request. + * + * The first load of the sources `bin/quak.ts` imports compiles them, and the + * first `init()` starts libsodium. On a loaded host that takes longer than a + * test's timeout, so both happen here, before the tests run. Each test's + * `vi.resetModules()` then only runs the compiled sources again. */ import { mkdtempSync, rmSync } from "node:fs"; @@ -14,8 +19,13 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterAll, afterEach, describe, expect, it, vi } from "vitest"; import type { ApiClientOptions } from "../../src/api/client.js"; +import "../../src/cli-commands.js"; +import { init } from "../../src/crypto/index.js"; +import "../../src/index.js"; import { UNATTENDED_RETRY_OPTIONS } from "../../src/retry.js"; +await init(); + const loaded = vi.hoisted(() => [] as (ApiClientOptions | undefined)[]); vi.mock("../../src/cli-session.js", () => ({ diff --git a/test/ente-server.ts b/test/ente-server.ts index 64b95db..58b0ee2 100644 --- a/test/ente-server.ts +++ b/test/ente-server.ts @@ -19,8 +19,11 @@ import { SRP, SrpServer } from "fast-srp-hap"; import { toBase64, deriveKEK, deriveLoginSubkey } from "../src/crypto/index.js"; import type { KeyAttributes } from "../src/auth/types.js"; -const TEST_OPS = 2; -const TEST_MEM = 64 * 1024 * 1024; +// The account's password is stretched at libsodium's lowest Argon2id cost. +// Every login stretches it again, and at a realistic cost that takes seconds on +// a loaded host. +const TEST_OPS = 1; +const TEST_MEM = 8192; export interface MetaMockState { verifier: Buffer; diff --git a/test/thumbnails/thumbnails.test.ts b/test/thumbnails/thumbnails.test.ts index 9db0e48..8582091 100644 --- a/test/thumbnails/thumbnails.test.ts +++ b/test/thumbnails/thumbnails.test.ts @@ -57,8 +57,11 @@ import type { RetryOptions } from "../../src/retry.js"; const TEST_EMAIL = "thumb@example.com"; const TEST_PASSWORD = "thumbpass"; -const TEST_OPS = 2; -const TEST_MEM = 64 * 1024 * 1024; +// The password is stretched at libsodium's lowest Argon2id cost. Each test +// builds an account and logs in, and at a realistic cost that takes seconds on +// a loaded host. +const TEST_OPS = 1; +const TEST_MEM = 8192; const TEST_TIME = 1700000000000000; interface ThumbMockState { diff --git a/vitest.config.ts b/vitest.config.ts index e39bf41..786a9f4 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -3,8 +3,15 @@ import { configDefaults, defineConfig } from "vitest/config"; // vitest does not read .gitignore when looking for tests. A checkout nested // under .claude/ has its own test/ tree, and without this exclude the suite // runs once per nested checkout and still reports success. +// +// vitest starts one worker per CPU core by default, and a shared host has +// dozens. Four keep the suite well inside the Dockerfile's time limit. vitest +// 2 rejects a maxWorkers below its default minWorkers, so minWorkers is set +// too. export default defineConfig({ test: { exclude: [...configDefaults.exclude, ".claude/**"], + minWorkers: 1, + maxWorkers: 4, }, });