#!/usr/bin/env bash # Cross-language CLI feature parity. Runs the SAME battery of commands on the # Python CLI and the TypeScript CLI, records normalized results per language, # then asserts (1) the two languages produce identical results and (2) the # results match the expected values. Covers subshell isolation, sessions, # git-backed versioning, and fuse config wiring. Uses RAM mounts only (no # redis/minio/fuse kernel deps), so it runs anywhere both CLIs are built. # # Usage: parity.sh "" "" set -uo pipefail PY_CLI="${1:?python mirage cli command}" TS_CLI="${2:?typescript mirage cli command}" HERE="$(cd "$(dirname "$0")" && pwd)" fail=0 YAML=/tmp/parity-ws.yaml cat > "$YAML" <<'YML' mounts: /: resource: ram mode: WRITE YML FUSE_YAML=/tmp/parity-fuse.yaml cat > "$FUSE_YAML" <<'YML' mode: WRITE mounts: /: resource: ram fuse: true YML freeport() { lsof -ti:8765 2>/dev/null | xargs kill -9 2>/dev/null; sleep 1; } sout() { jq -r '.stdout // .result.stdout // empty'; } sexit() { jq -r '.exit_code // .exitCode // .result.exit_code // empty'; } # Run the full battery against one CLI; emit one "key=value" line per probe. probe() { local cli="$1" lang="$2" export MIRAGE_VERSION_ROOT="/tmp/parity-repos-$lang" rm -rf "$MIRAGE_VERSION_ROOT" freeport $cli daemon stop >/dev/null 2>&1 /dev/null 2>&1 /dev/null /dev/null # ── subshell: cd / export must not leak; exit + pipe ── echo "subshell.cd_inner=$($cli execute -w pw -c '(cd /data && pwd)' /dev/null || echo gone' /data/f.txt' /dev/null echo "cwd.home_default=$($cli execute -w pw -c 'echo $HOME' /dev/null $cli execute -w pw -c 'cd /data' /dev/null echo "session.default_pwd=$($cli execute -w pw -c 'pwd' /data/s.txt && ln -s /data/s.txt /data/link.txt' /dev/null echo "sym.readlink=$($cli execute -w pw -c 'readlink /data/link.txt' > /data/link.txt && tail -n 1 /data/s.txt' "' &1' /dev/null 2>&1 /dev/null 2>&1 /dev/null /a.txt' /dev/null $cli workspace commit vw -m first /dev/null $cli workspace branch vw feature /dev/null # feature @ first $cli execute -w vw -c 'echo two > /a.txt' /dev/null $cli workspace commit vw -m second /dev/null # main @ second echo "version.log=$($cli workspace log vw /dev/null 2>&1 /dev/null echo "clone.content=$($cli execute -w vwc -c 'cat /a.txt' /dev/null 2>&1 /dev/null 2>&1 /dev/null echo "clone.at_first=$($cli execute -w vwa -c 'cat /a.txt' /dev/null 2>&1 /dev/null 2>&1 echo "version.checkout_first=$($cli execute -w vw -c 'cat /a.txt' /dev/null 2>&1 $cli execute -w vw -c 'echo three > /a.txt' /dev/null echo "version.diff=$($cli workspace diff vw /dev/null 2>&1 /dev/null 2>&1 /dev/null /dev/null 2>&1 /tmp/parity-py.txt echo "===== probing TypeScript CLI =====" probe "$TS_CLI" ts | sort > /tmp/parity-ts.txt echo echo "===== Python results =====" cat /tmp/parity-py.txt echo echo "===== language parity (py vs ts) =====" if diff -u /tmp/parity-py.txt /tmp/parity-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/parity-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 "subshell.cd_inner" "/data" expect "subshell.cd_after" "/" expect "subshell.export_inner" "bar" expect "subshell.export_after" "[]" expect "subshell.false_exit" "1" expect "subshell.pipe" "2" expect "subshell.oldpwd_no_leak" "/" expect "subshell.func_redef" "A" expect "subshell.func_no_leak" "gone" expect "subshell.positional" "3" expect "cwd.home_default" "" expect "cwd.pwd_var" "/data" expect "cwd.oldpwd" "/data" expect "cwd.cd_dash" "/data" expect "cwd.tilde_pwd" "/data" expect "cwd.tilde_cat" "hi" expect "cwd.rel_cat" "hi" expect "cwd.wc_disp" "1 f.txt" expect "session.default_pwd" "/data" expect "session.s2_pwd" "/" expect "version.log" "second,first" expect "version.branch_log" "first" expect "ws.get_mounts" "/" expect "ws.list_has_vw" "yes" expect "clone.content" "two" expect "clone.at_first" "one" expect "version.checkout_first" "one" expect 'version.diff' '{"added":[],"modified":["a.txt"],"deleted":[]}' expect "fuse.operates" "alive" expect "sym.readlink" "/data/s.txt" expect "sym.cat_follow" "sym1" expect "sym.write_through" "via" expect "sym.ls_arrow" "$(printf 'l\t-\t-\tlink.txt -> /data/s.txt')" expect "sym.mv_entry" "/data/s.txt" expect "sym.rm_entry" "1" expect "sym.dangle" "1" expect "sym.eloop" "cat: /data/l1: Too many levels of symbolic links" if [ "$fail" != "0" ]; then echo echo "CLI feature parity FAILED." exit 1 fi echo echo "CLI feature parity OK (subshell, sessions, versioning, fuse; py == ts)."