Мутационное тестирование c Mull
Здесь я не буду описывать, что такое мутационное тестирование. Если интересно, то воспользуйтесь списком ссылок внизу статьи. Отмечу только два факта: количество публикаций о 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
- bitcodefilelist - файл со списком всех 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, но желание внедрить мутационное тестирование в какой-то реальный проект всё ещё оставалось.
Продолжение следует
Дополнительные ссылки по теме:
- Mutations: How close are they to real faults?
- Does Choice of Mutation Tool Matter?
- Are Mutants a Valid Substitute for Real Faults in Software Testing
- An Analysis and Survey of the Development of Mutation Testing
- RCU Mutation Testing
- Applying Mutation Analysis On Kernel Test Suites: An Experience Report
- State of Mutation Testing at Google