Files
nvrl 7b67db5ced fix(core,fuse,cli): harden writes, trash and cleanup found by account acceptance runs
The FUSE acceptance suite now runs on the signed-in account by default and
cleans up after itself; its first live runs found three daemon bugs.

- B101: a pwrite past EOF now authors the hole it leaves, so truncated bytes
  no longer come back from the base. A write attached to a create while that
  create uploads becomes a revision of the real uid instead of being dropped.
- B102: removing a folder drops the queued revisions of files below it, found
  through the node tree, with their blobs and in-flight uploads. `pdfs rm`
  now drops queued ops too.
- B103: each server call of a trash refresh times out after 120 s, so one hung
  call can no longer freeze the trash listing until restart.

Acceptance suite: account mode with a manifest, lock and reaper; cleanup by
uid; queued ops naming a test root count as leftovers; new edge-case,
throughput and B100/B101 cases; the wide-directory case is split from the
deep tree and gets a doubled budget. Adds `pdfs trash --json`.
2026-09-29 10:09:44 +02:00

97 lines
3.5 KiB
Bash
Executable File

#!/usr/bin/env bash
# Filesystem syscall contract, with optional tests against mounted Drive views.
set -Eeuo pipefail
usage() {
cat >&2 <<'EOF'
usage: scripts/fuse-acceptance.sh [OPTION ...]
scripts/fuse-acceptance.sh --offline-only [OPTION ...]
scripts/fuse-acceptance.sh --live MOUNTPOINT [MOUNTPOINT ...] [OPTION ...]
scripts/fuse-acceptance.sh --managed-live EMPTY_DIR EMPTY_DIR [OPTION ...]
scripts/fuse-acceptance.sh MOUNTPOINT [MOUNTPOINT ...] # compatibility
With no mode, everything runs on the signed-in account: My files, then two
sync folders it registers itself, through every pairing of on-demand and
mirror, with moves and copies between them. Everything it creates is removed
afterwards, on failure and on Ctrl-C too, and permanently deleted from the
trash; a run that was killed is cleaned up by the next one.
--offline-only runs only the local reference suite, which needs no account.
It always runs first; every mount is diffed against its recorded behaviour.
--live runs the same contract on each given mount. Set PDFS_ACCEPTANCE_CONVERGENCE=1 only when all live mountpoints
show the same remote folder.
Options are forwarded to the Python runner. The useful ones:
--list print every case and the targets it runs against
--timeout SECONDS per-case limit (default 180); a hung case dumps stacks
--fail-fast stop at the first failure instead of continuing
--quick one on-demand/mirror pairing instead of all four
--report-json PATH machine-readable results and recorded observations
--report-junit PATH JUnit XML for CI
--budget SECONDS report any case slower than this
--journal-check fail if the daemon logs errors during the run
--durability restart the daemon mid-suite and re-verify bytes
EOF
exit 2
}
command -v python3 >/dev/null || { echo "python3 is required" >&2; exit 2; }
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
runner=(python3 -u "$script_dir/fuse-acceptance.py")
mountpoints=()
passthrough=()
# Paths come first and options after them, so the first dash-prefixed argument
# ends the path list and everything from there is forwarded untouched.
collect_paths() {
while (( $# > 0 )) && [[ "$1" != -* ]]; do
mountpoints+=("$1")
shift
done
passthrough=("$@")
}
case "${1:-}" in
-h|--help) usage ;;
"")
exec "${runner[@]}" --account
;;
--offline-only)
shift
exec "${runner[@]}" "$@"
;;
--live)
shift
collect_paths "$@"
(( ${#mountpoints[@]} > 0 )) || usage
;;
--managed-live)
shift
(( $# >= 2 )) || usage
first="$1"; second="$2"; shift 2
exec "${runner[@]}" --managed-live "$first" "$second" "$@"
;;
--account)
exec "${runner[@]}" "$@"
;;
--*)
# Bare options with no mode: an account run with those options.
exec "${runner[@]}" --account "$@"
;;
*)
collect_paths "$@"
;;
esac
command -v findmnt >/dev/null || { echo "findmnt is required for --live" >&2; exit 2; }
for mountpoint in "${mountpoints[@]}"; do
[[ -d "$mountpoint" ]] || { echo "not a directory: $mountpoint" >&2; exit 2; }
done
fstype="$(findmnt -T "${mountpoints[0]}" -n -o FSTYPE | head -n 1)"
[[ "$fstype" == fuse* ]] || {
echo "refusing primary non-FUSE path ${mountpoints[0]} (type: ${fstype:-unknown})" >&2
exit 2
}
exec "${runner[@]}" --live "${mountpoints[@]}" "${passthrough[@]}"