TH3
Содержание
1. Обзор
Тестовый комплекс SQLite № 3 (далее «TH3») — один из трёх тестовых комплексов, используемых для тестирования SQLite. TH3 отвечает следующим требованиям:
TH3 может запускаться на встраиваемых платформах, на которых отсутствует инфраструктура для рабочих станций.
TH3 тестирует SQLite в конфигурации, используемой при развертывании, и применяет только опубликованные и документированные интерфейсы. Иными словами, TH3 тестирует скомпилированный объектный код, а не исходный код, тем самым проверяя, что ошибки компилятора не привели к проблемам. «Тестируй то, на чём летаешь, и летай на том, что тестируешь».
TH3 проверяет реакцию SQLite на ошибки нехватки памяти, ошибки дискового ввода-вывода и потерю питания во время фиксации транзакции.
TH3 проверяет SQLite в различных конфигурациях времени выполнения (UTF8 и UTF16, разные размеры страниц, различные режимы журналирования и т. д.).
TH3 обеспечивает 100%-ное покрытие ветвей (и 100% MC/DC) ядра SQLite. (Покрытие тестами расширений, таких как FTS и RTREE, составляет менее 100%.)
Изначально TH3 был написан только для валидационного тестирования, но впоследствии его стали использовать также для тестирования в процессе разработки и отладки; он оказался очень полезен и в этих задачах. Полный тест с покрытием выполняется на рабочей станции менее чем за пять минут и поэтому подходит для быстрого регрессионного тестирования при повседневном сопровождении кодовой базы SQLite.
1.1. История
TH3 появился в результате попытки протестировать SQLite на платформе SymbianOS. До появления TH3 все тесты SQLite выполнялись с использованием скриптового языка TCL, однако TCL было непросто скомпилировать для SymbianOS, что затрудняло тестирование. Первой попыткой решить эту проблему стал скриптовый язык «TH1» (Test Harness #1) — повторная реализация части языка TCL в более переносимой форме, которую можно было скомпилировать и запустить на SymbianOS и которой было достаточно для запуска тестов SQLite. TH1 не стал стандартным инструментом тестирования SQLite, но продолжил использоваться как язык скриптов для настройки системы контроля версий Fossil. Существовал также «Test Harness #2» — попытка создать простой скриптовый язык с префиксной записью операторов для управления тестами. TH3 стал третьей попыткой.
Примерно в то же время некоторые производители авионики проявили интерес к SQLite, что побудило разработчиков SQLite создать TH3 с поддержкой строгих стандартов тестирования DO-178B.
Первые строки кода TH3 были написаны 2008-09-25. Интенсивная работа в течение следующих 10 месяцев позволила TH3 достичь 100% MC/DC 2009-07-25. Код TH3 продолжает совершенствоваться и расширяться.
По состоянию на 2018-05-19 дерево исходного кода TH3 содержит более 500 000 строк кода в 1709 отдельных файлах.
2. Работа
TH3 — это генератор тестовых программ. Результатом работы TH3 является программа на языке C, предназначенная для компоновки с тестируемой библиотекой SQLite. Сгенерированная тестовая программа компилируется и запускается на целевой платформе, чтобы проверить правильность работы SQLite на этой платформе.
Входные данные TH3 — это тестовые модули, написанные на C или SQL, и небольшие файлы конфигурации, определяющие способ инициализации SQLite. Пакет TH3 включает 1 444 тестовых модуля и более 47 конфигураций (по состоянию на 2018-05-19). Для настройки TH3 под специализированные приложения можно добавлять новые модули и конфигурации. При каждом запуске TH3 считывает часть доступных тестовых модулей и файлов конфигурации и создает пользовательскую программу на C, которая выполняет все указанные тесты во всех заданных конфигурациях. Полное тестирование SQLite обычно требует нескольких запусков TH3 для создания нескольких тестовых программ, охватывающих разные аспекты работы SQLite, после чего все тестовые программы компонуются с одной и той же библиотекой SQLite и запускаются по отдельности на целевой платформе.
В TH3 нет произвольных ограничений. Можно сгенерировать одну тестовую программу, содержащую все тестовые модули и все файлы конфигурации. Однако такую программу может быть слишком сложно развернуть на встраиваемых платформах. (По состоянию на 2018-05-19 полный тест TH3 содержит более 850 000 строк и 58 МБ кода на C.) TH3 позволяет разбивать библиотеку тестовых модулей на более мелкие, удобные для обработки части.
Каждый отдельный тестовый модуль может содержать десятки, сотни или тысячи отдельных тестов. Тестовые модули можно писать на C, в виде скриптов SQL или сочетая оба языка. Около двух третей существующих тестовых модулей написаны только на SQL, а остальные — только на C или на C и SQL одновременно.
Каждый файл тестового модуля содержит заголовок с описанием условий, при которых тест применим. Для конкретной конфигурации запускаются только совместимые с ней модули.
3. Создание тестовой программы
Генератор программ TH3 — это скрипт TCL с именем «mkth3.tcl». Чтобы создать тестовую программу, достаточно запустить этот скрипт и передать в командной строке имена файлов с тестовыми модулями и конфигурациями. Тестовые модули — это файлы с суффиксом «.test», а конфигурации — файлы с суффиксом «.cfg». Типичный вызов mkth3.tcl может выглядеть так:
tclsh mkth3.tcl *.test *.cfg >testprog1.c
Результатом работы скрипта mkth3.tcl является программа на C, содержащая всё необходимое для запуска тестов, за исключением самой библиотеки SQLite. Сгенерированная тестовая программа содержит реализации всех вспомогательных интерфейсов, используемых тестовыми модулями, а также процедуру main(), управляющую выполнением тестов. Чтобы получить из тестовой программы работающий исполняемый файл, достаточно скомпилировать её вместе с SQLite:
cc -o testprog1 testprog1.c sqlite3.c
Приведённый выше пример команды компиляции служит лишь для иллюстрации. В реальной установке обычно следует указать параметры оптимизации и ключи компиляции в командной строке компилятора.
Для тестирования во встраиваемых системах скрипт mkth3.tcl и приведённые выше шаги компиляции выполняются на обычной рабочей станции с использованием кросс-компилятора, после чего полученная тестовая программа переносится на устройство для запуска.
После создания тестовая программа запускается без аргументов, чтобы выполнить тесты. Данные о ходе выполнения и диагностические сообщения об ошибках выводятся в стандартный поток вывода. (Для встраиваемых устройств без стандартного канала вывода можно настроить альтернативный вывод с помощью параметра компиляции.) Программа возвращает ноль, если ошибок нет, и ненулевое значение, если обнаружены какие-либо проблемы.
Типичный вывод при запуске одной тестовой программы TH3 выглядит так:
With SQLite 3.8.11 2015-05-15 04:13:15 56ef98a04765c34c1c2f3ed7a6f03a732f3b886e -DSQLITE_COVERAGE_TEST -DSQLITE_NO_SYNC -DSQLITE_SYSTEM_MALLOC -DSQLITE_THREADSAFE=1 Config-begin c1. Begin c1.pager08 End c1.pager08 Begin c1.build33 End c1.build33 Begin c1.orderby01 End c1.orderby01 ... 15014 lines of output omitted .... Begin 64k.syscall01 End 64k.syscall01 Begin 64k.build01 End 64k.build01 Begin 64k.auth01 End 64k.auth01 Config-end 64k. TH3 memory used: 6373738 Config-begin wal1. Begin wal1.wal37 End wal1.wal37 Config-end wal1. TH3 memory used: 100961 All 226 VDBE coverage points reached th3: 0 errors out of 1442264 tests in 213.741 seconds. 64-bit little-endian th3: SQLite 3.8.11 2015-05-15 04:13:15 56ef98a04765c34c1c2f3ed7a6f03a732f3b886e
Сначала выводится идентификатор SQLITE_SOURCE_ID (сверенный с результатом sqlite3_sourceid()) тестируемой версии SQLite, а также параметры компиляции, полученные с помощью sqlite3_compileoption_get(). В конце выводится сводка результатов тестирования и повторяется значение SQLITE_SOURCE_ID. Если обнаружены ошибки, дополнительные строки содержат подробную информацию о проблеме. Строки с сообщениями об ошибках всегда начинаются с одного пробела, поэтому их можно быстро извлечь из больших файлов вывода с помощью следующей команды:
grep "^ "
По умолчанию выводятся начало и конец выполнения каждой комбинации конфигурации и тестового модуля. В примере выше «c1» и «64k» — это конфигурации, а «pager08», «build33», «orderby01» и т. д. — тестовые модули. Параметры компиляции и выполнения позволяют увеличить или уменьшить объём вывода. Его можно увеличить, выводя информацию о каждом тестовом случае внутри каждого тестового модуля. Объём вывода можно уменьшать поэтапно: не выводить начало и конец выполнения тестовых модулей, не выводить начало и конец выполнения конфигураций, а затем полностью отключить вывод.
3.1. Скрипты автоматизации тестирования
В комплект TH3 входят дополнительные скрипты TCL, помогающие автоматизировать тестирование на рабочих станциях. Скрипт «th3make» автоматически запускает «mkth3.tcl» и «gcc», а затем выполняет полученную тестовую программу и проверяет результаты. В число аргументов th3make входят все тестовые модули «*.test» и конфигурации «*.cfg», которые нужно включить в тест. Дополнительные параметры th3make позволяют компилировать тестовую программу разными компиляторами (GCC, Clang, MSVC), выбирать уровень подробности вывода, запускать тестовую программу под valgrind, проверять покрытие с помощью gcov и выполнять другие действия. Скрипт th3make также принимает в качестве аргументов имена файлов «*.rc». Эти файлы *.rc представляют собой наборы других аргументов, обычно используемых вместе для одной цели. Например, файл «quick.rc» содержит восемь аргументов th3make для быстрого (3-минутного) тестирования с полным покрытием. Благодаря этому оператор может ввести «./th3make quick.rc» вместо того, чтобы набирать все необходимые параметры командной строки. Ниже приведены некоторые из более чем 40 доступных файлов *.rc:
- alignmentN.rc — эти файлы содержат параметры -D для компилятора, используемые различными известными downstream-проектами.
- cov.rc — параметры измерения покрытия тестами
- extensions.rc — параметры включения FTS4, R-деревьев и STAT4.
- fast.rc — запуск большинства тестов, включая не требуемые для покрытия, с пропуском только длительных soak-тестов и параметрами компилятора для поставки (например, -O3)
- memdebug.rc — как test.rc, но дополнительно включает -DSQLITE_MEMDEBUG.
- min.rc — запуск минимального набора тестов, необходимого для 100%-ного покрытия.
- quick.rc — запуск всех тестов, необходимых для 100%-ного покрытия, с использованием -Os и -DSQLITE_DEBUG.
- test.rc — запуск тех же тестов, что и в fast.rc, но без оптимизации компилятора и с включением таких параметров, как -DSQLITE_DEBUG и -DSQLITE_ENABLE_EXPENSIVE_ASSERT.
- test-ex.rc — длительные soak-тесты.
В репозитории TH3 также есть скрипт «multitest.tcl» — ещё один скрипт TCL для автоматизации тестирования TH3 на рабочих станциях. multitest.tcl автоматически компилирует SQLite, затем многократно запускает ./th3make с различными выравниваниями и выводит результаты в виде краткой сводки. Типичный запуск multitest.tcl выводит данные примерно в таком виде:
./multitest.tcl -q --jobs 3 start-time: 2018-05-19 03:17:12 UTC file mkdir sqlite3bld cd sqlite3bld exec sh /ramdisk/sqlite/configure file copy -force config.h ../config.h exec make clean sqlite3.c file rename sqlite3.c ../sqlite3.c file rename sqlite3.h ../sqlite3.h exec make clean sqlite3.c OPTS=-DSQLITE_ENABLE_UPDATE_DELETE_LIMIT=1 file rename sqlite3.c ../sqlite3udl.c exec make clean sqlite3.c OPTS=-DSQLITE_SMALL_STACK=1 file rename sqlite3.c ../sqlite3ss.c cd .. ******************************************************************************* t01: cov.rc.................................................... Ok (00:03:42) t02: cov.rc ++STAT4 ++DESERIALIZE -D_HAVE_SQLITE_CONFIG_H...... Ok (00:04:45) t03: vfs-cov.rc................................................ Ok (00:03:59) t04: demo.rc................................................... Ok (00:00:05) t07: test.rc ../th3private/*.test.............................. Ok (00:00:21) t08: test.rc ../th3private/*.test ++STAT4...................... Ok (00:01:41) t05: quick.rc.................................................. Ok (00:04:26) t09: quick.rc ~TEST_REALLOC_STRESS -funsigned-char............. Ok (00:05:39) t10: quick.rc ~THREADSAFE=0 -DLONGDOUBLE_TYPE=double........... Ok (00:03:24) t06: quick.rc extensions.rc -D_HAVE_SQLITE_CONFIG_H............ Ok (00:09:03) t11: quick.rc sqlite3ss.c ~MAX_ATTACHED=125.................... Ok (00:04:39) t12: quick.rc ~BYTEORDER=0 ++RTREE............................. Ok (00:07:28) t13: quick.rc ~DISABLE_INTRINSIC ++RTREE....................... Ok (00:07:31) t16: quick.rc ~TRACE_SIZE_LIMIT=15 cov1/main16.test............ Ok (00:00:22) t14: quick.rc ~DIRECT_OVERFLOW_READ -fsigned-char.............. Ok (00:04:35) t15: quick.rc ~UNTESTABLE ~EXTRA_IFNULLROW..................... Ok (00:01:44) t17: quick.rc ~MAX_MMAP_SIZE=0................................. Ok (00:04:46) t18: quick.rc ++NULL_TRIM ++OFFSET_SQL_FUNC.................... Ok (00:04:47) t19: quick.rc ++BATCH_ATOMIC_WRITE ++DESERIALIZE............... Ok (00:05:41) t20: lean1.rc quick.rc......................................... Ok (00:03:09) t22: test.rc alignment2.rc sqlite3udl.c........................ Ok (00:44:22) t21: test.rc alignment1.rc..................................... Ok (01:02:32) t23: memdebug1.rc extensions.rc................................ Ok (01:49:58) t25: valgrind1.rc -O3 extensions.rc............................ Ok (00:56:08) t24: memdebug2.rc extensions.rc................................ Ok (01:43:34) t27: test-ex1.rc............................................... Ok (00:45:00) t26: valgrind2.rc -O3 extensions.rc............................ Ok (01:02:52) t29: test-ex3.rc............................................... Ok (00:31:48) t28: test-ex2.rc............................................... Ok (01:12:03) t30: test-ex4.rc............................................... Ok (01:09:47) t32: test.rc alignment4.rc -m32 CC=clang....................... Ok (00:48:31) t31: test.rc alignment3.rc sqlite3udl.c........................ Ok (01:22:29) t34: test.rc alignment6.rc..................................... Ok (00:35:31) t33: test.rc alignment5.rc extensions.rc....................... Ok (00:59:33) t35: test.rc alignment7.rc..................................... Ok (00:44:10) t40: fast.rc alignment2.rc sqlite3udl.c........................ Ok (00:15:46) t39: fast.rc alignment1.rc extensions.rc -m32.................. Ok (00:33:19) t36: test.rc ~MUTATION_TEST.................................... Ok (00:35:45) t42: fast.rc alignment4.rc..................................... Ok (00:13:03) t43: fast.rc alignment5.rc..................................... Ok (00:13:32) t44: fast.rc alignment6.rc..................................... Ok (00:11:41) t41: fast.rc alignment3.rc sqlite3udl.c........................ Ok (00:26:31) t45: fast.rc alignment7.rc..................................... Ok (00:12:57) t46: fast.rc -fsanitize=undefined.............................. Ok (00:38:18) ******************************************************************************* 0 failures on 44 th3makes and 198583082 tests in (07:16:01) 3 cores on bella SQLite 3.24.0 2018-05-18 17:58:33 c6071ac99cfa4b6272ac4d739fc61a85acb544f6c1c2a
Как видно выше, один запуск multitest.tcl вызывает th3make десятки раз и занимает от 12 до 24 часов процессорного времени. В средней части вывода перечислены аргументы каждого отдельного запуска th3make, а также его результат и время выполнения. Все продукты сборки и вывод отдельных запусков th3make сохраняются в подкаталогах для последующего анализа. Двухстрочная сводка внизу показывает общее число ошибок и тестов во всех запусках th3make, а также общее время выполнения и сведения SQLITE_SOURCE_ID для протестированной версии SQLite. Эта сводная информация заносится в контрольный список выпуска на этапе финального тестирования.
В выводе multitest.tcl используются сокращения, чтобы каждый вызов th3make помещался в одну строку шириной 80 столбцов. Начальная команда «th3make» опускается. «~» — это сокращение для «-DSQLITE_», а «++» означает «-DSQLITE_ENABLE». Поэтому строка вывода multitest.tcl
quick.rc ~DISABLE_INTRINSIC ++RTREE
На самом деле означает
th3make quick.rc -DSQLITE_DISABLE_INTRINSIC -DSQLITE_ENABLE_RTREE
4. Покрытие тестами
При использовании одного конкретного подмножества доступных тестовых модулей TH3 (тестов «cov1») SQLite достигла 100%-ного покрытия ветвей и 100% MC/DC по результатам измерений с помощью gcov на оборудовании под управлением Linux x86 и x86_64. Все выпуски SQLite начиная с версии 3.6.17 (2009-08-10) проходят тестирование в соответствии с этим стандартом. Разработчики SQLite обязуются поддерживать 100%-ное покрытие ветвей и MC/DC во всех будущих выпусках SQLite.
Набор тестов cov1, использовавшийся для достижения 100%-ного покрытия ветвей, составляет лишь часть тестов, реализованных на данный момент с помощью TH3. Новые тестовые модули добавляются регулярно.
5. Мутационное тестирование
В дереве исходного кода TH3 находится скрипт «mutation-test.tcl», автоматизирующий процесс мутационного тестирования.
Скрипт mutation-test.tcl выполняет все необходимые действия для проведения мутационного тестирования:
- При необходимости скрипт компилирует тестовый комплекс TH3 в машинный код («th3.o»).
- При необходимости скрипт компилирует исходный файл sqlite3.c в язык ассемблера («sqlite3.s»).
- Скрипт перебирает инструкции в файле на языке ассемблера в поисках операций ветвления.
- Скрипт создает копию исходного файла sqlite3.s.
- В копии инструкция ветвления заменяется на пустую операцию или безусловный переход.
- Копия sqlite3.s собирается в sqlite3.o, после чего компонуется с th3.o для создания исполняемого файла «th3».
- Исполняемый файл «th3» запускается, а его вывод проверяется на наличие ошибок.
- Скрипт отображает ход выполнения каждого цикла предыдущего шага, а затем в конце выводит сводку по «выжившим». «Выжившая» мутация — это мутация, которую не обнаружил TH3.
Мутационное тестирование может выполняться медленно: каждый тест на быстрой рабочей станции может занять до 5 минут, для каждой инструкции ветвления требуется два теста, а таких инструкций более 20 000. Предпринимаются меры для ускорения работы. Например, TH3 компилируется так, чтобы завершать работу сразу после обнаружения первой ошибки; поскольку многие мутации обнаруживаются легко, выполнение многих циклов занимает всего несколько секунд. Тем не менее скрипт mutation-test.tcl предоставляет параметры командной строки, ограничивающие диапазон проверяемых строк кода, чтобы мутационное тестирование можно было выполнять только для недавно изменённых блоков кода.
6. Лицензия TH3
Сама SQLite находится в общественном достоянии и может использоваться в любых целях. Однако TH3 является проприетарным продуктом и требует лицензии.
Хотя пользователи открытого ПО не имеют прямого доступа к TH3, все пользователи SQLite косвенно получают от него пользу, поскольку перед выпуском каждая версия SQLite проходит проверку с помощью TH3 на нескольких платформах (Linux, Windows, WinRT, Mac, OpenBSD). Поэтому любой, кто использует официальный выпуск SQLite, может развернуть своё приложение, будучи уверенным, что оно прошло тестирование с помощью TH3. Однако повторно выполнить эти тесты самостоятельно можно только после приобретения лицензии TH3.
Последнее обновление этой страницы: 2025-06-12 09:10:23Z
SQLite is in the Public Domain.
https://sqlite.org/th3.html