| //===-- ErrnoSetterMatcher.h ------------------------------------*- C++ -*-===// |
| // |
| // 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 |
| // |
| //===----------------------------------------------------------------------===// |
| /// \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 |
| |
| #include "src/__support/FPUtil/FPBits.h" |
| #include "src/__support/FPUtil/fpbits_str.h" |
| #include "src/__support/StringUtil/error_to_string.h" |
| #include "src/__support/libc_errno.h" |
| #include "src/__support/macros/config.h" |
| #include "src/__support/macros/properties/architectures.h" |
| #include "test/UnitTest/Test.h" |
| |
| 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 }; |
| |
| constexpr const char *CompareMessage[] = { |
| "equal to", "greater than or equal to", |
| "greater than", "less than or equal to", |
| "less than", "not equal to"}; |
| |
| template <typename T> struct Comparator { |
| CompareAction cmp; |
| T expected; |
| bool compare(T actual) { |
| switch (cmp) { |
| case CompareAction::EQ: |
| return actual == expected; |
| case CompareAction::NE: |
| return actual != expected; |
| case CompareAction::GE: |
| return actual >= expected; |
| case CompareAction::GT: |
| return actual > expected; |
| case CompareAction::LE: |
| return actual <= expected; |
| case CompareAction::LT: |
| return actual < expected; |
| } |
| __builtin_unreachable(); |
| } |
| |
| // The NVPTX backend cannot handle circular dependencies on global variables. |
| // We provide a constant dummy implementation to prevent this from occurring. |
| #ifdef LIBC_TARGET_ARCH_IS_NVPTX |
| constexpr const char *str() { return ""; } |
| #else |
| const char *str() { return CompareMessage[static_cast<int>(cmp)]; } |
| #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; |
| ErrnoCheck errno_cmp; |
| T actual_return; |
| int actual_errno; |
| |
| // Even though this is a errno matcher primarily, it has to cater to platforms |
| // which do not have an errno. This predicate checks if errno matching is to |
| // be skipped. |
| static constexpr bool ignore_errno() { |
| #ifdef LIBC_TARGET_ARCH_IS_GPU |
| return true; |
| #else |
| return false; |
| #endif |
| } |
| |
| public: |
| ErrnoSetterMatcher(Comparator<T> rcmp) : return_cmp(rcmp) {} |
| ErrnoSetterMatcher(Comparator<T> rcmp, ErrnoCheck ecmp) |
| : return_cmp(rcmp), errno_cmp(ecmp) {} |
| |
| ErrnoSetterMatcher<T> with_errno(ErrnoCheck ecmp) { |
| errno_cmp = ecmp; |
| return *this; |
| } |
| |
| void explainError() override { |
| if (!return_cmp.compare(actual_return)) { |
| if constexpr (cpp::is_floating_point_v<T>) { |
| tlog << "Expected return value to be " << return_cmp.str() << ": " |
| << str(fputil::FPBits<T>(return_cmp.expected)) << '\n' |
| << " But got: " |
| << str(fputil::FPBits<T>(actual_return)) << '\n'; |
| } else { |
| tlog << "Expected return value to be " << return_cmp.str() << " " |
| << return_cmp.expected << " but got " << actual_return << ".\n"; |
| } |
| } |
| |
| if constexpr (!ignore_errno()) { |
| if (!errno_cmp.compare(actual_errno)) { |
| auto actual_str = try_get_errno_name(actual_errno); |
| tlog << "Expected errno to be "; |
| errno_cmp.print_expected(); |
| tlog << " but got " << (actual_str ? *actual_str : "<unknown>") << "(" |
| << actual_errno << ").\n"; |
| } |
| } |
| } |
| |
| bool match(T got) { |
| actual_return = got; |
| actual_errno = libc_errno; |
| libc_errno = 0; |
| if constexpr (ignore_errno()) |
| return return_cmp.compare(actual_return); |
| else |
| return return_cmp.compare(actual_return) && |
| errno_cmp.compare(actual_errno); |
| } |
| }; |
| |
| } // namespace internal |
| |
| namespace ErrnoSetterMatcher { |
| |
| template <typename T> internal::Comparator<T> LT(T val) { |
| return internal::Comparator<T>{internal::CompareAction::LT, val}; |
| } |
| |
| template <typename T> internal::Comparator<T> LE(T val) { |
| return internal::Comparator<T>{internal::CompareAction::LE, val}; |
| } |
| |
| template <typename T> internal::Comparator<T> GT(T val) { |
| return internal::Comparator<T>{internal::CompareAction::GT, val}; |
| } |
| |
| template <typename T> internal::Comparator<T> GE(T val) { |
| return internal::Comparator<T>{internal::CompareAction::GE, val}; |
| } |
| |
| template <typename T> internal::Comparator<T> EQ(T val) { |
| return internal::Comparator<T>{internal::CompareAction::EQ, val}; |
| } |
| |
| template <typename T> internal::Comparator<T> NE(T val) { |
| return internal::Comparator<T>{internal::CompareAction::NE, val}; |
| } |
| |
| template <typename RetT = int> |
| internal::ErrnoSetterMatcher<RetT> Succeeds(RetT ExpectedReturn = 0, |
| int ExpectedErrno = 0) { |
| return internal::ErrnoSetterMatcher<RetT>(EQ(ExpectedReturn), |
| EQ(ExpectedErrno)); |
| } |
| |
| template <typename RetT = int> |
| internal::ErrnoSetterMatcher<RetT> Fails(int ExpectedErrno, |
| RetT ExpectedReturn = -1) { |
| return internal::ErrnoSetterMatcher<RetT>(EQ(ExpectedReturn), |
| 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(internal::ErrnoCheck cmp) { |
| return internal::ErrnoSetterMatcher<RetT>(return_cmp, cmp); |
| } |
| |
| private: |
| Cmp<RetT> return_cmp; |
| }; |
| |
| template <typename RetT> |
| ErrnoSetterMatcherBuilder<RetT> returns(internal::Comparator<RetT> cmp) { |
| return ErrnoSetterMatcherBuilder<RetT>(cmp); |
| } |
| |
| } // namespace ErrnoSetterMatcher |
| |
| } // namespace testing |
| } // namespace LIBC_NAMESPACE_DECL |
| |
| #endif // LLVM_LIBC_TEST_ERRNOSETTERMATCHER_H |