diff --git a/libc/src/stdio/printf_core/core_structs.h b/libc/src/stdio/printf_core/core_structs.h --- a/libc/src/stdio/printf_core/core_structs.h +++ b/libc/src/stdio/printf_core/core_structs.h @@ -9,6 +9,8 @@ #ifndef LLVM_LIBC_SRC_STDIO_PRINTF_CORE_CORE_STRUCTS_H #define LLVM_LIBC_SRC_STDIO_PRINTF_CORE_CORE_STRUCTS_H +#include "src/__support/CPP/StringView.h" + #include #include @@ -47,6 +49,32 @@ void *conv_val_ptr; char conv_name; + + // This operator is only used for testing and should be automatically + // optimized out for release builds. + bool operator==(const FormatSection &other) { + if (has_conv != other.has_conv) + return false; + + if (!cpp::StringView(raw_string, raw_len) + .equals(cpp::StringView(other.raw_string, other.raw_len))) + return false; + + if (has_conv) { + if (!((static_cast(flags) == + static_cast(other.flags)) && + (min_width == other.min_width) && (precision == other.precision) && + (length_modifier == other.length_modifier) && + (conv_name == other.conv_name))) + return false; + + if (conv_name == 'p' || conv_name == 'n' || conv_name == 's') + return (conv_val_ptr == other.conv_val_ptr); + else if (conv_name != '%') + return (conv_val_raw == other.conv_val_raw); + } + return true; + } }; } // namespace printf_core 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_FORMAT_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_FORMAT_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_FORMAT_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_FORMAT_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_FORMAT_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_FORMAT_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_FORMAT_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_FORMAT_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_FORMAT_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_FORMAT_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_FORMAT_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_FORMAT_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_FORMAT_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_FORMAT_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_FORMAT_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 @@ -17,6 +17,14 @@ add_dependencies(LibcUnitTestMain LibcUnitTest) target_link_libraries(LibcUnitTestMain PUBLIC LibcUnitTest libc_test_utils) +add_header_library( + string_utils + HDRS + StringUtils.h + DEPENDS + libc.src.__support.CPP.type_traits +) + add_library( LibcFPTestHelpers FPExceptMatcher.cpp @@ -29,6 +37,7 @@ add_dependencies( LibcFPTestHelpers LibcUnitTest + libc.utils.UnitTest.string_utils libc.src.__support.FPUtil.fputil ) @@ -44,3 +53,17 @@ 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.utils.UnitTest.string_utils + libc.src.stdio.printf_core.core_structs +) diff --git a/libc/utils/UnitTest/FPMatcher.cpp b/libc/utils/UnitTest/FPMatcher.cpp --- a/libc/utils/UnitTest/FPMatcher.cpp +++ b/libc/utils/UnitTest/FPMatcher.cpp @@ -10,6 +10,8 @@ #include "src/__support/FPUtil/FPBits.h" +#include "utils/UnitTest/StringUtils.h" + #include #include @@ -17,20 +19,6 @@ namespace fputil { namespace testing { -// Return the first N hex digits of an integer as a string in upper case. -template -cpp::EnableIfType::Value, std::string> -uintToHex(T X, size_t Length = sizeof(T) * 2) { - std::string s(Length, '0'); - - for (auto it = s.rbegin(), end = s.rend(); it != end; ++it, X >>= 4) { - unsigned char Mod = static_cast(X) & 15; - *it = (Mod < 10 ? '0' + Mod : 'a' + Mod - 10); - } - - return s; -} - template cpp::EnableIfType::Value, void> describeValue(const char *label, ValType value, StreamType &stream) { @@ -53,13 +41,13 @@ sizeof(typename fputil::FPBits::UIntType) * 2; stream << "0x" - << uintToHex::UIntType>( + << int_to_hex::UIntType>( bits.uintval(), bitsWidthInHex) << ", (S | E | M) = (" << (bits.get_sign() ? '1' : '0') << " | 0x" - << uintToHex(bits.get_unbiased_exponent(), - exponentWidthInHex) + << int_to_hex(bits.get_unbiased_exponent(), + exponentWidthInHex) << " | 0x" - << uintToHex::UIntType>( + << int_to_hex::UIntType>( bits.get_mantissa(), mantissaWidthInHex) << ")"; } 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_FORMAT_EQ(expected, actual) \ + EXPECT_THAT(actual, __llvm_libc::printf_core::testing::FormatSectionMatcher( \ + expected)) + +#define ASSERT_FORMAT_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,90 @@ +//===-- 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 "utils/UnitTest/StringUtils.h" + +#include + +namespace __llvm_libc { +namespace printf_core { +namespace testing { + +bool FormatSectionMatcher::match(FormatSection actualValue) { + actual = actualValue; + return expected == actual; +} + +namespace { + +#define IF_FLAG_SHOW_FLAG(flag_name) \ + do { \ + if ((form.flags & FormatFlags::flag_name) == FormatFlags::flag_name) \ + stream << "\n\t\t" << #flag_name; \ + } while (false) +#define CASE_LM(lm) \ + case (LengthModifier::lm): \ + stream << #lm; \ + break + +void display(testutils::StreamWrapper &stream, FormatSection form) { + 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)) + << "\n"; + else if (form.conv_name != '%') + stream << "\tvalue: " << int_to_hex<__uint128_t>(form.conv_val_raw) + << "\n"; + } +} +} // anonymous namespace + +void FormatSectionMatcher::explainError(testutils::StreamWrapper &stream) { + stream << "expected format section: "; + display(stream, expected); + stream << '\n'; + stream << "actual format section : "; + display(stream, actual); + stream << '\n'; +} + +} // namespace testing +} // namespace printf_core +} // namespace __llvm_libc diff --git a/libc/utils/UnitTest/StringUtils.h b/libc/utils/UnitTest/StringUtils.h new file mode 100644 --- /dev/null +++ b/libc/utils/UnitTest/StringUtils.h @@ -0,0 +1,34 @@ +//===-- String utils for matchers -------------------------------*- 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_SIMPLE_STRING_CONV_H +#define LLVM_LIBC_UTILS_UNITTEST_SIMPLE_STRING_CONV_H + +#include "src/__support/CPP/TypeTraits.h" + +#include + +namespace __llvm_libc { + +// Return the first N hex digits of an integer as a string in upper case. +template +cpp::EnableIfType::Value, std::string> +int_to_hex(T X, size_t Length = sizeof(T) * 2) { + std::string s(Length, '0'); + + for (auto it = s.rbegin(), end = s.rend(); it != end; ++it, X >>= 4) { + unsigned char Mod = static_cast(X) & 15; + *it = (Mod < 10 ? '0' + Mod : 'a' + Mod - 10); + } + + return s; +} + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_UTILS_UNITTEST_SIMPLE_STRING_CONV_H diff --git a/libc/utils/UnitTest/StringUtils.cpp b/libc/utils/UnitTest/StringUtils.cpp new file mode 100644 --- /dev/null +++ b/libc/utils/UnitTest/StringUtils.cpp @@ -0,0 +1,16 @@ +//===-- String util implementations for matchers ----------------*- 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 "StringUtils.h" + +namespace __llvm_libc { + +// This file exists for future expansion and also so that CMake understands that +// StringUtils.h is a cpp file and not a C file. + +} // namespace __llvm_libc