feat: add official full pull smoke

Emit runtime download progress and validation summaries for official sync.

Add repeatable real-network full pull smoke runbook and script.

Fixes #4

Fixes #6
This commit is contained in:
2026-07-14 00:48:59 +08:00
parent 2ff99ae9e3
commit 25c2c4d40f
15 changed files with 435 additions and 57 deletions
+184
View File
@@ -0,0 +1,184 @@
#!/usr/bin/env bash
set -Eeuo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
cd "${REPO_ROOT}"
timestamp="$(date -u +%Y%m%dT%H%M%SZ)"
SMOKE_ROOT="${BAT_SMOKE_ROOT:-/tmp/bat-official-smoke-${timestamp}}"
OUTPUT_DIR="${BAT_SMOKE_OUTPUT:-${SMOKE_ROOT}/resources}"
STATE_DIR="${BAT_SMOKE_STATE_DIR:-${SMOKE_ROOT}/state}"
REPORT_DIR="${BAT_SMOKE_REPORT_DIR:-${SMOKE_ROOT}/report}"
BAT_BIN="${BAT_BIN:-${REPO_ROOT}/target/release/bat}"
fail() {
printf 'error: %s\n' "$*" >&2
exit 1
}
real_output="$(realpath -m "${OUTPUT_DIR}")"
case "${real_output}" in
/tmp/*) ;;
*)
if [[ "${BAT_SMOKE_ALLOW_NON_TMP:-}" != "1" ]]; then
fail "BAT_SMOKE_OUTPUT must be under /tmp by default; set BAT_SMOKE_ALLOW_NON_TMP=1 only for an isolated test path"
fi
;;
esac
if [[ -e "${OUTPUT_DIR}" ]] && [[ -n "$(find "${OUTPUT_DIR}" -mindepth 1 -maxdepth 1 -print -quit 2>/dev/null)" ]]; then
fail "output directory is not empty: ${OUTPUT_DIR}"
fi
mkdir -p "${OUTPUT_DIR}" "${STATE_DIR}" "${REPORT_DIR}"
if [[ "${BAT_SMOKE_SKIP_BUILD:-}" != "1" ]]; then
cargo build --release -p bat-infrastructure --bin bat
fi
[[ -x "${BAT_BIN}" ]] || fail "bat binary is not executable: ${BAT_BIN}"
write_command_file() {
local name="$1"
shift
local command_file="${REPORT_DIR}/${name}.command.txt"
: >"${command_file}"
printf '$' >>"${command_file}"
printf ' %q' "$@" >>"${command_file}"
printf '\n' >>"${command_file}"
}
run_step() {
local name="$1"
shift
printf '==> %s\n' "${name}"
write_command_file "${name}" "$@"
"$@" >"${REPORT_DIR}/${name}.stdout.json" 2>"${REPORT_DIR}/${name}.stderr.log"
}
require_contains() {
local path="$1"
local needle="$2"
local description="$3"
if ! grep -Fq "${needle}" "${path}"; then
fail "${description}; expected '${needle}' in ${path}"
fi
}
run_step "01-dry-run-plan" \
"${BAT_BIN}" \
--auto-discover \
--output "${OUTPUT_DIR}" \
--state-dir "${STATE_DIR}" \
--dry-run \
--plan \
--json \
--no-progress
run_step "02-first-full-pull" \
"${BAT_BIN}" \
--auto-discover \
--output "${OUTPUT_DIR}" \
--state-dir "${STATE_DIR}" \
--json \
--progress \
--no-banner
require_contains "${REPORT_DIR}/02-first-full-pull.stderr.log" "下载进度:总体" "first pull did not emit total download progress"
require_contains "${REPORT_DIR}/02-first-full-pull.stderr.log" "单文件" "first pull did not emit single-file download progress"
require_contains "${REPORT_DIR}/02-first-full-pull.stderr.log" "校验结果:" "first pull did not emit verification summary"
run_step "03-second-up-to-date" \
"${BAT_BIN}" \
--auto-discover \
--output "${OUTPUT_DIR}" \
--state-dir "${STATE_DIR}" \
--json \
--no-progress
require_contains "${REPORT_DIR}/03-second-up-to-date.stdout.json" '"update_status": "up_to_date"' "second run did not report up_to_date"
current_link="${OUTPUT_DIR}/current"
[[ -e "${current_link}" ]] || fail "current release symlink/path was not created: ${current_link}"
current_dir="$(realpath "${current_link}")"
damage_file="$(find "${current_dir}" -type f \
! -name 'official-sync-snapshot.json' \
! -name 'official-download-manifest.json' \
-print -quit)"
[[ -n "${damage_file}" ]] || fail "no resource file found to damage under ${current_dir}"
printf '%s\n' "${damage_file}" >"${REPORT_DIR}/damaged-file.txt"
{
printf 'path: %s\n' "${damage_file}"
printf 'bytes: %s\n' "$(wc -c <"${damage_file}" | tr -d ' ')"
printf 'sha256: %s\n' "$(sha256sum "${damage_file}" | awk '{print $1}')"
} >"${REPORT_DIR}/damaged-file.before.txt"
printf 'BAT official smoke intentional corruption\n' >"${damage_file}"
run_step "04-repair-after-damage" \
"${BAT_BIN}" \
repair \
--auto-discover \
--output "${OUTPUT_DIR}" \
--state-dir "${STATE_DIR}" \
--json \
--progress \
--no-banner
require_contains "${REPORT_DIR}/04-repair-after-damage.stdout.json" '"command": "repair"' "repair command report missing"
require_contains "${REPORT_DIR}/04-repair-after-damage.stdout.json" '"status": "completed"' "repair command did not complete"
require_contains "${REPORT_DIR}/04-repair-after-damage.stderr.log" "下载进度:总体" "repair did not emit total download progress"
require_contains "${REPORT_DIR}/04-repair-after-damage.stderr.log" "单文件" "repair did not emit single-file download progress"
require_contains "${REPORT_DIR}/04-repair-after-damage.stderr.log" "校验结果:" "repair did not emit verification summary"
run_step "05-verify-after-repair" \
"${BAT_BIN}" \
verify \
--auto-discover \
--output "${OUTPUT_DIR}" \
--state-dir "${STATE_DIR}" \
--json \
--no-progress
require_contains "${REPORT_DIR}/05-verify-after-repair.stdout.json" '"healthy": true' "verify after repair did not report healthy=true"
file_count="$(find "${current_dir}" -type f | wc -l | tr -d ' ')"
current_size="$(du -sh "${current_dir}" | awk '{print $1}')"
report_file="${REPORT_DIR}/SMOKE_REPORT.md"
cat >"${report_file}" <<EOF
# Official Full Pull Smoke Report
- generated_at_utc: ${timestamp}
- repo: ${REPO_ROOT}
- bat: ${BAT_BIN}
- output: ${OUTPUT_DIR}
- state_dir: ${STATE_DIR}
- current_release: ${current_dir}
- current_release_size: ${current_size}
- current_release_file_count: ${file_count}
- damaged_file: ${damage_file}
## Steps
1. dry-run with full URL plan: 01-dry-run-plan
2. first full pull: 02-first-full-pull
3. second up-to-date check: 03-second-up-to-date
4. intentional local file damage and repair: 04-repair-after-damage
5. verify after repair: 05-verify-after-repair
Each step has:
- <step>.command.txt
- <step>.stdout.json
- <step>.stderr.log
The large resource output is intentionally outside Git and remains under:
\`${OUTPUT_DIR}\`
EOF
printf '\nSmoke report: %s\n' "${report_file}"
printf 'Resource output: %s\n' "${OUTPUT_DIR}"