|
|
@@ -177,38 +177,106 @@ for m,p,b,c in top('SERVER_ERR'):
|
|
|
lines.append("")
|
|
|
lines.append("> 说明:冒烟为空体请求,部分 500/400 属预期(缺必填参数)。NOT_FOUND 与回归劣化才需优先处理。")
|
|
|
|
|
|
+# ───────────────────────── 守卫:识别无效轮 ─────────────────────────
|
|
|
+# 上游宕机 / 网关抖动 / 登录失败 / 部分轮 不应被当作真实回归提交,亦不可污染
|
|
|
+# 回归基线(PREV_TSV)。命中则标 INVALID,由 bash 侧跳过 commit/push/基线更新。
|
|
|
+non_json = cats.get('NON_JSON', 0)
|
|
|
+server_err = cats.get('SERVER_ERR', 0)
|
|
|
+conn_err = cats.get('CONN_ERR', 0)
|
|
|
+http_502 = sum(1 for v in cur.values() if v[2] == '502') # v[2]=http 列
|
|
|
+nz_rate = (non_json * 100 // total) if total else 0
|
|
|
+
|
|
|
+NON_JSON_THRESHOLD = 20 # NON_JSON 占比 > 20% 视为上游风暴
|
|
|
+HTTP502_THRESHOLD = 10 # HTTP 502 数 > 10 视为网关抖动
|
|
|
+MIN_TOTAL = 400 # 用例数 < 400 视为部分轮(如登录失败跳过 auth 用例)
|
|
|
+
|
|
|
+login_failed = False
|
|
|
+_smoke_out = os.path.join(os.path.dirname(cur_tsv), 'smoke_stdout.txt')
|
|
|
+try:
|
|
|
+ with open(_smoke_out, encoding='utf-8', errors='ignore') as _f:
|
|
|
+ _so = _f.read()
|
|
|
+ if ('父/师 token 均未取得' in _so) or ('未取到 token' in _so) or ('登录态接口将被跳过' in _so):
|
|
|
+ login_failed = True
|
|
|
+except FileNotFoundError:
|
|
|
+ pass
|
|
|
+
|
|
|
+reasons = []
|
|
|
+if login_failed:
|
|
|
+ reasons.append('登录失败(auto-login 未取得 token,auth 用例被跳过)')
|
|
|
+if nz_rate > NON_JSON_THRESHOLD:
|
|
|
+ reasons.append(f'NON_JSON 风暴({nz_rate}%)')
|
|
|
+if http_502 > HTTP502_THRESHOLD:
|
|
|
+ reasons.append(f'HTTP502 网关抖动({http_502})')
|
|
|
+if total < MIN_TOTAL:
|
|
|
+ reasons.append(f'用例数异常({total}<{MIN_TOTAL},疑似部分轮/中断)')
|
|
|
+
|
|
|
+INVALID = len(reasons) > 0
|
|
|
+
|
|
|
+if INVALID:
|
|
|
+ banner = ("\n> ⚠️ **本轮判定 INVALID:结果不提交、不推送、不更新回归基线。**\n"
|
|
|
+ f"> 原因:{';'.join(reasons)}\n"
|
|
|
+ "> 处置:保留上次有效基线不变,待上游稳定后重跑即可;勿将此轮数值当作真实回归。\n")
|
|
|
+ lines.append(banner)
|
|
|
+
|
|
|
report = "\n".join(lines) + "\n"
|
|
|
-with open(rpt, 'w', encoding='utf-8') as f:
|
|
|
+
|
|
|
+# 报告恒写入本轮运行目录(供排查);仅当有效时同步到仓库根(避免污染已提交产物)
|
|
|
+_run_report = os.path.join(os.path.dirname(cur_tsv), 'INTERFACE_TEST_REPORT.md')
|
|
|
+with open(_run_report, 'w', encoding='utf-8') as f:
|
|
|
f.write(report)
|
|
|
+if not INVALID:
|
|
|
+ with open(rpt, 'w', encoding='utf-8') as f:
|
|
|
+ f.write(report)
|
|
|
|
|
|
-# 趋势日志
|
|
|
+# 趋势日志(本地,不随提交推送;INVALID 也记录以留痕)
|
|
|
with open(trend, 'a', encoding='utf-8') as f:
|
|
|
f.write(f"{ts}\ttotal={total}\tBIZ_OK={ok}\trate={rate}%\t"
|
|
|
f"fixed={len(fixed)}\tregressed={len(regressed)}\t"
|
|
|
- f"NOT_FOUND={cats.get('NOT_FOUND',0)}\tSERVER_ERR={cats.get('SERVER_ERR',0)}\n")
|
|
|
+ f"NOT_FOUND={cats.get('NOT_FOUND',0)}\tSERVER_ERR={cats.get('SERVER_ERR',0)}\t"
|
|
|
+ f"NON_JSON={non_json}\tHTTP502={http_502}\tINVALID={1 if INVALID else 0}"
|
|
|
+ + (f"({ '|'.join(reasons) })" if INVALID else "") + "\n")
|
|
|
|
|
|
-# 导出真实指标供 bash 提交信息使用(修复 rate/fixed/regressed 在子进程不可见导致
|
|
|
+# 导出真实指标供 bash 判定(修复 rate/fixed/regressed 在子进程不可见导致
|
|
|
# commit 失败 + 假通过率 ?%;bash 侧 set -u 下 unbound variable 亦会导致整条命令展开失败)
|
|
|
_metrics_path = os.path.join(os.path.dirname(trend), 'interface_test_metrics.env')
|
|
|
with open(_metrics_path, 'w', encoding='utf-8') as _mf:
|
|
|
- _mf.write(f"RATE={rate}\nFIXED_COUNT={len(fixed)}\nREGRESSED_COUNT={len(regressed)}\n")
|
|
|
+ _mf.write(f"RATE={rate}\nFIXED_COUNT={len(fixed)}\nREGRESSED_COUNT={len(regressed)}\n"
|
|
|
+ f"TOTAL={total}\nOK_COUNT={ok}\nNON_JSON_COUNT={non_json}\n"
|
|
|
+ f"SERVER_ERR_COUNT={server_err}\nCONN_ERR_COUNT={conn_err}\n"
|
|
|
+ f"HTTP_502_COUNT={http_502}\nINVALID={1 if INVALID else 0}\n"
|
|
|
+ f"INVALID_REASON={'|'.join(reasons)}\n")
|
|
|
|
|
|
print(report)
|
|
|
print(f"\n[趋势] 已写入 {trend}")
|
|
|
+if INVALID:
|
|
|
+ print(f"[守卫] INVALID 触发:{';'.join(reasons)} --> 跳过提交/推送/基线更新")
|
|
|
PY
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
-# 5) 提交测试结果 + 补全用例
|
|
|
+# 5) 守卫判定:无效轮(宕机/NON_JSON 风暴/502 抖动/登录失败/部分轮)
|
|
|
+# 不提交、不推送、不更新回归基线,避免污染趋势与开发侧闭环
|
|
|
+# ---------------------------------------------------------------------------
|
|
|
+INVALID=0; INVALID_REASON=""
|
|
|
+RATE="?"; FIXED_COUNT=0; REGRESSED_COUNT=0
|
|
|
+METRICS_ENV="$REPO_DIR/interface_test_metrics.env"
|
|
|
+[ -f "$METRICS_ENV" ] && . "$METRICS_ENV"
|
|
|
+if [ "${INVALID:-0}" -eq 1 ]; then
|
|
|
+ log "⚠️ 守卫触发 INVALID:本轮结果不提交 / 不推送 / 不更新回归基线"
|
|
|
+ log " 原因: ${INVALID_REASON}"
|
|
|
+ log " 处置: 保留 PREV_TSV 基线不变,待上游稳定后重跑即可"
|
|
|
+ log " 排查: 本轮明细见 $RUN_DIR/results_$TS.tsv 与 $RUN_DIR/smoke_stdout.txt"
|
|
|
+ log "===== 本轮结束(INVALID,已跳过推送)====="
|
|
|
+ exit 0
|
|
|
+fi
|
|
|
+
|
|
|
+# ---------------------------------------------------------------------------
|
|
|
+# 6) 提交测试结果 + 补全用例(仅有效轮)
|
|
|
# ---------------------------------------------------------------------------
|
|
|
git add -f ops_interface_smoke.py
|
|
|
# 强制纳入报告产物(防止未来 .gitignore 规则或路径规范导致漏提);
|
|
|
# EXTRA_CASES 仅在存在且有内容时才加入,避免缺失路径使整个 git add 失败(旧逻辑缺陷)
|
|
|
git add -f INTERFACE_TEST_REPORT.md interface_test_report.tsv cfc-frontend/INTERFACE_TEST_MATRIX.md
|
|
|
[ -s "$EXTRA_CASES" ] && git add -f "$EXTRA_CASES"
|
|
|
-# 读取 Python 计算出的真实指标(修复 set -u 下 unbound variable 致 commit 失败 + 假通过率 ?%)
|
|
|
-RATE="?"; FIXED_COUNT=0; REGRESSED_COUNT=0
|
|
|
-METRICS_ENV="$REPO_DIR/interface_test_metrics.env"
|
|
|
-[ -f "$METRICS_ENV" ] && . "$METRICS_ENV"
|
|
|
if git diff --cached --quiet; then
|
|
|
log "无测试结果变更,跳过提交"
|
|
|
else
|
|
|
@@ -217,15 +285,42 @@ else
|
|
|
fi
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
-# 6) 推送远端,与开发团队形成闭环
|
|
|
+# 7) 推送远端,与开发团队形成闭环
|
|
|
+# 增强:远端领先(非快进)时自动 rebase 后重试,避免测试结果不落远端。
|
|
|
+# 凭证/网络类错误不盲目重试,直接失败并留待下次运行。
|
|
|
# ---------------------------------------------------------------------------
|
|
|
-if git push origin "$BRANCH" >>"$LOG_DIR/interface_cron.log" 2>&1; then
|
|
|
- log "git push 成功 — 测试闭环已同步远端"
|
|
|
-else
|
|
|
- log "git push 失败(凭证/网络),下次运行将重试"
|
|
|
+PUSH_LOG="$RUN_DIR/push.log"
|
|
|
+MAX_PUSH_RETRY=2
|
|
|
+push_ok=0
|
|
|
+attempt=0
|
|
|
+while [ "$attempt" -lt $((MAX_PUSH_RETRY + 1)) ]; do
|
|
|
+ attempt=$((attempt + 1))
|
|
|
+ if git push origin "$BRANCH" >"$PUSH_LOG" 2>&1; then
|
|
|
+ push_ok=1
|
|
|
+ log "git push 成功 — 测试闭环已同步远端(第 ${attempt} 次尝试)"
|
|
|
+ break
|
|
|
+ fi
|
|
|
+ # 仅在「远端领先/非快进」时自动 rebase 后重试
|
|
|
+ if grep -qiE "non-fast-forward|fetch first|Updates were rejected|rejected" "$PUSH_LOG"; then
|
|
|
+ log "git push 被拒(远端领先/非快进)。rebase 后重试(${attempt}/${MAX_PUSH_RETRY})"
|
|
|
+ # rebase 期间用 --autostash 保护未提交改动(如事后刷新的 interface_test_report.tsv)
|
|
|
+ if git pull --rebase --autostash origin "$BRANCH" >>"$LOG_DIR/interface_cron.log" 2>&1; then
|
|
|
+ log "rebase 完成,重新尝试 push"
|
|
|
+ else
|
|
|
+ log "rebase 失败(可能存在冲突),中止 push 重试"
|
|
|
+ break
|
|
|
+ fi
|
|
|
+ else
|
|
|
+ # 凭证/网络等其他错误:不盲目重试,避免空耗与误报
|
|
|
+ log "git push 失败(疑似凭证/网络,非非快进类): $(grep -iE 'error|fatal|timed out|could not|permission denied' "$PUSH_LOG" | head -3 | tr '\n' ' ')"
|
|
|
+ break
|
|
|
+ fi
|
|
|
+done
|
|
|
+if [ "$push_ok" -ne 1 ]; then
|
|
|
+ log "git push 最终失败,本轮结果未同步远端(下次运行将重试)"
|
|
|
fi
|
|
|
|
|
|
-# 更新回归基线
|
|
|
+# 更新回归基线(仅有效轮才覆盖,避免无效轮污染下一轮回归对比)
|
|
|
cp "$CUR_TSV" "$PREV_TSV"
|
|
|
# 同步仓库根目录的全量结果 TSV(供人工/工具读取,避免与报告一起变陈旧)
|
|
|
cp "$CUR_TSV" "interface_test_report.tsv"
|