mirror of
https://github.com/nextcloud/desktop.git
synced 2025-10-26 11:17:43 +00:00
Issue #5783 When the directry that should be removed by selective sync contains changes, we ignore the whole sub tree instead of only ignoreing new files. We cannot ignore the whole directory, we need to ignore only the directory that do not have files to remove
328 lines
15 KiB
C++
328 lines
15 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');
|
|
// Upload and calculate the checksums
|
|
// fakeFolder.syncOnce();
|
|
fakeFolder.syncOnce();
|
|
|
|
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.syncOnce();
|
|
|
|
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("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("subFolder"), {
|
|
{ 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' }
|
|
}}
|
|
}}
|
|
}}
|
|
}}
|
|
}}};
|
|
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
auto expectedServerState = fakeFolder.currentRemoteState();
|
|
|
|
// Remove subFolder with selectiveSync:
|
|
fakeFolder.syncEngine().journal()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList,
|
|
{"parentFolder/subFolder/"});
|
|
fakeFolder.syncEngine().journal()->avoidReadFromDbOnNextSync("parentFolder/subFolder/");
|
|
|
|
// But touch local file before the next sync, such that the local folder
|
|
// can't be removed
|
|
fakeFolder.localModifier().setContents("parentFolder/subFolder/fileB.txt", 'n');
|
|
fakeFolder.localModifier().setContents("parentFolder/subFolder/subsubFolder/fileD.txt", 'n');
|
|
fakeFolder.localModifier().setContents("parentFolder/subFolder/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/subFolder"));
|
|
QVERIFY(!local.find("parentFolder/subFolder/fileA.txt"));
|
|
QVERIFY(local.find("parentFolder/subFolder/fileB.txt"));
|
|
QVERIFY(!local.find("parentFolder/subFolder/subsubFolder/fileC.txt"));
|
|
QVERIFY(local.find("parentFolder/subFolder/subsubFolder/fileD.txt"));
|
|
QVERIFY(!local.find("parentFolder/subFolder/anotherFolder/subsubFolder/fileE.txt"));
|
|
QVERIFY(local.find("parentFolder/subFolder/anotherFolder/subsubFolder/fileF.txt"));
|
|
QVERIFY(!local.find("parentFolder/subFolder/anotherFolder/emptyFolder"));
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
QTEST_GUILESS_MAIN(TestSyncEngine)
|
|
#include "testsyncengine.moc"
|