[libc-commits] [libc] [libc] start porting process unit tests to hermetic mode (PR #135124)

via libc-commits libc-commits at lists.llvm.org
Wed Apr 9 20:35:12 PDT 2025


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-libc

Author: Schrodinger ZHU Yifan (SchrodingerZhu)

<details>
<summary>Changes</summary>



---
Full diff: https://github.com/llvm/llvm-project/pull/135124.diff


5 Files Affected:

- (modified) libc/hdr/CMakeLists.txt (+9) 
- (added) libc/hdr/sys_wait_macros.h (+22) 
- (modified) libc/test/UnitTest/CMakeLists.txt (+17) 
- (modified) libc/test/UnitTest/ExecuteFunctionUnix.cpp (+85-43) 
- (modified) libc/test/UnitTest/LibcDeathTestExecutors.cpp (+2-3) 


``````````diff
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;
 

``````````

</details>


https://github.com/llvm/llvm-project/pull/135124


More information about the libc-commits mailing list