项目文件夹

文件
2026-07-13 13:39:12 +08:00

243 行
8.1 KiB
TypeScript

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
/**
* tests/integration/live-gemini-workload.test.ts
*
* Streaming live Gemini workload test. Reuses shared generators from
* liveGeminiShared.ts. For non-streaming, see live-gemini-nonstream.test.ts.
*/
import test from "node:test";
import assert from "node:assert/strict";
import {
skip,
randomInt,
sendAndValidate,
CASE_BUILDERS,
ensureTestEnvironment,
DELAY_BETWEEN_REQUESTS_MS,
type Message,
} from "./liveGeminiShared.ts";
test.before(async () => {
await ensureTestEnvironment();
});
// --------------------------------------------------------------------------
// Concurrent load — 5 parallel threads × 15 iterations
// --------------------------------------------------------------------------
test("[26] concurrent load — 2 threads × 2 iterations", { skip }, async () => {
const THREAD_COUNT = 2;
const SET_COUNT = 2;
const TOTAL_REQUESTS = THREAD_COUNT * SET_COUNT;
console.log(
`\n Concurrent load: ${THREAD_COUNT} threads × ${SET_COUNT} iterations = ${TOTAL_REQUESTS} requests`
);
const start = performance.now();
// Shared state for parallel-request detection across threads
const requestWindows: { cid: string; start: number; end: number }[] = [];
let parallelViolation: string | null = null;
const threadResults = await Promise.allSettled(
Array.from({ length: THREAD_COUNT }, (_, threadIdx) =>
(async () => {
const results: {
status: number;
duration: number;
tokens: number;
contentLength: number;
correlationId: string;
}[] = [];
for (let set = 1; set <= SET_COUNT; set++) {
if (parallelViolation) break; // another thread detected a violation
const idx = randomInt(0, CASE_BUILDERS.length - 1);
const tc = CASE_BUILDERS[idx];
const label = `t${threadIdx + 1}-i${set}: ${tc.name}`;
const requestStart = Date.now();
const r = await sendAndValidate(label, tc.build);
const requestEnd = Date.now();
// Check for parallel requests with the same correlationId
const cid = r.correlationId;
const myWindow = { cid, start: requestStart, end: requestEnd };
const siblings = requestWindows.filter((w) => w.cid === cid);
for (const s of siblings) {
if (myWindow.start < s.end && s.start < myWindow.end) {
parallelViolation =
`PARALLEL REQUEST DETECTED: cid=${cid.slice(0, 12)}… ` +
`window [${new Date(myWindow.start).toISOString().slice(11, 23)}${new Date(myWindow.end).toISOString().slice(11, 23)}] ` +
`overlaps with [${new Date(s.start).toISOString().slice(11, 23)}${new Date(s.end).toISOString().slice(11, 23)}]`;
break;
}
}
requestWindows.push(myWindow);
results.push({ ...r } as any);
}
return results;
})()
)
);
const totalDuration = performance.now() - start;
const fulfilled = threadResults.filter((r) => r.status === "fulfilled") as PromiseFulfilledResult<
{
status: number;
duration: number;
tokens: number;
contentLength: number;
correlationId: string;
}[]
>[];
const rejected = threadResults.filter((r) => r.status === "rejected") as PromiseRejectedResult[];
const allResults = fulfilled.flatMap((r) => r.value);
const totalTokens = allResults.reduce((sum, r) => sum + r.tokens, 0);
const avgDuration =
allResults.length > 0
? Math.round(allResults.reduce((s, r) => s + r.duration, 0) / allResults.length)
: 0;
console.log(
`\n Concurrent summary: ${fulfilled.length}/${THREAD_COUNT} threads completed | ` +
`${allResults.length}/${TOTAL_REQUESTS} requests succeeded | ` +
`${Math.round(totalDuration)}ms wall clock | ` +
`${avgDuration}ms avg per request | ` +
`${totalTokens} total tokens`
);
if (rejected.length > 0) {
for (const r of rejected) {
const msg = r.reason instanceof Error ? r.message : String(r.reason);
console.log(` THREAD FAILED: ${msg}`);
}
}
if (parallelViolation) {
console.log(`\n !! ${parallelViolation}`);
}
assert.ok(
fulfilled.length === THREAD_COUNT,
`expected all ${THREAD_COUNT} threads to complete, ${rejected.length} failed`
);
assert.ok(
allResults.length === TOTAL_REQUESTS,
`expected ${TOTAL_REQUESTS} total requests, got ${allResults.length}`
);
assert.ok(!parallelViolation, parallelViolation);
});
// --------------------------------------------------------------------------
// Single-thread sequential — 1 thread × 5 iterations
// --------------------------------------------------------------------------
test("[27] single-thread sequential — 1 thread × 5 iterations", { skip }, async () => {
const SET_COUNT = 5;
console.log(`\n Single-thread sequential: 1 thread × ${SET_COUNT} iterations`);
const start = performance.now();
const results: {
status: number;
duration: number;
tokens: number;
contentLength: number;
correlationId: string;
}[] = [];
for (let i = 1; i <= SET_COUNT; i++) {
const idx = randomInt(0, CASE_BUILDERS.length - 1);
const tc = CASE_BUILDERS[idx];
const label = `seq-i${i}: ${tc.name}`;
try {
if (i > 1) await new Promise((r) => setTimeout(r, DELAY_BETWEEN_REQUESTS_MS));
const r = await sendAndValidate(label, tc.build);
results.push(r);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
const ts = new Date().toISOString().slice(11, 23);
console.log(`${ts} ${label.padEnd(45)} FAILED: ${msg}`);
assert.fail(`iteration ${i} failed: ${msg}`);
}
}
const totalDuration = performance.now() - start;
const totalTokens = results.reduce((sum, r) => sum + r.tokens, 0);
const avgDuration =
results.length > 0
? Math.round(results.reduce((s, r) => s + r.duration, 0) / results.length)
: 0;
console.log(
`\n Sequential summary: ${results.length}/${SET_COUNT} succeeded | ` +
`${Math.round(totalDuration)}ms wall clock | ` +
`${avgDuration}ms avg per request | ` +
`${totalTokens} total tokens`
);
// Verify no duplicate correlation IDs (no retries created parallel entries)
const cids = results.map((r) => r.correlationId);
const uniqueCids = new Set(cids);
assert.equal(
uniqueCids.size,
cids.length,
`expected ${cids.length} unique correlation IDs, got ${uniqueCids.size} — duplicates detected`
);
assert.equal(results.length, SET_COUNT, `expected ${SET_COUNT} results, got ${results.length}`);
});
// ── Streaming-specific tests ────────────────────────────────────────────
test("[28] streaming: all payloads return content", { skip }, async () => {
const failures: string[] = [];
for (let i = 0; i < CASE_BUILDERS.length; i++) {
const tc = CASE_BUILDERS[i];
const label = `stream-${String(i + 1).padStart(2, "0")}: ${tc.name}`;
try {
const r = await sendAndValidate(label, tc.build);
if (r.contentLength === 0) {
failures.push(`${tc.name}: 0 bytes content`);
}
if (r.status !== 200) {
failures.push(`${tc.name}: HTTP ${r.status}`);
}
} catch (err) {
failures.push(`${tc.name}: ${err instanceof Error ? err.message : String(err)}`);
}
}
if (failures.length > 0) {
console.log(`\n Streaming failures (${failures.length}):`);
for (const f of failures) console.log(` ${f}`);
}
assert.equal(
failures.length,
0,
`${failures.length}/${CASE_BUILDERS.length} streaming payloads failed`
);
});
test("[29] streaming: correlation IDs are unique per request", { skip }, async () => {
const cids: string[] = [];
const count = 5;
for (let i = 0; i < count; i++) {
const tc = CASE_BUILDERS[i % CASE_BUILDERS.length];
const r = await sendAndValidate(`cid-${i + 1}: ${tc.name}`, tc.build);
cids.push(r.correlationId);
}
const unique = new Set(cids);
assert.equal(
unique.size,
count,
`expected ${count} unique CIDs, got ${unique.size}: ${cids.join(", ")}`
);
});