Геймер? Неа — тестировщик с правильным микроскопом!
Геймер? Неа — тестировщик с правильным микроскопом!

В нашей ОС появилась новая фича, позволяющая разработчикам и тестировщикам производить анализ в рамках методик безопасной разработки. Новый механизм позволяет реализовывать как разнообразные сценарии тестирования UI, так и выполнять emergency-сценарии управления интерфейсом без вмешательства в кодовую базу программно-аппаратных систем. Как всё это работает вы узнаете из этой статьи.


Предыстория

Достаточно давно, когда мы ещё только начинали внедрять методики безопасной разработки, одним из критически важных квестов, появившихся перед отделом тестирования, стал вопрос тестирования UI. Если тестируется веб-интерфейс, набор инструментов достаточно понятен и браузер-ориентирован. Если задача в контроле качества GUI приложения — полагаемся на системные механизмы. Но что делать, если требуется исследовать сами эти механизмы, доверие к которым априори отсутствует?

Вопрос доверия к инструментам и методам

Вопрос доверия в тестировании является ключевым. Допустим, что анализируется некий аппаратный сигнал с помощью осциллографа. Исследователь обычно исходит из того, что прибор показывает достаточно точные данные. Однако, если устройство не внушает доверия, его отдают на поверку или заменяют средства измерения.

Эта же логика применима и к методам тестирования, которые не могут быть эндосистемными. В противном случае их обоснованно можно считать частью самого объекта тестирования, а это уже вызывает серьёзные вопросы к состоятельности такого тестирования. Ведь при тестировании объект тестирования — сущность, в характеристиках которой мы сомневаемся (а иначе зачем мы тестируем?). На практике сопричастность такого рода обычно легко доказуема.

За счет того, что ЗОСРВ «Нейтрино» является микроядерной ОС, процесс декомпозиции и анализа задачи оказался не очень сложным. Поскольку все системные сервисы имеют независимые адресные пространства (aka процессы), подсистема ввода строго изолирована от графической и оконной подсистем и, в конечном счете, от приложений. В этом случае любые негуманные эксперименты над первой не могут оказать существенного влияния на остальные компоненты системы (или модули в рамках модульного тестирования). Справедливо и обратное, при тестировании UI оконных приложений, модулем, к которому есть первичное доверие, может выступать подсистема ввода. Синтезированное решение появилось в виде разработки простейшего логического драйвера, позволяющего скриптовать манипуляции пользователя с устройствами ввода.

В окружении монолитного ядра это было бы выполнить концептуально сложнее ввиду тесного взаимодействия графики и ввода, что может косвенно выливаться в вопрос доверия к рендерингу UI приложениями.

Новый инструмент разрабатывался для достижения следующих целей:

  1. Тестирование HMI (Human-Machine Interface) как в графических, так и консольных режимах. Путём эмуляции действий оператора с устройствами ввода драйвер позволяет воспроизводить различные операции ввода и анализировать адекватность реакции системы на тест-кейсы.

  2. Автоматизация тестирования — для драйвера был разработан собственный командный интерфейс, позволяющий скриптовать операции и выполнять сложные сценарии.

  3. Emergency сценарии использования. В ряде систем предусматривается автоматическая реакция на нештатные состояния. Данный драйвер позволит реализовывать подобные действия прямо на уровне подсистемы ввода, блокируя весь ввод (исключая человеческий фактор). Пример: обнаружили критически важное событие → терминируем драйвер ввода → запускаем драйвер виртуального ввода → вместо пользователя автоматом посылаем нужную hotkey-комбинацию. Если "влезать" в прикладной код нежелательно — вполне себе альтернатива.

Способы реализации

Общая подсистемы ввода имеет следующую структуру:

Общая структура подсистемы ввода на примере клиента в виде оконного окружения Photon (legacy)
Общая структура подсистемы ввода на примере клиента в виде оконного окружения Photon (legacy)

Изначально рассматривались три варианта реализации, причем, не только драйверные:

  • HID-драйвер (devh-*) — компонент ОС, поддерживающий различные конкретные устройства ввода через интерфейсы менеджера io-hid. io-hid обеспечивает ввод информации от физических устройств и оперирует такими понятиями как адреса на шине, номера прерываний и т. п. На данном уровне необходимо писать отдельные драйвера под каждое виртуальное устройство ввода, которые потом отдельно придется поднимать при запуске io-hid с опциями -d. Получается слишком инвазивно и, при необходимости изменения конфигурации, потребует перезапуска всех смежных драйверов, что будет деструктивным фактором для системы в некоторых сценариях.

  • Драйвер ввода (devi-*) - standalone драйвер ввода, который может, как выполнять функции провайдера HID-устройств, так и напрямую взаимодействовать с произвольной периферией. Основной задачей этих компонентов является передача данных в вышележащие подсистемы и поэтому не требует обязательной коммуникации с физическими интерфейсами. В данном случае потребуется описать модули различных поддерживаемых драйвером устройств, а также реализовать менеджер ресурсов для захвата данных от внешнего кода. На этом уровне можно изолированно от других драйверов описать множественную логику виртуализируемых устройств (например, можно получать данные по сети или через промышленные протоколы).

  • Реализация в виде приложений для конкретных оконных окружений (которых у нас несколько: legacy и собственное). Основными недостатками являются отсутствие доверия к такому подходу и необходимость частных решений для каждого окружения.

По понятным причинам остановились на втором варианте. Вариант с использованием devh-* интерфейсов был отброшен как избыточно сложный и слишком низкоуровневый.

Детали реализации

Последовательность ввода данных для типичного devi-драйвера:

Последовательность ввода данных в драйвере
Последовательность ввода данных в драйвере

Основной функцией, которая используется для ввода с устройств, является devi_enqueue_packet(). Эта функция используется модулями уровня фильтра для отправки пакета данных клиенту, в качестве которого может выступать Photon или менеджер ресурсов, предоставляющий POSIX-интерфейс всем желающим.

Дескриптор модуля виртуального устройства ввода имеет следующий вид:

struct _input_module
{
    /* Module parameters */
    input_module_t *up;         /* Up module in the bus line */
    input_module_t *down;       /* Down module in the bus line */
    struct Line    *line;       /* Bus line */
    int            flags;       /* flags */
    int            type;        /* type of module */
    char           name[12];    /* Module name */
    char           date[12];    /* Date of compilation */
    const char     *args;       /* Module arguments */
    void           *data;       /* Private module data */

    /* Callbacks */
    int (*init)( input_module_t *module );
    int (*reset)( input_module_t *module );
    int (*input)( input_module_t *module, int num, void *data );
    int (*output)( input_module_t *module, void *data, int num );
    int (*pulse)( message_context_t *ctx, int code, unsigned flags, void *ptr );
    int (*parm)( input_module_t *module, int code, char *optarg );
    int (*devctrl)( input_module_t *module, int event, void *data );
    int (*shutdown)( input_module_t *module, int delay );
}

Последовательность инициализации:

Последовательность инициализации драйвера devi
Последовательность инициализации драйвера devi

Более подробно нюансы структуры и подходы к разработке описаны в корневом README публичного репозитория с примерами таких драйверов: https://git.kpda.ru/drivers/input.

Результат и примеры использования

В итоге появился devi-virtual (войдёт в следующий релиз ЗОСРВ «Нейтрино»). Изначально предполагалось, что будут реализованы два виртуальных устройства: клавиатура (/dev/virtual-kbd) и мышь (/dev/virtual-mouse). Но от мыши пока пришлось отказаться, так как она подразумевает перемещения в относительных координатах, что крайне неудобно для скриптования. Вместо неё сделали виртуальный тачскрин с абсолютными координатами (/dev/virtual-touch). Для удобства разрешение сопоставили с экранными координатами, чтобы не требовалось выполнять предварительную калибровку драйвера.

В качестве исключения интерфейс пришлось сделать текстовым, чтобы можно было из скриптов отправлять команды через echo.

Текстовые интерфейсы в драйверах

В наших драйверах такие подходы не являются best practice, так как текстовые парсеры не самая эффективная штука. Протокол получается изобилующим человеческим фаткором, на пустом месте капитально расширяется набор тест-кейсов и снижается общая надежность системного компонента.

Для работы клавиатуры добавлено два режима: "string" - для ввода строк и "key" - для ввода отдельных, в основном командных, клавиш. У тачпада задается нажимаемая кнопка мыши (да, действительно бывают аппаратные тачскрины с "правой клавишей", силой нажатия и т. п.) и координаты нажатия.

Драйвер запускается и терминируется совершенно автономно. С помощью опций -d и -n задаются задержка между инжектируемыми командами в миллисекундах и имя устройства. Пример запуска драйвера с обоими устройствами:

devi-virtual vkbd -d 500 vtouch -d2500

Ввод строки символов:

echo "string|ABCDE" > /dev/virtual-kbd

...

Ввод строки "ABCDE" с помощью виртуальной клавиатуры
Ввод строки "ABCDE" с помощью виртуальной клавиатуры

Эмуляция нажатия правой кнопки мыши в нужных координатах:

 echo "RIGHT_BUTTON|800|80" > /dev/virtual-touch

...

Нажатие ПКМ в точке с координатами 800х80
Нажатие ПКМ в точке с координатами 800х80

Для демонстрации работы драйвера виртуальных устройств ввода был написан скрипт, с помощью которого можно зайти на сайт www.habr.com.

devi-virtual vkbd -d 500 vtouch -d 2500
waitfor /dev/virtual-touch 2
waitfor /dev/virtual-kbd 2
echo "LEFT_BUTTON|1200|80" > /dev/virtual-touch
sleep 2 # Нужно дождаться запуска браузера
echo "LEFT_BUTTON|30|30" > /dev/virtual-touch
echo "LEFT_BUTTON|30|50" > /dev/virtual-touch
echo "LEFT_BUTTON|270|80" > /dev/virtual-touch
echo "string|www.habr.com" > /dev/virtual-kbd
echo "key|ENTER" > /dev/virtual-kbd

С помощью первой команды выполняется запуск браузера через панель быстрого доступа. А дальше по классике:

Открываем меню.

Нажатие на кнопку "Файл" на панели меню браузреа

Создаём новую вкладку через выпадающее меню,

Нажатие на кнопку "Новая вкладка" в выпадающем меню

меняем фокус ввода,

Нажатие на поле ввода адреса

вводим адрес

Ввод адреса

и загружаем страницу.

Нажатие клавиши "Enter"

Вместо заключения

Драйвер долгое время обкатывался во внутренних тепличных условиях, но со следующего релиза ЗОСРВ «Нейтрино» будет доступен для использования всем пользователям. Если интерес к этой разработке возникнет раньше релиза, опубликуем материалы в публичном репозитории компании.

Бонус: как определить координаты точки в оконной системе?

Для определения конкретных координат можно перезапустить devi-hid с добавлением опции -vvvvvvvv. В этом режиме драйвер сообщает обо всех передвижениях мыши в stdout (на картинке подчеркнуты красным):

Вывод devi-hid с абсолютными координатами в консоли
Вывод devi-hid с абсолютными координатами в консоли

Комментарии (0)