terminal/src/til/ut_til/string.cpp
2024-12-04 16:41:14 -08:00

271 lines
11 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
using namespace WEX::Common;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
template<>
class WEX::TestExecution::VerifyOutputTraits<std::vector<std::wstring>>
{
public:
static WEX::Common::NoThrowString ToString(const std::vector<std::wstring>& vec)
{
WEX::Common::NoThrowString str;
str.Append(L"{ ");
for (size_t i = 0; i < vec.size(); ++i)
{
str.Append(i == 0 ? L"\"" : L", \"");
str.Append(vec[i].c_str());
str.Append(L"\"");
}
str.Append(L" }");
return str;
}
};
class StringTests
{
TEST_CLASS(StringTests);
TEST_METHOD(VisualizeControlCodes)
{
const std::wstring_view input{ L"\u001b[A \u001b[B\x7f" };
const std::wstring_view expected{ L"\u241b[A\u2423\u241b[B\x2421" };
const auto actual = til::visualize_control_codes(input);
VERIFY_ARE_EQUAL(expected, actual);
}
TEST_METHOD(starts_with)
{
VERIFY_IS_TRUE(til::starts_with("", ""));
VERIFY_IS_TRUE(til::starts_with("abc", ""));
VERIFY_IS_TRUE(til::starts_with("abc", "a"));
VERIFY_IS_TRUE(til::starts_with("abc", "ab"));
VERIFY_IS_TRUE(til::starts_with("abc", "abc"));
VERIFY_IS_FALSE(til::starts_with("abc", "abcd"));
VERIFY_IS_FALSE(til::starts_with("", "abc"));
VERIFY_IS_FALSE(til::starts_with("a", "abc"));
VERIFY_IS_FALSE(til::starts_with("ab", "abc"));
VERIFY_IS_TRUE(til::starts_with("abc", "abc"));
VERIFY_IS_TRUE(til::starts_with("abcd", "abc"));
}
TEST_METHOD(ends_with)
{
VERIFY_IS_TRUE(til::ends_with("", ""));
VERIFY_IS_TRUE(til::ends_with("abc", ""));
VERIFY_IS_TRUE(til::ends_with("abc", "c"));
VERIFY_IS_TRUE(til::ends_with("abc", "bc"));
VERIFY_IS_TRUE(til::ends_with("abc", "abc"));
VERIFY_IS_FALSE(til::ends_with("abc", "0abc"));
VERIFY_IS_FALSE(til::ends_with("", "abc"));
VERIFY_IS_FALSE(til::ends_with("c", "abc"));
VERIFY_IS_FALSE(til::ends_with("bc", "abc"));
VERIFY_IS_TRUE(til::ends_with("abc", "abc"));
VERIFY_IS_TRUE(til::ends_with("0abc", "abc"));
}
// Normally this would be the spot where you'd find a TEST_METHOD(parse_u64).
// I didn't quite trust my coding skills and thus opted to use fuzz-testing.
// The below function was used to test parse_u64 for unsafety and conformance with clang's strtoul.
// The test was run as:
// clang++ -fsanitize=address,undefined,fuzzer -std=c++17 file.cpp
// and was run for 20min across 16 jobs in parallel.
#if 0
template<typename T, typename Traits>
std::optional<uint64_t> parse_u64(const std::basic_string_view<T, Traits>& str, int base = 0) noexcept
{
// ... implementation ...
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
{
while (size > 0 && (isspace(*data) || *data == '+' || *data == '-'))
{
--size;
++data;
}
if (size == 0 || size > 127)
{
return 0;
}
char narrow_buffer[128];
memcpy(narrow_buffer, data, size);
// strtoul requires a null terminator
narrow_buffer[size] = 0;
char* end;
const auto val = strtoull(narrow_buffer, &end, 0);
const auto bad = end != narrow_buffer + size || val == ULLONG_MAX;
const auto expected = bad ? std::nullopt : std::optional{ val };
const auto actual = parse_u64(std::string_view{ narrow_buffer, size });
if (expected != actual && actual.value_or(0) != ULLONG_MAX)
{
__builtin_trap();
}
return 0;
}
#endif
TEST_METHOD(parse_u64_overflow)
{
VERIFY_ARE_EQUAL(UINT64_C(18446744073709551614), til::details::parse_u64(std::string_view{ "18446744073709551614" }));
VERIFY_ARE_EQUAL(UINT64_C(18446744073709551615), til::details::parse_u64(std::string_view{ "18446744073709551615" }));
VERIFY_ARE_EQUAL(std::nullopt, til::details::parse_u64(std::string_view{ "18446744073709551616" }));
VERIFY_ARE_EQUAL(std::nullopt, til::details::parse_u64(std::string_view{ "18446744073709551617" }));
VERIFY_ARE_EQUAL(std::nullopt, til::details::parse_u64(std::string_view{ "88888888888888888888" }));
}
TEST_METHOD(parse_unsigned)
{
VERIFY_ARE_EQUAL(std::nullopt, til::parse_unsigned<uint32_t>(""));
VERIFY_ARE_EQUAL(std::nullopt, til::parse_unsigned<uint32_t>("0x"));
VERIFY_ARE_EQUAL(std::nullopt, til::parse_unsigned<uint32_t>("Z"));
VERIFY_ARE_EQUAL(std::nullopt, til::parse_unsigned<uint32_t>("0xZ"));
VERIFY_ARE_EQUAL(std::nullopt, til::parse_unsigned<uint32_t>("0Z"));
VERIFY_ARE_EQUAL(std::nullopt, til::parse_unsigned<uint32_t>("123abc"));
VERIFY_ARE_EQUAL(std::nullopt, til::parse_unsigned<uint32_t>("0123abc"));
VERIFY_ARE_EQUAL(std::nullopt, til::parse_unsigned<uint32_t>("0x100000000"));
VERIFY_ARE_EQUAL(0u, til::parse_unsigned<uint32_t>("0"));
VERIFY_ARE_EQUAL(0u, til::parse_unsigned<uint32_t>("0x0"));
VERIFY_ARE_EQUAL(0123u, til::parse_unsigned<uint32_t>("0123"));
VERIFY_ARE_EQUAL(123u, til::parse_unsigned<uint32_t>("123"));
VERIFY_ARE_EQUAL(0x123u, til::parse_unsigned<uint32_t>("0x123"));
VERIFY_ARE_EQUAL(0x123abcu, til::parse_unsigned<uint32_t>("0x123abc"));
VERIFY_ARE_EQUAL(0X123ABCu, til::parse_unsigned<uint32_t>("0X123ABC"));
VERIFY_ARE_EQUAL(UINT32_MAX, til::parse_unsigned<uint32_t>("0xffffffff"));
VERIFY_ARE_EQUAL(UINT32_MAX, til::parse_unsigned<uint32_t>("4294967295"));
}
TEST_METHOD(parse_signed)
{
VERIFY_ARE_EQUAL(std::nullopt, til::parse_signed<int32_t>(""));
VERIFY_ARE_EQUAL(std::nullopt, til::parse_signed<int32_t>("-"));
VERIFY_ARE_EQUAL(std::nullopt, til::parse_signed<int32_t>("--"));
VERIFY_ARE_EQUAL(std::nullopt, til::parse_signed<int32_t>("--0"));
VERIFY_ARE_EQUAL(std::nullopt, til::parse_signed<int32_t>("-0Z"));
VERIFY_ARE_EQUAL(std::nullopt, til::parse_signed<int32_t>("-123abc"));
VERIFY_ARE_EQUAL(std::nullopt, til::parse_signed<int32_t>("-0123abc"));
VERIFY_ARE_EQUAL(std::nullopt, til::parse_signed<int32_t>("0x80000000"));
VERIFY_ARE_EQUAL(std::nullopt, til::parse_signed<int32_t>("-0x80000001"));
VERIFY_ARE_EQUAL(0, til::parse_signed<int32_t>("0"));
VERIFY_ARE_EQUAL(0, til::parse_signed<int32_t>("-0"));
VERIFY_ARE_EQUAL(0, til::parse_signed<int32_t>("-0x0"));
VERIFY_ARE_EQUAL(0123, til::parse_signed<int32_t>("0123"));
VERIFY_ARE_EQUAL(123, til::parse_signed<int32_t>("123"));
VERIFY_ARE_EQUAL(0x123, til::parse_signed<int32_t>("0x123"));
VERIFY_ARE_EQUAL(-0123, til::parse_signed<int32_t>("-0123"));
VERIFY_ARE_EQUAL(-123, til::parse_signed<int32_t>("-123"));
VERIFY_ARE_EQUAL(-0x123, til::parse_signed<int32_t>("-0x123"));
VERIFY_ARE_EQUAL(-0x123abc, til::parse_signed<int32_t>("-0x123abc"));
VERIFY_ARE_EQUAL(-0X123ABC, til::parse_signed<int32_t>("-0X123ABC"));
VERIFY_ARE_EQUAL(INT32_MIN, til::parse_signed<int32_t>("-0x80000000"));
VERIFY_ARE_EQUAL(INT32_MIN, til::parse_signed<int32_t>("-2147483648"));
VERIFY_ARE_EQUAL(INT32_MAX, til::parse_signed<int32_t>("0x7fffffff"));
VERIFY_ARE_EQUAL(INT32_MAX, til::parse_signed<int32_t>("2147483647"));
}
TEST_METHOD(tolower_ascii)
{
for (wchar_t ch = 0; ch < 128; ++ch)
{
VERIFY_ARE_EQUAL(std::towlower(ch), til::tolower_ascii(ch));
}
}
TEST_METHOD(toupper_ascii)
{
for (wchar_t ch = 0; ch < 128; ++ch)
{
VERIFY_ARE_EQUAL(std::towupper(ch), til::toupper_ascii(ch));
}
}
TEST_METHOD(equals_insensitive_ascii)
{
VERIFY_IS_TRUE(til::equals_insensitive_ascii("", ""));
VERIFY_IS_FALSE(til::equals_insensitive_ascii("", "foo"));
VERIFY_IS_FALSE(til::equals_insensitive_ascii("foo", "fo"));
VERIFY_IS_FALSE(til::equals_insensitive_ascii("fooo", "foo"));
VERIFY_IS_TRUE(til::equals_insensitive_ascii("cOUnterStriKE", "COuntERStRike"));
}
TEST_METHOD(split_iterator)
{
static constexpr auto split = [](const std::string_view& str, const char needle) {
std::vector<std::string_view> substrings;
for (auto&& s : til::split_iterator{ str, needle })
{
substrings.emplace_back(s);
}
return substrings;
};
std::vector<std::string_view> expected;
std::vector<std::string_view> actual;
expected = { "foo" };
actual = split("foo", ' ');
VERIFY_ARE_EQUAL(expected, actual);
expected = { "", "foo" };
actual = split(" foo", ' ');
VERIFY_ARE_EQUAL(expected, actual);
expected = { "foo", "" };
actual = split("foo ", ' ');
VERIFY_ARE_EQUAL(expected, actual);
expected = { "foo", "bar", "baz" };
actual = split("foo bar baz", ' ');
VERIFY_ARE_EQUAL(expected, actual);
expected = { "", "", "foo", "", "bar", "", "" };
actual = split(";;foo;;bar;;", ';');
VERIFY_ARE_EQUAL(expected, actual);
}
TEST_METHOD(CleanPathAndFilename)
{
VERIFY_ARE_EQUAL(LR"(CUsersGeddyMusicAnalog Man)", til::clean_filename(LR"(C:\Users\Geddy\Music\"Analog Man")"));
VERIFY_ARE_EQUAL(LR"(C:\Users\Geddy\Music\Analog Man)", til::clean_path(LR"(C:\Users\Geddy\Music\"Analog Man")"));
}
TEST_METHOD(LegalPath)
{
VERIFY_IS_TRUE(til::is_legal_path(LR"(C:\Users\Documents and Settings\Users\;\Why not)"));
VERIFY_IS_FALSE(til::is_legal_path(LR"(C:\Users\Documents and Settings\"Quote-un-quote users")"));
}
TEST_METHOD(IterateFontFamilies)
{
static constexpr auto expected = [](auto&&... args) {
return std::vector<std::wstring>{ std::forward<decltype(args)>(args)... };
};
static constexpr auto actual = [](std::wstring_view families) {
std::vector<std::wstring> split;
til::iterate_font_families(families, [&](std::wstring&& str) {
split.emplace_back(std::move(str));
});
return split;
};
VERIFY_ARE_EQUAL(expected(L"foo", L" b a r ", LR"(b"az)"), actual(LR"( foo ," b a r ",b\"az)"));
VERIFY_ARE_EQUAL(expected(LR"(foo, bar)"), actual(LR"("foo, bar")"));
VERIFY_ARE_EQUAL(expected(LR"("foo")", LR"('bar')"), actual(LR"('"foo"', "'bar'")"));
VERIFY_ARE_EQUAL(expected(LR"("foo")", LR"('bar')"), actual(LR"("\"foo\"", '\'bar\'')"));
VERIFY_ARE_EQUAL(expected(L"foo"), actual(LR"(,,,,foo,,,,)"));
}
};