mirror of
https://github.com/nextcloud/desktop.git
synced 2025-10-26 11:17:43 +00:00
Comparison of file sizes for potential conflicts was added in
0eb9401c62, but did not extend to checking
the file size in case of potential local moves.
This commit adds this check and adds tests for various move+change
scenarios.
620 lines
27 KiB
C++
620 lines
27 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 "syncenginetestutils.h"
|
|
#include <syncengine.h>
|
|
|
|
using namespace OCC;
|
|
|
|
bool itemDidComplete(const QSignalSpy &spy, const QString &path)
|
|
{
|
|
for(const QList<QVariant> &args : spy) {
|
|
auto item = args[0].value<SyncFileItemPtr>();
|
|
if (item->destination() == path)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool itemDidCompleteSuccessfully(const QSignalSpy &spy, const QString &path)
|
|
{
|
|
for(const QList<QVariant> &args : spy) {
|
|
auto item = args[0].value<SyncFileItemPtr>();
|
|
if (item->destination() == path)
|
|
return item->_status == SyncFileItem::Success;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
class TestSyncEngine : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
private slots:
|
|
void testFileDownload() {
|
|
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
|
|
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
|
|
fakeFolder.remoteModifier().insert("A/a0");
|
|
fakeFolder.syncOnce();
|
|
QVERIFY(itemDidCompleteSuccessfully(completeSpy, "A/a0"));
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
}
|
|
|
|
void testFileUpload() {
|
|
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
|
|
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
|
|
fakeFolder.localModifier().insert("A/a0");
|
|
fakeFolder.syncOnce();
|
|
QVERIFY(itemDidCompleteSuccessfully(completeSpy, "A/a0"));
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
}
|
|
|
|
void testDirDownload() {
|
|
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
|
|
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
|
|
fakeFolder.remoteModifier().mkdir("Y");
|
|
fakeFolder.remoteModifier().mkdir("Z");
|
|
fakeFolder.remoteModifier().insert("Z/d0");
|
|
fakeFolder.syncOnce();
|
|
QVERIFY(itemDidCompleteSuccessfully(completeSpy, "Y"));
|
|
QVERIFY(itemDidCompleteSuccessfully(completeSpy, "Z"));
|
|
QVERIFY(itemDidCompleteSuccessfully(completeSpy, "Z/d0"));
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
}
|
|
|
|
void testDirUpload() {
|
|
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
|
|
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
|
|
fakeFolder.localModifier().mkdir("Y");
|
|
fakeFolder.localModifier().mkdir("Z");
|
|
fakeFolder.localModifier().insert("Z/d0");
|
|
fakeFolder.syncOnce();
|
|
QVERIFY(itemDidCompleteSuccessfully(completeSpy, "Y"));
|
|
QVERIFY(itemDidCompleteSuccessfully(completeSpy, "Z"));
|
|
QVERIFY(itemDidCompleteSuccessfully(completeSpy, "Z/d0"));
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
}
|
|
|
|
void testLocalDelete() {
|
|
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
|
|
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
|
|
fakeFolder.remoteModifier().remove("A/a1");
|
|
fakeFolder.syncOnce();
|
|
QVERIFY(itemDidCompleteSuccessfully(completeSpy, "A/a1"));
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
}
|
|
|
|
void testRemoteDelete() {
|
|
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
|
|
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
|
|
fakeFolder.localModifier().remove("A/a1");
|
|
fakeFolder.syncOnce();
|
|
QVERIFY(itemDidCompleteSuccessfully(completeSpy, "A/a1"));
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
}
|
|
|
|
void testEmlLocalChecksum() {
|
|
FakeFolder fakeFolder{FileInfo{}};
|
|
fakeFolder.localModifier().insert("a1.eml", 64, 'A');
|
|
fakeFolder.localModifier().insert("a2.eml", 64, 'A');
|
|
fakeFolder.localModifier().insert("a3.eml", 64, 'A');
|
|
fakeFolder.localModifier().insert("b3.txt", 64, 'A');
|
|
// Upload and calculate the checksums
|
|
// fakeFolder.syncOnce();
|
|
fakeFolder.syncOnce();
|
|
|
|
auto getDbChecksum = [&](QString path) {
|
|
SyncJournalFileRecord record;
|
|
fakeFolder.syncJournal().getFileRecord(path, &record);
|
|
return record._checksumHeader;
|
|
};
|
|
|
|
// printf 'A%.0s' {1..64} | sha1sum -
|
|
QByteArray referenceChecksum("SHA1:30b86e44e6001403827a62c58b08893e77cf121f");
|
|
QCOMPARE(getDbChecksum("a1.eml"), referenceChecksum);
|
|
QCOMPARE(getDbChecksum("a2.eml"), referenceChecksum);
|
|
QCOMPARE(getDbChecksum("a3.eml"), referenceChecksum);
|
|
QCOMPARE(getDbChecksum("b3.txt"), referenceChecksum);
|
|
|
|
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
|
|
// Touch the file without changing the content, shouldn't upload
|
|
fakeFolder.localModifier().setContents("a1.eml", 'A');
|
|
// Change the content/size
|
|
fakeFolder.localModifier().setContents("a2.eml", 'B');
|
|
fakeFolder.localModifier().appendByte("a3.eml");
|
|
fakeFolder.localModifier().appendByte("b3.txt");
|
|
fakeFolder.syncOnce();
|
|
|
|
QCOMPARE(getDbChecksum("a1.eml"), referenceChecksum);
|
|
QCOMPARE(getDbChecksum("a2.eml"), QByteArray("SHA1:84951fc23a4dafd10020ac349da1f5530fa65949"));
|
|
QCOMPARE(getDbChecksum("a3.eml"), QByteArray("SHA1:826b7e7a7af8a529ae1c7443c23bf185c0ad440c"));
|
|
QCOMPARE(getDbChecksum("b3.eml"), getDbChecksum("a3.txt"));
|
|
|
|
QVERIFY(!itemDidComplete(completeSpy, "a1.eml"));
|
|
QVERIFY(itemDidCompleteSuccessfully(completeSpy, "a2.eml"));
|
|
QVERIFY(itemDidCompleteSuccessfully(completeSpy, "a3.eml"));
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
}
|
|
|
|
void testRemoteChangeInMovedFolder() {
|
|
// issue #5192
|
|
FakeFolder fakeFolder{FileInfo{ QString(), {
|
|
FileInfo { QStringLiteral("folder"), {
|
|
FileInfo{ QStringLiteral("folderA"), { { QStringLiteral("file.txt"), 400 } } },
|
|
QStringLiteral("folderB")
|
|
}
|
|
}}}};
|
|
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
|
|
// Edit a file in a moved directory.
|
|
fakeFolder.remoteModifier().setContents("folder/folderA/file.txt", 'a');
|
|
fakeFolder.remoteModifier().rename("folder/folderA", "folder/folderB/folderA");
|
|
fakeFolder.syncOnce();
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
auto oldState = fakeFolder.currentLocalState();
|
|
QVERIFY(oldState.find("folder/folderB/folderA/file.txt"));
|
|
QVERIFY(!oldState.find("folder/folderA/file.txt"));
|
|
|
|
// This sync should not remove the file
|
|
fakeFolder.syncOnce();
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
QCOMPARE(fakeFolder.currentLocalState(), oldState);
|
|
|
|
}
|
|
|
|
void testSelectiveSyncModevFolder() {
|
|
// issue #5224
|
|
FakeFolder fakeFolder{FileInfo{ QString(), {
|
|
FileInfo { QStringLiteral("parentFolder"), {
|
|
FileInfo{ QStringLiteral("subFolderA"), { { QStringLiteral("fileA.txt"), 400 } } },
|
|
FileInfo{ QStringLiteral("subFolderB"), { { QStringLiteral("fileB.txt"), 400 } } }
|
|
}
|
|
}}}};
|
|
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
auto expectedServerState = fakeFolder.currentRemoteState();
|
|
|
|
// Remove subFolderA with selectiveSync:
|
|
fakeFolder.syncEngine().journal()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList,
|
|
{"parentFolder/subFolderA/"});
|
|
fakeFolder.syncEngine().journal()->avoidReadFromDbOnNextSync(QByteArrayLiteral("parentFolder/subFolderA/"));
|
|
|
|
fakeFolder.syncOnce();
|
|
|
|
{
|
|
// Nothing changed on the server
|
|
QCOMPARE(fakeFolder.currentRemoteState(), expectedServerState);
|
|
// The local state should not have subFolderA
|
|
auto remoteState = fakeFolder.currentRemoteState();
|
|
remoteState.remove("parentFolder/subFolderA");
|
|
QCOMPARE(fakeFolder.currentLocalState(), remoteState);
|
|
}
|
|
|
|
// Rename parentFolder on the server
|
|
fakeFolder.remoteModifier().rename("parentFolder", "parentFolderRenamed");
|
|
expectedServerState = fakeFolder.currentRemoteState();
|
|
fakeFolder.syncOnce();
|
|
|
|
{
|
|
QCOMPARE(fakeFolder.currentRemoteState(), expectedServerState);
|
|
auto remoteState = fakeFolder.currentRemoteState();
|
|
// The subFolderA should still be there on the server.
|
|
QVERIFY(remoteState.find("parentFolderRenamed/subFolderA/fileA.txt"));
|
|
// But not on the client because of the selective sync
|
|
remoteState.remove("parentFolderRenamed/subFolderA");
|
|
QCOMPARE(fakeFolder.currentLocalState(), remoteState);
|
|
}
|
|
|
|
// Rename it again, locally this time.
|
|
fakeFolder.localModifier().rename("parentFolderRenamed", "parentThirdName");
|
|
fakeFolder.syncOnce();
|
|
|
|
{
|
|
auto remoteState = fakeFolder.currentRemoteState();
|
|
// The subFolderA should still be there on the server.
|
|
QVERIFY(remoteState.find("parentThirdName/subFolderA/fileA.txt"));
|
|
// But not on the client because of the selective sync
|
|
remoteState.remove("parentThirdName/subFolderA");
|
|
QCOMPARE(fakeFolder.currentLocalState(), remoteState);
|
|
|
|
expectedServerState = fakeFolder.currentRemoteState();
|
|
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
|
|
fakeFolder.syncOnce(); // This sync should do nothing
|
|
QCOMPARE(completeSpy.count(), 0);
|
|
|
|
QCOMPARE(fakeFolder.currentRemoteState(), expectedServerState);
|
|
QCOMPARE(fakeFolder.currentLocalState(), remoteState);
|
|
}
|
|
}
|
|
|
|
void testSelectiveSyncBug() {
|
|
// issue owncloud/enterprise#1965: files from selective-sync ignored
|
|
// folders are uploaded anyway is some circumstances.
|
|
FakeFolder fakeFolder{FileInfo{ QString(), {
|
|
FileInfo { QStringLiteral("parentFolder"), {
|
|
FileInfo{ QStringLiteral("subFolderA"), {
|
|
{ QStringLiteral("fileA.txt"), 400 },
|
|
{ QStringLiteral("fileB.txt"), 400, 'o' },
|
|
FileInfo { QStringLiteral("subsubFolder"), {
|
|
{ QStringLiteral("fileC.txt"), 400 },
|
|
{ QStringLiteral("fileD.txt"), 400, 'o' }
|
|
}},
|
|
FileInfo{ QStringLiteral("anotherFolder"), {
|
|
FileInfo { QStringLiteral("emptyFolder"), { } },
|
|
FileInfo { QStringLiteral("subsubFolder"), {
|
|
{ QStringLiteral("fileE.txt"), 400 },
|
|
{ QStringLiteral("fileF.txt"), 400, 'o' }
|
|
}}
|
|
}}
|
|
}},
|
|
FileInfo{ QStringLiteral("subFolderB"), {} }
|
|
}}
|
|
}}};
|
|
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
auto expectedServerState = fakeFolder.currentRemoteState();
|
|
|
|
// Remove subFolderA with selectiveSync:
|
|
fakeFolder.syncEngine().journal()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList,
|
|
{"parentFolder/subFolderA/"});
|
|
fakeFolder.syncEngine().journal()->avoidReadFromDbOnNextSync(QByteArrayLiteral("parentFolder/subFolderA/"));
|
|
|
|
// But touch local file before the next sync, such that the local folder
|
|
// can't be removed
|
|
fakeFolder.localModifier().setContents("parentFolder/subFolderA/fileB.txt", 'n');
|
|
fakeFolder.localModifier().setContents("parentFolder/subFolderA/subsubFolder/fileD.txt", 'n');
|
|
fakeFolder.localModifier().setContents("parentFolder/subFolderA/anotherFolder/subsubFolder/fileF.txt", 'n');
|
|
|
|
// Several follow-up syncs don't change the state at all,
|
|
// in particular the remote state doesn't change and fileB.txt
|
|
// isn't uploaded.
|
|
|
|
for (int i = 0; i < 3; ++i) {
|
|
fakeFolder.syncOnce();
|
|
|
|
{
|
|
// Nothing changed on the server
|
|
QCOMPARE(fakeFolder.currentRemoteState(), expectedServerState);
|
|
// The local state should still have subFolderA
|
|
auto local = fakeFolder.currentLocalState();
|
|
QVERIFY(local.find("parentFolder/subFolderA"));
|
|
QVERIFY(!local.find("parentFolder/subFolderA/fileA.txt"));
|
|
QVERIFY(local.find("parentFolder/subFolderA/fileB.txt"));
|
|
QVERIFY(!local.find("parentFolder/subFolderA/subsubFolder/fileC.txt"));
|
|
QVERIFY(local.find("parentFolder/subFolderA/subsubFolder/fileD.txt"));
|
|
QVERIFY(!local.find("parentFolder/subFolderA/anotherFolder/subsubFolder/fileE.txt"));
|
|
QVERIFY(local.find("parentFolder/subFolderA/anotherFolder/subsubFolder/fileF.txt"));
|
|
QVERIFY(!local.find("parentFolder/subFolderA/anotherFolder/emptyFolder"));
|
|
QVERIFY(local.find("parentFolder/subFolderB"));
|
|
}
|
|
}
|
|
}
|
|
|
|
void abortAfterFailedMkdir() {
|
|
FakeFolder fakeFolder{FileInfo{}};
|
|
QSignalSpy finishedSpy(&fakeFolder.syncEngine(), SIGNAL(finished(bool)));
|
|
fakeFolder.serverErrorPaths().append("NewFolder");
|
|
fakeFolder.localModifier().mkdir("NewFolder");
|
|
// This should be aborted and would otherwise fail in FileInfo::create.
|
|
fakeFolder.localModifier().insert("NewFolder/NewFile");
|
|
fakeFolder.syncOnce();
|
|
QCOMPARE(finishedSpy.size(), 1);
|
|
QCOMPARE(finishedSpy.first().first().toBool(), false);
|
|
}
|
|
|
|
void testDirDownloadWithError() {
|
|
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
|
|
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
|
|
fakeFolder.remoteModifier().mkdir("Y");
|
|
fakeFolder.remoteModifier().mkdir("Y/Z");
|
|
fakeFolder.remoteModifier().insert("Y/Z/d0");
|
|
fakeFolder.remoteModifier().insert("Y/Z/d1");
|
|
fakeFolder.remoteModifier().insert("Y/Z/d2");
|
|
fakeFolder.remoteModifier().insert("Y/Z/d3");
|
|
fakeFolder.remoteModifier().insert("Y/Z/d4");
|
|
fakeFolder.remoteModifier().insert("Y/Z/d5");
|
|
fakeFolder.remoteModifier().insert("Y/Z/d6");
|
|
fakeFolder.remoteModifier().insert("Y/Z/d7");
|
|
fakeFolder.remoteModifier().insert("Y/Z/d8");
|
|
fakeFolder.remoteModifier().insert("Y/Z/d9");
|
|
fakeFolder.serverErrorPaths().append("Y/Z/d2", 503); // 503 is a fatal error
|
|
fakeFolder.serverErrorPaths().append("Y/Z/d3", 503); // 503 is a fatal error
|
|
QVERIFY(!fakeFolder.syncOnce());
|
|
QCoreApplication::processEvents(); // should not crash
|
|
|
|
QSet<QString> seen;
|
|
for(const QList<QVariant> &args : completeSpy) {
|
|
auto item = args[0].value<SyncFileItemPtr>();
|
|
qDebug() << item->_file << item->isDirectory() << item->_status;
|
|
QVERIFY(!seen.contains(item->_file)); // signal only sent once per item
|
|
seen.insert(item->_file);
|
|
if (item->_file == "Y/Z/d2") {
|
|
QVERIFY(item->_status == SyncFileItem::FatalError);
|
|
} else if(item->_file == "Y/Z/d3") {
|
|
QVERIFY(item->_status != SyncFileItem::Success);
|
|
}
|
|
QVERIFY(item->_file != "Y/Z/d9"); // we should have aborted the sync before d9 starts
|
|
}
|
|
}
|
|
|
|
void testFakeConflict()
|
|
{
|
|
FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
|
|
|
|
int nGET = 0;
|
|
fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &) {
|
|
if (op == QNetworkAccessManager::GetOperation)
|
|
++nGET;
|
|
return nullptr;
|
|
});
|
|
|
|
// For directly editing the remote checksum
|
|
FileInfo &remoteInfo = dynamic_cast<FileInfo &>(fakeFolder.remoteModifier());
|
|
|
|
// Base mtime with no ms content (filesystem is seconds only)
|
|
auto mtime = QDateTime::currentDateTimeUtc().addDays(-4);
|
|
mtime.setMSecsSinceEpoch(mtime.toMSecsSinceEpoch() / 1000 * 1000);
|
|
|
|
// Conflict: Same content, mtime, but no server checksum
|
|
// -> ignored in reconcile
|
|
fakeFolder.localModifier().setContents("A/a1", 'C');
|
|
fakeFolder.localModifier().setModTime("A/a1", mtime);
|
|
fakeFolder.remoteModifier().setContents("A/a1", 'C');
|
|
fakeFolder.remoteModifier().setModTime("A/a1", mtime);
|
|
QVERIFY(fakeFolder.syncOnce());
|
|
QCOMPARE(nGET, 0);
|
|
|
|
// Conflict: Same content, mtime, but weak server checksum
|
|
// -> ignored in reconcile
|
|
mtime = mtime.addDays(1);
|
|
fakeFolder.localModifier().setContents("A/a1", 'D');
|
|
fakeFolder.localModifier().setModTime("A/a1", mtime);
|
|
fakeFolder.remoteModifier().setContents("A/a1", 'D');
|
|
fakeFolder.remoteModifier().setModTime("A/a1", mtime);
|
|
remoteInfo.find("A/a1")->checksums = "Adler32:bad";
|
|
QVERIFY(fakeFolder.syncOnce());
|
|
QCOMPARE(nGET, 0);
|
|
|
|
// Conflict: Same content, mtime, but server checksum differs
|
|
// -> downloaded
|
|
mtime = mtime.addDays(1);
|
|
fakeFolder.localModifier().setContents("A/a1", 'W');
|
|
fakeFolder.localModifier().setModTime("A/a1", mtime);
|
|
fakeFolder.remoteModifier().setContents("A/a1", 'W');
|
|
fakeFolder.remoteModifier().setModTime("A/a1", mtime);
|
|
remoteInfo.find("A/a1")->checksums = "SHA1:bad";
|
|
QVERIFY(fakeFolder.syncOnce());
|
|
QCOMPARE(nGET, 1);
|
|
|
|
// Conflict: Same content, mtime, matching checksums
|
|
// -> PropagateDownload, but it skips the download
|
|
mtime = mtime.addDays(1);
|
|
fakeFolder.localModifier().setContents("A/a1", 'C');
|
|
fakeFolder.localModifier().setModTime("A/a1", mtime);
|
|
fakeFolder.remoteModifier().setContents("A/a1", 'C');
|
|
fakeFolder.remoteModifier().setModTime("A/a1", mtime);
|
|
remoteInfo.find("A/a1")->checksums = "SHA1:56900fb1d337cf7237ff766276b9c1e8ce507427";
|
|
QVERIFY(fakeFolder.syncOnce());
|
|
QCOMPARE(nGET, 1);
|
|
|
|
// Extra sync reads from db, no difference
|
|
QVERIFY(fakeFolder.syncOnce());
|
|
QCOMPARE(nGET, 1);
|
|
}
|
|
|
|
/**
|
|
* Checks whether SyncFileItems have the expected properties before start
|
|
* of propagation.
|
|
*/
|
|
void testSyncFileItemProperties()
|
|
{
|
|
auto initialMtime = QDateTime::currentDateTimeUtc().addDays(-7);
|
|
auto changedMtime = QDateTime::currentDateTimeUtc().addDays(-4);
|
|
auto changedMtime2 = QDateTime::currentDateTimeUtc().addDays(-3);
|
|
|
|
// Base mtime with no ms content (filesystem is seconds only)
|
|
initialMtime.setMSecsSinceEpoch(initialMtime.toMSecsSinceEpoch() / 1000 * 1000);
|
|
changedMtime.setMSecsSinceEpoch(changedMtime.toMSecsSinceEpoch() / 1000 * 1000);
|
|
changedMtime2.setMSecsSinceEpoch(changedMtime2.toMSecsSinceEpoch() / 1000 * 1000);
|
|
|
|
// Ensure the initial mtimes are as expected
|
|
auto initialFileInfo = FileInfo::A12_B12_C12_S12();
|
|
initialFileInfo.setModTime("A/a1", initialMtime);
|
|
initialFileInfo.setModTime("B/b1", initialMtime);
|
|
initialFileInfo.setModTime("C/c1", initialMtime);
|
|
|
|
FakeFolder fakeFolder{ initialFileInfo };
|
|
|
|
|
|
// upload a
|
|
fakeFolder.localModifier().appendByte("A/a1");
|
|
fakeFolder.localModifier().setModTime("A/a1", changedMtime);
|
|
// download b
|
|
fakeFolder.remoteModifier().appendByte("B/b1");
|
|
fakeFolder.remoteModifier().setModTime("B/b1", changedMtime);
|
|
// conflict c
|
|
fakeFolder.localModifier().appendByte("C/c1");
|
|
fakeFolder.localModifier().appendByte("C/c1");
|
|
fakeFolder.localModifier().setModTime("C/c1", changedMtime);
|
|
fakeFolder.remoteModifier().appendByte("C/c1");
|
|
fakeFolder.remoteModifier().setModTime("C/c1", changedMtime2);
|
|
|
|
connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToPropagate, [&](SyncFileItemVector &items) {
|
|
SyncFileItemPtr a1, b1, c1;
|
|
for (auto &item : items) {
|
|
if (item->_file == "A/a1")
|
|
a1 = item;
|
|
if (item->_file == "B/b1")
|
|
b1 = item;
|
|
if (item->_file == "C/c1")
|
|
c1 = item;
|
|
}
|
|
|
|
// a1: should have local size and modtime
|
|
QVERIFY(a1);
|
|
QCOMPARE(a1->_instruction, CSYNC_INSTRUCTION_SYNC);
|
|
QCOMPARE(a1->_direction, SyncFileItem::Up);
|
|
QCOMPARE(a1->_size, quint64(5));
|
|
|
|
QCOMPARE(Utility::qDateTimeFromTime_t(a1->_modtime), changedMtime);
|
|
QCOMPARE(a1->_previousSize, quint64(4));
|
|
QCOMPARE(Utility::qDateTimeFromTime_t(a1->_previousModtime), initialMtime);
|
|
|
|
// b2: should have remote size and modtime
|
|
QVERIFY(b1);
|
|
QCOMPARE(b1->_instruction, CSYNC_INSTRUCTION_SYNC);
|
|
QCOMPARE(b1->_direction, SyncFileItem::Down);
|
|
QCOMPARE(b1->_size, quint64(17));
|
|
QCOMPARE(Utility::qDateTimeFromTime_t(b1->_modtime), changedMtime);
|
|
QCOMPARE(b1->_previousSize, quint64(16));
|
|
QCOMPARE(Utility::qDateTimeFromTime_t(b1->_previousModtime), initialMtime);
|
|
|
|
// c1: conflicts are downloads, so remote size and modtime
|
|
QVERIFY(c1);
|
|
QCOMPARE(c1->_instruction, CSYNC_INSTRUCTION_CONFLICT);
|
|
QCOMPARE(c1->_direction, SyncFileItem::None);
|
|
QCOMPARE(c1->_size, quint64(25));
|
|
QCOMPARE(Utility::qDateTimeFromTime_t(c1->_modtime), changedMtime2);
|
|
QCOMPARE(c1->_previousSize, quint64(26));
|
|
QCOMPARE(Utility::qDateTimeFromTime_t(c1->_previousModtime), changedMtime);
|
|
});
|
|
|
|
QVERIFY(fakeFolder.syncOnce());
|
|
}
|
|
|
|
/**
|
|
* Checks whether subsequent large uploads are skipped after a 507 error
|
|
*/
|
|
void testInsufficientRemoteStorage()
|
|
{
|
|
FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
|
|
|
|
// Disable parallel uploads
|
|
SyncOptions syncOptions;
|
|
syncOptions._parallelNetworkJobs = false;
|
|
fakeFolder.syncEngine().setSyncOptions(syncOptions);
|
|
|
|
// Produce an error based on upload size
|
|
int remoteQuota = 1000;
|
|
int n507 = 0, nPUT = 0;
|
|
auto parent = new QObject;
|
|
fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request) -> QNetworkReply * {
|
|
if (op == QNetworkAccessManager::PutOperation) {
|
|
nPUT++;
|
|
if (request.rawHeader("OC-Total-Length").toInt() > remoteQuota) {
|
|
n507++;
|
|
return new FakeErrorReply(op, request, parent, 507);
|
|
}
|
|
}
|
|
return nullptr;
|
|
});
|
|
|
|
fakeFolder.localModifier().insert("A/big", 800);
|
|
QVERIFY(fakeFolder.syncOnce());
|
|
QCOMPARE(nPUT, 1);
|
|
QCOMPARE(n507, 0);
|
|
|
|
nPUT = 0;
|
|
fakeFolder.localModifier().insert("A/big1", 500); // ok
|
|
fakeFolder.localModifier().insert("A/big2", 1200); // 507 (quota guess now 1199)
|
|
fakeFolder.localModifier().insert("A/big3", 1200); // skipped
|
|
fakeFolder.localModifier().insert("A/big4", 1500); // skipped
|
|
fakeFolder.localModifier().insert("A/big5", 1100); // 507 (quota guess now 1099)
|
|
fakeFolder.localModifier().insert("A/big6", 900); // ok (quota guess now 199)
|
|
fakeFolder.localModifier().insert("A/big7", 200); // skipped
|
|
fakeFolder.localModifier().insert("A/big8", 199); // ok (quota guess now 0)
|
|
|
|
fakeFolder.localModifier().insert("B/big8", 1150); // 507
|
|
QVERIFY(!fakeFolder.syncOnce());
|
|
QCOMPARE(nPUT, 6);
|
|
QCOMPARE(n507, 3);
|
|
}
|
|
|
|
void testLocalMove()
|
|
{
|
|
FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
|
|
|
|
int nPUT = 0;
|
|
int nDELETE = 0;
|
|
fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &) {
|
|
if (op == QNetworkAccessManager::PutOperation)
|
|
++nPUT;
|
|
if (op == QNetworkAccessManager::DeleteOperation)
|
|
++nDELETE;
|
|
return nullptr;
|
|
});
|
|
|
|
// For directly editing the remote checksum
|
|
FileInfo &remoteInfo = fakeFolder.remoteModifier();
|
|
|
|
// Simple move causing a remote rename
|
|
fakeFolder.localModifier().rename("A/a1", "A/a1m");
|
|
QVERIFY(fakeFolder.syncOnce());
|
|
QCOMPARE(fakeFolder.currentLocalState(), remoteInfo);
|
|
QCOMPARE(nPUT, 0);
|
|
|
|
// Move-and-change, causing a upload and delete
|
|
fakeFolder.localModifier().rename("A/a2", "A/a2m");
|
|
fakeFolder.localModifier().appendByte("A/a2m");
|
|
QVERIFY(fakeFolder.syncOnce());
|
|
QCOMPARE(fakeFolder.currentLocalState(), remoteInfo);
|
|
QCOMPARE(nPUT, 1);
|
|
QCOMPARE(nDELETE, 1);
|
|
|
|
// Move-and-change, mtime+content only
|
|
fakeFolder.localModifier().rename("B/b1", "B/b1m");
|
|
fakeFolder.localModifier().setContents("B/b1m", 'C');
|
|
QVERIFY(fakeFolder.syncOnce());
|
|
QCOMPARE(fakeFolder.currentLocalState(), remoteInfo);
|
|
QCOMPARE(nPUT, 2);
|
|
QCOMPARE(nDELETE, 2);
|
|
|
|
// Move-and-change, size+content only
|
|
auto mtime = fakeFolder.remoteModifier().find("B/b2")->lastModified;
|
|
fakeFolder.localModifier().rename("B/b2", "B/b2m");
|
|
fakeFolder.localModifier().appendByte("B/b2m");
|
|
fakeFolder.localModifier().setModTime("B/b2m", mtime);
|
|
QVERIFY(fakeFolder.syncOnce());
|
|
QCOMPARE(fakeFolder.currentLocalState(), remoteInfo);
|
|
QCOMPARE(nPUT, 3);
|
|
QCOMPARE(nDELETE, 3);
|
|
|
|
// Move-and-change, content only -- c1 has no checksum, so we fail to detect this!
|
|
mtime = fakeFolder.remoteModifier().find("C/c1")->lastModified;
|
|
fakeFolder.localModifier().rename("C/c1", "C/c1m");
|
|
fakeFolder.localModifier().setContents("C/c1m", 'C');
|
|
fakeFolder.localModifier().setModTime("C/c1m", mtime);
|
|
QVERIFY(fakeFolder.syncOnce());
|
|
QCOMPARE(nPUT, 3);
|
|
QCOMPARE(nDELETE, 3);
|
|
QVERIFY(!(fakeFolder.currentLocalState() == remoteInfo));
|
|
|
|
// cleanup, and upload a file that will have a checksum in the db
|
|
fakeFolder.localModifier().remove("C/c1m");
|
|
fakeFolder.localModifier().insert("C/c3");
|
|
QVERIFY(fakeFolder.syncOnce());
|
|
QCOMPARE(fakeFolder.currentLocalState(), remoteInfo);
|
|
QCOMPARE(nPUT, 4);
|
|
QCOMPARE(nDELETE, 4);
|
|
|
|
// Move-and-change, content only, this time while having a checksum
|
|
mtime = fakeFolder.remoteModifier().find("C/c3")->lastModified;
|
|
fakeFolder.localModifier().rename("C/c3", "C/c3m");
|
|
fakeFolder.localModifier().setContents("C/c3m", 'C');
|
|
fakeFolder.localModifier().setModTime("C/c3m", mtime);
|
|
QVERIFY(fakeFolder.syncOnce());
|
|
QCOMPARE(nPUT, 5);
|
|
QCOMPARE(nDELETE, 5);
|
|
QCOMPARE(fakeFolder.currentLocalState(), remoteInfo);
|
|
}
|
|
};
|
|
|
|
QTEST_GUILESS_MAIN(TestSyncEngine)
|
|
#include "testsyncengine.moc"
|