mirror of
https://github.com/robbyrussell/oh-my-zsh.git
synced 2026-09-24 03:56:07 +02:00
refactor(cli): group theme browser files
This commit is contained in:
@@ -0,0 +1,350 @@
|
||||
#!/usr/bin/env zsh
|
||||
# Run with: zsh -df tools/theme-browser/tests/browser_test.zsh
|
||||
# Optionally pass one group, e.g. `slow-interrupt`, to run it alone.
|
||||
# Each browser runs under a real controlling PTY, with no user startup files.
|
||||
emulate -R zsh
|
||||
|
||||
if [[ $1 == --child ]]; then
|
||||
if [[ $2 == completion ]]; then
|
||||
function compdef { print -r -- "REGISTERED:${(j.:.)@}"; }
|
||||
fi
|
||||
source "$ZSH/lib/cli.zsh"
|
||||
# Match normal OMZ sessions without sourcing any user startup/configuration.
|
||||
setopt promptsubst
|
||||
print -r -- "LAZY:$+functions[_omz_theme_browser]:$+functions[_omz_theme_preview]"
|
||||
if [[ $2 == completion ]]; then
|
||||
# Capture the real completion function's candidates without loading the
|
||||
# user's completion setup or replacing any CLI/backend functions.
|
||||
function _describe { print -rl -- "DESCRIBE:$1" "${(@P)2}"; }
|
||||
CURRENT=3 words=(omz theme '')
|
||||
print -r -- SUBCOMMANDS
|
||||
_omz
|
||||
print -r -- END_SUBCOMMANDS
|
||||
for action in browse preview set use; do
|
||||
CURRENT=4 words=(omz theme "$action" '')
|
||||
print -r -- "CANDIDATES:$action"
|
||||
_omz
|
||||
print -r -- "END:$action"
|
||||
done
|
||||
print -r -- "STILL_LAZY:$+functions[_omz_theme_browser]:$+functions[_omz_theme_preview]"
|
||||
print -r -- COMPLETIONS_FINISHED
|
||||
exit
|
||||
fi
|
||||
if [[ $2 == reject* ]]; then
|
||||
case $2 in
|
||||
reject-stdin) omz theme browse </dev/null ;;
|
||||
reject-stdout) omz theme browse >/dev/null ;;
|
||||
*) omz theme browse ;;
|
||||
esac
|
||||
result=$?
|
||||
print -r -- "REJECT:$result:$+functions[_omz_theme_browser]:$+functions[_omz_theme_preview]"
|
||||
exit
|
||||
fi
|
||||
function _omz::theme::use { calls+=("use:$1"); ZSH_THEME=$1; }
|
||||
function _omz::theme::set { calls+=("set:$1"); ZSH_THEME=$1; }
|
||||
function browser_test_hook { : parent-hook; }
|
||||
function precmd { : parent-precmd; }
|
||||
typeset -a calls=() precmd_functions=(browser_test_hook) chpwd_functions=(browser_test_hook)
|
||||
ZSH_THEME=parent-theme PROMPT=parent-prompt RPROMPT=parent-rprompt
|
||||
typeset original_hook=$functions[browser_test_hook] original_precmd=$functions[precmd]
|
||||
typeset original_options=$(setopt) original_pwd=$PWD
|
||||
stty rows 24 cols 100
|
||||
# PENDIN is a transient kernel input-reprocessing flag, not a user mode.
|
||||
typeset original_tty=$(stty -a)
|
||||
original_tty=${${original_tty//-pendin/}//pendin/}
|
||||
tty > "$TEST_SCRATCH/tty"
|
||||
print -r -- READY
|
||||
# An interactive script otherwise aborts on SIGINT instead of returning to
|
||||
# the next prompt as an interactive command loop would.
|
||||
trap ':' INT
|
||||
omz theme browse "$2"
|
||||
result=$?
|
||||
typeset restored_tty=$(stty -a)
|
||||
restored_tty=${${restored_tty//-pendin/}//pendin/}
|
||||
if [[ $restored_tty == "$original_tty" ]]; then
|
||||
print -r -- TTY_RESTORED
|
||||
else
|
||||
print -r -- "TTY_MISMATCH:before=$original_tty after=$restored_tty"
|
||||
fi
|
||||
if [[ $PROMPT == parent-prompt && $RPROMPT == parent-rprompt &&
|
||||
$functions[browser_test_hook] == "$original_hook" &&
|
||||
$functions[precmd] == "$original_precmd" &&
|
||||
${(j:,:)precmd_functions} == browser_test_hook &&
|
||||
${(j:,:)chpwd_functions} == browser_test_hook &&
|
||||
-o promptsubst && $(setopt) == "$original_options" && $PWD == "$original_pwd" &&
|
||||
${BROWSER_TEST_LEAK-unset} == unset ]]; then
|
||||
print -r -- PARENT_UNCHANGED
|
||||
fi
|
||||
print -r -- "RESULT:$result:${(j:,:)calls}:$ZSH_THEME"
|
||||
print -r -- FINISHED
|
||||
exit 0
|
||||
fi
|
||||
|
||||
setopt err_exit pipe_fail
|
||||
[[ -n $BROWSER_TEST_TRACE ]] && setopt xtrace
|
||||
zmodload zsh/zpty
|
||||
zmodload zsh/datetime
|
||||
zmodload zsh/zselect
|
||||
typeset -r repo=${0:A:h:h:h:h} self=${0:A}
|
||||
typeset scratch=$(mktemp -d "${TMPDIR:-/tmp}/omz-browser-test.XXXXXXXX")
|
||||
scratch=${scratch:A}
|
||||
trap 'zpty -d 2>/dev/null; command rm -rf -- "$scratch"' EXIT
|
||||
trap 'exit 130' INT
|
||||
trap 'exit 143' TERM
|
||||
mkdir -p "$scratch/home" "$scratch/custom/themes/nested" "$scratch/tmp"
|
||||
export ZSH=$repo ZSH_CUSTOM=$scratch/custom HOME=$scratch/home ZDOTDIR=$scratch/home
|
||||
export TMPDIR=$scratch/tmp TEST_SCRATCH=$scratch TERM=xterm-256color
|
||||
unset ZSH_THEME
|
||||
# Even a mistakenly enabled startup-file load must never reach real dotfiles.
|
||||
print -r -- 'print STARTUP_RAN; exit 99' > "$HOME/.zshenv"
|
||||
print -r -- 'PROMPT="nested fixture"' > "$ZSH_CUSTOM/themes/nested/browser-fixture.zsh-theme"
|
||||
for number in {01..10}; do
|
||||
print -r -- 'typeset -g BROWSER_TEST_LEAK=bad
|
||||
ZSH_THEME=fixture-theme
|
||||
precmd_functions=() chpwd_functions=()
|
||||
function precmd { PROMPT="%F{red}FIXTURE_PREVIEW%f" }
|
||||
RPROMPT="fixture-right"' > "$ZSH_CUSTOM/zz-omz-test-$number.zsh-theme"
|
||||
done
|
||||
print -r -- 'sleep 30 &
|
||||
print -r -- $! > "$TEST_SCRATCH/slow-pid"
|
||||
wait
|
||||
PROMPT="SLOW_FINISHED"' > "$ZSH_CUSTOM/zz-omz-test-slow.zsh-theme"
|
||||
typeset filter_probe='$(print HIT > $TEST_SCRATCH/filter-executed)'
|
||||
typeset preview_probe='$(print HIT > $TEST_SCRATCH/preview-executed)'
|
||||
# Emit literal preview output, rather than asking the worker to evaluate a
|
||||
# prompt expression. Only a second evaluation by the browser can execute it.
|
||||
print -rl -- "print -r -- ${(q)preview_probe}" 'PROMPT="literal-preview-ready"' > "$ZSH_CUSTOM/literal-browser.zsh-theme"
|
||||
|
||||
typeset buffer='' transcript='' chunk='' label=''
|
||||
typeset -F browser_finished_at=0
|
||||
function cleanup_pty {
|
||||
local record group
|
||||
for record in "${(@f)$(zpty)}"; do
|
||||
[[ $record == \(<->\)\ browser:* ]] || continue
|
||||
group=${${record#\(}%%\)*}
|
||||
kill -HUP -- -$group 2>/dev/null || true
|
||||
zselect -t 20 || true
|
||||
# A renderer regression can loop without checking its cancellation flag.
|
||||
kill -KILL -- -$group 2>/dev/null || true
|
||||
done
|
||||
zpty -d 2>/dev/null || true
|
||||
return 0
|
||||
}
|
||||
function fail {
|
||||
print -u2 -r -- "FAIL: $label: $1"
|
||||
print -u2 -r -- "PTY transcript, last 4000 characters (escaped): ${(V)transcript[-4000,-1]}"
|
||||
exit 1
|
||||
}
|
||||
function check {
|
||||
"$@" || fail "$1 assertion failed"
|
||||
}
|
||||
function await {
|
||||
local wanted=$1
|
||||
local -F deadline=$(( EPOCHREALTIME + ${2:-6} ))
|
||||
[[ $buffer == *"$wanted"* ]] && return 0
|
||||
while (( EPOCHREALTIME < deadline )); do
|
||||
if zpty -r browser chunk; then
|
||||
buffer+=$chunk transcript+=$chunk
|
||||
[[ $buffer == *"$wanted"* ]] && return 0
|
||||
else
|
||||
zselect -t 1 || true
|
||||
fi
|
||||
done
|
||||
fail "timed out waiting for ${(V)wanted}"
|
||||
}
|
||||
function send {
|
||||
buffer=''
|
||||
zpty -w -n browser "$1"
|
||||
}
|
||||
function start {
|
||||
label=$1 buffer='' transcript=''
|
||||
local -a launch=("$commands[zsh]" -dfi "$self" --child "$2")
|
||||
zpty -b browser exec "${(@q)launch}"
|
||||
await 'LAZY:0:0'
|
||||
await "$3"
|
||||
}
|
||||
function finish {
|
||||
await FINISHED
|
||||
# zpty -d can spend a second reaping an already-completed child. It is
|
||||
# harness teardown, not browser cancellation/terminal-restoration latency.
|
||||
browser_finished_at=$EPOCHREALTIME
|
||||
check test "${transcript#*TTY_RESTORED}" != "$transcript"
|
||||
check test "${transcript#*PARENT_UNCHANGED}" != "$transcript"
|
||||
check test "${transcript#*RESULT:$1}" != "$transcript"
|
||||
check test "${transcript#*$'\e[?1049l'}" != "$transcript"
|
||||
check test "${transcript#*$'\e[?25h'}" != "$transcript"
|
||||
check test "${transcript#*STARTUP_RAN}" = "$transcript"
|
||||
zpty -d browser
|
||||
[[ -z $BROWSER_TEST_TIMING ]] || print -r -- "TIMING: PTY deletion $(( EPOCHREALTIME - browser_finished_at ))s"
|
||||
}
|
||||
|
||||
function test_completion {
|
||||
start 'completion registration, commands, theme candidates, lazy loading' completion COMPLETIONS_FINISHED
|
||||
check test "${transcript#*REGISTERED:_omz:omz}" != "$transcript"
|
||||
local section=${${transcript#*SUBCOMMANDS}%END_SUBCOMMANDS*} action
|
||||
check test "${section#*browse:Browse theme previews}" != "$section"
|
||||
check test "${section#*preview:Preview a theme without applying it}" != "$section"
|
||||
for action in browse preview set use; do
|
||||
section=${${transcript#*CANDIDATES:$action}%END:$action*}
|
||||
check test "${section#*DESCRIBE:theme}" != "$section"
|
||||
for name in robbyrussell zz-omz-test-01 nested/browser-fixture; do
|
||||
check test "${section#*$name}" != "$section"
|
||||
done
|
||||
done
|
||||
check test "${transcript#*STILL_LAZY:0:0}" != "$transcript"
|
||||
check test "${transcript#*STARTUP_RAN}" = "$transcript"
|
||||
zpty -d browser
|
||||
}
|
||||
|
||||
function test_rejection {
|
||||
# Noninteractive rejection even with both descriptors attached to a PTY.
|
||||
label=noninteractive
|
||||
typeset -a launch=("$commands[zsh]" -df "$self" --child reject)
|
||||
zpty -b browser exec "${(@q)launch}"
|
||||
await 'REJECT:1:0:0'
|
||||
check test "${transcript#*requires an interactive terminal}" != "$transcript"
|
||||
zpty -d browser
|
||||
for mode in stdin stdout; do
|
||||
start "redirected $mode" "reject-$mode" 'REJECT:1:0:0'
|
||||
check test "${transcript#*requires an interactive terminal}" != "$transcript"
|
||||
zpty -d browser
|
||||
done
|
||||
label='noninteractive and redirected-I/O rejection'
|
||||
}
|
||||
|
||||
function test_navigation {
|
||||
start 'filter, navigation, actions/back, Esc isolation' zz-omz-test- '> zz-omz-test-01'
|
||||
await FIXTURE_PREVIEW
|
||||
send $'\e[B'; await '> zz-omz-test-02'
|
||||
send $'\e[A'; await '> zz-omz-test-01'
|
||||
send $'\x0e'; await '> zz-omz-test-02'
|
||||
send $'\x10'; await '> zz-omz-test-01'
|
||||
send $'\e[6~'; await '> zz-omz-test-05'
|
||||
send $'\e[5~'; await '> zz-omz-test-01'
|
||||
send '09'; await '> zz-omz-test-09'
|
||||
send $'\x7f'; await '(9 themes)'
|
||||
send $'\x15'; await 'Filter: ('
|
||||
send 'ZZ-OMZ-TEST-03'; await '> zz-omz-test-03'
|
||||
send $'\r'; await '[u] Use in session'
|
||||
send b; await 'Enter: actions'
|
||||
send $'\x15no-such-omz-fixture'; await 'No matching themes.'
|
||||
send $'\r'; await 'No matching themes.'
|
||||
send $'\e'
|
||||
finish '0::parent-theme'
|
||||
}
|
||||
|
||||
function test_use {
|
||||
start 'explicit use dispatch' zz-omz-test-01 '> zz-omz-test-01'
|
||||
send $'\r'; await '[u] Use in session'
|
||||
send u
|
||||
finish '0:use:zz-omz-test-01:zz-omz-test-01'
|
||||
}
|
||||
|
||||
function test_promptsubst {
|
||||
start 'literal filter/preview substitutions with parent promptsubst enabled' literal-browser '> literal-browser'
|
||||
await "$preview_probe"
|
||||
await literal-preview-ready
|
||||
check test ! -e "$scratch/preview-executed"
|
||||
send $'\x15'"$filter_probe"
|
||||
await "Filter: $filter_probe"
|
||||
await 'No matching themes.'
|
||||
check test ! -e "$scratch/filter-executed"
|
||||
send $'\x15literal-browser'
|
||||
await "$preview_probe"
|
||||
await literal-preview-ready
|
||||
send $'\e'
|
||||
finish '0::parent-theme'
|
||||
check test ! -e "$scratch/filter-executed"
|
||||
check test ! -e "$scratch/preview-executed"
|
||||
}
|
||||
|
||||
function test_save {
|
||||
start 'explicit save dispatch (stubbed)' zz-omz-test-02 '> zz-omz-test-02'
|
||||
send $'\r'; await '[u] Use in session'
|
||||
send s
|
||||
finish '0:set:zz-omz-test-02:zz-omz-test-02'
|
||||
}
|
||||
|
||||
function test_interrupt {
|
||||
start 'Ctrl-C restores terminal' zz-omz-test-01 '> zz-omz-test-01'
|
||||
send $'\x03'
|
||||
finish '130::parent-theme'
|
||||
}
|
||||
|
||||
function test_resize {
|
||||
start 'resize small and back' zz-omz-test-01 '> zz-omz-test-01'
|
||||
typeset terminal=$(<"$scratch/tty")
|
||||
terminal=${terminal//$'\r'/}
|
||||
buffer=''
|
||||
stty rows 10 cols 30 < "$terminal"
|
||||
await '40 columns x 16 rows.'
|
||||
buffer=''
|
||||
stty rows 24 cols 100 < "$terminal"
|
||||
await '> zz-omz-test-01'
|
||||
send $'\e'
|
||||
finish '0::parent-theme'
|
||||
}
|
||||
|
||||
function test_slow {
|
||||
local cancellation=$1
|
||||
command rm -f "$scratch/slow-pid"
|
||||
start "slow preview cancellation: $cancellation" zz-omz-test-slow '> zz-omz-test-slow'
|
||||
for attempt in {1..150}; do
|
||||
[[ -s "$scratch/slow-pid" ]] && break
|
||||
zselect -t 1 || true
|
||||
done
|
||||
check test -s "$scratch/slow-pid"
|
||||
typeset -F started=$EPOCHREALTIME
|
||||
case $cancellation in
|
||||
navigation)
|
||||
send $'\x15zz-omz-test-01'
|
||||
await FIXTURE_PREVIEW 2
|
||||
[[ -z $BROWSER_TEST_TIMING ]] || print -r -- "TIMING: navigation preview $(( EPOCHREALTIME - started ))s"
|
||||
(( EPOCHREALTIME - started < 2 )) || fail 'replacement preview exceeded 2 seconds'
|
||||
send $'\e'
|
||||
finish '0::parent-theme'
|
||||
;;
|
||||
escape) send $'\e'; finish '0::parent-theme' ;;
|
||||
interrupt) send $'\x03'; finish '130::parent-theme' ;;
|
||||
esac
|
||||
[[ -z $BROWSER_TEST_TIMING ]] || print -r -- "TIMING: browser completed $(( browser_finished_at - started ))s; including teardown $(( EPOCHREALTIME - started ))s"
|
||||
(( browser_finished_at - started < 2 )) || fail "browser completion exceeded 2 seconds: $(( browser_finished_at - started ))s"
|
||||
typeset child=$(<"$scratch/slow-pid") child_state
|
||||
for attempt in {1..100}; do
|
||||
child_state=$(command ps -o stat= -p "$child" 2>/dev/null || true)
|
||||
[[ -z "${child_state//[ ZN+]/}" ]] && break
|
||||
zselect -t 1 || true
|
||||
done
|
||||
[[ -z $BROWSER_TEST_TIMING ]] || print -r -- "TIMING: descendant $child state=${child_state:-absent}, cleanup polls=$attempt"
|
||||
[[ -z "${child_state//[ ZN+]/}" ]] || fail "preview descendant $child still running: $child_state"
|
||||
}
|
||||
|
||||
# Keep independent regressions running after a failed UI session.
|
||||
unsetopt err_exit
|
||||
typeset -i failures=0
|
||||
for scenario in rejection completion navigation promptsubst use save interrupt resize slow-navigation slow-escape slow-interrupt; do
|
||||
[[ -z $1 || $scenario == "$1" ]] || continue
|
||||
(
|
||||
setopt err_exit
|
||||
trap 'cleanup_pty' EXIT
|
||||
if [[ $scenario == slow-* ]]; then
|
||||
test_slow "${scenario#slow-}"
|
||||
else
|
||||
test_$scenario
|
||||
fi
|
||||
print -r -- "PASS: $label"
|
||||
)
|
||||
(( $? == 0 )) || (( failures++ ))
|
||||
done
|
||||
typeset -a leftovers
|
||||
for attempt in {1..100}; do
|
||||
leftovers=("$TMPDIR"/omz-preview.*(N))
|
||||
(( $#leftovers == 0 )) && break
|
||||
zselect -t 1 || true
|
||||
done
|
||||
if (( $#leftovers )); then
|
||||
print -u2 -r -- "FAIL: $#leftovers preview temporary directories remain"
|
||||
(( failures++ ))
|
||||
fi
|
||||
print -r -- "Theme browser regression groups failed: $failures"
|
||||
(( failures == 0 ))
|
||||
@@ -0,0 +1,282 @@
|
||||
#!/usr/bin/env zsh
|
||||
# Run with: zsh -df tools/theme-browser/tests/preview_test.zsh
|
||||
emulate -R zsh
|
||||
setopt err_exit pipe_fail
|
||||
typeset -r repo=${0:A:h:h:h:h}
|
||||
source "$repo/tools/theme-browser/preview.zsh"
|
||||
typeset scratch=$(mktemp -d "${TMPDIR:-/tmp}/omz-preview-test.XXXXXXXX")
|
||||
scratch=${scratch:A}
|
||||
trap 'command rm -rf -- "$scratch"' EXIT
|
||||
trap 'exit 130' INT
|
||||
trap 'exit 143' TERM
|
||||
mkdir "$scratch/tmp"
|
||||
export TMPDIR=$scratch/tmp
|
||||
|
||||
function check {
|
||||
if ! "$@"; then
|
||||
print -u2 -r -- "FAIL: ${(j: :)@}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
export ZSH=$scratch/omz ZSH_CUSTOM=$scratch/custom
|
||||
mkdir -p "$ZSH/themes" "$ZSH_CUSTOM/themes/nested" "$scratch/home" "$scratch/work"
|
||||
print -r -- "PROMPT='bundled'" > "$ZSH/themes/same.zsh-theme"
|
||||
print -r -- "PROMPT='custom themes'" > "$ZSH_CUSTOM/themes/same.zsh-theme"
|
||||
print -r -- "PROMPT='custom root'" > "$ZSH_CUSTOM/same.zsh-theme"
|
||||
print -r -- "PROMPT='z'" > "$ZSH/themes/zebra.zsh-theme"
|
||||
print -r -- "PROMPT='a'" > "$ZSH/themes/alpha.zsh-theme"
|
||||
print -r -- "PROMPT='bad'" > "$ZSH/themes/"$'bad\nname.zsh-theme'
|
||||
print -r -- "PROMPT='random'" > "$ZSH/themes/random.zsh-theme"
|
||||
print -r -- "PROMPT='nested'" > "$ZSH_CUSTOM/themes/nested/example.zsh-theme"
|
||||
check test "$(_omz_theme_names)" = $'alpha\nnested/example\nsame\nzebra'
|
||||
check test "$(_omz_theme_resolve nested/example)" = "$ZSH_CUSTOM/themes/nested/example.zsh-theme"
|
||||
check test "$(_omz_theme_resolve same)" = "$ZSH_CUSTOM/same.zsh-theme"
|
||||
rm "$ZSH_CUSTOM/same.zsh-theme"
|
||||
check test "$(_omz_theme_resolve same)" = "$ZSH_CUSTOM/themes/same.zsh-theme"
|
||||
rm "$ZSH_CUSTOM/themes/same.zsh-theme"
|
||||
check test "$(_omz_theme_resolve same)" = "$ZSH/themes/same.zsh-theme"
|
||||
for name in ../same /same . .. nested/../same nested/./example nested//example random $'bad\nname' 'bad\name' missing ''; do
|
||||
if _omz_theme_resolve "$name" > /dev/null 2>&1; then
|
||||
print -u2 -r -- 'FAIL: accepted invalid/missing name'
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# The fixtures intentionally write only inside this private, always-cleaned tree.
|
||||
print -r -- 'print STARTUP_RAN; exit 99' > "$scratch/home/.zshenv"
|
||||
export ZDOTDIR=$scratch/home PREVIEW_ENV=preserved
|
||||
print -r -- '
|
||||
typeset -g PREVIEW_LEAK=bad
|
||||
export PREVIEW_ENV=changed
|
||||
alias preview_leak=true
|
||||
setopt noclobber
|
||||
function precmd { PROMPT="hook:$PWD:$PREVIEW_ENV:%?" }
|
||||
function second_hook { RPROMPT="second-hook:%?" }
|
||||
precmd_functions=(second_hook)
|
||||
' > "$ZSH_CUSTOM/isolation.zsh-theme"
|
||||
typeset before_pwd=$PWD before_options=$(setopt) before_env=$PREVIEW_ENV
|
||||
typeset output=$(_omz_theme_preview isolation 7)
|
||||
check test "$PWD" = "$before_pwd"
|
||||
check test "$(setopt)" = "$before_options"
|
||||
check test "$PREVIEW_ENV" = "$before_env"
|
||||
check test "${PREVIEW_LEAK-unset}" = unset
|
||||
check test "${aliases[preview_leak]-unset}" = unset
|
||||
check test "${functions[second_hook]-unset}" = unset
|
||||
check test "${output#*hook:${PWD}:changed:7}" != "$output"
|
||||
check test "${output#*second-hook:7}" != "$output"
|
||||
check test "${output#*STARTUP_RAN}" = "$output"
|
||||
|
||||
print -r -- 'PROMPT="env:$PREVIEW_ENV cwd:$PWD %(?.success.failure)"' > "$ZSH_CUSTOM/status.zsh-theme"
|
||||
output=$(_omz_theme_preview status)
|
||||
check test "${output#*env:preserved cwd:${PWD} success}" != "$output"
|
||||
output=$(_omz_theme_preview status 1)
|
||||
check test "${output#*failure}" != "$output"
|
||||
if _omz_theme_preview status nope >/dev/null 2>&1; then exit 1; fi
|
||||
|
||||
# Values produced by parameters or helpers are prompt text, not shell code for
|
||||
# a second substitution pass. Check both sides and the helper's incoming status.
|
||||
export PREVIEW_REEXECUTED=$scratch/reexecuted
|
||||
export PREVIEW_LITERAL='$(print reexecuted > "$PREVIEW_REEXECUTED")'
|
||||
print -r -- '
|
||||
function literal_helper { print -r -- "helper-status:$? $PREVIEW_LITERAL" }
|
||||
PROMPT='"'"'$PREVIEW_LITERAL $(literal_helper) %?'"'"'
|
||||
RPROMPT='"'"'$(literal_helper) $PREVIEW_LITERAL %?'"'"'
|
||||
' > "$ZSH_CUSTOM/literal.zsh-theme"
|
||||
output=$(_omz_theme_preview literal 7)
|
||||
check test ! -e "$PREVIEW_REEXECUTED"
|
||||
check test "${output#*"$PREVIEW_LITERAL"}" != "$output"
|
||||
check test "${output#*helper-status:7}" != "$output"
|
||||
check test "${output#*"$PREVIEW_LITERAL 7"}" != "$output"
|
||||
print -r -- 'unsetopt promptsubst; PROMPT='"'"'$PREVIEW_LITERAL %?'"'" > "$ZSH_CUSTOM/no-subst.zsh-theme"
|
||||
output=$(_omz_theme_preview no-subst 7)
|
||||
check test "${output#*'$PREVIEW_LITERAL 7'}" != "$output"
|
||||
|
||||
print -r -- 'PROMPT="conditional prompt"; [[ -n ${OMZ_PREVIEW_UNSET_SSH-} ]] && RPROMPT="remote"' > "$ZSH_CUSTOM/final-false.zsh-theme"
|
||||
output=$(_omz_theme_preview final-false)
|
||||
check test "${output#*conditional prompt}" != "$output"
|
||||
print -r -- 'PROMPT=""' > "$ZSH_CUSTOM/empty.zsh-theme"
|
||||
output=$(_omz_theme_preview empty)
|
||||
print -r -- 'RPROMPT="right-only"; false' > "$ZSH_CUSTOM/right-only.zsh-theme"
|
||||
output=$(_omz_theme_preview right-only)
|
||||
check test "${output#*right-only}" != "$output"
|
||||
|
||||
# A helper can fail inside command substitution without making print -P fail.
|
||||
# Its runtime diagnostic must remain visible in the sanitized partial preview.
|
||||
print -r -- 'PROMPT='"'"'$( _omz_preview_missing_helper )'"'" > "$ZSH_CUSTOM/helper-failure.zsh-theme"
|
||||
output=$(_omz_theme_preview helper-failure 2>&1)
|
||||
check test "${output#*command not found: _omz_preview_missing_helper}" != "$output"
|
||||
|
||||
# A failing precmd (or array hook) stops later hooks, as in interactive zsh.
|
||||
# Preserve any partial preview, but report failure rather than silently claiming
|
||||
# that an incomplete initialization is a faithful preview.
|
||||
for hook_setup in 'precmd() { return 3 }; precmd_functions=(must_not_run)' \
|
||||
'failed_hook() { return 4 }; precmd_functions=(failed_hook must_not_run)' \
|
||||
'failed_hook() { _omz_preview_missing_command }; precmd_functions=(failed_hook must_not_run)'; do
|
||||
print -rl -- 'PROMPT="partial prompt"; must_not_run() { print UNEXPECTED_HOOK; }' "$hook_setup" > "$ZSH_CUSTOM/hook-failure.zsh-theme"
|
||||
if _omz_theme_preview hook-failure >"$scratch/hook-error" 2>&1; then
|
||||
print -u2 -r -- 'FAIL: accepted failing precmd hook'
|
||||
exit 1
|
||||
fi
|
||||
output=$(<"$scratch/hook-error")
|
||||
check test "${output#*UNEXPECTED_HOOK}" = "$output"
|
||||
check test "${output#*remaining hooks skipped}" != "$output"
|
||||
check test "${output#*partial prompt}" != "$output"
|
||||
done
|
||||
|
||||
# No language runtime, timeout utility, or process-inspection utility is needed
|
||||
# by the backend. The worker still has zsh's usual autoload/module search paths.
|
||||
mkdir "$scratch/bin"
|
||||
for name in zsh mktemp mkfifo rm; do
|
||||
ln -s "$commands[$name]" "$scratch/bin/$name"
|
||||
done
|
||||
output=$(PATH="$scratch/bin" _omz_theme_preview status)
|
||||
check test "${output#*success}" != "$output"
|
||||
print -r -- '[[ ! -t 0 && ! -t 1 && ! -t 2 && ! -o interactive ]] || exit 9; PROMPT="non-tty"' > "$ZSH_CUSTOM/non-tty.zsh-theme"
|
||||
output=$(_omz_theme_preview non-tty)
|
||||
check test "${output#*non-tty}" != "$output"
|
||||
|
||||
print -r -- 'print -n -- "\e]52;c;SECRET\a\e[2J\r\b\ePSECRET\e\\"; PROMPT="%F{red}safe%f"' > "$ZSH_CUSTOM/escapes.zsh-theme"
|
||||
output=$(_omz_theme_preview escapes)
|
||||
check test "${output#*SECRET}" = "$output"
|
||||
check test "${output#*$'\e[2J'}" = "$output"
|
||||
check test "${output#*$'\r'}" = "$output"
|
||||
check test "${output#*$'\e[31m'safe}" != "$output"
|
||||
|
||||
for code in 'return 1' 'exit 0' 'exit 7' 'if then' 'PROMPT="partial"; if then' \
|
||||
'PROMPT="partial"; return 2' 'PROMPT="partial"; _omz_preview_missing_command'; do
|
||||
print -r -- "$code" > "$ZSH_CUSTOM/broken.zsh-theme"
|
||||
if _omz_theme_preview broken >"$scratch/error" 2>&1; then
|
||||
print -u2 -r -- "FAIL: accepted broken theme: $code"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
print -r -- 'while true; do print -r -- xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; done' > "$ZSH_CUSTOM/flood.zsh-theme"
|
||||
if _omz_theme_preview flood >"$scratch/flood" 2>&1; then exit 1; fi
|
||||
check test "$(wc -c < "$scratch/flood")" -le 32768
|
||||
print -r -- 'printf "\377%.0s" {1..11000}; PROMPT="safe"' > "$ZSH_CUSTOM/bytes.zsh-theme"
|
||||
_omz_theme_preview bytes >"$scratch/bytes" 2>&1
|
||||
check test "$(wc -c < "$scratch/bytes")" -le 32768
|
||||
check test "${$(<"$scratch/bytes")#*$'\xff'}" = "$(<"$scratch/bytes")"
|
||||
print -r -- 'printf "\233m%.0s" {1..12000}; PROMPT="safe"' > "$ZSH_CUSTOM/csi-bytes.zsh-theme"
|
||||
_omz_theme_preview csi-bytes >"$scratch/csi-bytes" 2>&1
|
||||
check test "$(wc -c < "$scratch/csi-bytes")" -le 32768
|
||||
|
||||
print -r -- 'sleep 30 & print -r -- $! > "$PREVIEW_PID_FILE"; wait' > "$ZSH_CUSTOM/slow.zsh-theme"
|
||||
export PREVIEW_PID_FILE=$scratch/child
|
||||
zmodload zsh/datetime
|
||||
typeset -F started=$EPOCHREALTIME
|
||||
if _omz_theme_preview slow >"$scratch/timeout" 2>&1; then exit 1; fi
|
||||
check test $(( EPOCHREALTIME - started < 5 )) = 1
|
||||
check test "${$(<"$scratch/timeout")#*timed out}" != "$(<"$scratch/timeout")"
|
||||
typeset child=$(<"$scratch/child")
|
||||
# A killed orphan may briefly remain a zombie until the system reaps it.
|
||||
typeset child_state=$(command ps -o stat= -p "$child" 2>/dev/null || true)
|
||||
check test -z "${child_state//[ ZN+]/}"
|
||||
|
||||
for signal in INT TERM HUP; do
|
||||
rm -f "$PREVIEW_PID_FILE"
|
||||
_omz_theme_preview slow >"$scratch/cancel" 2>&1 &
|
||||
typeset preview_job=$!
|
||||
for attempt in {1..100}; do
|
||||
[[ -s "$PREVIEW_PID_FILE" ]] && break
|
||||
sleep 0.01
|
||||
done
|
||||
check test -s "$PREVIEW_PID_FILE"
|
||||
# zsh adds a waiting wrapper around a backgrounded subshell function.
|
||||
# Signal the supervisor itself; terminal interrupts reach it via the job group.
|
||||
typeset supervisor='' candidate='' parent=''
|
||||
for candidate parent in ${(z)$(command ps -A -o pid= -o ppid=)}; do
|
||||
if [[ $parent == $preview_job ]]; then
|
||||
supervisor=$candidate
|
||||
break
|
||||
fi
|
||||
done
|
||||
check test -n "$supervisor"
|
||||
kill -$signal "$supervisor"
|
||||
typeset result=0
|
||||
wait "$preview_job" || result=$?
|
||||
if (( result != 130 && result != 143 && result != 129 )); then
|
||||
print -u2 -r -- "Unexpected cancellation result: $result: $(<"$scratch/cancel")"
|
||||
fi
|
||||
if [[ $signal == INT ]]; then
|
||||
check test "$result" = 130
|
||||
elif [[ $signal == TERM ]]; then
|
||||
check test "$result" = 143
|
||||
else
|
||||
check test "$result" = 129
|
||||
fi
|
||||
child=$(<"$PREVIEW_PID_FILE")
|
||||
child_state=$(command ps -o stat= -p "$child" 2>/dev/null || true)
|
||||
check test -z "${child_state//[ ZN+]/}"
|
||||
done
|
||||
|
||||
print -r -- 'sleep 30 & print -r -- $! > "$PREVIEW_PID_FILE"; PROMPT="background"' > "$ZSH_CUSTOM/background.zsh-theme"
|
||||
output=$(_omz_theme_preview background)
|
||||
child=$(<"$PREVIEW_PID_FILE")
|
||||
child_state=$(command ps -o stat= -p "$child" 2>/dev/null || true)
|
||||
check test -z "${child_state//[ ZN+]/}"
|
||||
|
||||
# Match the browser's nested-PTY invocation, preserving other owned PTYs.
|
||||
zmodload zsh/zpty
|
||||
zpty -b unrelated 'exec sleep 30'
|
||||
typeset -a nested_command=("$commands[zsh]" -dfc 'source "$1"; _omz_theme_preview status' zsh "$repo/tools/theme-browser/preview.zsh")
|
||||
zpty -b browser exec "${(@q)nested_command}"
|
||||
output=''
|
||||
for attempt in {1..300}; do
|
||||
if zpty -r browser chunk; then
|
||||
output+=$chunk
|
||||
elif ! zpty -t browser; then
|
||||
break
|
||||
else
|
||||
sleep 0.01
|
||||
fi
|
||||
done
|
||||
check test "${output#*success}" != "$output"
|
||||
check zpty -t unrelated
|
||||
zpty -d browser unrelated
|
||||
|
||||
# Cancellation by the browser's outer process group must also clean the
|
||||
# backend's separate inner PTY group and private FIFO directory.
|
||||
rm -f "$PREVIEW_PID_FILE"
|
||||
nested_command=("$commands[zsh]" -dfc 'source "$1"; _omz_theme_preview slow' zsh "$repo/tools/theme-browser/preview.zsh")
|
||||
zpty -b browser exec "${(@q)nested_command}"
|
||||
typeset browser_group record
|
||||
for record in "${(@f)$(zpty)}"; do
|
||||
[[ $record == \(<->\)\ browser:* ]] && browser_group=${${record#\(}%%\)*}
|
||||
done
|
||||
check test -n "$browser_group"
|
||||
for attempt in {1..100}; do
|
||||
[[ -s "$PREVIEW_PID_FILE" ]] && break
|
||||
sleep 0.01
|
||||
done
|
||||
check test -s "$PREVIEW_PID_FILE"
|
||||
child=$(<"$PREVIEW_PID_FILE")
|
||||
kill -TERM -- -$browser_group
|
||||
for attempt in {1..100}; do
|
||||
child_state=$(command ps -o stat= -p "$child" 2>/dev/null || true)
|
||||
[[ -z "${child_state//[ ZN+]/}" ]] && break
|
||||
sleep 0.01
|
||||
done
|
||||
check test -z "${child_state//[ ZN+]/}"
|
||||
zpty -d browser
|
||||
|
||||
# Real themes exercise synchronous git, top-level locals, command substitution,
|
||||
# and hook-driven prompt construction against an isolated repository.
|
||||
export ZSH=$repo
|
||||
git -C "$scratch/work" init -q
|
||||
git -C "$scratch/work" symbolic-ref HEAD refs/heads/preview-branch
|
||||
output=$(zsh -dfc 'builtin cd "$1"; source tools/theme-browser/preview.zsh; builtin cd "$2"; _omz_theme_preview robbyrussell' zsh "$repo" "$scratch/work")
|
||||
check test "${output#*preview-branch}" != "$output"
|
||||
for name in robbyrussell dieter agnoster bira nicoulaj half-life; do
|
||||
output=$(builtin cd "$scratch/work"; _omz_theme_preview "$name" 7)
|
||||
check test "${output#*Left prompt}" != "$output"
|
||||
check test "${output#*Right prompt}" != "$output"
|
||||
check test "${output#*preview-branch}" != "$output"
|
||||
if [[ $name == dieter || $name == bira ]]; then
|
||||
check test "${output#*7 }" != "$output"
|
||||
fi
|
||||
done
|
||||
typeset -a leftovers=("$TMPDIR"/omz-preview.*(N))
|
||||
check test "$#leftovers" = 0
|
||||
print -r -- 'PASS: theme preview resolution, isolation, single-pass expansion, conditional loads, hook failures, status, sanitization, limits, cancellation, descendant cleanup, and six bundled themes'
|
||||
Reference in New Issue
Block a user