mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-11-01 13:58:18 +00:00
Migrate CLI E2E tests to Node.js 26
Run the CLI subprocess suites and benchmark tools as native TypeScript with node:test, and move them under test/e2e-cli. Update CI and Compose runners, run local smoke tests on Linux, macOS, and Windows, and collect source-mapped CLI coverage with c8.
This commit is contained in:
1 parent
a0987016bb
commit
2433d117c2
91 files changed
+2219
-1615
No files matched your search
@@ -0,0 +1,178 @@
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { runProcess } from "./helpers/process.ts";
|
||||
|
||||
type SweepResult = {
|
||||
name: string;
|
||||
runner: "p2p" | "couchdb";
|
||||
rttMs?: number;
|
||||
repeatIndex: number;
|
||||
repeatCount: number;
|
||||
result: Record<string, unknown>;
|
||||
};
|
||||
|
||||
function readEnvString(name: string, fallback: string): string {
|
||||
const value = process.env[name]?.trim();
|
||||
return value && value.length > 0 ? value : fallback;
|
||||
}
|
||||
|
||||
function timestamp(): string {
|
||||
const d = new Date();
|
||||
const pad = (n: number) => String(n).padStart(2, "0");
|
||||
return (
|
||||
`${d.getUTCFullYear()}${pad(d.getUTCMonth() + 1)}${pad(d.getUTCDate())}-` +
|
||||
`${pad(d.getUTCHours())}${pad(d.getUTCMinutes())}${pad(d.getUTCSeconds())}`
|
||||
);
|
||||
}
|
||||
|
||||
function readEnvInteger(name: string, fallback: number): number {
|
||||
const raw = readEnvString(name, String(fallback));
|
||||
const parsed = Number(raw);
|
||||
if (!Number.isInteger(parsed) || parsed < 1) {
|
||||
throw new Error(`${name} must be a positive integer, got '${raw}'`);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function parseRttList(raw: string): number[] {
|
||||
const values = raw
|
||||
.split(",")
|
||||
.map((value) => Number(value.trim()))
|
||||
.filter((value) => Number.isFinite(value) && value > 0)
|
||||
.map((value) => Math.floor(value));
|
||||
if (values.length === 0) {
|
||||
throw new Error(`BENCH_SWEEP_RTT_MS must contain at least one positive number, got '${raw}'`);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
function buildBaseEnv(): Record<string, string> {
|
||||
return {
|
||||
BENCH_MD_FILE_COUNT: readEnvString("BENCH_MD_FILE_COUNT", "20"),
|
||||
BENCH_MD_MIN_SIZE_BYTES: readEnvString("BENCH_MD_MIN_SIZE_BYTES", "512"),
|
||||
BENCH_MD_MAX_SIZE_BYTES: readEnvString("BENCH_MD_MAX_SIZE_BYTES", "2048"),
|
||||
BENCH_BIN_FILE_COUNT: readEnvString("BENCH_BIN_FILE_COUNT", "5"),
|
||||
BENCH_BIN_SIZE_BYTES: readEnvString("BENCH_BIN_SIZE_BYTES", "8192"),
|
||||
BENCH_SYNC_TIMEOUT: readEnvString("BENCH_SYNC_TIMEOUT", "300"),
|
||||
BENCH_PEERS_TIMEOUT: readEnvString("BENCH_PEERS_TIMEOUT", "60"),
|
||||
BENCH_SEED: readEnvString("BENCH_SEED", "livesync-benchmark-seed"),
|
||||
BENCH_VERIFY_MODE: readEnvString("BENCH_VERIFY_MODE", "all"),
|
||||
LIVESYNC_TEST_TEE: readEnvString("BENCH_LIVESYNC_TEST_TEE", "0"),
|
||||
};
|
||||
}
|
||||
|
||||
async function runBenchmark(options: {
|
||||
taskName: "bench:p2p" | "bench:couchdb";
|
||||
name: string;
|
||||
outputDir: string;
|
||||
env: Record<string, string>;
|
||||
repeatIndex: number;
|
||||
repeatCount: number;
|
||||
}): Promise<Record<string, unknown>> {
|
||||
const suffix = options.repeatCount > 1
|
||||
? `-r${String(options.repeatIndex).padStart(2, "0")}`
|
||||
: "";
|
||||
const resultPath = join(options.outputDir, `${options.name}${suffix}.json`);
|
||||
const env = {
|
||||
...options.env,
|
||||
BENCH_RESULT_JSON: resultPath,
|
||||
BENCH_REPEAT_INDEX: String(options.repeatIndex),
|
||||
BENCH_REPEAT_COUNT: String(options.repeatCount),
|
||||
};
|
||||
|
||||
console.log(`[latency-sweep] running ${options.name}`);
|
||||
const child = await runProcess(process.execPath, {
|
||||
args: [fileURLToPath(new URL("./run-task.ts", import.meta.url)), options.taskName],
|
||||
cwd: import.meta.dirname,
|
||||
env,
|
||||
stdin: "null",
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
});
|
||||
if (!child.success) {
|
||||
throw new Error(`benchmark failed: ${options.name} (exit ${child.code})`);
|
||||
}
|
||||
return JSON.parse(await readFile(resultPath, "utf8")) as Record<string, unknown>;
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const outRoot = readEnvString("BENCH_SWEEP_ROOT", join(import.meta.dirname, "bench-results"));
|
||||
const outputDir = join(outRoot, `latency-sweep-${timestamp()}`);
|
||||
const rtts = parseRttList(readEnvString("BENCH_SWEEP_RTT_MS", "20,50,100,150,300"));
|
||||
const repeatCount = readEnvInteger("BENCH_REPEAT_COUNT", 1);
|
||||
const base = buildBaseEnv();
|
||||
|
||||
await mkdir(outputDir, { recursive: true });
|
||||
|
||||
const results: SweepResult[] = [];
|
||||
if (readEnvString("BENCH_SWEEP_INCLUDE_P2P", "true") !== "false") {
|
||||
for (let repeatIndex = 1; repeatIndex <= repeatCount; repeatIndex++) {
|
||||
const p2pResult = await runBenchmark({
|
||||
taskName: "bench:p2p",
|
||||
name: "p2p-direct-local",
|
||||
outputDir,
|
||||
repeatIndex,
|
||||
repeatCount,
|
||||
env: {
|
||||
...base,
|
||||
BENCH_CASE: "p2p-direct-local",
|
||||
BENCH_TURN_SERVERS: "",
|
||||
},
|
||||
});
|
||||
results.push({
|
||||
name: "p2p-direct-local",
|
||||
runner: "p2p",
|
||||
repeatIndex,
|
||||
repeatCount,
|
||||
result: p2pResult,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const rtt of rtts) {
|
||||
const name = `couchdb-rtt-${rtt}ms`;
|
||||
for (let repeatIndex = 1; repeatIndex <= repeatCount; repeatIndex++) {
|
||||
const couchdbResult = await runBenchmark({
|
||||
taskName: "bench:couchdb",
|
||||
name,
|
||||
outputDir,
|
||||
repeatIndex,
|
||||
repeatCount,
|
||||
env: {
|
||||
...base,
|
||||
BENCH_CASE: name,
|
||||
BENCH_COUCHDB_RTT_MS: String(rtt),
|
||||
},
|
||||
});
|
||||
results.push({
|
||||
name,
|
||||
runner: "couchdb",
|
||||
rttMs: rtt,
|
||||
repeatIndex,
|
||||
repeatCount,
|
||||
result: couchdbResult,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const summary = {
|
||||
generatedAt: new Date().toISOString(),
|
||||
outputDir,
|
||||
note:
|
||||
"This sweep applies half of each requested CouchDB RTT before forwarding requests and half before returning responses. It is not a full netem model of jitter, loss, MTU, bandwidth, or VPN encapsulation.",
|
||||
rtts,
|
||||
repeatCount,
|
||||
results,
|
||||
};
|
||||
await writeFile(join(outputDir, "summary.json"), JSON.stringify(summary, null, 2));
|
||||
console.log(JSON.stringify(summary, null, 2));
|
||||
console.log(`[latency-sweep] result directory: ${outputDir}`);
|
||||
}
|
||||
|
||||
if (import.meta.main) {
|
||||
main().catch((error) => {
|
||||
console.error("[Fatal Error]", error);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user