From ed4daf7429bf2580118aad87b8f3e2011efed7ec Mon Sep 17 00:00:00 2001 From: Jacob McDonnell Date: Thu, 14 Jul 2022 19:09:06 -0400 Subject: Initial commit --- .config/shells/zsh/plugins/fzf-tab/test/.gitignore | 1 + .config/shells/zsh/plugins/fzf-tab/test/comptest | 174 ++++++ .../shells/zsh/plugins/fzf-tab/test/fzftab.ztst | 211 ++++++++ .../shells/zsh/plugins/fzf-tab/test/runtests.zsh | 27 + .config/shells/zsh/plugins/fzf-tab/test/select | 32 ++ .config/shells/zsh/plugins/fzf-tab/test/ztst.zsh | 581 +++++++++++++++++++++ 6 files changed, 1026 insertions(+) create mode 100644 .config/shells/zsh/plugins/fzf-tab/test/.gitignore create mode 100644 .config/shells/zsh/plugins/fzf-tab/test/comptest create mode 100644 .config/shells/zsh/plugins/fzf-tab/test/fzftab.ztst create mode 100644 .config/shells/zsh/plugins/fzf-tab/test/runtests.zsh create mode 100755 .config/shells/zsh/plugins/fzf-tab/test/select create mode 100755 .config/shells/zsh/plugins/fzf-tab/test/ztst.zsh (limited to '.config/shells/zsh/plugins/fzf-tab/test') 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="" + zpty zsh "$comptest_zsh -f +Z" + + zpty -r zsh log1 "**" || { + 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=" "fi=" "di=" "ln=" "pi=" "so=" "bd=" "cd=" "ex=" "mi=" "tc=" "sp=" "lc=" "ec=\n" "rc=" +zstyle ":completion:*" group-name "" +zstyle ":completion:*:messages" format "%d +" +zstyle ":completion:*:descriptions" format "%d" +zstyle ":completion:*:options" verbose yes +zstyle ":completion:*:values" verbose yes +setopt noalwayslastprompt listrowsfirst completeinword +zmodload zsh/complist +expand-or-complete-with-report () { + print -lr "" + zle expand-or-complete + print -lr - "$LBUFFER" "$RBUFFER" + zle clear-screen + zle -R +} +list-choices-with-report () { + print -lr "" + zle list-choices + zle clear-screen + zle -R +} +comp-finish () { + print "" + 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 "" "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 "**" || { + 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 "**" || { + 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 "***" || { + print "failed to invoke finish widget." + return 1 + } + + logs=(${(s::)log}) + shift logs + + for log in "$logs[@]"; do + if [[ "$log" = (#b)*$''(*)$'\r\n'(*)$''* ]]; then + print -lr "line: {$match[1]}{$match[2]}" + fi + while (( ${(N)log#*(#b)(<(??)>(*)|(*)|(*)|(*)|(*)|(*)|(*))} )); 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 "***" || { + print "failed to invoke finish widget." + return 1 + } + # zpty_flush After zletest + print -lr "${(@)${(@ps:\r\n:)log##*}[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 "" + zstyle ":fzf-tab:*" single-group color header + zstyle ":fzf-tab:*" group-colors "" "" "" "" + fzf-tab-complete-with-report() { + print -lr "" + zle fzf-tab-complete 2>&1 + print -lr - "$LBUFFER" "$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 "%d"' + 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\`' 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//\"/}" >&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'}"" >&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 )) -- cgit v1.2.3