diff options
author | Erik Verbruggen <erik@verbruggen.consulting> | 2021-12-08 17:23:32 +0300 |
---|---|---|
committer | Erik Verbruggen <Erik.Verbruggen@Me.com> | 2021-12-09 15:28:23 +0300 |
commit | 108fcadce3a87dbf26abdb0126ed21298d2bdd69 (patch) | |
tree | a7391d1a4e4e49b6481b402b58a0827bd260c64a /test | |
parent | b7e25e3f26785c3e3667471539234e5d4bafef9f (diff) |
Support dehydrated files in autotests
When creating the local state, check if files are hydrated or not.
If not hydrated, don't read the file: the OS will trigger a
download. This is bad: first the read will fail, because the test is
running on the main thread, the same place where work from callbacks
from the OS get handled. This will result in a time-out for the OS,
and it will return 0 bytes read. So the size for the file in our
local state is set to zero bytes, which makes the comparisson with
the remote state fail, which in turn makes the comparisson fail.
Worse: the callbacks from the system do come in, and are emitted as
a _QueuedConnection_. So when another call to `syncOnce` is made,
the queued downloads will be done, and the file will be re-hydrated,
thus changing the state of the files on disk.
Diffstat (limited to 'test')
-rw-r--r-- | test/testchunkingng.cpp | 37 | ||||
-rw-r--r-- | test/testpermissions.cpp | 12 | ||||
-rw-r--r-- | test/testsyncconflict.cpp | 14 | ||||
-rw-r--r-- | test/testsyncmove.cpp | 62 | ||||
-rw-r--r-- | test/testsyncvirtualfiles.cpp | 6 | ||||
-rw-r--r-- | test/testutils/syncenginetestutils.cpp | 95 | ||||
-rw-r--r-- | test/testutils/syncenginetestutils.h | 39 |
7 files changed, 193 insertions, 72 deletions
diff --git a/test/testchunkingng.cpp b/test/testchunkingng.cpp index e7add5b4f..0cab9a090 100644 --- a/test/testchunkingng.cpp +++ b/test/testchunkingng.cpp @@ -36,8 +36,7 @@ static void partialUpload(FakeFolder &fakeFolder, const QString &name, qint64 si QCOMPARE(fakeFolder.uploadState().children.count(), 1); // the transfer was done with chunking auto upStateChildren = fakeFolder.uploadState().children.first().children; - QCOMPARE(sizeWhenAbort, std::accumulate(upStateChildren.cbegin(), upStateChildren.cend(), 0, - [](int s, const FileInfo &i) { return s + i.size; })); + QCOMPARE(sizeWhenAbort, std::accumulate(upStateChildren.cbegin(), upStateChildren.cend(), 0, [](int s, const FileInfo &i) { return s + i.contentSize; })); } // Reduce max chunk size a bit so we get more chunks @@ -66,7 +65,7 @@ private slots: QVERIFY(fakeFolder.syncOnce()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); QCOMPARE(fakeFolder.uploadState().children.count(), 1); // the transfer was done with chunking - QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->size, size); + QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->contentSize, size); // Check that another upload of the same file also work. fakeFolder.localModifier().appendByte("A/a0"); @@ -86,7 +85,7 @@ private slots: QCOMPARE(fakeFolder.uploadState().children.count(), 1); auto chunkingId = fakeFolder.uploadState().children.first().name; const auto &chunkMap = fakeFolder.uploadState().children.first().children; - qint64 uploadedSize = std::accumulate(chunkMap.begin(), chunkMap.end(), 0LL, [](qint64 s, const FileInfo &f) { return s + f.size; }); + qint64 uploadedSize = std::accumulate(chunkMap.begin(), chunkMap.end(), 0LL, [](qint64 s, const FileInfo &f) { return s + f.contentSize; }); QVERIFY(uploadedSize > 2 * 1000 * 1000); // at least 2 MB // Add a fake chunk to make sure it gets deleted @@ -105,7 +104,7 @@ private slots: QVERIFY(fakeFolder.syncOnce()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->size, size); + QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->contentSize, size); // The same chunk id was re-used QCOMPARE(fakeFolder.uploadState().children.count(), 1); QCOMPARE(fakeFolder.uploadState().children.first().name, chunkingId); @@ -121,7 +120,7 @@ private slots: QCOMPARE(fakeFolder.uploadState().children.count(), 1); auto chunkingId = fakeFolder.uploadState().children.first().name; const auto &chunkMap = fakeFolder.uploadState().children.first().children; - qint64 uploadedSize = std::accumulate(chunkMap.begin(), chunkMap.end(), 0LL, [](qint64 s, const FileInfo &f) { return s + f.size; }); + qint64 uploadedSize = std::accumulate(chunkMap.begin(), chunkMap.end(), 0LL, [](qint64 s, const FileInfo &f) { return s + f.contentSize; }); QVERIFY(uploadedSize > 2 * 1000 * 1000); // at least 50 MB QVERIFY(chunkMap.size() >= 3); // at least three chunks @@ -139,7 +138,7 @@ private slots: fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * { if (op == QNetworkAccessManager::PutOperation) { // Test that we properly resuming, not resending the first chunk - Q_ASSERT(request.rawHeader("OC-Chunk-Offset").toLongLong() >= firstChunk.size); + Q_ASSERT(request.rawHeader("OC-Chunk-Offset").toLongLong() >= firstChunk.contentSize); } else if (op == QNetworkAccessManager::DeleteOperation) { deletedPaths.append(request.url().path()); } @@ -160,7 +159,7 @@ private slots: } QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->size, size); + QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->contentSize, size); // The same chunk id was re-used QCOMPARE(fakeFolder.uploadState().children.count(), 1); QCOMPARE(fakeFolder.uploadState().children.first().name, chunkingId); @@ -177,7 +176,7 @@ private slots: QCOMPARE(fakeFolder.uploadState().children.count(), 1); auto chunkingId = fakeFolder.uploadState().children.first().name; const auto &chunkMap = fakeFolder.uploadState().children.first().children; - qint64 uploadedSize = std::accumulate(chunkMap.begin(), chunkMap.end(), 0LL, [](qint64 s, const FileInfo &f) { return s + f.size; }); + qint64 uploadedSize = std::accumulate(chunkMap.begin(), chunkMap.end(), 0LL, [](qint64 s, const FileInfo &f) { return s + f.contentSize; }); QVERIFY(uploadedSize > 5 * 1000 * 1000); // at least 5 MB // Add a chunk that makes the file completely uploaded @@ -204,7 +203,7 @@ private slots: QVERIFY(!sawDelete); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->size, size); + QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->contentSize, size); // The same chunk id was re-used QCOMPARE(fakeFolder.uploadState().children.count(), 1); QCOMPARE(fakeFolder.uploadState().children.first().name, chunkingId); @@ -222,7 +221,7 @@ private slots: QCOMPARE(fakeFolder.uploadState().children.count(), 1); auto chunkingId = fakeFolder.uploadState().children.first().name; const auto &chunkMap = fakeFolder.uploadState().children.first().children; - qint64 uploadedSize = std::accumulate(chunkMap.begin(), chunkMap.end(), 0LL, [](qint64 s, const FileInfo &f) { return s + f.size; }); + qint64 uploadedSize = std::accumulate(chunkMap.begin(), chunkMap.end(), 0LL, [](qint64 s, const FileInfo &f) { return s + f.contentSize; }); QVERIFY(uploadedSize > 5 * 1000 * 1000); // at least 5 MB // Add a chunk that makes the file more than completely uploaded @@ -232,7 +231,7 @@ private slots: QVERIFY(fakeFolder.syncOnce()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->size, size); + QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->contentSize, size); QCOMPARE(fakeFolder.uploadState().children.count(), 1); } @@ -384,7 +383,7 @@ private slots: QVERIFY(fakeFolder.syncOnce()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->size, size + 1); + QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->contentSize, size + 1); // A different chunk id was used, and the previous one is removed QCOMPARE(fakeFolder.uploadState().children.count(), 1); QVERIFY(fakeFolder.uploadState().children.first().name != chunkingId); @@ -443,7 +442,7 @@ private slots: auto localState = fakeFolder.currentLocalState(); // A0 is the one from the server - QCOMPARE(localState.find("A/a0")->size, size); + QCOMPARE(localState.find("A/a0")->contentSize, size); QCOMPARE(localState.find("A/a0")->contentChar, 'C'); // There is a conflict file with our version @@ -453,7 +452,7 @@ private slots: }); QVERIFY(it != stateAChildren.cend()); QCOMPARE(it->contentChar, 'B'); - QCOMPARE(it->size, size+1); + QCOMPARE(it->contentSize, size + 1); // Remove the conflict file so the comparison works! fakeFolder.localModifier().remove("A/" + it->name); @@ -494,7 +493,7 @@ private slots: QVERIFY(fakeFolder.syncOnce()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->size, size+1); + QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->contentSize, size + 1); // A different chunk id was used, and the previous one is removed QCOMPARE(fakeFolder.uploadState().children.count(), 1); @@ -517,7 +516,7 @@ private slots: QVERIFY(fakeFolder.syncOnce()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->size, size); + QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->contentSize, size); // A different chunk id was used QCOMPARE(fakeFolder.uploadState().children.count(), 1); @@ -618,7 +617,7 @@ private slots: // Now resume QVERIFY(fakeFolder.syncOnce()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->size, size); + QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->contentSize, size); // The same chunk id was re-used QCOMPARE(fakeFolder.uploadState().children.count(), 1); @@ -629,7 +628,7 @@ private slots: fakeFolder.localModifier().appendByte("A/a0"); QVERIFY(fakeFolder.syncOnce()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->size, size + 1); + QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->contentSize, size + 1); } diff --git a/test/testpermissions.cpp b/test/testpermissions.cpp index b1dc39378..e0cafd0a3 100644 --- a/test/testpermissions.cpp +++ b/test/testpermissions.cpp @@ -160,22 +160,22 @@ private slots: //3. // File should be recovered - QCOMPARE(currentLocalState.find("normalDirectory_PERM_CKDNV_/cannotBeModified_PERM_DVN_.data")->size, cannotBeModifiedSize); - QCOMPARE(currentLocalState.find("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data")->size, cannotBeModifiedSize); + QCOMPARE(currentLocalState.find("normalDirectory_PERM_CKDNV_/cannotBeModified_PERM_DVN_.data")->contentSize, cannotBeModifiedSize); + QCOMPARE(currentLocalState.find("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data")->contentSize, cannotBeModifiedSize); // and conflict created auto c1 = findConflict(currentLocalState, "normalDirectory_PERM_CKDNV_/cannotBeModified_PERM_DVN_.data"); QVERIFY(c1); - QCOMPARE(c1->size, cannotBeModifiedSize + 1); + QCOMPARE(c1->contentSize, cannotBeModifiedSize + 1); auto c2 = findConflict(currentLocalState, "readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data"); QVERIFY(c2); - QCOMPARE(c2->size, cannotBeModifiedSize + 1); + QCOMPARE(c2->contentSize, cannotBeModifiedSize + 1); // remove the conflicts for the next state comparison fakeFolder.localModifier().remove(c1->path()); fakeFolder.localModifier().remove(c2->path()); //4. File should be updated, that's tested by assertLocalAndRemoteDir - QCOMPARE(currentLocalState.find("normalDirectory_PERM_CKDNV_/canBeModified_PERM_W_.data")->size, canBeModifiedSize + 1); - QCOMPARE(currentLocalState.find("readonlyDirectory_PERM_M_/canBeModified_PERM_W_.data")->size, canBeModifiedSize + 1); + QCOMPARE(currentLocalState.find("normalDirectory_PERM_CKDNV_/canBeModified_PERM_W_.data")->contentSize, canBeModifiedSize + 1); + QCOMPARE(currentLocalState.find("readonlyDirectory_PERM_M_/canBeModified_PERM_W_.data")->contentSize, canBeModifiedSize + 1); //5. // the file should be in the server and local diff --git a/test/testsyncconflict.cpp b/test/testsyncconflict.cpp index dde1eb751..450569e92 100644 --- a/test/testsyncconflict.cpp +++ b/test/testsyncconflict.cpp @@ -136,8 +136,8 @@ private slots: QCOMPARE(remote.find(conflictMap[a1FileId])->contentChar, 'L'); QCOMPARE(remote.find("A/a1")->contentChar, 'R'); - QCOMPARE(remote.find(conflictMap[a2FileId])->size, 5); - QCOMPARE(remote.find("A/a2")->size, 6); + QCOMPARE(remote.find(conflictMap[a2FileId])->contentSize, 5); + QCOMPARE(remote.find("A/a2")->contentSize, 6); } void testSeparateUpload() @@ -206,8 +206,8 @@ private slots: QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); QCOMPARE(conflictMap.size(), 1); QVERIFY(conflictMap.contains(a1ConflictFileId)); - QCOMPARE(fakeFolder.currentRemoteState().find(conflictName)->size, 66); - QCOMPARE(fakeFolder.currentRemoteState().find(conflictMap[a1ConflictFileId])->size, 65); + QCOMPARE(fakeFolder.currentRemoteState().find(conflictName)->contentSize, 66); + QCOMPARE(fakeFolder.currentRemoteState().find(conflictMap[a1ConflictFileId])->contentSize, 65); conflictMap.clear(); } @@ -440,7 +440,7 @@ private slots: // 1) QVERIFY(itemConflict(completeSpy, "Z")); - QCOMPARE(fakeFolder.currentLocalState().find("Z")->size, 63); + QCOMPARE(fakeFolder.currentLocalState().find("Z")->contentSize, 63); QVERIFY(conflicts[2].contains("Z")); QCOMPARE(conflicts[2].toUtf8(), conflictRecords[2]); QVERIFY(QFileInfo(fakeFolder.localPath() + conflicts[2]).isDir()); @@ -448,7 +448,7 @@ private slots: // 2) QVERIFY(itemConflict(completeSpy, "A/a1")); - QCOMPARE(fakeFolder.currentLocalState().find("A/a1")->size, 5); + QCOMPARE(fakeFolder.currentLocalState().find("A/a1")->contentSize, 5); QVERIFY(conflicts[0].contains("A/a1")); QCOMPARE(conflicts[0].toUtf8(), conflictRecords[0]); QVERIFY(QFileInfo(fakeFolder.localPath() + conflicts[0]).isDir()); @@ -456,7 +456,7 @@ private slots: // 3) QVERIFY(itemConflict(completeSpy, "B")); - QCOMPARE(fakeFolder.currentLocalState().find("B")->size, 31); + QCOMPARE(fakeFolder.currentLocalState().find("B")->contentSize, 31); QVERIFY(conflicts[1].contains("B")); QCOMPARE(conflicts[1].toUtf8(), conflictRecords[1]); QVERIFY(QFileInfo(fakeFolder.localPath() + conflicts[1]).isDir()); diff --git a/test/testsyncmove.cpp b/test/testsyncmove.cpp index 047e5149b..c82bba3d2 100644 --- a/test/testsyncmove.cpp +++ b/test/testsyncmove.cpp @@ -841,21 +841,74 @@ private slots: } + void testRenameParallelism_data() + { + QTest::addColumn<Vfs::Mode>("vfsMode"); + QTest::addColumn<bool>("filesAreDehydrated"); + + QTest::newRow("Vfs::Off") << Vfs::Off << true; + + if (isVfsPluginAvailable(Vfs::WindowsCfApi)) { + QTest::newRow("Vfs::WindowsCfApi dehydrated") << Vfs::WindowsCfApi << true; + + // TODO: then hydrated version will fail due to an issue in the winvfs plugin, so leave it disabled for now. + // QTest::newRow("Vfs::WindowsCfApi hydrated") << Vfs::WindowsCfApi << false; + } else if (Utility::isWindows()) { + QWARN("Skipping Vfs::WindowsCfApi"); + } + } + // Test that deletes don't run before renames void testRenameParallelism() { - FakeFolder fakeFolder{ FileInfo{} }; + QFETCH(Vfs::Mode, vfsMode); + QFETCH(bool, filesAreDehydrated); + + FakeFolder fakeFolder({ FileInfo {} }, vfsMode); + + FileInfo::ComparissonOption cmpOpt = FileInfo::ContentIsKing; + if (vfsMode != Vfs::Off) { + auto vfs = QSharedPointer<Vfs>(createVfsFromPlugin(vfsMode).release()); + QVERIFY(vfs); + fakeFolder.switchToVfs(vfs); + fakeFolder.syncJournal().internalPinStates().setForPath("", filesAreDehydrated ? PinState::OnlineOnly : PinState::AlwaysLocal); + + // make files virtual + fakeFolder.syncOnce(); + + cmpOpt = FileInfo::IgnoreContentOfDehydratedFiles; + } + fakeFolder.remoteModifier().mkdir("A"); fakeFolder.remoteModifier().insert("A/file"); QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + { + auto localState = fakeFolder.currentLocalState(); + FileInfo *file = localState.find({ "A/file" }); + QVERIFY(file != nullptr); // check if the file exists + if (vfsMode != Vfs::Off) { + QCOMPARE(file->isDehydratedPlaceholder, filesAreDehydrated); + } + QVERIFY(localState.equals(fakeFolder.currentRemoteState(), cmpOpt)); + } fakeFolder.localModifier().mkdir("B"); fakeFolder.localModifier().rename("A/file", "B/file"); fakeFolder.localModifier().remove("A"); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + { + auto localState = fakeFolder.currentLocalState(); + QVERIFY(localState.find("A/file") == nullptr); // check if the file is gone + QVERIFY(localState.find("A") == nullptr); // check if the directory is gone + FileInfo *file = localState.find({ "B/file" }); + QVERIFY(file != nullptr); // check if the file exists + if (vfsMode != Vfs::Off) { + QCOMPARE(file->isDehydratedPlaceholder, filesAreDehydrated); // check that no-one messed with the placeholder state + } + QVERIFY(localState.equals(fakeFolder.currentRemoteState(), cmpOpt)); + } } void testMovedWithError_data() @@ -871,7 +924,6 @@ private slots: } else { QWARN("Skipping Vfs::WindowsCfApi"); } - #endif } diff --git a/test/testsyncvirtualfiles.cpp b/test/testsyncvirtualfiles.cpp index 627af1a2a..e52c46475 100644 --- a/test/testsyncvirtualfiles.cpp +++ b/test/testsyncvirtualfiles.cpp @@ -601,7 +601,7 @@ private slots: QVERIFY(!fakeFolder.currentLocalState().find("A/a1")); QVERIFY(fakeFolder.currentLocalState().find("A/a1" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("A/a1" DVSUFFIX)->size <= 1); + QVERIFY(fakeFolder.currentLocalState().find("A/a1" DVSUFFIX)->contentSize <= 1); QVERIFY(fakeFolder.currentRemoteState().find("A/a1")); QCOMPARE(itemInstruction(completeSpy, "A/a1" DVSUFFIX), CSYNC_INSTRUCTION_SYNC); QCOMPARE(dbRecord(fakeFolder, "A/a1" DVSUFFIX)._type, ItemTypeVirtualFile); @@ -840,10 +840,10 @@ private slots: QCOMPARE(itemInstruction(completeSpy, "B/b2"), CSYNC_INSTRUCTION_REMOVE); QCOMPARE(itemInstruction(completeSpy, "B/b3" DVSUFFIX), CSYNC_INSTRUCTION_NEW); - QCOMPARE(fakeFolder.currentRemoteState().find("C/c1")->size, 25); + QCOMPARE(fakeFolder.currentRemoteState().find("C/c1")->contentSize, 25); QCOMPARE(itemInstruction(completeSpy, "C/c1"), CSYNC_INSTRUCTION_SYNC); - QCOMPARE(fakeFolder.currentRemoteState().find("C/c2")->size, 26); + QCOMPARE(fakeFolder.currentRemoteState().find("C/c2")->contentSize, 26); QCOMPARE(itemInstruction(completeSpy, "C/c2"), CSYNC_INSTRUCTION_CONFLICT); cleanup(); diff --git a/test/testutils/syncenginetestutils.cpp b/test/testutils/syncenginetestutils.cpp index be3d7f984..7edcb6e19 100644 --- a/test/testutils/syncenginetestutils.cpp +++ b/test/testutils/syncenginetestutils.cpp @@ -132,11 +132,12 @@ FileInfo::FileInfo(const QString &name, const std::initializer_list<FileInfo> &c addChild(source); } -void FileInfo::addChild(const FileInfo &info) +FileInfo &FileInfo::addChild(const FileInfo &info) { - auto &dest = this->children[info.name] = info; + FileInfo &dest = this->children[info.name] = info; dest.parentPath = path(); dest.fixupParentPathRecursively(); + return dest; } void FileInfo::remove(const QString &relativePath) @@ -165,7 +166,7 @@ void FileInfo::appendByte(const QString &relativePath, char contentChar) Q_UNUSED(contentChar); FileInfo *file = findInvalidatingEtags(relativePath); Q_ASSERT(file); - file->size += 1; + file->contentSize += 1; } void FileInfo::modifyByte(const QString &relativePath, quint64 offset, char contentChar) @@ -254,11 +255,41 @@ bool FileInfo::operator==(const FileInfo &other) const // Consider files to be equal between local<->remote as a user would. return name == other.name && isDir == other.isDir - && size == other.size + && contentSize == other.contentSize && contentChar == other.contentChar && children == other.children; } +bool FileInfo::equals(const FileInfo &other, ComparissonOption opt) const +{ + switch (opt) { + case ContentIsKing: + return *this == other; + case IgnoreContentOfDehydratedFiles: + if (!isDehydratedPlaceholder && !other.isDehydratedPlaceholder) { + if (contentSize != other.contentSize || contentChar != other.contentChar) { + return false; + } + } + + if (name == other.name && isDir == other.isDir && fileSize == other.fileSize && lastModified == other.lastModified) { + if (children.size() == other.children.size()) { + for (auto it = children.constBegin(), eit = children.constEnd(), + oit = other.children.constBegin(), oeit = other.children.constEnd(); + it != eit && oit != oeit; ++it, ++oit) { + if (!it->equals(*oit, opt)) { + return false; + } + } + } + } + + return true; + } + + Q_UNREACHABLE(); +} + QString FileInfo::path() const { return (parentPath.isEmpty() ? QString() : (parentPath + QLatin1Char('/'))) + name; @@ -332,7 +363,7 @@ FakePropfindReply::FakePropfindReply(FileInfo &remoteRootFileInfo, QNetworkAcces auto gmtDate = fileInfo.lastModified.toUTC(); auto stringDate = QLocale::c().toString(gmtDate, QStringLiteral("ddd, dd MMM yyyy HH:mm:ss 'GMT'")); xml.writeTextElement(davUri, QStringLiteral("getlastmodified"), stringDate); - xml.writeTextElement(davUri, QStringLiteral("getcontentlength"), QString::number(fileInfo.size)); + xml.writeTextElement(davUri, QStringLiteral("getcontentlength"), QString::number(fileInfo.contentSize)); xml.writeTextElement(davUri, QStringLiteral("getetag"), QStringLiteral("\"%1\"").arg(QString::fromLatin1(fileInfo.etag))); xml.writeTextElement(ocUri, QStringLiteral("permissions"), !fileInfo.permissions.isNull() ? QString(fileInfo.permissions.toString()) : fileInfo.isShared ? QStringLiteral("SRDNVCKW") : QStringLiteral("RDNVCKW")); @@ -409,8 +440,9 @@ FileInfo *FakePutReply::perform(FileInfo &remoteRootFileInfo, const QNetworkRequ Q_ASSERT(!fileName.isEmpty()); FileInfo *fileInfo = remoteRootFileInfo.find(fileName); if (fileInfo) { - fileInfo->size = putPayload.size(); + fileInfo->contentSize = putPayload.size(); fileInfo->contentChar = putPayload.at(0); + fileInfo->fileSize = fileInfo->contentSize; // it's hydrated on the server, so these are the same } else { // Assume that the file is filled with the same character fileInfo = remoteRootFileInfo.create(fileName, putPayload.size(), putPayload.at(0)); @@ -422,7 +454,7 @@ FileInfo *FakePutReply::perform(FileInfo &remoteRootFileInfo, const QNetworkRequ void FakePutReply::respond() { - emit uploadProgress(fileInfo->size, fileInfo->size); + emit uploadProgress(fileInfo->contentSize, fileInfo->contentSize); setRawHeader("OC-ETag", fileInfo->etag); setRawHeader("ETag", fileInfo->etag); setRawHeader("OC-FileID", fileInfo->fileId); @@ -536,7 +568,7 @@ void FakeGetReply::respond() return; } payload = fileInfo->contentChar; - size = fileInfo->size; + size = fileInfo->contentSize; setHeader(QNetworkRequest::ContentLengthHeader, size); setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 200); setRawHeader("OC-ETag", fileInfo->etag); @@ -677,12 +709,12 @@ FileInfo *FakeChunkMoveReply::perform(FileInfo &uploadsFileInfo, FileInfo &remot if (chunkNameLongLong != prev) break; Q_ASSERT(!x.isDir); - Q_ASSERT(x.size > 0); // There should not be empty chunks - size += x.size; + Q_ASSERT(x.contentSize > 0); // There should not be empty chunks + size += x.contentSize; Q_ASSERT(!payload || payload == x.contentChar); payload = x.contentChar; ++count; - prev = chunkNameLongLong + x.size; + prev = chunkNameLongLong + x.contentSize; } Q_ASSERT(sourceFolderChildren.count() == count); // There should not be holes or extra files @@ -699,8 +731,9 @@ FileInfo *FakeChunkMoveReply::perform(FileInfo &uploadsFileInfo, FileInfo &remot if (request.rawHeader("If") != start + " ([\"" + fileInfo->etag + "\"])") { return nullptr; } - fileInfo->size = size; + fileInfo->contentSize = size; fileInfo->contentChar = payload; + fileInfo->fileSize = fileInfo->contentSize; // it's hydrated on the server, so these are the same } else { Q_ASSERT(!request.hasRawHeader("If")); // Assume that the file is filled with the same character @@ -881,8 +914,9 @@ QNetworkReply *FakeQNAM::createRequest(QNetworkAccessManager::Operation op, cons return reply; } -FakeFolder::FakeFolder(const FileInfo &fileTemplate) +FakeFolder::FakeFolder(const FileInfo &fileTemplate, OCC::Vfs::Mode vfsMode) : _localModifier(_tempDir.path()) + , _vfsMode(vfsMode) { // Needs to be done once OCC::SyncEngine::minimumFileAgeForUpload = std::chrono::milliseconds(0); @@ -1000,6 +1034,11 @@ void FakeFolder::execUntilItemCompleted(const QString &relativePath) QVERIFY(false); } +bool FakeFolder::isDehydratedPlaceholder(const QString &filePath) +{ + return _syncEngine->syncOptions()._vfs->isDehydratedPlaceholder(filePath); +} + void FakeFolder::toDisk(QDir &dir, const FileInfo &templateFi) { for (const auto &child : templateFi.children) { @@ -1011,7 +1050,7 @@ void FakeFolder::toDisk(QDir &dir, const FileInfo &templateFi) } else { QFile file { dir.filePath(child.name) }; file.open(QFile::WriteOnly); - file.write(QByteArray {}.fill(child.contentChar, child.size)); + file.write(QByteArray {}.fill(child.contentChar, child.contentSize)); file.close(); OCC::FileSystem::setModTime(file.fileName(), OCC::Utility::qDateTimeToTime_t(child.lastModified)); } @@ -1028,15 +1067,25 @@ void FakeFolder::fromDisk(QDir &dir, FileInfo &templateFi) FileInfo &subFi = templateFi.children[diskChild.fileName()] = FileInfo { diskChild.fileName() }; fromDisk(subDir, subFi); } else { - QFile f { diskChild.filePath() }; - f.open(QFile::ReadOnly); - auto content = f.read(1); - if (content.size() == 0) { - qWarning() << "Empty file at:" << diskChild.filePath(); - continue; + FileInfo fi(diskChild.fileName()); + fi.isDir = false; + fi.fileSize = diskChild.size(); + fi.isDehydratedPlaceholder = isDehydratedPlaceholder(diskChild.absoluteFilePath()); + if (fi.isDehydratedPlaceholder) { + fi.contentChar = '\0'; + } else { + QFile f { diskChild.filePath() }; + f.open(QFile::ReadOnly); + auto content = f.read(1); + if (content.size() == 0) { + qWarning() << "Empty file at:" << diskChild.filePath(); + continue; + } + fi.contentChar = content.at(0); + fi.contentSize = fi.fileSize; } - char contentChar = content.at(0); - templateFi.children.insert(diskChild.fileName(), FileInfo { diskChild.fileName(), diskChild.size(), contentChar }); + + templateFi.children.insert(fi.name, fi); } } } @@ -1065,7 +1114,7 @@ FileInfo FakeFolder::dbState() const auto &item = parentDir.children[name]; item.name = name; item.parentPath = parentDir.path(); - item.size = record._fileSize; + item.contentSize = record._fileSize; item.isDir = record._type == ItemTypeDirectory; item.permissions = record._remotePerm; item.etag = record._etag; diff --git a/test/testutils/syncenginetestutils.h b/test/testutils/syncenginetestutils.h index 9f8f31a2f..22b0fa296 100644 --- a/test/testutils/syncenginetestutils.h +++ b/test/testutils/syncenginetestutils.h @@ -109,6 +109,7 @@ public: void setModTime(const QString &relativePath, const QDateTime &modTime) override; }; +/// FIXME: we should make it explicit in the construtor if we're talking about a hydrated or a dehydrated file! class FileInfo : public FileModifier { public: @@ -122,19 +123,21 @@ public: FileInfo(const QString &name, qint64 size) : name { name } , isDir { false } - , size { size } + , fileSize(size) + , contentSize { size } { } FileInfo(const QString &name, qint64 size, char contentChar) : name { name } , isDir { false } - , size { size } + , fileSize(size) + , contentSize { size } , contentChar { contentChar } { } FileInfo(const QString &name, const std::initializer_list<FileInfo> &children); - void addChild(const FileInfo &info); + FileInfo &addChild(const FileInfo &info); void remove(const QString &relativePath) override; @@ -152,6 +155,7 @@ public: void setModTime(const QString &relativePath, const QDateTime &modTime) override; + /// Return a pointer to the FileInfo, or a nullptr if it doesn't exist FileInfo *find(PathComponents pathComponents, const bool invalidateEtags = false); FileInfo *createDir(const QString &relativePath); @@ -170,6 +174,13 @@ public: return !operator==(other); } + enum ComparissonOption { + IgnoreContentOfDehydratedFiles, + ContentIsKing, + }; + + bool equals(const FileInfo &other, ComparissonOption opt) const; + QString path() const; QString absolutePath() const; @@ -184,8 +195,10 @@ public: QByteArray fileId = generateFileId(); QByteArray checksums; QByteArray extraDavProperties; - qint64 size = 0; + qint64 fileSize = 0; + qint64 contentSize = 0; char contentChar = 'W'; + bool isDehydratedPlaceholder = false; // Sorted by name to be able to compare trees QMap<QString, FileInfo> children; @@ -195,7 +208,12 @@ public: friend inline QDebug operator<<(QDebug dbg, const FileInfo &fi) { - return dbg << "{ " << fi.path() << ": " << fi.children; + return dbg << "{ " << fi.path() << ": " + << ", fileSize:" << fi.fileSize + << ", contentSize:" << fi.contentSize + << ", contentChar:" << fi.contentChar + << ", isDehydratedPlaceholder:" << fi.isDehydratedPlaceholder + << ", children:" << fi.children; } }; @@ -471,9 +489,10 @@ class FakeFolder OCC::AccountPtr _account; std::unique_ptr<OCC::SyncJournalDb> _journalDb; std::unique_ptr<OCC::SyncEngine> _syncEngine; + OCC::Vfs::Mode _vfsMode; public: - FakeFolder(const FileInfo &fileTemplate); + FakeFolder(const FileInfo &fileTemplate, OCC::Vfs::Mode vfsMode = OCC::Vfs::Off); void switchToVfs(QSharedPointer<OCC::Vfs> vfs); @@ -523,10 +542,12 @@ public: return execUntilFinished(); } + bool isDehydratedPlaceholder(const QString &filePath); + private: static void toDisk(QDir &dir, const FileInfo &templateFi); - static void fromDisk(QDir &dir, FileInfo &templateFi); + void fromDisk(QDir &dir, FileInfo &templateFi); }; @@ -571,7 +592,7 @@ inline void addFiles(QStringList &dest, const FileInfo &fi) for (const auto &fi : fi.children) addFiles(dest, fi); } else { - dest += QStringLiteral("%1 - %2 %3-bytes").arg(fi.path()).arg(fi.size).arg(fi.contentChar); + dest += QStringLiteral("%1 - %2 %3-bytes").arg(fi.path()).arg(fi.contentSize).arg(fi.contentChar); } } @@ -597,7 +618,7 @@ inline void addFilesDbData(QStringList &dest, const FileInfo &fi) for (const auto &fi : fi.children) addFilesDbData(dest, fi); } else { - dest += QStringLiteral("%1 - %2 %3 %4 %5").arg(fi.name, fi.isDir ? QStringLiteral("dir") : QStringLiteral("file"), QString::number(fi.size), QString::number(fi.lastModified.toSecsSinceEpoch()), QString::fromUtf8(fi.fileId)); + dest += QStringLiteral("%1 - %2 %3 %4 %5").arg(fi.name, fi.isDir ? QStringLiteral("dir") : QStringLiteral("file"), QString::number(fi.contentSize), QString::number(fi.lastModified.toSecsSinceEpoch()), QString::fromUtf8(fi.fileId)); } } |