| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351 |
- #!/bin/bash
- set -euo pipefail
- # 本地一键 Base(单元测试矩阵)。
- # 默认值:
- # UNIT_MODE=full(8 组配置全覆盖)
- # UNIT_SKIP_COV=1(跳过覆盖率,提速)
- # UNIT_STOP_ON_FAIL=1(首个失败立即退出)
- # XMAKE_FORCE_CLEAN=0(增量配置,减少重编译)
- # UNIT_SYNC_ONLY=0(仅同步 runner 列表,不跑测试)
- # UNIT_SINGLE_CASE=""(可选,格式: "false false true" 覆盖语义矩阵)
- # 以上参数都可用同名环境变量临时覆盖。
- scriptDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
- # shellcheck source=scripts/lib/common.sh
- source "${scriptDir}/scripts/lib/common.sh"
- repoRoot="$(ryanjson_repo_root_from_source "${BASH_SOURCE[0]}" 0)"
- cd "${repoRoot}"
- ryanjson_normalize_bool() {
- # 统一布尔值归一化(true/false)
- local name="$1"
- local value="$2"
- local lower=""
- lower="$(printf '%s' "${value}" | tr '[:upper:]' '[:lower:]')"
- case "${lower}" in
- true|1|on|yes)
- printf 'true\n'
- return 0
- ;;
- false|0|off|no)
- printf 'false\n'
- return 0
- ;;
- *)
- ryanjson_log_error "${name} 仅支持 true/false/1/0/on/off/yes/no,当前值:${value}"
- return 1
- ;;
- esac
- }
- ryanjson_parse_single_case() {
- local raw="$1"
- local strictKey=""
- local addAtHead=""
- local scientific=""
- local extra=""
- read -r strictKey addAtHead scientific extra <<< "${raw}"
- if [[ -z "${strictKey}" || -z "${addAtHead}" || -z "${scientific}" || -n "${extra}" ]]; then
- ryanjson_log_error "UNIT_SINGLE_CASE 格式错误,应为:\"<strict> <head> <sci>\""
- return 1
- fi
- strictKey="$(ryanjson_normalize_bool "UNIT_SINGLE_CASE.strict" "${strictKey}")" || return 1
- addAtHead="$(ryanjson_normalize_bool "UNIT_SINGLE_CASE.head" "${addAtHead}")" || return 1
- scientific="$(ryanjson_normalize_bool "UNIT_SINGLE_CASE.sci" "${scientific}")" || return 1
- printf '%s %s %s\n' "${strictKey}" "${addAtHead}" "${scientific}"
- }
- ryanjson_sync_unity_runner_list() {
- # 同步 Unity runner 列表(确保 test_list.inc 与用例实现一致)
- local listFile="${repoRoot}/test/unityTest/runner/test_list.inc"
- local caseRoot="${repoRoot}/test/unityTest/cases"
- if [[ ! -d "${caseRoot}" ]]; then
- ryanjson_log_error "未找到 unity cases 目录: ${caseRoot}"
- return 1
- fi
- # 收集所有 Runner 函数实现
- local runnerPattern='void[[:space:]]+test[A-Za-z0-9_]+Runner[[:space:]]*\('
- local -a implLines=()
- mapfile -t implLines < <(grep -R -n -E "${runnerPattern}" "${caseRoot}" || true)
- if [[ ${#implLines[@]} -eq 0 ]]; then
- ryanjson_log_error "未找到任何 runner 实现: ${caseRoot}"
- return 1
- fi
- # 统一排序并去重(按文件、行号顺序)
- declare -A seen=()
- declare -a implemented=()
- local -a implSorted=()
- mapfile -t implSorted < <(
- printf '%s\n' "${implLines[@]}" | awk -F: '
- {
- file = $1;
- line = $2;
- if (match($0, /void[[:space:]]+(test[A-Za-z0-9_]+Runner)[[:space:]]*\(/, m)) {
- print file "\t" line "\t" m[1];
- }
- }
- ' | sort -t $'\t' -k1,1 -k2,2n
- )
- for line in "${implSorted[@]}"; do
- local name=""
- name="$(echo "${line}" | cut -f3)"
- if [[ -n "${name}" && -z "${seen[${name}]+x}" ]]; then
- implemented+=("${name}")
- seen["${name}"]=1
- fi
- done
- # 生成新的 list 文件
- local tmpFile=""
- local tmpRoot="${repoRoot}/localLogs/_tmp"
- mkdir -p "${tmpRoot}"
- tmpFile="$(mktemp "${tmpRoot}/unity_runner.XXXXXX")"
- {
- echo "// Auto-generated by run_local_base.sh. Do not edit by hand."
- for name in "${implemented[@]}"; do
- if [[ "${name}" == "testRfc8259Runner" ]]; then
- echo "#if !defined(RyanJsonTestPlatformQemu)"
- echo "UNITY_TEST_LIST_ENTRY(${name})"
- echo "#endif"
- else
- echo "UNITY_TEST_LIST_ENTRY(${name})"
- fi
- done
- } > "${tmpFile}"
- # 对比并更新
- if [[ ! -f "${listFile}" ]] || ! cmp -s "${tmpFile}" "${listFile}"; then
- mv "${tmpFile}" "${listFile}"
- ryanjson_log_info "Unity runner 列表已更新: ${listFile}"
- else
- rm -f "${tmpFile}"
- ryanjson_log_done "Unity runner 列表已是最新"
- fi
- }
- ryanjson_unit_run_case() {
- # 单用例执行:构建 -> 运行 ->(可选)覆盖率
- local index="$1"
- local total="$2"
- local strictKey="$3"
- local addAtHead="$4"
- local scientific="$5"
- local caseName=""
- local profraw=""
- caseName="$(ryanjson_semantic_case_name "${strictKey}" "${addAtHead}" "${scientific}")"
- profraw="${profileRoot}/${caseName}.profraw"
- ryanjson_print_banner_begin "【用例 ${index}/${total}】${caseName}"
- ryanjson_print_semantic_kv "${strictKey}" "${addAtHead}" "${scientific}"
- ryanjson_print_banner_end
- # 语义宏注入(影响编译与运行路径)
- ryanjson_export_semantic_macros "${strictKey}" "${addAtHead}" "${scientific}"
- # 配置与构建
- if ! ryanjson_run_xmake_config "${xmakeForceClean}" "${caseName}"; then
- return 1
- fi
- if ! ryanjson_run_xmake_build "RyanJson" "${caseName}"; then
- return 1
- fi
- # 运行测试二进制
- ryanjson_log_phase "正在运行单元测试二进制..."
- if ! LLVM_PROFILE_FILE="${profraw}" ./build/linux/x86/release/RyanJson; then
- ryanjson_log_error "单元测试执行失败:${caseName}"
- return 1
- fi
- # 覆盖率由外层统一处理
- if [[ "${unitSkipCov}" == "1" ]]; then
- ryanjson_log_info "UNIT_SKIP_COV=1,已跳过覆盖率生成。"
- return 0
- fi
- }
- ryanjson_unit_generate_coverage() {
- # 汇总覆盖率报告
- local mergedProfdata=""
- local reportTxt=""
- local reportHtml=""
- local -a profrawFiles=()
- if ! command -v llvm-profdata >/dev/null 2>&1; then
- ryanjson_log_error "未找到 llvm-profdata,无法生成覆盖率。"
- return 1
- fi
- if ! command -v llvm-cov >/dev/null 2>&1; then
- ryanjson_log_error "未找到 llvm-cov,无法生成覆盖率。"
- return 1
- fi
- shopt -s nullglob
- profrawFiles=("${profileRoot}"/*.profraw)
- shopt -u nullglob
- if [[ "${#profrawFiles[@]}" -eq 0 ]]; then
- ryanjson_log_error "未找到可合并的 profraw 文件。"
- return 1
- fi
- mergedProfdata="${coverageRoot}/coverage.profdata"
- reportTxt="${coverageRoot}/report.txt"
- reportHtml="${coverageRoot}/html"
- if ! llvm-profdata merge -sparse "${profrawFiles[@]}" -o "${mergedProfdata}"; then
- ryanjson_log_error "profraw 合并失败。"
- return 1
- fi
- if ! llvm-cov report ./build/linux/x86/release/RyanJson \
- -instr-profile="${mergedProfdata}" \
- -show-mcdc-summary \
- -sources ./RyanJson > "${reportTxt}"; then
- ryanjson_log_error "文本覆盖率生成失败。"
- return 1
- fi
- if ! llvm-cov show ./build/linux/x86/release/RyanJson \
- -instr-profile="${mergedProfdata}" \
- -format=html \
- -output-dir="${reportHtml}" \
- -show-mcdc-summary \
- -show-branches=count \
- -show-expansions \
- -show-regions \
- -show-line-counts-or-regions \
- -sources ./RyanJson; then
- ryanjson_log_error "HTML 覆盖率生成失败。"
- return 1
- fi
- }
- ryanjson_unit_main() {
- # 读取参数与默认值(保持与旧脚本一致)
- unitMode="${UNIT_MODE:-full}"
- unitSkipCov="${UNIT_SKIP_COV:-1}"
- unitStopOnFail="${UNIT_STOP_ON_FAIL:-1}"
- xmakeForceClean="${XMAKE_FORCE_CLEAN:-0}"
- unitSyncOnly="${UNIT_SYNC_ONLY:-0}"
- # 参数校验
- if ! ryanjson_require_01 "UNIT_SKIP_COV" "${unitSkipCov}"; then
- return 1
- fi
- if ! ryanjson_require_01 "UNIT_STOP_ON_FAIL" "${unitStopOnFail}"; then
- return 1
- fi
- if ! ryanjson_require_01 "XMAKE_FORCE_CLEAN" "${xmakeForceClean}"; then
- return 1
- fi
- if ! ryanjson_require_01 "UNIT_SYNC_ONLY" "${unitSyncOnly}"; then
- return 1
- fi
- # 环境依赖检查
- ryanjson_require_cmd xmake
- # 同步 Unity runner 列表
- ryanjson_log_phase "正在校验 Unity runner 列表..."
- ryanjson_sync_unity_runner_list
- if [[ "${unitSyncOnly}" == "1" ]]; then
- ryanjson_log_done "仅完成 Unity runner 列表同步(UNIT_SYNC_ONLY=1)。"
- return 0
- fi
- # 覆盖率输出目录
- coverageRoot="localLogs/unitMatrix"
- profileRoot="${coverageRoot}/profiles"
- ryanjson_prepare_clean_dir "${coverageRoot}"
- mkdir -p "${profileRoot}"
- # 生成语义矩阵
- unitSingleCase="${UNIT_SINGLE_CASE:-}"
- if [[ -n "${unitSingleCase}" ]]; then
- local normalizedCase=""
- if ! normalizedCase="$(ryanjson_parse_single_case "${unitSingleCase}")"; then
- return 1
- fi
- caseList=("${normalizedCase}")
- else
- if ! caseText="$(ryanjson_emit_semantic_cases "${unitMode}" "UNIT_MODE")"; then
- return 1
- fi
- mapfile -t caseList <<< "${caseText}"
- fi
- totalCases="${#caseList[@]}"
- caseIndex=0
- failedCases=0
- # 逐用例执行
- for entry in "${caseList[@]}"; do
- caseIndex=$((caseIndex + 1))
- read -r strictKey addAtHead scientific <<< "${entry}"
- if ryanjson_unit_run_case "${caseIndex}" "${totalCases}" "${strictKey}" "${addAtHead}" "${scientific}"; then
- :
- else
- failedCases=$((failedCases + 1))
- if [[ "${unitStopOnFail}" == "1" ]]; then
- echo
- ryanjson_log_error "用例 ${caseIndex}/${totalCases} 失败,已按 UNIT_STOP_ON_FAIL=1 提前终止。"
- return 1
- fi
- fi
- done
- # 汇总覆盖率
- if [[ "${unitSkipCov}" != "1" ]]; then
- ryanjson_unit_generate_coverage
- fi
- echo
- ryanjson_log_done "单元测试矩阵执行完成。"
- ryanjson_log_info "执行模式:${unitMode}"
- ryanjson_log_info "总用例数:${totalCases}"
- ryanjson_log_info "失败用例数:${failedCases}"
- ryanjson_log_info "覆盖率输出目录:${coverageRoot}"
- if [[ "${unitSkipCov}" != "1" ]]; then
- ryanjson_log_info "覆盖率文本报告:${coverageRoot}/report.txt"
- ryanjson_log_info "覆盖率HTML目录:${coverageRoot}/html"
- fi
- if [[ "${failedCases}" -gt 0 ]]; then
- return 1
- fi
- }
- main() {
- : "${UNIT_MODE:=full}"
- : "${UNIT_SKIP_COV:=1}"
- : "${UNIT_STOP_ON_FAIL:=1}"
- : "${XMAKE_FORCE_CLEAN:=0}"
- : "${UNIT_SYNC_ONLY:=0}"
- : "${UNIT_SINGLE_CASE:=}"
- ryanjson_print_banner_begin "本地 Base 启动(单元测试矩阵)"
- ryanjson_print_banner_kv "UNIT_MODE" "${UNIT_MODE}"
- ryanjson_print_banner_kv "UNIT_SKIP_COV" "${UNIT_SKIP_COV}"
- ryanjson_print_banner_kv "UNIT_STOP_ON_FAIL" "${UNIT_STOP_ON_FAIL}"
- ryanjson_print_banner_kv "XMAKE_FORCE_CLEAN" "${XMAKE_FORCE_CLEAN}"
- ryanjson_print_banner_kv "UNIT_SYNC_ONLY" "${UNIT_SYNC_ONLY}"
- ryanjson_print_banner_kv_optional "UNIT_SINGLE_CASE" "${UNIT_SINGLE_CASE:-}"
- ryanjson_print_banner_end
- export UNIT_MODE UNIT_SKIP_COV UNIT_STOP_ON_FAIL XMAKE_FORCE_CLEAN UNIT_SYNC_ONLY UNIT_SINGLE_CASE
- ryanjson_unit_main
- }
- main "$@"
|