TEST(HooksTest, UnknownChildHook)
{
    pid_t child_pid = fork();
    if (child_pid == 0) {
        _exit(0);
    }

    ASSERT_GE(child_pid, 0);

    std::optional<pid_t> exited_pid;
    std::optional<pid_t> unknown_pid;

    Tracer tracer;

    // Tracee must remain alive long enough for child_pid to exit
    auto tracee = tracer.fork([] { while (true) pause(); });
    ASSERT_TRUE(tracee);

    auto tracee_tid = tracee.value()->tid;

    Hooks hooks;

    hooks.syscall_entry = [&](auto t, auto &info) {
        if (unknown_pid) {
            SysCall sc("exit_group", info.syscall.abi());
            assert(sc);
            (void) t->modify_syscall_args(sc.num(), {});
        }
        return action::Default{};
    };

    hooks.tracee_exit = [&](auto pid, auto) {
        exited_pid = pid;
        return action::Default{};
    };

    hooks.unknown_child = [&](auto pid, auto) {
        unknown_pid = pid;

        // tracee must be a valid pointer because this hook would not have been
        // called if the only tracee exited or died
        (void) tracee.value()->interrupt();
    };

    ASSERT_TRUE(tracer.execute(hooks));

    ASSERT_EQ(exited_pid, tracee_tid);
    ASSERT_EQ(unknown_pid, child_pid);
}
TEST(HooksTest, TraceeDeathHook)
{
    std::optional<int> death_signal;

    Hooks hooks;

    hooks.tracee_death = [&](auto, auto signal) {
        death_signal = signal;
        return action::Default{};
    };

    Tracer tracer;

    ASSERT_TRUE(tracer.fork([&] {
        raise(SIGTERM);
    }));

    ASSERT_TRUE(tracer.execute(hooks));

    ASSERT_EQ(death_signal, SIGTERM);
}
TEST(HooksTest, TraceeExitHook)
{
    std::optional<int> exit_code;

    Hooks hooks;

    hooks.tracee_exit = [&](auto, auto ec) {
        exit_code = ec;
        return action::Default{};
    };

    Tracer tracer;

    ASSERT_TRUE(tracer.fork([&] {
        _exit(10);
    }));

    ASSERT_TRUE(tracer.execute(hooks));

    ASSERT_EQ(exit_code, 10);
}