aboutsummaryrefslogtreecommitdiff
path: root/libc
diff options
context:
space:
mode:
authorGuillaume Chatelet <gchatelet@google.com>2023-04-03 13:09:39 +0000
committerGuillaume Chatelet <gchatelet@google.com>2023-04-03 13:10:30 +0000
commitf5dcab0d894b139c2882dae617ca1ddc20a72395 (patch)
treebad7401c254947a50896f5f0dd95d6340ff5aa32 /libc
parentfd86789962964a98157e8159c3d95cdc241942e3 (diff)
downloadllvm-f5dcab0d894b139c2882dae617ca1ddc20a72395.zip
llvm-f5dcab0d894b139c2882dae617ca1ddc20a72395.tar.gz
llvm-f5dcab0d894b139c2882dae617ca1ddc20a72395.tar.bz2
[re-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
Diffstat (limited to 'libc')
-rw-r--r--libc/src/__support/CPP/CMakeLists.txt13
-rw-r--r--libc/src/__support/CPP/string.h226
-rw-r--r--libc/test/UnitTest/CMakeLists.txt43
-rw-r--r--libc/test/UnitTest/LibcTest.cpp122
-rw-r--r--libc/test/UnitTest/LibcTest.h9
-rw-r--r--libc/test/UnitTest/TestLogger.cpp49
-rw-r--r--libc/test/UnitTest/TestLogger.h27
-rw-r--r--libc/test/src/__support/CPP/CMakeLists.txt15
-rw-r--r--libc/test/src/__support/CPP/string_test.cpp204
9 files changed, 631 insertions, 77 deletions
diff --git a/libc/src/__support/CPP/CMakeLists.txt b/libc/src/__support/CPP/CMakeLists.txt
index 1104114..63f1fc6 100644
--- a/libc/src/__support/CPP/CMakeLists.txt
+++ b/libc/src/__support/CPP/CMakeLists.txt
@@ -56,6 +56,19 @@ add_header_library(
)
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
stringstream.h
diff --git a/libc/src/__support/CPP/string.h b/libc/src/__support/CPP/string.h
new file mode 100644
index 0000000..fa73d53
--- /dev/null
+++ b/libc/src/__support/CPP/string.h
@@ -0,0 +1,226 @@
+//===-- 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 (buffer_ != get_empty_string())
+ buffer_[size_] = NULL_CHARACTER;
+ }
+
+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(buffer_ == get_empty_string() ? nullptr : buffer_,
+ new_capacity)) {
+ buffer_ = static_cast<char *>(Ptr);
+ capacity_ = new_capacity;
+ } else {
+ __builtin_unreachable(); // out of memory
+ }
+ }
+
+ LIBC_INLINE void resize(size_t size) {
+ if (size > capacity_) {
+ reserve(size);
+ const size_t size_extension = size - size_;
+ inline_memset(data() + size_, '\0', size_extension);
+ }
+ 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 c3eabed..20400af 100644
--- a/libc/test/UnitTest/CMakeLists.txt
+++ b/libc/test/UnitTest/CMakeLists.txt
@@ -1,12 +1,29 @@
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
LibcTest.cpp
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 725ef54..55b2c10 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 8241fe9..976f79a 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 @@ protected:
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 0000000..98cbdbd
--- /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 0000000..2d46ea1
--- /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 b9696ba..9a95195 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 0000000..f7a1821
--- /dev/null
+++ b/libc/test/src/__support/CPP/string_test.cpp
@@ -0,0 +1,204 @@
+//===-- 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');
+ // Resize and check zero initialized
+ a.resize(10);
+ ASSERT_EQ(a.size(), size_t(10));
+ ASSERT_GE(a.capacity(), size_t(10));
+ for (size_t i = 0; i < 10; ++i)
+ ASSERT_EQ(a[i], '\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);
+ }
+ }
+}