summaryrefslogtreecommitdiff
path: root/.config/shells/zsh/plugins/fzf-tab/test
diff options
context:
space:
mode:
authorJacob McDonnell <jacob@simplelittledream.com>2022-07-14 19:09:06 -0400
committerJacob McDonnell <jacob@simplelittledream.com>2022-07-14 19:09:06 -0400
commited4daf7429bf2580118aad87b8f3e2011efed7ec (patch)
tree82f9d8ed68de119e5777f5ecfae9df4a92fe92b3 /.config/shells/zsh/plugins/fzf-tab/test
Initial commit
Diffstat (limited to '.config/shells/zsh/plugins/fzf-tab/test')
-rw-r--r--.config/shells/zsh/plugins/fzf-tab/test/.gitignore1
-rw-r--r--.config/shells/zsh/plugins/fzf-tab/test/comptest174
-rw-r--r--.config/shells/zsh/plugins/fzf-tab/test/fzftab.ztst211
-rw-r--r--.config/shells/zsh/plugins/fzf-tab/test/runtests.zsh27
-rwxr-xr-x.config/shells/zsh/plugins/fzf-tab/test/select32
-rwxr-xr-x.config/shells/zsh/plugins/fzf-tab/test/ztst.zsh581
6 files changed, 1026 insertions, 0 deletions
diff --git a/.config/shells/zsh/plugins/fzf-tab/test/.gitignore b/.config/shells/zsh/plugins/fzf-tab/test/.gitignore
new file mode 100644
index 0000000..dea2d4f
--- /dev/null
+++ b/.config/shells/zsh/plugins/fzf-tab/test/.gitignore
@@ -0,0 +1 @@
+.zcompdump
diff --git a/.config/shells/zsh/plugins/fzf-tab/test/comptest b/.config/shells/zsh/plugins/fzf-tab/test/comptest
new file mode 100644
index 0000000..1a58bcc
--- /dev/null
+++ b/.config/shells/zsh/plugins/fzf-tab/test/comptest
@@ -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]}"
+}
diff --git a/.config/shells/zsh/plugins/fzf-tab/test/fzftab.ztst b/.config/shells/zsh/plugins/fzf-tab/test/fzftab.ztst
new file mode 100644
index 0000000..98028f3
--- /dev/null
+++ b/.config/shells/zsh/plugins/fzf-tab/test/fzftab.ztst
@@ -0,0 +1,211 @@
+# 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: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'}
+>WARN:{`file'}
+# FIXME:why two warnings?
+
+ 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/}{}
+# FIXME:why two warnings?
+
+ 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/}
+
+%clean
+
+ zmodload -ui zsh/zpty
+
diff --git a/.config/shells/zsh/plugins/fzf-tab/test/runtests.zsh b/.config/shells/zsh/plugins/fzf-tab/test/runtests.zsh
new file mode 100644
index 0000000..18406c0
--- /dev/null
+++ b/.config/shells/zsh/plugins/fzf-tab/test/runtests.zsh
@@ -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 ))
diff --git a/.config/shells/zsh/plugins/fzf-tab/test/select b/.config/shells/zsh/plugins/fzf-tab/test/select
new file mode 100755
index 0000000..d06d4e9
--- /dev/null
+++ b/.config/shells/zsh/plugins/fzf-tab/test/select
@@ -0,0 +1,32 @@
+#!/usr/bin/env zsh
+
+zmodload zsh/zutil
+
+local -A headers range query
+
+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
diff --git a/.config/shells/zsh/plugins/fzf-tab/test/ztst.zsh b/.config/shells/zsh/plugins/fzf-tab/test/ztst.zsh
new file mode 100755
index 0000000..eb942b8
--- /dev/null
+++ b/.config/shells/zsh/plugins/fzf-tab/test/ztst.zsh
@@ -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 ))