mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-10-31 19:52:43 +00:00
* test: wait for state instead of fixed timing in three flaky tests Release run 33633468180 failed with all three workspace test shards red, each for an unrelated reason, and all three are timing assumptions that only hold on an idle machine. The message list pagination test polled a fixed budget of 32 animation frames before asserting a load had fired. A frame count does not stretch when the host is loaded and the queued effect lands late, so poll against a wall-clock deadline instead. The scroll-restore test in the same file asserted the restored offset exactly one frame after the load resolved, and now waits for the restore to land. The hook process test read the descendant's pid file the moment the hook returned, but the hook is killed by its own 300ms timeout, which can fire before the descendant has started far enough to write that file. Poll for it the way every other pid read in that file already does. The session PR refresh teardown removed its temp tree with a plain recursive delete. dispose() stops the next tick but does not await the sweep already in flight, so a sweep can still be writing under the tree while teardown walks it, and the delete fails with ENOTEMPTY when a file lands between the readdir and the rmdir. Retry so cleanup waits the writer out. Claude-Session: https://claude.ai/code/session_01AWWgJEqafyAT1Mc75T8N7h * fix(release): retry a flaky workspace test instead of failing the release A stable release ran its workspace tests with no retry at all, while nightly and preview retried twice. With ~30k tests behind that gate, any single flaky one reddened the release outright: six consecutive stable runs failed on one test each while build, typecheck, lint, scripts and both integration suites stayed green, and the same test passed on the next attempt. Retry every release schedule, behind a repository variable so the count can be retuned or switched off without a PR. A real break still fails all three attempts; what this stops is a contended runner deciding whether a release ships. Widen the timing bounds the process-tree hook tests impose on themselves. Every test in that file spawns real `node --import=tsx/esm` processes and then waits on a hard wall-clock deadline — 5s for startup, 3-4s for reaping. Process startup is not something a smarter wait can speed up, so on a shared runner those deadlines report host load rather than hook behaviour. Size them for the busiest host; a genuine hang still fails, only later, and the passing path is unchanged at 24s for the file. Assert the recall scan latency against the fastest of its samples rather than the median. Three release shards land on one machine, so a wall-clock median moves with the neighbours' load: it measured 131ms against a 50ms ceiling on a run whose every other gate passed. The fastest sample is the one least contaminated by contention and the closest available estimate of the intrinsic cost, and the shared lane gets a ceiling only a real regression can cross. The printed table now carries best, median and worst so the detail survives. Claude-Session: https://claude.ai/code/session_01AWWgJEqafyAT1Mc75T8N7h * fix(acp): keep a failing late-drain recovery from ending the process The mid-turn drain recovery is fired and forgotten with a bare `void`. Its own comment says it "bounds the wait and swallows a late rejection", but it only swallows a rejection of the drain promise: anything that throws after that race escapes as an unhandled rejection, and Node ends the process on one. The debug logger is among the things that can throw there, so a logging failure could take a session down — from a path whose entire purpose is best-effort recovery. This is also what made a release shard red while reporting no failing test. The throw lands on a later tick, so on a loaded host it arrives after the test that installed it has finished: Vitest attributes it to no test, counts it as an error, and exits non-zero with every one of the 8147 tests passed. A test retry cannot help, because nothing failed. Swallow silently rather than log, since the logger is itself one of the things that can throw here. Claude-Session: https://claude.ai/code/session_01AWWgJEqafyAT1Mc75T8N7h * fix(release): let the surviving-hook test's descendant outlive the deadline The previous commit polled for the descendant's pid file after the hook returned. That could not help the case it targeted: the hook's 300ms timeout has the supervisor SIGTERM then SIGKILL the whole process group, and the descendant is in that group, so by the time executeHook resolves the file either already exists or never will. On a host where node takes longer than 300ms to start, the poll only moved the same failure 30s later. Raise the hook deadline instead, so the descendant is alive to write its pid before the group is reaped; the poll stays as a harmless guard against filesystem visibility. Give the release retry an honest off switch. `vars.X || '2'` could not disable the retry safely: an empty variable falls back to the default, and '0' passes --retry=0, which outranks a workspace's own config-level retry — the very thing the comment said to avoid. 'off' now omits the flag, and the contract test pins it alongside the stale schedule wording it replaces. Say what the shared-lane latency bound actually checks. A 3x ceiling still read as a budget check while letting a 2.5x regression through. Make it an order-of-magnitude guard and label it as such; the budget itself is asserted off shared runners. Claude-Session: https://claude.ai/code/session_01AWWgJEqafyAT1Mc75T8N7h * fix(deps): update vulnerable production packages Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * chore(vscode-ide-companion): regenerate NOTICES.txt for bumped deps The dependency bump in this PR (fast-uri 3.1.5->3.1.7, qs 6.15.2->6.16.0, side-channel 1.1.0->1.1.1) left NOTICES.txt stale, failing the CI notices freshness gate. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-conflict/jmtkdz88hdi * test(release): align widened waits with per-test budgets and lane-aware deadlines - hook-runner.process.test.ts: raise the 'reaps an active %s hook tree on parent %s' (15s) and 'lets %s write output and finish' (10s) per-test budgets to 90s, matching every sibling — the widened 30s/15s inner waits can never complete inside the stale budgets on oversubscribed ECS runners. - recall-scan-latency.test.ts: the row tuple gained a 'best' column, so the strict bound silently asserted best-of-5; assert the median off shared runners and keep the loose best-of-5 bound on shared ECS lanes. - MessageList.dom.test.tsx: make FLUSH_DEADLINE_MS lane-aware (10s on ecs-qwen- runners, 4s elsewhere) so it stays inside vitest's 5s default testTimeout off the ECS lanes that grant 60s. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmtklu5f5dz * docs(test): correct two comments that now describe the opposite of the code The retry test's retained comment still said the stable path must omit the flag rather than zero it. That was true before this PR and is false after it: every schedule now retries by default, and only the operator sentinel omits the flag. The pinning test three lines down asserts exactly that, so the comment told the next reader the opposite of what the file checks — and a future edit justified by it would revert the fix. The process-test header claimed every test spawns `node --import=tsx/esm`. Four of eleven blocks do; four spawn plain node over `node:` builtins and three spawn nothing. The header carries the deadline-sizing rationale, so the claim invites applying a seconds-scale tsx startup model to processes that pay tens of milliseconds. Say which fixtures pay it, and that the budgets are generous rather than tight for the rest. Claude-Session: https://claude.ai/code/session_01AWWgJEqafyAT1Mc75T8N7h * test(ci): give the autofix workflow suite headroom over host contention `upserts deferred findings into a per-PR issue that survives the merge` scans the whole rendered workflow repeatedly and measures ~14s idle against the suite's 30s default. On a contended release runner it exhausted that budget in two consecutive Release runs, failing Quality Checks (Scripts) as the only red file out of 76. Claude-Session: https://claude.ai/code/session_01MCE9CXnMVrX4fUxHpQoUr8 * test(cli): pin the late drain recovery guard against unhandled rejections The `.catch(() => {})` on `#recoverLateDrain` had no committed regression test: removing it kept the drain suite green while anything thrown after the drain race — the debug logger among them — escaped as an unhandled rejection. The new test drives a real prompt through a mid-turn drain timeout, faults the module-graph logger exactly on the recovery's debug line, and asserts zero escaped rejections. Mutation-verified: deleting the guard turns it red. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(core): size the surviving-hook timeout fixture for loaded startup 'enforces a surviving hook timeout after the parent exits' configured its fixture deadline at 300ms, but the supervisor's clock starts at hook spawn, so on loaded hosts the group was killed before the hook wrote its ready/pid files; the driver's busy-wait then never broke and the test burned its 90s vitest budget instead of exercising the reap assertions. Use HOOK_GROUP_TIMEOUT_MS, the way the byte-similar group test was converted. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(ci): isolate Maven config per SDK Java job Concurrent self-hosted runner instances share HOME, so deleting or updating ~/.m2/toolchains.xml races with Maven startup in neighboring jobs. Point setup-java at the per-runner temp directory and make every Maven invocation consume those job-local settings and toolchains files. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(serve): wait for the shared session/list waiter before cutting a connection The release gate went red on this test for the first time on run 33701581941: `session/list scan alive when one connection is destroyed` failed with `-32603` instead of the empty session list, twice in a row. `PersistedSessionListCache` aborts an in-flight scan the moment its waiter count reaches zero, and a request only becomes a waiter once it reaches `lookup()`. The test posted both requests, waited for the loader, and then deleted the first connection — which proves the *first* request attached but says nothing about the second. On a loaded runner the second can still be resolving params when the DELETE lands, so the count drops to zero, the shared scan is aborted, its promise rejects, and the second request answers out of that rejection as an internal error. Watch `lookup()` for the single-flight join so the DELETE can never land before the second waiter is attached. The waiter count is bumped synchronously inside `lookup()`, so observing the `single_flight` status is exactly the "second waiter attached" edge the test needs — and it holds whichever of the two requests wins the race to start the scan. The cache's own unit test already covers the invariant deterministically ("cancels one waiter without aborting the shared load"), so this is a test-side race only; no production behavior changes. Claude-Session: https://claude.ai/code/session_012797rgiteWJxLT9TLkKq8G * test(serve): skip an interface that vanished mid-suite instead of failing on it `isOwnInterfaceAddress` re-reads `networkInterfaces()` on every call, while the suite snapshots the host's addresses once at collection. A shared CI host adds and drops veth interfaces continuously, so an address captured at collection can be gone minutes later when a case asserts on it — the function then correctly answers false and the case fails on a host that changed rather than on the lost normalisation it exists to guard. That is what reddened Workspace Tests (2/3) of release run 33676423730: `strips an RFC 6874 zone identifier before matching` and `matches an own address case-insensitively` — the two cases that run last here — failed `expected false to be true`, while the bare and bracketed cases that run before them passed on the same addresses. Intersect the collection snapshot with a fresh read per case. The fresh read comes from `node:os`, not from the function under test, so no case can confirm itself, and the vacuity guard stays: the loopback never churns, so the intersection never runs empty. Claude-Session: https://claude.ai/code/session_012797rgiteWJxLT9TLkKq8G --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: yiliang114 <jinjing.zzj@gmail.com> Co-authored-by: yiliang114 <yiliang114@users.noreply.github.com>
65 lines
2.6 KiB
JavaScript
65 lines
2.6 KiB
JavaScript
import { readFileSync } from 'node:fs';
|
|
import { describe, expect, it } from 'vitest';
|
|
|
|
const workflow = readFileSync('.github/workflows/sdk-java.yml', 'utf8');
|
|
const job = (name) => {
|
|
const start = workflow.indexOf(` ${name}:`);
|
|
const next = workflow.slice(start + 1).search(/\n {2}[a-z0-9-]+:\n/);
|
|
return workflow.slice(start, next < 0 ? undefined : start + 1 + next);
|
|
};
|
|
|
|
describe('SDK Java self-hosted workflow guards', () => {
|
|
it.each(['test', 'daemon-e2e'])('protects the %s job', (name) => {
|
|
const block = job(name);
|
|
for (const fragment of [
|
|
"github.repository == ''QwenLM/qwen-code''",
|
|
'github.event.pull_request.head.repo.full_name == github.repository',
|
|
"vars.MAINTAINER_ECS_RUNNER_DISABLED != ''true''",
|
|
// Write-access fork authors route to ECS too; the association list is
|
|
// the repo's established trusted set. Negative associations (CONTRIBUTOR,
|
|
// NONE, '') fail contains() and stay hosted.
|
|
'contains(fromJSON(\'\'["OWNER","MEMBER","COLLABORATOR"]\'\'), github.event.pull_request.author_association)',
|
|
'fromJSON(\'\'["self-hosted", "linux", "x64", "ecs-qwen"]\'\')',
|
|
"format('refs/pull/{0}/head', github.event.pull_request.number)",
|
|
"EXPECTED_SHA: '${{ github.event.pull_request.head.sha }}'",
|
|
'git merge-base --is-ancestor "${EXPECTED_SHA}" HEAD',
|
|
'exit 1',
|
|
]) {
|
|
expect(block).toContain(fragment);
|
|
}
|
|
});
|
|
|
|
it('serializes latency-sensitive tests on each physical ECS host', () => {
|
|
const block = job('test');
|
|
expect(block).toContain(
|
|
'if: "${{ runner.environment == \'self-hosted\' }}"',
|
|
);
|
|
expect(block).toContain(
|
|
'exec 9>"${HOME}/.cache/qwen-code-ci/sdk-java-tests.lock"',
|
|
);
|
|
expect(block).toContain('flock --wait 1200 9');
|
|
expect(block).toContain(
|
|
'::error::sdk-java host lock not acquired within 20 minutes',
|
|
);
|
|
expect(block).toContain(
|
|
'if: "${{ runner.environment == \'github-hosted\' }}"',
|
|
);
|
|
});
|
|
|
|
it.each(['test', 'daemon-e2e'])(
|
|
'keeps setup-java Maven files job-local in the %s job',
|
|
(name) => {
|
|
const block = job(name);
|
|
expect(block).toContain(
|
|
"settings-path: '${{ runner.temp }}/setup-java-m2'",
|
|
);
|
|
expect(
|
|
block.match(
|
|
/MAVEN_ARGS: '--settings \$\{\{ runner\.temp \}\}\/setup-java-m2\/settings\.xml --toolchains \$\{\{ runner\.temp \}\}\/setup-java-m2\/toolchains\.xml'/g,
|
|
),
|
|
).toHaveLength(name === 'test' ? 4 : 1);
|
|
expect(block).not.toContain('Drop shared Maven toolchains.xml');
|
|
expect(block).not.toContain('rm -f "${HOME}/.m2/toolchains.xml"');
|
|
},
|
|
);
|
|
});
|