release: restore ZUPT and harden source-only 5.2.2
This commit is contained in:
parent
74e393ba3e
commit
ff99770bd0
205 changed files with 19627 additions and 13215 deletions
|
|
@ -1,224 +1,207 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
||||
#
|
||||
# Sprint 2.4.7 regression: shell completions + manpage.
|
||||
#
|
||||
# Asserts:
|
||||
# - completions/vaptvupt.bash has bash-clean syntax
|
||||
# - completions/_vaptvupt has zsh-clean syntax (if zsh available)
|
||||
# - completions/vaptvupt.fish has fish-clean syntax (if fish available)
|
||||
# - Each completion file mentions all the major CLI flags the binary
|
||||
# actually parses (--kdf, --comment, --pq-sdk, --dedup, ...)
|
||||
# - doc/zupt.1 mentions current v2.4.x features (--kdf, --comment,
|
||||
# Argon2id, F-11, comment-file)
|
||||
# - doc/zupt.1 has the standard sections (NAME, SYNOPSIS, DESCRIPTION,
|
||||
# COMMANDS, EXAMPLES)
|
||||
|
||||
set -u
|
||||
|
||||
PASS=0
|
||||
FAIL=0
|
||||
P() { PASS=$((PASS+1)); echo " ✓ $1"; }
|
||||
F() { FAIL=$((FAIL+1)); echo " ✗ $1"; }
|
||||
SKIP() { echo " - skipped: $1"; }
|
||||
set -Eeuo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
VERSION=$(grep '^#define ZUPT_VERSION_STRING' include/zupt.h | awk -F'"' '{print $2}')
|
||||
echo "Completions + manpage (vaptvupt $VERSION)"
|
||||
pass_count=0
|
||||
fail_count=0
|
||||
skip_count=0
|
||||
pass() { pass_count=$((pass_count + 1)); printf ' PASS: %s\n' "$1"; }
|
||||
fail() { fail_count=$((fail_count + 1)); printf ' FAIL: %s\n' "$1"; }
|
||||
skip() { skip_count=$((skip_count + 1)); printf ' SKIP: %s\n' "$1"; }
|
||||
|
||||
# ─── Bash completion ───
|
||||
if [ -f completions/vaptvupt.bash ]; then
|
||||
if bash -n completions/vaptvupt.bash 2>/dev/null; then
|
||||
P "bash completion: syntax clean"
|
||||
else
|
||||
F "bash completion: syntax error"
|
||||
fi
|
||||
# Should define a _zupt function and register it via complete -F
|
||||
if grep -q "^_vaptvupt()" completions/vaptvupt.bash; then
|
||||
P "bash completion: defines _vaptvupt function"
|
||||
else
|
||||
F "bash completion: missing _vaptvupt function"
|
||||
fi
|
||||
if grep -qE "^complete -F _vaptvupt (vaptvupt|zupt)" completions/vaptvupt.bash; then
|
||||
P "bash completion: registers via complete -F"
|
||||
else
|
||||
F "bash completion: missing complete -F registration"
|
||||
fi
|
||||
version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
|
||||
[[ -n $version ]] || { printf 'FAIL: cannot determine version\n' >&2; exit 1; }
|
||||
printf 'ZUPT %s completion and manual-page checks\n' "$version"
|
||||
|
||||
completion_files=(
|
||||
completions/zupt.bash
|
||||
completions/_zupt
|
||||
completions/zupt.fish
|
||||
)
|
||||
|
||||
for file in "${completion_files[@]}"; do
|
||||
[[ -f $file ]] && pass "$file exists" || fail "$file is missing"
|
||||
done
|
||||
|
||||
if bash -n completions/zupt.bash; then
|
||||
pass 'bash completion parses'
|
||||
else
|
||||
F "completions/vaptvupt.bash missing"
|
||||
fail 'bash completion has a syntax error'
|
||||
fi
|
||||
|
||||
# ─── Zsh completion ───
|
||||
if [ -f completions/_vaptvupt ]; then
|
||||
if command -v zsh >/dev/null 2>&1; then
|
||||
if zsh -n completions/_vaptvupt 2>/dev/null; then
|
||||
P "zsh completion: syntax clean"
|
||||
else
|
||||
F "zsh completion: syntax error"
|
||||
fi
|
||||
if command -v zsh >/dev/null 2>&1; then
|
||||
if zsh -n completions/_zupt; then
|
||||
pass 'zsh completion parses'
|
||||
else
|
||||
SKIP "zsh not installed — skipping syntax check"
|
||||
fi
|
||||
# Should have #compdef directive
|
||||
if grep -qE "^#compdef vaptvupt( zupt)?$" completions/_vaptvupt; then
|
||||
P "zsh completion: has #compdef vaptvupt directive"
|
||||
else
|
||||
F "zsh completion: missing #compdef directive"
|
||||
fail 'zsh completion has a syntax error'
|
||||
fi
|
||||
else
|
||||
F "completions/_vaptvupt missing"
|
||||
skip 'zsh is unavailable'
|
||||
fi
|
||||
|
||||
# ─── Fish completion ───
|
||||
if [ -f completions/vaptvupt.fish ]; then
|
||||
if command -v fish >/dev/null 2>&1; then
|
||||
if fish -n completions/vaptvupt.fish 2>/dev/null; then
|
||||
P "fish completion: syntax clean"
|
||||
else
|
||||
F "fish completion: syntax error"
|
||||
fi
|
||||
if command -v fish >/dev/null 2>&1; then
|
||||
if fish -n completions/zupt.fish; then
|
||||
pass 'fish completion parses'
|
||||
else
|
||||
SKIP "fish not installed — skipping syntax check"
|
||||
fi
|
||||
# Should have complete -c zupt entries
|
||||
if grep -qE "^complete -c (vaptvupt|zupt)" completions/vaptvupt.fish; then
|
||||
P "fish completion: has complete -c vaptvupt entries"
|
||||
else
|
||||
F "fish completion: no complete -c vaptvupt entries"
|
||||
fail 'fish completion has a syntax error'
|
||||
fi
|
||||
else
|
||||
F "completions/vaptvupt.fish missing"
|
||||
skip 'fish is unavailable'
|
||||
fi
|
||||
|
||||
# ─── Flag-coverage check (across all three completion files) ───
|
||||
# Every flag the binary actually parses should appear in every completion file.
|
||||
# Each completion format has its own way of writing long options:
|
||||
# bash: --flag
|
||||
# zsh: --flag
|
||||
# fish: -l flag (or --flag in comments)
|
||||
critical_flags=(kdf comment comment-file pq pq-sdk dedup solid verbose quiet threads level block store fast lzhp vaptvupt)
|
||||
if grep -qxF 'complete -F _zupt zupt' completions/zupt.bash &&
|
||||
! grep -Eq '^complete[[:space:]].*[[:space:]]vaptvupt([[:space:]]|$)' completions/zupt.bash; then
|
||||
pass 'bash registers only zupt'
|
||||
else
|
||||
fail 'bash completion is not limited to the primary zupt command'
|
||||
fi
|
||||
|
||||
for f in completions/vaptvupt.bash completions/_vaptvupt; do
|
||||
[ -f "$f" ] || continue
|
||||
name=$(basename "$f")
|
||||
missing=""
|
||||
for flag in "${critical_flags[@]}"; do
|
||||
if ! grep -qF -- "--$flag" "$f"; then
|
||||
missing="$missing --$flag"
|
||||
if [[ $(sed -n '1p' completions/_zupt) == '#compdef zupt' ]]; then
|
||||
pass 'zsh registers only zupt'
|
||||
else
|
||||
fail 'zsh #compdef is not limited to zupt'
|
||||
fi
|
||||
|
||||
if grep -q '^complete -c zupt' completions/zupt.fish &&
|
||||
! grep -q '^complete -c vaptvupt\([[:space:]]\|$\)' completions/zupt.fish; then
|
||||
pass 'fish registers only zupt'
|
||||
else
|
||||
fail 'fish completion is not limited to the primary zupt command'
|
||||
fi
|
||||
|
||||
required_flags=(
|
||||
password-prompt pass-file pass-fd allow-legacy-no-ait kdf comment comment-file
|
||||
pq pq-only pq-sdk pq-box dedup solid force verbose threads
|
||||
level block store fast lzhp vaptvupt compare output key pub
|
||||
sdk box pqonly help version
|
||||
)
|
||||
|
||||
for file in "${completion_files[@]}"; do
|
||||
missing=()
|
||||
for flag in "${required_flags[@]}"; do
|
||||
if ! grep -qF -- "--$flag" "$file" &&
|
||||
! grep -qE -- "-l[[:space:]]+$flag([[:space:]]|$)" "$file"; then
|
||||
missing+=("--$flag")
|
||||
fi
|
||||
done
|
||||
if [ -z "$missing" ]; then
|
||||
P "$name: covers all ${#critical_flags[@]} critical flags"
|
||||
if ((${#missing[@]} == 0)); then
|
||||
pass "$file covers current critical flags"
|
||||
else
|
||||
F "$name: missing flags:$missing"
|
||||
fail "$file is missing: ${missing[*]}"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -f completions/vaptvupt.fish ]; then
|
||||
name="vaptvupt.fish"
|
||||
missing=""
|
||||
for flag in "${critical_flags[@]}"; do
|
||||
# fish uses `-l flag-name` for long opts
|
||||
if ! grep -qE -- "(-l $flag|--$flag)" completions/vaptvupt.fish; then
|
||||
missing="$missing $flag"
|
||||
unsupported_flags=(quiet jobs codec keyfile sync no-mtime strip-components block-size)
|
||||
for file in "${completion_files[@]}"; do
|
||||
advertised=()
|
||||
for flag in "${unsupported_flags[@]}"; do
|
||||
if grep -qF -- "--$flag" "$file" ||
|
||||
grep -qE -- "-l[[:space:]]+$flag([[:space:]]|$)" "$file"; then
|
||||
advertised+=("--$flag")
|
||||
fi
|
||||
done
|
||||
if [ -z "$missing" ]; then
|
||||
P "$name: covers all ${#critical_flags[@]} critical flags (via -l form)"
|
||||
if ((${#advertised[@]} == 0)); then
|
||||
pass "$file does not advertise unsupported flags"
|
||||
else
|
||||
F "$name: missing flags:$missing"
|
||||
fail "$file advertises unsupported flags: ${advertised[*]}"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# ─── Manpage refresh ───
|
||||
if [ -f doc/zupt.1 ]; then
|
||||
# v2.4.x features must be mentioned. Use shell-friendly regexes that
|
||||
# match groff's `\-\-` escape (literal backslash, dash, backslash, dash).
|
||||
declare -a manpage_checks=(
|
||||
"kdf:--kdf option"
|
||||
"comment:--comment option"
|
||||
"argon2id:Argon2id KDF"
|
||||
"Argon2id:Argon2id KDF (capital)"
|
||||
"verbal probe-oracle:F-11 message change"
|
||||
"ML-KEM-768:post-quantum KEM"
|
||||
)
|
||||
manpage_misses=0
|
||||
for entry in "${manpage_checks[@]}"; do
|
||||
key="${entry%%:*}"
|
||||
desc="${entry#*:}"
|
||||
if grep -qF "$key" doc/zupt.1; then
|
||||
:
|
||||
else
|
||||
F "manpage: doesn't mention '$desc' (looking for '$key')"
|
||||
manpage_misses=$((manpage_misses+1))
|
||||
fi
|
||||
done
|
||||
# Two additional checks for groff-escaped hyphens (--comment-file, --pq-sdk
|
||||
# render as `\-\-comment\-file` and `\-\-pq\-sdk` in the source)
|
||||
if grep -qE "comment\\\\-file|comment-file" doc/zupt.1; then
|
||||
:
|
||||
else
|
||||
F "manpage: doesn't mention --comment-file (looking for comment\\-file or comment-file)"
|
||||
manpage_misses=$((manpage_misses+1))
|
||||
fi
|
||||
if grep -qE "pq\\\\-sdk|pq-sdk" doc/zupt.1; then
|
||||
:
|
||||
else
|
||||
F "manpage: doesn't mention --pq-sdk (looking for pq\\-sdk or pq-sdk)"
|
||||
manpage_misses=$((manpage_misses+1))
|
||||
fi
|
||||
if [ "$manpage_misses" = 0 ]; then
|
||||
P "manpage: mentions all v2.4.x features"
|
||||
fi
|
||||
|
||||
# Required sections
|
||||
for section in NAME SYNOPSIS DESCRIPTION COMMANDS EXAMPLES; do
|
||||
if grep -qE "^\.SH $section" doc/zupt.1; then
|
||||
:
|
||||
else
|
||||
F "manpage: missing section '.SH $section'"
|
||||
fi
|
||||
done
|
||||
P "manpage: required sections present"
|
||||
|
||||
# Version header
|
||||
if grep -qE "\"(vaptvupt|zupt) $VERSION\"" doc/zupt.1; then
|
||||
P "manpage: TH version matches include/zupt.h ($VERSION)"
|
||||
else
|
||||
F "manpage: TH version doesn't match include/zupt.h"
|
||||
fi
|
||||
|
||||
# Try to render with groff if available
|
||||
if command -v groff >/dev/null 2>&1; then
|
||||
if groff -mandoc -Tutf8 doc/zupt.1 > /tmp/render.txt 2>/tmp/groff_warn.txt; then
|
||||
LINES=$(wc -l < /tmp/render.txt)
|
||||
if [ "$LINES" -gt 50 ]; then
|
||||
P "manpage: renders cleanly with groff ($LINES lines)"
|
||||
else
|
||||
F "manpage: groff produced suspiciously short output ($LINES lines)"
|
||||
fi
|
||||
else
|
||||
F "manpage: groff rendering failed"
|
||||
fi
|
||||
rm -f /tmp/render.txt /tmp/groff_warn.txt
|
||||
elif command -v mandoc >/dev/null 2>&1; then
|
||||
if mandoc -Tlint doc/zupt.1 >/tmp/mandoc.out 2>&1; then
|
||||
P "manpage: mandoc lint clean"
|
||||
else
|
||||
P "manpage: mandoc lint had warnings (acceptable)"
|
||||
fi
|
||||
rm -f /tmp/mandoc.out
|
||||
else
|
||||
SKIP "no groff or mandoc — skipping render lint"
|
||||
fi
|
||||
if [[ ! -e doc/vaptvupt.1 && ! -L doc/vaptvupt.1 ]]; then
|
||||
pass 'former primary man page is absent from the source tree'
|
||||
else
|
||||
F "doc/zupt.1 missing"
|
||||
fail 'doc/vaptvupt.1 remains despite the zupt-only default installation'
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo " ───────────────────────────────────────"
|
||||
echo " completions + manpage: $PASS passed, $FAIL failed"
|
||||
echo " ───────────────────────────────────────"
|
||||
[ "$FAIL" = 0 ] || exit 1
|
||||
manpage=doc/zupt.1
|
||||
if [[ ! -f $manpage ]]; then
|
||||
fail "$manpage is missing"
|
||||
else
|
||||
required_sections=(NAME SYNOPSIS DESCRIPTION COMMANDS PASSWORD\ INPUT EXAMPLES EXIT\ STATUS LICENSE)
|
||||
missing_sections=()
|
||||
for section in "${required_sections[@]}"; do
|
||||
grep -qxF ".SH $section" "$manpage" || missing_sections+=("$section")
|
||||
done
|
||||
if ((${#missing_sections[@]} == 0)); then
|
||||
pass 'manpage contains required sections'
|
||||
else
|
||||
fail "manpage is missing sections: ${missing_sections[*]}"
|
||||
fi
|
||||
|
||||
if grep -qF "ZUPT $version" "$manpage"; then
|
||||
pass 'manpage version matches include/zupt.h'
|
||||
else
|
||||
fail 'manpage version does not match include/zupt.h'
|
||||
fi
|
||||
|
||||
required_man_flags=(
|
||||
password-prompt pass-file pass-fd allow-legacy-no-ait kdf comment comment-file
|
||||
pq pq-only pq-sdk pq-box dedup solid force verbose threads
|
||||
level block store fast lzhp vaptvupt compare output key pub
|
||||
sdk box pqonly help version
|
||||
)
|
||||
missing=()
|
||||
for flag in "${required_man_flags[@]}"; do
|
||||
grep -qF -- "--$flag" "$manpage" || missing+=("--$flag")
|
||||
done
|
||||
if ((${#missing[@]} == 0)); then
|
||||
pass 'manpage documents current critical flags'
|
||||
else
|
||||
fail "manpage is missing: ${missing[*]}"
|
||||
fi
|
||||
|
||||
advertised=()
|
||||
for flag in "${unsupported_flags[@]}"; do
|
||||
grep -qF -- "--$flag" "$manpage" && advertised+=("--$flag")
|
||||
done
|
||||
if ((${#advertised[@]} == 0)); then
|
||||
pass 'manpage does not document unsupported flags'
|
||||
else
|
||||
fail "manpage documents unsupported flags: ${advertised[*]}"
|
||||
fi
|
||||
|
||||
if grep -qF 'Plain archives provide compression checksums' "$manpage" &&
|
||||
grep -qF 'does not restore ownership' "$manpage" &&
|
||||
grep -qF 'Automatic codec selection' "$manpage"; then
|
||||
pass 'manpage states current integrity, metadata, and codec behavior'
|
||||
else
|
||||
fail 'manpage is missing current behavioral limits'
|
||||
fi
|
||||
|
||||
if grep -q '^\.B 2$\|^\.B 3$\|^\.B 4$\|^\.B 5$' "$manpage"; then
|
||||
fail 'manpage advertises exit statuses not emitted by the CLI'
|
||||
else
|
||||
pass 'manpage documents only emitted exit statuses'
|
||||
fi
|
||||
|
||||
lint_tmp=$(mktemp -d "${TMPDIR:-/tmp}/zupt-man-lint.XXXXXXXX")
|
||||
trap 'rm -rf -- "$lint_tmp"' EXIT HUP INT TERM
|
||||
if command -v mandoc >/dev/null 2>&1; then
|
||||
if mandoc -Tlint "$manpage" >"$lint_tmp/mandoc.log" 2>&1; then
|
||||
pass 'mandoc lint passes'
|
||||
else
|
||||
fail 'mandoc lint reports diagnostics'
|
||||
sed -n '1,10p' "$lint_tmp/mandoc.log"
|
||||
fi
|
||||
elif command -v groff >/dev/null 2>&1; then
|
||||
if groff -mandoc -Tutf8 "$manpage" >"$lint_tmp/rendered" 2>"$lint_tmp/groff.log" &&
|
||||
[[ ! -s $lint_tmp/groff.log ]] &&
|
||||
(($(wc -l <"$lint_tmp/rendered") > 50)); then
|
||||
pass 'groff renders the manpage without diagnostics'
|
||||
else
|
||||
fail 'groff manpage rendering failed or emitted diagnostics'
|
||||
sed -n '1,10p' "$lint_tmp/groff.log"
|
||||
fi
|
||||
else
|
||||
skip 'mandoc and groff are unavailable'
|
||||
fi
|
||||
rm -rf -- "$lint_tmp"
|
||||
trap - EXIT HUP INT TERM
|
||||
fi
|
||||
|
||||
printf '\nSummary: PASS=%d FAIL=%d SKIP=%d\n' "$pass_count" "$fail_count" "$skip_count"
|
||||
((fail_count == 0))
|
||||
|
|
|
|||
Loading…
Reference in a new issue