Files
ipx/scripts/coverage.sh
Ray Slakinski 679a674fc9 Share an episode as a page, on ipx-play (#447)
Share handed over the raw file address or the item's link: no preview,
no time, nothing a person without iPX could do much with. Now, as
Overcast does, an episode shares as a short link to its own page,
play.ipxreader.app/e/<id>, which anyone can open: the show's art and
name, the episode's title, date, length and notes, a player on the
publisher's own file starting at ?t= if a time was shared, buttons to
subscribe in Apple Podcasts, Overcast, Pocket Casts or Castro, and Open
Graph tags for a preview where the link is pasted.

ipx-play is a service of its own (crates/play), built like the
directory: ipx sends it the episode with its token (POST /v1/shares)
and gets the link back. The id comes from the feed's address and the
guid, so the same episode is one link whoever shares it. Only episodes
of feeds the directory would be sent go there, by the directory's own
rule (snapshot_entries), so the two cannot disagree on what is private;
ipx-play also refuses an address that looks private. Anything else, and
any server without ipx-play set up, shares as before.

In the page, Share on an episode opens a menu once the link is back:
Share link, Share from 12:34 (the player's clock, or where you left
off), Copy link. A menu because the share sheet needs a press of its
own, and the request for the link used up the one that opened it.

The page runs no script: the start time is the media fragment #t=, and
its CSP loads nothing but its stylesheet, the art and the file.

Admin, Directory, Sharing episodes sets the address and token. The
browser suite starts its own ipx-play; it was not run here (no Caddy on
this Mac).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 09:24:18 -05:00

129 lines
5.9 KiB
Bash

#!/bin/sh
# Which lines of the Rust the tests reach (#390, #394), inside the limits builds are held to.
#
# sh scripts/coverage.sh the cargo tests, all four crates, then their report
# sh scripts/coverage.sh ipx-feed one crate's cargo tests, then the report of what has run
# sh scripts/coverage.sh api the browser suite against an instrumented ipx, ipx-directory
# and ipx-play: what the API and the daemon run (and the
# page's coverage, which the same run measures)
#
# A coverage build instruments every crate, dependencies included, in a target of its own: a whole
# rebuild, the kind that took Tower down on 2026-11-04. So it builds in a container under the same
# cap as scripts/build-image.sh's builder (6 GB, six threads), past which the kernel kills it and
# production is left alone; and, as CLAUDE.md asks of tests, one crate at a time. If the cap kills
# it, say so and leave the cap be. The browser suite runs here, as it always does, its servers the
# container's build: the image is Ubuntu's, as code-server is, so they start here.
#
# The cargo tests run on Postgres, production's database, when IPX_TEST_DATABASE_URL names the
# _test one (#407), from the shell or else /src/.envrc; on SQLite without it, whose paths are then
# the only ones measured. The integration tests' own files (tests/) are left out of the totals;
# a crate's inline `mod tests` cannot be, on stable Rust.
#
# Reports: target/coverage/cargo and target/coverage/api (summary.txt, a line a file; lcov.info;
# html/index.html), target/coverage/page (npm run coverage:page's, or api's). Each run ends by
# writing the numbers for Grafana (scripts/coverage-metrics.ts).
set -eu
MEM=6g
CPUS=0-5
# The container is capped at 6 GB, so 8 free leaves Tower 2 GB if the build fills its cap.
NEED_KB=$((8 * 1024 * 1024))
MAX_LOAD=12
IMAGE=192.168.1.130:5000/ipx-coverage:latest
# The Docker daemon is Tower's, so its paths are Tower's: this tree is /mnt/user/vscode/ipx there,
# and target/ is a link to target-real on /mnt/fast (builds through /mnt/user's FUSE took the load
# to 248, CLAUDE.md). code-server sees /mnt/fast at the same path, so the instrumented binaries
# and the profiles they write are at the same place for both.
SRC=/mnt/user/vscode/ipx
TARGET=/mnt/fast/vscode/ipx/target-real
REGISTRY=$TARGET/coverage-registry
LLVM_TARGET=$TARGET/llvm-cov-target
cd "$(dirname "$0")/.."
# Only the one variable, not the whole file: it holds keys the tests are not to see.
if [ -z "${IPX_TEST_DATABASE_URL:-}" ] && [ -f /src/.envrc ]; then
IPX_TEST_DATABASE_URL=$(sed -n 's/^export IPX_TEST_DATABASE_URL=//p' /src/.envrc | tr -d "\"'")
fi
export IPX_TEST_DATABASE_URL="${IPX_TEST_DATABASE_URL:-}"
[ -n "$IPX_TEST_DATABASE_URL" ] && echo "the cargo tests run on Postgres (IPX_TEST_DATABASE_URL)"
room() {
avail=$(awk '/^MemAvailable:/ {print $2}' /proc/meminfo)
load=$(cut -d' ' -f1 /proc/loadavg)
echo "MemAvailable $((avail / 1024)) MB, load $load"
if [ "$avail" -lt "$NEED_KB" ]; then
echo "refusing: under $((NEED_KB / 1024 / 1024)) GB available. Wait, or find what is using it." >&2
exit 1
fi
if awk -v l="$load" -v m="$MAX_LOAD" 'BEGIN { exit !(l > m) }'; then
echo "refusing: load over $MAX_LOAD. Wait for it to come down." >&2
exit 1
fi
}
run() {
# The source read only: nothing in the tree is the coverage run's to change. The registry is
# kept between runs, so the crates are downloaded once.
docker run --rm --memory "$MEM" --memory-swap "$MEM" --cpuset-cpus "$CPUS" \
-v "$SRC:/work:ro" -v "$TARGET:/target" -v "$REGISTRY:/usr/local/cargo/registry" \
-e CARGO_TARGET_DIR=/target/llvm-cov-target -e CARGO_LLVM_COV_TARGET_DIR=/target/llvm-cov-target \
-e IPX_TEST_DATABASE_URL \
"$IMAGE" "$@" || {
code=$?
[ "$code" -eq 137 ] && echo "killed at the ${MEM} memory cap: tell the user, do not raise it" >&2
exit "$code"
}
}
# The report of the profiles in the target, into target/coverage/$1, with the tree's own paths.
report() {
room
mkdir -p "$TARGET/coverage/$1"
skip="--ignore-filename-regex '(^|/)tests/'"
run sh -c "cargo llvm-cov report $skip --lcov --output-path /target/coverage/$1/lcov.info \
&& cargo llvm-cov report $skip --html --output-dir /target/coverage/$1 \
&& cargo llvm-cov report $skip --summary-only > /target/coverage/$1/summary.txt"
# The paths inside the container are /work/...; the tree's own, from here.
sed -i 's#/work/##' "$TARGET/coverage/$1/summary.txt" "$TARGET/coverage/$1/lcov.info"
tail -1 "$TARGET/coverage/$1/summary.txt"
}
docker pull -q "$IMAGE" >/dev/null
mkdir -p "$REGISTRY"
if [ "${1:-}" = api ]; then
# Built as cargo-llvm-cov builds for a test of its own, instrumented, with no tests: the
# binaries the browser suite starts. Profiles from an earlier run are not this one's.
run cargo llvm-cov clean --workspace
for p in ipx-directory ipx-play ipx; do
room
echo "== build $p"
run sh -c "eval \"\$(cargo llvm-cov show-env --export-prefix)\" && cargo build --locked -p $p"
done
room
echo "== the browser suite"
# Not `set -e`'s to stop on: a failing test still ran what it ran, and the report says so; the
# suite's own exit code is this script's at the end.
suite=0
sh scripts/coverage-node.sh
IPX_API_COVERAGE=$LLVM_TARGET IPX_COVERAGE=1 IPX_SIGNALS_TESTS=1 LLVM_PROFILE_FILE="$LLVM_TARGET/ipx-browser-%p-%14m.profraw" \
npx playwright test || suite=$?
ls "$LLVM_TARGET"/ipx-browser-*.profraw >/dev/null 2>&1 \
|| { echo "no profiles were written: did the servers stop on SIGTERM?" >&2; exit 1; }
report api
node scripts/coverage-metrics.ts
exit "$suite"
fi
crates=${1:-ipx-feed ipx-directory ipx-play ipx}
# A whole run starts from nothing; one crate adds to what has run.
[ $# -eq 0 ] && run cargo llvm-cov clean --workspace
for c in $crates; do
room
echo "== $c"
run cargo llvm-cov --no-report --locked -p "$c"
done
report cargo
node scripts/coverage-metrics.ts