2026-03-23 13:02:36 +08:00
|
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
|
#
|
|
|
|
|
|
# PRD-实现差距自动化检查脚本
|
|
|
|
|
|
# 生成可读的PRD差距报告,包含失败项和证据路径
|
|
|
|
|
|
#
|
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
|
|
ROOT_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
|
|
|
|
|
TMP_DIR="${ROOT_DIR}/tmp/prd-gap-report"
|
|
|
|
|
|
REPORT_FILE="${TMP_DIR}/prd-gap-report-$(date +%Y%m%d_%H%M%S).md"
|
|
|
|
|
|
JAVA_TMP_DIR="${TMP_DIR}/java"
|
|
|
|
|
|
JNA_TMP_DIR="${TMP_DIR}/jna"
|
|
|
|
|
|
PODMAN_SOCK_PATH="/run/user/$(id -u)/podman/podman.sock"
|
|
|
|
|
|
PODMAN_SOCK="unix://${PODMAN_SOCK_PATH}"
|
|
|
|
|
|
PODMAN_LOG="${TMP_DIR}/podman-service.log"
|
|
|
|
|
|
PODMAN_PID=""
|
|
|
|
|
|
|
|
|
|
|
|
# 颜色输出
|
|
|
|
|
|
RED='\033[0;31m'
|
|
|
|
|
|
GREEN='\033[0;32m'
|
|
|
|
|
|
YELLOW='\033[1;33m'
|
|
|
|
|
|
NC='\033[0m' # No Color
|
|
|
|
|
|
|
|
|
|
|
|
mkdir -p "${TMP_DIR}" "${JAVA_TMP_DIR}" "${JNA_TMP_DIR}"
|
|
|
|
|
|
|
|
|
|
|
|
cleanup() {
|
|
|
|
|
|
if [[ -n "${PODMAN_PID}" ]] && kill -0 "${PODMAN_PID}" >/dev/null 2>&1; then
|
|
|
|
|
|
kill "${PODMAN_PID}" >/dev/null 2>&1 || true
|
|
|
|
|
|
wait "${PODMAN_PID}" >/dev/null 2>&1 || true
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
trap cleanup EXIT
|
|
|
|
|
|
|
|
|
|
|
|
# 初始化Podman(如果可用)
|
|
|
|
|
|
init_podman() {
|
|
|
|
|
|
if ! command -v podman >/dev/null 2>&1; then
|
|
|
|
|
|
echo -e "${YELLOW}WARNING: podman 未安装,跳过容器测试${NC}" >&2
|
|
|
|
|
|
return 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
mkdir -p "$(dirname "${PODMAN_SOCK_PATH}")"
|
|
|
|
|
|
|
|
|
|
|
|
podman system service --time=0 "${PODMAN_SOCK}" > "${PODMAN_LOG}" 2>&1 &
|
|
|
|
|
|
PODMAN_PID=$!
|
|
|
|
|
|
|
|
|
|
|
|
for _ in {1..30}; do
|
|
|
|
|
|
if [[ -S "${PODMAN_SOCK_PATH}" ]] && podman --url "${PODMAN_SOCK}" info >/dev/null 2>&1; then
|
|
|
|
|
|
echo -e "${GREEN}Podman service 就绪${NC}"
|
|
|
|
|
|
return 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
sleep 1
|
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
|
|
echo -e "${RED}ERROR: podman service 未就绪${NC}" >&2
|
|
|
|
|
|
return 1
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# 写入报告头
|
|
|
|
|
|
write_report_header() {
|
|
|
|
|
|
cat > "${REPORT_FILE}" << 'EOF'
|
|
|
|
|
|
# PRD-实现差距报告
|
|
|
|
|
|
|
|
|
|
|
|
> 自动生成时间: TIMESTAMP
|
|
|
|
|
|
> 分支: BRANCH
|
|
|
|
|
|
> 提交: COMMIT
|
|
|
|
|
|
|
|
|
|
|
|
## 执行摘要
|
|
|
|
|
|
|
|
|
|
|
|
| 检查项 | 状态 | 证据路径 |
|
|
|
|
|
|
|--------|------|----------|
|
|
|
|
|
|
EOF
|
|
|
|
|
|
sed -i "s/TIMESTAMP/$(date '+%Y-%m-%d %H:%M:%S')/g" "${REPORT_FILE}"
|
|
|
|
|
|
sed -i "s/BRANCH/$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo 'unknown')/g" "${REPORT_FILE}"
|
|
|
|
|
|
sed -i "s/COMMIT/$(git rev-parse HEAD 2>/dev/null || echo 'unknown')/g" "${REPORT_FILE}"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# 添加检查结果到报告
|
|
|
|
|
|
add_check_result() {
|
|
|
|
|
|
local name="$1"
|
|
|
|
|
|
local status="$2"
|
|
|
|
|
|
local evidence="$3"
|
|
|
|
|
|
local details="$4"
|
|
|
|
|
|
|
|
|
|
|
|
local status_icon
|
|
|
|
|
|
if [[ "${status}" == "PASS" ]]; then
|
|
|
|
|
|
status_icon="✅"
|
|
|
|
|
|
elif [[ "${status}" == "FAIL" ]]; then
|
|
|
|
|
|
status_icon="❌"
|
|
|
|
|
|
else
|
|
|
|
|
|
status_icon="⚠️"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
cat >> "${REPORT_FILE}" << EOF |
|
|
|
|
|
|
|
|
|
|
|
|
| ${name} | ${status_icon} ${status} | ${evidence} |
|
|
|
|
|
|
|
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
|
|
if [[ -n "${details}" ]]; then
|
|
|
|
|
|
cat >> "${REPORT_FILE}" << EOF
|
|
|
|
|
|
<details>
|
|
|
|
|
|
<summary>详细信息</summary>
|
|
|
|
|
|
|
|
|
|
|
|
\`\`\`
|
|
|
|
|
|
${details}
|
|
|
|
|
|
\`\`\`
|
|
|
|
|
|
|
|
|
|
|
|
</details>
|
|
|
|
|
|
EOF
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# 运行单个测试类并捕获结果
|
|
|
|
|
|
run_test() {
|
|
|
|
|
|
local test_name="$1"
|
|
|
|
|
|
local test_class="$2"
|
|
|
|
|
|
# 清理test_class中的#和后续方法名,只保留类名作为文件路径
|
|
|
|
|
|
local clean_class="${test_class%%#*}"
|
|
|
|
|
|
local evidence_path="${TMP_DIR}/test-results/${clean_class}.txt"
|
|
|
|
|
|
|
|
|
|
|
|
mkdir -p "$(dirname "${evidence_path}")"
|
|
|
|
|
|
|
2026-03-26 15:59:53 +08:00
|
|
|
|
echo -e "\n${YELLOW}运行测试: ${test_name}${NC}" >&2
|
2026-03-23 13:02:36 +08:00
|
|
|
|
|
|
|
|
|
|
local start_time=$(date +%s)
|
|
|
|
|
|
|
|
|
|
|
|
if [[ -n "${PODMAN_SOCK}" ]] && [[ -S "${PODMAN_SOCK_PATH}" ]]; then
|
|
|
|
|
|
export DOCKER_HOST="${PODMAN_SOCK}"
|
|
|
|
|
|
fi
|
|
|
|
|
|
export TESTCONTAINERS_RYUK_DISABLED="true"
|
|
|
|
|
|
export JNA_TMPDIR="${JNA_TMP_DIR}"
|
|
|
|
|
|
export JAVA_IO_TMPDIR="${JAVA_TMP_DIR}"
|
|
|
|
|
|
|
2026-03-26 15:59:53 +08:00
|
|
|
|
# 使用临时文件捕获mvn输出,避免stdout被命令替换捕获
|
|
|
|
|
|
local mvn_output_file="${TMP_DIR}/mvn-output.tmp"
|
2026-03-23 13:02:36 +08:00
|
|
|
|
mvn -B test -Dtest="${test_class}" \
|
|
|
|
|
|
-Djna.tmpdir="${JNA_TMP_DIR}" \
|
|
|
|
|
|
-Djava.io.tmpdir="${JAVA_TMP_DIR}" \
|
|
|
|
|
|
-Dmigration.test.strict=true \
|
|
|
|
|
|
-Dsurefire.failIfNoSpecifiedTests=true \
|
2026-03-26 15:59:53 +08:00
|
|
|
|
> "${mvn_output_file}" 2>&1
|
|
|
|
|
|
local test_exit_code=$?
|
|
|
|
|
|
|
|
|
|
|
|
# tee复制到证据文件
|
|
|
|
|
|
tee "${evidence_path}" < "${mvn_output_file}" > /dev/null
|
|
|
|
|
|
rm -f "${mvn_output_file}"
|
2026-03-23 13:02:36 +08:00
|
|
|
|
|
|
|
|
|
|
local end_time=$(date +%s)
|
|
|
|
|
|
local duration=$((end_time - start_time))
|
|
|
|
|
|
|
|
|
|
|
|
local result
|
2026-03-26 15:59:53 +08:00
|
|
|
|
if [[ ${test_exit_code} -eq 0 ]]; then
|
2026-03-23 13:02:36 +08:00
|
|
|
|
result="PASS"
|
|
|
|
|
|
else
|
|
|
|
|
|
result="FAIL"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2026-03-26 15:59:53 +08:00
|
|
|
|
echo -e "${result}: ${test_name} (${duration}s)" >&2
|
2026-03-23 13:02:36 +08:00
|
|
|
|
|
2026-03-26 15:59:53 +08:00
|
|
|
|
# 返回退出码和证据路径,用冒号分隔
|
|
|
|
|
|
printf '%s:%s\n' "${test_exit_code}" "${evidence_path}"
|
2026-03-23 13:02:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# 主流程
|
|
|
|
|
|
main() {
|
|
|
|
|
|
echo -e "${GREEN}====== PRD-实现差距检查 ======${NC}"
|
|
|
|
|
|
echo "报告输出目录: ${TMP_DIR}"
|
|
|
|
|
|
|
|
|
|
|
|
# 初始化Podman
|
|
|
|
|
|
if init_podman; then
|
|
|
|
|
|
export DOCKER_HOST="${PODMAN_SOCK}"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# 生成报告头
|
|
|
|
|
|
write_report_header
|
|
|
|
|
|
|
|
|
|
|
|
# 定义要运行的PRD关键测试
|
|
|
|
|
|
declare -a TEST_CLASSES=(
|
|
|
|
|
|
"AuditLogImmutabilityIntegrationTest"
|
|
|
|
|
|
"PermissionCanonicalMigrationTest#shouldValidateCanonicalPermissionsAgainstBaseline"
|
|
|
|
|
|
"PermissionCanonicalMigrationTest#shouldHaveZeroLegacyPermissionCodes"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
local failed_count=0
|
|
|
|
|
|
local passed_count=0
|
|
|
|
|
|
|
|
|
|
|
|
for test_spec in "${TEST_CLASSES[@]}"; do
|
|
|
|
|
|
IFS='#' read -r test_class test_method <<< "${test_spec}"
|
2026-03-26 15:59:53 +08:00
|
|
|
|
local test_result
|
2026-03-23 13:02:36 +08:00
|
|
|
|
|
|
|
|
|
|
if [[ -n "${test_method}" ]]; then
|
2026-03-26 15:59:53 +08:00
|
|
|
|
test_result="$(run_test "${test_spec}" "${test_class}#${test_method}")"
|
2026-03-23 13:02:36 +08:00
|
|
|
|
else
|
2026-03-26 15:59:53 +08:00
|
|
|
|
test_result="$(run_test "${test_spec}" "${test_class}")"
|
2026-03-23 13:02:36 +08:00
|
|
|
|
fi
|
|
|
|
|
|
|
2026-03-26 15:59:53 +08:00
|
|
|
|
# 解析退出码和证据路径
|
|
|
|
|
|
local test_exit_code="${test_result%%:*}"
|
|
|
|
|
|
local evidence_path="${test_result#*:}"
|
|
|
|
|
|
|
|
|
|
|
|
if [[ ${test_exit_code} -eq 0 ]]; then
|
2026-03-23 13:02:36 +08:00
|
|
|
|
add_check_result "${test_spec}" "PASS" "${evidence_path}" ""
|
2026-03-26 15:59:53 +08:00
|
|
|
|
passed_count=$((passed_count + 1))
|
2026-03-23 13:02:36 +08:00
|
|
|
|
else
|
2026-03-26 15:59:53 +08:00
|
|
|
|
local details
|
|
|
|
|
|
details="$(tail -50 "${evidence_path}" 2>/dev/null || echo "无日志")"
|
2026-03-23 13:02:36 +08:00
|
|
|
|
add_check_result "${test_spec}" "FAIL" "${evidence_path}" "${details}"
|
2026-03-26 15:59:53 +08:00
|
|
|
|
failed_count=$((failed_count + 1))
|
2026-03-23 13:02:36 +08:00
|
|
|
|
fi
|
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
|
|
# 添加后端构建检查
|
|
|
|
|
|
echo -e "\n${YELLOW}运行后端构建检查${NC}"
|
|
|
|
|
|
local build_exit_code=0
|
|
|
|
|
|
local build_log="${TMP_DIR}/maven-build.txt"
|
|
|
|
|
|
|
|
|
|
|
|
mvn -B clean compile -DskipTests 2>&1 | tee "${build_log}" || build_exit_code=$?
|
|
|
|
|
|
|
|
|
|
|
|
if [[ ${build_exit_code} -eq 0 ]]; then
|
|
|
|
|
|
add_check_result "Maven构建" "PASS" "${build_log}" ""
|
2026-03-26 15:59:53 +08:00
|
|
|
|
passed_count=$((passed_count + 1))
|
2026-03-23 13:02:36 +08:00
|
|
|
|
else
|
|
|
|
|
|
local details=$(tail -50 "${build_log}" 2>/dev/null || echo "无日志")
|
|
|
|
|
|
add_check_result "Maven构建" "FAIL" "${build_log}" "${details}"
|
2026-03-26 15:59:53 +08:00
|
|
|
|
failed_count=$((failed_count + 1))
|
2026-03-23 13:02:36 +08:00
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# 生成总结
|
|
|
|
|
|
cat >> "${REPORT_FILE}" << EOF
|
|
|
|
|
|
|
|
|
|
|
|
## 总结
|
|
|
|
|
|
|
|
|
|
|
|
- 通过: ${passed_count}
|
|
|
|
|
|
- 失败: ${failed_count}
|
|
|
|
|
|
- 生成时间: $(date '+%Y-%m-%d %H:%M:%S')
|
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
|
|
echo -e "\n${GREEN}====== 检查完成 ======${NC}"
|
|
|
|
|
|
echo -e "通过: ${GREEN}${passed_count}${NC}"
|
|
|
|
|
|
echo -e "失败: ${RED}${failed_count}${NC}"
|
|
|
|
|
|
echo -e "报告: ${REPORT_FILE}"
|
|
|
|
|
|
|
|
|
|
|
|
if [[ ${failed_count} -gt 0 ]]; then
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
exit 0
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
main "$@"
|