![]() |
|
|||
WebMoney: WMZ Z294115950220 WMR R409981405661 WME E134003968233 |
Visa 4274 3200 2453 6495 |
Надежность SQLite достигаются частично
полным и тщательным тестированием. С version 3.42.0 (2023-05-16)
библиотека SQLite состоит приблизительно из 155.8 KSLOC кода C.
(KSLOC это тысяча "Source Lines Of Code" или, другими словами, строк кода,
исключая пустые строки и комментарии. Для сравнения у проекта есть в 590 раз
больше тестового кода и сценариев тестирования, 92053 KSLOC. Есть четыре независимых испытательных блока, используемые для тестирования
основной библиотеки SQLite. Каждый испытательный блок безопасности
разрабатывается, сохраняется и управляется отдельно от других.
Тесты TCL это оригинальные тесты для SQLite.
Они содержатся в том же самом исходном дереве, как ядро SQLite и как ядро
SQLite находится в общественном достоянии. Тесты TCL это
основные тесты, используемые во время развития. Тесты TCL написаны, используя
TCL scripting language.
Сам испытательный блок TCL состоит из 27.2 KSLOC кода C, используемого, чтобы
создать интерфейс TCL. Сценарии тестирования содержатся в 1390 файлах всего
23.2 МБ в размере. Есть 51445 отличных тестовых сценариев, но многие тестовые
сценарии параметризуются и исполняются
многократно (с различными параметрами) так, чтобы на полном тестовом прогоне
миллионы отдельных тестов были выполнены. TH3 блок тестов
это ряд собственных тестов, написанных на C,
которые обеспечивают 100% тестовое покрытие отделения (и
100% тестовое покрытие MC/DC)
основной библиотеки SQLite. Тесты TH3 разработаны, чтобы работать
на вложенных и специализированных платформах, которые легко не поддержали бы
TCL или другие службы рабочей станции. Тесты TH3 используют только изданные
интерфейсы SQLite. TH3 состоит приблизительно из 76.9 МБ или 1055.4 KSLOC
кода C, осуществляющего 50362 отличных тестовых сценария. Тесты TH3 в большой
степени параметризуются, тем не менее, так полный охват тестовые прогоны
приблизительно 2.4 миллиона различных испытательных случаев.
Случаи, которые обеспечивают 100% тестовое покрытие отделения, составляют
подмножество полного набора тестов TH3. Тест до выпуска делает приблизительно
248.5 миллионов тестов. Дополнительная информация о TH3
есть отдельно.
SQL Logic Test
или SLT используется, чтобы управлять огромными числами SQL-операторов для
SQLite и нескольких других движков базы данных SQL и проверить, что они все
получают те же самые ответы. SLT в настоящее время сравнивает SQLite с
PostgreSQL, MySQL, Microsoft SQL Server и Oracle 10g.
SLT управляет 7.2 миллионами запросов, включающих 1.12 ГБ данных
тестирования. Движок dbsqlfuzz это
собственный fuzz тестер. Другие
fuzzer для SQLite видоизменяют входы SQL или файл базы данных.
Dbsqlfuzz видоизменяет SQL и файл базы данных в то же время, и таким образом
в состоянии достигнуть новых состояний ошибки. Dbsqlfuzz строится, используя
libFuzzer
framework LLVM с собственным мутатором. Есть 336 файлов семени.
dbsqlfuzz fuzzer управляет приблизительно одним миллиардом испытательных
мутаций в день. Dbsqlfuzz помогает гарантировать, что SQLite прочен против
нападения через злонамеренный SQL или входные базы данных. В дополнение к четырем главным испытательным блокам
безопасности есть несколько других небольших программ. Все тесты выше должны работать успешно на многих платформах и под многими
конфигурациями времени компиляции перед каждым выпуском SQLite. До каждой регистрации к исходному дереву SQLite разработчики, как правило,
управляют подмножеством (названным "veryquick") тестов Tcl, состоящим
приблизительно из 304.7 тысяч тестовых сценариев. Тесты veryquick включают
большинство тестов кроме аномалии, fuzz и soak.
Идея позади тестов veryquick состоит в том, что они достаточны, чтобы
зафиксировать большинство ошибок, но также работают всего
несколько минут вместо нескольких часов. Тестирование аномалии это тесты, разработанные, чтобы проверить правильное
поведение SQLite, когда что-то идет не так, как надо.
(Относительно) легко построить движок базы данных SQL, который ведет себя
правильно на правильно построенных входах на
полностью функциональном компьютере. Более трудно построить систему, которая
нормально отвечает на
недействительные входы и продолжает функционировать после системных сбоев.
Тесты аномалии разработаны, чтобы проверить последнее поведение. SQLite, как все движки SQL, делает широкое применение malloc() (см.
отдельный отчет о динамическом выделении памяти в
SQLite для дополнительных подробностей).
На серверах и рабочих станциях malloc()
никогда не терпит неудачу на практике и таким образом, правильная обработка
ошибок out-of-memory (OOM) не особенно важна. Но на встроенных устройствах,
ошибки OOM пугающе распространены и так как SQLite часто используется на
встроенных устройствах, важно, чтобы SQLite был
в состоянии изящно обработать ошибки OOM. Тестирование OOM достигается, моделируя ошибки OOM.
SQLite позволяет запросу заменить альтернативой внедрение malloc(),
используя sqlite3_config(
SQLITE_CONFIG_MALLOC,...). Тесты TCL и TH3
способны к вставке измененной версии malloc(),
которая может быть подстроена, чтобы потерпеть неудачу после определенного
числа отчислений. Эти инструментованные malloc может собираться, чтобы
потерпеть неудачу только однажды, а затем начать работать снова,
или продолжать терпеть неудачу после первой неудачи. Тесты OOM сделаны в
цикле. На первом повторении инструментованный malloc подстроен, чтобы
потерпеть неудачу на первом распределении. Тогда некоторая операция SQLite
выполняется, и проверки сделаны, чтобы удостовериться, что SQLite обработал
ошибку OOM правильно. Тогда счетчик time-to-failure
на инструментованном malloc увеличен на единицу, и тест повторяется.
Цикл продолжается до конца всех операционных проходов, никогда не сталкиваясь
с моделируемой неудачей OOM. Тесты запущены дважды, однажды с
инструментованным набором malloc, чтобы потерпеть неудачу только однажды, и
снова с инструментованным набором malloc, чтобы терпеть неудачу непрерывно
после первой неудачи. Тестирование I/O error стремится проверить, что SQLite нормально отвечает
на сбои операции I/O. Ошибки I/O могли бы следовать из полного диска,
работающих со сбоями дисковых аппаратных средств, сетевых отключений
электричества, используя сетевую файловую систему, конфигурацию системы или
изменения разрешения, которые происходят посреди операции SQL, или других
сбоев аппаратной или операционной системы. Безотносительно причины важно,
чтобы SQLite были в состоянии правильно ответить на эти ошибки, и
тестирование I/O стремится проверить, что это делается. Тестирование I/O error подобно в понятии тестированию OOM,
ошибки I/O моделируются, и проверки осуществлены, чтобы проверить, что SQLite
правильно отвечает на моделируемые ошибки. Ошибки I/O моделируются в TCL и в
испытательных блоках TH3, вставляя новый объект
Virtual File System,
который особенно настроен, чтобы моделировать ошибку I/O после некоего
количества набора операций I/O. Как с ошибкой OOM при тестировании, ошибочные
симуляторы I/O могут собираться потерпеть неудачу только однажды или терпеть
неудачу непрерывно после первой неудачи.
Тесты запущены в цикле, медленно увеличивая пункт неудачи до выполнения
тестового сценария до завершения без ошибки.
Циклом управляют дважды, однажды с ошибочным набором симулятора I/O, чтобы
моделировать только единственную неудачу и во второй раз, чтобы нарушить
все операции I/O после первой неудачи. В тестах I/O error после того, как отключен механизм моделирования неудачи
I/O, база данных исследована, используя
PRAGMA integrity_check,
чтобы удостовериться, что ошибка I/O не ввела повреждение базы данных. Тестирование на сбои стремится продемонстрировать, что база данных SQLite
не повреждена, если прикладная или операционная система потерпит крах или
если есть перебой в питании посреди обновления базы данных.
Отдельный отчет, названный
атомная передача в SQLite
описывает защитную меру, которую SQLite принимает, чтобы предотвратить
повреждение базы данных после катастрофы. Краш-тесты стремятся проверить, что
те защитные меры работают правильно. Альтернативная Virtual File System
вставляется, которая позволяет испытательному блоку
моделировать состояние файла базы данных после катастрофы. В испытательном блоке TCL моделирование катастрофы сделано в отдельном
процессе. Главный процесс тестирования порождает дочерний процесс, который
управляет некоторой операцией SQLite и беспорядочно терпит крах где-нибудь
посреди операции записи. Специальный VFS
беспорядочно переупорядочивает и портит несинхронизированные операции записи,
чтобы моделировать эффект буферизированных файловых систем. После того, как
дочерний процесс вылетает, оригинальный испытательный процесс открывает и
читает испытательную базу данных и проверяет, что изменения
закончены успешно или были полностью отменены.
integrity_check
PRAGMA
используется, чтобы удостовериться, что никакое повреждение
базы данных не происходит. Блок тестов TH3 должен работать
во встроенных системах, у которых не обязательно есть способность породить
дочерние процессы, таким образом, это использует
an in-memory VFS
в памяти, чтобы моделировать катастрофы.
VFS в памяти может быть подстроен, чтобы сделать
снимок всей файловой системы после заданного количества операций I/O.
Краш-тестами управляют в цикле. На каждом повторении
пункт, в котором сделан снимок, продвинут до проверяемых операций SQLite.
В цикле, после того, как операция SQLite при тесте закончилась, файловая
система вернулась к снимку, вводится случайное повреждение файла, которое
характерно для видов повреждения, которое ожидают видеть
после потери питания. Тогда база данных открыта, и проверки осуществлены,
чтобы гарантировать, что это правильно построено и что транзакция дошла до
завершения или была полностью отменена. Интерьер цикла повторяется
многократно для каждого снимка с различным случайным
повреждением каждый раз. Наборы тестов для SQLite также исследуют результат укладки многократных
отказов. Например, тесты запущены, чтобы гарантировать правильное поведение,
когда ошибка I/O или ошибка OOM происходят, пытаясь прийти в себя
после предшествующей катастрофы.
Тестирование Fuzz
стремится установить, что SQLite правильно отвечает неправильным,
out-of-range или поврежденным вводам. SQL fuzz состоит из создания синтаксически корректных, но дико
бессмысленных SQL-операторов и передачи к SQLite, чтобы видеть то, что это
сделает с ними. Обычно некоторая ошибка возвращена (такая, как "no such
table"). Иногда, просто случайно, SQL-оператор оказывается семантически
правилен. В этом случае получающимся подготовленным запросом
управляют, чтобы удостовериться, что он дает разумный результат. Понятие тестирования fuzz было использовано
в течение многих десятилетий, но тестирование
не было эффективным способом найти ошибки до 2014, когда Michal Zalewski
изобрел первый практический управляемый профилем fuzzer,
American Fuzzy Lop или "AFL".
В отличие от предшествующих fuzzer, которые вслепую производят случайные
входы, AFL инструментует проверяемую программу (изменяя вывод
ассемблера из компилятора C) и использует ту инструментовку, чтобы
обнаружить, когда вход заставляет программу делать что-то другое,
чтобы следовать за новым путем контроля или закрепить цикл
различное число раз. Входы, которые вызывают новое поведение, сохранены и
далее видоизменены. Таким образом AFL в состоянии "обнаружить"
новые поведения программы при тесте, включая поведения, которые никогда
не предполагались проектировщиками. AFL оказался владеющим мастерством нахождения тайных ошибок в SQLite.
Большинство результатов было в assert(), где условное предложение было ложным
при неясных обстоятельствах. Но AFL также нашел справедливое число ошибок
в SQLite, и даже несколько случаев, где SQLite
вычислил неправильные результаты. Из-за его прошлого успеха AFL стал стандартным компонентом стратегии
тестирования SQLite с
version 3.8.10 (2015-05-07), пока это не было заменено лучшим fuzzer в
version 3.29.0 (2019-07-10).
С 2016 команда инженеров в Google начала проект
OSS Fuzz.
OSS Fuzz применяет fuzzer стиля AFL на инфраструктуре Google.
Fuzzer автоматически загружает последние регистрации для участвующих
проектов, тестирует их и посылает электронное письмо разработчикам,
сообщая о любых проблемах. Когда внесены правки,
fuzzer автоматически обнаруживает это и посылает подтверждение по
электронной почте разработчикам. SQLite это один из многих проектов с открытым исходным кодом, которые
проверяет OSS Fuzz.
test/ossfuzz.c
в хранилище SQLite это интерфейс SQLITE к OSS fuzz. OSS Fuzz больше не находит исторические ошибки в SQLite. Но это все еще
действительно иногда находит проблемы в регистрациях
новой разработки. Примеры:
[1]
[2]
[3].
Начавшись в конце 2018, SQLite был тестирован с
использованием собственного fuzzer, названного "dbsqlfuzz".
Dbsqlfuzz строится, используя
libFuzzer
framework of LLVM. dbsqlfuzz fuzzer видоизменяет вход SQL и файл базы данных в то же время.
Dbsqlfuzz использует свой
Structure-Aware Mutator на специализированном входном файле, который
определяет входную базу данных и код на SQL, которым будут управлять для той
базы данных. Поскольку это видоизменяет входную базу данных и вход SQL в то
же время, dbsqlfuzz в состоянии найти некоторые неясные ошибки в SQLite,
которые были пропущены предшествующим fuzzer, который видоизменил только
входы SQL или только файл базы данных.
Разработчики SQLite держат dbsqlfuzz, работающий для
ствола приблизительно на 16 ядрах в любом случае.
Каждый экземпляр программы dbsqlfuzz в состоянии оценить
приблизительно 400 тестовых сценариев в секунду, означая, так что
приблизительно 500 миллионов случаев проверяются каждый день. dbsqlfuzz fuzzer был очень успешен при укреплении кодовой базы SQLite
против вредоносной атаки. Так как dbsqlfuzz был добавлен к внутреннему набору
тестов SQLite, отчеты об ошибках от внешних fuzzer, таких как
OSSFuzz, почти остановились. Обратите внимание на то, что dbsqlfuzz не
основанный на Protobuf осведомленный о структуре fuzzer для SQLite,
который используется Хромом и описывается в
Structure-Aware Mutator article.
Нет никакой связи между этими двумя fuzzer кроме того, что они на основе
libFuzzer.
The Protobuf fuzzer для SQLite пишется и поддерживается авторами Chromium
в Google, а вот dbsqlfuzz пишется и поддерживается авторами SQLite.
Наличие многих независимо развитых fuzzer для SQLite хорошо, поскольку это
означает, что неясные проблемы, более вероятно, будут раскрыты.
SQLite, кажется, популярная цель третьих лиц для тестирования.
Разработчики слышат о многих попытках тестирования SQLite и они
действительно иногда находили отчеты об ошибках, найденных независимыми
fuzzer. Все такие отчеты быстро фиксируются, таким образом, продукт улучшен,
и все пользовательское сообщество SQLite извлекает выгоду.
Этот механизм наличия многих независимых тестеров подобен
Linus's law:
"given enough eyeballs, all bugs are shallow". Один особо значимый исследователь это
Manuel Rigger
в настоящее время (поскольку этот параграф написан 2019-12-21) в
ETH Zurich.
Большинство fuzzer ищет только ошибки утверждения, катастрофы,
неопределенное поведение (UB) или другие легко обнаруженные аномалии.
fuzzer доктора Риггера, с другой стороны, в состоянии найти случаи, где
SQLite вычисляет неправильный ответ. Риггер нашел
много таких случаев. Большинство этих находок это
неясные случаи, включающие преобразования типов и преобразования близости, и
большое количество находок против невыпущенных особенностей.
Тем не менее, его находки все еще важны, поскольку они реальные ошибки,
и разработчики SQLite благодарны, что в состоянии определить и решить
основные проблемы. Работа Риггера в настоящее время не опубликована.
Когда это выпущено, это могло влиять как изобретение Zalewski's AFL
и управляемого профилем fuzzing.
Исторические тестовые сценарии от AFL,
OSS Fuzz и
dbsqlfuzz
собраны в ряде файлов базы данных в главном исходном дереве SQLite и затем
запущены повторно программой "fuzzcheck" каждый раз, когда
выполняется "make test". Fuzzcheck управляет всего несколькими тысячами
"интересных" случаев из миллиардов случаев, которые различные
fuzzer исследовали за эти годы. "Интересные" случаи это такие
случаи, которые показывают ранее невидимое поведение.
Фактические ошибки, найденные fuzzer, всегда включаются среди интересных
тестовых сценариев, но большинство случаев, которыми управляет fuzzcheck,
никогда не было фактическими ошибками.
Тестирование Fuzz и 100% MC/DC
в противоречии дург с другом. То есть код, проверенный 100% MC/DC,
будет иметь тенденцию быть более уязвимым для проблем, найденных fuzzing,
и код, который выступает хорошо во время тестирования fuzz,
будет иметь тенденцию иметь (намного) меньше, чем 100% MC/DC.
Это вызвано тем, что тестирование MC/DC препятствует
защитному коду
с недостижимыми отделениями, но без защитного кода fuzzer
более вероятно, найдет путь, который вызывает проблемы.
Тестирование MC/DC, кажется, работает хорошо на нормы сборки
и правила, которые прочны во время нормальной эксплуатации, тогда как
тестирование fuzz хорошо для норм и правил, которые прочны
против вредоносной атаки. Конечно, пользователи предпочли бы код, который является
прочным в нормальной эксплуатации и стойким к вредоносной атаке.
Цель этой секции состоит в том, чтобы просто указать, что выполнение обоих
требований в то же время трудно. Для большой части его истории SQLite был сосредоточен на 100% MC/DC.
Сопротивление fuzzing стал беспокойством только с введением AFL в 2014.
Некоторое время fuzzer находили много проблем в SQLite. В более свежих годах
стратегия тестирования SQLite развилась, чтобы уделить больше внимания
тестированию fuzz. Мы все еще поддерживаем 100% MC/DC основного кода, но
большая часть тестирования циклов CPU теперь посвящена fuzzing. В то время как тестирование fuzz и 100% MC/DC
находятся в напряженности, они не полностью противоположны.
То, что набор тестов SQlite действительно проверяет к 100% MC/DC
означает, что, когда fuzzer
действительно находят проблемы, те проблемы могут быть решены быстро
и с небольшим риском новых ошибок. Есть многочисленные тестовые сценарии, которые проверяют, что SQLite в
состоянии иметь дело с уродливыми файлами базы данных. Эти тесты сначала
строят правильно построенный файл базы данных, затем добавляют повреждение,
изменяя один или несколько байтов в файле некоторыми средствами кроме SQLite.
Тогда SQLite используется, чтобы прочитать базу данных. В некоторых случаях
изменения байтов вносятся посреди данных.
Это заставляет содержание базы данных изменяться, сохраняя базу данных
правильно построенной. В других случаях изменяются неиспользованные байты
файла, которые не имеют никакого эффекта на целостность базы данных.
Интересные случаи это когда байты файла, которые определяют структуру базы
данных, изменяются. Уродливые тесты базы данных проверяют, что SQLite находит
ошибки формата файла и сообщает о них, используя код возврата
SQLITE_CORRUPT, не переполняя буфера
или выполняя другие вредные действия. dbsqlfuzz fuzzer
также делает превосходную работу по подтверждению, что SQLite нормально
отвечает на уродливые файлы базы данных. SQLite определяет лимиты
на свое действие, такие как максимальное количество колонок в таблице,
максимальную длину SQL-оператора или максимальное значение целого числа.
TCL и наборы тестов TH3 содержат многочисленные тесты, которые выдвигают
SQLite на край его определенных пределов и проверяют, что это работает
правильно для всех позволенных значений.
Дополнительные тесты идут вне определенных пределов и проверяют, что
SQLite правильно возвращает ошибки. Исходный код содержит
testcase macros,
чтобы проверить, что были проверены обе стороны каждой границы. Каждый раз, когда об ошибке сообщают, ту ошибку не считают устраненной
до введения новых тестовых сценариев, которые показали бы ошибку, и были
добавлены к TCL или к наборам тестов TH3. За эти годы это привело к тысячам и
тысячам новых тестов. Эти регрессионные тесты гарантируют, что ошибки,
которые были исправлены в прошлом, не представлены повторно в
будущих версиях SQLite. Утечка ресурсов происходит, когда системные ресурсы ассигнуются и никогда
не освобождаются. Самые неприятные утечки ресурсов во многих случаях это
утечки памяти, когда память ассигнуется, используя malloc(),
но никогда не освобождается через free().
Но другие виды ресурсов могут также быть пропущены: дескрипторы файлов,
потоки, mutexes и т. д. TCL и TH3 проверки автоматически контролируют ресурсы системы
транспортировки и утечки ресурсов на каждом тесте.
Никакая специальная конфигурация или установка не требуются.
Испытательные блоки особенно бдительны относительно утечек памяти.
Если изменение вызовет утечку памяти, испытательные блоки
признают это быстро. SQLite разработан, чтобы никогда не упустить память,
даже после исключения, такого как ошибка OOM или ошибка дискового I/O.
Испытательные блоки внимательны, чтобы провести в жизнь это. У ядра SQLite, включая Unix VFS,
есть 100% тестовое покрытие отделения под TH3
в его конфигурации по умолчанию, как измерено
gcov.
Расширения, такие как FTS3 и RTree, исключены из этого анализа. Есть много способов измерить тестовое покрытие. Самая популярная метрика
это "освещение заявления". Когда вы слышите, что кто-то говорит,
что их программа как "тестовое покрытие на "XX% test coverage"
без дальнейшего объяснения, они обычно имеют в виду освещение заявления.
Освещение заявления измеряет, какой процент строк кода
выполняется, по крайней мере, однажды набором тестов. Освещение отделения более строго, чем освещение заявления.
Освещение отделения измеряет количество команд перехода машинного кода,
которые оценены, по крайней мере, однажды на обоих направлениях. Чтобы иллюстрировать различие между освещением заявления и освещением
отделения, рассмотрите следующую гипотетическую строку кода C: Такая строка кода C могла бы произвести дюжину отдельных команд машинного
кода. Если кто-либо из тех инструкций когда-либо оценивается, то мы говорим,
что заявление было проверено. Так, например, могло бы иметь место, что
условное выражение всегда ложное, и переменная "d"
никогда не увеличивается. Несмотря на это, освещение заявления считает
эту строку проверенной. Освещение отделения более строго. С освещением отделения, каждый тест и
каждый подблок в рамках заявления рассматривается отдельно.
Чтобы достигнуть 100% освещения отделения в примере выше, должно быть по
крайней мере три тестовых сценария: Любой из вышеупомянутых тестовых сценариев предоставил бы
100% страховую защиту заявления, но все три требуются для 100% освещения
отделения. Вообще говоря, 100% освещение отделения подразумевает 100%
освещение заявления, но обратное неверно. Чтобы повторно подчеркнуть,
испытательный блок TH3
для SQLite обеспечивает более сильную форму тестового покрытия: 100%
тестовое покрытие отделения. Хорошо написанная программа на C будет, как правило, содержать некоторые
защитные условные предложения, которые на практике являются всегда верными
или всегда ложными. Это приводит к программной дилемме: надо ли удалять
защитный код, чтобы получить 100% освещение отделения? В SQLite ответ на предыдущий вопрос "нет".
Для целей тестирования исходный код SQLite определяет макросы под названиями
ALWAYS() и NEVER(). Макрос ALWAYS() окружает условия, которые, как ожидают,
всегда оценят как верные, и NEVER()
окружает условия, которые всегда оцениваются как ложные.
Макрос служит комментарием, чтобы указать, что условия это защитный код.
В сборках конечных версий макросы переданы: Во время большей части тестирования, однако, макрос
бросит ошибку утверждения, если у их аргумента не будет ожидаемого значения
истинности. Это предупреждает разработчиков быстро о
неправильных предположениях дизайна. Измеряя тестовое покрытие, макрос определяется, чтобы быть постоянным
значением истинности так, чтобы он не производил команды перехода ассемблера,
а следовательно не играл роли, вычисляя освещение отделения: Набор тестов разработан, чтобы управляться три раза, однажды для каждого
определения ALWAYS() и NEVER(), показанных выше. Все три тестовых прогона
должны привести точно к тому же самому результату. Есть тест во время
выполнения, используя
sqlite3_test_control(
SQLITE_TESTCTRL_ALWAYS, ...), который может использоваться, чтобы
проверить, что макрос правильно установлен в первую форму (форма
передачи) для развертывания. Другой макрос, используемый вместе с измерением тестового покрытия,
является макросом testcase().
Аргумент это условие, для которого мы хотим тестовые сценарии, которые
оцениваются к true и false. В сборках конечных версий макрос
testcase() ничего не делает: Но в измерении освещения макрос testcase() производит код,
который оценивает условное выражение в его аргументе. Тогда во время
анализа, проверка осуществлена, чтобы гарантировать, чтобы тесты
существовали, которые оценивают условное предложение к true и false.
Testcase() используется, например, чтобы помочь проверить, что
граничные значения проверены. Например: Макрос Testcase также используется, когда два или больше случая оператора
переключения идут в тот же самый блок кода, чтобы удостовериться, что код
был достигнут всеми случаями: Для тестов битовой маски макрос testcase() используется, чтобы
проверить, что каждая часть битовой маски затрагивает результат.
Например, в следующем блоке кода, условие верно, если маска содержит любой из
двух битов, указывающих на MAIN_DB или на TEMP_DB. Макрос testcase()
проверяет, что проверены оба случая: Исходный код SQLite содержит 1184 использования макроса
testcase(). Два метода измерения тестового покрытия были описаны выше:
"заявление" и освещение "отделения". Помимо этих двух
есть много других метрик тестового покрытия. Другая популярная метрика это
"Измененное освещение условия/решения" или MC/DC.
Wikipedia определяет MC/DC так: В языке C, где && и ||
это операторы "короткого замыкания", MC/DC и освещение отделения
это почти то же самое. Главная разница находится в булевых векторных тестах.
Можно проверить на любые из нескольких битов в битовом векторе
и все еще получить 100% тестовое покрытие отделения даже при том, что второй
элемент MC/DC (требование, чтобы каждое условие в решении взяло каждый
возможный исход) не мог бы быть удовлетворен. SQLite использует макрос testcase(), как описано в предыдущем
подразделе, чтобы удостовериться, что каждое условие в решении битового
вектора берет каждый возможный исход. Таким образом SQLite также достигает
100% MC/DC в дополнение к 100% освещению отделения. Освещение отделения в SQLite в настоящее время измеряется, используя
gcov
с опцией "-b". Сначала тестовая программа собрана, используя варианты
"-g -fprofile-arcs -ftest-coverage", затем тестовой программой управляют.
Далее "gcov -b" управляют, чтобы произвести отчет об освещении.
Отчет об освещении многословен и неудобен, чтобы читать, таким образом,
этот отчет обрабатывается, используя некоторые простые сценарии, чтобы
привести его в более человечески-благоприятный формат.
Этот процесс автоматизирован, используя скрипты, конечно. Обратите внимание на то, что управление SQLite с gcov не является тестом
SQLite, это тест набора тестов. gcov, которым управляют, не проверяет SQLite,
потому что опции -fprofile-args и -ftest-coverage
заставляют компилятор производить различный код. gcov, которым просто
управляют, проверяет, что набор тестов обеспечивает 100% тестовое покрытие
отделения. gcov, которым управляют, является тестом на тест, метатестом. После того, как gcov управляли, чтобы проверить 100%
тестовое покрытие отделения, тогда тестовая программа повторно собрана,
используя параметры компилятора без опций -fprofile-arcs и -ftest-coverage,
и тестовая программа запущена повторно. Этот второй пробег и есть
фактический тест SQLite. Важно проверить, что gcov тестовый прогон и второй реальный тестовый
прогон оба дают тот же вывод. Любые различия в нем
указывают на использование неопределенного поведения в коде
SQLite (и следовательно ошибку) или на ошибку в компиляторе.
Обратите внимание на то, что SQLite за предыдущее десятилетие столкнулся с
ошибками в GCC, Clang и MSVC. Ошибки компилятора, хоть и редки, но
действительно происходят, поэтому столь же важно проверить
код в поставляемой конфигурации.
Использование gcov (или подобного), чтобы показать то, что каждая команда
перехода взята, по крайней мере, однажды в обоих направлениях, является
хорошей мерой качества набора тестов.
Но еще лучше показывает, что каждая команда перехода имеет значение в выводе.
Другими словами, мы хотим показать не только, что каждая команда перехода
проваливаются, оба скачка и, но также и что каждое отделение делает полезную
работу и что набор тестов в состоянии обнаружить и проверить ту работу.
Когда найдено отделение, которое не имеет значения в выводе,
это предполагает, что код, связанный с отделением, может быть удален
(сокращение размера библиотеки и возможно ускорение работы) или что набор
тестов неверно проверяет опцию, которую реализует отделение. SQLite стремится проверить, что каждая команда перехода имеет значение,
используя
тестирование мутации. Скрипт
сначала собирает исходный код SQLite на ассемблере (используя, например,
опцию -S для gcc). Затем скрипт пошагово проходит
ассемблерный код, меняя каждую команду перехода на
безусловный переход или на no-op,
собирает результат и проверяют, что набор тестов ловит мутацию. К сожалению, SQLite содержит много команд перехода, которые помогают коду,
работать быстрее, не изменяя вывод. Такие отделения производят ложные
положительные сигналы во время тестирования мутации. Как пример, допустим,
что следующая
хэш-функция, которая ускоряет поиск имени таблицы: Если команда перехода, которая осуществляет "c!=0" в строке 58
изменяется в no-op, тогда цикл с условием продолжения образует вечный цикл,
и набор тестов потерпит неудачу с тайм-аутом.
Но если то отделение будет изменено на безусловный переход, то хэш-функция
будет всегда возвращать 0. Проблема состоит в том, что 0 это действительный
хэш. Хэш-функция, которая всегда возвращает 0, все еще работает в том смысле,
что SQLite все еще всегда получает правильный ответ.
Хэш-таблица имени таблицы ухудшается в связанный список и поиски имени
таблицы во время парсинга SQL-операторов могли бы быть немного медленнее, но
конечным результатом будет то же самое. Для обхода проблемы комментарии вида
" Разработчики SQLite нашли, что тестирование полного охвата это
чрезвычайно эффективный метод для выявления и предотвращения ошибок.
Поскольку каждая команда перехода в основном коде
SQLite покрыта тестовыми сценариями, разработчики могут быть уверены,
что у изменений, внесенных в одной части кода, нет непреднамеренных
последствий в других частях. Много новых особенностей и повышений
производительности, которые были добавлены к SQLite в последние годы,
невозможны без доступности тестирования полного охвата. p>Поддержание 100% MC/DC трудоемкое и отнимающее много времени.
Уровень усилий показывает, что тестирование полного охвата, вероятно, не
экономически эффективно для типового приложения. Однако мы думаем, что
тестирование полного охвата оправдано для
очень широко развернутой библиотеки
инфраструктуры как SQLite, и специально для библиотеки базы данных,
которая по самому ее характеру "помнит" прошлые ошибки.
Динамический анализ относится к внутренним и внешним проверкам кода
SQLite, которые выполнены, в то время как код действует.
Динамический анализ, оказалось, был большой помощью в
поддержании качества SQLite. Ядро SQLite включает 6754 assert(),
которые проверяют предварительные условия функции, инварианты циклов
и выходные условия. Assert() это макрос, который является стандартным
компонентом ANSI C. Аргумент это булево значение, которое, как
предполагается, всегда верно. Если утверждение ложное, программа печатает
сообщение об ошибке. Макрос Assert() отключен, собрав с определенным макросом NDEBUG.
В большинстве систем assert позволен по умолчанию. Но в SQLite утверждение
столь многочислено и находится в таких критически важных местах, что ядро
базы данных работает приблизительно в три раза медленнее, когда assert
позволены. Следовательно, умолчание собирает SQLite с выключенным assert.
Операторы контроля позволены только, когда SQLite собран с определенным
макросом препроцессора SQLITE_DEBUG. Valgrind это
возможно, самый удивительный и полезный инструмент разработчика в мире.
Valgrind это симулятор, он моделирует x86, управляющий x86 Linux binary.
Порты Valgrind для платформ кроме Linux находятся в развитии, но сейчас
Valgrind работает достоверно только в Linux, что по мнению разработчиков
SQLite означает, что Linux должен быть предпочтительной платформой для всей
разработки программного обеспечения. Поскольку Valgrind управляет Linux
binary, он ищет все виды интересных ошибок, такие как перерасходы множества,
чтение от неинициализированной памяти, переполнения стека, утечек памяти и
т. д. Valgrind находит проблемы, которые могут легко уменьшиться посредством
всего лишь тестового прогона против SQLite. И, когда Valgrind действительно
находит ошибку, он может перевести разработчика непосредственно в
символический отладчик в точном месте, где ошибка происходит,
чтобы облегчить быстрое исправление. Поскольку это симулятор, управлять binary в Valgrind
медленнее, чем управление им на собственном оборудовании.
Таким образом, невозможно управлять полным набором тестов SQLite через
Valgrind. Однако, тесты veryquick и освещение тестов TH3 выполняются Valgrind
для каждого выпуска. SQLite содержит подключаемую
подсистему выделения памяти.
Реализация по умолчанию использует системные malloc() и free().
Однако, если SQLite собран с
SQLITE_MEMDEBUG,
альтернативная обертка выделения памяти
(memsys2) вставляется, которая
ищет ошибки распределения памяти во время выполнения.
Обертка memsys2 проверяет на утечки памяти, конечно, но также ищет
переполнение буфера, использование неинициализированной памяти и пытается
использовать память после того, как это было освобождено. Эти же самые
проверки также сделаны valgrind (и, действительно, Valgrind делает их лучше),
но memsys2 имеет преимущество того, что он намного быстрее, чем Valgrind, что
означает, что проверки могут быть сделаны чаще и для
более длительных тестов. SQLite содержит подключаемую mutex-подсистему. В зависимости от вариантов
времени компиляции система mutex содержит интерфейсы
sqlite3_mutex_held() и
sqlite3_mutex_notheld(),
которые обнаруживают, проводится ли конкретный mutex вызывающим потоком.
Эти два интерфейса используются экстенсивно в assert() в SQLite,
чтобы проверить, что mutex проведены и выпущены во все правильные моменты,
чтобы перепроверить, что SQLite действительно работает
правильно в многопоточных приложениях. Одна из вещей, которые SQLite делает, чтобы гарантировать, что
транзакции атомарны через системные катастрофы и перебои в питании, состоит в
том, чтобы написать все изменения в файл журнала отмены
до изменения базы данных. Испытательный блок TCL содержит альтернативное
внедрение OS backend, который помогает проверить, что
это происходит правильно. "journal-test VFS" контролирует весь дисковый I/O
между файлом базы данных и журналом отмены
проверяя, чтобы удостовериться, что ничто не написано в файл базы данных,
что сначала не писалось и синхронизировалось к журналу отмены.
Если какие-либо несоответствия найдены, ошибка утверждения поднята. Тесты журнала это дополнительная перепроверка свыше краш-тестов,
чтобы удостовериться, что транзакции SQLite
будут атомными через системные катастрофы и перебои в питании. В языке C очень легко написать код, у которого есть
"неопределенное" поведение. Это означает, что код
мог бы работать во время развития, но дать различный ответ на различной
системе или когда повторно собран, используя различные параметры компилятора.
Примеры неопределенного и определенного внедрением
поведения в ANSI C включают: Так как неопределенное и определенное внедрением поведение непортативное и
может легко привести к неправильным ответам, SQLite очень упорно работает,
чтобы избежать его. Например, добавляя два значения столбцов целого числа
вместе как часть SQL-оператора, SQLite просто не добавляет их вместе с
использованиес оператора "+" языка C. Вместо этого это сначала
проверяет, чтобы удостовериться, что дополнение не переполнится, и если это
так, делает дополнение, используя плавающую точку вместо этого. Чтобы помочь гарантировать, что SQLite не использует неопределенное
поведение, наборы тестов запущены повторно,
используя инструментованную сборку, которая пытается
обнаружить неопределенное поведение. Например, наборами тестов управляют,
используя опцию "-ftrapv" в GCC. И ими управляют снова, используя опцию
"-fsanitize=undefined" Clang. А потом еще раз с опцией "/RTC1" MSVC.
Тогда наборы тестов запущены повторно, используя такие варианты, как
"-funsigned-char" и "-fsigned-char", чтобы удостовериться, что различия во
внедрении не имеют значения также. Тесты повторяются на 32-битных и 64-битных
системах и на системах с прямым и с обратным порядком байтов, используя
множество архитектур ЦП. Кроме того, наборы тестов увеличены со многими
тестовыми сценариями, которые сознательно разработаны, чтобы вызвать
неопределенное поведение. Например:
"SELECT -1*(-9223372036854775808);".
sqlite3_test_control(
SQLITE_TESTCTRL_OPTIMIZATIONS,
...) позволяет отобранной оптимизации SQL-оператора быть отключенной во
время выполнения. SQLite должен всегда производить точно тот же самый ответ с
позволенной и с отключенной оптимизацией, ответ просто будет более быстрый с
включенной оптимизацией. Таким образом в производственной среде, каждый
всегда оставляет оптимизацию включенной (настройка по умолчанию). Один метод проверки, используемый
SQLite, должен управлять всем набором тестов дважды, однажды с оптимизацией,
а во второй раз с выключенной оптимизацией, и проверить, что вывод получен
оба раза одинаковый. Это показывает, что оптимизация не вводит ошибки. Не все тестовые сценарии могут быть обработаны так.
Некоторые тестовые сценарии проверяют,
что оптимизация действительно уменьшает объем вычисления, считая количество
доступов к диску, операций по сортировке, шагов полного просмотра или других
шагов обработки, которые происходят во время запросов.
Те тестовые сценарии, будет казаться, потерпят неудачу, когда оптимизация
будет отключена. Но большинство тестовых сценариев просто проверяет, что
правильный ответ был получен, всеми теми случаями можно управлять успешно и
без оптимизации, чтобы показать, что оптимизация не вызывает сбои. Разработчики SQLite используют контрольный список онлайн, чтобы
скоординировать деятельность тестирования и проверить, что каждый выпуск
SQLite проходит все тесты.
Прошлые контрольные списки сохраняются для исторической справки.
Контрольные списки только для чтения для анонимных интернет-пользователей,
но разработчики могут авторизоваться и обновить пункты контрольного списка в
своих веб-браузерах. Использование контрольных списков для тестирования
SQLite и других опытно-конструкторских разработок вызвано
The Checklist Manifesto. Последние контрольные списки содержат приблизительно 200 пунктов, которые
индивидуально проверяются для каждого выпуска. Некоторые пункты контрольного
списка занимают только несколько секунд, чтобы проверить.
Другие включают наборы тестов, которые выполняются в
течение многих часов. Контрольный список выпуска не автоматизирован: разработчики управляют
каждым пунктом списка вручную. Мы находим, что важно держать человека в
тонусе. Иногда проблемы найдены, управляя пунктом контрольного списка
даже при том, что сам тест прошел.
Важно иметь человека, рассматривающего испытательный вывод
на высшем уровне и постоянно разбирающегося
"Это действительно правильно?" Контрольный список выпуска непрерывно развивается.
Поскольку новые проблемы или потенциальные проблемы обнаружены, новые пункты
контрольного списка добавляются, чтобы удостовериться, что те проблемы не
появляются в последующих версиях. Контрольный список выпуска, оказалось, был
неоценимым инструментом, чтобы гарантировать, что ничто не пропущено во
время процесса выпуска. Статический анализ означает анализировать исходный код во время
компиляции, чтобы проверить на правильность. Статический анализ включает
сообщения предупреждения компилятора и больше всесторонних аналитических
движков, таких как
Clang Static Analyzer.
SQLite собирается без предупреждений на GCC и Clang, используя флаги
-Wall и -Wextra в Linux и Mac и в MSVC под Windows.
Никакие действительные предупреждения не произведены Clang Static Analyzer
"scan-build" (хотя последние версии clang, кажется, производят много ложных
положительных срабатываний). Тем не менее, некоторые предупреждения могли бы
быть произведены другими статическими анализаторами. Пользователи поощряются
не заморачиваться этими предупреждениями, а сосредоточиться на интенсивном
тестировании SQLite, описанного выше. Статический анализ не был полезен в нахождении ошибок в SQLite.
Статический анализ нашел несколько ошибок в SQLite, но это исключения.
Больше ошибок было введено в SQLite, пытаясь заставить его собираться без
предупреждений, чем было найдено статическим анализом. SQLite open source. Это дает многим людям идею, что это не хорошо
проверено как коммерческое программное обеспечение и возможно ненадежно.
Но то впечатление ложное. SQLite показал очень высокую надежность в области и
очень низкую скорость дефектообразования, особенно рассмотрев, как быстро это
развивается. Качество SQLite достигается частично тщательной кодовой
разработкой и реализацией. Но обширное тестирование также играет жизненно
важную роль в поддержании и улучшении качества SQLite.
Этот документ суммировал процедуры тестирования, которым каждый выпуск SQLite
подвергается с надеждой на внушение доверия, что SQLite подходит для
использования в приложениях для решения ответственных задач.
Choose any three.
1. Введение
1.1. Резюме
2. Ограничения проверок
3. Тестирование аномалии
3.1. Тесты Out-Of-Memory
3.2. Тесты I/O Error
3.3. Тестирование на сбои
3.4. Составные тесты на неудачу
4. Тестирование Fuzz
4.1. SQL Fuzz
4.1.1. SQL Fuzz, используя The American Fuzzy Lop Fuzzer
4.1.2. Google OSS Fuzz
4.1.3. dbsqlfuzz fuzzer
4.1.4. Другой сторонний fuzzer
4.1.5. Ограничения тестирования fuzzcheck
4.1.6. Напряженность между тестированием Fuzz и 100% MC/DC
4.2. Уродливые файлы базы данных
4.3. Тесты граничного значения
5. Регрессионное тестирование
6. Автоматическое обнаружение утечки ресурсов
7. Тестовое покрытие
7.1. Заявление против освещения отделения
if( a>b && c!=25 ){ d++; }
7.2. Тестирование освещения защитного кода
#define ALWAYS(X) (X)
#define NEVER(X) (X)
#define ALWAYS(X) ((X)?1:assert(0),0)
#define NEVER(X) ((X)?assert(0),1:0)
#define ALWAYS(X) (1)
#define NEVER(X) (0)
7.3. Принуждение освещения граничных значений и
булевых векторных тестов
#define testcase(X)
testcase(a==b);
testcase(a==b+1);
if (a>b && c!=25)
{
d++;
}
switch( op )
{
case OP_Add:
case OP_Subtract:
{
testcase( op==OP_Add );
testcase( op==OP_Subtract );
/* ... */
break;
}
/* ... */
}
testcase( mask & SQLITE_OPEN_MAIN_DB );
testcase( mask & SQLITE_OPEN_TEMP_DB );
if( (mask & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_TEMP_DB))!=0 ){ ... }
7.4. Освещение отделения против MC/DC
7.5. Измерение освещения отделения
7.6. Тестирование мутации
55 static unsigned int strHash(const char *z){
56 unsigned int h = 0;
57 unsigned char c;
58 while( (c = (unsigned char)*z++)!=0 ){ /*OPTIMIZATION-IF-TRUE*/
59 h = (h<<3) ^ h ^ sqlite3UpperToLower[c];
60 }
61 return h;
62 }
/*OPTIMIZATION-IF-TRUE*/
" и
"/*OPTIMIZATION-IF-FALSE*/
"
вставляются в исходный код SQLite, чтобы сказать скрипту
тестирования мутации игнорировать некоторые команды перехода.
7.7. Опыт с полным тестовым покрытием
8. Динамический анализ
8.1. Assert
8.2. Valgrind
8.3. Memsys2
8.4. Mutex Assert
8.5. Тесты журнала
8.6. Неопределенные проверки поведения
9.
Отключенные тесты на оптимизацию
10. Контрольные списки
11. Статический анализ
12. Итог