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 NULL_CHARACTER = '\0'; + static constexpr char *get_empty_string() { + return const_cast(&NULL_CHARACTER); + } + + char *buffer_ = get_empty_string(); + size_t size_ = 0; + size_t capacity_ = 0; + + void reset_no_deallocate() { + buffer_ = get_empty_string(); + size_ = 0; + capacity_ = 0; + } + + void set_size_and_add_null_character(size_t size) { + size_ = size; + if (size_ > 0) + buffer_[size_] = NULL_CHARACTER; + else + buffer_ = get_empty_string(); + } + +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.reset_no_deallocate(); + } + LIBC_INLINE string(const char *cstr, size_t count) { + resize(count); + inline_memcpy((void *)buffer_, (const void *)cstr, count); + } + LIBC_INLINE string(const char *cstr) + : string(cstr, ::__llvm_libc::internal::string_length(cstr)) {} + 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.reset_no_deallocate(); + return *this; + } + + LIBC_INLINE ~string() { + if (buffer_ != get_empty_string()) + ::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 index) const { + return data()[index]; + } + LIBC_INLINE char &operator[](size_t index) { return data()[index]; } + + 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 new_capacity) { + ++new_capacity; // Accounting for the terminating '\0' + if (new_capacity <= 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 guard the extension so the operation doesn't overflow. + if (new_capacity < SIZE_MAX / 11) + new_capacity = new_capacity * 11 / 8; + if (void *Ptr = ::realloc(empty() ? nullptr : buffer_, new_capacity)) { + buffer_ = static_cast(Ptr); + } else { + __builtin_unreachable(); // out of memory + } + capacity_ = new_capacity; + } + + LIBC_INLINE void resize(size_t size) { + if (size > capacity_) + reserve(size); + set_size_and_add_null_character(size); + } + + LIBC_INLINE string &operator+=(const string &rhs) { + const size_t new_size = size_ + rhs.size(); + reserve(new_size); + inline_memcpy(buffer_ + size_, rhs.data(), rhs.size()); + set_size_and_add_null_character(new_size); + return *this; + } + + LIBC_INLINE string &operator+=(const char c) { + const size_t new_size = size_ + 1; + reserve(new_size); + buffer_[size_] = c; + set_size_and_add_null_character(new_size); + 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 dec_buf[IntegerToString::dec_bufsize()]; + auto maybe_string_view = IntegerToString::dec(value, dec_buf); + const auto &string_view = *maybe_string_view; + return string(string_view.data(), string_view.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,49 @@ +#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_view specialization +template <> +TestLogger &TestLogger::operator<< (cpp::string_view str) { + __llvm_libc::write_to_stderr(str); + return *this; +} + +// cpp::string specialization +template <> TestLogger &TestLogger::operator<< (cpp::string str) { + return *this << static_cast(str); +} + +// const char* specialization +template <> TestLogger &TestLogger::operator<< (const char *str) { + return *this << cpp::string_view(str); +} + +// char specialization +template <> TestLogger &TestLogger::operator<<(char ch) { + return *this << cpp::string_view(&ch, 1); +} + +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 *cstr = s.c_str(); + ASSERT_EQ(s.size(), size_t(3)); + ASSERT_STREQ(str, cstr); +} + +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",