Skip to content

Commit 0f3c16d

Browse files
authored
Merge pull request #135 from souloss/fix/ci-flakes-brotli-rmsync
fix(test): stop viewer resources before temp-dir cleanup
2 parents 617da66 + c768e8b commit 0f3c16d

4 files changed

Lines changed: 25 additions & 5 deletions

File tree

history.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- fix(test): **stop viewer resources before removing CLI-test temp directories**`server-ports-busy` and `branch-server` now tear down `server.js` timers/watchers before deleting their isolated `CCV_LOG_DIR`; shared best-effort cleanup uses `fs.promises.rm` with native retries for transient `EBUSY`/`ENOTEMPTY`/`EPERM` failures.
6+
57
- fix(migrate): **迁移完成后仍反复提示迁移**`pendingOf()` 仅按文件大小比对判断是否需要迁移,忽略了 `wire-v2-convert-state.json` 中的 `status: 'done'` 标记。迁移完成后活跃的 v1 日志继续增长(双写),大小不匹配导致误判为待迁移。修复:`pendingOf()` 检测到 `status === 'done'` 时直接返回无需迁移。
68

79
- fix(ccswitch-import): **running cc-switch → `database is locked`** — importing from cc-switch *while it is open* failed with `导入失败(未检测到 cc-switch 或读取出错): query failed: database is locked` because the read-only connection's first query contends with cc-switch's `BEGIN EXCLUSIVE` write lock (its real contention mode: a valid hot journal under an EXCLUSIVE transaction blocks even read-only readers, unlike `BEGIN IMMEDIATE`). The prior malformed-journal fix only caught `SQLITE_BUSY` on its own escalation path; on the main read path `BUSY` escaped to the catch-all and surfaced as the opaque `query failed: database is locked`. Fix in `server/lib/ccswitch-import.js`: detect `SQLITE_BUSY` on **every** path (the read-only open and the providers query), retry once after a 200ms backoff (cc-switch's write transactions are short — transient locks usually clear), and if still held surface the friendly `cc-switch db is locked (cc-switch may be running); retry shortly` instead of the raw wrapper. New `test/ccswitch-import.test.js` cases use a child process holding `BEGIN EXCLUSIVE` to deterministically reproduce both the held-lock (→ friendly message) and transient-lock (→ retry recovers providers) paths.

test/_helpers/rm-sync.mjs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Best-effort recursive removal with native retries for transient filesystem errors.
2+
//
3+
// Each caller first stops the server resources that can write into the directory.
4+
// fs.rm then handles transient EBUSY/ENOTEMPTY/EPERM failures with its built-in
5+
// retry policy. A final cleanup failure is harmless because every test uses a
6+
// unique directory under os.tmpdir().
7+
import { rm } from 'node:fs/promises';
8+
9+
export async function rmBestEffort(dir) {
10+
try {
11+
await rm(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 });
12+
} catch { /* isolated test cleanup; the OS will reap any leftover temp directory */ }
13+
}

test/branch-server.test.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { join } from 'node:path';
2929
import { tmpdir, networkInterfaces } from 'node:os';
3030
import { createRequire } from 'node:module';
3131
import { pathToFileURL } from 'node:url';
32+
import { rmBestEffort } from './_helpers/rm-sync.mjs';
3233

3334
// ── 必须在任何拉起 findcc.js 的 import 之前设置 ──
3435
const tmpDir = mkdtempSync(join(tmpdir(), 'ccv-branch-srv-'));
@@ -882,7 +883,10 @@ export default {
882883
describeCli('server.js turn-end 状态机 / SDK export 分支', { concurrency: false }, () => {
883884
let mod;
884885
before(async () => { mod = await import('../server/server.js'); });
885-
after(() => { rmSync(tmpDir, { recursive: true, force: true }); });
886+
after(async () => {
887+
mod.__testing.reset();
888+
try { await mod.stopViewer(); } finally { await rmBestEffort(tmpDir); }
889+
});
886890

887891
it('__testing namespace 在 NODE_ENV=test 下为真实实现(非 frozen no-op)', () => {
888892
assert.ok(mod.__testing);

test/server-ports-busy.test.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ import { describe, it, before, after } from 'node:test';
1212
import { describeCli } from './_helpers/cli-tier.mjs';
1313
import assert from 'node:assert/strict';
1414
import { createServer } from 'node:net';
15-
import { mkdtempSync, rmSync, mkdirSync } from 'node:fs';
15+
import { mkdtempSync, mkdirSync } from 'node:fs';
1616
import { join } from 'node:path';
1717
import { tmpdir } from 'node:os';
18+
import { rmBestEffort } from './_helpers/rm-sync.mjs';
1819

1920
const tmpDir = mkdtempSync(join(tmpdir(), 'ccv-ports-busy-'));
2021
mkdirSync(join(tmpDir, 'logs'), { recursive: true });
@@ -36,7 +37,7 @@ delete process.env.CCV_USE_PASSWORD;
3637
delete process.env.CCV_PASSWORD;
3738

3839
describeCli('server.js startViewer exhausts the port range (portsBusy)', { concurrency: false }, () => {
39-
let squatter;
40+
let squatter, mod;
4041

4142
before(async () => {
4243
// 占住唯一端口:监听 127.0.0.1:BUSY_PORT,让 startViewer 的 probe connect 命中。
@@ -49,12 +50,12 @@ describeCli('server.js startViewer exhausts the port range (portsBusy)', { concu
4950

5051
after(async () => {
5152
await new Promise((resolve) => { try { squatter.close(() => resolve()); } catch { resolve(); } });
52-
rmSync(tmpDir, { recursive: true, force: true });
53+
try { await mod?.stopViewer(); } finally { await rmBestEffort(tmpDir); }
5354
});
5455

5556
// ── 835-839:唯一端口被占 → probe connect 成功 → tryListen(port+1) → >MAX → resolve(null) ──
5657
it('startViewer resolves null when every port in the range is occupied', async () => {
57-
const mod = await import('../server/server.js');
58+
mod = await import('../server/server.js');
5859
const srv = await mod.startViewer();
5960
assert.equal(srv, null, 'startViewer must resolve null when the port range is exhausted');
6061
});

0 commit comments

Comments
 (0)