blob: f2b50056adbf47413c1bcf3a760a9ab579516ed6 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <gtest/gtest.h>
#include <tvm/ffi/any.h>
#include <tvm/ffi/string.h>
namespace {
using namespace tvm::ffi;
TEST(String, MoveFromStd) {
using namespace std;
string source = "this is a string";
string expect = source;
String s(std::move(source));
string copy = string(s);
EXPECT_EQ(copy, expect);
EXPECT_EQ(source.size(), 0); // NOLINT(bugprone-use-after-move)
}
TEST(String, CopyFromStd) {
using namespace std;
string source = "this is a string";
string expect = source; // NOLINT(performance-unnecessary-copy-initialization)
String s{source};
string copy = string(s);
EXPECT_EQ(copy, expect);
EXPECT_EQ(source.size(), expect.size());
}
TEST(String, Assignment) {
using namespace std;
String s{string{"hello"}};
s = string{"world"};
EXPECT_EQ(s == "world", true);
string s2{"world2"};
s = std::move(s2);
EXPECT_EQ(s == "world2", true);
Any r;
r = String("hello");
EXPECT_EQ(r != nullptr, true);
}
TEST(String, empty) {
using namespace std;
String s{"hello"};
EXPECT_EQ(s.empty(), false);
s = std::string("");
EXPECT_EQ(s.empty(), true);
}
TEST(String, Comparisons) {
using namespace std;
string source = "a string";
string mismatch = "a string but longer";
String s{"a string"};
String m{mismatch};
EXPECT_EQ("a str" >= s, false);
EXPECT_EQ(s == source, true);
EXPECT_EQ(s == mismatch, false);
EXPECT_EQ(s == source.data(), true);
EXPECT_EQ(s == mismatch.data(), false);
EXPECT_EQ(s < m, source < mismatch);
EXPECT_EQ(s > m, source > mismatch);
EXPECT_EQ(s <= m, source <= mismatch);
EXPECT_EQ(s >= m, source >= mismatch);
EXPECT_EQ(s == m, source == mismatch);
EXPECT_EQ(s != m, source != mismatch);
EXPECT_EQ(m < s, mismatch < source);
EXPECT_EQ(m > s, mismatch > source);
EXPECT_EQ(m <= s, mismatch <= source);
EXPECT_EQ(m >= s, mismatch >= source);
EXPECT_EQ(m == s, mismatch == source);
EXPECT_EQ(m != s, mismatch != source);
}
TEST(String, Compare) {
// string compare const char*
String s{"hello"};
EXPECT_EQ(s.compare("hello"), 0);
EXPECT_EQ(s.compare(String("hello")), 0);
EXPECT_EQ(s.compare("hallo"), 1);
EXPECT_EQ(s.compare(String("hallo")), 1);
EXPECT_EQ(s.compare("hfllo"), -1);
EXPECT_EQ(s.compare(String("hfllo")), -1);
// s is longer
EXPECT_EQ(s.compare("hell"), 1);
EXPECT_EQ(s.compare(String("hell")), 1);
// s is shorter
EXPECT_EQ(s.compare("hello world"), -1);
EXPECT_EQ(s.compare(String("helloworld")), -1);
}
// Check '\0' handling
TEST(String, NullByteHandling) {
using namespace std;
// Ensure string still compares equal if it contains '\0'.
string v1 = "hello world";
size_t v1_size = v1.size();
v1[5] = '\0';
EXPECT_EQ(v1[5], '\0');
EXPECT_EQ(v1.size(), v1_size);
String str_v1{v1};
EXPECT_EQ(str_v1.compare(v1), 0);
EXPECT_EQ(str_v1.size(), v1_size);
// Ensure bytes after '\0' are taken into account for mismatches.
string v2 = "aaa one";
string v3 = "aaa two";
v2[3] = '\0';
v3[3] = '\0';
String str_v2{v2};
String str_v3{v3};
EXPECT_EQ(str_v2.compare(str_v3), -1);
EXPECT_EQ(str_v2.size(), 7);
// strcmp won't be able to detect the mismatch
EXPECT_EQ(strcmp(v2.data(), v3.data()), 0);
// string::compare can handle \0 since it knows size
EXPECT_LT(v2.compare(v3), 0);
// If there is mismatch before '\0', should still handle it.
string v4 = "acc one";
string v5 = "abb two";
v4[3] = '\0';
v5[3] = '\0';
String str_v4{v4};
String str_v5{v5};
EXPECT_GT(str_v4.compare(str_v5), 0);
EXPECT_EQ(str_v4.size(), 7);
// strcmp is able to detect the mismatch
EXPECT_GT(strcmp(v4.data(), v5.data()), 0);
// string::compare can handle \0 since it knows size
EXPECT_GT(v4.compare(v5), 0);
}
TEST(String, CompareSameMemoryRegionDifferentSize) {
using namespace std;
string source = "a string";
String str_source{source};
char* memory = const_cast<char*>(str_source.data());
EXPECT_EQ(str_source.compare(memory), 0);
// This changes the string size
memory[2] = '\0';
// memory is logically shorter now
EXPECT_GT(str_source.compare(memory), 0);
}
TEST(String, compare) {
using namespace std;
constexpr auto mismatch1_cstr = "a string but longer";
string source = "a string";
string mismatch1 = mismatch1_cstr;
string mismatch2 = "a strin";
string mismatch3 = "a b";
string mismatch4 = "a t";
String str_source{source};
String str_mismatch1{mismatch1_cstr};
String str_mismatch2{mismatch2};
String str_mismatch3{mismatch3};
String str_mismatch4{mismatch4};
// compare with string
EXPECT_EQ(str_source.compare(source), 0);
EXPECT_TRUE(str_source == source);
EXPECT_TRUE(source == str_source);
EXPECT_TRUE(str_source <= source);
EXPECT_TRUE(source <= str_source);
EXPECT_TRUE(str_source >= source);
EXPECT_TRUE(source >= str_source);
EXPECT_LT(str_source.compare(mismatch1), 0);
EXPECT_TRUE(str_source < mismatch1);
EXPECT_TRUE(mismatch1 != str_source);
EXPECT_GT(str_source.compare(mismatch2), 0);
EXPECT_TRUE(str_source > mismatch2);
EXPECT_TRUE(mismatch2 < str_source);
EXPECT_GT(str_source.compare(mismatch3), 0);
EXPECT_TRUE(str_source > mismatch3);
EXPECT_LT(str_source.compare(mismatch4), 0);
EXPECT_TRUE(str_source < mismatch4);
EXPECT_TRUE(mismatch4 > str_source);
// compare with char*
EXPECT_EQ(str_source.compare(source.data()), 0);
EXPECT_TRUE(str_source == source.data());
EXPECT_TRUE(source.data() == str_source);
EXPECT_TRUE(str_source <= source.data());
EXPECT_TRUE(source <= str_source.data());
EXPECT_TRUE(str_source >= source.data());
EXPECT_TRUE(source >= str_source.data());
EXPECT_LT(str_source.compare(mismatch1.data()), 0);
EXPECT_TRUE(str_source < mismatch1.data());
EXPECT_TRUE(str_source != mismatch1.data());
EXPECT_TRUE(mismatch1.data() != str_source);
EXPECT_GT(str_source.compare(mismatch2.data()), 0);
EXPECT_TRUE(str_source > mismatch2.data());
EXPECT_TRUE(mismatch2.data() < str_source);
EXPECT_GT(str_source.compare(mismatch3.data()), 0);
EXPECT_TRUE(str_source > mismatch3.data());
EXPECT_LT(str_source.compare(mismatch4.data()), 0);
EXPECT_TRUE(str_source < mismatch4.data());
EXPECT_TRUE(mismatch4.data() > str_source);
// compare with String
EXPECT_LT(str_source.compare(str_mismatch1), 0);
EXPECT_TRUE(str_source < str_mismatch1);
EXPECT_GT(str_source.compare(str_mismatch2), 0);
EXPECT_TRUE(str_source > str_mismatch2);
EXPECT_GT(str_source.compare(str_mismatch3), 0);
EXPECT_TRUE(str_source > str_mismatch3);
EXPECT_LT(str_source.compare(str_mismatch4), 0);
EXPECT_TRUE(str_source < str_mismatch4);
}
TEST(String, CString) {
using namespace std;
string source = "this is a string";
string mismatch = "mismatch";
String s{source};
EXPECT_EQ(std::strcmp(s.c_str(), source.data()), 0);
EXPECT_NE(std::strcmp(s.c_str(), mismatch.data()), 0);
}
TEST(String, hash) {
using namespace std;
string source = "this is a string";
String s{source};
std::hash<String>()(s);
std::unordered_map<String, std::string> map;
String k1{string{"k1"}};
string v1{"v1"};
String k2{string{"k2"}};
string v2{"v2"};
map[k1] = v1;
map[k2] = v2;
EXPECT_EQ(map[k1], v1);
EXPECT_EQ(map[k2], v2);
}
TEST(String, Cast) {
using namespace std;
string source = "this is a string";
String s{source};
Any r = s;
String s2 = r.cast<String>();
}
TEST(String, Concat) {
String s1("hello");
String s2("world");
std::string s3("world");
String res1 = s1 + s2;
String res2 = s1 + s3;
String res3 = s3 + s1;
String res4 = s1 + "world";
String res5 = "world" + s1;
EXPECT_EQ(res1.compare("helloworld"), 0);
EXPECT_EQ(res2.compare("helloworld"), 0);
EXPECT_EQ(res3.compare("worldhello"), 0);
EXPECT_EQ(res4.compare("helloworld"), 0);
EXPECT_EQ(res5.compare("worldhello"), 0);
String storage_scope;
String res = "The input storage scope \"" + storage_scope + "\" is invalid.";
EXPECT_EQ(res.compare("The input storage scope \"\" is invalid."), 0);
}
TEST(String, Any) {
// test anyview promotion to any
AnyView view = "hello";
EXPECT_EQ(view.type_index(), TypeIndex::kTVMFFIRawStr);
Any b = view;
EXPECT_EQ(b.type_index(), TypeIndex::kTVMFFISmallStr);
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
EXPECT_EQ(b.as<String>().value(), "hello");
EXPECT_TRUE(b.as<String>().has_value());
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
EXPECT_EQ(b.try_cast<std::string>().value(), "hello");
std::string s_world = "world";
view = s_world;
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
EXPECT_EQ(view.try_cast<std::string>().value(), "world");
String s{"hello"};
Any a = s;
EXPECT_EQ(a.type_index(), TypeIndex::kTVMFFISmallStr);
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
EXPECT_EQ(a.as<String>().value(), "hello");
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
EXPECT_EQ(a.try_cast<std::string>().value(), "hello");
Any c = "long string very long";
EXPECT_EQ(c.type_index(), TypeIndex::kTVMFFIStr);
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
EXPECT_EQ(c.as<String>().value(), "long string very long");
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
EXPECT_EQ(c.try_cast<std::string>().value(), "long string very long");
}
TEST(String, Bytes) {
Bytes b0;
EXPECT_EQ(b0.size(), 0);
EXPECT_EQ(b0.operator std::string(), "");
// explicitly test zero element
std::string s = {'\0', 'a', 'b', 'c'};
Bytes b = s;
EXPECT_EQ(b.size(), 4);
EXPECT_EQ(b.operator std::string(), s);
TVMFFIByteArray arr{s.data(), static_cast<size_t>(s.size())};
Bytes b2 = arr;
EXPECT_EQ(b2.size(), 4);
EXPECT_EQ(b2.operator std::string(), s);
}
TEST(String, BytesAny) {
std::string s = {'\0', 'a', 'b', 'c'};
TVMFFIByteArray arr{s.data(), static_cast<size_t>(s.size())};
AnyView view = &arr;
EXPECT_EQ(view.type_index(), TypeIndex::kTVMFFIByteArrayPtr);
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
EXPECT_EQ(view.try_cast<Bytes>().value().operator std::string(), s);
Any b = view;
EXPECT_EQ(b.type_index(), TypeIndex::kTVMFFISmallBytes);
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
EXPECT_EQ(b.try_cast<Bytes>().value().operator std::string(), s);
EXPECT_EQ(b.cast<std::string>(), s);
std::string s2 = "hello long long long string";
s2[0] = '\0';
Any b2 = Bytes(s2);
EXPECT_EQ(b2.type_index(), TypeIndex::kTVMFFIBytes);
EXPECT_EQ(b2.try_cast<std::string>().value(), // NOLINT(bugprone-unchecked-optional-access)
s2);
EXPECT_EQ(b2.cast<std::string>(), s2);
}
TEST(String, StdString) {
std::string s1 = "test_string";
AnyView view1 = s1;
EXPECT_EQ(view1.type_index(), TypeIndex::kTVMFFIRawStr);
EXPECT_EQ(view1.try_cast<std::string>().value(), // NOLINT(bugprone-unchecked-optional-access)
s1);
TVMFFIByteArray arr1{s1.data(), static_cast<size_t>(s1.size())};
AnyView view2 = &arr1;
EXPECT_EQ(view2.type_index(), TypeIndex::kTVMFFIByteArrayPtr);
EXPECT_EQ(view2.try_cast<std::string>().value(), // NOLINT(bugprone-unchecked-optional-access)
s1);
Bytes bytes1 = s1;
AnyView view3 = bytes1;
EXPECT_EQ(view3.type_index(), TypeIndex::kTVMFFIBytes);
EXPECT_EQ(view3.try_cast<std::string>().value(), // NOLINT(bugprone-unchecked-optional-access)
s1);
String string1 = s1;
AnyView view4 = string1;
EXPECT_EQ(view4.type_index(), TypeIndex::kTVMFFIStr);
EXPECT_EQ(view4.try_cast<std::string>().value(), // NOLINT(bugprone-unchecked-optional-access)
s1);
// Test with Any
Any any1 = s1;
EXPECT_EQ(any1.type_index(), TypeIndex::kTVMFFIStr);
EXPECT_EQ(any1.try_cast<std::string>().value(), // NOLINT(bugprone-unchecked-optional-access)
s1);
Any any2 = &arr1;
EXPECT_EQ(any2.type_index(), TypeIndex::kTVMFFIBytes);
EXPECT_EQ(any2.try_cast<std::string>().value(), // NOLINT(bugprone-unchecked-optional-access)
s1);
Any any3 = bytes1;
EXPECT_EQ(any3.type_index(), TypeIndex::kTVMFFIBytes);
EXPECT_EQ(any3.try_cast<std::string>().value(), // NOLINT(bugprone-unchecked-optional-access)
s1);
Any any4 = string1;
EXPECT_EQ(any4.type_index(), TypeIndex::kTVMFFIStr);
EXPECT_EQ(any4.try_cast<std::string>().value(), // NOLINT(bugprone-unchecked-optional-access)
s1);
}
TEST(String, CAPIAccessor) {
using namespace std;
String s{"hello"};
TVMFFIByteArray arr{s.data(), s.size()};
EXPECT_EQ(arr.size, 5);
EXPECT_EQ(std::string(arr.data, arr.size), "hello");
}
TEST(String, BytesHash) {
std::vector<int64_t> data1(10);
std::vector<int64_t> data2(11);
for (size_t i = 0; i < data1.size(); ++i) {
data1[i] = static_cast<int64_t>(i);
}
char* data1_ptr = reinterpret_cast<char*>(data1.data());
char* data2_ptr = reinterpret_cast<char*>(data2.data()) + 1;
std::memcpy(data2_ptr, data1.data(), data1.size() * sizeof(int64_t));
// has of aligned and unaligned data should be the same
uint64_t hash1 = details::StableHashBytes(data1_ptr, data1.size() * sizeof(int64_t));
uint64_t hash2 = details::StableHashBytes(data2_ptr, data1.size() * sizeof(int64_t));
EXPECT_EQ(hash1, hash2);
}
TEST(String, StdHash) {
String s1 = "a";
String s2(std::string("a"));
EXPECT_EQ(std::hash<String>()(s1), std::hash<String>()(s2));
Bytes s3("a", 1);
Bytes s4(std::string("a"));
EXPECT_EQ(std::hash<Bytes>()(s3), std::hash<Bytes>()(s4));
}
TEST(String, Find) {
String s{"hello world"};
EXPECT_EQ(s.find("world"), 6);
EXPECT_EQ(s.find("hello"), 0);
EXPECT_EQ(s.find("o"), 4);
EXPECT_EQ(s.find("o", 5), 7);
EXPECT_EQ(s.find("notfound"), String::npos);
EXPECT_EQ(s.find(""), 0);
EXPECT_EQ(s.find("", 5), 5);
EXPECT_EQ(s.find("", 11), 11);
EXPECT_EQ(s.find("", 20), String::npos);
String pattern{"world"};
EXPECT_EQ(s.find(pattern), 6);
String empty{""};
EXPECT_EQ(empty.find("x"), String::npos);
EXPECT_EQ(empty.find(""), 0);
}
TEST(String, Substr) {
String s{"hello world"};
EXPECT_EQ(s.substr(0, 5), "hello");
EXPECT_EQ(s.substr(6, 5), "world");
EXPECT_EQ(s.substr(6), "world");
EXPECT_EQ(s.substr(0), "hello world");
EXPECT_EQ(s.substr(11), "");
EXPECT_EQ(s.substr(0, 0), "");
EXPECT_THROW(s.substr(12), std::out_of_range);
EXPECT_THROW(s.substr(100), std::out_of_range);
String empty{""};
EXPECT_EQ(empty.substr(0), "");
EXPECT_THROW(empty.substr(1), std::out_of_range);
}
TEST(String, StartsWith) {
String s{"hello world"};
EXPECT_TRUE(s.starts_with("hello"));
EXPECT_TRUE(s.starts_with("h"));
EXPECT_TRUE(s.starts_with(""));
EXPECT_TRUE(s.starts_with(String{"hello"}));
EXPECT_TRUE(s.starts_with(std::string_view{"hello"}));
EXPECT_FALSE(s.starts_with("world"));
EXPECT_FALSE(s.starts_with("Hello"));
EXPECT_FALSE(s.starts_with("hello world extra"));
EXPECT_FALSE(s.starts_with(std::string_view{"world"}));
String empty{""};
EXPECT_TRUE(empty.starts_with(""));
EXPECT_TRUE(empty.starts_with(std::string_view{""}));
EXPECT_FALSE(empty.starts_with("x"));
String single{"x"};
EXPECT_TRUE(single.starts_with("x"));
EXPECT_TRUE(single.starts_with(""));
EXPECT_FALSE(single.starts_with("xy"));
}
TEST(String, EndsWith) {
String s{"hello world"};
EXPECT_TRUE(s.ends_with("world"));
EXPECT_TRUE(s.ends_with("d"));
EXPECT_TRUE(s.ends_with(""));
EXPECT_TRUE(s.ends_with(String{"world"}));
EXPECT_TRUE(s.ends_with(std::string_view{"world"}));
EXPECT_FALSE(s.ends_with("hello"));
EXPECT_FALSE(s.ends_with("World"));
EXPECT_FALSE(s.ends_with("extra hello world"));
EXPECT_FALSE(s.ends_with(std::string_view{"hello"}));
String empty{""};
EXPECT_TRUE(empty.ends_with(""));
EXPECT_TRUE(empty.ends_with(std::string_view{""}));
EXPECT_FALSE(empty.ends_with("x"));
String single{"x"};
EXPECT_TRUE(single.ends_with("x"));
EXPECT_TRUE(single.ends_with(""));
EXPECT_FALSE(single.ends_with("yx"));
}
TEST(String, Split) {
String s{"a,b,c"};
auto parts = s.Split(',');
ASSERT_EQ(parts.size(), 3);
EXPECT_EQ(parts[0], "a");
EXPECT_EQ(parts[1], "b");
EXPECT_EQ(parts[2], "c");
// No delimiter present
String s2{"hello"};
auto parts2 = s2.Split(',');
ASSERT_EQ(parts2.size(), 1);
EXPECT_EQ(parts2[0], "hello");
// Empty string
String s3{""};
auto parts3 = s3.Split(',');
ASSERT_EQ(parts3.size(), 1);
EXPECT_EQ(parts3[0], "");
// Delimiter at boundaries
String s4{",a,b,"};
auto parts4 = s4.Split(',');
ASSERT_EQ(parts4.size(), 4);
EXPECT_EQ(parts4[0], "");
EXPECT_EQ(parts4[1], "a");
EXPECT_EQ(parts4[2], "b");
EXPECT_EQ(parts4[3], "");
// Consecutive delimiters
String s5{"a,,b"};
auto parts5 = s5.Split(',');
ASSERT_EQ(parts5.size(), 3);
EXPECT_EQ(parts5[0], "a");
EXPECT_EQ(parts5[1], "");
EXPECT_EQ(parts5[2], "b");
}
TEST(String, EscapeStringJSON) {
// Basic escaping
String s1{"hello"};
EXPECT_EQ(EscapeStringJSON(s1), "\"hello\"");
// Special characters
String s2{"line1\nline2\ttab"};
EXPECT_EQ(EscapeStringJSON(s2), "\"line1\\nline2\\ttab\"");
// Backslash and quote
String s3{"a\\b\"c"};
EXPECT_EQ(EscapeStringJSON(s3), "\"a\\\\b\\\"c\"");
// Control characters
String s4{std::string("a\x01\x1f z", 5)};
EXPECT_EQ(EscapeStringJSON(s4), "\"a\\u0001\\u001f z\"");
}
TEST(String, EscapedStringPyBasic) {
// Plain ASCII
String s1{"hello world"};
EXPECT_EQ(EscapedStringPy(s1), "\"hello world\"");
// C escape sequences
String s2{"a\nb\tc\r"};
EXPECT_EQ(EscapedStringPy(s2), "\"a\\nb\\tc\\r\"");
// Backslash and quote
String s3{"a\\b\"c"};
EXPECT_EQ(EscapedStringPy(s3), "\"a\\\\b\\\"c\"");
}
TEST(String, EscapedStringPyControlChars) {
// Control characters -> \xNN
String s1{std::string("\x01\x02\x7f", 3)};
String result = EscapedStringPy(s1);
EXPECT_EQ(result, "\"\\x01\\x02\\x7f\"");
}
TEST(String, EscapedStringPyANSI) {
// ANSI escape: ESC[31m (red)
String s1{std::string("\x1b[31mred\x1b[0m", 12)};
String result = EscapedStringPy(s1);
EXPECT_EQ(result, "\"\\x1b[31mred\\x1b[0m\"");
// ANSI erase line: ESC[K
String s2{std::string("\x1b[K", 3)};
EXPECT_EQ(EscapedStringPy(s2), "\"\\x1b[K\"");
}
TEST(String, EscapedStringPyUTF8) {
// 2-byte: U+00E9 (é) = C3 A9
String s1{std::string("\xc3\xa9", 2)};
EXPECT_EQ(EscapedStringPy(s1), "\"\\u00e9\"");
// 3-byte: U+4E16 (世) = E4 B8 96
String s2{std::string("\xe4\xb8\x96", 3)};
EXPECT_EQ(EscapedStringPy(s2), "\"\\u4e16\"");
// 4-byte: U+1F600 (😀) = F0 9F 98 80
String s3{std::string("\xf0\x9f\x98\x80", 4)};
EXPECT_EQ(EscapedStringPy(s3), "\"\\U0001f600\"");
}
TEST(String, EscapedStringPyMalformedUTF8) {
// Lone continuation byte -> \xNN fallback
String s1{std::string("\x80", 1)};
EXPECT_EQ(EscapedStringPy(s1), "\"\\x80\"");
// 2-byte leader followed by non-continuation -> fallback for leader
String s2{std::string("\xc3\x20", 2)};
String result2 = EscapedStringPy(s2);
EXPECT_EQ(result2, "\"\\xc3 \"");
// 3-byte leader with bad continuation -> fallback for leader
String s3{std::string("\xe4\xb8\x20", 3)};
String result3 = EscapedStringPy(s3);
EXPECT_EQ(result3, "\"\\xe4\\xb8 \"");
// Truncated 2-byte at end of string
String s4{std::string("\xc3", 1)};
EXPECT_EQ(EscapedStringPy(s4), "\"\\xc3\"");
}
} // namespace