When stdin is a terminal the prompt is read with bash's readline, `IFS= read -e -r -p '> ' REPLY`, so the arrow keys no longer print escape garbage: Left/Right, Home/End, Ctrl-A/E, Backspace/Delete edit the line, and Up/Down walk the lines entered in this session (`history -s` for every line with a non-blank character; empty lines are not recorded). Down past the newest entry restores the line typed before Up, and Up at the oldest entry or Down on the new line ring the bell, as readline does by default. The Nx y/N answer is read the same way and never recorded. Nothing persists: HISTFILE is unset at startup and the history is never read or written. Verified on bash 4.3.48 and 5.2.15 that `read -e` and `history -s` share one history list in a non-interactive shell, that inherited HISTSIZE/HISTCONTROL/HISTIGNORE have no effect there, and that Ctrl-C at the prompt restores the terminal. readline draws the prompt on stderr, as bash does. Piped stdin keeps the plain `read -r` exactly, including the echo of each line and EOF => exit 2. New unit tests (tests/unit/lineedit.bats) drive a real pty with script(1): history recall, the bell at both ends, the stashed line, Left-arrow editing, the y/N answer kept out of the history, no history file under HOME/TMPDIR, ^D => exit 2. The E2E suite gets a pty smoke test (Up recalls 1?), skipped where the image has no script(1).
109 lines
4.0 KiB
Bash
109 lines
4.0 KiB
Bash
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
# Copyright (C) 2026 Bryan Joshua Pedini
|
|
|
|
# tests/unit/test_helper.bash - common setup for the unit tests (sourced via `load test_helper`).
|
|
# The applet runs with a hermetic PATH: fake package managers (tests/unit/fakes/<dir>) in front
|
|
# of a directory with symlinks to the few system tools it needs. No root, no network, no real
|
|
# package manager; everything is written under $BATS_TEST_TMPDIR.
|
|
# shellcheck shell=bash
|
|
|
|
load '../vendor/bats-support/load'
|
|
load '../vendor/bats-assert/load'
|
|
|
|
SU_BIN=$(cd "$BATS_TEST_DIRNAME/../.." && pwd)/skeptical-updater
|
|
FAKES=$BATS_TEST_DIRNAME/fakes
|
|
export FAKE_DATA=$BATS_TEST_DIRNAME/data
|
|
SYS_TOOLS=(bash env cat sort sed grep tr cut join awk mktemp mv mkdir chmod rm cp ln date ls head tail wc touch sleep kill flock readlink timeout stat sha256sum)
|
|
|
|
common_setup() {
|
|
SYSBIN=$BATS_TEST_TMPDIR/sysbin
|
|
mkdir -p "$SYSBIN"
|
|
local t p
|
|
for t in "${SYS_TOOLS[@]}"; do
|
|
p=$(type -P "$t") || continue
|
|
ln -sf "$p" "$SYSBIN/$t"
|
|
done
|
|
export FAKE_DIR=$BATS_TEST_TMPDIR/fake
|
|
mkdir -p "$FAKE_DIR"
|
|
: >"$FAKE_DIR/holds"
|
|
export FAKE_LOG=$FAKE_DIR/log
|
|
: >"$FAKE_LOG"
|
|
export STATE=$BATS_TEST_TMPDIR/state
|
|
export STATE_FILE=$STATE/trusted.tsv
|
|
export SKEPTICAL_UPDATER_STATE_DIR=$STATE
|
|
export SKEPTICAL_UPDATER_EUID=0
|
|
export SKEPTICAL_UPDATER_OS_RELEASE=$FAKE_DATA/os-release-debian12
|
|
export TMPDIR=$BATS_TEST_TMPDIR
|
|
export FAKE_PACMAN_DBPATH=$FAKE_DIR/pacman-db
|
|
mkdir -p "$FAKE_PACMAN_DBPATH/sync" "$FAKE_PACMAN_DBPATH/local"
|
|
unset NO_COLOR ZYPP_CONF COLUMNS SKEPTICAL_UPDATER_LOCK_WAIT
|
|
use_fakes apt
|
|
}
|
|
|
|
# use_fakes DIR...: the applet sees only these fake dirs + the system tools
|
|
use_fakes() {
|
|
local d p=
|
|
for d in "$@"; do p+=$FAKES/$d:; done
|
|
TEST_PATH=$p$SYSBIN
|
|
}
|
|
|
|
# skup ARGS...: run the applet (use as `run skup ...`, stdin is passed through)
|
|
skup() { env PATH="$TEST_PATH" "$SU_BIN" "$@"; }
|
|
|
|
# pty_skup INPUT ARGS...: run the applet on a pseudo-terminal (script(1), util-linux/bsdutils) with
|
|
# INPUT typed in one go - readline consumes it as it reads, so no timing is involved; end INPUT with
|
|
# a line that ends the session (q or an empty line). Sets status, output and lines like `run`
|
|
# (output without the pty's CRs; NO_COLOR, as stdout is a terminal); the raw transcript stays in
|
|
# $PTY_RAW. Skips without script(1).
|
|
# shellcheck disable=SC2034 # status/lines are read by bats-assert
|
|
pty_skup() {
|
|
local input=$1 cmd
|
|
shift
|
|
command -v script >/dev/null 2>&1 || skip "script(1) (util-linux/bsdutils) not available: no pty to drive"
|
|
PTY_RAW=$BATS_TEST_TMPDIR/pty.raw
|
|
printf -v cmd '%q ' env PATH="$TEST_PATH" TERM=dumb NO_COLOR=1 "$SU_BIN" "$@"
|
|
status=0
|
|
printf '%s' "$input" | timeout 60 script -qfec "$cmd" /dev/null >"$PTY_RAW" 2>&1 || status=$?
|
|
output=$(tr -d '\r' <"$PTY_RAW")
|
|
mapfile -t lines <<<"$output"
|
|
}
|
|
# pty_bells: number of BEL bytes (readline's bell) in the raw transcript of the last pty_skup
|
|
pty_bells() { tr -cd '\a' <"$PTY_RAW" | wc -c; }
|
|
# count_lines LINE: number of output lines that are exactly LINE
|
|
count_lines() { grep -cxF -- "$1" <<<"$output" || true; }
|
|
|
|
# shellcheck disable=SC2034 # used by the .bats files
|
|
HEADER='# skeptical-updater trusted packages. Columns: backend package reason added(UTC ISO-8601)'
|
|
# shellcheck disable=SC2034
|
|
DATE_RE='[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z'
|
|
# shellcheck disable=SC2034
|
|
T=$'\t'
|
|
|
|
# write_state LINE...: create the statefile with exactly these lines
|
|
write_state() {
|
|
mkdir -p "$STATE"
|
|
printf '%s\n' "$@" >"$STATE_FILE"
|
|
}
|
|
|
|
# log_has LINE: the fake log contains exactly this line
|
|
log_has() {
|
|
grep -qxF -- "$1" "$FAKE_LOG" || {
|
|
printf 'expected log line: %s\n--- fake log:\n%s\n' "$1" "$(cat "$FAKE_LOG")" >&2
|
|
return 1
|
|
}
|
|
}
|
|
# log_matches REGEX: some fake log line matches
|
|
log_matches() {
|
|
grep -qE -- "$1" "$FAKE_LOG" || {
|
|
printf 'expected a log line matching: %s\n--- fake log:\n%s\n' "$1" "$(cat "$FAKE_LOG")" >&2
|
|
return 1
|
|
}
|
|
}
|
|
# log_lacks REGEX: no fake log line matches
|
|
log_lacks() {
|
|
if grep -qE -- "$1" "$FAKE_LOG"; then
|
|
printf 'unexpected log line matching: %s\n--- fake log:\n%s\n' "$1" "$(cat "$FAKE_LOG")" >&2
|
|
return 1
|
|
fi
|
|
}
|