[libc-commits] [libc] [libc] start porting process unit tests to hermetic mode (PR #135124)
Schrodinger ZHU Yifan via libc-commits
libc-commits at lists.llvm.org
Wed Apr 9 20:34:32 PDT 2025
https://github.com/SchrodingerZhu created https://github.com/llvm/llvm-project/pull/135124
None
>From 68de66be0f2df49084601b661ba9e8623fb1db58 Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <i at zhuyi.fan>
Date: Wed, 9 Apr 2025 23:29:32 -0400
Subject: [PATCH] [libc] start porting process unit tests to hermetic mode
---
libc/hdr/CMakeLists.txt | 9 ++
libc/hdr/sys_wait_macros.h | 22 +++
libc/test/UnitTest/CMakeLists.txt | 17 +++
libc/test/UnitTest/ExecuteFunctionUnix.cpp | 128 ++++++++++++------
libc/test/UnitTest/LibcDeathTestExecutors.cpp | 5 +-
5 files changed, 135 insertions(+), 46 deletions(-)
create mode 100644 libc/hdr/sys_wait_macros.h
diff --git a/libc/hdr/CMakeLists.txt b/libc/hdr/CMakeLists.txt
index db2dac9ff2822..d59d379305198 100644
--- a/libc/hdr/CMakeLists.txt
+++ b/libc/hdr/CMakeLists.txt
@@ -210,6 +210,15 @@ add_proxy_header_library(
libc.include.sys_auxv
)
+add_proxy_header_library(
+ sys_wait_macros
+ HDRS
+ sys_wait_macros.h
+ FULL_BUILD_DEPENDS
+ libc.include.llvm-libc-macros.sys_wait_macros
+ libc.include.sys_wait
+)
+
add_header_library(wchar_overlay HDRS wchar_overlay.h)
add_proxy_header_library(
diff --git a/libc/hdr/sys_wait_macros.h b/libc/hdr/sys_wait_macros.h
new file mode 100644
index 0000000000000..132702a81f797
--- /dev/null
+++ b/libc/hdr/sys_wait_macros.h
@@ -0,0 +1,22 @@
+//===-- Definition of macros from sys/wait.h ------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_HDR_SYS_WAIT_MACROS_H
+#define LLVM_LIBC_HDR_SYS_WAIT_MACROS_H
+
+#ifdef LIBC_FULL_BUILD
+
+#include "include/llvm-libc-macros/sys-wait-macros.h"
+
+#else // Overlay mode
+
+#include <sys/wait.h>
+
+#endif // LLVM_LIBC_FULL_BUILD
+
+#endif // LLVM_LIBC_HDR_SYS_WAIT_MACROS_H
diff --git a/libc/test/UnitTest/CMakeLists.txt b/libc/test/UnitTest/CMakeLists.txt
index b0a3a7431c222..bb86db65e11bc 100644
--- a/libc/test/UnitTest/CMakeLists.txt
+++ b/libc/test/UnitTest/CMakeLists.txt
@@ -91,6 +91,23 @@ add_unittest_framework_library(
${libc_death_test_srcs}
HDRS
ExecuteFunction.h
+ DEPENDS
+ libc.hdr.sys_epoll_macros
+ libc.hdr.sys_wait_macros
+ libc.src.__support.CPP.new
+ libc.src.__support.common
+ libc.src.__support.libc_assert
+ libc.src.signal.kill
+ libc.src.stdio.fflush
+ libc.src.stdlib.exit
+ libc.src.string.strsignal
+ libc.src.sys.epoll.epoll_create1
+ libc.src.sys.epoll.epoll_ctl
+ libc.src.sys.epoll.epoll_wait
+ libc.src.sys.wait.waitpid
+ libc.src.unistd.close
+ libc.src.unistd.fork
+ libc.src.unistd.pipe
)
add_unittest_framework_library(
diff --git a/libc/test/UnitTest/ExecuteFunctionUnix.cpp b/libc/test/UnitTest/ExecuteFunctionUnix.cpp
index c0e85c2144005..39d4263ef6468 100644
--- a/libc/test/UnitTest/ExecuteFunctionUnix.cpp
+++ b/libc/test/UnitTest/ExecuteFunctionUnix.cpp
@@ -6,17 +6,26 @@
//
//===----------------------------------------------------------------------===//
+#include "src/__support/CPP/new.h"
+
#include "ExecuteFunction.h"
+// TODO: for now, we use epoll directly. Use poll for unix compatibility when it
+// is available.
+#include "hdr/sys_epoll_macros.h"
+#include "hdr/sys_wait_macros.h"
+#include "src/__support/libc_assert.h"
#include "src/__support/macros/config.h"
-#include "test/UnitTest/ExecuteFunction.h" // FunctionCaller
-#include <assert.h>
-#include <poll.h>
-#include <signal.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/wait.h>
-#include <unistd.h>
+#include "src/signal/kill.h"
+#include "src/stdio/fflush.h"
+#include "src/stdlib/exit.h"
+#include "src/string/strsignal.h"
+#include "src/sys/epoll/epoll_create1.h"
+#include "src/sys/epoll/epoll_ctl.h"
+#include "src/sys/epoll/epoll_wait.h"
+#include "src/sys/wait/waitpid.h"
+#include "src/unistd/close.h"
+#include "src/unistd/fork.h"
+#include "src/unistd/pipe.h"
namespace LIBC_NAMESPACE_DECL {
namespace testutils {
@@ -24,7 +33,7 @@ namespace testutils {
bool ProcessStatus::exited_normally() { return WIFEXITED(platform_defined); }
int ProcessStatus::get_exit_code() {
- assert(exited_normally() && "Abnormal termination, no exit code");
+ LIBC_ASSERT(exited_normally() && "Abnormal termination, no exit code");
return WEXITSTATUS(platform_defined);
}
@@ -34,59 +43,92 @@ int ProcessStatus::get_fatal_signal() {
return WTERMSIG(platform_defined);
}
-ProcessStatus invoke_in_subprocess(FunctionCaller *func, int timeout_ms) {
+template <typename T> struct DeleteGuard {
+ T *ptr;
+ DeleteGuard(T *p) : ptr(p) {}
+ ~DeleteGuard() {
+ if (ptr)
+ delete ptr;
+ }
+};
+
+ProcessStatus invoke_in_subprocess(FunctionCaller *func, unsigned timeout_ms) {
+ DeleteGuard<FunctionCaller> guard(func);
+
int pipe_fds[2];
- if (::pipe(pipe_fds) == -1) {
- delete func;
+ if (LIBC_NAMESPACE::pipe(pipe_fds) == -1)
return ProcessStatus::error("pipe(2) failed");
- }
// Don't copy the buffers into the child process and print twice.
- ::fflush(stderr);
- ::fflush(stdout);
- pid_t pid = ::fork();
- if (pid == -1) {
- delete func;
+ LIBC_NAMESPACE::fflush(stdout);
+ LIBC_NAMESPACE::fflush(stderr);
+ pid_t pid = fork();
+ if (pid == -1)
return ProcessStatus::error("fork(2) failed");
- }
if (!pid) {
(*func)();
- delete func;
- ::exit(0);
+ LIBC_NAMESPACE::exit(0);
+ }
+
+ LIBC_NAMESPACE::close(pipe_fds[1]);
+
+ // Create an epoll instance.
+ int epfd = LIBC_NAMESPACE::epoll_create1(0);
+ if (epfd == -1) {
+ return ProcessStatus::error("epoll_create1(2) failed");
+ }
+
+ // Register the pipe FD with epoll. We monitor for reads (EPOLLIN)
+ // plus any half-close/hangup events (EPOLLRDHUP). epoll will set
+ // EPOLLHUP automatically if the peer closes, but typically you also
+ // include EPOLLIN or EPOLLRDHUP in the event mask.
+ epoll_event ev = {};
+ ev.events = EPOLLIN | EPOLLRDHUP;
+ ev.data.fd = pipe_fds[0];
+
+ if (LIBC_NAMESPACE::epoll_ctl(epfd, EPOLL_CTL_ADD, pipe_fds[0], &ev) == -1) {
+ LIBC_NAMESPACE::close(epfd);
+ return ProcessStatus::error("epoll_ctl(2) failed");
+ }
+
+ // Block until epoll signals an event or times out.
+ epoll_event result = {};
+ int nfds = LIBC_NAMESPACE::epoll_wait(epfd, &result, 1, timeout_ms);
+ LIBC_NAMESPACE::close(
+ epfd); // We’re done with the epoll FD regardless of outcome.
+
+ if (nfds == -1) {
+ return ProcessStatus::error("epoll_wait(2) failed");
}
- ::close(pipe_fds[1]);
-
- struct pollfd poll_fd {
- pipe_fds[0], 0, 0
- };
- // No events requested so this call will only return after the timeout or if
- // the pipes peer was closed, signaling the process exited.
- if (::poll(&poll_fd, 1, timeout_ms) == -1) {
- delete func;
- return ProcessStatus::error("poll(2) failed");
+
+ // If no FDs became “ready,” the child didn’t close the pipe before the
+ // timeout.
+ if (nfds == 0) {
+ LIBC_NAMESPACE::kill(pid, SIGKILL);
+ return ProcessStatus::timed_out_ps();
}
- // If the pipe wasn't closed by the child yet then timeout has expired.
- if (!(poll_fd.revents & POLLHUP)) {
- ::kill(pid, SIGKILL);
- delete func;
+
+ // If we did get an event, check for EPOLLHUP (or EPOLLRDHUP).
+ // If those are not set, the pipe wasn't closed in the manner we expected.
+ if (!(result.events & (EPOLLHUP | EPOLLRDHUP))) {
+ LIBC_NAMESPACE::kill(pid, SIGKILL);
return ProcessStatus::timed_out_ps();
}
int wstatus = 0;
// Wait on the pid of the subprocess here so it gets collected by the system
// and doesn't turn into a zombie.
- pid_t status = ::waitpid(pid, &wstatus, 0);
- if (status == -1) {
- delete func;
+ pid_t status = LIBC_NAMESPACE::waitpid(pid, &wstatus, 0);
+ if (status == -1)
return ProcessStatus::error("waitpid(2) failed");
- }
- assert(status == pid);
- delete func;
+ LIBC_ASSERT(status == pid);
return {wstatus};
}
-const char *signal_as_string(int signum) { return ::strsignal(signum); }
+const char *signal_as_string(int signum) {
+ return LIBC_NAMESPACE::strsignal(signum);
+}
} // namespace testutils
} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/UnitTest/LibcDeathTestExecutors.cpp b/libc/test/UnitTest/LibcDeathTestExecutors.cpp
index 943e2c23c5fde..fdb9aa796ecf1 100644
--- a/libc/test/UnitTest/LibcDeathTestExecutors.cpp
+++ b/libc/test/UnitTest/LibcDeathTestExecutors.cpp
@@ -8,12 +8,11 @@
#include "LibcTest.h"
+#include "src/__support/libc_assert.h"
#include "src/__support/macros/config.h"
#include "test/UnitTest/ExecuteFunction.h"
#include "test/UnitTest/TestLogger.h"
-#include <assert.h>
-
namespace {
constexpr unsigned TIMEOUT_MS = 10000;
} // Anonymous namespace
@@ -50,7 +49,7 @@ bool Test::testProcessKilled(testutils::FunctionCaller *Func, int Signal,
}
int KilledBy = Result.get_fatal_signal();
- assert(KilledBy != 0 && "Not killed by any signal");
+ LIBC_ASSERT(KilledBy != 0 && "Not killed by any signal");
if (Signal == -1 || KilledBy == Signal)
return true;
More information about the libc-commits
mailing list