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>
129 lines
5.9 KiB
Bash
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
|