Files
rond-with-you/scripts/verify.sh
T

183 lines
7.2 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.
#!/usr/bin/env bash
#
# 一键验收:把「必须带六个环境变量、且必须 0 skip」这条纪律固化下来。
#
# ./scripts/verify.sh # 全量验收(默认,约 8 分钟)
# ./scripts/verify.sh --fast # 只做 gofmt / vet / build(约 20 秒)
# ./scripts/verify.sh --race # 全量 + -race(更慢,需 mingw 在 PATH)
# ./scripts/verify.sh --cover # 全量 + 覆盖率(跨包口径,见下)
# ./scripts/verify.sh --smoke # 只跑关键页冒烟(需站点已在 --base 上运行)
# ./scripts/verify.sh --all # 全量 + 冒烟
# ./scripts/verify.sh --pkg ./internal/web/ --run 'TestMapPreview' # 定点跑
#
# 退出码:0 = 全绿且 0 skip;非 0 = 有失败或有跳过。
set -u -o pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT"
ABS="$ROOT"
command -v pwd >/dev/null && ABS="$(pwd -W 2>/dev/null || pwd)"
CONF="${ROND_TEST_CONF:-conf/app-test.ini}"
ROND_SRC="${ROND_SRC:-$ABS/data/20260914_151649.rondbackup}"
FOG_SRC="${FOG_SRC:-$ABS/data/Snapshot-20260914T154839+0800.fwss}"
OUT="$ABS/storage/.verify"
CLEAN_OUT="$OUT/acceptance-clean.rondbackup"
LOG="$OUT/verify.log"
COVOUT="$OUT/cover.out"
FAST=0; RACE=0; COVER=0; SMOKE=0; SMOKE_ONLY=0; PKGS="./..."; RUN=""
while [ $# -gt 0 ]; do
case "$1" in
--fast) FAST=1 ;;
--race) RACE=1 ;;
--cover) COVER=1 ;;
--smoke) SMOKE=1; SMOKE_ONLY=1 ;;
--all) SMOKE=1 ;;
--pkg) PKGS="${2:?--pkg 后面要给包路径}"; shift ;;
--run) RUN="${2:?--run 后面要给正则}"; shift ;;
*) echo "未知参数:$1" >&2; exit 2 ;;
esac
shift
done
fail() { echo "✗ $*" >&2; exit 1; }
ok() { echo "✓ $*"; }
echo "== 仓库:$ROOT"
# 冒烟单独跑:它要一个真在跑的站点(浏览器口径),跟单元测试是两回事
run_smoke() {
local py="${ROND_PY:-python}"
command -v "$py" >/dev/null 2>&1 || py="${ROND_PY:-python3}"
command -v "$py" >/dev/null 2>&1 || fail "找不到 python(可用 ROND_PY 指定)"
echo "== 冒烟:关键页渲染(站长浏览器 + 访客 HTTP/浏览器)"
# 路径必须给 Windows 形式:这里传的是 Git Bash 的 $ROOT(/c/...),
# Windows 版 python 会把它拼成 C:\c\... 然后报 No such file
"$py" "$ABS/scripts/smoke.py" --base "${ROND_BASE:-http://localhost:8080}" \
--shot-dir "$OUT/smoke" || fail "关键页冒烟"
ok "关键页冒烟"
}
if [ "$FAST" = 1 ]; then
unformatted="$(gofmt -l internal/ main.go 2>&1)"
[ -n "$unformatted" ] && fail "gofmt 不干净(注意 CRLF 会假警):$(echo "$unformatted" | tr '\n' ' ')"
ok "gofmt"
go vet ./... || fail "go vet"
ok "go vet"
go build ./... || fail "go build"
ok "go build"
exit 0
fi
if [ "$SMOKE_ONLY" = 1 ]; then
run_smoke
exit 0
fi
# --- 六个环境变量 ---------------------------------------------------------------
[ -f "$CONF" ] || fail "找不到 $CONF"
[ -f "$ROND_SRC" ] || fail "找不到备份包 ROND_SRC=$ROND_SRC"
[ -f "$FOG_SRC" ] || fail "找不到迷雾快照 FOG_SRC=$FOG_SRC"
if [ -n "${ROND_DSN:-}" ]; then
DSN="$ROND_DSN"
else
DSN="$(grep -m1 -iE '^[[:space:]]*dsn[[:space:]]*=' "$CONF" | sed 's/^[^=]*=[[:space:]]*//' | tr -d '\r\n')"
fi
[ -n "$DSN" ] || fail "没能从 $CONF 取到 dsn(也不在环境里),别把口令写进脚本"
# 指到维护库 postgres 会多出「users 不存在」+ 一堆 stats 假 skip,直接拦住
DBNAME="$(printf '%s' "$DSN" | sed -E 's#^[^/]*//[^/]*/([^/?]+).*#\1#')"
case "$DBNAME" in
test|*test*) : ;;
*) fail "ROND_DSN 指向的库名是 '$DBNAME',不含 test —— 验收必须用应用测试库(rond_fog_test)" ;;
esac
export ROND_TEST_CONF="$CONF" ROND_SRC FOG_SRC ROND_DSN="$DSN"
export ROND_CLEAN_OUT="$CLEAN_OUT" ROND_EXPORT="$CLEAN_OUT"
mkdir -p "$OUT"
echo "== 库:$DBNAME 备份:$(basename "$ROND_SRC") / $(basename "$FOG_SRC")"
GOFLAGS_EXTRA=""
[ "$RACE" = 1 ] && GOFLAGS_EXTRA="$GOFLAGS_EXTRA -race"
# 必须 -coverpkg:-cover 只统计「测试文件所在包」自己的执行,凡是主要由别的包驱动的
# 代码(rond 的导出被 internal/web 驱动、geo/gpx/notes/votes 被 web/stats 驱动)
# 一律假报 0%,看着像没测,其实是量错了。
[ "$COVER" = 1 ] && GOFLAGS_EXTRA="$GOFLAGS_EXTRA -coverpkg=./internal/... -coverprofile=$COVOUT"
if [ "$RACE" = 1 ] && command -v gcc >/dev/null 2>&1; then :; else
for d in "$HOME/scoop/apps/mingw/current/bin"; do
[ -d "$d" ] && export PATH="$d:$PATH"
done
fi
if [ "$RACE" = 1 ] && ! command -v gcc >/dev/null 2>&1; then
fail "-race 需要 C 编译器:scoop install mingw,或把 mingw 的 bin 加进 PATH"
fi
# --- 第一段:先产出一份「纯净往返包」,第二段要读它 ------------------------------
if [ -z "$RUN" ] && [ "$PKGS" = "./..." ]; then
echo "== 第一段:导出纯净往返包"
if ! go test -count=1 -run '^TestMovementCleanExport$' -v ./internal/web/ 2>&1 | tee "$OUT/stage1.log" | grep -E '^(--- (PASS|FAIL|SKIP)|ok|FAIL)'; then
fail "第一段导出失败,见 $OUT/stage1.log"
fi
grep -q '^--- PASS' "$OUT/stage1.log" || fail "TestMovementCleanExport 没跑起来(被 skip 了?)"
ok "纯净往返包已生成:$(basename "$CLEAN_OUT")"
fi
# --- 第二段:全量(或定点)------------------------------------------------------
RUNARG=""
[ -n "$RUN" ] && RUNARG="-run $RUN"
echo "== 第二段:go test -count=1 -v $GOFLAGS_EXTRA $RUNARG $PKGS"
# shellcheck disable=SC2086
go test -count=1 -v $GOFLAGS_EXTRA -timeout 2400s $RUNARG $PKGS >"$LOG" 2>&1
RC=$?
PASS=$(grep -cE '^[[:space:]]*--- PASS' "$LOG" || true)
FAILN=$(grep -cE '^[[:space:]]*--- FAIL' "$LOG" || true)
SKIP=$(grep -cE '^[[:space:]]*--- SKIP' "$LOG" || true)
BADPKG=$(grep -c '^FAIL' "$LOG" || true)
echo "== 结果:PASS=$PASS FAIL=$FAILN SKIP=$SKIP 非ok包=$BADPKG"
echo "== 日志:$LOG"
if [ "$SKIP" != 0 ]; then
echo "-- 以下用例被跳过(=没跑。门控测试缺环境变量时 go test 依然回 ok,别当通过看)--"
grep -n -B1 -E '^[[:space:]]*--- SKIP' "$LOG" | head -40
fi
if [ "$FAILN" != 0 ] || [ "$BADPKG" != 0 ]; then
echo "-- 失败明细(前 40 行)--"
grep -n -B2 -E '^[[:space:]]*--- FAIL' "$LOG" | head -40
fi
if [ "$RC" != 0 ] || [ "$FAILN" != 0 ] || [ "$BADPKG" != 0 ]; then
fail "有失败"
fi
if [ "$SKIP" != 0 ]; then
fail "有 $SKIP 个用例被跳过 ⇒ 这次不算验收过"
fi
ok "全绿且 0 skip(PASS=$PASS)"
if [ "$COVER" = 1 ] && [ -s "$COVOUT" ]; then
echo "== 覆盖率(按包,跨包执行已计入)"
# 必须按 (文件,行段) 取 max 而不是求和:-coverpkg 下每个测试二进制都会给同一段代码
# 写一条记录,求和等于把语句数乘上二进制份数,每包都会看着只有 1/N。
awk 'NR>1 { key=$1; n=$(NF-1); c=$NF+0
if (!(key in seen)) seen[key]=c; else if (c>seen[key]) seen[key]=c
stmt[key]=n; split(key,a,":"); pkg=a[1]; sub(/\/[^/]+$/,"",pkg); pk[key]=pkg }
END { for (k in stmt) { p=pk[k]; tot[p]+=stmt[k]; if (seen[k]>0) cov[p]+=stmt[k] }
for (p in tot) printf " %-36s %5.1f%% %d/%d\n", p, 100*cov[p]/tot[p], cov[p], tot[p] }' \
"$COVOUT" | sort -k2 -n
echo -n " == 项目合计 "
go tool cover -func="$COVOUT" | tail -1
echo " 明细:go tool cover -html=$COVOUT"
fi
if [ "$SMOKE" = 1 ]; then
run_smoke
fi