feat(eventsub): use custom String type (#5968)
This commit is contained in:
+1
-1
@@ -29,7 +29,7 @@
|
|||||||
- Bugfix: Fixed channel point redemptions with messages not showing up if PubSub is disconnected. (#5948)
|
- 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)
|
- 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: 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: Remove unneeded platform specifier for toasts. (#5914)
|
||||||
- Dev: Highlight checks now use non-capturing groups for the boundaries. (#5784)
|
- Dev: Highlight checks now use non-capturing groups for the boundaries. (#5784)
|
||||||
- Dev: Removed unused PubSub whisper code. (#5898)
|
- Dev: Removed unused PubSub whisper code. (#5898)
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
|
||||||
|
<Type Name="chatterino::eventsub::lib::String">
|
||||||
|
<Intrinsic Name="isQt" Expression="(flags & QT_BIT) != 0"></Intrinsic>
|
||||||
|
<Intrinsic Name="isAlloc" Expression="(flags & ALLOC_BIT) != 0"></Intrinsic>
|
||||||
|
<Intrinsic Name="isInPlace" Expression="!(flags & (ALLOC_BIT | QT_BIT))"></Intrinsic>
|
||||||
|
<Intrinsic Name="size" Expression="isQt() ? storage.qt.d.size : (flags & LENGTH_MASK)"></Intrinsic>
|
||||||
|
|
||||||
|
<DisplayString Condition="isInPlace()">{storage.inPlace,[size()]s8}</DisplayString>
|
||||||
|
<DisplayString Condition="isAlloc()">{storage.data,[size()]s8}</DisplayString>
|
||||||
|
<DisplayString Condition="isQt()">{storage.qt.d.ptr,su}</DisplayString>
|
||||||
|
|
||||||
|
<Expand>
|
||||||
|
<Synthetic Name="[type]">
|
||||||
|
<DisplayString Condition="isInPlace()">InPlace</DisplayString>
|
||||||
|
<DisplayString Condition="isAlloc()">Allocated</DisplayString>
|
||||||
|
<DisplayString Condition="isQt()">QString</DisplayString>
|
||||||
|
</Synthetic>
|
||||||
|
|
||||||
|
<!-- QString's visualizer includes the size already -->
|
||||||
|
<Item Name="[size]" Condition="!isQt()">size()</Item>
|
||||||
|
<ExpandedItem Condition="isInPlace() && size()">
|
||||||
|
storage.inPlace,[size()]
|
||||||
|
</ExpandedItem>
|
||||||
|
<ExpandedItem Condition="isAlloc()">
|
||||||
|
storage.data,[size()]
|
||||||
|
</ExpandedItem>
|
||||||
|
<ExpandedItem Condition="isQt()">
|
||||||
|
storage.qt
|
||||||
|
</ExpandedItem>
|
||||||
|
</Expand>
|
||||||
|
</Type>
|
||||||
|
</AutoVisualizer>
|
||||||
@@ -1,67 +1,227 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <boost/json.hpp>
|
#include <boost/json.hpp>
|
||||||
|
#include <QAnyStringView>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include <string>
|
#include <string_view>
|
||||||
#include <variant>
|
|
||||||
|
|
||||||
|
// NOLINTBEGIN(cppcoreguidelines-pro-type-union-access)
|
||||||
|
// NOLINTBEGIN(bugprone-undefined-memory-manipulation)
|
||||||
|
// NOLINTBEGIN(cppcoreguidelines-avoid-c-arrays)
|
||||||
namespace chatterino::eventsub::lib {
|
namespace chatterino::eventsub::lib {
|
||||||
|
|
||||||
template <class>
|
/// String is a struct that holds either an UTF-8 string or a QString
|
||||||
constexpr bool DEPENDENT_FALSE = false; // workaround before CWG2518/P2593R1
|
|
||||||
|
|
||||||
/// String is a struct that holds either an std::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
|
/// 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,
|
/// the `qt` function to convert the backing string to a QString,
|
||||||
/// while we ensure the conversion is only done once.
|
/// 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 {
|
struct String {
|
||||||
explicit String(std::string &&v)
|
constexpr String() noexcept = default;
|
||||||
: backingString(std::move(v))
|
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(const String &s) = delete;
|
||||||
String(String &&s) = default;
|
|
||||||
|
|
||||||
String &operator=(const String &) = delete;
|
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
|
/// Returns the string as a QString, modifying the backing string to ensure
|
||||||
/// the copy only happens once.
|
/// the copy only happens once.
|
||||||
QString qt() const
|
QString qt() const
|
||||||
{
|
{
|
||||||
return std::visit(
|
if (this->isEmpty())
|
||||||
[this](auto &&arg) -> QString {
|
{
|
||||||
using T = std::decay_t<std::remove_cvref_t<decltype(arg)>>;
|
return {};
|
||||||
if constexpr (std::is_same_v<T, std::string>)
|
}
|
||||||
{
|
|
||||||
auto qtString = QString::fromStdString(arg);
|
if (this->isQt())
|
||||||
this->backingString = qtString;
|
{
|
||||||
return qtString;
|
return this->storage.qt;
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_same_v<T, QString>)
|
|
||||||
{
|
// not a QString yet
|
||||||
return arg;
|
if (this->isAlloc())
|
||||||
}
|
{
|
||||||
else
|
auto s = QString::fromUtf8(
|
||||||
{
|
this->storage.data,
|
||||||
static_assert(DEPENDENT_FALSE<T>,
|
static_cast<qsizetype>(this->flags & LENGTH_MASK));
|
||||||
"unknown type in variant");
|
delete[] this->storage.data;
|
||||||
}
|
new (&this->storage.qt) QString(std::move(s));
|
||||||
},
|
this->flags &= ~ALLOC_BIT;
|
||||||
this->backingString);
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto s = QString::fromUtf8(
|
||||||
|
this->storage.inPlace,
|
||||||
|
static_cast<qsizetype>(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<qsizetype>(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:
|
private:
|
||||||
mutable std::variant<std::string, QString> 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<QString>::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<String, boost::json::value>::type tag_invoke(
|
boost::json::result_for<String, boost::json::value>::type tag_invoke(
|
||||||
boost::json::try_value_to_tag<String>, const boost::json::value &jvRoot);
|
boost::json::try_value_to_tag<String>, const boost::json::value &jvRoot);
|
||||||
|
|
||||||
} // namespace chatterino::eventsub::lib
|
} // namespace chatterino::eventsub::lib
|
||||||
|
// NOLINTEND(cppcoreguidelines-avoid-c-arrays)
|
||||||
|
// NOLINTEND(bugprone-undefined-memory-manipulation)
|
||||||
|
// NOLINTEND(cppcoreguidelines-pro-type-union-access)
|
||||||
|
|||||||
@@ -71,6 +71,11 @@ if (MSVC)
|
|||||||
# Use the function inlining level from 'Release' mode (2).
|
# Use the function inlining level from 'Release' mode (2).
|
||||||
string(REPLACE "/Ob1" "/Ob2" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
|
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
|
# Configure warnings
|
||||||
|
|
||||||
# Someone adds /W3 before we add /W4.
|
# Someone adds /W3 before we add /W4.
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
#include "twitch-eventsub-ws/string.hpp"
|
#include "twitch-eventsub-ws/string.hpp"
|
||||||
|
|
||||||
|
#include "twitch-eventsub-ws/errors.hpp"
|
||||||
|
|
||||||
#include <boost/json.hpp>
|
#include <boost/json.hpp>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace chatterino::eventsub::lib {
|
namespace chatterino::eventsub::lib {
|
||||||
|
|
||||||
boost::json::result_for<String, boost::json::value>::type tag_invoke(
|
boost::json::result_for<String, boost::json::value>::type tag_invoke(
|
||||||
@@ -14,16 +14,17 @@ boost::json::result_for<String, boost::json::value>::type tag_invoke(
|
|||||||
if (jvRoot.is_null())
|
if (jvRoot.is_null())
|
||||||
{
|
{
|
||||||
// We treat null "strings" as empty strings
|
// We treat null "strings" as empty strings
|
||||||
return String("");
|
return String();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto v = boost::json::try_value_to<std::string>(jvRoot);
|
if (!jvRoot.is_string())
|
||||||
if (v.has_error())
|
|
||||||
{
|
{
|
||||||
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
|
} // namespace chatterino::eventsub::lib
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ project(twitch-eventsub-ws-test)
|
|||||||
|
|
||||||
set(SOURCES
|
set(SOURCES
|
||||||
src/parse.cpp
|
src/parse.cpp
|
||||||
|
src/string.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(${PROJECT_NAME} ${SOURCES})
|
add_executable(${PROJECT_NAME} ${SOURCES})
|
||||||
|
|||||||
@@ -0,0 +1,439 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <QString>
|
||||||
|
#include <twitch-eventsub-ws/string.hpp>
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
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<String> &&
|
||||||
|
std::is_move_constructible_v<String> &&
|
||||||
|
!std::is_copy_assignable_v<String> &&
|
||||||
|
!std::is_copy_constructible_v<String> &&
|
||||||
|
!std::is_trivially_copyable_v<String> &&
|
||||||
|
!std::is_trivially_move_assignable_v<String> &&
|
||||||
|
!std::is_trivially_copy_assignable_v<String> &&
|
||||||
|
!std::is_trivially_move_constructible_v<String> &&
|
||||||
|
!std::is_trivially_copy_constructible_v<String>);
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user