diff --git a/libc/test/src/signal/sigaddset_test.cpp b/libc/test/src/signal/sigaddset_test.cpp --- a/libc/test/src/signal/sigaddset_test.cpp +++ b/libc/test/src/signal/sigaddset_test.cpp @@ -11,32 +11,24 @@ #include "src/errno/llvmlibc_errno.h" #include "src/signal/sigaddset.h" +#include "utils/UnitTest/ErrnoSetterMatcher.h" #include "utils/UnitTest/Test.h" // This tests invalid inputs and ensures errno is properly set. TEST(SignalTest, SigaddsetInvalid) { - llvmlibc_errno = 0; - EXPECT_EQ(__llvm_libc::sigaddset(nullptr, SIGSEGV), -1); - EXPECT_EQ(llvmlibc_errno, EINVAL); + using __llvm_libc::testing::ErrnoSetterMatcher::Fails; + using __llvm_libc::testing::ErrnoSetterMatcher::Succeeds; + EXPECT_THAT(__llvm_libc::sigaddset(nullptr, SIGSEGV), Fails(EINVAL)); sigset_t sigset; - llvmlibc_errno = 0; - EXPECT_EQ(__llvm_libc::sigaddset(&sigset, -1), -1); - EXPECT_EQ(llvmlibc_errno, EINVAL); + EXPECT_THAT(__llvm_libc::sigaddset(&sigset, -1), Fails(EINVAL)); // This doesn't use NSIG because __llvm_libc::sigaddset error checking is // against sizeof(sigset_t) not NSIG. constexpr int bitsInSigsetT = 8 * sizeof(sigset_t); - llvmlibc_errno = 0; - EXPECT_EQ(__llvm_libc::sigaddset(&sigset, bitsInSigsetT + 1), -1); - EXPECT_EQ(llvmlibc_errno, EINVAL); - - llvmlibc_errno = 0; - EXPECT_EQ(__llvm_libc::sigaddset(&sigset, 0), -1); - EXPECT_EQ(llvmlibc_errno, EINVAL); - - llvmlibc_errno = 0; - EXPECT_EQ(__llvm_libc::sigaddset(&sigset, bitsInSigsetT), 0); - EXPECT_EQ(llvmlibc_errno, 0); + EXPECT_THAT(__llvm_libc::sigaddset(&sigset, bitsInSigsetT + 1), + Fails(EINVAL)); + EXPECT_THAT(__llvm_libc::sigaddset(&sigset, 0), Fails(EINVAL)); + EXPECT_THAT(__llvm_libc::sigaddset(&sigset, bitsInSigsetT), Succeeds()); } diff --git a/libc/utils/UnitTest/CMakeLists.txt b/libc/utils/UnitTest/CMakeLists.txt --- a/libc/utils/UnitTest/CMakeLists.txt +++ b/libc/utils/UnitTest/CMakeLists.txt @@ -2,6 +2,7 @@ LibcUnitTest Test.cpp Test.h + ErrnoSetterMatcher.h LINK_COMPONENTS Support ) target_include_directories(LibcUnitTest PUBLIC ${LIBC_SOURCE_DIR}) diff --git a/libc/utils/UnitTest/ErrnoSetterMatcher.h b/libc/utils/UnitTest/ErrnoSetterMatcher.h new file mode 100644 --- /dev/null +++ b/libc/utils/UnitTest/ErrnoSetterMatcher.h @@ -0,0 +1,76 @@ +//===--------------------- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_UTILS_UNITTEST_ERRNOSETTERMATCHER_H +#define LLVM_LIBC_UTILS_UNITTEST_ERRNOSETTERMATCHER_H + +#include "Test.h" + +// Using LLVM libc headers in UnitTest is not ideal however we also want the +// test/ directory to have the same layout as libc/ so there is no clean place +// to put this file except for in utils/UnitTest/. +#include "src/errno/llvmlibc_errno.h" + +namespace __llvm_libc { +namespace testing { + +namespace internal { + +extern "C" const char *strerror(int); + +template class ErrnoSetterMatcher : public Matcher { + T ExpectedReturn; + T ActualReturn; + int ExpectedErrno; + int ActualErrno; + +public: + ErrnoSetterMatcher(T ExpectedReturn, int ExpectedErrno) + : ExpectedReturn(ExpectedReturn), ExpectedErrno(ExpectedErrno) {} + + void explainError(testutils::StreamWrapper &OS) override { + if (ActualReturn != ExpectedReturn) + OS << "Expected return to be " << ExpectedReturn << " but got " + << ActualReturn << ".\nExpecte errno to be " << strerror(ExpectedErrno) + << " but got " << strerror(ActualErrno) << ".\n"; + else + OS << "Correct value " << ExpectedReturn + << " was returned\nBut errno was unexpectely set to " + << strerror(ActualErrno) << ".\n"; + } + + bool match(T Got) { + ActualReturn = Got; + ActualErrno = llvmlibc_errno; + llvmlibc_errno = 0; + return Got == ExpectedReturn && ActualErrno == ExpectedErrno; + } +}; + +} // namespace internal + +namespace ErrnoSetterMatcher { + +template +static internal::ErrnoSetterMatcher Succeeds(RetT ExpectedReturn = 0, + int ExpectedErrno = 0) { + return {ExpectedReturn, ExpectedErrno}; +} + +template +static internal::ErrnoSetterMatcher Fails(int ExpectedErrno, + RetT ExpectedReturn = -1) { + return {ExpectedReturn, ExpectedErrno}; +} + +} // namespace ErrnoSetterMatcher + +} // namespace testing +} // namespace __llvm_libc + +#endif // LLVM_LIBC_UTILS_UNITTEST_ERRNOSETTERMATCHER_H diff --git a/libc/utils/UnitTest/Test.h b/libc/utils/UnitTest/Test.h --- a/libc/utils/UnitTest/Test.h +++ b/libc/utils/UnitTest/Test.h @@ -14,6 +14,7 @@ #include "utils/CPP/TypeTraits.h" #include "utils/testutils/ExecuteFunction.h" +#include "utils/testutils/StreamWrapper.h" namespace __llvm_libc { namespace testing { @@ -44,6 +45,15 @@ } // namespace internal +struct MatcherBase { + virtual ~MatcherBase() {} + virtual void explainError(testutils::StreamWrapper &OS) { + OS << "unknown error\n"; + } +}; + +template struct Matcher : public MatcherBase { bool match(T &t); }; + // NOTE: One should not create instances and call methods on them directly. One // should use the macros TEST or TEST_F to write test cases. class Test { @@ -93,6 +103,10 @@ const char *LHSStr, const char *RHSStr, const char *File, unsigned long Line); + static bool testMatch(RunContext &Ctx, bool MatchResult, MatcherBase &Matcher, + const char *LHSStr, const char *RHSStr, + const char *File, unsigned long Line); + static bool testProcessExits(RunContext &Ctx, testutils::FunctionCaller *Func, int ExitCode, const char *LHSStr, const char *RHSStr, const char *File, @@ -230,4 +244,18 @@ if (!EXPECT_DEATH(FUNC, EXIT)) \ return +#define __CAT1(a, b) a##b +#define __CAT(a, b) __CAT1(a, b) +#define UNIQUE_VAR(prefix) __CAT(prefix, __LINE__) + +#define EXPECT_THAT(MATCH, MATCHER) \ + auto UNIQUE_VAR(__matcher) = (MATCHER); \ + __llvm_libc::testing::Test::testMatch( \ + Ctx, UNIQUE_VAR(__matcher).match((MATCH)), UNIQUE_VAR(__matcher), \ + #MATCH, #MATCHER, __FILE__, __LINE__) + +#define ASSERT_THAT(MATCH, MATCHER) \ + if (!EXPECT_THAT(MATCH, MATCHER)) \ + return + #endif // LLVM_LIBC_UTILS_UNITTEST_H diff --git a/libc/utils/UnitTest/Test.cpp b/libc/utils/UnitTest/Test.cpp --- a/libc/utils/UnitTest/Test.cpp +++ b/libc/utils/UnitTest/Test.cpp @@ -232,6 +232,21 @@ llvm::StringRef(RHS), LHSStr, RHSStr, File, Line); } +bool Test::testMatch(RunContext &Ctx, bool MatchResult, MatcherBase &Matcher, + const char *LHSStr, const char *RHSStr, const char *File, + unsigned long Line) { + if (MatchResult) + return true; + + Ctx.markFail(); + llvm::outs() << File << ":" << Line << ": FAILURE\n" + << "Failed to match " << LHSStr << " against " << RHSStr + << ".\n"; + testutils::StreamWrapper OutsWrapper = testutils::outs(); + Matcher.explainError(OutsWrapper); + return false; +} + bool Test::testProcessKilled(RunContext &Ctx, testutils::FunctionCaller *Func, int Signal, const char *LHSStr, const char *RHSStr, const char *File, unsigned long Line) { diff --git a/libc/utils/testutils/CMakeLists.txt b/libc/utils/testutils/CMakeLists.txt --- a/libc/utils/testutils/CMakeLists.txt +++ b/libc/utils/testutils/CMakeLists.txt @@ -4,7 +4,10 @@ add_llvm_library( libc_test_utils + StreamWrapper.cpp + StreamWrapper.h ${EFFile} ExecuteFunction.h - LINK_COMPONENTS Support + LINK_COMPONENTS + Support ) diff --git a/libc/utils/testutils/StreamWrapper.h b/libc/utils/testutils/StreamWrapper.h new file mode 100644 --- /dev/null +++ b/libc/utils/testutils/StreamWrapper.h @@ -0,0 +1,32 @@ +//===------------------------ StreamWrapper.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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_UTILS_TESTUTILS_STREAMWRAPPER_H +#define LLVM_LIBC_UTILS_TESTUTILS_STREAMWRAPPER_H + +namespace __llvm_libc { +namespace testutils { + +// StreamWrapper is necessary because llvm/Support/raw_ostream.h includes +// standard headers so we must provide streams through indirection to not +// expose the system libc headers. +class StreamWrapper { + void *OS; + +public: + StreamWrapper(void *OS) : OS(OS) {} + + template StreamWrapper &operator<<(T t); +}; + +StreamWrapper outs(); + +} // namespace testutils +} // namespace __llvm_libc + +#endif // LLVM_LIBC_UTILS_TESTUTILS_STREAMWRAPPER_H diff --git a/libc/utils/testutils/StreamWrapper.cpp b/libc/utils/testutils/StreamWrapper.cpp new file mode 100644 --- /dev/null +++ b/libc/utils/testutils/StreamWrapper.cpp @@ -0,0 +1,45 @@ +//===--------------------------- StreamWrapper.cpp ------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "StreamWrapper.h" +#include "llvm/Support/raw_ostream.h" +#include +#include + +namespace __llvm_libc { +namespace testutils { + +StreamWrapper outs() { return {std::addressof(llvm::outs())}; } + +template StreamWrapper &StreamWrapper::operator<<(T t) { + assert(OS); + llvm::raw_ostream &Stream = *reinterpret_cast(OS); + Stream << t; + return *this; +} + +template StreamWrapper &StreamWrapper::operator<<(const char *t); +template StreamWrapper &StreamWrapper::operator<<(char *t); +template StreamWrapper &StreamWrapper::operator<<(char t); +template StreamWrapper &StreamWrapper::operator<<(short t); +template StreamWrapper &StreamWrapper::operator<<(int t); +template StreamWrapper &StreamWrapper::operator<<(long t); +template StreamWrapper &StreamWrapper::operator<<(long long t); +template StreamWrapper & + StreamWrapper::operator<<(unsigned char t); +template StreamWrapper & + StreamWrapper::operator<<(unsigned short t); +template StreamWrapper &StreamWrapper::operator<<(unsigned int t); +template StreamWrapper & + StreamWrapper::operator<<(unsigned long t); +template StreamWrapper & + StreamWrapper::operator<<(unsigned long long t); +template StreamWrapper &StreamWrapper::operator<<(bool t); + +} // namespace testutils +} // namespace __llvm_libc