222 lines
8.6 KiB
C++
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"
|