Initial pre-release
tmux-party: share a tmux session with people on the same UNIX host. Single-file POSIX shell (party) with a filesystem + tmux server-access trust model. See README.md.
This commit is contained in:
commit
6be0ac1877
20 changed files with 3869 additions and 0 deletions
136
tests/helpers.bash
Normal file
136
tests/helpers.bash
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
# Bats helpers shared by every test file.
|
||||
# `load 'helpers'` from a .bats file pulls these in.
|
||||
|
||||
# helpers.bash lives at <repo>/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"
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue