Files
Курнат Андрей 2315f25754 Initial QSfera import
2026-06-07 10:20:04 +03:00

222 lines
8.6 KiB
C++

/*
* This software is in the public domain, furnished "as is", without technical
* support, and with no warranty, express or implied, as to its usefulness for
* any purpose.
*
*/
#include <QtTest>
#include "testutils/syncenginetestutils.h"
#include <syncengine.h>
using namespace OCC;
SyncJournalFileRecord journalRecord(FakeFolder &folder, const QAnyStringView &path)
{
return folder.syncJournal().getFileRecord(path.toString());
}
class TestBlacklist : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase_data()
{
QTest::addColumn<Vfs::Mode>("vfsMode");
QTest::addColumn<bool>("filesAreDehydrated");
QTest::newRow("Vfs::Mode::Off") << Vfs::Mode::Off << false;
if (VfsPluginManager::instance().isVfsPluginAvailable(Vfs::Mode::OpenVFS)) {
QTest::newRow("Vfs::Mode::Xattr dehydrdeated") << Vfs::Mode::OpenVFS << false;
QTest::newRow("Vfs::Mode::Xattr hydrated") << Vfs::Mode::OpenVFS << true;
}
if (VfsPluginManager::instance().isVfsPluginAvailable(Vfs::Mode::WindowsCfApi)) {
QTest::newRow("Vfs::Mode::WindowsCfApi dehydrated") << Vfs::Mode::WindowsCfApi << true;
// TODO: the hydrated version will fail due to an issue in the winvfs plugin, so leave it disabled for now.
// QTest::newRow("Vfs::Mode::WindowsCfApi hydrated") << Vfs::Mode::WindowsCfApi << false;
} else if (Utility::isWindows()) {
qWarning("Skipping Vfs::Mode::WindowsCfApi");
}
}
void testBlacklistBasic_data()
{
QTest::addColumn<bool>("remote");
QTest::newRow("remote") << true;
QTest::newRow("local") << false;
}
void testBlacklistBasic()
{
QFETCH_GLOBAL(Vfs::Mode, vfsMode);
QFETCH_GLOBAL(bool, filesAreDehydrated);
QFETCH(bool, remote);
FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated);
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
ItemCompletedSpy completeSpy(fakeFolder);
auto &modifier = remote ? fakeFolder.remoteModifier() : fakeFolder.localModifier();
int counter = 0;
const QString testFileName = QStringLiteral("A/new");
QByteArray reqId;
fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *) -> QNetworkReply * {
if (req.url().path().endsWith(testFileName)) {
reqId = req.rawHeader("X-Request-ID");
}
if (!remote && op == QNetworkAccessManager::PutOperation)
++counter;
if (remote && op == QNetworkAccessManager::GetOperation)
++counter;
return nullptr;
});
auto cleanup = [&]() {
completeSpy.clear();
};
auto initialEtag = journalRecord(fakeFolder, "A").etag();
QVERIFY(!initialEtag.isEmpty());
// The first sync and the download will fail - the item will be blacklisted
modifier.insert(testFileName);
fakeFolder.serverErrorPaths().append(testFileName, 500); // will be blacklisted
const bool syncResult = fakeFolder.applyLocalModificationsAndSync();
if (filesAreDehydrated && remote) {
// With dehydrated files, only a PROPFIND is done, but not a GET request.
// And it is the GET request that fails, and causes a blacklist entry, all "syncs" will succeed.
QVERIFY(syncResult);
QSKIP("The remainder of this test is not applicable for dehydrated files");
}
QVERIFY(!syncResult);
{
auto it = completeSpy.findItem(testFileName);
QVERIFY(it);
QCOMPARE(it->_status, SyncFileItem::NormalError); // initial error visible
QCOMPARE(it->instruction(), CSYNC_INSTRUCTION_NEW);
auto entry = fakeFolder.syncJournal().errorBlacklistEntry(testFileName);
QVERIFY(entry.isValid());
QCOMPARE(entry._errorCategory, SyncJournalErrorBlacklistRecord::Category::Normal);
QCOMPARE(entry._retryCount, 1);
QCOMPARE(counter, 1);
QVERIFY(entry._ignoreDuration > 0);
QCOMPARE(entry._requestId, reqId);
if (remote) {
QCOMPARE(journalRecord(fakeFolder, "A").etag(), initialEtag);
}
}
cleanup();
// Ignored during the second run - but soft errors are also errors
QVERIFY(!fakeFolder.applyLocalModificationsAndSync());
{
auto it = completeSpy.findItem(testFileName);
QVERIFY(it);
QCOMPARE(it->_status, SyncFileItem::BlacklistedError);
QCOMPARE(it->instruction(), CSYNC_INSTRUCTION_IGNORE); // no retry happened!
auto entry = fakeFolder.syncJournal().errorBlacklistEntry(testFileName);
QVERIFY(entry.isValid());
QCOMPARE(entry._errorCategory, SyncJournalErrorBlacklistRecord::Category::Normal);
QCOMPARE(entry._retryCount, 1);
QCOMPARE(counter, 1);
QVERIFY(entry._ignoreDuration > 0);
QCOMPARE(entry._requestId, reqId);
if (remote)
QCOMPARE(journalRecord(fakeFolder, "A").etag(), initialEtag);
}
cleanup();
// Let's expire the blacklist entry to verify it gets retried
{
auto entry = fakeFolder.syncJournal().errorBlacklistEntry(testFileName);
entry._ignoreDuration = 1;
entry._lastTryTime -= 1;
fakeFolder.syncJournal().setErrorBlacklistEntry(entry);
}
QVERIFY(!fakeFolder.applyLocalModificationsAndSync());
{
auto it = completeSpy.findItem(testFileName);
QVERIFY(it);
QCOMPARE(it->_status, SyncFileItem::BlacklistedError); // blacklisted as it's just a retry
QCOMPARE(it->instruction(), CSYNC_INSTRUCTION_NEW); // retry!
auto entry = fakeFolder.syncJournal().errorBlacklistEntry(testFileName);
QVERIFY(entry.isValid());
QCOMPARE(entry._errorCategory, SyncJournalErrorBlacklistRecord::Category::Normal);
QCOMPARE(entry._retryCount, 2);
QCOMPARE(counter, 2);
QVERIFY(entry._ignoreDuration > 0);
QCOMPARE(entry._requestId, reqId);
if (remote)
QCOMPARE(journalRecord(fakeFolder, "A").etag(), initialEtag);
}
cleanup();
// Try to make sure that the modifications below do are not within the same second as the
// previous sync attempt was done, otherwise the changes go undetected.
QThread::sleep(1);
// When the file changes a retry happens immediately
modifier.appendByte(testFileName);
QVERIFY(!fakeFolder.applyLocalModificationsAndSync());
{
auto it = completeSpy.findItem(testFileName);
QVERIFY(it);
QCOMPARE(it->_status, SyncFileItem::BlacklistedError);
QCOMPARE(it->instruction(), CSYNC_INSTRUCTION_NEW); // retry!
auto entry = fakeFolder.syncJournal().errorBlacklistEntry(testFileName);
QVERIFY(entry.isValid());
QCOMPARE(entry._errorCategory, SyncJournalErrorBlacklistRecord::Category::Normal);
QCOMPARE(entry._retryCount, 3);
QCOMPARE(counter, 3);
QVERIFY(entry._ignoreDuration > 0);
QCOMPARE(entry._requestId, reqId);
if (remote)
QCOMPARE(journalRecord(fakeFolder, "A").etag(), initialEtag);
}
cleanup();
// When the error goes away and the item is retried, the sync succeeds
fakeFolder.serverErrorPaths().clear();
{
auto entry = fakeFolder.syncJournal().errorBlacklistEntry(testFileName);
entry._ignoreDuration = 1;
entry._lastTryTime -= 1;
fakeFolder.syncJournal().setErrorBlacklistEntry(entry);
}
QVERIFY(fakeFolder.applyLocalModificationsAndSync());
{
auto it = completeSpy.findItem(testFileName);
QVERIFY(it);
QCOMPARE(it->_status, SyncFileItem::Success);
QCOMPARE(it->instruction(), CSYNC_INSTRUCTION_NEW);
auto entry = fakeFolder.syncJournal().errorBlacklistEntry(testFileName);
QVERIFY(!entry.isValid());
QCOMPARE(counter, 4);
if (remote)
QCOMPARE(journalRecord(fakeFolder, "A").etag(), fakeFolder.currentRemoteState().find(QStringLiteral("A"))->etag);
}
cleanup();
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
}
};
QTEST_GUILESS_MAIN(TestBlacklist)
#include "testblacklist.moc"