为 libevent 贡献代码:checkpatch 代码风格检查与 tinytest 回归测试实践指南
【免费下载链接】libevent Event notification library 项目地址: https://gitcode.com/gh_mirrors/li/libevent
本篇指南以 CONTRIBUTING.md 为骨架,系统讲解向 libevent(事件通知库)提交代码的完整协作流程:如何遵循项目代码风格、如何用仓库自带的 checkpatch.sh 脚本对 git 提交、补丁和单个文件做自动化风格检查,以及如何在 test/regress_*.c 中编写单元测试并通过 make verify 跑通全量回归。读完本文,你将掌握向 libevent 贡献高质量补丁的"提交前自检清单",并能独立定位测试用例的挂载位置与运行方式。
一、贡献流程总览
向 libevent 提交代码通常遵循以下路径:
该流程在仓库中有完整的工具链支撑:风格检查依赖 checkpatch.sh 及其配置文件 .clang-format、.uncrustify;回归测试依赖 tinytest 框架(test/tinytest.c、test/tinytest.h)与测试驱动器 test/test.sh。
二、代码风格:第一原则是"just look around"
CONTRIBUTING.md 给出的首要且最通用的规则是:just look around(环顾四周)。即新代码在风格上应与周边既有代码保持一致,这是比任何静态规则都更优先的约定。
为了让风格约束可自动化执行,libevent 仓库固化了两套格式化配置:
- .clang-format:默认使用的 clang-format 配置。其关键约定包括:基于 LLVM 风格、ColumnLimit: 80(单行不超过 80 列)、DerivePointerAlignment: false(指针星号对齐不自动推导)、函数体大括号另起一行(BraceWrapping: { AfterFunction: true })等,并针对 libevent 内部大量使用的队列/哈希宏(如 LIST_FOREACH、TAILQ_FOREACH、HT_FOREACH)注册了 ForEachMacros,避免格式化器误改这些宏调用。
- .uncrustify:可选的 uncrustify 风格配置,供偏好 uncrustify 的开发者使用。
从 checkpatch.sh 的脚本实现看,它默认以 .clang-format 作为检查基准(cfg() 函数中 options[clang] 默认置 1),并在仓库根目录校验该配置文件存在(parse_options 中 abort "Config '%s' does not exist")。
三、checkpatch.sh:补丁与文件风格检查的完整用法
checkpatch.sh 是 libevent 提供的风格检查脚本,可以检查 git 提交(git ref)、补丁(patch) 和 普通文件 三类对象。原文档给出的全部用法如下,均可直接复制运行:
3.1 检查 git 提交
# 检查 HEAD 提交
./checkpatch.sh -r
./checkpatch.sh -r HEAD
-r 是默认模式(parse_options 中 options[ref]=1),不传参数时脚本默认检查 HEAD。-r 模式下脚本通过 git format-patch –stdout "$@" -1 取出对应提交并交给 clang-format-diff 比较(对应脚本中的 clang_format_git 函数)。
3.2 检查补丁(patch)
# 将最近 1 个提交导出为补丁并从 stdin 检查
git format-patch –stdout -1 | ./checkpatch.sh -p
# 将当前提交内容直接从 stdin 检查
git show -1 | ./checkpatch.sh -p
# 导出最近 2 个提交的补丁序列(配合 -p 检查,或结合下面的 *.patch 方式)
git format-patch –stdout -2
-p 模式会把输入当作统一 diff 格式的补丁处理,逐 hunk 检查格式是否符合项目风格(对应脚本中的 clang_format_diff,即 clang-format-diff -p1 -style=…)。若 ./checkpatch.sh -p 后未跟文件名,脚本会读取标准输入(checkpatch.sh 中会把 stdin 内容暂存到临时文件再检查)。
3.3 检查补丁文件
# 对当前目录下的所有 .patch 文件逐一检查
./checkpatch.sh *.patch
3.4 检查单个源文件
# 以普通文件方式检查,只输出格式差异摘要
./checkpatch.sh -d event.c
./checkpatch.sh -d < event.c
# 打印整个文件的格式化结果(而非仅摘要)
./checkpatch.sh -f event.c
./checkpatch.sh -f < event.c
-d(file-diff)模式会把原文件与格式化结果做 diff 输出(diff -u "$@" <(check_file "$@")),便于快速定位差异行;-f(file)模式则直接输出经 clang-format 处理后的完整文件内容。两者都支持从 stdin 读取(./checkpatch.sh -d < event.c),方便在管道中组合使用。
3.5 查看帮助与其它选项
./checkpatch.sh -h
-h 会打印完整的选项说明。脚本支持的选项汇总如下(依据 checkpatch.sh 中的 usage() 输出):
| -p | patch 模式 | 将输入视为补丁(stdin 或文件) |
| -f | file 模式 | 将输入视为普通文件,打印完整格式化结果 |
| -d | file-diff 模式 | 将输入视为普通文件,打印与格式化结果的 diff |
| -r | git ref 模式 | 检查 git 提交,默认值,默认对象为 HEAD |
| -C | clang-format | 使用 clang-format 检查(默认开启) |
| -U | uncrustify | 改用 uncrustify 检查 |
| -c | 指定配置文件 | 为 clang-format/uncrustify 指定自定义配置路径 |
| -h | 帮助 | 打印用法说明 |
实践提示:提交前最简自检组合是 ./checkpatch.sh -r HEAD(检查最新提交)与 ./checkpatch.sh -d < 你改过的文件(检查具体文件)。脚本最终输出经由 less -FRSX 分页展示,方便交互式查看。
四、编写单元测试:test/regress_*.c 与 tinytest 框架
CONTRIBUTING.md 对测试的要求只有两条,但背后是 libevent 一套完整的测试基础设施:
- Write new unit test in test/regress_{MORE_SUITABLE_FOR_YOU}.c
- make verify
4.1 测试文件如何组织
test/regress_*.c 是按功能模块划分的回归测试源码,构建定义位于 test/include.am(test_regress_SOURCES 列出了全部源文件)。常见模块包括:
- test/regress.c:事件核心(event_base、事件优先级、信号、fd 读写等)
- test/regress_buffer.c:evbuffer 缓冲
- test/regress_bufferevent.c:bufferevent
- test/regress_dns.c:异步 DNS
- test/regress_http.c:HTTP
- test/regress_util.c:util 工具函数(字符串、时间、地址解析等)
- test/regress_rpc.c、test/regress_listener.c、test/regress_watch.c、test/regress_ws.c 等
写新测试时,先判断改动属于哪个模块,把测试用例放进对应的 regress_*.c;如果都不合适,也可以新建文件并在 test/include.am 的 test_regress_SOURCES 中登记。
4.2 测试用例的数据结构
测试运行器是 tinytest(test/tinytest.h、test/tinytest.c)。一个测试用例由 struct testcase_t 描述(test/tinytest.h):
struct testcase_t {
const char *name; /**< 用例标识符 */
testcase_fn fn; /**< 执行该用例的函数 */
unsigned long flags; /**< TT_* 标志位集合 */
const struct testcase_setup_t *setup; /**< 可选的 setup/cleanup 函数 */
void *setup_data; /**< 供 setup 使用的附加数据 */
};
#define END_OF_TESTCASES { NULL, NULL, 0, NULL, NULL }
用例数组必须以 END_OF_TESTCASES 结尾。多个用例可用 struct testgroup_t 分组(如 "util/"、"http/"、"dns/",见 test/regress_main.c),分组前缀会在运行时拼接到用例名前。
4.3 真实用例示范:以 util 模块为例
以 test/regress_util.c 中的 util_testcases[] 为例,一个典型用例形如:
{ "date_rfc1123", test_evutil_date_rfc1123, 0, NULL, NULL },
其含义是:用例名 date_rfc1123,测试函数 test_evutil_date_rfc1123(定义在同文件 test/regress_util.c,内部使用 tt_str_op(result, ==, c.date) 逐项断言 RFC 1123 日期格式),无特殊标志,无 setup。另一些用例带有标志与 setup,例如:
{ "monotonic_res", test_evutil_monotonic_res, TT_RETRIABLE, &basic_setup, (void*)"" },
{ "getaddrinfo_live", test_evutil_getaddrinfo_live, TT_FORK|TT_OFF_BY_DEFAULT, NULL, NULL },
其中 &basic_setup 会自动创建 event_base 与 socketpair(对应 test/regress.h 的 struct basic_test_data),TT_FORK 表示在子进程中运行以隔离崩溃,TT_RETRIABLE 表示失败可重试,TT_OFF_BY_DEFAULT 表示默认关闭(需显式指定才运行,多用于依赖真实网络的用例)。
4.4 便捷宏:BASIC 与 LEGACY
test/regress.h 提供两个声明宏,绝大多数用例都用它们注册:
#define BASIC(name,flags) { #name, test_##name, flags, &basic_setup, NULL }
#define LEGACY(name,flags) { #name, run_legacy_test_fn, flags|TT_LEGACY, &legacy_setup, test_##name }
- BASIC:面向新式测试,自动获得 basic_setup 提供的 event_base 与 socketpair,标志位在 flags 参数传入。
- LEGACY:包装旧式测试函数(通过全局 test_ok/pair[]/global_base 通信,见 test/regress_main.c 的 legacy_test_setup 与 run_legacy_test_fn),便于渐进迁移老用例。
新代码建议优先使用 BASIC 写法。常用标志位(定义于 test/regress.h)包括 TT_NEED_SOCKETPAIR、TT_NEED_BASE、TT_NEED_THREADS、TT_ISOLATED(等价于 fork+socketpair+base)等。
五、运行测试:make verify 的全貌
5.1 verify 目标是什么
在 Makefile.am 中可以看到:
verify: check
即 make verify 等价于运行 automake 的 check 目标,它按 test/include.am 中的 TESTS 列表依次执行 test_runner_epoll、test_runner_select、test_runner_kqueue、test_runner_evport、test_runner_devpoll、test_runner_poll、test_runner_win32、test_runner_wepoll、test_runner_timerfd、test_runner_changelist、test_runner_timerfd_changelist、test_runner_signalfd 等驱动脚本。
每个 test_runner_* 都会调用 test/test.sh(如 test/test.sh -b EPOLL),test.sh 会依次运行 test-eof test-closed test-weof test-time test-changelist test-fdleak 等专用程序,以及聚合回归程序 test/regress(并额外以 EVENT_DEBUG_MODE=1 跑一遍 debug 模式,见 test/test.sh)。测试输出默认写到 /dev/null,可通过环境变量 TEST_OUTPUT_FILE 重定向;REGRESS_ARGS 可向 regress 传递额外参数。
5.2 构建与验证的完整命令
基于 README.md 与 Documentation/Building.md,两种构建方式都支持验证:
# CMake(推荐,Unix)
mkdir build && cd build
cmake ..
make
make verify # 可选但推荐
# Autoconf(自 2.2 起已弃用)
./configure
make
make verify # 可选但推荐
sudo make install
5.3 单独运行某个测试
调试时不必每次都跑全量矩阵,可以直接运行聚合测试程序并按名字过滤:
# 编译后在 test/ 目录下执行(CMake 构建产物位于 build/bin/)
./test/regress –list-tests # 列出所有用例
./test/regress util/date_rfc1123 # 只跑单个用例
./test/regress util/.. # 跑 util 组全部用例
./test/regress –quiet # 静默模式跑全部
tinytest 支持 glob 风格选择器(+ 前缀启用、- 禁用、.. 通配组),例如 ./test/regress evbuffer/.. 只运行 evbuffer 组。test/regress_main.c 还预置了三个别名:all(全部用例)、live_net(依赖真实网络的用例)、fine_timing(时间敏感的精确计时用例),可写作 ./test/regress all、./test/regress live_net 等。这些别名通过 testaliases[] 注册。
另外,test/test.sh 本身支持 -b(指定后端,如 -b KQUEUE)、-t(timerfd 模式,设置 EVENT_PRECISE_TIMER=1)、-c(changelist 模式,设置 EVENT_EPOLL_USE_CHANGELIST=yes)、-S(signalfd 模式,设置 EVENT_USE_SIGNALFD=1),在调试特定后端时非常有用。
5.4 后端与跨平台覆盖
libevent 支持多事件后端,test/test.sh 中的默认后端矩阵为 EVPORT KQUEUE EPOLL DEVPOLL POLL SELECT WIN32 WEPOLL。test.sh 通过 EVENT_NO<BACKEND>=yes 环境变量逐一屏蔽其它后端、只保留被测后端(setup() 函数),从而验证每个后端各自的正确性。因此 make verify 跑完意味着改动在可用的所有后端上都通过了回归——这是 libevent 对"提交前必须验证"的硬性要求。
六、提问与交流
CONTRIBUTING.md 明确鼓励提问:与其在本仓库提 issue,不如直接在项目官方维护的即时聊天频道(Gitter Room)留言讨论,适合快速答疑与方案探讨。正式的问题报告、功能请求(RFC)与补丁提交则通过拉取请求与 issue 渠道进行。
七、进一步阅读
- CONTRIBUTORS.md:项目致谢名单,可了解过往贡献者
- README.md:构建安装总览
- Documentation/Building.md:CMake / Autoconf 构建细节
- Documentation/Developers.md:开发者附加资料(如发布包签名校验)
- test/include.am:测试程序与测试目标的构建定义
- test/tinytest.h:tinytest 框架 API 与标志位说明
- test/regress_main.c:测试分组注册、别名与测试入口
- test/regress.h:BASIC / LEGACY 宏与测试辅助设施
掌握以上流程后,一次合格的 libevent 贡献应当是:改动文件通过 ./checkpatch.sh -d <file> 风格检查 → 在对应 test/regress_*.c 中补充新测试 → make verify 全绿 → 提交补丁。
【免费下载链接】libevent Event notification library 项目地址: https://gitcode.com/gh_mirrors/li/libevent
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

