mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-06 23:35:34 +00:00
* ci: run Windows merge queue tests on ECS
* test(channels): skip POSIX mode assertion on Windows
* ci: expose Git Bash on Windows ECS runner
* ci: scope Windows ECS tuning to self-hosted and restore full test:ci
Review feedback on the Windows ECS routing: dropping test:scripts removed the only Windows execution of 9 Windows-only install-script tests, and the job-wide PowerShell default plus narrowed test command changed the kill-switch fallback away from the known-good hosted configuration.
Restore the full npm run test:ci on both paths (bash is available: pre-installed on hosted runners, exposed via the Git Bash PATH entry on ECS) and gate every ECS-specific adjustment on runner.environment: the PowerShell setup step (now also skip_ci-guarded), TEMP/TMP/LC_ALL env writes, and the Linux-style Node setup split that fails with an actionable error naming MAINTAINER_ECS_RUNNER_DISABLED. The windows-2022 fallback is byte-for-byte the pre-ECS job again.
* test: make Windows CI suites platform-aware
* ci: add stale-checkout guard to Windows ECS test job
* test(core): compare canonical directory identity
* ci: add fork guard and review follow-ups to Windows ECS job
* test(core): exercise real directory identity change
* test(core): wait for killed lease process exit
* test(scripts): avoid cmd echo trailing spaces
* test(scripts): use unambiguous cmd echo syntax
* test(cli): avoid sidecar I/O in truncation test
* test: fix Windows script-suite gaps and unify platform gating
- Fix missed trailing-space cmd stub in package-scripts.test.js so the
'runs prepare steps in order' assertion passes on Windows.
- Add qwen-pr-review-workflow.test.js and pr-self-report-label.test.js to
the win32 exclude list (both test Linux-only workflows and are not
portable to Windows).
- Replace local itPosix/describeOnNonWindows consts with vitest's built-in
it.skipIf/it.runIf/describe.skipIf, matching the codebase idiom.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test(scripts): restore Windows workflow coverage
* test(scripts): re-exclude Windows-incompatible workflow tests on win32
Re-add pr-self-report-label.test.js and qwen-pr-review-workflow.test.js to
the win32 exclude list. Both fail on a Windows runner for reasons the code
still carries: qwen-pr-review-workflow.test.js calls execFileSync('mkdir'),
which has no executable to resolve there, and pr-self-report-label.test.js
joins PATH with ':', corrupting the ';'-separated Windows PATH so its gh
stub never resolves. Excluding them restores a green Windows gate; Linux CI
remains their authoritative coverage. Document the criterion inline.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* ci: extract checkout-head guard into composite action, pin Windows gate (#8386)
Address review round 2: move the stale-checkout guard shared by the four CI gates into .github/actions/verify-checkout-head so the copies cannot drift, pin the Windows gate kill-switch routing and guard wiring in the script tests, re-enable lint.test.js on Windows via separator normalization and a lazy linter setup in scripts/lint.js, unify the platform skips on it.skipIf(process.platform === 'win32'), and document the queued-run behavior of the ECS kill switch.
* ci: fail fast in Windows gate environment setup (#8386)
* ci: dedupe self-hosted runner steps into actions, pin gate mutations (#8386)
* fix(ci): checkout before repository-local actions in Windows gates (#8386)
* fix(ci): configure Windows runner before bash guard
* test(ci): pin remaining shared-action wiring in script tests (#8386)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(ci): skip zip-dependent packaging tests when zip is missing (#8386)
* fix(ci): validate full Windows smoke path
* fix(ci): match Windows smoke shell to gate and drop dead runs-on guard (#8386)
* fix(ci): make SIGTERM escalation test Windows-aware and tighten pins (#8386)
The CDP acceptance test asserted a POSIX-only SIGKILL escalation, which
fails deterministically on Windows where kill('SIGTERM') terminates the
child directly — blocking the Windows merge-queue gate. Assert the
platform-appropriate signal instead.
Also address review suggestions: probe `unzip` alongside `zip`, pin the
integration_cli guard's missing step-level `if:`, stop getWorkflowStep
at unnamed steps, pin install-script.test.js out of the win32 excludes,
add the stale-checkout guard to windows-runner-smoke.yml, pin the
Node preflight warning branch and the guard reject path contiguously,
and extend the smoke shell-parity loop to the npm cache step.
* docs(ci): clarify Windows runner trust boundary
---------
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci@service.alibaba.com>
551 lines
18 KiB
JavaScript
551 lines
18 KiB
JavaScript
/**
|
|
* @license
|
|
* Copyright 2026 Qwen Team
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import {
|
|
chmodSync,
|
|
mkdtempSync,
|
|
readFileSync,
|
|
rmSync,
|
|
writeFileSync,
|
|
} from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import path from 'node:path';
|
|
import { spawnSync } from 'node:child_process';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { describe, expect, it } from 'vitest';
|
|
|
|
import { getWorkflowJob, getWorkflowStep } from './workflow-helpers.js';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const root = path.resolve(__dirname, '../..');
|
|
|
|
function readPackageJson() {
|
|
return JSON.parse(readFileSync(path.join(root, 'package.json'), 'utf8'));
|
|
}
|
|
|
|
function readWorkflow(relativePath) {
|
|
return readFileSync(path.join(root, relativePath), 'utf8');
|
|
}
|
|
|
|
describe('package scripts', () => {
|
|
it('keeps the serve fast-path bundle check outside unit test scripts', () => {
|
|
const packageJson = readPackageJson();
|
|
|
|
expect(packageJson.scripts['test:ci']).not.toContain(
|
|
'npm run check:serve-fast-path-bundle',
|
|
);
|
|
expect(packageJson.scripts.preflight).toContain(
|
|
'npm run check:serve-fast-path-bundle',
|
|
);
|
|
});
|
|
|
|
it('cleans package build artifacts before checking the serve fast path bundle', () => {
|
|
const packageJson = readPackageJson();
|
|
|
|
expect(packageJson.scripts['check:serve-fast-path-bundle']).toBe(
|
|
[
|
|
'node scripts/clean-package-build-artifacts.js',
|
|
'&& npm run build -- --cli-only',
|
|
'&& cross-env DEV=true npm run bundle',
|
|
'&& node scripts/check-serve-fast-path-bundle.js',
|
|
].join(' '),
|
|
);
|
|
expect(packageJson.scripts['check:serve-fast-path-bundle']).not.toContain(
|
|
'npm run clean',
|
|
);
|
|
});
|
|
|
|
it('defines a release test script that disables workspace coverage', () => {
|
|
const packageJson = readPackageJson();
|
|
|
|
expect(packageJson.scripts['test:release']).toBe(
|
|
[
|
|
'cross-env NODE_OPTIONS="--max-old-space-size=3072"',
|
|
'npm run test:ci --workspaces --if-present --parallel -- --coverage.enabled=false',
|
|
'&& npm run test:scripts',
|
|
].join(' '),
|
|
);
|
|
|
|
const vscodePackageJson = JSON.parse(
|
|
readFileSync(
|
|
path.join(root, 'packages/vscode-ide-companion/package.json'),
|
|
'utf8',
|
|
),
|
|
);
|
|
expect(vscodePackageJson.scripts['test:ci']).toContain('--coverage');
|
|
});
|
|
|
|
it('skips build/bundle/husky but still generates git-commit info when CI builds explicitly', () => {
|
|
const packageJson = readPackageJson();
|
|
|
|
expect(packageJson.scripts.prepare).toBe('node scripts/prepare.js');
|
|
|
|
const binDir = mkdtempSync(path.join(tmpdir(), 'qwen-prepare-skip-'));
|
|
const logFile = path.join(binDir, 'commands.log');
|
|
writeFileSync(logFile, '');
|
|
|
|
try {
|
|
if (process.platform === 'win32') {
|
|
writeFileSync(
|
|
path.join(binDir, 'husky.cmd'),
|
|
'@echo(husky>>"%PREPARE_LOG_FILE%"\r\n',
|
|
);
|
|
writeFileSync(
|
|
path.join(binDir, 'npm.cmd'),
|
|
'@echo(npm %*>>"%PREPARE_LOG_FILE%"\r\n',
|
|
);
|
|
} else {
|
|
writeFileSync(
|
|
path.join(binDir, 'husky'),
|
|
'#!/bin/sh\necho husky >> "$PREPARE_LOG_FILE"\n',
|
|
);
|
|
writeFileSync(
|
|
path.join(binDir, 'npm'),
|
|
'#!/bin/sh\necho "npm $*" >> "$PREPARE_LOG_FILE"\n',
|
|
);
|
|
chmodSync(path.join(binDir, 'husky'), 0o755);
|
|
chmodSync(path.join(binDir, 'npm'), 0o755);
|
|
}
|
|
|
|
const result = spawnSync(
|
|
process.execPath,
|
|
[path.join(root, 'scripts/prepare.js')],
|
|
{
|
|
cwd: root,
|
|
encoding: 'utf8',
|
|
env: {
|
|
...process.env,
|
|
PATH: `${binDir}${path.delimiter}${process.env.PATH ?? ''}`,
|
|
PREPARE_LOG_FILE: logFile,
|
|
QWEN_SKIP_PREPARE: '1',
|
|
},
|
|
},
|
|
);
|
|
|
|
expect(result.status).toBe(0);
|
|
expect(result.stdout).toContain('Skipping prepare');
|
|
// git-commit info is still generated so a later per-workspace build or
|
|
// typecheck (e.g. the review tooling's) doesn't fail on the missing
|
|
// module; the heavy build/bundle/husky are skipped.
|
|
expect(readFileSync(logFile, 'utf8').trim().split(/\r?\n/)).toEqual([
|
|
'npm run generate',
|
|
]);
|
|
} finally {
|
|
rmSync(binDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it('runs prepare steps in order when CI does not skip prepare', () => {
|
|
const binDir = mkdtempSync(path.join(tmpdir(), 'qwen-prepare-bin-'));
|
|
const logFile = path.join(binDir, 'commands.log');
|
|
|
|
try {
|
|
if (process.platform === 'win32') {
|
|
writeFileSync(
|
|
path.join(binDir, 'husky.cmd'),
|
|
'@echo(husky>>"%PREPARE_LOG_FILE%"\r\n',
|
|
);
|
|
writeFileSync(
|
|
path.join(binDir, 'npm.cmd'),
|
|
'@echo(npm %*>>"%PREPARE_LOG_FILE%"\r\n',
|
|
);
|
|
} else {
|
|
writeFileSync(
|
|
path.join(binDir, 'husky'),
|
|
'#!/bin/sh\necho husky >> "$PREPARE_LOG_FILE"\n',
|
|
);
|
|
writeFileSync(
|
|
path.join(binDir, 'npm'),
|
|
'#!/bin/sh\necho "npm $*" >> "$PREPARE_LOG_FILE"\n',
|
|
);
|
|
chmodSync(path.join(binDir, 'husky'), 0o755);
|
|
chmodSync(path.join(binDir, 'npm'), 0o755);
|
|
}
|
|
|
|
const result = spawnSync(
|
|
process.execPath,
|
|
[path.join(root, 'scripts/prepare.js')],
|
|
{
|
|
cwd: root,
|
|
encoding: 'utf8',
|
|
env: {
|
|
...process.env,
|
|
PATH: `${binDir}${path.delimiter}${process.env.PATH ?? ''}`,
|
|
PREPARE_LOG_FILE: logFile,
|
|
QWEN_SKIP_PREPARE: '',
|
|
},
|
|
},
|
|
);
|
|
|
|
expect(result.status).toBe(0);
|
|
expect(readFileSync(logFile, 'utf8').trim().split(/\r?\n/)).toEqual([
|
|
'husky',
|
|
'npm run build',
|
|
'npm run bundle',
|
|
]);
|
|
} finally {
|
|
rmSync(binDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it('exits when a prepare step fails', () => {
|
|
const binDir = mkdtempSync(path.join(tmpdir(), 'qwen-prepare-fail-'));
|
|
const logFile = path.join(binDir, 'commands.log');
|
|
writeFileSync(logFile, '');
|
|
|
|
try {
|
|
if (process.platform === 'win32') {
|
|
writeFileSync(path.join(binDir, 'husky.cmd'), '@exit /b 7\r\n');
|
|
writeFileSync(
|
|
path.join(binDir, 'npm.cmd'),
|
|
'@echo npm %* >> "%PREPARE_LOG_FILE%"\r\n',
|
|
);
|
|
} else {
|
|
writeFileSync(path.join(binDir, 'husky'), '#!/bin/sh\nexit 7\n');
|
|
writeFileSync(
|
|
path.join(binDir, 'npm'),
|
|
'#!/bin/sh\necho "npm $*" >> "$PREPARE_LOG_FILE"\n',
|
|
);
|
|
chmodSync(path.join(binDir, 'husky'), 0o755);
|
|
chmodSync(path.join(binDir, 'npm'), 0o755);
|
|
}
|
|
|
|
const result = spawnSync(
|
|
process.execPath,
|
|
[path.join(root, 'scripts/prepare.js')],
|
|
{
|
|
cwd: root,
|
|
encoding: 'utf8',
|
|
env: {
|
|
...process.env,
|
|
PATH: `${binDir}${path.delimiter}${process.env.PATH ?? ''}`,
|
|
PREPARE_LOG_FILE: logFile,
|
|
QWEN_SKIP_PREPARE: '',
|
|
},
|
|
},
|
|
);
|
|
|
|
expect(result.status).toBe(7);
|
|
expect(result.stderr).toContain('prepare: husky exited with status 7');
|
|
expect(readFileSync(logFile, 'utf8')).toBe('');
|
|
} finally {
|
|
rmSync(binDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it('reports the failing prepare step after earlier steps succeed', () => {
|
|
const binDir = mkdtempSync(path.join(tmpdir(), 'qwen-prepare-late-fail-'));
|
|
const logFile = path.join(binDir, 'commands.log');
|
|
writeFileSync(logFile, '');
|
|
|
|
try {
|
|
if (process.platform === 'win32') {
|
|
writeFileSync(
|
|
path.join(binDir, 'husky.cmd'),
|
|
'@echo(husky>>"%PREPARE_LOG_FILE%"\r\n',
|
|
);
|
|
writeFileSync(
|
|
path.join(binDir, 'npm.cmd'),
|
|
[
|
|
'@echo(npm %*>>"%PREPARE_LOG_FILE%"',
|
|
'@if "%1 %2"=="run build" exit /b 7',
|
|
'@exit /b 0',
|
|
'',
|
|
].join('\r\n'),
|
|
);
|
|
} else {
|
|
writeFileSync(
|
|
path.join(binDir, 'husky'),
|
|
'#!/bin/sh\necho husky >> "$PREPARE_LOG_FILE"\n',
|
|
);
|
|
writeFileSync(
|
|
path.join(binDir, 'npm'),
|
|
[
|
|
'#!/bin/sh',
|
|
'echo "npm $*" >> "$PREPARE_LOG_FILE"',
|
|
'if [ "$1 $2" = "run build" ]; then exit 7; fi',
|
|
'',
|
|
].join('\n'),
|
|
);
|
|
chmodSync(path.join(binDir, 'husky'), 0o755);
|
|
chmodSync(path.join(binDir, 'npm'), 0o755);
|
|
}
|
|
|
|
const result = spawnSync(
|
|
process.execPath,
|
|
[path.join(root, 'scripts/prepare.js')],
|
|
{
|
|
cwd: root,
|
|
encoding: 'utf8',
|
|
env: {
|
|
...process.env,
|
|
PATH: `${binDir}${path.delimiter}${process.env.PATH ?? ''}`,
|
|
PREPARE_LOG_FILE: logFile,
|
|
QWEN_SKIP_PREPARE: '',
|
|
},
|
|
},
|
|
);
|
|
|
|
expect(result.status).toBe(7);
|
|
expect(result.stderr).toContain(
|
|
'prepare: npm run build exited with status 7',
|
|
);
|
|
expect(readFileSync(logFile, 'utf8').trim().split(/\r?\n/)).toEqual([
|
|
'husky',
|
|
'npm run build',
|
|
]);
|
|
} finally {
|
|
rmSync(binDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it.skipIf(process.platform === 'win32')(
|
|
'reports when a prepare command is killed by a signal',
|
|
() => {
|
|
const binDir = mkdtempSync(path.join(tmpdir(), 'qwen-prepare-signal-'));
|
|
|
|
try {
|
|
writeFileSync(path.join(binDir, 'husky'), '#!/bin/sh\nkill -TERM $$\n');
|
|
writeFileSync(path.join(binDir, 'npm'), '#!/bin/sh\nexit 0\n');
|
|
chmodSync(path.join(binDir, 'husky'), 0o755);
|
|
chmodSync(path.join(binDir, 'npm'), 0o755);
|
|
|
|
const result = spawnSync(
|
|
process.execPath,
|
|
[path.join(root, 'scripts/prepare.js')],
|
|
{
|
|
cwd: root,
|
|
encoding: 'utf8',
|
|
env: {
|
|
...process.env,
|
|
PATH: `${binDir}${path.delimiter}${process.env.PATH ?? ''}`,
|
|
QWEN_SKIP_PREPARE: '',
|
|
},
|
|
},
|
|
);
|
|
|
|
expect(result.status).toBe(1);
|
|
expect(result.stderr).toContain(
|
|
'prepare: husky killed by signal SIGTERM',
|
|
);
|
|
} finally {
|
|
rmSync(binDir, { recursive: true, force: true });
|
|
}
|
|
},
|
|
);
|
|
|
|
it.skipIf(process.platform === 'win32')(
|
|
'reports when a prepare command cannot be spawned',
|
|
() => {
|
|
const missingBinDir = mkdtempSync(
|
|
path.join(tmpdir(), 'qwen-prepare-missing-bin-'),
|
|
);
|
|
|
|
try {
|
|
const result = spawnSync(
|
|
process.execPath,
|
|
[path.join(root, 'scripts/prepare.js')],
|
|
{
|
|
cwd: root,
|
|
encoding: 'utf8',
|
|
env: {
|
|
...process.env,
|
|
PATH: missingBinDir,
|
|
QWEN_SKIP_PREPARE: '',
|
|
},
|
|
},
|
|
);
|
|
|
|
expect(result.status).toBe(1);
|
|
expect(result.stderr).toContain('prepare: husky failed:');
|
|
} finally {
|
|
rmSync(missingBinDir, { recursive: true, force: true });
|
|
}
|
|
},
|
|
);
|
|
|
|
it('wires release quality checks to fast explicit validation steps', () => {
|
|
const workflow = readWorkflow('.github/workflows/release.yml');
|
|
const qualityJob = getWorkflowJob(workflow, 'quality');
|
|
const buildStep = getWorkflowStep(qualityJob, 'Build Project');
|
|
const serveFastPathStep = getWorkflowStep(
|
|
qualityJob,
|
|
'Check Serve Fast Path Bundle',
|
|
);
|
|
const workspaceTestStep = getWorkflowStep(
|
|
qualityJob,
|
|
'Run Workspace Tests',
|
|
);
|
|
|
|
expect(qualityJob).toContain("name: 'Check Serve Fast Path Bundle'");
|
|
expect(qualityJob).toContain('npm run check:serve-fast-path-bundle');
|
|
expect(qualityJob.indexOf(serveFastPathStep)).toBeLessThan(
|
|
qualityJob.indexOf(buildStep),
|
|
);
|
|
expect(workspaceTestStep).toContain('npm run test:release');
|
|
expect(workspaceTestStep).not.toContain('npm run test:ci');
|
|
});
|
|
|
|
it('skips release install-time prepare and builds before publish bundling', () => {
|
|
const workflow = readWorkflow('.github/workflows/release.yml');
|
|
const installSteps =
|
|
workflow.match(
|
|
/ {6}- name: 'Install Dependencies'[\s\S]*? {10}npm ci --no-audit --progress=false/g,
|
|
) || [];
|
|
|
|
expect(installSteps.length).toBeGreaterThanOrEqual(5);
|
|
for (const installStep of installSteps) {
|
|
expect(installStep).toContain("QWEN_SKIP_PREPARE: '1'");
|
|
}
|
|
|
|
for (const jobName of ['integration_none', 'integration_docker']) {
|
|
const integrationJob = getWorkflowJob(workflow, jobName);
|
|
const buildStep = getWorkflowStep(integrationJob, 'Build Bundle');
|
|
expect(buildStep).toContain('npm run build\n npm run bundle');
|
|
}
|
|
|
|
const publishJob = getWorkflowJob(workflow, 'publish');
|
|
const gitConfigStep = getWorkflowStep(publishJob, 'Configure Git User');
|
|
const commitStep = getWorkflowStep(
|
|
publishJob,
|
|
'Commit and Conditionally Push package versions',
|
|
);
|
|
const buildStep = getWorkflowStep(
|
|
publishJob,
|
|
'Build Bundle and Prepare Package',
|
|
);
|
|
|
|
expect(gitConfigStep).toContain('git config core.hooksPath .husky');
|
|
expect(publishJob.indexOf(gitConfigStep)).toBeLessThan(
|
|
publishJob.indexOf(commitStep),
|
|
);
|
|
expect(buildStep).toContain('npm run build\n npm run bundle');
|
|
});
|
|
|
|
it('skips npm packages whose release version is already published', () => {
|
|
const workflow = readWorkflow('.github/workflows/release.yml');
|
|
const publishJob = getWorkflowJob(workflow, 'publish');
|
|
|
|
for (const stepName of [
|
|
'Publish @qwen-code/audio-capture',
|
|
'Publish @qwen-code/qwen-code',
|
|
'Publish @qwen-code/channel-base',
|
|
'Publish remaining channel packages',
|
|
]) {
|
|
const publishStep = getWorkflowStep(publishJob, stepName);
|
|
expect(publishStep).toContain(
|
|
"RELEASE_VERSION: '${{ needs.prepare.outputs.release_version }}'",
|
|
);
|
|
expect(publishStep).toContain(
|
|
'PACKAGE_NAME="$(node -p "require(\'./package.json\').name")"',
|
|
);
|
|
expect(publishStep).toContain('PUBLISH_ARGS+=(--dry-run)');
|
|
expect(publishStep).toContain(
|
|
'npm view "${PACKAGE_NAME}@${RELEASE_VERSION}" version',
|
|
);
|
|
expect(publishStep).toContain('already published; skipping');
|
|
expect(publishStep).toContain('exit 0');
|
|
expect(publishStep).toContain('npm publish "${PUBLISH_ARGS[@]}"');
|
|
}
|
|
|
|
// The channel loop must wrap each iteration in a subshell so that
|
|
// `exit 0` skips only the current channel, not the entire step.
|
|
const channelStep = getWorkflowStep(
|
|
publishJob,
|
|
'Publish remaining channel packages',
|
|
);
|
|
expect(channelStep).toContain('(\n');
|
|
expect(channelStep).toContain(')');
|
|
// A fully-skipped publish must be visible, not silently green.
|
|
expect(channelStep).toContain(
|
|
'Every channel package was already published; nothing shipped',
|
|
);
|
|
});
|
|
|
|
it('fast-tracks trusted autofix issue triggers before LLM assessment', () => {
|
|
const workflow = readWorkflow('.github/workflows/qwen-autofix.yml');
|
|
const issueJob = getWorkflowJob(workflow, 'issue-autofix');
|
|
const scanStep = getWorkflowStep(issueJob, 'Find candidate issues');
|
|
const fastTrackStep = getWorkflowStep(issueJob, 'Fast-track decision');
|
|
const assessStep = getWorkflowStep(issueJob, 'Assess candidates');
|
|
|
|
expect(issueJob.indexOf(scanStep)).toBeLessThan(
|
|
issueJob.indexOf(fastTrackStep),
|
|
);
|
|
expect(issueJob.indexOf(fastTrackStep)).toBeLessThan(
|
|
issueJob.indexOf(assessStep),
|
|
);
|
|
expect(fastTrackStep).toContain("id: 'fasttrack'");
|
|
expect(fastTrackStep).toContain('FAST_TRACK=false');
|
|
expect(fastTrackStep).toContain('FAST_TRACK=true');
|
|
expect(fastTrackStep).toContain('fast_tracked=false');
|
|
expect(fastTrackStep).toContain('-n "${FORCED_ISSUE}"');
|
|
expect(fastTrackStep).toContain(
|
|
'Fast-tracked: trusted trigger bypasses LLM assessment.',
|
|
);
|
|
expect(assessStep).toContain(
|
|
"steps.fasttrack.outputs.fast_tracked != 'true'",
|
|
);
|
|
});
|
|
|
|
it('skips autofix install-time prepare without disabling dependency scripts', () => {
|
|
const workflow = readWorkflow('.github/workflows/qwen-autofix.yml');
|
|
|
|
// review-address restores the shared build-cli bundle instead of
|
|
// compiling, so its install step is npm ci only; the other two jobs
|
|
// still build from sources. Husky hooks are re-armed after the
|
|
// prepare-skip only where git commits happen (build-cli never commits).
|
|
for (const [jobName, stepName, armsHooks] of [
|
|
['issue-autofix', 'Install dependencies and build', true],
|
|
['build-cli', 'Install dependencies and build', false],
|
|
['review-address', 'Install dependencies', true],
|
|
]) {
|
|
const job = getWorkflowJob(workflow, jobName);
|
|
const installStep = getWorkflowStep(job, stepName);
|
|
|
|
expect(installStep).toContain("QWEN_SKIP_PREPARE: '1'");
|
|
expect(installStep).toContain(
|
|
'npm ci --prefer-offline --no-audit --progress=false',
|
|
);
|
|
if (armsHooks) {
|
|
expect(installStep).toContain('git config core.hooksPath .husky');
|
|
}
|
|
expect(installStep).not.toContain('--ignore-scripts');
|
|
}
|
|
});
|
|
|
|
it('runs changed autofix tests instead of full touched-package suites', () => {
|
|
const workflow = readWorkflow('.github/workflows/qwen-autofix.yml');
|
|
const reviewVerificationRunner = readWorkflow(
|
|
'.github/scripts/run-autofix-review-verification.sh',
|
|
);
|
|
const reviewJob = getWorkflowJob(workflow, 'review-address');
|
|
|
|
for (const verificationBody of [
|
|
getWorkflowStep(
|
|
getWorkflowJob(workflow, 'issue-autofix'),
|
|
'Verification gate',
|
|
),
|
|
reviewVerificationRunner,
|
|
]) {
|
|
expect(verificationBody).toContain(
|
|
'npm run test --workspace "${p}" --if-present -- --changed origin/main --passWithNoTests',
|
|
);
|
|
expect(verificationBody).toContain(
|
|
'bash "${RUNNER_TEMP}/resolve-owning-packages.sh"',
|
|
);
|
|
expect(verificationBody).toContain('pkg.scripts?.test');
|
|
expect(verificationBody).toContain('!= *vitest*');
|
|
expect(verificationBody).not.toContain(
|
|
'npm run test --workspace "${p}" --if-present\n',
|
|
);
|
|
}
|
|
|
|
expect(getWorkflowStep(reviewJob, 'Verification gate')).toContain(
|
|
'bash "${RUNNER_TEMP}/run-autofix-review-verification.sh"',
|
|
);
|
|
});
|
|
});
|