You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何单元测试生成子进程的函数?minishell执行器测试疑难

Minishell测试问题与解决方案

问题背景

测试用例

Test(minishell_loop, test_ls_exit) 
{
    t_minishell sheldon;

    ft_bzero(&sheldon, sizeof(t_minishell));
    sheldon.command_line = ft_strdup("ls");
    minishell_loop(&sheldon);
}

minishell_loop函数

int32_t minishell_loop(t_minishell *sheldon)
{
    if (!sheldon->command_line)
        sheldon->command_line = readline(PROMPT);
    if (!sheldon->command_line && printf("\x1B[1A" PROMPT "exit\n"))
        return (STOP);
    if (!*sheldon->command_line)
    {
        free(sheldon->command_line);
        return (CONTINUE);
    }
    add_history(sheldon->command_line);
    sheldon->tokens = lexer(sheldon->command_line, &sheldon->exit_status);
    if (!sheldon->tokens)
        return (minishell_clean(sheldon));
    sheldon->ast = parser(sheldon->tokens);
    if (!sheldon->ast)
        return (minishell_clean(sheldon));
    // sheldon->exit_status = executor(sheldon);
    return (minishell_clean(sheldon));
}

执行器核心函数(execute_simple_command)

int32_t execute_simple_command(t_command *cmd, t_minishell *shell)
{
    pid_t       pid;
    int32_t     status;
    char        **arguments;

    arguments = get_arguments(cmd);
    setup_redirects(cmd);
    free(cmd);
    status = execute_builtin(arguments, shell);
    if (status >= 0)
        return (status);
    pid = fork();
    if (pid != 0)
        return (wait_for_child_processes(pid));
    reset_signals();
    execute_child_command(shell, arguments);
    return (0);
}

错误现象

注释executor调用时测试正常运行,能打印AST;取消注释后,测试输出错误:

Command_table #1 at 0x561aa47ce490
        Command #1 at 0x561aa47ce490
                Argument #1: ls
[ERR ] Received message identified by a PID '468695' that is not a child process.

问题解答

1. 不注释executor代码正常运行测试的方法

错误核心是wait_for_child_processes捕获了非当前shell创建的子进程,或测试环境与fork/信号逻辑冲突,可通过以下方式修复:

(1)限制waitpid仅等待当前fork的子进程

检查wait_for_child_processes实现,确保使用waitpid(pid, &status, 0)而非waitpid(-1, ...),避免捕获测试框架的其他进程:

int32_t wait_for_child_processes(pid_t target_pid) {
    int32_t status;
    // 只等待指定的子进程,而非任意子进程
    waitpid(target_pid, &status, 0);
    return WEXITSTATUS(status);
}

(2)测试模式下跳过真实外部命令执行

添加环境变量标记,在测试时mock外部命令的执行,避免真实fork:

  • 修改execute_simple_command:
#include <stdlib.h>

int32_t execute_simple_command(t_command *cmd, t_minishell *shell)
{
    pid_t       pid;
    int32_t     status;
    char        **arguments;

    arguments = get_arguments(cmd);
    setup_redirects(cmd);
    free(cmd);
    status = execute_builtin(arguments, shell);
    if (status >= 0)
        return (status);
    
    // 测试模式下直接返回模拟状态,跳过fork
    if (getenv("TEST_MODE")) {
        free(arguments);
        return 0;
    }

    pid = fork();
    if (pid != 0)
        return (wait_for_child_processes(pid));
    reset_signals();
    execute_child_command(shell, arguments);
    _exit(EXIT_FAILURE); // 确保子进程不会返回测试框架
    return (0);
}
  • 修改测试用例,设置测试环境变量:
Test(minishell_loop, test_ls_exit) 
{
    t_minishell sheldon;

    setenv("TEST_MODE", "1", 1);
    ft_bzero(&sheldon, sizeof(t_minishell));
    sheldon.command_line = ft_strdup("ls");
    minishell_loop(&sheldon);
    unsetenv("TEST_MODE");
}

(3)修复子进程退出逻辑

确保execute_child_command执行后调用_exit而非return,防止子进程回到测试框架代码,导致PID混乱。


2. 无法直接测试时的替代方案

如果上述方法无法生效,可拆分测试粒度,或隔离外部依赖:

  • 单元拆分测试:

    • 单独测试lexer:输入命令行字符串,验证输出的token列表是否符合预期。
    • 单独测试parser:输入预设的token列表,验证生成的AST结构正确。
    • 单独测试内置命令:直接调用execute_builtin,传入模拟的参数和shell结构。
  • Mock系统调用:
    使用测试框架(如cmocka)对fork、waitpid、execve等系统调用做mock,模拟子进程执行结果,无需真实创建进程。

  • 独立进程集成测试:
    不在测试用例中直接调用minishell_loop,而是启动独立的minishell进程,通过管道重定向输入输出,发送命令并捕获结果,避免测试框架与minishell的进程逻辑冲突。


3. 输出与bash对比的实现方法

要验证minishell输出与bash一致,可通过重定向+文件对比实现:

(1)重定向输出到临时文件

#include <fcntl.h>
#include <sys/wait.h>
#include <unistd.h>
#include <assert.h>

// 辅助函数:对比两个文件内容是否一致
int file_content_equal(const char *file1, const char *file2) {
    // 实现文件内容对比逻辑(略)
    return 1; // 示例返回值,需替换为真实逻辑
}

Test(minishell_output, test_ls) {
    t_minishell sheldon;
    ft_bzero(&sheldon, sizeof(t_minishell));
    sheldon.command_line = ft_strdup("ls");

    // 捕获minishell输出
    int ms_fd = open("minishell_out", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    int old_stdout = dup(STDOUT_FILENO);
    int old_stderr = dup(STDERR_FILENO);
    dup2(ms_fd, STDOUT_FILENO);
    dup2(ms_fd, STDERR_FILENO);
    minishell_loop(&sheldon);
    close(ms_fd);
    dup2(old_stdout, STDOUT_FILENO);
    dup2(old_stderr, STDERR_FILENO);
    close(old_stdout);
    close(old_stderr);

    // 捕获bash输出
    int bash_fd = open("bash_out", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    pid_t bash_pid = fork();
    if (bash_pid == 0) {
        dup2(bash_fd, STDOUT_FILENO);
        dup2(bash_fd, STDERR_FILENO);
        execl("/bin/bash", "bash", "-c", "ls", NULL);
        _exit(EXIT_FAILURE);
    }
    waitpid(bash_pid, NULL, 0);
    close(bash_fd);

    // 对比输出
    assert(file_content_equal("minishell_out", "bash_out"));

    // 清理临时文件
    unlink("minishell_out");
    unlink("bash_out");
}

(2)忽略环境相关差异

bash输出可能因环境(如颜色、文件顺序)产生差异,可:

  • 对输出内容排序后再对比。
  • 忽略空格、换行的细微差异,只验证核心内容(如文件名列表)。
  • 禁用bash的颜色输出(执行bash -c 'LS_COLORS= ls')。

内容的提问来源于stack exchange,提问作者Saladin Afoh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 15:37:07