feat: add plugins for zsh
This commit is contained in:
1
.zsh/zsh-fzf-tab/test/.gitignore
vendored
Normal file
1
.zsh/zsh-fzf-tab/test/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.zcompdump
|
||||
174
.zsh/zsh-fzf-tab/test/comptest
Normal file
174
.zsh/zsh-fzf-tab/test/comptest
Normal file
@@ -0,0 +1,174 @@
|
||||
comptestinit () {
|
||||
setopt extendedglob
|
||||
|
||||
zmodload zsh/zpty || return $?
|
||||
|
||||
comptest_zsh=${ZSH:-zsh}
|
||||
comptest_keymap=e
|
||||
|
||||
while getopts vz: opt; do
|
||||
case $opt in
|
||||
z) comptest_zsh="$OPTARG";;
|
||||
v) comptest_keymap="v";;
|
||||
esac
|
||||
done
|
||||
(( OPTIND > 1 )) && shift $(( OPTIND - 1 ))
|
||||
|
||||
export PS1="<PROMPT>"
|
||||
zpty zsh "$comptest_zsh -f +Z"
|
||||
|
||||
zpty -r zsh log1 "*<PROMPT>*" || {
|
||||
print "first prompt hasn't appeared."
|
||||
return 1
|
||||
}
|
||||
|
||||
comptesteval \
|
||||
"export LC_ALL=${ZSH_TEST_LANG:-C}" \
|
||||
"emulate -R zsh" \
|
||||
"export ZDOTDIR=$ZTST_testdir" \
|
||||
"bindkey -$comptest_keymap" \
|
||||
'LISTMAX=10000000
|
||||
stty 38400 columns 80 rows 24 tabs -icanon -iexten
|
||||
TERM=vt100
|
||||
KEYTIMEOUT=1
|
||||
setopt zle
|
||||
autoload -U compinit
|
||||
compinit -u
|
||||
zstyle ":completion:*:default" list-colors "no=<NO>" "fi=<FI>" "di=<DI>" "ln=<LN>" "pi=<PI>" "so=<SO>" "bd=<BD>" "cd=<CD>" "ex=<EX>" "mi=<MI>" "tc=<TC>" "sp=<SP>" "lc=<LC>" "ec=<EC>\n" "rc=<RC>"
|
||||
zstyle ":completion:*" group-name ""
|
||||
zstyle ":completion:*:messages" format "<MESSAGE>%d</MESSAGE>
|
||||
"
|
||||
zstyle ":completion:*:descriptions" format "<DESCRIPTION>%d</DESCRIPTION>"
|
||||
zstyle ":completion:*:options" verbose yes
|
||||
zstyle ":completion:*:values" verbose yes
|
||||
setopt noalwayslastprompt listrowsfirst completeinword
|
||||
zmodload zsh/complist
|
||||
expand-or-complete-with-report () {
|
||||
print -lr "<WIDGET><expand-or-complete>"
|
||||
zle expand-or-complete
|
||||
print -lr - "<LBUFFER>$LBUFFER</LBUFFER>" "<RBUFFER>$RBUFFER</RBUFFER>"
|
||||
zle clear-screen
|
||||
zle -R
|
||||
}
|
||||
list-choices-with-report () {
|
||||
print -lr "<WIDGET><list-choices>"
|
||||
zle list-choices
|
||||
zle clear-screen
|
||||
zle -R
|
||||
}
|
||||
comp-finish () {
|
||||
print "<WIDGET><finish>"
|
||||
zle kill-whole-line
|
||||
zle clear-screen
|
||||
zle -R
|
||||
}
|
||||
zle-finish () {
|
||||
local buffer="$BUFFER" cursor="$CURSOR" mark="$MARK"
|
||||
(( region_active)) || unset mark
|
||||
BUFFER=""
|
||||
zle -I
|
||||
zle clear-screen
|
||||
zle redisplay
|
||||
print -lr "<WIDGET><finish>" "BUFFER: $buffer" "CURSOR: $cursor"
|
||||
(( $+mark )) && print -lr "MARK: $mark"
|
||||
zle accept-line
|
||||
}
|
||||
zle -N expand-or-complete-with-report
|
||||
zle -N list-choices-with-report
|
||||
zle -N comp-finish
|
||||
zle -N zle-finish
|
||||
bindkey "^I" expand-or-complete-with-report
|
||||
bindkey "^D" list-choices-with-report
|
||||
bindkey "^Z" comp-finish
|
||||
bindkey "^X" zle-finish
|
||||
bindkey -a "^X" zle-finish
|
||||
'
|
||||
}
|
||||
|
||||
zpty_flush() {
|
||||
local junk
|
||||
if zpty -r -t zsh junk \*; then
|
||||
(( ZTST_verbose > 2 )) && print -n -u $ZTST_fd "$*: ${(V)junk}"
|
||||
while zpty -r -t zsh junk \* ; do
|
||||
(( ZTST_verbose > 2 )) && print -n -u $ZTST_fd "${(V)junk}"
|
||||
done
|
||||
(( ZTST_verbose > 2 )) && print -u $ZTST_fd ''
|
||||
fi
|
||||
}
|
||||
|
||||
zpty_run() {
|
||||
zpty -w zsh "$*"
|
||||
zpty -r -m zsh log "*<PROMPT>*" || {
|
||||
print "prompt hasn't appeared."
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
comptesteval () {
|
||||
local tmp=/tmp/comptest.$$
|
||||
|
||||
print -lr - "$@" > $tmp
|
||||
# zpty_flush Before comptesteval
|
||||
zpty -w zsh ". $tmp"
|
||||
zpty -r -m zsh log_eval "*<PROMPT>*" || {
|
||||
print "prompt hasn't appeared."
|
||||
return 1
|
||||
}
|
||||
zpty_flush After comptesteval
|
||||
rm $tmp
|
||||
}
|
||||
|
||||
comptest () {
|
||||
input="$*"
|
||||
zpty -n -w zsh "$input"$'\C-Z'
|
||||
zpty -r -m zsh log "*<WIDGET><finish>*<PROMPT>*" || {
|
||||
print "failed to invoke finish widget."
|
||||
return 1
|
||||
}
|
||||
|
||||
logs=(${(s:<WIDGET>:)log})
|
||||
shift logs
|
||||
|
||||
for log in "$logs[@]"; do
|
||||
if [[ "$log" = (#b)*$'<LBUFFER>'(*)$'</LBUFFER>\r\n<RBUFFER>'(*)$'</RBUFFER>'* ]]; then
|
||||
print -lr "line: {$match[1]}{$match[2]}"
|
||||
fi
|
||||
while (( ${(N)log#*(#b)(<LC><(??)><RC>(*)<EC>|<DESCRIPTION>(*)</DESCRIPTION>|<MESSAGE>(*)</MESSAGE>|<COMPADD>(*)</COMPADD>|<INSERT_POSITIONS>(*)</INSERT_POSITIONS>|<QUERY>(*)</QUERY>|<WARN>(*)</WARN>)} )); do
|
||||
log="${log[$mend[1]+1,-1]}"
|
||||
if (( 0 <= $mbegin[2] )); then
|
||||
if [[ $match[2] != TC && $match[3] != \ # ]]; then
|
||||
print -lr "$match[2]:{${match[3]%${(%):-%E}}}"
|
||||
fi
|
||||
elif (( 0 <= $mbegin[4] )); then
|
||||
print -lr "DESCRIPTION:{$match[4]}"
|
||||
elif (( 0 <= $mbegin[5] )); then
|
||||
print -lr "MESSAGE:{$match[5]}"
|
||||
elif (( 0 <= $mbegin[6] )); then
|
||||
print -lr "COMPADD:{${${match[6]}//[$'\r\n']/}}"
|
||||
elif (( 0 <= $mbegin[7] )); then
|
||||
print -lr "INSERT_POSITIONS:{${${match[7]}//[$'\r\n']/}}"
|
||||
elif (( 0 <= $mbegin[8] )); then
|
||||
print -lr "QUERY:{${match[8]}}"
|
||||
elif (( 0 <= $mbegin[9] )); then
|
||||
print -lr "WARN:{${match[9]}}"
|
||||
fi
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
zletest () {
|
||||
local first=0
|
||||
for input; do
|
||||
# zpty_flush Before zletest
|
||||
# sleep for $KEYTIMEOUT
|
||||
(( first++ )) && { sleep 2 & } | read -t 0.011 -u 0 -k 1
|
||||
zpty -n -w zsh "$input"
|
||||
done
|
||||
zpty -n -w zsh $'\C-X'
|
||||
zpty -r -m zsh log "*<WIDGET><finish>*<PROMPT>*" || {
|
||||
print "failed to invoke finish widget."
|
||||
return 1
|
||||
}
|
||||
# zpty_flush After zletest
|
||||
print -lr "${(@)${(@ps:\r\n:)log##*<WIDGET><finish>}[2,-2]}"
|
||||
}
|
||||
253
.zsh/zsh-fzf-tab/test/fzftab.ztst
Normal file
253
.zsh/zsh-fzf-tab/test/fzftab.ztst
Normal file
@@ -0,0 +1,253 @@
|
||||
# Tests for fzf tab.
|
||||
|
||||
%prep
|
||||
unset -m LC_\*
|
||||
ZSH_TEST_LANG=
|
||||
langs=(en_{US,GB}.{UTF-,utf}8 en.UTF-8
|
||||
$(locale -a 2>/dev/null | egrep 'utf8|UTF-8'))
|
||||
for LANG in $langs; do
|
||||
if [[ é = ? ]]; then
|
||||
ZSH_TEST_LANG=$LANG
|
||||
break;
|
||||
fi
|
||||
done
|
||||
if [[ $OSTYPE = cygwin ]]; then
|
||||
ZTST_unimplemented="the zsh/zpty module does not work on Cygwin"
|
||||
elif ( zmodload zsh/zpty 2>/dev/null ); then
|
||||
. $ZTST_srcdir/comptest
|
||||
mkdir comp.tmp
|
||||
cd comp.tmp
|
||||
comptestinit -z zsh &&
|
||||
{
|
||||
comptesteval 'compdef _tst tst'
|
||||
mkdir dir1 &&
|
||||
mkdir dir2 &&
|
||||
touch file1 &&
|
||||
touch file2
|
||||
touch dir1/file1
|
||||
git init
|
||||
}
|
||||
else
|
||||
ZTST_unimplemented="the zsh/zpty module is not available"
|
||||
fi
|
||||
|
||||
comptesteval ". $ZTST_srcdir/../fzf-tab.zsh"
|
||||
comptesteval "zstyle ':fzf-tab:*' debug-command $ZTST_srcdir/select -n 1 -h '\$#_ftb_headers' -q '\"\$_ftb_query\"'"
|
||||
comptesteval '
|
||||
zstyle ":fzf-tab:*" default-color "<LC><C0><RC>"
|
||||
zstyle ":fzf-tab:*" single-group color header
|
||||
zstyle ":fzf-tab:*" group-colors "<LC><C1><RC>" "<LC><C2><RC>" "<LC><C3><RC>" "<LC><C4><RC>"
|
||||
fzf-tab-complete-with-report() {
|
||||
print -lr "<WIDGET><fzf-tab-complete>"
|
||||
zle fzf-tab-complete 2>&1
|
||||
print -lr - "<LBUFFER>$LBUFFER</LBUFFER>" "<RBUFFER>$RBUFFER</RBUFFER>"
|
||||
zle clear-screen
|
||||
zle -R
|
||||
}
|
||||
zle -N fzf-tab-complete-with-report
|
||||
bindkey "^I" fzf-tab-complete-with-report
|
||||
'
|
||||
|
||||
%test
|
||||
|
||||
comptest $': \t'
|
||||
0:directories and files
|
||||
>line: {: dir1/}{}
|
||||
>QUERY:{}
|
||||
>DESCRIPTION:{file}
|
||||
>C1:{dir1/}
|
||||
>C1:{dir2/}
|
||||
>C1:{file1}
|
||||
>C1:{file2}
|
||||
|
||||
comptest $': d\t'
|
||||
0:unambiguous prefix
|
||||
>line: {: dir}{}
|
||||
|
||||
comptesteval '_tst() { compadd ".#abc" ".#def" ".#hij" }'
|
||||
comptest $'tst ".#"\t'
|
||||
0:unambiguous prefix in quote string
|
||||
>line: {tst ".#abc }{"}
|
||||
>QUERY:{.#}
|
||||
>C0:{.#abc}
|
||||
>C0:{.#def}
|
||||
>C0:{.#hij}
|
||||
|
||||
comptesteval '_tst() { compadd /home /usr /lib; compstate[insert]=menu }'
|
||||
comptest $'tst \t'
|
||||
0:force list
|
||||
>line: {tst /home }{}
|
||||
>QUERY:{/}
|
||||
>C0:{/home}
|
||||
>C0:{/lib}
|
||||
>C0:{/usr}
|
||||
|
||||
comptesteval 'zstyle ":completion:*" menu true'
|
||||
comptest $': d\t'
|
||||
0:prefix
|
||||
>line: {: dir1/}{}
|
||||
>QUERY:{dir}
|
||||
>DESCRIPTION:{file}
|
||||
>C1:{dir1/}
|
||||
>C1:{dir2/}
|
||||
|
||||
comptesteval '_tst () { compadd d c b a }'
|
||||
comptest $'tst \t'
|
||||
0:normal
|
||||
>line: {tst a }{}
|
||||
>QUERY:{}
|
||||
>C0:{a}
|
||||
>C0:{b}
|
||||
>C0:{c}
|
||||
>C0:{d}
|
||||
|
||||
comptesteval 'zstyle ":completion:*:tst:*" sort false'
|
||||
comptest $'tst \t'
|
||||
0:no sort
|
||||
>line: {tst d }{}
|
||||
>QUERY:{}
|
||||
>C0:{d}
|
||||
>C0:{c}
|
||||
>C0:{b}
|
||||
>C0:{a}
|
||||
|
||||
comptesteval 'zstyle ":fzf-tab:*:tst:*" fzf-flags -n 1,2'
|
||||
comptest $'tst \t'
|
||||
comptesteval 'zstyle -d ":fzf-tab:*:tst:*" fzf-flags'
|
||||
0:multi select
|
||||
>line: {tst c d }{}
|
||||
>QUERY:{}
|
||||
>C0:{d}
|
||||
>C0:{c}
|
||||
>C0:{b}
|
||||
>C0:{a}
|
||||
|
||||
comptest $': *\t'
|
||||
0:expand
|
||||
>line: {: dir1 dir2 file1 file2 }{}
|
||||
|
||||
comptesteval 'zstyle ":completion:*:warnings" format "<WARN>%d</WARN>"'
|
||||
comptest $': asd\t'
|
||||
0:warnings
|
||||
>line: {: asd}{}
|
||||
>WARN:{`file'}
|
||||
|
||||
# enclose ' for syntax highlight
|
||||
|
||||
comptesteval "touch 'abc def'"
|
||||
comptest $': ./a\t'
|
||||
0:filename with space
|
||||
>line: {: ./abc\ def }{}
|
||||
|
||||
comptest $': ./abdef\C-b\C-b\C-b\t'
|
||||
0:complete in word
|
||||
>line: {: ./abc\ def }{}
|
||||
|
||||
comptest $': ./abc def\C-b\C-b\C-b\C-b\t'
|
||||
comptesteval "rm 'abc def'"
|
||||
0:complete in word(with known bug)
|
||||
>line: {: ./abc\ def}{ def}
|
||||
|
||||
comptesteval 'mkdir -p abc/def/hij abc/dfe/hij'
|
||||
comptest $': ./a/d/h\t'
|
||||
comptesteval 'rm -rd abc'
|
||||
0:nested directory
|
||||
>line: {: ./abc/def/h}{}
|
||||
>QUERY:{d}
|
||||
>DESCRIPTION:{file}
|
||||
>C1:{def/}
|
||||
>C1:{dfe/}
|
||||
|
||||
comptesteval '_tst() { a=(a); _describe "group1" a; a=(b); _describe "group2" a }'
|
||||
comptest $'tst \t'
|
||||
0:multi headers
|
||||
>line: {tst a }{}
|
||||
>QUERY:{}
|
||||
>DESCRIPTION:{group1}
|
||||
>DESCRIPTION:{group2}
|
||||
>C1:{·a}
|
||||
>C2:{·b}
|
||||
|
||||
comptest $'git add dir1\t'
|
||||
0:add empty word
|
||||
>line: {git add dir1/}{}
|
||||
|
||||
comptesteval "zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}' 'r:|=*' 'l:|=* r:|=*'"
|
||||
comptesteval "touch vim.coc"
|
||||
comptest $': coc\t'
|
||||
comptesteval "rm vim.coc; zstyle -d ':completion:*' matcher-list"
|
||||
0:matcher-list
|
||||
>line: {: vim.coc }{}
|
||||
|
||||
comptesteval $'cd dir1'
|
||||
comptest $': ../d\t'
|
||||
comptesteval $'cd ..'
|
||||
0:IPREFIX
|
||||
>line: {: ../dir1/}{}
|
||||
>QUERY:{dir}
|
||||
>DESCRIPTION:{file}
|
||||
>C1:{dir1/}
|
||||
>C1:{dir2/}
|
||||
|
||||
comptest $': $PWD/d\t'
|
||||
0:expansion
|
||||
>line: {: $PWD/dir1/}{}
|
||||
>QUERY:{dir}
|
||||
>DESCRIPTION:{file}
|
||||
>C1:{dir1/}
|
||||
>C1:{dir2/}
|
||||
|
||||
comptesteval 'echo no > called'
|
||||
comptesteval "touch 'dir\`echo yes > called\`'"
|
||||
comptest $': d\t'
|
||||
echo called:$(<called)
|
||||
comptesteval "rm 'dir\`echo yes > called\`' called"
|
||||
0:don''t expand file name
|
||||
>line: {: dir1/}{}
|
||||
>QUERY:{dir}
|
||||
>DESCRIPTION:{file}
|
||||
>C1:{dir1/}
|
||||
>C1:{dir2/}
|
||||
>C1:{dir`echo yes > called`}
|
||||
>called:no
|
||||
|
||||
comptesteval "zstyle ':fzf-tab:*' debug-command true"
|
||||
comptest $': d\t'
|
||||
comptesteval "zstyle ':fzf-tab:*' debug-command $ZTST_srcdir/select -n 1 -h '\$#headers' -q '\"\$query\"'"
|
||||
0:cancel completion
|
||||
>line: {: d}{}
|
||||
|
||||
comptesteval "zstyle ':fzf-tab:*' debug-command $ZTST_srcdir/select -n QUERY -h '\$#headers' -q '\"dragon\"'"
|
||||
comptest $': ./d\t'
|
||||
comptesteval "zstyle ':fzf-tab:*' debug-command $ZTST_srcdir/select -n 1 -h '\$#headers' -q '\"\$query\"'"
|
||||
0:use query directly
|
||||
>line: {: ./dragon}{}
|
||||
>QUERY:{dragon}
|
||||
>DESCRIPTION:{file}
|
||||
>C1:{dir1/}
|
||||
>C1:{dir2/}
|
||||
|
||||
comptesteval 'zstyle ":completion:*" menu false'
|
||||
comptesteval "local prefix_1=1 prefix_2=1 prenofix_3=1"
|
||||
comptest $'echo $pre\t'
|
||||
comptesteval 'zstyle ":completion:*" menu true'
|
||||
0:parameter completion
|
||||
>line: {echo $pre}{}
|
||||
>QUERY:{}
|
||||
>DESCRIPTION:{parameter}
|
||||
>C1:{prefix_1}
|
||||
>C1:{prefix_2}
|
||||
>C1:{prenofix_3}
|
||||
|
||||
comptesteval '_tst() { compadd -J packages -X package openpgp-keys-gentoo-release -MERGING-pnpm-bin }'
|
||||
comptest $'tst \t'
|
||||
0:completions starts with dash
|
||||
>line: {tst }{}
|
||||
>QUERY:{}
|
||||
>C1:{package }
|
||||
>C1:{openpgp-keys-gentoo-release}
|
||||
>C1:{-MERGING-pnpm-bin}
|
||||
%clean
|
||||
|
||||
zmodload -ui zsh/zpty
|
||||
|
||||
27
.zsh/zsh-fzf-tab/test/runtests.zsh
Normal file
27
.zsh/zsh-fzf-tab/test/runtests.zsh
Normal file
@@ -0,0 +1,27 @@
|
||||
#!/bin/zsh -f
|
||||
|
||||
emulate zsh
|
||||
|
||||
# Run all specified tests, keeping count of which succeeded.
|
||||
# The reason for this extra layer above the test script is to
|
||||
# protect from catastrophic failure of an individual test.
|
||||
# We could probably do that with subshells instead.
|
||||
|
||||
integer success failure skipped retval
|
||||
for file in ${@:1}; do
|
||||
zsh +Z -f ./ztst.zsh $file
|
||||
retval=$?
|
||||
if (( $retval == 2 )); then
|
||||
(( skipped++ ))
|
||||
elif (( $retval )); then
|
||||
(( failure++ ))
|
||||
else
|
||||
(( success++ ))
|
||||
fi
|
||||
done
|
||||
print "**************************************
|
||||
$success successful test script${${success:#1}:+s}, \
|
||||
$failure failure${${failure:#1}:+s}, \
|
||||
$skipped skipped
|
||||
**************************************"
|
||||
return $(( failure ? 1 : 0 ))
|
||||
36
.zsh/zsh-fzf-tab/test/select
Executable file
36
.zsh/zsh-fzf-tab/test/select
Executable file
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env zsh
|
||||
|
||||
zmodload zsh/zutil
|
||||
|
||||
local -A headers range query expect
|
||||
|
||||
# -h: lines of headers
|
||||
# -n: selection index, if it is QUERY, query string will be used
|
||||
# -q: query string
|
||||
# -e: expect key
|
||||
zparseopts -E h:=headers n:=range q:=query e:=expect
|
||||
|
||||
print -r -- "${query//\"/}"
|
||||
print -r -- "$expect"
|
||||
print -r -- "<QUERY>${query//\"/}</QUERY>" >&2
|
||||
|
||||
local -a lines=()
|
||||
while read input; do
|
||||
lines+=(${input%$'\033[00m'})
|
||||
done
|
||||
|
||||
for ((i = 1; i <= headers; i++)); do
|
||||
print -r -- $lines[i] >&2
|
||||
done
|
||||
|
||||
lines[1,headers]=()
|
||||
|
||||
for i in {1..$#lines}; do
|
||||
print -r -- ${lines[i]//$'\0'}"<EC>" >&2
|
||||
done
|
||||
|
||||
if [[ $range != QUERY ]]; then
|
||||
for i in ${(s:,:)range}; do
|
||||
print -r -- $lines[i]
|
||||
done
|
||||
fi
|
||||
581
.zsh/zsh-fzf-tab/test/ztst.zsh
Executable file
581
.zsh/zsh-fzf-tab/test/ztst.zsh
Executable file
@@ -0,0 +1,581 @@
|
||||
#!/bin/zsh -f
|
||||
# The line above is just for convenience. Normally tests will be run using
|
||||
# a specified version of zsh. With dynamic loading, any required libraries
|
||||
# must already have been installed in that case.
|
||||
#
|
||||
# Takes one argument: the name of the test file. Currently only one such
|
||||
# file will be processed each time ztst.zsh is run. This is slower, but
|
||||
# much safer in terms of preserving the correct status.
|
||||
# To avoid namespace pollution, all functions and parameters used
|
||||
# only by the script begin with ZTST_.
|
||||
#
|
||||
# Options (without arguments) may precede the test file argument; these
|
||||
# are interpreted as shell options to set. -x is probably the most useful.
|
||||
|
||||
# Produce verbose messages if non-zero.
|
||||
# If 1, produce reports of tests executed; if 2, also report on progress.
|
||||
# Defined in such a way that any value from the environment is used.
|
||||
: ${ZTST_verbose:=0}
|
||||
|
||||
# We require all options to be reset, not just emulation options.
|
||||
# Unfortunately, due to the crud which may be in /etc/zshenv this might
|
||||
# still not be good enough. Maybe we should trick it somehow.
|
||||
emulate -R zsh
|
||||
|
||||
# Ensure the locale does not screw up sorting. Don't supply a locale
|
||||
# unless there's one set, to minimise problems.
|
||||
[[ -n $LC_ALL ]] && LC_ALL=C
|
||||
[[ -n $LC_COLLATE ]] && LC_COLLATE=C
|
||||
[[ -n $LC_NUMERIC ]] && LC_NUMERIC=C
|
||||
[[ -n $LC_MESSAGES ]] && LC_MESSAGES=C
|
||||
[[ -n $LANG ]] && LANG=C
|
||||
|
||||
# Don't propagate variables that are set by default in the shell.
|
||||
typeset +x WORDCHARS
|
||||
|
||||
# We need to be able to save and restore the options used in the test.
|
||||
# We use the $options variable of the parameter module for this.
|
||||
zmodload zsh/parameter
|
||||
|
||||
# Note that both the following are regular arrays, since we only use them
|
||||
# in whole array assignments to/from $options.
|
||||
# Options set in test code (i.e. by default all standard options)
|
||||
ZTST_testopts=(${(kv)options})
|
||||
|
||||
setopt extendedglob nonomatch
|
||||
while [[ $1 = [-+]* ]]; do
|
||||
set $1
|
||||
shift
|
||||
done
|
||||
# Options set in main script
|
||||
ZTST_mainopts=(${(kv)options})
|
||||
|
||||
# We run in the current directory, so remember it.
|
||||
ZTST_testdir=$PWD
|
||||
ZTST_testname=$1
|
||||
|
||||
integer ZTST_testfailed
|
||||
|
||||
# This is POSIX nonsense. Because of the vague feeling someone, somewhere
|
||||
# may one day need to examine the arguments of "tail" using a standard
|
||||
# option parser, every Unix user in the world is expected to switch
|
||||
# to using "tail -n NUM" instead of "tail -NUM". Older versions of
|
||||
# tail don't support this.
|
||||
tail() {
|
||||
emulate -L zsh
|
||||
|
||||
if [[ -z $TAIL_SUPPORTS_MINUS_N ]]; then
|
||||
local test
|
||||
test=$(echo "foo\nbar" | command tail -n 1 2>/dev/null)
|
||||
if [[ $test = bar ]]; then
|
||||
TAIL_SUPPORTS_MINUS_N=1
|
||||
else
|
||||
TAIL_SUPPORTS_MINUS_N=0
|
||||
fi
|
||||
fi
|
||||
|
||||
integer argi=${argv[(i)-<->]}
|
||||
|
||||
if [[ $argi -le $# && $TAIL_SUPPORTS_MINUS_N = 1 ]]; then
|
||||
argv[$argi]=(-n ${argv[$argi][2,-1]})
|
||||
fi
|
||||
|
||||
command tail "$argv[@]"
|
||||
}
|
||||
|
||||
# The source directory is not necessarily the current directory,
|
||||
# but if $0 doesn't contain a `/' assume it is.
|
||||
if [[ $0 = */* ]]; then
|
||||
ZTST_srcdir=${0%/*}
|
||||
else
|
||||
ZTST_srcdir=$PWD
|
||||
fi
|
||||
[[ $ZTST_srcdir = /* ]] || ZTST_srcdir="$ZTST_testdir/$ZTST_srcdir"
|
||||
|
||||
|
||||
: ${TMPPREFIX:=/tmp/zsh}
|
||||
ZTST_tmp=${TMPPREFIX}.ztst.$$
|
||||
if ! rm -f $ZTST_tmp || ! mkdir -p $ZTST_tmp || ! chmod go-w $ZTST_tmp; then
|
||||
print "Can't create $ZTST_tmp for exclusive use." >&2
|
||||
exit 1
|
||||
fi
|
||||
# Temporary files for redirection inside tests.
|
||||
ZTST_in=${ZTST_tmp}/ztst.in
|
||||
# hold the expected output
|
||||
ZTST_out=${ZTST_tmp}/ztst.out
|
||||
ZTST_err=${ZTST_tmp}/ztst.err
|
||||
# hold the actual output from the test
|
||||
ZTST_tout=${ZTST_tmp}/ztst.tout
|
||||
ZTST_terr=${ZTST_tmp}/ztst.terr
|
||||
|
||||
ZTST_cleanup() {
|
||||
cd $ZTST_testdir
|
||||
rm -rf $ZTST_testdir/dummy.tmp $ZTST_testdir/*.tmp(N) ${ZTST_tmp}
|
||||
}
|
||||
|
||||
# This cleanup always gets performed, even if we abort. Later,
|
||||
# we should try and arrange that any test-specific cleanup
|
||||
# always gets called as well.
|
||||
##trap 'print cleaning up...
|
||||
##ZTST_cleanup' INT QUIT TERM
|
||||
# Make sure it's clean now.
|
||||
rm -rf dummy.tmp *.tmp
|
||||
|
||||
# Report failure. Note that all output regarding the tests goes to stdout.
|
||||
# That saves an unpleasant mixture of stdout and stderr to sort out.
|
||||
ZTST_testfailed() {
|
||||
print -r "Test $ZTST_testname failed: $1"
|
||||
if [[ -n $ZTST_message ]]; then
|
||||
print -r "Was testing: $ZTST_message"
|
||||
fi
|
||||
print -r "$ZTST_testname: test failed."
|
||||
if [[ -n $ZTST_failmsg ]]; then
|
||||
print -r "The following may (or may not) help identifying the cause:
|
||||
$ZTST_failmsg"
|
||||
fi
|
||||
ZTST_testfailed=1
|
||||
return 1
|
||||
}
|
||||
|
||||
# Print messages if $ZTST_verbose is non-empty
|
||||
ZTST_verbose() {
|
||||
local lev=$1
|
||||
shift
|
||||
if [[ -n $ZTST_verbose && $ZTST_verbose -ge $lev ]]; then
|
||||
print -r -u $ZTST_fd -- $*
|
||||
fi
|
||||
}
|
||||
ZTST_hashmark() {
|
||||
if [[ ZTST_verbose -le 0 && -t $ZTST_fd ]]; then
|
||||
print -n -u$ZTST_fd -- ${(pl:SECONDS::\#::\#\r:)}
|
||||
fi
|
||||
(( SECONDS > COLUMNS+1 && (SECONDS -= COLUMNS) ))
|
||||
}
|
||||
|
||||
if [[ ! -r $ZTST_testname ]]; then
|
||||
ZTST_testfailed "can't read test file."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec {ZTST_fd}>&1
|
||||
exec {ZTST_input}<$ZTST_testname
|
||||
|
||||
# The current line read from the test file.
|
||||
ZTST_curline=''
|
||||
# The current section being run
|
||||
ZTST_cursect=''
|
||||
|
||||
# Get a new input line. Don't mangle spaces; set IFS locally to empty.
|
||||
# We shall skip comments at this level.
|
||||
ZTST_getline() {
|
||||
local IFS=
|
||||
while true; do
|
||||
read -u $ZTST_input -r ZTST_curline || return 1
|
||||
[[ $ZTST_curline == \#* ]] || return 0
|
||||
done
|
||||
}
|
||||
|
||||
# Get the name of the section. It may already have been read into
|
||||
# $curline, or we may have to skip some initial comments to find it.
|
||||
# If argument present, it's OK to skip the reset of the current section,
|
||||
# so no error if we find garbage.
|
||||
ZTST_getsect() {
|
||||
local match mbegin mend
|
||||
|
||||
while [[ $ZTST_curline != '%'(#b)([[:alnum:]]##)* ]]; do
|
||||
ZTST_getline || return 1
|
||||
[[ $ZTST_curline = [[:blank:]]# ]] && continue
|
||||
if [[ $# -eq 0 && $ZTST_curline != '%'[[:alnum:]]##* ]]; then
|
||||
ZTST_testfailed "bad line found before or after section:
|
||||
$ZTST_curline"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
# have the next line ready waiting
|
||||
ZTST_getline
|
||||
ZTST_cursect=${match[1]}
|
||||
ZTST_verbose 2 "ZTST_getsect: read section name: $ZTST_cursect"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Read in an indented code chunk for execution
|
||||
ZTST_getchunk() {
|
||||
# Code chunks are always separated by blank lines or the
|
||||
# end of a section, so if we already have a piece of code,
|
||||
# we keep it. Currently that shouldn't actually happen.
|
||||
ZTST_code=''
|
||||
# First find the chunk.
|
||||
while [[ $ZTST_curline = [[:blank:]]# ]]; do
|
||||
ZTST_getline || break
|
||||
done
|
||||
while [[ $ZTST_curline = [[:blank:]]##[^[:blank:]]* ]]; do
|
||||
ZTST_code="${ZTST_code:+${ZTST_code}
|
||||
}${ZTST_curline}"
|
||||
ZTST_getline || break
|
||||
done
|
||||
ZTST_verbose 2 "ZTST_getchunk: read code chunk:
|
||||
$ZTST_code"
|
||||
[[ -n $ZTST_code ]]
|
||||
}
|
||||
|
||||
# Read in a piece for redirection.
|
||||
ZTST_getredir() {
|
||||
local char=${ZTST_curline[1]} fn
|
||||
ZTST_redir=${ZTST_curline[2,-1]}
|
||||
while ZTST_getline; do
|
||||
[[ $ZTST_curline[1] = $char ]] || break
|
||||
ZTST_redir="${ZTST_redir}
|
||||
${ZTST_curline[2,-1]}"
|
||||
done
|
||||
ZTST_verbose 2 "ZTST_getredir: read redir for '$char':
|
||||
$ZTST_redir"
|
||||
|
||||
case $char in
|
||||
('<') fn=$ZTST_in
|
||||
;;
|
||||
('>') fn=$ZTST_out
|
||||
;;
|
||||
('?') fn=$ZTST_err
|
||||
;;
|
||||
(*) ZTST_testfailed "bad redir operator: $char"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
if [[ $ZTST_flags = *q* && $char = '<' ]]; then
|
||||
# delay substituting output until variables are set
|
||||
print -r -- "${(e)ZTST_redir}" >>$fn
|
||||
else
|
||||
print -r -- "$ZTST_redir" >>$fn
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Execute an indented chunk. Redirections will already have
|
||||
# been set up, but we need to handle the options.
|
||||
ZTST_execchunk() {
|
||||
setopt localloops # don't let continue & break propagate out
|
||||
options=($ZTST_testopts)
|
||||
() {
|
||||
unsetopt localloops
|
||||
eval "$ZTST_code"
|
||||
}
|
||||
ZTST_status=$?
|
||||
# careful... ksh_arrays may be in effect.
|
||||
ZTST_testopts=(${(kv)options[*]})
|
||||
options=(${ZTST_mainopts[*]})
|
||||
ZTST_verbose 2 "ZTST_execchunk: status $ZTST_status"
|
||||
return $ZTST_status
|
||||
}
|
||||
|
||||
# Functions for preparation and cleaning.
|
||||
# When cleaning up (non-zero string argument), we ignore status.
|
||||
ZTST_prepclean() {
|
||||
# Execute indented code chunks.
|
||||
while ZTST_getchunk; do
|
||||
ZTST_execchunk >/dev/null || [[ -n $1 ]] || {
|
||||
[[ -n "$ZTST_unimplemented" ]] ||
|
||||
ZTST_testfailed "non-zero status from preparation code:
|
||||
$ZTST_code" && return 0
|
||||
}
|
||||
done
|
||||
}
|
||||
|
||||
# diff wrapper
|
||||
ZTST_diff() {
|
||||
emulate -L zsh
|
||||
setopt extendedglob
|
||||
|
||||
local diff_out
|
||||
integer diff_pat diff_ret
|
||||
|
||||
case $1 in
|
||||
(p)
|
||||
diff_pat=1
|
||||
;;
|
||||
|
||||
(d)
|
||||
;;
|
||||
|
||||
(*)
|
||||
print "Bad ZTST_diff code: d for diff, p for pattern match"
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
|
||||
if (( diff_pat )); then
|
||||
local -a diff_lines1 diff_lines2
|
||||
integer failed i l
|
||||
local p
|
||||
|
||||
diff_lines1=("${(f@)$(<$argv[-2])}")
|
||||
diff_lines2=("${(f@)$(<$argv[-1])}")
|
||||
if (( ${#diff_lines1} != ${#diff_lines2} )); then
|
||||
failed=1
|
||||
print -r "Pattern match failed, line mismatch (${#diff_lines1}/${#diff_lines2}):"
|
||||
else
|
||||
for (( i = 1; i <= ${#diff_lines1}; i++ )); do
|
||||
if [[ ${diff_lines2[i]} != ${~diff_lines1[i]} ]]; then
|
||||
failed=1
|
||||
print -r "Pattern match failed, line $i:"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
if (( failed )); then
|
||||
for (( l = 1; l <= ${#diff_lines1}; ++l )); do
|
||||
if (( l == i )); then
|
||||
p="-"
|
||||
else
|
||||
p=" "
|
||||
fi
|
||||
print -r -- "$p<${diff_lines1[l]}"
|
||||
done
|
||||
for (( l = 1; l <= ${#diff_lines2}; ++l )); do
|
||||
if (( l == i )); then
|
||||
p="+"
|
||||
else
|
||||
p=" "
|
||||
fi
|
||||
print -r -- "$p>${diff_lines2[l]}"
|
||||
done
|
||||
diff_ret=1
|
||||
fi
|
||||
else
|
||||
diff_out=$(diff -a "$@")
|
||||
diff_ret="$?"
|
||||
if [[ "$diff_ret" != "0" ]]; then
|
||||
print -r -- "$diff_out"
|
||||
fi
|
||||
fi
|
||||
|
||||
return "$diff_ret"
|
||||
}
|
||||
|
||||
ZTST_test() {
|
||||
local last match mbegin mend found substlines
|
||||
local diff_out diff_err
|
||||
local ZTST_skip
|
||||
integer expected_to_fail
|
||||
|
||||
while true; do
|
||||
rm -f $ZTST_in $ZTST_out $ZTST_err
|
||||
touch $ZTST_in $ZTST_out $ZTST_err
|
||||
ZTST_message=''
|
||||
ZTST_failmsg=''
|
||||
found=0
|
||||
diff_out=d
|
||||
diff_err=d
|
||||
|
||||
ZTST_verbose 2 "ZTST_test: looking for new test"
|
||||
|
||||
while true; do
|
||||
ZTST_verbose 2 "ZTST_test: examining line:
|
||||
$ZTST_curline"
|
||||
case $ZTST_curline in
|
||||
(%*) if [[ $found = 0 ]]; then
|
||||
break 2
|
||||
else
|
||||
last=1
|
||||
break
|
||||
fi
|
||||
;;
|
||||
([[:space:]]#)
|
||||
if [[ $found = 0 ]]; then
|
||||
ZTST_getline || break 2
|
||||
continue
|
||||
else
|
||||
break
|
||||
fi
|
||||
;;
|
||||
([[:space:]]##[^[:space:]]*) ZTST_getchunk
|
||||
if [[ $ZTST_curline == (#b)([-0-9]##)([[:alpha:]]#)(:*)# ]]; then
|
||||
ZTST_xstatus=$match[1]
|
||||
ZTST_flags=$match[2]
|
||||
ZTST_message=${match[3]:+${match[3][2,-1]}}
|
||||
else
|
||||
ZTST_testfailed "expecting test status at:
|
||||
$ZTST_curline"
|
||||
return 1
|
||||
fi
|
||||
ZTST_getline
|
||||
found=1
|
||||
;;
|
||||
('<'*) ZTST_getredir || return 1
|
||||
found=1
|
||||
;;
|
||||
('*>'*)
|
||||
ZTST_curline=${ZTST_curline[2,-1]}
|
||||
diff_out=p
|
||||
;&
|
||||
('>'*)
|
||||
ZTST_getredir || return 1
|
||||
found=1
|
||||
;;
|
||||
('*?'*)
|
||||
ZTST_curline=${ZTST_curline[2,-1]}
|
||||
diff_err=p
|
||||
;&
|
||||
('?'*)
|
||||
ZTST_getredir || return 1
|
||||
found=1
|
||||
;;
|
||||
('F:'*) ZTST_failmsg="${ZTST_failmsg:+${ZTST_failmsg}
|
||||
} ${ZTST_curline[3,-1]}"
|
||||
ZTST_getline
|
||||
found=1
|
||||
;;
|
||||
(*) ZTST_testfailed "bad line in test block:
|
||||
$ZTST_curline"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# If we found some code to execute...
|
||||
if [[ -n $ZTST_code ]]; then
|
||||
ZTST_hashmark
|
||||
ZTST_verbose 1 "Running test: $ZTST_message"
|
||||
ZTST_verbose 2 "ZTST_test: expecting status: $ZTST_xstatus"
|
||||
ZTST_verbose 2 "Input: $ZTST_in, output: $ZTST_out, error: $ZTST_terr"
|
||||
|
||||
ZTST_execchunk <$ZTST_in >$ZTST_tout 2>$ZTST_terr
|
||||
|
||||
if [[ -n $ZTST_skip ]]; then
|
||||
ZTST_verbose 0 "Test case skipped: $ZTST_skip"
|
||||
ZTST_skip=
|
||||
if [[ -n $last ]]; then
|
||||
break
|
||||
else
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ $ZTST_flags = *f* ]]; then
|
||||
expected_to_fail=1
|
||||
ZTST_xfail_diff() { ZTST_diff "$@" > /dev/null }
|
||||
ZTST_diff=ZTST_xfail_diff
|
||||
else
|
||||
expected_to_fail=0
|
||||
ZTST_diff=ZTST_diff
|
||||
fi
|
||||
|
||||
# First check we got the right status, if specified.
|
||||
if [[ $ZTST_xstatus != - && $ZTST_xstatus != $ZTST_status ]]; then
|
||||
if (( expected_to_fail )); then
|
||||
ZTST_verbose 1 "Test failed, as expected."
|
||||
continue
|
||||
fi
|
||||
ZTST_testfailed "bad status $ZTST_status, expected $ZTST_xstatus from:
|
||||
$ZTST_code${$(<$ZTST_terr):+
|
||||
Error output:
|
||||
$(<$ZTST_terr)}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
ZTST_verbose 2 "ZTST_test: test produced standard output:
|
||||
$(<$ZTST_tout)
|
||||
ZTST_test: and standard error:
|
||||
$(<$ZTST_terr)"
|
||||
|
||||
# Now check output and error.
|
||||
if [[ $ZTST_flags = *q* && -s $ZTST_out ]]; then
|
||||
substlines="$(<$ZTST_out)"
|
||||
rm -rf $ZTST_out
|
||||
print -r -- "${(e)substlines}" >$ZTST_out
|
||||
fi
|
||||
if [[ $ZTST_flags != *d* ]] && ! $ZTST_diff $diff_out -u $ZTST_out $ZTST_tout; then
|
||||
if (( expected_to_fail )); then
|
||||
ZTST_verbose 1 "Test failed, as expected."
|
||||
continue
|
||||
fi
|
||||
ZTST_testfailed "output differs from expected as shown above for:
|
||||
$ZTST_code${$(<$ZTST_terr):+
|
||||
Error output:
|
||||
$(<$ZTST_terr)}"
|
||||
return 1
|
||||
fi
|
||||
if [[ $ZTST_flags = *q* && -s $ZTST_err ]]; then
|
||||
substlines="$(<$ZTST_err)"
|
||||
rm -rf $ZTST_err
|
||||
print -r -- "${(e)substlines}" >$ZTST_err
|
||||
fi
|
||||
if [[ $ZTST_flags != *D* ]] && ! $ZTST_diff $diff_err -u $ZTST_err $ZTST_terr; then
|
||||
if (( expected_to_fail )); then
|
||||
ZTST_verbose 1 "Test failed, as expected."
|
||||
continue
|
||||
fi
|
||||
ZTST_testfailed "error output differs from expected as shown above for:
|
||||
$ZTST_code"
|
||||
return 1
|
||||
fi
|
||||
if (( expected_to_fail )); then
|
||||
ZTST_testfailed "test was expected to fail, but passed."
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
ZTST_verbose 1 "Test successful."
|
||||
[[ -n $last ]] && break
|
||||
done
|
||||
|
||||
ZTST_verbose 2 "ZTST_test: all tests successful"
|
||||
|
||||
# reset message to keep ZTST_testfailed output correct
|
||||
ZTST_message=''
|
||||
}
|
||||
|
||||
|
||||
# Remember which sections we've done.
|
||||
typeset -A ZTST_sects
|
||||
ZTST_sects=(prep 0 test 0 clean 0)
|
||||
|
||||
print "$ZTST_testname: starting."
|
||||
|
||||
# Now go through all the different sections until the end.
|
||||
# prep section may set ZTST_unimplemented, in this case the actual
|
||||
# tests will be skipped
|
||||
ZTST_skipok=
|
||||
ZTST_unimplemented=
|
||||
while [[ -z "$ZTST_unimplemented" ]] && ZTST_getsect $ZTST_skipok; do
|
||||
case $ZTST_cursect in
|
||||
(prep) if (( ${ZTST_sects[prep]} + ${ZTST_sects[test]} + \
|
||||
${ZTST_sects[clean]} )); then
|
||||
ZTST_testfailed "\`prep' section must come first"
|
||||
exit 1
|
||||
fi
|
||||
ZTST_prepclean
|
||||
ZTST_sects[prep]=1
|
||||
;;
|
||||
(test)
|
||||
if (( ${ZTST_sects[test]} + ${ZTST_sects[clean]} )); then
|
||||
ZTST_testfailed "bad placement of \`test' section"
|
||||
exit 1
|
||||
fi
|
||||
# careful here: we can't execute ZTST_test before || or &&
|
||||
# because that affects the behaviour of traps in the tests.
|
||||
ZTST_test
|
||||
(( $? )) && ZTST_skipok=1
|
||||
ZTST_sects[test]=1
|
||||
;;
|
||||
(clean)
|
||||
if (( ${ZTST_sects[test]} == 0 || ${ZTST_sects[clean]} )); then
|
||||
ZTST_testfailed "bad use of \`clean' section"
|
||||
else
|
||||
ZTST_prepclean 1
|
||||
ZTST_sects[clean]=1
|
||||
fi
|
||||
ZTST_skipok=
|
||||
;;
|
||||
*) ZTST_testfailed "bad section name: $ZTST_cursect"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -n "$ZTST_unimplemented" ]]; then
|
||||
print "$ZTST_testname: skipped ($ZTST_unimplemented)"
|
||||
ZTST_testfailed=2
|
||||
elif (( ! $ZTST_testfailed )); then
|
||||
print "$ZTST_testname: all tests successful."
|
||||
fi
|
||||
ZTST_cleanup
|
||||
exit $(( ZTST_testfailed ))
|
||||
Reference in New Issue
Block a user