[sisyphus] Автоматизация тестирование работоспособности графических программ в репозитории

Михаил Новоселов, Думалогия mikhailnov на dumalogiya.ru
Вс Апр 1 14:38:08 MSK 2018


Добрый день.

Недавно Chromium 65 перестал запускаться на x86_32 (i586) 
(https://bugzilla.altlinux.org/show_bug.cgi?id=34722), в связи с этим 
подумал, что есть способ автоматически тестировать минимальную 
работоспособность почти каждого графического приложения после сборки 
перед отправкой в репозиторий с низкими накладными расходами на 
тестирование.

Недавно для себя сделал скрипт, автоматизирующий контроль доступности 
вебинарной системы на базе BigBlueButton. Суть скрипта в том, что он 
через Xvfb или Xephyr запускает браузер в отдельном X-сервере с жестко 
заданным разрешением экрана, открывает в браузере нужный сайт, далее с 
помощью xdotool путем нажатий и ввода текста в нужные места открытой 
веб-страницы с заранее известными координатами заполняет форму входа на 
вебинарную площадку, ждет, пока прогрузится и делает скриншот в формате 
png (scrot --quality 100 filename.png). Далее этот скриншот обрезается 
по заданным координатам, с помощью утилиты compare из imagemagick 
сравнивается с заранее сделанным скриншотом, высчитывается количество 
несвопадающих пикселей. Если оно меньше эмпирически подобранного числа, 
то система считается работающей, иначе принимаются нужные действия. 
Сравнение скриншотов можно заменить на выделение нужного участка текста 
на веб-странице путем тройного клика левой мышью (xdotool X Y click 1 
click 1 click 1) и сравнения текста в промежуточном буфере (xclip -o) с 
заранее известным текстом.

В случае репозитория можно проверять:
- код возврата запуска программы (echo $?)
- ps aux | grep $(which chromium) или pgrep после запуска программы и 
некоторой паузы, чтобы проверить, работает ли она на самом деле
- сравнивать скриншот мейнтейнера пакета и/или этой системы автотеста со 
скриншотом того, что запустилось. Скриншоты обрезать так, чтобы в них не 
было заголовков окна (хотя их нет при рабоет в Xfvb/Xephyr), 
скролл-баров и прочих элементов, внешний вид которых трудно 
проконтролировать, выделять нужно какую-то небольшую область, по которой 
можно определить, что программа работает, в случае с Chromium этим может 
быть содержимое стартовой страницы chrome://welcome.
Если тесты не проходятся, то не отправлять пакет в репозиторий.

Ниже код моего несложного скрипта почти без изменений, только заменил 
реальные числовые координаты на X1, Y2 и т.д., убрал вводимые пароли и 
вызываемую ссылку для оповещения.
Обратите внимание, что если к серверу вообще не подключен монитор и/или 
не запущено DE, то Xvfb запускается не с заданным разрешением, а 
меньшим, думаю, это можно побороть, но у меня это работает на сервере с 
DE и подключенным физическим монитором, пока не разбирался.

Если скрипт запустить с параметром xephyr (./bbb-autotest.sh xephyr), то 
откроется окно, в котором откроется тестируемая программа, при этом его 
можно свернуть, т.к. работает полностью автономно. Режим xephyr для 
запуска при разработке скрипта, Xfvb — на сервере, там вообще никакое 
окно не открывается.

#!/bin/bash
###################################################################################################################
# Dependencies: bash wget scrot imagemagick xephyr Xvfb xrandr xdotool 
firefox

# Example crontab line:
# */15 * * * * /bin/bash -c "export DISPLAY=:0; cd 
/home/username/bbb-autotest; ./bbb-autotest.sh"

# Нужно создать директрию /var/log/bbb-autotest и сделать нашего 
пользователяее владельцем для прав на запись
###################################################################################################################

# запомним директорию, откуда начинаем работать
dir0="$(pwd)"

# перед запуском сбросим значение переменной DISPLAY на стандартное 
(полезно при ручной отладке скрипта)
export DISPLAY=:0
# будем запускать браузер с английской локалью, чтобы текст был 
гарантированно предсказуемым
export LANG=c

log_dir="/var/log/bbb-autotest"
log_file="${log_dir}/bbb-autotest.log"

# -w — это проверка, есть ли права на запись в указанный файл
if [ -w "$log_dir" ]
     then
         # создать файл-лог, если он не существует
         test -f "$log_file" || touch "$log_file"
         # если есть права на запись в папку, то установить значение 
переменной
         LOG_DIR_ENABLED=1
     else
         echo "Директория $log_dir недоступна для записи!"
fi
if [ -w "$log_file" ]
then
     echo "Пишем в лог $log_file"
else
     log_file_unavailable="$log_file"
     log_file="/tmp/bbb-autotest.log"
     echo "Основной лог (${log_file_unavailable}) недоступен для записи, 
пишем в $log_file"
     # sleep, чтобы при запуске скрипта обратить внимание на это 
сообщение об ошибке
     sleep 15
fi
# проверим, есть ли необходимый нам для работы скриншот-образец
if [ -f "${dir0}/screenshot_sample_cropped.png" ]
then
     echo "Скриншот-образец найден"
else
     echo "Скриншот-образец не найден! Завершаем работу."
     exit 1
fi

# номер бота; пока не знаю, зачем, может быть, пригодится
bot_number="001"

tmp_dir="/tmp/bbb-autotest"
mkdir -p ${tmp_dir}

rm -fvr ${tmp_dir}/*

virt_display="$(( ( RANDOM % 100 )  + 1 ))"
echo "Random DISPLAY = $virt_display"

# Запустим отдельный икс-сервер, в котором без оконных менеджеров и 
прочих прибабмбасов будем запускать то, что нам нужно
# Использование отдельного X-сервера с заранее известным размером окна 
позволит нам спокойно использовать единый набор координат для xdotool (в 
какие точки внутри этого окна нажимать), которые не будут зависеть от 
размера экрана и иных параметров системы, где запускается этот скрипт
if [[ $@ == *xephyr* ]]
# если в качестве аргумента скрипту передано "xephyr", то запускаем 
графическое окно с отдельным икс-сервером; это для разработки скрипта
# иначе запускает фреймбуффер Xvfb, где не открывается никакое 
графическое окно; это для запуска на настоящем сервере
     then
         echo "Работаем в режиме Xephyr"
         Xephyr -br -ac -noreset -screen 1024x720 ":${virt_display}" &
     else
         echo "Работаем в режиме Xvfb"
         Xvfb ":${virt_display}" -screen 0 1024x720x24 &
fi
# PID запускаемых процессов записываем в файл, а не в переменную, чтобы 
можно было их все завершить даже после завершения этого скрипта, считав 
хранящиеся на диске значения
echo $! >${tmp_dir}/X-server.pid

# export DISPLAY делать ПОСЛЕ запуска Xephyr, иначе сам Xephyr пытается 
запуститсья на еще не существующем DISPLAY
export DISPLAY=":${virt_display}"

# теперь запустим Firefox; т.к. переменная окружения DISPLAY уже ранее 
была экспортирована, Firefox запустится именно в нашем виртуальном 
икс-сервере
firefox --private-window "http://URL0" &
echo $! >${tmp_dir}/firefox.pid
sleep 20 # ждем, пока Firefox полностью запустится

# чтобы получить текущее расположение мыши в Xephyr: sleep 5; xdotool 
getmouselocation --shell; за эти 5 сек перевести мышь в нужное место, в 
терминале с нужной переменной DISPLAY будут напечатаны координаты 
курсора мыши
# вводим логин в форму входа
xdotool mousemove X1 Y1 click 1
# теперь сотрем все, что ранее было написано в этом поле (на всякий случай)
xdotool key Control+a
xdotool key BackSpace
xdotool type bbb-autotest-bot${bot_number}
# теперь вводим пароль
xdotool mousemove X2 Y2 click 1
xdotool type password
# нажимаем на кнопку "Войти на вебинар"
xdotool mousemove X3 Y3 click 1

# ждем, пока загрузится площадка на флеш-плеере
sleep 50

# делаем скриншот вирутального экрана и вырезаем из него область с 
вопросом "Только слушать или микрофон?"
scrot --quality 100 ${tmp_dir}/current_screenshot_full.png
# уменьшаем кол-во цветом до двух, чтобы уменьшить влияние различий в 
рендеринге шрифтов
convert -crop X4xY4+P1+P2 +dither -colors 2 
"${tmp_dir}/current_screenshot_full.png" 
"${tmp_dir}/current_screenshot_cropped.png"
# точно так же уменьшим кол-во цветом в скриншоте-образце
convert +dither -colors 2 "${dir0}/screenshot_sample_cropped.png" 
"${tmp_dir}/screenshot_sample_cropped_colors2.png"
### вообще такой метод не очень, т.к. "convert +dither -colors 2" 
уменьшает кол-во цветом до двух исходя из расчитанного среднего цвета, а 
что, если из-за особенностей рендеринга шрифта средние цвета окажется 
разными, пусть и самую малость? Тогда можно не заниматься уменьшением 
кол-ва цветов, но пока забью на это.
# теперь сравниваем 2 картинки, сколько пикселей различается 
(https://stackoverflow.com/questions/29229535/measure-similarity-of-two-images-in-java-or-imagemagick)
screenshots_diff_value="$(compare -metric ae 
"${tmp_dir}/screenshot_sample_cropped_colors2.png" 
"${tmp_dir}/current_screenshot_cropped.png" null: 2>&1)" # 2>&1 
обязательно, иначе выводимое значение не попадает в значение переменной
echo screenshots_diff_value = "$screenshots_diff_value"

# сохраним текущее время в переменную, чтобы в логе и названии 
файла-архива с запакованными скриншотами/логами была одна и та же дата
date0="$(date +%d.%m.%Y_%H.%M.%S)"
# если кол-во несовпадающих пикселей меньше 8000, то считаем проверку на 
работу площадки успешно пройденной; 8000 наполовину от балды, наполовину 
подобрано экспериментально
# -le: less or equal, <=
if [ "${screenshots_diff_value}" -le 8000 ]
     then
         echo "Площадка работает!"
         echo "pass;;${screenshots_diff_value};;${date0}" >>"${log_file}"
     else
         echo "Площадка НЕ работает!"
         echo "fail;;${screenshots_diff_value};;${date0}" >>"${log_file}"
         # оповещаем
         wget -qO- "URL" >/dev/null

         if [ "$LOG_DIR_ENABLED" -eq 1 ]
         # если существует папка для логов и у нас есть права на запись 
в нее, то в случае неудачной проверки заархивируем временную папку в 
нее, чтобы потом посмотреть на скриншоты для выяснения причин ошибки
             then
                 tar cfJ "${log_dir}/fail_${date0}.tar.xz" "${tmp_dir}" 
#>/dev/null
         fi
fi

# убъем все запущенные в ходе проверки процессы; убиваем в конце, а не в 
начале, чтобы в начале случайно не убить вообще другой процесс, у 
которого совпадет PID
for i in ${tmp_dir}/*.pid; do kill $(cat $i); done

###################################################################################################################

В случае тестирования репозитория перед каждым запуском браузера делать 
пустую директорию значением $HOME, чтобы все программы запускались на 
заранее известной и, очень желательно, почти не изменяемой от релиза к 
релизу теме GTK/Qt. Значение разницы скриншотов в пикселях подобрать 
такое, чтобы нерадикальные изменения в интерфейсе программы укладывались 
в него, но, если программа не запустилась, запустилась с черным или 
белым экраном или выпало маленькое сообщение об ошибке вместо нее, чтобы 
срабатывало оповещение об ошибке.

Что касается иных способов контроля, кроме уже описанных скриншотов и 
выделения текста. Я пробовал распознавать скриншоты Tesseract'ом, 
накладные расходы по времени и ресурсам небольшие, но, если распознавать 
стандартный скриншот с DPI 96 или включенным субпиксельным рендерингом 
шрифтов, то Tesseract может одно и то же распознавать по-разному раз от 
разу, например, цифру 6 то как 6, то как 5, точку как точку или как 
запятую. Поэтому скриншоты нужно увеличить примерно до DPI 300 или 
больше, как стандартный сканированный текст, и убрать цветные пиксели 
субпиксельного рендеринга путем уменьшения количества цветов скриншота. 
Пример:
convert -crop 
${crop_real_width}x${crop_real_height}+${crop_real_x}+${crop_real_y} 
+dither -colors 2 -resize 400% -depth 500 /tmp/checkresult.png 
/tmp/checkresult2.png

Для стабильности работы такой системы в случае сравнения скриншотов 
нужно стандартизировать тему GTK (стандартная, которая серая, а не 
Adwaita, хорошо подойдет) и настройки рендеринга шрифтов, можно создать 
отдельный пакет с нужными настройками в /etc/skel.

Как видите, накладные расходы низкие потому, что не нужно создавать и 
разворачивать контейнеры и виртуальные машины, достаточно создавать 
чистую или стандартизированную папку под $HOME. На тестированием одной 
программы с графическим интерфейсом будет уходить в среднем меньше 
минуты. Можно добавить, например, exit 0 в случае успешного 
тетсирования, exit 2 при такой-то ошибке и т.д. и эти кода возврата 
обрабатывать тем, что отвечает за репозиторий и запускает тестирование.



-- 
------
С уважением,
Михаил Новоселов,
https://nixtux.ru/ | mikhailnov на dumalogiya.ru | Telegram: mikhailnov@



Подробная информация о списке рассылки Sisyphus