From bb7da1720e053b7fcf6683c4662bed326ae61cc5 Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Fri, 28 Mar 2025 17:28:46 +0100 Subject: [PATCH] UnitTests: Refactor qml types test Using paraemterized test to make the test smaller but at the same time check more parameter. Change-Id: I6294dfb2483943de52a31e5939cff993f97d40ae Reviewed-by: Thomas Hartmann --- .../projectstorage/projectstorageupdater.cpp | 19 +- .../unit/tests/printers/gtest-qt-printing.cpp | 8 + tests/unit/tests/printers/gtest-qt-printing.h | 1 + .../projectstorageupdater-test.cpp | 411 +++++++++++++----- .../tests/utils/google-using-declarations.h | 1 + 5 files changed, 321 insertions(+), 119 deletions(-) diff --git a/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorageupdater.cpp b/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorageupdater.cpp index b9778f07636..a6fc2320ee3 100644 --- a/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorageupdater.cpp +++ b/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorageupdater.cpp @@ -484,7 +484,7 @@ void ProjectStorageUpdater::updateDirectoryChanged(Utils::SmallStringView direct qmldirState, qmldirSourceId, isInsideProject); - tracer.tick("append updated project source id", keyValue("module id", moduleId)); + tracer.tick("append updated directory source id", keyValue("module id", moduleId)); package.updatedDirectoryInfoDirectoryIds.push_back(directoryId); if (isInsideProject == IsInsideProject::Yes) { @@ -1168,13 +1168,21 @@ void ProjectStorageUpdater::parseTypeInfos(const QStringList &typeInfos, tracer.tick("append module dependenct source source id", keyValue("source id", sourceId)); package.updatedModuleDependencySourceIds.push_back(sourceId); - const auto &directoryInfo = package.directoryInfos.emplace_back( - directoryId, sourceId, moduleId, Storage::Synchronization::FileType::QmlTypes); - tracer.tick("append project data", keyValue("source id", sourceId)); + const Storage::Synchronization::DirectoryInfo directoryInfo{ + directoryId, sourceId, moduleId, Storage::Synchronization::FileType::QmlTypes}; const QString qmltypesPath = directoryPath + "/" + typeInfo; - parseTypeInfo(directoryInfo, qmltypesPath, package, notUpdatedSourceIds, isInsideProject); + auto state = parseTypeInfo(directoryInfo, + qmltypesPath, + package, + notUpdatedSourceIds, + isInsideProject); + + if (isExisting(state)) { + package.directoryInfos.push_back(directoryInfo); + tracer.tick("append directory info", keyValue("source id", directoryInfo.sourceId)); + } } } @@ -1225,6 +1233,7 @@ auto ProjectStorageUpdater::parseTypeInfo(const Storage::Synchronization::Direct const auto content = m_fileSystem.contentAsQString(qmltypesPath); m_qmlTypesParser.parse(content, package.imports, package.types, directoryInfo, isInsideProject); + break; } case FileState::Unchanged: { diff --git a/tests/unit/tests/printers/gtest-qt-printing.cpp b/tests/unit/tests/printers/gtest-qt-printing.cpp index 9b0e166be5f..d9c3d4d4ad3 100644 --- a/tests/unit/tests/printers/gtest-qt-printing.cpp +++ b/tests/unit/tests/printers/gtest-qt-printing.cpp @@ -91,6 +91,14 @@ std::ostream &operator<<(std::ostream &out, const QIcon &icon) out << icon.cacheKey() << ")"; } +std::ostream &operator<<(std::ostream &out, const QStringList &list) +{ + if (list.isEmpty()) + return out << "[]"; + + return out << "[" << list.join("\", \"") << "]"; +} + void PrintTo(QStringView text, std::ostream *os) { *os << text; diff --git a/tests/unit/tests/printers/gtest-qt-printing.h b/tests/unit/tests/printers/gtest-qt-printing.h index 2711f633198..27b2e8fa6a4 100644 --- a/tests/unit/tests/printers/gtest-qt-printing.h +++ b/tests/unit/tests/printers/gtest-qt-printing.h @@ -43,6 +43,7 @@ std::ostream &operator<<(std::ostream &out, QByteArrayView byteArray); std::ostream &operator<<(std::ostream &out, const QTextCharFormat &format); std::ostream &operator<<(std::ostream &out, const QImage &image); std::ostream &operator<<(std::ostream &out, const QIcon &icon); +std::ostream &operator<<(std::ostream &out, const QStringList &list); void PrintTo(const QString &text, std::ostream *os); void PrintTo(QStringView text, std::ostream *os); diff --git a/tests/unit/tests/unittests/projectstorage/projectstorageupdater-test.cpp b/tests/unit/tests/unittests/projectstorage/projectstorageupdater-test.cpp index 4ef25c57438..100991573cc 100644 --- a/tests/unit/tests/unittests/projectstorage/projectstorageupdater-test.cpp +++ b/tests/unit/tests/unittests/projectstorage/projectstorageupdater-test.cpp @@ -19,6 +19,43 @@ #include #include +namespace QmlDesigner { + +static std::string toString(ProjectStorageUpdater::FileState state) +{ + using FileState = ProjectStorageUpdater::FileState; + switch (state) { + case FileState::Added: + return "added"; + case FileState::Changed: + return "changed"; + case FileState::Removed: + return "removed"; + case FileState::NotExists: + return "not_exists"; + case FileState::NotExistsUnchanged: + return "not_exists_unchanged"; + case FileState::Unchanged: + return "unchanged"; + } + + return ""; +} + +[[maybe_unused]] static std::ostream &operator<<(std::ostream &out, + ProjectStorageUpdater::FileState state) +{ + return out << toString(state); +} + +[[maybe_unused]] static std::ostream &operator<<(std::ostream &out, + ProjectStorageUpdater::Update update) +{ + return out << "(" << update.projectDirectory << "," << update.qtDirectories << "," + << update.propertyEditorResourcesPath << "," << update.typeAnnotationPaths << ")"; +} +} // namespace QmlDesigner + namespace { using namespace Qt::StringLiterals; @@ -50,6 +87,8 @@ using Storage::Synchronization::Type; using Storage::TypeTraits; using Storage::TypeTraitsKind; using Storage::Version; +using FileState = QmlDesigner::ProjectStorageUpdater::FileState; +using Update = QmlDesigner::ProjectStorageUpdater::Update; MATCHER_P5(IsStorageType, typeName, @@ -240,6 +279,29 @@ public: } } + void setFiles(FileState state, const QmlDesigner::SourceIds &sourceIds) + { + switch (state) { + case FileState::Unchanged: + setFilesUnchanged(sourceIds); + break; + case FileState::Changed: + setFilesChanged(sourceIds); + break; + case FileState::Added: + setFilesAdded(sourceIds); + break; + case FileState::Removed: + setFilesRemoved(sourceIds); + break; + case FileState::NotExists: + setFilesNotExists(sourceIds); + break; + case FileState::NotExistsUnchanged: + setFilesNotExistsUnchanged(sourceIds); + } + } + void setFileNames(QStringView directoryPath, const QStringList &fileNames, const QStringList &nameFilters) @@ -805,49 +867,6 @@ TEST_F(ProjectStorageUpdater_synchronize_empty, not_for_added_ignored_subdirecto updater.update({.projectDirectory = "/root"}); } -TEST_F(ProjectStorageUpdater, synchronize_qml_types) -{ - Storage::Import import{qmlModuleId, Storage::Version{2, 3}, qmltypesPathSourceId}; - QString qmltypes{"Module {\ndependencies: []}"}; - setQmlFileNames(u"/path", {}); - setContent(u"/path/example.qmltypes", qmltypes); - ON_CALL(qmlTypesParserMock, parse(qmltypes, _, _, _, _)) - .WillByDefault([&](auto, auto &imports, auto &types, auto, auto) { - types.push_back(objectType); - imports.push_back(import); - }); - - EXPECT_CALL(projectStorageMock, moduleId(Eq("Example"), ModuleKind::QmlLibrary)); - EXPECT_CALL(projectStorageMock, moduleId(Eq("Example"), ModuleKind::CppLibrary)); - EXPECT_CALL(projectStorageMock, moduleId(Eq("/path"), ModuleKind::PathLibrary)); - EXPECT_CALL(projectStorageMock, - synchronize( - AllOf(Field("SynchronizationPackage::imports", - &SynchronizationPackage::imports, - ElementsAre(import)), - Field("SynchronizationPackage::types", - &SynchronizationPackage::types, - ElementsAre(Eq(objectType))), - Field("SynchronizationPackage::updatedSourceIds", - &SynchronizationPackage::updatedSourceIds, - UnorderedElementsAre(qmlDirPathSourceId, qmltypesPathSourceId)), - Field("SynchronizationPackage::fileStatuses", - &SynchronizationPackage::fileStatuses, - UnorderedElementsAre(IsFileStatus(qmlDirPathSourceId, 1, 21), - IsFileStatus(qmltypesPathSourceId, 1, 21))), - Field("SynchronizationPackage::directoryInfos", - &SynchronizationPackage::directoryInfos, - UnorderedElementsAre(IsDirectoryInfo(directoryPathId, - qmltypesPathSourceId, - exampleCppNativeModuleId, - FileType::QmlTypes))), - Field("SynchronizationPackage::updatedDirectoryInfoDirectoryIds", - &SynchronizationPackage::updatedDirectoryInfoDirectoryIds, - UnorderedElementsAre(directoryPathId))))); - - updater.update({.qtDirectories = directories}); -} - class ProjectStorageUpdater_synchronize_subdirectories : public BaseProjectStorageUpdater { public: @@ -1052,34 +1071,254 @@ TEST_F(ProjectStorageUpdater_synchronize_subdirectories, for_deleted_project_sub updater.update({.projectDirectory = {"/root"}}); } -TEST_F(ProjectStorageUpdater, synchronize_qml_types_notfies_error_if_qmltypes_does_not_exists) +class synchronize_qml_types : public BaseProjectStorageUpdater { +public: + synchronize_qml_types() + + { + setQmlFileNames(u"/path", {}); + + QString qmltypes{"Module {\ndependencies: []}"}; + setContent(u"/path/example.qmltypes", qmltypes); + setContent(u"/path/qmldir", "module Example\ntypeinfo example.qmltypes\n"); + ON_CALL(qmlTypesParserMock, parse(qmltypes, _, _, _, _)) + .WillByDefault([&](auto, auto &imports, auto &types, auto, auto) { + types.push_back(objectType); + imports.push_back(import); + }); + setFilesNotExistsUnchanged({annotationDirectorySourceId}); + } + +public: + SourceId qmltypesPathSourceId = sourcePathCache.sourceId("/path/example.qmltypes"); + SourceId qmlDirPathSourceId = sourcePathCache.sourceId("/path/qmldir"); + SourceContextId directoryPathId = qmlDirPathSourceId.contextId(); + SourceId directoryPathSourceId = SourceId::create(QmlDesigner::SourceNameId{}, directoryPathId); + SourceId annotationDirectorySourceId = createDirectorySourceId("/path/designer"); + ModuleId qmlModuleId{storage.moduleId("Qml", ModuleKind::QmlLibrary)}; + ModuleId exampleModuleId{storage.moduleId("Example", ModuleKind::QmlLibrary)}; + ModuleId exampleCppNativeModuleId{storage.moduleId("Example", ModuleKind::CppLibrary)}; Storage::Import import{qmlModuleId, Storage::Version{2, 3}, qmltypesPathSourceId}; - setFilesNotExists({qmltypesPathSourceId}); + Type objectType{"QObject", + ImportedType{}, + ImportedType{}, + Storage::TypeTraitsKind::Reference, + qmltypesPathSourceId, + {ExportedType{exampleModuleId, "Object"}, ExportedType{exampleModuleId, "Obj"}}}; +}; + +using ChangeQmlTypesParameters = std::tuple; + +class synchronize_changed_qml_types : public synchronize_qml_types, + public testing::WithParamInterface +{ +public: + synchronize_changed_qml_types() + : state{std::get<1>(GetParam())} + , directoryState{std::get<2>(GetParam())} + , qmldirState{std::get<3>(GetParam())} + , update{std::get<0>(GetParam())} + + { + if (qmldirState == FileState::Unchanged) { + setDirectoryInfos( + directoryPathId, + {{directoryPathId, qmltypesPathSourceId, exampleModuleId, FileType::QmlTypes}}); + } + + if (state == FileState::Added) + directoryState = FileState::Changed; + } + +public: + FileState state; + FileState directoryState; + FileState qmldirState; + const Update &update; +}; + +auto updateStatesName = [](const testing::TestParamInfo &info) { + std::string name = toString(std::get<1>(info.param)); + + name += "_qmltypes_file_in_"; + + if (std::get<0>(info.param).projectDirectory.isEmpty()) + name += "qt_"; + else + name += "project_"; + + name += toString(std::get<2>(info.param)); + + name += "_directory_"; + + name += toString(std::get<3>(info.param)); + + name += "_qmldir"; + + return name; +}; + +INSTANTIATE_TEST_SUITE_P(ProjectStorageUpdater, + synchronize_changed_qml_types, + testing::Combine(testing::Values(Update{.qtDirectories = {"/path"}}, + Update{.projectDirectory = "/path"}), + testing::Values(FileState::Added, FileState::Changed), + testing::Values(FileState::Changed, FileState::Unchanged), + testing::Values(FileState::Changed, FileState::Unchanged)), + updateStatesName); + +TEST_P(synchronize_changed_qml_types, from_qt_directory_update_types) +{ + setFiles(directoryState, {directoryPathSourceId}); + setFiles(qmldirState, {qmlDirPathSourceId}); + setFiles(state, {qmltypesPathSourceId}); + + EXPECT_CALL(projectStorageMock, + synchronize(AllOf(Field("SynchronizationPackage::imports", + &SynchronizationPackage::imports, + ElementsAre(import)), + Field("SynchronizationPackage::types", + &SynchronizationPackage::types, + ElementsAre(Eq(objectType))), + Field("SynchronizationPackage::updatedSourceIds", + &SynchronizationPackage::updatedSourceIds, + Contains(qmltypesPathSourceId))))); + + updater.update(update); +} + +TEST_P(synchronize_changed_qml_types, from_qt_directory_update_file_status) +{ + setFiles(directoryState, {directoryPathSourceId}); + setFiles(qmldirState, {qmlDirPathSourceId}); + setFiles(state, {qmltypesPathSourceId}); + + EXPECT_CALL(projectStorageMock, + synchronize(AllOf(Field("SynchronizationPackage::fileStatuses", + &SynchronizationPackage::fileStatuses, + Contains(IsFileStatus(qmltypesPathSourceId, 1, 21))), + Field("SynchronizationPackage::updatedFileStatusSourceIds", + &SynchronizationPackage::updatedFileStatusSourceIds, + Contains(qmltypesPathSourceId))))); + + updater.update(update); +} + +TEST_P(synchronize_changed_qml_types, from_qt_directory_update_directory_infos) +{ + bool directoryUnchanged = directoryState == FileState::Unchanged + && qmldirState == FileState::Unchanged; + setFiles(directoryState, {directoryPathSourceId}); + setFiles(qmldirState, {qmlDirPathSourceId}); + setFiles(state, {qmltypesPathSourceId}); + + EXPECT_CALL(projectStorageMock, + synchronize( + AllOf(Field("SynchronizationPackage::directoryInfos", + &SynchronizationPackage::directoryInfos, + Conditional(directoryUnchanged, + IsEmpty(), + Contains(IsDirectoryInfo(directoryPathId, + qmltypesPathSourceId, + exampleCppNativeModuleId, + FileType::QmlTypes)))), + Field("SynchronizationPackage::updatedDirectoryInfoDirectoryIds", + &SynchronizationPackage::updatedDirectoryInfoDirectoryIds, + Conditional(directoryUnchanged, IsEmpty(), Contains(directoryPathId)))))); + + updater.update(update); +} + +using NonExistingQmlTypesParameters = std::tuple; + +class synchronize_non_existing_qml_types + : public synchronize_qml_types, + public testing::WithParamInterface +{ +public: + synchronize_non_existing_qml_types() + : state{std::get<1>(GetParam())} + , update{std::get<0>(GetParam())} + + { + setDirectoryInfos(directoryPathId, + {{directoryPathId, qmltypesPathSourceId, exampleModuleId, FileType::QmlTypes}}); + } + +public: + FileState state; + const Update &update; +}; + +auto updateStateName = [](const testing::TestParamInfo &info) { + std::string name = toString(std::get<1>(info.param)); + + if (std::get<0>(info.param).projectDirectory.isEmpty()) + name += "_qt"; + else + name += "_project"; + + return name; +}; + +INSTANTIATE_TEST_SUITE_P(ProjectStorageUpdater, + synchronize_non_existing_qml_types, + testing::Combine(testing::Values(Update{.qtDirectories = {"/path"}}, + Update{.projectDirectory = "/path"}), + testing::Values(FileState::Removed, FileState::NotExists)), + updateStateName); + +TEST_P(synchronize_non_existing_qml_types, notfies_error) +{ + setFilesUnchanged({directoryPathSourceId, qmlDirPathSourceId}); + setFiles(state, {qmltypesPathSourceId}); EXPECT_CALL(errorNotifierMock, qmltypesFileMissing(Eq("/path/example.qmltypes"_L1))); - updater.update({.qtDirectories = directories}); + updater.update(update); } -TEST_F(ProjectStorageUpdater, - synchronize_qml_types_adds_updated_source_id_and_directory_info_for_missing_qmltypes_file) +TEST_P(synchronize_non_existing_qml_types, notfies_error_if_direcory_and_qmldir_file_is_changed) { - Storage::Import import{qmlModuleId, Storage::Version{2, 3}, qmltypesPathSourceId}; - setFilesNotExists({qmltypesPathSourceId}); + setFilesChanged({directoryPathSourceId, qmlDirPathSourceId}); + setFiles(state, {qmltypesPathSourceId}); + + EXPECT_CALL(errorNotifierMock, qmltypesFileMissing(Eq("/path/example.qmltypes"_L1))); + + updater.update(update); +} + +TEST_P(synchronize_non_existing_qml_types, updates_source_ids) +{ + setFilesUnchanged({directoryPathSourceId, qmlDirPathSourceId}); + setFiles(state, {qmltypesPathSourceId}); EXPECT_CALL(projectStorageMock, synchronize(AllOf(Field("SynchronizationPackage::updatedSourceIds", &SynchronizationPackage::updatedSourceIds, - Contains(qmltypesPathSourceId)), - Field("SynchronizationPackage::directoryInfos", - &SynchronizationPackage::directoryInfos, - Contains(IsDirectoryInfo(directoryPathId, - qmltypesPathSourceId, - exampleCppNativeModuleId, - FileType::QmlTypes)))))); + Contains(qmltypesPathSourceId))))); - updater.update({.qtDirectories = directories}); + updater.update(update); +} + +TEST_P(synchronize_non_existing_qml_types, updates_directory_info) +{ + setFilesChanged({directoryPathSourceId, qmlDirPathSourceId}); + setFiles(state, {qmltypesPathSourceId}); + + EXPECT_CALL(projectStorageMock, + synchronize(AllOf(Field("SynchronizationPackage::directoryInfos", + &SynchronizationPackage::directoryInfos, + Not(Contains(IsDirectoryInfo(directoryPathId, + qmltypesPathSourceId, + exampleCppNativeModuleId, + FileType::QmlTypes)))), + Field("SynchronizationPackage::updatedDirectoryInfoDirectoryIds", + &SynchronizationPackage::updatedDirectoryInfoDirectoryIds, + Contains(directoryPathId))))); + + updater.update(update); } TEST_F(ProjectStorageUpdater, synchronize_qml_types_are_empty_if_file_does_not_changed) @@ -1830,62 +2069,6 @@ TEST_F(ProjectStorageUpdater, synchronize_qml_documents_dont_update_if_up_to_dat updater.update({.qtDirectories = directories}); } -TEST_F(ProjectStorageUpdater, synchroniz_if_qmldir_file_has_not_changed) -{ - setDirectoryInfos(directoryPathId, - {{directoryPathId, qmltypesPathSourceId, exampleModuleId, FileType::QmlTypes}, - {directoryPathId, qmltypes2PathSourceId, exampleModuleId, FileType::QmlTypes}, - {directoryPathId, qmlDocumentSourceId1, exampleModuleId, FileType::QmlDocument}, - {directoryPathId, qmlDocumentSourceId2, exampleModuleId, FileType::QmlDocument}}); - setFilesUnchanged({qmlDirPathSourceId}); - - EXPECT_CALL(projectStorageMock, - synchronize(AllOf( - Field("SynchronizationPackage::imports", - &SynchronizationPackage::imports, - UnorderedElementsAre(import1, import2, import4, import5)), - Field("SynchronizationPackage::types", - &SynchronizationPackage::types, - UnorderedElementsAre( - Eq(objectType), - Eq(itemType), - AllOf(IsStorageType("First.qml", - ImportedType{"Object"}, - TypeTraitsKind::Reference, - qmlDocumentSourceId1, - ChangeLevel::ExcludeExportedTypes), - Field("Type::exportedTypes", &Type::exportedTypes, IsEmpty())), - AllOf(IsStorageType("First2.qml", - ImportedType{"Object2"}, - TypeTraitsKind::Reference, - qmlDocumentSourceId2, - ChangeLevel::ExcludeExportedTypes), - Field("Type::exportedTypes", &Type::exportedTypes, IsEmpty())))), - Field("SynchronizationPackage::updatedSourceIds", - &SynchronizationPackage::updatedSourceIds, - UnorderedElementsAre(qmltypesPathSourceId, - qmltypes2PathSourceId, - qmlDocumentSourceId1, - qmlDocumentSourceId2)), - Field("SynchronizationPackage::fileStatuses", - &SynchronizationPackage::fileStatuses, - UnorderedElementsAre(IsFileStatus(qmltypesPathSourceId, 1, 21), - IsFileStatus(qmltypes2PathSourceId, 1, 21), - IsFileStatus(qmlDocumentSourceId1, 1, 21), - IsFileStatus(qmlDocumentSourceId2, 1, 21))), - Field("SynchronizationPackage::updatedFileStatusSourceIds", - &SynchronizationPackage::updatedFileStatusSourceIds, - UnorderedElementsAre(qmltypesPathSourceId, - qmltypes2PathSourceId, - qmlDocumentSourceId1, - qmlDocumentSourceId2)), - Field("SynchronizationPackage::directoryInfos", - &SynchronizationPackage::directoryInfos, - IsEmpty())))); - - updater.update({.qtDirectories = directories}); -} - TEST_F(ProjectStorageUpdater, synchroniz_if_qmldir_file_has_not_changed_and_some_updated_files) { setDirectoryInfos(directoryPathId, diff --git a/tests/unit/tests/utils/google-using-declarations.h b/tests/unit/tests/utils/google-using-declarations.h index 213608916fb..8dd8e790b5c 100644 --- a/tests/unit/tests/utils/google-using-declarations.h +++ b/tests/unit/tests/utils/google-using-declarations.h @@ -17,6 +17,7 @@ using testing::AtMost; using testing::Between; using testing::ByMove; using testing::ByRef; +using testing::Conditional; using testing::ContainerEq; using testing::Contains; using testing::Each;