diff --git a/libc/test/UnitTest/LibcTest.cpp b/libc/test/UnitTest/LibcTest.cpp index 846ad331e5237..539a2e4d196e2 100644 --- a/libc/test/UnitTest/LibcTest.cpp +++ b/libc/test/UnitTest/LibcTest.cpp @@ -127,19 +127,37 @@ void Test::addTest(Test *T) { End = T; } -int Test::runTests(const char *TestFilter) { - int TestCount = 0; +int Test::getNumTests() { + int N = 0; + for (Test *T = Start; T; T = T->Next, ++N) + ; + return N; +} + +int Test::runTests(const TestOptions &Options) { + const char *green = Options.PrintColor ? "\033[32m" : ""; + const char *red = Options.PrintColor ? "\033[31m" : ""; + const char *reset = Options.PrintColor ? "\033[0m" : ""; + + int TestCount = getNumTests(); + if (TestCount) { + tlog << green << "[==========] " << reset << "Running " << TestCount + << " test"; + if (TestCount > 1) + tlog << "s"; + tlog << " from 1 test suite.\n"; + } + int FailCount = 0; for (Test *T = Start; T != nullptr; T = T->Next) { const char *TestName = T->getName(); - cpp::string StrTestName(TestName); - constexpr auto GREEN = "\033[32m"; - constexpr auto RED = "\033[31m"; - constexpr auto RESET = "\033[0m"; - if ((TestFilter != nullptr) && (StrTestName != TestFilter)) { + + if (Options.TestFilter && cpp::string(TestName) != Options.TestFilter) { + --TestCount; continue; } - tlog << GREEN << "[ RUN ] " << RESET << TestName << '\n'; + + tlog << green << "[ RUN ] " << reset << TestName << '\n'; [[maybe_unused]] const auto start_time = clock(); RunContext Ctx; T->SetUp(); @@ -149,13 +167,13 @@ int Test::runTests(const char *TestFilter) { [[maybe_unused]] const auto end_time = clock(); switch (Ctx.status()) { case RunContext::RunResult::Fail: - tlog << RED << "[ FAILED ] " << RESET << TestName << '\n'; + tlog << red << "[ FAILED ] " << reset << TestName << '\n'; ++FailCount; break; case RunContext::RunResult::Pass: - tlog << GREEN << "[ OK ] " << RESET << TestName; + tlog << green << "[ OK ] " << reset << TestName; #ifdef LIBC_TEST_USE_CLOCK - tlog << " (took "; + tlog << " ("; if (start_time > end_time) { tlog << "unknown - try rerunning)\n"; } else { @@ -164,7 +182,7 @@ int Test::runTests(const char *TestFilter) { const uint64_t duration_us = (duration * 1000 * 1000) / CLOCKS_PER_SEC; const uint64_t duration_ns = (duration * 1000 * 1000 * 1000) / CLOCKS_PER_SEC; - if (duration_ms != 0) + if (Options.TimeInMs || duration_ms != 0) tlog << duration_ms << " ms)\n"; else if (duration_us != 0) tlog << duration_us << " us)\n"; @@ -176,7 +194,6 @@ int Test::runTests(const char *TestFilter) { #endif break; } - ++TestCount; } if (TestCount > 0) { @@ -185,8 +202,8 @@ int Test::runTests(const char *TestFilter) { << '\n'; } else { tlog << "No tests run.\n"; - if (TestFilter) { - tlog << "No matching test for " << TestFilter << '\n'; + if (Options.TestFilter) { + tlog << "No matching test for " << Options.TestFilter << '\n'; } } diff --git a/libc/test/UnitTest/LibcTest.h b/libc/test/UnitTest/LibcTest.h index bba3c6d743bec..42ba37af45834 100644 --- a/libc/test/UnitTest/LibcTest.h +++ b/libc/test/UnitTest/LibcTest.h @@ -100,6 +100,15 @@ bool test(RunContext *Ctx, TestCond Cond, ValType LHS, ValType RHS, } // 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 { @@ -107,13 +116,14 @@ class Test { 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 char *); + static int runTests(const TestOptions &Options); protected: static void addTest(Test *T); diff --git a/libc/test/UnitTest/LibcTestMain.cpp b/libc/test/UnitTest/LibcTestMain.cpp index bf1a921c04ca9..94536e9716468 100644 --- a/libc/test/UnitTest/LibcTestMain.cpp +++ b/libc/test/UnitTest/LibcTestMain.cpp @@ -7,16 +7,46 @@ //===----------------------------------------------------------------------===// #include "LibcTest.h" +#include "src/__support/CPP/string_view.h" -static const char *getTestFilter(int argc, char *argv[]) { - return argc > 1 ? argv[1] : nullptr; +using LIBC_NAMESPACE::cpp::string_view; +using LIBC_NAMESPACE::testing::TestOptions; + +namespace { + +// A poor-man's getopt_long. +// Run unit tests with --gtest_color=no to disable printing colors, or +// --gtest_print_time to print timings in milliseconds only (as GTest does, so +// external tools such as Android's atest may expect that format to parse the +// output). Other command line flags starting with --gtest_ are ignored. +// Otherwise, the last command line arg is used as a test filter, if command +// line args are specified. +TestOptions parseOptions(int argc, char **argv) { + TestOptions Options; + + for (int i = 1; i < argc; ++i) { + string_view arg{argv[i]}; + + if (arg == "--gtest_color=no") + Options.PrintColor = false; + else if (arg == "--gtest_print_time") + Options.TimeInMs = true; + // Ignore other unsupported gtest specific flags. + else if (arg.starts_with("--gtest_")) + continue; + else + Options.TestFilter = argv[i]; + } + + return Options; } +} // anonymous namespace + extern "C" int main(int argc, char **argv, char **envp) { LIBC_NAMESPACE::testing::argc = argc; LIBC_NAMESPACE::testing::argv = argv; LIBC_NAMESPACE::testing::envp = envp; - const char *TestFilter = getTestFilter(argc, argv); - return LIBC_NAMESPACE::testing::Test::runTests(TestFilter); + return LIBC_NAMESPACE::testing::Test::runTests(parseOptions(argc, argv)); } diff --git a/libc/test/utils/UnitTest/testfilter_test.cpp b/libc/test/utils/UnitTest/testfilter_test.cpp index 567b5e2bde855..d7e68252cf2aa 100644 --- a/libc/test/utils/UnitTest/testfilter_test.cpp +++ b/libc/test/utils/UnitTest/testfilter_test.cpp @@ -8,6 +8,8 @@ #include "test/UnitTest/LibcTest.h" +using LIBC_NAMESPACE::testing::TestOptions; + TEST(LlvmLibcTestFilterTest, CorrectFilter) {} TEST(LlvmLibcTestFilterTest, CorrectFilter2) {} @@ -17,22 +19,22 @@ TEST(LlvmLibcTestFilterTest, IncorrectFilter) {} TEST(LlvmLibcTestFilterTest, NoFilter) {} TEST(LlvmLibcTestFilterTest, CheckCorrectFilter) { - ASSERT_EQ(LIBC_NAMESPACE::testing::Test::runTests( - "LlvmLibcTestFilterTest.NoFilter"), - 0); - ASSERT_EQ(LIBC_NAMESPACE::testing::Test::runTests( - "LlvmLibcTestFilterTest.IncorrFilter"), - 1); - ASSERT_EQ(LIBC_NAMESPACE::testing::Test::runTests( - "LlvmLibcTestFilterTest.CorrectFilter"), - 0); - ASSERT_EQ(LIBC_NAMESPACE::testing::Test::runTests( - "LlvmLibcTestFilterTest.CorrectFilter2"), - 0); + TestOptions Options; + Options.TestFilter = "LlvmLibcTestFilterTest.NoFilter"; + ASSERT_EQ(LIBC_NAMESPACE::testing::Test::runTests(Options), 0); + + Options.TestFilter = "LlvmLibcTestFilterTest.IncorrFilter"; + ASSERT_EQ(LIBC_NAMESPACE::testing::Test::runTests(Options), 1); + + Options.TestFilter = "LlvmLibcTestFilterTest.CorrectFilter"; + ASSERT_EQ(LIBC_NAMESPACE::testing::Test::runTests(Options), 0); + + Options.TestFilter = "LlvmLibcTestFilterTest.CorrectFilter2"; + ASSERT_EQ(LIBC_NAMESPACE::testing::Test::runTests(Options), 0); } int main() { - LIBC_NAMESPACE::testing::Test::runTests( - "LlvmLibcTestFilterTest.CheckCorrectFilter"); + TestOptions Options{"LlvmLibcTestFilterTest.NoFilter", /*PrintColor=*/true}; + LIBC_NAMESPACE::testing::Test::runTests(Options); return 0; }