#!/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}" <.command.txt - .stdout.json - .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}"