小。速度很快。可靠。
选择任意三个选项。
SQLite是如何测试的

1介绍

SQLite的可靠性和健壮性部分得以实现通过彻底仔细的测试。

截至版本3.42.0(2023-05-16),SQLite库由大约155.8 KSLOC of C代码。(KSLOC是指数千条“源代码行”,换句话说,代码行,不包括空行和注释。)相比之下,该项目590倍测试代码和测试脚本-92053.1肯尼亚克朗。

1.1.执行摘要

2测试线束

有四个独立的测试线束用于测试核心SQLite库。每个测试线束都是单独设计、维护和管理的从其他人那里。

  1. 这个TCL测试是SQLite的原始测试。它们与SQLite核心和类似的SQLite核心都在公共领域。这个TCL测试是开发期间使用的主要测试。TCL测试使用TCL脚本语言.TCL测试线束本身由27.2 KSLOC组成用于创建TCL接口的C代码。包含测试脚本共1390个文件23.2MB大小。51445个不同的测试用例,但许多测试案例被参数化并多次运行(使用不同的参数)所以在一次完整的测试中进行单独的测试。

  2. 这个TH3型测试工具是一组用提供100%分支测试覆盖率的C(和100%MC/DC测试覆盖率)至核心SQLite库。TH3测试旨在运行在不易支持的嵌入式和专用平台上TCL或其他工作站服务。TH3测试仅使用发布的SQLite接口。TH3由大约76.9 MB或1055.4 KSLOC实现50362个不同测试用例的C代码。然而,TH3测试是高度参数化的,因此可以运行全覆盖测试约240万种不同的测试实例。提供100%分支测试覆盖率的案例构成总TH3测试套件的子集。浸泡试验在发布之前2.485亿次测试。关于TH3的其他信息可单独购买.

  3. 这个SQL逻辑测试或SLT测试线束用于运行大量数据针对SQLite和其他几个SQL数据库引擎的SQL语句并验证他们是否都得到了相同的答案。SLT目前比较针对PostgreSQL、MySQL、Microsoft SQL Server和Oracle 10g的SQLite。SLT运行720万个查询,包括1.12GB测试数据。

  4. 这个dbsqlfuzz发动机是专利模糊测试仪。其他SQLite模糊器修改SQL输入或数据库文件。Dbsqlfuff突变同时使用SQL和数据库文件,因此能够以达到新的错误状态。Dbsqlfuzz是使用libFuzzer软件LLVM框架带有自定义的赋值函数。336个种子文件。dbsqlfuzz模糊器每天大约运行10亿个测试突变。Dbsqlfuzz有助于确保SQLite能够抵抗恶意SQL或数据库的攻击输入。

除了四个主要测试线束外,还有许多其他线束实现专门测试的小程序。这里有一些示例:

  1. “speedtest1.c”程序评估SQLite在典型工作负载下的性能。
  2. “mptest.c”程序是对多个进程的压力测试同时读取和写入单个数据库。
  3. “threadtest3.c”程序是使用同时出现SQLite。
  4. “fuzzershell.c”程序用于运行一些模糊测试.
  5. “jfuzz”程序是一个基于libfuzzer的模糊器,用于JSONB格式输入到JSON SQL函数.

以上所有测试必须在多个平台上成功运行并且在多种编译时配置下,每次发布SQLite之前。

在每次检入SQLite源代码树之前,开发人员通常运行Tcl测试的子集(称为“veryquick”)由大约30.47万个测试用例。快速测试包括除异常、模糊和浸泡试验。快速测试背后的理念是足以捕获大多数错误,而且只需几分钟即可运行而不是几个小时。

三。异常测试

异常测试是旨在验证正确行为的测试当SQLite出现问题时。它(相对)容易建造对格式良好的输入进行正确操作的SQL数据库引擎在功能齐全的计算机上。建立一个系统更困难对无效输入做出理智响应并继续执行以下操作系统故障。异常测试旨在验证后者行为。

3.1.内存不足测试

与所有SQL数据库引擎一样,SQLite广泛使用malloc()(请参阅关于SQLite中的动态内存分配对于其他细节。)在服务器和工作站上,malloc()在实践中从未失败,因此是正确的内存外(OOM)错误的处理并不特别重要。但在嵌入式设备上,OOM错误非常常见,而且SQLite经常用于嵌入式设备,重要的是SQLite能够优雅地处理OOM错误。

OOM测试是通过模拟OOM错误来完成的。SQLite允许应用程序替换替代的malloc()使用sqlite3_config(SQLITE_CONFIG_MALLOC公司,...)接口。TCL和TH3测试线束都能够插入malloc()的修改版本,该版本可能会被操纵失败在一定数量的分配之后。这些装有仪器的mallocs可以设置为只失败一次,然后再次开始工作,或者设置为第一次失败后继续失败。OOM测试在循环。在循环的第一次迭代中,插入指令的malloc被操纵在第一次分配时失败。然后是一些SQLite操作并进行检查以确保SQLite处理了OOM错误正确。然后是故障时间计数器在被检测的malloc上增加一个,测试为重复的。循环一直持续到整个操作运行到在没有遇到模拟OOM故障的情况下完成。这样的测试运行两次,一次使用插入指令的malloc设置为仅失败一次,并使用插入指令的malloc集再次失败第一次失败后持续失败。

3.2.I/O错误测试

I/O错误测试旨在验证SQLite的响应是否正常失败的I/O操作。完整磁盘驱动器可能会导致I/O错误,磁盘硬件故障,使用网络时网络中断中发生的文件系统、系统配置或权限更改SQL操作或其他硬件或操作系统的中间故障。无论是什么原因,SQLite能够为了正确响应这些错误,I/O错误测试寻求验证它是否正确。

I/O错误测试在概念上与OOM测试类似;I/O错误进行模拟并进行检查以验证SQLite是否响应正确地模拟误差。I/O错误在两个TCL和TH3通过插入新的虚拟文件系统对象那是特别装配的以模拟在设定数量的I/O操作之后的I/O错误。与OOM错误测试一样,I/O错误模拟器可以设置为只失败一次,或在第一次失败后继续失败。测试在循环中运行,慢慢增加故障点,直到测试用例运行到完成时没有错误。循环运行两次,一次,将I/O错误模拟器设置为仅模拟单一故障第二次设置为在第一次之后使所有I/O操作失败失败。

在I/O错误测试中,I/O错误模拟失败机制之后禁用,则使用检查数据库PRAGMA完整性检查确保I/O错误没有引入了数据库损坏。

3.3.碰撞试验

崩溃测试旨在证明SQLite数据库不会如果应用程序或操作系统崩溃或是数据库更新过程中的电源故障。一个单独的白皮书标题SQLite中的原子提交描述了SQLite为防止数据库损坏而采取的防御措施撞车事故。碰撞试验旨在验证这些防御措施工作正常。

使用实际电源故障进行碰撞测试是不切实际的当然,所以碰撞测试是在模拟中进行的。另一种选择虚拟文件系统插入以允许测试用于模拟崩溃后数据库文件的状态。

在TCL测试线束中,碰撞模拟在单独的过程。主测试进程生成一个子进程,该子进程运行一些SQLite操作和随机崩溃写入操作。一个特别的变频调速系统随机重新排序和损坏未同步的写操作来模拟缓冲文件系统的效果。之后孩子死了,原始测试过程打开并读取测试数据库并验证子级尝试的更改成功完成或完全回滚。这个完整性检查 PRAGMA公司用于确保数据库不会损坏发生。

TH3测试线束需要在不需要必须具有生成子进程的能力,因此它使用内存中的变频调速系统以模拟崩溃。内存中的变频调速系统可以操纵在完成一定数量的I/O后,对整个文件系统制作快照操作。碰撞测试在循环中运行。在循环的每次迭代中,创建快照的点被提前到SQLite正在测试的操作一直运行到完成,而没有碰到快照。在循环中,在被测试的SQLite操作完成后,文件系统将恢复为快照和随机文件引入了损伤,这是损伤类型的特征人们预计会出现断电后的情况。然后打开数据库并进行检查,以确保其格式良好,并且事务要么运行完成,要么完全回滚。每个循环的内部重复多次每次随机损伤不同的快照。

3.4.复合失效试验

SQLite的测试套件还探索了堆叠的结果多次失败。例如,运行测试以确保行为正确当尝试从之前的碰撞。

4模糊测试

模糊测试试图确定SQLite对无效、超出范围、,或输入格式错误。

4.1.SQL模糊

SQL模糊测试包括创建语法正确的毫无意义的SQL语句并将其提供给SQLite查看这会对他们有什么影响。通常会返回某种错误(例如“没有这样的表”)。有时,纯粹是偶然,SQL语句的语义也恰好正确。在这种情况下运行生成的预处理语句以确保它给出合理的结果。

4.1.1.使用美国模糊Lop模糊器的SQL模糊

模糊测试的概念已经存在了几十年,但模糊直到2014年,测试才成为发现错误的有效方法Michal Zalewski发明了第一个实用的轮廓引导模糊器,美种费斯垂耳兔或“AFL”。与以前盲目生成随机输入的模糊器不同,AFL检测正在测试的程序(通过修改汇编语言并使用该工具检测输入会导致程序执行不同的操作新的控制路径或循环的次数不同。激发的输入新的行为被保留并进一步变异。通过这种方式,AFL能够“发现”测试程序的新行为,包括行为这是设计师们从未想到的。

事实证明,AFL擅长在SQLite中发现神秘的错误。大多数结果都是assert()语句,其中条件在模糊的情况下是虚假的。但AFL也发现SQLite中有很多崩溃错误,甚至在SQLite中计算出了错误的结果。

由于过去的成功,AFL成为测试的标准部分SQLite的战略版本3.8.10(2015年5月7日)至它在年被更好的模糊者取代版本3.29.0(2019-07-10).

4.1.2.谷歌OSS Fuzz

从2016年开始,谷歌的一个工程师团队开始OSS模糊项目。OSS Fuzz使用AFL风格的引导模糊器,运行在谷歌的基础设施上。Fuzzer会自动下载最新的签到以供参与项目,模糊化它们,并向开发人员发送电子邮件报告任何问题。当一个修复被检入时,模糊器会自动检测到并通过电子邮件向开发人员发送确认。

SQLite是OSS Fuzz测试的许多开源项目之一。这个测试/ossfuzz。c(c)源文件SQLite存储库中是SQLite与OSS fuzz的接口。

OSS Fuzz不再在SQLite中发现历史错误。但它仍然是正在运行,并且偶尔会在新的开发签入中发现问题。示例:[1] [2] [3].

4.1.3.dbsqlfuzz和jfuzz模糊器

从2018年末开始,SQLite已经使用专有的fuzzer称为“dbsqlfuzz”。Dbsqlfuzz是使用libFuzzer软件LLVM框架。

dbsqlfuzz fuzzer修改SQL输入和数据库文件同时。Dbsqlfuzz使用自定义结构感知突变体定义输入数据库和SQL的专用输入文件要针对该数据库运行的文本。因为它改变了两个输入数据库和输入SQL同时,dbsqlfuzz已经能够在SQLite中发现一些以前模糊器忽略的模糊错误只修改了SQL输入或数据库文件。SQLite开发人员在大约始终16芯。dbsqlfuzz程序的每个实例都能够每秒评估大约400个测试用例,这意味着大约5亿个每天检查病例。

dbsqlfuzz模糊器在强化针对恶意攻击的SQLite代码库。由于dbsqlfuzz已经添加到SQLite内部测试套件中,来自外部的错误报告像OSSFuzz这样的模糊者几乎已经停止了。

注意,dbsqlfuzz是基于Protobuf的结构软件Chromium使用的SQLite的fuzzer,在Structure-Aware Mutator文章.这两个模糊者之间没有联系,除了他们都是基于libFuzzer软件SQLite的Protobuf fuzzer由Chromium编写和维护团队,而dbsqlfuzz是由原始团队编写和维护的SQLite开发人员。具有多个独立开发的SQLite模糊器这很好,因为这意味着模糊的问题更有可能被发现。

2024年1月底,第二个基于libFuzzer的工具“jfuzz”开始使用。Jfuzz生成损坏JSONB格式blob和feed他们进入JSON SQL函数验证JSON函数能够安全有效地处理损坏的二进制输入。

4.1.4.其他第三方模糊器

SQLite似乎是第三方的热门目标。开发人员听说有很多人试图模糊SQLite他们偶尔会收到独立用户发现的错误报告模糊者。所有此类报告都会立即修复,因此产品改进后,整个SQLite用户社区都受益匪浅。拥有多个独立测试人员的机制类似于莱纳斯定律:“只要有足够的眼球,所有的虫子都是浅的”。

一位值得注意的模糊研究者是曼努埃尔起重工.大多数模糊器只查找断言错误、崩溃、未定义行为(UB)、,或其他容易检测到的异常。另一方面,Rigger博士的模糊器,能够找到SQLite计算错误答案的情况。Rigger发现许多这样的案例.这些发现大多是涉及类型的模糊角落案例转换和关联转换,以及大量的查找结果反对未发布的功能。尽管如此,他的发现仍然很重要因为它们是真正的虫子,SQLite开发人员很高兴能够识别并修复根本问题。

4.1.5.模糊检查测试线束

历史测试用例来自AFL公司,OSS模糊、和dbsqlfuzz收集在主SQLite源代码树中的一组数据库文件中然后在运行时由“fuzzcheck”实用程序重新运行“进行测试”。Fuzzcheck只运行数千个“有趣”的案例在各种模糊者拥有的数十亿案件中经过多年的检验。“有趣的”案例是指以前看不见的行为。fuzzer发现的实际错误总是包括在有趣的测试用例中,但大多数用例都在运行fuzzcheck从来都不是真正的bug。

4.1.6.模糊测试和100%MC/DC测试之间的张力

模糊测试和100%MC/DC测试与……紧张彼此之间。也就是说,经过100%MC/DC测试的代码更容易受到模糊处理和执行的代码发现的问题的影响嗯,在模糊测试期间,(很大程度上)100%MC/DC。这是因为MC/DC测试不鼓励防御性代码具有无法访问的分支,但如果没有防御代码,fuzzer更有可能找到导致问题的途径。MC/DC测试似乎可以很好地构建在正常使用,而模糊测试有助于构建抵抗恶意攻击的健壮性。

当然,用户更喜欢正常情况下既健壮又健壮的代码使用并抵抗恶意攻击。SQLite开发人员致力于提供这一点。本节的目的仅仅是指出同时做这两件事很困难。

SQLite在其历史上的大部分时间里都专注于100%的MC/DC测试。引入后,对模糊攻击的抵抗才成为关注的焦点2014年AFL。有一段时间,模糊者发现了许多问题在SQLite中。近年来,SQLite的测试策略发展到更加重视模糊测试。我们仍然坚持核心SQLite代码的100%MC/DC,但大多数测试CPU周期是现在致力于模糊处理。

当模糊测试和100%MC/DC测试处于紧张状态时并不完全是相互矛盾的。SQlite测试的事实套件确实测试到100%MC/DC意味着当模糊器确实发现问题时,这些问题可以很快解决,而且引入风险很小新错误。

4.2.格式不正确的数据库文件

有许多测试用例可以验证SQLite能够处理格式错误的数据库文件。这些测试首先构建一个格式良好的数据库文件,然后添加通过某种方式更改文件中的一个或多个字节而导致损坏而不是SQLite。然后使用SQLite读取数据库。在某些情况下,字节更改位于数据中间。这会导致数据库内容更改,同时保留数据库格式良好。在其他情况下,会修改文件中未使用的字节对数据库的完整性没有影响。有趣的情况是,当文件的字节定义数据库结构发生更改。错误的数据库测试验证SQLite是否找到文件格式错误并报告它们使用SQLITE_CORRUPT没有溢出的返回代码缓冲区、取消引用NULL指针或执行其他操作不健康的行为。

这个dbsqlfuzzfuzzer在验证方面也做得很好SQLite对格式错误的数据库文件做出了明智的响应。

4.3.边界值测试

SQLite定义了某些限制对其操作,例如表中的最大列数SQL语句或整数的最大值。TCL和TH3测试套件都包含大量将SQLite推向边缘的测试并验证其正确执行所有允许的值。额外测试超出了规定的限制并验证SQLite是否正确返回错误。源代码包含测试用例宏验证每个边界的两侧已经过测试。

5回归测试

每当针对SQLite报告错误时,都不会考虑该错误修复,直到添加了新的测试用例,这些测试用例将显示错误TCL或TH3测试套件。多年来,这导致了成千上万的新测试。这些回归测试确保了过去已修复的不会重新引入未来版本的苏莱特。

6自动资源泄漏检测

系统资源时发生资源泄漏已分配,但从未释放。最麻烦的资源泄漏在许多应用程序中存在内存泄漏-当使用malloc(),但从未使用free()释放。但其他种类的资源也可能被泄露:文件描述符、线程、互斥体等。

TCL和TH3测试线束都自动跟踪系统资源并报告资源泄漏每一个试运行。无需特殊配置或设置。测试线束对内存泄漏特别警惕。如果发生变化导致内存泄漏,测试线束将识别这一点迅速地。SQLite被设计为永远不会泄漏内存,即使在异常,如OOM错误或磁盘I/O错误。测试马勒热心于实施这一点。

7测试覆盖范围

SQLite核心,包括unix变频调速系统,100%的分支测试覆盖率TH3型在里面其默认配置由覆盖测试.FTS3和RTree等扩展不在此列分析。

7.1.报表与分行覆盖范围

有很多方法可以测量测试覆盖率。最受欢迎的指标是“报表覆盖率”。当你听到有人说计划为“XX%测试覆盖率”,没有进一步解释,他们通常平均报表覆盖率。报表覆盖率衡量百分比测试套件至少执行一次代码行。

分支覆盖比报表覆盖更严格。分支机构覆盖率衡量机器代码分支指令的数量在两个方向上至少评估一次。

为了说明报表覆盖范围和分支覆盖率,考虑以下假设C行代码:

如果(a>b&&c!=25){d++;}

这样一行C代码可能会生成十几个单独的机器代码说明。如果这些指令中的任何一条被评估过,那么我们说,这一说法已经过检验。例如,它可能条件表达式是始终为false,“d”变量为从未递增。即便如此,报表覆盖范围也包括这一行代码为已测试。

分行覆盖面更严格。通过分支覆盖,每个测试和语句中的每个子块都是单独考虑的。整齐要在上面的示例中实现100%的分支覆盖率,必须有至少三个测试用例:

上述任何一个测试用例都将提供100%的语句覆盖率但这三项都是100%分行覆盖率所必需的。一般来说,100%的分支覆盖率意味着100%的语句覆盖率,但反之亦然这不是真的。再次强调TH3型SQLite的测试工具提供了更强大的测试覆盖率-100%的分支测试覆盖率。

7.2.防御代码的覆盖测试

一个编写良好的C程序通常会包含一些防御在实践中总是对或总是错的条件句。这导致了编程困境:为了获得100%分行覆盖率?

在SQLite中,前一个问题的答案是“否”。出于测试目的,SQLite源代码定义名为ALWAYS()和NEVER()的宏。ALWAYS()宏周围条件它们的求值结果总是为true和NEVER()始终计算为false的条件。这些宏用作注释以指示条件是防御代码。在发布版本中,这些宏是直通的:

#定义ALWAYS(X)(X)#定义NEVER(X)(X)

然而,在大多数测试期间,这些宏将抛出一个断言错误,如果他们的参数没有预期的真值。这个快速提醒开发人员错误的设计假设。

#定义ALWAYS(X)((X)?1:断言(0),0)#定义NEVER(X)(X)?断言(0),1:0)

当测量测试覆盖率时,这些宏被定义为常量真值,以便它们不生成汇编语言分支指令,因此在计算分支覆盖范围:

#始终定义(X)(1)#定义NEVER(X)(0)

测试套件设计为运行三次,每一次上面显示的ALWAYS()和NEVER()定义。所有三次测试运行应该会产生完全相同的结果。有一个运行时测试使用这个sqlite3_测试控制(SQLITE_TESTCTRL_ALWAYS数据库, ...) 接口可用于验证宏是否正确设置为第一个表单(直通表单)用于部署。

7.3.强制覆盖边界值和布尔向量测试

与测试覆盖率测量一起使用的另一个宏是这个测试用例()宏。参数是一个条件我们需要评估为true和false的测试用例。在非覆盖构建中(也就是说,在发布构建中)测试用例()宏是no-op:

#定义测试用例(X)

但在覆盖率测量构建中测试用例()生成对参数中的条件表达式求值的代码。然后在分析过程中,检查是为了确保存在评估条件为true和true的测试和false。测试用例()例如,宏用于帮助验证测试边界值。例如:

测试用例(a==b);测试用例(a==b+1);如果(a>b&&c!=25){d++;}

当一个开关有两个或多个案例时,也会使用测试用例宏语句转到同一代码块,以确保代码适用于所有情况:

开关(op){案例OP_添加:案例OP_Subtract:{测试用例(op==op_Add);测试用例(op==op_Subtract);/* ... */断裂;}/* ... */}

对于位掩码测试,测试用例()宏用于验证位掩码的位会影响结果。例如,在下面的块中对于代码,如果掩码包含两个位中的任何一个,则条件为true指示MAIN_DB或TEMP_DB正在打开。这个测试用例()if语句前面的宏验证是否测试了这两种情况:

测试用例(掩码&SQLITE_OPEN_MAIN_DB);测试用例(掩码&SQLITE_OPEN_TEMP_DB);if((掩码&(SQLITE_OPEN_MAIN_DB|SQLITE_OBEN_TEMP_DB))=0 ){ ... }

SQLite源代码包含1184使用测试用例()宏。

7.4.分行覆盖率与MC/DC

上述描述了两种测量测试覆盖率的方法:“声明”和“分支”覆盖范围。还有许多其他测试覆盖范围除这两个指标之外的其他指标。另一个流行的指标是“修改条件/决策覆盖”或MC/DC。维基百科MC/DC定义如下:

在C编程语言中哪里&&||是“短路”运营商,MC/DC和分支覆盖范围非常接近同样的事情。主要区别在于布尔向量测试。可以测试位向量中的任意几个位,仍然可以获得即使MC/DC的第二个元素-要求决策中的每个条件对每个可能的结果产生影响-可能不满意。

SQLite使用测试用例()宏,如前所述分段以确保位向量决策中的每个条件每一个可能的结果。这样,SQLite也实现了100%的MC/DC除了100%的分支覆盖之外。

7.5.测量分支覆盖率

目前正在测量SQLite中的分支覆盖率使用覆盖测试带有“-b”选项。首先,使用选项编译测试程序“-g-fprofile-arcs-ftest-coverage”,然后运行测试程序。然后运行“gcov-b”生成覆盖报告。报道冗长,阅读不便,因此,使用以下命令处理gcov生成的报告一些简单的脚本将其转换为更人性化的格式。当然,整个过程是使用脚本自动完成的。

请注意,使用gcov运行SQLite并不是对SQLite的测试-这是对测试套件的测试。gcov运行不会测试SQLite,因为-fprofile-args和-ftest-coverage选项使编译器生成不同的代码。gcov运行只是验证测试套件是否提供了100%的分支测试新闻报道。gcov运行是测试的一种测试——元测试。

运行gcov以验证100%的分支测试覆盖率后,然后使用交付编译器选项重新编译测试程序(没有特殊的-fprofile-arcs和-ftest-coverage选项)并且重新运行测试程序。第二次运行是SQLite的实际测试。

重要的是要验证gcov测试运行第二次实际测试都给出了相同的输出。任何输出差异表明使用了未定义或SQLite代码中的不确定行为(因此是一个bug),或编译器中的错误。请注意,SQLite在过去十年中遇到了错误在GCC、Clang和MSVC中。编译器错误虽然很少发生,这就是为什么在as-delivered中测试代码如此重要的原因配置。

7.6.突变测试

使用gcov(或类似工具)显示执行了每个分支指令至少双向一次是测试套件质量的良好度量。但更好的是显示每个分支指令输出中的差异。换句话说,我们想展示不仅每个分支指令都会跳转和失败,而且每个分支都在做有用的工作,并且测试套件能够检测并验证工作。当发现分支时改变输出,这表明与可以删除分支(减小库的大小,也许或者测试套件没有充分测试分支实现的功能。

SQLite努力验证每个分支指令是否都会产生差异使用突变试验.一个脚本首先将SQLite源代码编译为汇编语言(例如,使用gcc的-S选项)。然后脚本逐步完成生成的汇编语言,并逐个更改每个分支指令转换为无条件跳转或no-op,编译结果,并验证测试套件捕捉到了变异。

不幸的是,SQLite包含许多分支指令在不更改输出的情况下帮助代码更快地运行。这种分支在突变测试中产生假阳性。例如,考虑以下内容散列函数用于加速表名查找:

55静态无符号int strHash(const char*z){56无符号整数h=0;57个无符号字符c;58 while((c=(unsigned char)*z++)=0){/*优化-IF-TRUE*/59 h=(h<<3)^h^sqlite3上下[c];60    }61返回h;62  }

如果在第58行上实现“c!=0”测试的分支指令变成no-op,while-lop将永远循环测试套件将因超时而失败。但如果分支被更改转换为无条件跳转,则哈希函数将始终返回0。问题是0是一个有效的哈希。始终返回0仍然有效,因为SQLite仍然可以得到正确的结果回答。表名哈希表退化为链表因此,在解析SQL语句时发生的表名查找可能会慢一点,但最终结果是一样的。

要解决此问题,请使用表单的注释"/*优化-真*/“和"/*优化-假*/“插入SQLite告诉变异测试脚本忽略某些分支的源代码说明。

7.7.具有全面测试覆盖的经验

SQLite的开发人员发现,全覆盖测试定位和防止错误的非常有效的方法。因为每个分支SQLite核心代码中的指令包含在测试用例中,开发人员可以确信在代码的某一部分中所做的更改不要在代码的其他部分产生意想不到的后果。许多新功能和性能改进如果没有,近年来添加到SQLite是不可能的全覆盖测试的可用性。

维持100%的MC/DC既费力又耗时。维护全覆盖测试所需的工作量对于典型应用程序来说,可能不具有成本效益。然而,我们认为全覆盖测试对于部署非常广泛基础设施库比如SQLite,特别是对于一个数据库库大自然会“记住”过去的错误。

8动力学分析

动态分析是指对在代码处于活动状态和运行状态时执行的SQLite代码。事实证明,动态分析在维护SQLite的质量。

8.1.断言

SQLite核心包含6754个资产()验证函数前置条件和后置条件的语句循环不变量。Assert()是一个宏,它是ANSI-C。参数是一个布尔值,假定它总是真的。如果断言为false,程序将打印错误消息和停顿。

通过使用定义的NDEBUG宏进行编译,可以禁用Assert()宏。在大多数系统中,默认情况下启用断言。但在SQLite中资产数量众多,且处于性能关键的位置启用断言时,数据库引擎的运行速度大约慢三倍。因此,SQLite的默认(生产)构建将禁用断言。只有使用SQLITE_DEBUG预处理器宏已定义。

请参阅在SQLite中使用Of断言文件有关SQLite如何使用assert()的其他信息。

8.2.瓦尔格林德

瓦尔格林德也许是最令人惊奇的以及世界上有用的开发工具。Valgrind是一个模拟器-它模拟运行Linux二进制文件的x86。(Valgrind港口用于其他平台比Linux正在开发中,但截至本文撰写之时,Valgrind仅在Linux上可靠地工作,SQLite开发人员认为意味着Linux应该是所有软件开发的首选平台。)当Valgrind运行Linux二进制文件时,它会寻找各种有趣的错误,如数组溢出、从未初始化内存读取、,堆栈溢出、内存泄漏等等。Valgrind发现问题它可以轻松通过针对SQLite运行的所有其他测试。而且,当Valgrind发现错误时,它可以直接转储开发人员在错误发生的确切位置进入符号调试器便于快速修复。

因为它是一个模拟器,所以在Valgrind中运行二进制文件比在本机硬件上运行它。(对于第一近似值,应用程序在工作站上运行Valgrind的性能与之大致相同将在智能手机上运行。)因此,完全运行是不切实际的通过Valgrind的SQLite测试套件。然而,快速测试和TH3测试的覆盖范围在释放。

8.3.Memsys2型

SQLite包含一个可插拔的内存分配子系统.默认实现使用系统malloc()和free()。但是,如果使用编译SQLiteSQLITE_MEMDEBUG数据库,另一种选择内存分配包装器(内存系统2)插入查找内存分配的运行时出错。memsys2包装器检查内存泄漏,共当然,但也会查找缓冲区溢出、未初始化内存的使用、,并在释放内存后尝试使用内存。这些同样的支票也是由瓦尔格林德完成的(实际上,瓦尔格林德做得更好)但memsys2的优点是比Valgrind快得多意味着可以更频繁地进行检查,并进行更长的测试。

8.4.互斥断言

SQLite包含一个可插入的互斥子系统。取决于编译时选项,默认互斥系统包含接口sqlite3_mutex_held()sqlite3_mutex_notheld()检测到无论调用线程是否持有特定的互斥对象。这两个接口在assert()语句中广泛使用在SQLite中验证互斥锁是否正确地保持和释放时刻,以便再次检查SQLite是否正常工作在多线程应用程序中。

8.5.日志测试

SQLite为确保事务跨系统崩溃和电源故障是原子的吗在更改数据库。TCL测试线束包含替代品操作系统后端有助于实现验证这是否正确发生。“日志测试VFS”监控器数据库文件和回滚日志之间的所有磁盘I/O流量,检查以确保没有任何内容写入数据库尚未首先写入并同步到回滚日志的文件。如果发现任何差异,则会引发断言错误。

日志测试是一个额外的双重检查崩溃测试以确保SQLite事务是原子的跨系统崩溃和电源故障。

8.6.未定义的行为检查

在C编程语言中,很容易编写以下代码具有“未定义”或“实现定义”行为。这意味着代码可能在开发期间工作,但随后会给出不同系统上的不同答案,或使用不同的编译器选项。中未定义和实现定义的行为示例ANSI C包括:

由于未定义和实现定义的行为是不可移植的而且很容易导致错误答案,SQLite非常努力地避免它。例如,将两个整数值作为SQL语句的一部分相加时,SQLite并不是简单地使用C语言“+”操作符将它们添加到一起。相反,它首先检查以确保加法不会溢出,如果溢出,则使用而是浮点。

帮助确保SQLite不会使用undefined或实现定义的行为,测试套件使用尝试检测未定义行为的插入指令的构建。例如,测试套件使用GCC的“-ftrapv”选项运行。而且他们使用Clang上的“-fsanitize=undefined”选项再次运行。而且再次使用MSVC中的“/RTC1”选项。然后重新运行测试套件使用“-funsigned-char”和“-fsigned-char”等选项确保实现差异也无关紧要。然后重复测试在32位和64位系统以及大发动机和小发动机系统上,使用各种CPU架构。此外,测试套件还增加了许多测试用例故意挑起未定义的行为。例如:"选择-1*(-9223372036854775808);".

9禁用的优化测试

这个sqlite3_测试控制(SQLITE_TESTCTRL_OPTIMIZATIONS数据库, ...) 接口允许在运行时禁用选定的SQL语句优化。SQLite应该始终通过优化生成完全相同的答案启用并禁用优化;答案来得更快启用优化。因此,在生产环境中保持优化处于打开状态(默认设置)。

SQLite上使用的一种验证技术是运行整个测试套件两次,一次启用优化,第二次启用优化关闭,并验证两次都获得了相同的输出。这个显示优化不会引入错误。

并非所有的测试用例都可以这样处理。一些测试用例检查验证优化是否真的减少了通过计算磁盘访问次数、排序操作、,完整扫描步骤或查询期间发生的其他处理步骤。当优化被禁用时,这些测试用例似乎会失败。但大多数测试用例只是检查正确答案获得,所有这些案例都可以使用和成功运行如果没有优化,为了表明优化不会导致故障。

10检查表

SQLite开发人员使用在线检查表来协调测试活动,并在每次SQLite发布之前验证所有测试是否通过。过去的清单保留以供历史参考。(检查列表对匿名互联网观众是只读的,但开发人员可以登录并更新其web中的清单项目浏览器。)SQLite测试和其他开发活动检查表的使用灵感来自 不犯错的秘密武器 .

最新的清单包含大约200项对每个版本进行单独验证。一些清单项目只需要验证和标记几秒钟。其他涉及测试套件持续了好几个小时。

发布清单不是自动的:开发人员在上面运行每个项目手动检查清单。我们发现让一个人呆在循环。有时在运行检查表项目时发现问题即使测试本身通过了。有一个人很重要审查最高级别的测试输出,并不断询问“这真的对吗?”

发布清单正在不断发展。作为新问题或发现潜在问题,添加新的检查表项目确保这些问题不会出现在后续版本中。这个发布清单已被证明是一个非常宝贵的工具,有助于确保在发布过程中没有遗漏任何内容。

11静态分析

静态分析意味着在编译时分析源代码检查正确性。静态分析包括编译器警告消息和更深入的分析引擎,如叮当声静态分析仪.SQLite编译时没有GCC和Clang警告,使用Linux和Mac上的-Wall和-Wextra标志以及Windows上的MSVC标志。Clang Static Analyzer工具“scan-build”未生成有效警告两者都有(尽管最近版本的clang似乎产生了许多假阳性。)然而,一些警告可能由其他静态分析仪。鼓励用户不要对这些感到压力警告并在SQLite的激烈测试中获得安慰如上所述。

静态分析对发现SQLite中的错误。静态分析在SQLite中发现了一些错误,但是这些都是例外。出现了更多错误在尝试编译SQLite时引入了静态分析发现的警告。

12总结

SQLite是开源的。这让许多人认为作为商业软件,它没有得到很好的测试,可能也不可靠。但这种印象是错误的。SQLite在现场和非常低的缺陷率,特别是考虑到它的发展速度。SQLite的质量部分是通过仔细的代码设计和实施。但广泛的测试在维护和提高SQLite的质量。此文档具有总结了SQLite每次发布所经历的测试过程希望激发人们对SQLite的信心适用于任务关键型应用。

此页面上次修改时间2024-03-13 17:43:35联合技术公司