diff options
author | Erik Verbruggen <erik@verbruggen.consulting> | 2021-12-09 16:24:22 +0300 |
---|---|---|
committer | Erik Verbruggen <Erik.Verbruggen@Me.com> | 2021-12-15 15:23:55 +0300 |
commit | 8484e759060f221cbc6f519165572e3559cbc560 (patch) | |
tree | 9c097283ddf675031ba88f4f46748f17acedfe32 /test | |
parent | 6300c004300655c2de18da42766a547137217f8a (diff) |
Tests: always check the hydration state and the mtime
operator== will now always also check the hydration state and mtime.
For the few cases where this will fail (on purpose), the equals
method can be called with `IgnoreLastModified` passed to it.
Diffstat (limited to 'test')
-rw-r--r-- | test/testdatabaseerror.cpp | 12 | ||||
-rw-r--r-- | test/testsyncengine.cpp | 12 | ||||
-rw-r--r-- | test/testsyncmove.cpp | 50 | ||||
-rw-r--r-- | test/testutils/syncenginetestutils.cpp | 86 | ||||
-rw-r--r-- | test/testutils/syncenginetestutils.h | 66 |
5 files changed, 145 insertions, 81 deletions
diff --git a/test/testdatabaseerror.cpp b/test/testdatabaseerror.cpp index 871514a50..ff25c2139 100644 --- a/test/testdatabaseerror.cpp +++ b/test/testdatabaseerror.cpp @@ -24,6 +24,13 @@ private slots: */ FileInfo finalState; + + auto checkAgainstFinalState = [&finalState](const FileInfo ¤tRemoteState) { + // The final state should be the same for every iteration, EXCEPT for the lastModified times: + // these will differ (well, increase), because files are re-created with every iteration. + return currentRemoteState.equals(finalState, FileInfo::IgnoreLastModified); + }; + for (int count = 0; true; ++count) { qInfo() << "Starting Iteration" << count; @@ -55,7 +62,7 @@ private slots: // No error was thrown, we are finished QVERIFY(result); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(fakeFolder.currentRemoteState(), finalState); + QVERIFY(checkAgainstFinalState(fakeFolder.currentRemoteState())); return; } @@ -71,8 +78,7 @@ private slots: if (count == 0) { finalState = fakeFolder.currentRemoteState(); } else { - // the final state should be the same for every iteration - QCOMPARE(fakeFolder.currentRemoteState(), finalState); + QVERIFY(checkAgainstFinalState(fakeFolder.currentRemoteState())); } } } diff --git a/test/testsyncengine.cpp b/test/testsyncengine.cpp index 5e7058d31..80ab8dcca 100644 --- a/test/testsyncengine.cpp +++ b/test/testsyncengine.cpp @@ -123,6 +123,9 @@ private slots: QCOMPARE(getDbChecksum("a3.eml"), referenceChecksum); QCOMPARE(getDbChecksum("b3.txt"), referenceChecksum); + // Make sure that the lastModified time caused by the setContent calls below is actually different: + QThread::sleep(1); + ItemCompletedSpy completeSpy(fakeFolder); // Touch the file without changing the content, shouldn't upload fakeFolder.localModifier().setContents("a1.eml", 'A'); @@ -140,7 +143,12 @@ private slots: QVERIFY(!itemDidComplete(completeSpy, "a1.eml")); QVERIFY(itemDidCompleteSuccessfully(completeSpy, "a2.eml")); QVERIFY(itemDidCompleteSuccessfully(completeSpy, "a3.eml")); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + // The local and remote state now differ: the local mtime for `a1.eml` is bigger (newer) than on the server, because + // the upload was skipped (same checksum). So first verify that this is the case: + QVERIFY(fakeFolder.currentLocalState().find("a1.eml")->lastModified() > fakeFolder.currentRemoteState().find("a1.eml")->lastModified()); + // And then check if everything else actually is the same: + QVERIFY(fakeFolder.currentLocalState().equals(fakeFolder.currentRemoteState(), FileInfo::IgnoreLastModified)); } void testSelectiveSyncBug() { @@ -753,7 +761,7 @@ private slots: fakeFolder.remoteModifier().insert("foo/bar"); auto datetime = QDateTime::currentDateTime(); datetime.setSecsSinceEpoch(datetime.toSecsSinceEpoch()); // wipe ms - fakeFolder.remoteModifier().find("foo")->lastModified = datetime; + fakeFolder.remoteModifier().find("foo")->setLastModified(datetime); QVERIFY(fakeFolder.syncOnce()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); diff --git a/test/testsyncmove.cpp b/test/testsyncmove.cpp index c82bba3d2..6b118c55b 100644 --- a/test/testsyncmove.cpp +++ b/test/testsyncmove.cpp @@ -209,7 +209,7 @@ private slots: QCOMPARE(nDELETE, 2); // Move-and-change, size+content only - auto mtime = fakeFolder.remoteModifier().find("B/b2")->lastModified; + 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); @@ -221,7 +221,7 @@ private slots: // Move-and-change, content only -- c1 has no checksum, so we fail to detect this! // NOTE: This is an expected failure. - mtime = fakeFolder.remoteModifier().find("C/c1")->lastModified; + 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); @@ -240,7 +240,7 @@ private slots: QCOMPARE(nDELETE, 4); // Move-and-change, content only, this time while having a checksum - mtime = fakeFolder.remoteModifier().find("C/c3")->lastModified; + 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); @@ -642,8 +642,8 @@ private slots: fakeFolder.setServerOverride(counter.functor()); // Changing the mtime on the server (without invalidating the etag) - fakeFolder.remoteModifier().find("A/a1")->lastModified = QDateTime::currentDateTimeUtc().addSecs(-50000); - fakeFolder.remoteModifier().find("A/a2")->lastModified = QDateTime::currentDateTimeUtc().addSecs(-40000); + fakeFolder.remoteModifier().find("A/a1")->setLastModified(QDateTime::currentDateTime().addSecs(-50000)); + fakeFolder.remoteModifier().find("A/a2")->setLastModified(QDateTime::currentDateTime().addSecs(-40000)); // Move a few files fakeFolder.remoteModifier().rename("A/a1", "A/a1_server_renamed"); @@ -662,7 +662,8 @@ private slots: QCOMPARE(counter.nMOVE, 1); QCOMPARE(counter.nDELETE, 0); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + // Check that everything other than the mtime is still equal: + QVERIFY(fakeFolder.currentLocalState().equals(fakeFolder.currentRemoteState(), FileInfo::IgnoreLastModified)); } // Test for https://github.com/owncloud/client/issues/6694 @@ -851,7 +852,7 @@ private slots: 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. + // TODO: the 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"); @@ -866,7 +867,6 @@ private slots: FakeFolder fakeFolder({ FileInfo {} }, vfsMode); - FileInfo::ComparissonOption cmpOpt = FileInfo::ContentIsKing; if (vfsMode != Vfs::Off) { auto vfs = QSharedPointer<Vfs>(createVfsFromPlugin(vfsMode).release()); QVERIFY(vfs); @@ -875,8 +875,6 @@ private slots: // make files virtual fakeFolder.syncOnce(); - - cmpOpt = FileInfo::IgnoreContentOfDehydratedFiles; } fakeFolder.remoteModifier().mkdir("A"); @@ -885,12 +883,16 @@ private slots: { 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)); + FileInfo *localFile = localState.find({ "A/file" }); + QVERIFY(localFile != nullptr); // check if the file exists + QCOMPARE(vfsMode == Vfs::Off || localFile->isDehydratedPlaceholder, vfsMode == Vfs::Off || filesAreDehydrated); + + auto remoteState = fakeFolder.currentRemoteState(); + FileInfo *remoteFile = remoteState.find({ "A/file" }); + QVERIFY(remoteFile != nullptr); + QCOMPARE(localFile->lastModified(), remoteFile->lastModified()); + + QCOMPARE(localState, remoteState); } fakeFolder.localModifier().mkdir("B"); @@ -902,12 +904,16 @@ private slots: 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)); + FileInfo *localFile = localState.find({ "B/file" }); + QVERIFY(localFile != nullptr); // check if the file exists + QCOMPARE(vfsMode == Vfs::Off || localFile->isDehydratedPlaceholder, vfsMode == Vfs::Off || filesAreDehydrated); + + auto remoteState = fakeFolder.currentRemoteState(); + FileInfo *remoteFile = remoteState.find({ "B/file" }); + QVERIFY(remoteFile != nullptr); + QCOMPARE(localFile->lastModified(), remoteFile->lastModified()); + + QCOMPARE(localState, remoteState); } } diff --git a/test/testutils/syncenginetestutils.cpp b/test/testutils/syncenginetestutils.cpp index 7edcb6e19..fe72c642f 100644 --- a/test/testutils/syncenginetestutils.cpp +++ b/test/testutils/syncenginetestutils.cpp @@ -166,7 +166,10 @@ void FileInfo::appendByte(const QString &relativePath, char contentChar) Q_UNUSED(contentChar); FileInfo *file = findInvalidatingEtags(relativePath); Q_ASSERT(file); - file->contentSize += 1; + if (!file->isDehydratedPlaceholder) { + file->contentSize += 1; + } + file->fileSize += 1; } void FileInfo::modifyByte(const QString &relativePath, quint64 offset, char contentChar) @@ -203,7 +206,7 @@ void FileInfo::setModTime(const QString &relativePath, const QDateTime &modTime) { FileInfo *file = findInvalidatingEtags(relativePath); Q_ASSERT(file); - file->lastModified = modTime; + file->setLastModified(modTime); } FileInfo *FileInfo::find(PathComponents pathComponents, const bool invalidateEtags) @@ -250,44 +253,45 @@ FileInfo *FileInfo::create(const QString &relativePath, qint64 size, char conten return &child; } -bool FileInfo::operator==(const FileInfo &other) const +bool FileInfo::equals(const FileInfo &other, CompareWhat compareWhat) const { - // Consider files to be equal between local<->remote as a user would. - return name == other.name - && isDir == other.isDir - && 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; - } + // Only check the content and contentSize if both files are hydrated: + 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; - } - } - } + // We need to check this before we use isDir in the next if-statement: + if (isDir != other.isDir) { + return false; + } + + if (compareWhat == CompareLastModified) { + // Don't check directory mtime: it might change when (unsynced) files get created. + if (!isDir && _lastModifiedInSecondsUTC != other._lastModifiedInSecondsUTC) { + return false; } + } + + if (name != other.name || fileSize != other.fileSize) { + return false; + } - return true; + if (children.size() != other.children.size()) { + return false; } - Q_UNREACHABLE(); + for (auto it = children.constBegin(), eit = children.constEnd(); it != eit; ++it) { + auto oit = other.children.constFind(it.key()); + if (oit == other.children.constEnd()) { + return false; + } else if (!it.value().equals(oit.value(), compareWhat)) { + return false; + } + } + + return true; } QString FileInfo::path() const @@ -360,7 +364,7 @@ FakePropfindReply::FakePropfindReply(FileInfo &remoteRootFileInfo, QNetworkAcces } else xml.writeEmptyElement(davUri, QStringLiteral("resourcetype")); - auto gmtDate = fileInfo.lastModified.toUTC(); + auto gmtDate = fileInfo.lastModifiedInUtc(); 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.contentSize)); @@ -442,12 +446,12 @@ FileInfo *FakePutReply::perform(FileInfo &remoteRootFileInfo, const QNetworkRequ if (fileInfo) { 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)); } - fileInfo->lastModified = OCC::Utility::qDateTimeFromTime_t(request.rawHeader("X-OC-Mtime").toLongLong()); + fileInfo->fileSize = fileInfo->contentSize; // it's hydrated on the server, so these are the same + fileInfo->setLastModifiedFromSecondsUTC(request.rawHeader("X-OC-Mtime").toLongLong()); remoteRootFileInfo.find(fileName, /*invalidate_etags=*/true); return fileInfo; } @@ -574,6 +578,7 @@ void FakeGetReply::respond() setRawHeader("OC-ETag", fileInfo->etag); setRawHeader("ETag", fileInfo->etag); setRawHeader("OC-FileId", fileInfo->fileId); + setRawHeader("X-OC-Mtime", QByteArray::number(fileInfo->lastModifiedInSecondsUTC())); emit metaDataChanged(); if (bytesAvailable()) emit readyRead(); @@ -641,6 +646,7 @@ void FakeGetWithDataReply::respond() setRawHeader("OC-ETag", fileInfo->etag); setRawHeader("ETag", fileInfo->etag); setRawHeader("OC-FileId", fileInfo->fileId); + setRawHeader("X-OC-Mtime", QByteArray::number(fileInfo->lastModifiedInSecondsUTC())); emit metaDataChanged(); if (bytesAvailable()) emit readyRead(); @@ -739,7 +745,7 @@ FileInfo *FakeChunkMoveReply::perform(FileInfo &uploadsFileInfo, FileInfo &remot // Assume that the file is filled with the same character fileInfo = remoteRootFileInfo.create(fileName, size, payload); } - fileInfo->lastModified = OCC::Utility::qDateTimeFromTime_t(request.rawHeader("X-OC-Mtime").toLongLong()); + fileInfo->setLastModifiedFromSecondsUTC(request.rawHeader("X-OC-Mtime").toLongLong()); remoteRootFileInfo.find(fileName, /*invalidate_etags=*/true); return fileInfo; @@ -1052,7 +1058,7 @@ void FakeFolder::toDisk(QDir &dir, const FileInfo &templateFi) file.open(QFile::WriteOnly); file.write(QByteArray {}.fill(child.contentChar, child.contentSize)); file.close(); - OCC::FileSystem::setModTime(file.fileName(), OCC::Utility::qDateTimeToTime_t(child.lastModified)); + OCC::FileSystem::setModTime(file.fileName(), child.lastModifiedInSecondsUTC()); } } } @@ -1065,12 +1071,14 @@ void FakeFolder::fromDisk(QDir &dir, FileInfo &templateFi) QDir subDir = dir; subDir.cd(diskChild.fileName()); FileInfo &subFi = templateFi.children[diskChild.fileName()] = FileInfo { diskChild.fileName() }; + subFi.setLastModified(diskChild.lastModified()); fromDisk(subDir, subFi); } else { FileInfo fi(diskChild.fileName()); fi.isDir = false; fi.fileSize = diskChild.size(); fi.isDehydratedPlaceholder = isDehydratedPlaceholder(diskChild.absoluteFilePath()); + fi.setLastModified(diskChild.lastModified()); if (fi.isDehydratedPlaceholder) { fi.contentChar = '\0'; } else { @@ -1118,7 +1126,7 @@ FileInfo FakeFolder::dbState() const item.isDir = record._type == ItemTypeDirectory; item.permissions = record._remotePerm; item.etag = record._etag; - item.lastModified = OCC::Utility::qDateTimeFromTime_t(record._modtime); + item.setLastModifiedFromSecondsUTC(record._modtime); item.fileId = record._fileId; item.checksums = record._checksumHeader; // item.contentChar can't be set from the db diff --git a/test/testutils/syncenginetestutils.h b/test/testutils/syncenginetestutils.h index 22b0fa296..61b258b4a 100644 --- a/test/testutils/syncenginetestutils.h +++ b/test/testutils/syncenginetestutils.h @@ -109,6 +109,13 @@ public: void setModTime(const QString &relativePath, const QDateTime &modTime) override; }; +static inline qint64 defaultLastModified() +{ + auto precise = QDateTime::currentDateTimeUtc().addDays(-7); + time_t timeInSeconds = OCC::Utility::qDateTimeToTime_t(precise); + return timeInSeconds; +} + /// FIXME: we should make it explicit in the construtor if we're talking about a hydrated or a dehydrated file! class FileInfo : public FileModifier { @@ -167,19 +174,44 @@ public: return name < other.name; } - bool operator==(const FileInfo &other) const; + enum CompareWhat { + CompareLastModified, + IgnoreLastModified, + }; + + bool operator==(const FileInfo &other) const { return equals(other, CompareLastModified); } + + bool equals(const FileInfo &other, CompareWhat compareWhat) const; bool operator!=(const FileInfo &other) const { return !operator==(other); } - enum ComparissonOption { - IgnoreContentOfDehydratedFiles, - ContentIsKing, - }; + QDateTime lastModified() const + { + return QDateTime::fromSecsSinceEpoch(_lastModifiedInSecondsUTC, Qt::LocalTime); + } + + QDateTime lastModifiedInUtc() const + { + return QDateTime::fromSecsSinceEpoch(_lastModifiedInSecondsUTC, Qt::UTC); + } + + void setLastModified(const QDateTime &t) + { + _lastModifiedInSecondsUTC = t.toSecsSinceEpoch(); + } - bool equals(const FileInfo &other, ComparissonOption opt) const; + qint64 lastModifiedInSecondsUTC() const + { + return _lastModifiedInSecondsUTC; + } + + void setLastModifiedFromSecondsUTC(qint64 utcSecs) + { + _lastModifiedInSecondsUTC = utcSecs; + } QString path() const; QString absolutePath() const; @@ -190,7 +222,7 @@ public: bool isDir = true; bool isShared = false; OCC::RemotePermissions permissions; // When uset, defaults to everything - QDateTime lastModified = QDateTime::currentDateTimeUtc().addDays(-7); + qint64 _lastModifiedInSecondsUTC = defaultLastModified(); QByteArray etag = generateEtag(); QByteArray fileId = generateFileId(); QByteArray checksums; @@ -208,12 +240,16 @@ public: friend inline QDebug operator<<(QDebug dbg, const FileInfo &fi) { - return dbg << "{ " << fi.path() << ": " - << ", fileSize:" << fi.fileSize - << ", contentSize:" << fi.contentSize - << ", contentChar:" << fi.contentChar - << ", isDehydratedPlaceholder:" << fi.isDehydratedPlaceholder - << ", children:" << fi.children; + return dbg.nospace().noquote() + << "{ '" << fi.path() << "': " + << ", isDir:" << fi.isDir + << QStringLiteral(", lastModified: %1 (%2)").arg(QString::number(fi._lastModifiedInSecondsUTC), fi.lastModifiedInUtc().toString()) + << ", fileSize:" << fi.fileSize + << ", contentSize:" << fi.contentSize + << QStringLiteral(", contentChar: 0x%1").arg(QString::number(int(fi.contentChar), 16)) + << ", isDehydratedPlaceholder:" << fi.isDehydratedPlaceholder + << ", children:" << fi.children + << " }"; } }; @@ -614,11 +650,11 @@ inline void addFilesDbData(QStringList &dest, const FileInfo &fi) { // could include etag, permissions etc, but would need extra work if (fi.isDir) { - dest += QStringLiteral("%1 - %2 %3 %4").arg(fi.name, fi.isDir ? QStringLiteral("dir") : QStringLiteral("file"), QString::number(fi.lastModified.toSecsSinceEpoch()), QString::fromUtf8(fi.fileId)); + dest += QStringLiteral("%1 - %2 %3 %4").arg(fi.name, fi.isDir ? QStringLiteral("dir") : QStringLiteral("file"), QString::number(fi.lastModifiedInSecondsUTC()), QString::fromUtf8(fi.fileId)); 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.contentSize), 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.lastModifiedInSecondsUTC()), QString::fromUtf8(fi.fileId)); } } |