海山数据库(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 | 查看代码 |