[libc-commits] [libc] [libc] Move test macro implementations out of the Test class (PR #212783)
Pavel Labath via libc-commits
libc-commits at lists.llvm.org
Wed Jul 29 07:43:40 PDT 2026
https://github.com/labath created https://github.com/llvm/llvm-project/pull/212783
This is useful for several reasons:
- it makes it possible to use the macros outside of the test methods, aligning with googletest. Although this isn't recommended, the alternatives are often not worth it.
- it makes it possible (with additional changes) to expose this infrastructure to C_TESTs (which currently use the assert() macro, which is a no-op in release mode)
- it makes it possible to add (and have assertions in) googletest style SetUp/TearDownTestSuite functions.
I've converted the main assertion macros for now, to test the waters. I'm planning to handle EXPECT/ASSERT_THAT and EXPECT/ASSERT_EXIT/DEATH in a follow-up.
>From 419d33a0270f36cc5f4aec968f802bc7e3b66473 Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Wed, 29 Jul 2026 14:25:27 +0000
Subject: [PATCH] [libc] Move test macro implementations out of the Test class
This is useful for several reasons:
- it makes it possible to use the macros outside of the test methods,
aligning with googletest. Although this isn't recommended, the
alternatives are often not worth it.
- it makes it possible (with additional changes) to expose this
infrastructure to C_TESTs (which currently use the assert() macro,
which is a no-op in release mode)
- it makes it possible to add (and have assertions in) googletest style
SetUp/TearDownTestSuite functions.
I've converted the main assertion macros for now, to test the waters.
I'm planning to handle EXPECT/ASSERT_THAT and EXPECT/ASSERT_EXIT/DEATH
in a follow-up.
---
libc/test/UnitTest/LibcDeathTestExecutors.cpp | 16 +-
libc/test/UnitTest/LibcTest.cpp | 45 +++--
libc/test/UnitTest/LibcTest.h | 173 +++++++++---------
.../src/strings/wide_read_memory_test.cpp | 4 +-
4 files changed, 124 insertions(+), 114 deletions(-)
diff --git a/libc/test/UnitTest/LibcDeathTestExecutors.cpp b/libc/test/UnitTest/LibcDeathTestExecutors.cpp
index 9a0a749a3404a..d579ae52e6383 100644
--- a/libc/test/UnitTest/LibcDeathTestExecutors.cpp
+++ b/libc/test/UnitTest/LibcDeathTestExecutors.cpp
@@ -28,21 +28,21 @@ bool Test::testProcessKilled(testutils::FunctionCaller *Func, int Signal,
testutils::invoke_in_subprocess(Func, TIMEOUT_MS);
if (const char *error = Result.get_error()) {
- Ctx->markFail();
+ internal::current_context->markFail();
tlog << Loc;
tlog << error << '\n';
return false;
}
if (Result.timed_out()) {
- Ctx->markFail();
+ internal::current_context->markFail();
tlog << Loc;
tlog << "Process timed out after " << TIMEOUT_MS << " milliseconds.\n";
return false;
}
if (Result.exited_normally()) {
- Ctx->markFail();
+ internal::current_context->markFail();
tlog << Loc;
tlog << "Expected " << LHSStr
<< " to be killed by a signal\nBut it exited normally!\n";
@@ -55,7 +55,7 @@ bool Test::testProcessKilled(testutils::FunctionCaller *Func, int Signal,
return true;
using testutils::signal_as_string;
- Ctx->markFail();
+ internal::current_context->markFail();
tlog << Loc;
tlog << " Expected: " << LHSStr << '\n'
<< "To be killed by signal: " << Signal << '\n'
@@ -72,21 +72,21 @@ bool Test::testProcessExits(testutils::FunctionCaller *Func, int ExitCode,
testutils::invoke_in_subprocess(Func, TIMEOUT_MS);
if (const char *error = Result.get_error()) {
- Ctx->markFail();
+ internal::current_context->markFail();
tlog << Loc;
tlog << error << '\n';
return false;
}
if (Result.timed_out()) {
- Ctx->markFail();
+ internal::current_context->markFail();
tlog << Loc;
tlog << "Process timed out after " << TIMEOUT_MS << " milliseconds.\n";
return false;
}
if (!Result.exited_normally()) {
- Ctx->markFail();
+ internal::current_context->markFail();
tlog << Loc;
tlog << "Expected " << LHSStr << '\n'
<< "to exit with exit code " << ExitCode << '\n'
@@ -98,7 +98,7 @@ bool Test::testProcessExits(testutils::FunctionCaller *Func, int ExitCode,
if (ActualExit == ExitCode)
return true;
- Ctx->markFail();
+ internal::current_context->markFail();
tlog << Loc;
tlog << "Expected exit code of: " << LHSStr << '\n'
<< " Which is: " << ActualExit << '\n'
diff --git a/libc/test/UnitTest/LibcTest.cpp b/libc/test/UnitTest/LibcTest.cpp
index d8e5314ece213..578f2ff8e2960 100644
--- a/libc/test/UnitTest/LibcTest.cpp
+++ b/libc/test/UnitTest/LibcTest.cpp
@@ -33,6 +33,8 @@ namespace testing {
namespace internal {
+RunContext *current_context = nullptr;
+
TestLogger &operator<<(TestLogger &logger, Location Loc) {
return logger << Loc.file << ":" << Loc.line << ": FAILURE\n";
}
@@ -87,8 +89,8 @@ cpp::string describeValue(cpp::wstring_view Value) {
}
template <typename ValType>
-bool test(RunContext *Ctx, TestCond Cond, ValType LHS, ValType RHS,
- const char *LHSStr, const char *RHSStr, Location Loc) {
+bool test_impl(RunContext *Ctx, TestCond Cond, ValType LHS, ValType RHS,
+ const char *LHSStr, const char *RHSStr, Location Loc) {
auto ExplainDifference = [=, &Ctx](bool Cond,
cpp::string_view OpString) -> bool {
if (Cond)
@@ -174,13 +176,14 @@ int Test::runTests(const TestOptions &Options) {
}
tlog << green << "[ RUN ] " << reset << TestName << '\n';
- [[maybe_unused]] const uint64_t start_time = static_cast<uint64_t>(clock());
RunContext Ctx;
+ internal::current_context = &Ctx;
+ [[maybe_unused]] const uint64_t start_time = static_cast<uint64_t>(clock());
T->SetUp();
- T->setContext(&Ctx);
T->Run();
T->TearDown();
[[maybe_unused]] const uint64_t end_time = static_cast<uint64_t>(clock());
+ internal::current_context = nullptr;
switch (Ctx.status()) {
case RunContext::RunResult::Fail:
tlog << red << "[ FAILED ] " << reset << TestName << '\n';
@@ -229,9 +232,9 @@ int Test::runTests(const TestOptions &Options) {
namespace internal {
#define TEST_SPECIALIZATION(TYPE) \
- template bool test<TYPE>(RunContext * Ctx, TestCond Cond, TYPE LHS, \
- TYPE RHS, const char *LHSStr, const char *RHSStr, \
- Location Loc)
+ template bool test_impl<TYPE>(RunContext * Ctx, TestCond Cond, TYPE LHS, \
+ TYPE RHS, const char *LHSStr, \
+ const char *RHSStr, Location Loc)
TEST_SPECIALIZATION(wchar_t);
@@ -286,28 +289,30 @@ TEST_SPECIALIZATION(unsigned accum);
TEST_SPECIALIZATION(unsigned long accum);
#endif // LIBC_COMPILER_HAS_FIXED_POINT
-} // namespace internal
-
-bool Test::testStrEq(const char *LHS, const char *RHS, const char *LHSStr,
- const char *RHSStr, internal::Location Loc) {
- return internal::test(
- Ctx, TestCond::EQ, LHS ? cpp::string_view(LHS) : cpp::string_view(),
- RHS ? cpp::string_view(RHS) : cpp::string_view(), LHSStr, RHSStr, Loc);
+bool test_str_eq(const char *LHS, const char *RHS, const char *LHSStr,
+ const char *RHSStr, internal::Location Loc) {
+ return test_impl(internal::current_context, TestCond::EQ,
+ LHS ? cpp::string_view(LHS) : cpp::string_view(),
+ RHS ? cpp::string_view(RHS) : cpp::string_view(), LHSStr,
+ RHSStr, Loc);
}
-bool Test::testStrNe(const char *LHS, const char *RHS, const char *LHSStr,
- const char *RHSStr, internal::Location Loc) {
- return internal::test(
- Ctx, TestCond::NE, LHS ? cpp::string_view(LHS) : cpp::string_view(),
- RHS ? cpp::string_view(RHS) : cpp::string_view(), LHSStr, RHSStr, Loc);
+bool test_str_ne(const char *LHS, const char *RHS, const char *LHSStr,
+ const char *RHSStr, internal::Location Loc) {
+ return test_impl(internal::current_context, TestCond::NE,
+ LHS ? cpp::string_view(LHS) : cpp::string_view(),
+ RHS ? cpp::string_view(RHS) : cpp::string_view(), LHSStr,
+ RHSStr, Loc);
}
+} // namespace internal
+
bool Test::testMatch(bool MatchResult, MatcherBase &Matcher, const char *LHSStr,
const char *RHSStr, internal::Location Loc) {
if (MatchResult)
return true;
- Ctx->markFail();
+ internal::current_context->markFail();
if (!Matcher.is_silent()) {
tlog << Loc;
tlog << "Failed to match " << LHSStr << " against " << RHSStr << ".\n";
diff --git a/libc/test/UnitTest/LibcTest.h b/libc/test/UnitTest/LibcTest.h
index 7e9d34f3cb2cb..740d08335ab24 100644
--- a/libc/test/UnitTest/LibcTest.h
+++ b/libc/test/UnitTest/LibcTest.h
@@ -95,8 +95,81 @@ struct RunContext {
};
template <typename ValType>
-bool test(RunContext *Ctx, TestCond Cond, ValType LHS, ValType RHS,
- const char *LHSStr, const char *RHSStr, Location Loc);
+bool test_impl(RunContext *Ctx, TestCond Cond, ValType LHS, ValType RHS,
+ const char *LHSStr, const char *RHSStr, Location Loc);
+
+extern RunContext *current_context;
+
+// 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<cpp::is_integral_v<ValType> ||
+ is_big_int_v<ValType> ||
+ cpp::is_fixed_point_v<ValType>,
+ int> = 0>
+bool test(TestCond Cond, ValType LHS, ValType RHS, const char *LHSStr,
+ const char *RHSStr, internal::Location Loc) {
+ return test_impl(current_context, Cond, LHS, RHS, LHSStr, RHSStr, Loc);
+}
+
+template <typename ValType, cpp::enable_if_t<cpp::is_enum_v<ValType>, int> = 0>
+bool test(TestCond Cond, ValType LHS, ValType RHS, const char *LHSStr,
+ const char *RHSStr, internal::Location Loc) {
+ return test_impl(current_context, Cond, (long long)LHS, (long long)RHS,
+ LHSStr, RHSStr, Loc);
+}
+
+template <typename ValType,
+ cpp::enable_if_t<cpp::is_pointer_v<ValType>, ValType> = nullptr>
+bool test(TestCond Cond, ValType LHS, ValType RHS, const char *LHSStr,
+ const char *RHSStr, internal::Location Loc) {
+ return test_impl(current_context, Cond, (unsigned long long)LHS,
+ (unsigned long long)RHS, LHSStr, RHSStr, Loc);
+}
+
+// Helper to allow macro invocations like `ASSERT_EQ(foo, nullptr)`.
+template <typename ValType,
+ cpp::enable_if_t<cpp::is_pointer_v<ValType>, 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<ValType>(nullptr), LHSStr, RHSStr, Loc);
+}
+
+template <
+ typename ValType,
+ cpp::enable_if_t<cpp::is_same_v<ValType, LIBC_NAMESPACE::cpp::string_view>,
+ int> = 0>
+bool test(TestCond Cond, ValType LHS, ValType RHS, const char *LHSStr,
+ const char *RHSStr, internal::Location Loc) {
+ return test_impl(current_context, Cond, LHS, RHS, LHSStr, RHSStr, Loc);
+}
+
+template <
+ typename ValType,
+ cpp::enable_if_t<cpp::is_same_v<ValType, LIBC_NAMESPACE::cpp::wstring_view>,
+ int> = 0>
+bool test(TestCond Cond, ValType LHS, ValType RHS, const char *LHSStr,
+ const char *RHSStr, internal::Location Loc) {
+ return test_impl(current_context, Cond, LHS, RHS, LHSStr, RHSStr, Loc);
+}
+
+template <typename ValType,
+ cpp::enable_if_t<cpp::is_same_v<ValType, LIBC_NAMESPACE::cpp::string>,
+ int> = 0>
+bool test(TestCond Cond, ValType LHS, ValType RHS, const char *LHSStr,
+ const char *RHSStr, internal::Location Loc) {
+ return test_impl(current_context, Cond, LHS, RHS, LHSStr, RHSStr, Loc);
+}
+
+bool test_str_eq(const char *LHS, const char *RHS, const char *LHSStr,
+ const char *RHSStr, internal::Location Loc);
+
+bool test_str_ne(const char *LHS, const char *RHS, const char *LHSStr,
+ const char *RHSStr, internal::Location Loc);
} // namespace internal
@@ -113,9 +186,7 @@ struct TestOptions {
// 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:
@@ -128,79 +199,6 @@ class Test {
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<cpp::is_integral_v<ValType> || is_big_int_v<ValType> ||
- cpp::is_fixed_point_v<ValType>,
- 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 <typename ValType,
- cpp::enable_if_t<cpp::is_enum_v<ValType>, 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 <typename ValType,
- cpp::enable_if_t<cpp::is_pointer_v<ValType>, 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 <typename ValType,
- cpp::enable_if_t<cpp::is_pointer_v<ValType>, 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<ValType>(nullptr), LHSStr, RHSStr, Loc);
- }
-
- template <
- typename ValType,
- cpp::enable_if_t<
- cpp::is_same_v<ValType, LIBC_NAMESPACE::cpp::string_view>, 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 <
- typename ValType,
- cpp::enable_if_t<
- cpp::is_same_v<ValType, LIBC_NAMESPACE::cpp::wstring_view>, 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 <typename ValType,
- cpp::enable_if_t<
- cpp::is_same_v<ValType, LIBC_NAMESPACE::cpp::string>, 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);
@@ -439,8 +437,9 @@ CString libc_make_test_file_path_func(const char *file_name);
LIBC_NAMESPACE::testing::internal::Message()
#define LIBC_TEST_BINOP_(COND, LHS, RHS, RET_OR_EMPTY) \
- LIBC_TEST_SCAFFOLDING_(test(LIBC_NAMESPACE::testing::TestCond::COND, LHS, \
- RHS, #LHS, #RHS, LIBC_TEST_LOC_()), \
+ LIBC_TEST_SCAFFOLDING_(LIBC_NAMESPACE::testing::internal::test( \
+ LIBC_NAMESPACE::testing::TestCond::COND, LHS, \
+ RHS, #LHS, #RHS, LIBC_TEST_LOC_()), \
RET_OR_EMPTY)
////////////////////////////////////////////////////////////////////////////////
@@ -480,11 +479,17 @@ CString libc_make_test_file_path_func(const char *file_name);
LIBC_TEST_SCAFFOLDING_(TEST_FUNC(LHS, RHS, #LHS, #RHS, LIBC_TEST_LOC_()), \
RET_OR_EMPTY)
-#define EXPECT_STREQ(LHS, RHS) LIBC_TEST_STR_(testStrEq, LHS, RHS, )
-#define ASSERT_STREQ(LHS, RHS) LIBC_TEST_STR_(testStrEq, LHS, RHS, return)
-
-#define EXPECT_STRNE(LHS, RHS) LIBC_TEST_STR_(testStrNe, LHS, RHS, )
-#define ASSERT_STRNE(LHS, RHS) LIBC_TEST_STR_(testStrNe, LHS, RHS, return)
+#define EXPECT_STREQ(LHS, RHS) \
+ LIBC_TEST_STR_(LIBC_NAMESPACE::testing::internal::test_str_eq, LHS, RHS, )
+#define ASSERT_STREQ(LHS, RHS) \
+ LIBC_TEST_STR_(LIBC_NAMESPACE::testing::internal::test_str_eq, LHS, RHS, \
+ return)
+
+#define EXPECT_STRNE(LHS, RHS) \
+ LIBC_TEST_STR_(LIBC_NAMESPACE::testing::internal::test_str_ne, LHS, RHS, )
+#define ASSERT_STRNE(LHS, RHS) \
+ LIBC_TEST_STR_(LIBC_NAMESPACE::testing::internal::test_str_ne, LHS, RHS, \
+ return)
////////////////////////////////////////////////////////////////////////////////
// Subprocess checks.
diff --git a/libc/test/src/strings/wide_read_memory_test.cpp b/libc/test/src/strings/wide_read_memory_test.cpp
index 44b328d2c9c3f..7b173d479dc9d 100644
--- a/libc/test/src/strings/wide_read_memory_test.cpp
+++ b/libc/test/src/strings/wide_read_memory_test.cpp
@@ -93,7 +93,7 @@ TEST_F(LlvmLibcWideAccessMemoryTest, StringLength) {
inline_memset(buf.data(), 'a', buf.size());
// Make sure it is null terminated.
buf[buf.size() - 1] = '\0';
- this->TestMemoryAccess(buf, [this, buf](const char *test_data) {
+ this->TestMemoryAccess(buf, [buf](const char *test_data) {
// -1 for the null character.
ASSERT_EQ(internal::string_length(test_data), size_t(buf.size() - 1));
});
@@ -104,7 +104,7 @@ TEST_F(LlvmLibcWideAccessMemoryTest, FindFirstChar) {
TwoKilobyteBuffer buf;
inline_memset(buf.data(), 'a', buf.size());
buf[buf.size() - 1] = 'b';
- this->TestMemoryAccess(buf, [this, buf](const char *test_data) {
+ this->TestMemoryAccess(buf, [buf](const char *test_data) {
// Found case
ASSERT_EQ(
reinterpret_cast<const void *>(internal::find_first_character_impl(
More information about the libc-commits
mailing list