Вопрос или проблема
У меня есть проект, в котором я автоматически генерирую функции автодополнения zsh. Работая над ним, я нахожу граничные случаи и ошибки, которые просто записываю и убеждаюсь, что они проходят проверку при внесении изменений. Очевидно, я хочу создать полноценный набор тестов, но не могу понять, как это сделать.
Для автодополнений bash тестирование довольно простое — установите переменные COMP_*
, выполните функцию и проверьте COMP_REPLY
. Я хотел бы сделать что-то подобное для zsh.
Я изучил документацию compsys, насколько это было возможно, но решения не нашел.
Я хотел бы вручную установить контекст, запустить свои автодополнения и затем увидеть массив описаний или что-то подобное.
Кто-нибудь нашел способ тестировать автодополнения?
Тестирование автодополнений в Zsh немного сложнее. Это связано с тем, что команды автодополнения Zsh могут выполняться только изнутри виджета автодополнения, который, в свою очередь, может быть вызван только тогда, когда активен редактор строк Zsh. Чтобы можно было выполнить автодополнение внутри скрипта, нам нужно использовать так называемый псевдотерминал, в котором у нас есть активная командная строка, на которой можно активировать виджет автодополнения:
# Настройте свои автодополнения как обычно.
compdef _my-command my-command
_my-command () {
_arguments '--help[отображение текстовой помощи]' # Просто пример.
}
# Определите нашу тестовую функцию.
comptest () {
# Добавьте разметку, чтобы упростить разбор вывода.
zstyle ':completion:*:default' list-colors \
'no=<COMPLETION>' 'lc=" "rc=" "ec=</COMPLETION>'
zstyle ':completion:*' group-name ''
zstyle ':completion:*:messages' format \
'<MESSAGE>%d</MESSAGE>'
zstyle ':completion:*:descriptions' format \
'<HEADER>%d</HEADER>'
# Привяжите пользовательский виджет к TAB.
bindkey '^I' complete-word
zle -C {,,}complete-word
complete-word () {
# Заставьте систему автодополнения поверить, что мы находимся на
# обычной командной строке, а не в vared.
unset 'compstate[vared]'
# Добавьте разделитель до и после дополнений.
# Использование ^B и ^C в качестве разделителей здесь произвольное.
# Просто используйте что-то, что обычно не будет напечатано.
compadd -x $'\C-B'
_main_complete "$@"
compadd -J -last- -x $'\C-C'
exit
}
vared -c tmp
}
zmodload zsh/zpty # Загрузите модуль псевдотерминала.
zpty {,}comptest # Создайте новый pty и выполните в нем нашу функцию.
# Смоделируйте ввод команды, заканчивающейся TAB для получения дополнений.
zpty -w comptest $'my-command --h\t'
# Чтение до первого разделителя. Отбросьте все это.
zpty -r comptest REPLY $'*\C-B'
zpty -r comptest REPLY $'*\C-C' # Чтение до второго разделителя.
# Вывод результатов.
print -r -- "${REPLY%$'\C-C'}" # Обрезка ^C, на всякий случай.
zpty -d comptest # Удалите pty.
Запуск примера выше выведет:
<HEADER>option</HEADER>
<COMPLETION>--help отображение текстовой помощи</COMPLETION>
Если вы не хотите тестировать весь вывод автодополнения, а только хотите проверить строки, которые будут вставлены в командную строку, посмотрите https://stackoverflow.com/questions/65386043/unit-testing-zsh-completion-script/69164362#69164362
Ответ Марлона Рихерта очень помогает, но не работает, если терминал слишком мал для отображения всех дополнений. Если это становится проблемой, самым простым решением будет изменение размера псевдотерминала с помощью stty rows 100000 lines 5000
(или любых других больших значений).
Здесь (постоянная ссылка) находится скрипт, который я разработал для практического использования в проекте с открытым исходным кодом:
#!/bin/zsh
# https://unix.stackexchange.com/questions/668618/how-to-write-automated-tests-for-zsh-completion
usage() { echo "Использование: $0 [ -p префикс] [-s файл] ввод [ввод ...]"; exit 1; }
# Настройка автодополнений
autoload -U compinit
compinit
prefix=
while :; do
case "$1" in
-s) source "$2" && shift 2 || usage ;;
-p) prefix="$2" && shift 2 || usage ;;
*) break ;;
esac
done
compfake () {
zstyle ':completion:*' list-prompt '<неактуально>'
zstyle ':completion:*' list-colors $'no=\CA' lc= rc= $'ec=\CD' $'sp=\CB' $'fi=\CF'
zstyle ':completion:*' list-separator '<ОПИСАНИЕ>'
zstyle ':completion:*:descriptions' format '<HEADER>%d</HEADER>'
zstyle ':completion:*' force-list always
# Привязать пользовательский виджет к \v.
bindkey '\v' complete-word
zle -C {,,}complete-word
complete-word () {
unset 'compstate[vared]' # Игнорируем, что мы в vared
_main_complete "$@"
}
stty rows 100000 cols 2000
vared -c tmp # вызов редактора строк
}
comptest() {
zmodload zsh/zpty # Загрузите модуль псевдотерминала.
zpty pty compfake # Создайте новый pty и выполните в нем нашу функцию.
zpty -w pty "$@"$'\v' # Запись в vared
(zpty -r pty) | # Чтение из pty с использованием подпроцесса, ...
grep -E $'\CA|\CF|<HEADER>' | # ... фильтрация для актуальных строк ...
sed -E $' # ... и разбор в нужный нам формат.
s/\e\\[J//g
s/(\CB? +\CD)?\r$//
s/\CA<ОПИСАНИЕ>/:/
s/(^|( )(\CD))[\CA\CF]([^\CD]+)\CD/\\3\\2\\4/g
s/\CB *\CD//g
s/\CA\CD//g
'
zpty -d pty # Удалите pty.
}
for input; do
echo -E ">>> $prefix$input..."
comptest "$prefix$input"
done
Если этот скрипт называется script.sh
, вызовите его как ./script.sh -p 'date ' - +%
, чтобы получить вывод, такой как
>>> date -...
<HEADER>option</HEADER>
--date -d: вывод даты, указанной строкой
--debug: аннотировать разобранную дату и предупреждать о сомнительном использовании
--file -f: вывод дат, указанных в файле
--help: вывод справки и выход
--iso-8601 -I: отображение в формате ISO 8601
--reference -r: вывод времени последнего изменения указанного файла
--rfc-3339: отображение в формате RFC 3339
--rfc-email -R: отображение в формате RFC5322
--set -s: установка времени
--universal --utc -u: отображение или установка времени в UTC
--version: вывод информации о версии и выход
>>> date +%...
<HEADER>формат даты</HEADER>
#: изменение регистра букв
%: символ %
-: не добавлять символов для заполнения числовых значений
^: преобразование строчных букв в заглавные
_: заполнение числовых значений пробелами
0: заполнение числовых значений нулями
a: сокращенное название дня
A: полное название дня
B: полное название месяца
[...]
z: смещение по UTC
Z: название часового пояса
Ответ или решение
Тестирование автодополнения в Zsh может представлять собой серьезную задачу, учитывая уникальную природу оболочки и ее механизмов. Ваша цель — автоматизировать этот процесс, чтобы гарантировать, что написанные вами функции автодополнения работают корректно во всех случаях, включая различные пограничные ситуации.
Теория
В Zsh процесс автодополнения управляется компонентом zsh Line Editor (ZLE), который может активироваться только в интерактивной среде. Это отличает Zsh от Bash, где можно просто установить переменные окружения и проверить результат. В случае Zsh, автодополнение требует активного терминала, что делает непосредственное тестирование сложным.
Основной подход для тестирования Zsh автодополнений заключается в использовании псевдотерминала (PTY). PTY позволяет создать виртуальный терминал в вашем скрипте, который функционирует как обычное командное окно. Это дает возможность отправлять команды на исполнение и получать результаты выполнения автодополнений, как если бы вы работали в реальной командной строке.
Пример
Вот примерный скрипт, с помощью которого вы можете тестировать Zsh автодополнения:
#!/bin/zsh
# Загрузка модулей для автодополнения
autoload -U compinit
compinit
# Определение функции, тестирующей автодополнение
compfake () {
# Настройка для удобного чтения вывода
zstyle ':completion:*' list-prompt '<НЕ СУТЬ ВАЖНО>'
zstyle ':completion:*' list-colors $'no=\CA' lc= rc= $'ec=\CD' $'sp=\CB' $'fi=\CF'
zstyle ':completion:*' list-separator '<ОПИСАНИЕ>'
zstyle ':completion:*:descriptions' format '<ЗАГОЛОВОК>%d<ЗАГОЛОВОК>'
zstyle ':completion:*' force-list always
# Привязка виджета к клавише \v
bindkey '\v' complete-word
zle -C {,,}complete-word
complete-word () {
unset 'compstate[vared]' # Игнорировать, что мы в vared
_main_complete "$@"
}
stty rows 100000 cols 2000 # Увеличение размеров терминала
vared -c tmp # Активируем редактор командной строки
}
# Функция, запускающая тесты
comptest() {
zmodload zsh/zpty # Загружаем модуль псевдотерминала
zpty ztest compfake # Создаем новый псевдотерминал и запускаем в нем функцию compfake
zpty -w ztest "$@"$'\v' # Пишем команду и добавляем кнопку вкладки
(zpty -r ztest) | # Читаем из псевдотерминала
grep -E $'\CA|\CF|<ЗАГОЛОВОК>' | # Фильтруем вывод
sed -E $' # Парсинг результата
s/\e\\[J//g
s/(\CB? +\CD)?\r$//
s/\CA<ОПИСАНИЕ>/:/
s/(^|( )(\CD))[\CA\CF]([^\CD]+)\CD/\\3\\2\\4/g
s/\CB *\CD//g
s/\CA\CD//g
'
zpty -d ztest # Удаление псевдотерминала
}
# Пример запуска скрипта с разными вводами
for input; do
echo -E ">>> $prefix$input..."
comptest "$prefix$input"
done
Этот скрипт позволяет сымитировать ввод команды с автодополнением и получить структурированный результат вывода, который можно проанализировать или проверить на соответствие ожиданиям.
Применение
Теперь, когда у вас есть рабочий скрипт для тестирования автодополнений в Zsh, вы можете интегрировать его в вашу систему сборки или как часть CI/CD процесса. Это позволит вам автоматически проверять корректность работы автодополнений при каждом изменении кода и тем самым существенно улучшить стабильность и предсказуемость вашего ПО.
Представленный подход с использованием псевдотерминала универсален и может быть адаптирован для тестирования различных функций и сценариев командной строки. Важно регулярно обновлять тесты, добавляя новые сценарии, чтобы они отражали все возможные пути использования ваших функций автодополнения.
В заключение, автоматизация тестирования автодополнений в Zsh требует нестандартного подхода, но как только процесс налажен, он становится мощным средством обеспечения качества и надежности разрабатываемого программного обеспечения.