diff --git a/testcase/command-execute-docker.sh b/testcase/command-execute-docker.sh new file mode 100755 index 0000000000000000000000000000000000000000..e25056965189148fb6ceebe3909edef1dc710449 --- /dev/null +++ b/testcase/command-execute-docker.sh @@ -0,0 +1,1000 @@ +#!/bin/bash +############################################################################### +# @用例ID: 2026xxxx-xxxxxx-xxxxxxxxx +# @用例名称: command-execute-docker +# @用例级别: 3 +# @用例标签: +# @用例类型: 功能测试 +# @自动化: 1 +############################################################################### +[ -z "$TST_TS_TOPDIR" ] && { + TST_TS_TOPDIR="$(realpath "$(dirname "$0")/..")" + export TST_TS_TOPDIR +} +source "${TST_TS_TOPDIR}/tst_common/lib/common.sh" || exit 1 +source "${TST_TS_TOPDIR}/tst_lib/ts_common.sh" || exit 1 +############################################################################### + +# ============================================ +# 配置区 +# ============================================ + +readonly BASE_IMAGE="opencloudos-container-init-9.6-20260514.2.x86_64" +readonly CONTAINER_TIMEOUT=300 +readonly SYSTEMD_WAIT=3 +readonly RESULT_BASE="/tmp/docker-test" + +# ============================================ +# 全局进度变量 +# ============================================ + +COMPLETED=0 +FAILED=0 +SKIPPED=0 +TOTAL=0 +JOBS=0 + +# ============================================ +# 工具函数 +# ============================================ + +generate_container_name() { + local pkg="$1" + echo "test_${pkg//[^a-zA-Z0-9]/_}_$(date +%s)_$RANDOM" +} + +cleanup_container() { + local name="$1" + docker stop "$name" >/dev/null 2>&1 || true + docker rm "$name" >/dev/null 2>&1 || true +} + +# 在工具函数区添加 +determine_status() { + local pkg="$1" + local inst="$2" + local cmd="$3" + local svc="$4" + local rem="$5" + local pkg_dir="${RESULT_DIR}/${pkg}" + + if [ "$inst" = "FAIL" ]; then + echo "failed" + return 0 + fi + + if [ "$cmd" = "FAIL" ] || [ "$svc" = "FAIL" ] || [ "$rem" = "FAIL" ]; then + echo "failed" + return 0 + fi + + if [ "$rem" = "SKIP" ] && grep -qi "protected" "${pkg_dir}/remove.log" 2>/dev/null; then + echo "skipped" + return 0 + fi + + if [ "$svc" = "SKIP" ] && [ -f "${pkg_dir}/service.skip_reason" ]; then + echo "skipped" + return 0 + fi + + if [ "$cmd" = "SKIP" ] && [ -n "$DESKTOP_WHITE" ] && grep -q "^$pkg" "$DESKTOP_WHITE" 2>/dev/null; then + echo "skipped" + return 0 + fi + + if [ "$svc" = "SKIP" ] && [ -n "$SERVICE_WHITE" ] && grep -q "^$pkg" "$SERVICE_WHITE" 2>/dev/null; then + echo "skipped" + return 0 + fi + + if [ "$inst" = "PASS" ] && [ "$rem" = "PASS" ]; then + echo "completed" + return 0 + fi + + echo "completed" +} + +# ============================================ +# 容器管理 +# ============================================ + +container_start() { + local pkg="$1" + local pkg_dir="$2" + local name + name=$(generate_container_name "$pkg") + + local mounts=() + mounts+=("-v" "${pkg_dir}:/logs") + mounts+=("-v" "${TST_TS_TOPDIR}/tst_lib:/tst_lib:ro") + mounts+=("-v" "${TST_TS_TOPDIR}/tst_common:/tst_common:ro") + + [ -n "$DESKTOP_WHITE" ] && [ -f "$DESKTOP_WHITE" ] && \ + mounts+=("-v" "$(realpath "$DESKTOP_WHITE"):/whitelist:ro") + + [ -n "$SERVICE_WHITE" ] && [ -f "$SERVICE_WHITE" ] && \ + mounts+=("-v" "$(realpath "$SERVICE_WHITE"):/servicelist:ro") + + docker run -d \ + --privileged \ + --name "$name" \ + --stop-timeout "$CONTAINER_TIMEOUT" \ + --memory=2g \ + --memory-swap=2g \ + "${mounts[@]}" \ + "$BASE_IMAGE" \ + /sbin/init >/dev/null + + + sleep "$SYSTEMD_WAIT" + + if ! docker ps --filter "name=$name" --format '{{.Names}}' | grep -q "$name"; then + msg "容器启动失败: $name" + cleanup_container "$name" + return 1 + fi + + echo "$name" +} + +# ============================================ +# 容器内测试脚本 +# ============================================ + +generate_container_script() { + local pkg="$1" + + local install_func remove_func elf_func cmd_func + install_func=$(declare -f test_single_rpm_install) + remove_func=$(declare -f test_single_rpm_remove) + elf_func=$(declare -f get_elf_executable) + cmd_func=$(declare -f test_command_execute) + + cat </dev/null | grep "Source RPM" | awk '{print \$NF}') + [ -z "\$src" ] && src="\$PKG" + local i c s r + i=\$(cat "\$LOG/install.result" 2>/dev/null || echo "FAIL") + c=\$(cat "\$LOG/command.result" 2>/dev/null || echo "SKIP") + s=\$(cat "\$LOG/service.result" 2>/dev/null || echo "SKIP") + r=\$(cat "\$LOG/remove.result" 2>/dev/null || echo "SKIP") + echo -e "\${src}\t\${PKG}\t\${i}\t\${c}\t\${s}\t\${r}" > "\$LOG/result.txt" +} + +# 注入现有库函数 +$install_func +$remove_func +$elf_func +$cmd_func + + +PKG="$pkg" +LOG="/logs" + +# ---------- 安装 ---------- +msg "[1/4] 安装 \$PKG" +{ + + echo "=== \$(date) 开始安装 \$PKG ===" + if test_single_rpm_install '' "\$PKG" "\$LOG/pkg"; then + echo "=== 安装成功 ===" + echo "PASS" > "\$LOG/install.result" + else + echo "=== 安装失败 ===" + echo "FAIL" > "\$LOG/install.result" + echo "SKIP" > "\$LOG/command.result" + echo "SKIP" > "\$LOG/service.result" + echo "SKIP" > "\$LOG/remove.result" + write_result + exit 0 + fi + + +} >"\$LOG/install.log" 2>&1 +cat "\$LOG/result.txt" + +msg "安装成功" + +# ---------- 命令测试 ---------- +msg "[2/4] 命令测试" +if [ -f "/whitelist" ] && grep -q "^\$PKG" "/whitelist" 2>/dev/null; then + echo "SKIP" > "\$LOG/command.result" + msg "桌面白名单,跳过" +else + { + echo "=== \$(date) 开始命令测试 \$PKG ===" + rpm -ql "\$PKG" 2>/dev/null | grep -v '\.so$' | grep -v '\.so\.' > "\$LOG/all_files.txt" + get_elf_executable "\$LOG/all_files.txt" "\$LOG/elf.txt" + + if [ -s "\$LOG/elf.txt" ]; then + echo "ELF文件数: \$(wc -l < \$LOG/elf.txt)" + if test_command_execute "\$LOG" "\$LOG/elf.txt"; then + echo "=== 命令测试通过 ===" + echo "PASS" > "\$LOG/command.result" + else + echo "=== 命令测试失败 ===" + echo "FAIL" > "\$LOG/command.result" + fi + else + echo "无ELF可执行文件" + echo "SKIP" > "\$LOG/command.result" + fi + } >"\$LOG/command.log" 2>&1 + if [ "\$(cat \$LOG/command.result)" = "PASS" ]; then + msg "命令测试通过" + elif [ "\$(cat \$LOG/command.result)" = "FAIL" ]; then + msg "命令测试失败" + else + msg "无ELF可执行文件,跳过" + fi +fi + +# ---------- 服务测试 ---------- +msg "[3/4] 服务测试" +if [ -f "/servicelist" ] && grep -q "^\$PKG" "/servicelist" 2>/dev/null; then + echo "SKIP" > "\$LOG/service.result" + msg "服务白名单,跳过" +else + { + SERVICES=\$(rpm -ql "\$PKG" 2>/dev/null | grep -E '\.(service|socket|target|timer)$' | grep -v '@' | awk -F/ '{print \$NF}') + if [ -z "\$SERVICES" ]; then + echo "无服务文件" + echo "SKIP" > "\$LOG/service.result" + else + echo "服务列表: \$SERVICES" + SVC_FAIL=0 + SVC_SKIP_REASON="" + + for s in \$SERVICES; do + case "\$s" in + systemd-*|initrd-*|anaconda-*|*@*|*.target) + echo "跳过系统服务: \$s" + continue ;; + esac + + echo "--- 服务: \$s ---" + timeout 15 systemctl start "\$s" >"\$LOG/\${s}.start" 2>&1 + start_exit=\$? + + if [ \$start_exit -ne 0 ]; then + # 检查是否是环境限制(非包缺陷) + start_err=\$(cat "\$LOG/\${s}.start" 2>/dev/null | head -3 | tr '\n' ' ') + + if echo "\$start_err" | grep -Eiq 'unit.*not found|failed because the control process exited|device not found \ + |no such device|operation not permitted|permission denied|cannot access|refuse manual start|dbus.*not available \ + |hardware.*not available|kernel module|no such file or directory'; then + # 环境限制,标记整个服务测试为 SKIP + SVC_SKIP_REASON="容器环境限制: \$start_err" + echo "环境限制跳过: \$start_err" + SVC_FAIL=-1 + break + else + echo "服务启动失败: \$start_err" + SVC_FAIL=\$((SVC_FAIL+1)) + fi + fi + + # 检查状态(仅在启动成功时) + if [ \$SVC_FAIL -ge 0 ]; then + systemctl is-active "\$s" >/dev/null 2>&1 || SVC_FAIL=\$((SVC_FAIL+1)) + timeout 10 systemctl stop "\$s" >/dev/null 2>&1 || true + fi + done + + if [ \$SVC_FAIL -eq -1 ]; then + # 环境限制导致的跳过 + echo "=== 服务测试跳过(环境限制)===" + echo "SKIP" > "\$LOG/service.result" + echo "\$SVC_SKIP_REASON" > "\$LOG/service.skip_reason" + elif [ \$SVC_FAIL -eq 0 ]; then + echo "=== 服务测试通过 ===" + echo "PASS" > "\$LOG/service.result" + else + echo "=== 服务测试失败 ===" + echo "FAIL" > "\$LOG/service.result" + fi + fi + } >"\$LOG/service.log" 2>&1 + + if [ "\$(cat \$LOG/service.result)" = "PASS" ]; then + msg "服务测试通过" + elif [ "\$(cat \$LOG/service.result)" = "SKIP" ]; then + if [ -f "\$LOG/service.skip_reason" ]; then + msg "服务测试跳过(环境限制)" + else + msg "服务测试跳过" + fi + else + msg "服务测试失败" + fi +fi + +# ---------- 卸载 ---------- +msg "[4/4] 卸载 \$PKG" +{ + echo "=== \$(date) 开始卸载 \$PKG ===" + if test_single_rpm_remove "\$PKG" "\$PKG" "\$LOG/pkg" "/dev/null"; then + echo "=== 卸载成功 ===" + echo "PASS" > "\$LOG/remove.result" + else + echo "=== 卸载失败 ===" + echo "FAIL" > "\$LOG/remove.result" + fi +} >"\$LOG/remove.log" 2>&1 +if [ "\$(cat \$LOG/remove.result)" = "PASS" ]; then + msg "卸载成功" +else + msg "卸载失败" +fi + +write_result +cat "\$LOG/result.txt" +INNER +} + +# ============================================ +# 状态转符号 +# ============================================ + +status_sym() { + case "$1" in + PASS) echo "✓" ;; + FAIL) echo "✗" ;; + SKIP) echo "-" ;; + *) echo "?" ;; + esac +} + +# ============================================ +# 进度读取 +# ============================================ +read_progress() { + COMPLETED=$(find "${STATUS_DIR}/completed" -maxdepth 1 -type f 2>/dev/null | wc -l) + FAILED=$(find "${STATUS_DIR}/failed" -maxdepth 1 -type f 2>/dev/null | wc -l) + SKIPPED=$(find "${STATUS_DIR}/skipped" -maxdepth 1 -type f 2>/dev/null | wc -l) +} + +# ============================================ +# 进度显示 +# ============================================ + +draw_screen() { + clear + echo "镜像: $BASE_IMAGE" + echo "并发度: $JOBS" + echo "结果: $RESULT_DIR" + echo "待测: $TOTAL" + echo "" + echo "========== 进度 ==========" + read_progress + echo "完成: $COMPLETED | 失败: $FAILED | 跳过: $SKIPPED | 进行中: $((TOTAL - COMPLETED - FAILED - SKIPPED))" + echo "" + echo "========== 进行中 ==========" + local i pkg status + for i in $(seq 1 "$JOBS"); do + if [ -f "${STATUS_DIR}/slot${i}" ]; then + pkg=$(cat "${STATUS_DIR}/slot${i}") + status=$(cat "${STATUS_DIR}/slot${i}.status" 2>/dev/null || echo "") + printf "[槽位%02d] %-20s %s\n" "$i" "$pkg" "$status" + else + printf "[槽位%02d] %-20s %s\n" "$i" "空闲" "" + fi + done + echo "" + echo "========== 最近完成 ==========" + local f pkg result inst cmd svc rem i_sym c_sym s_sym r_sym + + local files=() + for d in "${STATUS_DIR}/completed" "${STATUS_DIR}/failed" "${STATUS_DIR}/skipped"; do + [ -d "$d" ] || continue + while IFS= read -r -d '' f; do + files+=("$f") + done < <(find "$d" -maxdepth 1 -type f -print0 2>/dev/null) + done + + local sorted=() + if [ ${#files[@]} -gt 0 ]; then + while IFS= read -r f; do + sorted+=("$f") + done < <(printf '%s\n' "${files[@]}" | xargs -I{} stat -c '%Y %n' {} 2>/dev/null | sort -rn | head -5 | cut -d' ' -f2-) + fi + + for f in "${sorted[@]}"; do + pkg=$(basename "$f") + result=$(cat "${RESULT_DIR}/${pkg}/result.txt" 2>/dev/null || echo "$pkg\t$pkg\tFAIL\tSKIP\tSKIP\tSKIP") + IFS=$'\t' read -r _ _ inst cmd svc rem <<< "$result" + i_sym=$(status_sym "$inst") + c_sym=$(status_sym "$cmd") + s_sym=$(status_sym "$svc") + r_sym=$(status_sym "$rem") + printf "%-20s 安装:%s 命令:%s 服务:%s 卸载:%s\n" "$pkg" "$i_sym" "$c_sym" "$s_sym" "$r_sym" + done +} + +# ============================================ +# 分配/释放槽位 +# ============================================ + +alloc_slot() { + local pkg="$1" + local i + ( + flock 200 + echo "DEBUG: alloc_slot PID=$$ 获得锁 pkg=$pkg" >> /tmp/docker_debug.log + for i in $(seq 1 "$JOBS"); do + if [ ! -f "${STATUS_DIR}/slot${i}" ]; then + echo "$pkg" > "${STATUS_DIR}/slot${i}" + echo "[启动中]" > "${STATUS_DIR}/slot${i}.status" + echo "$i" + exit 0 + fi + done + echo "" + exit 1 + ) 200>"${STATUS_DIR}/.alloc.lock" +} + +free_slot() { + local slot="$1" + echo "DEBUG: free_slot PID=$$ 释放槽位${slot} pkg=$(cat "${STATUS_DIR}/slot${slot}" 2>/dev/null || echo '空')" >> /tmp/docker_debug.log + rm -f "${STATUS_DIR}/slot${slot}" "${STATUS_DIR}/slot${slot}.status" +} + +update_slot() { + local slot="$1" + local status="$2" + echo "$status" > "${STATUS_DIR}/slot${slot}.status" +} + +# ============================================ +# 主控 +# ============================================ + +test_single_pkg() { + echo "DEBUG: test_single_pkg 开始 PID=$$ pkg=$1" >> /tmp/docker_debug.log + local pkg="$1" + local pkg_dir="${RESULT_DIR}/${pkg}" + + mkdir -p "$pkg_dir" + + # 分配槽位(修复:确保一定拿到有效槽位) + local slot + while true; do + slot=$(alloc_slot "$pkg") + [ -n "$slot" ] && break + sleep 0.5 + done + + update_slot "$slot" "[容器启动] [= ]" + + local cname + cname=$(container_start "$pkg" "$pkg_dir") || { + echo -e "$pkg\t$pkg\tFAIL\tSKIP\tSKIP\tSKIP" > "$pkg_dir/result.txt" + touch "${STATUS_DIR}/failed/${pkg}" 2>/dev/null || \ + echo "ERROR: 无法标记失败状态 ${pkg}" >> /tmp/docker_debug.log + free_slot "$slot" + return 1 + } + + update_slot "$slot" "[测试中...] [======== ]" + + local script + script=$(generate_container_script "$pkg") + + #容器超时处理 360s + timeout 360 docker exec "$cname" bash -c "$script" > "$pkg_dir/exec.raw" 2>&1 + exec_exit=$? + + if [ "$exec_exit" -eq 124 ]; then + echo "容器执行超时 (360秒),强制停止容器" + docker kill "$cname" 2>/dev/null + echo -e "$pkg\t$pkg\tFAIL\tSKIP\tSKIP\tSKIP" > "$pkg_dir/result.txt" + cleanup_container "$cname" + else + grep -vE '^[0-9]{2}:[0-9]{2}:[0-9]{2} [A-Z]{3} [0-9]+ ' "$pkg_dir/exec.raw" > "$pkg_dir/exec.log" || true +fi + + + # docker exec "$cname" bash -c "$script" > "$pkg_dir/exec.raw" 2>&1 + # grep -vE '^[0-9]{2}:[0-9]{2}:[0-9]{2} [A-Z]{3} [0-9]+ ' "$pkg_dir/exec.raw" > "$pkg_dir/exec.log" || true + + + cleanup_container "$cname" + + # 读取结果 + local result + result=$(cat "$pkg_dir/result.txt" 2>/dev/null || echo "$pkg\t$pkg\tFAIL\tSKIP\tSKIP\tSKIP") + local src inst cmd svc rem + IFS=$'\t' read -r src pkg inst cmd svc rem <<< "$result" + +# # 更新计数 +# # 状态目录判定(只影响进度显示) +# if [ "$inst" = "FAIL" ]; then +# status_dir="failed" +# elif [ "$cmd" = "FAIL" ] || [ "$svc" = "FAIL" ] || [ "$rem" = "FAIL" ]; then +# status_dir="failed" +# elif [ "$rem" = "SKIP" ] && grep -qi "protected" "${pkg_dir}/remove.log" 2>/dev/null; then +# # protected 包:卸载被跳过,整个包算"跳过" +# status_dir="skipped" +# elif [ "$inst" = "PASS" ] && [ "$rem" = "PASS" ]; then +# # 安装通过 + 卸载通过 = 正常完成(cmd和svc的SKIP是预期行为) +# status_dir="completed" +# else +# # 兜底:其他情况算完成 +# status_dir="completed" +# fi + + # # 更新计数 + # # 状态目录判定(同步 generate_report 的逻辑,确保进度与最终报告对账一致) + # if [ "$inst" = "FAIL" ]; then + # status_dir="failed" + # elif [ "$cmd" = "FAIL" ] || [ "$svc" = "FAIL" ] || [ "$rem" = "FAIL" ]; then + # status_dir="failed" + # elif [ "$rem" = "SKIP" ] && grep -qi "protected" "${pkg_dir}/remove.log" 2>/dev/null; then + # # protected 包:卸载被跳过,整个包算"跳过" + # status_dir="skipped" + # elif [ "$svc" = "SKIP" ] && [ -f "${pkg_dir}/service.skip_reason" ]; then + # # 服务因容器环境限制被跳过(有 skip_reason),算作"跳过" + # status_dir="skipped" + # elif [ "$cmd" = "SKIP" ] && [ -n "$DESKTOP_WHITE" ] && grep -q "^$pkg" "$DESKTOP_WHITE" 2>/dev/null; then + # # 桌面白名单包:跳过了命令测试,算作"跳过" + # status_dir="skipped" + # elif [ "$svc" = "SKIP" ] && [ -n "$SERVICE_WHITE" ] && grep -q "^$pkg" "$SERVICE_WHITE" 2>/dev/null; then + # # 含服务包且在服务白名单内:跳过了服务启动,算作"跳过" + # status_dir="skipped" + # elif [ "$inst" = "PASS" ] && [ "$rem" = "PASS" ]; then + # # 安装通过 + 卸载通过 = 正常完成 + # status_dir="completed" + # else + # # 兜底:其他情况算完成 + # status_dir="completed" + # fi + + local status_dir + status_dir=$(determine_status "$pkg" "$inst" "$cmd" "$svc" "$rem") + + # 确保状态目录存在 + mkdir -p "${STATUS_DIR}/${status_dir}" 2>/dev/null + + # 原子操作:先写状态,再释放槽位 + if touch "${STATUS_DIR}/${status_dir}/${pkg}" 2>/dev/null; then + : # 成功 + else + # 补救:写入备用标记 + echo "${status_dir}" > "${pkg_dir}/.status" 2>/dev/null + echo "ERROR: 无法 touch 状态文件 ${pkg}" >> /tmp/docker_debug.log + fi + + echo "$(date '+%H:%M:%S') DEBUG: $pkg 准备释放槽位 $slot" >> /tmp/docker_debug.log + free_slot "$slot" + echo "$(date '+%H:%M:%S') DEBUG: $pkg 已释放槽位 $slot" >> /tmp/docker_debug.log + +} + +# ============================================ +# 从日志提取失败原因 +# ============================================ + +get_fail_reason() { + local pkg="$1" + local type="$2" + local log_dir="${RESULT_DIR}/${pkg}" + local reason="未知原因" + + # 调试 + echo "DEBUG: get_fail_reason pkg=$pkg type=$type log_dir=$log_dir" >> /tmp/docker_debug.log + +case "$type" in + install) + if [ -f "${log_dir}/install.log" ]; then + # 排除 result.txt 格式的行(制表符分隔的 6 列) + reason=$(grep -Ei "(error|fail|conflict|cannot|unable|timeout)" "${log_dir}/install.log" | tail -1) + reason=$(echo "$reason" | sed -E 's/^[0-9]{2}:[0-9]{2}:[0-9]{2} //') + [ -z "$reason" ] && reason="yum install 返回非0" + fi + ;; + + command) + if [ -f "${log_dir}/command.log" ]; then + reason=$(grep -E "exit code|command not found|segmentation fault|fail" "${log_dir}/command.log" | tail -1) + reason=$(echo "$reason" | sed -E 's/^[0-9]{2}:[0-9]{2}:[0-9]{2} //') + [ -z "$reason" ] && reason="命令执行失败" + fi + ;; + + service) + # 先检查是否是环境限制导致的 SKIP + if [ -f "${log_dir}/service.skip_reason" ]; then + reason=$(cat "${log_dir}/service.skip_reason") + # 截断过长的原因 + [ ${#reason} -gt 100 ] && reason="${reason:0:97}..." + echo "$reason" + return 0 + fi + + local svc_log + svc_log=$(find "${log_dir}" -name "*.start" | head -1) + if [ -n "$svc_log" ] && [ -f "$svc_log" ]; then + reason=$(cat "$svc_log" | head -3 | tr '\n' ';') + [ -z "$reason" ] && reason="systemctl start 失败" + fi + ;; + + remove) + if [ -f "${log_dir}/remove.log" ]; then + reason=$(grep -Ei "(protected|can.t be removed|essential|needed by|dependency|error|fail)" "${log_dir}/remove.log" | tail -1) + reason=$(echo "$reason" | sed -E 's/^[0-9]{2}:[0-9]{2}:[0-9]{2} //') + [ -z "$reason" ] && reason="yum remove 返回非0" + fi + ;; + esac + + reason=$(echo "$reason" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | tr -d '\n\r') + [ ${#reason} -gt 100 ] && reason="${reason:0:97}..." + + echo "$reason" +} + +# ============================================ +# 报告生成 +# ============================================ + +generate_report() { + local report_file="${RESULT_DIR}/report.txt" + local all_results="${RESULT_DIR}/all_results.txt" + local timestamp + timestamp=$(date '+%Y-%m-%d %H:%M:%S') + + # 重建 all_results,确保每个包只出现一次 + echo -e "# source-package\tbinary_package\tinstall\tcommand\tservice\tremove" > "$all_results" + + local d pkg_name + for d in "$RESULT_DIR"/*/; do + pkg_name=$(basename "$d") + [ "$pkg_name" = ".status" ] && continue + [ -f "${d}result.txt" ] && cat "${d}result.txt" >> "$all_results" + done + + local total=0 + local pass=0 + local fail_install=0 fail_cmd=0 fail_svc=0 fail_remove=0 + local skip=0 + + while IFS=$'\t' read -r src pkg inst cmd svc rem; do + [ -z "$pkg" ] && continue + [ "$pkg" = "binary_package" ] && continue + [[ "$src" == \#* ]] && continue + + total=$((total + 1)) + + # # 安装失败 + # if [ "$inst" = "FAIL" ]; then + # fail_install=$((fail_install + 1)) + # continue + # fi + + # # 命令失败 + # if [ "$cmd" = "FAIL" ]; then + # fail_cmd=$((fail_cmd + 1)) + # continue + # fi + + # # 服务失败(真正的包缺陷) + # if [ "$svc" = "FAIL" ]; then + # fail_svc=$((fail_svc + 1)) + # continue + # fi + + # # 卸载失败 + # if [ "$rem" = "FAIL" ]; then + # fail_remove=$((fail_remove + 1)) + # continue + # fi + + # # 跳过项判定(优先级高于通过) + # # 1. 桌面白名单包(cmd=SKIP 且在白名单) + # if [ "$cmd" = "SKIP" ] && grep -q "^$pkg" "$DESKTOP_WHITE" 2>/dev/null; then + # skip=$((skip + 1)) + # continue + # fi + + # # 2. 服务白名单包(svc=SKIP 且在白名单) + # if [ "$svc" = "SKIP" ] && grep -q "^$pkg" "$SERVICE_WHITE" 2>/dev/null; then + # skip=$((skip + 1)) + # continue + # fi + + # # 3. 环境限制导致的服务跳过(svc=SKIP 且有 skip_reason) + # if [ "$svc" = "SKIP" ] && [ -f "${RESULT_DIR}/${pkg}/service.skip_reason" ]; then + # skip=$((skip + 1)) + # continue + # fi + + # # 4. protected包(rem=SKIP) + # if [ "$rem" = "SKIP" ]; then + # skip=$((skip + 1)) + # continue + # fi + + # # # 5. 其他 SKIP(无 ELF、无服务等正常跳过) + # # if [ "$cmd" = "SKIP" ] || [ "$svc" = "SKIP" ] || [ "$rem" = "SKIP" ]; then + # # skip=$((skip + 1)) + # # continue + # # fi + + # # 通过 + # pass=$((pass + 1)) + + local st + st=$(determine_status "$pkg" "$inst" "$cmd" "$svc" "$rem") + + case "$st" in + failed) + if [ "$inst" = "FAIL" ]; then + fail_install=$((fail_install + 1)) + elif [ "$cmd" = "FAIL" ]; then + fail_cmd=$((fail_cmd + 1)) + elif [ "$svc" = "FAIL" ]; then + fail_svc=$((fail_svc + 1)) + elif [ "$rem" = "FAIL" ]; then + fail_remove=$((fail_remove + 1)) + fi + ;; + skipped) + skip=$((skip + 1)) + ;; + completed) + pass=$((pass + 1)) + ;; + esac + + done < "$all_results" + + local fail_total=$((fail_install + fail_cmd + fail_svc + fail_remove)) + local calculated=$((pass + fail_total + skip)) + + { + echo "========== 测试失败汇总 (${timestamp}) ==========" + echo "环境: docker | 并发度: ${JOBS} | 待测包总数: ${total}" + echo "镜像: ${BASE_IMAGE} (init: /sbin/init, privileged: yes)" + echo "通过: ${pass} | 失败: ${fail_total} | 跳过: ${skip}" + echo "" + + echo "【安装失败】(${fail_install})" + if [ "$fail_install" -eq 0 ]; then + echo "(无)" + else + # awk -F'\t' 'NR>1 && $3=="FAIL"' "$all_results" | while IFS=$'\t' read -r src pkg _ _ _ _; do + # local reason + # reason=$(get_fail_reason "$pkg" "install") + # echo "${pkg} 原因: ${reason}" + # done + awk -F'\t' 'NR>1 && $3=="FAIL" {print $2}' "$all_results" | while read -r pkg; do + reason=$(get_fail_reason "$pkg" "install") + echo "${pkg} 原因: ${reason}" + done + fi + echo "" + + echo "【命令测试失败】(${fail_cmd})" + if [ "$fail_cmd" -eq 0 ]; then + echo "(无)" + else + awk -F'\t' 'NR>1 && $3=="PASS" && $4=="FAIL"' "$all_results" | while IFS=$'\t' read -r src pkg _ _ _ _; do + local reason + reason=$(get_fail_reason "$pkg" "command") + echo "${pkg} 原因: ${reason}" + done + fi + echo "" + + echo "【服务测试失败】(${fail_svc})" + if [ "$fail_svc" -eq 0 ]; then + echo "(无)" + else + awk -F'\t' 'NR>1 && $3=="PASS" && $5=="FAIL"' "$all_results" | while IFS=$'\t' read -r src pkg _ _ _ _; do + local reason + reason=$(get_fail_reason "$pkg" "service") + echo "${pkg} 原因: ${reason}" + done + fi + echo "" + + echo "【卸载失败】(${fail_remove})" + if [ "$fail_remove" -eq 0 ]; then + echo "(无)" + else + awk -F'\t' 'NR>1 && $3=="PASS" && $6=="FAIL"' "$all_results" | while IFS=$'\t' read -r src pkg _ _ _ _; do + local reason + reason=$(get_fail_reason "$pkg" "remove") + echo "${pkg} 原因: ${reason}" + done + fi + echo "" + + echo "【跳过项】(${skip})" + if [ "$skip" -eq 0 ]; then + echo "(无)" + else + while IFS=$'\t' read -r src pkg inst cmd svc rem; do + [ -z "$pkg" ] && continue + [ "$pkg" = "binary_package" ] && continue + [[ "$src" == \#* ]] && continue + + local detail="" + + # 安装失败的已经 continue 了,这里只处理非安装失败 + + if [ "$cmd" = "SKIP" ] && grep -q "^$pkg" "$DESKTOP_WHITE" 2>/dev/null; then + detail="桌面白名单, 跳过命令测试" + echo "${pkg} (${detail})" + elif [ "$svc" = "SKIP" ] && grep -q "^$pkg" "$SERVICE_WHITE" 2>/dev/null; then + detail="含服务包, 需特殊配置, 跳过服务启动" + echo "${pkg} (${detail})" + elif [ "$svc" = "SKIP" ] && [ -f "${RESULT_DIR}/${pkg}/service.skip_reason" ]; then + # 环境限制导致的服务跳过 + local skip_reason + skip_reason=$(cat "${RESULT_DIR}/${pkg}/service.skip_reason" 2>/dev/null | head -1) + [ ${#skip_reason} -gt 60 ] && skip_reason="${skip_reason:0:57}..." + detail="容器环境限制, 跳过服务测试: ${skip_reason}" + echo "${pkg} (${detail})" + fi + done < "$all_results" + fi + echo "" + + echo "=======================================================" + + } > "$report_file" + + cat "$report_file" + + # 校验 + if [ "$calculated" -ne "$total" ]; then + echo "警告: 数学校验失败! 总计=${total}, 计算=${calculated} (pass=${pass} + fail=${fail_total} + skip=${skip})" + else + echo "数学校验通过: ${pass} + ${fail_total} + ${skip} = ${total}" + fi +} +# ============================================ +# CPU 核数 +# ============================================ + +get_cpu_cores() { + nproc 2>/dev/null || grep -c ^processor /proc/cpuinfo 2>/dev/null || echo 4 +} + +# ============================================ +# 入口 +# ============================================ + + +main() { + > /tmp/docker_debug.log + echo "=== MAIN_START PID=$$ PPID=$PPID 时间=$(date '+%H:%M:%S.%N') 参数=$* ===" >> /tmp/docker_debug.log + PKG_LIST="${1:-}" + DESKTOP_WHITE="${2:-}" + SERVICE_WHITE="${3:-}" + JOBS="${4:-$(get_cpu_cores)}" + + # 处理 -j N 选项 + shift 3 # 跳过前三个固定参数 + while [ $# -gt 0 ]; do + case "$1" in + -j) + [ -n "$2" ] && [[ "$2" =~ ^[0-9]+$ ]] || err "-j 参数需要跟一个正整数" + JOBS="$2" + shift 2 + ;; + *) + err "未知参数: $1" + ;; + esac + done + + [ -f "$PKG_LIST" ] || err "包列表不存在: $PKG_LIST" + + cleanup_on_exit() { + echo "" + echo "收到中断信号,正在清理..." + rm -f /tmp/docker-test/.status/.running 2>/dev/null + local p + for p in $(jobs -p 2>/dev/null); do + kill -9 "$p" 2>/dev/null + done + docker ps -aq --filter "name=test_" 2>/dev/null | \ + xargs -r docker rm -f 2>/dev/null + echo "清理完成" + exit 130 + } + trap cleanup_on_exit SIGINT SIGTERM + + RESULT_DIR="${RESULT_BASE}-$(date +%Y%m%d-%H%M%S)" + mkdir -p "$RESULT_DIR" + + STATUS_DIR="${RESULT_BASE}/.status" + rm -rf "$STATUS_DIR" 2>/dev/null + mkdir -p "$STATUS_DIR" "$STATUS_DIR/completed" "$STATUS_DIR/failed" "$STATUS_DIR/skipped" + touch "${STATUS_DIR}/.running" + + local -a pkgs=() + local -a unique_pkgs=() + local -A seen_pkgs=() + + while read -r p; do + [ -z "$p" ] && continue + [ "${p:0:1}" = "#" ] && continue + pkgs+=("$p") + if [ -z "${seen_pkgs[$p]}" ]; then + seen_pkgs[$p]=1 + unique_pkgs+=("$p") + else + msg "警告: 包 $p 重复出现,跳过重复项" + fi + done < "$PKG_LIST" + + pkgs=("${unique_pkgs[@]}") + TOTAL=${#pkgs[@]} + echo "DEBUG: 包数量=$TOTAL 内容=[${pkgs[*]}]" >> /tmp/docker_debug.log + + msg "镜像: $BASE_IMAGE" + msg "并发度: $JOBS" + msg "结果: $RESULT_DIR" + msg "待测: $TOTAL" + + # 启动后台刷新 + ( + while [ -f "${STATUS_DIR}/.running" ]; do + draw_screen + sleep 3 + done + ) & + DRAW_PID=$! + + # ========== 启动所有测试(修复:控制并发数 = 槽位数)========== + local -a test_pids=() + local pkg + local running=0 + + for pkg in "${pkgs[@]}"; do + + #如果已有 JOBS 个进程在运行,等待一个完成再启动新的 + while [ "$running" -ge "$JOBS" ]; do + wait -n 2>/dev/null + running=$((running - 1)) + done + + test_single_pkg "$pkg" & + test_pids+=($!) + running=$((running + 1)) + done + + # 等待剩余所有进程完成 + echo "DEBUG: 等待 ${#test_pids[@]} 个测试任务完成..." >> /tmp/docker_debug.log + while [ "$running" -gt 0 ]; do + wait -n 2>/dev/null + running=$((running - 1)) + done + echo "DEBUG: 所有测试任务已完成" >> /tmp/docker_debug.log + + # 让 draw_screen 退出 + rm -f "${STATUS_DIR}/.running" + kill $DRAW_PID 2>/dev/null + wait $DRAW_PID 2>/dev/null + + # 最终刷新和报告 + draw_screen + echo "" + generate_report +} + +main "$@" \ No newline at end of file