diff --git a/libc/test/src/stdio/printf_core/CMakeLists.txt b/libc/test/src/stdio/printf_core/CMakeLists.txt --- a/libc/test/src/stdio/printf_core/CMakeLists.txt +++ b/libc/test/src/stdio/printf_core/CMakeLists.txt @@ -8,3 +8,5 @@ libc.src.stdio.printf_core.parser libc.src.__support.arg_list ) + +target_link_libraries(libc.test.src.stdio.printf_core.parser_test PRIVATE LibcPrintfHelpers) diff --git a/libc/test/src/stdio/printf_core/parser_test.cpp b/libc/test/src/stdio/printf_core/parser_test.cpp --- a/libc/test/src/stdio/printf_core/parser_test.cpp +++ b/libc/test/src/stdio/printf_core/parser_test.cpp @@ -12,37 +12,9 @@ #include +#include "utils/UnitTest/PrintfMatcher.h" #include "utils/UnitTest/Test.h" -class LlvmLibcPrintfParserTest : public __llvm_libc::testing::Test { -public: - void assert_eq_fs(__llvm_libc::printf_core::FormatSection expected, - __llvm_libc::printf_core::FormatSection actual) { - ASSERT_EQ(expected.has_conv, actual.has_conv); - ASSERT_EQ(expected.raw_len, actual.raw_len); - - for (size_t i = 0; i < expected.raw_len; ++i) { - EXPECT_EQ(expected.raw_string[i], actual.raw_string[i]); - } - - if (expected.has_conv) { - ASSERT_EQ(static_cast(expected.flags), - static_cast(actual.flags)); - ASSERT_EQ(expected.min_width, actual.min_width); - ASSERT_EQ(expected.precision, actual.precision); - ASSERT_TRUE(expected.length_modifier == actual.length_modifier); - ASSERT_EQ(expected.conv_name, actual.conv_name); - - if (expected.conv_name == 'p' || expected.conv_name == 'n' || - expected.conv_name == 's') { - ASSERT_EQ(expected.conv_val_ptr, actual.conv_val_ptr); - } else if (expected.conv_name != '%') { - ASSERT_EQ(expected.conv_val_raw, actual.conv_val_raw); - } - } - } -}; - void init(const char *__restrict str, ...) { va_list vlist; va_start(vlist, str); @@ -68,9 +40,9 @@ } } -TEST_F(LlvmLibcPrintfParserTest, Constructor) { init("test", 1, 2); } +TEST(LlvmLibcPrintfParserTest, Constructor) { init("test", 1, 2); } -TEST_F(LlvmLibcPrintfParserTest, EvalRaw) { +TEST(LlvmLibcPrintfParserTest, EvalRaw) { __llvm_libc::printf_core::FormatSection format_arr[10]; const char *str = "test"; evaluate(format_arr, str); @@ -80,10 +52,10 @@ expected.raw_len = 4; expected.raw_string = str; - assert_eq_fs(expected, format_arr[0]); + ASSERT_FORM_EQ(expected, format_arr[0]); } -TEST_F(LlvmLibcPrintfParserTest, EvalSimple) { +TEST(LlvmLibcPrintfParserTest, EvalSimple) { __llvm_libc::printf_core::FormatSection format_arr[10]; const char *str = "test %% test"; evaluate(format_arr, str); @@ -93,23 +65,23 @@ expected0.raw_len = 5; expected0.raw_string = str; - assert_eq_fs(expected0, format_arr[0]); + ASSERT_FORM_EQ(expected0, format_arr[0]); expected1.has_conv = true; expected1.raw_len = 2; expected1.raw_string = str + 5; expected1.conv_name = '%'; - assert_eq_fs(expected1, format_arr[1]); + ASSERT_FORM_EQ(expected1, format_arr[1]); expected2.has_conv = false; expected2.raw_len = 5; expected2.raw_string = str + 7; - assert_eq_fs(expected2, format_arr[2]); + ASSERT_FORM_EQ(expected2, format_arr[2]); } -TEST_F(LlvmLibcPrintfParserTest, EvalOneArg) { +TEST(LlvmLibcPrintfParserTest, EvalOneArg) { __llvm_libc::printf_core::FormatSection format_arr[10]; const char *str = "%d"; int arg1 = 12345; @@ -122,10 +94,10 @@ expected.conv_val_raw = arg1; expected.conv_name = 'd'; - assert_eq_fs(expected, format_arr[0]); + ASSERT_FORM_EQ(expected, format_arr[0]); } -TEST_F(LlvmLibcPrintfParserTest, EvalOneArgWithFlags) { +TEST(LlvmLibcPrintfParserTest, EvalOneArgWithFlags) { __llvm_libc::printf_core::FormatSection format_arr[10]; const char *str = "%+-0 #d"; int arg1 = 12345; @@ -144,10 +116,10 @@ expected.conv_val_raw = arg1; expected.conv_name = 'd'; - assert_eq_fs(expected, format_arr[0]); + ASSERT_FORM_EQ(expected, format_arr[0]); } -TEST_F(LlvmLibcPrintfParserTest, EvalOneArgWithWidth) { +TEST(LlvmLibcPrintfParserTest, EvalOneArgWithWidth) { __llvm_libc::printf_core::FormatSection format_arr[10]; const char *str = "%12d"; int arg1 = 12345; @@ -161,10 +133,10 @@ expected.conv_val_raw = arg1; expected.conv_name = 'd'; - assert_eq_fs(expected, format_arr[0]); + ASSERT_FORM_EQ(expected, format_arr[0]); } -TEST_F(LlvmLibcPrintfParserTest, EvalOneArgWithPrecision) { +TEST(LlvmLibcPrintfParserTest, EvalOneArgWithPrecision) { __llvm_libc::printf_core::FormatSection format_arr[10]; const char *str = "%.34d"; int arg1 = 12345; @@ -178,10 +150,10 @@ expected.conv_val_raw = arg1; expected.conv_name = 'd'; - assert_eq_fs(expected, format_arr[0]); + ASSERT_FORM_EQ(expected, format_arr[0]); } -TEST_F(LlvmLibcPrintfParserTest, EvalOneArgWithTrivialPrecision) { +TEST(LlvmLibcPrintfParserTest, EvalOneArgWithTrivialPrecision) { __llvm_libc::printf_core::FormatSection format_arr[10]; const char *str = "%.d"; int arg1 = 12345; @@ -195,10 +167,10 @@ expected.conv_val_raw = arg1; expected.conv_name = 'd'; - assert_eq_fs(expected, format_arr[0]); + ASSERT_FORM_EQ(expected, format_arr[0]); } -TEST_F(LlvmLibcPrintfParserTest, EvalOneArgWithShortLengthModifier) { +TEST(LlvmLibcPrintfParserTest, EvalOneArgWithShortLengthModifier) { __llvm_libc::printf_core::FormatSection format_arr[10]; const char *str = "%hd"; int arg1 = 12345; @@ -212,10 +184,10 @@ expected.conv_val_raw = arg1; expected.conv_name = 'd'; - assert_eq_fs(expected, format_arr[0]); + ASSERT_FORM_EQ(expected, format_arr[0]); } -TEST_F(LlvmLibcPrintfParserTest, EvalOneArgWithLongLengthModifier) { +TEST(LlvmLibcPrintfParserTest, EvalOneArgWithLongLengthModifier) { __llvm_libc::printf_core::FormatSection format_arr[10]; const char *str = "%lld"; int arg1 = 12345; @@ -229,10 +201,10 @@ expected.conv_val_raw = arg1; expected.conv_name = 'd'; - assert_eq_fs(expected, format_arr[0]); + ASSERT_FORM_EQ(expected, format_arr[0]); } -TEST_F(LlvmLibcPrintfParserTest, EvalOneArgWithAllOptions) { +TEST(LlvmLibcPrintfParserTest, EvalOneArgWithAllOptions) { __llvm_libc::printf_core::FormatSection format_arr[10]; const char *str = "% -056.78jd"; int arg1 = 12345; @@ -252,10 +224,10 @@ expected.conv_val_raw = arg1; expected.conv_name = 'd'; - assert_eq_fs(expected, format_arr[0]); + ASSERT_FORM_EQ(expected, format_arr[0]); } -TEST_F(LlvmLibcPrintfParserTest, EvalThreeArgs) { +TEST(LlvmLibcPrintfParserTest, EvalThreeArgs) { __llvm_libc::printf_core::FormatSection format_arr[10]; const char *str = "%d%f%s"; int arg1 = 12345; @@ -270,7 +242,7 @@ expected0.conv_val_raw = arg1; expected0.conv_name = 'd'; - assert_eq_fs(expected0, format_arr[0]); + ASSERT_FORM_EQ(expected0, format_arr[0]); expected1.has_conv = true; expected1.raw_len = 2; @@ -278,7 +250,7 @@ expected1.conv_val_raw = __llvm_libc::bit_cast(arg2); expected1.conv_name = 'f'; - assert_eq_fs(expected1, format_arr[1]); + ASSERT_FORM_EQ(expected1, format_arr[1]); expected2.has_conv = true; expected2.raw_len = 2; @@ -286,5 +258,5 @@ expected2.conv_val_ptr = const_cast(arg3); expected2.conv_name = 's'; - assert_eq_fs(expected2, format_arr[2]); + ASSERT_FORM_EQ(expected2, format_arr[2]); } 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 @@ -44,3 +44,16 @@ LibcUnitTest libc.src.__support.CPP.array_ref ) + +add_library( + LibcPrintfHelpers + PrintfMatcher.h + PrintfMatcher.cpp +) +target_include_directories(LibcPrintfHelpers PUBLIC ${LIBC_SOURCE_DIR}) +target_link_libraries(LibcPrintfHelpers LibcUnitTest) +add_dependencies( + LibcPrintfHelpers + LibcUnitTest + libc.src.stdio.printf_core.core_structs +) diff --git a/libc/utils/UnitTest/PrintfMatcher.h b/libc/utils/UnitTest/PrintfMatcher.h new file mode 100644 --- /dev/null +++ b/libc/utils/UnitTest/PrintfMatcher.h @@ -0,0 +1,46 @@ +//===-- PrintfMatcher.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_PRINTF_MATCHER_H +#define LLVM_LIBC_UTILS_UNITTEST_PRINTF_MATCHER_H + +#include "src/stdio/printf_core/core_structs.h" +#include "utils/UnitTest/Test.h" + +#include + +namespace __llvm_libc { +namespace printf_core { +namespace testing { + +class FormatSectionMatcher + : public __llvm_libc::testing::Matcher { + FormatSection expected; + FormatSection actual; + +public: + FormatSectionMatcher(FormatSection expectedValue) : expected(expectedValue) {} + + bool match(FormatSection actualValue); + + void explainError(testutils::StreamWrapper &stream) override; +}; + +} // namespace testing +} // namespace printf_core +} // namespace __llvm_libc + +#define EXPECT_FORM_EQ(expected, actual) \ + EXPECT_THAT(actual, __llvm_libc::printf_core::testing::FormatSectionMatcher( \ + expected)) + +#define ASSERT_FORM_EQ(expected, actual) \ + ASSERT_THAT(actual, __llvm_libc::printf_core::testing::FormatSectionMatcher( \ + expected)) + +#endif // LLVM_LIBC_UTILS_UNITTEST_PRINTF_MATCHER_H diff --git a/libc/utils/UnitTest/PrintfMatcher.cpp b/libc/utils/UnitTest/PrintfMatcher.cpp new file mode 100644 --- /dev/null +++ b/libc/utils/UnitTest/PrintfMatcher.cpp @@ -0,0 +1,124 @@ +//===-- PrintfMatcher.cpp ---------------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#include "PrintfMatcher.h" +#include "src/stdio/printf_core/core_structs.h" +#include + +namespace __llvm_libc { +namespace printf_core { +namespace testing { + +bool FormatSectionMatcher::match(FormatSection actualValue) { + actual = actualValue; + + if (!((expected.has_conv == actual.has_conv) && + (expected.raw_len == actual.raw_len))) + return false; + + for (size_t i = 0; i < expected.raw_len; ++i) { + if (!(expected.raw_string[i] == actual.raw_string[i])) + return false; + } + + if (expected.has_conv) { + if (!((static_cast(expected.flags) == + static_cast(actual.flags)) && + (expected.min_width == actual.min_width) && + (expected.precision == actual.precision) && + (expected.length_modifier == actual.length_modifier) && + (expected.conv_name == actual.conv_name))) + return false; + + if (expected.conv_name == 'p' || expected.conv_name == 'n' || + expected.conv_name == 's') + return (expected.conv_val_ptr == actual.conv_val_ptr); + else if (expected.conv_name != '%') + return (expected.conv_val_raw == actual.conv_val_raw); + } + return true; +} + +template +// int_to_hex takes an integer and a buffer and writes the value of that integer +// to buff in hex. It also returns buff for ease of use. +char *int_to_hex(T num, char *buff) { + constexpr size_t len = sizeof(T) * 2; + buff[len + 2] = '\0'; + for (size_t i = len + 1; i > 1; --i) { + uint8_t digit = num % 16; + num = num / 16; + buff[i] = (digit < 10 ? '0' + digit : 'A' + digit - 10); + } + buff[1] = 'x'; + buff[0] = '0'; + return buff; +} + +#define IF_FLAG_SHOW_FLAG(flag_name) \ + if ((form.flags & FormatFlags::flag_name) == FormatFlags::flag_name) \ + stream << "\n\t\t" << #flag_name +#define CASE_LM(lm) \ + case (LengthModifier::lm): \ + stream << #lm; \ + break + +void display(testutils::StreamWrapper &stream, FormatSection form) { + char buffer[40]; + stream << "Raw String (len " << form.raw_len << "): \""; + for (size_t i = 0; i < form.raw_len; ++i) { + stream << form.raw_string[i]; + } + stream << "\""; + if (form.has_conv) { + stream << "\n\tHas Conv\n\tFlags:"; + IF_FLAG_SHOW_FLAG(LEFT_JUSTIFIED); + IF_FLAG_SHOW_FLAG(FORCE_SIGN); + IF_FLAG_SHOW_FLAG(SPACE_PREFIX); + IF_FLAG_SHOW_FLAG(ALTERNATE_FORM); + IF_FLAG_SHOW_FLAG(LEADING_ZEROES); + stream << "\n"; + stream << "\tmin width: " << form.min_width << "\n"; + stream << "\tprecision: " << form.precision << "\n"; + stream << "\tlength modifier: "; + switch (form.length_modifier) { + CASE_LM(none); + CASE_LM(l); + CASE_LM(ll); + CASE_LM(h); + CASE_LM(hh); + CASE_LM(j); + CASE_LM(z); + CASE_LM(t); + CASE_LM(L); + } + stream << "\n"; + stream << "\tconversion name: " << form.conv_name << "\n"; + if (form.conv_name == 'p' || form.conv_name == 'n' || form.conv_name == 's') + stream << "\tpointer value: " + << int_to_hex( + reinterpret_cast(form.conv_val_ptr), buffer) + << "\n"; + else if (form.conv_name != '%') + stream << "\tvalue: " + << int_to_hex<__uint128_t>(form.conv_val_raw, buffer) << "\n"; + } +} + +void FormatSectionMatcher::explainError(testutils::StreamWrapper &stream) { + stream << "expected : "; + display(stream, expected); + stream << '\n'; + stream << "actual : "; + display(stream, actual); + stream << '\n'; +} + +} // namespace testing +} // namespace printf_core +} // namespace __llvm_libc