[Python, Разработка игр, Тестирование игр] Как убедить гейм-дизайнера запустить тесты?

Автор Сообщение
news_bot ®

Стаж: 6 лет 3 месяца
Сообщений: 27286

Создавать темы news_bot ® написал(а)
05-Мар-2021 19:31

Полагаю, ни для кого не секрет, что в разработке игр участвует очень много специалистов, а не только программисты. Выпуск игры невозможен без художников, моделлеров, VFX-художников, и, конечно, гейм-дизайнеров. Кстати о последних. Мы их очень любим, но они часто ломают ресурсы. Не то чтобы они хотят это делать, но из-за особенностей работы им нужно делать много мелких правок, и шанс накосячить выше. И ведь множество ошибок — это тривиальные опечатки, недописанная или, наоборот, лишняя удалённая строка. Всё это можно исправить не отходя от кассы. Но как это сделать? Прописать в регламенте, что перед коммитом обязательно запустить %my_folder%/scripts/mega_checker? Мы проверяли — не работает. Человек — существо сложное и забывчивое. А проверять ресурсы хочется.
Но мы нашли выход — теперь нельзя закоммитить в репозиторий без тестов. По крайней мере незаметно и безнаказанно.

Система тестирования
Первое, что нам нужно — это система тестирования. Мы её уже описывали здесь. Напомним, что нужен один и тот же код и для запуска на сервере Ci, и локально, чтобы не было сложности в поддержке. Желательно, чтобы на проекте могли задавать разнообразные параметры для общих тестов, а ещё лучше — расширять собственными. Конечно, конфетка сразу не получилась.
Этап первый — запускать можно, но больно. Что делать с python-кодом ещё понятно, а вот со всевозможными утилитами вроде CppCheck, Bloaty, optipng, нашими внутренними костылями-велосипедами — нет. Для корректного запуска нужны исполняемые файлы для всех платформ, на которых работают наши коллеги (mac, windows и linux). На данном этапе все необходимые бинарные файлы находились в репозитории, а в настройках системы тестов указывался относительный путь к папке с бинарниками.
<CppCheck bin_folder=”utils/cppcheck”>...</CppCheck>

Это порождает несколько проблем:
  • со стороны проекта нужно хранить в репозитории лишние файлы, так как они нужны на компьютере каждого разработчика. Естественно, репозиторий из-за этого больше.
  • когда возникает проблема, сложно понять, какая именно версия стоит у проекта, нужная ли структура в папке.
  • где брать нужные бинарные файлы? Компилировать самому, скачивать в интернете?

Этап второй — наводим порядок в утилитах. А что, если выписать все нужные утилиты и собрать их в одном хранилище? Идея в том, что на сервере находятся уже собранные утилиты для всех нужных платформ, которые ещё и версионируются. У нас уже использовался Nexus Sonatype, поэтому мы пошли в соседний отдел и договорились за файлики. В итоге получилась структура: 

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

Опуская тонкости реализации

SPL
def get_tools_info(project_tools_xml, available_tools_xml):
    # Parse available tools at first and feel up dictionary
    root = etree.parse(available_tools_xml).getroot()
    tools = {}
    # Parse xml and find current installed version ...
    return tools
def update_tool(tool_info: ToolInfo):
    if tool_info.current_version == tool_info.needed_version:
        return
    if tool_info.needed_version not in tool_info.versions:
        raise RuntimeError(f'Tool "{tool_info.tool_id}" has no version "{tool_info.needed_version}"')
    if os.path.isdir(tool_info.output_folder):
        shutil.rmtree(tool_info.output_folder)
    g_server_interface.download(tool_id=tool_info.tool_id, version=tool_info.needed_version,
                                output_folder=tool_info.output_folder)
def run_tool(tool_info: ToolInfo, tool_args):
    system_name = platform.system().lower()
    tool_bin = tool_info.exe_infos[system_name].executable
    full_path = os.path.join(tool_info.output_folder, tool_bin)
    command = [full_path] + tool_args
    try:
        print(f'Run tool: "{tool_info.tool_id}" with commands: "{" ".join(tool_args)}"')
        output = subprocess.check_output(command)
        print(output)
    except Exception as e:
        print(f'Fail with: {e}')
        return 1
    return 0
def run(project_tools_xml, available_tools_xml, tool_id, tool_args):
    tools = get_tools_info(project_tools_xml=project_tools_xml, available_tools_xml=available_tools_xml)
    update_tool(tools[tool_id])
    return run_tool(tool_info, tool_args)

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

xml «на сервере»

SPL
<?xml version='1.0' encoding='utf-8'?>
<Tools>
  <CppCheck>
    <windows executable="cppcheck.exe" />
    <darwin executable="cppcheck" />
    <linux executable="cppcheck" />
  </CppCheck>
</Tools>


А на проекте добавляем файл с описанием того, что нужно.

xml проекта

SPL
<?xml version='1.0' encoding='utf-8'?>
<Tools>
  <CppCheck version="1.89" />
</Tools>

Чтобы было совсем хорошо, и не каждый раз перекачивать с сервера, можно заморочиться, сделав локальный кеш. Тогда переключение версий будет очень дешёвой операцией.
python -m utility_runner --available-source D:\Playrix\![habr]\gd_hooks\available_source.xml --project-tools D:\Playrix\![habr]\gd_hooks\project\project_tools.xml --run-tool CppCheck -- --version

После данных манипуляций процесс работы стал выглядеть предельно прозрачно:
  • на проекте есть только один файл, где прописаны версии утилит, которые на данный момент актуальны
  • стало просто распространять обновления, и главное, что понятна текущая версия на проекте. А это правда сильно упрощает поиск проблемы.


На сервере мы это относительно быстро подняли, но наша глобальная цель — запустить тесты у ГД.
А запускать-то как?
Вы когда-нибудь пробовали объяснить, как приготовить круассаны человеку, который никогда не готовил даже яичницы? Вот и нам сложно было объяснять гейм-дизайнерам, как запускать нужные скрипты. Если на сервере один раз настроил — и оно бодро выполняется, то с локальным запуском есть не так много вариантов. Точнее только один: хуки git.
По-простому, хук — это bash-скрипт, который запускается в определённые моменты при работе с git: перед pull или push, во время создания коммита, есть хуки даже на git-сервере.
Из всего разнообразия нас интересуют только три, которые запускаются во время создания коммита:
  • pre-commit — он выполняется первым и стоит на страже данных. Если код выхода отличный от нуля, то создание коммита прерывается.
  • prepare-commit-msg — он работает до вызова редактора сообщения коммита, но после создания стандартного сообщения. Нам он нужен, чтобы модифицировать сообщения коммитов слияния или rebase.
  • commit-msg — в этом хуке можно проверить сообщение к коммиту. Например, что разработчик не забыл добавить ссылку на задачу. Если он вернёт не ноль, то создание коммита прерывается.

Чтобы хук начал действовать, его мало положить в репозиторий, как скрипт, его нужно скопировать в папку .git/hooks. Автоматически это сделать нельзя — эксплойт. Мы не выдумывали хитрых технологий, а сделали два командных файла (для Windows и Mac), которые копируют хуки из одной папки в другую и запускаются двойным кликом. Выполнить их нужно только один раз, и такое уже несложно объяснить человеку без технического образования.
Конечно, не всегда всё идеально. Иногда бывают сбои, которые в основном типичны и делятся на две группы.
Магия у пользователя. Непонятная на первый взгляд ошибка, но стандартная проблема вроде нестандартных символов в путях, отсутствия git-bash на Windows. Для этих случаев мы пишем FAQ.

Недавний случай

SPL
Мы перебрали несколько предположений: нет прав на запись в папку, нет доступа на сервер, dns не резолвится. А оказалось, что curl не переваривает символы [ в пути.

Тонкости работы систем. Предусмотреть все возможные варианты и параметры мы не смогли, поэтому периодически вылавливаем разнообразные баги. Мы или подпираем их в скрипте, или добавляем пункт в FAQ. Например, папка .git/hooks не всегда находится в корне репозитория. Чтобы узнать точное расположение, можно использовать команду:

git rev-parse

SPL
git rev-parse --git-path hooks

В зависимости от того, в каком типе репозитория запускается команда, она вернёт следующее:
Главная папка репозитория
.git/hooks
Worktree
%repo_abs%/.git/hooks
submodule
%repo_abs%/.git/modules/hooks

Другой интересный случай — это переключение между ветками во время разработки. Мы не очищали папку .git/hooks, и там могли оставаться старые хуки. Они пытались запуститься и падали. Это довольно сильно расстраивало пользователей, поэтому мы добавили в скрипт очистку .git/hooks перед тем, как начать копирование новых хуков.
Всё сделать идеально с первого раза нельзя, поэтому просто необходима  возможность внести правки в репозитории, и чтобы они каким-либо образом автоматически подхватились у всех локально. Это сильно спасает, когда мы находим серьезный просчёт и не бегаем всем в личные сообщения с просьбой опять вызвать наш убер-скрипт. Вся эта работа должна быть максимально скрыта для разработчика — это не его война. Единственная сложность в том, что в момент выполнения хука мы не можем обновить сам файл хука — запись в него заблокирована системой. Одно из решений:
  • При вызове  pre-commit обновить все файлы, кроме него самого. И создать pre-commit-tmp
  • При вызове commit-msg заменить файл pre-commit на созданный в первом шаге pre-commit-tmp

Вот, теперь хорошо: один раз скопировали хуки, и они будут запускаться во время коммита и автоматически обновляться. Мы выдохнули, но пользователи прислали нам скриншот.

<spoiler title=«Причина ошибки:>Причина простая: сначала установили 32-битный питон в глобальном окружении; поняли свою ошибку, удалили и поставили 64-битный; pip install видит, что пакет уже установлен и ничего не делает. Но пакет-то для 32-битной версии — возникает конфликт.

Но всё же, как запускать?
Сначала мы сделали многостраничную инструкцию о том, какие круассаны вкуснее, какой python нужно установить. Но мы помним про гейм-дизайнеров и яичницу? Она всегда была подгоревшей: то python не той битности, то 2.7 вместо 3.7. И всё это множится ещё и на две платформы, где работают пользователи: windows и mac. (Пользователи Linux у нас либо гуру и сами всё настраивали, тихо притопывая под звуки бубна, либо их миновали проблемы.)
Мы решили вопрос радикально — собрали python нужной версии и битности. А на вопрос «как нам его поставить и где хранить» ответили: Nexus! Единственная проблема: у нас ещё нет python, чтобы запустить python-скрипт, который мы сделали для запуска утилит из Nexus.
И тут на помощь приходит bash! Он не такой уж и страшный, а даже хороший, когда к нему привыкнешь. И работает везде: на unix уже всё хорошо, а на Windows он ставится вместе с git-bash (это наше единственное требование к локальной системе). Алгоритм установки очень простой:
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 10-Май 16:34
Часовой пояс: UTC + 5