Commit 5535d6e5 authored by Daniel Vrátil's avatar Daniel Vrátil 🤖

Generate Protocol from an XML specification (ABI break)

Instead of maintaining 12k lines of hand-written protocol code, we
specify the protocol in an XML and use a custom-written generator
that generates the code for us.

It's not only much easier to modify the protocol - we only need to
change a single thing in the XML instead of touching several places
of the implementation - but it's also much safer, as there's less
risk of accidentally introducing a bug in the code.

The major difference between the original hand-written code and the
generated code is that we no longer use QSharedDataPointer and virtual
methods in the Private classes, but instead all members are directly
in the command clas with most getters and setters inlined. This means
that copying commands is quite costly, so we pass them around as
QSharedPointers or const references. This should give us a tiny little
bit more performance.
parent 5da66a91
......@@ -24,8 +24,8 @@ macro(add_akonadi_isolated_test_advanced _source _additionalsources _linklibrari
add_executable( ${_name} ${_test} ${_additionalsources})
ecm_mark_as_test(${_name})
target_link_libraries(${_name}
Qt5::Test Qt5::Gui Qt5::Widgets Qt5::Network KF5::KIOCore KF5::AkonadiCore KF5::DBusAddons
${_linklibraries})
Qt5::Test Qt5::Gui Qt5::Widgets Qt5::Network KF5::KIOCore KF5::AkonadiCore
KF5::AkonadiPrivate KF5::DBusAddons ${_linklibraries})
if (NOT DEFINED _testrunner)
if (${PROJECT_NAME} STREQUAL Akonadi AND TARGET akonaditest)
......
......@@ -133,7 +133,7 @@ public:
virtual ~FakeNotificationConnection()
{}
void emitNotify(const Akonadi::Protocol::ChangeNotification &ntf)
void emitNotify(const Akonadi::Protocol::ChangeNotificationPtr &ntf)
{
Q_EMIT commandReceived(3, ntf);
}
......
......@@ -38,7 +38,7 @@ public:
{
}
bool emitNotification(const Akonadi::Protocol::ChangeNotification &msg) Q_DECL_OVERRIDE {
bool emitNotification(const Akonadi::Protocol::ChangeNotificationPtr &msg) Q_DECL_OVERRIDE {
// TODO: Check/Log
return Akonadi::ChangeRecorderPrivate::emitNotification(msg);
}
......@@ -55,11 +55,11 @@ public:
return qobject_cast<FakeNotificationConnection *>(d_ptr->ntfConnection);
}
QQueue<Akonadi::Protocol::ChangeNotification> pendingNotifications() const
QQueue<Akonadi::Protocol::ChangeNotificationPtr> pendingNotifications() const
{
return d_ptr->pendingNotifications;
}
QQueue<Akonadi::Protocol::ChangeNotification> pipeline() const
QQueue<Akonadi::Protocol::ChangeNotificationPtr> pipeline() const
{
return d_ptr->pipeline;
}
......
......@@ -38,7 +38,7 @@ public:
{
}
bool emitNotification(const Akonadi::Protocol::ChangeNotification &msg) Q_DECL_OVERRIDE {
bool emitNotification(const Akonadi::Protocol::ChangeNotificationPtr &msg) Q_DECL_OVERRIDE {
// TODO: Check/Log
return Akonadi::MonitorPrivate::emitNotification(msg);
}
......@@ -55,11 +55,11 @@ public:
return qobject_cast<FakeNotificationConnection *>(d_ptr->ntfConnection);
}
QQueue<Akonadi::Protocol::ChangeNotification> pendingNotifications() const
QQueue<Akonadi::Protocol::ChangeNotificationPtr> pendingNotifications() const
{
return d_ptr->pendingNotifications;
}
QQueue<Akonadi::Protocol::ChangeNotification> pipeline() const
QQueue<Akonadi::Protocol::ChangeNotificationPtr> pipeline() const
{
return d_ptr->pipeline;
}
......
......@@ -349,6 +349,7 @@ void ItemAppendTest::testItemMerge()
QCOMPARE(merge->item().remoteRevision(), mergedItem.remoteRevision());
QCOMPARE(merge->item().payloadData(), mergedItem.payloadData());
QCOMPARE(merge->item().size(), mergedItem.size());
qDebug() << merge->item().flags() << mergedItem.flags();
QCOMPARE(merge->item().flags(), mergedItem.flags());
}
......
......@@ -99,15 +99,15 @@ void MonitorNotificationTest::testSingleMessage_impl(MonitorImpl *monitor, FakeC
monitor->setSession(m_fakeSession);
monitor->fetchCollection(true);
Protocol::ChangeNotification::List list;
Protocol::ChangeNotificationList list;
Collection parent(1);
Collection added(2);
Protocol::CollectionChangeNotification msg;
msg.setParentCollection(parent.id());
msg.setOperation(Protocol::CollectionChangeNotification::Add);
msg.setId(added.id());
auto msg = Protocol::CollectionChangeNotificationPtr::create();
msg->setParentCollection(parent.id());
msg->setOperation(Protocol::CollectionChangeNotification::Add);
msg->setId(added.id());
QHash<Collection::Id, Collection> data;
data.insert(parent.id(), parent);
......@@ -163,7 +163,7 @@ void MonitorNotificationTest::testFillPipeline_impl(MonitorImpl *monitor, FakeCo
monitor->setSession(m_fakeSession);
monitor->fetchCollection(true);
Protocol::ChangeNotification::List list;
Protocol::ChangeNotificationList list;
QHash<Collection::Id, Collection> data;
int i = 1;
......@@ -171,10 +171,10 @@ void MonitorNotificationTest::testFillPipeline_impl(MonitorImpl *monitor, FakeCo
Collection parent(i++);
Collection added(i++);
Protocol::CollectionChangeNotification msg;
msg.setParentCollection(parent.id());
msg.setOperation(Protocol::CollectionChangeNotification::Add);
msg.setId(added.id());
auto msg = Protocol::CollectionChangeNotificationPtr::create();
msg->setParentCollection(parent.id());
msg->setOperation(Protocol::CollectionChangeNotification::Add);
msg->setId(added.id());
data.insert(parent.id(), parent);
data.insert(added.id(), added);
......@@ -185,7 +185,7 @@ void MonitorNotificationTest::testFillPipeline_impl(MonitorImpl *monitor, FakeCo
QVERIFY(monitor->pipeline().isEmpty());
QVERIFY(monitor->pendingNotifications().isEmpty());
Q_FOREACH (const Protocol::ChangeNotification &ntf, list) {
Q_FOREACH (const Protocol::ChangeNotificationPtr &ntf, list) {
monitor->notificationConnection()->emitNotify(ntf);
}
......@@ -232,7 +232,7 @@ void MonitorNotificationTest::testMonitor_impl(MonitorImpl *monitor, FakeCollect
monitor->setSession(m_fakeSession);
monitor->fetchCollection(true);
Protocol::ChangeNotification::List list;
Protocol::ChangeNotificationList list;
Collection col2(2);
col2.setParentCollection(Collection::root());
......@@ -244,10 +244,10 @@ void MonitorNotificationTest::testMonitor_impl(MonitorImpl *monitor, FakeCollect
while (i < 8) {
Collection added(i++);
Protocol::CollectionChangeNotification msg;
msg.setParentCollection(i % 2 ? 2 : added.id() - 1);
msg.setOperation(Protocol::CollectionChangeNotification::Add);
msg.setId(added.id());
auto msg = Protocol::CollectionChangeNotificationPtr::create();
msg->setParentCollection(i % 2 ? 2 : added.id() - 1);
msg->setOperation(Protocol::CollectionChangeNotification::Add);
msg->setId(added.id());
list << msg;
}
......@@ -271,7 +271,7 @@ void MonitorNotificationTest::testMonitor_impl(MonitorImpl *monitor, FakeCollect
QVERIFY(monitor->pipeline().isEmpty());
QVERIFY(monitor->pendingNotifications().isEmpty());
Q_FOREACH (const Protocol::ChangeNotification &ntf, list) {
Q_FOREACH (const Protocol::ChangeNotificationPtr &ntf, list) {
monitor->notificationConnection()->emitNotify(ntf);
}
......
......@@ -24,9 +24,7 @@ using namespace Akonadi;
Q_DECLARE_METATYPE(Scope)
Q_DECLARE_METATYPE(QVector<Protocol::Ancestor>)
Q_DECLARE_METATYPE(Protocol::FetchCollectionsResponse)
Q_DECLARE_METATYPE(Protocol::FetchScope)
Q_DECLARE_METATYPE(Protocol::FetchTagsResponse)
class ProtocolHelperTest : public QObject
{
......@@ -257,7 +255,7 @@ private Q_SLOTS:
Protocol::FetchScope::RemoteRevision |
Protocol::FetchScope::MTime |
Protocol::FetchScope::IgnoreErrors);
fs.setAncestorDepth(Akonadi::Protocol::Ancestor::AllAncestors);
fs.setAncestorDepth(Protocol::FetchScope::AllAncestors);
QTest::newRow("full") << scope << fs;
}
......
......@@ -30,64 +30,73 @@ using namespace Akonadi::Protocol;
void NotificationMessageTest::testCompress()
{
ChangeNotification::List list;
ChangeNotificationList list;
CollectionChangeNotification msg;
msg.setOperation(CollectionChangeNotification::Add);
CollectionChangeNotification::appendAndCompress(list, msg);
QVERIFY(CollectionChangeNotification::appendAndCompress(
list, CollectionChangeNotificationPtr::create(msg)));
QCOMPARE(list.count(), 1);
msg.setOperation(CollectionChangeNotification::Modify);
CollectionChangeNotification::appendAndCompress(list, msg);
QVERIFY(!CollectionChangeNotification::appendAndCompress(
list, CollectionChangeNotificationPtr::create(msg)));
QCOMPARE(list.count(), 1);
QCOMPARE(static_cast<CollectionChangeNotification&>(list.first()).operation(), CollectionChangeNotification::Add);
QCOMPARE(list.first().staticCast<CollectionChangeNotification>()->operation(), CollectionChangeNotification::Add);
msg.setOperation(CollectionChangeNotification::Remove);
CollectionChangeNotification::appendAndCompress(list, msg);
QVERIFY(CollectionChangeNotification::appendAndCompress(
list, CollectionChangeNotificationPtr::create(msg)));
QCOMPARE(list.count(), 2);
}
void NotificationMessageTest::testCompress2()
{
ChangeNotification::List list;
ChangeNotificationList list;
CollectionChangeNotification msg;
msg.setOperation(CollectionChangeNotification::Modify);
CollectionChangeNotification::appendAndCompress(list, msg);
QVERIFY(CollectionChangeNotification::appendAndCompress(
list, CollectionChangeNotificationPtr::create(msg)));
QCOMPARE(list.count(), 1);
msg.setOperation(CollectionChangeNotification::Remove);
CollectionChangeNotification::appendAndCompress(list, msg);
QVERIFY(CollectionChangeNotification::appendAndCompress(
list, CollectionChangeNotificationPtr::create(msg)));
QCOMPARE(list.count(), 2);
QCOMPARE(static_cast<CollectionChangeNotification&>(list.first()).operation(), CollectionChangeNotification::Modify);
QCOMPARE(static_cast<CollectionChangeNotification&>(list.last()).operation(), CollectionChangeNotification::Remove);
QCOMPARE(list.first().staticCast<CollectionChangeNotification>()->operation(), CollectionChangeNotification::Modify);
QCOMPARE(list.last().staticCast<CollectionChangeNotification>()->operation(), CollectionChangeNotification::Remove);
}
void NotificationMessageTest::testCompress3()
{
ChangeNotification::List list;
ChangeNotificationList list;
CollectionChangeNotification msg;
msg.setOperation(CollectionChangeNotification::Modify);
CollectionChangeNotification::appendAndCompress(list, msg);
QVERIFY(CollectionChangeNotification::appendAndCompress(
list, CollectionChangeNotificationPtr::create(msg)));
QCOMPARE(list.count(), 1);
CollectionChangeNotification::appendAndCompress(list, msg);
QVERIFY(!CollectionChangeNotification::appendAndCompress(
list, CollectionChangeNotificationPtr::create(msg)));
QCOMPARE(list.count(), 1);
}
void NotificationMessageTest::testPartModificationMerge()
{
ChangeNotification::List list;
ChangeNotificationList list;
CollectionChangeNotification msg;
msg.setOperation(CollectionChangeNotification::Modify);
msg.setChangedParts(QSet<QByteArray>() << "PART1");
CollectionChangeNotification::appendAndCompress(list, msg);
QVERIFY(CollectionChangeNotification::appendAndCompress(
list, CollectionChangeNotificationPtr::create(msg)));
QCOMPARE(list.count(), 1);
msg.setChangedParts(QSet<QByteArray>() << "PART2");
CollectionChangeNotification::appendAndCompress(list, msg);
QVERIFY(!CollectionChangeNotification::appendAndCompress(
list, CollectionChangeNotificationPtr::create(msg)));
QCOMPARE(list.count(), 1);
QCOMPARE(static_cast<CollectionChangeNotification&>(list.first()).changedParts(), (QSet<QByteArray>() << "PART1" << "PART2"));
QCOMPARE(list.first().staticCast<CollectionChangeNotification>()->changedParts(), (QSet<QByteArray>() << "PART1" << "PART2"));
}
......@@ -128,17 +128,17 @@ void ProtocolTest::testFactory()
QFETCH(bool, response);
QFETCH(bool, success);
Command result;
CommandPtr result;
if (response) {
result = Factory::response(type);
} else {
result = Factory::command(type);
}
QCOMPARE(result.isValid(), success);
QCOMPARE(result.isResponse(), response);
QCOMPARE(result->isValid(), success);
QCOMPARE(result->isResponse(), response);
if (success) {
QCOMPARE(result.type(), type);
QCOMPARE(result->type(), type);
}
}
......@@ -147,15 +147,15 @@ void ProtocolTest::testFactory()
void ProtocolTest::testCommand()
{
// There is no way to construct a valid Command directly
Command cmd;
QCOMPARE(cmd.type(), Command::Invalid);
QVERIFY(!cmd.isValid());
QVERIFY(!cmd.isResponse());
Command cmdTest = serializeAndDeserialize(cmd);
QCOMPARE(cmdTest.type(), Command::Invalid);
QVERIFY(!cmd.isValid());
QVERIFY(!cmd.isResponse());
auto cmd = CommandPtr::create();
QCOMPARE(cmd->type(), Command::Invalid);
QVERIFY(!cmd->isValid());
QVERIFY(!cmd->isResponse());
CommandPtr cmdTest = serializeAndDeserialize(cmd);
QCOMPARE(cmdTest->type(), Command::Invalid);
QVERIFY(!cmd->isValid());
QVERIFY(!cmd->isResponse());
}
void ProtocolTest::testResponse_data()
......@@ -179,15 +179,15 @@ void ProtocolTest::testResponse()
response.setError(errorCode, errorString);
}
const Response res = serializeAndDeserialize(response);
QCOMPARE(res.type(), Command::Invalid);
QVERIFY(!res.isValid());
QVERIFY(res.isResponse());
QCOMPARE(res.isError(), isError);
QCOMPARE(res.errorCode(), errorCode);
QCOMPARE(res.errorMessage(), errorString);
QVERIFY(res == response);
const bool notEquals = (res != response);
const auto res = serializeAndDeserialize(ResponsePtr::create(response));
QCOMPARE(res->type(), Command::Invalid);
QVERIFY(!res->isValid());
QVERIFY(res->isResponse());
QCOMPARE(res->isError(), isError);
QCOMPARE(res->errorCode(), errorCode);
QCOMPARE(res->errorMessage(), errorString);
QVERIFY(*res == response);
const bool notEquals = (*res != response);
QVERIFY(!notEquals);
}
......@@ -250,7 +250,7 @@ void ProtocolTest::testFetchScope()
in.setRequestedParts(requestedParts);
in.setChangedSince(QDateTime(QDate(2015, 8, 10), QTime(23, 52, 20), Qt::UTC));
in.setTagFetchScope({ "TAGID" });
in.setAncestorDepth(Ancestor::AllAncestors);
in.setAncestorDepth(FetchScope::AllAncestors);
in.setFetch(FetchScope::CacheOnly);
in.setFetch(FetchScope::CheckCachedPayloadPartsOnly);
in.setFetch(FetchScope::FullPayload, fullPayload);
......@@ -271,7 +271,7 @@ void ProtocolTest::testFetchScope()
QCOMPARE(out.requestedPayloads(), expectedPayloads);
QCOMPARE(out.changedSince(), QDateTime(QDate(2015, 8, 10), QTime(23, 52, 20), Qt::UTC));
QCOMPARE(out.tagFetchScope(), QSet<QByteArray>{ "TAGID" });
QCOMPARE(out.ancestorDepth(), Ancestor::AllAncestors);
QCOMPARE(out.ancestorDepth(), FetchScope::AllAncestors);
QCOMPARE(out.fetch(FetchScope::None), false);
QCOMPARE(out.cacheOnly(), true);
QCOMPARE(out.checkCachedPayloadPartsOnly(), true);
......@@ -474,17 +474,17 @@ void ProtocolTest::testHelloResponse()
in.setProtocolVersion(42);
in.setError(10, QStringLiteral("Ooops"));
const HelloResponse out = serializeAndDeserialize(in);
QVERIFY(out.isValid());
QVERIFY(out.isResponse());
QVERIFY(out.isError());
QCOMPARE(out.errorCode(), 10);
QCOMPARE(out.errorMessage(), QStringLiteral("Ooops"));
QCOMPARE(out.serverName(), QStringLiteral("AkonadiTest"));
QCOMPARE(out.message(), QStringLiteral("Oh, hello there!"));
QCOMPARE(out.protocolVersion(), 42);
QCOMPARE(out, in);
const bool notEquals = (out != in);
const auto out = serializeAndDeserialize(HelloResponsePtr::create(in));
QVERIFY(out->isValid());
QVERIFY(out->isResponse());
QVERIFY(out->isError());
QCOMPARE(out->errorCode(), 10);
QCOMPARE(out->errorMessage(), QStringLiteral("Ooops"));
QCOMPARE(out->serverName(), QStringLiteral("AkonadiTest"));
QCOMPARE(out->message(), QStringLiteral("Oh, hello there!"));
QCOMPARE(out->protocolVersion(), 42);
QCOMPARE(*out, in);
const bool notEquals = (*out != in);
QVERIFY(!notEquals);
}
......@@ -495,12 +495,12 @@ void ProtocolTest::testLoginCommand()
QVERIFY(in.isValid());
in.setSessionId("MySession-123-notifications");
const LoginCommand out = serializeAndDeserialize(in);
QVERIFY(out.isValid());
QVERIFY(!out.isResponse());
QCOMPARE(out.sessionId(), QByteArray("MySession-123-notifications"));
QCOMPARE(out, in);
const bool notEquals = (out != in);
const auto out = serializeAndDeserialize(LoginCommandPtr::create(in));
QVERIFY(out->isValid());
QVERIFY(!out->isResponse());
QCOMPARE(out->sessionId(), QByteArray("MySession-123-notifications"));
QCOMPARE(*out, in);
const bool notEquals = (*out != in);
QVERIFY(!notEquals);
}
......@@ -512,14 +512,14 @@ void ProtocolTest::testLoginResponse()
QVERIFY(!in.isError());
in.setError(42, QStringLiteral("Ooops"));
const LoginResponse out = serializeAndDeserialize(in);
QVERIFY(out.isValid());
QVERIFY(out.isResponse());
QVERIFY(out.isError());
QCOMPARE(out.errorCode(), 42);
QCOMPARE(out.errorMessage(), QStringLiteral("Ooops"));
QCOMPARE(out, in);
const bool notEquals = (out != in);
const auto out = serializeAndDeserialize(LoginResponsePtr::create(in));
QVERIFY(out->isValid());
QVERIFY(out->isResponse());
QVERIFY(out->isError());
QCOMPARE(out->errorCode(), 42);
QCOMPARE(out->errorMessage(), QStringLiteral("Ooops"));
QCOMPARE(*out, in);
const bool notEquals = (*out != in);
QVERIFY(!notEquals);
}
......@@ -529,11 +529,11 @@ void ProtocolTest::testLogoutCommand()
QVERIFY(!in.isResponse());
QVERIFY(in.isValid());
const LogoutCommand out = serializeAndDeserialize(in);
QVERIFY(!out.isResponse());
QVERIFY(out.isValid());
QCOMPARE(out, in);
const bool notEquals = (out != in);
const auto out = serializeAndDeserialize(LogoutCommandPtr::create(in));
QVERIFY(!out->isResponse());
QVERIFY(out->isValid());
QCOMPARE(*out, in);
const bool notEquals = (*out != in);
QVERIFY(!notEquals);
}
......@@ -545,14 +545,14 @@ void ProtocolTest::testLogoutResponse()
QVERIFY(!in.isError());
in.setError(42, QStringLiteral("Ooops"));
const LogoutResponse out = serializeAndDeserialize(in);
QVERIFY(out.isValid());
QVERIFY(out.isResponse());
QVERIFY(out.isError());
QCOMPARE(out.errorCode(), 42);
QCOMPARE(out.errorMessage(), QStringLiteral("Ooops"));
QCOMPARE(out, in);
const bool notEquals = (out != in);
const auto out = serializeAndDeserialize(LogoutResponsePtr::create(in));
QVERIFY(out->isValid());
QVERIFY(out->isResponse());
QVERIFY(out->isError());
QCOMPARE(out->errorCode(), 42);
QCOMPARE(out->errorMessage(), QStringLiteral("Ooops"));
QCOMPARE(*out, in);
const bool notEquals = (*out != in);
QVERIFY(!notEquals);
}
......@@ -564,12 +564,12 @@ void ProtocolTest::testTransactionCommand()
QVERIFY(in.isValid());
in.setMode(TransactionCommand::Begin);
const TransactionCommand out = serializeAndDeserialize(in);
QVERIFY(out.isValid());
QVERIFY(!out.isResponse());
QCOMPARE(out.mode(), TransactionCommand::Begin);
QCOMPARE(out, in);
const bool notEquals = (out != in);
const auto out = serializeAndDeserialize(TransactionCommandPtr::create(in));
QVERIFY(out->isValid());
QVERIFY(!out->isResponse());
QCOMPARE(out->mode(), TransactionCommand::Begin);
QCOMPARE(*out, in);
const bool notEquals = (*out != in);
QVERIFY(!notEquals);
}
......@@ -581,14 +581,14 @@ void ProtocolTest::testTransactionResponse()
QVERIFY(!in.isError());
in.setError(42, QStringLiteral("Ooops"));
const TransactionResponse out = serializeAndDeserialize(in);
QVERIFY(out.isValid());
QVERIFY(out.isResponse());
QVERIFY(out.isError());
QCOMPARE(out.errorCode(), 42);
QCOMPARE(out.errorMessage(), QStringLiteral("Ooops"));
QCOMPARE(out, in);
const bool notEquals = (out != in);
const auto out = serializeAndDeserialize(TransactionResponsePtr::create(in));
QVERIFY(out->isValid());
QVERIFY(out->isResponse());
QVERIFY(out->isError());
QCOMPARE(out->errorCode(), 42);
QCOMPARE(out->errorMessage(), QStringLiteral("Ooops"));
QCOMPARE(*out, in);
const bool notEquals = (*out != in);
QVERIFY(!notEquals);
}
......@@ -607,7 +607,7 @@ void ProtocolTest::testCreateItemCommand()
in.setCollection(Scope(1));
in.setItemSize(100);
in.setMimeType(QStringLiteral("text/directory"));
in.setGID(QStringLiteral("GID"));
in.setGid(QStringLiteral("GID"));
in.setRemoteId(QStringLiteral("RID"));
in.setRemoteRevision(QStringLiteral("RREV"));
in.setDateTime(QDateTime(QDate(2015, 8, 11), QTime(14, 32, 21), Qt::UTC));
......@@ -621,28 +621,28 @@ void ProtocolTest::testCreateItemCommand()
in.setAttributes(attrs);
in.setParts(parts);
const CreateItemCommand out = serializeAndDeserialize(in);
QVERIFY(out.isValid());
QVERIFY(!out.isResponse());
QCOMPARE(out.mergeModes(), CreateItemCommand::GID | CreateItemCommand::RemoteID);
QCOMPARE(out.collection(), Scope(1));
QCOMPARE(out.itemSize(), 100);
QCOMPARE(out.mimeType(), QStringLiteral("text/directory"));
QCOMPARE(out.gid(), QStringLiteral("GID"));
QCOMPARE(out.remoteId(), QStringLiteral("RID"));
QCOMPARE(out.remoteRevision(), QStringLiteral("RREV"));
QCOMPARE(out.dateTime(), QDateTime(QDate(2015, 8, 11), QTime(14, 32, 21), Qt::UTC));
QCOMPARE(out.flags(), QSet<QByteArray>() << "\\SEEN" << "FLAG");
QCOMPARE(out.flagsOverwritten(), true);
QCOMPARE(out.addedFlags(), QSet<QByteArray>{ "FLAG2" });
QCOMPARE(out.removedFlags(), QSet<QByteArray>{ "FLAG3" });
QCOMPARE(out.tags(), Scope(2));
QCOMPARE(out.addedTags(), addedTags);
QCOMPARE(out.removedTags(), removedTags);
QCOMPARE(out.attributes(), attrs);
QCOMPARE(out.parts(), parts);
QCOMPARE(out, in);