//===-- Base class for libc unittests ---------------------------*- 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_TEST_UNITTEST_LIBCTEST_H #define LLVM_LIBC_TEST_UNITTEST_LIBCTEST_H // This is defined as a simple macro in test.h so that it exists for platforms // that don't use our test infrastructure. It's defined as a proper function // below. #include "src/__support/macros/config.h" #ifdef libc_make_test_file_path #undef libc_make_test_file_path #endif // libc_make_test_file_path // This is defined as a macro here to avoid namespace issues. #define libc_make_test_file_path(file_name) \ (LIBC_NAMESPACE::testing::libc_make_test_file_path_func(file_name)) // This file can only include headers from src/__support/ or test/UnitTest. No // other headers should be included. #include "PlatformDefs.h" #include "src/__support/CPP/string.h" #include "src/__support/CPP/string_view.h" #include "src/__support/CPP/type_traits.h" #include "src/__support/c_string.h" #include "test/UnitTest/ExecuteFunction.h" #include "test/UnitTest/TestLogger.h" namespace LIBC_NAMESPACE_DECL { namespace testing { // Only the following conditions are supported. Notice that we do not have // a TRUE or FALSE condition. That is because, C library functions do not // return boolean values, but use integral return values to indicate true or // false conditions. Hence, it is more appropriate to use the other comparison // conditions for such cases. enum class TestCond { EQ, NE, LT, LE, GT, GE }; struct MatcherBase { virtual ~MatcherBase() {} virtual void explainError() { tlog << "unknown error\n"; } // Override and return true to skip `explainError` step. virtual bool is_silent() const { return false; } }; template struct Matcher : public MatcherBase { bool match(const T &t); }; namespace internal { // A simple location object to allow consistent passing of __FILE__ and // __LINE__. struct Location { Location(const char *file, int line) : file(file), line(line) {} const char *file; int line; }; // Supports writing a failing Location to tlog. TestLogger &operator<<(TestLogger &logger, Location Loc); #define LIBC_TEST_LOC_() \ LIBC_NAMESPACE::testing::internal::Location(__FILE__, __LINE__) // Object to forward custom logging after the EXPECT / ASSERT macros. struct Message { template Message &operator<<(T value) { tlog << value; return *this; } }; // A trivial object to catch the Message, this enables custom logging and // returning from the test function, see LIBC_TEST_SCAFFOLDING_ below. struct Failure { void operator=(Message msg) {} }; struct RunContext { enum class RunResult : bool { Pass, Fail }; RunResult status() const { return Status; } void markFail() { Status = RunResult::Fail; } private: RunResult Status = RunResult::Pass; }; template bool test(RunContext *Ctx, TestCond Cond, ValType LHS, ValType RHS, const char *LHSStr, const char *RHSStr, Location Loc); } // namespace internal struct TestOptions { // If set, then just this one test from the suite will be run. const char *TestFilter = nullptr; // Should the test results print color codes to stdout? bool PrintColor = true; // Should the test results print timing only in milliseconds, as GTest does? bool TimeInMs = false; }; // 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 { Test *Next = nullptr; internal::RunContext *Ctx = nullptr; void setContext(internal::RunContext *C) { Ctx = C; } static int getNumTests(); public: virtual ~Test() {} virtual void SetUp() {} virtual void TearDown() {} static int runTests(const TestOptions &Options); protected: static void addTest(Test *T); // We make use of a template function, with |LHS| and |RHS| as explicit // parameters, for enhanced type checking. Other gtest like unittest // frameworks have a similar function which takes a boolean argument // instead of the explicit |LHS| and |RHS| arguments. This boolean argument // is the result of the |Cond| operation on |LHS| and |RHS|. Though not bad, // |Cond| on mismatched |LHS| and |RHS| types can potentially succeed because // of type promotion. template < typename ValType, cpp::enable_if_t || is_big_int_v || cpp::is_fixed_point_v, int> = 0> bool test(TestCond Cond, ValType LHS, ValType RHS, const char *LHSStr, const char *RHSStr, internal::Location Loc) { return internal::test(Ctx, Cond, LHS, RHS, LHSStr, RHSStr, Loc); } template , int> = 0> bool test(TestCond Cond, ValType LHS, ValType RHS, const char *LHSStr, const char *RHSStr, internal::Location Loc) { return internal::test(Ctx, Cond, (long long)LHS, (long long)RHS, LHSStr, RHSStr, Loc); } template , ValType> = nullptr> bool test(TestCond Cond, ValType LHS, ValType RHS, const char *LHSStr, const char *RHSStr, internal::Location Loc) { return internal::test(Ctx, Cond, (unsigned long long)LHS, (unsigned long long)RHS, LHSStr, RHSStr, Loc); } // Helper to allow macro invocations like `ASSERT_EQ(foo, nullptr)`. template , ValType> = nullptr> bool test(TestCond Cond, ValType LHS, cpp::nullptr_t, const char *LHSStr, const char *RHSStr, internal::Location Loc) { return test(Cond, LHS, static_cast(nullptr), LHSStr, RHSStr, Loc); } template < typename ValType, cpp::enable_if_t< cpp::is_same_v, int> = 0> bool test(TestCond Cond, ValType LHS, ValType RHS, const char *LHSStr, const char *RHSStr, internal::Location Loc) { return internal::test(Ctx, Cond, LHS, RHS, LHSStr, RHSStr, Loc); } template , int> = 0> bool test(TestCond Cond, ValType LHS, ValType RHS, const char *LHSStr, const char *RHSStr, internal::Location Loc) { return internal::test(Ctx, Cond, LHS, RHS, LHSStr, RHSStr, Loc); } bool testStrEq(const char *LHS, const char *RHS, const char *LHSStr, const char *RHSStr, internal::Location Loc); bool testStrNe(const char *LHS, const char *RHS, const char *LHSStr, const char *RHSStr, internal::Location Loc); bool testMatch(bool MatchResult, MatcherBase &Matcher, const char *LHSStr, const char *RHSStr, internal::Location Loc); template bool matchAndExplain(MatcherT &&Matcher, ValType Value, const char *MatcherStr, const char *ValueStr, internal::Location Loc) { return testMatch(Matcher.match(Value), Matcher, ValueStr, MatcherStr, Loc); } bool testProcessExits(testutils::FunctionCaller *Func, int ExitCode, const char *LHSStr, const char *RHSStr, internal::Location Loc); bool testProcessKilled(testutils::FunctionCaller *Func, int Signal, const char *LHSStr, const char *RHSStr, internal::Location Loc); template testutils::FunctionCaller *createCallable(Func f) { struct Callable : public testutils::FunctionCaller { Func f; Callable(Func f) : f(f) {} void operator()() override { f(); } }; return new Callable(f); } private: virtual void Run() = 0; virtual const char *getName() const = 0; static Test *Start; static Test *End; }; extern int argc; extern char **argv; extern char **envp; namespace internal { constexpr bool same_prefix(char const *lhs, char const *rhs, int const len) { for (int i = 0; (*lhs || *rhs) && (i < len); ++lhs, ++rhs, ++i) if (*lhs != *rhs) return false; return true; } constexpr bool valid_prefix(char const *lhs) { return same_prefix(lhs, "LlvmLibc", 8); } // 'str' is a null terminated string of the form // "const char *LIBC_NAMESPACE::testing::internal::GetTypeName() [ParamType = // XXX]" We return the substring that start at character '[' or a default // message. constexpr char const *GetPrettyFunctionParamType(char const *str) { for (const char *ptr = str; *ptr != '\0'; ++ptr) if (*ptr == '[') return ptr; return "UNSET : declare with REGISTER_TYPE_NAME"; } // This function recovers ParamType at compile time by using __PRETTY_FUNCTION__ // It can be customized by using the REGISTER_TYPE_NAME macro below. template static constexpr const char *GetTypeName() { return GetPrettyFunctionParamType(__PRETTY_FUNCTION__); } template static inline void GenerateName(char *buffer, int buffer_size, const char *prefix) { if (buffer_size == 0) return; // Make sure string is null terminated. --buffer_size; buffer[buffer_size] = '\0'; const auto AppendChar = [&](char c) { if (buffer_size > 0) { *buffer = c; ++buffer; --buffer_size; } }; const auto AppendStr = [&](const char *str) { for (; str && *str != '\0'; ++str) AppendChar(*str); }; AppendStr(prefix); AppendChar(' '); AppendStr(GetTypeName()); AppendChar('\0'); } // TestCreator implements a linear hierarchy of test instances, effectively // instanciating all tests with Types in a single object. template