mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 12:00:44 +00:00
test: speed extension and contract scenarios
This commit is contained in:
@@ -188,6 +188,7 @@ describe("active-memory plugin", () => {
|
||||
payloads: [{ text: "- lemon pepper wings\n- blue cheese" }],
|
||||
});
|
||||
__testing.resetActiveRecallCacheForTests();
|
||||
__testing.setTimeoutPartialDataGraceMsForTests(5);
|
||||
plugin.register(api as unknown as OpenClawPluginApi);
|
||||
});
|
||||
|
||||
|
||||
@@ -248,6 +248,7 @@ const toggleStoreLocks = new Map<string, AsyncLock>();
|
||||
let lastActiveRecallCacheSweepAt = 0;
|
||||
let minimumTimeoutMs = DEFAULT_MIN_TIMEOUT_MS;
|
||||
let setupGraceTimeoutMs = DEFAULT_SETUP_GRACE_TIMEOUT_MS;
|
||||
let timeoutPartialDataGraceMs = TIMEOUT_PARTIAL_DATA_GRACE_MS;
|
||||
|
||||
function createAsyncLock(): AsyncLock {
|
||||
let lock: Promise<void> = Promise.resolve();
|
||||
@@ -1906,7 +1907,7 @@ async function waitForSubagentPartialTimeoutData(
|
||||
}
|
||||
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
||||
const timeoutPromise = new Promise<undefined>((resolve) => {
|
||||
timeoutId = setTimeout(() => resolve(undefined), TIMEOUT_PARTIAL_DATA_GRACE_MS);
|
||||
timeoutId = setTimeout(() => resolve(undefined), timeoutPartialDataGraceMs);
|
||||
timeoutId.unref?.();
|
||||
});
|
||||
try {
|
||||
@@ -3009,6 +3010,7 @@ const testing = {
|
||||
lastActiveRecallCacheSweepAt = 0;
|
||||
minimumTimeoutMs = DEFAULT_MIN_TIMEOUT_MS;
|
||||
setupGraceTimeoutMs = DEFAULT_SETUP_GRACE_TIMEOUT_MS;
|
||||
timeoutPartialDataGraceMs = TIMEOUT_PARTIAL_DATA_GRACE_MS;
|
||||
},
|
||||
setMinimumTimeoutMsForTests(value: number) {
|
||||
minimumTimeoutMs = value;
|
||||
@@ -3016,6 +3018,9 @@ const testing = {
|
||||
setSetupGraceTimeoutMsForTests(value: number) {
|
||||
setupGraceTimeoutMs = Math.max(0, Math.floor(value));
|
||||
},
|
||||
setTimeoutPartialDataGraceMsForTests(value: number) {
|
||||
timeoutPartialDataGraceMs = Math.max(0, Math.floor(value));
|
||||
},
|
||||
setCachedResult,
|
||||
getCircuitBreakerEntry(key: string) {
|
||||
return timeoutCircuitBreaker.get(key);
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
convertGoogleMeetTtsAudioForBridge,
|
||||
extendGoogleMeetOutputEchoSuppression,
|
||||
isGoogleMeetLikelyAssistantEchoTranscript,
|
||||
GOOGLE_MEET_AGENT_TRANSCRIPT_DEBOUNCE_MS,
|
||||
resolveGoogleMeetRealtimeProvider,
|
||||
resolveGoogleMeetRealtimeTranscriptionProvider,
|
||||
startCommandAgentAudioBridge,
|
||||
@@ -315,6 +316,7 @@ describe("google-meet plugin", () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
vi.unstubAllGlobals();
|
||||
chromeTransportTesting.setDepsForTest(null);
|
||||
googleMeetPluginTesting.setCallGatewayFromCliForTests();
|
||||
@@ -1250,14 +1252,16 @@ describe("google-meet plugin", () => {
|
||||
introSent: true,
|
||||
},
|
||||
});
|
||||
expect(voiceCallMocks.joinMeetViaVoiceCallGateway).toHaveBeenCalledWith({
|
||||
config: expect.objectContaining({ defaultTransport: "twilio" }),
|
||||
dialInNumber: "+15551234567",
|
||||
dtmfSequence: "123456#",
|
||||
logger: expect.objectContaining({ info: expect.any(Function) }),
|
||||
message: "Say exactly: I'm here and listening.",
|
||||
sessionKey: expect.stringMatching(/^voice:google-meet:meet_/),
|
||||
});
|
||||
expect(voiceCallMocks.joinMeetViaVoiceCallGateway).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: expect.objectContaining({ defaultTransport: "twilio" }),
|
||||
dialInNumber: "+15551234567",
|
||||
dtmfSequence: "123456#",
|
||||
logger: expect.objectContaining({ info: expect.any(Function) }),
|
||||
message: "Say exactly: I'm here and listening.",
|
||||
sessionKey: expect.stringMatching(/^voice:google-meet:meet_/),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("passes the caller session key through tool joins for agent context forking", async () => {
|
||||
@@ -2762,6 +2766,7 @@ describe("google-meet plugin", () => {
|
||||
url: "https://meet.google.com/abc-defg-hij",
|
||||
});
|
||||
const { methods } = setup({
|
||||
realtime: { introMessage: "" },
|
||||
chrome: {
|
||||
audioBridgeCommand: ["bridge", "start"],
|
||||
waitForInCallMs: 1,
|
||||
@@ -3781,6 +3786,7 @@ describe("google-meet plugin", () => {
|
||||
const { methods, runCommandWithTimeout } = setup({
|
||||
defaultMode: "bidi",
|
||||
chrome: {
|
||||
waitForInCallMs: 1,
|
||||
audioBridgeHealthCommand: ["bridge", "status"],
|
||||
audioBridgeCommand: ["bridge", "start"],
|
||||
},
|
||||
@@ -3822,6 +3828,7 @@ describe("google-meet plugin", () => {
|
||||
});
|
||||
|
||||
it("uses realtime transcription plus regular TTS in Chrome agent mode", async () => {
|
||||
vi.useFakeTimers();
|
||||
let callbacks: Parameters<RealtimeTranscriptionProviderPlugin["createSession"]>[0] | undefined;
|
||||
const sendAudio = vi.fn();
|
||||
const sttSession = {
|
||||
@@ -3919,7 +3926,7 @@ describe("google-meet plugin", () => {
|
||||
);
|
||||
inputStdout.write(Buffer.from([1, 0, 2, 0, 3, 0, 4, 0]));
|
||||
callbacks?.onTranscript?.("Please summarize the launch.");
|
||||
await new Promise((resolve) => setTimeout(resolve, 1100));
|
||||
await vi.advanceTimersByTimeAsync(GOOGLE_MEET_AGENT_TRANSCRIPT_DEBOUNCE_MS);
|
||||
|
||||
expect(sendAudio).toHaveBeenCalledWith(expect.any(Buffer));
|
||||
expect(runtime.agent.runEmbeddedPiAgent).toHaveBeenCalled();
|
||||
@@ -4497,7 +4504,7 @@ describe("google-meet plugin", () => {
|
||||
if (pullCount === 1) {
|
||||
return { bridgeId: "bridge-1", base64: Buffer.from([9, 8, 7]).toString("base64") };
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 1_000));
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
return { bridgeId: "bridge-1" };
|
||||
}
|
||||
return { ok: true };
|
||||
@@ -4683,7 +4690,7 @@ describe("google-meet plugin", () => {
|
||||
if (pullCount === 2) {
|
||||
return { bridgeId: "bridge-1", base64: Buffer.from([5, 4, 3]).toString("base64") };
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 1_000));
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
return { bridgeId: "bridge-1" };
|
||||
}
|
||||
return { ok: true };
|
||||
|
||||
@@ -318,6 +318,14 @@ describe("matrix thread bindings", () => {
|
||||
|
||||
await vi.advanceTimersByTimeAsync(61_000);
|
||||
|
||||
await vi.waitFor(
|
||||
() => expect(sendMessageMatrixMock.mock.calls.length).toBeGreaterThanOrEqual(2),
|
||||
{
|
||||
interval: 1,
|
||||
timeout: 1_000,
|
||||
},
|
||||
);
|
||||
|
||||
await vi.waitFor(
|
||||
async () => {
|
||||
const persistedRaw = await fs.readFile(resolveBindingsFilePath(), "utf-8");
|
||||
|
||||
@@ -325,6 +325,7 @@ describe("qa-lab server", () => {
|
||||
port: 0,
|
||||
repoRoot,
|
||||
embeddedGateway: "disabled",
|
||||
selfCheckWaitTimeoutMs: 1,
|
||||
});
|
||||
cleanups.push(async () => {
|
||||
await lab.stop();
|
||||
|
||||
@@ -244,6 +244,7 @@ export async function startQaLabServer(
|
||||
transportId: "qa-channel",
|
||||
outputPath: params?.outputPath,
|
||||
repoRoot,
|
||||
waitTimeoutMs: params?.selfCheckWaitTimeoutMs,
|
||||
});
|
||||
latestScenarioRun = withQaLabRunCounts({
|
||||
kind: "self-check",
|
||||
|
||||
@@ -55,6 +55,7 @@ export type QaLabServerStartParams = {
|
||||
autoKickoffTarget?: string;
|
||||
embeddedGateway?: string;
|
||||
sendKickoffOnStart?: boolean;
|
||||
selfCheckWaitTimeoutMs?: number;
|
||||
};
|
||||
|
||||
export type QaLabServerHandle = {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { extractQaToolPayload } from "./extract-tool-payload.js";
|
||||
import type { QaScenarioDefinition } from "./scenario.js";
|
||||
|
||||
export function createQaSelfCheckScenario(): QaScenarioDefinition {
|
||||
export function createQaSelfCheckScenario(options?: {
|
||||
waitTimeoutMs?: number;
|
||||
}): QaScenarioDefinition {
|
||||
const waitTimeoutMs = options?.waitTimeoutMs ?? 5_000;
|
||||
return {
|
||||
name: "Synthetic Slack-class roundtrip",
|
||||
steps: [
|
||||
@@ -18,7 +21,7 @@ export function createQaSelfCheckScenario(): QaScenarioDefinition {
|
||||
kind: "message-text",
|
||||
textIncludes: "qa-echo: hello from qa",
|
||||
direction: "outbound",
|
||||
timeoutMs: 5_000,
|
||||
timeoutMs: waitTimeoutMs,
|
||||
});
|
||||
},
|
||||
},
|
||||
@@ -52,7 +55,7 @@ export function createQaSelfCheckScenario(): QaScenarioDefinition {
|
||||
kind: "message-text",
|
||||
textIncludes: "qa-echo: inside thread",
|
||||
direction: "outbound",
|
||||
timeoutMs: 5_000,
|
||||
timeoutMs: waitTimeoutMs,
|
||||
});
|
||||
return threadId;
|
||||
},
|
||||
|
||||
@@ -29,6 +29,7 @@ export async function runQaSelfCheckAgainstState(params: {
|
||||
outputPath?: string;
|
||||
repoRoot?: string;
|
||||
notes?: string[];
|
||||
waitTimeoutMs?: number;
|
||||
}): Promise<QaSelfCheckResult> {
|
||||
const startedAt = new Date();
|
||||
const transport = createQaTransportAdapter({
|
||||
@@ -36,16 +37,19 @@ export async function runQaSelfCheckAgainstState(params: {
|
||||
state: params.state,
|
||||
});
|
||||
params.state.reset();
|
||||
const scenarioResult = await runQaScenario(createQaSelfCheckScenario(), {
|
||||
state: params.state,
|
||||
performAction: async (action, args) =>
|
||||
await transport.handleAction({
|
||||
action,
|
||||
args,
|
||||
cfg: params.cfg,
|
||||
accountId: transport.accountId,
|
||||
}),
|
||||
});
|
||||
const scenarioResult = await runQaScenario(
|
||||
createQaSelfCheckScenario({ waitTimeoutMs: params.waitTimeoutMs }),
|
||||
{
|
||||
state: params.state,
|
||||
performAction: async (action, args) =>
|
||||
await transport.handleAction({
|
||||
action,
|
||||
args,
|
||||
cfg: params.cfg,
|
||||
accountId: transport.accountId,
|
||||
}),
|
||||
},
|
||||
);
|
||||
const checks = [
|
||||
{
|
||||
name: "QA self-check scenario",
|
||||
|
||||
@@ -105,6 +105,7 @@ export function startMatrixQaOpenClawCli(params: {
|
||||
const stderr: Buffer[] = [];
|
||||
let closed = false;
|
||||
let closeResult: MatrixQaCliRunResult | undefined;
|
||||
let timedOut = false;
|
||||
let settleWait:
|
||||
| {
|
||||
reject: (error: Error) => void;
|
||||
@@ -138,24 +139,31 @@ export function startMatrixQaOpenClawCli(params: {
|
||||
};
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
const result = buildMatrixQaCliResult({
|
||||
args: params.args,
|
||||
exitCode: 1,
|
||||
output: readOutput(),
|
||||
});
|
||||
timedOut = true;
|
||||
child.kill("SIGTERM");
|
||||
finish(
|
||||
result,
|
||||
new Error(
|
||||
[
|
||||
`${formatMatrixQaCliCommand(params.args)} timed out after ${params.timeoutMs}ms`,
|
||||
result.stderr.trim() ? `stderr:\n${redactMatrixQaCliOutput(result.stderr.trim())}` : null,
|
||||
result.stdout.trim() ? `stdout:\n${redactMatrixQaCliOutput(result.stdout.trim())}` : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n"),
|
||||
),
|
||||
);
|
||||
setTimeout(() => {
|
||||
const result = buildMatrixQaCliResult({
|
||||
args: params.args,
|
||||
exitCode: 1,
|
||||
output: readOutput(),
|
||||
});
|
||||
finish(
|
||||
result,
|
||||
new Error(
|
||||
[
|
||||
`${formatMatrixQaCliCommand(params.args)} timed out after ${params.timeoutMs}ms`,
|
||||
result.stderr.trim()
|
||||
? `stderr:\n${redactMatrixQaCliOutput(result.stderr.trim())}`
|
||||
: null,
|
||||
result.stdout.trim()
|
||||
? `stdout:\n${redactMatrixQaCliOutput(result.stdout.trim())}`
|
||||
: null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n"),
|
||||
),
|
||||
);
|
||||
}, 25);
|
||||
}, params.timeoutMs);
|
||||
|
||||
child.stdout.on("data", (chunk) => stdout.push(Buffer.from(chunk)));
|
||||
@@ -176,6 +184,9 @@ export function startMatrixQaOpenClawCli(params: {
|
||||
});
|
||||
child.on("close", (exitCode) => {
|
||||
clearTimeout(timeout);
|
||||
if (timedOut) {
|
||||
return;
|
||||
}
|
||||
const result = buildMatrixQaCliResult({
|
||||
args: params.args,
|
||||
exitCode: exitCode ?? 1,
|
||||
|
||||
Reference in New Issue
Block a user