我在之前的文章中介绍过SQLite,这是个嵌入式数据库,也是世界上最流行的数据库。

(详情参见这篇文章:我开发了世界上最流行的数据库,并把100%的公司股份送给了老婆)
有一天我突发奇想:既然是嵌入式数据库,那应该不复杂,能不能用AI写一个?
说干就干,我把SQLite的架构文档发给了AI,让它根据现有架构来实现。
你别说,AI实现得相当不错,把Tokenizer,Parser,Code Generator,Bytecode Engine这些核心的概念都考虑到了,代码写得也不错,着实是让我吃惊。

但是,当我看到SQLite的测试的时候,我就知道,现在的AI,想写出SQLite级别的代码,恐怕是做梦。
因为SQLite简直不是一个软件,而是个怪物。
截至 3.42.0 版本(2023-05-16),SQLite 库本身大约包含15.58万行C代码。
而测试代码和测试脚本脚本加起来一共有9205万行,是核心代码的 590 倍!
不是5倍,50倍,而是590倍 !
测试的目标不但要覆盖“功能”,还要覆盖分支,覆盖异常,覆盖现实世界的灾难。
SQLite设立了4套“互不信任”的测试体系,避免同一思路的盲区。
01
“保命”测试
首先,它用TCL写了一套“日常保命测试”,测试脚本有1390个文件,一共23.2M,包含了51,445 个不同的测试用例,参数化执行,会跑出数百万测试。
开发者日常主要依赖一个非常快的、几分钟就能跑完的子集,用于每次提交前自己检查。
02
“彻底”测试
SQLite有一套私有的C语言测试系统(名称为TH3),为 SQLite 核心库提供 100% 分支覆盖率。
这个数字是非常恐怖的,大家都知道覆盖所有分支很麻烦,例如这行代码:
if( a>b && c!=25 ){ d++; }
想要达到100%分支覆盖,至少需要3个测试用例:
(1) a<=b
(2) a>b 且 c==25
(3) a>b 且 c!=25
除了分支覆盖,SQLite还是是实现了100% MC/DC 覆盖率。
MC/DC(Modified Condition/Decision Coverage)是航空、核工业级别的测试标准。也就是说SQLite 把一个嵌入式数据库,按飞控软件的标准来测。
TH3有个非常重要的特点,就是不靠“后门”,只是用SQLite公开的API接口,完全模拟用户的行为。
TH3 包含大约 76.9MB、100万行C 代码,实现了 50,362 个不同测试用例。
在发布之前的一个长时间压力测试中,会执行2.485亿次测试。
03
对标测试
这是一个裁判性测试,它将大量 SQL 语句同时运行在 SQLite 和其他4个数据库引擎(PostgreSQL、MySQL、SQL Server、Oracle 10g )上,并比较结果是否一致。

换句话说,SQLite 不只要自洽,还要和“行业共识”对齐。
这个测试共运行 720 万条查询,测试数据总量 1.12GB。
04
模糊测试
模糊测试通过向SQLite提供无效、意外或者随机数据来测试它的健壮性。
例如这样的SQL语言:
SELECT (SELECT COUNT(*) FROM sqlite_master), hex(randomblob(2147483647)), substr('abc', -999999999, 5)FROM (SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3)WHERE CAST('999999999999999999999999' AS INTEGER) < 0;
它完全符合SQL语法,但是几乎不可能是人写的,因为它精准命中了“超大内存申请”,“整数溢出”,“负索引”等极端情况。
更残酷的是,模糊测试还会破坏数据库文件(.db),对它做随机位翻转、截断、重复、拼接。
例如,这是原始的数据文件(简化):
[Header][Page1][Page2][Page3]
模糊测试工具把它改成:
[Header][Page1][Page1][乱码][Page3][Page3]
然后尝试去打开它:
sqlite3_open("mutated.db", &db);
SQLite要做到不崩溃,不越界,不死循环,要么报错,要么优雅失败。
最后,模糊测试还会开关所有的宏定义,对不同的编译器设置不同的编译优化级别,让同一条SQL在不同的情况下疯狂运行,疯狂地鞭打SQLite,折磨SQLite,直到它漏出所有的破绽。
由于SQLite在嵌入式领域非常流行,手机、路由器、车载、工业设备等在大量使用它,所以它特别注重对异常情况的处理。
内存不足测试
在服务器和工作站上,内存分配malloc()几乎不会失败,在嵌入式设备上,OOM就太常见了,SQLite必须得能优雅的处理OOM错误。
SQLite的测试框架(自己写的)可以插入自定义的可控的malloc函数,设置成在调用一定次数后内存分配失败,这样就可以在循环中执行SQLite操作,确保能处理OOM错误。
I/O错误测试
磁盘空间不足、磁盘硬件故障、网络中断、SQL操作过程中发生的系统配置或权限更改,或其他硬件或操作系统故障......无论原因如何,SQLite 必须能够正确响应这些错误。
测试的方法和OOM测试类似,都是模拟I/O错误,然后检查SQLite能否正确响应
崩溃测试
应用系统崩溃,操作系统崩溃,数据库更新过程中断电,SQLite都不能发生故障。
这也是SQLite“原子提交”能被信任的原因。
复合失效测试
先发生I/O错误,然后出现OOM,再搞一个崩溃..... 保证程序还能正常运行。
SQLite的这些测试,它的数量、完善程度和复杂程度,看起来真是让人脊背发凉。
这得付出多年的努力,经历多少事故才能写出来啊!
被这样测试保护的软件,代码质量可以说是固若金汤了。
AI能写出这样高质量的SQLite吗?
写个玩具版是可以的,但是想要复刻是万万不行的。
SQLite这种“高质量”不是体现在代码本身,而是体现在几十年积累的测试体系、失败经验和工程纪律上。
AI 可以帮助我们写实现、补测试、找 bug,但无法凭空生成那些来自真实事故、长期演化出来的异常场景和验证策略。
换句话说,AI 能参与写 SQLite,但还不能“独立孕育”一个 SQLite。
SQLite只是一个嵌入式数据库,就已经恐怖如斯了,那些更加复杂的数据库会怎么样呢?
欢迎了解详情的朋友在评论区留言!

32

被折叠的 条评论
为什么被折叠?



