# zsh interactive self-test payload -- the ZLE (line editor) leg the script # payload structurally cannot reach. Emits Z- OK/FAIL lines, then # ALL-ZSH-ZLE-OK iff all six pass. # # WHY EXPECT AND NOT A SCRIPT. Everything zsh is chosen for as an INTERACTIVE # shell -- history recall, completion, cursor movement -- lives in ZLE, which # only runs when the shell is interactive AND stdin is a terminal. A non- # interactive `zsh -c` never enters ZLE at all, so a port with ZLE entirely # broken would still pass every script-level case in 16-zsh.test. # # ONE SPAWN, DELIBERATELY. exec on wasm32-hwjs is a fresh module instantiation # (~2-3 s), so all six cases run inside a single interactive shell. # # THE KEYMAP IS PINNED, AND THAT IS NOT COSMETIC. zsh picks its main keymap from # $EDITOR/$VISUAL at startup: with "vi" in either, main is viins, where ^P and ^A # are NOT bound to up-line-or-history/beginning-of-line -- they insert as literal # control characters and the line runs as the command "^P^P". Written without the # `bindkey -e` below, this file reported a broken line editor on a perfectly good # native zsh 5.9, purely because the developer's $EDITOR was vi. On a target whose # environment we do not control that is a false red waiting to be attributed to # the port. # # THE ECHO TRAP, AND HOW EACH CASE AVOIDS IT. Everything typed at a ZLE prompt is # echoed back, so expecting the text you just sent proves only that the terminal # echoes. Every case below matches on a string that is PRODUCED rather than typed: # the command line references $m and the output carries its VALUE, so seeing # "HIST-BETA" cannot be an echo of anything. log_user 0 set env(TERM) vt100 set timeout 60 set pass 0 proc verdict {tag ok} { global pass if {$ok} { incr pass; puts "Z-$tag OK" } else { puts "Z-$tag FAIL" } } # -f -d: no rc files at all, global ones included, so the session is the shipped # shell's own defaults and not some /etc/zshenv the image may grow later. spawn -noecho zsh -f -d -i # The prompt is assembled from two variables so the string we synchronise on is # never present in the echo of the line that sets it. send {p1=ZPRO; p2=MPT; PROMPT="${p1}${p2}> "} send "\r" set z1 0 expect { "ZPROMPT> " { set z1 1 } timeout { set z1 0 } eof { set z1 0 } } verdict Z1prompt $z1 # ------------------------------------------------------------------ Z2 ------- # ZLE is the interactive line editor; $options[zle] reads "on" only when the # shell entered it, which is the precondition for every case below. set z2 0 if {$z1} { send {print -r -- ZLE-IS-$options[zle]} send "\r" expect { -re {ZLE-IS-on\r?\n} { set z2 1 } "ZPROMPT> " { set z2 0 } timeout { set z2 0 } } expect "ZPROMPT> " } verdict Z2zle $z2 # ------------------------------------------------------------------ Z3 ------- # Pin the emacs keymap, then read back what ^P and ^A are actually bound to -- # the two widgets Z4 and Z5 go on to exercise. Asserting the bindings rather # than the keymap NAME is deliberate: `bindkey -lL main` prints the alias # ("bindkey -A emacs main") whose last field is "main", and reading the name out # of it is one off-by-one away from asserting nothing. set z3 0 if {$z2} { send {bindkey -e; bindkey '^P'; bindkey '^A'} send "\r" set sawp 0 expect { -re {up-line-or-history} { set sawp 1 } timeout {} } if {$sawp} { expect { -re {beginning-of-line} { set z3 1 } timeout { set z3 0 } } } expect "ZPROMPT> " } verdict Z3keymap $z3 # ------------------------------------------------------------------ Z4 ------- # History recall through the line editor: ^P (up-line-or-history) twice walks # back over "m=BETA" to the print line, which is then re-executed with the NEW # value of m. "HIST-BETA" has never appeared on the terminal at that point -- # not as output and not as echo -- so it can only come from the recalled line # actually running. set z4 0 if {$z3} { send {m=ALPHA} send "\r" expect "ZPROMPT> " send {print -r -- HIST-$m} send "\r" expect { -re {HIST-ALPHA\r?\n} {} timeout {} } expect "ZPROMPT> " send {m=BETA} send "\r" expect "ZPROMPT> " send "\x10\x10" expect { -re {HIST-\$m} {} timeout {} } send "\r" expect { -re {HIST-BETA\r?\n} { set z4 1 } timeout { set z4 0 } } expect "ZPROMPT> " } verdict Z4history $z4 # ------------------------------------------------------------------ Z5 ------- # Cursor movement and mid-line insertion: type the line MISSING its leading "p", # ^A to the start, insert it, execute. The output "EDIT-BETA" proves the edited # line ran; a ZLE that ignored ^A would have run "rint ..." and reported # command not found. set z5 0 if {$z3} { send {rint -r -- EDIT-$m} send "\x01p\r" expect { -re {EDIT-BETA\r?\n} { set z5 1 } timeout { set z5 0 } } expect "ZPROMPT> " } verdict Z5edit $z5 # ------------------------------------------------------------------ Z6 ------- # Filename completion on TAB. The word typed is the 5-character prefix "zuniq"; # only the completer can turn it into "zuniquemarker", so the marker appearing # at all is the assertion. set z6 0 if {$z3} { # zmodload zsh/files first: mkdir/rm below must be zsh BUILTINS, because a # forked child is never reaped on this arm (linux-in-the-browser#161) and # the coreutils mkdir would park this session instead of setting up. # BRINTOS-WORKAROUND: linux-in-the-browser#161 remove — drop the zmodload and use # the coreutils mkdir once a forked child is reaped again. send {zmodload zsh/files; d=${TMPDIR:-/tmp}/bst-zsh-comp.$$; mkdir -p $d && : > $d/zuniquemarker && cd $d && print -r -- SETUP-$?} send "\r" expect { -re {SETUP-0\r?\n} { send {print -r -- CMP } send "zuniq\t" expect { -re {zuniquemarker} { set z6 1 } timeout { set z6 0 } } send "\r" expect "ZPROMPT> " } timeout { set z6 0 } } send {cd / && rm -rf $d} send "\r" expect "ZPROMPT> " } verdict Z6complete $z6 send "exit\r" expect eof puts "Z-SUMMARY pass=$pass/6" if {$pass == 6} { puts "ALL-ZSH-ZLE-OK" }