Files
wehub-resource-sync bcbd1bdb22
Integ / changes (push) Has been skipped
Pre-commit / pre-commit (push) Failing after 1s
CLI exit codes / changes (push) Has been skipped
Test (Install) / changes (push) Has been skipped
Test (Python) / changes (push) Has been skipped
Test (TypeScript) / changes (push) Has been skipped
CLI exit codes / cli-gate (push) Has been cancelled
Test (Install) / test-install-gate (push) Has been cancelled
Integ / integ-gate (push) Has been cancelled
Test (Python) / test-python-gate (push) Has been cancelled
Test (TypeScript) / test-typescript-gate (push) Has been cancelled
Test (Install) / python-minimal (3.12) (push) Has been cancelled
Test (Install) / python-minimal (3.11) (push) Has been cancelled
Test (Install) / python-extra (agno, mirage.agents.agno) (push) Has been cancelled
Test (Install) / python-extra (chroma, mirage.resource.chroma) (push) Has been cancelled
Test (Install) / python-extra (pdf, mirage.core.filetype.pdf) (push) Has been cancelled
Integ / integ (push) Has been cancelled
Integ / integ-database (push) Has been cancelled
Integ / integ-database-ts (push) Has been cancelled
Integ / integ-data (push) Has been cancelled
Integ / integ-ssh (push) Has been cancelled
Integ / integ-ssh-ts (push) Has been cancelled
Test (Python) / audit (push) Has been cancelled
Test (TypeScript) / test (push) Has been cancelled
Test (TypeScript) / python-fs-shim (push) Has been cancelled
CLI exit codes / Python CLI (push) Has been cancelled
CLI exit codes / TypeScript CLI (push) Has been cancelled
CLI exit codes / Cross-language snapshot interop (push) Has been cancelled
Test (Python) / test (push) Has been cancelled
Test (Python) / import-isolation (deepagents, openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Python) / import-isolation (deepagents, pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Integ / integ-ts (push) Has been cancelled
Integ / integ-fuse (push) Has been cancelled
Test (Install) / python-extra (databricks, mirage.resource.databricks_volume) (push) Has been cancelled
Test (Install) / python-extra (deepagents, mirage.agents.langchain) (push) Has been cancelled
Test (Install) / python-extra (email, mirage.resource.email) (push) Has been cancelled
Test (Install) / python-extra (fuse, mirage.fuse.mount) (push) Has been cancelled
Test (Install) / python-extra (hdf5, mirage.core.filetype.hdf5) (push) Has been cancelled
Test (Install) / python-extra (hf, mirage.resource.hf_buckets) (push) Has been cancelled
Test (Install) / python-extra (lancedb, mirage.resource.lancedb) (push) Has been cancelled
Test (Install) / python-extra (langfuse, mirage.resource.langfuse) (push) Has been cancelled
Test (Install) / python-extra (mongodb, mirage.resource.mongodb) (push) Has been cancelled
Test (Install) / python-extra (nextcloud, mirage.resource.nextcloud) (push) Has been cancelled
Test (Install) / python-extra (openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Install) / python-extra (openhands, mirage.agents.openhands, 3.12) (push) Has been cancelled
Test (Install) / python-extra (parquet, mirage.core.filetype.parquet) (push) Has been cancelled
Test (Install) / python-extra (postgres, mirage.resource.postgres) (push) Has been cancelled
Test (Install) / python-extra (pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Test (Install) / python-extra (qdrant, mirage.resource.qdrant) (push) Has been cancelled
Test (Install) / python-extra (redis, mirage.resource.redis) (push) Has been cancelled
Test (Install) / python-extra (s3, mirage.resource.s3) (push) Has been cancelled
Test (Install) / python-extra (ssh, mirage.resource.ssh) (push) Has been cancelled
Test (Install) / ts-minimal (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:44 +08:00

210 lines
7.7 KiB
Bash

#!/usr/bin/env bash
# CLI end-to-end FUSE parity. Drives the daemon-backed CLI to create a workspace
# with TWO per-mount FUSE subtrees pinned to known OS paths plus ONE generated
# mount (fuse: true), writes content through the VFS, then reads it back THROUGH
# the kernel mountpoints. The daemon hosts the mounts in its own process, so the
# reading shell never deadlocks. Proves the CLI really mounts each subtree on
# both languages, and that close() cleanup is ownership-aware: caller-owned
# (pinned) mountpoint directories survive unmount, while generated temp
# directories are removed with an empty-dir rmdir.
# Requires libfuse + /dev/fuse, so it runs only in the fuse CI job.
#
# Usage: cli_fuse.sh "<py-cli>" "<ts-cli>"
set -uo pipefail
PY_CLI="${1:?python mirage cli command}"
TS_CLI="${2:?typescript mirage cli command}"
fail=0
points() { jq -r '.fuse_mountpoints // .fuseMountpoints'; }
# Run the full CLI battery against one CLI; emit one "key=value" line per probe.
probe() {
local cli="$1" lang="$2"
local dmnt="/tmp/cli-fuse-$lang-data"
local lmnt="/tmp/cli-fuse-$lang-logs"
fusermount -u "$dmnt" 2>/dev/null || umount "$dmnt" 2>/dev/null || true
fusermount -u "$lmnt" 2>/dev/null || umount "$lmnt" 2>/dev/null || true
rm -rf "$dmnt" "$lmnt"
local yaml="/tmp/cli-fuse-$lang.yaml"
cat > "$yaml" <<YML
mode: WRITE
mounts:
/data:
resource: ram
fuse: $dmnt
/logs:
resource: ram
fuse: $lmnt
/auto:
resource: ram
fuse: true
YML
$cli daemon stop >/dev/null 2>&1 </dev/null || true
sleep 1
# Collision rejection: two mounts whose fuse: resolves to the SAME OS path must
# be rejected at workspace-create time (server returns 409, CLI exits non-zero),
# and the server must not leak a partial mount. Self-contained sub-check that
# never touches the main cf workspace.
local collide_mp="/tmp/cli-fuse-$lang-collide-mp"
local yaml_collide="/tmp/cli-fuse-$lang-collide.yaml"
cat > "$yaml_collide" <<YML
mode: WRITE
mounts:
/one:
resource: ram
fuse: $collide_mp
/two:
resource: ram
fuse: $collide_mp
YML
$cli workspace delete cfx >/dev/null 2>&1 </dev/null || true
if $cli workspace create "$yaml_collide" --id cfx >/dev/null 2>&1 </dev/null; then
echo "collision_rejected=no"
$cli workspace delete cfx >/dev/null 2>&1 </dev/null || true
else
echo "collision_rejected=yes"
fi
$cli workspace delete cf >/dev/null 2>&1 </dev/null || true
$cli workspace create "$yaml" --id cf >/dev/null </dev/null
# RAM mounts start empty; seed through the VFS. The live workspace is what
# FUSE serves, so these writes are immediately visible at the mountpoints.
$cli execute -w cf -c 'echo alpha > /data/a.txt' </dev/null >/dev/null
$cli execute -w cf -c 'echo beta > /logs/b.txt' </dev/null >/dev/null
# The daemon mounts asynchronously; wait for both files to appear via the OS.
local i
for i in $(seq 1 50); do
[ -f "$dmnt/a.txt" ] && [ -f "$lmnt/b.txt" ] && break
sleep 0.2
done
local detail data_mp logs_mp auto_mp
detail="$($cli workspace get cf </dev/null)"
data_mp="$(printf '%s' "$detail" | points | jq -r '."/data" // empty')"
logs_mp="$(printf '%s' "$detail" | points | jq -r '."/logs" // empty')"
auto_mp="$(printf '%s' "$detail" | points | jq -r '."/auto" // empty')"
# The generated mount got a temp mountpoint Mirage picked; exercise it through
# the kernel the same way as the pinned mounts.
$cli execute -w cf -c 'echo gamma > /auto/c.txt' </dev/null >/dev/null
for i in $(seq 1 50); do
[ -n "$auto_mp" ] && [ -f "$auto_mp/c.txt" ] && break
sleep 0.2
done
echo "data_cat=$(cat "$dmnt/a.txt" 2>/dev/null)"
echo "logs_cat=$(cat "$lmnt/b.txt" 2>/dev/null)"
echo "auto_cat=$(cat "$auto_mp/c.txt" 2>/dev/null)"
echo "logs_size=$(wc -c < "$lmnt/b.txt" 2>/dev/null | tr -d ' ')"
echo "mount_keys=$(printf '%s' "$detail" | points | jq -r 'keys | sort | join(",")')"
echo "data_pinned=$([ "$data_mp" == "$dmnt" ] && echo yes || echo no)"
echo "logs_pinned=$([ "$logs_mp" == "$lmnt" ] && echo yes || echo no)"
echo "auto_generated=$([ -n "$auto_mp" ] && [ "$auto_mp" != "$dmnt" ] && [ "$auto_mp" != "$lmnt" ] && echo yes || echo no)"
echo "distinct=$([ -n "$data_mp" ] && [ "$data_mp" != "$logs_mp" ] && echo yes || echo no)"
# Deleting the workspace runs the daemon-side close() for every mount. Cleanup
# must keep caller-owned (pinned) directories and remove only the generated
# temp directory. Wait for the generated dir to disappear, then assert.
$cli workspace delete cf >/dev/null 2>&1 </dev/null || true
for i in $(seq 1 25); do
[ -n "$auto_mp" ] && [ ! -d "$auto_mp" ] && break
sleep 0.2
done
echo "data_dir_survives=$([ -d "$dmnt" ] && echo yes || echo no)"
echo "logs_dir_survives=$([ -d "$lmnt" ] && echo yes || echo no)"
echo "gen_dir_removed=$([ -n "$auto_mp" ] && [ ! -d "$auto_mp" ] && echo yes || echo no)"
# Reuse a pinned mountpoint across re-create: the caller-owned $dmnt directory
# survived the cf delete above, so a NEW workspace pinned to the SAME path must
# mount fine and serve a file through the kernel.
local reuse_yaml="/tmp/cli-fuse-$lang-reuse.yaml"
cat > "$reuse_yaml" <<YML
mode: WRITE
mounts:
/data:
resource: ram
fuse: $dmnt
YML
$cli workspace delete cf2 >/dev/null 2>&1 </dev/null || true
$cli workspace create "$reuse_yaml" --id cf2 >/dev/null </dev/null
$cli execute -w cf2 -c 'echo reused > /data/r.txt' </dev/null >/dev/null
for i in $(seq 1 50); do
[ -f "$dmnt/r.txt" ] && break
sleep 0.2
done
echo "reuse_after_recreate=$(cat "$dmnt/r.txt" 2>/dev/null)"
$cli workspace delete cf2 >/dev/null 2>&1 </dev/null || true
$cli daemon stop >/dev/null 2>&1 </dev/null || true
fusermount -u "$dmnt" 2>/dev/null || umount "$dmnt" 2>/dev/null || true
fusermount -u "$lmnt" 2>/dev/null || umount "$lmnt" 2>/dev/null || true
[ -n "$auto_mp" ] && { fusermount -u "$auto_mp" 2>/dev/null || umount "$auto_mp" 2>/dev/null || true; }
fusermount -u "$collide_mp" 2>/dev/null || umount "$collide_mp" 2>/dev/null || true
rm -rf "$dmnt" "$lmnt" "$collide_mp" "$yaml_collide" "$reuse_yaml" ${auto_mp:+"$auto_mp"}
}
echo "===== probing Python CLI ====="
probe "$PY_CLI" py | sort > /tmp/cli-fuse-py.txt
echo "===== probing TypeScript CLI ====="
probe "$TS_CLI" ts | sort > /tmp/cli-fuse-ts.txt
echo
echo "===== Python results ====="
cat /tmp/cli-fuse-py.txt
echo
echo "===== language parity (py vs ts) ====="
if diff -u /tmp/cli-fuse-py.txt /tmp/cli-fuse-ts.txt; then
echo " OK Python and TypeScript produced identical results"
else
echo " FAIL Python and TypeScript diverged (see diff above)"
fail=1
fi
echo
echo "===== expected values ====="
expect() {
local key="$1" want="$2"
local got
got="$(grep -F "$key=" /tmp/cli-fuse-py.txt | head -1 | cut -d= -f2-)"
if [ "$got" == "$want" ]; then
echo " OK $key == $(printf '%q' "$want")"
else
echo " FAIL $key: got $(printf '%q' "$got") expected $(printf '%q' "$want")"
fail=1
fi
}
expect "data_cat" "alpha"
expect "logs_cat" "beta"
expect "auto_cat" "gamma"
expect "logs_size" "5"
expect "mount_keys" "/auto,/data,/logs"
expect "data_pinned" "yes"
expect "logs_pinned" "yes"
expect "auto_generated" "yes"
expect "distinct" "yes"
expect "data_dir_survives" "yes"
expect "logs_dir_survives" "yes"
expect "gen_dir_removed" "yes"
expect "collision_rejected" "yes"
expect "reuse_after_recreate" "reused"
if [ "$fail" != "0" ]; then
echo
if [ -f "$HOME/.mirage/daemon.log" ]; then
echo "===== ~/.mirage/daemon.log (last 60 lines) ====="
tail -60 "$HOME/.mirage/daemon.log"
echo
fi
echo "CLI FUSE parity FAILED."
exit 1
fi
echo
echo "CLI FUSE parity OK (two pinned + one generated FUSE subtree read through the kernel; ownership-aware cleanup; py == ts)."