test(web-shell): include transcript coverage in recovery fixture (#15883)

* test(web-shell): include transcript coverage in recovery fixture

Co-authored-by: Qwen-Coder <[email protected]>

* test(web-shell): complete recovery event fixtures

Remove the unevaluated transcript type guard and supply required event terminal flags without changing assertions.

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): use runner temp for Web Shell browser smoke (#15884)

* fix(ci): use runner temp for Web Shell browser smoke

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): resolve runner temp in the browser step context

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): partition the complete macOS test suite into bounded jobs (#15888)

* fix(ci): use runner temp for Web Shell browser smoke

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): resolve runner temp in the browser step context

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): partition the complete macOS test suite into bounded jobs

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): account for the macOS aggregate job budget

Record the additional five-minute aggregate without changing any test execution timeout.

Co-authored-by: Qwen-Coder <[email protected]>

* test(install): isolate Windows fixtures from persistent PATH (#15889)

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): write the macOS shard matrix in block style and pin its gates

The seven-row matrix used YAML flow mappings, which `.yamllint.yml`
forbids (`braces: forbid: 'non-empty'`), so the `Run yamllint` step
cannot go green on this branch. Rewrite the rows in the block style
every other workflow matrix in this repo already uses.

Add the test pins the shard plumbing was missing:

- the `cli`/`core` dispatch argv, including the bare `--` that keeps
  `--shard` forwarded to vitest instead of consumed by npm, and the
  per-group `NODE_OPTIONS` choice
- `id: 'install'` on `test_macos_shards`, which the suite's
  `steps.install.outcome` gate silently reads as '' if it is renamed
- the aggregate `test_macos` gate against the shards' gate, so the
  preserved check name cannot report `skipped` while the legs fail

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvu6810z1y

* fix(ci): preserve shell fixture gates on macOS system Bash

Handle empty issue families under nounset without mapfile and explicitly reject either missing OSS credential. Keep failure assertions and include shell diagnostics.

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): keep macOS aggregate failures eligible for infra retry

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: Qwen-Coder <[email protected]>
This commit is contained in:
易良andQwen-Coder authored and GitHub committed 2026-10-30 18:24:19 +08:00
1 parent 54fd8e0372
commit 84aee5b9b7
16 files changed
+464 -94

No files matched your search

+3 -3
View File
@@ -88,13 +88,13 @@ describe('ci.yml disk-pressure evidence', () => {
);
assert.match(tests, /hosttests\[\$\{hosttests:-unknown\}\]/);
// test_macos and test_windows inline the same sampler (plain echo, no
// test_macos_shards and test_windows inline the same sampler (plain echo, no
// DISK_SAMPLES append), and no equality pin reaches them: the cross-leg
// byte-identity in no-ak-integration-ci.test.js stops at the `( while
// true` sentinel by design. Pin the sampler expression per leg, or an
// edit that only touches the pinned test-job copy ships green while
// these nightly-only lanes silently drop `load`/`hosttests`.
for (const jobName of ['test_macos', 'test_windows']) {
for (const jobName of ['test_macos_shards', 'test_windows']) {
const leg = ciJobs[jobName].steps.find(
(candidate) => candidate.name === 'Run tests and generate reports',
);
@@ -257,7 +257,7 @@ describe('ci.yml disk-pressure evidence', () => {
// reaches them ('Dump disk state on failure' is this lane's alone). Pin
// them textually like the sampler loop above — not executed, because
// test_windows greps /proc/meminfo, which Git Bash lacks.
for (const jobName of ['test', 'test_macos', 'test_windows']) {
for (const jobName of ['test', 'test_macos_shards', 'test_windows']) {
const leg = ciJobs[jobName].steps.find(
(candidate) => candidate.name === 'Run tests and generate reports',
);
+1 -1
View File
@@ -1,7 +1,7 @@
# PR-stage limits: jobs, declared timeout-minutes, jobs without fixed timeout, file.
# Update the corresponding entry when intentionally increasing a budget.
1 5 0 assign-pr-owner.yml
8 300 4 ci.yml
9 305 4 ci.yml
4 90 1 live-host-release.yml
1 30 0 live-host.yml
3 15 1 managed-agent-gate.yml
+54 -9
View File
@@ -1778,9 +1778,15 @@ jobs:
echo "Using web-shell Playwright port ${port}"
- name: 'Run transcript document browser gate'
env:
# Avoid the pool's shared /tmp tmpfs for Chromium temporary files.
TMPDIR: '${{ runner.temp }}'
run: 'npx vitest run --root ./integration-tests ./chat-transcript-document.test.ts --retry=0'
- name: 'Run web-shell browser smoke'
env:
# Avoid the pool's shared /tmp tmpfs for Chromium temporary files.
TMPDIR: '${{ runner.temp }}'
# Two workers is the hosted default (half of 4 vCPUs). Unpinned, a pool
# job would start browsers for half the host's cores while the host
# runs its other jobs.
@@ -1837,14 +1843,32 @@ jobs:
# is most of the traffic here and most of what the macOS queue was spent
# on.
#
# Two named jobs, not a matrix: a skipped matrix job reports one collapsed
# check name, never the per-OS contexts, so a required-check configuration
# would sit "Expected" forever. A skipped named job reports under its exact
# name. (No status check is required on `main` today — the ruleset carries
# only deletion, non-fast-forward and pull_request rules — so this shape is
# currently insurance, not a live constraint.)
# Preserve the original macOS check name while running its complete suites
# in bounded groups. The aggregate rejects failed, skipped or cancelled
# groups, including when one group fails before the others finish.
test_macos:
name: 'Test (macos-latest, Node 22.x)'
needs: 'test_macos_shards'
if: |-
${{
always() && (
github.event_name == 'merge_group' ||
github.event_name == 'schedule' ||
github.event_name == 'workflow_dispatch'
)
}}
runs-on: 'ubuntu-latest'
timeout-minutes: 5
permissions:
contents: 'read'
steps:
- name: 'Require every macOS test group to pass'
env:
MACOS_RESULT: '${{ needs.test_macos_shards.result }}'
run: 'test "$MACOS_RESULT" = success'
test_macos_shards:
name: 'macOS unit tests (${{ matrix.group }} ${{ matrix.shard }})'
needs: 'classify_pr'
if: |-
${{
@@ -1854,6 +1878,24 @@ jobs:
github.event_name == 'workflow_dispatch'
)
}}
strategy:
fail-fast: false
matrix:
include:
- group: 'cli'
shard: '1/3'
- group: 'cli'
shard: '2/3'
- group: 'cli'
shard: '3/3'
- group: 'core'
shard: '1/2'
- group: 'core'
shard: '2/2'
- group: 'rest'
shard: ''
- group: 'scripts'
shard: ''
runs-on: 'macos-latest'
timeout-minutes: 60
permissions:
@@ -1889,19 +1931,22 @@ jobs:
npm config set fetch-timeout 300000
- name: 'Install dependencies'
id: 'install'
if: "${{ needs.classify_pr.outputs.skip_ci != 'true' }}"
run: |-
corepack pnpm install --frozen-lockfile --prefer-offline --reporter=append-only
- name: 'Hosted portable process smoke'
if: "${{ needs.classify_pr.outputs.skip_ci != 'true' }}"
if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && matrix.group == 'scripts' }}"
timeout-minutes: 5
run: |-
npm run test:integration:hosted:sandbox:none -- -t 'portable startup'
- name: 'Run tests and generate reports'
if: "${{ needs.classify_pr.outputs.skip_ci != 'true' }}"
if: "${{ !cancelled() && steps.install.outcome == 'success' && needs.classify_pr.outputs.skip_ci != 'true' }}"
env:
MACOS_TEST_GROUP: '${{ matrix.group }}'
MACOS_TEST_SHARD: '${{ matrix.shard }}'
NO_COLOR: true
HOME: '${{ runner.temp }}/qwen-ci-home'
USERPROFILE: '${{ runner.temp }}/qwen-ci-home'
@@ -1930,7 +1975,7 @@ jobs:
( while true; do hosttests=$(pgrep -fc '[v]itest' 2>/dev/null || true); echo "DFSAMPLE $(date -u +%H:%M:%S 2>/dev/null) tmpdir[${TMPDIR}] load[$(cut -d' ' -f1-3 /proc/loadavg 2>/dev/null)] hosttests[${hosttests:-unknown}] space[$(df -h "${TMPDIR}" 2>/dev/null | tail -1)] inodes[$(df -i "${TMPDIR}" 2>/dev/null | tail -1)] memavail[$(awk '/MemAvailable/ {print $2, $3}' /proc/meminfo 2>/dev/null)]" 2>/dev/null; sleep 10; done ) &
SAMPLER_PID=$!
set +e
npm run test:ci
node scripts/run-macos-ci-group.js "$MACOS_TEST_GROUP" "$MACOS_TEST_SHARD"
RC=$?
set -e
pkill -TERM -P "$SAMPLER_PID" 2>/dev/null || true
+14 -11
View File
@@ -298,7 +298,10 @@ jobs:
# Both bodies read this list; the flag lets them say when it is short.
echo "jobs_complete=${jobs_complete}" >> "${GITHUB_OUTPUT}"
mapfile -t job_ids < <(
job_ids=()
while IFS= read -r job_id; do
job_ids+=("${job_id}")
done < <(
jq -r '.jobs[] | select(.conclusion == "failure") | .id' \
"${jobs_json}" 2>/dev/null
)
@@ -316,7 +319,7 @@ jobs:
# every download fails. Logs feed analyze, whose tests populate the
# comment renderer's Failing tests section; without them the summary
# degrades to job/step names. The analyzer strips ANSI before matching.
for job_id in "${job_ids[@]}"; do
for job_id in ${job_ids[@]+"${job_ids[@]}"}; do
if ! gh api "repos/${REPO}/actions/jobs/${job_id}/logs" \
--allow-escape-sequences > "${log_dir}/${job_id}.log"; then
# A missing log costs test details, not the job/step summary.
@@ -369,14 +372,14 @@ jobs:
echo "Failed job ${job_id} carries no infrastructure annotation; the run gets an issue."
fi
done < <(
# The gate workflow's reporter job is always() and fails with
# only the tier-result error, which carries no infrastructure
# signature; excluding it by its failing step keeps a lane's
# The gate reporter and macOS aggregate mirror lane failures
# without an infrastructure signature of their own; excluding
# them by their failing step keeps a lane's
# infra death classifiable. Filtering the step rather than the
# job keeps the legitimate rerun: when the reporter's own
# runner dies no step has failed, so the job still counts and
# its GitHub-generated annotation still matches.
jq -r '.jobs[] | select(.conclusion == "failure") | select(([(.steps // [])[] | select(.conclusion == "failure") | .name] | index("Report the tier result")) | not) | [(.id | tostring), ((.check_run_url // "") | split("/")[-1])] | @tsv' \
jq -r '.jobs[] | select(.conclusion == "failure") | select(([(.steps // [])[] | select(.conclusion == "failure") | .name] | map(select(. == "Report the tier result" or . == "Require every macOS test group to pass")) | length) == 0) | [(.id | tostring), ((.check_run_url // "") | split("/")[-1])] | @tsv' \
"${jobs_json}" 2>/dev/null
)
if [[ "${total}" -gt 0 && "${infra}" -eq "${total}" ]]; then
@@ -591,7 +594,7 @@ jobs:
existing_issue=''
matched_body=''
signature_unresolved=false
for family_line in "${family[@]}"; do
for family_line in ${family[@]+"${family[@]}"}; do
candidate="${family_line%%$'\t'*}"
candidate_body="${RUNNER_TEMP}/candidate-${candidate}.md"
if ! gh issue view "${candidate}" --repo "${REPO}" \
@@ -719,7 +722,7 @@ jobs:
siblings_ok=true
siblings_corpus="${RUNNER_TEMP}/sibling-bodies.md"
: > "${siblings_corpus}"
for family_line in "${family[@]}"; do
for family_line in ${family[@]+"${family[@]}"}; do
sibling="${family_line%%$'\t'*}"
[[ "${sibling}" == "${existing_issue}" ]] && continue
sibling_body="${RUNNER_TEMP}/candidate-${sibling}.md"
@@ -828,13 +831,13 @@ jobs:
# enough: a human can retitle a per-signature issue to the exact
# shape, so every candidate passes the same body screen in order.
family_ordered=()
for family_line in "${family[@]}"; do
for family_line in ${family[@]+"${family[@]}"}; do
[[ "${family_line#*$'\t'}" == exact ]] && family_ordered+=("${family_line}")
done
for family_line in "${family[@]}"; do
for family_line in ${family[@]+"${family[@]}"}; do
[[ "${family_line#*$'\t'}" == exact ]] || family_ordered+=("${family_line}")
done
for family_line in "${family_ordered[@]}"; do
for family_line in ${family_ordered[@]+"${family_ordered[@]}"}; do
candidate="${family_line%%$'\t'*}"
candidate_body="${RUNNER_TEMP}/family-${candidate}.md"
if ! gh issue view "${candidate}" --repo "${REPO}" \
@@ -212,7 +212,10 @@ jobs:
run: |-
set -euo pipefail
umask 077
[[ -n "${ALIYUN_OSS_ACCESS_KEY_ID}" && -n "${ALIYUN_OSS_ACCESS_KEY_SECRET}" ]]
if [[ -z "${ALIYUN_OSS_ACCESS_KEY_ID}" || -z "${ALIYUN_OSS_ACCESS_KEY_SECRET}" ]]; then
echo '::error::Both OSS credentials are required' >&2
exit 1
fi
ossutil config -e "${ALIYUN_OSS_ENDPOINT}" -i "${ALIYUN_OSS_ACCESS_KEY_ID}" -k "${ALIYUN_OSS_ACCESS_KEY_SECRET}" -L EN -c "${RUNNER_TEMP}/managed-ossutilconfig"
- name: 'Recheck the immutable tag and existing publication bytes'
+25
View File
@@ -0,0 +1,25 @@
# Complete macOS CI within bounded jobs
[中文版](./macos-ci-test-groups.zh-CN.md)
## Problem
The macOS Node job in run 40885875352 exceeded its 60-minute limit twice. In the second attempt, installation took about 13 minutes and tests continued making progress until cancellation. The CLI suite had not finished, so later workspace and script suites never ran. One hook also timed out; partitioning does not establish that this separate failure is fixed.
## Decision
Keep the 60-minute limit and all existing assertions. Run CLI in three Vitest shards, core in two, the other workspace suites in one group, and standalone/runtime plus root script tests in one group. Disable matrix fail-fast so a failing group does not cancel the others. Run the existing portable process smoke once in the scripts group.
The remaining workspace group obtains all declared `test:ci` scripts from npm's workspace metadata and excludes only CLI and core. Newly added workspace suites therefore enter the run automatically. Heavy groups select workspaces by path to avoid confusing the CLI with the identically named root package. No group uses test filters, `--if-present`, or `--passWithNoTests`.
## Failure handling
Keep the original `Test (macos-latest, Node 22.x)` name as an aggregate check. It succeeds only when the entire matrix succeeds; failed, skipped, or cancelled groups cannot yield a green aggregate. The scripts group executes both standalone and helper suites and retains either failure. Workspace discovery failure or an empty remaining group fails closed.
Record the additional five-minute aggregate job in the workflow budget (nine declared jobs, 305 declared timeout minutes). This budget counts job definitions, not matrix expansion; each macOS execution group still has its original 60-minute limit.
## Validation and limits
CI contract checks pin the complete shard partition, automatic inclusion of new workspace suites, failure propagation, and the aggregate result. No local tests or builds were run. The complete workflow must demonstrate that all groups finish within their existing limits; the previous hook timeout still requires investigation if it recurs. Seven installations cost more runner time, trading repeated setup for completion and independent failure reports. Existing trigger, permission, and checkout rules remain in place.
The first complete partitioned run exposed workflow fixtures that previously had not been reached on macOS. Keep their assertions and make the exercised shell paths work with the system Bash 3.2: read failed-job IDs without `mapfile`, safely iterate empty issue-family arrays under `nounset`, and reject missing OSS credentials with an explicit exit. Include shell stderr in issue-routing assertion diagnostics. These changes require remote validation; the remaining CLI and Windows failures are not established as resolved by them.
+25
View File
@@ -0,0 +1,25 @@
# 在有界任务内完成 macOS 全量 CI
[English](./macos-ci-test-groups.md)
## 问题
工作流 40885875352 的 macOS Node 任务两次超过 60 分钟限制。第二次安装约耗时 13 分钟,测试一直推进到被取消。CLI 套件尚未结束,后续 workspace 和脚本套件没有执行。另有一项 hook 超时;分组不代表这个独立问题已修复。
## 决策
保留 60 分钟限制和全部原有断言。CLI 使用三个 Vitest 分片,core 使用两个,其余 workspace 套件合为一组,standalone/runtime 与根目录脚本测试合为一组。关闭矩阵 fail-fast,避免一个分组失败取消其他组。原有 portable process smoke 在脚本组执行一次。
其余 workspace 组从 npm 的 workspace 元数据中读取全部已声明的 `test:ci`,只排除 CLI 和 core。因此新增 workspace 测试会自动纳入。大套件按路径选择 workspace,避免将 CLI 与同名根包混淆。各组均不使用测试过滤、`--if-present` 或 `--passWithNoTests`。
## 失败处理
保留原检查名 `Test (macos-latest, Node 22.x)` 作为汇总检查。只有整个矩阵成功才通过;失败、跳过或取消都不能产生绿色汇总。脚本组执行 standalone 和 helper 两套测试,并保留任一失败。workspace 发现失败或其余分组为空时直接失败。
将新增的五分钟汇总任务计入工作流预算(九个任务定义,声明的超时合计 305 分钟)。该预算统计任务定义,不展开矩阵;每个 macOS 执行组仍保留原有的 60 分钟上限。
## 验证与限制
CI 契约检查约束完整分片、自动纳入新 workspace、失败传递和汇总结果。未运行本地测试或构建。需要完整工作流证明所有分组在原有限制内结束;此前的 hook 超时若再次出现,仍须继续排查。七次安装会增加 runner 总耗时,以重复准备换取完整执行和独立失败报告。原有触发条件、权限和 checkout 规则保持不变。
首次完整分组执行暴露了此前在 macOS 尚未执行到的工作流夹具问题。保留断言,使被测 shell 路径兼容系统 Bash 3.2:读取失败任务 ID 时不依赖 `mapfile`,在 `nounset` 下安全遍历空的 issue 家族数组,并在 OSS 凭据缺失时明确退出。issue 路由状态断言同时输出 shell stderr。这些修改仍需远程验证;不能据此声称其余 CLI 和 Windows 失败已解决。
@@ -2,7 +2,6 @@ import { expect, test, type Page } from '@playwright/test';
import type {
JavaAgentEvent,
JavaAgentSession,
JavaAgentTranscript,
} from '../components/managed/java-managed-agent-client';
async function installRecovery(page: Page, running = false) {
@@ -95,7 +94,9 @@ async function installRecovery(page: Page, running = false) {
sessionId: 'source',
eventId: `event-${event.sequence}`,
createdAt: event.sequence,
terminal: event.type === 'turn.completed' || event.type === 'turn.failed',
terminal: ['turn.completed', 'turn.failed', 'turn.cancelled'].includes(
event.type,
),
}));
page.on('pageerror', (error) => errors.push(error.message));
await page.route('**/api/agent/web-shell/v1/**', async (route) => {
@@ -153,7 +154,7 @@ async function installRecovery(page: Page, running = false) {
coveredSequence: 6,
lastSequence: 6,
hasMore: false,
} satisfies JavaAgentTranscript,
},
});
if (path.endsWith('/events/stream'))
return route.fulfill({ contentType: 'text/event-stream', body: '' });
+64
View File
@@ -0,0 +1,64 @@
/**
* @license
* Copyright 2026 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
import { spawnSync } from 'node:child_process';
import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
export function runMacOsGroup(group, shard, spawn = spawnSync) {
const options = {
stdio: 'inherit',
env:
group === 'scripts'
? process.env
: { ...process.env, NODE_OPTIONS: '--max-old-space-size=3072' },
};
const run = (args) => spawn('npm', args, options).status ?? 1;
if (group === 'cli' || group === 'core') {
return run([
'run',
'test:ci',
`--workspace=packages/${group}`,
'--',
`--shard=${shard}`,
]);
}
if (group === 'scripts') {
const standalone = run(['run', 'test:standalone:runtimes:ci']);
const scripts = run(['run', 'test:scripts:unit']);
return standalone || scripts;
}
if (group !== 'rest') {
throw new Error(`Unknown macOS test group: ${group}`);
}
const metadata = spawn(
'npm',
['pkg', 'get', 'scripts.test:ci', '--workspaces', '--json'],
{ encoding: 'utf8' },
);
if (metadata.status !== 0) return metadata.status ?? 1;
const workspaces = Object.entries(JSON.parse(metadata.stdout))
.filter(
([name, script]) =>
typeof script === 'string' &&
name !== '@qwen-code/qwen-code' &&
name !== '@qwen-code/qwen-code-core',
)
.map(([name]) => `--workspace=${name}`);
if (workspaces.length === 0) {
throw new Error('No remaining workspace test suites were discovered');
}
return run(['run', 'test:ci', ...workspaces]);
}
if (
process.argv[1] &&
resolve(process.argv[1]) === fileURLToPath(import.meta.url)
) {
process.exitCode = runMacOsGroup(process.argv[2], process.argv[3]);
}
+61 -2
View File
@@ -39,10 +39,64 @@ const failureIssue = parse(
);
// `on:` parses as the boolean true in YAML 1.1.
const triggers = ci[true] ?? ci['on'];
const LANES = ['test_macos', 'test_windows'];
const LANES = ['test_macos_shards', 'test_windows'];
const PUSH_JOBS = ['classify_pr', 'test', 'lint_and_static'];
const condOf = (job) => String(ci.jobs[job].if ?? '');
describe('complete macOS test groups', () => {
it('partitions both heavy suites and retains every remaining test group', () => {
const job = ci.jobs.test_macos_shards;
expect(job.strategy['fail-fast']).toBe(false);
expect(job.strategy.matrix.include).toEqual([
{ group: 'cli', shard: '1/3' },
{ group: 'cli', shard: '2/3' },
{ group: 'cli', shard: '3/3' },
{ group: 'core', shard: '1/2' },
{ group: 'core', shard: '2/2' },
{ group: 'rest', shard: '' },
{ group: 'scripts', shard: '' },
]);
const step = job.steps.find(
(candidate) => candidate.name === 'Run tests and generate reports',
);
expect(step.env.MACOS_TEST_GROUP).toBe('${{ matrix.group }}');
expect(step.env.MACOS_TEST_SHARD).toBe('${{ matrix.shard }}');
expect(step.run).toContain(
'node scripts/run-macos-ci-group.js "$MACOS_TEST_GROUP" "$MACOS_TEST_SHARD"',
);
});
it('keeps the original check name and rejects incomplete matrix results', () => {
const aggregate = ci.jobs.test_macos;
expect(aggregate.name).toBe('Test (macos-latest, Node 22.x)');
expect(aggregate.needs).toBe('test_macos_shards');
expect(aggregate.if).toContain('always()');
const [step] = aggregate.steps;
expect(step.env.MACOS_RESULT).toBe('${{ needs.test_macos_shards.result }}');
expect(step.run).toBe('test "$MACOS_RESULT" = success');
for (const result of ['failure', 'cancelled', 'skipped', '']) {
expect(() =>
execFileSync('bash', ['-c', step.run], {
env: { ...process.env, MACOS_RESULT: result },
stdio: 'pipe',
}),
).toThrow();
}
});
it('keeps the aggregate gate on exactly the events the shards run on', () => {
// The aggregate only reads `needs.test_macos_shards.result`, so a gate
// that drifts from the shards' makes the preserved check name report
// `skipped` while the seven legs run and fail.
for (const event of ['merge_group', 'schedule', 'workflow_dispatch']) {
expect(gateRunsOn(condOf('test_macos'), event)).toBe(
gateRunsOn(condOf('test_macos_shards'), event),
);
}
expect(gateRunsOn(condOf('test_macos'), 'pull_request')).toBe(false);
});
});
// The extended ceilings below, except the test lane's, are scoped to the
// self-hosted pool: `runs-on` falls back to ubuntu-latest for fork PRs from
// untrusted authors and when MAINTAINER_ECS_RUNNER_DISABLED=true, so those
@@ -259,6 +313,11 @@ it('routes the browser smoke to ARM64 independently of the shared Linux test lan
(step) => step.name === 'Run web-shell browser smoke',
);
expect(smoke.run).toMatch(/ -- --workers=2$/);
expect(smoke.env.TMPDIR).toBe('${{ runner.temp }}');
const transcript = ci.jobs.web_shell_e2e_smoke.steps.find(
(step) => step.name === 'Run transcript document browser gate',
);
expect(transcript.env.TMPDIR).toBe('${{ runner.temp }}');
// Slow hosted browser installs took over 23 min, so reserve time in the
// 60-minute job for transcript/smoke tests and artifact upload, while the
// install step itself stays bounded.
@@ -452,7 +511,7 @@ describe('platform lanes — triggers', () => {
// therefore either exclude `schedule` outright or gate on an event
// allowlist that cannot contain it — otherwise the nightly quietly
// becomes a full CI run every day.
assertEventReachesOnly('schedule', LANES);
assertEventReachesOnly('schedule', [...LANES, 'test_macos']);
});
});
+14 -10
View File
@@ -275,10 +275,11 @@ describe('Hosted real-process gates', () => {
it.each([
{
name: 'test_macos',
name: 'test_macos_shards',
step: 'Hosted portable process smoke',
after: /Install dependencies/i,
condition: "${{ needs.classify_pr.outputs.skip_ci != 'true' }}",
condition:
"${{ needs.classify_pr.outputs.skip_ci != 'true' && matrix.group == 'scripts' }}",
run: `npm run ${focused} -- -t 'portable startup'`,
minutes: 5,
},
@@ -308,14 +309,17 @@ describe('Hosted real-process gates', () => {
},
);
it('gates the Windows Hosted suite on a successful install', () => {
const steps = ci.jobs.test_windows.steps;
// An unknown step id reads as '' and silently disables the condition
// above, so the suite would never run again.
expect(steps.find((step) => step.name === 'Install dependencies').id).toBe(
'install',
);
});
it.each(['test_windows', 'test_macos_shards'])(
'gates the %s suite on a successful install',
(name) => {
// An unknown step id reads as '' and silently disables the condition
// above, so the suite would never run again.
expect(
ci.jobs[name].steps.find((step) => step.name === 'Install dependencies')
.id,
).toBe('install');
},
);
it('keeps Hosted MySQL separate from the non-Hosted MySQL slice and fails on missing tests', () => {
const job = java.jobs['hosted-harness-mysql'];
+13 -2
View File
@@ -495,7 +495,10 @@ describe('installation scripts', () => {
expect(prepared.command).toBe(
'set "PROCESSOR_ARCHITECTURE=AMD64" && set "PROCESSOR_ARCHITEW6432=" && set "PSModulePath=C:\\PowerShell\\7\\Modules" && call "C:\\tools\\install-qwen-standalone.bat"',
);
expect(prepared.env).toEqual({ Path: 'C:\\fake-bin' });
expect(prepared.env).toEqual({
Path: 'C:\\fake-bin',
QWEN_NO_MODIFY_PATH: '1',
});
});
it('creates PowerShell validation scripts with a ps1 extension', () => {
@@ -4948,6 +4951,13 @@ describe('Windows installer end-to-end', () => {
const home = path.join(tmpDir, 'home');
runWindowsInstaller(archive, installRoot, home);
const persistentPaths = runWindowsCommand(
`powershell -NoProfile -Command "[Environment]::GetEnvironmentVariable('Path', 'User'); [Environment]::GetEnvironmentVariable('Path', 'Machine')"`,
).toString();
expect(persistentPaths.toLowerCase()).not.toContain(
installRoot.toLowerCase(),
);
expect(existsSync(path.join(installRoot, 'bin', 'qwen.cmd'))).toBe(
true,
);
@@ -6061,7 +6071,8 @@ const WINDOWS_COMMAND_ENV_OVERRIDES = [
];
function prepareWindowsCommand(command, env = {}, baseEnv = process.env) {
const commandEnv = { ...baseEnv, ...env };
// USERPROFILE does not isolate the account's persistent registry PATH.
const commandEnv = { ...baseEnv, QWEN_NO_MODIFY_PATH: '1', ...env };
const commandPrefix = [];
for (const key of WINDOWS_COMMAND_ENV_OVERRIDES) {
+93
View File
@@ -0,0 +1,93 @@
/**
* @license
* Copyright 2026 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, expect, it, vi } from 'vitest';
import { runMacOsGroup } from '../run-macos-ci-group.js';
describe('macOS CI groups', () => {
it('includes newly added workspace suites without duplicating heavy groups', () => {
const spawn = vi
.fn()
.mockReturnValueOnce({
status: 0,
stdout: JSON.stringify({
'@qwen-code/qwen-code': 'vitest run',
'@qwen-code/qwen-code-core': 'vitest run',
'@qwen-code/new-package': 'vitest run',
'@qwen-code/sdk': 'npm run typecheck && vitest run',
'@qwen-code/no-tests': {},
}),
})
.mockReturnValueOnce({ status: 7 });
expect(runMacOsGroup('rest', '', spawn)).toBe(7);
expect(spawn.mock.calls[1][1]).toEqual([
'run',
'test:ci',
'--workspace=@qwen-code/new-package',
'--workspace=@qwen-code/sdk',
]);
});
it('shards both heavy groups by workspace path and keeps their heap cap', () => {
for (const [group, shard] of [
['cli', '2/3'],
['core', '1/2'],
]) {
const spawn = vi.fn().mockReturnValue({ status: 0 });
expect(runMacOsGroup(group, shard, spawn)).toBe(0);
// Without the bare '--', npm consumes --shard itself and every leg
// re-runs the whole workspace suite — the overrun this split removes.
expect(spawn.mock.calls[0][1]).toEqual([
'run',
'test:ci',
`--workspace=packages/${group}`,
'--',
`--shard=${shard}`,
]);
expect(spawn.mock.calls[0][2].env.NODE_OPTIONS).toBe(
'--max-old-space-size=3072',
);
}
});
it('leaves the scripts group on the ambient environment', () => {
const spawn = vi.fn().mockReturnValue({ status: 0 });
expect(runMacOsGroup('scripts', '', spawn)).toBe(0);
expect(spawn.mock.calls[0][2].env).toBe(process.env);
});
it('runs helper tests after standalone failure and preserves either failure', () => {
for (const [standalone, scripts] of [
[3, 0],
[0, 5],
[3, 5],
]) {
const spawn = vi
.fn()
.mockReturnValueOnce({ status: standalone })
.mockReturnValueOnce({ status: scripts });
expect(runMacOsGroup('scripts', '', spawn)).not.toBe(0);
expect(spawn.mock.calls.map((call) => call[1])).toEqual([
['run', 'test:standalone:runtimes:ci'],
['run', 'test:scripts:unit'],
]);
}
});
it('fails closed when workspace discovery fails or yields no suites', () => {
expect(
runMacOsGroup('rest', '', vi.fn().mockReturnValue({ status: null })),
).toBe(1);
expect(() =>
runMacOsGroup(
'rest',
'',
vi.fn().mockReturnValue({ status: 0, stdout: '{}' }),
),
).toThrow('No remaining workspace');
});
});
@@ -975,6 +975,7 @@ fi
return {
status: result.status,
stdout: result.stdout,
stderr: result.stderr,
migratedBody: existsSync(join(tmp, 'migrated-body.md'))
? readFileSync(join(tmp, 'migrated-body.md'), 'utf8')
: null,
@@ -1000,7 +1001,7 @@ fi
'comments on an open workflow issue with suffix %s',
(suffix) => {
const result = run([botIssue(42, suffix)]);
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toContain('issue comment 42');
expect(result.output).toContain(
'issue_url=https://github.com/QwenLM/qwen-code/issues/42',
@@ -1026,7 +1027,7 @@ fi
// aggregate's own body file is a third write site: a prepend narrowed to
// the per-signature branch would silently drop it here.
const result = run([botIssue(42)], { provisional: true });
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toContain('issue comment 42');
expect(result.body).toMatch(
/^> \*\*Provisional\.\*\* Main advanced past this head/,
@@ -1039,7 +1040,7 @@ fi
issueBody:
'Main CI failed at commit abc123.\n\n<!-- qwen-main-ci-failure:abc123 -->',
});
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toContain('issue comment 42');
expect(result.calls).toContain(
'issue edit 42 --repo QwenLM/qwen-code --remove-label status/ready-for-agent --remove-label autofix/approved',
@@ -1059,7 +1060,7 @@ fi
7: 'Tracked failure.\n\n<!-- qwen-main-ci-failure-test:aaa111bbb222 -->\n',
},
});
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toContain('issue create');
expect(result.calls).not.toContain('issue comment 7');
expect(result.calls).not.toContain('issue edit 7');
@@ -1074,7 +1075,7 @@ fi
{ ...botIssue(42), body: '<!-- main-ci-failure-aggregate:1234 -->' },
],
});
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toContain('issue reopen 42');
expect(result.calls).toContain('issue comment 42');
expect(result.calls).not.toContain('issue comment 7');
@@ -1087,7 +1088,7 @@ fi
const result = run([botIssue(42, ' — Foo.test')], {
issueBodies: { 42: both },
});
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toContain('issue comment 42');
expect(result.calls).toContain(
'issue edit 42 --repo QwenLM/qwen-code --title Main CI failed: SDK Java — no test identified (latest: 2026-10-24)',
@@ -1104,7 +1105,7 @@ fi
},
],
});
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).not.toContain('issue reopen 7');
expect(result.calls).toContain('issue create');
});
@@ -1123,7 +1124,7 @@ fi
{ ...botIssue(42), body: '<!-- main-ci-failure-aggregate:1234 -->' },
],
});
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toContain('issue reopen 42');
expect(result.calls).toContain('issue comment 42');
expect(result.calls).not.toContain('issue create');
@@ -1139,7 +1140,7 @@ fi
const result = run([], {
closedIssues: [{ ...botIssue(42, ' — Foo.test'), body: both }],
});
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toContain('issue reopen 42');
expect(result.calls).toContain('issue comment 42');
expect(result.calls).not.toContain('issue create');
@@ -1163,7 +1164,7 @@ fi
{ ...botIssue(42, ' on abc123'), body: 'no markers here' },
],
});
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toContain('issue reopen 42');
expect(result.calls).toContain('issue comment 42');
expect(result.calls).not.toContain('--remove-label');
@@ -1189,7 +1190,7 @@ fi
],
},
);
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toContain('issue comment 42');
expect(result.calls).not.toContain('issue comment 9');
expect(result.calls).not.toMatch(/issue (edit|reopen) 9/);
@@ -1203,7 +1204,7 @@ fi
botIssue(10, ' extra'),
{ ...botIssue(11), title: 'Main CI failed: SDK Python on abc123' },
]);
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toContain(
'issue create --repo QwenLM/qwen-code --title Main CI failed: SDK Java --body-file',
);
@@ -1223,7 +1224,7 @@ fi
const result = run([
{ ...botIssue(7), author: { login: 'not-the-bot' } },
]);
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toContain('issue create');
expect(result.calls).not.toContain('issue comment 7');
});
@@ -1243,7 +1244,7 @@ fi
marked,
],
});
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toContain('issue comment 42');
expect(result.calls).not.toMatch(/issue (create|edit|reopen)/);
});
@@ -1309,7 +1310,7 @@ fi
issueBody: 'Original unmarked body',
...failure,
});
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toContain('issue comment 42');
expect(result.output).toContain(
'issue_url=https://github.com/QwenLM/qwen-code/issues/42',
@@ -1339,7 +1340,7 @@ fi
{ ...botIssue(42), body: '<!-- main-ci-failure-aggregate:1234 -->' },
],
});
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toContain('issue reopen 42');
expect(result.calls).toContain('issue comment 42');
expect(result.calls).not.toContain('issue comment 9');
@@ -1348,7 +1349,7 @@ fi
it('retries a transient light lookup failure and still records the failure', () => {
const result = run([botIssue(42)], { lookupFailures: 1 });
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls.match(/issue list .*--state open/g)).toHaveLength(2);
expect(result.calls).toContain('issue comment 42');
});
@@ -1357,7 +1358,7 @@ fi
// Every lookup retries 3x; the comment carrying the occurrence gets the
// same tolerance, or a transient 5xx permanently loses the record.
const result = run([botIssue(42)], { failCommentOnce: true });
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls.match(/issue comment 42/g)).toHaveLength(2);
expect(result.output).toContain(
'issue_url=https://github.com/QwenLM/qwen-code/issues/42',
@@ -1373,14 +1374,14 @@ fi
issueBodies: { 7: tracked },
closedIssues: [{ ...botIssue(7, ' — Foo.test'), body: tracked }],
});
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls.match(/issue reopen 7/g)).toHaveLength(2);
expect(result.calls).toContain('issue comment 7');
});
it('retries a transient create failure for a fresh aggregate', () => {
const result = run([], { failCreateOnce: true });
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls.match(/issue create/g)).toHaveLength(2);
expect(result.output).toContain(
'issue_url=https://github.com/QwenLM/qwen-code/issues/999',
@@ -1413,7 +1414,7 @@ fi
it('keeps a newly created failure record when adding triage labels fails', () => {
const result = run([], { failEdit: true });
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.output).toContain(
'issue_url=https://github.com/QwenLM/qwen-code/issues/999',
);
@@ -1431,7 +1432,7 @@ fi
JSON.stringify([{ ...marked, number: 50 }]) +
JSON.stringify([{ ...marked, number: 42 }]),
});
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toContain('issue comment 42');
expect(result.calls).not.toContain('issue create');
});
@@ -1446,7 +1447,7 @@ fi
botIssue(42, suffix),
],
});
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toContain('issue reopen 42');
expect(result.calls).toContain('issue comment 42');
expect(result.calls.indexOf('issue reopen 42')).toBeLessThan(
@@ -1459,7 +1460,7 @@ fi
it('creates a fresh aggregate when the bounded closed lookup fails', () => {
const result = run([], { failClosedLookup: true });
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toContain('--state closed');
expect(result.calls).toContain('issue create');
expect(result.calls).not.toContain('issue reopen');
@@ -1540,7 +1541,7 @@ fi
signatureTitle,
testMarkers: `${markerA}\n${markerB}`,
});
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toContain(
`issue create --repo QwenLM/qwen-code --title ${signatureTitle} --body-file`,
);
@@ -1570,7 +1571,7 @@ fi
testMarkers: markerA,
provisional: true,
});
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.body).toMatch(
/^> \*\*Provisional\.\*\* Main advanced past this head/,
);
@@ -1600,7 +1601,7 @@ fi
testMarkers: `${markerA}\n${markerB}`,
issueBodies: { 7: tracked },
});
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toContain('issue comment 7');
expect(result.calls).not.toContain('issue create');
expect(result.output).toContain(
@@ -1633,7 +1634,7 @@ fi
testMarkers: `${markerA}\n${markerB}`,
issueBodies: { 7: tracked },
});
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toContain('issue comment 7');
expect(result.calls).not.toContain('issue edit');
});
@@ -1649,7 +1650,7 @@ fi
issueBodies: { 7: tracked },
provisional: true,
});
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toContain('issue comment 7');
expect(result.commentBody).toMatch(
/^> \*\*Provisional\.\*\* Main advanced past this head/,
@@ -1667,7 +1668,7 @@ fi
issueBodies: { 7: `<!-- ${markerA} -->\n` },
failEdit: true,
});
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toContain('issue comment 7');
expect(result.output).toContain(
'issue_url=https://github.com/QwenLM/qwen-code/issues/7',
@@ -1693,7 +1694,7 @@ fi
},
],
});
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.stdout).toContain(
'Falling back to the workflow aggregate',
);
@@ -1710,7 +1711,7 @@ fi
issueBodies: { 7: `<!-- ${markerA} -->\n` },
},
);
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toContain('issue create');
expect(result.calls).not.toContain('issue comment 7');
});
@@ -1741,7 +1742,7 @@ fi
issueBodies: { 9: retitled.body },
},
);
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toContain('issue comment 9');
expect(result.calls).not.toContain('issue create');
expect(result.calls).not.toContain('issue edit');
@@ -1756,7 +1757,7 @@ fi
testMarkers: markerA,
failMarkerLookup: true,
});
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.stdout).toContain(
'Falling back to the workflow aggregate',
);
@@ -1779,7 +1780,7 @@ fi
testMarkers: markerA,
markerIssues: [foreign],
});
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toContain('issue create');
expect(result.calls).not.toContain('issue comment 9');
});
@@ -1794,7 +1795,7 @@ fi
issueBodies: { 7: tracked },
closedIssues: [{ ...botIssue(7, ' — Foo.test'), body: tracked }],
});
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls.indexOf('issue reopen 7')).toBeLessThan(
result.calls.indexOf('issue comment 7'),
);
@@ -1822,7 +1823,7 @@ fi
},
],
});
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).not.toContain('issue reopen 7');
expect(result.calls).toContain('issue create');
});
@@ -1833,7 +1834,7 @@ fi
testMarkers: markerA,
failClosedLookup: true,
});
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toContain('issue comment 42');
expect(result.calls).not.toContain('issue create');
});
@@ -1852,7 +1853,7 @@ fi
issueBodies: { 20: owned(markerA), 21: owned(markerB) },
},
);
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toContain('issue comment 20');
expect(result.calls).not.toContain('issue edit');
});
@@ -1874,7 +1875,7 @@ fi
},
markerIssues: [retitled],
});
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toContain('issue comment 20');
expect(result.calls).not.toContain('issue edit');
});
@@ -1894,7 +1895,7 @@ fi
},
},
);
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toContain('issue comment 20');
expect(result.calls).not.toContain('issue edit');
expect(result.stdout).toContain(
@@ -1965,13 +1966,15 @@ fi
).toBe(1);
// The ids projection is bound to the array the download loop iterates and to
// the payload the fetch wrote. Swapping it with the TSV projection below
// hands `mapfile` whole TSV lines so every log download 404s; renaming the
// array on one side only loses the failing-test details while the job/step
// hands the reader whole TSV lines so every log download 404s. Renaming
// the array on one side loses failing-test details while the job/step
// summary still renders.
expect(download).toContain(
'mapfile -t job_ids < <( jq -r \'.jobs[] | select(.conclusion == "failure") | .id\' "${jobs_json}" 2>/dev/null )',
'job_ids=() while IFS= read -r job_id; do job_ids+=("${job_id}") done < <( jq -r \'.jobs[] | select(.conclusion == "failure") | .id\' "${jobs_json}" 2>/dev/null )',
);
expect(download).toContain(
'for job_id in ${job_ids[@]+"${job_ids[@]}"}; do',
);
expect(download).toContain('for job_id in "${job_ids[@]}"; do');
// The same bindings for the projection this PR adds, plus the `|| true` that
// keeps an errored jobs response from aborting the step under `bash -e`
// before a failure summary is rendered, and the `2>/dev/null` that keeps the
@@ -2229,7 +2232,7 @@ printf '%s\\n' '{"jobs":[]}'
// The annotation read is per check-run id taken from the jobs payload's
// own check_run_url, and the match is the same grep the PR scan uses.
expect(infraScript).toContain(
'jq -r \'.jobs[] | select(.conclusion == "failure") | select(([(.steps // [])[] | select(.conclusion == "failure") | .name] | index("Report the tier result")) | not) | [(.id | tostring), ((.check_run_url // "") | split("/")[-1])] | @tsv\'',
'jq -r \'.jobs[] | select(.conclusion == "failure") | select(([(.steps // [])[] | select(.conclusion == "failure") | .name] | map(select(. == "Report the tier result" or . == "Require every macOS test group to pass")) | length) == 0) | [(.id | tostring), ((.check_run_url // "") | split("/")[-1])] | @tsv\'',
);
expect(infraScript).toContain(
'gh api --paginate "repos/${REPO}/check-runs/${check_run_id}/annotations"',
@@ -2358,6 +2361,30 @@ printf '%s\\n' '{"jobs":[]}'
expect(out).toContain('infra_only=true');
});
it.each([
['No space left on device', 'true'],
['AssertionError: expected the daemon to answer', 'false'],
])(
'classifies the shard behind a failed macOS aggregate: %s',
(message, expected) => {
const out = classify(
[
failedJob(1001, 9001, [
{ name: 'Run tests and generate reports', conclusion: 'failure' },
]),
failedJob(1002, 9002, [
{
name: 'Require every macOS test group to pass',
conclusion: 'failure',
},
]),
],
{ 9001: message, 9002: 'Process completed with exit code 1.' },
);
expect(out).toContain(`infra_only=${expected}`);
},
);
it('keeps counting the reporter when its own runner died before any step', () => {
// Filtering by the failing STEP name is what keeps this rerun: no step
// failed, so the job still counts and its infra annotation matches.
+11 -1
View File
@@ -697,7 +697,17 @@ describe.skipIf(process.platform === 'win32')('release gate execution', () => {
ALIYUN_OSS_ACCESS_KEY_SECRET: '',
ALIYUN_OSS_ENDPOINT: 'https://example.invalid',
};
expect(run(configure, env).status).not.toBe(0);
for (const missing of [
'ALIYUN_OSS_ACCESS_KEY_ID',
'ALIYUN_OSS_ACCESS_KEY_SECRET',
]) {
const result = run(configure, {
...env,
ALIYUN_OSS_ACCESS_KEY_SECRET: 'fixture-secret',
[missing]: '',
});
expect(result.status, result.stderr).not.toBe(0);
}
expect(fs.existsSync(path.join(temporary, 'oss-called'))).toBe(false);
expect(
run(configure, { ...env, ALIYUN_OSS_ACCESS_KEY_SECRET: 'fixture-secret' })
+5 -5
View File
@@ -39,7 +39,7 @@ describe('no-AK integration CI wiring', () => {
path.join(ROOT, '.github/workflows/ci.yml'),
'utf8',
);
const routingBlocks = ['test', 'test_macos', 'test_windows'].map(
const routingBlocks = ['test', 'test_macos_shards', 'test_windows'].map(
(jobName) => {
const testStep = getWorkflowStep(
getWorkflowJob(workflow, jobName),
@@ -105,7 +105,7 @@ describe('no-AK integration CI wiring', () => {
'utf8',
);
for (const jobName of ['test', 'test_macos']) {
for (const jobName of ['test', 'test_macos_shards']) {
const testStep = getWorkflowStep(
getWorkflowJob(workflow, jobName),
'Run tests and generate reports',
@@ -119,7 +119,7 @@ describe('no-AK integration CI wiring', () => {
// The Linux job runs the suites through the lane script.
jobName === 'test'
? '.github/scripts/ci/run-unit-test-lanes.sh'
: 'npm run test:ci',
: 'node scripts/run-macos-ci-group.js',
'RC=$?',
'set -e',
'pkill -TERM -P "$SAMPLER_PID" 2>/dev/null || true',
@@ -413,7 +413,7 @@ describe('no-AK integration CI wiring', () => {
'utf8',
);
const ubuntuJob = getWorkflowJob(workflow, 'test');
const macosJob = getWorkflowJob(workflow, 'test_macos');
const macosJob = getWorkflowJob(workflow, 'test_macos_shards');
const windowsJob = getWorkflowJob(workflow, 'test_windows');
const gateJob = getWorkflowJob(workflow, 'integration_no_ak');
const classifyJob = getWorkflowJob(workflow, 'classify_pr');
@@ -426,7 +426,7 @@ describe('no-AK integration CI wiring', () => {
expect(workflow.split(`npm run ${NO_AK_SCRIPT}`).length - 1).toBe(1);
for (const [name, job] of Object.entries({
test: ubuntuJob,
test_macos: macosJob,
test_macos_shards: macosJob,
test_windows: windowsJob,
})) {
expect(job, `${name} must not run the no-AK script`).not.toContain(