Мутационное тестирование c Mull


Давно хотел попробовать мутационное тестирование (далее MT) в деле и всё никак не получалось.

Здесь я не буду описывать, что такое мутационное тестирование. Если интересно, то воспользуйтесь списком ссылок внизу статьи. Отмечу только два факта: количество публикаций о MT и количество программ, которые тестировали с помощью MT, показывают, что интерес к MT неуклонно растёт с 1988 года. В разных научных статьях фигурирует цифра в 70-90% реальных дефектов, найденных с помощью MT. Мне этого достаточно, чтобы заинтересоваться MT и попробовать на одном из реальных проектов.

В прошлом году предоставилась возможность это сделать. Знакомые ребята активно работали над инструментом для MT - Mull и у них уже была рабочая версия. Mull использует LLVM для получения IR любого исходного кода, который поддерживается в LLVM. Потом мутирует получившийся код, компилирует, запускает юнит-тесты и анализирует результаты. На тот момент для использования Mull нужна была поддержка последней версии clang в проекте, тесты на Google Test и свободное время. Я тогда работал над коммерческим проектом, который написан на C++, имел хорошее покрытие юнит-тестами на Google Test - около 80% и для сборки использовалась одна из последних версий clang. Проект как нельзя лучше подходил для эксперимента с Mull.

Разработчики Mull не поленились написать инструкции по использованию для CentOS и Ubuntu и установка прошла без особых проблем. А дальше начались проблемы.

Для использования Mull нужна версия LLVM c поддержкой LTO (>=3.9). Мы в проекте использовали не общесистемный компилятор, а из своего тулчейна. И версия LLVM там была не самая последняя. Эксперимент пришлось поставить на паузу до тех пор, пока не обновили тулчейн. Дождавшись планового обновления тулчейна я начал разбираться в сборочной инфраструктуре проекта, чтобы понять как добавить опции для сборки всего проекта с поддержкой LTO. После добавления опций после сборки стали появляться файлы с LLVM Bitcode вместо объектных файлов ELF. Теперь всё было готово для первого запуска Mull.

Для конфигурационного файла я взял за основу тестовый конфиг и немного отредактировал:

bitcode_file_list: MULL_BITCODE_FILES
project_name: PROJECT
max_distance: 1
fork: true
exclude_locations:
- XXX
  • bitcode_file_list - файл со списком всех bc файлов (find . -name "*.bc")
  • max_distance - по-моему это расстояние между мутациями в одном файле. Оптимизация, чтобы не делать слишком много мутаций.
  • fork - запуск мутантов происходит в дочерних процессах (на случай крэша или таймаута), лучше выставить в true.

Запуск: /opt/mull-driver/bin/mull-driver config.yml

После непродолжительной работы OOM-киллер отстреливает процесс Mull.

[1122049.396117] Out of memory: Kill process 16802 (mull-driver) score 483 or sacrifice child
[1122049.396123] Killed process 16802 (mull-driver) total-vm:1999272kB, anon-rss:816808kB, file-rss:36kB, shmem-rss:0kB

В ходе переписки с авторами Mull выясняю, что мутации пока выполняются последовательно и распараллеливание пока только в планах (сейчас уже исправлено и запуск тестов происходит параллельно):

alexdenisov [4:48 PM]
это так из-за мутантов, над этим тоже работаем есть более грамотный способ, но
на данный момент это tradeoff между памятью и скоростью сейчас каждый мутант
создает копию модуля в котором он находится, и эта копия висит в памяти
(ограничение LLVM такое) более грамотный способ это выкусывать только те
функции которые требуют выполнения, это должно разительно снизить и потребление
памяти и увеличить саму скорость выполнения, вот я как раз над этим работаю
сейчас, но там не очень тривиальное решение, требует времени

Я попробовал ограничить количество тестов одной группой тестов, с которыми работает Mull в конфиге и указать явно какие мутации мне интересны:

tests:
  - gtest_active_component_controller

add_mutation_operator
negate_mutation_operator
remove_void_function_mutation_operator

В этот раз тестирование завершается с другой ошибкой:

LLVM ERROR: Cannot select: 0x23e592060: i64 = X86ISD::WrapperRIP TargetGlobalTLSAddress:i64<i8** @_ZSt15__once_callable> 0 [TF=10]
 0x23e5192e0: i64 = TargetGlobalTLSAddress<i8** @_ZSt15__once_callable> 0 [TF=10]
In function: _ZSt9call_onceIMNSt13__future_base13_State_baseV2EFvPSt8functionIFSt10unique_ptrINS0_12_Result_baseENS4_8_DeleterEEvEEPbEJPS1_S9_SA_EEvRSt9once_flagOT_DpOT0_

Но после этого выяснилось, что ошибка эта известная, LLVM JIT не поддерживает thread local storage (TLS).

На этом я прекратил свои эксперименты с Mull, но желание внедрить мутационное тестирование в какой-то реальный проект всё ещё оставалось.

Продолжение следует

Дополнительные ссылки по теме:

Метки: softwaretesting opensource