runBaseCoverage.sh 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. #!/bin/bash
  2. set -euo pipefail
  3. # 单元测试配置矩阵入口(Linux)。
  4. # 脚本路径:scripts/ci/runBaseCoverage.sh
  5. # 执行模式:UNIT_MODE=quick|nightly|full
  6. # quick: 2 组(PR 快检)
  7. # nightly: 4 组(strict × addAtHead,scientific 固定 true)
  8. # full: 8 组(三个布尔宏全组合)
  9. # 常用参数:
  10. # UNIT_SKIP_COV=0|1:是否跳过覆盖率
  11. # UNIT_STOP_ON_FAIL=0|1:失败是否立即终止
  12. # XMAKE_FORCE_CLEAN=0|1:每组前是否先清理配置
  13. unitMode="${UNIT_MODE:-full}"
  14. unitSkipCov="${UNIT_SKIP_COV:-0}"
  15. unitStopOnFail="${UNIT_STOP_ON_FAIL:-1}"
  16. xmakeForceClean="${XMAKE_FORCE_CLEAN:-0}"
  17. # 统一切到仓库根目录,避免从任意 cwd 启动时相对路径失效
  18. scriptDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  19. repoRoot="$(cd "${scriptDir}/../.." && pwd)"
  20. cd "${repoRoot}"
  21. # 覆盖率目录固定为 coverage/unitMatrix,每次执行前清理,保证只保留最新结果
  22. coverageRoot="coverage/unitMatrix"
  23. rm -rf "${coverageRoot}"
  24. profileRoot="${coverageRoot}/profiles"
  25. mkdir -p "${profileRoot}"
  26. declare -a caseList=()
  27. addCase() {
  28. local strictKey="$1"
  29. local addAtHead="$2"
  30. local scientific="$3"
  31. caseList+=("${strictKey} ${addAtHead} ${scientific}")
  32. }
  33. # 根据模式生成组合列表
  34. case "${unitMode}" in
  35. quick)
  36. # PR 快检:默认组合 + 对立组合
  37. addCase false true true
  38. addCase true false true
  39. ;;
  40. nightly)
  41. # 夜间:覆盖 strict × addAtHead 四种核心语义
  42. for strictKey in false true; do
  43. for addAtHead in false true; do
  44. addCase "${strictKey}" "${addAtHead}" true
  45. done
  46. done
  47. ;;
  48. full)
  49. # 全量:三个布尔宏全组合
  50. for strictKey in false true; do
  51. for addAtHead in false true; do
  52. for scientific in false true; do
  53. addCase "${strictKey}" "${addAtHead}" "${scientific}"
  54. done
  55. done
  56. done
  57. ;;
  58. *)
  59. echo "[错误] UNIT_MODE 仅支持 quick/nightly/full,当前值:${unitMode}"
  60. exit 1
  61. ;;
  62. esac
  63. totalCases="${#caseList[@]}"
  64. caseIndex=0
  65. failedCases=0
  66. runCase() {
  67. local index="$1"
  68. local total="$2"
  69. local strictKey="$3"
  70. local addAtHead="$4"
  71. local scientific="$5"
  72. local caseName="strict_${strictKey}__head_${addAtHead}__sci_${scientific}"
  73. local profraw="${profileRoot}/${caseName}.profraw"
  74. echo "===================================================="
  75. echo "【用例 ${index}/${total}】${caseName}"
  76. echo " - RyanJsonStrictObjectKeyCheck=${strictKey}"
  77. echo " - RyanJsonDefaultAddAtHead=${addAtHead}"
  78. echo " - RyanJsonSnprintfSupportScientific=${scientific}"
  79. echo "===================================================="
  80. export RYANJSON_STRICT_OBJECT_KEY_CHECK="${strictKey}"
  81. export RYANJSON_DEFAULT_ADD_AT_HEAD="${addAtHead}"
  82. export RYANJSON_SNPRINTF_SUPPORT_SCIENTIFIC="${scientific}"
  83. # 重新配置,确保宏变化进入编译命令
  84. # 默认走增量配置,配合第三方静态库可减少重复编译
  85. if [[ "${xmakeForceClean}" == "1" ]]; then
  86. echo "[阶段] 正在执行 xmake 配置(clean 模式)..."
  87. if ! xmake f -c; then
  88. echo "[错误] xmake 配置失败:${caseName}"
  89. return 1
  90. fi
  91. else
  92. echo "[阶段] 正在执行 xmake 配置(增量模式)..."
  93. if ! xmake f; then
  94. echo "[错误] xmake 配置失败:${caseName}"
  95. return 1
  96. fi
  97. fi
  98. echo "[阶段] 正在执行 xmake 构建(target=RyanJson)..."
  99. if ! xmake -b RyanJson; then
  100. echo "[错误] xmake 构建失败:${caseName}"
  101. return 1
  102. fi
  103. # 单测执行,profile 分文件隔离,避免组合间互相覆盖
  104. echo "[阶段] 正在运行单元测试二进制..."
  105. if ! LLVM_PROFILE_FILE="${profraw}" ./build/linux/x86/release/RyanJson; then
  106. echo "[错误] 单元测试执行失败:${caseName}"
  107. return 1
  108. fi
  109. # 快检模式可跳过覆盖率阶段以缩短总时长
  110. if [[ "${unitSkipCov}" == "1" ]]; then
  111. echo "[信息] UNIT_SKIP_COV=1,已跳过覆盖率生成。"
  112. return 0
  113. fi
  114. }
  115. # 按组合清单执行
  116. for entry in "${caseList[@]}"; do
  117. caseIndex=$((caseIndex + 1))
  118. read -r strictKey addAtHead scientific <<< "${entry}"
  119. if runCase "${caseIndex}" "${totalCases}" "${strictKey}" "${addAtHead}" "${scientific}"; then
  120. :
  121. else
  122. failedCases=$((failedCases + 1))
  123. if [[ "${unitStopOnFail}" == "1" ]]; then
  124. echo
  125. echo "[错误] 用例 ${caseIndex}/${totalCases} 失败,已按 UNIT_STOP_ON_FAIL=1 提前终止。"
  126. exit 1
  127. fi
  128. fi
  129. done
  130. # 若启用覆盖率,则把矩阵中所有组合的 profraw 合并后只生成一份报告
  131. if [[ "${unitSkipCov}" != "1" ]]; then
  132. if ! command -v llvm-profdata >/dev/null 2>&1; then
  133. echo "[错误] 未找到 llvm-profdata,无法生成覆盖率。"
  134. exit 1
  135. fi
  136. if ! command -v llvm-cov >/dev/null 2>&1; then
  137. echo "[错误] 未找到 llvm-cov,无法生成覆盖率。"
  138. exit 1
  139. fi
  140. shopt -s nullglob
  141. profrawFiles=("${profileRoot}"/*.profraw)
  142. shopt -u nullglob
  143. if [[ "${#profrawFiles[@]}" -eq 0 ]]; then
  144. echo "[错误] 未找到可合并的 profraw 文件。"
  145. exit 1
  146. fi
  147. mergedProfdata="${coverageRoot}/coverage.profdata"
  148. reportTxt="${coverageRoot}/report.txt"
  149. reportHtml="${coverageRoot}/html"
  150. if ! llvm-profdata merge -sparse "${profrawFiles[@]}" -o "${mergedProfdata}"; then
  151. echo "[错误] profraw 合并失败。"
  152. exit 1
  153. fi
  154. if ! llvm-cov report ./build/linux/x86/release/RyanJson \
  155. -instr-profile="${mergedProfdata}" \
  156. -show-mcdc-summary \
  157. -sources ./RyanJson > "${reportTxt}"; then
  158. echo "[错误] 文本覆盖率生成失败。"
  159. exit 1
  160. fi
  161. if ! llvm-cov show ./build/linux/x86/release/RyanJson \
  162. -instr-profile="${mergedProfdata}" \
  163. -format=html \
  164. -output-dir="${reportHtml}" \
  165. -show-mcdc-summary \
  166. -show-branches=count \
  167. -show-expansions \
  168. -show-regions \
  169. -show-line-counts-or-regions \
  170. -sources ./RyanJson; then
  171. echo "[错误] HTML 覆盖率生成失败。"
  172. exit 1
  173. fi
  174. fi
  175. echo
  176. echo "单元测试矩阵执行完成。"
  177. echo "执行模式:${unitMode}"
  178. echo "总用例数:${totalCases}"
  179. echo "失败用例数:${failedCases}"
  180. echo "覆盖率输出目录:${coverageRoot}"
  181. if [[ "${unitSkipCov}" != "1" ]]; then
  182. echo "覆盖率文本报告:${coverageRoot}/report.txt"
  183. echo "覆盖率HTML目录:${coverageRoot}/html"
  184. fi
  185. if [[ "${failedCases}" -gt 0 ]]; then
  186. exit 1
  187. fi