Test that the sync behave well if there are errors while reading the database

This commit is contained in:
Olivier Goffart 2020-11-26 15:27:03 +01:00 committed by Kevin Ottens
parent b79e57d1c1
commit bb9ce8db21
No known key found for this signature in database
GPG key ID: 074BBBCB8DECC9E2
5 changed files with 108 additions and 5 deletions

View file

@ -279,6 +279,13 @@ bool SyncJournalDb::sqlFail(const QString &log, const SqlQuery &query)
bool SyncJournalDb::checkConnect()
{
if (autotestFailCounter >= 0) {
if (!autotestFailCounter--) {
qCInfo(lcDb) << "Error Simulated";
return false;
}
}
if (_db.isOpen()) {
// Unfortunately the sqlite isOpen check can return true even when the underlying storage
// has become unavailable - and then some operations may cause crashes. See #6049
@ -634,7 +641,7 @@ bool SyncJournalDb::updateMetadataTableStructure()
bool re = true;
// check if the file_id column is there and create it if not
if (!checkConnect()) {
if (columns.isEmpty()) {
return false;
}
@ -751,7 +758,10 @@ bool SyncJournalDb::updateMetadataTableStructure()
commitInternal("update database structure: add isE2eEncrypted col");
}
if (!tableColumns("uploadinfo").contains("contentChecksum")) {
auto uploadInfoColumns = tableColumns("uploadinfo");
if (uploadInfoColumns.isEmpty())
return false;
if (!uploadInfoColumns.contains("contentChecksum")) {
SqlQuery query(_db);
query.prepare("ALTER TABLE uploadinfo ADD COLUMN contentChecksum TEXT;");
if (!query.exec()) {
@ -761,7 +771,10 @@ bool SyncJournalDb::updateMetadataTableStructure()
commitInternal("update database structure: add contentChecksum col for uploadinfo");
}
if (!tableColumns("conflicts").contains("basePath")) {
auto conflictsColumns = tableColumns("conflicts");
if (conflictsColumns.isEmpty())
return false;
if (!conflictsColumns.contains("basePath")) {
SqlQuery query(_db);
query.prepare("ALTER TABLE conflicts ADD COLUMN basePath TEXT;");
if (!query.exec()) {
@ -788,8 +801,7 @@ bool SyncJournalDb::updateErrorBlacklistTableStructure()
auto columns = tableColumns("blacklist");
bool re = true;
// check if the file_id column is there and create it if not
if (!checkConnect()) {
if (columns.isEmpty()) {
return false;
}

View file

@ -245,6 +245,13 @@ public:
*/
void markVirtualFileForDownloadRecursively(const QByteArray &path);
/**
* Only used for auto-test:
* when positive, will decrease the counter for every database operation.
* reaching 0 makes the operation fails
*/
int autotestFailCounter = -1;
private:
int getFileRecordCount();
bool updateDatabaseStructure();

View file

@ -58,6 +58,7 @@ nextcloud_add_test(LocalDiscovery "syncenginetestutils.h")
nextcloud_add_test(RemoteDiscovery "syncenginetestutils.h")
nextcloud_add_test(Permissions "syncenginetestutils.h")
nextcloud_add_test(SelectiveSync "syncenginetestutils.h")
nextcloud_add_test(DatabaseError "syncenginetestutils.h")
nextcloud_add_test(LockedFiles "syncenginetestutils.h;../src/gui/lockwatcher.cpp")
nextcloud_add_test(FolderWatcher "${FolderWatcher_SRC}")

View file

@ -12,6 +12,7 @@
#include "filesystem.h"
#include "syncengine.h"
#include "common/syncjournaldb.h"
#include "csync_exclude.h"
#include <QDir>
#include <QNetworkReply>
@ -920,6 +921,8 @@ public:
_journalDb = std::make_unique<OCC::SyncJournalDb>(localPath() + "._sync_test.db");
_syncEngine = std::make_unique<OCC::SyncEngine>(_account, localPath(), "", _journalDb.get());
// Ignore temporary files from the download. (This is in the default exclude list, but we don't load it)
_syncEngine->excludedFiles().addManualExclude("]*.~*");
// A new folder will update the local file state database on first sync.
// To have a state matching what users will encounter, we have to a sync

View file

@ -0,0 +1,80 @@
/*
* 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;
class TestDatabaseError : public QObject
{
Q_OBJECT
private slots:
void testDatabaseError() {
/* This test will make many iteration, at each iteration, the iᵗʰ database access will fail.
* The test ensure that if there is a failure, the next sync recovers. And if there was
* no error, then everything was sync'ed properly.
*/
FileInfo finalState;
for (int count = 0; true; ++count) {
qInfo() << "Starting Iteration" << count;
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
// Do a couple of changes
fakeFolder.remoteModifier().insert("A/a0");
fakeFolder.remoteModifier().appendByte("A/a1");
fakeFolder.remoteModifier().remove("A/a2");
fakeFolder.remoteModifier().rename("S/s1", "S/s1_renamed");
fakeFolder.remoteModifier().mkdir("D");
fakeFolder.remoteModifier().mkdir("D/subdir");
fakeFolder.remoteModifier().insert("D/subdir/file");
fakeFolder.localModifier().insert("B/b0");
fakeFolder.localModifier().appendByte("B/b1");
fakeFolder.remoteModifier().remove("B/b2");
fakeFolder.localModifier().mkdir("NewDir");
fakeFolder.localModifier().rename("C", "NewDir/C");
// Set the counter
fakeFolder.syncJournal().autotestFailCounter = count;
// run the sync
bool result = fakeFolder.syncOnce();
qInfo() << "Result of iteration" << count << "was" << result;
if (fakeFolder.syncJournal().autotestFailCounter >= 0) {
// No error was thrown, we are finished
QVERIFY(result);
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
QCOMPARE(fakeFolder.currentRemoteState(), finalState);
return;
}
if (!result) {
fakeFolder.syncJournal().autotestFailCounter = -1;
// Try again
QVERIFY(fakeFolder.syncOnce());
}
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
if (count == 0) {
finalState = fakeFolder.currentRemoteState();
} else {
// the final state should be the same for every iteration
QCOMPARE(fakeFolder.currentRemoteState(), finalState);
}
}
}
};
QTEST_GUILESS_MAIN(TestDatabaseError)
#include "testdatabaseerror.moc"