Files
httrack/tests/01_engine-doitlog.test
Xavier Roche 6ee539619e htscoremain: keep empty quoted args when reloading doit.log (#106)
An empty footer (-%F "") is written to hts-cache/doit.log correctly as the
two-character token "", and next_token() unquotes it back to an empty string.
But the doit.log reload loop only re-inserted a token when strnotempty(lastp),
which dropped the empty one. With its argument gone, -%F absorbed the following
token (or had none), so a no-url --continue/--update reprise misparsed and
failed.

Track whether the token started with a quote (before next_token() strips it in
place) and keep it even when empty, so "" survives the round-trip. Whitespace
gaps still produce no token, so spacing behavior is unchanged.

01_engine-doitlog.test gains a scenario that mirrors with -%F "" -r2, then on
the no-url reprise checks the regenerated doit.log still round-trips the empty
token -- probing the reader's rebuilt argv, not just that the reprise didn't
crash. The trailing -r2 makes a dropped-token bug visible (it shifts into -%F's
slot and panics) rather than a harmless run off the end of argv. Reverting only
the guard makes the scenario fail (reprise exits 255).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-06-19 08:09:57 +02:00

126 lines
4.7 KiB
Bash

#!/bin/bash
#
# doit.log reprise (no network). Re-running httrack in a mirror directory with
# NO url makes the engine read hts-cache/doit.log and re-insert every recorded
# argument into the command line through htscoremain.c's cmdl_ins macro (the
# x_argvblk builder). That path is distinct from the rc-file one in
# 01_engine-rcfile.test (htsalias.c) and from the url-on-command-line update in
# 02_update-cache.test, and nothing else exercises it. Two properties:
# 1. A multi-token reprise re-mirrors cleanly: every token (the url and each
# option) survives the back-to-back packing, so the no-url run reproduces
# the file set with no errors. A packing/bound bug corrupts a later token
# and surfaces as an error or a missing file.
# 2. The reprise actually re-crawls through the inserted url: changing a source
# file and re-running with no url picks up the new content.
set -euo pipefail
# Resolve httrack to an absolute path before we cd: PATH may hold a
# build-relative entry that would not resolve from the temp directory.
bin=$(command -v httrack) || {
echo "FAIL: httrack not found on PATH"
exit 1
}
case "$bin" in
/*) ;;
*) bin="$(cd "$(dirname "$bin")" && pwd)/$(basename "$bin")" ;;
esac
tmp=$(mktemp -d "${TMPDIR:-/tmp}/httrack_doitlog.XXXXXX") || exit 1
trap 'rm -rf "$tmp"' EXIT HUP INT QUIT PIPE TERM
site="$tmp/site"
out="$tmp/out"
mkdir -p "$site/sub"
printf '<a href="a.html">a</a> <a href="sub/b.html">b</a>' >"$site/index.html"
echo 'OLDCONTENT' >"$site/a.html"
echo '<p>bbb</p>' >"$site/sub/b.html"
url="file://$site/index.html"
# count Error: lines in the log (grep -c exits 1 on zero matches: guard it)
errors() { grep -ciE '^[0-9:]*[[:space:]]Error:' "$out/hts-log.txt" || true; }
# initial mirror with the url and a handful of options, so doit.log records a
# multi-token command line for cmdl_ins to re-insert one token at a time.
rc=0
"$bin" "$url" -O "$out" --quiet -n -%v0 -r3 >/dev/null 2>&1 || rc=$?
test "$rc" -eq 0 || {
echo "FAIL: initial mirror exited $rc"
exit 1
}
test -f "$out/hts-cache/doit.log" || {
echo "FAIL: doit.log not written by the initial mirror"
exit 1
}
# --- 1. no-url reprise re-mirrors cleanly -----------------------------------
# No url on the command line, so the engine loads doit.log and re-inserts the
# recorded arguments (cmdl_ins). -O selects the mirror; argv carries no url.
rc=0
"$bin" -O "$out" --quiet >/dev/null 2>&1 || rc=$?
test "$rc" -eq 0 || {
echo "FAIL: doit.log reprise exited $rc"
exit 1
}
test "$(errors)" = 0 || {
echo "FAIL: doit.log reprise reported errors (a token may have been corrupted)"
grep -iE 'Error:' "$out/hts-log.txt" | head -3
exit 1
}
for suffix in a.html sub/b.html; do
test -n "$(find "$out" -path "*/$suffix" -print -quit)" || {
echo "FAIL: $suffix missing after the no-url reprise"
exit 1
}
done
# --- 2. the reprise re-crawls through the inserted url -----------------------
sleep 1
echo 'NEWCONTENT' >"$site/a.html"
rc=0
"$bin" -O "$out" --quiet >/dev/null 2>&1 || rc=$?
test "$rc" -eq 0 || {
echo "FAIL: second reprise exited $rc"
exit 1
}
grep -q NEWCONTENT "$(find "$out" -path '*/a.html' -print -quit)" || {
echo "FAIL: reprise did not pick up the changed source (inserted url not re-crawled)"
exit 1
}
# --- 3. an empty quoted arg survives the doit.log round-trip (#106) ----------
# -%F "" (empty footer) records an empty "" token in doit.log; -r2 follows it so
# a "drop the empty token" bug shifts -r2 into -%F's slot (the reprise then sees
# -%F -r2 and panics "%F needs to be followed by ..."), making the bug visible
# rather than a harmless run off the end of argv.
out2="$tmp/out2"
rc=0
"$bin" "$url" -O "$out2" --quiet -n -%v0 -%F "" -r2 >/dev/null 2>&1 || rc=$?
test "$rc" -eq 0 || {
echo "FAIL: initial mirror with empty footer exited $rc"
exit 1
}
# precondition: the writer put the empty token on disk for the reader to reload.
grep -q ' -%F "" -r2' "$out2/hts-cache/doit.log" || {
echo "FAIL: empty footer not recorded as -%F \"\" -r2 in doit.log"
grep -- '-%F' "$out2/hts-cache/doit.log" || true
exit 1
}
# no-url reprise: the reader rebuilds argv from doit.log and rewrites doit.log
# from it. The empty token surviving in the regenerated file proves the reader
# kept it (a drop/swallow would panic above or rewrite -%F without the "").
rc=0
"$bin" -O "$out2" --quiet >/dev/null 2>&1 || rc=$?
test "$rc" -eq 0 || {
echo "FAIL: empty-footer reprise exited $rc (empty token dropped from doit.log?)"
exit 1
}
grep -q ' -%F "" -r2' "$out2/hts-cache/doit.log" || {
echo "FAIL: empty footer did not survive the doit.log reload round-trip"
grep -- '-%F' "$out2/hts-cache/doit.log" || true
exit 1
}
exit 0