From e4fea9f159904b71f5c11a0fcca7b6760bceb483 Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Wed, 28 Apr 2021 16:18:59 +0200 Subject: [PATCH] QmlDesigner: Add project storage Task-number: QDS-4252 Change-Id: Ic74e027b20bc41f3712e3ae155cddc64f8972cb0 Reviewed-by: Qt CI Bot Reviewed-by: Thomas Hartmann --- .../designercore/include/projectstorageids.h | 99 ++ .../designercore/metainfo/projectstorage.h | 446 +++++++ .../metainfo/projectstorageexceptions.h | 68 ++ .../metainfo/projectstoragetypes.h | 167 +++ .../designercore/metainfo/sourceid.h | 73 ++ .../designercore/metainfo/sourcepath.h | 148 +++ .../metainfo/sourcepathcontextid.h | 66 ++ .../designercore/metainfo/sourcepathview.h | 107 ++ .../designercore/metainfo/storagecache.h | 115 +- .../designercore/metainfo/storagecacheentry.h | 5 + .../qmldesigner/qmldesignerunittestfiles.pri | 10 +- .../unit/unittest/gtest-creator-printing.cpp | 8 + tests/unit/unittest/gtest-creator-printing.h | 14 + tests/unit/unittest/projectstorage-test.cpp | 1034 +++++++++++++++++ tests/unit/unittest/projectstoragemock.h | 63 + tests/unit/unittest/sourcepath-test.cpp | 89 ++ tests/unit/unittest/sourcepathview-test.cpp | 123 ++ tests/unit/unittest/sqlitedatabasemock.h | 3 + tests/unit/unittest/sqlitereadstatementmock.h | 81 +- .../unittest/sqlitereadwritestatementmock.cpp | 35 + .../unittest/sqlitereadwritestatementmock.h | 86 ++ .../unit/unittest/sqlitewritestatementmock.h | 2 + tests/unit/unittest/storagecache-test.cpp | 173 +-- tests/unit/unittest/unittest.pro | 10 +- 24 files changed, 2905 insertions(+), 120 deletions(-) create mode 100644 src/plugins/qmldesigner/designercore/include/projectstorageids.h create mode 100644 src/plugins/qmldesigner/designercore/metainfo/projectstorage.h create mode 100644 src/plugins/qmldesigner/designercore/metainfo/projectstorageexceptions.h create mode 100644 src/plugins/qmldesigner/designercore/metainfo/projectstoragetypes.h create mode 100644 src/plugins/qmldesigner/designercore/metainfo/sourceid.h create mode 100644 src/plugins/qmldesigner/designercore/metainfo/sourcepath.h create mode 100644 src/plugins/qmldesigner/designercore/metainfo/sourcepathcontextid.h create mode 100644 src/plugins/qmldesigner/designercore/metainfo/sourcepathview.h create mode 100644 tests/unit/unittest/projectstorage-test.cpp create mode 100644 tests/unit/unittest/projectstoragemock.h create mode 100644 tests/unit/unittest/sourcepath-test.cpp create mode 100644 tests/unit/unittest/sourcepathview-test.cpp create mode 100644 tests/unit/unittest/sqlitereadwritestatementmock.cpp create mode 100644 tests/unit/unittest/sqlitereadwritestatementmock.h diff --git a/src/plugins/qmldesigner/designercore/include/projectstorageids.h b/src/plugins/qmldesigner/designercore/include/projectstorageids.h new file mode 100644 index 00000000000..9d3ef1f28e5 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/include/projectstorageids.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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. +** +****************************************************************************/ + +#pragma once + +#include + +namespace QmlDesigner { + +template +class BasicId +{ +public: + using DatabaseType = InternalIntergerType; + + constexpr explicit BasicId() = default; + + BasicId(const char *) = delete; + + constexpr explicit BasicId(InternalIntergerType id) + : id{id} + {} + + constexpr friend bool operator==(BasicId first, BasicId second) + { + return first.id == second.id && first.isValid() && second.isValid(); + } + + constexpr friend bool operator!=(BasicId first, BasicId second) { return !(first == second); } + + constexpr friend bool operator<(BasicId first, BasicId second) { return first.id < second.id; } + constexpr friend bool operator>(BasicId first, BasicId second) { return first.id > second.id; } + constexpr friend bool operator<=(BasicId first, BasicId second) + { + return first.id <= second.id; + } + constexpr friend bool operator>=(BasicId first, BasicId second) + { + return first.id >= second.id; + } + + constexpr bool isValid() const { return id >= 0; } + + explicit operator bool() const { return isValid(); } + + explicit operator std::size_t() const { return static_cast(id); } + + InternalIntergerType operator&() const { return id; } + +public: + InternalIntergerType id = -1; +}; + +enum class BasicIdType { + Type, + PropertyType, + PropertyDeclaration, + SourceId, + SourceContextId, + StorageCacheIndex +}; + +using TypeId = BasicId; +using TypeIds = std::vector; + +using PropertyDeclarationId = BasicId; +using PropertyDeclarationIds = std::vector; + +using SourceContextId = BasicId; +using SourceContextIds = std::vector; + +using SourceId = BasicId; +using SourceIds = std::vector; + +enum class TypeAccessSemantics { Reference, Value, Sequence, IsEnum = 0xF }; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/metainfo/projectstorage.h b/src/plugins/qmldesigner/designercore/metainfo/projectstorage.h new file mode 100644 index 00000000000..c81d1f6c4ee --- /dev/null +++ b/src/plugins/qmldesigner/designercore/metainfo/projectstorage.h @@ -0,0 +1,446 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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. +** +****************************************************************************/ + +#pragma once + +#include "projectstorageexceptions.h" +#include "projectstorageids.h" +#include "projectstoragetypes.h" + +#include +#include + +#include + +#include + +namespace QmlDesigner { + +template +class ProjectStorage +{ +public: + template + using ReadStatement = typename Database::template ReadStatement; + template + using ReadWriteStatement = typename Database::template ReadWriteStatement; + using WriteStatement = typename Database::WriteStatement; + + ProjectStorage(Database &database, bool isInitialized) + : database{database} + , initializer{database, isInitialized} + {} + + template + TypeId upsertType(Utils::SmallStringView name, + TypeId prototype, + TypeAccessSemantics accessSemantics, + const Container &qualifiedNames) + { + Sqlite::ImmediateTransaction transaction{database}; + + auto typeId = upsertTypeStatement.template value(name, + static_cast(accessSemantics), + &prototype); + + for (Utils::SmallStringView qualifiedName : qualifiedNames) + upsertQualifiedTypeNameStatement.write(qualifiedName, &typeId); + + transaction.commit(); + + return typeId; + } + + PropertyDeclarationId upsertPropertyDeclaration(TypeId typeId, + Utils::SmallStringView name, + TypeId propertyTypeId) + { + Sqlite::ImmediateTransaction transaction{database}; + + auto propertyDeclarationId = upsertPropertyDeclarationStatement + .template value(&typeId, + name, + &propertyTypeId); + + transaction.commit(); + + return propertyDeclarationId; + } + + PropertyDeclarationId fetchPropertyDeclarationByTypeIdAndName(TypeId typeId, + Utils::SmallStringView name) + { + return selectPropertyDeclarationByTypeIdAndNameStatement + .template valueWithTransaction(&typeId, name); + } + + TypeId fetchTypeIdByQualifiedName(Utils::SmallStringView name) + { + return selectTypeIdByQualifiedNameStatement.template valueWithTransaction(name); + } + + bool fetchIsProtype(TypeId type, TypeId prototype) + { + return bool( + selectPrototypeIdStatement.template valueWithTransaction(&type, &prototype)); + } + + auto fetchPrototypes(TypeId type) + { + return selectPrototypeIdsStatement.template rangeWithTransaction(&type); + } + + SourceContextId fetchSourceContextIdUnguarded(Utils::SmallStringView sourceContextPath) + { + auto sourceContextId = readSourceContextId(sourceContextPath); + + return sourceContextId ? sourceContextId : writeSourceContextId(sourceContextPath); + } + + SourceContextId fetchSourceContextId(Utils::SmallStringView sourceContextPath) + { + try { + Sqlite::DeferredTransaction transaction{database}; + + auto sourceContextId = fetchSourceContextIdUnguarded(sourceContextPath); + + transaction.commit(); + + return sourceContextId; + } catch (const Sqlite::ConstraintPreventsModification &) { + return fetchSourceContextId(sourceContextPath); + } + } + + Utils::PathString fetchSourceContextPath(SourceContextId sourceContextId) const + { + Sqlite::DeferredTransaction transaction{database}; + + auto optionalSourceContextPath = selectSourceContextPathFromSourceContextsBySourceContextIdStatement + .template optionalValue( + &sourceContextId); + + if (!optionalSourceContextPath) + throw SourceContextIdDoesNotExists(); + + transaction.commit(); + + return std::move(*optionalSourceContextPath); + } + + auto fetchAllSourceContexts() const + { + return selectAllSourceContextsStatement.template rangeWithTransaction(); + } + + SourceId fetchSourceId(SourceContextId sourceContextId, Utils::SmallStringView sourceName) + { + Sqlite::DeferredTransaction transaction{database}; + + auto sourceId = fetchSourceIdUnguarded(sourceContextId, sourceName); + + transaction.commit(); + + return sourceId; + } + + Sources::SourceNameAndSourceContextId fetchSourceNameAndSourceContextId(SourceId sourceId) const + { + auto value = selectSourceNameAndSourceContextIdFromSourcesBySourceIdStatement + .template valueWithTransaction( + &sourceId); + + if (!value.sourceContextId) + throw SourceIdDoesNotExists(); + + return value; + } + + SourceContextId fetchSourceContextId(SourceId sourceId) const + { + auto sourceContextId = selectSourceContextIdFromSourcesBySourceIdStatement + .template valueWithTransaction(sourceId.id); + + if (!sourceContextId) + throw SourceIdDoesNotExists(); + + return sourceContextId; + } + + auto fetchAllSources() const + { + return selectAllSourcesStatement.template rangeWithTransaction(); + } + + SourceId fetchSourceIdUnguarded(SourceContextId sourceContextId, Utils::SmallStringView sourceName) + { + auto sourceId = readSourceId(sourceContextId, sourceName); + + if (sourceId) + return sourceId; + + return writeSourceId(sourceContextId, sourceName); + } + +private: + SourceContextId readSourceContextId(Utils::SmallStringView sourceContextPath) + { + return selectSourceContextIdFromSourceContextsBySourceContextPathStatement + .template value(sourceContextPath); + } + + SourceContextId writeSourceContextId(Utils::SmallStringView sourceContextPath) + { + insertIntoSourceContextsStatement.write(sourceContextPath); + + return SourceContextId(database.lastInsertedRowId()); + } + + SourceId writeSourceId(SourceContextId sourceContextId, Utils::SmallStringView sourceName) + { + insertIntoSourcesStatement.write(&sourceContextId, sourceName); + + return SourceId(database.lastInsertedRowId()); + } + + SourceId readSourceId(SourceContextId sourceContextId, Utils::SmallStringView sourceName) + { + return selectSourceIdFromSourcesBySourceContextIdAndSourceNameStatement + .template value(&sourceContextId, sourceName); + } + + class Initializer + { + public: + Initializer(Database &database, bool isInitialized) + { + if (!isInitialized) { + Sqlite::ExclusiveTransaction transaction{database}; + + createTypesTable(database); + createQualifiedTypeNamesTable(database); + createPropertyDeclarationsTable(database); + createEnumValuesTable(database); + createMethodsTable(database); + createSignalsTable(database); + createSourceContextsTable(database); + createSourcesTable(database); + + transaction.commit(); + + database.walCheckpointFull(); + } + } + + void createPropertyDeclarationsTable(Database &database) + { + Sqlite::Table table; + table.setUseIfNotExists(true); + table.setName("propertyDeclarations"); + table.addColumn("propertyDeclarationId", + Sqlite::ColumnType::Integer, + {Sqlite::PrimaryKey{}}); + auto &typeIdColumn = table.addColumn("typeId"); + auto &nameColumn = table.addColumn("name"); + table.addColumn("propertyTypeId"); + + table.addUniqueIndex({typeIdColumn, nameColumn}); + + table.initialize(database); + } + + void createTypesTable(Database &database) + { + Sqlite::Table table; + table.setUseIfNotExists(true); + table.setName("types"); + table.addColumn("typeId", Sqlite::ColumnType::Integer, {Sqlite::PrimaryKey{}}); + auto &nameColumn = table.addColumn("name"); + table.addColumn("accessSemantics"); + table.addColumn("prototype"); + table.addColumn("defaultProperty"); + + table.addUniqueIndex({nameColumn}); + + table.initialize(database); + } + + void createQualifiedTypeNamesTable(Database &database) + { + Sqlite::Table table; + table.setUseIfNotExists(true); + table.setUseWithoutRowId(true); + table.setName("qualifiedTypeNames"); + auto &qualifiedNameColumn = table.addColumn("qualifiedName"); + table.addColumn("typeId"); + + table.addPrimaryKeyContraint({qualifiedNameColumn}); + + table.initialize(database); + } + + void createEnumValuesTable(Database &database) + { + Sqlite::Table table; + table.setUseIfNotExists(true); + table.setName("enumerationValues"); + table.addColumn("enumerationValueId", Sqlite::ColumnType::Integer, {Sqlite::PrimaryKey{}}); + auto &enumIdColumn = table.addColumn("typeId"); + auto &nameColumn = table.addColumn("name"); + + table.addUniqueIndex({enumIdColumn, nameColumn}); + + table.initialize(database); + } + + void createMethodsTable(Database &database) + { + Sqlite::Table table; + table.setUseIfNotExists(true); + table.setName("methods"); + table.addColumn("methodId", Sqlite::ColumnType::Integer, {Sqlite::PrimaryKey{}}); + auto &nameColumn = table.addColumn("name"); + + table.addUniqueIndex({nameColumn}); + + table.initialize(database); + } + + void createSignalsTable(Database &database) + { + Sqlite::Table table; + table.setUseIfNotExists(true); + table.setName("signals"); + table.addColumn("signalId", Sqlite::ColumnType::Integer, {Sqlite::PrimaryKey{}}); + auto &nameColumn = table.addColumn("name"); + + table.addUniqueIndex({nameColumn}); + + table.initialize(database); + } + + void createSourceContextsTable(Database &database) + { + Sqlite::Table table; + table.setUseIfNotExists(true); + table.setName("sourceContexts"); + table.addColumn("sourceContextId", Sqlite::ColumnType::Integer, {Sqlite::PrimaryKey{}}); + const Sqlite::Column &sourceContextPathColumn = table.addColumn("sourceContextPath"); + + table.addUniqueIndex({sourceContextPathColumn}); + + table.initialize(database); + } + + void createSourcesTable(Database &database) + { + Sqlite::Table table; + table.setUseIfNotExists(true); + table.setName("sources"); + table.addColumn("sourceId", Sqlite::ColumnType::Integer, {Sqlite::PrimaryKey{}}); + const Sqlite::Column &sourceContextIdColumn = table.addColumn( + "sourceContextId", + Sqlite::ColumnType::Integer, + {Sqlite::NotNull{}, + Sqlite::ForeignKey{"sourceContexts", + "sourceContextId", + Sqlite::ForeignKeyAction::NoAction, + Sqlite::ForeignKeyAction::Cascade}}); + const Sqlite::Column &sourceNameColumn = table.addColumn("sourceName", + Sqlite::ColumnType::Text); + table.addUniqueIndex({sourceContextIdColumn, sourceNameColumn}); + + table.initialize(database); + } + }; + +public: + Database &database; + Initializer initializer; + ReadWriteStatement<1> upsertTypeStatement{ + "INSERT INTO types(name, accessSemantics, prototype) VALUES(?1, ?2, nullif(?3, -1)) ON " + "CONFLICT DO UPDATE SET " + "prototype=excluded.prototype, accessSemantics=excluded.accessSemantics RETURNING typeId", + database}; + mutable ReadStatement<1> selectTypeIdByQualifiedNameStatement{ + "SELECT typeId FROM qualifiedTypeNames WHERE qualifiedName=?", database}; + mutable ReadStatement<1> selectPrototypeIdStatement{ + "WITH RECURSIVE " + " typeSelection(typeId) AS (" + " VALUES(?1) " + " UNION ALL " + " SELECT prototype FROM types JOIN typeSelection USING(typeId)) " + "SELECT typeId FROM typeSelection WHERE typeId=?2 LIMIT 1", + database}; + ReadWriteStatement<1> upsertPropertyDeclarationStatement{ + "INSERT INTO propertyDeclarations(typeId, name, propertyTypeId) VALUES(?1, ?2, ?3) ON " + "CONFLICT DO UPDATE SET " + "typeId=excluded.typeId, name=excluded.name, propertyTypeId=excluded.propertyTypeId " + "RETURNING propertyDeclarationId", + database}; + mutable ReadStatement<1> selectPropertyDeclarationByTypeIdAndNameStatement{ + "WITH RECURSIVE " + " typeSelection(typeId) AS (" + " VALUES(?1) " + " UNION ALL " + " SELECT prototype FROM types JOIN typeSelection USING(typeId)) " + "SELECT propertyDeclarationId FROM propertyDeclarations JOIN typeSelection USING(typeId) " + " WHERE name=?2 LIMIT 1", + database}; + WriteStatement upsertQualifiedTypeNameStatement{ + "INSERT INTO qualifiedTypeNames(qualifiedName, typeId) VALUES(?1, ?2) ON CONFLICT DO " + "UPDATE SET typeId=excluded.typeId", + database}; + mutable ReadStatement<1> selectAccessSemanticsStatement{ + "SELECT typeId FROM qualifiedTypeNames WHERE qualifiedName=?", database}; + mutable ReadStatement<1> selectPrototypeIdsStatement{ + "WITH RECURSIVE " + " typeSelection(typeId) AS (" + " VALUES(?1) " + " UNION ALL " + " SELECT prototype FROM types JOIN typeSelection USING(typeId)) " + "SELECT typeId FROM typeSelection", + database}; + mutable ReadStatement<1> selectSourceContextIdFromSourceContextsBySourceContextPathStatement{ + "SELECT sourceContextId FROM sourceContexts WHERE sourceContextPath = ?", database}; + mutable ReadStatement<1> selectSourceContextPathFromSourceContextsBySourceContextIdStatement{ + "SELECT sourceContextPath FROM sourceContexts WHERE sourceContextId = ?", database}; + mutable ReadStatement<2> selectAllSourceContextsStatement{ + "SELECT sourceContextPath, sourceContextId FROM sourceContexts", database}; + WriteStatement insertIntoSourceContextsStatement{ + "INSERT INTO sourceContexts(sourceContextPath) VALUES (?)", database}; + mutable ReadStatement<1> selectSourceIdFromSourcesBySourceContextIdAndSourceNameStatement{ + "SELECT sourceId FROM sources WHERE sourceContextId = ? AND sourceName = ?", database}; + mutable ReadStatement<2> selectSourceNameAndSourceContextIdFromSourcesBySourceIdStatement{ + "SELECT sourceName, sourceContextId FROM sources WHERE sourceId = ?", database}; + mutable ReadStatement<1> selectSourceContextIdFromSourcesBySourceIdStatement{ + "SELECT sourceContextId FROM sources WHERE sourceId = ?", database}; + WriteStatement insertIntoSourcesStatement{ + "INSERT INTO sources(sourceContextId, sourceName) VALUES (?,?)", database}; + mutable ReadStatement<3> selectAllSourcesStatement{ + "SELECT sourceName, sourceContextId, sourceId FROM sources", database}; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/metainfo/projectstorageexceptions.h b/src/plugins/qmldesigner/designercore/metainfo/projectstorageexceptions.h new file mode 100644 index 00000000000..20134a5ee37 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/metainfo/projectstorageexceptions.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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. +** +****************************************************************************/ + +#pragma once + +#include + +namespace QmlDesigner { + +class NoFilePathForInvalidFilePathId : std::exception +{ +public: + const char *what() const noexcept override + { + return "You cannot get a file path for an invalid file path id!"; + } +}; + +class NoSourceContextPathForInvalidSourceContextId : std::exception +{ +public: + const char *what() const noexcept override + { + return "You cannot get a directory path for an invalid directory path id!"; + } +}; + +class SourceContextIdDoesNotExists : std::exception +{ +public: + const char *what() const noexcept override + { + return "The source context id does not exist in the database!"; + } +}; + +class SourceIdDoesNotExists : std::exception +{ +public: + const char *what() const noexcept override + { + return "The source id does not exist in the database!"; + } +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/metainfo/projectstoragetypes.h b/src/plugins/qmldesigner/designercore/metainfo/projectstoragetypes.h new file mode 100644 index 00000000000..bb5da21296b --- /dev/null +++ b/src/plugins/qmldesigner/designercore/metainfo/projectstoragetypes.h @@ -0,0 +1,167 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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. +** +****************************************************************************/ + +#pragma once + +#include "projectstorageids.h" +#include "storagecacheentry.h" + +#include + +#include +#include +#include +#include + +namespace QmlDesigner { + +class FileNameView +{ +public: + friend bool operator==(const FileNameView &first, const FileNameView &second) + { + return first.sourceContextId == second.sourceContextId && first.fileName == second.fileName; + } + + static int compare(FileNameView first, FileNameView second) noexcept + { + int directoryDifference = first.sourceContextId.id - second.sourceContextId.id; + + if (directoryDifference) + return directoryDifference; + + return Utils::compare(first.fileName, second.fileName); + } + +public: + Utils::SmallStringView fileName; + SourceContextId sourceContextId; +}; + +class FileNameEntry +{ +public: + FileNameEntry(Utils::SmallStringView fileName, int sourceContextId) + : fileName(fileName) + , sourceContextId(sourceContextId) + {} + + FileNameEntry(Utils::SmallStringView fileName, SourceContextId sourceContextId) + : fileName(fileName) + , sourceContextId(sourceContextId) + {} + + FileNameEntry(FileNameView view) + : fileName(view.fileName) + , sourceContextId(view.sourceContextId) + {} + + friend bool operator==(const FileNameEntry &first, const FileNameEntry &second) + { + return first.sourceContextId == second.sourceContextId && first.fileName == second.fileName; + } + + friend bool operator!=(const FileNameEntry &first, const FileNameEntry &second) + { + return !(first == second); + } + + friend bool operator==(const FileNameEntry &first, const FileNameView &second) + { + return first.sourceContextId == second.sourceContextId && first.fileName == second.fileName; + } + + friend bool operator!=(const FileNameEntry &first, const FileNameView &second) + { + return !(first == second); + } + + operator FileNameView() const { return {fileName, sourceContextId}; } + + operator Utils::SmallString() && { return std::move(fileName); } + +public: + Utils::SmallString fileName; + SourceContextId sourceContextId; +}; + +namespace Sources { +class SourceContext + : public StorageCacheEntry +{ + using Base = StorageCacheEntry; + +public: + using Base::Base; + + friend bool operator==(const SourceContext &first, const SourceContext &second) + { + return first.id == second.id && first.value == second.value; + } +}; + +using SourceContexts = std::vector; + +class Source : public StorageCacheEntry +{ + using Base = StorageCacheEntry; + +public: + using Base::Base; + Source(Utils::SmallStringView sourceName, SourceContextId sourceContextId, SourceId sourceId) + : Base{{sourceName, sourceContextId}, sourceId} + {} + + Source(Utils::SmallStringView sourceName, int sourceContextId, int sourceId) + : Base{{sourceName, SourceContextId{sourceContextId}}, SourceId{sourceId}} + {} + + friend bool operator==(const Source &first, const Source &second) + { + return first.id == second.id && first.value == second.value; + } +}; + +using Sources = std::vector; + +class SourceNameAndSourceContextId +{ +public: + constexpr SourceNameAndSourceContextId() = default; + SourceNameAndSourceContextId(Utils::SmallStringView sourceName, int sourceContextId) + : sourceName(sourceName) + , sourceContextId(sourceContextId) + {} + SourceNameAndSourceContextId(Utils::SmallStringView sourceName, SourceContextId sourceContextId) + : sourceName{sourceName} + , sourceContextId{sourceContextId} + {} + + Utils::SmallString sourceName; + SourceContextId sourceContextId; +}; +} // namespace ClangBackEnd + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/metainfo/sourceid.h b/src/plugins/qmldesigner/designercore/metainfo/sourceid.h new file mode 100644 index 00000000000..d0b3de3ec4b --- /dev/null +++ b/src/plugins/qmldesigner/designercore/metainfo/sourceid.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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. +** +****************************************************************************/ + +#pragma once + +#include +#include +#include + +namespace QmlDesigner { + +class SourceId +{ +public: + constexpr SourceId() = default; + + SourceId(const char *) = delete; + + explicit SourceId(int id) + : id(id) + {} + + bool isValid() const { return id >= 0; } + + friend bool operator==(SourceId first, SourceId second) + { + return first.isValid() && first.id == second.id; + } + + friend bool operator!=(SourceId first, SourceId second) { return !(first == second); } + + friend bool operator<(SourceId first, SourceId second) { return first.id < second.id; } + +public: + int id = -1; +}; + +using SourceIds = std::vector; + +} // namespace QmlDesigner + +namespace std { +template<> +struct hash +{ + using argument_type = QmlDesigner::SourceId; + using result_type = std::size_t; + result_type operator()(const argument_type &id) const { return std::hash{}(id.id); } +}; + +} // namespace std diff --git a/src/plugins/qmldesigner/designercore/metainfo/sourcepath.h b/src/plugins/qmldesigner/designercore/metainfo/sourcepath.h new file mode 100644 index 00000000000..b99ab044902 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/metainfo/sourcepath.h @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#pragma once + +#include "sourcepathview.h" + +#include + +namespace QmlDesigner { + +class SourcePath : public Utils::PathString +{ + using size_type = Utils::PathString::size_type; + +public: + SourcePath() = default; + explicit SourcePath(Utils::PathString &&sourcePath) + : Utils::PathString(std::move(sourcePath)) + { + SourcePathView view{*this}; + + m_slashIndex = view.slashIndex(); + } + + explicit SourcePath(Utils::SmallStringView &&sourcePath) + : Utils::PathString(sourcePath) + { + SourcePathView view{*this}; + + m_slashIndex = view.slashIndex(); + } + + SourcePath(SourcePathView sourcePathView) + : Utils::PathString(sourcePathView.toStringView()) + , m_slashIndex(sourcePathView.slashIndex()) + { + } + + template + SourcePath(const char (&string)[Size]) noexcept + : SourcePath(SourcePathView(string, Size - 1)) + { + static_assert(Size >= 1, "Invalid string literal! Length is zero!"); + } + + explicit SourcePath(const Utils::PathString &sourcePath) + : SourcePath(sourcePath.clone()) + { + } + + explicit SourcePath(Utils::PathString &&sourcePath, std::ptrdiff_t slashIndex) + : Utils::PathString(std::move(sourcePath)) + , m_slashIndex(slashIndex) + { + } + + explicit SourcePath(const QString &sourcePath) + : SourcePath(Utils::PathString(sourcePath)) + { + } + + SourcePath(Utils::SmallStringView directory, Utils::SmallStringView name) + : Utils::PathString({directory, "/", name}) + , m_slashIndex(std::ptrdiff_t(directory.size())) + {} + + bool isValid() const { return size() > 0 && m_slashIndex >= 0; } + + Utils::SmallStringView directory() const noexcept + { + return mid(0, std::size_t(std::max(std::ptrdiff_t(0), m_slashIndex))); + } + + Utils::SmallStringView name() const noexcept + { + return mid(std::size_t(m_slashIndex + 1), + std::size_t(std::ptrdiff_t(size()) - m_slashIndex - std::ptrdiff_t(1))); + } + + const Utils::PathString &path() const noexcept + { + return *this; + } + + operator SourcePathView() const noexcept { return SourcePathView(toStringView()); } + + operator Utils::SmallStringView() const noexcept + { + return toStringView(); + } + + friend bool operator==(const SourcePath &first, const SourcePath &second) + { + return first.slashIndex() == second.slashIndex() + && first.name() == second.name() + && first.directory() == second.directory(); + } + + friend bool operator==(const SourcePath &first, const SourcePathView &second) + { + return first.toStringView() == second.toStringView(); + } + + friend bool operator==(const SourcePathView &first, const SourcePath &second) + { + return second == first; + } + + friend bool operator<(const SourcePath &first, const SourcePath &second) + { + return std::make_tuple(first.slashIndex(), first.name(), first.directory()) + < std::make_tuple(second.slashIndex(), second.name(), second.directory()); + } + + SourcePath clone() const { return *this; } + + std::ptrdiff_t slashIndex() const { return m_slashIndex; } + +private: + std::ptrdiff_t m_slashIndex = -1; +}; + +using SourcePaths = std::vector; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/metainfo/sourcepathcontextid.h b/src/plugins/qmldesigner/designercore/metainfo/sourcepathcontextid.h new file mode 100644 index 00000000000..5121a44d04f --- /dev/null +++ b/src/plugins/qmldesigner/designercore/metainfo/sourcepathcontextid.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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. +** +****************************************************************************/ + +#pragma once + +#include + +namespace QmlDesigner { + +class SourcePathContextId +{ +public: + constexpr SourcePathContextId() = default; + + SourcePathContextId(const char *) = delete; + + SourcePathContextId(int directoryPathId) + : directoryPathId(directoryPathId) + {} + + bool isValid() const { return directoryPathId >= 0; } + + friend bool operator==(SourcePathContextId first, SourcePathContextId second) + { + return first.isValid() && first.directoryPathId == second.directoryPathId; + } + + friend bool operator!=(SourcePathContextId first, SourcePathContextId second) + { + return !(first == second); + } + + friend bool operator<(SourcePathContextId first, SourcePathContextId second) + { + return first.directoryPathId < second.directoryPathId; + } + +public: + int directoryPathId = -1; +}; + +using SourcePathContextIds = std::vector; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/metainfo/sourcepathview.h b/src/plugins/qmldesigner/designercore/metainfo/sourcepathview.h new file mode 100644 index 00000000000..6689e28c48b --- /dev/null +++ b/src/plugins/qmldesigner/designercore/metainfo/sourcepathview.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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. +** +****************************************************************************/ + +#pragma once + +#include + +#include +#include + +namespace QmlDesigner { + +template +class AbstractSourcePathView : public Utils::SmallStringView +{ +public: + constexpr AbstractSourcePathView() = default; + explicit AbstractSourcePathView(const char *const string, const size_type size) noexcept + : AbstractSourcePathView{Utils::SmallStringView{string, size}} + { + } + + explicit AbstractSourcePathView(Utils::SmallStringView filePath) + : Utils::SmallStringView(filePath) + , m_slashIndex(lastSlashIndex(filePath)) + { + } + + template::value>> + explicit AbstractSourcePathView(String &&filePath) + : AbstractSourcePathView(filePath.data(), filePath.size()) + { + } + + template + AbstractSourcePathView(const char (&string)[Size]) noexcept + : AbstractSourcePathView(string, Size - 1) + { + static_assert(Size >= 1, "Invalid string literal! Length is zero!"); + } + + Utils::SmallStringView toStringView() const + { + return *this; + } + + std::ptrdiff_t slashIndex() const + { + return m_slashIndex; + } + + Utils::SmallStringView directory() const noexcept + { + return mid(0, std::size_t(std::max(std::ptrdiff_t(0), m_slashIndex))); + } + + Utils::SmallStringView name() const noexcept + { + return mid(std::size_t(m_slashIndex + 1), + std::size_t(std::ptrdiff_t(size()) - m_slashIndex - std::ptrdiff_t(1))); + } + + static + std::ptrdiff_t lastSlashIndex(Utils::SmallStringView filePath) + { + auto foundReverse = std::find(filePath.rbegin(), filePath.rend(), separator); + auto found = foundReverse.base(); + + auto distance = std::distance(filePath.begin(), found); + + return distance - 1; + } + + friend bool operator==(const AbstractSourcePathView &first, const AbstractSourcePathView &second) + { + return first.toStringView() == second.toStringView(); + } + +private: + std::ptrdiff_t m_slashIndex = -1; +}; + +using SourcePathView = AbstractSourcePathView<'/'>; +using SourcePathViews = std::vector; +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/metainfo/storagecache.h b/src/plugins/qmldesigner/designercore/metainfo/storagecache.h index 55759c69f5d..b0aa7c574cf 100644 --- a/src/plugins/qmldesigner/designercore/metainfo/storagecache.h +++ b/src/plugins/qmldesigner/designercore/metainfo/storagecache.h @@ -25,6 +25,7 @@ #pragma once +#include "projectstorageids.h" #include "storagecacheentry.h" #include "storagecachefwd.h" @@ -51,8 +52,8 @@ class NonLockingMutex { public: constexpr NonLockingMutex() noexcept {} - NonLockingMutex(const NonLockingMutex&) = delete; - NonLockingMutex& operator=(const NonLockingMutex&) = delete; + NonLockingMutex(const NonLockingMutex &) = delete; + NonLockingMutex &operator=(const NonLockingMutex &) = delete; void lock() {} void unlock() {} void lock_shared() {} @@ -71,6 +72,57 @@ class StorageCache friend StorageCache; using ResultType = std::conditional_t::value, ViewType, Type>; + using IndexDatabaseType = typename IndexType::DatabaseType; + class StorageCacheIndex + { + public: + constexpr explicit StorageCacheIndex() = default; + + StorageCacheIndex(const char *) = delete; + + constexpr explicit StorageCacheIndex(int id) + : id{id} + {} + + constexpr explicit StorageCacheIndex(std::size_t id) + : id{static_cast(id)} + {} + + constexpr explicit StorageCacheIndex(std::ptrdiff_t id) + : id{static_cast(id)} + {} + + constexpr StorageCacheIndex operator=(std::ptrdiff_t newId) + { + id = static_cast(newId); + + return *this; + } + + constexpr StorageCacheIndex operator+(int amount) { return StorageCacheIndex{id + amount}; } + + constexpr friend bool operator==(StorageCacheIndex first, StorageCacheIndex second) + { + return first.id == second.id && first.isValid() && second.isValid(); + } + + constexpr friend bool operator<(StorageCacheIndex first, StorageCacheIndex second) + { + return first.id < second.id; + } + + constexpr friend bool operator>=(StorageCacheIndex first, StorageCacheIndex second) + { + return first.id >= second.id; + } + + constexpr bool isValid() const { return id >= 0; } + + explicit operator std::size_t() const { return static_cast(id); } + + public: + int id = -1; + }; public: using MutexType = Mutex; @@ -127,7 +179,7 @@ public: m_entries = std::move(entries); - int max_id = 0; + std::size_t max_id = 0; auto found = std::max_element(m_entries.begin(), m_entries.end(), @@ -136,9 +188,9 @@ public: }); if (found != m_entries.end()) - max_id = found->id + 1; + max_id = static_cast(found->id) + 1; - m_indices.resize(max_id, -1); + m_indices.resize(max_id); updateIndices(); } @@ -171,10 +223,10 @@ public: return first.id < second.id; }); - int max_id = found->id + 1; + auto max_id = static_cast(found->id) + 1; - if (max_id > int(m_indices.size())) - m_indices.resize(max_id, -1); + if (max_id > m_indices.size()) + m_indices.resize(max_id); CacheEntries mergedCacheEntries; mergedCacheEntries.reserve(newCacheEntries.size() + m_entries.size()); @@ -234,8 +286,11 @@ public: { std::shared_lock sharedLock(m_mutex); - if (IndexType(m_indices.size()) > id && m_indices.at(id) >= 0) - return m_entries.at(m_indices.at(id)).value; + if (IndexType{static_cast(m_indices.size())} > id) { + if (auto indirectionIndex = m_indices.at(static_cast(id)); + indirectionIndex.isValid()) + return m_entries.at(static_cast(indirectionIndex)).value; + } sharedLock.unlock(); std::lock_guard exclusiveLock(m_mutex); @@ -253,25 +308,23 @@ public: std::vector values; values.reserve(ids.size()); - for (IndexType id : ids) - values.emplace_back(m_entries.at(m_indices.at(id)).value); - + for (IndexType id : ids) { + values.emplace_back( + m_entries.at(static_cast(m_indices.at(static_cast(id)))).value); + } return values; } bool isEmpty() const { return m_entries.empty() && m_indices.empty(); } - Mutex &mutex() const - { - return m_mutex; - } + Mutex &mutex() const { return m_mutex; } private: void updateIndices() { auto begin = m_entries.cbegin(); for (auto current = begin; current != m_entries.cend(); ++current) - m_indices[current->id] = std::distance(begin, current); + m_indices[static_cast(current->id)] = std::distance(begin, current); } auto find(ViewType view) @@ -287,32 +340,30 @@ private: return m_entries.end(); } - void incrementLargerOrEqualIndicesByOne(IndexType newIndex) + void incrementLargerOrEqualIndicesByOne(StorageCacheIndex newIndirectionIndex) { - std::transform(m_indices.begin(), - m_indices.end(), - m_indices.begin(), - [&] (IndexType index) { - return index >= newIndex ? ++index : index; + std::transform(m_indices.begin(), m_indices.end(), m_indices.begin(), [&](StorageCacheIndex index) { + return index >= newIndirectionIndex ? index + 1 : index; }); } - void ensureSize(IndexType id) + void ensureSize(std::size_t size) { - if (m_indices.size() <= std::size_t(id)) - m_indices.resize(id + 1, -1); + if (m_indices.size() <= size) + m_indices.resize(size + 1); } auto insertEntry(const_iterator beforeIterator, ViewType view, IndexType id) { auto inserted = m_entries.emplace(beforeIterator, view, id); - auto newIndex = IndexType(std::distance(m_entries.begin(), inserted)); + StorageCacheIndex newIndirectionIndex{std::distance(m_entries.begin(), inserted)}; - incrementLargerOrEqualIndicesByOne(newIndex); + incrementLargerOrEqualIndicesByOne(newIndirectionIndex); - ensureSize(id); - m_indices.at(id) = newIndex; + auto indirectionIndex = static_cast(id); + ensureSize(indirectionIndex); + m_indices.at(indirectionIndex) = newIndirectionIndex; return inserted; } @@ -327,7 +378,7 @@ private: private: CacheEntries m_entries; - std::vector m_indices; + std::vector m_indices; mutable Mutex m_mutex; Storage m_storage; }; diff --git a/src/plugins/qmldesigner/designercore/metainfo/storagecacheentry.h b/src/plugins/qmldesigner/designercore/metainfo/storagecacheentry.h index f121c6df2fa..e2557c623c4 100644 --- a/src/plugins/qmldesigner/designercore/metainfo/storagecacheentry.h +++ b/src/plugins/qmldesigner/designercore/metainfo/storagecacheentry.h @@ -36,6 +36,11 @@ public: , id(id) {} + StorageCacheEntry(ViewType value, typename IndexType::DatabaseType id) + : value(value) + , id{id} + {} + operator ViewType() const { return value; } friend bool operator==(const StorageCacheEntry &first, const StorageCacheEntry &second) { diff --git a/src/plugins/qmldesigner/qmldesignerunittestfiles.pri b/src/plugins/qmldesigner/qmldesignerunittestfiles.pri index e8a4eaf3586..97df5729d60 100644 --- a/src/plugins/qmldesigner/qmldesignerunittestfiles.pri +++ b/src/plugins/qmldesigner/qmldesignerunittestfiles.pri @@ -56,6 +56,11 @@ HEADERS += \ $$PWD/designercore/metainfo/storagecache.h \ $$PWD/designercore/metainfo/storagecacheentry.h \ $$PWD/designercore/metainfo/storagecachefwd.h \ + $$PWD/designercore/metainfo/sourceid.h \ + $$PWD/designercore/metainfo/sourcepathcontextid.h \ + $$PWD/designercore/metainfo/sourcepath.h \ + $$PWD/designercore/metainfo/sourcepathview.h \ + $$PWD/designercore/metainfo/sourcepathcache.h \ $$PWD/designercore/model/model_p.h \ $$PWD/designercore/include/qmldesignercorelib_global.h \ $$PWD/designercore/model/internalbindingproperty.h \ @@ -73,4 +78,7 @@ HEADERS += \ $$PWD/designercore/include/signalhandlerproperty.h \ $$PWD/designercore/include/variantproperty.h \ $$PWD/designercore/rewritertransaction.h \ - $$PWD/components/listmodeleditor/listmodeleditormodel.h + $$PWD/components/listmodeleditor/listmodeleditormodel.h \ + $$PWD/designercore/metainfo/projectstorage.h \ + $$PWD/designercore/include/projectstorageids.h \ + $$PWD/designercore/metainfo/projectstoragetypes.h diff --git a/tests/unit/unittest/gtest-creator-printing.cpp b/tests/unit/unittest/gtest-creator-printing.cpp index bbe9e64b178..ad6587dcaf0 100644 --- a/tests/unit/unittest/gtest-creator-printing.cpp +++ b/tests/unit/unittest/gtest-creator-printing.cpp @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -1570,6 +1571,13 @@ std::ostream &operator<<(std::ostream &out, const VariantProperty &property) return out << "(" << property.parentModelNode() << ", " << property.name() << ", " << property.value() << ")"; } +namespace Sources { + +std::ostream &operator<<(std::ostream &out, const SourceContext &sourceContext) +{ + return out << "(" << sourceContext.id << ", " << sourceContext.value << ")"; +} +} // namespace Sources namespace Internal { std::ostream &operator<<(std::ostream &out, const ImageCacheStorageImageEntry &entry) diff --git a/tests/unit/unittest/gtest-creator-printing.h b/tests/unit/unittest/gtest-creator-printing.h index 28329c3a2a3..6fb408b78b9 100644 --- a/tests/unit/unittest/gtest-creator-printing.h +++ b/tests/unit/unittest/gtest-creator-printing.h @@ -374,10 +374,24 @@ std::ostream &operator<<(std::ostream &out, const Diagnostic &diag); namespace QmlDesigner { class ModelNode; class VariantProperty; +template +class BasicId; std::ostream &operator<<(std::ostream &out, const ModelNode &node); std::ostream &operator<<(std::ostream &out, const VariantProperty &property); +template +std::ostream &operator<<(std::ostream &out, const BasicId &id) +{ + return out << "(" << &id << ")"; +} + +namespace Sources { +class SourceContext; + +std::ostream &operator<<(std::ostream &out, const SourceContext &sourceContext); +} // namespace Sources + namespace Internal { class ImageCacheStorageImageEntry; class ImageCacheStorageIconEntry; diff --git a/tests/unit/unittest/projectstorage-test.cpp b/tests/unit/unittest/projectstorage-test.cpp new file mode 100644 index 00000000000..6cd0abf527c --- /dev/null +++ b/tests/unit/unittest/projectstorage-test.cpp @@ -0,0 +1,1034 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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 "sqlitedatabasemock.h" + +#include +#include +#include +#include +#include + +namespace { + +using QmlDesigner::PropertyDeclarationId; +using QmlDesigner::SourceContextId; +using QmlDesigner::SourceId; +using QmlDesigner::TypeAccessSemantics; +using QmlDesigner::TypeId; +using QmlDesigner::Sources::Source; +using QmlDesigner::Sources::SourceContext; + +MATCHER_P2(IsSourceContext, + id, + value, + std::string(negation ? "isn't " : "is ") + PrintToString(SourceContext{value, id})) +{ + const SourceContext &sourceContext = arg; + + return sourceContext.id == id && sourceContext.value == value; +} + +MATCHER_P2(IsSourceNameAndSourceContextId, + name, + id, + std::string(negation ? "isn't " : "is ") + + PrintToString(QmlDesigner::Sources::SourceNameAndSourceContextId{name, id})) +{ + const QmlDesigner::Sources::SourceNameAndSourceContextId &sourceNameAndSourceContextId = arg; + + return sourceNameAndSourceContextId.sourceName == name + && sourceNameAndSourceContextId.sourceContextId == id; +} + +class ProjectStorage : public testing::Test +{ +public: + ProjectStorage() + { + ON_CALL(selectSourceContextIdFromSourceContextsBySourceContextPathStatement, + valueReturnsSourceContextId(A())) + .WillByDefault(Return(SourceContextId())); + ON_CALL(selectSourceContextIdFromSourceContextsBySourceContextPathStatement, + valueReturnsSourceContextId(Utils::SmallStringView(""))) + .WillByDefault(Return(SourceContextId(0))); + ON_CALL(selectSourceContextIdFromSourceContextsBySourceContextPathStatement, + valueReturnsSourceContextId(Utils::SmallStringView("/path/to"))) + .WillByDefault(Return(SourceContextId(5))); + ON_CALL(databaseMock, lastInsertedRowId()).WillByDefault(Return(12)); + ON_CALL(selectSourceIdFromSourcesBySourceContextIdAndSourceNameStatment, + valueReturnInt32(_, _)) + .WillByDefault(Return(Utils::optional())); + ON_CALL(selectSourceIdFromSourcesBySourceContextIdAndSourceNameStatment, + valueReturnInt32(0, Utils::SmallStringView(""))) + .WillByDefault(Return(Utils::optional(0))); + ON_CALL(selectSourceIdFromSourcesBySourceContextIdAndSourceNameStatment, + valueReturnInt32(5, Utils::SmallStringView("file.h"))) + .WillByDefault(Return(Utils::optional(42))); + ON_CALL(selectAllSourcesStatement, valuesReturnSourcesSources(_)) + .WillByDefault(Return(std::vector{{"file.h", SourceContextId{1}, SourceId{1}}, + {"file.cpp", SourceContextId{2}, SourceId{4}}})); + ON_CALL(selectSourceContextPathFromSourceContextsBySourceContextIdStatement, + valueReturnPathString(5)) + .WillByDefault(Return(Utils::optional("/path/to"))); + ON_CALL(selectSourceNameAndSourceContextIdFromSourcesBySourceIdStatement, + valueReturnSourcesSourceNameAndSourceContextId(42)) + .WillByDefault(Return(QmlDesigner::Sources::SourceNameAndSourceContextId{"file.cpp", 5})); + ON_CALL(selectSourceContextIdFromSourcesBySourceIdStatement, + valueReturnInt32(TypedEq(42))) + .WillByDefault(Return(Utils::optional(5))); + } + +protected: + using Storage = QmlDesigner::ProjectStorage; + template + using ReadStatement = Storage::ReadStatement; + using WriteStatement = Storage::WriteStatement; + template + using ReadWriteStatement = SqliteDatabaseMock::ReadWriteStatement; + + NiceMock databaseMock; + Storage storage{databaseMock, true}; + ReadWriteStatement<1> &upsertTypeStatement = storage.upsertTypeStatement; + ReadStatement<1> &selectTypeIdByQualifiedNameStatement = storage.selectTypeIdByQualifiedNameStatement; + ReadWriteStatement<1> &upsertPropertyDeclarationStatement = storage.upsertPropertyDeclarationStatement; + ReadStatement<1> &selectPropertyDeclarationByTypeIdAndNameStatement = storage.selectPropertyDeclarationByTypeIdAndNameStatement; + WriteStatement &upsertQualifiedTypeNameStatement = storage.upsertQualifiedTypeNameStatement; + ReadStatement<1> &selectSourceContextIdFromSourceContextsBySourceContextPathStatement + = storage.selectSourceContextIdFromSourceContextsBySourceContextPathStatement; + ReadStatement<1> &selectSourceIdFromSourcesBySourceContextIdAndSourceNameStatment + = storage.selectSourceIdFromSourcesBySourceContextIdAndSourceNameStatement; + ReadStatement<1> &selectSourceContextPathFromSourceContextsBySourceContextIdStatement + = storage.selectSourceContextPathFromSourceContextsBySourceContextIdStatement; + ReadStatement<2> &selectSourceNameAndSourceContextIdFromSourcesBySourceIdStatement + = storage.selectSourceNameAndSourceContextIdFromSourcesBySourceIdStatement; + ReadStatement<2> &selectAllSourceContextsStatement = storage.selectAllSourceContextsStatement; + WriteStatement &insertIntoSourceContexts = storage.insertIntoSourceContextsStatement; + WriteStatement &insertIntoSourcesStatement = storage.insertIntoSourcesStatement; + ReadStatement<3> &selectAllSourcesStatement = storage.selectAllSourcesStatement; + ReadStatement<1> &selectSourceContextIdFromSourcesBySourceIdStatement = storage.selectSourceContextIdFromSourcesBySourceIdStatement; +}; + +TEST_F(ProjectStorage, InsertTypeCalls) +{ + InSequence s; + TypeId prototypeId{3}; + TypeId newTypeId{11}; + + EXPECT_CALL(databaseMock, immediateBegin()); + EXPECT_CALL(upsertTypeStatement, + valueReturnsTypeId(Eq("QObject"), + TypedEq( + static_cast(TypeAccessSemantics::Reference)), + Eq(prototypeId.id))) + .WillOnce(Return(newTypeId)); + EXPECT_CALL(upsertQualifiedTypeNameStatement, + write(TypedEq("Qml.Object"), TypedEq(newTypeId.id))); + EXPECT_CALL(upsertQualifiedTypeNameStatement, + write(TypedEq("Quick.Object"), + TypedEq(newTypeId.id))); + EXPECT_CALL(databaseMock, commit); + + storage.upsertType("QObject", + prototypeId, + TypeAccessSemantics::Reference, + std::vector{"Qml.Object", "Quick.Object"}); +} + +TEST_F(ProjectStorage, InsertTypeReturnsInternalPropertyId) +{ + TypeId prototypeId{3}; + TypeId newTypeId{11}; + ON_CALL(upsertTypeStatement, valueReturnsTypeId(Eq("QObject"), _, Eq(prototypeId.id))) + .WillByDefault(Return(newTypeId)); + + auto internalId = storage.upsertType("QObject", + prototypeId, + TypeAccessSemantics::Reference, + std::vector{}); + + ASSERT_THAT(internalId, Eq(newTypeId)); +} + +TEST_F(ProjectStorage, UpsertPropertyDeclaration) +{ + InSequence s; + + EXPECT_CALL(databaseMock, immediateBegin()); + EXPECT_CALL(upsertPropertyDeclarationStatement, + valueReturnsPropertyDeclarationId(TypedEq(11), + TypedEq("boo"), + TypedEq(33))) + .WillOnce(Return(PropertyDeclarationId{3})); + EXPECT_CALL(databaseMock, commit); + + storage.upsertPropertyDeclaration(TypeId{11}, "boo", TypeId{33}); +} + +/* +TEST_F(ProjectStorage, ReadSourceContextIdForNotContainedPath) +{ + auto sourceContextId = storage.readSourceContextId("/some/not/known/path"); + + ASSERT_FALSE(sourceContextId); +} + +TEST_F(ProjectStorage, ReadSourceIdForNotContainedPathAndSourceContextId) +{ + auto sourceId = storage.readSourceId(23, "/some/not/known/path"); + + ASSERT_FALSE(sourceId); +} + +TEST_F(ProjectStorage, ReadSourceContextIdForEmptyPath) +{ + auto sourceContextId = storage.readSourceContextId(""); + + ASSERT_THAT(sourceContextId.value(), 0); +} + +TEST_F(ProjectStorage, ReadSourceIdForEmptyNameAndZeroSourceContextId) +{ + auto sourceId = storage.readSourceId(0, ""); + + ASSERT_THAT(sourceId.value(), 0); +} + +TEST_F(ProjectStorage, ReadSourceContextIdForPath) +{ + auto sourceContextId = storage.readSourceContextId("/path/to"); + + ASSERT_THAT(sourceContextId.value(), 5); +} + +TEST_F(ProjectStorage, ReadSourceIdForPathAndSourceContextId) +{ + auto sourceId = storage.readSourceId(5, "file.h"); + + ASSERT_THAT(sourceId.value(), 42); +} + +TEST_F(ProjectStorage, FetchSourceContextIdForEmptyPath) +{ + auto sourceContextId = storage.fetchSourceContextId(""); + + ASSERT_THAT(sourceContextId, 0); +} + +TEST_F(ProjectStorage, FetchSourceIdForEmptyNameAndZeroSourceContextId) +{ + auto sourceId = storage.fetchSourceId(0, ""); + + ASSERT_THAT(sourceId, 0); +} + +TEST_F(ProjectStorage, FetchSourceContextIdForPath) +{ + auto sourceContextId = storage.fetchSourceContextId("/path/to"); + + ASSERT_THAT(sourceContextId, 5); +} + +TEST_F(ProjectStorage, FetchSourceIdForPathAndSourceContextId) +{ + auto sourceId = storage.fetchSourceId(5, "file.h"); + + ASSERT_THAT(sourceId, 42); +} + +TEST_F(ProjectStorage, CallWriteForWriteDirectory) +{ + EXPECT_CALL(insertIntoSourceContexts, write(TypedEq("/some/not/known/path"))); + + storage.writeSourceContextId("/some/not/known/path"); +} + +TEST_F(ProjectStorage, CallWriteForWriteSource) +{ + EXPECT_CALL(insertIntoSources, + write(TypedEq(5), TypedEq("unknownfile.h"))); + + storage.writeSourceId(5, "unknownfile.h"); +} + +TEST_F(ProjectStorage, GetTheSourceContextIdBackAfterWritingANewEntryInSourceContexts) +{ + auto sourceContextId = storage.writeSourceContextId("/some/not/known/path"); + + ASSERT_THAT(sourceContextId, 12); +} + +TEST_F(ProjectStorage, GetTheSourceIdBackAfterWritingANewEntryInSources) +{ + auto sourceId = storage.writeSourceId(5, "unknownfile.h"); + + ASSERT_THAT(sourceId, 12); +} + +TEST_F(ProjectStorage, GetTheSourceContextIdBackAfterFetchingANewEntryFromSourceContexts) +{ + auto sourceContextId = storage.fetchSourceContextId("/some/not/known/path"); + + ASSERT_THAT(sourceContextId, 12); +} + +TEST_F(ProjectStorage, GetTheSourceIdBackAfterFetchingANewEntryFromSources) +{ + auto sourceId = storage.fetchSourceId(5, "unknownfile.h"); + + ASSERT_THAT(sourceId, 12); +} + +*/ + +TEST_F(ProjectStorage, SelectForFetchingSourceContextIdForKnownPathCalls) +{ + InSequence s; + + EXPECT_CALL(databaseMock, deferredBegin()); + EXPECT_CALL(selectSourceContextIdFromSourceContextsBySourceContextPathStatement, + valueReturnsSourceContextId(TypedEq("/path/to"))); + EXPECT_CALL(databaseMock, commit()); + + storage.fetchSourceContextId("/path/to"); +} + +TEST_F(ProjectStorage, SelectForFetchingSourceIdForKnownPathCalls) +{ + InSequence s; + + EXPECT_CALL(databaseMock, deferredBegin()); + EXPECT_CALL(selectSourceIdFromSourcesBySourceContextIdAndSourceNameStatment, + valueReturnsSourceId(5, Eq("file.h"))); + EXPECT_CALL(databaseMock, commit()); + + storage.fetchSourceId(SourceContextId{5}, "file.h"); +} + +TEST_F(ProjectStorage, NotWriteForFetchingSourceContextIdForKnownPathCalls) +{ + EXPECT_CALL(insertIntoSourceContexts, write(An())).Times(0); + + storage.fetchSourceContextId("/path/to"); +} + +TEST_F(ProjectStorage, NotWriteForFetchingSoureIdForKnownEntryCalls) +{ + EXPECT_CALL(insertIntoSourcesStatement, write(An(), An())).Times(0); + + storage.fetchSourceId(SourceContextId{5}, "file.h"); +} + +TEST_F(ProjectStorage, SelectAndWriteForFetchingSourceContextIdForUnknownPathCalls) +{ + InSequence s; + + EXPECT_CALL(databaseMock, deferredBegin()); + EXPECT_CALL(selectSourceContextIdFromSourceContextsBySourceContextPathStatement, + valueReturnsSourceContextId( + TypedEq("/some/not/known/path"))); + EXPECT_CALL(insertIntoSourceContexts, + write(TypedEq("/some/not/known/path"))); + EXPECT_CALL(databaseMock, commit()); + + storage.fetchSourceContextId("/some/not/known/path"); +} + +TEST_F(ProjectStorage, SelectAndWriteForFetchingSourceIdForUnknownEntryCalls) +{ + InSequence s; + + EXPECT_CALL(databaseMock, deferredBegin()); + EXPECT_CALL(selectSourceIdFromSourcesBySourceContextIdAndSourceNameStatment, + valueReturnsSourceId(5, Eq("unknownfile.h"))); + EXPECT_CALL(insertIntoSourcesStatement, + write(TypedEq(5), TypedEq("unknownfile.h"))); + EXPECT_CALL(databaseMock, commit()); + + storage.fetchSourceId(SourceContextId{5}, "unknownfile.h"); +} + +TEST_F(ProjectStorage, ValueForFetchSourceContextForIdCalls) +{ + EXPECT_CALL(databaseMock, deferredBegin()); + EXPECT_CALL(selectSourceContextPathFromSourceContextsBySourceContextIdStatement, + valueReturnPathString(5)); + EXPECT_CALL(databaseMock, commit()); + + storage.fetchSourceContextPath(SourceContextId{5}); +} + +TEST_F(ProjectStorage, FetchSourceContextForId) +{ + auto path = storage.fetchSourceContextPath(SourceContextId{5}); + + ASSERT_THAT(path, Eq("/path/to")); +} + +TEST_F(ProjectStorage, ThrowAsFetchingSourceContextPathForNonExistingId) +{ + ASSERT_THROW(storage.fetchSourceContextPath(SourceContextId{12}), + QmlDesigner::SourceContextIdDoesNotExists); +} + +TEST_F(ProjectStorage, FetchSourceContextIdForUnknownSourceId) +{ + ASSERT_THROW(storage.fetchSourceContextId(SourceId{1111}), QmlDesigner::SourceIdDoesNotExists); +} + +TEST_F(ProjectStorage, FetchSourceContextIdThrows) +{ + ASSERT_THROW(storage.fetchSourceContextId(SourceId{41}), QmlDesigner::SourceIdDoesNotExists); +} + +TEST_F(ProjectStorage, GetTheSourceContextIdBackAfterFetchingANewEntryFromSourceContextsUnguarded) +{ + auto sourceContextId = storage.fetchSourceContextIdUnguarded("/some/not/known/path"); + + ASSERT_THAT(sourceContextId, SourceContextId{12}); +} + +TEST_F(ProjectStorage, GetTheSourceIdBackAfterFetchingANewEntryFromSourcesUnguarded) +{ + auto sourceId = storage.fetchSourceIdUnguarded(SourceContextId{5}, "unknownfile.h"); + + ASSERT_THAT(sourceId, SourceId{12}); +} + +TEST_F(ProjectStorage, SelectForFetchingSourceContextIdForKnownPathUnguardedCalls) +{ + InSequence s; + + EXPECT_CALL(selectSourceContextIdFromSourceContextsBySourceContextPathStatement, + valueReturnsSourceContextId(TypedEq("/path/to"))); + + storage.fetchSourceContextIdUnguarded("/path/to"); +} + +TEST_F(ProjectStorage, SelectForFetchingSourceIdForKnownPathUnguardedCalls) +{ + EXPECT_CALL(selectSourceIdFromSourcesBySourceContextIdAndSourceNameStatment, + valueReturnsSourceId(5, Eq("file.h"))); + + storage.fetchSourceIdUnguarded(SourceContextId{5}, "file.h"); +} + +TEST_F(ProjectStorage, NotWriteForFetchingSourceContextIdForKnownPathUnguardedCalls) +{ + EXPECT_CALL(insertIntoSourceContexts, write(An())).Times(0); + + storage.fetchSourceContextIdUnguarded("/path/to"); +} + +TEST_F(ProjectStorage, NotWriteForFetchingSoureIdForKnownEntryUnguardedCalls) +{ + EXPECT_CALL(insertIntoSourcesStatement, write(An(), An())).Times(0); + + storage.fetchSourceIdUnguarded(SourceContextId{5}, "file.h"); +} + +TEST_F(ProjectStorage, SelectAndWriteForFetchingSourceContextIdForUnknownPathUnguardedCalls) +{ + InSequence s; + + EXPECT_CALL(selectSourceContextIdFromSourceContextsBySourceContextPathStatement, + valueReturnsSourceContextId( + TypedEq("/some/not/known/path"))); + EXPECT_CALL(insertIntoSourceContexts, + write(TypedEq("/some/not/known/path"))); + + storage.fetchSourceContextIdUnguarded("/some/not/known/path"); +} + +TEST_F(ProjectStorage, SelectAndWriteForFetchingSourceIdForUnknownEntryUnguardedCalls) +{ + InSequence s; + + EXPECT_CALL(selectSourceIdFromSourcesBySourceContextIdAndSourceNameStatment, + valueReturnsSourceId(5, Eq("unknownfile.h"))); + EXPECT_CALL(insertIntoSourcesStatement, + write(TypedEq(5), TypedEq("unknownfile.h"))); + + storage.fetchSourceIdUnguarded(SourceContextId{5}, "unknownfile.h"); +} + +TEST_F(ProjectStorage, + SelectAndWriteForFetchingSourceContextIdTwoTimesIfTheIndexIsConstraintBecauseTheEntryExistsAlreadyCalls) +{ + InSequence s; + + EXPECT_CALL(databaseMock, deferredBegin()); + EXPECT_CALL(selectSourceContextIdFromSourceContextsBySourceContextPathStatement, + valueReturnsSourceContextId(TypedEq("/other/unknow/path"))); + EXPECT_CALL(insertIntoSourceContexts, write(TypedEq("/other/unknow/path"))) + .WillOnce(Throw(Sqlite::ConstraintPreventsModification("busy"))); + EXPECT_CALL(databaseMock, rollback()); + EXPECT_CALL(databaseMock, deferredBegin()); + EXPECT_CALL(selectSourceContextIdFromSourceContextsBySourceContextPathStatement, + valueReturnsSourceContextId(TypedEq("/other/unknow/path"))); + EXPECT_CALL(insertIntoSourceContexts, + write(TypedEq("/other/unknow/path"))); + EXPECT_CALL(databaseMock, commit()); + + storage.fetchSourceContextId("/other/unknow/path"); +} + +class ProjectStorageSlowTest : public testing::Test +{ +protected: + template + static auto toValues(Range &&range) + { + using Type = typename std::decay_t; + + return std::vector{range.begin(), range.end()}; + } + + void addSomeDummyData() + { + auto sourceContextId1 = storage.fetchSourceContextId("/path/dummy"); + auto sourceContextId2 = storage.fetchSourceContextId("/path/dummy2"); + auto sourceContextId3 = storage.fetchSourceContextId("/path/"); + + storage.fetchSourceId(sourceContextId1, "foo"); + storage.fetchSourceId(sourceContextId1, "dummy"); + storage.fetchSourceId(sourceContextId2, "foo"); + storage.fetchSourceId(sourceContextId2, "bar"); + storage.fetchSourceId(sourceContextId3, "foo"); + storage.fetchSourceId(sourceContextId3, "bar"); + storage.fetchSourceId(sourceContextId1, "bar"); + storage.fetchSourceId(sourceContextId3, "bar"); + } + +protected: + Sqlite::Database database{":memory:", Sqlite::JournalMode::Memory}; + QmlDesigner::ProjectStorage storage{database, database.isInitialized()}; +}; + +TEST_F(ProjectStorageSlowTest, FetchTypeIdByName) +{ + storage.upsertType("Yi", + TypeId{}, + TypeAccessSemantics::Reference, + std::vector{"Qml.Yi"}); + auto internalTypeId = storage.upsertType("Er", + TypeId{}, + TypeAccessSemantics::Reference, + std::vector{"Qml.Er"}); + storage.upsertType("San", + TypeId{}, + TypeAccessSemantics::Reference, + std::vector{"Qml.San"}); + + auto id = storage.fetchTypeIdByQualifiedName("Qml.Er"); + + ASSERT_THAT(id, internalTypeId); +} + +TEST_F(ProjectStorageSlowTest, InsertType) +{ + auto internalTypeId = storage.upsertType("Yi", + TypeId{}, + TypeAccessSemantics::Reference, + std::vector{"Qml.Yi"}); + + ASSERT_THAT(storage.fetchTypeIdByQualifiedName("Qml.Yi"), internalTypeId); +} + +TEST_F(ProjectStorageSlowTest, UpsertType) +{ + auto internalTypeId = storage.upsertType("Yi", + TypeId{}, + TypeAccessSemantics::Reference, + std::vector{"Qml.Yi"}); + + auto internalTypeId2 = storage.upsertType("Yi", + TypeId{}, + TypeAccessSemantics::Reference, + std::vector{"Qml.Yi"}); + + ASSERT_THAT(internalTypeId2, internalTypeId); +} + +TEST_F(ProjectStorageSlowTest, InsertTypeIdAreUnique) +{ + auto internalTypeId = storage.upsertType("Yi", + TypeId{}, + TypeAccessSemantics::Reference, + std::vector{"Qml.Yi"}); + auto internalTypeId2 = storage.upsertType("Er", + TypeId{}, + TypeAccessSemantics::Reference, + std::vector{"Qml.Er"}); + + ASSERT_TRUE(internalTypeId != internalTypeId2); +} + +TEST_F(ProjectStorageSlowTest, IsConvertibleTypeToBase) +{ + auto baseId = storage.upsertType("Base", + TypeId{}, + TypeAccessSemantics::Reference, + std::vector{"Qml.Base"}); + auto objectId = storage.upsertType("QObject", + baseId, + TypeAccessSemantics::Reference, + std::vector{"Qml.Object"}); + auto itemId = storage.upsertType("QQuickItem", + objectId, + TypeAccessSemantics::Reference, + std::vector{"Quick.Item"}); + + auto isConvertible = storage.fetchIsProtype(itemId, baseId); + + ASSERT_TRUE(isConvertible); +} + +TEST_F(ProjectStorageSlowTest, IsConvertibleTypeToSameType) +{ + auto baseId = storage.upsertType("Base", + TypeId{}, + TypeAccessSemantics::Reference, + std::vector{"Qml.Base"}); + auto objectId = storage.upsertType("QObject", + baseId, + TypeAccessSemantics::Reference, + std::vector{"Qml.Object"}); + auto itemId = storage.upsertType("QQuickItem", + objectId, + TypeAccessSemantics::Reference, + std::vector{"Quick.Item"}); + + auto isConvertible = storage.fetchIsProtype(itemId, itemId); + + ASSERT_TRUE(isConvertible); +} + +TEST_F(ProjectStorageSlowTest, IsConvertibleTypeToSomeTypeInTheMiddle) +{ + auto baseId = storage.upsertType("Base", + TypeId{}, + TypeAccessSemantics::Reference, + std::vector{"Qml.Base"}); + auto objectId = storage.upsertType("QObject", + baseId, + TypeAccessSemantics::Reference, + std::vector{"Qml.Object"}); + auto itemId = storage.upsertType("QQuickItem", + objectId, + TypeAccessSemantics::Reference, + std::vector{"Quick.Item"}); + + auto isConvertible = storage.fetchIsProtype(itemId, objectId); + + ASSERT_TRUE(isConvertible); +} + +TEST_F(ProjectStorageSlowTest, IsNotConvertibleToUnrelatedType) +{ + auto unrelatedId = storage.upsertType("Base", + TypeId{}, + TypeAccessSemantics::Reference, + std::vector{"Qml.Base"}); + auto objectId = storage.upsertType("QObject", + TypeId{}, + TypeAccessSemantics::Reference, + std::vector{"Qml.Object"}); + auto itemId = storage.upsertType("QQuickItem", + objectId, + TypeAccessSemantics::Reference, + std::vector{"Quick.Item"}); + + auto isConvertible = storage.fetchIsProtype(itemId, unrelatedId); + + ASSERT_FALSE(isConvertible); +} + +TEST_F(ProjectStorageSlowTest, IsNotPrototypeOrSameType) +{ + auto baseId = storage.upsertType("Base", + TypeId{}, + TypeAccessSemantics::Reference, + std::vector{"Qml.Base"}); + auto objectId = storage.upsertType("QObject", + baseId, + TypeAccessSemantics::Reference, + std::vector{"Qml.Object"}); + auto itemId = storage.upsertType("QQuickItem", + baseId, + TypeAccessSemantics::Reference, + std::vector{"Quick.Item"}); + + auto isPrototype = storage.fetchIsProtype(itemId, objectId); + + ASSERT_FALSE(isPrototype); +} + +TEST_F(ProjectStorageSlowTest, IsNotConvertibleToDerivedType) +{ + auto baseId = storage.upsertType("Base", + TypeId{}, + TypeAccessSemantics::Reference, + std::vector{"Qml.Base"}); + auto objectId = storage.upsertType("QObject", + baseId, + TypeAccessSemantics::Reference, + std::vector{"Qml.Object"}); + + auto isConvertible = storage.fetchIsProtype(baseId, objectId); + + ASSERT_FALSE(isConvertible); +} + +TEST_F(ProjectStorageSlowTest, InsertPropertyDeclaration) +{ + auto typeId = storage.upsertType("QObject", + TypeId{}, + TypeAccessSemantics::Reference, + std::vector{"Qml.Object"}); + auto propertyTypeId = storage.upsertType("double", + TypeId{}, + TypeAccessSemantics::Value, + std::vector{"Qml.doube"}); + + auto propertyDeclarationId = storage.upsertPropertyDeclaration(typeId, "foo", propertyTypeId); + + ASSERT_THAT(storage.fetchPropertyDeclarationByTypeIdAndName(typeId, "foo"), propertyDeclarationId); +} + +TEST_F(ProjectStorageSlowTest, UpsertPropertyDeclaration) +{ + auto typeId = storage.upsertType("QObject", + TypeId{}, + TypeAccessSemantics::Reference, + std::vector{"Qml.Object"}); + auto propertyTypeId = storage.upsertType("double", + TypeId{}, + TypeAccessSemantics::Value, + std::vector{"Qml.doube"}); + auto propertyDeclarationId = storage.upsertPropertyDeclaration(typeId, "foo", propertyTypeId); + + auto propertyDeclarationId2 = storage.upsertPropertyDeclaration(typeId, "foo", propertyTypeId); + + ASSERT_THAT(propertyDeclarationId2, Eq(propertyDeclarationId)); +} + +TEST_F(ProjectStorageSlowTest, FetchPropertyDeclarationByTypeIdAndNameFromSameType) +{ + auto typeId = storage.upsertType("QObject", + TypeId{}, + TypeAccessSemantics::Reference, + std::vector{"Qml.Object"}); + auto propertyTypeId = storage.upsertType("double", + TypeId{}, + TypeAccessSemantics::Value, + std::vector{"Qml.doube"}); + auto propertyDeclarationId = storage.upsertPropertyDeclaration(typeId, "foo", propertyTypeId); + + auto id = storage.fetchPropertyDeclarationByTypeIdAndName(typeId, "foo"); + + ASSERT_THAT(id, propertyDeclarationId); +} + +TEST_F(ProjectStorageSlowTest, CannotFetchPropertyDeclarationByTypeIdAndNameForNonExistingProperty) +{ + auto typeId = storage.upsertType("QObject", + TypeId{}, + TypeAccessSemantics::Reference, + std::vector{"Qml.Object"}); + auto propertyTypeId = storage.upsertType("double", + TypeId{}, + TypeAccessSemantics::Value, + std::vector{"Qml.doube"}); + storage.upsertPropertyDeclaration(typeId, "foo", propertyTypeId); + + auto id = storage.fetchPropertyDeclarationByTypeIdAndName(typeId, "bar"); + + ASSERT_FALSE(id.isValid()); +} + +TEST_F(ProjectStorageSlowTest, FetchPropertyDeclarationByTypeIdAndNameFromDerivedType) +{ + auto baseTypeId = storage.upsertType("QObject", + TypeId{}, + TypeAccessSemantics::Reference, + std::vector{"Qml.Object"}); + auto propertyTypeId = storage.upsertType("double", + TypeId{}, + TypeAccessSemantics::Value, + std::vector{"Qml.doube"}); + auto derivedTypeId = storage.upsertType("Derived", + baseTypeId, + TypeAccessSemantics::Reference, + std::vector{"Qml.Derived"}); + auto propertyDeclarationId = storage.upsertPropertyDeclaration(baseTypeId, "foo", propertyTypeId); + + auto id = storage.fetchPropertyDeclarationByTypeIdAndName(derivedTypeId, "foo"); + + ASSERT_THAT(id, propertyDeclarationId); +} + +TEST_F(ProjectStorageSlowTest, FetchPropertyDeclarationByTypeIdAndNameFromBaseType) +{ + auto baseTypeId = storage.upsertType("QObject", + TypeId{}, + TypeAccessSemantics::Reference, + std::vector{"Qml.Object"}); + auto propertyTypeId = storage.upsertType("double", + TypeId{}, + TypeAccessSemantics::Value, + std::vector{"Qml.doube"}); + auto derivedTypeId = storage.upsertType("Derived", + baseTypeId, + TypeAccessSemantics::Reference, + std::vector{"Qml.Derived"}); + storage.upsertPropertyDeclaration(derivedTypeId, "foo", propertyTypeId); + + auto id = storage.fetchPropertyDeclarationByTypeIdAndName(baseTypeId, "foo"); + + ASSERT_FALSE(id.isValid()); +} + +TEST_F(ProjectStorageSlowTest, DISABLED_FetchPrototypes) +{ + auto baseId = storage.upsertType("Base", + TypeId{}, + TypeAccessSemantics::Reference, + std::vector{"Qml.Base"}); + auto objectId = storage.upsertType("QObject", + baseId, + TypeAccessSemantics::Reference, + std::vector{"Qml.Object"}); + auto itemId = storage.upsertType("QQuickItem", + objectId, + TypeAccessSemantics::Reference, + std::vector{"Quick.Item"}); + + auto prototypeIds = storage.fetchPrototypes(itemId); + + ASSERT_THAT(prototypeIds, ElementsAre(itemId, objectId, baseId)); +} + +TEST_F(ProjectStorageSlowTest, FetchSourceContextIdReturnsAlwaysTheSameIdForTheSamePath) +{ + auto sourceContextId = storage.fetchSourceContextId("/path/to"); + + auto newSourceContextId = storage.fetchSourceContextId("/path/to"); + + ASSERT_THAT(newSourceContextId, Eq(sourceContextId)); +} + +TEST_F(ProjectStorageSlowTest, FetchSourceContextIdReturnsNotTheSameIdForDifferentPath) +{ + auto sourceContextId = storage.fetchSourceContextId("/path/to"); + + auto newSourceContextId = storage.fetchSourceContextId("/path/to2"); + + ASSERT_THAT(newSourceContextId, Ne(sourceContextId)); +} + +TEST_F(ProjectStorageSlowTest, FetchSourceContextPath) +{ + auto sourceContextId = storage.fetchSourceContextId("/path/to"); + + auto path = storage.fetchSourceContextPath(sourceContextId); + + ASSERT_THAT(path, Eq("/path/to")); +} + +TEST_F(ProjectStorageSlowTest, FetchUnknownSourceContextPathThrows) +{ + ASSERT_THROW(storage.fetchSourceContextPath(SourceContextId{323}), + QmlDesigner::SourceContextIdDoesNotExists); +} + +TEST_F(ProjectStorageSlowTest, FetchAllSourceContextsAreEmptyIfNoSourceContextsExists) +{ + auto sourceContexts = storage.fetchAllSourceContexts(); + + ASSERT_THAT(toValues(sourceContexts), IsEmpty()); +} + +TEST_F(ProjectStorageSlowTest, FetchAllSourceContexts) +{ + auto sourceContextId = storage.fetchSourceContextId("/path/to"); + auto sourceContextId2 = storage.fetchSourceContextId("/path/to2"); + + auto sourceContexts = storage.fetchAllSourceContexts(); + + ASSERT_THAT(toValues(sourceContexts), + UnorderedElementsAre(IsSourceContext(sourceContextId, "/path/to"), + IsSourceContext(sourceContextId2, "/path/to2"))); +} + +TEST_F(ProjectStorageSlowTest, FetchSourceIdFirstTime) +{ + addSomeDummyData(); + auto sourceContextId = storage.fetchSourceContextId("/path/to"); + + auto sourceId = storage.fetchSourceId(sourceContextId, "foo"); + + ASSERT_TRUE(sourceId.isValid()); +} + +TEST_F(ProjectStorageSlowTest, FetchExistingSourceId) +{ + addSomeDummyData(); + auto sourceContextId = storage.fetchSourceContextId("/path/to"); + auto createdSourceId = storage.fetchSourceId(sourceContextId, "foo"); + + auto sourceId = storage.fetchSourceId(sourceContextId, "foo"); + + ASSERT_THAT(sourceId, createdSourceId); +} + +TEST_F(ProjectStorageSlowTest, FetchSourceIdWithDifferentContextIdAreNotEqual) +{ + addSomeDummyData(); + auto sourceContextId = storage.fetchSourceContextId("/path/to"); + auto sourceContextId2 = storage.fetchSourceContextId("/path/to2"); + auto sourceId2 = storage.fetchSourceId(sourceContextId2, "foo"); + + auto sourceId = storage.fetchSourceId(sourceContextId, "foo"); + + ASSERT_THAT(sourceId, Ne(sourceId2)); +} + +TEST_F(ProjectStorageSlowTest, FetchSourceIdWithDifferentNameAreNotEqual) +{ + addSomeDummyData(); + auto sourceContextId = storage.fetchSourceContextId("/path/to"); + auto sourceId2 = storage.fetchSourceId(sourceContextId, "foo"); + + auto sourceId = storage.fetchSourceId(sourceContextId, "foo2"); + + ASSERT_THAT(sourceId, Ne(sourceId2)); +} + +TEST_F(ProjectStorageSlowTest, FetchSourceIdWithNonExistingSourceContextIdThrows) +{ + ASSERT_THROW(storage.fetchSourceId(SourceContextId{42}, "foo"), + Sqlite::ConstraintPreventsModification); +} + +TEST_F(ProjectStorageSlowTest, FetchSourceNameAndSourceContextIdForNonExistingSourceId) +{ + ASSERT_THROW(storage.fetchSourceNameAndSourceContextId(SourceId{212}), + QmlDesigner::SourceIdDoesNotExists); +} + +TEST_F(ProjectStorageSlowTest, FetchSourceNameAndSourceContextIdForNonExistingEntry) +{ + addSomeDummyData(); + auto sourceContextId = storage.fetchSourceContextId("/path/to"); + auto sourceId = storage.fetchSourceId(sourceContextId, "foo"); + + auto sourceNameAndSourceContextId = storage.fetchSourceNameAndSourceContextId(sourceId); + + ASSERT_THAT(sourceNameAndSourceContextId, IsSourceNameAndSourceContextId("foo", sourceContextId)); +} + +TEST_F(ProjectStorageSlowTest, FetchSourceContextIdForNonExistingSourceId) +{ + ASSERT_THROW(storage.fetchSourceContextId(SourceId{212}), QmlDesigner::SourceIdDoesNotExists); +} + +TEST_F(ProjectStorageSlowTest, FetchSourceContextIdForExistingSourceId) +{ + addSomeDummyData(); + auto originalSourceContextId = storage.fetchSourceContextId("/path/to3"); + auto sourceId = storage.fetchSourceId(originalSourceContextId, "foo"); + + auto sourceContextId = storage.fetchSourceContextId(sourceId); + + ASSERT_THAT(sourceContextId, Eq(originalSourceContextId)); +} + +TEST_F(ProjectStorageSlowTest, FetchAllSources) +{ + auto sources = storage.fetchAllSources(); + + ASSERT_THAT(toValues(sources), IsEmpty()); +} + +TEST_F(ProjectStorageSlowTest, FetchSourceIdUnguardedFirstTime) +{ + addSomeDummyData(); + auto sourceContextId = storage.fetchSourceContextId("/path/to"); + std::lock_guard lock{database}; + + auto sourceId = storage.fetchSourceIdUnguarded(sourceContextId, "foo"); + + ASSERT_TRUE(sourceId.isValid()); +} + +TEST_F(ProjectStorageSlowTest, FetchExistingSourceIdUnguarded) +{ + addSomeDummyData(); + auto sourceContextId = storage.fetchSourceContextId("/path/to"); + std::lock_guard lock{database}; + auto createdSourceId = storage.fetchSourceIdUnguarded(sourceContextId, "foo"); + + auto sourceId = storage.fetchSourceIdUnguarded(sourceContextId, "foo"); + + ASSERT_THAT(sourceId, createdSourceId); +} + +TEST_F(ProjectStorageSlowTest, FetchSourceIdUnguardedWithDifferentContextIdAreNotEqual) +{ + addSomeDummyData(); + auto sourceContextId = storage.fetchSourceContextId("/path/to"); + auto sourceContextId2 = storage.fetchSourceContextId("/path/to2"); + std::lock_guard lock{database}; + auto sourceId2 = storage.fetchSourceIdUnguarded(sourceContextId2, "foo"); + + auto sourceId = storage.fetchSourceIdUnguarded(sourceContextId, "foo"); + + ASSERT_THAT(sourceId, Ne(sourceId2)); +} + +TEST_F(ProjectStorageSlowTest, FetchSourceIdUnguardedWithDifferentNameAreNotEqual) +{ + addSomeDummyData(); + auto sourceContextId = storage.fetchSourceContextId("/path/to"); + std::lock_guard lock{database}; + auto sourceId2 = storage.fetchSourceIdUnguarded(sourceContextId, "foo"); + + auto sourceId = storage.fetchSourceIdUnguarded(sourceContextId, "foo2"); + + ASSERT_THAT(sourceId, Ne(sourceId2)); +} + +TEST_F(ProjectStorageSlowTest, FetchSourceIdUnguardedWithNonExistingSourceContextIdThrows) +{ + std::lock_guard lock{database}; + + ASSERT_THROW(storage.fetchSourceIdUnguarded(SourceContextId{42}, "foo"), + Sqlite::ConstraintPreventsModification); +} + +} // namespace diff --git a/tests/unit/unittest/projectstoragemock.h b/tests/unit/unittest/projectstoragemock.h new file mode 100644 index 00000000000..e670f4bd17e --- /dev/null +++ b/tests/unit/unittest/projectstoragemock.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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. +** +****************************************************************************/ + +#pragma once + +#include "googletest.h" + +#include "sqlitedatabasemock.h" + +#include +#include + +class ProjectStorageMock +{ +public: + ProjectStorageMock(SqliteDatabaseMock &databaseMock) + : databaseMock{databaseMock} + {} + + MOCK_METHOD1(fetchSourceContextId, + QmlDesigner::SourceContextId(Utils::SmallStringView SourceContextPath)); + MOCK_METHOD2(fetchSourceId, + QmlDesigner::SourceId(QmlDesigner::SourceContextId SourceContextId, + Utils::SmallStringView sourceName)); + MOCK_METHOD1(fetchSourceContextIdUnguarded, + QmlDesigner::SourceContextId(Utils::SmallStringView SourceContextPath)); + MOCK_METHOD2(fetchSourceIdUnguarded, + QmlDesigner::SourceId(QmlDesigner::SourceContextId SourceContextId, + Utils::SmallStringView sourceName)); + MOCK_METHOD1(fetchSourceContextPath, + Utils::PathString(QmlDesigner::SourceContextId sourceContextId)); + MOCK_METHOD1(fetchSourceNameAndSourceContextId, + QmlDesigner::Sources::SourceNameAndSourceContextId(QmlDesigner::SourceId sourceId)); + MOCK_METHOD0(fetchAllSourceContexts, std::vector()); + MOCK_METHOD0(fetchAllSources, std::vector()); + + SqliteDatabaseMock &database() { return databaseMock; } + + SqliteDatabaseMock &databaseMock; +}; + diff --git a/tests/unit/unittest/sourcepath-test.cpp b/tests/unit/unittest/sourcepath-test.cpp new file mode 100644 index 00000000000..38e26b4682d --- /dev/null +++ b/tests/unit/unittest/sourcepath-test.cpp @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 + +namespace { + +TEST(SourcePath, CreateFromPathString) +{ + QmlDesigner::SourcePath sourcePath{Utils::PathString{"/file/pathOne"}}; + + ASSERT_THAT(sourcePath.directory(), "/file"); + ASSERT_THAT(sourcePath.name(), "pathOne"); +} + +TEST(FilePath, CreateFromDirectoryAndFileName) +{ + QmlDesigner::SourcePath sourcePath{Utils::PathString{"/file"}, Utils::PathString{"pathOne"}}; + + ASSERT_THAT(sourcePath.directory(), "/file"); + ASSERT_THAT(sourcePath.name(), "pathOne"); + ASSERT_THAT(sourcePath.path(), "/file/pathOne"); +} + +TEST(FilePath, CreateFromCString) +{ + QmlDesigner::SourcePath sourcePath{"/file/pathOne"}; + + ASSERT_THAT(sourcePath.directory(), "/file"); + ASSERT_THAT(sourcePath.name(), "pathOne"); +} + +TEST(FilePath, CreateFromFilePathView) +{ + QmlDesigner::SourcePath sourcePath{QmlDesigner::SourcePathView{"/file/pathOne"}}; + + ASSERT_THAT(sourcePath.directory(), "/file"); + ASSERT_THAT(sourcePath.name(), "pathOne"); +} + +TEST(FilePath, CreateFromQString) +{ + QmlDesigner::SourcePath sourcePath{QString{"/file/pathOne"}}; + + ASSERT_THAT(sourcePath.directory(), "/file"); + ASSERT_THAT(sourcePath.name(), "pathOne"); +} + +TEST(FilePath, DefaultFilePath) +{ + QmlDesigner::SourcePath sourcePath; + + ASSERT_THAT(sourcePath.directory(), ""); + ASSERT_THAT(sourcePath.name(), ""); +} + +TEST(FilePath, EmptyFilePath) +{ + QmlDesigner::SourcePath sourcePath(""); + + ASSERT_THAT(sourcePath.directory(), ""); + ASSERT_THAT(sourcePath.name(), ""); +} + +} diff --git a/tests/unit/unittest/sourcepathview-test.cpp b/tests/unit/unittest/sourcepathview-test.cpp new file mode 100644 index 00000000000..512464b4791 --- /dev/null +++ b/tests/unit/unittest/sourcepathview-test.cpp @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 + +namespace { + +TEST(SourcePathView, FilePathSlashForEmptyPath) +{ + QmlDesigner::SourcePathView filePath(""); + + ASSERT_THAT(filePath.slashIndex(), -1); +} + +TEST(SourcePathView, FilePathSlashForSingleSlash) +{ + QmlDesigner::SourcePathView filePath("/"); + + ASSERT_THAT(filePath.slashIndex(), 0); +} + +TEST(SourcePathView, FilePathSlashForFileInRoot) +{ + QmlDesigner::SourcePathView filePath("/file.h"); + + ASSERT_THAT(filePath.slashIndex(), 0); +} + +TEST(SourcePathView, FilePathSlashForSomeLongerPath) +{ + QmlDesigner::SourcePathView filePath("/path/to/some/file.h"); + + ASSERT_THAT(filePath.slashIndex(), 13); +} + +TEST(SourcePathView, FilePathSlashForFileNameOnly) +{ + QmlDesigner::SourcePathView filePath("file.h"); + + ASSERT_THAT(filePath.slashIndex(), -1); +} + +TEST(SourcePathView, DirectoryPathForEmptyPath) +{ + QmlDesigner::SourcePathView filePath(""); + + ASSERT_THAT(filePath.directory(), ""); +} + +TEST(SourcePathView, DirectoryPathForSingleSlashPath) +{ + QmlDesigner::SourcePathView filePath{"/"}; + + ASSERT_THAT(filePath.directory(), ""); +} + +TEST(SourcePathView, DirectoryPathForLongerPath) +{ + QmlDesigner::SourcePathView filePath{"/path/to/some/file.h"}; + + ASSERT_THAT(filePath.directory(), "/path/to/some"); +} + +TEST(SourcePathView, DirectoryPathForFileNameOnly) +{ + QmlDesigner::SourcePathView filePath{"file.h"}; + + ASSERT_THAT(filePath.directory(), IsEmpty()); +} + +TEST(SourcePathView, FileNameForEmptyPath) +{ + QmlDesigner::SourcePathView filePath(""); + + ASSERT_THAT(filePath.name(), ""); +} + +TEST(SourcePathView, FileNameForSingleSlashPath) +{ + QmlDesigner::SourcePathView filePath{"/"}; + + ASSERT_THAT(filePath.name(), ""); +} + +TEST(SourcePathView, FileNameForLongerPath) +{ + QmlDesigner::SourcePathView filePath{"/path/to/some/file.h"}; + + ASSERT_THAT(filePath.name(), "file.h"); +} + +TEST(SourcePathView, FileNameForFileNameOnly) +{ + QmlDesigner::SourcePathView filePath{"file.h"}; + + ASSERT_THAT(filePath.name(), "file.h"); +} + +} diff --git a/tests/unit/unittest/sqlitedatabasemock.h b/tests/unit/unittest/sqlitedatabasemock.h index 29e0a57a0c7..3314dd3a651 100644 --- a/tests/unit/unittest/sqlitedatabasemock.h +++ b/tests/unit/unittest/sqlitedatabasemock.h @@ -28,6 +28,7 @@ #include "googletest.h" #include "sqlitereadstatementmock.h" +#include "sqlitereadwritestatementmock.h" #include "sqlitetransactionbackendmock.h" #include "sqlitewritestatementmock.h" @@ -43,6 +44,8 @@ public: template using ReadStatement = NiceMock>; using WriteStatement = NiceMock; + template + using ReadWriteStatement = NiceMock>; MOCK_METHOD(void, prepare, (Utils::SmallStringView sqlStatement), ()); diff --git a/tests/unit/unittest/sqlitereadstatementmock.h b/tests/unit/unittest/sqlitereadstatementmock.h index b3e65ec18fb..43219fbdb0e 100644 --- a/tests/unit/unittest/sqlitereadstatementmock.h +++ b/tests/unit/unittest/sqlitereadstatementmock.h @@ -29,11 +29,13 @@ #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -160,6 +162,33 @@ public: valuesReturnPrecompiledHeaderTimeStamps, (int projectPartId)); + MOCK_METHOD(QmlDesigner::TypeId, valueReturnsTypeId, (Utils::SmallStringView name), ()); + MOCK_METHOD(QmlDesigner::TypeId, valueWithTransactionReturnsTypeId, (long long, long long), ()); + MOCK_METHOD(QmlDesigner::PropertyDeclarationId, + valueWithTransactionReturnsPropertyDeclarationId, + (long long, Utils::SmallStringView), + ()); + MOCK_METHOD((std::tuple), + valueReturnsPropertyDeclaration, + (long long, Utils::SmallStringView), + ()); + + MOCK_METHOD(std::vector, + valuesReturnSourcesSourceContexts, + (std::size_t), + ()); + + MOCK_METHOD(std::vector, valuesReturnSourcesSources, (std::size_t), ()); + + MOCK_METHOD(QmlDesigner::Sources::SourceNameAndSourceContextId, + valueReturnSourcesSourceNameAndSourceContextId, + (int) ); + + MOCK_METHOD(QmlDesigner::SourceContextId, valueReturnsSourceContextId, (Utils::SmallStringView), ()); + MOCK_METHOD(QmlDesigner::SourceContextId, valueWithTransactionReturnsSourceContextId, (int), ()); + + MOCK_METHOD(QmlDesigner::SourceId, valueReturnsSourceId, (int, Utils::SmallStringView), ()); + template auto optionalValue(const QueryTypes &...queryValues) { @@ -199,8 +228,38 @@ public: template auto value(const QueryTypes &...queryValues) { - static_assert(!std::is_same_v, - "SqliteReadStatementMock::value does not handle result type!"); + if constexpr (std::is_same_v) + return valueReturnsTypeId(queryValues...); + else if constexpr (std::is_same_v) + return valueReturnsPropertyDeclarationId(queryValues...); + else if constexpr (std::is_same_v>) + return valueReturnsPropertyDeclaration(queryValues...); + else if constexpr (std::is_same_v) + return valueReturnSourcesSourceNameAndSourceContextId(queryValues...); + else if constexpr (std::is_same_v) + return valueReturnsSourceContextId(queryValues...); + else if constexpr (std::is_same_v) + return valueReturnsSourceId(queryValues...); + else + static_assert(!std::is_same_v, + "SqliteReadStatementMock::value does not handle result type!"); + } + template + auto valueWithTransaction(const QueryTypes &...queryValues) + { + if constexpr (std::is_same_v) + return valueWithTransactionReturnsTypeId(queryValues...); + else if constexpr (std::is_same_v) + return valueWithTransactionReturnsPropertyDeclarationId(queryValues...); + else if constexpr (std::is_same_v>) + return valueReturnsPropertyDeclaration(queryValues...); + else if constexpr (std::is_same_v) + return valueWithTransactionReturnsSourceContextId(queryValues...); + else + static_assert(!std::is_same_v, + "SqliteReadStatementMock::value does not handle result type!"); } template @@ -240,11 +299,29 @@ public: return valuesReturnSourceEntries(reserveSize, queryValues...); else if constexpr (std::is_same_v) return valuesReturnSourceTimeStamps(reserveSize, queryValues...); + else if constexpr (std::is_same_v) + return valuesReturnSourcesSourceContexts(reserveSize); + else if constexpr (std::is_same_v) + return valuesReturnSourcesSources(reserveSize); else static_assert(!std::is_same_v, "SqliteReadStatementMock::values does not handle result type!"); } + template + auto range(const QueryTypes &...queryValues) + { + static_assert(!std::is_same_v, + "SqliteReadStatementMock::values does not handle result type!"); + } + + template + auto rangeWithTransaction(const QueryTypes &...queryValues) + { + static_assert(!std::is_same_v, + "SqliteReadStatementMock::values does not handle result type!"); + } + public: Utils::SmallString sqlStatement; }; diff --git a/tests/unit/unittest/sqlitereadwritestatementmock.cpp b/tests/unit/unittest/sqlitereadwritestatementmock.cpp new file mode 100644 index 00000000000..f11e61d1e7f --- /dev/null +++ b/tests/unit/unittest/sqlitereadwritestatementmock.cpp @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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 "sqlitereadwritestatementmock.h" + +#include "sqlitedatabasemock.h" + +SqliteReadWriteStatementMockBase::SqliteReadWriteStatementMockBase(Utils::SmallStringView sqlStatement, + SqliteDatabaseMock &databaseMock) + : sqlStatement{sqlStatement} +{ + databaseMock.prepare(sqlStatement); +} diff --git a/tests/unit/unittest/sqlitereadwritestatementmock.h b/tests/unit/unittest/sqlitereadwritestatementmock.h new file mode 100644 index 00000000000..b9a6176a344 --- /dev/null +++ b/tests/unit/unittest/sqlitereadwritestatementmock.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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. +** +****************************************************************************/ + +#pragma once + +#include "googletest.h" + +#include + +#include +#include +#include + +#include +#include +#include + +class SqliteDatabaseMock; + +class SqliteReadWriteStatementMockBase +{ +public: + SqliteReadWriteStatementMockBase() = default; + SqliteReadWriteStatementMockBase(Utils::SmallStringView sqlStatement, + SqliteDatabaseMock &databaseMock); + + MOCK_METHOD(QmlDesigner::TypeId, + valueReturnsTypeId, + (Utils::SmallStringView name, long long id, long long), + ()); + MOCK_METHOD(QmlDesigner::PropertyDeclarationId, + valueReturnsPropertyDeclarationId, + (long long, Utils::SmallStringView, long long), + ()); + + template + auto optionalValue(const QueryTypes &...queryValues) + { + static_assert(!std::is_same_v, + "SqliteReadStatementMock::value does not handle result type!"); + } + + template + auto value(const QueryTypes &...queryValues) + { + if constexpr (std::is_same_v) + return valueReturnsTypeId(queryValues...); + else if constexpr (std::is_same_v) + return valueReturnsPropertyDeclarationId(queryValues...); + else + static_assert(!std::is_same_v, + "SqliteReadStatementMock::value does not handle result type!"); + } + +public: + Utils::SmallString sqlStatement; +}; + +template +class SqliteReadWriteStatementMock : public SqliteReadWriteStatementMockBase +{ +public: + using SqliteReadWriteStatementMockBase::SqliteReadWriteStatementMockBase; +}; diff --git a/tests/unit/unittest/sqlitewritestatementmock.h b/tests/unit/unittest/sqlitewritestatementmock.h index 30f8383b6be..613b41b1d8b 100644 --- a/tests/unit/unittest/sqlitewritestatementmock.h +++ b/tests/unit/unittest/sqlitewritestatementmock.h @@ -42,6 +42,8 @@ public: MOCK_METHOD(void, write, (Utils::SmallStringView), ()); MOCK_METHOD(void, write, (long long), ()); + MOCK_METHOD(void, write, (long long, long long), ()); + MOCK_METHOD(void, write, (Utils::SmallStringView, long long), ()); MOCK_METHOD(void, write, (Utils::SmallStringView, Utils::SmallStringView), ()); MOCK_METHOD(void, write, (long long, Utils::SmallStringView), ()); MOCK_METHOD(void, diff --git a/tests/unit/unittest/storagecache-test.cpp b/tests/unit/unittest/storagecache-test.cpp index 4ffb5caa8f1..d1d10aec453 100644 --- a/tests/unit/unittest/storagecache-test.cpp +++ b/tests/unit/unittest/storagecache-test.cpp @@ -25,31 +25,30 @@ #include "googletest.h" -#include "mockfilepathstorage.h" #include "mockmutex.h" +#include "projectstoragemock.h" #include "sqlitedatabasemock.h" #include +#include #include namespace { +using QmlDesigner::SourceContextId; using QmlDesigner::StorageCacheException; - -using uint64 = unsigned long long; - using Utils::compare; using Utils::reverseCompare; class StorageAdapter { public: - auto fetchId(Utils::SmallStringView view) { return storage.fetchDirectoryId(view); } + auto fetchId(Utils::SmallStringView view) { return storage.fetchSourceContextId(view); } - auto fetchValue(int id) { return storage.fetchDirectoryPath(id); } + auto fetchValue(SourceContextId id) { return storage.fetchSourceContextPath(id); } - MockFilePathStorage &storage; + ProjectStorageMock &storage; }; auto less(Utils::SmallStringView first, Utils::SmallStringView second) -> bool @@ -58,10 +57,14 @@ auto less(Utils::SmallStringView first, Utils::SmallStringView second) -> bool }; using CacheWithMockLocking = QmlDesigner:: - StorageCache, less>; + StorageCache, less>; -using CacheWithoutLocking = QmlDesigner:: - StorageCache, less>; +using CacheWithoutLocking = QmlDesigner::StorageCache, + less>; template class StorageCache : public testing::Test @@ -76,21 +79,22 @@ protected: return reverseCompare(f, l) < 0; }); - ON_CALL(this->mockStorage, fetchDirectoryId(Eq("foo"))).WillByDefault(Return(42)); - ON_CALL(this->mockStorage, fetchDirectoryId(Eq("bar"))).WillByDefault(Return(43)); - ON_CALL(this->mockStorage, fetchDirectoryId(Eq("poo"))).WillByDefault(Return(44)); - ON_CALL(this->mockStorage, fetchDirectoryId(Eq("taa"))).WillByDefault(Return(45)); - ON_CALL(this->mockStorage, fetchDirectoryPath(41)).WillByDefault(Return(Utils::PathString("bar"))); - ON_CALL(this->mockStorage, fetchDirectoryId(Eq(filePath1))).WillByDefault(Return(0)); - ON_CALL(this->mockStorage, fetchDirectoryId(Eq(filePath2))).WillByDefault(Return(1)); - ON_CALL(this->mockStorage, fetchDirectoryId(Eq(filePath3))).WillByDefault(Return(2)); - ON_CALL(this->mockStorage, fetchDirectoryId(Eq(filePath4))).WillByDefault(Return(3)); - ON_CALL(this->mockStorage, fetchDirectoryId(Eq(filePath5))).WillByDefault(Return(4)); + ON_CALL(this->mockStorage, fetchSourceContextId(Eq("foo"))).WillByDefault(Return(id42)); + ON_CALL(this->mockStorage, fetchSourceContextId(Eq("bar"))).WillByDefault(Return(id43)); + ON_CALL(this->mockStorage, fetchSourceContextId(Eq("poo"))).WillByDefault(Return(id44)); + ON_CALL(this->mockStorage, fetchSourceContextId(Eq("taa"))).WillByDefault(Return(id45)); + ON_CALL(this->mockStorage, fetchSourceContextPath(this->id41)) + .WillByDefault(Return(Utils::PathString("bar"))); + ON_CALL(this->mockStorage, fetchSourceContextId(Eq(filePath1))).WillByDefault(Return(id1)); + ON_CALL(this->mockStorage, fetchSourceContextId(Eq(filePath2))).WillByDefault(Return(id2)); + ON_CALL(this->mockStorage, fetchSourceContextId(Eq(filePath3))).WillByDefault(Return(id3)); + ON_CALL(this->mockStorage, fetchSourceContextId(Eq(filePath4))).WillByDefault(Return(id4)); + ON_CALL(this->mockStorage, fetchSourceContextId(Eq(filePath5))).WillByDefault(Return(id5)); } protected: NiceMock databaseMock; - NiceMock mockStorage{databaseMock}; + NiceMock mockStorage{databaseMock}; StorageAdapter storageAdapter{mockStorage}; Cache cache{storageAdapter}; typename Cache::MutexType &mockMutex = cache.mutex(); @@ -99,16 +103,18 @@ protected: Utils::PathString filePath3{"/file/pathThree"}; Utils::PathString filePath4{"/file/pathFour"}; Utils::PathString filePath5{"/file/pathFife"}; - Utils::PathStringVector filePaths{filePath1, - filePath2, - filePath3, - filePath4, - filePath5}; - Utils::PathStringVector reverseFilePaths{filePath1, - filePath2, - filePath3, - filePath4, - filePath5}; + Utils::PathStringVector filePaths{filePath1, filePath2, filePath3, filePath4, filePath5}; + Utils::PathStringVector reverseFilePaths{filePath1, filePath2, filePath3, filePath4, filePath5}; + SourceContextId id1{0}; + SourceContextId id2{1}; + SourceContextId id3{2}; + SourceContextId id4{3}; + SourceContextId id5{4}; + SourceContextId id41{41}; + SourceContextId id42{42}; + SourceContextId id43{43}; + SourceContextId id44{44}; + SourceContextId id45{45}; }; using CacheTypes = ::testing::Types; @@ -118,7 +124,7 @@ TYPED_TEST(StorageCache, AddFilePath) { auto id = this->cache.id(this->filePath1); - ASSERT_THAT(id, 0); + ASSERT_THAT(id, this->id1); } TYPED_TEST(StorageCache, AddSecondFilePath) @@ -127,7 +133,7 @@ TYPED_TEST(StorageCache, AddSecondFilePath) auto id = this->cache.id(this->filePath2); - ASSERT_THAT(id, 1); + ASSERT_THAT(id, this->id2); } TYPED_TEST(StorageCache, AddDuplicateFilePath) @@ -136,7 +142,7 @@ TYPED_TEST(StorageCache, AddDuplicateFilePath) auto id = this->cache.id(this->filePath1); - ASSERT_THAT(id, 0); + ASSERT_THAT(id, this->id1); } TYPED_TEST(StorageCache, AddDuplicateFilePathBetweenOtherEntries) @@ -148,14 +154,14 @@ TYPED_TEST(StorageCache, AddDuplicateFilePathBetweenOtherEntries) auto id = this->cache.id(this->filePath3); - ASSERT_THAT(id, 2); + ASSERT_THAT(id, this->id3); } TYPED_TEST(StorageCache, GetFilePathForIdWithOneEntry) { this->cache.id(this->filePath1); - auto filePath = this->cache.value(0); + auto filePath = this->cache.value(this->id1); ASSERT_THAT(filePath, this->filePath1); } @@ -167,7 +173,7 @@ TYPED_TEST(StorageCache, GetFilePathForIdWithSomeEntries) this->cache.id(this->filePath3); this->cache.id(this->filePath4); - auto filePath = this->cache.value(2); + auto filePath = this->cache.value(this->id3); ASSERT_THAT(filePath, this->filePath3); } @@ -179,7 +185,7 @@ TYPED_TEST(StorageCache, GetAllFilePaths) this->cache.id(this->filePath3); this->cache.id(this->filePath4); - auto filePaths = this->cache.values({0, 1, 2, 3}); + auto filePaths = this->cache.values({this->id1, this->id2, this->id3, this->id4}); ASSERT_THAT(filePaths, ElementsAre(this->filePath1, this->filePath2, this->filePath3, this->filePath4)); @@ -189,14 +195,14 @@ TYPED_TEST(StorageCache, AddFilePaths) { auto ids = this->cache.ids({this->filePath1, this->filePath2, this->filePath3, this->filePath4}); - ASSERT_THAT(ids, ElementsAre(0, 1, 2, 3)); + ASSERT_THAT(ids, ElementsAre(this->id1, this->id2, this->id3, this->id4)); } TYPED_TEST(StorageCache, AddFilePathsWithStorageFunction) { auto ids = this->cache.ids({"foo", "taa", "poo", "bar"}); - ASSERT_THAT(ids, UnorderedElementsAre(42, 43, 44, 45)); + ASSERT_THAT(ids, UnorderedElementsAre(this->id42, this->id43, this->id44, this->id45)); } TYPED_TEST(StorageCache, IsEmpty) @@ -226,10 +232,10 @@ TYPED_TEST(StorageCache, PopulateWithEmptyVector) TYPED_TEST(StorageCache, IsNotEmptyAfterPopulateWithSomeEntries) { - typename TypeParam::CacheEntries entries{{this->filePath1.clone(), 0}, - {this->filePath2.clone(), 3}, - {this->filePath3.clone(), 2}, - {this->filePath4.clone(), 5}}; + typename TypeParam::CacheEntries entries{{this->filePath1.clone(), this->id1}, + {this->filePath2.clone(), this->id4}, + {this->filePath3.clone(), this->id3}, + {this->filePath4.clone(), SourceContextId{5}}}; this->cache.uncheckedPopulate(std::move(entries)); @@ -238,33 +244,33 @@ TYPED_TEST(StorageCache, IsNotEmptyAfterPopulateWithSomeEntries) TYPED_TEST(StorageCache, GetEntryAfterPopulateWithSomeEntries) { - typename TypeParam::CacheEntries entries{{this->filePath1.clone(), 0}, - {this->filePath2.clone(), 1}, - {this->filePath3.clone(), 7}, - {this->filePath4.clone(), 3}}; + typename TypeParam::CacheEntries entries{{this->filePath1.clone(), this->id1}, + {this->filePath2.clone(), this->id2}, + {this->filePath3.clone(), SourceContextId{7}}, + {this->filePath4.clone(), this->id4}}; this->cache.uncheckedPopulate(std::move(entries)); - auto value = this->cache.value(7); + auto value = this->cache.value(SourceContextId{7}); ASSERT_THAT(value, this->filePath3); } TYPED_TEST(StorageCache, EntriesHaveUniqueIds) { - typename TypeParam::CacheEntries entries{{this->filePath1.clone(), 0}, - {this->filePath2.clone(), 1}, - {this->filePath3.clone(), 2}, - {this->filePath4.clone(), 2}}; + typename TypeParam::CacheEntries entries{{this->filePath1.clone(), this->id1}, + {this->filePath2.clone(), this->id2}, + {this->filePath3.clone(), this->id3}, + {this->filePath4.clone(), this->id3}}; ASSERT_THROW(this->cache.populate(std::move(entries)), StorageCacheException); } TYPED_TEST(StorageCache, MultipleEntries) { - typename TypeParam::CacheEntries entries{{this->filePath1.clone(), 0}, - {this->filePath1.clone(), 1}, - {this->filePath3.clone(), 2}, - {this->filePath4.clone(), 3}}; + typename TypeParam::CacheEntries entries{{this->filePath1.clone(), this->id1}, + {this->filePath1.clone(), this->id2}, + {this->filePath3.clone(), this->id3}, + {this->filePath4.clone(), this->id4}}; ASSERT_THROW(this->cache.populate(std::move(entries)), StorageCacheException); } @@ -288,7 +294,7 @@ TYPED_TEST(StorageCache, IdWithStorageFunctionIsReadAndWriteLockedForUnknownEntr EXPECT_CALL(this->mockMutex, lock_shared()); EXPECT_CALL(this->mockMutex, unlock_shared()); EXPECT_CALL(this->mockMutex, lock()); - EXPECT_CALL(this->mockStorage, fetchDirectoryId(Eq("foo"))); + EXPECT_CALL(this->mockStorage, fetchSourceContextId(Eq("foo"))); EXPECT_CALL(this->mockMutex, unlock()); this->cache.id("foo"); @@ -302,7 +308,7 @@ TYPED_TEST(StorageCache, IdWithStorageFunctionIsReadLockedForKnownEntry) EXPECT_CALL(this->mockMutex, lock_shared()); EXPECT_CALL(this->mockMutex, unlock_shared()); EXPECT_CALL(this->mockMutex, lock()).Times(0); - EXPECT_CALL(this->mockStorage, fetchDirectoryId(Eq("foo"))).Times(0); + EXPECT_CALL(this->mockStorage, fetchSourceContextId(Eq("foo"))).Times(0); EXPECT_CALL(this->mockMutex, unlock()).Times(0); this->cache.id("foo"); @@ -363,29 +369,29 @@ TYPED_TEST(StorageCache, ValueWithStorageFunctionIsReadAndWriteLockedForUnknownI EXPECT_CALL(this->mockMutex, lock_shared()); EXPECT_CALL(this->mockMutex, unlock_shared()); EXPECT_CALL(this->mockMutex, lock()); - EXPECT_CALL(this->mockStorage, fetchDirectoryPath(Eq(41))); + EXPECT_CALL(this->mockStorage, fetchSourceContextPath(Eq(this->id41))); EXPECT_CALL(this->mockMutex, unlock()); - this->cache.value(41); + this->cache.value(this->id41); } TYPED_TEST(StorageCache, ValueWithStorageFunctionIsReadLockedForKnownId) { InSequence s; - this->cache.value(41); + this->cache.value(this->id41); EXPECT_CALL(this->mockMutex, lock_shared()); EXPECT_CALL(this->mockMutex, unlock_shared()); EXPECT_CALL(this->mockMutex, lock()).Times(0); - EXPECT_CALL(this->mockStorage, fetchDirectoryPath(Eq(41))).Times(0); + EXPECT_CALL(this->mockStorage, fetchSourceContextPath(Eq(this->id41))).Times(0); EXPECT_CALL(this->mockMutex, unlock()).Times(0); - this->cache.value(41); + this->cache.value(this->id41); } TYPED_TEST(StorageCache, IdWithStorageFunctionWhichHasNoEntryIsCallingStorageFunction) { - EXPECT_CALL(this->mockStorage, fetchDirectoryId(Eq("foo"))); + EXPECT_CALL(this->mockStorage, fetchSourceContextId(Eq("foo"))); this->cache.id("foo"); } @@ -394,7 +400,7 @@ TYPED_TEST(StorageCache, IdWithStorageFunctionWhichHasEntryIsNotCallingStorageFu { this->cache.id("foo"); - EXPECT_CALL(this->mockStorage, fetchDirectoryId(Eq("foo"))).Times(0); + EXPECT_CALL(this->mockStorage, fetchSourceContextId(Eq("foo"))).Times(0); this->cache.id("foo"); } @@ -405,14 +411,14 @@ TYPED_TEST(StorageCache, IndexOfIdWithStorageFunctionWhichHasEntry) auto index = this->cache.id("foo"); - ASSERT_THAT(index, 42); + ASSERT_THAT(index, this->id42); } TYPED_TEST(StorageCache, IndexOfIdWithStorageFunctionWhichHasNoEntry) { auto index = this->cache.id("foo"); - ASSERT_THAT(index, 42); + ASSERT_THAT(index, this->id42); } TYPED_TEST(StorageCache, GetEntryByIndexAfterInsertingByCustomIndex) @@ -424,34 +430,35 @@ TYPED_TEST(StorageCache, GetEntryByIndexAfterInsertingByCustomIndex) ASSERT_THAT(value, Eq("foo")); } -TYPED_TEST(StorageCache, CallFetchDirectoryPathForLowerIndex) +TYPED_TEST(StorageCache, CallFetchSourceContextPathForLowerIndex) { auto index = this->cache.id("foo"); + SourceContextId lowerIndex{&index - 1}; - EXPECT_CALL(this->mockStorage, fetchDirectoryPath(Eq(index - 1))); + EXPECT_CALL(this->mockStorage, fetchSourceContextPath(Eq(lowerIndex))); - this->cache.value(index - 1); + this->cache.value(lowerIndex); } -TYPED_TEST(StorageCache, CallFetchDirectoryPathForUnknownIndex) +TYPED_TEST(StorageCache, CallFetchSourceContextPathForUnknownIndex) { - EXPECT_CALL(this->mockStorage, fetchDirectoryPath(Eq(0))); + EXPECT_CALL(this->mockStorage, fetchSourceContextPath(Eq(this->id1))); - this->cache.value(0); + this->cache.value(this->id1); } -TYPED_TEST(StorageCache, FetchDirectoryPathForUnknownIndex) +TYPED_TEST(StorageCache, FetchSourceContextPathForUnknownIndex) { - auto value = this->cache.value(41); + auto value = this->cache.value(this->id41); ASSERT_THAT(value, Eq("bar")); } TYPED_TEST(StorageCache, AddCalls) { - EXPECT_CALL(this->mockStorage, fetchDirectoryId(Eq("foo"))); - EXPECT_CALL(this->mockStorage, fetchDirectoryId(Eq("bar"))); - EXPECT_CALL(this->mockStorage, fetchDirectoryId(Eq("poo"))); + EXPECT_CALL(this->mockStorage, fetchSourceContextId(Eq("foo"))); + EXPECT_CALL(this->mockStorage, fetchSourceContextId(Eq("bar"))); + EXPECT_CALL(this->mockStorage, fetchSourceContextId(Eq("poo"))); this->cache.add({"foo", "bar", "poo"}); } @@ -460,8 +467,8 @@ TYPED_TEST(StorageCache, AddCallsOnlyForNewValues) { this->cache.add({"foo", "poo"}); - EXPECT_CALL(this->mockStorage, fetchDirectoryId(Eq("taa"))); - EXPECT_CALL(this->mockStorage, fetchDirectoryId(Eq("bar"))); + EXPECT_CALL(this->mockStorage, fetchSourceContextId(Eq("taa"))); + EXPECT_CALL(this->mockStorage, fetchSourceContextId(Eq("bar"))); this->cache.add({"foo", "bar", "poo", "taa"}); } @@ -508,12 +515,12 @@ TYPED_TEST(StorageCache, FetchIdsFromStorageCalls) EXPECT_CALL(this->mockMutex, lock_shared()); EXPECT_CALL(this->mockMutex, unlock_shared()); EXPECT_CALL(this->mockMutex, lock()); - EXPECT_CALL(this->mockStorage, fetchDirectoryId(Eq("foo"))); + EXPECT_CALL(this->mockStorage, fetchSourceContextId(Eq("foo"))); EXPECT_CALL(this->mockMutex, unlock()); EXPECT_CALL(this->mockMutex, lock_shared()); EXPECT_CALL(this->mockMutex, unlock_shared()); EXPECT_CALL(this->mockMutex, lock()); - EXPECT_CALL(this->mockStorage, fetchDirectoryId(Eq("bar"))); + EXPECT_CALL(this->mockStorage, fetchSourceContextId(Eq("bar"))); EXPECT_CALL(this->mockMutex, unlock()); this->cache.ids({"foo", "bar"}); diff --git a/tests/unit/unittest/unittest.pro b/tests/unit/unittest/unittest.pro index d00920fdb5e..ec2a1a7f0d9 100644 --- a/tests/unit/unittest/unittest.pro +++ b/tests/unit/unittest/unittest.pro @@ -144,7 +144,11 @@ SOURCES += \ sqlstatementbuilder-test.cpp \ createtablesqlstatementbuilder-test.cpp \ sqlitereadstatementmock.cpp \ - sqlitewritestatementmock.cpp + sqlitewritestatementmock.cpp \ + sqlitereadwritestatementmock.cpp \ + sourcepath-test.cpp \ + sourcepathview-test.cpp \ + projectstorage-test.cpp !isEmpty(QTC_UNITTEST_BUILD_CPP_PARSER):SOURCES += matchingtext-test.cpp @@ -319,7 +323,9 @@ HEADERS += \ sqlitereadstatementmock.h \ sqlitestatementmock.h \ sqlitetransactionbackendmock.h \ - sqlitewritestatementmock.h + sqlitewritestatementmock.h \ + sqlitereadwritestatementmock.h \ + projectstoragemock.h !isEmpty(LIBCLANG_LIBS) {