作者:卢文双 资深数据库内核研发
去年年底通过微信公众号【数据库内核】设定了一个目标——2023 年要写一系列 特性介绍+内核解析 的文章(现阶段还是以 MySQL 为主)。 虽然关注者很少,但本着“说到就要做到”的原则,从这篇就开始了。
序言:
以前对 MySQL 测试框架 MTR 的使用,主要集中于 SQL 正确性验证。近期由于工作需要,深入了解了 MTR 的方方面面,发现 MTR 的能力不仅限于此,还支持单元测试、压力测试、代码覆盖率测试、内存错误检测、线程竞争与死锁等功能,因此,本着分享的精神,将其总结成一个系列。
主要内容如下:
入门篇:工作机制、编译安装、参数、指令示例、推荐用法、添加 case、常见问题、异常调试
进阶篇:高阶用法,包括单元测试、压力测试、代码覆盖率测试、内存错误检测、线程竞争与死锁
源码篇:分析 MTR 的源码
语法篇:单元测试、压力测试、mysqltest 语法、异常调试
由于个人水平有限,所述难免有错误之处,望雅正。
本文是第一篇入门篇 。
本文首发于 2023-03-18 21:58:52
本系列基于 MySQL 8.0.29 版本,且主要在 Ubuntu 22.04 X86_64 验证(部分指令也在 Ubuntu 20.04 X86_64、Ubuntu 22.04 ARM64、MacOS M1 做了验证),如有例外,会特别说明。
简介 在修改内核代码后,不仅需要测试新增功能,同时也要对原有功能做回归测试,以保证新加代码对原有功能没有影响,这就需要用到 MySQL 源码自带的测试框架 mtr。
MySQL 测试框架是一个以 MySQL 框架和内部引擎为测试对象的工具,主要执行脚本在安装路径(make install
后的路径)下的mysql-test
目录,基本覆盖了所有 MySQL 的特性和异常情况。
MySQL 测试框架 mtr 主要包含如下几个组件:
mysql-test-run.pl :perl 脚本,简称 mtr ,是 MySQL 最常用的测试工具,负责控制流程,包括启停、识别执行哪些用例、创建文件夹、收集结果等等,主要作用是验证 SQL 语句在各种场景下是否返回正确的结果。
mysqltest :C++二进制程序,负责执行测试用例,包括读文件、解析特定语法、执行用例。
用例的特殊语法(比如,--source
,--replace_column
等)都在command_names
和enum_commands
两个枚举结构体中。
mysql_client_test :C++二进制程序,用于测试 MySQL 客户端 API(mysqltest 无法用于测试 API)。
从代码看,只有启用--valgrind
或 --valgrind-mysqltest
选项,才会用到 mysql_client_test
。
mysql-stress-test.pl :perl 脚本,用于 MySQL Server 的压力测试。
支持 gcov/gprof 代码覆盖率测试工具。
除此之外,还提供了单元测试工具(严格来说不属于 mtr ),以便为存储引擎和插件创建单独的单元测试程序。
由于 MySQL 测试框架的入口是 mysql-test-run.pl (它会调用上述其他组件),因此,一般将 MySQL 测试框架简称为 mtr 。
mtr 工作原理 概述 mtr 采用t/r
模式(t
目录中存储具体的测试 case,文件以.test
结尾;r
目录中存储了对应 case 的期望结果,文件以.result
结尾),主要测试步骤是“通过执行一个 case,将该 case 的输出结果,与标准的输出结果(期望结果)作 diff”:
如果完全一样,则说明该 case 通过;
反之,则说明该 case 失败。
可能原因:case 本身写的有问题;MySQL 服务有问题。
如果t
目录中的某个 case 在r
目录中没有对应.result
文件:
那么,只要该 case 能正常执行完,mtr 就会判定该 case 通过;
反之,若执行过程中出现 mysql server crash 等异常问题,mtr 就会判定该 case 失败。
上文说的 case 是指一系列的语句,包括 SQL 语句和一些必要的 mysqltest command。
所有 case 可分为三部分,分别为:
main :测试 case 位于 mysql-test/t
目录,期望结果(如果有的话)位于mysql-test/r
目录,二者中的文件是一一对应的,比如:mysql-test/t/alter_debug.test
、mysql-test/r/alter_debug.result
。
suite :路径位于mysql-test/suite
目录,其中包含很多测试 case 的集合,每个集合都是一个单独的子目录(比如 mysql-test/suite/binlog
),在子目录中又分别包含 r、t 两个目录。
extra :应该是对上述两种 case 的补充,位于mysql-test/extra/
目录,在 8.0.29 版本中只包含binlog_tests
、rpl_tests
两个集合。
框架流程 mysql-test-run.pl
框架运行流程如下:
1、初始化(Initialization) 。
确定用例执行范围,包括运行哪些 suite,skip 哪些用例,在本阶段根据disabled.def
文件、--skip-xxx
命令(比如skip-rpl
)等确定执行用例。
同时,初始化数据库。后面运行用例启动数据库时,不需要每次初始化,只需从这里的目录中拷贝启动。
2、运行用例(run test) 。
主线程根据参数--parallel
(默认是 1)启动一个或者多个用例执行线程(worker) ,各线程有自己独立的 client port,data dir 等。
启动的 worker 与主线程之间是 server-client 模式,主线程是 server,worker 是 client。
主线程与 worker 是一问一答模式,主线程向 worker 发送运行用例的文件路径、配置文件参数等各种参数信息,worker 向主线程返回运行结果,直到所有在 collection 中的用例都运行完毕,主线程 close 各 worker,进行收尾工作。
主线程先读取各 worker 返回值,对上一个用例进行收尾工作。之后,读取 collection 中的用例,通过本地 socket 发送到 worker 线程,worker 线程接收到主线程命令,运行本次用例测试的核心逻辑,主要包括 3 件事:启动 mysqld、启动并监控 mysqltest,处理执行结果 。
启动 mysqld : 根据参数启动一个或者多个 mysqld server 进程 ,大多数情况下会拷贝主线程初始化后的目录到 worker 的数据目录,作为新实例的启动目录,用 shell 命令启动数据库。
启动并监控 mysqltest :用例在 mysqltest 中执行(会逐行扫描 *.test
文件中的 SQL 或指令并于 MySQL 中执行 ),worker 线程会监控 mysqltest 的运行状态,监测其是否运行超时或者运行结束。
处理执行结果 :mysqltest 执行结束会留下执行日志,框架根据执行日志判断执行是否通过,如果没通过是否需要重试等。
以 rpl.rpl_multi_source_basic
(对应于文件 mysql-test/suite/rpl/t/rpl_multi_source_basic.test
)测试 case 为例来说明执行过程,用例内容如下(开头注释部分为测试过程):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 --source include/not_group_replication_plugin.inc --source include/have_slave_repository_type_table.inc --echo --echo --echo --let $rpl_topology = 1->2,3->2 --let $rpl_multi_source = 1 --source include/rpl_init.inc --echo --echo --echo --let $rpl_connection_name = server_1 --source include/rpl_connection.inc CREATE DATABASE db1; CREATE TABLE db1.t1 ( a int); ......
启动测试指令 perl mysql-test-run.pl --do-test=rpl_multi_source
后,会启动 3 个 mysqld 进程,其中 2 个 master 节点,1 个 slave 节点:
1 2 3 4 5 6 7 8 9 10 11 ➜ rpl ps -xf | grep mysql 6982 pts/2 S+ 0:00 \_ perl mysql-test-run.pl rpl_multi_source_basic 7125 pts/2 S+ 0:00 \_ perl mysql-test-run.pl rpl_multi_source_basic 7130 pts/2 S+ 0:00 \_ /data/work/mysql/mysql80-install.bak_valgrind/bin//mysqltest_safe_process -- /data/work/mysql/mysql80-install.bak_valgrind/bin/mysqld --defaults-group-suffix=.1 --defaults-file=/data/work/mysql/mysql80-install.bak_valgrind/mysql-test/var/my.cnf --log-output=file --loose-debug-sync-timeout=600 --binlog-format=mixed --core-file 7131 pts/2 Sl 0:04 | \_ /data/work/mysql/mysql80-install.bak_valgrind/bin/mysqld --defaults-group-suffix=.1 --defaults-file=/data/work/mysql/mysql80-install.bak_valgrind/mysql-test/var/my.cnf --log-output=file --loose-debug-sync-timeout=600 --binlog-format=mixed --core-file 7132 pts/2 S+ 0:00 \_ /data/work/mysql/mysql80-install.bak_valgrind/bin//mysqltest_safe_process -- /data/work/mysql/mysql80-install.bak_valgrind/bin/mysqld --defaults-group-suffix=.2 --defaults-file=/data/work/mysql/mysql80-install.bak_valgrind/mysql-test/var/my.cnf --log-output=file --loose-debug-sync-timeout=600 --binlog-format=mixed --core-file 7133 pts/2 Sl 0:06 | \_ /data/work/mysql/mysql80-install.bak_valgrind/bin/mysqld --defaults-group-suffix=.2 --defaults-file=/data/work/mysql/mysql80-install.bak_valgrind/mysql-test/var/my.cnf --log-output=file --loose-debug-sync-timeout=600 --binlog-format=mixed --core-file 7134 pts/2 S+ 0:00 \_ /data/work/mysql/mysql80-install.bak_valgrind/bin//mysqltest_safe_process -- /data/work/mysql/mysql80-install.bak_valgrind/bin/mysqld --defaults-group-suffix=.3 --defaults-file=/data/work/mysql/mysql80-install.bak_valgrind/mysql-test/var/my.cnf --log-output=file --loose-debug-sync-timeout=600 --binlog-format=mixed --core-file 7135 pts/2 Sl 0:04 | \_ /data/work/mysql/mysql80-install.bak_valgrind/bin/mysqld --defaults-group-suffix=.3 --defaults-file=/data/work/mysql/mysql80-install.bak_valgrind/mysql-test/var/my.cnf --log-output=file --loose-debug-sync-timeout=600 --binlog-format=mixed --core-file 7283 pts/2 S+ 0:00 \_ /data/work/mysql/mysql80-install.bak_valgrind/bin//mysqltest_safe_process -- /data/work/mysql/mysql80-install.bak_valgrind/bin/mysqltest --defaults-file=/data/work/mysql/mysql80-install.bak_valgrind/mysql-test/var/my.cnf --silent --tmpdir=/data/work/mysql/mysql80-install.bak_valgrind/mysql-test/var/tmp --character-sets-dir=/data/work/mysql/mysql80-install.bak_valgrind/share/charsets --logdir=/data/work/mysql/mysql80-install.bak_valgrind/mysql-test/var/log --database=test --plugin_dir=/data/work/mysql/mysql80-install.bak_valgrind/lib/plugin --timer-file=/data/work/mysql/mysql80-install.bak_valgrind/mysql-test/var/log /timer --test-file=/data/work/mysql/mysql80-install.bak_valgrind/mysql-test/suite/rpl/t/rpl_multi_source_basic.test --tail-lines=20 --result-file=/data/work/mysql/mysql80-install.bak_valgrind/mysql-test/suite/rpl/r/rpl_multi_source_basic.result 7284 pts/2 R 0:00 \_ /data/work/mysql/mysql80-install.bak_valgrind/bin/mysqltest --defaults-file=/data/work/mysql/mysql80-install.bak_valgrind/mysql-test/var/my.cnf --silent --tmpdir=/data/work/mysql/mysql80-install.bak_valgrind/mysql-test/var/tmp --character-sets-dir=/data/work/mysql/mysql80-install.bak_valgrind/share/charsets --logdir=/data/work/mysql/mysql80-install.bak_valgrind/mysql-test/var/log --database=test --plugin_dir=/data/work/mysql/mysql80-install.bak_valgrind/lib/plugin --timer-file=/data/work/mysql/mysql80-install.bak_valgrind/mysql-test/var/log /timer --test-file=/data/work/mysql/mysql80-install.bak_valgrind/mysql-test/suite/rpl/t/rpl_multi_source_basic.test --tail-lines=20 --result-file=/data/work/mysql/mysql80-install.bak_valgrind/mysql-test/suite/rpl/r/rpl_multi_source_basic.result
可见:
无论是 mysqldtest 还是 mysqld,都是由 mysqltest_safe_process 程序启动的。
--defaults-group-suffix=.1 到 3
分别对应 3 个 mysqld 进程,说明 mtr 不是靠 mock 的形式来测试的,而是启动真 mysqld 进程来测试 。
编译安装 安装依赖 mysql-server 编译需要:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 sudo apt install gdb gcc g++ cmake -y sudo apt install openssl libssl-dev -y sudo apt install libncurses-dev libudev-dev -y sudo apt install bison flex libaio-dev libreadline-dev libjemalloc-dev -y sudo apt install libevent-dev zlib1g-dev libmecab-dev libgcrypt20-dev -y sudo apt install libsasl2-dev libldap2-dev libtirpc-dev sudo apt-get install libsasl2-dev sudo apt-get install slapd ldap-utils sudo apt install valgrind doxygen libcurl4-gnutls-dev -y sudo yum install cmake gcc g++ sudo yum install readline-devel bison flex libarchive openssl-devel sudo yum install rpcgen libudev-devel ncurses-devel libtirpc libtirpc-devel sudo yum install cyrus-sasl-devel sudo yum install openldap openldap-devel sudo yum install valgrind sudo yum install cmake gcc g++ gcc-toolset-12-gcc gcc-toolset-12-gcc-c++ gcc-toolset-12-binutils sudo yum install readline-devel bison flex libarchive openssl-devel sudo yum install rpcgen libudev-devel ncurses-devel libtirpc libtirpc-devel sudo yum install cyrus-sasl-devel sudo yum install openldap openldap-devel sudo yum install valgrind brew install lz4 brew install zlib brew install clang
由于系统及版本差异,这里罗列的软件包可能会有所缺失,版本也可能会有所不同。
对于 mtr 来说,也需要额外安装一些依赖:
1 2 3 4 5 6 7 8 9 yum -y install perl -y sudo yum install perl-JSON -y sudo yum install perl-Test-use-ok.noarch -y sudo apt install perl -y sudo perl -MCPAN -e 'install JSON'
编译 Debug 版本编译选项示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 CURDIR=`pwd ` INSTALLDIR=$CURDIR /../../mysql80-install DATADIR=$CURDIR /../../mysql80-default-data BOOSTDIR=$CURDIR /../../boost_1_77_0 rm CMakeCache.txt -f cmake .. \ -DCMAKE_BUILD_TYPE=Debug \ -DCMAKE_INSTALL_PREFIX=$INSTALLDIR \ -DSYSCONFDIR=/etc \ -DMYSQL_DATADIR=$DATADIR \ -DMYSQL_UNIX_ADDR=/tmp/mysqld.sock \ -DMYSQL_TCP_PORT=3306 \ -DWITH_MYISAM_STORAGE_ENGINE=1 \ -DWITH_INNOBASE_STORAGE_ENGINE=1 \ -DENABLED_LOCAL_INFILE=1 \ -DWITH_DEBUG=1 \ -DWITH_BOOST=$BOOSTDIR \ -DWITH_SSL=/usr/local /openssl-1.1.1 \ -DFORCE_INSOURCE_BUILD=1 if [ $? != 0 ]; then exit 1 fi make -j4 make install
Release 版本编译选项示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #!/bin/bash CURDIR=`pwd ` INSTALLDIR=$CURDIR /../../mysql80-install DATADIR=$CURDIR /../../mysql80-default-data BOOSTDIR=$CURDIR /../../boost_1_77_0 rm CMakeCache.txt -f cmake .. \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_CONFIG=mysql_release \ -DFEATURE_SET=community \ -DWITH_EMBEDDED_SERVER=OFF \ -DWITHOUT_ROCKSDB=ON \ -DWITH_UNIT_TESTS=OFF \ -DWITH_BOOST=$BOOSTDIR \ -DFORCE_INSOURCE_BUILD=1 \ -DCOMPILATION_COMMENT="MySQL build $(date +%Y%m%d.%H%M%S.$(git rev-parse --short HEAD) )" if [ $? != 0 ]; then exit 1 fi make -j4 make install
-DCMAKE_BUILD_TYPE=type
选项说明:
The type of build to produce:
RelWithDebInfo
: default value 。Enable optimizations and generate debugging information . This is the default MySQL build type.
Release
: Enable optimizations but omit debugging information to reduce the build size. This build type was added in MySQL 8.0.13 (MySQL 5.7 is not supported).
Debug
: Disable optimizations and generate debugging information. This build type is also used if the WITH_DEBUG
option is enabled. That is, -DWITH_DEBUG=1
has the same effect as -DCMAKE_BUILD_TYPE=Debug
.
目录结构 编译安装后,mysql-test
目录树结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 mysql-test ├── README ├── README.gcov ├── README.stress ├── collections │ ├── README │ ├── coverage.ignore │ ├── disabled.def │ ├── disabled-asan.list │ ├── disabled-ubsan.list │ ├── disabled-valgrind.list │ ├── disabled_ndb.def │ ├── default.daily │ ├── default.daily-valgrind │ ├── default.weekly │ ├── default.weekly-ndbcluster │ ├── default.weekly-protocol │ ├── default.weekly-valgrind │ ├── default.weekly.basic │ ├── default.push │ ├── default.push-ndbcluster │ ├── default.push-valgrind │ ├── mysql-8.0-stage.push │ ├── mysql-8.0-stage.push.basic │ ├── mysql-trunk-meb-itch.push │ ├── mysql-trunk-stage.push │ ├── mysql-trunk-stage.push.basic │ └── mysql-trunk-tsan.push ├── extra │ ├── binlog_tests │ │ ├── binlog.test │ │ ├── binlog_cache_stat.test │ │ ├── binlog_crash_safe_ddl.inc │ │ ├── binlog_ddl.inc ...... │ │ └── tmp_table.test │ └── rpl_tests │ ├── binlog_transaction_compression.inc │ ├── check_slave_delay.inc ...... │ └── type_conversions.test ├── lib │ ├── My │ │ ├── Config.pm │ │ ├── ConfigFactory.pm ...... │ │ └── Test.pm │ ├── mtr_cases.pm ...... ├── lock_order_dependencies.txt ├── asan.supp ├── lsan.supp ├── tsan.supp ├── valgrind.supp ├── mtr -> ./mysql-test-run.pl ├── mysql-stress-test.pl ├── mysql-test-run -> ./mysql-test-run.pl ├── mysql-test-run.dox ├── mysql-test-run.pl ├── include │ ├── Load_data.inc ...... │ ├── json_lookup.inc │ ├── keyring_tests │ │ ├── binlog │ │ │ ├── rpl_binlog_cache_encryption.inc ...... │ ├── keyring_udf_keyring_plugin_loaded.inc ...... │ └── year-engine.test ├── t │ ├── 1st.test │ ├── admin_interface.test ...... ├── r │ ├── 1st.result │ ├── admin_interface.result ...... │ └── year-myisam.result ├── std_data │ ├── 14897.frm │ ├── 256kb.json │ ├── 41_decimal.frm │ ├── 57import.zip ...... │ └── x_y_data.csv ├── suite │ ├── audit_null ...... │ ├── innodb │ │ ├── include │ │ │ ├── alter_table_pk_no_sort.inc ...... │ │ ├── r │ │ │ ├── add_foreign_key.result │ │ │ ├── alter_crash.result ...... │ │ └── t │ │ ├── add_foreign_key.test │ │ ├── alter_crash.test ...... │ │ └── zlob_update_purge.test │ ├── innodb_fts ...... └── var ├── data │ ├── ...... ...... ├── my.cnf ├── run ├── std_data │ ├── 14897.frm ...... └── tmp └── mysqld.1
参数 参考:
常用参数
--force
默认情况下,只要遇到一个 case 出错,测试程序就会退出。
加入该参数后,mtr 会忽略错误并继续执行下一个 case 直到所有 case 执行结束再退出。
但如果脚本存在太多错误还是会退出,可设置--max-test-fail=0
忽略计数。
--max-test-fail
测试过程中失败 case 数达到一定值会退出,默认值是 10,设置为 0 则会忽略计数。
--record
是否记录 results 结果,首次执行建议带上,让其自动生成 .results
文件,再基于该文件修改成我们预期的结果 。
若一个执行输出结果和 testname.result
文件不同,会生成一个 testname.reject
文件,该文件在下次执行成功之后被删除;
检查.reject
文件的内容,如果里面是期望的输出,则将内容拷贝到 .result
文件中,作为以后判断运行结果是否通过的依据;
--parallel
—-nowarnings
忽略 warnings 错误。
设置该参数后,当出现 warnings 错误,不再累加 --max-test-fail
。
--big-test
执行标记为 big
的 test cases,也就是同时覆盖 非 big + big 。这是因为标记为 big 的 case 较大、耗时较长,默认不会执行。
--only-big-test
:只启用带 big 标记的 test cases,也就是会跳过普通的非 big 标记的 cases。
--suite=[suitename1,...]
默认情况下 mtr 会执行所有测试 case,但有时候我们要执行一个测试集,就可用该参数来指定,比如./mtr --suite=rpl
只执行 rpl 测试集。
--do-test=events
执行所有以 events
为前缀的 case(搜索范围为 t/和所有的 suite)。
--do-test
的参数支持正则表达式,上述命令等效于 ./mtr --do-test=events.*
所以如果想测试所有的包括 innodb 的 case,可以用 ./mtr --do-test=.*innodb.*
连接远程的数据库进行 mtr 执行:
1 2 3 4 ./mtr --extern host=192.168.6.1 --extern port=3306 --extern user=root --extern password='123456' --record --force example.1 ./mtr --extern host=127.0.0.1 --extern port=3306 --extern user=root --extern password= --force --max-test-fail=0 --suite=main ./mtr --extern host=127.0.0.1 --extern port=3306 --extern user=root --extern password= --force --max-test-fail=0 --fast --suite=main
--debug-server
:Use debug version of server, but without turning on tracing.
--platform
和 --exclude-platform
:用于指定或排除平台的选项。
如果 MTR 不是运行在 pushbuild test 环境中(存在环境变量PB2WORKDIR
,即export PB2WORKDIR=
),这两个选项是不生效的。
comment=STR
:添加该选项后,mtr 会将注释信息打印到 stdout 。比如 --comment=all-default-big
:
--vardir=DIR
:指定测试过程中生成的文件存放的目录,默认是当前路径下的var/
。
--report-features
:指定该选项后,mtr 首先运行名为 report_features
的 case,该 case 没有任何输出(设置了--disable_query_log
) 。
--unit-tests-report
:加上该参数后,如果在编译后的源码目录执行 mtr,会在测试的最后阶段加上每个测试用例的报告信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 ...... [----------] 1027 tests from Spec/ReuseConnectionTest (404 ms total) [----------] Global test environment tear-down [==========] 1027 tests from 1 test suite ran. (70804 ms total) Total Test time (real) = 3363.87 sec ...... The following tests FAILED: 203 - routertest_component_metadata_ttl (Subprocess aborted) 206 - routertest_component_rest_api_enable (Failed) 222 - routertest_component_routing_splicer (Failed) 224 - routertest_integration_routing_reuse (Failed) Errors while running CTest [ FAILED ] CheckEdgeHttpsPortValues/UseEdgeHttpsPortValues.ensure_bootstrap_works_for_edge_https_port_values/1, where GetParam() = 65535 (1444 ms) [ FAILED ] 1 test , listed below: [ FAILED ] CheckEdgeHttpsPortValues/UseEdgeHttpsPortValues.ensure_bootstrap_works_for_edge_https_port_values/1, where GetParam() = 65535 1 FAILED TEST [ FAILED ] Spec/SplicerFailParamTest.fails/client_ssl_dh_params_not_exists, where GetParam() = 64-byte object <C8-CE 3D-2F 4D-56 00-00 F0-DA BB-30 4D-56 00-00 B0-DB BB-30 4D-56 00-00 B0-DB BB-30 4D-56 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 3D-D1 F9-2E 4D-56 00-00 AD-D0 F9-2E 4D-56 00-00> (383 ms) [ FAILED ] 1 test , listed below: [ FAILED ] Spec/SplicerFailParamTest.fails/client_ssl_dh_params_not_exists, where GetParam() = 64-byte object <C8-CE 3D-2F 4D-56 00-00 F0-DA BB-30 4D-56 00-00 B0-DB BB-30 4D-56 00-00 B0-DB BB-30 4D-56 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 3D-D1 F9-2E 4D-56 00-00 AD-D0 F9-2E 4D-56 00-00> 1 FAILED TEST [ FAILED ] 0 tests, listed below: 0 FAILED TESTS [ FAILED ] Spec/ReuseConnectionTest: SetUpTestSuite or TearDownTestSuite 1 FAILED TEST SUITE Unit tests: 98% tests passed, 4 tests failed out of 224 The following tests FAILED: 206 - routertest_component_rest_api_enable (Failed) 222 - routertest_component_routing_splicer (Failed) 224 - routertest_integration_routing_reuse (Failed) Report from unit tests in /data/work/mysql/mysql-server/mysql-test/var-all-default-big/ctest.log ------------------------------------------------------------------------------ The servers were restarted 3 times The servers were reinitialized 0 times Spent 53.181 of 3579 seconds executing testcases Completed: Failed 1/6 tests, 83.33% were successful. Failing test (s): unit_tests
--no-skip
:指定该选项后,即使 .inc
文件中要求的条件不满足,也会运行所有的 mtr 测试 cases 。特别地,在 include/excludenoskip.list
文件中指定的 .inc
文件列表依然会跳过。
--skip-ndb
:与选项--skip-ndbcluster
含义相同,表示跳过与 ndb 相关的 suites,默认启用。
ndb 引擎也是开源的(storage/ndb/
),涉及 ndb 引擎的 suites 包括:
1 2 3 4 5 6 7 8 9 10 - ndb - ndb_big - ndb_opt - ndb_ddl - ndb_binlog - ndb_rpl - rpl_ndb - ndbcluster - gcol_ndb - json_ndb
--with-ndb-only
:与选项--with-ndbcluster-only
含义相同,只运行与 ndb 相关的 suites 。如果没显示指定--suites
参数,则会跳过所有非 ndb 的 suites ;反之,若指定了,也会额外运行指定的 suites 。
--ps-protocol
:在 client 和 server 端之间使用 prepared-statement 协议(binary),会将--ps-protocol
参数直接传给 mysqltest 程序。
--skip-combinations
:忽略组合文件或选项,也就是忽略:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Collecting tests - Adding combinations for binlog - Adding combinations for binlog_gtid - Adding combinations for binlog_nogtid - Adding combinations for rpl - Adding combinations for rpl_gtid - Adding combinations for rpl_nogtid ./suite/rpl_nogtid/combinations ./suite/binlog_gtid/combinations ./suite/binlog/combinations ./suite/rpl/combinations ./suite/rpl_gtid/combinations ./suite/binlog_nogtid/combinations ./suite/ndb_rpl/t/ndb_rpl_innodb2ndb.combinations ./suite/ndb_rpl/t/ndb_rpl_conflict_epoch.combinations ./suite/ndb_rpl/t/ndb_rpl_basic.combinations
suitename 可选范围 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 main, audit_null, auth_sec, binlog, binlog_gtid, binlog_nogtid, clone ,collations, component_keyring_file, connection_control, encryption, engines, engines/funcs, engines/iuds, engines/rr_trx, federated, funcs_1, funcs_2, gcol, gis, group_replication, information_schema, innodb, innodb_fts, innodb_gis, innodb_stress, innodb_undo, innodb_zip, interactive_utilities, jp, json, large_tests, lock_order, max_parts, memcached, network_namespace, opt_trace, parts,parts/special_tests, perfschema, query_rewrite_plugins, rpl, rpl_gtid, rpl_nogtid, secondary_engine, service_status_var_registration, service_sys_var_registration, service_udf_registration, special, stress, sys_vars, sysschema, test_service_sql_api, test_services, x
suites 分类 default suites: 1 2 3 4 5 6 7 8 9 auth_sec,binlog,binlog_gtid,binlog_nogtid,clone , collations,component_keyring_file,connection_control,encryption, federated,funcs_2,gcol,gis,information_schema, innodb,innodb_fts,innodb_gis,innodb_undo,innodb_zip, interactive_utilities,json, main, opt_trace,parts,perfschema,query_rewrite_plugins,rpl,rpl_gtid,rpl_nogtid,secondary_engine, service_status_var_registration,service_sys_var_registration,service_udf_registration, sys_vars,sysschema,test_service_sql_api,test_services,x
非 default suites: 1 2 3 funcs_2, stress, jp, nist engines, memcached, audit_null group_replication
指令示例 mtr 执行路径:
代码覆盖率、单元测试只能在 编译的源码目录/mysql-test
执行。
其他测试在 编译的源码目录/mysql-test
和 安装目录/mysql-test
都可以执行。
如无特殊需求,更建议在安装目录执行 mtr 测试 (目录结构更清晰)。
常用指令:
在未编写 .result
文件的情况下,可先通过--reocrd
选项生成.result
文件,再基于该文件修改成期望的结果:
1 perl mysql-test-run.pl --record mytest
常态下执行,不加 --reocrd
选项,这样才会比对实际结果与期望结果是否相同:
1 2 perl mysql-test-run.pl mytestcase1 perl mysql-test-run.pl --suites=main,rpl
当一些测试 case 频繁失败时,可单独运行这些 case 以便调试:
1 2 3 4 5 6 7 8 9 ./mtr testcasename --record ./mtr --force --big-test --nowarnings --max-test-fail=0 main.subquery_all ./mtr --force --big-test --nowarnings --max-test-fail=0 main.subquery_all main.myisam_explain_json_non_select_none
如果不指定任何 suite,mtr 默认会执行所有 default suites(包括 main) :
执行 main suite 中的所有 case(所有mysql-test/t/*.test
),忽略中间的 warnings 报错,强制运行完所有 case:
1 2 3 ./mtr --suite=main --force --max-test-fail=0 --nowarnings --parallel=8 ./mtr --suite=main --force --max-test-fail=0 --nowarnings --parallel=8 --big-test
执行所有以 events
为前缀的 case,搜索范围为 mysql-test/t
、mysql-test/suite
,注意不包括extra/
:
1 2 3 4 5 ./mtr --do-test=events --force --max-test-fail=0
特殊用法:
1 2 3 --fast Do not perform controlled shutdown when servers need to be restarted or at the end of the test run. This is equivalent to using --shutdown-timeout=0.
3、添加后,虽然不重启了,但会导致一些 case 失败。 这是因为有些 case 需要初始化一些参数 :
1 2 3 4 Note If a test case has an .opt file that requires the server to be restarted with specific options, the file will not be used. The test case likely will fail as a result.
可见,官方对这种用法的支持尚不完善 。
推荐用法 如果需要验证 release 版本稳定性(适用于 QA、研发),可参考 default.daily
中的指令集。
该指令集覆盖了单元测试(必须以 DEBUG 编译)、压力测试等。
如何添加测试用例? 1. 示例一 我们通过一个最简单的例子来说明这个框架是怎么使用的。
1.1. 创建测试用例 在 mysql-test/t
目录下创建一个文件名为 mytest.test
的测试用例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 DROP TABLE IF EXISTS t1;SET @@sql _mode= 'NO_ENGINE_SUBSTITUTION' ;SET SQL_WARNINGS= 1 ;CREATE TABLE t1 (a INT );INSERT INTO t1 VALUES (1 );INSERT INTO t1 VALUES (2 );SELECT * FROM t1;DROP TABLE t1;
在mysql-test/r
目录下创建名为mytest.result
的文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 DROP TABLE IF EXISTS t1;SET @@sql _mode= 'NO_ENGINE_SUBSTITUTION' ;SET SQL_WARNINGS= 1 ;# # test content # CREATE TABLE t1 (a INT );INSERT INTO t1 VALUES (1 );INSERT INTO t1 VALUES (2 );SELECT * FROM t1;a 1 2 DROP TABLE t1;
可见,.result
文件中不仅要记录 SQL,还要记录输出结果。
1.2. 执行测试,成功 指令:
1 2 cd mysql80-debug/mysql-test./mtr main.mytest
输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 Logging: ./mtr main.mytest MySQL Version 8.0.29 Checking supported features - Binaries are debug compiled Using 'all' suites Collecting tests Checking leftover processes Removing old var directory Creating var directory '/Users/wslu/work/mysql/mysql80-debug/mysql-test/var' Installing system database Using parallel: 1 ============================================================================== TEST NAME RESULT TIME (ms) COMMENT ------------------------------------------------------------------------------ [ 50%] main.mytest [ pass ] 63 [100%] shutdown_report [ pass ] ------------------------------------------------------------------------------ The servers were restarted 0 times The servers were reinitialized 0 times Spent 0.063 of 16 seconds executing testcases Completed: All 2 tests were successful.
看到 successful 说明执行成功。
1.3. 修改 result 文件 在 mytest.result
文件中添加一些字符:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 DROP TABLE IF EXISTS t1; SET @@sql_mode='NO_ENGINE_SUBSTITUTION' ; SET SQL_WARNINGS=1; CREATE TABLE t1 (a INT); INSERT INTO t1 VALUES (1); INSERT INTO t1 VALUES (2); SELECT * FROM t1; a 1 2 DROP TABLE t1;
1.4. 再次执行测试,失败 再次执行指令./mtr main.mytest
,可见# new comment
那一行报错:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = TEST NAME RESULT TIME (ms) COMMENT [ 50 % ] main.mytest [ fail ] Test ended at 2023 -03 -20 15 :07 :50 CURRENT_TEST: main.mytest + + + / Users/ wslu/ work/ mysql/ mysql80- debug.bak_asan_ubsan_gcov/ mysql- test/ var/ log/ mytest.reject 2023 -03 -20 10 :07 :50.000000000 + 0300 @@ -7 ,7 + 7 ,7 @@ CREATE TABLE t1 (a INT ); INSERT INTO t1 VALUES (1 ); INSERT INTO t1 VALUES (2 ); - SELECT * FROM t1; # new comment+ SELECT * FROM t1; a 1 2 mysqltest: Result length mismatch The result from queries just before the failure was: DROP TABLE IF EXISTS t1;SET @@sql _mode= 'NO_ENGINE_SUBSTITUTION' ;SET SQL_WARNINGS= 1 ;# # test content # CREATE TABLE t1 (a INT );INSERT INTO t1 VALUES (1 );INSERT INTO t1 VALUES (2 );SELECT * FROM t1;a 1 2 DROP TABLE t1;safe_process[19130 ]: Child process: 19131 , exit: 1 - the logfile can be found in '/Users/wslu/work/mysql/mysql80-debug.bak_asan_ubsan_gcov/mysql-test/var/log/main.mytest/mytest.log' [100 % ] shutdown_report [ pass ]
mtr 会指出具体是哪行导致的 case 失败。
常见问题 FAQ test case failed 原因
产生的测试结果文件与预期输出文件 diff 结果不一致:
期望输入的 SQL 执行成功,实际执行失败。
期望输入的 SQL 执行失败,实际执行成功。
比如:mysql-test/t/select_all.test
这个测试 case,其预期结果在mysql-test/r/select_all.result
,在实际执行时,会将执行结果与mysql-test/r/select_all.result
作比较,若不一致,则失败,并在mysql-test/var/log
目录生成一个.reject
文件。
测试过程中 mysql server 挂掉。这种情况一般会报“丢失连接”的错误。
测试期间 MySQL Server 端写入了未过滤的 warnings 或 errors 日志。
此外,测试用例可以执行外部程序,因此在某些方面,测试框架可以扩展为测试 SQL 语句以外的用途。 最后,可以在测试中嵌入一小段 Perl 代码。这有时可用于执行超出测试语言或 SQL 能力的操作或执行逻辑。
可使用一些技巧来定为具体的错误原因,详见下节。
异常调试 分析日志 默认情况下,在目录 mysql-test/var/log/
中有日志生成(若指定 --vardir
参数,则以该参数路径为准),分析该日志也能得到一些有用信息。
比如 启动失败,则可以查看 bootstrap.log
文件,去掉命令中的 --bootstrap
并运行即可启动对应的 MySQL 服务来验证、调试。
verbose 参数 启动 mtr 时加 --verbose
参数,定位到引用的脚本位置后可以配置 --echo
命令修改调试。
如果加上 --verbose
打印的内容还不够详细,可以再加一个,即 --verbose --verbose
,能打印出 mtr perl 脚本中的日志信息。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 wslu@ubuntu:/data/work/mysql/mysql80-install.bak_asan_ubsan/mysql-test$ perl mysql-test-run.pl --timer --force --parallel=1 --vardir=var-rpl --suite=rpl --verbose Logging: mysql-test-run.pl --timer --force --parallel=1 --vardir=var-rpl --suite=rpl --verbose > exe_name: mysqld MySQL Version 8.0.29 Checking supported features - Binaries are debug compiled > Testing FIPS: --test-ssl-fips-mode 0 error:0F06D065:common libcrypto routines:FIPS_mode_set:fips mode not supported Using suite(s): rpl Collecting tests > Collecting: rpl > suitedir: /data/work/mysql/mysql80-install.bak_asan_ubsan/mysql-test/suite/rpl > testdir: /data/work/mysql/mysql80-install.bak_asan_ubsan/mysql-test/suite/rpl/t > resdir: /data/work/mysql/mysql80-install.bak_asan_ubsan/mysql-test/suite/rpl/r > Read combinations file /data/work/mysql/mysql80-install.bak_asan_ubsan/mysql-test/suite/rpl/combinations. - Adding combinations for rpl > Collecting: i_rpl Removing old var directory > opt_vardir: /data/work/mysql/mysql80-install.bak_asan_ubsan/mysql-test/var-rpl > Removing /data/work/mysql/mysql80-install.bak_asan_ubsan/mysql-test/var > Removing /data/work/mysql/mysql80-install.bak_asan_ubsan/mysql-test/var-rpl/ > Removing /data/work/mysql/mysql80-install.bak_asan_ubsan/mysql-test/var-rpl/tmp/ Creating var directory '/data/work/mysql/mysql80-install.bak_asan_ubsan/mysql-test/var-rpl' > Creating /data/work/mysql/mysql80-install.bak_asan_ubsan/mysql-test/var-rpl Installing system database Using parallel: 1 ============================================================================== TEST NAME RESULT TIME (ms) COMMENT ------------------------------------------------------------------------------ > Client connected worker[1] > mtr_ping_port: 13000 worker[1] > FREE worker[1] > mtr_ping_port: 13001 worker[1] > FREE worker[1] > mtr_ping_port: 13002 worker[1] > FREE worker[1] > mtr_ping_port: 13003 worker[1] > FREE ...... worker[1] > mtr_ping_port: 13029 worker[1] > FREE worker[1] > Using MTR_BUILD_THREAD 300, with reserved ports 13000..13029 worker[1] Creating var directory '/data/work/mysql/mysql80-install.bak_asan_ubsan/mysql-test/var-rpl' worker[1] > result: , file_mode: 0 [ 0%] rpl.rpl_atomic_ddl [ skipped ] Test needs 'big-test' or 'only-big-test' option. [ 0%] rpl.rpl_atomic_ddl_no_binlog [ skipped ] Test needs 'big-test' or 'only-big-test' option. [ 0%] rpl.rpl_binlog_cache_encryption [ skipped ] Test needs 'big-test' or 'only-big-test' option. [ 0%] rpl.rpl_filters_error_cases_on_startup [ skipped ] Test needs 'big-test' or 'only-big-test' option. [ 0%] rpl.rpl_group_commit_deadlock [ skipped ] Test needs 'big-test' or 'only-big-test' option. [ 0%] rpl.rpl_group_commit_deadlock_myisam [ skipped ] Test needs 'big-test' or 'only-big-test' option. [ 0%] rpl.rpl_innodb_auto_increment [ skipped ] Test needs 'big-test' or 'only-big-test' option. [ 0%] rpl.rpl_killed_ddl [ skipped ] Test needs 'big-test' or 'only-big-test' option. [ 0%] rpl.rpl_log_info_repository_persistence_assign_gtids_to_anonymous_transactions [ skipped ] Test needs 'big-test' or 'only-big-test' option. [ 0%] rpl.rpl_log_info_repository_persistence_require_row [ skipped ] Test needs 'big-test' or 'only-big-test' option. [ 0%] rpl.rpl_log_info_repository_persistence_require_table_primary_key_check [ skipped ] Test needs 'big-test' or 'only-big-test' option. [ 0%] rpl.rpl_row_crash_safe [ skipped ] Test needs 'big-test' or 'only-big-test' option. [ 0%] rpl.rpl_row_mts_rec_crash_safe [ skipped ] Test needs 'big-test' or 'only-big-test' option. [ 0%] rpl.rpl_stm_mixed_crash_safe [ skipped ] Test needs 'big-test' or 'only-big-test' option. [ 0%] rpl.rpl_stm_mixed_mts_rec_crash_safe [ skipped ] Test needs 'big-test' or 'only-big-test' option. [ 0%] rpl.rpl_stm_mixed_mts_rec_crash_safe_checksum [ skipped ] Test needs 'big-test' or 'only-big-test' option. [ 0%] rpl.rpl_io_thd_wait_for_disk_space_stress [ disabled ] BUG [ 0%] rpl.rpl_writeset_add_unique_key [ disabled ] Bug worker[1] > Running test : rpl.rpl_plugin_load worker[1] > Setting timezone: GMT-3 worker[1] > Cleaning datadirs... worker[1] > clean_dir: /data/work/mysql/mysql80-install.bak_asan_ubsan/mysql-test/var-rpl/tmp worker[1] > unlink: '/data/work/mysql/mysql80-install.bak_asan_ubsan/mysql-test/var-rpl/tmp/bootstrap.sql' worker[1] > Generating my.cnf from '/data/work/mysql/mysql80-install.bak_asan_ubsan/mysql-test/suite/rpl/my.cnf' worker[1] > MASTER_MYPORT = 13000 worker[1] > MASTER_MYSOCK = /data/work/mysql/mysql80-install.bak_asan_ubsan/mysql-test/var-rpl/tmp/mysqld.1.sock worker[1] > MASTER_X_MYSOCK = /data/work/mysql/mysql80-install.bak_asan_ubsan/mysql-test/var-rpl/tmp/mysqlx.1.sock worker[1] > SLAVE_MYPORT = 13002 worker[1] > SLAVE_MYSOCK = /data/work/mysql/mysql80-install.bak_asan_ubsan/mysql-test/var-rpl/tmp/mysqld.2.sock worker[1] > SLAVE_X_MYSOCK = /data/work/mysql/mysql80-install.bak_asan_ubsan/mysql-test/var-rpl/tmp/mysqlx.2.sock worker[1] > mysqld_start: [' --plugin-dir=/data/work/mysql/mysql80-install.bak_asan_ubsan/lib/plugin' , '--binlog-format=mixed ' ] worker[1] > Started [mysqld.1 - pid: 61921, winpid: 61921] worker[1] > mysqld_start: [' --plugin-dir=/data/work/mysql/mysql80-install.bak_asan_ubsan/lib/plugin' , '--binlog-format=mixed ' ] ......
debug 参数和 gdb 参数 mtr 支持的一些 debug 参数:
1 2 3 4 5 6 7 8 9 10 11 debug Dump trace output for all servers and client programs. debug-common Same as debug, but sets 'd' debug flags to "query,info,error,enter,exit" ; you need this if you want both to see debug printouts and to use DBUG_EXECUTE_IF. debug-server Use debug version of server, but without turning on tracing. debugger=NAME Start mysqld in the selected debugger. gdb Start the mysqld(s) in gdb. lldb Start the mysqld(s) in lldb.
可见,要想跟踪调用过程,只有 --debug
和 --gdb
参数满足要求,会生成 trace 信息。
示例:
1 2 3 4 5 ./mtr --debug --suite=rpl ./mtr --gdb --suite=rpl ./mtr --debug --gdb --suite=rpl
指令执行后,生成 trace 文件,比如 var/log/bootstrap.trace
。
脚本自身支持 debug 参数 如果引用(source
)的脚本支持 debug 参数,比如常用的 $rpl_debug
,则可以修改相应的 .inc
文件以获得更多的 debug 信息。
perl 的调试模式 添加-d
参数可进入 perl 语言的 debug 模式,便于调试 mysql-test-run.pl
及其调用。示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 wslu@ubuntu:/data/work/mysql/mysql80-install.bak_asan_ubsan/mysql-test$ perl -d mysql-test-run.pl --timer --force --parallel=1 --vardir=var-rpl --suite=rpl Loading DB routines from perl5db.pl version 1.60 Editor support available. Enter h or 'h h' for help , or 'man perldebug' for more help . main::(mysql-test-run.pl:54): push @INC, "." ; DB<1> l 54==> push @INC, "." ; 55 56: use My::ConfigFactory; 57: use My::CoreDump; 58: use My::File::Path; 59: use My::Find; 60: use My::Options; 61: use My::Platform; 62: use My::SafeProcess; 63: use My::SysInfo; DB<1> n main::(mysql-test-run.pl:72): require "lib/mtr_gcov.pl" ; DB<1> l 72==> require "lib/mtr_gcov.pl" ; 73: require "lib/mtr_gprof.pl" ; 74: require "lib/mtr_io.pl" ; 75: require "lib/mtr_lock_order.pl" ; 76: require "lib/mtr_misc.pl" ; 77: require "lib/mtr_process.pl" ; 78 79: our $secondary_engine_support = eval 'use mtr_secondary_engine; 1' ; 80 81 DB<1>
调试模式常用命令:
1 2 3 4 5 6 h 查看帮助文档 c line 运行到指定行 n 运行到下一行 s 跳到函数内部运行 l 查看代码 q 退出
欢迎关注我的微信公众号【数据库内核】:分享主流开源数据库和存储引擎相关技术。