Files
clawhdf5/examples/wasm-viewer/test/browser.sh
T
osobhandClaude Opus 5.5 581c6ddef8 wasm-viewer tests: the cross-origin path without exposed headers
serve.py always exposed Content-Range and ETag, and Node has no CORS, so
openUrl's documented cross-origin path (length from a HEAD request,
answers checked by body length alone, no validator) was never run.

- serve.py /noexpose/ serves ranges without Content-Range, ETag,
  Last-Modified or Accept-Ranges (what a page sees of a server that
  does not expose them); /unexposed/ sends them but exposes none, for a
  real browser. HEAD requests are counted (0 bytes).
- test.mjs: every fixture check through /noexpose/ at 1 MiB and 512-byte
  blocks (one HEAD each, requests and bytes as the server counted them),
  concurrent reads with cacheSize 0, a short answer still caught, and a
  server without a HEAD length a clear error.
- browser.sh: the page on 127.0.0.1 opens the file from localhost, once
  with Content-Range exposed and once through /unexposed/, where the
  server's log must show the HEAD.

Checked by breaking the HEAD length in remote.js: the new checks fail.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-27 07:51:30 -05:00

127 lines
5.6 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# Load the viewer page in headless Chromium and check what it renders.
#
# browser.sh FIXTURE_DIR
#
# FIXTURE_DIR holds fixture.h5 from make_fixture.py (and big.h5 when it was
# run with WASM_BIG_MB); ../pkg must be built. test/serve.py serves the
# page and, under /fix, the fixtures, with HTTP range requests (no copies,
# no links). The page is opened with ?file=fix/<name>&path=<object>, which
# opens the file with openUrl (range requests), builds the tree down to
# <object> and shows it; the rendered DOM is dumped and checked for the
# values libhdf5 reads and for the request counter.
#
# Browser: $CHROME, else chromium/google-chrome on PATH, else a Playwright
# download under ~/.cache/ms-playwright. Exit 3 when none is found.
# BROWSER_DEBUG=/some/prefix saves each rendered page as prefix.<path>.html.
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
FIX="$(cd "$1" && pwd)"
PY="${CLAWHDF5_PYTHON:-python3}"
chrome="${CHROME:-}"
if [ -z "$chrome" ]; then
for c in chromium chromium-browser google-chrome chrome-headless-shell; do
if command -v "$c" >/dev/null; then chrome="$(command -v "$c")"; break; fi
done
fi
if [ -z "$chrome" ]; then
chrome="$(ls -d "$HOME"/.cache/ms-playwright/chromium_headless_shell-*/chrome-headless-shell-linux64/chrome-headless-shell 2>/dev/null | tail -1 || true)"
fi
if [ -z "$chrome" ] || [ ! -x "$chrome" ]; then
echo "no Chromium found (set CHROME)" >&2
exit 3
fi
profiles="$(mktemp -d)"
server=""
cleanup() {
[ -n "$server" ] && kill "$server" 2>/dev/null || true
rm -rf "$profiles"
}
trap cleanup EXIT
"$PY" "$HERE/serve.py" --root "fix=$FIX" --root "=$HERE/.." > "$profiles/port" &
server=$!
for _ in $(seq 50); do
[ -s "$profiles/port" ] && break
sleep 0.1
done
port="$(head -1 "$profiles/port")"
fails=0
# A fresh profile per page: a second instance on the same profile fails.
render() {
local profile
profile="$(mktemp -d "$profiles/profile.XXXXXX")"
"$chrome" --headless --no-sandbox --disable-gpu --user-data-dir="$profile" \
--virtual-time-budget=20000 \
--dump-dom "http://127.0.0.1:$port/index.html?file=$1&path=$2" 2>/dev/null
}
# expect FILE PATH TEXT...: every TEXT appears in the page rendered for PATH
# of FILE (a URL relative to the page); "re:TEXT" is an extended regex.
expect() {
local file="$1" path="$2" dom
shift 2
dom="$(render "$file" "$path")"
[ -n "${BROWSER_DEBUG:-}" ] && printf "%s\n" "$dom" > "$BROWSER_DEBUG.$(echo "$file$path" | tr / _).html"
for text in "$@"; do
local flag=-qF
if [ "${text#re:}" != "$text" ]; then flag=-qE; text="${text#re:}"; fi
if ! grep $flag -- "$text" <<<"$dom"; then
echo "FAIL: page for $file $path lacks: $text" >&2
fails=$((fails + 1))
fi
done
echo "rendered $file $path"
}
H5=fix/fixture.h5
# Tree (root expanded; the group row carries its path) and root attributes.
expect $H5 "/" 'data-path="/sensors"' 'data-path="/grid"' '<td>title</td><td>"wasm fixture"</td>' \
'<td>big</td><td>9223372036854775813</td>' '(compound{x: f64, n: i32})'
# A chunked, deflated 2-D dataset: type, shape, the first window of values;
# the request counter.
expect $H5 "/grid" '<dd>f64</dd>' '<dd>(6, 10)</dd>' '<th>9</th>' '<td>0.25</td>' '<td>14.75</td>' \
'showing rows 0–5, columns 0–9 of 60 values' 'request' 'fetched (100.0%)'
# Nested path revealed through the tree; big-endian float32.
expect $H5 "/sensors/temp" 'data-path="/sensors/temp"' '<dd>f32</dd>' '<td>21.5</td>' '<td>22.25</td>'
# 64-bit integers stay exact; strings; array datatype cells.
expect $H5 "/u64" '<td>18446744073709551615</td>'
expect $H5 "/vlen_str" '<td>"двa"</td>' '<dd>vlen string</dd>'
expect $H5 "/pairs" '<td>[2, 3]</td>' '<dd>array[2]&lt;i32&gt;</dd>'
# 3-D: leading dimension held at 0, window over the last two.
expect $H5 "/cube" '<dd>(2, 5, 6)</dd>' '<td>29</td>' 'dim 0'
# Unsupported type: an error, not values.
expect $H5 "/table" 'class="error"' 'reading compound{x: f64, n: i32} datasets is not supported'
# Cross-origin: the page is on 127.0.0.1, the file on localhost. CORS that
# exposes Content-Range, and CORS that exposes nothing (/unexposed/: the
# browser hides Content-Range and ETag, so the page learns the length with
# a HEAD request, which the server's log must show).
expect "http://localhost:$port/$H5" "/grid" '<td>14.75</td>' 'request'
get() { "$PY" -c 'import sys, urllib.request; print(urllib.request.urlopen(sys.argv[1]).read().decode())' "$1"; }
get "http://127.0.0.1:$port/__reset" >/dev/null
expect "http://localhost:$port/unexposed/$H5" "/grid" '<td>0.25</td>' '<td>14.75</td>' 'request'
heads="$(get "http://127.0.0.1:$port/__stats" | "$PY" -c \
'import json, sys; print(sum(1 for l in json.load(sys.stdin)["log"] if l[4] == "HEAD" and l[0].endswith("fixture.h5")))')"
if [ "$heads" -lt 1 ]; then
echo "FAIL: no HEAD request for the length with unexposed headers" >&2
fails=$((fails + 1))
fi
# A server without range support: the file is downloaded whole, and says so.
expect norange/$H5 "/grid" '<td>14.75</td>' 'downloaded whole'
# A large file: a small dataset, and a window of the large one, fetch a few
# blocks (the counter shows a small percentage: "(1.5%)", not "(100.0%)").
if [ -f "$FIX/big.h5" ]; then
expect fix/big.h5 "/small" '<td>1.5</td>' '<td>3.25</td>' 're:MiB of 191 MiB fetched \([0-9]\.[0-9]%\)'
expect fix/big.h5 "/big" '<dd>(25000000)</dd>' '<td>0</td>' '<td>24.5</td>' \
're:MiB of 191 MiB fetched \([0-9]\.[0-9]%\)'
fi
if [ "$fails" -gt 0 ]; then
echo "browser: $fails checks failed" >&2
exit 1
fi
echo "browser: all checks passed ($chrome)"