海山数据库(He3DB)源码详解:海山Mysql 自动测试框架MTR
一、MySQL自动测试框架原理
1、简介
在修改内核代码后,不仅需要测试新增功能,同时也要对原有功能做回归测试,以保证新加代码对原有功能没有影响,这就需要用到MySQL源码自带的测试框架MySQL Test Framework,简称MTR。
Test Framework主要应用于MySQL等相关数据库项目开发测试,对单个功能点或者模块进行正确性和功能性测试,该工具在SQL级别对MySQL进行单元测试。Test Framework不仅仅能测试SQL语句,也能根据实际需要测试不同启动参数配置的MySQL服务器和客户端从而验证指定的功能逻辑。
2、原理实现
- 操作步骤:
(1)初始化参数
(2)收集测试用例
(3)初始化服务器
(4)运行工作子进程
(5)结果信息的输出保存

Test Framework需要初始化参数、收集测试用例、初始化服务器、运行工作子进程、结果信息的输出保存等步骤。接下来重点分析:
1)初始化服务器(initialize_servers())
在设置读取到的命令行的选项参数(command_line_setup())以及收集完本次所需的测试用例文件(collect_test_cases())后,需要初始化mysqld服务器。
首先创建var目录,var目录保存MySQL数据文件、日志信息、临时文件,运行期间产生的临时文件会在成功执行后删除。之后运行mysql_install_db()进行服务器的初始化。
2)运行工作子进程(run_worker())
通过_safe_fork()创建子进程,主进程在初始化套接字后,根据并行数parallel(默认为1)启动一个或多个子进程用于执行测试用例。在子进程run_worker()中循环监听主进程发送的消息,如果接收到‘TESTCASE’类型消息,则执行测试用例run_testcase()。
在执行真正的测试用例文件之前,Test Framework会进行第一次check_testcase(),mode=”before”,并在结束后再执行一次check_testcase(),mode=”after”,每次执行都是调用mysqltest执行一遍check_testcase.test测试用例文件,并将结果保存。执行两次的目的是,检查对比前后执行的结果,防止执行真正的测试用例改变服务器中的数据,保证前后数据的一致性。
check_warnings()检查服务器的输出日志是否存在可疑的日志消息。Test Framework明确100多种通用的需要抑制的警告或者错误信息,其他的输出信息会被当做可疑日志信息,保存在后缀为.warnings的文件中。
3)主进程接收子进程的执行结果(run_test_server())
启动的run_worker与主线程之间是server-client模式,主线程是server,run_worker()是 client。
主线程与run_worker是一问一答模式,主线程向run_worker发送运行用例的文件路径、配置文件参数等各种参数信息,run_worker向主线程返回运行结果,直到所有在collection中的用例都运行完毕,主线程close各run_worker,进行收尾工作。
主线程先读取各run_worker返回值,对上一个用例进行收尾工作。之后,读取collection中的用例,通过本地socket发送到run_worker线程,run_worker线程接收到主线程命令,运行本次用例执行函数run_testcase(),而run_testcase()主要负责3件事:启动mysqld、启动并监控mysqltest,处理执行结果。
- 整体流程
-启动
mysqld:根据参数启动一个或者多个mysqld(start_servers()),在start_servers大多数情况下会拷贝主线程初始化后的目录到run_worker的目录,作为新实例的启动目录,用shell命令启动数据库。
-启动并监控mysqltest:用例在mysqltest中执行(会逐行扫描*.test文件中的SQL或指令并于MySQL中执行),run_worker线程会监控mysqltest的运行状态,监测其是否运行超时或者运行结束。
-处理执行结果:mysqltest执行结束会留下执行日志,框架根据执行日志判断执行是否通过,如果没通过是否需要重试等。
3、MySQL与NCNDB自动测试框架区别


两者的区别见图中红色框处。
二、使用MTR
1、mtr相关文件
MySQL测试框架是一个以MySQL框架和内部引擎为测试对象的工具,主要执行脚本在安装路径下的mysql-test目录,基本覆盖了所有MySQL的特性和异常情况。

它的位置在MySQL源码同级的目录./mysql-test下,使用perl脚本编写。
mysql-test-run.pl:perl脚本,是MySQL最常用的测试工具,负责控制流程,包括启停、识别执行哪些用例、创建文件夹、收集结果等等,主要作用是验证SQL语句在各种场景下是否返回正确的结果。
mysql-stress-test.pl:perl脚本,用于MySQL Server的压力测试。
在mysql-test/目录下还有其他目录,列出其中几个常用的目录:
collections:包含在集成和发布测试期间运行的测试运行的集合
include目录:一些头文件,这些文件在*.test文件中调用,使用source命令引入;
lib目录:是一些库函数,主要被perl脚本调用;
std_data目录:一些标准数据,某些测试用例可以直接拷贝使用即可。
suite目录:保存套装组件的测试用例,方便测试同一类测试用例。
extra目录:一些binlog、replication测试相关的文件。
t目录:默认main套件测试用例
r目录:默认main套件测试用例结果文件\
2、测试用例与预期结果文件
Test Framework通过执行一个测试用例文件,将该测试用例的实际测试结果,与预期结果文件作对比。如果一致,则认为测试通过,无问题;否则,不一致,则测试失败,可以根据输出结果查找问题。
测试用例的输入存储在一个文件中,运行测试的预期结果存储在另一个文件中。测试用例文件主要由SQL语句和命令组成,也可以使用测试语言结构来控制如何运行测试和验证它们的结果。测试用例文件统一放在t/文件夹下,文件名以.test为后缀。测试用例文件支持脚本语言函数。预期结果文件与测试用例文件的大部分代码几乎一致,只是在文件中增加了输出结果。在有输出结果的语句后面(如查询语句select),还将输出结果写在预期结果文件中。预期结果文件统一放在r/文件夹下,文件名以.result为后缀。
测试用例文件应与预期结果文件同名,仅以后缀区分是测试用例文件还是预期结果文件,使用时加不加.test后缀执行都可以。
3、mysqltest编写语法
| 语法 | 功能 |
|---|---|
| –echo xxxxx | 输出信息xxxxx |
| –source xxx.inc | 等效于将目标文件的内容拷贝到当前位置 |
| –error N | 测试出错语句。N为错误号或者宏,指定下一个命令预期返回的一个或多个逗号分隔的error 值。 |
| –send | 向server发送一条query,但并不等待结果,而是立即返回,该query的结果必须由 reap 指令来接收。 |
| –require | 条件要求,需要满足某个条件 |
| connect (name, host_name, user_name, password, db_name [,port_num [,socket [,options [,default_auth [,compression algorithm, [,compression level]]]]]]) | 创建一个到mysql server的新连接并作为当前连接 |
| connection connection_name | 选择 connection_name作为当前连接。 |
| disconnect connection_name | 关闭连接connection_name |
| –enable_query_log | 记录sql语句以及输出结果 |
| –disable_query_log | 不想记录某些sql语句及结果 |
| –exec command [arg] | 执行shell命令 |
| –shutdown_server [timeout] | 停止服务器。如果服务器的进程 ID 文件在超时秒后仍未消失,则该进程将被终止。 |
| dec $var_name | 递减数值变量 |
| inc $var_name | 递增数值变量 |
| –exit | 终止测试用例 |
| –expr | 计算表达式并将结果分配给变量(不支持非整数计算) |
| –let | 为变量赋值;设置环境变量 |
| –lowercase_result | 将SQL语句的输出转换为小写 |
| output file_name | 将下一个SQL语句的输出直接输出到命名文件 |
| perl [terminator] | 使用Perl执行测试文件的以下行。当遇到包含终止符的行时,这些行结束。默认终止符为 EOF,但可以提供不同的终止符。 |
| query [statement] | 将语句发送到服务器执行。 |
| query_get_value(query, col_name, row_num) | 只能在 let 语句中变量分配的右侧使用。第一个参数指示要执行的查询。第二个和第三个参数指示列名和行号,用于指定要从结果集中提取的值。 |
| –send_eval | 评估命令,然后将其发送到服务器。 |
| –sleep num | 睡眠 (sleep num seconds) |
| if (expr) { command list} | / |
| while (expr) { command list} | / |
| call mtr.add_suppression(“xxxxx”); call mtr.add_suppression_for_se(“xxxxx”); |
抑制规则,抑制错误和警告,用于在测试输出中忽略特定的错误或失败情况。 add_suppression: 用于屏蔽计算节点错误信息 add_suppression_for_se:用于屏蔽存储节点日志中可能出现的预期内报错信息 |
3.1 关闭和重启服务器
(1)在启动或停止服务器的操作之前,测试用例应将 restart 或 wait 写入.expect文件。
| 测试用例 | 语法解析 |
|---|---|
| –exec echo “wait” > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect –shutdown_server 10 –source include/wait_until_disconnected.inc # Do something while server is down –exec echo “restart” > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect –source include/wait_until_connected_again.inc |
restart:服务器将立即重新启动 wait:服务器将保持关闭状态,在以后通过将 restart 写入文件来重新启动。 |
(2)关闭和重启的文件
restart_mysqld.inc: 重启服务器,可以使用$shutdown_server_timeout设置关闭超时值
| 测试用例 |
|---|
| # Restart server with extra parameters –let $shutdown_server_timeout= 10 –let $restart_parameters= “restart: --innodb_autoinc_lock_mode=1” –source include/restart_mysqld.inc |
kill_and_restart_mysqld.inc: 关闭后立即重启服务器
| 测试用例 |
|---|
| # Restart server with extra parameters –let $restart_paramters= “restart: --innodb_autoinc_lock_mode=0” –source include/kill_and_restart_mysqld.inc |
shutdown_mysqld.inc and start_mysqld.inc: 分别用于关闭和启动服务器
| 测试用例 |
|---|
| # Timeout of 10 seconds –let $shutdown_server_timeout= 10 –source include/shutdown_mysqld.inc # Do something while the server is down –source include/start_mysqld.inc |
kill_mysqld.inc:设置shutdown timeout的值将不起作用wait_until_disconnected.inc:断开与服务器的所有连接wait_until_connected_again.inc:在服务器再次启动后使用,以确保恢复客户端连接。它会尝试建立连接,直到发生超时。
(3)NCNDB新增有关重启服务器的文件c.inc:重启第一个sekill_first_se:关闭第一个serestart_ce_mysqld.inc:重启ce
4、mtr执行命令
mysql-test-run、mysql-test-run.pl、mtr三个文件一模一样,选择任一文件来执行即可,如./mtr。mtr实际上是调用的mysqltest来进行测试的。
MySQL: mysqltest — 运行测试用例的程序
MySQL: mysql-test-run.pl — 运行 MySQL 测试套件
| 参数名称 | 描述 |
|---|---|
| [什么参数都不加] | 执行t/目录和suits/目录下所有以.test为后缀的测试用例文件(测试时间较长),并且,任何一个测试用例执行失败都导致整个执行计划推出。 |
| –do-test=events | 执行所有以events为前缀的测试用例文件(搜索范围为t/和所有的suite) 如果想测试所有的包括innodb的case,可以用 ./mtr –do-test=.innodb. |
| –skip-test=events | 将以events开头的所有测试用例跳过,支持正则表达式。 |
| –vardir | mtr允许并行执行,需要特别指定不同的日志目录。 |
| –suite=suite_name | suits目录下有多个目录,是一些测试的套餐。此命令单独执行suits/suite_name目录下的所有测试用例文件(其他的目录不执行)。 t/目录下的所有文件组成了默认的套餐main。 因此 ./mtr --suite=main则只执行t/*.test |
| –force | 忽略错误并继续执行直到所有的测试用例执行结束。 |
| –parallel=auto | 以多线程执行测试用例 |
| –record | 生成.result文件。将.test的实际测试结果.reject作为预期结果.result |
| –result-file=file_name | 此选项指定测试用例预期结果的文件。-- result-file与-- record一起决定了mysqltest如何处理测试用例的测试实际结果和预期结果。 |
| –manual-gdb | 以debug模式启动mysqld,适合单个用例。这样的好处是,如果挂了,gdb中会存在相应的堆栈。 |
5、执行结果
5.1 测试执行成功

5.2 测试执行失败

5.3 失败原因
(1)产生的测试结果文件与预期输出文件结果不一致:期望输入的 SQL 执行成功,实际执行失败;期望输入的 SQL 执行失败,实际执行成功。在实际执行时,会将执行结果与.result文件作比较,若不一致,则失败,并在mysql-test/var/log目录生成一个.reject文件。
(2)测试过程中mysql server挂掉。这种情况一般会报“丢失连接”的错误。
(3)测试期间MySQL Server端写入了未过滤的warnings或errors日志。
5.4 异常调试
(1)分析日志
测试过程生成的所有数据都保存在var/目录下,其中var/install.db/目录在首次初始化后生成,后续直接拷贝给后续服务器的启动使用。var/log/目录下保存本次执行的日志文件,包括错误信息、警告信息、初始化过程的参数、执行的测试用例名以及执行时间文件。var/tmp/目录保存执行SQL语句的通过情况,包括两次check-testcase和check-warnings过程。var/mysqld.1/目录保存本次执行生成的数据文件。var/my.cnf是执行测试用例的配置文件。
(2)verbose参数
启动 mtr 时加 --verbose 参数,打印脚本执行信息
(3)--debug 参数和 --gdb参数
(4)perl的调试
添加-d 参数可进入 perl 语言的 debug 模式
- 调试模式常用命令:
| 命令 | 功能 |
|---|---|
| h | 查看帮助文档 |
| c line | 运行到指定行 |
| n | 运行到下一行 |
| s | 跳到函数内部运行 |
| l | 查看代码 |
| q | 查看代码 |
:
| 命令 | 功能 |
|---|---|
| h | 查看帮助文档 |
| c line | 运行到指定行 |
| n | 运行到下一行 |
| s | 跳到函数内部运行 |
| l | 查看代码 |
| q | 查看代码 |




