qwen-code/scripts/tests/sdk-java-workflow.test.js
易良 38fae41ddc
fix(release): stop one flaky test from failing a stable release (#10842)
* 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>
2026-09-03 03:39:37 +00:00

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"');
},
);
});