fix(ones-sync): step 回写按 ONES 形态列全量 step
经实测确认 ONES 写接口形态:case result 可保持 to_do,只在跑过的 step 上 填 execute_result,未跑 step 仅列 uuid,且需列出该用例全部 step。 - OnesStepResult.execute_result 改为可选(未跑 step 只带 uuid) - buildAnchoredPayloads 支持 fullStepsByNumber:列全量 step, 全部跑完才聚合 case 结果(passed/failed),否则保持 to_do - fetchCaseSteps: 取用例全部 step uuid - sync 脚本对含 step 锚点的用例先拉全量 step 列表再回写 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
88435b0ffc
commit
fda826446a
|
|
@ -20,6 +20,7 @@ import {
|
|||
matchResults,
|
||||
buildAnchoredPayloads,
|
||||
postPayloads,
|
||||
fetchCaseSteps,
|
||||
OnesUpdatePayload,
|
||||
} from '../utils/ones-sync';
|
||||
import { execSync } from 'child_process';
|
||||
|
|
@ -88,9 +89,23 @@ function main() {
|
|||
|
||||
// 3. 匹配:锚点优先(支持 step 级),无锚点回退 LCS
|
||||
console.log(`\n[3/4] 匹配 (锚点优先 + LCS 兜底) ...`);
|
||||
const { payloads: anchored, unanchored } = buildAnchoredPayloads(planCases, testResults, executor);
|
||||
// 含 step 锚点的用例需列全量 step → 先取其完整 step 列表
|
||||
const stepNums = new Set<number>();
|
||||
for (const tr of testResults) {
|
||||
const m = /\[ONES:(\d+)#/.exec(tr.name);
|
||||
if (m) stepNums.add(parseInt(m[1], 10));
|
||||
}
|
||||
const fullStepsByNumber = new Map<number, string[]>();
|
||||
for (const num of stepNums) fullStepsByNumber.set(num, fetchCaseSteps(num));
|
||||
|
||||
const { payloads: anchored, unanchored } = buildAnchoredPayloads(
|
||||
planCases,
|
||||
testResults,
|
||||
executor,
|
||||
{ fullStepsByNumber }
|
||||
);
|
||||
const anchoredUUIDs = new Set(anchored.map(p => p.uuid));
|
||||
const stepCount = anchored.reduce((n, p) => n + p.steps.length, 0);
|
||||
const stepCount = anchored.reduce((n, p) => n + p.steps.filter(s => s.execute_result).length, 0);
|
||||
console.log(` 锚点匹配: ${anchored.length} 用例 (含 ${stepCount} 个 step 级结果)`);
|
||||
|
||||
// LCS 兜底:仅补充锚点未覆盖的用例
|
||||
|
|
|
|||
|
|
@ -14,8 +14,9 @@ export interface OnesPlanCase {
|
|||
|
||||
export interface OnesStepResult {
|
||||
uuid: string;
|
||||
// 执行结果填到 execute_result(step.result 是预期文本,属定义不可覆盖)
|
||||
execute_result: 'passed' | 'failed' | 'skipped';
|
||||
// 执行结果填到 execute_result(step.result 是预期文本,属定义不可覆盖)。
|
||||
// 未执行的 step 只列 uuid、不带 execute_result。
|
||||
execute_result?: 'passed' | 'failed' | 'skipped';
|
||||
actual_result?: string;
|
||||
}
|
||||
|
||||
|
|
@ -140,22 +141,43 @@ function lcs(a: string, b: string): number {
|
|||
return dp[m][n];
|
||||
}
|
||||
|
||||
/**
|
||||
* 取某用例的全部 step uuid(按定义顺序),用于回写时列全量 step。
|
||||
*/
|
||||
export function fetchCaseSteps(caseNumber: number): string[] {
|
||||
try {
|
||||
const out = execSync(`${ONES_CLI} testcase case search --key ${caseNumber}`, {
|
||||
encoding: 'utf-8',
|
||||
timeout: 30000,
|
||||
});
|
||||
const c = JSON.parse(out).cases?.[0];
|
||||
return (c?.steps || []).map((s: any) => s.uuid);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按测试名锚点构建回写 payload(支持 case 级与 step 级)。
|
||||
* - [ONES:<num>] → case 级结果
|
||||
* - [ONES:<num>#<stepUuid>] → step 级结果,并聚合出 case 级结果
|
||||
* - [ONES:<num>#<stepUuid>] → step 级结果
|
||||
* 无锚点的结果不在此处理(交由 matchResults LCS 兜底)。
|
||||
*
|
||||
* opts.fullStepsByNumber: 用例号 → 全部 step uuid。提供时按 ONES 要求列全量 step,
|
||||
* 只给跑过的填 execute_result,未跑的仅列 uuid;全部跑完才聚合 case 结果,否则保持 to_do。
|
||||
*/
|
||||
export function buildAnchoredPayloads(
|
||||
planCases: OnesPlanCase[],
|
||||
testResults: TestResult[],
|
||||
executor: string
|
||||
executor: string,
|
||||
opts: { fullStepsByNumber?: Map<number, string[]> } = {}
|
||||
): { payloads: OnesUpdatePayload[]; unanchored: TestResult[] } {
|
||||
const byNumber = new Map<number, OnesPlanCase>();
|
||||
for (const pc of planCases) byNumber.set(pc.caseNumber, pc);
|
||||
|
||||
const caseResult = new Map<string, SyncStatus>();
|
||||
const caseSteps = new Map<string, Map<string, OnesStepResult>>();
|
||||
const caseLevel = new Map<string, SyncStatus>(); // caseUUID -> result (add/feature)
|
||||
const runSteps = new Map<string, Map<string, OnesStepResult>>(); // caseUUID -> stepUuid -> result
|
||||
const numberByUUID = new Map<string, number>();
|
||||
const unanchored: TestResult[] = [];
|
||||
|
||||
for (const tr of testResults) {
|
||||
|
|
@ -166,23 +188,47 @@ export function buildAnchoredPayloads(
|
|||
}
|
||||
const pc = byNumber.get(parseInt(m[1], 10));
|
||||
if (!pc) continue; // 锚点用例不在本计划内
|
||||
numberByUUID.set(pc.caseUUID, pc.caseNumber);
|
||||
const status = toStatus(tr.status);
|
||||
caseResult.set(pc.caseUUID, mergeStatus(caseResult.get(pc.caseUUID), status));
|
||||
if (m[2]) {
|
||||
if (!caseSteps.has(pc.caseUUID)) caseSteps.set(pc.caseUUID, new Map());
|
||||
caseSteps.get(pc.caseUUID)!.set(m[2], {
|
||||
if (!runSteps.has(pc.caseUUID)) runSteps.set(pc.caseUUID, new Map());
|
||||
runSteps.get(pc.caseUUID)!.set(m[2], {
|
||||
uuid: m[2],
|
||||
execute_result: status,
|
||||
actual_result: (tr.detail || '').slice(0, 500),
|
||||
});
|
||||
} else {
|
||||
caseLevel.set(pc.caseUUID, mergeStatus(caseLevel.get(pc.caseUUID), status));
|
||||
}
|
||||
}
|
||||
|
||||
const payloads: OnesUpdatePayload[] = [];
|
||||
for (const [caseUUID, result] of caseResult) {
|
||||
const steps = caseSteps.has(caseUUID) ? Array.from(caseSteps.get(caseUUID)!.values()) : [];
|
||||
|
||||
// step 级用例:列全量 step(未跑的仅 uuid),全部跑完才聚合 case 结果
|
||||
for (const [caseUUID, stepMap] of runSteps) {
|
||||
const num = numberByUUID.get(caseUUID)!;
|
||||
const full = opts.fullStepsByNumber?.get(num);
|
||||
let steps: OnesStepResult[];
|
||||
let allRun: boolean;
|
||||
if (full && full.length) {
|
||||
steps = full.map((uuid) => stepMap.get(uuid) ?? { uuid });
|
||||
allRun = full.every((uuid) => stepMap.has(uuid));
|
||||
} else {
|
||||
steps = Array.from(stepMap.values());
|
||||
allRun = true; // 无全量列表时,以已有结果为准
|
||||
}
|
||||
let agg: SyncStatus | undefined;
|
||||
for (const s of stepMap.values()) if (s.execute_result) agg = mergeStatus(agg, s.execute_result);
|
||||
const result: OnesUpdatePayload['result'] = allRun ? agg ?? 'to_do' : 'to_do';
|
||||
payloads.push({ uuid: caseUUID, executor, note: '', result, steps });
|
||||
}
|
||||
|
||||
// case 级用例(添加/功能,无 step 结果)
|
||||
for (const [caseUUID, result] of caseLevel) {
|
||||
if (runSteps.has(caseUUID)) continue;
|
||||
payloads.push({ uuid: caseUUID, executor, note: '', result, steps: [] });
|
||||
}
|
||||
|
||||
return { payloads, unanchored };
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue