Как писать автоматизированные тесты для завершения команд в zsh?

Вопрос или проблема

У меня есть проект, в котором я автоматически генерирую функции автодополнения 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 требует нестандартного подхода, но как только процесс налажен, он становится мощным средством обеспечения качества и надежности разрабатываемого программного обеспечения.

Оцените материал
Добавить комментарий

Капча загружается...