特性介绍 | MySQL 测试框架 MTR 系列教程(一):入门篇

作者:卢文双 资深数据库内核研发

去年年底通过微信公众号【数据库内核】设定了一个目标——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_namesenum_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.testmysql-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_testsrpl_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
# This is the basic test required in for multisource replication
# The aim of this file is to test the basic usecases of msr.
# 0. Create two masters and a slave and setup a multisource replication
# between them.
# 1. create a different databases on each master and test if they are replicated
# to the slave.
# 2. create a different table on each master and test if they are replicated to
# the to the slave.
# 3. Create a table with the same name on both masters and update non conflicting
# data on that table. Test if the replication is done properly.
# 4. Check if updates happen on different master such that the resulting
# data on slave is conflicting, check that one of the channels the slave
# SQL thread is stopped.
#
#
# Note: Out of convention, server 2 is always made a slave for multisource testing.
#

#Skip on group replication runs
--source include/not_group_replication_plugin.inc
# Test requires master-info-repository=TABLE, relay-log-info-repository=TABLE
--source include/have_slave_repository_type_table.inc

--echo #
--echo # set up masters server_1 and server_3 with server_2 being a slave.
--echo #.
--let $rpl_topology= 1->2,3->2
--let $rpl_multi_source= 1
--source include/rpl_init.inc

--echo #
--echo # Test case 1: 1.a) create a database and table db1.t1 on server_1
--echo # and insert values in the table.
--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

可见:

  1. 无论是 mysqldtest 还是 mysqld,都是由 mysqltest_safe_process 程序启动的。
  2. --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
# for mysql 8.0

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 # SASL
sudo apt-get install slapd ldap-utils # LDAP
sudo apt install valgrind doxygen libcurl4-gnutls-dev -y # extra

# centos 7.6
sudo yum install cmake gcc g++ # 由于 cmake、gcc 版本偏低,需要自行通过源码编译安装
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 # SASL
sudo yum install openldap openldap-devel # LDAP
sudo yum install valgrind # extra

# centos stream 9
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 # libtirpc-devel
sudo yum install rpcgen libudev-devel ncurses-devel libtirpc libtirpc-devel
sudo yum install cyrus-sasl-devel # SASL
sudo yum install openldap openldap-devel # LDAP
sudo yum install valgrind # extra

# macos
brew install lz4
brew install zlib
brew install clang

由于系统及版本差异,这里罗列的软件包可能会有所缺失,版本也可能会有所不同。

对于 mtr 来说,也需要额外安装一些依赖:

1
2
3
4
5
6
7
8
9
# centos
yum -y install perl -y
sudo yum install perl-JSON -y
sudo yum install perl-Test-use-ok.noarch -y

# ubuntu
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
# for MacOS and Ubuntu
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 \ # 必须是 debug 版本
-DWITH_BOOST=$BOOSTDIR \
-DWITH_SSL=/usr/local/openssl-1.1.1 \
-DFORCE_INSOURCE_BUILD=1

# -DWITH_ASAN=ON -DWITH_ASAN_SCOPE=ON -DWITH_UBSAN=ON \ # 选择启用哪些组件
# -DWITH_VALGRIND=ON \
# -DENABLE_GCOV=1 -DENABLE_GPROF=1 \

if [ $? != 0 ]; then
exit 1
fi

# for MacOS, only need make
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
# for MacOS and Ubuntu
CURDIR=`pwd`

# for 8.0.29
INSTALLDIR=$CURDIR/../../mysql80-install
#INSTALLDIR=/usr
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))"

#-DCMAKE_INSTALL_PREFIX=$INSTALLDIR \
#-DSYSCONFDIR=/etc \
#-DMYSQL_DATADIR=$DATADIR \
#-DWITH_MYISAM_STORAGE_ENGINE=1 \
#-DWITH_INNOBASE_STORAGE_ENGINE=1 \
#-DWITH_MEMORY_STORAGE_ENGINE=1 \
#-DWITH_PARTITION_STORAGE_ENGINE=1 \

if [ $? != 0 ]; then
exit 1
fi
# for MacOS, only need make
make -j4
make install

-DCMAKE_BUILD_TYPE=type 选项说明:

The type of build to produce:

  • RelWithDebInfo: default valueEnable 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 # 代码覆盖率测试说明,最后更新于2006年
├── README.stress # 压力测试说明,针对 mysql-stress-test.pl ,最后更新于2006年

├── collections # 该目录下的文件是官方推荐的回归测试指令集
│ ├── README # 说明文档
│ ├── coverage.ignore # 指定需要忽略代码覆盖率测试的目录
│ ├── disabled.def # 列出需要临时禁用的测试用例,在运行测试时会跳过
│ ├── disabled-asan.list # 除 disabled.def 文件所列用例之外,还需要临时禁用的测试用例
│ ├── disabled-ubsan.list # 同上
│ ├── disabled-valgrind.list # 同上
│ ├── disabled_ndb.def # 仅在运行 MySQL Cluster 时才需要临时禁用的测试用例

# 适合每天都运行的回归测试指令集
# 涵盖 default suites、非 default suites、针对复制和binlog的扩展测试(区分不同的复制参数)、InnoDB 扩展测试(区分不同页面大小)
│ ├── default.daily
# 由于 valgrind 运行比较耗时,因此,该指令集只能涵盖除 big-test 之外的所有 suites 。
# 需要编译时添加选项 -DWITH_DEBUG=1 -DWITH_VALGRIND=1 的情况下,才能执行 valgrind 测试。
# 注意:通过实测、分析代码,运行 mtr 时必须添加 --valgrind 选项才能用到 valgrind 组件。
│ ├── default.daily-valgrind


# 适合每周运行一次的指令集,运行耗时能达到48小时。
# 是 default.daily 的超集,同时,还指定了 --debug-server 。
# 覆盖 default suites + 非 default suites + 复制和binlog的扩展 + InnoDB扩展 + 其他按周运行的指令集。
│ ├── default.weekly
│ ├── default.weekly-ndbcluster # 覆盖 default.daily + ndbcluster + 部分非默认指令集
│ ├── default.weekly-protocol # 编译时需要设置 DWITH_TEST_TRACE_PLUGIN=1,只覆盖 main suite。
# 在启用 --big-test 和 --debug-server 选项的前提下,运行所有的指令集。
# 需要编译时添加选项 -DWITH_DEBUG=1 -DWITH_VALGRIND=1 的情况下,才能执行 valgrind 测试。
# 注意:通过实测、分析代码,运行 mtr 时必须添加 --valgrind 选项才能用到 valgrind 组件。
│ ├── default.weekly-valgrind
│ ├── default.weekly.basic # 在禁用 --big-test 选项的前提下,运行所有的指令集,即包含 default suites + 非 default suites。

# 适用于每次push代码时运行的指令集,能控制在一个小时内。
# 更适用于 mysql 5.7 版本。
│ ├── default.push
│ ├── default.push-ndbcluster # 分为 default suites + 与 ndbcluster 相关的指令集
│ ├── default.push-valgrind # 分为 default suites(排除 rpl)+ ndb 相关 suites + group_replication suite
│ ├── mysql-8.0-stage.push # 在 default.push 基础上,为 mysql-8.0-stage 扩展的测试用例,在merge到main分支前使用
│ ├── mysql-8.0-stage.push.basic # mysql-8.0-stage.push 的子集

│ ├── mysql-trunk-meb-itch.push # 文件为空
# default.push 的超集,目的是在 push 到 main 分支前,提前发现问题。
│ ├── mysql-trunk-stage.push # 内容与 mysql-8.0-stage.push 一样,在merge到main分支前使用
│ ├── mysql-trunk-stage.push.basic # mysql-trunk-stage.push 的子集
│ └── mysql-trunk-tsan.push # 由于 ThreadSanitizer 非常慢,因此,只测试 main suite

├── extra # 不属于 main 和 其他 suites 的测试 case
│ ├── 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 # 测试框架相关依赖文件,里面主要是一些用perl实现的逻辑。
│ ├── My
│ │ ├── Config.pm
│ │ ├── ConfigFactory.pm
......
│ │ └── Test.pm
│ ├── mtr_cases.pm
......

├── lock_order_dependencies.txt # mysql-test-run.pl 读取该文件来控制加锁顺序,与 --lock-order 选项有关。该文件非空。

# 在 mtr 运行对应工具期间,比如 asan,对应的 .supp 文件用于指定需要跳过的测试用例。
#
# ASAN、LSAN、TSAN 出自谷歌的 Sanitizer 项目,包含了 ASAN、LSAN、MSAN、TSAN等内存、线程错误的检测工具。
├── asan.supp # ASAN(Address-Sanitizier),内存错误检测工具。早期是LLVM中的特性,后被加入GCC 4.8。
├── lsan.supp # LSAN(LeakSanitizer),内存泄漏检测工具,已集成在 ASAN(AddressSanitizer)中。
├── tsan.supp # TSAN(ThreadSanitizer),线程间数据竞争的检测工具。
├── valgrind.supp # Valgrind 是一个工具集。集成了:
# Memcheck 内存错误检测器。
# Cachegrind 缓存和分支预测分析器。
# Callgrind 可生成缓存分析器的调用图。
# Helgrind 线程错误检测器。
# DRD 也是线程错误检测器。
# Massif 堆分析器,它可以帮助程序使用更少的内存。
# DHAT 一种不同类型的堆分析器。使用它可以了解块寿命,块利用率和布局效率低下的问题。

├── mtr -> ./mysql-test-run.pl # mysql-test-run.pl 脚本别名
├── mysql-stress-test.pl
├── mysql-test-run -> ./mysql-test-run.pl
├── mysql-test-run.dox
├── mysql-test-run.pl # mtr 入口文件,测试框架核心逻辑

# include/ 目录包含.inc 文件,在测试用例中通过 source 命令引入,就像 C/C++ 的头文件。建议将多次重复使用的测试语句整合到 .inc 文件中。
├── include # include 下所有 *.inc 都会被 t/ 目录下的 *.test 引用
│ ├── Load_data.inc
......
│ ├── json_lookup.inc
│ ├── keyring_tests
│ │ ├── binlog
│ │ │ ├── rpl_binlog_cache_encryption.inc
......
│ ├── keyring_udf_keyring_plugin_loaded.inc
......
│ └── year-engine.test
# t/ 和 r/ 目录分别对应于 main suite 的测试 case 和 期望结果。
# 测试 case 以 .test 后缀结尾。
# 另外还有 .opt 后缀文件,它里面指定了MySQL的参数。某些测试用例会涉及重启,在重启时可能会变更 mysql 参数,可能会用 .opt 文件中指定的参数。
├── t # 该目录下的每个 *.test 都对应一个测试 case 。
│ ├── 1st.test
│ ├── admin_interface.test
......
├── r # 路径和命名 与 t/ 目录一一对应,表示对应测试用例的期望输出。
│ ├── 1st.result
│ ├── admin_interface.result
......
│ └── year-myisam.result

├── std_data # 测试所用的数据文件,某些测试 case 需要使用到。
│ ├── 14897.frm
│ ├── 256kb.json
│ ├── 41_decimal.frm
│ ├── 57import.zip
......
│ └── x_y_data.csv

# 测试框架有 suite 的概念,每个 suite 为一个测试用例集合,默认的 suite 为 main,它的测试集合位于当前目录下的 t/ 目录。
# 除了 main suite 之外,其他的 suite 基本都以子目录的形式存放于当前文件夹,比如 json、binlog 等。
├── suite # 本目录下每个子目录都包含 include/r/t 三个子目录,其中:
# include/*.inc 会被 t/*.test 引用
# t/*.test 是各个测试case的主文件
# r/*.result 是期望的测试输出
# 另外,t/ 与 r/ 路径中的文件是一一对应的。
│ ├── 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 # 测试开启后 mtr 创建的目录,用于存放测试过程产生的数据目录、日志等。
├── data
│ ├── #ib_16384_0.dblwr
......
......
├── 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
    • 指定运行测试 case 的并行线程数。
  • —-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
# --extern  一般情况下mtr是启动自己的MySQL服务来进行测试,如果在启动时指定参数 --extern,则可以使用指定的 MySQL 服务进行测试
./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
1
2
3
##############################################################################
# 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
# 启动 mtr 时的日志:
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, # 额外功能(包括视图、存储过程、INFORMATION_SCHEMA等)
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 # 指定多个 suites
  • 当一些测试 case 频繁失败时,可单独运行这些 case 以便调试:
1
2
3
4
5
6
7
8
9
./mtr testcasename --record

# 只运行基础套餐里的 subquery_all 用例( t/subquery_all.test )
# 可选 --charset-for-testdb=utf8mb4
./mtr --force --big-test --nowarnings --max-test-fail=0 main.subquery_all

# 如需执行多个 case,可通过空格分割,比如:
./mtr --force --big-test --nowarnings --max-test-fail=0 main.subquery_all main.myisam_explain_json_non_select_none

  • 如果不指定任何 suite,mtr 默认会执行所有 default suites(包括 main)
1
./mtr --force
  • 执行 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/tmysql-test/suite,注意不包括extra/
1
2
3
4
5
# --do-test 参数支持正则表达式,该指令等效于./mtr --do-test=events.*
./mtr --do-test=events --force --max-test-fail=0

# 如果想测试所有包含 innodb 的 case,可以用 ./mtr --do-test=.*innodb.*

特殊用法:

  • 1、准备数据库: create database test

    • a)执行 ./mtr --extern host=127.0.0.1 --extern port=3306 --extern user=root --extern password= --force --max-test-fail=0 --suite=main ,第一个非 skipped case 可以执行成功,但之后的 case 全部失败。

    • b)分析原因,发现是每执行完一个 case ,mtr 就会 shutdown mysqld server,下一个 case 再启动,而这里是使用的外部 mysql,则不会启动。

  • 2、查看手册,发现有一个参数可以控制是否每个 case 都重启 mysqld:

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
--disable_warnings
DROP TABLE IF EXISTS t1;
SET @@sql_mode='NO_ENGINE_SUBSTITUTION';
--enable_warnings

SET SQL_WARNINGS=1;

--echo #
--echo # test content
--echo #
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;
#
# test content
#
CREATE TABLE t1 (a INT);
INSERT INTO t1 VALUES (1);
INSERT INTO t1 VALUES (2);
SELECT * FROM t1; # new comment
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/r/mytest.result 2023-03-20 10:07:31.000000000 +0300
+++ /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 原因

  1. 产生的测试结果文件与预期输出文件 diff 结果不一致:
    1. 期望输入的 SQL 执行成功,实际执行失败。
    2. 期望输入的 SQL 执行失败,实际执行成功。
    3. 比如:mysql-test/t/select_all.test 这个测试 case,其预期结果在mysql-test/r/select_all.result,在实际执行时,会将执行结果与mysql-test/r/select_all.result作比较,若不一致,则失败,并在mysql-test/var/log目录生成一个.reject文件。
  2. 测试过程中 mysql server 挂掉。这种情况一般会报“丢失连接”的错误。
  3. 测试期间 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
### safe_path: /data/work/mysql/mysql80-install.bak_asan_ubsan/bin//mysqltest_safe_process --verbose -- /data/work/mysql/mysql80-install.bak_asan_ubsan/bin/mysqld --no-defaults --initialize-insecure --loose-skip-ndbcluster --tmpdir=/data/work/mysql/mysql80-install.bak_asan_ubsan/mysql-test/var-rpl/tmp/ --core-file --datadir=/data/work/mysql/mysql80-install.bak_asan_ubsan/mysql-test/var-rpl/data/ --secure-file-priv=/data/work/mysql/mysql80-install.bak_asan_ubsan/mysql-test/var-rpl --innodb_buffer_pool_size=24M --innodb-log-file-size=5M --innodb_autoextend_increment=8 --character-sets-dir=/data/work/mysql/mysql80-install.bak_asan_ubsan/share/charsets --loose-auto_generate_certs=OFF --loose-sha256_password_auto_generate_rsa_keys=OFF --loose-caching_sha2_password_auto_generate_rsa_keys=OFF --init-file=/data/work/mysql/mysql80-install.bak_asan_ubsan/mysql-test/var-rpl/tmp/bootstrap.sql
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#23581287 Disabled until bug is fixed.
[ 0%] rpl.rpl_writeset_add_unique_key [ disabled ] Bug#33134835 RPL_WRITESET_ADD_UNIQUE_KEY FAILS SPORADICALLY
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 ']

### safe_path: /data/work/mysql/mysql80-install.bak_asan_ubsan/bin//mysqltest_safe_process --verbose -- /data/work/mysql/mysql80-install.bak_asan_ubsan/bin/mysqld --defaults-group-suffix=.1 --defaults-file=/data/work/mysql/mysql80-install.bak_asan_ubsan/mysql-test/var-rpl/my.cnf --log-output=file --loose-debug-sync-timeout=600 --plugin-dir=/data/work/mysql/mysql80-install.bak_asan_ubsan/lib/plugin --binlog-format=mixed --core-file
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; # Patched version of 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 # Global variable to keep track of completed test cases
DB<1>

调试模式常用命令:

1
2
3
4
5
6
h       查看帮助文档
c line 运行到指定行
n 运行到下一行
s 跳到函数内部运行
l 查看代码
q 退出

欢迎关注我的微信公众号【数据库内核】:分享主流开源数据库和存储引擎相关技术。

欢迎关注公众号数据库内核
标题 网址
GitHub https://dbkernel.github.io
知乎 https://www.zhihu.com/people/dbkernel/posts
思否(SegmentFault) https://segmentfault.com/u/dbkernel
掘金 https://juejin.im/user/5e9d3ed251882538083fed1f/posts
CSDN https://blog.csdn.net/dbkernel
博客园(cnblogs) https://www.cnblogs.com/dbkernel
文章目录
  1. 1. 简介
  2. 2. mtr 工作原理
    1. 2.1. 概述
    2. 2.2. 框架流程
  3. 3. 编译安装
    1. 3.1. 安装依赖
    2. 3.2. 编译
  4. 4. 目录结构
  5. 5. 参数
    1. 5.1. 常用参数
    2. 5.2. suitename 可选范围
  6. 6. suites 分类
    1. 6.1. default suites:
    2. 6.2. 非 default suites:
  7. 7. 指令示例
    1. 7.1. mtr 执行路径:
    2. 7.2. 常用指令:
    3. 7.3. 特殊用法:
  8. 8. 推荐用法
  9. 9. 如何添加测试用例?
    1. 9.1. 1. 示例一
      1. 9.1.1. 1.1. 创建测试用例
      2. 9.1.2. 1.2. 执行测试,成功
      3. 9.1.3. 1.3. 修改 result 文件
      4. 9.1.4. 1.4. 再次执行测试,失败
  10. 10. 常见问题 FAQ
    1. 10.1. test case failed 原因
  11. 11. 异常调试
    1. 11.1. 分析日志
    2. 11.2. verbose 参数
    3. 11.3. debug 参数和 gdb 参数
    4. 11.4. 脚本自身支持 debug 参数
    5. 11.5. perl 的调试模式
|