mirror of
https://github.com/chylex/Nextcloud-Desktop.git
synced 2024-11-26 22:42:44 +01:00
c5a0ce5a43
When a new folder becomes selective-sync excluded, we already mark it and all its parent folders with _invalid_ etags to force rediscovery. That's not enough however. Later calls to csync_statedb_get_below_path could still pull data about the excluded files into the remote tree. That lead to incorrect behavior, such as uploads happening for folders that had been explicitly excluded from sync. To fix the problem, statedb_get_below_path is adjusted to not read the data about excluded folders from the database. Currently we can't wipe this data from the database outright because we need it to determine whether the files in the excluded folder can be wiped away or not. See owncloud/enterprise#1965
309 lines
13 KiB
C++
309 lines
13 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' }
|
|
}}
|
|
}}
|
|
}}};
|
|
|
|
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 a local file before the next sync, such that the local folder
|
|
// can't be removed
|
|
fakeFolder.localModifier().setContents("parentFolder/subFolder/fileB.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"));
|
|
}
|
|
}
|
|
}
|
|
|
|
void abortAfterFailedMkdir() {
|
|
QSKIP("Skip for 2.3");
|
|
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" || item->_file == "Y/Z/d3") {
|
|
QVERIFY(item->_status == SyncFileItem::FatalError);
|
|
}
|
|
QVERIFY(item->_file != "Y/Z/d9"); // we should have aborted the sync before d9 starts
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
QTEST_GUILESS_MAIN(TestSyncEngine)
|
|
#include "testsyncengine.moc"
|