/**************************************************************************** ** ** 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 "filesystemmock.h" #include "projectmanagermock.h" #include "projectstoragemock.h" #include "qmldocumentparsermock.h" #include "qmltypesparsermock.h" #include #include #include #include #include namespace { namespace Storage = QmlDesigner::Storage; using QmlDesigner::FileStatus; using QmlDesigner::SourceId; using QmlDesigner::Storage::TypeAccessSemantics; namespace Storage = QmlDesigner::Storage; using QmlDesigner::Storage::Version; MATCHER_P5(IsStorageType, module, typeName, prototype, accessSemantics, sourceId, std::string(negation ? "isn't " : "is ") + PrintToString(Storage::Type{module, typeName, prototype, accessSemantics, sourceId})) { const Storage::Type &type = arg; return type.module == module && type.typeName == typeName && type.accessSemantics == accessSemantics && type.sourceId == sourceId && Storage::ImportedTypeName{prototype} == type.prototype; } MATCHER_P3(IsPropertyDeclaration, name, typeName, traits, std::string(negation ? "isn't " : "is ") + PrintToString(Storage::PropertyDeclaration{name, typeName, traits})) { const Storage::PropertyDeclaration &propertyDeclaration = arg; return propertyDeclaration.name == name && Storage::ImportedTypeName{typeName} == propertyDeclaration.typeName && propertyDeclaration.traits == traits; } MATCHER_P3(IsExportedType, name, majorVersion, minorVersion, std::string(negation ? "isn't " : "is ") + PrintToString(Storage::ExportedType{name, Storage::Version{majorVersion, minorVersion}})) { const Storage::ExportedType &type = arg; return type.name == name && type.version == Storage::Version{majorVersion, minorVersion}; } MATCHER_P2(IsModule, name, sourceId, std::string(negation ? "isn't " : "is ") + PrintToString(Storage::Module{name, sourceId})) { const Storage::Module &module = arg; return module.name == name && module.sourceId == sourceId; } MATCHER_P3(IsFileStatus, sourceId, size, lastModified, std::string(negation ? "isn't " : "is ") + PrintToString(FileStatus{sourceId, size, lastModified})) { const FileStatus &fileStatus = arg; return fileStatus.sourceId == sourceId && fileStatus.size == size && fileStatus.lastModified == lastModified; } class ProjectStorageUpdater : public testing::Test { public: ProjectStorageUpdater() { ON_CALL(fileSystemMock, fileStatus(Eq(qmltypesPathSourceId))) .WillByDefault(Return(FileStatus{qmltypesPathSourceId, 21, 421})); ON_CALL(projectStorageMock, fetchFileStatus(Eq(qmltypesPathSourceId))) .WillByDefault(Return(FileStatus{qmltypesPathSourceId, 2, 421})); ON_CALL(fileSystemMock, fileStatus(Eq(qmltypes2PathSourceId))) .WillByDefault(Return(FileStatus{qmltypes2PathSourceId, 21, 421})); ON_CALL(projectStorageMock, fetchFileStatus(Eq(qmltypes2PathSourceId))) .WillByDefault(Return(FileStatus{qmltypes2PathSourceId, 2, 421})); ON_CALL(fileSystemMock, fileStatus(Eq(qmlDirPathSourceId))) .WillByDefault(Return(FileStatus{qmlDirPathSourceId, 21, 421})); ON_CALL(projectStorageMock, fetchFileStatus(Eq(qmlDirPathSourceId))) .WillByDefault(Return(FileStatus{qmlDirPathSourceId, 2, 421})); ON_CALL(projectStorageMock, fetchSourceDependencieIds(Eq(qmlDirPathSourceId))) .WillByDefault(Return(QmlDesigner::SourceIds{qmltypesPathSourceId, qmltypes2PathSourceId})); QString qmldir{"module Example\ntypeinfo example.qmltypes\n"}; ON_CALL(projectManagerMock, qtQmlDirs()).WillByDefault(Return(QStringList{"/path/qmldir"})); ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/qmldir")))) .WillByDefault(Return(qmldir)); ON_CALL(fileSystemMock, fileStatus(Eq(qmlDocumentSourceId1))) .WillByDefault(Return(FileStatus{qmlDocumentSourceId1, 22, 12})); ON_CALL(projectStorageMock, fetchFileStatus(Eq(qmlDocumentSourceId1))) .WillByDefault(Return(FileStatus{qmlDocumentSourceId1, 22, 2})); ON_CALL(fileSystemMock, fileStatus(Eq(qmlDocumentSourceId2))) .WillByDefault(Return(FileStatus{qmlDocumentSourceId2, 22, 13})); ON_CALL(projectStorageMock, fetchFileStatus(Eq(qmlDocumentSourceId2))) .WillByDefault(Return(FileStatus{qmlDocumentSourceId2, 22, 2})); ON_CALL(fileSystemMock, fileStatus(Eq(qmlDocumentSourceId3))) .WillByDefault(Return(FileStatus{qmlDocumentSourceId3, 22, 14})); ON_CALL(projectStorageMock, fetchFileStatus(Eq(qmlDocumentSourceId3))) .WillByDefault(Return(FileStatus{qmlDocumentSourceId3, 22, 2})); } protected: NiceMock projectManagerMock; NiceMock fileSystemMock; NiceMock projectStorageMock; NiceMock qmlTypesParserMock; NiceMock qmlDocumentParserMock; QmlDesigner::FileStatusCache fileStatusCache{fileSystemMock}; Sqlite::Database database{":memory:", Sqlite::JournalMode::Memory}; QmlDesigner::ProjectStorage storage{database, database.isInitialized()}; QmlDesigner::SourcePathCache> sourcePathCache{ storage}; QmlDesigner::ProjectUpdater updater{projectManagerMock, fileSystemMock, projectStorageMock, fileStatusCache, sourcePathCache, qmlDocumentParserMock, qmlTypesParserMock}; SourceId objectTypeSourceId{sourcePathCache.sourceId("/path/Object")}; Storage::Type objectType{Storage::Module{"Qml"}, "QObject", Storage::NativeType{}, Storage::TypeAccessSemantics::Reference, objectTypeSourceId, {Storage::ExportedType{"Object"}, Storage::ExportedType{"Obj"}}}; SourceId qmltypesPathSourceId = sourcePathCache.sourceId("/path/example.qmltypes"); SourceId qmltypes2PathSourceId = sourcePathCache.sourceId("/path/example2.qmltypes"); SourceId qmlDirPathSourceId = sourcePathCache.sourceId("/path/qmldir"); SourceId qmlDocumentSourceId1 = sourcePathCache.sourceId("/path/First.qml"); SourceId qmlDocumentSourceId2 = sourcePathCache.sourceId("/path/First.2.qml"); SourceId qmlDocumentSourceId3 = sourcePathCache.sourceId("/path/Second.qml"); }; TEST_F(ProjectStorageUpdater, GetContentForQmlDirPathsIfFileStatusIsDifferent) { SourceId qmlDir1PathSourceId = sourcePathCache.sourceId("/path/one/qmldir"); SourceId qmlDir2PathSourceId = sourcePathCache.sourceId("/path/two/qmldir"); SourceId qmlDir3PathSourceId = sourcePathCache.sourceId("/path/three/qmldir"); ON_CALL(projectManagerMock, qtQmlDirs()) .WillByDefault( Return(QStringList{"/path/one/qmldir", "/path/two/qmldir", "/path/three/qmldir"})); ON_CALL(fileSystemMock, fileStatus(_)).WillByDefault([](auto sourceId) { return FileStatus{sourceId, 21, 421}; }); ON_CALL(projectStorageMock, fetchFileStatus(_)).WillByDefault([](auto sourceId) { return FileStatus{sourceId, 2, 421}; }); ON_CALL(fileSystemMock, fileStatus(Eq(qmlDir3PathSourceId))) .WillByDefault(Return(FileStatus{qmlDir3PathSourceId, 21, 421})); ON_CALL(projectStorageMock, fetchFileStatus(Eq(qmlDir3PathSourceId))) .WillByDefault(Return(FileStatus{qmlDir3PathSourceId, 21, 421})); EXPECT_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/one/qmldir")))); EXPECT_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/two/qmldir")))); updater.update(); } TEST_F(ProjectStorageUpdater, RequestFileStatusFromFileSystem) { EXPECT_CALL(fileSystemMock, fileStatus(Ne(qmlDirPathSourceId))).Times(AnyNumber()); EXPECT_CALL(fileSystemMock, fileStatus(Eq(qmlDirPathSourceId))); updater.update(); } TEST_F(ProjectStorageUpdater, GetContentForQmlTypes) { QString qmldir{"module Example\ntypeinfo example.qmltypes\n"}; EXPECT_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/qmldir")))) .WillRepeatedly(Return(qmldir)); EXPECT_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/example.qmltypes")))); updater.update(); } TEST_F(ProjectStorageUpdater, GetContentForQmlTypesIfProjectStorageFileStatusIsInvalid) { QString qmldir{"module Example\ntypeinfo example.qmltypes\n"}; EXPECT_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/qmldir")))) .WillRepeatedly(Return(qmldir)); ON_CALL(projectStorageMock, fetchFileStatus(Eq(qmltypesPathSourceId))) .WillByDefault(Return(FileStatus{})); EXPECT_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/example.qmltypes")))); updater.update(); } TEST_F(ProjectStorageUpdater, DontGetContentForQmlTypesIfFileSystemFileStatusIsInvalid) { QString qmldir{"module Example\ntypeinfo example.qmltypes\n"}; EXPECT_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/qmldir")))) .WillRepeatedly(Return(qmldir)); ON_CALL(fileSystemMock, fileStatus(Eq(qmltypesPathSourceId))).WillByDefault(Return(FileStatus{})); EXPECT_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/example.qmltypes")))).Times(0); updater.update(); } TEST_F(ProjectStorageUpdater, ParseQmlTypes) { QString qmldir{"module Example\ntypeinfo example.qmltypes\ntypeinfo example2.qmltypes\n"}; ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/qmldir")))).WillByDefault(Return(qmldir)); QString qmltypes{"Module {\ndependencies: []}"}; QString qmltypes2{"Module {\ndependencies: [foo]}"}; ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/example.qmltypes")))) .WillByDefault(Return(qmltypes)); ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/example2.qmltypes")))) .WillByDefault(Return(qmltypes2)); EXPECT_CALL(qmlTypesParserMock, parse(qmltypes, _, _, _)); EXPECT_CALL(qmlTypesParserMock, parse(qmltypes2, _, _, _)); updater.update(); } TEST_F(ProjectStorageUpdater, SynchronizeIsEmptyForNoChange) { ON_CALL(projectStorageMock, fetchFileStatus(Eq(qmltypesPathSourceId))) .WillByDefault(Return(FileStatus{qmltypesPathSourceId, 21, 421})); ON_CALL(projectStorageMock, fetchFileStatus(Eq(qmltypes2PathSourceId))) .WillByDefault(Return(FileStatus{qmltypes2PathSourceId, 21, 421})); ON_CALL(projectStorageMock, fetchFileStatus(Eq(qmlDirPathSourceId))) .WillByDefault(Return(FileStatus{qmlDirPathSourceId, 21, 421})); EXPECT_CALL(projectStorageMock, synchronize(IsEmpty(), IsEmpty(), IsEmpty(), IsEmpty(), IsEmpty())); updater.update(); } TEST_F(ProjectStorageUpdater, SynchronizeQmlTypes) { auto qmlDirPathSourceId = sourcePathCache.sourceId("/path/qmldir"); auto qmltypesPathSourceId = sourcePathCache.sourceId("/path/example.qmltypes"); Storage::Import import{"Qml", Storage::Version{2, 3}, qmltypesPathSourceId}; QString qmltypes{"Module {\ndependencies: []}"}; ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/example.qmltypes")))) .WillByDefault(Return(qmltypes)); ON_CALL(qmlTypesParserMock, parse(qmltypes, _, _, _)) .WillByDefault([&](auto, auto &imports, auto &types, auto &sourceIds) { types.push_back(objectType); imports.push_back(import); }); EXPECT_CALL(projectStorageMock, synchronize(ElementsAre(IsModule("Example", qmlDirPathSourceId)), ElementsAre(import), ElementsAre(Eq(objectType)), UnorderedElementsAre(qmlDirPathSourceId, qmltypesPathSourceId), UnorderedElementsAre(IsFileStatus(qmlDirPathSourceId, 21, 421), IsFileStatus(qmltypesPathSourceId, 21, 421)))); updater.update(); } TEST_F(ProjectStorageUpdater, SynchronizeQmlTypesAreEmptyIfFileDoesNotChanged) { QString qmltypes{"Module {\ndependencies: []}"}; ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/example.qmltypes")))) .WillByDefault(Return(qmltypes)); ON_CALL(qmlTypesParserMock, parse(qmltypes, _, _, _)) .WillByDefault([&](auto, auto &imports, auto &types, auto &sourceIds) { types.push_back(objectType); }); ON_CALL(fileSystemMock, fileStatus(Eq(qmltypesPathSourceId))) .WillByDefault(Return(FileStatus{qmltypesPathSourceId, 2, 421})); ON_CALL(fileSystemMock, fileStatus(Eq(qmltypes2PathSourceId))) .WillByDefault(Return(FileStatus{qmltypes2PathSourceId, 2, 421})); ON_CALL(fileSystemMock, fileStatus(Eq(qmlDirPathSourceId))) .WillByDefault(Return(FileStatus{qmlDirPathSourceId, 2, 421})); EXPECT_CALL(projectStorageMock, synchronize(IsEmpty(), IsEmpty(), IsEmpty(), IsEmpty(), IsEmpty())); updater.update(); } TEST_F(ProjectStorageUpdater, GetContentForQmlDocuments) { QString qmldir{"module Example\nFirstType 1.0 First.qml\nFirstTypeV2 2.2 " "First.2.qml\nSecondType 2.1 OldSecond.qml\nSecondType 2.2 Second.qml\n"}; EXPECT_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/qmldir")))) .WillRepeatedly(Return(qmldir)); EXPECT_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/First.qml")))); EXPECT_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/First.2.qml")))); EXPECT_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/Second.qml")))); updater.update(); } TEST_F(ProjectStorageUpdater, ParseQmlDocuments) { QString qmldir{"module Example\nFirstType 1.0 First.qml\nFirstTypeV2 2.2 " "First.2.qml\nSecondType 2.1 OldSecond.qml\nSecondType 2.2 Second.qml\n"}; QString qmlDocument1{"First{}"}; QString qmlDocument2{"Second{}"}; QString qmlDocument3{"Third{}"}; ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/qmldir")))).WillByDefault(Return(qmldir)); ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/First.qml")))) .WillByDefault(Return(qmlDocument1)); ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/First.2.qml")))) .WillByDefault(Return(qmlDocument2)); ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/Second.qml")))) .WillByDefault(Return(qmlDocument3)); EXPECT_CALL(qmlDocumentParserMock, parse(qmlDocument1, _)); EXPECT_CALL(qmlDocumentParserMock, parse(qmlDocument2, _)); EXPECT_CALL(qmlDocumentParserMock, parse(qmlDocument3, _)); updater.update(); } TEST_F(ProjectStorageUpdater, SynchronizeQmlDocuments) { Storage::Module exampleModule{"Example"}; QString qmldir{"module Example\nFirstType 1.0 First.qml\nFirstType 2.2 " "First.2.qml\nSecondType 2.1 OldSecond.qml\nSecondType 2.2 Second.qml\n"}; QString qmlDocument1{"First{}"}; QString qmlDocument2{"Second{}"}; QString qmlDocument3{"Third{}"}; Storage::Type firstType; firstType.prototype = Storage::ImportedType{"Object"}; Storage::Type secondType; secondType.prototype = Storage::ImportedType{"Object2"}; Storage::Type thirdType; thirdType.prototype = Storage::ImportedType{"Object3"}; Storage::Import import1{"Qml", Storage::Version{2, 3}, qmlDocumentSourceId1}; Storage::Import import2{"Qml", Storage::Version{}, qmlDocumentSourceId2}; Storage::Import import3{"Qml", Storage::Version{2}, qmlDocumentSourceId3}; ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/qmldir")))).WillByDefault(Return(qmldir)); ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/First.qml")))) .WillByDefault(Return(qmlDocument1)); ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/First.2.qml")))) .WillByDefault(Return(qmlDocument2)); ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/Second.qml")))) .WillByDefault(Return(qmlDocument3)); ON_CALL(qmlDocumentParserMock, parse(qmlDocument1, _)).WillByDefault([&](auto, auto &imports) { imports.push_back(import1); return firstType; }); ON_CALL(qmlDocumentParserMock, parse(qmlDocument2, _)).WillByDefault([&](auto, auto &imports) { imports.push_back(import2); return secondType; }); ON_CALL(qmlDocumentParserMock, parse(qmlDocument3, _)).WillByDefault([&](auto, auto &imports) { imports.push_back(import3); return thirdType; }); EXPECT_CALL(projectStorageMock, synchronize(ElementsAre(IsModule("Example", qmlDirPathSourceId)), UnorderedElementsAre(import1, import2, import3), UnorderedElementsAre( AllOf(IsStorageType(Storage::Module{"Example"}, "First.qml", Storage::ImportedType{"Object"}, TypeAccessSemantics::Reference, qmlDocumentSourceId1), Field(&Storage::Type::exportedTypes, ElementsAre(IsExportedType("FirstType", 1, 0)))), AllOf(IsStorageType(Storage::Module{"Example"}, "First.2.qml", Storage::ImportedType{"Object2"}, TypeAccessSemantics::Reference, qmlDocumentSourceId2), Field(&Storage::Type::exportedTypes, ElementsAre(IsExportedType("FirstType", 2, 2)))), AllOf(IsStorageType(Storage::Module{"Example"}, "Second.qml", Storage::ImportedType{"Object3"}, TypeAccessSemantics::Reference, qmlDocumentSourceId3), Field(&Storage::Type::exportedTypes, ElementsAre(IsExportedType("SecondType", 2, 2))))), UnorderedElementsAre(qmlDirPathSourceId, qmlDocumentSourceId1, qmlDocumentSourceId2, qmlDocumentSourceId3), UnorderedElementsAre(IsFileStatus(qmlDirPathSourceId, 21, 421), IsFileStatus(qmlDocumentSourceId1, 22, 12), IsFileStatus(qmlDocumentSourceId2, 22, 13), IsFileStatus(qmlDocumentSourceId3, 22, 14)))); updater.update(); } TEST_F(ProjectStorageUpdater, SynchronizeModules) { SourceId qmlDirPathSourceId2 = sourcePathCache.sourceId("/path2/qmldir"); ON_CALL(fileSystemMock, fileStatus(Eq(qmlDirPathSourceId2))) .WillByDefault(Return(FileStatus{qmlDirPathSourceId2, 22, 423})); ON_CALL(projectStorageMock, fetchFileStatus(Eq(qmlDirPathSourceId2))) .WillByDefault(Return(FileStatus{qmlDirPathSourceId2, 2, 421})); QString qmldir2{"module Example2\n"}; ON_CALL(projectManagerMock, qtQmlDirs()) .WillByDefault(Return(QStringList{"/path/qmldir", "/path2/qmldir"})); ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path2/qmldir")))).WillByDefault(Return(qmldir2)); EXPECT_CALL(projectStorageMock, synchronize(UnorderedElementsAre(IsModule("Example", qmlDirPathSourceId), IsModule("Example2", qmlDirPathSourceId2)), _, _, _, UnorderedElementsAre(IsFileStatus(qmlDirPathSourceId, 21, 421), IsFileStatus(qmltypesPathSourceId, 21, 421), IsFileStatus(qmlDirPathSourceId2, 22, 423)))); updater.update(); } } // namespace