rule-evaluator-regression.sh 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. #!/usr/bin/env bash
  2. # S8-REGRESSION-FIXTURE-1 统一回归 driver(dev/test only, aidopdev)。
  3. #
  4. # 顺序运行子脚本,统一汇总 PASS / SKIP / FAIL:
  5. # 1. sched-exec-regression.sh — 调度执行 + antiflap + DetectionLog 主链
  6. # 2. r2-timeout-regression.sh — TIMEOUT 主链(默认复用 demo rule 10)
  7. # 3. r3-shortage-regression.sh — SHORTAGE 主链(当前 dev 无 demo SHORTAGE,SKIP)
  8. # 4. r3-out-of-range-regression.sh — OUT_OF_RANGE 主链(默认复用 demo rule 11)
  9. # 5. r6-detection-log-edge-regression.sh — NO_HIT/EVALUATE_FAILED edge
  10. # 6. recovered-edge-regression.sh — RECOVERED 主链 + recover_count_required 抗抖
  11. # 7. active-exception-id-trigger1-regression.sh — trigger=1 首 tick state.active_exception_id 回填
  12. #
  13. # 退出码:
  14. # - 仅 FAIL>0 时 exit 1;
  15. # - 全 PASS 或 PASS+SKIP 混合(FAIL=0)→ exit 0;
  16. # - 致命错误(mysql/auth)→ exit 2。
  17. set -uo pipefail
  18. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  19. # shellcheck source=./s8-regression-common.sh
  20. source "${SCRIPT_DIR}/s8-regression-common.sh"
  21. TOTAL_PASS=0
  22. TOTAL_FAIL=0
  23. TOTAL_SKIP=0
  24. declare -a CHILD_RESULTS
  25. # 入场前快照 demo rule 初态(disabled,CTO 决策默认值);末态比对此快照而不是硬编码 enabled=1。
  26. DEMO_INITIAL_SNAPSHOT=$(snapshot_demo_rule_state)
  27. baseline_before=$(read_baseline)
  28. echo "============================================================"
  29. echo "S8 Regression Driver"
  30. echo " baseline_before=${baseline_before}"
  31. echo " demo_initial_snapshot=${DEMO_INITIAL_SNAPSHOT}"
  32. echo "============================================================"
  33. # trap:异常退出(child fail / Ctrl+C / SIGTERM)也必须恢复 demo rule disabled 并清 ghost。
  34. # 正常路径会在 trap 触发前显式调用 restore,trap 二次调用是幂等的。
  35. RESTORED_DEMO=0
  36. on_exit() {
  37. if [[ "${RESTORED_DEMO}" == "0" ]]; then
  38. cleanup_temp_sched_approval_ghost_tasks 2>/dev/null || true
  39. restore_demo_rules_disabled_after_regression 2>/dev/null || true
  40. RESTORED_DEMO=1
  41. fi
  42. }
  43. trap on_exit EXIT INT TERM
  44. # 入场 arm:CTO 决策"demo rule 默认关闭",回归期间临时启用以驱动 r2/r3-oor 等子链。
  45. arm_demo_rules_for_regression
  46. # 每个 child 结束后调用:
  47. # 1. 关闭 TEMP_SCHED_% 残留(防跨 child 状态污染);
  48. # 2. 软删 TEMP_SCHED_% 的活跃异常;
  49. # 3. cleanup_temp_sched_approval_ghost_tasks:取消 TEMP 软删后遗留的 Pending 审批任务。
  50. # 不清除 demo rule 10/11/12、DEMO-S2/S3 数据、或真实业务 exception。
  51. # 不动 detection_state:避免与 Job scheduler 并发竞态破坏 active_exception_id 回填。
  52. # 各 child 自身负责 setup 阶段清自己的 detection_state。
  53. cleanup_between_children() {
  54. local label="$1"
  55. mysql_run "UPDATE ado_s8_watch_rule SET enabled=0, lock_token=NULL, locked_by=NULL, lock_until=NULL, updated_at=NOW() WHERE rule_code LIKE 'TEMP_SCHED_%' AND tenant_id=${TENANT_ID} AND factory_id=${FACTORY_ID} AND enabled=1;" 2>/dev/null || true
  56. mysql_run "UPDATE ado_s8_exception SET is_deleted=1, updated_at=NOW() WHERE source_rule_code LIKE 'TEMP_SCHED_%' AND is_deleted=0;" 2>/dev/null || true
  57. cleanup_temp_sched_approval_ghost_tasks 2>/dev/null || true
  58. echo "[teardown] after ${label}: TEMP_SCHED_% disabled + active exceptions soft-deleted + ghost approval cancelled"
  59. }
  60. run_child() {
  61. local label="$1"
  62. local script="$2"
  63. if [[ ! -x "${script}" ]]; then
  64. echo "[${label}] SKIP — script not executable: ${script}"
  65. TOTAL_SKIP=$((TOTAL_SKIP + 1))
  66. CHILD_RESULTS+=("${label}: SKIP (not executable)")
  67. return 0
  68. fi
  69. echo
  70. echo "===> [${label}] ${script}"
  71. local out
  72. local rc=0
  73. set +e
  74. out=$(bash "${script}" 2>&1)
  75. rc=$?
  76. set -e
  77. echo "${out}"
  78. local p f s
  79. p=$(printf '%s\n' "${out}" | grep -c '^PASS:' || true)
  80. f=$(printf '%s\n' "${out}" | grep -c '^FAIL:' || true)
  81. s=$(printf '%s\n' "${out}" | grep -c '^SKIP:' || true)
  82. TOTAL_PASS=$((TOTAL_PASS + p))
  83. TOTAL_FAIL=$((TOTAL_FAIL + f))
  84. TOTAL_SKIP=$((TOTAL_SKIP + s))
  85. local status
  86. if [[ "${rc}" == "0" && "${f}" == "0" ]]; then
  87. if [[ "${p}" == "0" && "${s}" -gt 0 ]]; then
  88. status="SKIP"
  89. else
  90. status="PASS"
  91. fi
  92. else
  93. status="FAIL"
  94. fi
  95. CHILD_RESULTS+=("${label}: ${status} (pass=${p} fail=${f} skip=${s} rc=${rc})")
  96. echo "[${label}] result: ${status} pass=${p} fail=${f} skip=${s} rc=${rc}"
  97. cleanup_between_children "${label}"
  98. }
  99. run_child "sched-exec" "${SCRIPT_DIR}/sched-exec-regression.sh"
  100. run_child "r2-timeout" "${SCRIPT_DIR}/r2-timeout-regression.sh"
  101. run_child "r3-shortage" "${SCRIPT_DIR}/r3-shortage-regression.sh"
  102. run_child "r3-oor" "${SCRIPT_DIR}/r3-out-of-range-regression.sh"
  103. run_child "r6-edge" "${SCRIPT_DIR}/r6-detection-log-edge-regression.sh"
  104. run_child "recovered" "${SCRIPT_DIR}/recovered-edge-regression.sh"
  105. run_child "trigger1-active" "${SCRIPT_DIR}/active-exception-id-trigger1-regression.sh"
  106. run_child "oor-shortage-edge" "${SCRIPT_DIR}/oor-shortage-edge-regression.sh"
  107. # 显式 restore(trap 是兜底,正常路径走这里;幂等)。
  108. cleanup_temp_sched_approval_ghost_tasks
  109. restore_demo_rules_disabled_after_regression
  110. RESTORED_DEMO=1
  111. baseline_after=$(read_baseline)
  112. echo
  113. echo "============================================================"
  114. echo "Driver Summary"
  115. echo "============================================================"
  116. echo "baseline_before=${baseline_before}"
  117. echo "baseline_after=${baseline_after}"
  118. if [[ "${baseline_before}" == "${baseline_after}" ]]; then
  119. echo "baseline: UNCHANGED"
  120. else
  121. echo "baseline: DRIFTED (${baseline_before} -> ${baseline_after})"
  122. TOTAL_FAIL=$((TOTAL_FAIL + 1))
  123. fi
  124. # 验证 demo rule 10/11/12 末态 = 入场前快照(业务侧 enabled=0 不漂移)。
  125. demo_state_after=$(snapshot_demo_rule_state)
  126. echo "demo_rule_state_initial=${DEMO_INITIAL_SNAPSHOT}"
  127. echo "demo_rule_state_final =${demo_state_after}"
  128. if [[ "${demo_state_after}" == "${DEMO_INITIAL_SNAPSHOT}" ]]; then
  129. echo "demo rule 10/11/12: RESTORED to initial state (CTO policy: default off)"
  130. else
  131. echo "demo rule 10/11/12: DRIFTED from initial (initial=${DEMO_INITIAL_SNAPSHOT} final=${demo_state_after})"
  132. TOTAL_FAIL=$((TOTAL_FAIL + 1))
  133. fi
  134. # 验证 TEMP_SCHED_TIMEOUT_ANTIFLAP 收尾态
  135. temp_state=$(mysql_run "SELECT IFNULL(GROUP_CONCAT(CONCAT(id,':',enabled)),'absent') FROM ado_s8_watch_rule WHERE rule_code='TEMP_SCHED_TIMEOUT_ANTIFLAP';")
  136. temp_visible=$(mysql_run "SELECT COUNT(*) FROM ado_s8_exception WHERE source_rule_code='TEMP_SCHED_TIMEOUT_ANTIFLAP' AND is_deleted=0;")
  137. echo "temp_rule_state=${temp_state} temp_active_in_default_list=${temp_visible}"
  138. echo
  139. echo "Per-child:"
  140. for r in "${CHILD_RESULTS[@]}"; do
  141. echo " - ${r}"
  142. done
  143. echo
  144. echo "Used fixtures:"
  145. echo " - DEMO_ORDER_DELIVERY_TIMEOUT (rule id=10)"
  146. echo " - DEMO_ORDER_DIMENSION_OOR (rule id=11)"
  147. echo " - TEMP_SCHED_TIMEOUT_ANTIFLAP (TEMP fixture, post-test enabled=0)"
  148. echo " - SHORTAGE / G01_TEST_* (absent → SKIP)"
  149. echo
  150. echo "Totals: passed=${TOTAL_PASS} failed=${TOTAL_FAIL} skipped=${TOTAL_SKIP}"
  151. if (( TOTAL_FAIL > 0 )); then
  152. echo "Driver result: FAIL"
  153. exit 1
  154. fi
  155. echo "Driver result: PASS (FAIL=0)"
  156. exit 0