如何使用libcheck对调用fork/exec的程序进行单元测试?
解决libcheck测试fork/exec程序时的信号终止问题
问题回顾
我用libcheck测试一个调用fork()和exec()的程序,正常运行程序时能创建子进程、等待片刻后用SIGTERM终止子进程,完全没问题。但在测试里执行相同操作时:
- 测试失败,提示“Received signal 15 (Terminated)”,疑似测试进程被终止;
- 用
strace跑测试,看不到kill操作执行; - 设置
CK_FORK=no后用strace跑,kill会执行,但程序在wait阶段中止; - 测试成功率和系统负载相关:CPU空闲时约50%成功,负载高时几乎全失败;
- 编译命令:
gcc test.c -lcheck_pic -pthread -lrt -lm -lsubunit
核心原因
libcheck默认通过fork子进程运行测试用例,你的测试逻辑又嵌套了fork操作,导致两层进程的信号处理、进程调度出现冲突:
- 默认模式下,libcheck测试子进程和你自己fork的子进程共享信号上下文,
SIGTERM可能误发给libcheck的测试进程; CK_FORK=no时,测试用例在主进程运行,你的wait逻辑和libcheck内部进程管理逻辑冲突,高负载下进程调度延迟加剧了竞态条件。
解决方案
1. 隔离测试逻辑到独立进程
在测试用例中手动fork一个独立进程执行你的核心逻辑,避免和libcheck的测试进程干扰:
void test_my_program() { pid_t wrapper_pid = fork(); if (wrapper_pid == 0) { // 这里放原测试逻辑:fork子进程、exec、等待、发送SIGTERM pid_t child_pid = fork(); if (child_pid == 0) { execvp("your_program", argv); exit(EXIT_FAILURE); } // 等待片刻后发送SIGTERM sleep(1); kill(child_pid, SIGTERM); // 等待子进程退出 int status; waitpid(child_pid, &status, 0); exit(WIFEXITED(status) ? WEXITSTATUS(status) : EXIT_FAILURE); } else { // 测试进程等待独立子进程完成 int status; waitpid(wrapper_pid, &status, 0); ck_assert(WIFEXITED(status) && WEXITSTATUS(status) == 0); } }
2. 重置子进程信号处理
在你自己fork的子进程中,重置信号处理为默认,避免继承libcheck的信号掩码:
pid_t child_pid = fork(); if (child_pid == 0) { // 重置SIGTERM处理为默认 struct sigaction sa; sigemptyset(&sa.sa_mask); sa.sa_handler = SIG_DFL; sa.sa_flags = 0; sigaction(SIGTERM, &sa, NULL); execvp("your_program", argv); exit(EXIT_FAILURE); }
3. 优化wait逻辑避免竞态
用waitpid()指定目标PID,并且处理被信号中断的情况:
int status; // 循环等待,处理EINTR中断 while (waitpid(child_pid, &status, 0) == -1) { if (errno != EINTR) { perror("waitpid failed"); ck_abort_msg("Failed to wait for child process"); } }
调试技巧
- 启用verbose日志:
CK_VERBOSE=1 ./test,查看测试进程的详细执行流程; - 打印进程ID:在关键位置加入
printf("Test PID: %d, Child PID: %d\n", getpid(), child_pid);,确认进程层级; - 跟踪系统调用:
CK_FORK=no strace -f -e fork,execve,kill,waitpid ./test,跟踪所有进程的fork/exec/kill/wait操作,定位异常点;
内容的提问来源于stack exchange,提问作者Caulder
相关产品推荐
相关产品推荐

