Drop a file (or pass ?file=<url>&path=<object>), browse the tree lazily, see a dataset's type, shape, max shape and attributes, and page through its values as 50x12 hyperslab windows (leading dims of 3-D+ data held at chosen indices). build.sh produces pkg/ (not committed) with wasm-bindgen --target web and checks the CLI matches the crate version. test/run.sh builds it and runs test.mjs under Node against the h5py/ netCDF4 fixture (250 checks: every dataset whole and as a strided hyperslab, listings, attributes, error paths, the page's DOM-free helpers), then browser.sh renders the page in headless Chromium for eight objects and checks the DOM. The fixture gains LZ4 (read) and Zstd (refused: links C) datasets and a compound attribute (value null plus its type). ci-test.sh runs it when node and wasm-bindgen exist; the CI container has neither, so CI relies on the native h5py_interop test. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
97 lines
3.9 KiB
Bash
97 lines
3.9 KiB
Bash
#!/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; ../pkg must be built.
|
||
# The page is opened with ?file=fixture.h5&path=<object>, which fetches the
|
||
# file, builds the tree down to <object> and shows it; the rendered DOM is
|
||
# dumped and checked for the values libhdf5 reads.
|
||
#
|
||
# 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
|
||
|
||
root="$(mktemp -d)"
|
||
server=""
|
||
cleanup() {
|
||
[ -n "$server" ] && kill "$server" 2>/dev/null || true
|
||
rm -rf "$root"
|
||
}
|
||
trap cleanup EXIT
|
||
ln -s "$HERE/../index.html" "$HERE/../viewer-lib.js" "$HERE/../pkg" "$FIX/fixture.h5" "$root/"
|
||
|
||
port=$("$PY" -c 'import socket; s = socket.socket(); s.bind(("127.0.0.1", 0)); print(s.getsockname()[1])')
|
||
"$PY" -m http.server --bind 127.0.0.1 --directory "$root" "$port" >/dev/null 2>&1 &
|
||
server=$!
|
||
for _ in $(seq 50); do
|
||
"$PY" -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:$port/index.html')" 2>/dev/null && break
|
||
sleep 0.1
|
||
done
|
||
|
||
fails=0
|
||
# A fresh profile per page: a second instance on the same profile fails.
|
||
render() {
|
||
local profile
|
||
profile="$(mktemp -d "$root/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=fixture.h5&path=$1" 2>/dev/null
|
||
}
|
||
# expect PATH TEXT...: every TEXT appears in the page rendered for PATH.
|
||
expect() {
|
||
local path="$1" dom
|
||
shift
|
||
dom="$(render "$path")"
|
||
[ -n "${BROWSER_DEBUG:-}" ] && printf "%s\n" "$dom" > "$BROWSER_DEBUG.$(echo "$path" | tr / _).html"
|
||
for text in "$@"; do
|
||
if ! grep -qF -- "$text" <<<"$dom"; then
|
||
echo "FAIL: page for $path lacks: $text" >&2
|
||
fails=$((fails + 1))
|
||
fi
|
||
done
|
||
echo "rendered $path"
|
||
}
|
||
|
||
# Tree (root expanded; the group row carries its path) and root attributes.
|
||
expect "/" '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.
|
||
expect "/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'
|
||
# Nested path revealed through the tree; big-endian float32.
|
||
expect "/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 "/u64" '<td>18446744073709551615</td>'
|
||
expect "/vlen_str" '<td>"двa"</td>' '<dd>vlen string</dd>'
|
||
expect "/pairs" '<td>[2, 3]</td>' '<dd>array[2]<i32></dd>'
|
||
# 3-D: leading dimension held at 0, window over the last two.
|
||
expect "/cube" '<dd>(2, 5, 6)</dd>' '<td>29</td>' 'dim 0'
|
||
# Unsupported type: an error, not values.
|
||
expect "/table" 'class="error"' 'reading compound{x: f64, n: i32} datasets is not supported'
|
||
|
||
if [ "$fails" -gt 0 ]; then
|
||
echo "browser: $fails checks failed" >&2
|
||
exit 1
|
||
fi
|
||
echo "browser: all checks passed ($chrome)"
|