1 Star 0 Fork 3

recan-li / rtt-gcov

forked from latercomer / rtt-gcov 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MIT

RT-Thread 单元测试 & 代码覆盖率

本项目使用RT-Thread的QEMU VExpress A9板级支持包,演示如何在RT-Thread项目中进行单元测试和覆盖率测试。

本项目应用到的代码、程序、工具,包含:

在此十分感谢海南大学刘伟同学的热心帮助。

目录结构

工程主目录
├───applications
│   ├───code_be_test   # 即将被测试的代码
│   └───utest_tc_cases # 编写的测试用例,本项目采用RTT的UTEST框架
│   └───main.c         # 必要的时候调用__gcov_call_constructors初始化GCOV
├───packages
│   └───embedded-gcov  # 用于进行覆盖率测试的工具包,需要在rtconfig.h中#define PKG_USING_GCOV才能启用
│       ├───code       # 核心代码,用户只需要#include "gcov_public.h"即可,或者开启某些宏
│       └───scripts    # 后处理脚本
├───rtconfig.py       # gcc编译选项,在链接开启--covrage
├───rtconfig.h        # 需要的定义#define PKG_USING_GCOV和#define RT_USING_UTEST开启单元测试和覆盖率测试

如何使用

1、先安装好Git for windows、VS Code和7zip。建议将7zip安装目录C:\Program Files\7-Zip添加到系统PATH环境变量。

2、下载安装env和lcov两个工具,前者是RTT开发工具链,后者用于覆盖率报告生成。

下载env-v1.3.5,解压到不包含中文路径(比如C:\env-windows-v1.3.5),并建议将C:\env-windows-v1.3.5\tools\qemu\qemu32C:\env-windows-v1.3.5\tools\gnu_gcc\arm_gcc\mingw\bin添加到系统PATH环境变量。

下载lcov lcov-1.15不要用v1.16哈,用v1.15就好了),解压到不包含中文的路径(比如C:\lcov-1.16),并将C:\lcov-1.15\bin添加到系统PATH环境变量。

3、下载本项目代码,比如clone到D:\nextpilot\rtt-gcov。

git clone https://gitee.com/latercomer/rtt-gcov.git

4、打开env终端C:\env-windows-v1.3.5\env.exe,并在终端运行:

# 切换到工程目录
cd /d D:\nextpilot\rtt-gcov
# 编译项目
scons -j10
# 将qemu添加到path,如果之前添加了PATH环境变量就不需要
set PATH=C:\env-windows-v1.3.5\tools\qemu\qemu32;%PATH%
# 运行qemu模拟器
qemu.bat

此时rtt-gcov工程就会在qemu模拟器中运行起来,终端显示如下

5、执行utest中的测试用例

# 查看包含了哪些用例
utest_list
# 运行所有测试用例
utest_run

5、Ctr+C停止env终端,使用7zip打开压缩包D:\nextpilot\rtt-gcov\sd.bin,并将其中gcov_output.bin文件提取出来。

# 切换到工程目录
cd /d D:\nextpilot\rtt-gcov
set PATH=C:\Program Files\7-Zip;%PATH%
# 从sd.bin中提取gcov_output.bin文件
7z e sd.bin gcov_output.bin

6、调用parse_gcov_output.py脚本将gcov_output.bin文件拆分成独立的.gcda文件。

cd D:\nextpilot\rtt-gcov
python3 packages/embedded-gcov/scripts/parse_gcov_output.py

运行结果,命令行会显示以下内容:

b'D:\nextpilot\rtt-gcov\build\applications\utest_tc_cases\test_bubble_sort.gcda' b'D:\nextpilot\rtt-gcov\build\applications\code_be_test\bubble_sort.gcda'

7、调用lcov生成覆盖率测试报告,在工程主目录右键,选择Git Bash Here,输入:

# 切换到工程目录
cd D:\nextpilot\rtt-gcov
# 运行lcov
lcov -c -d . --rc lcov_branch_coverage=1 -o build/test.coverage --gcov-tool arm-none-eabi-gcov && genhtml --branch-coverage build/test.coverage -o build/lcov

注意:运行上述命令需要将C:\lcov-1.15\binC:\env-windows-v1.3.5\tools\gnu_gcc\arm_gcc\mingw\bin路径添加到系统PATH环境变量,否则lcov和arm-none-eabi-gcov之前需要添加完整路径名。

8、用浏览器打开build/lcov/index.html,查看被测试代码的覆盖结果:

示例代码测试结果.png

代码移植

1、将packages\embedded-gcov拷贝到您的工程,并设置gcov_public.h中部分宏,意义如下:

// 使用为覆盖率信息存储动态分配内存
// 否则使用预定义gcov_GcovInfo和gcov_buf存储空间
// 对于与嵌入式系统不建议开启
// #define GCOV_OPT_USE_MALLOC

// 是否使用stdlib.h库,我们使用RTT的rt_kprintf,因此不需要开启
// #define GCOV_OPT_USE_STDLIB

// 是否打印过程信息,调试阶段建议开启
#define GCOV_OPT_PRINT_STATUS

// 这个我没有使用,也没有开启
// #define GCOV_OPT_RESET_WATCHDOG

// 是否定义覆盖率初始化构造函数,建议开启
// 如果RTT启用了RT_USING_CPLUSPLUS就不需要
#define GCOV_OPT_PROVIDE_CALL_CONSTRUCTORS

// 是否使用原作者提供了gcov_printf函数
// 我们使用了rtt的rt_kprintf,因此不需要开启
// #define GCOV_OPT_PROVIDE_PRINTF_IMITATION

// 是否将覆盖率信息写入到文件,如果有SD卡和文件系统,建议开启
#define GCOV_OPT_OUTPUT_BINARY_FILE
// 覆盖率信息存储文件名
#define GCOV_OUTPUT_BINARY_FILENAME "/gcov_output.bin"

// 是否在内存里面保存覆盖率信息,不需要开启
// #define GCOV_OPT_OUTPUT_BINARY_MEMORY

// 是否通过打印串口输入出覆盖率信息
// 如果没有文件系统,这是将覆盖率信息输出一种手段,平时也可以作为调试使用
// 如果需要统计覆盖率信息的文件很多,建议关闭
#define GCOV_OPT_OUTPUT_SERIAL_HEXDUMP

如果关闭了GCOV_OPT_USE_MALLOC宏,则一定注意调整gcov_pulbic.c中gcov_GcovInfo和gcov_buf两个数组的大小,防止开辟的空间不够。

#ifndef GCOV_OPT_USE_MALLOC
/* Declare space. Need one entry per file compiled for coverage. */
static GcovInfo gcov_GcovInfo[1000];
static gcov_unsigned_t gcov_GcovIndex = 0;

/* Declare space. Needs to be enough for the largest single file coverage data. */
/* Size used will depend on size and complexity of source code
 * that you have compiled for coverage. */
/* Need buffer to be 32-bit-aligned for type-safe internal usage */
gcov_unsigned_t gcov_buf[81920];
#endif // not GCOV_OPT_USE_MALLOC

2、在rtconfig.py中开启覆盖率链接选项,也就是LFLAGS中增加--coverage。

# 使链接时将相关的代码也使用覆盖率测试 --coverage
LFLAGS = DEVICE + ' -nostartfiles -Wl,--gc-sections,-Map=rtthread.map,-cref,-u,system_vectors --coverage' + ' -T %s' % LINK_SCRIPT

3、修改rtconfig.h文件,定义以下几个宏:

/* Utilities */
// 启用utest测试框架,非必须
#define RT_USING_UTEST
#define UTEST_THR_STACK_SIZE 4096
#define UTEST_THR_PRIORITY 20
// 启用packages\embedded-gcov
#define PKG_USING_GCOV

4、在合适的地方调用__gcov_call_constructors函数,启动覆盖率测试初始化函数。比如我是放在main.c文件中,当然其它任意位置都可以。

__gcov_call_constructors()只能被调用一次,且越早调用越好,如果rtt开启了RT_USING_CPLUSPLUS,则rtt会在cplusplus_system_init()将.init.array区的函数调用一遍,因此不需要pkg_gcov_init()再次调用了。

#include <rtthread.h>
#ifdef PKG_USING_GCOV
#include "gcov_public.h"
static int pkg_gcov_init(void){
// 如果开启了c++,在cplusplus_system_init()会自动调用的.init.array,因此这里不需要再次调用
#ifndef RT_USING_CPLUSPLUS
    __gcov_call_constructors();
#endif
return 0;
}
// 在INIT_COMPONENT_EXPORT阶段初始化GCOV
INIT_COMPONENT_EXPORT(pkg_gcov_init);
#endif

5、在合适的地方调用__gcov_exit函数,将覆盖率信息输入到终端或文件,本项目一般写入到utest_tc_cleanup()中,详细见后面的utest用例编写。

__gcov_exit()函数可以被不同的地方多次调用哈。一般是在测试结束之后调用,比如通过上位机、MSH命令行,或者utest运行结束等方式触发。

// utest测试的结束程序,一般是用来释放资源
static rt_err_t utest_tc_cleanup(void)
{
    // 在测试用例结束之后,将覆盖率结果输出来
    #ifdef PKG_USING_GCOV
    __gcov_exit();
    #endif
    return RT_EOK;
}

6、添加待测试代码,并在DefineGroup中增加LOCAL_CFLAGS=' --coverage'参数。假如待测试代码在application/code_be_test目录下,则编译脚本SConscript该如下修改:

import os
from building import *

cwd     = GetCurrentDir()
src     = Glob('*.c') + Glob('*.cpp')
CPPPATH = [cwd]

if GetDepend(["PKG_USING_GCOV"]):
    # 如果定义了PKG_USING_GCOV,则开启覆盖率编译选项
    group = DefineGroup('src', src, depend = [''], CPPPATH = CPPPATH, LOCAL_CFLAGS=' --coverage')
else:
    group = DefineGroup('src', src, depend = [''], CPPPATH = CPPPATH)

Return('group')

其中--coverage等价于-fprofile-arcs -ftest-coverage表示将这个文件夹的源码添加覆盖率测试(插桩)。

7、编写utest测试用例,需要注意的是在utest_tc_cleanup()函数中调用了__gcov_exit也就是用例跑完之后输出覆盖率信息,utest测试框架代码如下:

RTT UTEST测试框架UTEST_UNIT_RUN宏展开发现,当任意测试套件中,有用例断言失败的时候,将会终止当前套件后续用例的执行。

#include "utest.h"
// 如果定义了PKG_USING_GCOV宏,引用gcov_public.h头文件
#ifdef PKG_USING_GCOV
#include "gcov_public.h"
#endif
#include "bubble_sort.h"

// 这是冒泡算法的测试用例1
static void test_bubble_sort1(void){
    // 定义等待被排序的数组
    int arr [] ={5,3,1,4,2};
    // 调用冒泡排序算法
    bubble_sort(arr, 5);
    // 使用utest断言
    for (int i = 0; i< 5; i++){
        uassert_int_equal(arr[i], i+1);
    }
}

// 这是冒泡算法的测试用例2
static void test_bubble_sort2(void){
    // 定义等待被排序的数组
    int arr [] = {3,1,2};
    // 调用冒泡排序算法
    bubble_sort(arr, 3);
    // 使用utest断言
    for (int i = 0; i< 3; i++){
        uassert_int_equal(arr[i], i+1);
    }
}

// utest测试的初始化程序,一般是用来准备资源
static rt_err_t utest_tc_init(void)
{
    return RT_EOK;
}

// utest测试的结束程序,一般是用来释放资源
static rt_err_t utest_tc_cleanup(void)
{
    // 在测试用例运行结束后,将覆盖率结果输出来
    #ifdef PKG_USING_GCOV
    __gcov_exit();
    #endif
    return RT_EOK;
}

// 将所有测试用例放在一个套件里面调用
static void utest_tc_cases(void)
{
    UTEST_UNIT_RUN(test_bubble_sort1);
    UTEST_UNIT_RUN(test_bubble_sort2);

}
// 导出测试用例套件,之后可以通过MSH命令行运行
UTEST_TC_EXPORT(utest_tc_cases, "bubble_sort", utest_tc_init, utest_tc_cleanup, 10);

Scons脚本SConscript,增加了RT_USING_UTEST的依赖项:

import os
from building import *

cwd     = GetCurrentDir()
src     = Glob('*.c') + Glob('*.cpp')
CPPPATH = [cwd]

# 增加RT_USING_UTEST依赖项,如果定义RT_USING_UTEST则编译测试用例
# 不需要做覆盖率测试的代码,千万不要添加LOCAL_CFLAGS=' --coverage'
group = DefineGroup('src', src, depend = ['RT_USING_UTEST'], CPPPATH = CPPPATH)

Return('group')

基本原理

1、gcov_output.bin文件存储格式

// 前面是N个gcda文件的信息
for i = 0 : N
  igcda文件路径\0,用\0分割字符串
  igcda信息长度,4个字节(大端在前)
  igcda信息内容
end
// 文件最后输出
Gcov End\0

编写了一个python脚本来解析该文件,并生成分离的gcda文件

import struct

gcov_output_file = r"gcov_output.bin"

with open(gcov_output_file, "rb") as fd:
    content = fd.read()
    while len(content) > 0:
        # 读取gcda文件名
        is_file_find = False
        for i in range(len(content)):
            if content[i] == 0:
                gcda_file_name = struct.unpack(str(i)+'s', content[:i])[0]
                content = content[i+1:]
                is_file_find = True
                break

        if not is_file_find:
            break

        if gcda_file_name == b"Gcov End":
            break

        # 读取gcda数据长度
        gcda_data_size = struct.unpack('>I', content[:4])[0]
        content = content[4:]

        # 读取gcada数据内容
        gcda_data_buff = struct.unpack(str(gcda_data_size)+'s', content[:gcda_data_size])[0]
        content = content[gcda_data_size:]

        # 将数据写入到文件
        with open(gcda_file_name, "wb") as gcda:
            gcda.write(gcda_data_buff)

后续工作

1、目前使用过程需要输入较多命令,后续会将相关命令写成一个python脚本,然后通过scons coverage来一次性执行。

2、计划支持gcovr工具,相比lcov,gcovr对跨平台更友好,命令行也更加简介。

注意事项

1、不需要做覆盖率测试的代码,DefineGroup千万不要添加LOCAL_CFLAGS=' --coverage'参数

2、lcov版本就用v1.15就好,不要升级到v1.16,否则在git bash中无法正确运行

MIT License Copyright (c) 2023 latercomer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

简介

本项目使用RT-Thread的QEMU VExpress A9板级支持包,演示如何在RT-Thread项目中进行单元测试和覆盖率测试。 展开 收起
MIT
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
1
https://gitee.com/recan-li/rtt-gcov.git
git@gitee.com:recan-li/rtt-gcov.git
recan-li
rtt-gcov
rtt-gcov
master

搜索帮助