[libc-commits] [libc] b82d04e - [reland][libc] Adds string and TestLogger classes, use them in LibcTest

Guillaume Chatelet via libc-commits libc-commits at lists.llvm.org
Mon Apr 3 00:35:49 PDT 2023


Author: Guillaume Chatelet
Date: 2023-04-03T07:35:37Z
New Revision: b82d04ea4b4f26037be369f101ae7011975df486

URL: https://github.com/llvm/llvm-project/commit/b82d04ea4b4f26037be369f101ae7011975df486
DIFF: https://github.com/llvm/llvm-project/commit/b82d04ea4b4f26037be369f101ae7011975df486.diff

LOG: [reland][libc] Adds string and TestLogger classes, use them in LibcTest

This is an implementation of https://discourse.llvm.org/t/rfc-running-libc-unit-tests-as-integration-tests/69461.

Differential Revision: https://reviews.llvm.org/D147231

Added: 
    libc/src/__support/CPP/string.h
    libc/test/UnitTest/TestLogger.cpp
    libc/test/UnitTest/TestLogger.h
    libc/test/src/__support/CPP/string_test.cpp

Modified: 
    libc/src/__support/CPP/CMakeLists.txt
    libc/test/UnitTest/CMakeLists.txt
    libc/test/UnitTest/LibcTest.cpp
    libc/test/UnitTest/LibcTest.h
    libc/test/src/__support/CPP/CMakeLists.txt
    utils/bazel/llvm-project-overlay/libc/BUILD.bazel
    utils/bazel/llvm-project-overlay/libc/test/UnitTest/BUILD.bazel

Removed: 
    


################################################################################
diff  --git a/libc/src/__support/CPP/CMakeLists.txt b/libc/src/__support/CPP/CMakeLists.txt
index 11041142627bb..63f1fc6673fde 100644
--- a/libc/src/__support/CPP/CMakeLists.txt
+++ b/libc/src/__support/CPP/CMakeLists.txt
@@ -55,6 +55,19 @@ add_header_library(
     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
index 0000000000000..cdf8d28437ecb
--- /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 <stddef.h> // size_t
+#include <stdlib.h> // 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<char *>(&NULL_CHARACTER);
+  }
+
+  char *buffer_ = get_empty_string();
+  size_t size_ = 0;
+  size_t capacity_ = 0;
+
+  constexpr 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 constexpr 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 constexpr size_t capacity() const { return capacity_; }
+  LIBC_INLINE constexpr size_t size() const { return size_; }
+  LIBC_INLINE constexpr bool empty() const { return size_ == 0; }
+
+  LIBC_INLINE constexpr const char *data() const { return buffer_; }
+  LIBC_INLINE char *data() { return buffer_; }
+
+  LIBC_INLINE constexpr const char *begin() const { return data(); }
+  LIBC_INLINE char *begin() { return data(); }
+
+  LIBC_INLINE constexpr const char *end() const { return data() + size_; }
+  LIBC_INLINE char *end() { return data() + size_; }
+
+  LIBC_INLINE constexpr const char &front() const { return data()[0]; }
+  LIBC_INLINE char &front() { return data()[0]; }
+
+  LIBC_INLINE constexpr 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<char *>(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 <typename T> string to_dec_string(T value) {
+  char dec_buf[IntegerToString::dec_bufsize<T>()];
+  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<int>(value);
+}
+LIBC_INLINE string to_string(long value) {
+  return internal::to_dec_string<long>(value);
+}
+LIBC_INLINE string to_string(long long value) {
+  return internal::to_dec_string<long long>(value);
+}
+LIBC_INLINE string to_string(unsigned value) {
+  return internal::to_dec_string<unsigned>(value);
+}
+LIBC_INLINE string to_string(unsigned long value) {
+  return internal::to_dec_string<unsigned long>(value);
+}
+LIBC_INLINE string to_string(unsigned long long value) {
+  return internal::to_dec_string<unsigned long long>(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
index c3eabeda076af..20400af2e8853 100644
--- 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 @@ add_library(
   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 @@ target_link_libraries(LibcUnitTestMain PUBLIC LibcUnitTest libc_test_utils)
 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_dependencies(
 
 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_dependencies(
 
 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_dependencies(
 
 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.cpp b/libc/test/UnitTest/LibcTest.cpp
index 725ef54ebbf56..55b2c10bc9b3f 100644
--- 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 <cassert>
-#include <iostream>
-#include <string>
 
 namespace __llvm_libc {
 namespace testing {
@@ -41,18 +41,18 @@ namespace internal {
 // be able to unittest UInt<128> on platforms where UInt128 resolves to
 // UInt128.
 template <typename T>
-cpp::enable_if_t<cpp::is_integral_v<T> && cpp::is_unsigned_v<T>, std::string>
+cpp::enable_if_t<cpp::is_integral_v<T> && cpp::is_unsigned_v<T>, 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<unsigned char>(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 @@ describeValueUInt(T Value) {
 
 // When the value is of integral type, just display it as normal.
 template <typename ValType>
-cpp::enable_if_t<cpp::is_integral_v<ValType>, std::string>
+cpp::enable_if_t<cpp::is_integral_v<ValType>, 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 <typename ValType>
 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 <typename ValType>
 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 @@ bool test(RunContext *Ctx, TestCondition Cond, ValType LHS, ValType RHS,
     return false;
   default:
     Ctx->markFail();
-    std::cout << "Unexpected test condition.\n";
+    tlog << "Unexpected test condition.\n";
     return false;
   }
 }
@@ -167,14 +165,14 @@ int Test::runTests(const char *TestFilter) {
   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 @@ int Test::runTests(const char *TestFilter) {
     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 @@ template bool test<__llvm_libc::cpp::string_view>(
 
 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 @@ bool Test::testMatch(bool MatchResult, MatcherBase &Matcher, const char *LHSStr,
 
   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 @@ bool Test::testProcessKilled(testutils::FunctionCaller *Func, int Signal,
 
   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 @@ bool Test::testProcessKilled(testutils::FunctionCaller *Func, int Signal,
 
   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 @@ bool Test::testProcessExits(testutils::FunctionCaller *Func, int ExitCode,
 
   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 @@ bool Test::testProcessExits(testutils::FunctionCaller *Func, int ExitCode,
     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/LibcTest.h b/libc/test/UnitTest/LibcTest.h
index 8241fe90187d2..976f79ac394a9 100644
--- 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 @@ class Test {
     return internal::test(Ctx, Cond, LHS, RHS, LHSStr, RHSStr, File, Line);
   }
 
+  template <typename ValType,
+            cpp::enable_if_t<cpp::is_same_v<ValType, __llvm_libc::cpp::string>,
+                             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/TestLogger.cpp b/libc/test/UnitTest/TestLogger.cpp
new file mode 100644
index 0000000000000..98cbdbdaedb41
--- /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>(cpp::string_view str) {
+  __llvm_libc::write_to_stderr(str);
+  return *this;
+}
+
+// cpp::string specialization
+template <> TestLogger &TestLogger::operator<< <cpp::string>(cpp::string str) {
+  return *this << static_cast<cpp::string_view>(str);
+}
+
+// const char* specialization
+template <> TestLogger &TestLogger::operator<< <const char *>(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 <typename T> TestLogger &TestLogger::operator<<(T t) {
+  return *this << cpp::to_string(t);
+}
+
+// is_integral specializations
+template TestLogger &TestLogger::operator<< <int>(int);
+template TestLogger &TestLogger::operator<< <unsigned int>(unsigned int);
+template TestLogger &TestLogger::operator<< <long>(long);
+template TestLogger &TestLogger::operator<< <unsigned long>(unsigned long);
+template TestLogger &TestLogger::operator<< <long long>(long long);
+template TestLogger &
+TestLogger::operator<< <unsigned long long>(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/UnitTest/TestLogger.h b/libc/test/UnitTest/TestLogger.h
new file mode 100644
index 0000000000000..2d46ea1ca1465
--- /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 <typename T> 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/src/__support/CPP/CMakeLists.txt b/libc/test/src/__support/CPP/CMakeLists.txt
index b9696baef3822..9a9519559db33 100644
--- a/libc/test/src/__support/CPP/CMakeLists.txt
+++ b/libc/test/src/__support/CPP/CMakeLists.txt
@@ -79,7 +79,7 @@ add_libc_unittest(
   SRCS
     optional_test.cpp
   DEPENDS
-  libc.src.__support.CPP.optional
+    libc.src.__support.CPP.optional
 )
 
 add_libc_unittest(
@@ -89,5 +89,16 @@ add_libc_unittest(
   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
index 0000000000000..54a3610387374
--- /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<string &&>(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
index 9ac7a1d327d6c..02ae15e1d49c1 100644
--- 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(
     ],
 )
 
+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
index 297b98c1193b0..7810735ef4b26 100644
--- 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 @@ package(default_visibility = ["//visibility:public"])
 
 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 @@ cc_library(
         "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",


        


More information about the libc-commits mailing list