本文提供相关源码,请放心食用,详见网页侧边栏或底部,有疑问请评论或 Issue
1.1 LTP介绍
LTP(Linux Test Project)
,是基于 GPL 协议的开源社区合作项目。2000 年由 SGI 发起,IBM、OSDL 和 Bull 等公司共同参与,2001年后由 SUSE、富士通、Red Hat、Oracle 共同开发和维护。
通过功能测试
、压力测试
和回归测试
来验证 Linux 系统的可靠性、稳定性和健壮性。整个项目约4000个测试用例,绝大部分用例采用 C 或 Shell。
LTP 不仅测试内核,还测试整体系统环境,对功能执行失败时的返回和处理也进行测试。
1.1.1 功能测试
主要对 man pages
中1、8命令和2系统调用所描述的功能进行验证。
1.1.2 回归测试
修改了旧代码后,重新进行测试已确认修改没有引入新的错误或导致其他代码产生错误。
1.1.3 压力测试
测试系统功能特性再大负荷压力下的稳定性和可靠性。
1.2 LTP 环境部署
1.2.1 下载 LTP
LTP 项目目前位于 GitHub,项目地址:https://github.com/linux-test-project/ltp 。获取最新版可以执行以下命令:git clone https://github.com/linux-test-project/ltp.git
。
1.2.2 部署 LTP
首先执行下面命令安装相关软件包(已安装可跳过):
1 2 3 4 5 # CentOS sudo yum install autoconf automake autotools-dev m4 # Ubuntu sudo apt-get install autoconf automake autotools-dev m4
在上节中我将 ltp 项目下载到了 wxs 用户的家目录(/home/wxs)下,如图所示:
1 2 3 4 5 6 7 8 9 10 [wxs@bogon ~]$ cd ltp/ [wxs@bogon ltp]$ ls aclocal.m4 configure.ac INSTALL pan testcases autom4te.cache confLkNw6U install-sh README.kernel_config testscripts confc20wzw COPYING lib README.md TODO config.guess doc ltpmenu runltp tools config.log execltp m4 runltplite.sh utils config.status execltp.in Makefile runtest ver_linux config.sub IDcheck.sh Makefile.release scenario_groups Version configure include missing scripts VERSION
进入ltp目录 :cd ltp
生成自动工具 :make autotools
系统环境配置 :./configure
编译 :make -j$(getconf_NPROCESSORS_ONLN)
安装 :sudo make install
依次执行以上命令后,LTP 已经被正确安装到你的 Linux 系统中,默认安装位于 /opt/ltp/
。
1 2 3 4 [wxs@bogon ltp]$ cd /opt/ltp/ [wxs@bogon ltp]$ ls bin runltp runtest share testscripts Version IDcheck.sh runltplite.sh scenario_groups testcases ver_linux
需要注意的是,我们通过 git clone
命令下载的位于home目录下的ltp文件夹为 ltp源码文件夹 ,我将在后文简称为 源码包
。
通过执行一系列命令安装到 /opt
目录下的 ltp 文件夹为ltp安装文件夹 ,我将在后文简称为**安装包
**。
1.3 目录结构
1.3.1 源码包
LTP 源码包目录结构描述如下:
名称
说明
INSTALL
LTP安装配置指导文档
README
LTP介绍
CREDITS
记录对LTP有很大贡献的人
COPYING
GNU公开许可证
ChangeLog
描述版本变化
ltpmenu
规划执行LTP的图形化界面接口
Makefile
LTP顶层目录的Makefile,负责编译安装pan、testcases和tools
runalltests.sh
顺序运行全部测试用例并且报告结果的脚本
doc/*
工程文档包含工具和库函数使用手册,描述各种测试
include/*
通用的头文件目录
lib/*
通用的函数目录
testcases/*
包含在LTP下运行和bin目录下的所有测试用例和链接
testscripts/*
存放分组的测试脚本
runtest/*
为自动化测试提供命令列表
pan/*
测试的驱动装置,具备随机和并行测试的能力
scratch/*
存放零碎测试
tools/*
存放自动化测试脚本和辅助工具
LTP 测试套件包含以下内容:
1 2 3 4 [wxs@bogon ~]$ cd ltp/testcases/ [wxs@bogon testcases]$ ls commands demoA kernel Makefile network realtime cve kdump lib misc open_posix_testsuite
目录结构描述如下:
名称
说明
commands
常用命令测试
kernel
内核模块及其相关模块
kdump
内核现崩溃转储测试
network
网络测试
realtime
系统实时性测试
open_posix_testsuite
posix标准测试
misc
崩溃、核心转出、浮点运算等测试
1.3.2 安装包
LTP安装包目录结构描述如下:
名称
说明
bin
存放LTP测试的一些辅助脚本
results
测试结果默认存储目录
testcases
测试项集
output
测试日志默认存储目录
share
脚本使用说明目录
runtest
测试驱动(用于链接testscripts内的测试脚本和testcases测试项目)
lib
通用的库函数目录
1.4 测试框架
1.4.1 整体测试流程
ltp 安装包根目录下的runltp
脚本是 LTP 自动测试系统的入口,其提供了一系列参数选项,允许用户设定测试环境制定测试集、控制测试结果输出方式和路径等,运行 runltp 会生成指定的测试列表并调用测试驱动PAN
来开始测试,待执行完毕后根据 PAN
返回的结果来生成报告。
PAN
是 LTP 的一组测试驱动程序,负责实际测试的执行,根据 runltp 传递的参数和测试列表来依次执行测试,输出执行过程中的详细信息,对每个测试用例的执行结果进行统计,并将整体测试结果返回给 runltp。
1.4.2 测试用例执行流程
测试结果的输出类型如下:
Type
Description
BROK
程序执行中途发生错误而使测试遭到破坏
CONF
测试环境不满足而跳过执行
WARN
测试中途发生异常
INFO
输出通用测试信息
PASS
测试成功
FAIL
测试失败
1.4.3 测试库
LTP 目前测试库存在新旧测试库交替的情况,本文均采用新测试库 的框架,具体的更新说明可以参考更新文档 An update on the Linux Test Project 。
Old Library
New Library
测试用例在执行时调用测试库的API
在测试库维护的子进程中回调测试用例
setup()中逐一调用API完成测试准备
setup()中测试属性以结构体变量定义
main()定义在每个测试用例当中
main()定义在测试库中
cleanup()中不能调用SAFE函数
cleanup()中允许调用SAFE函数
这里以 umount02 为例,比较新旧框架的区别:
旧框架代码:
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 #include <errno.h> #include <sys/mount.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/fcntl.h> #include <pwd.h> #include "test.h" #include "safe_macros.h" static void setup (void ) ;static void cleanup (void ) ;char *TCID = "umount02" ;#define DIR_MODE S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH #define FILE_MODE S_IRWXU | S_IRWXG | S_IRWXO #define MNTPOINT "mntpoint" static char long_path[PATH_MAX + 2 ];static int mount_flag;static int fd;static const char *device;static struct test_case_t { char *err_desc; char *mntpoint; int exp_errno; char *exp_retval; } testcases[] = { {"Already mounted/busy" , MNTPOINT, EBUSY, "EBUSY" }, {"Invalid address space" , NULL , EFAULT, "EFAULT" }, {"Directory not found" , "nonexistent" , ENOENT, "ENOENT" }, {"Invalid device" , "./" , EINVAL, "EINVAL" }, {"Pathname too long" , long_path, ENAMETOOLONG, "ENAMETOOLONG" } }; int TST_TOTAL = ARRAY_SIZE(testcases);int main (int ac, char **av) { int lc, i; tst_parse_opts(ac, av, NULL , NULL ); setup(); for (lc = 0 ; TEST_LOOPING(lc); lc++) { tst_count = 0 ; for (i = 0 ; i < TST_TOTAL; ++i) { TEST(umount(testcases[i].mntpoint)); if ((TEST_RETURN == -1 ) && (TEST_ERRNO == testcases[i].exp_errno)) { tst_resm(TPASS, "umount(2) expected failure; " "Got errno - %s : %s" , testcases[i].exp_retval, testcases[i].err_desc); } else { tst_resm(TFAIL, "umount(2) failed to produce " "expected error; %d, errno:%s got %d" , testcases[i].exp_errno, testcases[i].exp_retval, TEST_ERRNO); } } } cleanup(); tst_exit(); } static void setup (void ) { const char *fs_type; tst_sig(FORK, DEF_HANDLER, cleanup); tst_require_root(); tst_tmpdir(); fs_type = tst_dev_fs_type(); device = tst_acquire_device(cleanup); if (!device) tst_brkm(TCONF, cleanup, "Failed to obtain block device" ); tst_mkfs(cleanup, device, fs_type, NULL , NULL ); memset (long_path, 'a' , PATH_MAX + 1 ); SAFE_MKDIR(cleanup, MNTPOINT, DIR_MODE); if (mount(device, MNTPOINT, fs_type, 0 , NULL )) tst_brkm(TBROK | TERRNO, cleanup, "mount() failed" ); mount_flag = 1 ; fd = SAFE_OPEN(cleanup, MNTPOINT "/file" , O_CREAT | O_RDWR); TEST_PAUSE; } static void cleanup (void ) { if (fd > 0 && close(fd)) tst_resm(TWARN | TERRNO, "Failed to close file" ); if (mount_flag && tst_umount(MNTPOINT)) tst_resm(TWARN | TERRNO, "umount() failed" ); if (device) tst_release_device(device); tst_rmdir(); }
新框架代码:
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 #include <errno.h> #include <string.h> #include <sys/mount.h> #include "tst_test.h" #define MNTPOINT "mntpoint" static char long_path[PATH_MAX + 2 ];static int mount_flag;static int fd;static struct tcase { const char *err_desc; const char *mntpoint; int exp_errno; } tcases[] = { {"Already mounted/busy" , MNTPOINT, EBUSY}, {"Invalid address" , NULL , EFAULT}, {"Directory not found" , "nonexistent" , ENOENT}, {"Invalid device" , "./" , EINVAL}, {"Pathname too long" , long_path, ENAMETOOLONG} }; static void verify_umount (unsigned int n) { struct tcase *tc = &tcases[n]; TEST(umount(tc->mntpoint)); if (TEST_RETURN != -1 ) { tst_res(TFAIL, "umount() succeeds unexpectedly" ); return ; } if (tc->exp_errno != TEST_ERRNO) { tst_res(TFAIL | TTERRNO, "umount() should fail with %s" , tst_strerrno(tc->exp_errno)); return ; } tst_res(TPASS | TTERRNO, "umount() fails as expected: %s" , tc->err_desc); } static void setup (void ) { memset (long_path, 'a' , PATH_MAX + 1 ); SAFE_MKFS(tst_device->dev, tst_device->fs_type, NULL , NULL ); SAFE_MKDIR(MNTPOINT, 0775 ); SAFE_MOUNT(tst_device->dev, MNTPOINT, tst_device->fs_type, 0 , NULL ); mount_flag = 1 ; fd = SAFE_CREAT(MNTPOINT "/file" , 0777 ); } static void cleanup (void ) { if (fd > 0 && close(fd)) tst_res(TWARN | TERRNO, "Failed to close file" ); if (mount_flag) tst_umount(MNTPOINT); } static struct tst_test test = { .tid = "umount02" , .tcnt = ARRAY_SIZE(tcases), .needs_root = 1 , .needs_tmpdir = 1 , .needs_device = 1 , .setup = setup, .cleanup = cleanup, .test = verify_umount, };
1.5 测试执行
1.5.1 整体测试
我们可以测试所有的测试集,直接运行 runltp
命令将测试 ltp/scenario_groups/default
中的所有测试集,一次测试约 2 ~ 3 小时。
1 2 [wxs@bogon ltp]$ cd /opt/ltp [wxs@bogon ltp]$ sudo ./runltp
当然我们可以只测试某个测试集,测试集可以在 ltp/runtest/
下查看。
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 [wxs@bogon ltp]$ ls runtest/ admin_tools ipc net_stress.ipsec_udp can kernel_misc net_stress.multicast cap_bounds ltp-aiodio.part1 net_stress.route commands ltp-aiodio.part2 net.tcp_cmds connectors ltp-aiodio.part3 net.tirpc_tests containers ltp-aiodio.part4 network_commands controllers ltp-aio-stress.part1 nptl cpuhotplug ltp-aio-stress.part2 numa crashme ltplite pipes cve lvm.part1 power_management_tests dio lvm.part2 power_management_tests_exclusive dma_thread_diotest math pty fcntl-locktests mm quickhit filecaps modules sched fs net.features scsi_debug.part1 fs_bind net.ipv6 securebits fs_ext4 net.ipv6_lib smack fs_perms_simple net.multicast stress.part1 fs_readonly net.nfs stress.part2 fsx net.rpc stress.part3 hugetlb net.rpc_tests syscalls hyperthreading net.sctp syscalls-ipc ima net_stress.appl timers input net_stress.broken_ip tpm_tools io net_stress.interface tracing io_cd net_stress.ipsec_icmp io_floppy net_stress.ipsec_tcp [wxs@bogon ltp]$ sudo ./runltp -f modules
需要注意的是,如果我们测试某个测试集,runltp 需要指定 -f
参数。
1.5.2 单独测试
如果我们不想测试某个测试集,只想测试某个单独的测试,可以采用安装包测试或者源码包测试。下面以 access01 为例,讲解单独测试。
1.5.2.1 安装包测试
进入安装包,执行以下命令即可。
1 2 [wxs@bogon ltp]$ cd /opt/ltp/ [wxs@bogon ltp]$ sudo ./runltp -s access01
需要注意的是,如果我们测试某个测试,runltp 需要指定 -s
参数。
1.5.2.2 源码包测试
进入源码包,找到 access01 的位置,直接执行 ./access01
即可。
1 2 [wxs@bogon access]$ cd ~/ltp/testcases/kernel/syscalls/access/ [wxs@bogon access]$ sudo ./access01
我们看到access01位于testcases目录下,实际上testcases目录下每个文件都是一个完整的可执行程序,可以在编译后的源码路径直接执行。
1.6 牛刀小试
本节中牵扯到的项目均位于源码包 testcases 下自建的 demoA 文件夹中。测试方法均采用测试集
测试(C 实现的测试用例可以采用源码测试和测试集测试,Shell 实现的测试用例只可以采用测试集测试)。
1 2 3 4 5 [wxs@bogon ~]$ cd ~/ltp/testcases/ [wxs@bogon testcases]$ mkdir demoA [wxs@bogon testcases]$ cd demoA/ [wxs@bogon demoA]$ pwd /home/wxs/ltp/testcases/demoA
1.6.1 验证 sqrt() 函数
参考1.3.3节中给出的示例代码,编写测试用例 sqrt.c
:
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 #include <errno.h> #include <string.h> #include <sys/mount.h> #include "tst_test.h" static struct tcase { const int input; const int output; } tcases[] = { {-1 ,1 }, {9 ,3 } }; static void testSqrt (unsigned int n) { struct tcase *tc = &tcases[n]; TEST(sqrt (tc->input)); if (TEST_RETURN != tc->output) { tst_res(TFAIL, "sqrt() failed" ); return ; } tst_res(TPASS, "sqrt() succeeds" ); } static struct tst_test test = { .tid = "testSqrt" , .tcnt = ARRAY_SIZE(tcases), .test = testSqrt, };
参考 testcases 目录下的其他 Makefile 文件,编写 Makefile
:
1 2 3 4 5 6 7 top_srcdir ?= ../.. include $(top_srcdir)/include/mk/testcases.mk include $(top_srcdir)/include/mk/generic_leaf_target.mk sqrt: LDLIBS += -lm
需要注意的是,这里的 top_srcdir
指的是 ltp 目录,因为 demoA 目录位于 ltp 目录的内两层,所以使用了 ../..
。
还需要注意的是,要想成功使用 sqrt 命令必须附加 -lm
参数,在 Makefile 中已经体现这一点。
执行 make 命令,生成可执行文件 sqrt
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 [wxs@bogon demoA]$ make make -C "/home/wxs/ltp/lib" -f "/home/wxs/ltp/lib/Makefile" all make[1]: 进入目录“/home/wxs/ltp/lib” make[2]: 进入目录“/home/wxs/ltp/lib/newlib_tests” make[2]: 对“all”无需做任何事。 make[2]: 离开目录“/home/wxs/ltp/lib/newlib_tests” make[2]: 进入目录“/home/wxs/ltp/lib/tests” make[2]: 对“all”无需做任何事。 make[2]: 离开目录“/home/wxs/ltp/lib/tests” make[1]: 离开目录“/home/wxs/ltp/lib” gcc -g -O2 -g -O2 -fno-strict-aliasing -pipe -Wall -W -Wold-style-definition -D_FORTIFY_SOURCE=2 -I../../include -I../../include -I../../include/old/ -L../../lib sqrt.c -lltp -lm -o sqrt sqrt.c: 在函数‘testSqrt’中: sqrt.c:17:2: 警告:隐式声明函数‘sqrt’ [-Wimplicit-function-declaration] TEST(sqrt(tc-> input)); ^ In file included from sqrt.c:4:0: sqrt.c:17:7: 警告:隐式声明与内建函数‘sqrt’不兼容 [默认启用] TEST(sqrt(tc-> input)); ^ ../../include/tst_test.h:176:17: 附注:in definition of macro ‘TEST’ TEST_RETURN = SCALL; \ ^
这里可以直接执行命令 ./sqrt
来运行这个测试用例,但是对于下节的 shell 测试用例就不能这样做了,本节我都将它写在自定义测试集中。
首先复制可执行文件 sqrt
到 安装包
的 testcases/bin/
目录下:
1 [wxs@bogon demoA]$ sudo cp sqrt /opt/ltp/testcases/bin/
然后进入 安装包
的 runtest
目录下,编写自定义测试用例集 demoA:
1 2 [wxs@bogon runtest]$ cat demoA sqrt sqrt
测试用例集中每个测试用例包括两部分:前部分为昵称,后部分为 testcases/bin/
目录下的文件名,中间用空格
分隔。
进入上层目录,执行整体测试命令:
1 2 [wxs@bogon runtest]$ cd .. [wxs@bogon ltp]$ sudo ./runltp -f demoA
执行后程序会输出一大段,我们只需关心最重要的部分。我定义从 <<<test_start>>>
到 <<<test_end>>>
中间的内容为**test体
**,后文均以此称呼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ··· <<<test_start>>> tag=sqrt stime=1506106767 cmdline="sqrt" contacts="" analysis=exit <<<test_output>>> tst_test.c:915: INFO: Timeout per run is 0h 05m 00s incrementing stop sqrt.c:20: FAIL: sqrt() failed sqrt.c:23: PASS: sqrt() succeeds Summary: passed 1 failed 1 skipped 0 warnings 0 <<<execution_status>>> initiation_status="ok" duration=0 termination_type=exited termination_id=1 corefile=no cutime=0 cstime=0 <<<test_end>>> ···
可以看到,两个测试点一个 FAIL,一个 PASS。对照 sqrt.c 文件中给出的测试数据,发现测试没有问题。至此,一个简单的 LTP 测试用例就完成了。
1.6.2 验证 echo 命令
重新回到 demoA 文件夹中,编写 echo.sh
文件,代码可以参考源码包中 testcases/commands
中其他的测试用例:
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 TST_CNT=1 TST_TESTFUNC=do_test . tst_test.sh echo_test (){ local std_in=$1 local echo_cmd=$(echo $std_in > a.out) local echo_res=$std_in local cat_res=$(cat a.out) if [ $echo_res = $cat_res ] then tst_res TPASS "$echo_cmd sucessed" else tst_res TFAIL "$echo_cmd failed" fi } do_test (){ echo_test "hello\tworld" } tst_run
为 echo.sh 添加可执行权限,并复制到安装包中的 testcases/bin/
目录中。这里一定要注意,不可以在当前路径直接 ./echo.sh
执行测试用例!!
1 2 3 4 [wxs@bogon demoA]$ pwd /home/wxs/ltp/testcases/demoA [wxs@bogon demoA]$ sudo chmod +x echo.sh [wxs@bogon demoA]$ sudo cp echo.sh /opt/ltp/testcases/bin/
修改上一节中编写的 demoA 测试用例集,将 echo.sh 测试用例添加进去:
1 2 3 4 [wxs@bogon demoA]$ cd /opt/ltp/runtest/ [wxs@bogon runtest]$ cat demoA sqrt sqrt echo echo.sh
注意了,此时测试用例集中包含了两个测试用例,那么后面运行该测试用例集将会执行这两个测试用例。
进入上层目录,执行整体测试命令:
1 2 [wxs@bogon runtest]$ cd .. [wxs@bogon ltp]$ sudo ./runltp -f demoA
结果输出依然很多,我们依然只看最关键的部分:
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 ··· <<<test_output>>> tst_test.c:915: INFO: Timeout per run is 0h 05m 00s sqrt.c:20: FAIL: sqrt() failed sqrt.c:23: PASS: sqrt() succeeds Summary: passed 1 failed 1 skipped 0 warnings 0 <<<execution_status>>> initiation_status="ok" duration=1 termination_type=exited termination_id=1 corefile=no cutime=0 cstime=0 <<<test_end>>> <<<test_start>>> tag=echo stime=1506107663 cmdline="echo.sh" contacts="" analysis=exit <<<test_output>>> incrementing stop 1 TPASS: sucessed Summary: passed 1 failed 0 skipped 0 warnings 0 <<<execution_status>>> initiation_status="ok" duration=0 termination_type=exited termination_id=0 corefile=no cutime=2 cstime=5 <<<test_end>>> ···
本次测试输出包含两个 test 体,第一个 test 体我们在上一节已经说过了,我们直接看第二个 test 体。该体中共计有 1 个测试点,状态为 TPASS。
对照 echo.sh 中的测试点,发现没有问题。至此你已经学会了最基本的创建 C 和 Shell 的测试用例以及整体测试的方法,本章内容你已经完成了。