[libc-commits] [libc] [libc] Update some syscall tests to be more tolerance to QEMU behaviors. (PR #208920)

via libc-commits libc-commits at lists.llvm.org
Sun Jul 12 19:19:35 PDT 2026


https://github.com/lntue updated https://github.com/llvm/llvm-project/pull/208920

>From d67fed2fedbc478de6323227058de99c2dec61bf Mon Sep 17 00:00:00 2001
From: Tue Ly <lntue.h at gmail.com>
Date: Sat, 11 Jul 2026 15:12:15 +0000
Subject: [PATCH 1/2] [libc] Update some syscall tests to be more tolerance of
 QEMU behaviors.

---
 libc/test/src/sys/mman/linux/madvise_test.cpp | 11 +++++--
 libc/test/src/sys/mman/linux/mincore_test.cpp |  3 +-
 libc/test/src/sys/mman/linux/mlock_test.cpp   | 26 +++++++++++++++--
 libc/test/src/sys/mman/linux/mremap_test.cpp  |  4 ++-
 .../src/sys/mman/linux/posix_madvise_test.cpp |  9 ++++--
 .../sys/mman/linux/remap_file_pages_test.cpp  | 16 ++++++++++
 .../sys/socket/linux/sendrecvmmsg_test.cpp    | 29 ++++++++++++++-----
 .../src/sys/socket/linux/socketopt_test.cpp   |  7 +++--
 .../src/sys/socket/linux/socketpair_test.cpp  |  5 +++-
 libc/test/src/unistd/pread_pwrite_test.cpp    |  3 +-
 libc/test/src/unistd/read_write_test.cpp      |  3 +-
 11 files changed, 94 insertions(+), 22 deletions(-)

diff --git a/libc/test/src/sys/mman/linux/madvise_test.cpp b/libc/test/src/sys/mman/linux/madvise_test.cpp
index 5818696ce3840..68846d9b3d665 100644
--- a/libc/test/src/sys/mman/linux/madvise_test.cpp
+++ b/libc/test/src/sys/mman/linux/madvise_test.cpp
@@ -37,6 +37,13 @@ TEST_F(LlvmLibcMadviseTest, NoError) {
 }
 
 TEST_F(LlvmLibcMadviseTest, Error_BadPtr) {
-  EXPECT_THAT(LIBC_NAMESPACE::madvise(nullptr, 8, MADV_SEQUENTIAL),
-              Fails(ENOMEM));
+  int err = LIBC_NAMESPACE::madvise(nullptr, 8, MADV_SEQUENTIAL);
+  if (err == 0) {
+    // Under emulators like QEMU, madvise with hint flags is a no-op returning
+    // 0.
+    return;
+  }
+  EXPECT_EQ(err, -1);
+  EXPECT_EQ(static_cast<int>(libc_errno), ENOMEM);
+  libc_errno = 0;
 }
diff --git a/libc/test/src/sys/mman/linux/mincore_test.cpp b/libc/test/src/sys/mman/linux/mincore_test.cpp
index 65a184dc383ae..60817955ca200 100644
--- a/libc/test/src/sys/mman/linux/mincore_test.cpp
+++ b/libc/test/src/sys/mman/linux/mincore_test.cpp
@@ -36,7 +36,8 @@ TEST_F(LlvmLibcMincoreTest, UnalignedAddr) {
                                     MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
   EXPECT_NE(addr, MAP_FAILED);
   EXPECT_EQ(reinterpret_cast<unsigned long>(addr) % page_size, 0ul);
-  int res = LIBC_NAMESPACE::mincore(static_cast<char *>(addr) + 1, 1, nullptr);
+  unsigned char vec;
+  int res = LIBC_NAMESPACE::mincore(static_cast<char *>(addr) + 1, 1, &vec);
   EXPECT_THAT(res, Fails(EINVAL, -1));
   EXPECT_THAT(LIBC_NAMESPACE::munmap(addr, page_size), Succeeds());
 }
diff --git a/libc/test/src/sys/mman/linux/mlock_test.cpp b/libc/test/src/sys/mman/linux/mlock_test.cpp
index f8742e1b6749f..01fc19e2b9178 100644
--- a/libc/test/src/sys/mman/linux/mlock_test.cpp
+++ b/libc/test/src/sys/mman/linux/mlock_test.cpp
@@ -75,8 +75,20 @@ TEST_F(LlvmLibcMlockTest, Overflow) {
               Fails(EINVAL));
 }
 
+static bool mlock2_supported() {
+  static bool supported = []() {
+    LIBC_NAMESPACE::mlock2(nullptr, 0, 0);
+    int err = libc_errno;
+    libc_errno = 0;
+    return err != ENOSYS;
+  }();
+  return supported;
+}
+
 #ifdef SYS_mlock2
 TEST_F(LlvmLibcMlockTest, MLock2) {
+  if (!mlock2_supported())
+    return;
   PageHolder holder;
   EXPECT_TRUE(holder.is_valid());
   EXPECT_THAT(LIBC_NAMESPACE::madvise(holder.addr, holder.size, MADV_DONTNEED),
@@ -110,10 +122,20 @@ TEST_F(LlvmLibcMlockTest, InvalidFlag) {
   EXPECT_NE(addr, MAP_FAILED);
 
   // Invalid mlock2 flags.
-  EXPECT_THAT(LIBC_NAMESPACE::mlock2(addr, alloc_size, 1234), Fails(EINVAL));
+  if (mlock2_supported()) {
+    EXPECT_THAT(LIBC_NAMESPACE::mlock2(addr, alloc_size, 1234), Fails(EINVAL));
+  }
 
   // Invalid mlockall flags.
-  EXPECT_THAT(LIBC_NAMESPACE::mlockall(1234), Fails(EINVAL));
+  int mlockall_ret = LIBC_NAMESPACE::mlockall(1234);
+  if (mlockall_ret == 0) {
+    // Under QEMU, mlockall can be a stub returning 0. Clean up immediately.
+    LIBC_NAMESPACE::munlockall();
+  } else {
+    EXPECT_EQ(mlockall_ret, -1);
+    EXPECT_EQ(static_cast<int>(libc_errno), EINVAL);
+    libc_errno = 0;
+  }
 
   // man 2 mlockall says EINVAL is a valid return code when MCL_ONFAULT was
   // specified without MCL_FUTURE or MCL_CURRENT, but this seems to fail on
diff --git a/libc/test/src/sys/mman/linux/mremap_test.cpp b/libc/test/src/sys/mman/linux/mremap_test.cpp
index 400719daf3af1..7811011c7d781 100644
--- a/libc/test/src/sys/mman/linux/mremap_test.cpp
+++ b/libc/test/src/sys/mman/linux/mremap_test.cpp
@@ -59,7 +59,9 @@ TEST_F(LlvmLibcMremapTest, Error_InvalidSize) {
   // Attempt to re-map the memory with an invalid new size (0).
   void *new_addr =
       LIBC_NAMESPACE::mremap(addr, initial_size, 0, MREMAP_MAYMOVE);
-  EXPECT_THAT(new_addr, Fails(EINVAL, MAP_FAILED));
+  EXPECT_EQ(new_addr, MAP_FAILED);
+  EXPECT_TRUE(libc_errno == EINVAL || libc_errno == ENOMEM);
+  libc_errno = 0; // Clear errno
 
   // Clean up the original mapping.
   EXPECT_THAT(LIBC_NAMESPACE::munmap(addr, initial_size), Succeeds());
diff --git a/libc/test/src/sys/mman/linux/posix_madvise_test.cpp b/libc/test/src/sys/mman/linux/posix_madvise_test.cpp
index 59c580f61a5f9..a0932fdaf5314 100644
--- a/libc/test/src/sys/mman/linux/posix_madvise_test.cpp
+++ b/libc/test/src/sys/mman/linux/posix_madvise_test.cpp
@@ -44,7 +44,10 @@ TEST_F(LlvmLibcPosixMadviseTest, Error_BadPtr) {
   EXPECT_EQ(LIBC_NAMESPACE::posix_madvise(nullptr, 8, POSIX_MADV_DONTNEED), 0);
 
   // posix_madvise doesn't set errno, but the return value is actually the error
-  // code.
-  EXPECT_EQ(LIBC_NAMESPACE::posix_madvise(nullptr, 8, POSIX_MADV_SEQUENTIAL),
-            ENOMEM);
+  // code. Under emulators like QEMU, madvise with hint flags can be a no-op, so
+  // posix_madvise might return 0.
+  int ret = LIBC_NAMESPACE::posix_madvise(nullptr, 8, POSIX_MADV_SEQUENTIAL);
+  if (ret != 0) {
+    EXPECT_EQ(ret, ENOMEM);
+  }
 }
diff --git a/libc/test/src/sys/mman/linux/remap_file_pages_test.cpp b/libc/test/src/sys/mman/linux/remap_file_pages_test.cpp
index 94e4c63286ad2..31104bab3f012 100644
--- a/libc/test/src/sys/mman/linux/remap_file_pages_test.cpp
+++ b/libc/test/src/sys/mman/linux/remap_file_pages_test.cpp
@@ -26,7 +26,19 @@ using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
 using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;
 using LlvmLibcRemapFilePagesTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
 
+static bool remap_file_pages_supported() {
+  static bool supported = []() {
+    LIBC_NAMESPACE::remap_file_pages(nullptr, 0, 0, 0, 0);
+    int err = libc_errno;
+    libc_errno = 0;
+    return err != ENOSYS;
+  }();
+  return supported;
+}
+
 TEST_F(LlvmLibcRemapFilePagesTest, NoError) {
+  if (!remap_file_pages_supported())
+    return;
   size_t page_size = PAGE_SIZE;
   ASSERT_GT(page_size, size_t(0));
 
@@ -53,6 +65,8 @@ TEST_F(LlvmLibcRemapFilePagesTest, NoError) {
 }
 
 TEST_F(LlvmLibcRemapFilePagesTest, ErrorInvalidFlags) {
+  if (!remap_file_pages_supported())
+    return;
   size_t page_size = PAGE_SIZE;
   ASSERT_GT(page_size, size_t(0));
 
@@ -80,6 +94,8 @@ TEST_F(LlvmLibcRemapFilePagesTest, ErrorInvalidFlags) {
 }
 
 TEST_F(LlvmLibcRemapFilePagesTest, ErrorInvalidAddress) {
+  if (!remap_file_pages_supported())
+    return;
   size_t page_size = PAGE_SIZE;
   ASSERT_GT(page_size, size_t(0));
 
diff --git a/libc/test/src/sys/socket/linux/sendrecvmmsg_test.cpp b/libc/test/src/sys/socket/linux/sendrecvmmsg_test.cpp
index 75f352802f124..dcb37784cc826 100644
--- a/libc/test/src/sys/socket/linux/sendrecvmmsg_test.cpp
+++ b/libc/test/src/sys/socket/linux/sendrecvmmsg_test.cpp
@@ -72,13 +72,19 @@ TEST_F(LlvmLibcSendRecvMmsgTest, SendRecvMmsgSucceedsWithSocketPair) {
   }
 
   struct timespec invalid_timeout = {-1, 0};
-  ASSERT_THAT(LIBC_NAMESPACE::recvmmsg(sockpair[1], recv_msg_hdr,
-                                       MESSAGES_COUNT, 0, &invalid_timeout),
-              Fails<int>(EINVAL));
+  int recv_res = LIBC_NAMESPACE::recvmmsg(sockpair[1], recv_msg_hdr,
+                                          MESSAGES_COUNT, 0, &invalid_timeout);
+  if (recv_res == -1) {
+    ASSERT_EQ(static_cast<int>(libc_errno), EINVAL);
+    libc_errno = 0;
 
-  ASSERT_THAT(LIBC_NAMESPACE::recvmmsg(sockpair[1], recv_msg_hdr,
-                                       MESSAGES_COUNT, 0, nullptr),
-              Succeeds<int>(MESSAGES_COUNT));
+    ASSERT_THAT(LIBC_NAMESPACE::recvmmsg(sockpair[1], recv_msg_hdr,
+                                         MESSAGES_COUNT, 0, nullptr),
+                Succeeds<int>(MESSAGES_COUNT));
+  } else {
+    // Under QEMU, the call might succeed and return MESSAGES_COUNT.
+    ASSERT_EQ(recv_res, static_cast<int>(MESSAGES_COUNT));
+  }
 
   for (size_t i = 0; i < MESSAGES_COUNT; ++i) {
     ASSERT_EQ(static_cast<size_t>(recv_msg_hdr[i].msg_len),
@@ -94,6 +100,13 @@ TEST_F(LlvmLibcSendRecvMmsgTest, SendMmsgFails) {
 
 TEST_F(LlvmLibcSendRecvMmsgTest, RecvmmsgFails) {
   struct mmsghdr msg_hdrs = {};
-  ASSERT_THAT(LIBC_NAMESPACE::recvmmsg(-1, &msg_hdrs, 1, 0, nullptr),
-              Fails(EBADF, -1));
+  int ret = LIBC_NAMESPACE::recvmmsg(-1, &msg_hdrs, 1, 0, nullptr);
+  if (ret == 1) {
+    // Under QEMU, recvmmsg(-1, ...) is buggy and can return 1 instead of -1
+    // (EBADF).
+    return;
+  }
+  EXPECT_EQ(ret, -1);
+  EXPECT_EQ(static_cast<int>(libc_errno), EBADF);
+  libc_errno = 0;
 }
diff --git a/libc/test/src/sys/socket/linux/socketopt_test.cpp b/libc/test/src/sys/socket/linux/socketopt_test.cpp
index 8fd03c2cf24d4..979eb57fc29c1 100644
--- a/libc/test/src/sys/socket/linux/socketopt_test.cpp
+++ b/libc/test/src/sys/socket/linux/socketopt_test.cpp
@@ -139,8 +139,11 @@ TEST_F(LlvmLibcSocketOptTest, ReceiveTimeout) {
   ASSERT_THAT(LIBC_NAMESPACE::getsockopt(sv[0], SOL_SOCKET, SO_RCVTIMEO,
                                          &retrieved_tv, &retrieved_optlen),
               Succeeds(0));
-  ASSERT_EQ(retrieved_optlen, optlen);
-  ASSERT_EQ(retrieved_tv.tv_sec, tv.tv_sec);
+  // Under QEMU getsockopt(SO_RCVTIMEO) may return a length of 0.
+  ASSERT_TRUE(retrieved_optlen == optlen || retrieved_optlen == 0);
+  if (retrieved_optlen == optlen) {
+    ASSERT_EQ(retrieved_tv.tv_sec, tv.tv_sec);
+  }
 
   char buffer[10];
   struct timespec start, end;
diff --git a/libc/test/src/sys/socket/linux/socketpair_test.cpp b/libc/test/src/sys/socket/linux/socketpair_test.cpp
index 94b7412f8c061..cd450ef61392a 100644
--- a/libc/test/src/sys/socket/linux/socketpair_test.cpp
+++ b/libc/test/src/sys/socket/linux/socketpair_test.cpp
@@ -34,5 +34,8 @@ TEST_F(LlvmLibcSocketPairTest, LocalSocket) {
 
 TEST_F(LlvmLibcSocketPairTest, SocketFails) {
   int sockpair[2] = {-1, -1};
-  ASSERT_THAT(LIBC_NAMESPACE::socketpair(-1, -1, -1, sockpair), Fails(EINVAL));
+  int ret = LIBC_NAMESPACE::socketpair(-1, -1, -1, sockpair);
+  EXPECT_EQ(ret, -1);
+  EXPECT_TRUE(libc_errno == EINVAL || libc_errno == EAFNOSUPPORT);
+  libc_errno = 0;
 }
diff --git a/libc/test/src/unistd/pread_pwrite_test.cpp b/libc/test/src/unistd/pread_pwrite_test.cpp
index 7cbac04b60a95..ed7d2113b502a 100644
--- a/libc/test/src/unistd/pread_pwrite_test.cpp
+++ b/libc/test/src/unistd/pread_pwrite_test.cpp
@@ -74,5 +74,6 @@ TEST_F(LlvmLibcUniStd, PWriteFails) {
 
 TEST_F(LlvmLibcUniStd, PReadFails) {
   using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
-  EXPECT_THAT(LIBC_NAMESPACE::pread(-1, nullptr, 1, 0), Fails<ssize_t>(EBADF));
+  char buf[1];
+  EXPECT_THAT(LIBC_NAMESPACE::pread(-1, buf, 1, 0), Fails<ssize_t>(EBADF));
 }
diff --git a/libc/test/src/unistd/read_write_test.cpp b/libc/test/src/unistd/read_write_test.cpp
index 8952e6083f97d..39953a5cd43d6 100644
--- a/libc/test/src/unistd/read_write_test.cpp
+++ b/libc/test/src/unistd/read_write_test.cpp
@@ -58,7 +58,8 @@ TEST_F(LlvmLibcUniStd, WriteFails) {
 TEST_F(LlvmLibcUniStd, ReadFails) {
   using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
 
-  EXPECT_THAT(LIBC_NAMESPACE::read(-1, nullptr, 1), Fails<ssize_t>(EBADF));
+  char buf[1];
+  EXPECT_THAT(LIBC_NAMESPACE::read(-1, buf, 1), Fails<ssize_t>(EBADF));
   EXPECT_THAT(LIBC_NAMESPACE::read(0, reinterpret_cast<void *>(-1), 1),
               Fails<ssize_t>(EFAULT));
 }

>From 95e3a5f5306ba1cd8d49be3f8e427d2dd7b4f51e Mon Sep 17 00:00:00 2001
From: Tue Ly <lntue.h at gmail.com>
Date: Mon, 13 Jul 2026 02:18:54 +0000
Subject: [PATCH 2/2] Address comments.

---
 libc/cmake/modules/LLVMLibCTestRules.cmake    |   4 +
 libc/test/UnitTest/ErrnoSetterMatcher.h       | 120 ++++++++++++++++--
 libc/test/src/sys/mman/linux/CMakeLists.txt   |   6 +
 libc/test/src/sys/mman/linux/madvise_test.cpp |  15 +--
 libc/test/src/sys/mman/linux/mincore_test.cpp |   6 +
 libc/test/src/sys/mman/linux/mlock_test.cpp   |  35 ++---
 libc/test/src/sys/mman/linux/mremap_test.cpp  |   5 +-
 .../src/sys/mman/linux/posix_madvise_test.cpp |  15 ++-
 .../sys/mman/linux/remap_file_pages_test.cpp  |  16 ---
 .../sys/socket/linux/sendrecvmmsg_test.cpp    |  40 +++---
 .../src/sys/socket/linux/socketopt_test.cpp   |   5 +
 .../src/sys/socket/linux/socketpair_test.cpp  |   7 +-
 libc/test/src/unistd/pread_pwrite_test.cpp    |   5 +
 libc/test/src/unistd/read_write_test.cpp      |   5 +
 14 files changed, 189 insertions(+), 95 deletions(-)

diff --git a/libc/cmake/modules/LLVMLibCTestRules.cmake b/libc/cmake/modules/LLVMLibCTestRules.cmake
index d86fb4369a381..b7d3a98059723 100644
--- a/libc/cmake/modules/LLVMLibCTestRules.cmake
+++ b/libc/cmake/modules/LLVMLibCTestRules.cmake
@@ -28,6 +28,10 @@ function(_get_common_test_compile_options output_var c_test flags)
     list(APPEND compile_options "-DLIBC_TEST_SKIP_DEATH_TESTS")
   endif()
 
+  if(CMAKE_CROSSCOMPILING_EMULATOR)
+    list(APPEND compile_options "-DLIBC_TEST_UNDER_EMULATOR")
+  endif()
+
   if(LLVM_LIBC_COMPILER_IS_GCC_COMPATIBLE)
     list(APPEND compile_options "-fpie")
 
diff --git a/libc/test/UnitTest/ErrnoSetterMatcher.h b/libc/test/UnitTest/ErrnoSetterMatcher.h
index e883308314087..a7f21843e602c 100644
--- a/libc/test/UnitTest/ErrnoSetterMatcher.h
+++ b/libc/test/UnitTest/ErrnoSetterMatcher.h
@@ -5,6 +5,31 @@
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 //
 //===----------------------------------------------------------------------===//
+/// \file
+/// ErrnoSetterMatcher is a testing utility to assert system call/function
+/// return values and the expected libc_errno.
+///
+/// Usage:
+///
+///   1. Asserting success (return value matches and libc_errno is 0):
+///
+///      EXPECT_THAT(LIBC_NAMESPACE::close(fd), Succeeds(0));
+///
+///   2. Asserting failure (return value is -1 and libc_errno matches expected):
+///
+///      EXPECT_THAT(LIBC_NAMESPACE::read(-1, buf, 1), Fails(EBADF));
+///
+///   3. Asserting failure with custom return value:
+///
+///      EXPECT_THAT(LIBC_NAMESPACE::mmap(nullptr, size, ...),
+///                  Fails(ENOMEM, MAP_FAILED));
+///
+///   4. Asserting failure with multiple possible errnos (e.g. QEMU vs actual
+///      hardware):
+///
+///      EXPECT_THAT(LIBC_NAMESPACE::socketpair(-1, -1, -1, sv),
+///                  Fails(any_of(EINVAL, EAFNOSUPPORT)));
+//===----------------------------------------------------------------------===//
 
 #ifndef LLVM_LIBC_TEST_ERRNOSETTERMATCHER_H
 #define LLVM_LIBC_TEST_ERRNOSETTERMATCHER_H
@@ -20,6 +45,11 @@
 namespace LIBC_NAMESPACE_DECL {
 namespace testing {
 
+struct ErrnoList {
+  int errs[4];
+  size_t count;
+};
+
 namespace internal {
 
 enum class CompareAction { EQ = 0, GE, GT, LE, LT, NE };
@@ -59,9 +89,71 @@ template <typename T> struct Comparator {
 #endif
 };
 
+class ErrnoCheck {
+  enum class Type { EQ = 0, NE, ANY_OF } type;
+  int expected;
+  int expected_list[4];
+  size_t list_count;
+
+public:
+  ErrnoCheck() : type(Type::EQ), expected(0), list_count(0) {}
+  ErrnoCheck(int val) : type(Type::EQ), expected(val), list_count(0) {}
+  template <typename T> ErrnoCheck(Comparator<T> cmp) : list_count(0) {
+    if (cmp.cmp == CompareAction::NE) {
+      type = Type::NE;
+    } else {
+      type = Type::EQ;
+    }
+    expected = static_cast<int>(cmp.expected);
+  }
+  ErrnoCheck(const ErrnoList &list)
+      : type(Type::ANY_OF), expected(0), list_count(list.count) {
+    for (size_t i = 0; i < list.count && i < 4; ++i) {
+      expected_list[i] = list.errs[i];
+    }
+  }
+
+  bool compare(int actual) const {
+    if (type == Type::EQ) {
+      return actual == expected;
+    } else if (type == Type::NE) {
+      return actual != expected;
+    } else if (type == Type::ANY_OF) {
+      for (size_t i = 0; i < list_count; ++i) {
+        if (actual == expected_list[i])
+          return true;
+      }
+      return false;
+    }
+    return false;
+  }
+
+  void print_expected() const {
+    if (type == Type::EQ) {
+      auto expected_str = try_get_errno_name(expected);
+      tlog << "equal to " << (expected_str ? *expected_str : "<unknown>") << "("
+           << expected << ")";
+    } else if (type == Type::NE) {
+      auto expected_str = try_get_errno_name(expected);
+      tlog << "not equal to " << (expected_str ? *expected_str : "<unknown>")
+           << "(" << expected << ")";
+    } else if (type == Type::ANY_OF) {
+      tlog << "one of [";
+      for (size_t i = 0; i < list_count; ++i) {
+        auto expected_str = try_get_errno_name(expected_list[i]);
+        tlog << (expected_str ? *expected_str : "<unknown>") << "("
+             << expected_list[i] << ")";
+        if (i + 1 < list_count)
+          tlog << ", ";
+      }
+      tlog << "]";
+    }
+  }
+};
+
 template <typename T> class ErrnoSetterMatcher : public Matcher<T> {
   Comparator<T> return_cmp;
-  Comparator<int> errno_cmp;
+  ErrnoCheck errno_cmp;
   T actual_return;
   int actual_errno;
 
@@ -78,10 +170,10 @@ template <typename T> class ErrnoSetterMatcher : public Matcher<T> {
 
 public:
   ErrnoSetterMatcher(Comparator<T> rcmp) : return_cmp(rcmp) {}
-  ErrnoSetterMatcher(Comparator<T> rcmp, Comparator<int> ecmp)
+  ErrnoSetterMatcher(Comparator<T> rcmp, ErrnoCheck ecmp)
       : return_cmp(rcmp), errno_cmp(ecmp) {}
 
-  ErrnoSetterMatcher<T> with_errno(Comparator<int> ecmp) {
+  ErrnoSetterMatcher<T> with_errno(ErrnoCheck ecmp) {
     errno_cmp = ecmp;
     return *this;
   }
@@ -101,13 +193,11 @@ template <typename T> class ErrnoSetterMatcher : public Matcher<T> {
 
     if constexpr (!ignore_errno()) {
       if (!errno_cmp.compare(actual_errno)) {
-        auto expected_str = try_get_errno_name(errno_cmp.expected);
         auto actual_str = try_get_errno_name(actual_errno);
-        tlog << "Expected errno to be " << errno_cmp.str() << " "
-             << (expected_str ? *expected_str : "<unknown>") << "("
-             << errno_cmp.expected << ") but got "
-             << (actual_str ? *actual_str : "<unknown>") << "(" << actual_errno
-             << ").\n";
+        tlog << "Expected errno to be ";
+        errno_cmp.print_expected();
+        tlog << " but got " << (actual_str ? *actual_str : "<unknown>") << "("
+             << actual_errno << ").\n";
       }
     }
   }
@@ -166,12 +256,22 @@ internal::ErrnoSetterMatcher<RetT> Fails(int ExpectedErrno,
                                             EQ(ExpectedErrno));
 }
 
+template <typename RetT = int>
+internal::ErrnoSetterMatcher<RetT> Fails(const ErrnoList &ExpectedErrs,
+                                         RetT ExpectedReturn = -1) {
+  return internal::ErrnoSetterMatcher<RetT>(EQ(ExpectedReturn), ExpectedErrs);
+}
+
+template <typename... Args> inline ErrnoList any_of(Args... args) {
+  return ErrnoList{{static_cast<int>(args)...}, sizeof...(args)};
+}
+
 template <typename RetT = int> class ErrnoSetterMatcherBuilder {
 public:
   template <typename T> using Cmp = internal::Comparator<T>;
   ErrnoSetterMatcherBuilder(Cmp<RetT> cmp) : return_cmp(cmp) {}
 
-  internal::ErrnoSetterMatcher<RetT> with_errno(Cmp<int> cmp) {
+  internal::ErrnoSetterMatcher<RetT> with_errno(internal::ErrnoCheck cmp) {
     return internal::ErrnoSetterMatcher<RetT>(return_cmp, cmp);
   }
 
diff --git a/libc/test/src/sys/mman/linux/CMakeLists.txt b/libc/test/src/sys/mman/linux/CMakeLists.txt
index a841d047b9fd7..9d989d4f877c2 100644
--- a/libc/test/src/sys/mman/linux/CMakeLists.txt
+++ b/libc/test/src/sys/mman/linux/CMakeLists.txt
@@ -187,6 +187,11 @@ add_libc_unittest(
     libc.test.UnitTest.ErrnoSetterMatcher
 )
 
+if(CMAKE_CROSSCOMPILING_EMULATOR)
+  if(LIBC_CMAKE_VERBOSE_LOGGING)
+    message(STATUS "Skipping remap_file_pages_test under emulator.")
+  endif()
+else()
 add_libc_unittest(
   remap_file_pages_test
   SUITE
@@ -207,6 +212,7 @@ add_libc_unittest(
     libc.src.unistd.close
     libc.src.unistd.sysconf
 )
+endif()
 
 add_libc_unittest(
   shm_test
diff --git a/libc/test/src/sys/mman/linux/madvise_test.cpp b/libc/test/src/sys/mman/linux/madvise_test.cpp
index 68846d9b3d665..d3bbb6425a9e3 100644
--- a/libc/test/src/sys/mman/linux/madvise_test.cpp
+++ b/libc/test/src/sys/mman/linux/madvise_test.cpp
@@ -36,14 +36,11 @@ TEST_F(LlvmLibcMadviseTest, NoError) {
   EXPECT_THAT(LIBC_NAMESPACE::munmap(addr, alloc_size), Succeeds());
 }
 
+// QEMU user space emulation stubs madvise hints (e.g. MADV_SEQUENTIAL) to
+// return 0, which makes this test fail as it expects ENOMEM on nullptr.
+#ifndef LIBC_TEST_UNDER_EMULATOR
 TEST_F(LlvmLibcMadviseTest, Error_BadPtr) {
-  int err = LIBC_NAMESPACE::madvise(nullptr, 8, MADV_SEQUENTIAL);
-  if (err == 0) {
-    // Under emulators like QEMU, madvise with hint flags is a no-op returning
-    // 0.
-    return;
-  }
-  EXPECT_EQ(err, -1);
-  EXPECT_EQ(static_cast<int>(libc_errno), ENOMEM);
-  libc_errno = 0;
+  EXPECT_THAT(LIBC_NAMESPACE::madvise(nullptr, 8, MADV_SEQUENTIAL),
+              Fails(ENOMEM));
 }
+#endif // LIBC_TEST_UNDER_EMULATOR
diff --git a/libc/test/src/sys/mman/linux/mincore_test.cpp b/libc/test/src/sys/mman/linux/mincore_test.cpp
index 60817955ca200..fbbe6d6b00d2f 100644
--- a/libc/test/src/sys/mman/linux/mincore_test.cpp
+++ b/libc/test/src/sys/mman/linux/mincore_test.cpp
@@ -36,8 +36,14 @@ TEST_F(LlvmLibcMincoreTest, UnalignedAddr) {
                                     MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
   EXPECT_NE(addr, MAP_FAILED);
   EXPECT_EQ(reinterpret_cast<unsigned long>(addr) % page_size, 0ul);
+#ifdef LIBC_TEST_UNDER_EMULATOR
+  // QEMU user space emulation returns EFAULT instead of EINVAL because it
+  // validates the pointer parameters first.
   unsigned char vec;
   int res = LIBC_NAMESPACE::mincore(static_cast<char *>(addr) + 1, 1, &vec);
+#else
+  int res = LIBC_NAMESPACE::mincore(static_cast<char *>(addr) + 1, 1, nullptr);
+#endif
   EXPECT_THAT(res, Fails(EINVAL, -1));
   EXPECT_THAT(LIBC_NAMESPACE::munmap(addr, page_size), Succeeds());
 }
diff --git a/libc/test/src/sys/mman/linux/mlock_test.cpp b/libc/test/src/sys/mman/linux/mlock_test.cpp
index 01fc19e2b9178..4e81cf4a3d9e4 100644
--- a/libc/test/src/sys/mman/linux/mlock_test.cpp
+++ b/libc/test/src/sys/mman/linux/mlock_test.cpp
@@ -75,20 +75,9 @@ TEST_F(LlvmLibcMlockTest, Overflow) {
               Fails(EINVAL));
 }
 
-static bool mlock2_supported() {
-  static bool supported = []() {
-    LIBC_NAMESPACE::mlock2(nullptr, 0, 0);
-    int err = libc_errno;
-    libc_errno = 0;
-    return err != ENOSYS;
-  }();
-  return supported;
-}
-
-#ifdef SYS_mlock2
+// QEMU user space emulation does not support mlock2 and returns ENOSYS.
+#if defined(SYS_mlock2) && !defined(LIBC_TEST_UNDER_EMULATOR)
 TEST_F(LlvmLibcMlockTest, MLock2) {
-  if (!mlock2_supported())
-    return;
   PageHolder holder;
   EXPECT_TRUE(holder.is_valid());
   EXPECT_THAT(LIBC_NAMESPACE::madvise(holder.addr, holder.size, MADV_DONTNEED),
@@ -112,8 +101,11 @@ TEST_F(LlvmLibcMlockTest, MLock2) {
   EXPECT_EQ(vec & 1, 1);
   EXPECT_THAT(LIBC_NAMESPACE::munlock(holder.addr, holder.size), Succeeds());
 }
-#endif
+#endif // defined(SYS_mlock2) && !defined(LIBC_TEST_UNDER_EMULATOR)
 
+// QEMU user space emulation stubs mlockall to return 0 instead of EINVAL on
+// invalid flags.
+#ifndef LIBC_TEST_UNDER_EMULATOR
 TEST_F(LlvmLibcMlockTest, InvalidFlag) {
   size_t alloc_size = 128; // page size
   void *addr = LIBC_NAMESPACE::mmap(nullptr, alloc_size, PROT_READ,
@@ -122,20 +114,10 @@ TEST_F(LlvmLibcMlockTest, InvalidFlag) {
   EXPECT_NE(addr, MAP_FAILED);
 
   // Invalid mlock2 flags.
-  if (mlock2_supported()) {
-    EXPECT_THAT(LIBC_NAMESPACE::mlock2(addr, alloc_size, 1234), Fails(EINVAL));
-  }
+  EXPECT_THAT(LIBC_NAMESPACE::mlock2(addr, alloc_size, 1234), Fails(EINVAL));
 
   // Invalid mlockall flags.
-  int mlockall_ret = LIBC_NAMESPACE::mlockall(1234);
-  if (mlockall_ret == 0) {
-    // Under QEMU, mlockall can be a stub returning 0. Clean up immediately.
-    LIBC_NAMESPACE::munlockall();
-  } else {
-    EXPECT_EQ(mlockall_ret, -1);
-    EXPECT_EQ(static_cast<int>(libc_errno), EINVAL);
-    libc_errno = 0;
-  }
+  EXPECT_THAT(LIBC_NAMESPACE::mlockall(1234), Fails(EINVAL));
 
   // man 2 mlockall says EINVAL is a valid return code when MCL_ONFAULT was
   // specified without MCL_FUTURE or MCL_CURRENT, but this seems to fail on
@@ -146,6 +128,7 @@ TEST_F(LlvmLibcMlockTest, InvalidFlag) {
 
   LIBC_NAMESPACE::munmap(addr, alloc_size);
 }
+#endif // LIBC_TEST_UNDER_EMULATOR
 
 TEST_F(LlvmLibcMlockTest, MLockAll) {
   {
diff --git a/libc/test/src/sys/mman/linux/mremap_test.cpp b/libc/test/src/sys/mman/linux/mremap_test.cpp
index 7811011c7d781..418cf741d1e46 100644
--- a/libc/test/src/sys/mman/linux/mremap_test.cpp
+++ b/libc/test/src/sys/mman/linux/mremap_test.cpp
@@ -14,6 +14,7 @@
 #include "test/UnitTest/ErrnoSetterMatcher.h"
 #include "test/UnitTest/Test.h"
 
+using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::any_of;
 using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
 using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;
 using LlvmLibcMremapTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
@@ -59,9 +60,7 @@ TEST_F(LlvmLibcMremapTest, Error_InvalidSize) {
   // Attempt to re-map the memory with an invalid new size (0).
   void *new_addr =
       LIBC_NAMESPACE::mremap(addr, initial_size, 0, MREMAP_MAYMOVE);
-  EXPECT_EQ(new_addr, MAP_FAILED);
-  EXPECT_TRUE(libc_errno == EINVAL || libc_errno == ENOMEM);
-  libc_errno = 0; // Clear errno
+  EXPECT_THAT(new_addr, Fails(any_of(EINVAL, ENOMEM), MAP_FAILED));
 
   // Clean up the original mapping.
   EXPECT_THAT(LIBC_NAMESPACE::munmap(addr, initial_size), Succeeds());
diff --git a/libc/test/src/sys/mman/linux/posix_madvise_test.cpp b/libc/test/src/sys/mman/linux/posix_madvise_test.cpp
index a0932fdaf5314..05edb39d590c4 100644
--- a/libc/test/src/sys/mman/linux/posix_madvise_test.cpp
+++ b/libc/test/src/sys/mman/linux/posix_madvise_test.cpp
@@ -44,10 +44,13 @@ TEST_F(LlvmLibcPosixMadviseTest, Error_BadPtr) {
   EXPECT_EQ(LIBC_NAMESPACE::posix_madvise(nullptr, 8, POSIX_MADV_DONTNEED), 0);
 
   // posix_madvise doesn't set errno, but the return value is actually the error
-  // code. Under emulators like QEMU, madvise with hint flags can be a no-op, so
-  // posix_madvise might return 0.
-  int ret = LIBC_NAMESPACE::posix_madvise(nullptr, 8, POSIX_MADV_SEQUENTIAL);
-  if (ret != 0) {
-    EXPECT_EQ(ret, ENOMEM);
-  }
+  // code.
+#ifdef LIBC_TEST_UNDER_EMULATOR
+  // QEMU stubs madvise hints, so posix_madvise returns 0.
+  EXPECT_EQ(LIBC_NAMESPACE::posix_madvise(nullptr, 8, POSIX_MADV_SEQUENTIAL),
+            0);
+#else
+  EXPECT_EQ(LIBC_NAMESPACE::posix_madvise(nullptr, 8, POSIX_MADV_SEQUENTIAL),
+            ENOMEM);
+#endif
 }
diff --git a/libc/test/src/sys/mman/linux/remap_file_pages_test.cpp b/libc/test/src/sys/mman/linux/remap_file_pages_test.cpp
index 31104bab3f012..94e4c63286ad2 100644
--- a/libc/test/src/sys/mman/linux/remap_file_pages_test.cpp
+++ b/libc/test/src/sys/mman/linux/remap_file_pages_test.cpp
@@ -26,19 +26,7 @@ using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
 using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;
 using LlvmLibcRemapFilePagesTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
 
-static bool remap_file_pages_supported() {
-  static bool supported = []() {
-    LIBC_NAMESPACE::remap_file_pages(nullptr, 0, 0, 0, 0);
-    int err = libc_errno;
-    libc_errno = 0;
-    return err != ENOSYS;
-  }();
-  return supported;
-}
-
 TEST_F(LlvmLibcRemapFilePagesTest, NoError) {
-  if (!remap_file_pages_supported())
-    return;
   size_t page_size = PAGE_SIZE;
   ASSERT_GT(page_size, size_t(0));
 
@@ -65,8 +53,6 @@ TEST_F(LlvmLibcRemapFilePagesTest, NoError) {
 }
 
 TEST_F(LlvmLibcRemapFilePagesTest, ErrorInvalidFlags) {
-  if (!remap_file_pages_supported())
-    return;
   size_t page_size = PAGE_SIZE;
   ASSERT_GT(page_size, size_t(0));
 
@@ -94,8 +80,6 @@ TEST_F(LlvmLibcRemapFilePagesTest, ErrorInvalidFlags) {
 }
 
 TEST_F(LlvmLibcRemapFilePagesTest, ErrorInvalidAddress) {
-  if (!remap_file_pages_supported())
-    return;
   size_t page_size = PAGE_SIZE;
   ASSERT_GT(page_size, size_t(0));
 
diff --git a/libc/test/src/sys/socket/linux/sendrecvmmsg_test.cpp b/libc/test/src/sys/socket/linux/sendrecvmmsg_test.cpp
index dcb37784cc826..16900d6f01a26 100644
--- a/libc/test/src/sys/socket/linux/sendrecvmmsg_test.cpp
+++ b/libc/test/src/sys/socket/linux/sendrecvmmsg_test.cpp
@@ -71,20 +71,21 @@ TEST_F(LlvmLibcSendRecvMmsgTest, SendRecvMmsgSucceedsWithSocketPair) {
     recv_msg_hdr[i].msg_hdr.msg_iovlen = 1;
   }
 
+#ifdef LIBC_TEST_UNDER_EMULATOR
+  // QEMU does not validate timespec when messages are available in the queue.
+  ASSERT_THAT(LIBC_NAMESPACE::recvmmsg(sockpair[1], recv_msg_hdr,
+                                       MESSAGES_COUNT, 0, nullptr),
+              Succeeds<int>(MESSAGES_COUNT));
+#else
   struct timespec invalid_timeout = {-1, 0};
-  int recv_res = LIBC_NAMESPACE::recvmmsg(sockpair[1], recv_msg_hdr,
-                                          MESSAGES_COUNT, 0, &invalid_timeout);
-  if (recv_res == -1) {
-    ASSERT_EQ(static_cast<int>(libc_errno), EINVAL);
-    libc_errno = 0;
+  ASSERT_THAT(LIBC_NAMESPACE::recvmmsg(sockpair[1], recv_msg_hdr,
+                                       MESSAGES_COUNT, 0, &invalid_timeout),
+              Fails<int>(EINVAL));
 
-    ASSERT_THAT(LIBC_NAMESPACE::recvmmsg(sockpair[1], recv_msg_hdr,
-                                         MESSAGES_COUNT, 0, nullptr),
-                Succeeds<int>(MESSAGES_COUNT));
-  } else {
-    // Under QEMU, the call might succeed and return MESSAGES_COUNT.
-    ASSERT_EQ(recv_res, static_cast<int>(MESSAGES_COUNT));
-  }
+  ASSERT_THAT(LIBC_NAMESPACE::recvmmsg(sockpair[1], recv_msg_hdr,
+                                       MESSAGES_COUNT, 0, nullptr),
+              Succeeds<int>(MESSAGES_COUNT));
+#endif
 
   for (size_t i = 0; i < MESSAGES_COUNT; ++i) {
     ASSERT_EQ(static_cast<size_t>(recv_msg_hdr[i].msg_len),
@@ -98,15 +99,12 @@ TEST_F(LlvmLibcSendRecvMmsgTest, SendMmsgFails) {
   ASSERT_THAT(LIBC_NAMESPACE::sendmmsg(-1, &msg_hdrs, 1, 0), Fails(EBADF, -1));
 }
 
+// QEMU user space emulation has a bug where recvmmsg on fd -1 returns 1 instead
+// of -1 (with EBADF).
+#ifndef LIBC_TEST_UNDER_EMULATOR
 TEST_F(LlvmLibcSendRecvMmsgTest, RecvmmsgFails) {
   struct mmsghdr msg_hdrs = {};
-  int ret = LIBC_NAMESPACE::recvmmsg(-1, &msg_hdrs, 1, 0, nullptr);
-  if (ret == 1) {
-    // Under QEMU, recvmmsg(-1, ...) is buggy and can return 1 instead of -1
-    // (EBADF).
-    return;
-  }
-  EXPECT_EQ(ret, -1);
-  EXPECT_EQ(static_cast<int>(libc_errno), EBADF);
-  libc_errno = 0;
+  ASSERT_THAT(LIBC_NAMESPACE::recvmmsg(-1, &msg_hdrs, 1, 0, nullptr),
+              Fails(EBADF, -1));
 }
+#endif // LIBC_TEST_UNDER_EMULATOR
diff --git a/libc/test/src/sys/socket/linux/socketopt_test.cpp b/libc/test/src/sys/socket/linux/socketopt_test.cpp
index 979eb57fc29c1..629f1b6ae75b9 100644
--- a/libc/test/src/sys/socket/linux/socketopt_test.cpp
+++ b/libc/test/src/sys/socket/linux/socketopt_test.cpp
@@ -139,11 +139,16 @@ TEST_F(LlvmLibcSocketOptTest, ReceiveTimeout) {
   ASSERT_THAT(LIBC_NAMESPACE::getsockopt(sv[0], SOL_SOCKET, SO_RCVTIMEO,
                                          &retrieved_tv, &retrieved_optlen),
               Succeeds(0));
+#ifdef LIBC_TEST_UNDER_EMULATOR
   // Under QEMU getsockopt(SO_RCVTIMEO) may return a length of 0.
   ASSERT_TRUE(retrieved_optlen == optlen || retrieved_optlen == 0);
   if (retrieved_optlen == optlen) {
     ASSERT_EQ(retrieved_tv.tv_sec, tv.tv_sec);
   }
+#else
+  ASSERT_EQ(retrieved_optlen, optlen);
+  ASSERT_EQ(retrieved_tv.tv_sec, tv.tv_sec);
+#endif
 
   char buffer[10];
   struct timespec start, end;
diff --git a/libc/test/src/sys/socket/linux/socketpair_test.cpp b/libc/test/src/sys/socket/linux/socketpair_test.cpp
index cd450ef61392a..b7a5f0ae6a251 100644
--- a/libc/test/src/sys/socket/linux/socketpair_test.cpp
+++ b/libc/test/src/sys/socket/linux/socketpair_test.cpp
@@ -16,6 +16,7 @@
 
 #include <sys/socket.h> // For AF_UNIX and SOCK_DGRAM
 
+using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::any_of;
 using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
 using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;
 using LlvmLibcSocketPairTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
@@ -34,8 +35,6 @@ TEST_F(LlvmLibcSocketPairTest, LocalSocket) {
 
 TEST_F(LlvmLibcSocketPairTest, SocketFails) {
   int sockpair[2] = {-1, -1};
-  int ret = LIBC_NAMESPACE::socketpair(-1, -1, -1, sockpair);
-  EXPECT_EQ(ret, -1);
-  EXPECT_TRUE(libc_errno == EINVAL || libc_errno == EAFNOSUPPORT);
-  libc_errno = 0;
+  ASSERT_THAT(LIBC_NAMESPACE::socketpair(-1, -1, -1, sockpair),
+              Fails(any_of(EINVAL, EAFNOSUPPORT)));
 }
diff --git a/libc/test/src/unistd/pread_pwrite_test.cpp b/libc/test/src/unistd/pread_pwrite_test.cpp
index ed7d2113b502a..b1491a07f15fb 100644
--- a/libc/test/src/unistd/pread_pwrite_test.cpp
+++ b/libc/test/src/unistd/pread_pwrite_test.cpp
@@ -74,6 +74,11 @@ TEST_F(LlvmLibcUniStd, PWriteFails) {
 
 TEST_F(LlvmLibcUniStd, PReadFails) {
   using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
+#ifdef LIBC_TEST_UNDER_EMULATOR
+  // QEMU returns EFAULT instead of EBADF when reading into a nullptr.
   char buf[1];
   EXPECT_THAT(LIBC_NAMESPACE::pread(-1, buf, 1, 0), Fails<ssize_t>(EBADF));
+#else
+  EXPECT_THAT(LIBC_NAMESPACE::pread(-1, nullptr, 1, 0), Fails<ssize_t>(EBADF));
+#endif
 }
diff --git a/libc/test/src/unistd/read_write_test.cpp b/libc/test/src/unistd/read_write_test.cpp
index 39953a5cd43d6..3fd9c5047fb5d 100644
--- a/libc/test/src/unistd/read_write_test.cpp
+++ b/libc/test/src/unistd/read_write_test.cpp
@@ -58,8 +58,13 @@ TEST_F(LlvmLibcUniStd, WriteFails) {
 TEST_F(LlvmLibcUniStd, ReadFails) {
   using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
 
+#ifdef LIBC_TEST_UNDER_EMULATOR
+  // QEMU returns EFAULT instead of EBADF when reading into a nullptr.
   char buf[1];
   EXPECT_THAT(LIBC_NAMESPACE::read(-1, buf, 1), Fails<ssize_t>(EBADF));
+#else
+  EXPECT_THAT(LIBC_NAMESPACE::read(-1, nullptr, 1), Fails<ssize_t>(EBADF));
+#endif
   EXPECT_THAT(LIBC_NAMESPACE::read(0, reinterpret_cast<void *>(-1), 1),
               Fails<ssize_t>(EFAULT));
 }



More information about the libc-commits mailing list