Repository: rapidsai/kvikio
Pull request #697: Improve unit tests; add a dedicated macro for Linux system calls
Author: login: kingcrimsontianyu
Labels: name: improvement
description: Improves an existing functionality
name: non-breaking
description: Introduces a non-breaking change
name: c++
description: Affects the C++ API of KvikIO
Created: 2025-04-21T21:51:29Z | Merged: 2025-04-22T21:49:14Z

- This PR reorganizes the C++ unit tests to put functionally independent tests in separate binaries. Note that the boilerplate `main.cpp` file is removed and its equivalent replacement is a reference to `GTest::gtest_main` in CMake. If in the future we need a main function with custom logic, we will add the file back.
- This PR also adds a dedicated macro `SYSCALL_CHECK(ret)` for Linux system calls as a replacement for the general assertion `KVIKIO_EXPECT(ret != -1, "extra_msg", GenericSystemError);`


Diff:
diff --git a/cpp/include/kvikio/error.hpp b/cpp/include/kvikio/error.hpp
index 738493f6e8..29538f5b6a 100644
--- a/cpp/include/kvikio/error.hpp
+++ b/cpp/include/kvikio/error.hpp
@@ -285,4 +285,81 @@ void kvikio_assertion(bool condition, const std::string& msg, int line_number, c
 }
 }  // namespace detail
 
+/**
+ * @addtogroup utility_error
+ * @{
+ */
+
+/**
+ * @brief Error checking macro for Linux system call.
+ *
+ * Error checking for a Linux system call typically involves:
+ * - Check the return value of the system call. A value of -1 indicates failure for the overwhelming
+ *   majority of system calls.
+ * - If failure, check the global error number <a
+ *   href="https://man7.org/linux/man-pages/man3/errno.3.html" target="_blank">errno</a>. Use
+ *   Linux utility functions to obtain detailed error information.
+ *
+ * This macro is used to perform the steps above. A simple SYSCALL_CHECK(ret) is
+ * designed for the common cases where an integer of -1 indicates a failure, whereas a more complex
+ * SYSCALL_CHECK(ret, "extra msg", error_value) for the remaining rare cases, such
+ * as `(void*)-1` for mmap. At any rate, if a failure occurs, this macro throws an exception
+ * (kvikio::GenericSystemError) with detailed error information.
+ *
+ * Example:
+ * ```
+ * // Common case: (int)-1 indicates an error.
+ * SYSCALL_CHECK(open(file_name, flags, mode));
+ *
+ * // Rare case: (void*)-1 indicates an error.
+ * SYSCALL_CHECK(mmap(addr, length,prot, flags, fd, offset), "mmap failed",
+ * reinterpret_cast<void*>(-1));
+ * ```
+ *
+ * @param ... This macro accepts the following arguments:
+ *   - The first argument must be the return value of a Linux system call.
+ *   - When given, the second argument is the extra message for the exception. When not specified,
+ *     defaults to empty.
+ *   - When given, the third argument is the error code value used to indicate an error. When not
+ *     specified, defaults to -1.
+ */
+#define SYSCALL_CHECK(...)                                                                \
+  GET_SYSCALL_CHECK_MACRO(__VA_ARGS__, SYSCALL_CHECK_3, SYSCALL_CHECK_2, SYSCALL_CHECK_1) \
+  (__VA_ARGS__)
+/** @} */
+
+#define GET_SYSCALL_CHECK_MACRO(_1, _2, _3, NAME, ...) NAME
+#define SYSCALL_CHECK_1(_return_value)                                   \
+  do {                                                                   \
+    kvikio::detail::check_linux_call(__LINE__, __FILE__, _return_value); \
+  } while (0)
+#define SYSCALL_CHECK_2(_return_value, _extra_msg)                                   \
+  do {                                                                               \
+    kvikio::detail::check_linux_call(__LINE__, __FILE__, _return_value, _extra_msg); \
+  } while (0)
+#define SYSCALL_CHECK_3(_return_value, _extra_msg, _error_value)                                   \
+  do {                                                                                             \
+    kvikio::detail::check_linux_call(__LINE__, __FILE__, _return_value, _extra_msg, _error_value); \
+  } while (0)
+
+namespace detail {
+void handle_linux_call_error(int line_number, char const* filename, std::string_view extra_msg);
+
+inline void check_linux_call(int line_number,
+                             char const* filename,
+                             int return_value,
+                             std::string_view extra_msg = "",
+                             int error_value            = -1)
+{
+  if (return_value == error_value) { handle_linux_call_error(line_number, filename, extra_msg); }
+}
+
+template <typename T>
+void check_linux_call(
+  int line_number, char const* filename, T return_value, std::string_view extra_msg, T error_value)
+{
+  if (return_value == error_value) { handle_linux_call_error(line_number, filename, extra_msg); }
+}
+}  // namespace detail
+
 }  // namespace kvikio
diff --git a/cpp/src/error.cpp b/cpp/src/error.cpp
index f73a2f9334..5d479b36e4 100644
--- a/cpp/src/error.cpp
+++ b/cpp/src/error.cpp
@@ -15,6 +15,7 @@
  */
 
 #include <iostream>
+#include <sstream>
 
 #include <kvikio/error.hpp>
 
@@ -34,6 +35,17 @@ void log_error(std::string_view err_msg, int line_number, char const* filename)
   std::cerr << "KvikIO error at: " << filename << ":" << line_number << ": " << err_msg << "\n";
 }
 
+void handle_linux_call_error(int line_number, char const* filename, std::string_view extra_msg)
+{
+  std::stringstream ss;
+  if (!extra_msg.empty()) { ss << extra_msg << " "; }
+  ss << "Linux system/library function call error at: " << filename << ":" << line_number;
+
+  // std::system_error::what() automatically contains the detailed error description
+  // equivalent to calling strerrordesc_np(errno)
+  throw kvikio::GenericSystemError(ss.str());
+}
+
 }  // namespace detail
 
 }  // namespace kvikio
diff --git a/cpp/src/file_utils.cpp b/cpp/src/file_utils.cpp
index a6f3712ab4..8f37a4dea4 100644
--- a/cpp/src/file_utils.cpp
+++ b/cpp/src/file_utils.cpp
@@ -150,7 +150,7 @@ int open_fd(std::string const& file_path, std::string const& flags, bool o_direc
   KVIKIO_NVTX_FUNC_RANGE();
   // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
   int fd = ::open(file_path.c_str(), open_fd_parse_flags(flags, o_direct), mode);
-  KVIKIO_EXPECT(fd != -1, "Unable to open file", GenericSystemError);
+  SYSCALL_CHECK(fd, "Unable to open file.");
   return fd;
 }
 
@@ -158,7 +158,7 @@ int open_fd(std::string const& file_path, std::string const& flags, bool o_direc
 {
   KVIKIO_NVTX_FUNC_RANGE();
   int ret = fcntl(fd, F_GETFL);  // NOLINT(cppcoreguidelines-pro-type-vararg)
-  KVIKIO_EXPECT(ret != -1, "Unable to retrieve open flags", GenericSystemError);
+  SYSCALL_CHECK(ret, "Unable to retrieve open flags.");
   return ret;
 }
 
@@ -167,7 +167,7 @@ int open_fd(std::string const& file_path, std::string const& flags, bool o_direc
   KVIKIO_NVTX_FUNC_RANGE();
   struct stat st {};
   int ret = fstat(file_descriptor, &st);
-  KVIKIO_EXPECT(ret != -1, "Unable to query file size", GenericSystemError);
+  SYSCALL_CHECK(ret, "Unable to query file size.");
   return static_cast<std::size_t>(st.st_size);
 }
 
diff --git a/cpp/tests/CMakeLists.txt b/cpp/tests/CMakeLists.txt
index cf37a2a751..ae1febebb9 100644
--- a/cpp/tests/CMakeLists.txt
+++ b/cpp/tests/CMakeLists.txt
@@ -17,26 +17,63 @@ enable_testing()
 include(rapids-test)
 rapids_test_init()
 
-file(GLOB SOURCES "*.cpp")
-add_executable(cpp_tests ${SOURCES})
-set_target_properties(
-  cpp_tests
-  PROPERTIES RUNTIME_OUTPUT_DIRECTORY "$<BUILD_INTERFACE:${KvikIO_BINARY_DIR}/gtests>"
-             INSTALL_RPATH "\$ORIGIN/../../../lib"
-             CXX_STANDARD 17
-             CXX_STANDARD_REQUIRED ON
-             # For std:: support of __int128_t. Can be removed once using cuda::std
-             CXX_EXTENSIONS ON
-             CUDA_STANDARD 17
-             CUDA_STANDARD_REQUIRED ON
-)
-target_link_libraries(cpp_tests PRIVATE kvikio::kvikio GTest::gmock GTest::gtest CUDA::cudart)
-
-rapids_test_add(
-  NAME cpp_tests
-  COMMAND cpp_tests
-  GPUS 1
-  INSTALL_COMPONENT_SET testing
-)
+#[=======================================================================[.rst:
+kvikio_add_test
+--------------------
+
+Create a KvikIO test.
+
+.. code-block:: cmake
+
+   kvikio_add_test(NAME <name> SOURCES <sources>)
+
+   ``NAME``
+   Test name. Single-value argument.
+
+   ``SOURCES``
+   List of source files for the test. Multi-value argument.
+#]=======================================================================]
+function(kvikio_add_test)
+  cmake_parse_arguments(
+    _KVIKIO # prefix
+    "" # optional
+    "NAME" # single value
+    "SOURCES" # multi-value
+    ${ARGN}
+  )
+
+  if(DEFINED _KVIKIO_UNPARSED_ARGUMENTS)
+    message(FATAL_ERROR "Unknown argument: ${_KVIKIO_UNPARSED_ARGUMENTS}")
+  endif()
+
+  add_executable(${_KVIKIO_NAME} ${_KVIKIO_SOURCES})
+  set_target_properties(
+    ${_KVIKIO_NAME}
+    PROPERTIES RUNTIME_OUTPUT_DIRECTORY "$<BUILD_INTERFACE:${KvikIO_BINARY_DIR}/gtests>"
+               CXX_STANDARD 17
+               CXX_STANDARD_REQUIRED ON
+               # For std:: support of __int128_t. Can be removed once using cuda::std
+               CXX_EXTENSIONS ON
+               CUDA_STANDARD 17
+               CUDA_STANDARD_REQUIRED ON
+  )
+  target_link_libraries(
+    ${_KVIKIO_NAME} PRIVATE kvikio::kvikio GTest::gmock GTest::gmock_main GTest::gtest
+                            GTest::gtest_main CUDA::cudart
+  )
+
+  rapids_test_add(
+    NAME ${_KVIKIO_NAME}
+    COMMAND ${_KVIKIO_NAME}
+    GPUS 1
+    INSTALL_COMPONENT_SET testing
+  )
+endfunction()
+
+kvikio_add_test(NAME BASIC_IO_TEST SOURCES test_basic_io.cpp)
+
+kvikio_add_test(NAME DEFAULTS_TEST SOURCES test_defaults.cpp)
+
+kvikio_add_test(NAME ERROR_TEST SOURCES test_error.cpp)
 
 rapids_test_install_relocatable(INSTALL_COMPONENT_SET testing DESTINATION bin/tests/libkvikio)
diff --git a/cpp/tests/main.cpp b/cpp/tests/main.cpp
deleted file mode 100644
index 73acda862a..0000000000
--- a/cpp/tests/main.cpp
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (c) 2024, NVIDIA CORPORATION.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <gtest/gtest.h>
-
-int main(int argc, char** argv)
-{
-  ::testing::InitGoogleTest(&argc, argv);
-  return RUN_ALL_TESTS();
-}
diff --git a/cpp/tests/test_basic_io.cpp b/cpp/tests/test_basic_io.cpp
index a8f1aaf549..d72ba8841f 100644
--- a/cpp/tests/test_basic_io.cpp
+++ b/cpp/tests/test_basic_io.cpp
@@ -16,7 +16,7 @@
 
 #include <kvikio/defaults.hpp>
 #include <kvikio/file_handle.hpp>
-#include "utils.hpp"
+#include "utils/utils.hpp"
 
 using namespace kvikio::test;
 
diff --git a/cpp/tests/test_defaults.cpp b/cpp/tests/test_defaults.cpp
index 99a597032d..f4f3a92dc1 100644
--- a/cpp/tests/test_defaults.cpp
+++ b/cpp/tests/test_defaults.cpp
@@ -19,7 +19,7 @@
 #include <gtest/gtest.h>
 #include <kvikio/defaults.hpp>
 
-TEST(Defaults, parse_compat_mode_str)
+TEST(DefaultsTest, parse_compat_mode_str)
 {
   {
     std::vector<std::string> inputs{
@@ -52,7 +52,7 @@ TEST(Defaults, parse_compat_mode_str)
   }
 }
 
-TEST(Defaults, parse_http_status_codes)
+TEST(DefaultsTest, parse_http_status_codes)
 {
   {
     std::vector<std::string> inputs{
diff --git a/cpp/tests/test_error.cpp b/cpp/tests/test_error.cpp
new file mode 100644
index 0000000000..27713b7830
--- /dev/null
+++ b/cpp/tests/test_error.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2025, NVIDIA CORPORATION.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fcntl.h>
+#include <sys/mman.h>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <kvikio/error.hpp>
+#include <kvikio/file_handle.hpp>
+
+using ::testing::HasSubstr;
+using ::testing::ThrowsMessage;
+
+TEST(ErrorTest, syscall_check_for_int_return_value)
+{
+  auto open_nonexistent_file = []() -> auto {
+    int flag    = O_RDONLY;
+    mode_t mode = kvikio::FileHandle::m644;
+    return open("kvikio_nonexistent_file.bin", flag, mode);
+  };
+
+  // If the file does not exist, open() returns (int)-1, and the error number is ENOENT (No such
+  // file or directory).
+  EXPECT_THAT([=] { SYSCALL_CHECK(open_nonexistent_file()); },
+              ThrowsMessage<kvikio::GenericSystemError>(HasSubstr("No such file or directory")));
+  EXPECT_THAT([=] { SYSCALL_CHECK(open_nonexistent_file(), "open failed.", -1); },
+              ThrowsMessage<kvikio::GenericSystemError>(HasSubstr("No such file or directory")));
+}
+
+TEST(ErrorTest, syscall_check_for_voidp_return_value)
+{
+  auto map_anonymous_with_0_length = []() -> auto {
+    std::size_t length = 0;
+    int fd             = -1;
+    off_t offset       = 0;
+    return mmap(nullptr, length, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, fd, offset);
+  };
+
+  // If the mapping fails, mmap() returns MAP_FAILED i.e. (void*)-1, and the error number is EINVAL
+  // (invalid argument).
+  EXPECT_THAT([=] { SYSCALL_CHECK(map_anonymous_with_0_length(), "mmap failed.", MAP_FAILED); },
+              ThrowsMessage<kvikio::GenericSystemError>(HasSubstr("Invalid argument")));
+}
diff --git a/cpp/tests/utils.hpp b/cpp/tests/utils/utils.hpp
similarity index 100%
rename from cpp/tests/utils.hpp
rename to cpp/tests/utils/utils.hpp
diff --git a/python/kvikio/tests/test_basic_io.py b/python/kvikio/tests/test_basic_io.py
index 744941861a..f1cef48b65 100644
--- a/python/kvikio/tests/test_basic_io.py
+++ b/python/kvikio/tests/test_basic_io.py
@@ -77,7 +77,9 @@ def test_no_file_error(tmp_path):
     """Test "No such file" error"""
 
     filename = tmp_path / "test-file"
-    with pytest.raises(RuntimeError, match="Unable to open file: No such file"):
+    with pytest.raises(
+        RuntimeError, match="Unable to open file.*No such file or directory"
+    ):
         kvikio.CuFile(filename, "r")
 
 


Reviews:
user: login: bdice
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T03:21:04Z
user: login: kingcrimsontianyu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T04:19:08Z
user: login: kingcrimsontianyu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T04:23:21Z
user: login: bdice
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T05:33:16Z
user: login: vuule
body: looks good, just one possible suggestion
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T19:13:25Z
user: login: kingcrimsontianyu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T19:51:58Z
user: login: vuule
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T20:13:32Z

Review comments:
diff_hunk: @@ -34,6 +35,20 @@ void log_error(std::string_view err_msg, int line_number, char const* filename)
   std::cerr << "KvikIO error at: " << filename << ":" << line_number << ": " << err_msg << "\n";
 }
 
+void handle_linux_call_error(int line_number, char const* filename, std::string_view extra_msg)
+{
+  auto err_name = strerrorname_np(errno);
path: cpp/src/error.cpp
user: login: bdice
body: Do we need to check if the compiler is GNU with some kind of macro? This is a GNU extension, according to https://www.gnu.org/software/libc/manual/html_node/Error-Messages.html. Please check what happens with, e.g. clang.

Also we probably need to `#include <string.h>` to use `strerrorname_np`.
created_at: 2025-04-22T03:19:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2025, NVIDIA CORPORATION.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fcntl.h>
+#include <sys/mman.h>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <kvikio/error.hpp>
+#include <kvikio/file_handle.hpp>
+#include "gmock/gmock.h"
path: cpp/tests/test_error.cpp
user: login: bdice
body: This is included twice, once with "" and once with <>.
created_at: 2025-04-22T03:20:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2025, NVIDIA CORPORATION.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fcntl.h>
+#include <sys/mman.h>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <kvikio/error.hpp>
+#include <kvikio/file_handle.hpp>
+#include "gmock/gmock.h"
path: cpp/tests/test_error.cpp
user: login: kingcrimsontianyu
body: Thanks. Fixed.
created_at: 2025-04-22T04:19:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -34,6 +35,20 @@ void log_error(std::string_view err_msg, int line_number, char const* filename)
   std::cerr << "KvikIO error at: " << filename << ":" << line_number << ": " << err_msg << "\n";
 }
 
+void handle_linux_call_error(int line_number, char const* filename, std::string_view extra_msg)
+{
+  auto err_name = strerrorname_np(errno);
path: cpp/src/error.cpp
user: login: kingcrimsontianyu
body: Good catch. Clang supports `_GNU_SOURCE` but I'm hesitant to continue using this non-POSIX/C++-standard feature now. Scrapped related code.
created_at: 2025-04-22T04:23:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2025, NVIDIA CORPORATION.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fcntl.h>
+#include <sys/mman.h>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <kvikio/error.hpp>
+#include <kvikio/file_handle.hpp>
+
+using ::testing::HasSubstr;
+using ::testing::ThrowsMessage;
+
+TEST(ErrorTest, syscall_check_for_int_return_value)
+{
+  auto open_nonexistent_file = []() -> auto {
+    int flag    = O_RDONLY;
+    mode_t mode = kvikio::FileHandle::m644;
+    return open("kvikio_nonexistent_file.bin", flag, mode);
+  };
+
+  // If the file does not exist, open() returns (int)-1, and the error number is ENOENT (No such
+  // file or directory).
+  EXPECT_THAT([=] { SYSCALL_CHECK(open_nonexistent_file()); },
+              ThrowsMessage<kvikio::GenericSystemError>(HasSubstr("No such file or directory")));
+  EXPECT_THAT([=] { SYSCALL_CHECK(open_nonexistent_file(), "open failed.", -1); },
+              ThrowsMessage<kvikio::GenericSystemError>(HasSubstr("No such file or directory")));
+}
+
+TEST(ErrorTest, syscall_check_for_voidp_return_value)
+{
+  auto map_anonymous_with_0_length = []() -> auto {
+    std::size_t length = 0;
+    int fd             = -1;
+    off_t offset       = 0;
+    return mmap(nullptr, length, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, fd, offset);
+  };
+
+  // If the mapping fails, mmap() returns (void*)-1, and the error number is EINVAL (invalid
+  // argument).
+  EXPECT_THAT(
+    [=] {
+      SYSCALL_CHECK(map_anonymous_with_0_length(), "mmap failed.", reinterpret_cast<void*>(-1));
path: cpp/tests/test_error.cpp
user: login: vuule
body: could we just do this?
```suggestion
      SYSCALL_CHECK(map_anonymous_with_0_length(), "mmap failed.", MAP_FAILED);
```
created_at: 2025-04-22T19:12:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2025, NVIDIA CORPORATION.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fcntl.h>
+#include <sys/mman.h>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <kvikio/error.hpp>
+#include <kvikio/file_handle.hpp>
+
+using ::testing::HasSubstr;
+using ::testing::ThrowsMessage;
+
+TEST(ErrorTest, syscall_check_for_int_return_value)
+{
+  auto open_nonexistent_file = []() -> auto {
+    int flag    = O_RDONLY;
+    mode_t mode = kvikio::FileHandle::m644;
+    return open("kvikio_nonexistent_file.bin", flag, mode);
+  };
+
+  // If the file does not exist, open() returns (int)-1, and the error number is ENOENT (No such
+  // file or directory).
+  EXPECT_THAT([=] { SYSCALL_CHECK(open_nonexistent_file()); },
+              ThrowsMessage<kvikio::GenericSystemError>(HasSubstr("No such file or directory")));
+  EXPECT_THAT([=] { SYSCALL_CHECK(open_nonexistent_file(), "open failed.", -1); },
+              ThrowsMessage<kvikio::GenericSystemError>(HasSubstr("No such file or directory")));
+}
+
+TEST(ErrorTest, syscall_check_for_voidp_return_value)
+{
+  auto map_anonymous_with_0_length = []() -> auto {
+    std::size_t length = 0;
+    int fd             = -1;
+    off_t offset       = 0;
+    return mmap(nullptr, length, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, fd, offset);
+  };
+
+  // If the mapping fails, mmap() returns (void*)-1, and the error number is EINVAL (invalid
+  // argument).
+  EXPECT_THAT(
+    [=] {
+      SYSCALL_CHECK(map_anonymous_with_0_length(), "mmap failed.", reinterpret_cast<void*>(-1));
path: cpp/tests/test_error.cpp
user: login: kingcrimsontianyu
body: Thanks. Done.
created_at: 2025-04-22T19:51:58Z
author_association: CONTRIBUTOR

PR comments:
user: login: copy-pr-bot[bot]
created_at: 2025-04-21T21:51:33Z
body: Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message [here](https://docs.gha-runners.nvidia.com/cpr/auto-sync).

author_association: NONE
user: login: kingcrimsontianyu
created_at: 2025-04-22T21:49:10Z
body: /merge
author_association: CONTRIBUTOR