diff --git a/libc/src/__support/CPP/CMakeLists.txt b/libc/src/__support/CPP/CMakeLists.txt --- a/libc/src/__support/CPP/CMakeLists.txt +++ b/libc/src/__support/CPP/CMakeLists.txt @@ -55,6 +55,19 @@ libc.src.__support.common ) +add_header_library( + string + HDRS + string.h + DEPENDS + .string_view + libc.src.__support.common + libc.src.__support.integer_to_string + libc.src.string.memory_utils.memcpy_implementation + libc.src.string.memory_utils.memset_implementation + libc.src.string.string_utils +) + add_header_library( stringstream HDRS diff --git a/libc/src/__support/CPP/string.h b/libc/src/__support/CPP/string.h new file mode 100644 --- /dev/null +++ b/libc/src/__support/CPP/string.h @@ -0,0 +1,224 @@ +//===-- A simple implementation of the string class -------------*- 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_SRC_SUPPORT_CPP_STRING_H +#define LLVM_LIBC_SRC_SUPPORT_CPP_STRING_H + +#include "src/__support/CPP/string_view.h" +#include "src/__support/integer_to_string.h" // IntegerToString +#include "src/string/memory_utils/memcpy_implementations.h" +#include "src/string/memory_utils/memset_implementations.h" +#include "src/string/string_utils.h" // string_length + +#include // size_t +#include // malloc, free + +namespace __llvm_libc { +namespace cpp { + +// This class mimics std::string but does not intend to be a full fledged +// implementation. Most notably it does not provide support for character traits +// nor custom allocator. +class string { +private: + static constexpr char kNullCharacter = '\0'; + static constexpr char *getEmptyString() { + return const_cast(&kNullCharacter); + } + + char *Buffer = getEmptyString(); + size_t Size = 0; + size_t Capacity = 0; + + void ResetNoDeallocate() { + Buffer = getEmptyString(); + Size = 0; + Capacity = 0; + } + + void SetSizeAndAddNullCharacter(size_t Count) { + Size = Count; + if (Size > 0) + Buffer[Size] = kNullCharacter; + else + Buffer = getEmptyString(); + } + +public: + LIBC_INLINE constexpr string() {} + LIBC_INLINE string(const string &Other) { this->operator+=(Other); } + LIBC_INLINE string(string &&Other) + : Buffer(Other.Buffer), Size(Other.Size), Capacity(Other.Capacity) { + Other.ResetNoDeallocate(); + } + LIBC_INLINE string(const char *String, size_t Count) { + resize(Count); + inline_memcpy((void *)Buffer, (const void *)String, Count); + } + LIBC_INLINE string(const char *String) + : string(String, ::__llvm_libc::internal::string_length(String)) {} + LIBC_INLINE string(size_t Size, char Value) { + resize(Size); + inline_memset((void *)Buffer, Value, Size); + } + + LIBC_INLINE string &operator=(const string &Other) { + resize(0); + return (*this) += Other; + } + + LIBC_INLINE string &operator=(string &&Other) { + Buffer = Other.Buffer; + Size = Other.Size; + Capacity = Other.Capacity; + Other.ResetNoDeallocate(); + return *this; + } + + LIBC_INLINE ~string() { + if (Buffer != getEmptyString()) + ::free(Buffer); + } + + LIBC_INLINE size_t capacity() const { return Capacity; } + LIBC_INLINE size_t size() const { return Size; } + LIBC_INLINE bool empty() const { return Size == 0; } + + LIBC_INLINE const char *data() const { return Buffer; } + LIBC_INLINE char *data() { return Buffer; } + + LIBC_INLINE const char *begin() const { return data(); } + LIBC_INLINE char *begin() { return data(); } + + LIBC_INLINE const char *end() const { return data() + Size; } + LIBC_INLINE char *end() { return data() + Size; } + + LIBC_INLINE const char &front() const { return data()[0]; } + LIBC_INLINE char &front() { return data()[0]; } + + LIBC_INLINE const char &back() const { return data()[Size - 1]; } + LIBC_INLINE char &back() { return data()[Size - 1]; } + + LIBC_INLINE constexpr const char &operator[](size_t Pos) const { + return data()[Pos]; + } + LIBC_INLINE char &operator[](size_t Pos) { return data()[Pos]; } + + LIBC_INLINE const char *c_str() const { return data(); } + + LIBC_INLINE operator string_view() const { return string_view(Buffer, Size); } + + LIBC_INLINE void reserve(size_t NewCapacity) { + ++NewCapacity; // Accounting for the terminating '\0' + if (NewCapacity <= Capacity) + return; + // We extend the capacity to amortize buffer reallocations. + // We choose to augment the value by 11 / 8, this is about +40% and division + // by 8 is cheap. We trap in case of overflow during this computation. + if (NewCapacity >= SIZE_MAX / 11) + __builtin_unreachable(); + NewCapacity = NewCapacity * 11 / 8; + if (void *Ptr = ::realloc(empty() ? nullptr : Buffer, NewCapacity)) { + Buffer = static_cast(Ptr); + } else { + __builtin_unreachable(); // out of memory + } + Capacity = NewCapacity; + } + + LIBC_INLINE void resize(size_t Count) { + if (Count > Capacity) + reserve(Count); + SetSizeAndAddNullCharacter(Count); + } + + LIBC_INLINE string &operator+=(const string &Rhs) { + const size_t NewSize = size() + Rhs.size(); + reserve(NewSize); + inline_memcpy((void *)(Buffer + Size), (const void *)Rhs.data(), + Rhs.size()); + SetSizeAndAddNullCharacter(NewSize); + return *this; + } + + LIBC_INLINE string &operator+=(const char Char) { + const size_t NewSize = Size + 1; + reserve(NewSize); + Buffer[Size] = Char; + SetSizeAndAddNullCharacter(NewSize); + return *this; + } +}; + +LIBC_INLINE bool operator==(const string &Lhs, const string &Rhs) { + return string_view(Lhs) == string_view(Rhs); +} +LIBC_INLINE bool operator!=(const string &Lhs, const string &Rhs) { + return string_view(Lhs) != string_view(Rhs); +} +LIBC_INLINE bool operator<(const string &Lhs, const string &Rhs) { + return string_view(Lhs) < string_view(Rhs); +} +LIBC_INLINE bool operator<=(const string &Lhs, const string &Rhs) { + return string_view(Lhs) <= string_view(Rhs); +} +LIBC_INLINE bool operator>(const string &Lhs, const string &Rhs) { + return string_view(Lhs) > string_view(Rhs); +} +LIBC_INLINE bool operator>=(const string &Lhs, const string &Rhs) { + return string_view(Lhs) >= string_view(Rhs); +} + +LIBC_INLINE string operator+(const string &Lhs, const string &Rhs) { + string Tmp(Lhs); + return Tmp += Rhs; +} +LIBC_INLINE string operator+(const string &Lhs, const char *Rhs) { + return Lhs + string(Rhs); +} +LIBC_INLINE string operator+(const char *Lhs, const string &Rhs) { + return string(Lhs) + Rhs; +} + +namespace internal { +template string to_dec_string(T Value) { + char DecBuf[IntegerToString::dec_bufsize()]; + auto MaybeStringView = IntegerToString::dec(Value, DecBuf); + const auto &StringView = *MaybeStringView; + return string(StringView.data(), StringView.size()); +} +} // namespace internal + +LIBC_INLINE string to_string(int Value) { + return internal::to_dec_string(Value); +} +LIBC_INLINE string to_string(long Value) { + return internal::to_dec_string(Value); +} +LIBC_INLINE string to_string(long long Value) { + return internal::to_dec_string(Value); +} +LIBC_INLINE string to_string(unsigned Value) { + return internal::to_dec_string(Value); +} +LIBC_INLINE string to_string(unsigned long Value) { + return internal::to_dec_string(Value); +} +LIBC_INLINE string to_string(unsigned long long Value) { + return internal::to_dec_string(Value); +} + +// TODO: Support floating point +// LIBC_INLINE string to_string(float Value); +// LIBC_INLINE string to_string(double Value); +// LIBC_INLINE string to_string(long double Value); + +} // namespace cpp +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_SUPPORT_CPP_STRING_H diff --git a/libc/test/UnitTest/CMakeLists.txt b/libc/test/UnitTest/CMakeLists.txt --- a/libc/test/UnitTest/CMakeLists.txt +++ b/libc/test/UnitTest/CMakeLists.txt @@ -1,3 +1,15 @@ +add_library( + TestLogger + TestLogger.cpp + TestLogger.h +) +target_include_directories(TestLogger PUBLIC ${LIBC_SOURCE_DIR}) +add_dependencies(TestLogger + libc.src.__support.CPP.string + libc.src.__support.CPP.string_view + libc.src.__support.OSUtil.osutil +) + add_library( LibcUnitTest Test.h @@ -5,8 +17,13 @@ LibcTest.h ) target_include_directories(LibcUnitTest PUBLIC ${LIBC_SOURCE_DIR}) -add_dependencies(LibcUnitTest libc.src.__support.CPP.type_traits libc.src.__support.uint128) -target_link_libraries(LibcUnitTest PUBLIC libc_test_utils) +add_dependencies( + LibcUnitTest + libc.src.__support.CPP.string + libc.src.__support.CPP.string_view + libc.src.__support.CPP.type_traits + libc.src.__support.uint128 TestLogger) +target_link_libraries(LibcUnitTest PUBLIC libc_test_utils TestLogger) add_library( LibcUnitTestMain @@ -20,17 +37,17 @@ add_header_library( string_utils HDRS - StringUtils.h + StringUtils.h DEPENDS libc.src.__support.CPP.type_traits ) add_library( LibcFPTestHelpers - FPExceptMatcher.cpp - FPExceptMatcher.h - FPMatcher.cpp - FPMatcher.h + FPExceptMatcher.cpp + FPExceptMatcher.h + FPMatcher.cpp + FPMatcher.h ) target_include_directories(LibcFPTestHelpers PUBLIC ${LIBC_SOURCE_DIR}) target_link_libraries(LibcFPTestHelpers LibcUnitTest libc_test_utils) @@ -44,8 +61,8 @@ add_library( LibcMemoryHelpers - MemoryMatcher.h - MemoryMatcher.cpp + MemoryMatcher.h + MemoryMatcher.cpp ) target_include_directories(LibcMemoryHelpers PUBLIC ${LIBC_SOURCE_DIR}) target_link_libraries(LibcMemoryHelpers LibcUnitTest) @@ -57,8 +74,8 @@ add_library( LibcPrintfHelpers - PrintfMatcher.h - PrintfMatcher.cpp + PrintfMatcher.h + PrintfMatcher.cpp ) target_include_directories(LibcPrintfHelpers PUBLIC ${LIBC_SOURCE_DIR}) target_link_libraries(LibcPrintfHelpers LibcUnitTest) @@ -72,8 +89,8 @@ add_library( LibcScanfHelpers - ScanfMatcher.h - ScanfMatcher.cpp + ScanfMatcher.h + ScanfMatcher.cpp ) target_include_directories(LibcScanfHelpers PUBLIC ${LIBC_SOURCE_DIR}) target_link_libraries(LibcScanfHelpers LibcUnitTest) diff --git a/libc/test/UnitTest/LibcTest.h b/libc/test/UnitTest/LibcTest.h --- a/libc/test/UnitTest/LibcTest.h +++ b/libc/test/UnitTest/LibcTest.h @@ -14,6 +14,7 @@ #include "PlatformDefs.h" +#include "src/__support/CPP/string.h" #include "src/__support/CPP/string_view.h" #include "src/__support/CPP/type_traits.h" #include "utils/testutils/ExecuteFunction.h" @@ -119,6 +120,14 @@ return internal::test(Ctx, Cond, LHS, RHS, LHSStr, RHSStr, File, Line); } + template , + int> = 0> + bool test(TestCondition Cond, ValType LHS, ValType RHS, const char *LHSStr, + const char *RHSStr, const char *File, unsigned long Line) { + return internal::test(Ctx, Cond, LHS, RHS, LHSStr, RHSStr, File, Line); + } + bool testStrEq(const char *LHS, const char *RHS, const char *LHSStr, const char *RHSStr, const char *File, unsigned long Line); diff --git a/libc/test/UnitTest/LibcTest.cpp b/libc/test/UnitTest/LibcTest.cpp --- a/libc/test/UnitTest/LibcTest.cpp +++ b/libc/test/UnitTest/LibcTest.cpp @@ -8,12 +8,12 @@ #include "LibcTest.h" +#include "src/__support/CPP/string.h" #include "src/__support/CPP/string_view.h" #include "src/__support/UInt128.h" +#include "test/UnitTest/TestLogger.h" #include "utils/testutils/ExecuteFunction.h" #include -#include -#include namespace __llvm_libc { namespace testing { @@ -41,18 +41,18 @@ // be able to unittest UInt<128> on platforms where UInt128 resolves to // UInt128. template -cpp::enable_if_t && cpp::is_unsigned_v, std::string> +cpp::enable_if_t && cpp::is_unsigned_v, cpp::string> describeValueUInt(T Value) { static_assert(sizeof(T) % 8 == 0, "Unsupported size of UInt"); - std::string S(sizeof(T) * 2, '0'); + cpp::string S(sizeof(T) * 2, '0'); constexpr char HEXADECIMALS[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - - for (auto I = S.rbegin(), End = S.rend(); I != End; ++I, Value >>= 8) { + const size_t Size = S.size(); + for (size_t I = 0; I < Size; I += 2, Value >>= 8) { unsigned char Mod = static_cast(Value) & 0xFF; - *(I++) = HEXADECIMALS[Mod & 0x0F]; - *I = HEXADECIMALS[Mod >> 4]; + S[Size - I] = HEXADECIMALS[Mod & 0x0F]; + S[Size - (I + 1)] = HEXADECIMALS[Mod & 0x0F]; } return "0x" + S; @@ -60,39 +60,37 @@ // When the value is of integral type, just display it as normal. template -cpp::enable_if_t, std::string> +cpp::enable_if_t, cpp::string> describeValue(ValType Value) { if constexpr (sizeof(ValType) <= sizeof(uint64_t)) { - return std::to_string(Value); + return cpp::to_string(Value); } else { return describeValueUInt(Value); } } -std::string describeValue(std::string Value) { return std::string(Value); } -std::string describeValue(cpp::string_view Value) { - return std::string(Value.data(), Value.size()); -} +cpp::string describeValue(cpp::string Value) { return Value; } +cpp::string_view describeValue(cpp::string_view Value) { return Value; } template void explainDifference(ValType LHS, ValType RHS, const char *LHSStr, const char *RHSStr, const char *File, unsigned long Line, - std::string OpString) { + cpp::string OpString) { size_t OffsetLength = OpString.size() > 2 ? OpString.size() - 2 : 0; - std::string Offset(OffsetLength, ' '); + cpp::string Offset(OffsetLength, ' '); - std::cout << File << ":" << Line << ": FAILURE\n" - << Offset << "Expected: " << LHSStr << '\n' - << Offset << "Which is: " << describeValue(LHS) << '\n' - << "To be " << OpString << ": " << RHSStr << '\n' - << Offset << "Which is: " << describeValue(RHS) << '\n'; + tlog << File << ":" << Line << ": FAILURE\n" + << Offset << "Expected: " << LHSStr << '\n' + << Offset << "Which is: " << describeValue(LHS) << '\n' + << "To be " << OpString << ": " << RHSStr << '\n' + << Offset << "Which is: " << describeValue(RHS) << '\n'; } template bool test(RunContext *Ctx, TestCondition Cond, ValType LHS, ValType RHS, const char *LHSStr, const char *RHSStr, const char *File, unsigned long Line) { - auto ExplainDifference = [=](std::string OpString) { + auto ExplainDifference = [=](cpp::string OpString) { explainDifference(LHS, RHS, LHSStr, RHSStr, File, Line, OpString); }; @@ -141,7 +139,7 @@ return false; default: Ctx->markFail(); - std::cout << "Unexpected test condition.\n"; + tlog << "Unexpected test condition.\n"; return false; } } @@ -167,14 +165,14 @@ int FailCount = 0; for (Test *T = Start; T != nullptr; T = T->Next) { const char *TestName = T->getName(); - std::string StrTestName(TestName); + cpp::string StrTestName(TestName); constexpr auto GREEN = "\033[32m"; constexpr auto RED = "\033[31m"; constexpr auto RESET = "\033[0m"; if ((TestFilter != nullptr) && (StrTestName != TestFilter)) { continue; } - std::cout << GREEN << "[ RUN ] " << RESET << TestName << '\n'; + tlog << GREEN << "[ RUN ] " << RESET << TestName << '\n'; RunContext Ctx; T->SetUp(); T->setContext(&Ctx); @@ -183,24 +181,24 @@ auto Result = Ctx.status(); switch (Result) { case RunContext::Result_Fail: - std::cout << RED << "[ FAILED ] " << RESET << TestName << '\n'; + tlog << RED << "[ FAILED ] " << RESET << TestName << '\n'; ++FailCount; break; case RunContext::Result_Pass: - std::cout << GREEN << "[ OK ] " << RESET << TestName << '\n'; + tlog << GREEN << "[ OK ] " << RESET << TestName << '\n'; break; } ++TestCount; } if (TestCount > 0) { - std::cout << "Ran " << TestCount << " tests. " - << " PASS: " << TestCount - FailCount << ' ' - << " FAIL: " << FailCount << '\n'; + tlog << "Ran " << TestCount << " tests. " + << " PASS: " << TestCount - FailCount << ' ' << " FAIL: " << FailCount + << '\n'; } else { - std::cout << "No tests run.\n"; + tlog << "No tests run.\n"; if (TestFilter) { - std::cout << "No matching test for " << TestFilter << '\n'; + tlog << "No matching test for " << TestFilter << '\n'; } } @@ -302,15 +300,15 @@ bool Test::testStrEq(const char *LHS, const char *RHS, const char *LHSStr, const char *RHSStr, const char *File, unsigned long Line) { - return internal::test(Ctx, Cond_EQ, LHS ? std::string(LHS) : std::string(), - RHS ? std::string(RHS) : std::string(), LHSStr, RHSStr, + return internal::test(Ctx, Cond_EQ, LHS ? cpp::string(LHS) : cpp::string(), + RHS ? cpp::string(RHS) : cpp::string(), LHSStr, RHSStr, File, Line); } bool Test::testStrNe(const char *LHS, const char *RHS, const char *LHSStr, const char *RHSStr, const char *File, unsigned long Line) { - return internal::test(Ctx, Cond_NE, LHS ? std::string(LHS) : std::string(), - RHS ? std::string(RHS) : std::string(), LHSStr, RHSStr, + return internal::test(Ctx, Cond_NE, LHS ? cpp::string(LHS) : cpp::string(), + RHS ? cpp::string(RHS) : cpp::string(), LHSStr, RHSStr, File, Line); } @@ -321,8 +319,8 @@ Ctx->markFail(); if (!Matcher.is_silent()) { - std::cout << File << ":" << Line << ": FAILURE\n" - << "Failed to match " << LHSStr << " against " << RHSStr << ".\n"; + tlog << File << ":" << Line << ": FAILURE\n" + << "Failed to match " << LHSStr << " against " << RHSStr << ".\n"; testutils::StreamWrapper OutsWrapper = testutils::outs(); Matcher.explainError(OutsWrapper); } @@ -338,22 +336,22 @@ if (const char *error = Result.get_error()) { Ctx->markFail(); - std::cout << File << ":" << Line << ": FAILURE\n" << error << '\n'; + tlog << File << ":" << Line << ": FAILURE\n" << error << '\n'; return false; } if (Result.timed_out()) { Ctx->markFail(); - std::cout << File << ":" << Line << ": FAILURE\n" - << "Process timed out after " << 500 << " milliseconds.\n"; + tlog << File << ":" << Line << ": FAILURE\n" + << "Process timed out after " << 500 << " milliseconds.\n"; return false; } if (Result.exited_normally()) { Ctx->markFail(); - std::cout << File << ":" << Line << ": FAILURE\n" - << "Expected " << LHSStr - << " to be killed by a signal\nBut it exited normally!\n"; + tlog << File << ":" << Line << ": FAILURE\n" + << "Expected " << LHSStr + << " to be killed by a signal\nBut it exited normally!\n"; return false; } @@ -364,12 +362,12 @@ using testutils::signal_as_string; Ctx->markFail(); - std::cout << File << ":" << Line << ": FAILURE\n" - << " Expected: " << LHSStr << '\n' - << "To be killed by signal: " << Signal << '\n' - << " Which is: " << signal_as_string(Signal) << '\n' - << " But it was killed by: " << KilledBy << '\n' - << " Which is: " << signal_as_string(KilledBy) << '\n'; + tlog << File << ":" << Line << ": FAILURE\n" + << " Expected: " << LHSStr << '\n' + << "To be killed by signal: " << Signal << '\n' + << " Which is: " << signal_as_string(Signal) << '\n' + << " But it was killed by: " << KilledBy << '\n' + << " Which is: " << signal_as_string(KilledBy) << '\n'; return false; } @@ -380,23 +378,23 @@ if (const char *error = Result.get_error()) { Ctx->markFail(); - std::cout << File << ":" << Line << ": FAILURE\n" << error << '\n'; + tlog << File << ":" << Line << ": FAILURE\n" << error << '\n'; return false; } if (Result.timed_out()) { Ctx->markFail(); - std::cout << File << ":" << Line << ": FAILURE\n" - << "Process timed out after " << 500 << " milliseconds.\n"; + tlog << File << ":" << Line << ": FAILURE\n" + << "Process timed out after " << 500 << " milliseconds.\n"; return false; } if (!Result.exited_normally()) { Ctx->markFail(); - std::cout << File << ":" << Line << ": FAILURE\n" - << "Expected " << LHSStr << '\n' - << "to exit with exit code " << ExitCode << '\n' - << "But it exited abnormally!\n"; + tlog << File << ":" << Line << ": FAILURE\n" + << "Expected " << LHSStr << '\n' + << "to exit with exit code " << ExitCode << '\n' + << "But it exited abnormally!\n"; return false; } @@ -405,11 +403,11 @@ return true; Ctx->markFail(); - std::cout << File << ":" << Line << ": FAILURE\n" - << "Expected exit code of: " << LHSStr << '\n' - << " Which is: " << ActualExit << '\n' - << " To be equal to: " << RHSStr << '\n' - << " Which is: " << ExitCode << '\n'; + tlog << File << ":" << Line << ": FAILURE\n" + << "Expected exit code of: " << LHSStr << '\n' + << " Which is: " << ActualExit << '\n' + << " To be equal to: " << RHSStr << '\n' + << " Which is: " << ExitCode << '\n'; return false; } diff --git a/libc/test/UnitTest/TestLogger.h b/libc/test/UnitTest/TestLogger.h new file mode 100644 --- /dev/null +++ b/libc/test/UnitTest/TestLogger.h @@ -0,0 +1,27 @@ +//===-- Utilities to log to standard output during tests --------*- 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_TESTLOGGER_H +#define LLVM_LIBC_TEST_UNITTEST_TESTLOGGER_H + +namespace __llvm_libc { +namespace testing { + +// A class to log to standard output in the context of hermetic tests. +struct TestLogger { + constexpr TestLogger() = default; + template TestLogger &operator<<(T); +}; + +// A global TestLogger instance to be used in tests. +extern TestLogger tlog; + +} // namespace testing +} // namespace __llvm_libc + +#endif /* LLVM_LIBC_TEST_UNITTEST_TESTLOGGER_H */ diff --git a/libc/test/UnitTest/TestLogger.cpp b/libc/test/UnitTest/TestLogger.cpp new file mode 100644 --- /dev/null +++ b/libc/test/UnitTest/TestLogger.cpp @@ -0,0 +1,53 @@ +#include "test/UnitTest/TestLogger.h" +#include "src/__support/CPP/string.h" +#include "src/__support/CPP/string_view.h" +#include "src/__support/OSUtil/io.h" //write_to_stderr + +namespace __llvm_libc { +namespace testing { + +// cpp::string specialization +template <> +TestLogger &TestLogger::operator<< (cpp::string String) { + __llvm_libc::write_to_stderr(String.c_str()); + return *this; +} + +// cpp::string_view specialization +template <> +TestLogger &TestLogger::operator<< (cpp::string_view String) { + return *this << cpp::string(String.data(), String.size()); +} + +// const char* specialization +template <> +TestLogger &TestLogger::operator<< (const char *CString) { + __llvm_libc::write_to_stderr(CString); + return *this; +} + +// char specialization +template <> TestLogger &TestLogger::operator<<(char Char) { + const char CString[] = {Char, '\0'}; + return *this << CString; +} + +template TestLogger &TestLogger::operator<<(T t) { + return *this << cpp::to_string(t); +} + +// is_integral specializations +template TestLogger &TestLogger::operator<< (int); +template TestLogger &TestLogger::operator<< (unsigned int); +template TestLogger &TestLogger::operator<< (long); +template TestLogger &TestLogger::operator<< (unsigned long); +template TestLogger &TestLogger::operator<< (long long); +template TestLogger & +TestLogger::operator<< (unsigned long long); + +// TODO: Add floating point formatting once it's supported by StringStream. + +TestLogger tlog; + +} // namespace testing +} // namespace __llvm_libc diff --git a/libc/test/src/__support/CPP/CMakeLists.txt b/libc/test/src/__support/CPP/CMakeLists.txt --- a/libc/test/src/__support/CPP/CMakeLists.txt +++ b/libc/test/src/__support/CPP/CMakeLists.txt @@ -79,7 +79,7 @@ SRCS optional_test.cpp DEPENDS - libc.src.__support.CPP.optional + libc.src.__support.CPP.optional ) add_libc_unittest( @@ -89,5 +89,16 @@ SRCS span_test.cpp DEPENDS - libc.src.__support.CPP.span + libc.src.__support.CPP.span +) + +add_libc_unittest( + string_test + SUITE + libc_cpp_utils_unittests + SRCS + string_test.cpp + DEPENDS + libc.src.__support.CPP.string + libc.src.__support.CPP.string_view ) diff --git a/libc/test/src/__support/CPP/string_test.cpp b/libc/test/src/__support/CPP/string_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/__support/CPP/string_test.cpp @@ -0,0 +1,198 @@ +//===-- Unittests for string ----------------------------------------------===// +// +// 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 "src/__support/CPP/string.h" +#include "test/UnitTest/Test.h" + +using __llvm_libc::cpp::string; +using __llvm_libc::cpp::string_view; +using __llvm_libc::cpp::to_string; + +TEST(LlvmLibcStringTest, InitializeEmpty) { + const string s; + ASSERT_EQ(s.size(), size_t(0)); + ASSERT_TRUE(s.empty()); + ASSERT_STREQ(s.data(), ""); + ASSERT_STREQ(s.c_str(), ""); + ASSERT_EQ(s.data(), s.c_str()); + ASSERT_EQ(s.capacity(), size_t(0)); +} + +TEST(LlvmLibcStringTest, InitializeCString) { + const char *const str = "abc"; + const string s(str); + ASSERT_EQ(s.size(), size_t(3)); + ASSERT_FALSE(s.empty()); + ASSERT_NE(s.data(), &str[0]); + ASSERT_EQ(s[0], 'a'); + ASSERT_EQ(s[1], 'b'); + ASSERT_EQ(s[2], 'c'); + ASSERT_EQ(s.front(), 'a'); + ASSERT_EQ(s.back(), 'c'); + ASSERT_EQ(s.data(), s.c_str()); +} + +TEST(LlvmLibcStringTest, ToCString) { + const char *const str = "abc"; + string s(str); + const char *CBuffer = s.c_str(); + ASSERT_EQ(s.size(), size_t(3)); + ASSERT_STREQ(str, CBuffer); +} + +TEST(LlvmLibcStringTest, ToStringView) { + const char *const str = "abc"; + string s(str); + string_view view = s; + ASSERT_EQ(view, string_view(str)); +} + +TEST(LlvmLibcStringTest, InitializeCStringWithSize) { + const char *const str = "abc"; + const string s(str, 2); + ASSERT_EQ(s.size(), size_t(2)); + ASSERT_EQ(s[0], 'a'); + ASSERT_EQ(s[1], 'b'); + ASSERT_EQ(s.front(), 'a'); + ASSERT_EQ(s.back(), 'b'); +} + +TEST(LlvmLibcStringTest, InitializeRepeatedChar) { + const string s(4, '1'); + ASSERT_EQ(string_view(s), string_view("1111")); +} + +TEST(LlvmLibcStringTest, InitializeZeorChar) { + const string s(0, '1'); + ASSERT_TRUE(s.empty()); +} + +TEST(LlvmLibcStringTest, CopyConstruct) { + const char *const str = "abc"; + string a(str); + string b(a); + // Same content + ASSERT_STREQ(a.c_str(), str); + ASSERT_STREQ(b.c_str(), str); + // Different pointers + ASSERT_NE(a.data(), b.data()); +} + +string &&move(string &Value) { return static_cast(Value); } + +TEST(LlvmLibcStringTest, CopyAssign) { + const char *const str = "abc"; + string a(str); + string b; + b = a; + // Same content + ASSERT_STREQ(a.c_str(), str); + ASSERT_STREQ(b.c_str(), str); + // Different pointers + ASSERT_NE(a.data(), b.data()); +} + +TEST(LlvmLibcStringTest, MoveConstruct) { + const char *const str = "abc"; + string a(str); + string b(move(a)); + ASSERT_STREQ(b.c_str(), str); + ASSERT_STREQ(a.c_str(), ""); +} + +TEST(LlvmLibcStringTest, MoveAssign) { + const char *const str = "abc"; + string a(str); + string b; + b = move(a); + ASSERT_STREQ(b.c_str(), str); + ASSERT_STREQ(a.c_str(), ""); +} + +TEST(LlvmLibcStringTest, Concat) { + const char *const str = "abc"; + string a(str); + string b; + b += a; + ASSERT_STREQ(b.c_str(), "abc"); + b += a; + ASSERT_STREQ(b.c_str(), "abcabc"); +} + +TEST(LlvmLibcStringTest, AddChar) { + string a; + a += 'a'; + ASSERT_STREQ(a.c_str(), "a"); + a += 'b'; + ASSERT_STREQ(a.c_str(), "ab"); +} + +TEST(LlvmLibcStringTest, ResizeCapacityAndNullTermination) { + string a; + // Empty + ASSERT_EQ(a.capacity(), size_t(0)); + ASSERT_EQ(a.data()[0], '\0'); + // Still empty + a.resize(0); + ASSERT_EQ(a.capacity(), size_t(0)); + ASSERT_EQ(a.data()[0], '\0'); + // One char + a.resize(1); + ASSERT_EQ(a.size(), size_t(1)); + ASSERT_GE(a.capacity(), size_t(2)); + ASSERT_EQ(a.data()[1], '\0'); + // Clear + a.resize(0); + ASSERT_EQ(a.size(), size_t(0)); + ASSERT_GE(a.capacity(), size_t(2)); + ASSERT_EQ(a.data()[0], '\0'); +} + +TEST(LlvmLibcStringTest, ConcatWithCString) { + ASSERT_STREQ((string("a") + string("b")).c_str(), "ab"); + ASSERT_STREQ((string("a") + "b").c_str(), "ab"); + ASSERT_STREQ(("a" + string("b")).c_str(), "ab"); +} + +TEST(LlvmLibcStringTest, Comparison) { + // Here we simply check that comparison of string and string_view have the + // same semantic. + struct CStringPair { + const char *const a; + const char *const b; + } kTestPairs[] = {{"a", "b"}, {"", "xyz"}}; + for (const auto [pa, pb] : kTestPairs) { + const string sa(pa); + const string sb(pb); + const string_view sva(pa); + const string_view svb(pb); + ASSERT_EQ(sa == sb, sva == svb); + ASSERT_EQ(sa != sb, sva != svb); + ASSERT_EQ(sa >= sb, sva >= svb); + ASSERT_EQ(sa <= sb, sva <= svb); + ASSERT_EQ(sa < sb, sva < svb); + ASSERT_EQ(sa > sb, sva > svb); + } +} + +TEST(LlvmLibcStringTest, ToString) { + struct CStringPair { + const int Value; + const string Str; + } kTestPairs[] = {{123, "123"}, {0, "0"}, {-321, "-321"}}; + for (const auto &[Value, Str] : kTestPairs) { + ASSERT_EQ(to_string((int)(Value)), Str); + ASSERT_EQ(to_string((long)(Value)), Str); + ASSERT_EQ(to_string((long long)(Value)), Str); + if (Value >= 0) { + ASSERT_EQ(to_string((unsigned int)(Value)), Str); + ASSERT_EQ(to_string((unsigned long)(Value)), Str); + ASSERT_EQ(to_string((unsigned long long)(Value)), Str); + } + } +} diff --git a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel --- a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel +++ b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel @@ -189,6 +189,19 @@ ], ) +libc_support_library( + name = "__support_cpp_string", + hdrs = ["src/__support/CPP/string.h"], + deps = [ + ":__support_common", + ":__support_cpp_string_view", + ":__support_integer_to_string", + ":libc_root", + ":string_memory_utils", + ":string_utils", + ], +) + libc_support_library( name = "__support_cpp_type_traits", hdrs = ["src/__support/CPP/type_traits.h"], diff --git a/utils/bazel/llvm-project-overlay/libc/test/UnitTest/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/test/UnitTest/BUILD.bazel --- a/utils/bazel/llvm-project-overlay/libc/test/UnitTest/BUILD.bazel +++ b/utils/bazel/llvm-project-overlay/libc/test/UnitTest/BUILD.bazel @@ -8,6 +8,18 @@ licenses(["notice"]) +cc_library( + name = "test_logger", + srcs = ["TestLogger.cpp"], + hdrs = ["TestLogger.h"], + deps = [ + "//libc:__support_cpp_string", + "//libc:__support_cpp_string_view", + "//libc:__support_osutil_io", + "//libc:libc_root", + ], +) + cc_library( name = "LibcUnitTest", srcs = [ @@ -20,9 +32,11 @@ "Test.h", ], deps = [ + ":test_logger", "//libc:__support_cpp_bit", "//libc:__support_cpp_bitset", "//libc:__support_cpp_span", + "//libc:__support_cpp_string", "//libc:__support_cpp_string_view", "//libc:__support_cpp_type_traits", "//libc:__support_uint128",