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

540 lines
34 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 "libsync/csync_exclude.h"
#include "testutils/syncenginetestutils.h"
#include <QtTest>
using namespace OCC;
class StatusPushSpy : public QSignalSpy
{
SyncEngine &_syncEngine;
public:
StatusPushSpy(SyncEngine &syncEngine)
: QSignalSpy(&syncEngine.syncFileStatusTracker(), &SyncFileStatusTracker::fileStatusChanged)
, _syncEngine(syncEngine)
{
}
SyncFileStatus statusOf(const QString &relativePath) const
{
const QFileInfo file(_syncEngine.localPath(), relativePath);
// Start from the end to get the latest status
for (auto it = crbegin(); it != crend(); ++it) {
const auto info = QFileInfo(it->at(0).toString());
if (info == file) {
return it->at(1).value<SyncFileStatus>();
}
}
return SyncFileStatus();
}
bool statusEmittedBefore(const QString &firstPath, const QString &secondPath) const {
QFileInfo firstFile(_syncEngine.localPath(), firstPath);
QFileInfo secondFile(_syncEngine.localPath(), secondPath);
// Start from the end to get the latest status
int i = size() - 1;
for (; i >= 0; --i) {
if (QFileInfo(at(i)[0].toString()) == secondFile)
break;
else if (QFileInfo(at(i)[0].toString()) == firstFile)
return false;
}
for (; i >= 0; --i) {
if (QFileInfo(at(i)[0].toString()) == firstFile)
return true;
}
return false;
}
};
//
// IMPORTANT: these tests are only run with VFS off, because the tests assume a very controlled order of status changes.
// When using some form of VFS, the event loop has to run in order to fulfill requests from the OS, and therefore things
// like stopping execution in `execUntilBeforePropagation()` are very hard to do.
//
class TestSyncFileStatusTracker : public QObject
{
Q_OBJECT
void verifyThatPushMatchesPull(const FakeFolder &fakeFolder, const StatusPushSpy &statusSpy) {
QString root = fakeFolder.localPath();
QDirIterator it(root, QDir::AllEntries | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
while (it.hasNext()) {
QString filePath = it.next().mid(root.size());
auto pushedStatus = statusSpy.statusOf(filePath);
if (pushedStatus != SyncFileStatus()) {
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(filePath), pushedStatus);
}
}
}
private Q_SLOTS:
void parentsGetSyncStatusUploadDownload() {
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
fakeFolder.localModifier().appendByte(QStringLiteral("B/b1"));
QVERIFY(fakeFolder.applyLocalModificationsWithoutSync());
fakeFolder.remoteModifier().appendByte(QStringLiteral("C/c1"));
StatusPushSpy statusSpy(fakeFolder.syncEngine());
fakeFolder.scheduleSync();
fakeFolder.execUntilBeforePropagation();
verifyThatPushMatchesPull(fakeFolder, statusSpy);
QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b1")), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QStringLiteral("C")), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QStringLiteral("C/c1")), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("B/b2")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("C/c2")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
statusSpy.clear();
fakeFolder.execUntilFinished();
verifyThatPushMatchesPull(fakeFolder, statusSpy);
QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b1")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("C")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("C/c1")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
}
void parentsGetSyncStatusNewFileUploadDownload() {
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
fakeFolder.localModifier().insert(QStringLiteral("B/b0"));
QVERIFY(fakeFolder.applyLocalModificationsWithoutSync());
fakeFolder.remoteModifier().insert(QStringLiteral("C/c0"));
StatusPushSpy statusSpy(fakeFolder.syncEngine());
fakeFolder.scheduleSync();
fakeFolder.execUntilBeforePropagation();
verifyThatPushMatchesPull(fakeFolder, statusSpy);
QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b0")), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QStringLiteral("C")), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QStringLiteral("C/c0")), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("B/b1")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("C/c1")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
statusSpy.clear();
fakeFolder.execUntilFinished();
verifyThatPushMatchesPull(fakeFolder, statusSpy);
QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b0")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("C")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("C/c0")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
}
void parentsGetSyncStatusNewDirDownload() {
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
fakeFolder.remoteModifier().mkdir(QStringLiteral("D"));
fakeFolder.remoteModifier().insert(QStringLiteral("D/d0"));
StatusPushSpy statusSpy(fakeFolder.syncEngine());
fakeFolder.scheduleSync();
fakeFolder.execUntilBeforePropagation();
verifyThatPushMatchesPull(fakeFolder, statusSpy);
QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QStringLiteral("D")), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QStringLiteral("D/d0")), SyncFileStatus(SyncFileStatus::StatusSync));
statusSpy.clear();
fakeFolder.execUntilItemCompleted(QStringLiteral("D"));
verifyThatPushMatchesPull(fakeFolder, statusSpy);
QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("D")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("D/d0")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
statusSpy.clear();
fakeFolder.execUntilFinished();
verifyThatPushMatchesPull(fakeFolder, statusSpy);
QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("D")), SyncFileStatus(SyncFileStatus::StatusNone));
QCOMPARE(statusSpy.statusOf(QStringLiteral("D/d0")), SyncFileStatus(SyncFileStatus::StatusNone));
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
}
void parentsGetSyncStatusNewDirUpload() {
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
fakeFolder.localModifier().mkdir(QStringLiteral("D"));
fakeFolder.localModifier().insert(QStringLiteral("D/d0"));
QVERIFY(fakeFolder.applyLocalModificationsWithoutSync());
StatusPushSpy statusSpy(fakeFolder.syncEngine());
fakeFolder.scheduleSync();
fakeFolder.execUntilBeforePropagation();
verifyThatPushMatchesPull(fakeFolder, statusSpy);
QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QStringLiteral("D")), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QStringLiteral("D/d0")), SyncFileStatus(SyncFileStatus::StatusSync));
statusSpy.clear();
fakeFolder.execUntilItemCompleted(QStringLiteral("D"));
verifyThatPushMatchesPull(fakeFolder, statusSpy);
QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("D")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("D/d0")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
statusSpy.clear();
fakeFolder.execUntilFinished();
verifyThatPushMatchesPull(fakeFolder, statusSpy);
QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("D")), SyncFileStatus(SyncFileStatus::StatusNone));
QCOMPARE(statusSpy.statusOf(QStringLiteral("D/d0")), SyncFileStatus(SyncFileStatus::StatusNone));
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
}
void parentsGetSyncStatusDeleteUpDown() {
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
fakeFolder.remoteModifier().remove(QStringLiteral("B/b1"));
fakeFolder.localModifier().remove(QStringLiteral("C/c1"));
QVERIFY(fakeFolder.applyLocalModificationsWithoutSync());
StatusPushSpy statusSpy(fakeFolder.syncEngine());
fakeFolder.scheduleSync();
fakeFolder.execUntilBeforePropagation();
verifyThatPushMatchesPull(fakeFolder, statusSpy);
QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusSync));
// Discovered as remotely removed, pending for local removal.
QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b1")), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QStringLiteral("C")), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("B/b2")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("C/c2")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
statusSpy.clear();
fakeFolder.execUntilFinished();
verifyThatPushMatchesPull(fakeFolder, statusSpy);
QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("C")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
}
void warningStatusForExcludedFile() {
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
fakeFolder.syncEngine().addManualExclude(QStringLiteral("A/a1"));
fakeFolder.syncEngine().addManualExclude(QStringLiteral("B"));
fakeFolder.localModifier().appendByte(QStringLiteral("A/a1"));
fakeFolder.localModifier().appendByte(QStringLiteral("B/b1"));
QVERIFY(fakeFolder.applyLocalModificationsWithoutSync());
StatusPushSpy statusSpy(fakeFolder.syncEngine());
fakeFolder.scheduleSync();
fakeFolder.execUntilBeforePropagation();
verifyThatPushMatchesPull(fakeFolder, statusSpy);
QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusExcluded));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusExcluded));
QEXPECT_FAIL("", "csync will stop at ignored directories without traversing children, so we don't currently push the status for newly ignored children of an ignored directory.", Continue);
QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b1")), SyncFileStatus(SyncFileStatus::StatusExcluded));
fakeFolder.execUntilFinished();
verifyThatPushMatchesPull(fakeFolder, statusSpy);
QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusExcluded));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusExcluded));
QEXPECT_FAIL("", "csync will stop at ignored directories without traversing children, so we don't currently push the status for newly ignored children of an ignored directory.", Continue);
QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b1")), SyncFileStatus(SyncFileStatus::StatusExcluded));
QEXPECT_FAIL("", "csync will stop at ignored directories without traversing children, so we don't currently push the status for newly ignored children of an ignored directory.", Continue);
QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b2")), SyncFileStatus(SyncFileStatus::StatusExcluded));
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QString()), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
statusSpy.clear();
// Clears the exclude expr above
fakeFolder.syncEngine().clearManualExcludes();
fakeFolder.scheduleSync();
fakeFolder.execUntilBeforePropagation();
QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b1")), SyncFileStatus(SyncFileStatus::StatusSync));
statusSpy.clear();
fakeFolder.execUntilFinished();
verifyThatPushMatchesPull(fakeFolder, statusSpy);
QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b1")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
}
void warningStatusForExcludedFile_CasePreserving() {
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
fakeFolder.syncEngine().addManualExclude(QStringLiteral("B"));
fakeFolder.serverErrorPaths().append(QStringLiteral("A/a1"));
fakeFolder.localModifier().appendByte(QStringLiteral("A/a1"));
QVERIFY(fakeFolder.applyLocalModificationsWithoutSync());
fakeFolder.syncOnce();
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QString()), SyncFileStatus(SyncFileStatus::StatusWarning));
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusWarning));
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusError));
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusExcluded));
// Should still get the status for different casing on macOS and Windows.
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("a")),
SyncFileStatus(Utility::fsCasePreserving() ? SyncFileStatus::StatusWarning : SyncFileStatus::StatusNone));
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A/A1")),
SyncFileStatus(Utility::fsCasePreserving() ? SyncFileStatus::StatusError : SyncFileStatus::StatusNone));
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("b")),
SyncFileStatus(Utility::fsCasePreserving() ? SyncFileStatus::StatusExcluded : SyncFileStatus::StatusNone));
}
void parentsGetWarningStatusForError() {
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
fakeFolder.serverErrorPaths().append(QStringLiteral("A/a1"));
fakeFolder.serverErrorPaths().append(QStringLiteral("B/b0"));
fakeFolder.localModifier().appendByte(QStringLiteral("A/a1"));
fakeFolder.localModifier().insert(QStringLiteral("B/b0"));
QVERIFY(fakeFolder.applyLocalModificationsWithoutSync());
StatusPushSpy statusSpy(fakeFolder.syncEngine());
fakeFolder.scheduleSync();
fakeFolder.execUntilBeforePropagation();
verifyThatPushMatchesPull(fakeFolder, statusSpy);
QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b0")), SyncFileStatus(SyncFileStatus::StatusSync));
statusSpy.clear();
fakeFolder.execUntilFinished();
verifyThatPushMatchesPull(fakeFolder, statusSpy);
QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusWarning));
QCOMPARE(statusSpy.statusOf(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusWarning));
QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusError));
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A/a2")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusWarning));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b0")), SyncFileStatus(SyncFileStatus::StatusError));
statusSpy.clear();
// Remove the error and start a second sync, the blacklist should kick in
fakeFolder.serverErrorPaths().clear();
fakeFolder.scheduleSync();
fakeFolder.execUntilBeforePropagation();
verifyThatPushMatchesPull(fakeFolder, statusSpy);
// A/a1 and B/b0 should be on the black list for the next few seconds
QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusWarning));
QCOMPARE(statusSpy.statusOf(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusWarning));
QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusError));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusWarning));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b0")), SyncFileStatus(SyncFileStatus::StatusError));
statusSpy.clear();
fakeFolder.execUntilFinished();
verifyThatPushMatchesPull(fakeFolder, statusSpy);
QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusWarning));
QCOMPARE(statusSpy.statusOf(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusWarning));
QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusError));
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A/a2")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusWarning));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b0")), SyncFileStatus(SyncFileStatus::StatusError));
statusSpy.clear();
// Start a third sync, this time together with a real file to sync
fakeFolder.localModifier().appendByte(QStringLiteral("C/c1"));
QVERIFY(fakeFolder.applyLocalModificationsWithoutSync());
fakeFolder.scheduleSync();
fakeFolder.execUntilBeforePropagation();
verifyThatPushMatchesPull(fakeFolder, statusSpy);
// The root should show SYNC even though there is an error underneath,
// since C/c1 is syncing and the SYNC status has priority.
QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusWarning));
QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusError));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusWarning));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b0")), SyncFileStatus(SyncFileStatus::StatusError));
QCOMPARE(statusSpy.statusOf(QStringLiteral("C")), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QStringLiteral("C/c1")), SyncFileStatus(SyncFileStatus::StatusSync));
statusSpy.clear();
fakeFolder.execUntilFinished();
verifyThatPushMatchesPull(fakeFolder, statusSpy);
QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusWarning));
QCOMPARE(statusSpy.statusOf(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusWarning));
QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusError));
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A/a2")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusWarning));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b0")), SyncFileStatus(SyncFileStatus::StatusError));
QCOMPARE(statusSpy.statusOf(QStringLiteral("C")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("C/c1")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
statusSpy.clear();
// Another sync after clearing the blacklist entry, everything should return to order.
fakeFolder.syncEngine().journal()->wipeErrorBlacklistEntry(QStringLiteral("A/a1"));
fakeFolder.syncEngine().journal()->wipeErrorBlacklistEntry(QStringLiteral("B/b0"));
fakeFolder.scheduleSync();
fakeFolder.execUntilBeforePropagation();
verifyThatPushMatchesPull(fakeFolder, statusSpy);
QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b0")), SyncFileStatus(SyncFileStatus::StatusSync));
statusSpy.clear();
fakeFolder.execUntilFinished();
verifyThatPushMatchesPull(fakeFolder, statusSpy);
QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b0")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
}
void parentsGetWarningStatusForError_SibblingStartsWithPath() {
// A is a parent of A/a1, but A/a is not even if it's a substring of A/a1
FakeFolder fakeFolder{{QString{},{
{QStringLiteral("A"), {
{QStringLiteral("a"), 4},
{QStringLiteral("a1"), 4}
}}}}};
fakeFolder.serverErrorPaths().append(QStringLiteral("A/a1"));
fakeFolder.localModifier().appendByte(QStringLiteral("A/a1"));
QVERIFY(fakeFolder.applyLocalModificationsWithoutSync());
fakeFolder.scheduleSync();
fakeFolder.execUntilBeforePropagation();
// The SyncFileStatusTraker won't push any status for all of them, test with a pull.
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QString()), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A/a")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
fakeFolder.execUntilFinished();
// We use string matching for paths in the implementation,
// an error should affect only parents and not every path that starts with the problem path.
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QString()), SyncFileStatus(SyncFileStatus::StatusWarning));
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusWarning));
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusError));
QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A/a")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
}
// Even for status pushes immediately following each other, macOS
// can sometimes have 1s delays between updates, so make sure that
// children are marked as OK before their parents do.
void childOKEmittedBeforeParent() {
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
fakeFolder.localModifier().appendByte(QStringLiteral("B/b1"));
QVERIFY(fakeFolder.applyLocalModificationsWithoutSync());
fakeFolder.remoteModifier().appendByte(QStringLiteral("C/c1"));
StatusPushSpy statusSpy(fakeFolder.syncEngine());
fakeFolder.syncOnce();
verifyThatPushMatchesPull(fakeFolder, statusSpy);
QVERIFY(statusSpy.statusEmittedBefore(QStringLiteral("B/b1"), QStringLiteral("B")));
QVERIFY(statusSpy.statusEmittedBefore(QStringLiteral("C/c1"), QStringLiteral("C")));
QVERIFY(statusSpy.statusEmittedBefore(QStringLiteral("B"), QString()));
QVERIFY(statusSpy.statusEmittedBefore(QStringLiteral("C"), QString()));
QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b1")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("C")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("C/c1")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
}
void sharedStatus() {
SyncFileStatus sharedUpToDateStatus(SyncFileStatus::StatusUpToDate);
sharedUpToDateStatus.setShared(true);
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
fakeFolder.remoteModifier().insert(QStringLiteral("S/s0"));
fakeFolder.remoteModifier().appendByte(QStringLiteral("S/s1"));
fakeFolder.remoteModifier().find(QStringLiteral("A/a1"))->isShared = true; // becomes shared
fakeFolder.remoteModifier().find(QStringLiteral("A"), true); // change the etags of the parent
StatusPushSpy statusSpy(fakeFolder.syncEngine());
fakeFolder.scheduleSync();
fakeFolder.execUntilBeforePropagation();
QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusSync));
// We don't care about the shared flag for the sync status,
// Mac and Windows won't show it and we can't know it for new files.
QCOMPARE(statusSpy.statusOf(QStringLiteral("S")).tag(), SyncFileStatus::StatusSync);
QCOMPARE(statusSpy.statusOf(QStringLiteral("S/s0")).tag(), SyncFileStatus::StatusSync);
QCOMPARE(statusSpy.statusOf(QStringLiteral("S/s1")).tag(), SyncFileStatus::StatusSync);
fakeFolder.execUntilFinished();
verifyThatPushMatchesPull(fakeFolder, statusSpy);
QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("S")), sharedUpToDateStatus);
QEXPECT_FAIL("", "We currently only know if a new file is shared on the second sync, after a PROPFIND.", Continue);
QCOMPARE(statusSpy.statusOf(QStringLiteral("S/s0")), sharedUpToDateStatus);
QCOMPARE(statusSpy.statusOf(QStringLiteral("S/s1")), sharedUpToDateStatus);
QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b1")).shared(), false);
QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), sharedUpToDateStatus);
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
}
void renameError() {
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
fakeFolder.serverErrorPaths().append(QStringLiteral("A/a1"));
fakeFolder.localModifier().rename(QStringLiteral("A/a1"), QStringLiteral("A/a1m"));
fakeFolder.localModifier().rename(QStringLiteral("B/b1"), QStringLiteral("B/b1m"));
QVERIFY(fakeFolder.applyLocalModificationsWithoutSync());
StatusPushSpy statusSpy(fakeFolder.syncEngine());
fakeFolder.scheduleSync();
fakeFolder.execUntilBeforePropagation();
verifyThatPushMatchesPull(fakeFolder, statusSpy);
QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1m")), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), statusSpy.statusOf(QStringLiteral("A/a1notexist")));
QCOMPARE(statusSpy.statusOf(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusSync));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b1m")), SyncFileStatus(SyncFileStatus::StatusSync));
fakeFolder.execUntilFinished();
verifyThatPushMatchesPull(fakeFolder, statusSpy);
QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1m")), SyncFileStatus(SyncFileStatus::StatusError));
QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), statusSpy.statusOf(QStringLiteral("A/a1notexist")));
QCOMPARE(statusSpy.statusOf(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusWarning));
QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusWarning));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b1m")), SyncFileStatus(SyncFileStatus::StatusUpToDate));
statusSpy.clear();
QVERIFY(!fakeFolder.syncOnce());
verifyThatPushMatchesPull(fakeFolder, statusSpy);
statusSpy.clear();
QVERIFY(!fakeFolder.syncOnce());
verifyThatPushMatchesPull(fakeFolder, statusSpy);
QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1m")), SyncFileStatus(SyncFileStatus::StatusError));
QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), statusSpy.statusOf(QStringLiteral("A/a1notexist")));
QCOMPARE(statusSpy.statusOf(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusWarning));
QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusWarning));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusNone));
QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b1m")), SyncFileStatus(SyncFileStatus::StatusNone));
statusSpy.clear();
}
};
QTEST_GUILESS_MAIN(TestSyncFileStatusTracker)
#include "testsyncfilestatustracker.moc"