From e29439dc71fbf260e85a4dbe45b3febdd7ca4bbc Mon Sep 17 00:00:00 2001 From: Xavier Roche Date: Mon, 13 Jul 2026 19:18:12 +0200 Subject: [PATCH 1/7] Run the engine's offline test suite on Windows The engine ships ~90 tests and not one has ever run on Windows: they only run under `make check` on Linux and macOS. Start with the 50 offline ones -- no network, no server, just httrack.exe driven from PATH. They are the engine's tests, but ours is the binary that ships, and this code has never executed on this platform. Pin core.autocrlf: the scripts are #!/bin/bash and the engine has no .gitattributes, so a converting checkout would rewrite them and bash would die on $'\r'. Assert tests actually ran, rather than that none failed. Every gate in these scripts exits 77 -- no python3, no HTTPS, no codec -- so a suite that quietly degrades to "everything skipped" would otherwise report success having tested nothing. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/windows-build.yml | 44 +++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index 719d80d..f91ccfe 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -75,6 +75,13 @@ jobs: platform: [x64, Win32] configuration: [Release] steps: + # The engine's test scripts are #!/bin/bash and the engine has no .gitattributes, + # so an autocrlf checkout would rewrite them and bash would die on $'\r'. Files + # land exactly as stored either way, which is what the Windows tooling wants too. + - name: Check out with the line endings the repos actually store + shell: pwsh + run: git config --global core.autocrlf false + - name: Checkout httrack-windows uses: actions/checkout@v4 with: @@ -337,6 +344,43 @@ jobs: Write-Host "--- package contents ---" Get-ChildItem $bin | ForEach-Object { Write-Host " $($_.Name)" } + # The engine ships ~90 tests and not one of them has ever run on Windows -- they + # only run under `make check` on Linux/macOS. These are the offline ones: no + # network, no server, just httrack.exe driven from PATH. They are the engine's + # tests, but ours is the binary that ships, and this code has never been executed + # on this platform. + - name: Run the engine test suite (offline tests) + shell: bash + working-directory: httrack/tests + run: | + set -u + bin="$(cygpath -u "$GITHUB_WORKSPACE")/httrack-windows/WinHTTrack/bin/${{ matrix.platform }}/${{ matrix.configuration }}" + export PATH="$bin:$PATH" + command -v httrack >/dev/null || { echo "::error::httrack.exe is not on PATH ($bin)"; exit 1; } + echo "httrack: $(command -v httrack)" + + pass=0; fail=0; skip=0; failed="" + for t in 00_runnable.test 01_engine-*.test 01_zlib-*.test; do + rc=0 + bash "$t" >"$t.log" 2>&1 || rc=$? + case "$rc" in + 0) pass=$((pass+1)); echo "PASS $t" ;; + 77) skip=$((skip+1)); echo "SKIP $t -- $(tail -n 1 "$t.log")" ;; + *) fail=$((fail+1)); failed="$failed $t" + echo "FAIL $t (exit $rc)" + sed 's/^/ /' "$t.log" | tail -n 25 ;; + esac + done + total=$((pass + fail + skip)) + echo "--- ran=$total pass=$pass fail=$fail skip=$skip ---" + + # A skipped test is not a passing test. Every gate in these scripts exits 77 -- + # no python3, no HTTPS, no codec -- so a suite that quietly degrades to + # "everything skipped" would otherwise report success having tested nothing. + [ "$total" -ge 45 ] || { echo "::error::only $total tests were discovered: the glob found nothing"; exit 1; } + [ "$pass" -ge 40 ] || { echo "::error::only $pass tests actually ran and passed ($skip skipped)"; exit 1; } + [ "$fail" -eq 0 ] || { echo "::error::failing tests:$failed"; exit 1; } + # Code signing, 1 of 2: the payload. SignPath signs an Inno installer as an opaque # PE and cannot reach the files inside it, so sign these BEFORE ISCC packages them. # Dormant until SIGNPATH_ORGANIZATION_ID exists, then tags only: releases need a From 384bfcfd2f72187e2a49cea4dafb7be974f59d60 Mon Sep 17 00:00:00 2001 From: Xavier Roche Date: Mon, 13 Jul 2026 19:32:53 +0200 Subject: [PATCH 2/7] Keep the full output of every engine test A 25-line tail cannot tell a real Windows bug from a POSIX assumption in the test, and 16 of the 50 fail. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/windows-build.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index f91ccfe..7b128fb 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -381,6 +381,16 @@ jobs: [ "$pass" -ge 40 ] || { echo "::error::only $pass tests actually ran and passed ($skip skipped)"; exit 1; } [ "$fail" -eq 0 ] || { echo "::error::failing tests:$failed"; exit 1; } + # A 25-line tail cannot tell a real Windows bug from a POSIX assumption in the + # test. Keep every test's whole output. + - name: Upload the test logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: engine-tests-${{ matrix.platform }}-${{ matrix.configuration }} + path: httrack/tests/*.log + if-no-files-found: warn + # Code signing, 1 of 2: the payload. SignPath signs an Inno installer as an opaque # PE and cannot reach the files inside it, so sign these BEFORE ISCC packages them. # Dormant until SIGNPATH_ORGANIZATION_ID exists, then tags only: releases need a From af03e7e2bf06cbde7551781280c828524e4b7516 Mon Sep 17 00:00:00 2001 From: Xavier Roche Date: Tue, 14 Jul 2026 07:22:23 +0200 Subject: [PATCH 3/7] Stop MSYS rewriting the tests' URL arguments httrack.exe is a native binary, so MSYS rewrites any argument shaped like a POSIX path -- and a URL path is shaped exactly like one. '-#test=mime /a/b.html' reached the engine as 'C:/Program Files/Git/a/b.html', and four tests failed silently on it. Switch the rewriting off and give the tests a temp dir that is already a Windows path, so nothing needs translating in either direction. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/windows-build.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index 7b128fb..b88d673 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -356,6 +356,16 @@ jobs: set -u bin="$(cygpath -u "$GITHUB_WORKSPACE")/httrack-windows/WinHTTrack/bin/${{ matrix.platform }}/${{ matrix.configuration }}" export PATH="$bin:$PATH" + + # httrack.exe is a native binary, so MSYS rewrites any argument shaped like a + # POSIX path -- including URL paths it has no way to recognise as such. It turned + # into . Switch the rewriting + # off, and hand the tests a temp dir that is already a Windows path, so nothing + # needs translating in either direction. + export MSYS_NO_PATHCONV=1 + export MSYS2_ARG_CONV_EXCL='*' + export TMPDIR="$(cygpath -m "${RUNNER_TEMP}")" + echo "TMPDIR=$TMPDIR" command -v httrack >/dev/null || { echo "::error::httrack.exe is not on PATH ($bin)"; exit 1; } echo "httrack: $(command -v httrack)" From 947efb82010487c161406ea176b2df41ceacb2ae Mon Sep 17 00:00:00 2001 From: Xavier Roche Date: Tue, 14 Jul 2026 07:23:12 +0200 Subject: [PATCH 4/7] Restore the comment an unquoted heredoc ate A shell heredoc expanded the backticks in the previous commit and substituted the example away, leaving "# into ." Same code, readable reason. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/windows-build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index b88d673..060e4e4 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -358,10 +358,10 @@ jobs: export PATH="$bin:$PATH" # httrack.exe is a native binary, so MSYS rewrites any argument shaped like a - # POSIX path -- including URL paths it has no way to recognise as such. It turned - # into . Switch the rewriting - # off, and hand the tests a temp dir that is already a Windows path, so nothing - # needs translating in either direction. + # POSIX path -- and a URL path is shaped exactly like one. The mime self-test was + # handed "/a/b.html" and the engine received "C:/Program Files/Git/a/b.html". + # Switch the rewriting off, and hand the tests a temp dir that is already a Windows + # path, so nothing needs translating in either direction. export MSYS_NO_PATHCONV=1 export MSYS2_ARG_CONV_EXCL='*' export TMPDIR="$(cygpath -m "${RUNNER_TEMP}")" From 1b1d50fea6e942896350d9f00398fefb24ed457e Mon Sep 17 00:00:00 2001 From: Xavier Roche Date: Tue, 14 Jul 2026 09:19:52 +0200 Subject: [PATCH 5/7] Print what the cache self-tests actually say They pipe stderr to /dev/null and print one word, so a Windows failure reads "FAIL" and nothing more. The engine side needs the assertion, not the verdict. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/windows-build.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index 060e4e4..e9c0f43 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -391,6 +391,31 @@ jobs: [ "$pass" -ge 40 ] || { echo "::error::only $pass tests actually ran and passed ($skip skipped)"; exit 1; } [ "$fail" -eq 0 ] || { echo "::error::failing tests:$failed"; exit 1; } + # The cache tests pipe stderr to /dev/null and print one word, so a failure says + # "FAIL" and nothing else. Run the two self-tests verbatim and keep everything they + # say: the engine side needs the assertion, not the verdict. + - name: Cache self-tests, verbatim + if: always() + shell: bash + working-directory: httrack/tests + run: | + bin="$(cygpath -u "$GITHUB_WORKSPACE")/httrack-windows/WinHTTrack/bin/${{ matrix.platform }}/${{ matrix.configuration }}" + export PATH="$bin:$PATH" + export MSYS_NO_PATHCONV=1 + export MSYS2_ARG_CONV_EXCL='*' + export TMPDIR="$(cygpath -m "${RUNNER_TEMP}")" + command -v httrack >/dev/null || exit 0 + for t in cache cache-corrupt; do + d="$TMPDIR/selftest-$t" + mkdir -p "$d" + echo "===== httrack -#test=$t $d" + httrack "-#test=$t" "$d" 2>&1 || echo "(exit $?)" + echo + done + echo "===== httrack -#test=sniff 'image/svg+xml' with a BOM" + printf '\xef\xbb\xbf ' > "$TMPDIR/bom.txt" + httrack "-#test=sniff" 'image/svg+xml' "text:$(cat "$TMPDIR/bom.txt")" 2>&1 || echo "(exit $?)" + # A 25-line tail cannot tell a real Windows bug from a POSIX assumption in the # test. Keep every test's whole output. - name: Upload the test logs From e1ca45a528702d397f920a51401654544f64a0b1 Mon Sep 17 00:00:00 2001 From: Xavier Roche Date: Tue, 14 Jul 2026 17:33:00 +0200 Subject: [PATCH 6/7] Trace a failing test instead of staring at an empty log Several of these assert with `test "$(httrack ...)" == "..." || exit 1`, which prints nothing at all when it fails. Re-run the test under a trace and keep it, so the log holds the call that failed and what it actually printed. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/windows-build.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index 28c542c..fc7673f 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -407,6 +407,10 @@ jobs: 77) skip=$((skip+1)); echo "SKIP $t -- $(tail -n 1 "$t.log")" ;; *) fail=$((fail+1)); failed="$failed $t" echo "FAIL $t (exit $rc)" + # Several of these assert with `test "$(httrack ...)" == "..." || exit 1`, + # which says nothing at all on failure. Re-run under a trace so the log + # holds the call that failed and what it actually printed. + bash -x "$t" >>"$t.log" 2>&1 || true sed 's/^/ /' "$t.log" | tail -n 25 ;; esac done From 5200187d99d37ec8b42e811b7de611d9191d7110 Mon Sep 17 00:00:00 2001 From: Xavier Roche Date: Tue, 14 Jul 2026 17:45:05 +0200 Subject: [PATCH 7/7] Get an exit code out of filterbounds It prints nothing and the test only sees an empty string, which is what a process that died silently looks like. The self-test drives the matcher deep on purpose and Windows gives a thread 1MB of stack where Linux gives 8MB. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/windows-build.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index fc7673f..b93b718 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -445,6 +445,12 @@ jobs: httrack "-#test=$t" "$d" 2>&1 || echo "(exit $?)" echo done + # filterbounds prints nothing at all and the test just sees an empty string. + # An exit code tells us whether it died: 0xC00000FD is a stack overflow, and this + # self-test drives the matcher deep on purpose. + echo "===== httrack -#test=filterbounds" + httrack "-#test=filterbounds"; echo "exit=$? (139/0xC00000FD would be a stack overflow)" + echo "===== httrack -#test=sniff 'image/svg+xml' with a BOM" printf '\xef\xbb\xbf ' > "$TMPDIR/bom.txt" httrack "-#test=sniff" 'image/svg+xml' "text:$(cat "$TMPDIR/bom.txt")" 2>&1 || echo "(exit $?)"