Files
llm-intelligence/scripts/verify_phase6.sh
phamnazage-jpg 567d1f89ec feat(pipeline): enhance verification scripts and pipeline
- verify_phase6.sh: improve phase 6 verification logic
- report_utils.sh: update report generation utilities
- run_daily.sh: harden daily pipeline execution
- run_intel_pipeline.sh: improve intel pipeline runner
- run_real_pipeline.sh: improve real pipeline runner
- generate_daily_report.go: enhance daily report generation
2026-05-22 07:33:45 +08:00

237 lines
8.6 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/verify_common.sh"
DB_URL="${DATABASE_URL:-host=/var/run/postgresql dbname=llm_intelligence user=long sslmode=disable}"
SERVER_BIN="/tmp/llm_phase6_server"
SERVER_LOG="/tmp/llm_phase6_server.log"
SERVER_PORT="${PHASE6_PORT:-}"
SERVER_PID=""
cleanup() {
if [ -n "${SERVER_PID:-}" ] && kill -0 "$SERVER_PID" >/dev/null 2>&1; then
kill "$SERVER_PID" >/dev/null 2>&1 || true
wait "$SERVER_PID" >/dev/null 2>&1 || true
fi
rm -f "$SERVER_BIN"
}
trap cleanup EXIT
port_in_use() {
local port="$1"
(echo >"/dev/tcp/127.0.0.1/$port") >/dev/null 2>&1
}
reserve_server_port() {
if [ -n "${SERVER_PORT:-}" ]; then
return 0
fi
for candidate in $(seq 18080 18120); do
if ! port_in_use "$candidate"; then
SERVER_PORT="$candidate"
return 0
fi
done
return 1
}
start_server() {
DATABASE_URL="$DB_URL" PORT="$SERVER_PORT" "$SERVER_BIN" >"$SERVER_LOG" 2>&1 &
SERVER_PID=$!
for _ in $(seq 1 20); do
if ! kill -0 "$SERVER_PID" >/dev/null 2>&1; then
return 1
fi
if curl -fsS "http://127.0.0.1:${SERVER_PORT}/health" >/tmp/llm_phase6_health.out 2>/tmp/llm_phase6_health.err &&
grep -q '"status":"ok"' /tmp/llm_phase6_health.out; then
return 0
fi
sleep 0.5
done
return 1
}
last_nonempty_line() {
awk 'NF { line=$0 } END { print line }'
}
last_meaningful_failure_line() {
awk 'NF && $0 !~ /^exit status [0-9]+$/ { line=$0 } END { print line }'
}
extract_window_metric() {
local name="$1"
local payload="$2"
printf '%s\n' "$payload" | awk -v key="$name" '
$0 ~ key"=" {
for (i = 1; i <= NF; i++) {
split($i, parts, "=")
if (parts[1] == key) {
print parts[2]
exit
}
}
}
'
}
classify_window_failure() {
local payload="$1"
local precondition_missing external_provider_failure collector_runtime_failure unknown_failure
precondition_missing="$(extract_window_metric precondition_missing "$payload")"
external_provider_failure="$(extract_window_metric external_provider_failure "$payload")"
collector_runtime_failure="$(extract_window_metric collector_runtime_failure "$payload")"
unknown_failure="$(extract_window_metric unknown_failure "$payload")"
precondition_missing="${precondition_missing:-0}"
external_provider_failure="${external_provider_failure:-0}"
collector_runtime_failure="${collector_runtime_failure:-0}"
unknown_failure="${unknown_failure:-0}"
if [ "$precondition_missing" -gt 0 ] && [ "$external_provider_failure" -eq 0 ] && [ "$collector_runtime_failure" -eq 0 ] && [ "$unknown_failure" -eq 0 ]; then
echo "precondition_missing_only"
else
echo "mixed"
fi
}
run_live_pipeline_gate() {
local live_output live_rc live_tail
set +e
live_output="$(bash scripts/run_real_pipeline.sh 2>&1)"
live_rc=$?
set -e
printf '%s\n' "$live_output" >/tmp/llm_phase6_live_pipeline.out
live_tail="$(printf '%s\n' "$live_output" | last_meaningful_failure_line)"
if [ "$live_rc" -eq 0 ]; then
pass "live_run_result=PASS 真实采集并输出今日日报"
else
fail "live_run_result=FAIL 真实采集并输出今日日报 (${live_tail:-see /tmp/llm_phase6_live_pipeline.out})"
fi
}
run_importer_smoke_gate() {
local smoke_output smoke_rc smoke_tail
set +e
smoke_output="$(bash scripts/verify_importer_smoke.sh 2>&1)"
smoke_rc=$?
set -e
printf '%s\n' "$smoke_output"
printf '%s\n' "$smoke_output" >/tmp/llm_phase6_importer_smoke.out
if [ "$smoke_rc" -eq 0 ]; then
pass "importer_smoke_gate_result=PASS 新增导入器 smoke gate 通过"
return 0
fi
smoke_tail="$(printf '%s\n' "$smoke_output" | last_meaningful_failure_line)"
fail "importer_smoke_gate_result=FAIL 新增导入器 smoke gate 未通过 (${smoke_tail:-see /tmp/llm_phase6_importer_smoke.out})"
return 1
}
run_window_gate() {
local collector_window_output collector_window_rc window_failure_class
set +e
collector_window_output="$(bash scripts/collector_stats_window_audit.sh --db "$DB_URL" --limit 7 --assert-success-rate 95 2>&1)"
collector_window_rc=$?
set -e
echo "$collector_window_output"
if [ "$collector_window_rc" -eq 0 ]; then
pass "window_gate_result=PASS 最近 7 次采集成功率达到 95%(已输出分类摘要)"
return
fi
window_failure_class="$(classify_window_failure "$collector_window_output")"
if [ "$window_failure_class" = "precondition_missing_only" ]; then
fail "window_gate_result=FAIL 最近 7 次采集成功率达到 95%window_failure_class=precondition_missing_only环境纪律问题"
else
fail "window_gate_result=FAIL 最近 7 次采集成功率达到 95%window_failure_class=${window_failure_class}"
fi
}
echo "=== Phase 6 综合验收检查 ==="
check_shell "Phase 1~5 总门禁通过" "bash scripts/verify_pre_phase6.sh"
check_shell "全仓 Go 测试通过" "go test ./..."
check_shell "脚本级采集器单测通过" "bash scripts/test.sh"
if run_importer_smoke_gate; then
run_live_pipeline_gate
else
warn "live_run_result=SKIPPED 因 importer_smoke_gate_result=FAIL"
fi
check_shell "API Server 可构建" "go build -o /dev/null ./cmd/server"
check_shell "健康检查脚本通过" "DATABASE_URL='$DB_URL' bash healthcheck.sh"
check_shell "密钥未硬编码进源码" "grep -R -n 'sk-' cmd internal frontend/src scripts .github/workflows --include='*.go' --include='*.ts' --include='*.tsx' --include='*.sh' --include='*.yml' --include='*.yaml' --exclude='verify_phase6.sh' >/tmp/llm_phase6_secret_scan.out 2>/dev/null; test ! -s /tmp/llm_phase6_secret_scan.out"
run_window_gate
if go build -o "$SERVER_BIN" ./cmd/server >/tmp/llm_phase6_server_build.out 2>/tmp/llm_phase6_server_build.err; then
if reserve_server_port && start_server; then
pass "API /health 可用"
set +e
api_metrics="$(curl -sS -o /tmp/llm_phase6_models.json -w '%{http_code} %{time_total}' "http://127.0.0.1:${SERVER_PORT}/api/v1/models")"
api_rc=$?
set -e
if [ "$api_rc" -eq 0 ]; then
api_code="$(printf '%s' "$api_metrics" | awk '{print $1}')"
api_time="$(printf '%s' "$api_metrics" | awk '{print $2}')"
if [ "$api_code" = "200" ]; then
pass "API /api/v1/models 返回 200"
else
fail "API /api/v1/models 返回异常状态 (HTTP ${api_code:-unknown})"
fi
if awk "BEGIN { exit !($api_time < 0.5) }"; then
pass "API 响应 < 500ms (当前: ${api_time}s)"
else
fail "API 响应 >= 500ms (当前: ${api_time}s)"
fi
if grep -q '"data"' /tmp/llm_phase6_models.json; then
pass "API 返回模型数据载荷"
else
fail "API 返回体缺少 data 字段"
fi
else
fail "API /api/v1/models 请求失败"
fi
set +e
plan_metrics="$(curl -sS -o /tmp/llm_phase6_subscription_plans.json -w '%{http_code} %{time_total}' "http://127.0.0.1:${SERVER_PORT}/api/v1/subscription-plans")"
plan_rc=$?
set -e
if [ "$plan_rc" -eq 0 ]; then
plan_code="$(printf '%s' "$plan_metrics" | awk '{print $1}')"
if [ "$plan_code" = "200" ]; then
pass "API /api/v1/subscription-plans 返回 200"
else
fail "API /api/v1/subscription-plans 返回异常状态 (HTTP ${plan_code:-unknown})"
fi
if grep -q '"data"' /tmp/llm_phase6_subscription_plans.json; then
pass "API 返回套餐数据载荷"
else
fail "套餐 API 返回体缺少 data 字段"
fi
else
fail "API /api/v1/subscription-plans 请求失败"
fi
else
details="$(tr '\n' ' ' <"$SERVER_LOG" | sed 's/[[:space:]]\+/ /g' | sed 's/ $//')"
fail "API Server 启动失败 (${details:-no server log})"
fi
else
details="$(tr '\n' ' ' </tmp/llm_phase6_server_build.err | sed 's/[[:space:]]\+/ /g' | sed 's/ $//')"
fail "API Server 构建失败 (${details:-unknown build error})"
fi
check_shell "Phase 6 性能文档存在" "test -f docs/PERFORMANCE_TEST.md"
check_shell "前端已具备测试入口" "cd frontend && npm run test -- --run >/tmp/llm_phase6_frontend_test.out 2>/tmp/llm_phase6_frontend_test.err"
finish_phase