Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit testing Zsh completion script

Tags:

zsh

I'm trying to write a completion script for Zsh. I'd like to unit test the completion script. For example, I'd like to test that completions for my-command --h include --help.

For Fish, I can use complete -C 'my-command --h', which would then output --help and any other valid completions.

I can't seem to find an equivalent command for Zsh. Does one exist? I've tried things like _main_complete, _complete and _normal, but either they don't support this or I'm not invoking them in the correct way (I get a lot of can only be called from completion function errors).

like image 582
Charles Avatar asked Sep 20 '25 02:09

Charles


1 Answers

I get a lot of can only be called from completion function errors

This is because Zsh's completion commands can run only from inside a completion widget, which in turn can only be called while the Zsh Line Editor is active. We can work around this by activating a completion widget on an active command line inside a so-called pseudo terminal:

# Set up your completions as you would normally.
compdef _my-command my-command
_my-command () {
  _arguments '--help[display help text]'  # Just an example.
}

# Define our test function.
comptest () {
  # Gather all matching completions in this array.
  # -U discards duplicates.
  typeset -aU completions=()  

  # Override the builtin compadd command.
  compadd () {
    # Gather all matching completions for this call in $reply.
    # Note that this call overwrites the specified array.
    # Therefore we cannot use $completions directly.
    builtin compadd -O reply "$@"

    completions+=("$reply[@]") # Collect them.
    builtin compadd "$@"       # Run the actual command.
  }

  # Bind a custom widget to TAB.
  bindkey "^I" complete-word
  zle -C {,,}complete-word
  complete-word () {
  # Make the completion system believe we're on a normal 
  # command line, not in vared.
  unset 'compstate[vared]'

  _main_complete "$@"  # Generate completions.

  # Print out our completions.
  # Use of ^B and ^C as delimiters here is arbitrary.
  # Just use something that won't normally be printed.
  print -n $'\C-B'
  print -nlr -- "$completions[@]"  # Print one per line.
  print -n $'\C-C'
  exit
  }

  vared -c tmp
}

zmodload zsh/zpty  # Load the pseudo terminal module.
zpty {,}comptest   # Create a new pty and run our function in it.

# Simulate a command being typed, ending with TAB to get completions.
zpty -w comptest $'my-command --h\t'

# Read up to the first delimiter. Discard all of this.
zpty -r comptest REPLY $'*\C-B'

zpty -r comptest REPLY $'*\C-C'  # Read up to the second delimiter.

# Print out the results.
print -r -- "${REPLY%$'\C-C'}"   # Trim off the ^C, just in case.

zpty -d comptest  # Delete the pty.

Running the example above will print out:

--help

If you want to test the entire completion output and not just the strings that would be inserted on the command line, then see https://unix.stackexchange.com/questions/668618/how-to-write-automated-tests-for-zsh-completion/668827#668827

like image 174
Marlon Richert Avatar answered Sep 23 '25 10:09

Marlon Richert