diff options
author | Eike Ziller <eike.ziller@qt.io> | 2020-06-08 16:04:15 +0200 |
---|---|---|
committer | Eike Ziller <eike.ziller@qt.io> | 2020-06-08 16:04:15 +0200 |
commit | c644e7c80aa752c6154fa36fdc3ce47e0e17e758 (patch) | |
tree | 7aea6f2d15477c7dc0b0e4aa9e00156920c65b1d /tests/unit | |
parent | bfcd1149eb688707a5495ce63f39e93bcf0c0a54 (diff) | |
parent | 42cad8970819c650c73f5daa4b6ea2f775ff7ad0 (diff) |
Merge remote-tracking branch 'origin/qds-1.59'
Conflicts:
src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp
src/plugins/qmlpreview/qmlpreviewconnectionmanager.cpp
Change-Id: Ifa7c66330c1995378280cdb4c57c30015dc11b68
Diffstat (limited to 'tests/unit')
25 files changed, 2174 insertions, 258 deletions
diff --git a/tests/unit/unittest/commandlinebuilder-test.cpp b/tests/unit/unittest/commandlinebuilder-test.cpp index 08c4e32f76..ee16438039 100644 --- a/tests/unit/unittest/commandlinebuilder-test.cpp +++ b/tests/unit/unittest/commandlinebuilder-test.cpp @@ -52,8 +52,8 @@ public: CommandLineBuilder() { cppProjectInfo.language = Utils::Language::Cxx; } public: - ClangBackEnd::PchTask emptyProjectInfo{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; - ClangBackEnd::PchTask cppProjectInfo{1, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; + ClangBackEnd::PchTask emptyProjectInfo{0, {}, {}, {}, {}, {}, {}, {}, {}, {}}; + ClangBackEnd::PchTask cppProjectInfo{1, {}, {}, {}, {}, {}, {}, {}, {}, {}}; }; template <> diff --git a/tests/unit/unittest/createtablesqlstatementbuilder-test.cpp b/tests/unit/unittest/createtablesqlstatementbuilder-test.cpp index 9a56777a17..0e92b06897 100644 --- a/tests/unit/unittest/createtablesqlstatementbuilder-test.cpp +++ b/tests/unit/unittest/createtablesqlstatementbuilder-test.cpp @@ -30,20 +30,40 @@ namespace { +using Sqlite::Column; using Sqlite::ColumnType; -using Sqlite::Contraint; +using Sqlite::ConstraintType; +using Sqlite::Enforment; +using Sqlite::ForeignKey; +using Sqlite::ForeignKeyAction; using Sqlite::JournalMode; using Sqlite::OpenMode; -using Sqlite::Column; +using Sqlite::PrimaryKey; using Sqlite::SqliteColumns; - using Sqlite::SqlStatementBuilderException; +using Sqlite::Unique; class CreateTableSqlStatementBuilder : public ::testing::Test { protected: - void bindValues(); - static SqliteColumns createColumns(); + void bindValues() + { + builder.clear(); + builder.setTableName("test"); + builder.addColumn("id", ColumnType::Integer, {PrimaryKey{}}); + builder.addColumn("name", ColumnType::Text); + builder.addColumn("number", ColumnType::Numeric); + } + + static SqliteColumns createColumns() + { + SqliteColumns columns; + columns.emplace_back("", "id", ColumnType::Integer, Sqlite::Constraints{PrimaryKey{}}); + columns.emplace_back("", "name", ColumnType::Text); + columns.emplace_back("", "number", ColumnType::Numeric); + + return columns; + } protected: Sqlite::CreateTableSqlStatementBuilder builder; @@ -157,7 +177,7 @@ TEST_F(CreateTableSqlStatementBuilder, UniqueContraint) builder.clear(); builder.setTableName("test"); - builder.addColumn("id", ColumnType::Integer, Contraint::Unique); + builder.addColumn("id", ColumnType::Integer, {Unique{}}); ASSERT_THAT(builder.sqlStatement(), "CREATE TABLE test(id INTEGER UNIQUE)"); @@ -167,7 +187,7 @@ TEST_F(CreateTableSqlStatementBuilder, IfNotExitsModifier) { builder.clear(); builder.setTableName("test"); - builder.addColumn("id", ColumnType::Integer, Contraint::NoConstraint); + builder.addColumn("id", ColumnType::Integer, {}); builder.setUseIfNotExists(true); @@ -179,7 +199,7 @@ TEST_F(CreateTableSqlStatementBuilder, TemporaryTable) { builder.clear(); builder.setTableName("test"); - builder.addColumn("id", ColumnType::Integer, Contraint::NoConstraint); + builder.addColumn("id", ColumnType::Integer, {}); builder.setUseTemporaryTable(true); @@ -187,23 +207,340 @@ TEST_F(CreateTableSqlStatementBuilder, TemporaryTable) "CREATE TEMPORARY TABLE test(id INTEGER)"); } -void CreateTableSqlStatementBuilder::bindValues() +TEST_F(CreateTableSqlStatementBuilder, ForeignKeyWithoutColumn) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", ColumnType::Integer, {ForeignKey{"otherTable", ""}}); + + ASSERT_THAT(builder.sqlStatement(), "CREATE TABLE test(id INTEGER REFERENCES otherTable)"); +} + +TEST_F(CreateTableSqlStatementBuilder, ForeignKeyWithColumn) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", ColumnType::Integer, {ForeignKey{"otherTable", "otherColumn"}}); + + ASSERT_THAT(builder.sqlStatement(), + "CREATE TABLE test(id INTEGER REFERENCES otherTable(otherColumn))"); +} + +TEST_F(CreateTableSqlStatementBuilder, ForeignKeyUpdateNoAction) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", ColumnType::Integer, {ForeignKey{"otherTable", "otherColumn"}}); + + ASSERT_THAT(builder.sqlStatement(), + "CREATE TABLE test(id INTEGER REFERENCES otherTable(otherColumn))"); +} + +TEST_F(CreateTableSqlStatementBuilder, ForeignKeyUpdateRestrict) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", + ColumnType::Integer, + {ForeignKey{"otherTable", "otherColumn", ForeignKeyAction::Restrict}}); + + ASSERT_THAT( + builder.sqlStatement(), + "CREATE TABLE test(id INTEGER REFERENCES otherTable(otherColumn) ON UPDATE RESTRICT)"); +} + +TEST_F(CreateTableSqlStatementBuilder, ForeignKeyUpdateSetNull) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", + ColumnType::Integer, + {ForeignKey{"otherTable", "otherColumn", ForeignKeyAction::SetNull}}); + + ASSERT_THAT( + builder.sqlStatement(), + "CREATE TABLE test(id INTEGER REFERENCES otherTable(otherColumn) ON UPDATE SET NULL)"); +} + +TEST_F(CreateTableSqlStatementBuilder, ForeignKeyUpdateSetDefault) { builder.clear(); builder.setTableName("test"); - builder.addColumn("id", ColumnType::Integer, Contraint::PrimaryKey); - builder.addColumn("name", ColumnType::Text); - builder.addColumn("number",ColumnType:: Numeric); + + builder.addColumn("id", + ColumnType::Integer, + {ForeignKey{"otherTable", "otherColumn", ForeignKeyAction::SetDefault}}); + + ASSERT_THAT( + builder.sqlStatement(), + "CREATE TABLE test(id INTEGER REFERENCES otherTable(otherColumn) ON UPDATE SET DEFAULT)"); } -SqliteColumns CreateTableSqlStatementBuilder::createColumns() +TEST_F(CreateTableSqlStatementBuilder, ForeignKeyUpdateCascade) { - SqliteColumns columns; - columns.emplace_back("id", ColumnType::Integer, Contraint::PrimaryKey); - columns.emplace_back("name", ColumnType::Text); - columns.emplace_back("number", ColumnType::Numeric); + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", + ColumnType::Integer, + {ForeignKey{"otherTable", "otherColumn", ForeignKeyAction::Cascade}}); - return columns; + ASSERT_THAT( + builder.sqlStatement(), + "CREATE TABLE test(id INTEGER REFERENCES otherTable(otherColumn) ON UPDATE CASCADE)"); } +TEST_F(CreateTableSqlStatementBuilder, ForeignKeyDeleteNoAction) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", ColumnType::Integer, {ForeignKey{"otherTable", "otherColumn"}}); + + ASSERT_THAT(builder.sqlStatement(), + "CREATE TABLE test(id INTEGER REFERENCES otherTable(otherColumn))"); +} + +TEST_F(CreateTableSqlStatementBuilder, ForeignKeyDeleteRestrict) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", + ColumnType::Integer, + {ForeignKey{"otherTable", "otherColumn", {}, ForeignKeyAction::Restrict}}); + + ASSERT_THAT( + builder.sqlStatement(), + "CREATE TABLE test(id INTEGER REFERENCES otherTable(otherColumn) ON DELETE RESTRICT)"); +} + +TEST_F(CreateTableSqlStatementBuilder, ForeignKeyDeleteSetNull) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", + ColumnType::Integer, + {ForeignKey{"otherTable", "otherColumn", {}, ForeignKeyAction::SetNull}}); + + ASSERT_THAT( + builder.sqlStatement(), + "CREATE TABLE test(id INTEGER REFERENCES otherTable(otherColumn) ON DELETE SET NULL)"); +} + +TEST_F(CreateTableSqlStatementBuilder, ForeignKeyDeleteSetDefault) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", + ColumnType::Integer, + {ForeignKey{"otherTable", "otherColumn", {}, ForeignKeyAction::SetDefault}}); + + ASSERT_THAT( + builder.sqlStatement(), + "CREATE TABLE test(id INTEGER REFERENCES otherTable(otherColumn) ON DELETE SET DEFAULT)"); +} + +TEST_F(CreateTableSqlStatementBuilder, ForeignKeyDeleteCascade) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", + ColumnType::Integer, + {ForeignKey{"otherTable", "otherColumn", {}, ForeignKeyAction::Cascade}}); + + ASSERT_THAT( + builder.sqlStatement(), + "CREATE TABLE test(id INTEGER REFERENCES otherTable(otherColumn) ON DELETE CASCADE)"); +} + +TEST_F(CreateTableSqlStatementBuilder, ForeignKeyDeleteAndUpdateAction) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", + ColumnType::Integer, + {ForeignKey{"otherTable", + "otherColumn", + ForeignKeyAction::SetDefault, + ForeignKeyAction::Cascade}}); + + ASSERT_THAT(builder.sqlStatement(), + "CREATE TABLE test(id INTEGER REFERENCES otherTable(otherColumn) ON UPDATE SET " + "DEFAULT ON DELETE CASCADE)"); +} + +TEST_F(CreateTableSqlStatementBuilder, ForeignKeyDeferred) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", + ColumnType::Integer, + {ForeignKey{"otherTable", + "otherColumn", + ForeignKeyAction::SetDefault, + ForeignKeyAction::Cascade, + Enforment::Deferred}}); + + ASSERT_THAT(builder.sqlStatement(), + "CREATE TABLE test(id INTEGER REFERENCES otherTable(otherColumn) ON UPDATE SET " + "DEFAULT ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)"); } + +TEST_F(CreateTableSqlStatementBuilder, NotNullConstraint) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", ColumnType::Integer, {Sqlite::NotNull{}}); + + ASSERT_THAT(builder.sqlStatement(), "CREATE TABLE test(id INTEGER NOT NULL)"); +} + +TEST_F(CreateTableSqlStatementBuilder, NotNullAndUniqueConstraint) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", ColumnType::Integer, {Sqlite::Unique{}, Sqlite::NotNull{}}); + + ASSERT_THAT(builder.sqlStatement(), "CREATE TABLE test(id INTEGER UNIQUE NOT NULL)"); +} + +TEST_F(CreateTableSqlStatementBuilder, Check) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", ColumnType::Text, {Sqlite::Check{"id != ''"}}); + + ASSERT_THAT(builder.sqlStatement(), "CREATE TABLE test(id TEXT CHECK (id != ''))"); +} + +TEST_F(CreateTableSqlStatementBuilder, DefaultValueInt) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", ColumnType::Integer, {Sqlite::DefaultValue{1LL}}); + + ASSERT_THAT(builder.sqlStatement(), "CREATE TABLE test(id INTEGER DEFAULT 1)"); +} + +TEST_F(CreateTableSqlStatementBuilder, DefaultValueFloat) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", ColumnType::Real, {Sqlite::DefaultValue{1.1}}); + + ASSERT_THAT(builder.sqlStatement(), "CREATE TABLE test(id REAL DEFAULT 1.100000)"); +} + +TEST_F(CreateTableSqlStatementBuilder, DefaultValueString) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", ColumnType::Text, {Sqlite::DefaultValue{"foo"}}); + + ASSERT_THAT(builder.sqlStatement(), "CREATE TABLE test(id TEXT DEFAULT 'foo')"); +} + +TEST_F(CreateTableSqlStatementBuilder, DefaultExpression) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", + ColumnType::Integer, + {Sqlite::DefaultExpression{"SELECT name FROM foo WHERE id=?"}}); + + ASSERT_THAT(builder.sqlStatement(), + "CREATE TABLE test(id INTEGER DEFAULT (SELECT name FROM foo WHERE id=?))"); +} + +TEST_F(CreateTableSqlStatementBuilder, Collation) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", ColumnType::Text, {Sqlite::Collate{"unicode"}}); + + ASSERT_THAT(builder.sqlStatement(), "CREATE TABLE test(id TEXT COLLATE unicode)"); +} + +TEST_F(CreateTableSqlStatementBuilder, GeneratedAlwaysStored) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", + ColumnType::Text, + {Sqlite::GeneratedAlways{"SELECT name FROM foo WHERE id=?", + Sqlite::GeneratedAlwaysStorage::Stored}}); + + ASSERT_THAT( + builder.sqlStatement(), + "CREATE TABLE test(id TEXT GENERATED ALWAYS AS (SELECT name FROM foo WHERE id=?) STORED)"); +} + +TEST_F(CreateTableSqlStatementBuilder, GeneratedAlwaysVirtual) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", + ColumnType::Text, + {Sqlite::GeneratedAlways{"SELECT name FROM foo WHERE id=?", + Sqlite::GeneratedAlwaysStorage::Virtual}}); + + ASSERT_THAT(builder.sqlStatement(), + "CREATE TABLE test(id TEXT GENERATED ALWAYS AS (SELECT name FROM foo WHERE id=?) " + "VIRTUAL)"); +} + +TEST_F(CreateTableSqlStatementBuilder, PrimaryKeyAutoincrement) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", ColumnType::Integer, {Sqlite::PrimaryKey{Sqlite::AutoIncrement::Yes}}); + + ASSERT_THAT(builder.sqlStatement(), "CREATE TABLE test(id INTEGER PRIMARY KEY AUTOINCREMENT)"); +} + +TEST_F(CreateTableSqlStatementBuilder, BlobType) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("data", ColumnType::Blob); + + ASSERT_THAT(builder.sqlStatement(), "CREATE TABLE test(data BLOB)"); +} + +TEST_F(CreateTableSqlStatementBuilder, TablePrimaryKeyConstaint) +{ + builder.clear(); + builder.setTableName("test"); + builder.addColumn("id", ColumnType::Integer); + builder.addColumn("text", ColumnType::Text); + + builder.addConstraint(Sqlite::TablePrimaryKey{{"id, text"}}); + auto statement = builder.sqlStatement(); + + ASSERT_THAT(statement, "CREATE TABLE test(id INTEGER, text TEXT, PRIMARY KEY(id, text))"); +} + +} // namespace diff --git a/tests/unit/unittest/data/sqlite_database.db b/tests/unit/unittest/data/sqlite_database.db Binary files differindex 9c5879579e..5a7284d087 100644 --- a/tests/unit/unittest/data/sqlite_database.db +++ b/tests/unit/unittest/data/sqlite_database.db diff --git a/tests/unit/unittest/google-using-declarations.h b/tests/unit/unittest/google-using-declarations.h index e8bbd52800..1a92ff3388 100644 --- a/tests/unit/unittest/google-using-declarations.h +++ b/tests/unit/unittest/google-using-declarations.h @@ -37,21 +37,28 @@ using testing::AnyOf; using testing::Assign; using testing::ByMove; using testing::ByRef; -using testing::Contains; using testing::ContainerEq; +using testing::Contains; using testing::ElementsAre; +using testing::Eq; using testing::Field; +using testing::Ge; +using testing::Gt; using testing::HasSubstr; using testing::InSequence; using testing::Invoke; using testing::IsEmpty; using testing::IsNull; +using testing::Le; +using testing::Lt; using testing::Matcher; using testing::Mock; using testing::MockFunction; +using testing::Ne; using testing::NiceMock; -using testing::NotNull; using testing::Not; +using testing::NotNull; +using testing::Optional; using testing::Pair; using testing::PrintToString; using testing::Property; @@ -64,10 +71,4 @@ using testing::StrEq; using testing::Throw; using testing::TypedEq; using testing::UnorderedElementsAre; - -using testing::Eq; -using testing::Ge; -using testing::Gt; -using testing::Le; -using testing::Lt; -using testing::Ne; +using testing::VariantWith; diff --git a/tests/unit/unittest/gtest-creator-printing.cpp b/tests/unit/unittest/gtest-creator-printing.cpp index 62561d279b..0f79e048b9 100644 --- a/tests/unit/unittest/gtest-creator-printing.cpp +++ b/tests/unit/unittest/gtest-creator-printing.cpp @@ -58,6 +58,7 @@ #include <sourcedependency.h> #include <sourcelocationentry.h> #include <sourcelocationscontainer.h> +#include <sqlitesessionchangeset.h> #include <sqlitevalue.h> #include <symbol.h> #include <symbolentry.h> @@ -67,6 +68,8 @@ #include <usedmacro.h> #include <utils/link.h> +#include <sqlite3ext.h> + namespace { ClangBackEnd::FilePathCaching *filePathCache = nullptr; } @@ -321,6 +324,90 @@ std::ostream &operator<<(std::ostream &out, const Value &value) return out << ")"; } + +namespace { +Utils::SmallStringView operationText(int operation) +{ + switch (operation) { + case SQLITE_INSERT: + return "INSERT"; + case SQLITE_UPDATE: + return "UPDATE"; + case SQLITE_DELETE: + return "DELETE"; + } + + return {}; +} + +std::ostream &operator<<(std::ostream &out, sqlite3_changeset_iter *iter) +{ + out << "("; + + const char *tableName = nullptr; + int columns = 0; + int operation = 0; + sqlite3_value *value = nullptr; + + sqlite3changeset_op(iter, &tableName, &columns, &operation, 0); + + out << operationText(operation) << " " << tableName << " {"; + + if (operation == SQLITE_UPDATE || operation == SQLITE_DELETE) { + out << "Old: ["; + + for (int i = 0; i < columns; i++) { + sqlite3changeset_old(iter, i, &value); + + if (value) + out << " " << sqlite3_value_text(value); + else + out << " -"; + } + out << "]"; + } + + if (operation == SQLITE_UPDATE) + out << ", "; + + if (operation == SQLITE_UPDATE || operation == SQLITE_INSERT) { + out << "New: ["; + for (int i = 0; i < columns; i++) { + sqlite3changeset_new(iter, i, &value); + + if (value) + out << " " << sqlite3_value_text(value); + else + out << " -"; + } + out << "]"; + } + + out << "})"; + + return out; +} +} // namespace + +std::ostream &operator<<(std::ostream &out, const SessionChangeSet &changeset) +{ + sqlite3_changeset_iter *iter = nullptr; + sqlite3changeset_start(&iter, changeset.size, const_cast<void *>(changeset.data)); + + out << "ChangeSets(["; + + if (SQLITE_ROW == sqlite3changeset_next(iter)) { + out << iter; + while (SQLITE_ROW == sqlite3changeset_next(iter)) + out << ", " << iter; + } + + sqlite3changeset_finalize(iter); + + out << "])"; + + return out; +} } // namespace Sqlite namespace ClangBackEnd { diff --git a/tests/unit/unittest/gtest-creator-printing.h b/tests/unit/unittest/gtest-creator-printing.h index b91c8d46be..c791290ae2 100644 --- a/tests/unit/unittest/gtest-creator-printing.h +++ b/tests/unit/unittest/gtest-creator-printing.h @@ -66,8 +66,10 @@ void PrintTo(const TextRange &range, ::std::ostream *os); namespace Sqlite { class Value; +class SessionChangeSet; std::ostream &operator<<(std::ostream &out, const Value &value); +std::ostream &operator<<(std::ostream &out, const SessionChangeSet &changeset); } // namespace Sqlite namespace ProjectExplorer { @@ -348,4 +350,24 @@ std::ostream &operator<<(std::ostream &out, const Diagnostic &diag); } // namespace Internal } // namespace CppTools +namespace std { +template<typename T> +ostream &operator<<(ostream &out, const vector<T> &vector) +{ + out << "["; + + for (auto current = vector.begin(); current != vector.end(); ++current) { + out << *current; + + if (std::next(current) != vector.end()) + out << ", "; + } + + out << "]"; + + return out; +} + +} // namespace std + void setFilePathCache(ClangBackEnd::FilePathCaching *filePathCache); diff --git a/tests/unit/unittest/gtest-llvm-printing.cpp b/tests/unit/unittest/gtest-llvm-printing.cpp index c78b4f9eea..467f4a5261 100644 --- a/tests/unit/unittest/gtest-llvm-printing.cpp +++ b/tests/unit/unittest/gtest-llvm-printing.cpp @@ -23,6 +23,8 @@ ** ****************************************************************************/ +#include "gtest-creator-printing.h" + #include <gtest/gtest-printers.h> #include <utils/smallstringio.h> diff --git a/tests/unit/unittest/lastchangedrowid-test.cpp b/tests/unit/unittest/lastchangedrowid-test.cpp new file mode 100644 index 0000000000..07b29bee57 --- /dev/null +++ b/tests/unit/unittest/lastchangedrowid-test.cpp @@ -0,0 +1,471 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "googletest.h" + +#include "mocksqlitedatabase.h" + +#include <lastchangedrowid.h> + +namespace { + +class LastChangedRowId : public testing::Test +{ +protected: + NiceMock<MockSqliteDatabase> mockSqliteDatabase; + Sqlite::LastChangedRowId lastRowId{mockSqliteDatabase, "main", "foo"}; +}; + +TEST_F(LastChangedRowId, SetUpdateHookInContructor) +{ + EXPECT_CALL(mockSqliteDatabase, setUpdateHook(_)); + + Sqlite::LastChangedRowId lastRowId{mockSqliteDatabase, "main", "foo"}; +} + +TEST_F(LastChangedRowId, ResetUpdateHookInDestructor) +{ + EXPECT_CALL(mockSqliteDatabase, resetUpdateHook()); +} + +TEST_F(LastChangedRowId, GetMinusOneAsRowIdIfNoCallbackWasCalled) +{ + ASSERT_THAT(lastRowId.lastRowId, -1); +} + +TEST_F(LastChangedRowId, CallbackSetsLastRowId) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 42); + + ASSERT_THAT(lastRowId.lastRowId, 42); +} + +TEST_F(LastChangedRowId, CallbackChecksDatabaseName) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 33); + + lastRowId.callback(Sqlite::ChangeType::Update, "temp", "foo", 42); + + ASSERT_THAT(lastRowId.lastRowId, 33); +} + +TEST_F(LastChangedRowId, CallbackChecksTableName) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 33); + + lastRowId.callback(Sqlite::ChangeType::Update, "main", "bar", 42); + + ASSERT_THAT(lastRowId.lastRowId, 33); +} + +TEST_F(LastChangedRowId, LastCallSetsRowId) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 42); + lastRowId.callback(Sqlite::ChangeType::Insert, "main", "foo", 33); + + lastRowId.callback(Sqlite::ChangeType::Delete, "main", "foo", 66); + + ASSERT_THAT(lastRowId.lastRowId, 66); +} + +TEST_F(LastChangedRowId, TakeLastRowId) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 42); + + auto id = lastRowId.takeLastRowId(); + + ASSERT_THAT(id, 42); +} + +TEST_F(LastChangedRowId, TakeLastRowIdResetsRowIdToMinusOne) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 42); + lastRowId.takeLastRowId(); + + auto id = lastRowId.takeLastRowId(); + + ASSERT_THAT(id, -1); +} + +class LastChangedRowIdWithTwoTables : public testing::Test +{ +protected: + NiceMock<MockSqliteDatabase> mockSqliteDatabase; + + Sqlite::LastChangedRowId lastRowId{mockSqliteDatabase, "main", "foo", "bar"}; +}; + +TEST_F(LastChangedRowIdWithTwoTables, SetUpdateHookInContructor) +{ + EXPECT_CALL(mockSqliteDatabase, setUpdateHook(_)); + + Sqlite::LastChangedRowId lastRowId{mockSqliteDatabase, "main", "foo"}; +} + +TEST_F(LastChangedRowIdWithTwoTables, ResetUpdateHookInDestructor) +{ + EXPECT_CALL(mockSqliteDatabase, resetUpdateHook()); +} + +TEST_F(LastChangedRowIdWithTwoTables, GetMinusOneAsRowIdIfNoCallbackWasCalled) +{ + ASSERT_THAT(lastRowId.lastRowId, -1); +} + +TEST_F(LastChangedRowIdWithTwoTables, CallbackSetsLastRowIdFirstTable) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 42); + + ASSERT_THAT(lastRowId.lastRowId, 42); +} + +TEST_F(LastChangedRowIdWithTwoTables, CallbackSetsLastRowIdSecondTable) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "bar", 66); + + ASSERT_THAT(lastRowId.lastRowId, 66); +} + +TEST_F(LastChangedRowIdWithTwoTables, CallbackChecksDatabaseName) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 33); + + lastRowId.callback(Sqlite::ChangeType::Update, "temp", "foo", 42); + + ASSERT_THAT(lastRowId.lastRowId, 33); +} + +TEST_F(LastChangedRowIdWithTwoTables, CallbackChecksTableName) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 33); + + lastRowId.callback(Sqlite::ChangeType::Update, "main", "zoo", 42); + + ASSERT_THAT(lastRowId.lastRowId, 33); +} + +TEST_F(LastChangedRowIdWithTwoTables, LastCallSetsRowId) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 42); + + lastRowId.callback(Sqlite::ChangeType::Delete, "main", "bar", 66); + + ASSERT_THAT(lastRowId.lastRowId, 66); +} + +TEST_F(LastChangedRowIdWithTwoTables, TakeLastRowId) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 42); + + auto id = lastRowId.takeLastRowId(); + + ASSERT_THAT(id, 42); +} + +TEST_F(LastChangedRowIdWithTwoTables, TakeLastRowIdResetsRowIdToMinusOne) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 42); + lastRowId.takeLastRowId(); + + auto id = lastRowId.takeLastRowId(); + + ASSERT_THAT(id, -1); +} + +class LastChangedRowIdWithThreeTables : public testing::Test +{ +protected: + NiceMock<MockSqliteDatabase> mockSqliteDatabase; + + Sqlite::LastChangedRowId lastRowId{mockSqliteDatabase, "main", "foo", "bar", "too"}; +}; + +TEST_F(LastChangedRowIdWithThreeTables, SetUpdateHookInContructor) +{ + EXPECT_CALL(mockSqliteDatabase, setUpdateHook(_)); + + Sqlite::LastChangedRowId lastRowId{mockSqliteDatabase, "main", "foo"}; +} + +TEST_F(LastChangedRowIdWithThreeTables, ResetUpdateHookInDestructor) +{ + EXPECT_CALL(mockSqliteDatabase, resetUpdateHook()); +} + +TEST_F(LastChangedRowIdWithThreeTables, GetMinusOneAsRowIdIfNoCallbackWasCalled) +{ + ASSERT_THAT(lastRowId.lastRowId, -1); +} + +TEST_F(LastChangedRowIdWithThreeTables, CallbackSetsLastRowIdFirstTable) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 42); + + ASSERT_THAT(lastRowId.lastRowId, 42); +} + +TEST_F(LastChangedRowIdWithThreeTables, CallbackSetsLastRowIdSecondTable) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "bar", 42); + + ASSERT_THAT(lastRowId.lastRowId, 42); +} + +TEST_F(LastChangedRowIdWithThreeTables, CallbackSetsLastRowIdThirdTable) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "too", 42); + + ASSERT_THAT(lastRowId.lastRowId, 42); +} + +TEST_F(LastChangedRowIdWithThreeTables, CallbackChecksDatabaseName) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 33); + + lastRowId.callback(Sqlite::ChangeType::Update, "temp", "foo", 42); + + ASSERT_THAT(lastRowId.lastRowId, 33); +} + +TEST_F(LastChangedRowIdWithThreeTables, CallbackChecksTableName) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 33); + + lastRowId.callback(Sqlite::ChangeType::Update, "main", "zoo", 42); + + ASSERT_THAT(lastRowId.lastRowId, 33); +} + +TEST_F(LastChangedRowIdWithThreeTables, LastCallSetsRowId) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "bar", 42); + lastRowId.callback(Sqlite::ChangeType::Insert, "main", "too", 33); + + lastRowId.callback(Sqlite::ChangeType::Delete, "main", "too", 66); + + ASSERT_THAT(lastRowId.lastRowId, 66); +} + +TEST_F(LastChangedRowIdWithThreeTables, TakeLastRowId) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 42); + + auto id = lastRowId.takeLastRowId(); + + ASSERT_THAT(id, 42); +} + +TEST_F(LastChangedRowIdWithThreeTables, TakeLastRowIdResetsRowIdToMinusOne) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 42); + lastRowId.takeLastRowId(); + + auto id = lastRowId.takeLastRowId(); + + ASSERT_THAT(id, -1); +} + +class LastChangedRowIdWithNoDatabaseAndTable : public testing::Test +{ +protected: + NiceMock<MockSqliteDatabase> mockSqliteDatabase; + Sqlite::LastChangedRowId lastRowId{mockSqliteDatabase}; +}; + +TEST_F(LastChangedRowIdWithNoDatabaseAndTable, SetUpdateHookInContructor) +{ + EXPECT_CALL(mockSqliteDatabase, setUpdateHook(_)); + + Sqlite::LastChangedRowId lastRowId{mockSqliteDatabase, "main", "foo"}; +} + +TEST_F(LastChangedRowIdWithNoDatabaseAndTable, ResetUpdateHookInDestructor) +{ + EXPECT_CALL(mockSqliteDatabase, resetUpdateHook()); +} + +TEST_F(LastChangedRowIdWithNoDatabaseAndTable, GetMinusOneAsRowIdIfNoCallbackWasCalled) +{ + ASSERT_THAT(lastRowId.lastRowId, -1); +} + +TEST_F(LastChangedRowIdWithNoDatabaseAndTable, CallbackSetsLastRowId) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 42); + + ASSERT_THAT(lastRowId.lastRowId, 42); +} + +TEST_F(LastChangedRowIdWithNoDatabaseAndTable, CallbackDoNotChecksDatabaseName) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 33); + + lastRowId.callback(Sqlite::ChangeType::Update, "temp", "foo", 42); + + ASSERT_THAT(lastRowId.lastRowId, 42); +} + +TEST_F(LastChangedRowIdWithNoDatabaseAndTable, CallbackDoNotChecksTableName) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 33); + + lastRowId.callback(Sqlite::ChangeType::Update, "main", "bar", 42); + + ASSERT_THAT(lastRowId.lastRowId, 42); +} + +TEST_F(LastChangedRowIdWithNoDatabaseAndTable, LastCallSetsRowId) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 42); + lastRowId.callback(Sqlite::ChangeType::Insert, "main", "foo", 33); + + lastRowId.callback(Sqlite::ChangeType::Delete, "main", "foo", 66); + + ASSERT_THAT(lastRowId.lastRowId, 66); +} + +TEST_F(LastChangedRowIdWithNoDatabaseAndTable, TakeLastRowId) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 42); + + auto id = lastRowId.takeLastRowId(); + + ASSERT_THAT(id, 42); +} + +TEST_F(LastChangedRowIdWithNoDatabaseAndTable, TakeLastRowIdResetsRowIdToMinusOne) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 42); + lastRowId.takeLastRowId(); + + auto id = lastRowId.takeLastRowId(); + + ASSERT_THAT(id, -1); +} + +class LastChangedRowIdWithNoTable : public testing::Test +{ +protected: + NiceMock<MockSqliteDatabase> mockSqliteDatabase; + Sqlite::LastChangedRowId lastRowId{mockSqliteDatabase, "main"}; +}; + +TEST_F(LastChangedRowIdWithNoTable, SetUpdateHookInContructor) +{ + EXPECT_CALL(mockSqliteDatabase, setUpdateHook(_)); + + Sqlite::LastChangedRowId lastRowId{mockSqliteDatabase, "main", "foo"}; +} + +TEST_F(LastChangedRowIdWithNoTable, ResetUpdateHookInDestructor) +{ + EXPECT_CALL(mockSqliteDatabase, resetUpdateHook()); +} + +TEST_F(LastChangedRowIdWithNoTable, GetMinusOneAsRowIdIfNoCallbackWasCalled) +{ + ASSERT_THAT(lastRowId.lastRowId, -1); +} + +TEST_F(LastChangedRowIdWithNoTable, CallbackSetsLastRowId) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 42); + + ASSERT_THAT(lastRowId.lastRowId, 42); +} + +TEST_F(LastChangedRowIdWithNoTable, CallbackChecksDatabaseName) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 33); + + lastRowId.callback(Sqlite::ChangeType::Update, "temp", "foo", 42); + + ASSERT_THAT(lastRowId.lastRowId, 33); +} + +TEST_F(LastChangedRowIdWithNoTable, CallbackDoNotChecksTableName) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 33); + + lastRowId.callback(Sqlite::ChangeType::Update, "main", "bar", 42); + + ASSERT_THAT(lastRowId.lastRowId, 42); +} + +TEST_F(LastChangedRowIdWithNoTable, LastCallSetsRowId) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 42); + lastRowId.callback(Sqlite::ChangeType::Insert, "main", "foo", 33); + + lastRowId.callback(Sqlite::ChangeType::Delete, "main", "foo", 66); + + ASSERT_THAT(lastRowId.lastRowId, 66); +} + +TEST_F(LastChangedRowIdWithNoTable, TakeLastRowId) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 42); + + auto id = lastRowId.takeLastRowId(); + + ASSERT_THAT(id, 42); +} + +TEST_F(LastChangedRowIdWithNoTable, TakeLastRowIdResetsRowIdToMinusOne) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 42); + lastRowId.takeLastRowId(); + + auto id = lastRowId.takeLastRowId(); + + ASSERT_THAT(id, -1); +} + +TEST_F(LastChangedRowIdWithNoTable, LastRowIdIsNotValidForNegativeValues) +{ + auto isValid = lastRowId.lastRowIdIsValid(); + + ASSERT_FALSE(isValid); +} + +TEST_F(LastChangedRowIdWithNoTable, LastRowIdIsValidForNull) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 0); + + auto isValid = lastRowId.lastRowIdIsValid(); + + ASSERT_TRUE(isValid); +} + +TEST_F(LastChangedRowIdWithNoTable, LastRowIdIsValidForPositiveValues) +{ + lastRowId.callback(Sqlite::ChangeType::Update, "main", "foo", 777); + + auto isValid = lastRowId.lastRowIdIsValid(); + + ASSERT_TRUE(isValid); +} + +} // namespace diff --git a/tests/unit/unittest/mocksqlitedatabase.h b/tests/unit/unittest/mocksqlitedatabase.h index 05f6f4e958..c627f995a4 100644 --- a/tests/unit/unittest/mocksqlitedatabase.h +++ b/tests/unit/unittest/mocksqlitedatabase.h @@ -59,5 +59,13 @@ public: void (bool)); MOCK_METHOD0(walCheckpointFull, void()); + + MOCK_METHOD1(setUpdateHook, void(Sqlite::DatabaseInterface::UpdateCallback &)); + + MOCK_METHOD0(resetUpdateHook, void()); + + MOCK_METHOD0(applyAndUpdateSessions, void()); + + MOCK_METHOD1(setAttachedTables, void(const Utils::SmallStringVector &tables)); }; diff --git a/tests/unit/unittest/mocksqlitestatement.h b/tests/unit/unittest/mocksqlitestatement.h index 26f5b0de6e..7be7f3f05e 100644 --- a/tests/unit/unittest/mocksqlitestatement.h +++ b/tests/unit/unittest/mocksqlitestatement.h @@ -52,9 +52,10 @@ public: MOCK_METHOD2(bind, void (int, double)); MOCK_METHOD2(bind, void (int, Utils::SmallStringView)); MOCK_METHOD2(bind, void (int, long)); - MOCK_CONST_METHOD1(bindingIndexForName, int (Utils::SmallStringView name)); - MOCK_METHOD1(prepare, void (Utils::SmallStringView sqlStatement)); + MOCK_METHOD1(prepare, void(Utils::SmallStringView sqlStatement)); + + MOCK_METHOD1(checkColumnCount, void(int)); }; template<> diff --git a/tests/unit/unittest/mocksqlitetransactionbackend.h b/tests/unit/unittest/mocksqlitetransactionbackend.h index 0cfeee2892..363ad573d7 100644 --- a/tests/unit/unittest/mocksqlitetransactionbackend.h +++ b/tests/unit/unittest/mocksqlitetransactionbackend.h @@ -40,4 +40,7 @@ public: MOCK_METHOD0(rollback, void ()); MOCK_METHOD0(lock, void ()); MOCK_METHOD0(unlock, void ()); + MOCK_METHOD0(immediateSessionBegin, void()); + MOCK_METHOD0(sessionCommit, void()); + MOCK_METHOD0(sessionRollback, void()); }; diff --git a/tests/unit/unittest/pchcreator-test.cpp b/tests/unit/unittest/pchcreator-test.cpp index ad5b57b4f5..c7268a51d8 100644 --- a/tests/unit/unittest/pchcreator-test.cpp +++ b/tests/unit/unittest/pchcreator-test.cpp @@ -137,7 +137,6 @@ protected: sorted({id(main2Path), id(generatedFilePath)}), {}, {}, - {}, {{TESTDATA_DIR "/builddependencycollector/system", 2, IncludeSearchPathType::BuiltIn}, {TESTDATA_DIR "/builddependencycollector/external", 1, IncludeSearchPathType::System}}, {{TESTDATA_DIR "/builddependencycollector/project", 1, IncludeSearchPathType::User}}, @@ -374,7 +373,6 @@ TEST_F(PchCreatorVerySlowTest, FaultyProjectPartPchForCreatesFaultyPchForPchTask {}, {{"DEFINE", "1", 1}}, {}, - {}, {{TESTDATA_DIR "/builddependencycollector/external", 1, IncludeSearchPathType::System}}, {{TESTDATA_DIR "/builddependencycollector/project", 1, IncludeSearchPathType::User}}}; diff --git a/tests/unit/unittest/pchtaskqueue-test.cpp b/tests/unit/unittest/pchtaskqueue-test.cpp index f516858862..598a2a5132 100644 --- a/tests/unit/unittest/pchtaskqueue-test.cpp +++ b/tests/unit/unittest/pchtaskqueue-test.cpp @@ -71,7 +71,6 @@ protected: {3, 4}, {6, 7}, {{"YI", "1", 1}, {"SAN", "3", 3}}, - {{"LIANG", 0}, {"YI", 1}}, {"--yi"}, systemIncludeSearchPaths, projectIncludeSearchPaths}; @@ -82,7 +81,6 @@ protected: {3, 4}, {6, 7}, {{"YI", "1", 1}, {"SAN", "3", 3}}, - {{"LIANG", 0}, {"YI", 1}}, {"--yi"}, systemIncludeSearchPaths, projectIncludeSearchPaths}; @@ -93,7 +91,6 @@ protected: {4, 7}, {8, 9}, {{"YI", "1", 1}, {"SAN", "3", 3}}, - {{"LIANG", 0}, {"YI", 1}}, {"--yi"}, systemIncludeSearchPaths, projectIncludeSearchPaths}; @@ -104,7 +101,6 @@ protected: {3, 4}, {6, 7}, {{"YI", "1", 1}, {"SAN", "3", 3}}, - {{"LIANG", 0}, {"YI", 1}}, {"--yi"}, systemIncludeSearchPaths, projectIncludeSearchPaths}; @@ -115,7 +111,6 @@ protected: {13, 14}, {16, 17}, {{"SE", "4", 4}, {"WU", "5", 5}}, - {{"ER", 2}, {"SAN", 3}}, {"--yi"}, systemIncludeSearchPaths, projectIncludeSearchPaths}; @@ -126,7 +121,6 @@ protected: {13, 14}, {16, 17}, {{"SE", "4", 4}, {"WU", "5", 5}}, - {{"ER", 2}, {"SAN", 3}}, {"--yi"}, systemIncludeSearchPaths, projectIncludeSearchPaths}; @@ -137,7 +131,6 @@ protected: {23, 24}, {26, 27}, {{"SE", "4", 4}, {"WU", "5", 5}}, - {{"ER", 2}, {"SAN", 3}}, {"--yi"}, systemIncludeSearchPaths, projectIncludeSearchPaths}; @@ -148,7 +141,6 @@ protected: {23, 24}, {26, 27}, {{"SE", "4", 4}, {"WU", "5", 5}}, - {{"ER", 2}, {"SAN", 3}}, {"--yi"}, systemIncludeSearchPaths, projectIncludeSearchPaths}; @@ -159,7 +151,6 @@ protected: {3, 4}, {5, 8}, {{"YI", "1", 1}, {"SAN", "3", 3}}, - {{"LIANG", 0}, {"YI", 1}}, {"--yi"}, systemIncludeSearchPaths, projectIncludeSearchPaths}; diff --git a/tests/unit/unittest/pchtasksmerger-test.cpp b/tests/unit/unittest/pchtasksmerger-test.cpp index 28f9593ad4..a816a7f5cb 100644 --- a/tests/unit/unittest/pchtasksmerger-test.cpp +++ b/tests/unit/unittest/pchtasksmerger-test.cpp @@ -59,7 +59,6 @@ protected: {}, {}, {{"YI", "1", 1}, {"SAN", "3", 3}}, - {"YI", "LIANG"}, {"--yi"}, {{"/system/path", 2, IncludeSearchPathType::System}, {"/builtin/path2", 3, IncludeSearchPathType::BuiltIn}, @@ -73,7 +72,6 @@ protected: {31, 32}, {41, 42}, {{"SE", "4", 4}, {"WU", "5", 5}}, - {"ER", "SAN"}, {"--yi"}, {{"/system/path", 1, IncludeSearchPathType::System}, {"/builtin/path", 2, IncludeSearchPathType::BuiltIn}}, @@ -86,7 +84,6 @@ protected: {}, {}, {{"SE", "4", 4}, {"WU", "5", 5}}, - {"ER", "SAN"}, {"--yi"}, {{"/system/path", 2, IncludeSearchPathType::System}, {"/builtin/path", 3, IncludeSearchPathType::BuiltIn}, @@ -100,7 +97,6 @@ protected: {31, 32}, {41, 42}, {{"SE", "4", 4}, {"WU", "5", 5}}, - {"ER", "SAN"}, {"--yi"}, {{"/system/path", 2, IncludeSearchPathType::System}, {"/builtin/path", 3, IncludeSearchPathType::BuiltIn}, @@ -114,7 +110,6 @@ protected: {}, {}, {{"YI", "2", 1}, {"SAN", "3", 3}}, - {"YI", "LIANG"}, {"--yi"}, {{"/system/path", 2, IncludeSearchPathType::System}, {"/builtin/path", 3, IncludeSearchPathType::BuiltIn}, diff --git a/tests/unit/unittest/sqlitecolumn-test.cpp b/tests/unit/unittest/sqlitecolumn-test.cpp index 3daa32bd23..9753457c94 100644 --- a/tests/unit/unittest/sqlitecolumn-test.cpp +++ b/tests/unit/unittest/sqlitecolumn-test.cpp @@ -29,70 +29,93 @@ namespace { -using testing::AllOf; -using testing::Contains; -using testing::Property; - using Sqlite::ColumnType; -using Sqlite::Contraint; +using Sqlite::ConstraintType; using Sqlite::JournalMode; using Sqlite::OpenMode; using Column = Sqlite::Column; +using Sqlite::Enforment; +using Sqlite::ForeignKey; +using Sqlite::ForeignKeyAction; using Sqlite::SqliteColumns; class SqliteColumn : public ::testing::Test { protected: - void SetUp() override; - Sqlite::Column column; }; -TEST_F(SqliteColumn, ChangeName) +TEST_F(SqliteColumn, DefaultConstruct) { - column.setName("Claudia"); - - ASSERT_THAT(column.name(), "Claudia"); -} - -TEST_F(SqliteColumn, DefaultType) -{ - ASSERT_THAT(column.type(), ColumnType::Numeric); + ASSERT_THAT(column, + AllOf(Field(&Column::name, IsEmpty()), + Field(&Column::tableName, IsEmpty()), + Field(&Column::type, ColumnType::Numeric), + Field(&Column::constraints, IsEmpty()))); } -TEST_F(SqliteColumn, ChangeType) +TEST_F(SqliteColumn, Clear) { - column.setType(ColumnType::Text); + column.name = "foo"; + column.name = "foo"; + column.type = ColumnType::Text; + column.constraints = {Sqlite::PrimaryKey{}}; - ASSERT_THAT(column.type(), ColumnType::Text); -} + column.clear(); -TEST_F(SqliteColumn, DefaultConstraint) -{ - ASSERT_THAT(column.constraint(), Contraint::NoConstraint); + ASSERT_THAT(column, + AllOf(Field(&Column::name, IsEmpty()), + Field(&Column::tableName, IsEmpty()), + Field(&Column::type, ColumnType::Numeric), + Field(&Column::constraints, IsEmpty()))); } -TEST_F(SqliteColumn, SetConstraint) +TEST_F(SqliteColumn, Constructor) { - column.setContraint(Contraint::PrimaryKey); + column = Sqlite::Column{"table", + "column", + ColumnType::Text, + {ForeignKey{"referencedTable", + "referencedColumn", + ForeignKeyAction::SetNull, + ForeignKeyAction::Cascade, + Enforment::Deferred}}}; - ASSERT_THAT(column.constraint(), Contraint::PrimaryKey); + ASSERT_THAT(column, + AllOf(Field(&Column::name, Eq("column")), + Field(&Column::tableName, Eq("table")), + Field(&Column::type, ColumnType::Text), + Field(&Column::constraints, + ElementsAre(VariantWith<ForeignKey>( + AllOf(Field(&ForeignKey::table, Eq("referencedTable")), + Field(&ForeignKey::column, Eq("referencedColumn")), + Field(&ForeignKey::updateAction, ForeignKeyAction::SetNull), + Field(&ForeignKey::deleteAction, ForeignKeyAction::Cascade), + Field(&ForeignKey::enforcement, Enforment::Deferred))))))); } -TEST_F(SqliteColumn, GetColumnDefinition) +TEST_F(SqliteColumn, FlatConstructor) { - column.setName("Claudia"); + column = Sqlite::Column{"table", + "column", + ColumnType::Text, + {ForeignKey{"referencedTable", + "referencedColumn", + ForeignKeyAction::SetNull, + ForeignKeyAction::Cascade, + Enforment::Deferred}}}; ASSERT_THAT(column, - AllOf( - Property(&Column::name, "Claudia"), - Property(&Column::type, ColumnType::Numeric), - Property(&Column::constraint, Contraint::NoConstraint))); -} - -void SqliteColumn::SetUp() -{ - column.clear(); + AllOf(Field(&Column::name, Eq("column")), + Field(&Column::tableName, Eq("table")), + Field(&Column::type, ColumnType::Text), + Field(&Column::constraints, + ElementsAre(VariantWith<ForeignKey>( + AllOf(Field(&ForeignKey::table, Eq("referencedTable")), + Field(&ForeignKey::column, Eq("referencedColumn")), + Field(&ForeignKey::updateAction, ForeignKeyAction::SetNull), + Field(&ForeignKey::deleteAction, ForeignKeyAction::Cascade), + Field(&ForeignKey::enforcement, Enforment::Deferred))))))); } -} +} // namespace diff --git a/tests/unit/unittest/sqlitedatabase-test.cpp b/tests/unit/unittest/sqlitedatabase-test.cpp index 66c1aa8387..5d7fdb1e54 100644 --- a/tests/unit/unittest/sqlitedatabase-test.cpp +++ b/tests/unit/unittest/sqlitedatabase-test.cpp @@ -28,6 +28,7 @@ #include "spydummy.h" #include <sqlitedatabase.h> +#include <sqlitereadstatement.h> #include <sqlitetable.h> #include <sqlitewritestatement.h> #include <utf8string.h> @@ -50,27 +51,36 @@ using Sqlite::Table; class SqliteDatabase : public ::testing::Test { protected: - void SetUp() override + SqliteDatabase() { database.setJournalMode(JournalMode::Memory); database.setDatabaseFilePath(databaseFilePath); auto &table = database.addTable(); table.setName("test"); + table.addColumn("id", Sqlite::ColumnType::Integer, {Sqlite::PrimaryKey{}}); table.addColumn("name"); database.open(); } - void TearDown() override + ~SqliteDatabase() { if (database.isOpen()) database.close(); } + std::vector<Utils::SmallString> names() const + { + return Sqlite::ReadStatement("SELECT name FROM test", database).values<Utils::SmallString>(8); + } + +protected: SpyDummy spyDummy; QString databaseFilePath{":memory:"}; - Sqlite::Database database; + mutable Sqlite::Database database; Sqlite::TransactionInterface &transactionInterface = database; + MockFunction<void(Sqlite::ChangeType tupe, char const *, char const *, long long)> callbackMock; + Sqlite::Database::UpdateCallback callback = callbackMock.AsStdFunction(); }; TEST_F(SqliteDatabase, SetDatabaseFilePath) @@ -220,4 +230,118 @@ TEST_F(SqliteDatabase, Rollback) ASSERT_NO_THROW(transactionInterface.rollback()); } +TEST_F(SqliteDatabase, SetUpdateHookSet) +{ + database.setUpdateHook(callback); + + EXPECT_CALL(callbackMock, Call(_, _, _, _)); + Sqlite::WriteStatement("INSERT INTO test(name) VALUES (?)", database).write(42); +} + +TEST_F(SqliteDatabase, SetNullUpdateHook) +{ + database.setUpdateHook(callback); + Sqlite::Database::UpdateCallback newCallback; + + database.setUpdateHook(newCallback); + + EXPECT_CALL(callbackMock, Call(_, _, _, _)).Times(0); + Sqlite::WriteStatement("INSERT INTO test(name) VALUES (?)", database).write(42); +} + +TEST_F(SqliteDatabase, ResetUpdateHook) +{ + database.setUpdateHook(callback); + Sqlite::Database::UpdateCallback newCallback; + + database.resetUpdateHook(); + + EXPECT_CALL(callbackMock, Call(_, _, _, _)).Times(0); + Sqlite::WriteStatement("INSERT INTO test(name) VALUES (?)", database).write(42); +} + +TEST_F(SqliteDatabase, DeleteUpdateHookCall) +{ + Sqlite::WriteStatement("INSERT INTO test(name) VALUES (?)", database).write(42); + database.setUpdateHook(callback); + + EXPECT_CALL(callbackMock, Call(Eq(Sqlite::ChangeType::Delete), _, _, _)); + + Sqlite::WriteStatement("DELETE FROM test WHERE name = 42", database).execute(); } + +TEST_F(SqliteDatabase, InsertUpdateHookCall) +{ + database.setUpdateHook(callback); + + EXPECT_CALL(callbackMock, Call(Eq(Sqlite::ChangeType::Insert), _, _, _)); + + Sqlite::WriteStatement("INSERT INTO test(name) VALUES (?)", database).write(42); +} + +TEST_F(SqliteDatabase, UpdateUpdateHookCall) +{ + database.setUpdateHook(callback); + + EXPECT_CALL(callbackMock, Call(Eq(Sqlite::ChangeType::Insert), _, _, _)); + + Sqlite::WriteStatement("INSERT INTO test(name) VALUES (?)", database).write(42); +} + +TEST_F(SqliteDatabase, RowIdUpdateHookCall) +{ + database.setUpdateHook(callback); + + EXPECT_CALL(callbackMock, Call(_, _, _, Eq(42))); + + Sqlite::WriteStatement("INSERT INTO test(rowid, name) VALUES (?,?)", database).write(42, "foo"); +} + +TEST_F(SqliteDatabase, DatabaseUpdateHookCall) +{ + database.setUpdateHook(callback); + + EXPECT_CALL(callbackMock, Call(_, StrEq("main"), _, _)); + + Sqlite::WriteStatement("INSERT INTO test(name) VALUES (?)", database).write(42); +} + +TEST_F(SqliteDatabase, TableUpdateHookCall) +{ + database.setUpdateHook(callback); + + EXPECT_CALL(callbackMock, Call(_, _, StrEq("test"), _)); + + Sqlite::WriteStatement("INSERT INTO test(name) VALUES (?)", database).write(42); +} + +TEST_F(SqliteDatabase, SessionsCommit) +{ + database.setAttachedTables({"test"}); + Sqlite::WriteStatement("INSERT INTO test(id, name) VALUES (?,?)", database).write(1, "foo"); + + Sqlite::ImmediateSessionTransaction transaction{database}; + Sqlite::WriteStatement("INSERT INTO test(id, name) VALUES (?,?)", database).write(2, "bar"); + transaction.commit(); + Sqlite::WriteStatement("INSERT OR REPLACE INTO test(id, name) VALUES (?,?)", database).write(2, "hoo"); + database.applyAndUpdateSessions(); + + ASSERT_THAT(names(), ElementsAre("foo", "bar")); +} + +TEST_F(SqliteDatabase, SessionsRollback) +{ + database.setAttachedTables({"test"}); + Sqlite::WriteStatement("INSERT INTO test(id, name) VALUES (?,?)", database).write(1, "foo"); + + { + Sqlite::ImmediateSessionTransaction transaction{database}; + Sqlite::WriteStatement("INSERT INTO test(id, name) VALUES (?,?)", database).write(2, "bar"); + } + Sqlite::WriteStatement("INSERT OR REPLACE INTO test(id, name) VALUES (?,?)", database).write(2, "hoo"); + database.applyAndUpdateSessions(); + + ASSERT_THAT(names(), ElementsAre("foo", "hoo")); +} + +} // namespace diff --git a/tests/unit/unittest/sqlitedatabasebackend-test.cpp b/tests/unit/unittest/sqlitedatabasebackend-test.cpp index 40227148bc..2390bd4a3c 100644 --- a/tests/unit/unittest/sqlitedatabasebackend-test.cpp +++ b/tests/unit/unittest/sqlitedatabasebackend-test.cpp @@ -39,10 +39,9 @@ namespace { using Backend = Sqlite::DatabaseBackend; using Sqlite::ColumnType; -using Sqlite::Contraint; +using Sqlite::ConstraintType; using Sqlite::JournalMode; using Sqlite::OpenMode; -using Sqlite::TextEncoding; using Sqlite::Exception; using Sqlite::WriteStatement; @@ -111,49 +110,6 @@ TEST_F(SqliteDatabaseBackend, PersistJournalMode) ASSERT_THAT(databaseBackend.journalMode(), JournalMode::Persist); } -TEST_F(SqliteDatabaseBackend, DefaultTextEncoding) -{ - ASSERT_THAT(databaseBackend.textEncoding(), TextEncoding::Utf8); -} - -TEST_F(SqliteDatabaseBackend, Utf16TextEncoding) -{ - databaseBackend.setTextEncoding(TextEncoding::Utf16); - - ASSERT_THAT(databaseBackend.textEncoding(), TextEncoding::Utf16); -} - -TEST_F(SqliteDatabaseBackend, Utf16beTextEncoding) -{ - databaseBackend.setTextEncoding(TextEncoding::Utf16be); - - ASSERT_THAT(databaseBackend.textEncoding(),TextEncoding::Utf16be); -} - -TEST_F(SqliteDatabaseBackend, Utf16leTextEncoding) -{ - databaseBackend.setTextEncoding(TextEncoding::Utf16le); - - ASSERT_THAT(databaseBackend.textEncoding(), TextEncoding::Utf16le); -} - -TEST_F(SqliteDatabaseBackend, Utf8TextEncoding) -{ - databaseBackend.setTextEncoding(TextEncoding::Utf8); - - ASSERT_THAT(databaseBackend.textEncoding(), TextEncoding::Utf8); -} - -TEST_F(SqliteDatabaseBackend, TextEncodingCannotBeChangedAfterTouchingDatabase) -{ - databaseBackend.setJournalMode(JournalMode::Memory); - - databaseBackend.execute("CREATE TABLE text(name, number)"); - - ASSERT_THROW(databaseBackend.setTextEncoding(TextEncoding::Utf16), - Sqlite::PragmaValueNotSet); -} - TEST_F(SqliteDatabaseBackend, OpenModeReadOnly) { auto mode = Backend::openMode(OpenMode::ReadOnly); diff --git a/tests/unit/unittest/sqlitesessions-test.cpp b/tests/unit/unittest/sqlitesessions-test.cpp new file mode 100644 index 0000000000..f10dde1bc8 --- /dev/null +++ b/tests/unit/unittest/sqlitesessions-test.cpp @@ -0,0 +1,443 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "googletest.h" + +#include <sqlitedatabase.h> +#include <sqlitereadstatement.h> +#include <sqlitesessionchangeset.h> +#include <sqlitesessions.h> +#include <sqlitetransaction.h> +#include <sqlitewritestatement.h> + +#include <ostream> + +namespace { + +using Sqlite::SessionChangeSet; +using Sqlite::SessionChangeSets; + +class DatabaseExecute +{ +public: + DatabaseExecute(Utils::SmallStringView sqlStatement, Sqlite::Database &database) + { + database.execute(sqlStatement); + } +}; + +class Data +{ +public: + Data(Sqlite::ValueView name, Sqlite::ValueView number, Sqlite::ValueView value) + : name(name) + , number(number) + , value(value) + {} + + Sqlite::Value name; + Sqlite::Value number; + Sqlite::Value value; +}; + +std::ostream &operator<<(std::ostream &out, const Data &data) +{ + return out << "(" << data.name << ", " << data.number << " " << data.value << ")"; +} + +MATCHER_P3(HasData, + name, + number, + value, + std::string(negation ? "hasn't " : "has ") + PrintToString(name) + ", " + + PrintToString(number) + ", " + PrintToString(value)) +{ + const Data &data = arg; + + return data.name == name && data.number == number && data.value == value; +} + +class Tag +{ +public: + Tag(Sqlite::ValueView name, Sqlite::ValueView tag) + : name(name) + , tag(tag) + {} + + Sqlite::Value name; + Sqlite::Value tag; +}; + +std::ostream &operator<<(std::ostream &out, const Tag &tag) +{ + return out << "(" << tag.name << ", " << tag.tag << ")"; +} + +MATCHER_P2(HasTag, + name, + tag, + std::string(negation ? "hasn't " : "has ") + PrintToString(name) + ", " + PrintToString(tag)) +{ + const Tag &t = arg; + + return t.name == name && t.tag == tag; +} + +class Sessions : public testing::Test +{ +protected: + Sessions() { sessions.setAttachedTables({"data", "tags"}); } + + std::vector<Data> fetchData() { return selectData.values<Data, 3>(8); } + std::vector<Tag> fetchTags() { return selectTags.values<Tag, 2>(8); } + SessionChangeSets fetchChangeSets() { return selectChangeSets.values<SessionChangeSet>(8); } + +protected: + Sqlite::Database database{":memory:", Sqlite::JournalMode::Memory}; + DatabaseExecute createTable{"CREATE TABLE data(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT " + "UNIQUE, number NUMERIC, value NUMERIC)", + database}; + DatabaseExecute createTable2{"CREATE TABLE tags(id INTEGER PRIMARY KEY AUTOINCREMENT, dataId " + "INTEGER NOT NULL REFERENCES data ON DELETE CASCADE DEFERRABLE " + "INITIALLY DEFERRED, tag NUMERIC)", + database}; + Sqlite::Sessions sessions{database, "main", "testsessions"}; + Sqlite::WriteStatement insertData{"INSERT INTO data(name, number, value) VALUES (?1, ?2, ?3) " + "ON CONFLICT (name) DO UPDATE SET (number, value) = (?2, ?3)", + database}; + Sqlite::WriteStatement updateNumber{"UPDATE data SET number = ?002 WHERE name=?001", database}; + Sqlite::WriteStatement updateValue{"UPDATE data SET value = ?002 WHERE name=?001", database}; + Sqlite::WriteStatement deleteData{"DELETE FROM data WHERE name=?", database}; + Sqlite::WriteStatement deleteTag{ + "DELETE FROM tags WHERE dataId=(SELECT id FROM data WHERE name=?)", database}; + Sqlite::WriteStatement insertTag{ + "INSERT INTO tags(dataId, tag) VALUES ((SELECT id FROM data WHERE name=?1), ?2) ", database}; + Sqlite::ReadStatement selectData{"SELECT name, number, value FROM data", database}; + Sqlite::ReadStatement selectTags{"SELECT name, tag FROM tags JOIN data ON data.id=tags.dataId", + database}; + Sqlite::ReadStatement selectChangeSets{"SELECT changeset FROM testsessions", database}; +}; + +TEST_F(Sessions, DontThrowForCommittingWithoutSessionStart) +{ + ASSERT_NO_THROW(sessions.commit()); +} + +TEST_F(Sessions, CreateEmptySession) +{ + sessions.create(); + sessions.commit(); + + ASSERT_THAT(fetchChangeSets(), IsEmpty()); +} + +TEST_F(Sessions, CreateSessionWithInsert) +{ + sessions.create(); + insertData.write("foo", 22, 3.14); + sessions.commit(); + + ASSERT_THAT(fetchChangeSets(), SizeIs(1)); +} + +TEST_F(Sessions, CreateSessionWithUpdate) +{ + insertData.write("foo", 22, 3.14); + + sessions.create(); + updateNumber.write("foo", "bar"); + sessions.commit(); + + ASSERT_THAT(fetchChangeSets(), SizeIs(1)); +} + +TEST_F(Sessions, CreateSessionWithDelete) +{ + insertData.write("foo", 22, 3.14); + + sessions.create(); + deleteData.write("foo"); + sessions.commit(); + + ASSERT_THAT(fetchChangeSets(), SizeIs(1)); +} + +TEST_F(Sessions, CreateSessionWithInsertAndUpdate) +{ + sessions.create(); + insertData.write("foo", 22, 3.14); + sessions.commit(); + + sessions.create(); + updateNumber.write("foo", "bar"); + sessions.commit(); + + ASSERT_THAT(fetchChangeSets(), SizeIs(2)); +} + +TEST_F(Sessions, CreateSession) +{ + sessions.create(); + insertData.write("foo", 22, 3.14); + + sessions.commit(); + + ASSERT_THAT(fetchChangeSets(), SizeIs(1)); +} + +TEST_F(Sessions, RevertSession) +{ + sessions.create(); + insertData.write("foo", 22, 3.14); + sessions.commit(); + + sessions.revert(); + + ASSERT_THAT(fetchData(), IsEmpty()); +} + +TEST_F(Sessions, RevertSessionToBase) +{ + insertData.write("bar", "foo", 99); + sessions.create(); + insertData.write("foo", 22, 3.14); + sessions.commit(); + + sessions.revert(); + + ASSERT_THAT(fetchData(), ElementsAre(HasData("bar", "foo", 99))); +} + +TEST_F(Sessions, RevertMultipleSession) +{ + sessions.create(); + insertData.write("foo", 22, 3.14); + sessions.commit(); + sessions.create(); + updateNumber.write("foo", "bar"); + sessions.commit(); + + sessions.revert(); + + ASSERT_THAT(fetchData(), IsEmpty()); +} + +TEST_F(Sessions, ApplySession) +{ + sessions.create(); + insertData.write("foo", 22, 3.14); + sessions.commit(); + + sessions.apply(); + + ASSERT_THAT(fetchData(), ElementsAre(HasData("foo", 22, 3.14))); +} + +TEST_F(Sessions, ApplySessionAfterAddingNewEntries) +{ + sessions.create(); + insertData.write("foo", 22, 3.14); + sessions.commit(); + insertData.write("bar", "foo", 99); + + sessions.apply(); + + ASSERT_THAT(fetchData(), + UnorderedElementsAre(HasData("foo", 22, 3.14), HasData("bar", "foo", 99))); +} + +TEST_F(Sessions, ApplyOverridesEntriesWithUniqueConstraint) +{ + sessions.create(); + insertData.write("foo", 22, 3.14); + sessions.commit(); + insertData.write("foo", "bar", 3.14); + + sessions.apply(); + + ASSERT_THAT(fetchData(), ElementsAre(HasData("foo", 22, 3.14))); +} + +TEST_F(Sessions, ApplyDoesNotOverrideDeletedEntries) +{ + insertData.write("foo", "bar", 3.14); + sessions.create(); + insertData.write("foo", 22, 3.14); + sessions.commit(); + deleteData.write("foo"); + + sessions.apply(); + + ASSERT_THAT(fetchData(), IsEmpty()); +} + +TEST_F(Sessions, ApplyDoesOnlyOverwriteUpdatedValues) +{ + insertData.write("foo", "bar", 3.14); + sessions.create(); + updateValue.write("foo", 1234); + sessions.commit(); + insertData.write("foo", "poo", 891); + + sessions.apply(); + + ASSERT_THAT(fetchData(), ElementsAre(HasData("foo", "poo", 1234))); +} + +TEST_F(Sessions, ApplyDoesDoesNotOverrideForeignKeyIfReferenceIsDeleted) +{ + insertData.write("foo2", "bar", 3.14); + insertData.write("foo", "bar", 3.14); + sessions.create(); + insertTag.write("foo2", 4321); + insertTag.write("foo", 1234); + sessions.commit(); + deleteData.write("foo"); + + sessions.apply(); + + ASSERT_THAT(fetchTags(), ElementsAre(HasTag("foo2", 4321))); +} + +TEST_F(Sessions, ApplyDoesDoesNotOverrideIfConstraintsIsApplied) +{ + insertData.write("foo", "bar", 3.14); + sessions.create(); + deleteData.write("foo"); + sessions.commit(); + sessions.revert(); + insertTag.write("foo", 1234); + + sessions.apply(); + + ASSERT_THAT(fetchTags(), IsEmpty()); +} + +TEST_F(Sessions, ApplyDoesDoesNotOverrideForeignKeyIfReferenceIsDeletedDeferred) +{ + Sqlite::DeferredTransaction transaction{database}; + insertData.write("foo2", "bar", 3.14); + insertData.write("foo", "bar", 3.14); + sessions.create(); + insertTag.write("foo2", 4321); + insertTag.write("foo", 1234); + sessions.commit(); + deleteData.write("foo"); + + sessions.apply(); + + transaction.commit(); + ASSERT_THAT(fetchTags(), ElementsAre(HasTag("foo2", 4321))); +} + +TEST_F(Sessions, EndSessionOnRollback) +{ + insertData.write("foo", "bar", 3.14); + sessions.create(); + updateValue.write("foo", 99); + sessions.rollback(); + sessions.commit(); + sessions.create(); + updateNumber.write("foo", 333); + sessions.commit(); + updateValue.write("foo", 666); + + sessions.apply(); + + ASSERT_THAT(fetchData(), ElementsAre(HasData("foo", 333, 666))); +} + +TEST_F(Sessions, EndSessionOnCommit) +{ + insertData.write("foo", "bar", 3.14); + sessions.create(); + updateValue.write("foo", 99); + sessions.commit(); + updateValue.write("foo", 666); + sessions.commit(); + + sessions.apply(); + + ASSERT_THAT(fetchData(), ElementsAre(HasData("foo", "bar", 99))); +} + +TEST_F(Sessions, DeleteSessions) +{ + insertData.write("foo", "bar", 3.14); + sessions.create(); + updateValue.write("foo", 99); + sessions.commit(); + sessions.revert(); + + sessions.deleteAll(); + + sessions.apply(); + ASSERT_THAT(fetchData(), ElementsAre(HasData("foo", "bar", 3.14))); +} + +TEST_F(Sessions, DeleteAllSessions) +{ + insertData.write("foo", "bar", 3.14); + sessions.create(); + updateValue.write("foo", 99); + sessions.commit(); + sessions.revert(); + + sessions.deleteAll(); + + sessions.apply(); + ASSERT_THAT(fetchData(), ElementsAre(HasData("foo", "bar", 3.14))); +} + +TEST_F(Sessions, ApplyAndUpdateSessions) +{ + insertData.write("foo", "bar", 3.14); + sessions.create(); + updateValue.write("foo", 99); + sessions.commit(); + updateValue.write("foo", 99); + + sessions.applyAndUpdateSessions(); + + updateValue.write("foo", 22); + sessions.apply(); + ASSERT_THAT(fetchData(), ElementsAre(HasData("foo", "bar", 22))); +} + +TEST_F(Sessions, ApplyAndUpdateSessionsHasOnlyOneChangeSet) +{ + insertData.write("foo", "bar", 3.14); + sessions.create(); + updateValue.write("foo", 99); + sessions.commit(); + updateValue.write("foo", 99); + + sessions.applyAndUpdateSessions(); + + ASSERT_THAT(fetchChangeSets(), SizeIs(1)); +} + +} // namespace diff --git a/tests/unit/unittest/sqlitestatement-test.cpp b/tests/unit/unittest/sqlitestatement-test.cpp index c7da3971e4..a7dd5d5f9b 100644 --- a/tests/unit/unittest/sqlitestatement-test.cpp +++ b/tests/unit/unittest/sqlitestatement-test.cpp @@ -38,6 +38,14 @@ #include <vector> +namespace Sqlite { +bool operator==(Utils::span<const byte> first, Utils::span<const byte> second) +{ + return first.size() == second.size() + && std::memcmp(first.data(), second.data(), first.size()) == 0; +} +} // namespace Sqlite + namespace { using Sqlite::Database; @@ -66,11 +74,33 @@ MATCHER_P3(HasValues, value1, value2, rowid, && statement.fetchSmallStringViewValue(1) == value2; } +MATCHER_P(HasNullValues, rowid, std::string(negation ? "isn't null" : "is null")) +{ + Database &database = arg.database(); + + SqliteTestStatement statement("SELECT name, number FROM test WHERE rowid=?", database); + statement.bind(1, rowid); + + statement.next(); + + return statement.fetchValueView(0).isNull() && statement.fetchValueView(1).isNull(); +} + class SqliteStatement : public ::testing::Test { protected: - void SetUp() override; - void TearDown() override; + void SetUp() override + { + database.execute("CREATE TABLE test(name TEXT UNIQUE, number NUMERIC, value NUMERIC)"); + database.execute("INSERT INTO test VALUES ('bar', 'blah', 1)"); + database.execute("INSERT INTO test VALUES ('foo', 23.3, 2)"); + database.execute("INSERT INTO test VALUES ('poo', 40, 3)"); + } + void TearDown() override + { + if (database.isOpen()) + database.close(); + } protected: Database database{":memory:", Sqlite::JournalMode::Memory}; @@ -148,37 +178,6 @@ TEST_F(SqliteStatement, Value) ASSERT_THAT(statement.fetchValueView(2), Eq(2)); } -TEST_F(SqliteStatement, ThrowNoValuesToFetchForNotSteppedStatement) -{ - SqliteTestStatement statement("SELECT name, number FROM test", database); - - ASSERT_THROW(statement.fetchValue<int>(0), Sqlite::NoValuesToFetch); -} - -TEST_F(SqliteStatement, ThrowNoValuesToFetchForDoneStatement) -{ - SqliteTestStatement statement("SELECT name, number FROM test", database); - while (statement.next()) {} - - ASSERT_THROW(statement.fetchValue<int>(0), Sqlite::NoValuesToFetch); -} - -TEST_F(SqliteStatement, ThrowInvalidColumnFetchedForNegativeColumn) -{ - SqliteTestStatement statement("SELECT name, number FROM test", database); - statement.next(); - - ASSERT_THROW(statement.fetchValue<int>(-1), Sqlite::InvalidColumnFetched); -} - -TEST_F(SqliteStatement, ThrowInvalidColumnFetchedForNotExistingColumn) -{ - SqliteTestStatement statement("SELECT name, number FROM test", database); - statement.next(); - - ASSERT_THROW(statement.fetchValue<int>(2), Sqlite::InvalidColumnFetched); -} - TEST_F(SqliteStatement, ToIntegerValue) { auto value = ReadStatement::toValue<int>("SELECT number FROM test WHERE name='foo'", database); @@ -201,13 +200,16 @@ TEST_F(SqliteStatement, ToStringValue) ASSERT_THAT(ReadStatement::toValue<Utils::SmallString>("SELECT name FROM test WHERE name='foo'", database), "foo"); } -TEST_F(SqliteStatement, ColumnNames) +TEST_F(SqliteStatement, BindNull) { - SqliteTestStatement statement("SELECT name, number FROM test", database); + database.execute("INSERT INTO test VALUES (NULL, 323, 344)"); + SqliteTestStatement statement("SELECT name, number FROM test WHERE name IS ?", database); - auto columnNames = statement.columnNames(); + statement.bind(1, Sqlite::NullValue{}); + statement.next(); - ASSERT_THAT(columnNames, ElementsAre("name", "number")); + ASSERT_TRUE(statement.fetchValueView(0).isNull()); + ASSERT_THAT(statement.fetchValue<int>(1), 323); } TEST_F(SqliteStatement, BindString) @@ -216,7 +218,6 @@ TEST_F(SqliteStatement, BindString) SqliteTestStatement statement("SELECT name, number FROM test WHERE name=?", database); statement.bind(1, "foo"); - statement.next(); ASSERT_THAT(statement.fetchSmallStringViewValue(0), "foo"); @@ -253,55 +254,96 @@ TEST_F(SqliteStatement, BindDouble) ASSERT_THAT(statement.fetchSmallStringViewValue(0), "foo"); } -TEST_F(SqliteStatement, BindIntegerByParameter) +TEST_F(SqliteStatement, BindPointer) { - SqliteTestStatement statement("SELECT name, number FROM test WHERE number=@number", database); + SqliteTestStatement statement("SELECT value FROM carray(?, 5, 'int64')", database); + std::vector<long long> values{1, 1, 2, 3, 5}; - statement.bind("@number", 40); + statement.bind(1, values.data()); statement.next(); - ASSERT_THAT(statement.fetchSmallStringViewValue(0), "poo"); + ASSERT_THAT(statement.fetchIntValue(0), 1); } -TEST_F(SqliteStatement, BindLongIntegerByParameter) +TEST_F(SqliteStatement, BindBlob) { - SqliteTestStatement statement("SELECT name, number FROM test WHERE number=@number", database); + SqliteTestStatement statement("WITH T(blob) AS (VALUES (?)) SELECT blob FROM T", database); + const unsigned char chars[] = "aaafdfdlll"; + auto bytePointer = reinterpret_cast<const Sqlite::byte *>(chars); + Utils::span<const Sqlite::byte> bytes{bytePointer, sizeof(chars) - 1}; - statement.bind("@number", int64_t(40)); + statement.bind(1, bytes); statement.next(); - ASSERT_THAT(statement.fetchSmallStringViewValue(0), "poo"); + ASSERT_THAT(statement.fetchBlobValue(0), Eq(bytes)); } -TEST_F(SqliteStatement, BindDoubleByIndex) +TEST_F(SqliteStatement, BindEmptyBlob) { - SqliteTestStatement statement("SELECT name, number FROM test WHERE number=@number", database); + SqliteTestStatement statement("WITH T(blob) AS (VALUES (?)) SELECT blob FROM T", database); + Utils::span<const Sqlite::byte> bytes; - statement.bind(statement.bindingIndexForName("@number"), 23.3); + statement.bind(1, bytes); statement.next(); - ASSERT_THAT(statement.fetchSmallStringViewValue(0), "foo"); + ASSERT_THAT(statement.fetchBlobValue(0), IsEmpty()); } -TEST_F(SqliteStatement, BindIndexIsZeroIsThrowingBindingIndexIsOutOfBound) +TEST_F(SqliteStatement, BindIndexIsZeroIsThrowingBindingIndexIsOutOfBoundInt) { SqliteTestStatement statement("SELECT name, number FROM test WHERE number=$1", database); ASSERT_THROW(statement.bind(0, 40), Sqlite::BindingIndexIsOutOfRange); } -TEST_F(SqliteStatement, BindIndexIsTpLargeIsThrowingBindingIndexIsOutOfBound) +TEST_F(SqliteStatement, BindIndexIsZeroIsThrowingBindingIndexIsOutOfBoundNull) +{ + SqliteTestStatement statement("SELECT name, number FROM test WHERE number=$1", database); + + ASSERT_THROW(statement.bind(0, Sqlite::NullValue{}), Sqlite::BindingIndexIsOutOfRange); +} + +TEST_F(SqliteStatement, BindIndexIsToLargeIsThrowingBindingIndexIsOutOfBoundLongLong) +{ + SqliteTestStatement statement("SELECT name, number FROM test WHERE number=$1", database); + + ASSERT_THROW(statement.bind(2, 40LL), Sqlite::BindingIndexIsOutOfRange); +} + +TEST_F(SqliteStatement, BindIndexIsToLargeIsThrowingBindingIndexIsOutOfBoundStringView) { SqliteTestStatement statement("SELECT name, number FROM test WHERE number=$1", database); - ASSERT_THROW(statement.bind(2, 40), Sqlite::BindingIndexIsOutOfRange); + ASSERT_THROW(statement.bind(2, "foo"), Sqlite::BindingIndexIsOutOfRange); } -TEST_F(SqliteStatement, WrongBindingNameThrowingBindingIndexIsOutOfBound) +TEST_F(SqliteStatement, BindIndexIsToLargeIsThrowingBindingIndexIsOutOfBoundStringFloat) { - SqliteTestStatement statement("SELECT name, number FROM test WHERE number=@name", database); + SqliteTestStatement statement("SELECT name, number FROM test WHERE number=$1", database); + + ASSERT_THROW(statement.bind(2, 2.), Sqlite::BindingIndexIsOutOfRange); +} + +TEST_F(SqliteStatement, BindIndexIsToLargeIsThrowingBindingIndexIsOutOfBoundPointer) +{ + SqliteTestStatement statement("SELECT name, number FROM test WHERE number=$1", database); + + ASSERT_THROW(statement.bind(2, nullptr), Sqlite::BindingIndexIsOutOfRange); +} + +TEST_F(SqliteStatement, BindIndexIsToLargeIsThrowingBindingIndexIsOutOfBoundValue) +{ + SqliteTestStatement statement("SELECT name, number FROM test WHERE number=$1", database); - ASSERT_THROW(statement.bind("@name2", 40), Sqlite::WrongBindingName); + ASSERT_THROW(statement.bind(2, Sqlite::Value{1}), Sqlite::BindingIndexIsOutOfRange); +} + +TEST_F(SqliteStatement, BindIndexIsToLargeIsThrowingBindingIndexIsOutOfBoundBlob) +{ + SqliteTestStatement statement("WITH T(blob) AS (VALUES (?)) SELECT blob FROM T", database); + Utils::span<const Sqlite::byte> bytes; + + ASSERT_THROW(statement.bind(2, bytes), Sqlite::BindingIndexIsOutOfRange); } TEST_F(SqliteStatement, BindValues) @@ -314,6 +356,16 @@ TEST_F(SqliteStatement, BindValues) ASSERT_THAT(statement, HasValues("see", "7.23", 1)); } +TEST_F(SqliteStatement, BindNullValues) +{ + SqliteTestStatement statement("UPDATE test SET name=?, number=? WHERE rowid=?", database); + + statement.bindValues(Sqlite::NullValue{}, Sqlite::Value{}, 1); + statement.execute(); + + ASSERT_THAT(statement, HasNullValues(1)); +} + TEST_F(SqliteStatement, WriteValues) { WriteStatement statement("UPDATE test SET name=?, number=? WHERE rowid=?", database); @@ -323,6 +375,25 @@ TEST_F(SqliteStatement, WriteValues) ASSERT_THAT(statement, HasValues("see", "7.23", 1)); } +TEST_F(SqliteStatement, WritePointerValues) +{ + SqliteTestStatement statement("SELECT value FROM carray(?, ?, 'int64')", database); + std::vector<long long> values{1, 1, 2, 3, 5}; + + statement.write(values.data(), int(values.size())); + + ASSERT_THAT(statement.template values<int>(5), ElementsAre(1, 1, 2, 3, 5)); +} + +TEST_F(SqliteStatement, WriteNullValues) +{ + WriteStatement statement("UPDATE test SET name=?, number=? WHERE rowid=?", database); + + statement.write(Sqlite::NullValue{}, Sqlite::Value{}, 1); + + ASSERT_THAT(statement, HasNullValues(1)); +} + TEST_F(SqliteStatement, WriteSqliteValues) { WriteStatement statement("UPDATE test SET name=?, number=? WHERE rowid=?", database); @@ -332,23 +403,38 @@ TEST_F(SqliteStatement, WriteSqliteValues) ASSERT_THAT(statement, HasValues("see", "7.23", 1)); } -TEST_F(SqliteStatement, BindNamedValues) +TEST_F(SqliteStatement, WriteEmptyBlobs) { - SqliteTestStatement statement("UPDATE test SET name=@name, number=@number WHERE rowid=@id", database); + SqliteTestStatement statement("WITH T(blob) AS (VALUES (?)) SELECT blob FROM T", database); - statement.bindNameValues("@name", "see", "@number", 7.23, "@id", 1); - statement.execute(); + Utils::span<const Sqlite::byte> bytes; - ASSERT_THAT(statement, HasValues("see", "7.23", 1)); + statement.write(bytes); + + ASSERT_THAT(statement.fetchBlobValue(0), IsEmpty()); } -TEST_F(SqliteStatement, WriteNamedValues) +class Blob { - WriteStatement statement("UPDATE test SET name=@name, number=@number WHERE rowid=@id", database); +public: + Blob(Utils::span<const Sqlite::byte> bytes) + : bytes(bytes.begin(), bytes.end()) + {} - statement.writeNamed("@name", "see", "@number", 7.23, "@id", 1); + std::vector<Sqlite::byte> bytes; +}; - ASSERT_THAT(statement, HasValues("see", "7.23", 1)); +TEST_F(SqliteStatement, WriteBlobs) +{ + SqliteTestStatement statement("INSERT INTO test VALUES ('blob', 40, ?)", database); + SqliteTestStatement readStatement("SELECT value FROM test WHERE name = 'blob'", database); + const unsigned char chars[] = "aaafdfdlll"; + auto bytePointer = reinterpret_cast<const Sqlite::byte *>(chars); + Utils::span<const Sqlite::byte> bytes{bytePointer, sizeof(chars) - 1}; + + statement.write(bytes); + + ASSERT_THAT(readStatement.template value<Blob>(), Optional(Field(&Blob::bytes, Eq(bytes)))); } TEST_F(SqliteStatement, CannotWriteToClosedDatabase) @@ -374,9 +460,8 @@ TEST_F(SqliteStatement, GetTupleValuesWithoutArguments) auto values = statement.values<Tuple, 3>(3); - ASSERT_THAT(values, ElementsAre(Tuple{"bar", 0, 1}, - Tuple{"foo", 23.3, 2}, - Tuple{"poo", 40.0, 3})); + ASSERT_THAT(values, + UnorderedElementsAre(Tuple{"bar", 0, 1}, Tuple{"foo", 23.3, 2}, Tuple{"poo", 40.0, 3})); } TEST_F(SqliteStatement, GetSingleValuesWithoutArguments) @@ -385,7 +470,7 @@ TEST_F(SqliteStatement, GetSingleValuesWithoutArguments) std::vector<Utils::SmallString> values = statement.values<Utils::SmallString>(3); - ASSERT_THAT(values, ElementsAre("bar", "foo", "poo")); + ASSERT_THAT(values, UnorderedElementsAre("bar", "foo", "poo")); } class FooValue @@ -407,10 +492,11 @@ public: TEST_F(SqliteStatement, GetSingleSqliteValuesWithoutArguments) { ReadStatement statement("SELECT number FROM test", database); + database.execute("INSERT INTO test VALUES (NULL, NULL, NULL)"); std::vector<FooValue> values = statement.values<FooValue>(3); - ASSERT_THAT(values, ElementsAre(Eq("blah"), Eq(23.3), Eq(40))); + ASSERT_THAT(values, UnorderedElementsAre(Eq("blah"), Eq(23.3), Eq(40), IsNull())); } TEST_F(SqliteStatement, GetStructValuesWithoutArguments) @@ -419,9 +505,10 @@ TEST_F(SqliteStatement, GetStructValuesWithoutArguments) auto values = statement.values<Output, 3>(3); - ASSERT_THAT(values, ElementsAre(Output{"bar", "blah", 1}, - Output{"foo", "23.3", 2}, - Output{"poo", "40", 3})); + ASSERT_THAT(values, + UnorderedElementsAre(Output{"bar", "blah", 1}, + Output{"foo", "23.3", 2}, + Output{"poo", "40", 3})); } TEST_F(SqliteStatement, GetValuesForSingleOutputWithBindingMultipleTimes) @@ -442,18 +529,17 @@ TEST_F(SqliteStatement, GetValuesForMultipleOutputValuesAndContainerQueryValues) auto values = statement.values<Tuple, 3>(3, queryValues); - ASSERT_THAT(values, ElementsAre(Tuple{"poo", 40, 3.}, - Tuple{"foo", 23.3, 2.})); + ASSERT_THAT(values, UnorderedElementsAre(Tuple{"poo", 40, 3.}, Tuple{"foo", 23.3, 2.})); } TEST_F(SqliteStatement, GetValuesForSingleOutputValuesAndContainerQueryValues) { std::vector<double> queryValues = {40, 23.3}; - ReadStatement statement("SELECT name, number FROM test WHERE number=?", database); + ReadStatement statement("SELECT name FROM test WHERE number=?", database); std::vector<Utils::SmallString> values = statement.values<Utils::SmallString>(3, queryValues); - ASSERT_THAT(values, ElementsAre("poo", "foo")); + ASSERT_THAT(values, UnorderedElementsAre("poo", "foo")); } TEST_F(SqliteStatement, GetValuesForMultipleOutputValuesAndContainerQueryTupleValues) @@ -465,19 +551,18 @@ TEST_F(SqliteStatement, GetValuesForMultipleOutputValuesAndContainerQueryTupleVa auto values = statement.values<ResultTuple, 3>(3, queryValues); - ASSERT_THAT(values, ElementsAre(ResultTuple{"poo", 40, 3}, - ResultTuple{"bar", 0, 1})); + ASSERT_THAT(values, UnorderedElementsAre(ResultTuple{"poo", 40, 3}, ResultTuple{"bar", 0, 1})); } TEST_F(SqliteStatement, GetValuesForSingleOutputValuesAndContainerQueryTupleValues) { using Tuple = std::tuple<Utils::SmallString, Utils::SmallString>; std::vector<Tuple> queryValues = {{"poo", "40"}, {"bar", "blah"}}; - ReadStatement statement("SELECT name, number FROM test WHERE name= ? AND number=?", database); + ReadStatement statement("SELECT name FROM test WHERE name= ? AND number=?", database); std::vector<Utils::SmallString> values = statement.values<Utils::SmallString>(3, queryValues); - ASSERT_THAT(values, ElementsAre("poo", "bar")); + ASSERT_THAT(values, UnorderedElementsAre("poo", "bar")); } TEST_F(SqliteStatement, GetValuesForMultipleOutputValuesAndMultipleQueryValue) @@ -529,8 +614,47 @@ TEST_F(SqliteStatement, GetStructOutputValuesAndContainerQueryTupleValues) auto values = statement.values<Output, 3>(3, queryValues); - ASSERT_THAT(values, ElementsAre(Output{"poo", "40", 3}, - Output{"bar", "blah", 1})); + ASSERT_THAT(values, UnorderedElementsAre(Output{"poo", "40", 3}, Output{"bar", "blah", 1})); +} + +TEST_F(SqliteStatement, GetBlobValues) +{ + database.execute("INSERT INTO test VALUES ('blob', 40, x'AABBCCDD')"); + ReadStatement statement("SELECT value FROM test WHERE name='blob'", database); + const int value = 0xDDCCBBAA; + auto bytePointer = reinterpret_cast<const Sqlite::byte *>(&value); + Utils::span<const Sqlite::byte> bytes{bytePointer, 4}; + + auto values = statement.values<Blob>(1); + + ASSERT_THAT(values, ElementsAre(Field(&Blob::bytes, Eq(bytes)))); +} + +TEST_F(SqliteStatement, GetEmptyBlobValueForInteger) +{ + ReadStatement statement("SELECT value FROM test WHERE name='poo'", database); + + auto value = statement.value<Blob>(); + + ASSERT_THAT(value, Optional(Field(&Blob::bytes, IsEmpty()))); +} + +TEST_F(SqliteStatement, GetEmptyBlobValueForFloat) +{ + ReadStatement statement("SELECT number FROM test WHERE name='foo'", database); + + auto value = statement.value<Blob>(); + + ASSERT_THAT(value, Optional(Field(&Blob::bytes, IsEmpty()))); +} + +TEST_F(SqliteStatement, GetEmptyBlobValueForText) +{ + ReadStatement statement("SELECT number FROM test WHERE name='bar'", database); + + auto value = statement.value<Blob>(); + + ASSERT_THAT(value, Optional(Field(&Blob::bytes, IsEmpty()))); } TEST_F(SqliteStatement, GetOptionalSingleValueAndMultipleQueryValue) @@ -688,41 +812,56 @@ TEST_F(SqliteStatement, ResetIfWriteIsThrowingException) ASSERT_ANY_THROW(mockStatement.write("bar")); } -TEST_F(SqliteStatement, ResetIfWriteNamedIsThrowingException) +TEST_F(SqliteStatement, ResetIfExecuteThrowsException) { MockSqliteStatement mockStatement; - EXPECT_CALL(mockStatement, bindingIndexForName(TypedEq<Utils::SmallStringView>("@foo"))) - .WillOnce(Return(1)); - EXPECT_CALL(mockStatement, bind(1, TypedEq<Utils::SmallStringView>("bar"))) - .WillOnce(Throw(Sqlite::StatementIsBusy(""))); + EXPECT_CALL(mockStatement, next()).WillOnce(Throw(Sqlite::StatementIsBusy(""))); EXPECT_CALL(mockStatement, reset()); - ASSERT_ANY_THROW(mockStatement.writeNamed("@foo", "bar")); + ASSERT_ANY_THROW(mockStatement.execute()); } -TEST_F(SqliteStatement, ResetIfExecuteThrowsException) +TEST_F(SqliteStatement, ThrowInvalidColumnFetchedForToManyArgumentsForValue) { - MockSqliteStatement mockStatement; + SqliteTestStatement statement("SELECT name, number FROM test", database); - EXPECT_CALL(mockStatement, next()).WillOnce(Throw(Sqlite::StatementIsBusy(""))); - EXPECT_CALL(mockStatement, reset()); + ASSERT_THROW(statement.value<int>(), Sqlite::ColumnCountDoesNotMatch); +} - ASSERT_ANY_THROW(mockStatement.execute()); +TEST_F(SqliteStatement, ThrowInvalidColumnFetchedForToManyArgumentsForValues) +{ + SqliteTestStatement statement("SELECT name, number FROM test", database); + + ASSERT_THROW(statement.values<int>(1), Sqlite::ColumnCountDoesNotMatch); +} + +TEST_F(SqliteStatement, ThrowInvalidColumnFetchedForToManyArgumentsForValuesWithArguments) +{ + SqliteTestStatement statement("SELECT name, number FROM test WHERE name=?", database); + + ASSERT_THROW(statement.values<int>(1, 2), Sqlite::ColumnCountDoesNotMatch); } -void SqliteStatement::SetUp() +TEST_F(SqliteStatement, ThrowInvalidColumnFetchedForToManyArgumentsForValuesWithVectorArguments) { - database.execute("CREATE TABLE test(name TEXT UNIQUE, number NUMERIC, value NUMERIC)"); - database.execute("INSERT INTO test VALUES ('bar', 'blah', 1)"); - database.execute("INSERT INTO test VALUES ('foo', 23.3, 2)"); - database.execute("INSERT INTO test VALUES ('poo', 40, 3)"); + SqliteTestStatement statement("SELECT name, number FROM test", database); + + ASSERT_THROW(statement.values<int>(1, std::vector<int>{}), Sqlite::ColumnCountDoesNotMatch); } -void SqliteStatement::TearDown() +TEST_F(SqliteStatement, ThrowInvalidColumnFetchedForToManyArgumentsForValuesWithTupleArguments) { - if (database.isOpen()) - database.close(); + SqliteTestStatement statement("SELECT name, number FROM test", database); + + ASSERT_THROW(statement.values<int>(1, std::vector<std::tuple<int>>{}), + Sqlite::ColumnCountDoesNotMatch); } +TEST_F(SqliteStatement, ThrowInvalidColumnFetchedForToManyArgumentsForToValues) +{ + ASSERT_THROW(SqliteTestStatement::toValue<int>("SELECT name, number FROM test", database), + Sqlite::ColumnCountDoesNotMatch); } + +} // namespace diff --git a/tests/unit/unittest/sqlitetable-test.cpp b/tests/unit/unittest/sqlitetable-test.cpp index c5ce8e325d..f96b451785 100644 --- a/tests/unit/unittest/sqlitetable-test.cpp +++ b/tests/unit/unittest/sqlitetable-test.cpp @@ -32,11 +32,15 @@ namespace { +using Sqlite::Column; using Sqlite::ColumnType; +using Sqlite::ConstraintType; +using Sqlite::Database; +using Sqlite::Enforment; +using Sqlite::ForeignKey; +using Sqlite::ForeignKeyAction; using Sqlite::JournalMode; using Sqlite::OpenMode; -using Sqlite::Column; -using Sqlite::Database; class SqliteTable : public ::testing::Test { @@ -110,21 +114,195 @@ TEST_F(SqliteTable, InitializeTableWithIndex) table.initialize(mockDatabase); } +TEST_F(SqliteTable, AddForeignKeyColumnWithTableCalls) +{ + Sqlite::Table foreignTable; + foreignTable.setName("foreignTable"); + table.setName(tableName); + table.addForeignKeyColumn("name", + foreignTable, + ForeignKeyAction::SetNull, + ForeignKeyAction::Cascade, + Enforment::Deferred); + + EXPECT_CALL(mockDatabase, + execute(Eq("CREATE TABLE testTable(name INTEGER REFERENCES foreignTable ON UPDATE " + "SET NULL ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)"))); -TEST_F(SqliteTable, InitializeTableWithUniqueIndex) + table.initialize(mockDatabase); +} + +TEST_F(SqliteTable, AddForeignKeyColumnWithColumnCalls) { - InSequence sequence; - table.setName(tableName.clone()); - auto &column = table.addColumn("name"); - auto &column2 = table.addColumn("value"); - table.addUniqueIndex({column}); - table.addIndex({column2}); + Sqlite::Table foreignTable; + foreignTable.setName("foreignTable"); + auto &foreignColumn = foreignTable.addColumn("foreignColumn", ColumnType::Text, {Sqlite::Unique{}}); + table.setName(tableName); + table.addForeignKeyColumn("name", + foreignColumn, + ForeignKeyAction::SetDefault, + ForeignKeyAction::Restrict, + Enforment::Deferred); - EXPECT_CALL(mockDatabase, execute(Eq("CREATE TABLE testTable(name NUMERIC, value NUMERIC)"))); - EXPECT_CALL(mockDatabase, execute(Eq("CREATE UNIQUE INDEX IF NOT EXISTS index_testTable_name ON testTable(name)"))); - EXPECT_CALL(mockDatabase, execute(Eq("CREATE INDEX IF NOT EXISTS index_testTable_value ON testTable(value)"))); + EXPECT_CALL( + mockDatabase, + execute( + Eq("CREATE TABLE testTable(name TEXT REFERENCES foreignTable(foreignColumn) ON UPDATE " + "SET DEFAULT ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED)"))); table.initialize(mockDatabase); } +TEST_F(SqliteTable, AddColumn) +{ + table.setName(tableName); + + auto &column = table.addColumn("name", ColumnType::Text, {Sqlite::Unique{}}); + + ASSERT_THAT(column, + AllOf(Field(&Column::name, Eq("name")), + Field(&Column::tableName, Eq(tableName)), + Field(&Column::type, ColumnType::Text), + Field(&Column::constraints, + ElementsAre(VariantWith<Sqlite::Unique>(Eq(Sqlite::Unique{})))))); +} + +TEST_F(SqliteTable, AddForeignKeyColumnWithTable) +{ + Sqlite::Table foreignTable; + foreignTable.setName("foreignTable"); + + table.setName(tableName); + + auto &column = table.addForeignKeyColumn("name", + foreignTable, + ForeignKeyAction::SetNull, + ForeignKeyAction::Cascade, + Enforment::Deferred); + + ASSERT_THAT(column, + AllOf(Field(&Column::name, Eq("name")), + Field(&Column::tableName, Eq(tableName)), + Field(&Column::type, ColumnType::Integer), + Field(&Column::constraints, + ElementsAre(VariantWith<ForeignKey>( + AllOf(Field(&ForeignKey::table, Eq("foreignTable")), + Field(&ForeignKey::column, IsEmpty()), + Field(&ForeignKey::updateAction, ForeignKeyAction::SetNull), + Field(&ForeignKey::deleteAction, ForeignKeyAction::Cascade), + Field(&ForeignKey::enforcement, Enforment::Deferred))))))); +} + +TEST_F(SqliteTable, AddForeignKeyColumnWithColumn) +{ + Sqlite::Table foreignTable; + foreignTable.setName("foreignTable"); + auto &foreignColumn = foreignTable.addColumn("foreignColumn", ColumnType::Text, {Sqlite::Unique{}}); + table.setName(tableName); + + auto &column = table.addForeignKeyColumn("name", + foreignColumn, + ForeignKeyAction::SetNull, + ForeignKeyAction::Cascade, + Enforment::Deferred); + + ASSERT_THAT(column, + AllOf(Field(&Column::name, Eq("name")), + Field(&Column::tableName, Eq(tableName)), + Field(&Column::type, ColumnType::Text), + Field(&Column::constraints, + ElementsAre(VariantWith<ForeignKey>( + AllOf(Field(&ForeignKey::table, Eq("foreignTable")), + Field(&ForeignKey::column, Eq("foreignColumn")), + Field(&ForeignKey::updateAction, ForeignKeyAction::SetNull), + Field(&ForeignKey::deleteAction, ForeignKeyAction::Cascade), + Field(&ForeignKey::enforcement, Enforment::Deferred))))))); +} + +TEST_F(SqliteTable, AddForeignKeyWhichIsNotUniqueThrowsAnExceptions) +{ + Sqlite::Table foreignTable; + foreignTable.setName("foreignTable"); + auto &foreignColumn = foreignTable.addColumn("foreignColumn", ColumnType::Text); + table.setName(tableName); + + ASSERT_THROW(table.addForeignKeyColumn("name", + foreignColumn, + ForeignKeyAction::SetNull, + ForeignKeyAction::Cascade, + Enforment::Deferred), + Sqlite::ForeignKeyColumnIsNotUnique); +} + +TEST_F(SqliteTable, AddForeignKeyColumnWithTableAndNotNull) +{ + Sqlite::Table foreignTable; + foreignTable.setName("foreignTable"); + + table.setName(tableName); + + auto &column = table.addForeignKeyColumn("name", + foreignTable, + ForeignKeyAction::SetNull, + ForeignKeyAction::Cascade, + Enforment::Deferred, + {Sqlite::NotNull{}}); + + ASSERT_THAT(column, + AllOf(Field(&Column::name, Eq("name")), + Field(&Column::tableName, Eq(tableName)), + Field(&Column::type, ColumnType::Integer), + Field(&Column::constraints, + UnorderedElementsAre( + VariantWith<ForeignKey>( + AllOf(Field(&ForeignKey::table, Eq("foreignTable")), + Field(&ForeignKey::column, IsEmpty()), + Field(&ForeignKey::updateAction, ForeignKeyAction::SetNull), + Field(&ForeignKey::deleteAction, ForeignKeyAction::Cascade), + Field(&ForeignKey::enforcement, Enforment::Deferred))), + VariantWith<Sqlite::NotNull>(Eq(Sqlite::NotNull{})))))); +} + +TEST_F(SqliteTable, AddForeignKeyColumnWithColumnAndNotNull) +{ + Sqlite::Table foreignTable; + foreignTable.setName("foreignTable"); + auto &foreignColumn = foreignTable.addColumn("foreignColumn", ColumnType::Text, {Sqlite::Unique{}}); + table.setName(tableName); + + auto &column = table.addForeignKeyColumn("name", + foreignColumn, + ForeignKeyAction::SetNull, + ForeignKeyAction::Cascade, + Enforment::Deferred, + {Sqlite::NotNull{}}); + + ASSERT_THAT(column, + AllOf(Field(&Column::name, Eq("name")), + Field(&Column::tableName, Eq(tableName)), + Field(&Column::type, ColumnType::Text), + Field(&Column::constraints, + UnorderedElementsAre( + VariantWith<ForeignKey>( + AllOf(Field(&ForeignKey::table, Eq("foreignTable")), + Field(&ForeignKey::column, Eq("foreignColumn")), + Field(&ForeignKey::updateAction, ForeignKeyAction::SetNull), + Field(&ForeignKey::deleteAction, ForeignKeyAction::Cascade), + Field(&ForeignKey::enforcement, Enforment::Deferred))), + VariantWith<Sqlite::NotNull>(Eq(Sqlite::NotNull{})))))); +} + +TEST_F(SqliteTable, AddPrimaryTableContraint) +{ + table.setName(tableName.clone()); + const auto &idColumn = table.addColumn("id"); + const auto &nameColumn = table.addColumn("name"); + table.addPrimaryKeyContraint({idColumn, nameColumn}); + + EXPECT_CALL(mockDatabase, + execute( + Eq("CREATE TABLE testTable(id NUMERIC, name NUMERIC, PRIMARY KEY(id, name))"))); + + table.initialize(mockDatabase); } +} // namespace diff --git a/tests/unit/unittest/sqlitetransaction-test.cpp b/tests/unit/unittest/sqlitetransaction-test.cpp index dc3e5bba70..0aa426db67 100644 --- a/tests/unit/unittest/sqlitetransaction-test.cpp +++ b/tests/unit/unittest/sqlitetransaction-test.cpp @@ -33,12 +33,13 @@ namespace { +using Sqlite::DeferredNonThrowingDestructorTransaction; using Sqlite::DeferredTransaction; -using Sqlite::ImmediateTransaction; +using Sqlite::ExclusiveNonThrowingDestructorTransaction; using Sqlite::ExclusiveTransaction; -using Sqlite::DeferredNonThrowingDestructorTransaction; using Sqlite::ImmediateNonThrowingDestructorTransaction; -using Sqlite::ExclusiveNonThrowingDestructorTransaction; +using Sqlite::ImmediateSessionTransaction; +using Sqlite::ImmediateTransaction; class SqliteTransaction : public testing::Test { @@ -316,4 +317,56 @@ TEST_F(SqliteTransaction, TransactionRollbackInDestructorDontThrows) ASSERT_NO_THROW(ExclusiveNonThrowingDestructorTransaction{mockTransactionBackend}); } +TEST_F(SqliteTransaction, ImmediateSessionTransactionCommit) +{ + InSequence s; + + EXPECT_CALL(mockTransactionBackend, lock()); + EXPECT_CALL(mockTransactionBackend, immediateSessionBegin()); + EXPECT_CALL(mockTransactionBackend, sessionCommit()); + EXPECT_CALL(mockTransactionBackend, unlock()); + + ImmediateSessionTransaction transaction{mockTransactionBackend}; + transaction.commit(); +} + +TEST_F(SqliteTransaction, ImmediateSessionTransactionRollBack) +{ + InSequence s; + + EXPECT_CALL(mockTransactionBackend, lock()); + EXPECT_CALL(mockTransactionBackend, immediateSessionBegin()); + EXPECT_CALL(mockTransactionBackend, sessionRollback()); + EXPECT_CALL(mockTransactionBackend, unlock()); + + ImmediateSessionTransaction transaction{mockTransactionBackend}; +} + +TEST_F(SqliteTransaction, SessionTransactionRollbackInDestructorThrows) +{ + ON_CALL(mockTransactionBackend, sessionRollback()).WillByDefault(Throw(Sqlite::Exception("foo"))); + + ASSERT_THROW(ImmediateSessionTransaction{mockTransactionBackend}, Sqlite::Exception); +} + +TEST_F(SqliteTransaction, ImmidiateSessionTransactionBeginThrows) +{ + ON_CALL(mockTransactionBackend, immediateSessionBegin()) + .WillByDefault(Throw(Sqlite::Exception("foo"))); + + ASSERT_THROW(ImmediateSessionTransaction{mockTransactionBackend}, Sqlite::Exception); } + +TEST_F(SqliteTransaction, ImmediateSessionTransactionBeginThrowsAndNotRollback) +{ + InSequence s; + + EXPECT_CALL(mockTransactionBackend, lock()); + EXPECT_CALL(mockTransactionBackend, immediateSessionBegin()).WillOnce(Throw(Sqlite::Exception("foo"))); + EXPECT_CALL(mockTransactionBackend, sessionRollback()).Times(0); + EXPECT_CALL(mockTransactionBackend, unlock()); + + ASSERT_ANY_THROW(ImmediateSessionTransaction{mockTransactionBackend}); +} + +} // namespace diff --git a/tests/unit/unittest/sqlitevalue-test.cpp b/tests/unit/unittest/sqlitevalue-test.cpp index e9cc5c9e40..1a90c330f0 100644 --- a/tests/unit/unittest/sqlitevalue-test.cpp +++ b/tests/unit/unittest/sqlitevalue-test.cpp @@ -29,6 +29,20 @@ namespace { +TEST(SqliteValue, ConstructDefault) +{ + Sqlite::Value value{}; + + ASSERT_TRUE(value.isNull()); +} + +TEST(SqliteValue, ConstructNullValue) +{ + Sqlite::Value value{Sqlite::NullValue{}}; + + ASSERT_TRUE(value.isNull()); +} + TEST(SqliteValue, ConstructLongLong) { Sqlite::Value value{1LL}; @@ -36,7 +50,7 @@ TEST(SqliteValue, ConstructLongLong) ASSERT_THAT(value.toInteger(), Eq(1LL)); } -TEST(SqliteValue, Construct) +TEST(SqliteValue, ConstructInteger) { Sqlite::Value value{1}; @@ -71,6 +85,24 @@ TEST(SqliteValue, ConstructStringFromQString) ASSERT_THAT(value.toStringView(), Eq("foo")); } +TEST(SqliteValue, ConstructStringFromBlob) +{ + // Utils::span<const Sqlite::byte> bytes{reinterpret_cast<const Sqlite::byte *>("abcd"), 4}; + + // Sqlite::Value value{bytes}; + + //ASSERT_THAT(value.toBlob(), Eq(bytes)); +} + +TEST(SqliteValue, ConstructNullFromNullQVariant) +{ + QVariant variant{}; + + Sqlite::Value value{variant}; + + ASSERT_TRUE(value.isNull()); +} + TEST(SqliteValue, ConstructStringFromIntQVariant) { QVariant variant{1}; @@ -116,6 +148,15 @@ TEST(SqliteValue, ConstructStringFromStringQVariant) ASSERT_THAT(value.toStringView(), Eq("foo")); } +TEST(SqliteValue, ConvertToNullQVariant) +{ + Sqlite::Value value{}; + + auto variant = QVariant{value}; + + ASSERT_TRUE(variant.isNull()); +} + TEST(SqliteValue, ConvertToStringQVariant) { Sqlite::Value value{"foo"}; @@ -192,6 +233,13 @@ TEST(SqliteValue, IntegerAndFloatAreNotEquals) ASSERT_FALSE(isEqual); } +TEST(SqliteValue, NullValuesNeverEqual) +{ + bool isEqual = Sqlite::Value{} == Sqlite::Value{}; + + ASSERT_FALSE(isEqual); +} + TEST(SqliteValue, IntegerValuesAreEquals) { bool isEqual = Sqlite::Value{1} == Sqlite::Value{1}; @@ -248,6 +296,13 @@ TEST(SqliteValue, IntegersAreUnequalInverse) ASSERT_TRUE(isUnequal); } +TEST(SqliteValue, NullType) +{ + auto type = Sqlite::Value{}.type(); + + ASSERT_THAT(type, Sqlite::ValueType::Null); +} + TEST(SqliteValue, IntegerType) { auto type = Sqlite::Value{1}.type(); @@ -269,6 +324,20 @@ TEST(SqliteValue, StringType) ASSERT_THAT(type, Sqlite::ValueType::String); } +TEST(SqliteValue, NullValueAndValueViewAreNotEqual) +{ + bool isEqual = Sqlite::ValueView::create(Sqlite::NullValue{}) == Sqlite::Value{}; + + ASSERT_FALSE(isEqual); +} + +TEST(SqliteValue, NullValueViewAndValueAreNotEqual) +{ + bool isEqual = Sqlite::Value{} == Sqlite::ValueView::create(Sqlite::NullValue{}); + + ASSERT_FALSE(isEqual); +} + TEST(SqliteValue, StringValueAndValueViewEquals) { bool isEqual = Sqlite::ValueView::create("foo") == Sqlite::Value{"foo"}; @@ -318,6 +387,15 @@ TEST(SqliteValue, StringValueAndIntergerValueViewAreNotEqual) ASSERT_FALSE(isEqual); } +TEST(SqliteValue, ConvertNullValueViewIntoValue) +{ + auto view = Sqlite::ValueView::create(Sqlite::NullValue{}); + + Sqlite::Value value{view}; + + ASSERT_TRUE(value.isNull()); +} + TEST(SqliteValue, ConvertStringValueViewIntoValue) { auto view = Sqlite::ValueView::create("foo"); diff --git a/tests/unit/unittest/symbolquery-test.cpp b/tests/unit/unittest/symbolquery-test.cpp index 8a7fad4272..3ff541564e 100644 --- a/tests/unit/unittest/symbolquery-test.cpp +++ b/tests/unit/unittest/symbolquery-test.cpp @@ -74,15 +74,18 @@ class SymbolQuerySlowTest : public testing::Test protected: void SetUp() override { - database.execute("INSERT INTO sources VALUES (1, 1, \"filename.h\")"); - database.execute("INSERT INTO sources VALUES (2, 1, \"filename.cpp\")"); - database.execute("INSERT INTO directories VALUES (1, \"/path/to\")"); + database.execute("INSERT INTO sources VALUES (1, 1, 'filename.h')"); + database.execute("INSERT INTO sources VALUES (2, 1, 'filename.cpp')"); + database.execute("INSERT INTO directories VALUES (1, '/path/to')"); database.execute("INSERT INTO locations VALUES (1, 2, 3, 1, 2)"); database.execute("INSERT INTO locations VALUES (1, 4, 6, 2, 1)"); database.execute("INSERT INTO locations VALUES (1, 20, 36, 2, 3)"); - database.execute("INSERT INTO symbols VALUES (1, \"functionusr\", \"Function\", 3, \"void function(int)\")"); - database.execute("INSERT INTO symbols VALUES (2, \"classusr\", \"Class\", 2, \"class Class final\")"); - database.execute("INSERT INTO symbols VALUES (3, \"enumusr\", \"Enum\", 1, \"enum Enum : char\")"); + database.execute( + "INSERT INTO symbols VALUES (1, 'functionusr', 'Function', 3, 'void function(int)')"); + database.execute( + "INSERT INTO symbols VALUES (2, 'classusr', 'Class', 2, 'class Class final')"); + database.execute( + "INSERT INTO symbols VALUES (3, 'enumusr', 'Enum', 1, 'enum Enum : char')"); } protected: diff --git a/tests/unit/unittest/unittest.pro b/tests/unit/unittest/unittest.pro index 6155cf3212..03f04f3982 100644 --- a/tests/unit/unittest/unittest.pro +++ b/tests/unit/unittest/unittest.pro @@ -63,6 +63,7 @@ SOURCES += \ filepathview-test.cpp \ gtest-creator-printing.cpp \ gtest-qt-printing.cpp \ + lastchangedrowid-test.cpp \ lineprefixer-test.cpp \ locatorfilter-test.cpp \ matchingtext-test.cpp \ @@ -80,6 +81,7 @@ SOURCES += \ smallstring-test.cpp \ sourcerangefilter-test.cpp \ spydummy.cpp \ + sqlitesessions-test.cpp \ sqlitevalue-test.cpp \ symbolindexer-test.cpp \ symbolsfindfilter-test.cpp \ diff --git a/tests/unit/unittest/unittests-main.cpp b/tests/unit/unittest/unittests-main.cpp index f5eaa8846c..6704a08034 100644 --- a/tests/unit/unittest/unittests-main.cpp +++ b/tests/unit/unittest/unittests-main.cpp @@ -25,8 +25,8 @@ #include "googletest.h" +#include <sqlitedatabase.h> #include <sqliteglobal.h> - #include <utils/temporarydirectory.h> #include <QCoreApplication> @@ -38,6 +38,7 @@ int main(int argc, char *argv[]) { + Sqlite::Database::activateLogging(); const QString temporayDirectoryPath = QDir::tempPath() +"/QtCreator-UnitTests-XXXXXX"; Utils::TemporaryDirectory::setMasterTemporaryDirectory(temporayDirectoryPath); qputenv("TMPDIR", Utils::TemporaryDirectory::masterDirectoryPath().toUtf8()); |