diff --git a/CHANGELOG.md b/CHANGELOG.md
index babb653f..50f1976a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -29,7 +29,7 @@
- Bugfix: Fixed channel point redemptions with messages not showing up if PubSub is disconnected. (#5948)
- Bugfix: Fixed the input font not immediately updating when zooming in/out. (#5960)
- Dev: Subscriptions to PubSub channel points redemption topics now use no auth token, making it continue to work during PubSub shutdown. (#5947)
-- Dev: Add initial experimental EventSub support. (#5837, #5895, #5897, #5904, #5910, #5903, #5915, #5916, #5930, #5935, #5932, #5943, #5952, #5953)
+- Dev: Add initial experimental EventSub support. (#5837, #5895, #5897, #5904, #5910, #5903, #5915, #5916, #5930, #5935, #5932, #5943, #5952, #5953, #5968)
- Dev: Remove unneeded platform specifier for toasts. (#5914)
- Dev: Highlight checks now use non-capturing groups for the boundaries. (#5784)
- Dev: Removed unused PubSub whisper code. (#5898)
diff --git a/lib/twitch-eventsub-ws/docs/twitch-eventsub-ws.natvis b/lib/twitch-eventsub-ws/docs/twitch-eventsub-ws.natvis
new file mode 100644
index 00000000..3e51b5e3
--- /dev/null
+++ b/lib/twitch-eventsub-ws/docs/twitch-eventsub-ws.natvis
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+ {storage.inPlace,[size()]s8}
+ {storage.data,[size()]s8}
+ {storage.qt.d.ptr,su}
+
+
+
+ InPlace
+ Allocated
+ QString
+
+
+
+ - size()
+
+ storage.inPlace,[size()]
+
+
+ storage.data,[size()]
+
+
+ storage.qt
+
+
+
+
diff --git a/lib/twitch-eventsub-ws/include/twitch-eventsub-ws/string.hpp b/lib/twitch-eventsub-ws/include/twitch-eventsub-ws/string.hpp
index d9178ed2..674047ad 100644
--- a/lib/twitch-eventsub-ws/include/twitch-eventsub-ws/string.hpp
+++ b/lib/twitch-eventsub-ws/include/twitch-eventsub-ws/string.hpp
@@ -1,67 +1,227 @@
#pragma once
#include
+#include
#include
-#include
-#include
+#include
+// NOLINTBEGIN(cppcoreguidelines-pro-type-union-access)
+// NOLINTBEGIN(bugprone-undefined-memory-manipulation)
+// NOLINTBEGIN(cppcoreguidelines-avoid-c-arrays)
namespace chatterino::eventsub::lib {
-template
-constexpr bool DEPENDENT_FALSE = false; // workaround before CWG2518/P2593R1
-
-/// String is a struct that holds either an std::string or a QString
+/// String is a struct that holds either an UTF-8 string or a QString
///
-/// The intended use is for it to receive an std::string that has been built by
+/// The intended use is for it to receive an UTF-8 string that has been built by
/// boost::json, and once it's been passed into a GUI appliciaton, it can use
/// the `qt` function to convert the backing string to a QString,
/// while we ensure the conversion is only done once.
+/// The string can always be viewed using \ref view() in Qt6.
+///
+/// It uses the small-string-optimization and has `sizeof(QString)` bytes of
+/// storage it can use without allocating (Qt6: 24B, Qt5: 8B). The stored string
+/// is **not** null-terminated.
struct String {
- explicit String(std::string &&v)
- : backingString(std::move(v))
+ constexpr String() noexcept = default;
+ constexpr String(std::string_view sv)
+ : flags(sv.length() & LENGTH_MASK)
{
+ if (sv.length() <= SSO_CAPACITY)
+ {
+ std::memcpy(this->storage.inPlace, sv.data(), sv.length());
+ return;
+ }
+
+ this->flags |= ALLOC_BIT;
+ auto *data = new char[sv.length()];
+ std::memcpy(data, sv.data(), sv.length());
+ this->storage.data = data;
}
- ~String() = default;
+ ~String()
+ {
+ if (this->isQt())
+ {
+ this->storage.qt.~QString();
+ }
+ else if (this->isAlloc())
+ {
+ delete[] this->storage.data;
+ }
+ // else: inPlace
+ }
String(const String &s) = delete;
- String(String &&s) = default;
-
String &operator=(const String &) = delete;
- String &operator=(String &&) = default;
+
+ String(String &&other) noexcept
+ : storage(std::move(other.storage))
+ , flags(other.flags)
+ {
+ other.flags = 0;
+ }
+
+ String &operator=(String &&other) noexcept
+ {
+ // clear our data (if needed)
+ if (this->isQt())
+ {
+ this->storage.qt.~QString();
+ }
+ else if (this->isAlloc())
+ {
+ delete[] this->storage.data;
+ }
+
+ this->storage = std::move(other.storage);
+ this->flags = other.flags;
+ other.flags = 0;
+
+ return *this;
+ }
/// Returns the string as a QString, modifying the backing string to ensure
/// the copy only happens once.
QString qt() const
{
- return std::visit(
- [this](auto &&arg) -> QString {
- using T = std::decay_t>;
- if constexpr (std::is_same_v)
- {
- auto qtString = QString::fromStdString(arg);
- this->backingString = qtString;
- return qtString;
- }
- else if constexpr (std::is_same_v)
- {
- return arg;
- }
- else
- {
- static_assert(DEPENDENT_FALSE,
- "unknown type in variant");
- }
- },
- this->backingString);
+ if (this->isEmpty())
+ {
+ return {};
+ }
+
+ if (this->isQt())
+ {
+ return this->storage.qt;
+ }
+
+ // not a QString yet
+ if (this->isAlloc())
+ {
+ auto s = QString::fromUtf8(
+ this->storage.data,
+ static_cast(this->flags & LENGTH_MASK));
+ delete[] this->storage.data;
+ new (&this->storage.qt) QString(std::move(s));
+ this->flags &= ~ALLOC_BIT;
+ }
+ else
+ {
+ auto s = QString::fromUtf8(
+ this->storage.inPlace,
+ static_cast(this->flags & LENGTH_MASK));
+ new (&this->storage.qt) QString(std::move(s));
+ }
+ this->flags |= QT_BIT;
+
+ return this->storage.qt;
+ }
+
+ QAnyStringView view() const noexcept
+ {
+ if (this->isQt())
+ {
+ return this->storage.qt;
+ }
+ return {
+ this->isAlloc() ? this->storage.data : this->storage.inPlace,
+ static_cast(this->flags & LENGTH_MASK),
+ };
+ }
+
+ constexpr bool isEmpty() const noexcept
+ {
+ return this->flags == 0;
+ }
+
+ constexpr bool isQt() const noexcept
+ {
+ return (this->flags & QT_BIT) != 0;
+ }
+
+ constexpr bool isAlloc() const noexcept
+ {
+ return (this->flags & ALLOC_BIT) != 0;
+ }
+
+ constexpr bool isInPlace() const noexcept
+ {
+ return (this->flags & (ALLOC_BIT | QT_BIT)) == 0;
}
private:
- mutable std::variant backingString;
+ static constexpr size_t QT_BIT = 1ULL << (sizeof(size_t) * 8 - 1);
+ static constexpr size_t ALLOC_BIT = 1ULL << (sizeof(size_t) * 8 - 2);
+ static constexpr size_t LENGTH_MASK = std::min(ALLOC_BIT, QT_BIT) - 1;
+ static constexpr size_t SSO_CAPACITY = sizeof(QString);
+
+ static_assert((LENGTH_MASK & ALLOC_BIT) == 0);
+ static_assert((LENGTH_MASK & QT_BIT) == 0);
+
+ constexpr size_t length() const noexcept
+ {
+ if ((this->flags & QT_BIT) != 0)
+ {
+ return this->storage.qt.length();
+ }
+ return this->flags & LENGTH_MASK;
+ }
+
+ mutable union Storage {
+ constexpr Storage() noexcept
+ {
+ }
+ ~Storage() noexcept
+ {
+ }
+
+ Storage(const Storage &) = delete;
+ Storage &operator=(const Storage &) = delete;
+
+ // we can memcpy QStrings as they're relocatable
+ static_assert(QTypeInfo::isRelocatable != 0);
+
+ Storage(Storage &&other) noexcept
+ {
+ Storage::move(std::addressof(other), this);
+ };
+ Storage &operator=(Storage &&other) noexcept
+ {
+ Storage::move(std::addressof(other), this);
+ return *this;
+ };
+
+ char inPlace[SSO_CAPACITY]{};
+ const char *data;
+ QString qt;
+
+ private:
+ static void move(Storage *from, Storage *to)
+ {
+ // copy `from` -> `to`
+ std::memcpy(to, from, sizeof(Storage));
+ // clear `from`
+ std::memset(from, 0, sizeof(Storage));
+ }
+ } storage;
+ static_assert(sizeof(Storage) == sizeof(QString));
+
+ /// Flags both store the length as well as the current state of the string.
+ /// They're defined as follows (big endian, MSB is first, 64bit):
+ /// ```
+ /// ┌──────┬──────┬─────────┬─────────────────────────────────────────┐
+ /// │Bit(s)│ 63 │ 62 │ 61..0 │
+ /// ├──────┼──────┼─────────┼─────────────────────────────────────────┤
+ /// │Value │ isQt │ isAlloc │ length │
+ /// └──────┴──────┴─────────┴─────────────────────────────────────────┘
+ /// ```
+ mutable size_t flags = 0;
};
boost::json::result_for::type tag_invoke(
boost::json::try_value_to_tag, const boost::json::value &jvRoot);
} // namespace chatterino::eventsub::lib
+// NOLINTEND(cppcoreguidelines-avoid-c-arrays)
+// NOLINTEND(bugprone-undefined-memory-manipulation)
+// NOLINTEND(cppcoreguidelines-pro-type-union-access)
diff --git a/lib/twitch-eventsub-ws/src/CMakeLists.txt b/lib/twitch-eventsub-ws/src/CMakeLists.txt
index 094dbc0e..79b9d918 100644
--- a/lib/twitch-eventsub-ws/src/CMakeLists.txt
+++ b/lib/twitch-eventsub-ws/src/CMakeLists.txt
@@ -71,6 +71,11 @@ if (MSVC)
# Use the function inlining level from 'Release' mode (2).
string(REPLACE "/Ob1" "/Ob2" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
+ # Add nativs to PDB
+ set(_natvis_path "${CMAKE_CURRENT_LIST_DIR}/../docs/twitch-eventsub-ws.natvis")
+ cmake_path(ABSOLUTE_PATH _natvis_path NORMALIZE)
+ target_link_options(${PROJECT_NAME} INTERFACE "/NATVIS:${_natvis_path}")
+
# Configure warnings
# Someone adds /W3 before we add /W4.
diff --git a/lib/twitch-eventsub-ws/src/string.cpp b/lib/twitch-eventsub-ws/src/string.cpp
index c6b0312d..6dde2378 100644
--- a/lib/twitch-eventsub-ws/src/string.cpp
+++ b/lib/twitch-eventsub-ws/src/string.cpp
@@ -1,10 +1,10 @@
#include "twitch-eventsub-ws/string.hpp"
+#include "twitch-eventsub-ws/errors.hpp"
+
#include
#include
-#include
-
namespace chatterino::eventsub::lib {
boost::json::result_for::type tag_invoke(
@@ -14,16 +14,17 @@ boost::json::result_for::type tag_invoke(
if (jvRoot.is_null())
{
// We treat null "strings" as empty strings
- return String("");
+ return String();
}
- auto v = boost::json::try_value_to(jvRoot);
- if (v.has_error())
+ if (!jvRoot.is_string())
{
- return v.error();
+ // we don't need a source_location - it doesn't tell us much
+ return lib::error::makeCode(error::Kind::ExpectedString, nullptr);
}
- return String(std::move(v.value()));
+ const auto &str = jvRoot.get_string();
+ return String(str);
}
} // namespace chatterino::eventsub::lib
diff --git a/lib/twitch-eventsub-ws/tests/CMakeLists.txt b/lib/twitch-eventsub-ws/tests/CMakeLists.txt
index c2289c65..da285a9b 100644
--- a/lib/twitch-eventsub-ws/tests/CMakeLists.txt
+++ b/lib/twitch-eventsub-ws/tests/CMakeLists.txt
@@ -2,6 +2,7 @@ project(twitch-eventsub-ws-test)
set(SOURCES
src/parse.cpp
+ src/string.cpp
)
add_executable(${PROJECT_NAME} ${SOURCES})
diff --git a/lib/twitch-eventsub-ws/tests/src/string.cpp b/lib/twitch-eventsub-ws/tests/src/string.cpp
new file mode 100644
index 00000000..1f7292f0
--- /dev/null
+++ b/lib/twitch-eventsub-ws/tests/src/string.cpp
@@ -0,0 +1,439 @@
+#include
+#include
+#include
+
+#include
+
+namespace {
+
+constexpr const char *LONGEST_SSO = "mylongstringyesitslongwo";
+constexpr const char *TOO_LONG = "mylongstringyesitslongwow";
+
+constexpr const char *REALLY_LONG =
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi lectus "
+ "massa, efficitur non elit vel, faucibus pellentesque enim.";
+
+} // namespace
+
+using String = chatterino::eventsub::lib::String;
+
+static_assert(std::is_move_assignable_v &&
+ std::is_move_constructible_v &&
+ !std::is_copy_assignable_v &&
+ !std::is_copy_constructible_v &&
+ !std::is_trivially_copyable_v &&
+ !std::is_trivially_move_assignable_v &&
+ !std::is_trivially_copy_assignable_v &&
+ !std::is_trivially_move_constructible_v &&
+ !std::is_trivially_copy_constructible_v);
+
+TEST(String, Construction)
+{
+ {
+ String s;
+ ASSERT_TRUE(s.isInPlace());
+ ASSERT_FALSE(s.isQt());
+ ASSERT_FALSE(s.isAlloc());
+ ASSERT_TRUE(s.isEmpty());
+ ASSERT_EQ(s.view(), "");
+ }
+ {
+ String s("");
+ ASSERT_TRUE(s.isInPlace());
+ ASSERT_FALSE(s.isQt());
+ ASSERT_FALSE(s.isAlloc());
+ ASSERT_TRUE(s.isEmpty());
+ ASSERT_EQ(s.view(), "");
+ }
+ {
+ String s("foo");
+ ASSERT_TRUE(s.isInPlace());
+ ASSERT_FALSE(s.isQt());
+ ASSERT_FALSE(s.isAlloc());
+ ASSERT_FALSE(s.isEmpty());
+ ASSERT_EQ(s.view(), "foo");
+ }
+ {
+ String s(LONGEST_SSO);
+ ASSERT_TRUE(s.isInPlace());
+ ASSERT_FALSE(s.isQt());
+ ASSERT_FALSE(s.isAlloc());
+ ASSERT_FALSE(s.isEmpty());
+ ASSERT_EQ(s.view(), LONGEST_SSO);
+ }
+ {
+ String s(TOO_LONG);
+ ASSERT_FALSE(s.isInPlace());
+ ASSERT_FALSE(s.isQt());
+ ASSERT_TRUE(s.isAlloc());
+ ASSERT_FALSE(s.isEmpty());
+ ASSERT_EQ(s.view(), TOO_LONG);
+ }
+}
+
+TEST(String, ToQt)
+{
+ {
+ String s;
+ ASSERT_FALSE(s.isQt());
+ ASSERT_EQ(s.qt(), "");
+ ASSERT_FALSE(s.isQt());
+ ASSERT_FALSE(s.isAlloc());
+ ASSERT_TRUE(s.isEmpty());
+ }
+ {
+ String s("");
+ ASSERT_FALSE(s.isQt());
+ ASSERT_EQ(s.qt(), "");
+ ASSERT_FALSE(s.isQt());
+ ASSERT_FALSE(s.isAlloc());
+ ASSERT_TRUE(s.isEmpty());
+ }
+ {
+ String s("foo");
+ ASSERT_FALSE(s.isQt());
+ ASSERT_EQ(s.qt(), "foo");
+ ASSERT_TRUE(s.isQt());
+ ASSERT_FALSE(s.isAlloc());
+ ASSERT_FALSE(s.isEmpty());
+ }
+ {
+ String s(LONGEST_SSO);
+ ASSERT_FALSE(s.isQt());
+ ASSERT_EQ(s.qt(), LONGEST_SSO);
+ ASSERT_TRUE(s.isQt());
+ ASSERT_FALSE(s.isAlloc());
+ ASSERT_FALSE(s.isEmpty());
+ }
+ {
+ String s(TOO_LONG);
+ ASSERT_FALSE(s.isQt());
+ ASSERT_EQ(s.qt(), TOO_LONG);
+ ASSERT_TRUE(s.isQt());
+ ASSERT_FALSE(s.isAlloc());
+ ASSERT_FALSE(s.isEmpty());
+ }
+ {
+ String s(REALLY_LONG);
+ ASSERT_FALSE(s.isQt());
+ ASSERT_EQ(s.qt(), REALLY_LONG);
+ ASSERT_TRUE(s.isQt());
+ ASSERT_FALSE(s.isAlloc());
+ ASSERT_FALSE(s.isEmpty());
+ }
+}
+
+TEST(String, MoveCtor)
+{
+ {
+ String u;
+ ASSERT_FALSE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_TRUE(u.isEmpty());
+ String v(std::move(u));
+ ASSERT_FALSE(v.isQt());
+ ASSERT_FALSE(v.isAlloc());
+ ASSERT_TRUE(v.isEmpty());
+ ASSERT_EQ(v.view(), "");
+ ASSERT_FALSE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_TRUE(u.isEmpty());
+ ASSERT_EQ(u.view(), "");
+ }
+ {
+ String u("");
+ ASSERT_FALSE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_TRUE(u.isEmpty());
+ String v(std::move(u));
+ ASSERT_FALSE(v.isQt());
+ ASSERT_FALSE(v.isAlloc());
+ ASSERT_TRUE(v.isEmpty());
+ ASSERT_EQ(v.view(), "");
+ ASSERT_FALSE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_TRUE(u.isEmpty());
+ ASSERT_EQ(u.view(), "");
+ }
+ {
+ String u("foo");
+ ASSERT_FALSE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_FALSE(u.isEmpty());
+ String v(std::move(u));
+ ASSERT_FALSE(v.isQt());
+ ASSERT_FALSE(v.isAlloc());
+ ASSERT_FALSE(v.isEmpty());
+ ASSERT_EQ(v.view(), "foo");
+ ASSERT_FALSE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_TRUE(u.isEmpty());
+ ASSERT_EQ(u.view(), "");
+ }
+ {
+ String u(LONGEST_SSO);
+ ASSERT_FALSE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_FALSE(u.isEmpty());
+ String v(std::move(u));
+ ASSERT_FALSE(v.isQt());
+ ASSERT_FALSE(v.isAlloc());
+ ASSERT_FALSE(v.isEmpty());
+ ASSERT_EQ(v.view(), LONGEST_SSO);
+ ASSERT_FALSE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_TRUE(u.isEmpty());
+ ASSERT_EQ(u.view(), "");
+ }
+ {
+ String u(TOO_LONG);
+ ASSERT_FALSE(u.isQt());
+ ASSERT_TRUE(u.isAlloc());
+ ASSERT_FALSE(u.isEmpty());
+ String v(std::move(u));
+ ASSERT_FALSE(v.isQt());
+ ASSERT_TRUE(v.isAlloc());
+ ASSERT_FALSE(v.isEmpty());
+ ASSERT_EQ(v.view(), TOO_LONG);
+ ASSERT_FALSE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_TRUE(u.isEmpty());
+ ASSERT_EQ(u.view(), "");
+ }
+ {
+ String u(LONGEST_SSO);
+ ASSERT_FALSE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_FALSE(u.isEmpty());
+ ASSERT_EQ(u.qt(), LONGEST_SSO);
+ ASSERT_TRUE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_FALSE(u.isEmpty());
+ String v(std::move(u));
+ ASSERT_TRUE(v.isQt());
+ ASSERT_FALSE(v.isAlloc());
+ ASSERT_FALSE(v.isEmpty());
+ ASSERT_EQ(v.view(), LONGEST_SSO);
+ ASSERT_FALSE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_TRUE(u.isEmpty());
+ ASSERT_EQ(u.view(), "");
+ }
+ {
+ String u(TOO_LONG);
+ ASSERT_FALSE(u.isQt());
+ ASSERT_TRUE(u.isAlloc());
+ ASSERT_FALSE(u.isEmpty());
+ ASSERT_EQ(u.qt(), TOO_LONG);
+ ASSERT_TRUE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_FALSE(u.isEmpty());
+ String v(std::move(u));
+ ASSERT_TRUE(v.isQt());
+ ASSERT_FALSE(v.isAlloc());
+ ASSERT_FALSE(v.isEmpty());
+ ASSERT_EQ(v.view(), TOO_LONG);
+ ASSERT_FALSE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_TRUE(u.isEmpty());
+ ASSERT_EQ(u.view(), "");
+ }
+ {
+ String u(REALLY_LONG);
+ ASSERT_FALSE(u.isQt());
+ ASSERT_TRUE(u.isAlloc());
+ ASSERT_FALSE(u.isEmpty());
+ ASSERT_EQ(u.qt(), REALLY_LONG);
+ ASSERT_TRUE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_FALSE(u.isEmpty());
+ String v(std::move(u));
+ ASSERT_TRUE(v.isQt());
+ ASSERT_FALSE(v.isAlloc());
+ ASSERT_FALSE(v.isEmpty());
+ ASSERT_EQ(v.view(), REALLY_LONG);
+ ASSERT_FALSE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_TRUE(u.isEmpty());
+ ASSERT_EQ(u.view(), "");
+ }
+}
+
+TEST(String, MoveAssign)
+{
+ {
+ String u;
+ ASSERT_FALSE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_TRUE(u.isEmpty());
+ String v;
+ ASSERT_FALSE(v.isQt());
+ ASSERT_FALSE(v.isAlloc());
+ ASSERT_TRUE(v.isEmpty());
+ v = std::move(u);
+ ASSERT_FALSE(v.isQt());
+ ASSERT_FALSE(v.isAlloc());
+ ASSERT_TRUE(v.isEmpty());
+ ASSERT_EQ(v.view(), "");
+ ASSERT_FALSE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_TRUE(u.isEmpty());
+ ASSERT_EQ(u.view(), "");
+ }
+ {
+ String u("");
+ ASSERT_FALSE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_TRUE(u.isEmpty());
+ String v;
+ ASSERT_FALSE(v.isQt());
+ ASSERT_FALSE(v.isAlloc());
+ ASSERT_TRUE(v.isEmpty());
+ v = std::move(u);
+ ASSERT_FALSE(v.isQt());
+ ASSERT_FALSE(v.isAlloc());
+ ASSERT_TRUE(v.isEmpty());
+ ASSERT_EQ(v.view(), "");
+ ASSERT_FALSE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_TRUE(u.isEmpty());
+ ASSERT_EQ(u.view(), "");
+ }
+ {
+ String u("foo");
+ ASSERT_FALSE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_FALSE(u.isEmpty());
+ String v;
+ ASSERT_FALSE(v.isQt());
+ ASSERT_FALSE(v.isAlloc());
+ ASSERT_TRUE(v.isEmpty());
+ v = std::move(u);
+ ASSERT_FALSE(v.isQt());
+ ASSERT_FALSE(v.isAlloc());
+ ASSERT_FALSE(v.isEmpty());
+ ASSERT_EQ(v.view(), "foo");
+ ASSERT_FALSE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_TRUE(u.isEmpty());
+ ASSERT_EQ(u.view(), "");
+ }
+ {
+ String u(LONGEST_SSO);
+ ASSERT_FALSE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_FALSE(u.isEmpty());
+ String v;
+ ASSERT_FALSE(v.isQt());
+ ASSERT_FALSE(v.isAlloc());
+ ASSERT_TRUE(v.isEmpty());
+ v = std::move(u);
+ ASSERT_FALSE(v.isQt());
+ ASSERT_FALSE(v.isAlloc());
+ ASSERT_FALSE(v.isEmpty());
+ ASSERT_EQ(v.view(), LONGEST_SSO);
+ ASSERT_FALSE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_TRUE(u.isEmpty());
+ ASSERT_EQ(u.view(), "");
+ }
+ {
+ String u(TOO_LONG);
+ ASSERT_FALSE(u.isQt());
+ ASSERT_TRUE(u.isAlloc());
+ ASSERT_FALSE(u.isEmpty());
+ String v;
+ ASSERT_FALSE(v.isQt());
+ ASSERT_FALSE(v.isAlloc());
+ ASSERT_TRUE(v.isEmpty());
+ v = std::move(u);
+ ASSERT_FALSE(v.isQt());
+ ASSERT_TRUE(v.isAlloc());
+ ASSERT_FALSE(v.isEmpty());
+ ASSERT_EQ(v.view(), TOO_LONG);
+ ASSERT_FALSE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_TRUE(u.isEmpty());
+ ASSERT_EQ(u.view(), "");
+ }
+ {
+ String u(LONGEST_SSO);
+ ASSERT_FALSE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_FALSE(u.isEmpty());
+ ASSERT_EQ(u.qt(), LONGEST_SSO);
+ ASSERT_TRUE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_FALSE(u.isEmpty());
+ String v;
+ ASSERT_FALSE(v.isQt());
+ ASSERT_FALSE(v.isAlloc());
+ ASSERT_TRUE(v.isEmpty());
+ v = std::move(u);
+ ASSERT_TRUE(v.isQt());
+ ASSERT_FALSE(v.isAlloc());
+ ASSERT_FALSE(v.isEmpty());
+ ASSERT_EQ(v.view(), LONGEST_SSO);
+ ASSERT_FALSE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_TRUE(u.isEmpty());
+ ASSERT_EQ(u.view(), "");
+ }
+ {
+ String u(TOO_LONG);
+ ASSERT_FALSE(u.isQt());
+ ASSERT_TRUE(u.isAlloc());
+ ASSERT_FALSE(u.isEmpty());
+ ASSERT_EQ(u.qt(), TOO_LONG);
+ ASSERT_TRUE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_FALSE(u.isEmpty());
+ String v;
+ ASSERT_FALSE(v.isQt());
+ ASSERT_FALSE(v.isAlloc());
+ ASSERT_TRUE(v.isEmpty());
+ v = std::move(u);
+ ASSERT_TRUE(v.isQt());
+ ASSERT_FALSE(v.isAlloc());
+ ASSERT_FALSE(v.isEmpty());
+ ASSERT_EQ(v.view(), TOO_LONG);
+ ASSERT_FALSE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_TRUE(u.isEmpty());
+ ASSERT_EQ(u.view(), "");
+ }
+ {
+ String u(REALLY_LONG);
+ ASSERT_FALSE(u.isQt());
+ ASSERT_TRUE(u.isAlloc());
+ ASSERT_FALSE(u.isEmpty());
+ ASSERT_EQ(u.qt(), REALLY_LONG);
+ ASSERT_TRUE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_FALSE(u.isEmpty());
+ String v;
+ ASSERT_FALSE(v.isQt());
+ ASSERT_FALSE(v.isAlloc());
+ ASSERT_TRUE(v.isEmpty());
+ v = std::move(u);
+ ASSERT_TRUE(v.isQt());
+ ASSERT_FALSE(v.isAlloc());
+ ASSERT_FALSE(v.isEmpty());
+ ASSERT_EQ(v.view(), REALLY_LONG);
+ ASSERT_FALSE(u.isQt());
+ ASSERT_FALSE(u.isAlloc());
+ ASSERT_TRUE(u.isEmpty());
+ ASSERT_EQ(u.view(), "");
+ }
+}
+
+TEST(String, QtLifetime)
+{
+ QString qt;
+ {
+ String s(REALLY_LONG);
+ qt = s.qt();
+ ASSERT_TRUE(!qt.isDetached()); // s holds the string too
+ }
+ ASSERT_TRUE(qt.isDetached());
+}