项目文件夹

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

247 行
8.1 KiB
TypeScript

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
/**
* tests/integration/live-gemini-nonstream.test.ts
*
* Non-streaming variant of live-gemini-workload.test.ts.
* Reuses the same CASE_BUILDERS payload generators but sends stream: false.
* Validates that non-streaming responses return content and complete without errors.
*/
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();
});
// ── Non-streaming concurrent load — 5 parallel threads × 5 iterations ──
test("[00] non-streaming: concurrent load — 5 threads × 2 iterations", { skip }, async () => {
const THREAD_COUNT = 5;
const SET_COUNT = 2;
const TOTAL_REQUESTS = THREAD_COUNT * SET_COUNT;
console.log(
`\n Non-streaming concurrent: ${THREAD_COUNT} threads × ${SET_COUNT} iterations = ${TOTAL_REQUESTS} requests`
);
const start = performance.now();
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;
const idx = randomInt(0, CASE_BUILDERS.length - 1);
const tc = CASE_BUILDERS[idx];
const label = `ns-t${threadIdx + 1}-i${set}: ${tc.name}`;
const requestStart = Date.now();
const r = await sendAndValidate(label, tc.build, false);
const requestEnd = Date.now();
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 Non-streaming 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);
// Verify all correlation IDs are unique
const cids = allResults.map((r) => r.correlationId);
const uniqueCids = new Set(cids);
assert.equal(
uniqueCids.size,
cids.length,
`expected ${cids.length} unique CIDs, got ${uniqueCids.size}`
);
});
// ── Non-streaming sequential test ───────────────────────────────────────
test("[01] non-streaming: sequential — 1 thread × 5 iterations", { skip }, async () => {
const SET_COUNT = 5;
console.log(`\n Non-streaming 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 = `ns-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, false);
results.push(r);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
console.log(` ${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 Non-streaming summary: ${results.length}/${SET_COUNT} succeeded | ` +
`${Math.round(totalDuration)}ms wall clock | ` +
`${avgDuration}ms avg per request | ` +
`${totalTokens} total tokens`
);
const cids = results.map((r) => r.correlationId);
const uniqueCids = new Set(cids);
assert.equal(
uniqueCids.size,
cids.length,
`expected ${cids.length} unique CIDs, got ${uniqueCids.size}`
);
assert.equal(results.length, SET_COUNT, `expected ${SET_COUNT} results, got ${results.length}`);
});
// ── Non-streaming: all payloads return content ──────────────────────────
test("[02] non-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 = `ns-${String(i + 1).padStart(2, "0")}: ${tc.name}`;
try {
const r = await sendAndValidate(label, tc.build, false);
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 Non-streaming failures (${failures.length}):`);
for (const f of failures) console.log(` ${f}`);
}
assert.equal(
failures.length,
0,
`${failures.length}/${CASE_BUILDERS.length} non-streaming payloads failed`
);
});
// ── Non-streaming: correlation IDs are unique ───────────────────────────
test("[03] non-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(`ns-cid-${i + 1}: ${tc.name}`, tc.build, false);
cids.push(r.correlationId);
}
const unique = new Set(cids);
assert.equal(
unique.size,
count,
`expected ${count} unique CIDs, got ${unique.size}: ${cids.join(", ")}`
);
});