From 3da520cdd1de73b09c32fe2aad08e2c3d577550b Mon Sep 17 00:00:00 2001 From: Martin Kleusberg Date: Mon, 21 May 2018 18:27:41 +0200 Subject: [PATCH] Clean up multi threading patch, fix build and some bugs Make strings translatable, remove some more debug code, fix tests, reduce size of patch slightly, remove weird tooltip, don't crash when closing database, simplify code, fix filters, don't link agains pthread on Windows. --- CMakeLists.txt | 6 +- src/ExportDataDialog.cpp | 4 +- src/ExtendedTableWidget.cpp | 4 - src/ImportCsvDialog.cpp | 2 +- src/MainWindow.cpp | 14 +-- src/PlotDock.cpp | 3 +- src/RowLoader.cpp | 9 -- src/sqlitedb.cpp | 18 ++-- src/sqlitedb.h | 4 +- src/sqlitetablemodel.cpp | 43 ++------ src/sqlitetablemodel.h | 12 +-- src/src.pro | 4 +- src/tests/CMakeLists.txt | 27 ++++- src/tests/TestRowCache.cpp | 189 +++++++++++++++++++++++++++++++++++ src/tests/TestRowCache.h | 22 ++++ src/tests/test_row_cache.cpp | 179 --------------------------------- 16 files changed, 275 insertions(+), 265 deletions(-) create mode 100644 src/tests/TestRowCache.cpp create mode 100644 src/tests/TestRowCache.h delete mode 100644 src/tests/test_row_cache.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 17696e00..0a8bba40 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,8 +78,6 @@ endif() add_subdirectory(${QHEXEDIT_DIR}) add_subdirectory(${QCUSTOMPLOT_DIR}) -add_subdirectory(/home/michael/dev/gtest/git/googletest /home/michael/dev/gtest/build) - find_package(Qt5Widgets REQUIRED) find_package(Qt5LinguistTools REQUIRED) find_package(Qt5Network REQUIRED) @@ -292,6 +290,8 @@ if(WIN32) ELSE( MINGW ) set(SQLB_SRC ${SQLB_SRC} "${CMAKE_CURRENT_SOURCE_DIR}/src/winapp.rc") ENDIF( MINGW ) +else() + set(LPTHREAD pthread) endif(WIN32) #enable version check for MacOS @@ -376,7 +376,7 @@ endif() target_link_libraries(${PROJECT_NAME} qhexedit qcustomplot - pthread + ${LPTHREAD} ${QT_LIBRARIES} ${WIN32_STATIC_LINK} ${LIBSQLITE} diff --git a/src/ExportDataDialog.cpp b/src/ExportDataDialog.cpp index f2f21420..19968af1 100644 --- a/src/ExportDataDialog.cpp +++ b/src/ExportDataDialog.cpp @@ -117,7 +117,7 @@ bool ExportDataDialog::exportQueryCsv(const QString& sQuery, const QString& sFil QByteArray utf8Query = sQuery.toUtf8(); sqlite3_stmt *stmt; - auto pDb = pdb.get("exporting CSV"); + auto pDb = pdb.get(tr("exporting CSV")); int status = sqlite3_prepare_v2(pDb.get(), utf8Query.data(), utf8Query.size(), &stmt, nullptr); if(SQLITE_OK == status) { @@ -201,7 +201,7 @@ bool ExportDataDialog::exportQueryJson(const QString& sQuery, const QString& sFi QByteArray utf8Query = sQuery.toUtf8(); sqlite3_stmt *stmt; - auto pDb = pdb.get("exporting JSON"); + auto pDb = pdb.get(tr("exporting JSON")); int status = sqlite3_prepare_v2(pDb.get(), utf8Query.data(), utf8Query.size(), &stmt, nullptr); QJsonArray json_table; diff --git a/src/ExtendedTableWidget.cpp b/src/ExtendedTableWidget.cpp index c6b0a9bd..d55b5da0 100644 --- a/src/ExtendedTableWidget.cpp +++ b/src/ExtendedTableWidget.cpp @@ -1,5 +1,3 @@ -#include - #include "ExtendedTableWidget.h" #include "sqlitetablemodel.h" #include "FilterTableHeader.h" @@ -612,8 +610,6 @@ void ExtendedTableWidget::updateGeometries() void ExtendedTableWidget::vscrollbarChanged(int value) { - //std::cout << "ExtendedTableWidget::vscrollbarChanged " << value << std::endl; - // Cancel if there is no model set yet - this shouldn't happen (because without a model there should be no scrollbar) but just to be sure... if(!model()) return; diff --git a/src/ImportCsvDialog.cpp b/src/ImportCsvDialog.cpp index 9d5fdc92..6a768b24 100644 --- a/src/ImportCsvDialog.cpp +++ b/src/ImportCsvDialog.cpp @@ -607,7 +607,7 @@ bool ImportCsvDialog::importCsv(const QString& fileName, const QString& name) sQuery.chop(1); // Remove last comma sQuery.append(")"); sqlite3_stmt* stmt; - auto pDb = pdb->get("importing CSV"); + auto pDb = pdb->get(tr("importing CSV")); sqlite3_prepare_v2(pDb.get(), sQuery.toUtf8(), sQuery.toUtf8().length(), &stmt, nullptr); // Parse entire file diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 4c5eac0d..d1bfdbbb 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -1,5 +1,3 @@ -#include - #include "MainWindow.h" #include "ui_MainWindow.h" @@ -824,7 +822,7 @@ void MainWindow::setRecordsetLabel() } ui->labelRecordset->setText(txt); - ui->dataTable->setDisabled( m_browseTableModel->rowCountAvailable() == SqliteTableModel::RowCount::Unknown ); + enableEditing(m_browseTableModel->rowCountAvailable() != SqliteTableModel::RowCount::Unknown, 0); } void MainWindow::refresh() @@ -1152,7 +1150,7 @@ void MainWindow::executeQuery() // Execute next statement int tail_length_before = tail_length; const char* qbegin = tail; - auto pDb = db.get("executing query"); + auto pDb = db.get(tr("executing query")); sql3status = sqlite3_prepare_v2(pDb.get(), tail, tail_length, &vm, &tail); QString queryPart = QString::fromUtf8(qbegin, tail - qbegin); tail_length -= (tail - qbegin); @@ -1668,8 +1666,10 @@ void MainWindow::enableEditing(bool enable_edit, bool enable_insert) bool insert = enable_insert && !db.readOnly(); // Apply settings - ui->buttonNewRecord->setEnabled(insert); - ui->buttonDeleteRecord->setEnabled(edit); + if(insert) + ui->buttonNewRecord->setEnabled(insert); + if(edit) + ui->buttonDeleteRecord->setEnabled(edit); ui->dataTable->setEditTriggers(edit ? QAbstractItemView::SelectedClicked | QAbstractItemView::AnyKeyPressed | QAbstractItemView::EditKeyPressed : QAbstractItemView::NoEditTriggers); } @@ -2953,7 +2953,7 @@ void MainWindow::requestCollation(const QString& name, int eTextRep) "If you choose to proceed, be aware bad things can happen to your database.\n" "Create a backup!").arg(name), QMessageBox::Yes | QMessageBox::No); if(reply == QMessageBox::Yes) { - auto pDb = db.get("creating collation"); + auto pDb = db.get(tr("creating collation")); sqlite3_create_collation(pDb.get(), name.toUtf8(), eTextRep, nullptr, collCompare); } } diff --git a/src/PlotDock.cpp b/src/PlotDock.cpp index 3df62579..316541dd 100644 --- a/src/PlotDock.cpp +++ b/src/PlotDock.cpp @@ -424,12 +424,11 @@ void PlotDock::updatePlot(SqliteTableModel* model, BrowseDataTableSettings* sett ui->plotWidget->replot(); // Warn user if not all data has been fetched and hint about the button for loading all the data - if (model->rowCountAvailable() != SqliteTableModel::RowCount::Complete || !model->isCacheComplete()) { + if (model && (model->rowCountAvailable() != SqliteTableModel::RowCount::Complete || !model->isCacheComplete())) { ui->buttonLoadAllData->setEnabled(true); ui->buttonLoadAllData->setStyleSheet("QToolButton {color: white; background-color: rgb(255, 102, 102)}"); ui->buttonLoadAllData->setToolTip(tr("Load all data and redraw plot.\n" "Warning: not all data has been fetched from the table yet due to the partial fetch mechanism.")); - QToolTip::showText(ui->buttonLoadAllData->mapToGlobal(QPoint(0, 0)), ui->buttonLoadAllData->toolTip()); } else { ui->buttonLoadAllData->setEnabled(false); ui->buttonLoadAllData->setStyleSheet(""); diff --git a/src/RowLoader.cpp b/src/RowLoader.cpp index 7acde9fa..cfa48b2a 100644 --- a/src/RowLoader.cpp +++ b/src/RowLoader.cpp @@ -201,8 +201,6 @@ void RowLoader::run () void RowLoader::process (Task & t) { - //std::cout << "RowLoader new task: " << t.row_begin << " --> " << t.row_end << std::endl; - QString sLimitQuery; if(query.startsWith("PRAGMA", Qt::CaseInsensitive) || query.startsWith("EXPLAIN", Qt::CaseInsensitive)) { @@ -254,11 +252,4 @@ void RowLoader::process (Task & t) if(row != t.row_begin) emit fetched(t.token, t.row_begin, row); - -#if 0 - if(t.cancel) - std::cout << "RowLoader task was CANCELLED\n"; - else - std::cout << "RowLoader task done\n"; -#endif } diff --git a/src/sqlitedb.cpp b/src/sqlitedb.cpp index b563d80c..5361550d 100644 --- a/src/sqlitedb.cpp +++ b/src/sqlitedb.cpp @@ -497,7 +497,8 @@ bool DBBrowserDB::close() emit dbChanged(getDirty()); emit structureUpdated(); - return true; //< not cancelled + // Return true to tell the calling function that the closing wasn't cancelled by the user + return true; } DBBrowserDB::db_pointer_type DBBrowserDB::get(QString user) @@ -505,7 +506,7 @@ DBBrowserDB::db_pointer_type DBBrowserDB::get(QString user) if(!_db) return nullptr; - auto lk = waitForDbRelease(); + waitForDbRelease(); db_user = user; db_used = true; @@ -513,10 +514,10 @@ DBBrowserDB::db_pointer_type DBBrowserDB::get(QString user) return db_pointer_type(_db, DatabaseReleaser(this)); } -std::unique_lock DBBrowserDB::waitForDbRelease() +void DBBrowserDB::waitForDbRelease() { if(!_db) - return std::unique_lock(); + return; std::unique_lock lk(m); while(db_used) { @@ -525,8 +526,8 @@ std::unique_lock DBBrowserDB::waitForDbRelease() lk.unlock(); QMessageBox msgBox; - msgBox.setText("The database is currently busy: " + str); - msgBox.setInformativeText("Do you want to abort that other operation?"); + msgBox.setText(tr("The database is currently busy: ") + str); + msgBox.setInformativeText(tr("Do you want to abort that other operation?")); msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); msgBox.setDefaultButton(QMessageBox::No); int ret = msgBox.exec(); @@ -537,8 +538,6 @@ std::unique_lock DBBrowserDB::waitForDbRelease() lk.lock(); cv.wait(lk, [this](){ return !db_used; }); } - - return std::move(lk); } bool DBBrowserDB::dump(const QString& filename, @@ -1035,9 +1034,8 @@ QString DBBrowserDB::addRecord(const sqlb::ObjectIdentifier& tablename) } else { if(table->isWithoutRowidTable()) return pk_value; - else { + else return QString::number(sqlite3_last_insert_rowid(_db)); - } } } diff --git a/src/sqlitedb.h b/src/sqlitedb.h index 84ce8964..6558b657 100644 --- a/src/sqlitedb.h +++ b/src/sqlitedb.h @@ -192,8 +192,8 @@ private: /// wait for release of the DB locked through a previous get(), /// giving users the option to discard running task through a - /// message box. \returns active lock. - std::unique_lock waitForDbRelease (); + /// message box. + void waitForDbRelease(); QString curDBFilename; QString lastErrorMessage; diff --git a/src/sqlitetablemodel.cpp b/src/sqlitetablemodel.cpp index c9a97ee2..4df1de79 100644 --- a/src/sqlitetablemodel.cpp +++ b/src/sqlitetablemodel.cpp @@ -1,4 +1,3 @@ - #include "sqlitetablemodel.h" #include "sqlitedb.h" #include "sqlite.h" @@ -24,7 +23,7 @@ SqliteTableModel::SqliteTableModel(DBBrowserDB& db, QObject* parent, size_t chun , m_encoding(encoding) { worker = new RowLoader( - [this](){ return m_db.get("reading rows"); }, + [this](){ return m_db.get(tr("reading rows")); }, [this](QString stmt){ return m_db.logSQL(stmt, kLogMsg_App); }, m_headers, m_mutexDataCache, m_cache ); @@ -95,8 +94,6 @@ void SqliteTableModel::handleRowCountComplete (int life_id, int num_rows) void SqliteTableModel::reset() { - //std::cout << "\n\nSqliteTableModel::reset()\n"; - clearCache(); m_sTable.clear(); @@ -249,9 +246,7 @@ QVariant SqliteTableModel::data(const QModelIndex &index, int role) const { cached_row = &m_cache.at(index.row()); row_available = true; - } - else - { + } else { blank_data = makeDefaultCacheEntry(); cached_row = &blank_data; row_available = false; @@ -260,17 +255,13 @@ QVariant SqliteTableModel::data(const QModelIndex &index, int role) const if(role == Qt::DisplayRole || role == Qt::EditRole) { if(!row_available) - return decode("loading..."); + return tr("loading..."); if(role == Qt::DisplayRole && cached_row->at(index.column()).isNull()) { return Settings::getValue("databrowser", "null_text").toString(); - } - else if(role == Qt::DisplayRole && nosync_isBinary(index)) - { + } else if(role == Qt::DisplayRole && nosync_isBinary(index)) { return Settings::getValue("databrowser", "blob_text").toString(); - } - else if(role == Qt::DisplayRole) - { + } else if(role == Qt::DisplayRole) { int limit = Settings::getValue("databrowser", "symbol_limit").toInt(); QByteArray displayText = cached_row->at(index.column()); if (displayText.length() > limit) { @@ -282,16 +273,12 @@ QVariant SqliteTableModel::data(const QModelIndex &index, int role) const } else { return decode(cached_row->at(index.column())); } - } - else if(role == Qt::FontRole) - { + } else if(role == Qt::FontRole) { QFont font; if(!row_available || cached_row->at(index.column()).isNull() || nosync_isBinary(index)) font.setItalic(true); return font; - } - else if(role == Qt::ForegroundRole) - { + } else if(role == Qt::ForegroundRole) { if(!row_available) return QColor(100, 100, 100); if(cached_row->at(index.column()).isNull()) @@ -299,9 +286,7 @@ QVariant SqliteTableModel::data(const QModelIndex &index, int role) const else if (nosync_isBinary(index)) return QColor(Settings::getValue("databrowser", "bin_fg_colour").toString()); return QColor(Settings::getValue("databrowser", "reg_fg_colour").toString()); - } - else if (role == Qt::BackgroundRole) - { + } else if (role == Qt::BackgroundRole) { if(!row_available) return QColor(255, 200, 200); if(cached_row->at(index.column()).isNull()) @@ -309,9 +294,7 @@ QVariant SqliteTableModel::data(const QModelIndex &index, int role) const else if (nosync_isBinary(index)) return QColor(Settings::getValue("databrowser", "bin_bg_colour").toString()); return QColor(Settings::getValue("databrowser", "reg_bg_colour").toString()); - } - else if(role == Qt::ToolTipRole) - { + } else if(role == Qt::ToolTipRole) { sqlb::ForeignKeyClause fk = getForeignKeyClause(index.column()-1); if(fk.isSet()) return tr("References %1(%2)\nHold Ctrl+Shift and click to jump there").arg(fk.table()).arg(fk.columns().join(",")); @@ -503,7 +486,6 @@ bool SqliteTableModel::insertRows(int row, int count, const QModelIndex& parent) beginInsertRows(parent, row, row + count - 1); for(unsigned int i = 0; i < tempList.size(); ++i) { - //std::cout << "inserting at " << i + row << std::endl; m_cache.insert(i + row, std::move(tempList.at(i))); m_currentRowCount++; } @@ -691,7 +673,7 @@ void SqliteTableModel::removeCommentsFromQuery(QString& query) QStringList SqliteTableModel::getColumns(std::shared_ptr pDb, const QString& sQuery, QVector& fieldsTypes) { if(!pDb) - pDb = m_db.get("retrieving list of columns"); + pDb = m_db.get(tr("retrieving list of columns")); sqlite3_stmt* stmt; QByteArray utf8Query = sQuery.toUtf8(); @@ -945,11 +927,8 @@ void SqliteTableModel::triggerCacheLoad (int row) const QMutexLocker lk(&m_mutexDataCache); m_cache.smallestNonAvailableRange(row_begin, row_end); - if(row_end != row_begin) { + if(row_end != row_begin) worker->triggerFetch(m_lifeCounter, row_begin, row_end); - } else { - //std::cout << "entire range already loaded\n"; - } } void SqliteTableModel::triggerCacheLoad (int row_begin, int row_end) const diff --git a/src/sqlitetablemodel.h b/src/sqlitetablemodel.h index 5ea08938..b261ded9 100644 --- a/src/sqlitetablemodel.h +++ b/src/sqlitetablemodel.h @@ -1,13 +1,12 @@ #ifndef SQLITETABLEMODEL_H #define SQLITETABLEMODEL_H -#include - #include #include #include #include #include +#include #include "sqlitetypes.h" #include "RowCache.h" @@ -15,7 +14,6 @@ struct sqlite3; class DBBrowserDB; - class SqliteTableModel : public QAbstractTableModel { Q_OBJECT @@ -87,10 +85,10 @@ public: void setTable(const sqlb::ObjectIdentifier& table, int sortColumn = 0, Qt::SortOrder sortOrder = Qt::AscendingOrder, const QVector &display_format = QVector()); void setChunkSize(size_t chunksize); - void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override; const sqlb::ObjectIdentifier& currentTableName() const { return m_sTable; } - Qt::ItemFlags flags(const QModelIndex& index) const; + Qt::ItemFlags flags(const QModelIndex& index) const override; bool isBinary(const QModelIndex& index) const; @@ -118,8 +116,8 @@ signals: void finishedFetch(int fetched_row_begin, int fetched_row_end); protected: - virtual Qt::DropActions supportedDropActions() const; - virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent); + virtual Qt::DropActions supportedDropActions() const override; + virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) override; private: friend class RowLoader; diff --git a/src/src.pro b/src/src.pro index 5ce86d5d..fc79d313 100644 --- a/src/src.pro +++ b/src/src.pro @@ -15,8 +15,8 @@ QMAKE_CXXFLAGS += -std=c++11 CONFIG(unittest) { QT += testlib - HEADERS += tests/testsqlobjects.h tests/TestImport.h tests/TestRegex.h - SOURCES += tests/testsqlobjects.cpp tests/TestImport.cpp tests/TestMain.cpp tests/TestRegex.cpp + HEADERS += tests/testsqlobjects.h tests/TestImport.h tests/TestRegex.h tests/TestRowCache.h + SOURCES += tests/testsqlobjects.cpp tests/TestImport.cpp tests/TestRegex.cpp tests/TestRowCache.cpp } else { SOURCES += main.cpp } diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 26a6e9d5..86611f74 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -1,5 +1,9 @@ include_directories("${CMAKE_CURRENT_BINARY_DIR}" ..) +if(NOT WIN32) + set(LPTHREAD pthread) +endif() + # test-sqlobjects set(TESTSQLOBJECTS_SRC @@ -54,7 +58,7 @@ else() endif() link_directories("${CMAKE_CURRENT_BINARY_DIR}/${QSCINTILLA_DIR}") add_dependencies(test-sqlobjects qscintilla2) -target_link_libraries(test-sqlobjects qscintilla2 pthread) +target_link_libraries(test-sqlobjects qscintilla2 ${LPTHREAD}) add_test(test-sqlobjects test-sqlobjects) # test-import @@ -126,10 +130,23 @@ else() endif() link_directories("${CMAKE_CURRENT_BINARY_DIR}/${QSCINTILLA_DIR}") add_dependencies(test-regex qscintilla2) -target_link_libraries(test-regex qscintilla2 pthread) +target_link_libraries(test-regex qscintilla2 ${LPTHREAD}) add_test(test-regex test-regex) +# test cache -add_executable(test-cache test_row_cache.cpp) -target_link_libraries(test-cache gtest_main) -add_test(NAME test-cache COMMAND test-cache) +set(TESTCACHE_SRC + TestRowCache.cpp +) + +set(TESTCACHE_MOC_HDR + TestRowCache.h +) + +add_executable(test-cache ${TESTCACHE_MOC} ${TESTCACHE_SRC}) + +qt5_use_modules(test-cache Test Core) +set(QT_LIBRARIES "") + +target_link_libraries(test-cache ${QT_LIBRARIES}) +add_test(test-cache test-cache) diff --git a/src/tests/TestRowCache.cpp b/src/tests/TestRowCache.cpp new file mode 100644 index 00000000..610ba2cf --- /dev/null +++ b/src/tests/TestRowCache.cpp @@ -0,0 +1,189 @@ +#include + +#include "TestRowCache.h" +#include "../RowCache.h" + +QTEST_APPLESS_MAIN(TestRowCache) + +TestRowCache::TestRowCache() +{ +} + +TestRowCache::~TestRowCache() +{ +} + +using C = RowCache; + +void TestRowCache::construction() +{ + C c; + + QCOMPARE(c.numSet(), static_cast(0)); + + QVERIFY(c.count(0) == false); + QVERIFY(c.count(1) == false); + QVERIFY(c.count(2) == false); + + QVERIFY_EXCEPTION_THROWN(c.at(0), std::out_of_range); +} + +void TestRowCache::setGet() +{ + C c; + + c.set(1, 10); + c.set(5, 50); + c.set(0, 0); + c.set(6, 60); + c.set(100, 1000); + + QCOMPARE(c.numSet(), static_cast(5)); + QCOMPARE(c.numSegments(), static_cast(4)); // the '0' set after the '1' position does not merge currently + + int cnt = 0; + const C & cc = c; + for(size_t i = 0; i < 200; i++) { + if(c.count(i)) { + QCOMPARE(c.at(i), static_cast(10*i)); + QCOMPARE(cc.at(i), static_cast(10*i)); + cnt++; + } else { + QVERIFY_EXCEPTION_THROWN(c.at(i), std::out_of_range); + QVERIFY_EXCEPTION_THROWN(cc.at(i), std::out_of_range); + } + } + QCOMPARE(cnt, 5); +} + +void TestRowCache::insert() +{ + C c; + + c.insert(3, 30); + QCOMPARE(c.numSet(), static_cast(1)); + QCOMPARE(c.numSegments(), static_cast(1)); + QCOMPARE(c.at(3), 30); + + c.insert(3, 31); + QCOMPARE(c.numSet(), static_cast(2)); + QCOMPARE(c.numSegments(), static_cast(1)); + QCOMPARE(c.at(3), 31); + QCOMPARE(c.at(4), 30); + + c.insert(0, 0); + QCOMPARE(c.numSet(), static_cast(3)); + QCOMPARE(c.numSegments(), static_cast(2)); + QCOMPARE(c.at(0), 0); + QVERIFY_EXCEPTION_THROWN(c.at(3), std::out_of_range); + QCOMPARE(c.at(4), 31); + QCOMPARE(c.at(5), 30); + QVERIFY_EXCEPTION_THROWN(c.at(6), std::out_of_range); + + c.insert(1, 100); + QCOMPARE(c.numSet(), static_cast(4)); + QCOMPARE(c.numSegments(), static_cast(2)); + QCOMPARE(c.at(0), 0); + QCOMPARE(c.at(1), 100); + QCOMPARE(c.at(5), 31); + QCOMPARE(c.at(6), 30); + + c.insert(8, 1); + QCOMPARE(c.numSet(), static_cast(5)); + QCOMPARE(c.numSegments(), static_cast(3)); + QCOMPARE(c.at(0), 0); + QCOMPARE(c.at(1), 100); + QCOMPARE(c.at(5), 31); + QCOMPARE(c.at(6), 30); + QCOMPARE(c.at(8), 1); +} + +void TestRowCache::erase() +{ + C c; + c.insert(3, 30); + c.insert(3, 31); + c.insert(0, 0); + c.insert(8, 1); + QCOMPARE(c.numSet(), static_cast(4)); + QCOMPARE(c.numSegments(), static_cast(3)); + QCOMPARE(c.at(0), 0); + QCOMPARE(c.at(4), 31); + QCOMPARE(c.at(5), 30); + QCOMPARE(c.at(8), 1); + + // erase entire segment + c.erase(0); + QCOMPARE(c.numSet(), static_cast(3)); + QCOMPARE(c.numSegments(), static_cast(2)); + QCOMPARE(c.at(3), 31); + QCOMPARE(c.at(4), 30); + QCOMPARE(c.at(7), 1); + + // erase inside segment + c.erase(4); + QCOMPARE(c.numSet(), static_cast(2)); + QCOMPARE(c.numSegments(), static_cast(2)); + QCOMPARE(c.at(3), 31); + QCOMPARE(c.at(6), 1); + + // erase non-filled row + c.erase(5); + QCOMPARE(c.numSet(), static_cast(2)); + QCOMPARE(c.numSegments(), static_cast(2)); + QCOMPARE(c.at(3), 31); + QCOMPARE(c.at(5), 1); + + c.erase(5); + QCOMPARE(c.numSet(), static_cast(1)); + QCOMPARE(c.numSegments(), static_cast(1)); + QCOMPARE(c.at(3), 31); + + c.erase(3); + QCOMPARE(c.numSet(), static_cast(0)); + QCOMPARE(c.numSegments(), static_cast(0)); +} + +void TestRowCache::smallestNonAvailableRange() +{ + C c; + c.insert(3, 0); + c.insert(3, 0); + c.insert(0, 0); + c.insert(8, 0); + QCOMPARE(c.numSet(), static_cast(4)); + QVERIFY(c.count(0)); + QVERIFY(c.count(4)); + QVERIFY(c.count(5)); + QVERIFY(c.count(8)); + + using P = std::pair; + + auto test = [&](size_t begin, size_t end) { + P p{ begin, end }; + c.smallestNonAvailableRange(p.first, p.second); + return p; + }; + + QCOMPARE(test( 0, 0), P( 0, 0)); + QCOMPARE(test( 0, 1), P( 1, 1)); + QCOMPARE(test( 0, 2), P( 1, 2)); + QCOMPARE(test( 0, 3), P( 1, 3)); + QCOMPARE(test( 0, 4), P( 1, 4)); + QCOMPARE(test( 0, 5), P( 1, 4)); + QCOMPARE(test( 0, 6), P( 1, 4)); + QCOMPARE(test( 0, 7), P( 1, 7)); + QCOMPARE(test( 0, 8), P( 1, 8)); + QCOMPARE(test( 0, 9), P( 1, 8)); + QCOMPARE(test( 0,10), P( 1,10)); + QCOMPARE(test( 1,10), P( 1,10)); + QCOMPARE(test( 2,10), P( 2,10)); + QCOMPARE(test( 3,10), P( 3,10)); + QCOMPARE(test( 4,10), P( 6,10)); + QCOMPARE(test( 5,10), P( 6,10)); + QCOMPARE(test( 6,10), P( 6,10)); + QCOMPARE(test( 7,10), P( 7,10)); + QCOMPARE(test( 8,10), P( 9,10)); + QCOMPARE(test( 9,10), P( 9,10)); + QCOMPARE(test(10,10), P(10,10)); +} diff --git a/src/tests/TestRowCache.h b/src/tests/TestRowCache.h new file mode 100644 index 00000000..beeeee9c --- /dev/null +++ b/src/tests/TestRowCache.h @@ -0,0 +1,22 @@ +#ifndef TESTROWCACHE_H +#define TESTROWCACHE_H + +#include + +class TestRowCache : public QObject +{ + Q_OBJECT + +public: + TestRowCache(); + ~TestRowCache(); + +private slots: + void construction(); + void setGet(); + void insert(); + void erase(); + void smallestNonAvailableRange(); +}; + +#endif diff --git a/src/tests/test_row_cache.cpp b/src/tests/test_row_cache.cpp deleted file mode 100644 index 24e9e54f..00000000 --- a/src/tests/test_row_cache.cpp +++ /dev/null @@ -1,179 +0,0 @@ - -#include "../RowCache.h" - -#include "gtest/gtest.h" - -using C = RowCache; - -TEST(RowCache, Construction) -{ - C c; - - EXPECT_EQ(0u, c.numSet()); - - EXPECT_FALSE(c.count(0)); - EXPECT_FALSE(c.count(1)); - EXPECT_FALSE(c.count(2)); - - EXPECT_THROW(c.at(0), std::out_of_range); -} - -TEST(RowCache, set_get) -{ - C c; - - c.set(1, 10); - c.set(5, 50); - c.set(0, 0); - c.set(6, 60); - c.set(100, 1000); - - EXPECT_EQ(5u, c.numSet()); - EXPECT_EQ(4u, c.numSegments()); // the '0' set after the '1' position does not merge currently - - int cnt = 0; - const C & cc = c; - for(size_t i = 0; i < 200; i++) { - if(c.count(i)) { - EXPECT_EQ(10*i, c.at(i)); - EXPECT_EQ(10*i, cc.at(i)); - cnt++; - } else { - EXPECT_THROW(c.at(i), std::out_of_range); - EXPECT_THROW(cc.at(i), std::out_of_range); - } - } - EXPECT_EQ(5, cnt); -} - -TEST(RowCache, insert) -{ - C c; - - c.insert(3, 30); - EXPECT_EQ(1u, c.numSet()); - EXPECT_EQ(1u, c.numSegments()); - EXPECT_EQ(30, c.at(3)); - - c.insert(3, 31); - EXPECT_EQ(2u, c.numSet()); - EXPECT_EQ(1u, c.numSegments()); - EXPECT_EQ(31, c.at(3)); - EXPECT_EQ(30, c.at(4)); - - c.insert(0, 0); - EXPECT_EQ(3u, c.numSet()); - EXPECT_EQ(2u, c.numSegments()); - EXPECT_EQ(0, c.at(0)); - EXPECT_THROW(c.at(3), std::out_of_range); - EXPECT_EQ(31, c.at(4)); - EXPECT_EQ(30, c.at(5)); - EXPECT_THROW(c.at(6), std::out_of_range); - - c.insert(1, 100); - EXPECT_EQ(4u, c.numSet()); - EXPECT_EQ(2u, c.numSegments()); - EXPECT_EQ(0, c.at(0)); - EXPECT_EQ(100, c.at(1)); - EXPECT_EQ(31, c.at(5)); - EXPECT_EQ(30, c.at(6)); - - c.insert(8, 1); - EXPECT_EQ(5u, c.numSet()); - EXPECT_EQ(3u, c.numSegments()); - EXPECT_EQ(0, c.at(0)); - EXPECT_EQ(100, c.at(1)); - EXPECT_EQ(31, c.at(5)); - EXPECT_EQ(30, c.at(6)); - EXPECT_EQ(1, c.at(8)); -} - -TEST(RowCache, erase) -{ - C c; - c.insert(3, 30); - c.insert(3, 31); - c.insert(0, 0); - c.insert(8, 1); - EXPECT_EQ(4u, c.numSet()); - EXPECT_EQ(3u, c.numSegments()); - EXPECT_EQ(0, c.at(0)); - EXPECT_EQ(31, c.at(4)); - EXPECT_EQ(30, c.at(5)); - EXPECT_EQ(1, c.at(8)); - - // erase entire segment - c.erase(0); - EXPECT_EQ(3u, c.numSet()); - EXPECT_EQ(2u, c.numSegments()); - EXPECT_EQ(31, c.at(3)); - EXPECT_EQ(30, c.at(4)); - EXPECT_EQ(1, c.at(7)); - - // erase inside segment - c.erase(4); - EXPECT_EQ(2u, c.numSet()); - EXPECT_EQ(2u, c.numSegments()); - EXPECT_EQ(31, c.at(3)); - EXPECT_EQ(1, c.at(6)); - - // erase non-filled row - c.erase(5); - EXPECT_EQ(2u, c.numSet()); - EXPECT_EQ(2u, c.numSegments()); - EXPECT_EQ(31, c.at(3)); - EXPECT_EQ(1, c.at(5)); - - c.erase(5); - EXPECT_EQ(1u, c.numSet()); - EXPECT_EQ(1u, c.numSegments()); - EXPECT_EQ(31, c.at(3)); - - c.erase(3); - EXPECT_EQ(0u, c.numSet()); - EXPECT_EQ(0u, c.numSegments()); -} - -TEST(RowCache, smallestNonAvailableRange) -{ - C c; - c.insert(3, 0); - c.insert(3, 0); - c.insert(0, 0); - c.insert(8, 0); - EXPECT_EQ(4u, c.numSet()); - EXPECT_TRUE(c.count(0)); - EXPECT_TRUE(c.count(4)); - EXPECT_TRUE(c.count(5)); - EXPECT_TRUE(c.count(8)); - - using P = std::pair; - - auto test = [&](int begin, int end) { - P p{ begin, end }; - c.smallestNonAvailableRange(p.first, p.second); - return p; - }; - - EXPECT_EQ(P( 0, 0), test( 0, 0)); - EXPECT_EQ(P( 1, 1), test( 0, 1)); - EXPECT_EQ(P( 1, 2), test( 0, 2)); - EXPECT_EQ(P( 1, 3), test( 0, 3)); - EXPECT_EQ(P( 1, 4), test( 0, 4)); - EXPECT_EQ(P( 1, 4), test( 0, 5)); - EXPECT_EQ(P( 1, 4), test( 0, 6)); - EXPECT_EQ(P( 1, 7), test( 0, 7)); - EXPECT_EQ(P( 1, 8), test( 0, 8)); - EXPECT_EQ(P( 1, 8), test( 0, 9)); - EXPECT_EQ(P( 1,10), test( 0,10)); - EXPECT_EQ(P( 1,10), test( 1,10)); - EXPECT_EQ(P( 2,10), test( 2,10)); - EXPECT_EQ(P( 3,10), test( 3,10)); - EXPECT_EQ(P( 6,10), test( 4,10)); - EXPECT_EQ(P( 6,10), test( 5,10)); - EXPECT_EQ(P( 6,10), test( 6,10)); - EXPECT_EQ(P( 7,10), test( 7,10)); - EXPECT_EQ(P( 9,10), test( 8,10)); - EXPECT_EQ(P( 9,10), test( 9,10)); - EXPECT_EQ(P(10,10), test(10,10)); -}