# Bats helpers shared by every test file. # `load 'helpers'` from a .bats file pulls these in. # helpers.bash lives at /tests/helpers.bash, so repo root is one level # up from its own directory. This avoids depending on .git, which may not # be present on remote test hosts (e.g. rsynced without history). PARTY_BIN="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/party" # Per-test scratch dir. Bats sets BATS_TEST_TMPDIR but we centralize so # party's overridable env vars all point under one tree. setup_party_sandbox() { # Pretend the test runner is NOT inside tmux. iTerm2's tmux -CC mode # (or any nested tmux) sets $TMUX, which several party subcommands # treat as "you're already attached somewhere." Tests that need # $TMUX set re-export it themselves. unset TMUX TMUX_PANE TMUX_TMPDIR # USER may not be exported in all environments (e.g. some CI shells, # zsh with clean env). party uses $USER under set -eu, so guarantee it. export USER="${USER:-$(id -un)}" export PARTY_TMP="$BATS_TEST_TMPDIR/party" export PARTY_SOCKET_DIR="$PARTY_TMP/sockets" mkdir -p "$PARTY_SOCKET_DIR" chmod 0700 "$PARTY_SOCKET_DIR" # No shared roster directory in this layout. Each per-party dir under # PARTY_SOCKET_DIR carries its own roster file. validate_socket_dir_parent # accepts an owner-only-writable parent like this one. } # Tear down any tmux servers we spawned under PARTY_SOCKET_DIR. # Sockets live at ${PARTY_SOCKET_DIR}/party-USER:NAME.d/sock under the # per-party-private-dir layout. The old glob (party-*) matched the # *directory* and the [ -S ] filter dropped everything, so servers # leaked across tests. teardown_party_sandbox() { if [ -d "${PARTY_SOCKET_DIR:-}" ]; then for d in "$PARTY_SOCKET_DIR"/party-*.d; do [ -d "$d" ] || continue sock="$d/sock" [ -S "$sock" ] || continue tmux -S "$sock" kill-server 2>/dev/null || true done fi } # Pre-create the per-party private dir so roster_write can land its # record inside. Mirrors cmd_host's mkdir -m 0700 + chmod 0750. Also # creates a placeholder sock file so roster_read (which requires # $d/sock to exist and be owned by the basename's HOST_USER) accepts # the dir. ensure_party_dir() { local d="$PARTY_SOCKET_DIR/party-$1:$2.d" mkdir -p "$d" chmod 0750 "$d" : > "$d/sock" } # Skip the test if the running user is not a member of TMUX_PARTY_GROUP. require_party_group() { local g="${TMUX_PARTY_GROUP:-party}" id -nG "$USER" 2>/dev/null | tr ' ' '\n' | grep -qx "$g" \ || skip "user not in group '$g'; run: sudo usermod -aG $g $USER && relog" } # Print one username per line for users in group $1 — both supplementary # members (4th /etc/group field) and primary-group members (passwd gid # match). Linux / illumos / FreeBSD / OpenBSD use getent(1); macOS has # no getent and stores users in Directory Services, so fall back to # dscl(1) there. Returns silently if neither resolver is available or # the group is unknown. list_users_in_group() { local g="$1" gid if command -v getent >/dev/null 2>&1; then gid=$(getent group "$g" 2>/dev/null | awk -F: '{print $3}') [ -n "$gid" ] || return 0 { getent group "$g" | awk -F: '{n=split($4,m,","); for(i=1;i<=n;i++) if(m[i]!="") print m[i]}' getent passwd | awk -F: -v gid="$gid" '$4==gid {print $1}' } elif command -v dscl >/dev/null 2>&1; then gid=$(dscl . -read "/Groups/$g" PrimaryGroupID 2>/dev/null \ | awk '/^PrimaryGroupID:/ {print $2}') [ -n "$gid" ] || return 0 { # Supplementary members. macOS records gid 20 (staff) members as # primary-only, so this branch is usually empty for staff but # populates for groups created with `dseditgroup -o edit -a`. dscl . -read "/Groups/$g" GroupMembership 2>/dev/null \ | sed -n 's/^GroupMembership: //p' | tr ' ' '\n' # Primary members: every user whose PrimaryGroupID matches. dscl . -list /Users PrimaryGroupID | awk -v gid="$gid" '$2==gid {print $1}' } | grep -v '^$' || true fi } # Print the login shell of user $1, or nothing if unknown. Mirrors # list_users_in_group's portability split: getent(1) where present, # dscl(1) on macOS. lookup_user_shell() { local u="$1" if command -v getent >/dev/null 2>&1; then getent passwd "$u" 2>/dev/null | awk -F: '{print $7}' elif command -v dscl >/dev/null 2>&1; then dscl . -read "/Users/$u" UserShell 2>/dev/null | sed -n 's/^UserShell: //p' fi } # Pick another invitable user on this box: a member of TMUX_PARTY_GROUP # (supplementary or primary) other than $USER. Empty output if none. # Uses group membership rather than a UID floor so it works across # Linux (uid>=1000), illumos/Solaris (uid>=100), and BSD conventions. # Excludes root and no-login system accounts (tmux server-access refuses # to manage root, and system pseudo-users aren't sensible test peers). pick_other_user() { local g="${TMUX_PARTY_GROUP:-party}" local candidates u shell candidates=$(list_users_in_group "$g") for u in $candidates; do [ "$u" = "$USER" ] && continue [ "$u" = "root" ] && continue shell=$(lookup_user_shell "$u") case "$shell" in *false|*nologin|"") continue ;; esac printf '%s\n' "$u" return 0 done } # Source ./party in library mode for pure-function tests. load_party_lib() { __PARTY_LIB_ONLY=1 # shellcheck disable=SC1090 . "$PARTY_BIN" }