From 94f5f445ee18633db2973033081700fc2a0b4b03 Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Sun, 9 Jun 2024 21:09:59 +0200 Subject: [PATCH] Improved test coverage (#287) --- .github/workflows/build.yml | 3 +- Makefile | 5 +- src/common/generators/generators.cc | 2 + src/common/generators/progress_indicator.cc | 16 +++ src/common/generators/progress_indicator.h | 3 + src/common/visitor/comment/clang_visitor.cc | 5 +- .../mermaid/include_diagram_generator.cc | 19 ---- .../mermaid/include_diagram_generator.h | 12 -- src/include_diagram/model/diagram.cc | 19 ---- src/include_diagram/model/diagram.h | 11 -- tests/CMakeLists.txt | 3 +- tests/t00020/.clang-uml | 14 ++- tests/t00020/test_case.h | 36 ++++-- tests/t00043/.clang-uml | 7 +- tests/t00043/t00043.cc | 18 +++ tests/t00043/test_case.h | 5 + tests/t00050/t00050.cc | 5 + tests/test_compilation_database.cc | 9 ++ tests/test_model.cc | 16 +++ tests/test_progress_indicator.cc | 105 ++++++++++++++++++ tests/test_types.cc | 22 ++++ 21 files changed, 252 insertions(+), 83 deletions(-) create mode 100644 tests/test_progress_indicator.cc diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1d467a67..a1c115af 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -45,7 +45,8 @@ jobs: - name: Run coverage run: | lcov -c -d debug -o coverage.info - lcov -e coverage.info "$PWD/src/*" -o coverage-src.info + lcov -r coverage.info -o coverage-src.info "$PWD/src/main.cc" "$PWD/src/common/generators/generators.cc" + lcov -e coverage-src.info -o coverage-src.info "$PWD/src/*" lcov -l coverage-src.info - name: Upload coverage uses: codecov/codecov-action@v3 diff --git a/Makefile b/Makefile index 3d55eecf..b375b87a 100644 --- a/Makefile +++ b/Makefile @@ -50,7 +50,7 @@ DESTDIR ?= .PHONY: clean clean: - rm -rf debug release debug_tidy + rm -rf debug release debug_tidy coverage.info coverage-src.info debug/CMakeLists.txt: cmake -S . -B debug \ @@ -117,7 +117,8 @@ test_release: release coverage_report: test lcov -c -d debug -o coverage.info - lcov -e coverage.info "${PWD}/src/*" -o coverage-src.info + lcov -r coverage.info -o coverage-src.info "${PWD}/src/main.cc" "${PWD}/src/common/generators/generators.cc" + lcov -e coverage-src.info -o coverage-src.info "${PWD}/src/*" lcov -l coverage-src.info genhtml coverage-src.info --output-directory debug/coverage_html diff --git a/src/common/generators/generators.cc b/src/common/generators/generators.cc index 03494ceb..592c6c31 100644 --- a/src/common/generators/generators.cc +++ b/src/common/generators/generators.cc @@ -166,6 +166,8 @@ void generate_diagram_impl(const std::string &name, runtime_config.output_directory, name, diagram, model); } + // Convert plantuml or mermaid to an image using command provided + // in the command line arguments if (runtime_config.render_diagrams) { render_diagram(generator_type, diagram); } diff --git a/src/common/generators/progress_indicator.cc b/src/common/generators/progress_indicator.cc index ce019d87..8fe095db 100644 --- a/src/common/generators/progress_indicator.cc +++ b/src/common/generators/progress_indicator.cc @@ -23,6 +23,12 @@ namespace clanguml::common::generators { progress_indicator::progress_indicator() + : progress_indicator(std::cout) +{ +} + +progress_indicator::progress_indicator(std::ostream &ostream) + : ostream_(ostream) { progress_bars_.set_option(indicators::option::HideBarWhenComplete{false}); } @@ -36,6 +42,7 @@ void progress_indicator::add_progress_bar( const auto kPrefixTextWidth = 25U; auto bar = std::make_shared( + indicators::option::Stream{ostream_}, indicators::option::BarWidth{kBarWidth}, indicators::option::ForegroundColor{color}, indicators::option::ShowElapsedTime{true}, @@ -78,6 +85,7 @@ void progress_indicator::increment(const std::string &name) bar.set_progress((p.progress * kASTTraverseProgressPercent) / p.max); bar.set_option(indicators::option::PostfixText{ fmt::format("{}/{}", p.progress, p.max)}); + progress_bars_mutex_.unlock(); } @@ -106,6 +114,8 @@ void progress_indicator::complete(const std::string &name) auto &p = progress_bar_index_.at(name); auto &bar = progress_bars_[p.index]; + p.progress = p.max; + bar.set_progress(kCompleteProgressPercent); #if _MSC_VER @@ -124,6 +134,12 @@ void progress_indicator::complete(const std::string &name) void progress_indicator::fail(const std::string &name) { progress_bars_mutex_.lock(); + + if (progress_bar_index_.count(name) == 0) { + progress_bars_mutex_.unlock(); + return; + } + auto &p = progress_bar_index_.at(name); auto &bar = progress_bars_[p.index]; diff --git a/src/common/generators/progress_indicator.h b/src/common/generators/progress_indicator.h index 0c5a7279..1c25e88d 100644 --- a/src/common/generators/progress_indicator.h +++ b/src/common/generators/progress_indicator.h @@ -45,6 +45,8 @@ public: progress_indicator(); + progress_indicator(std::ostream &ostream); + /** * Add a new progress bar to the indicator set * @@ -86,5 +88,6 @@ private: std::vector> bars_; std::map progress_bar_index_; std::mutex progress_bars_mutex_; + std::ostream &ostream_; }; } // namespace clanguml::common::generators diff --git a/src/common/visitor/comment/clang_visitor.cc b/src/common/visitor/comment/clang_visitor.cc index 7fd5a226..91a61308 100644 --- a/src/common/visitor/comment/clang_visitor.cc +++ b/src/common/visitor/comment/clang_visitor.cc @@ -17,6 +17,7 @@ */ #include "clang_visitor.h" +#include "util/util.h" #if LLVM_VERSION_MAJOR > 17 #define CLANG_UML_LLVM_COMMENT_KIND(COMMENT_KIND) \ @@ -191,7 +192,7 @@ void clang_visitor::visit_param_command( inja::json param = inja::json::object(); param["name"] = name; - param["description"] = description; + param["description"] = util::trim(description); cmt["param"].push_back(std::move(param)); } } @@ -226,7 +227,7 @@ void clang_visitor::visit_tparam_command( inja::json param = inja::json::object(); param["name"] = name; - param["description"] = description; + param["description"] = util::trim(description); cmt["tparam"].push_back(std::move(param)); } } diff --git a/src/include_diagram/generators/mermaid/include_diagram_generator.cc b/src/include_diagram/generators/mermaid/include_diagram_generator.cc index 475a7733..be7188de 100644 --- a/src/include_diagram/generators/mermaid/include_diagram_generator.cc +++ b/src/include_diagram/generators/mermaid/include_diagram_generator.cc @@ -98,25 +98,6 @@ void generator::generate(const source_file &f, std::ostream &ostr) const } } -void generator::generate_notes( - std::ostream &ostr, const common::model::diagram_element &element) const -{ - const auto &config = - common_generator::config(); - - for (const auto &decorator : element.decorators()) { - auto note = std::dynamic_pointer_cast(decorator); - if (note && note->applies_to_diagram(config.name)) { - auto note_id_str = fmt::format("N_{}", note_id_++); - - ostr << indent(1) << note_id_str << "(" << note->text << ")\n"; - - ostr << indent(1) << note_id_str << "-.-" << element.alias() - << '\n'; - } - } -} - void generator::generate_diagram(std::ostream &ostr) const { // Generate files and folders diff --git a/src/include_diagram/generators/mermaid/include_diagram_generator.h b/src/include_diagram/generators/mermaid/include_diagram_generator.h index 0a530004..bbe0483f 100644 --- a/src/include_diagram/generators/mermaid/include_diagram_generator.h +++ b/src/include_diagram/generators/mermaid/include_diagram_generator.h @@ -79,15 +79,6 @@ public: */ void generate_relationships(const source_file &p, std::ostream &ostr) const; - /** - * @brief Generate notes attached to files - * - * @param ostr Output stream - * @param element Element with a note - */ - void generate_notes(std::ostream &ostr, - const common::model::diagram_element &element) const override; - /** * @brief Generate diagram element * @@ -95,9 +86,6 @@ public: * @param parent Output stream */ void generate(const source_file &e, std::ostream &ostr) const; - -private: - mutable uint64_t note_id_{0UL}; }; } // namespace clanguml::include_diagram::generators::mermaid diff --git a/src/include_diagram/model/diagram.cc b/src/include_diagram/model/diagram.cc index 6a200b61..8bce0ae1 100644 --- a/src/include_diagram/model/diagram.cc +++ b/src/include_diagram/model/diagram.cc @@ -89,25 +89,6 @@ void diagram::add_file(std::unique_ptr &&f) add_element(p, std::move(f)); } -std::string diagram::to_alias(const std::string &full_name) const -{ - LOG_DBG("Looking for alias for {}", full_name); - - auto path = common::model::filesystem_path{full_name}; - - if (path.is_empty()) - throw error::uml_alias_missing( - fmt::format("Missing alias for '{}'", path.to_string())); - - auto source_file = get_element(path); - - if (!source_file) - throw error::uml_alias_missing( - fmt::format("Missing alias for '{}'", path.to_string())); - - return source_file.value().alias(); -} - const common::reference_vector & diagram::files() const { diff --git a/src/include_diagram/model/diagram.h b/src/include_diagram/model/diagram.h index 3abbfb02..38fe3a9a 100644 --- a/src/include_diagram/model/diagram.h +++ b/src/include_diagram/model/diagram.h @@ -103,17 +103,6 @@ public: */ template opt_ref find(eid_t id) const; - /** - * @brief Convert element id to PlantUML alias. - * - * @todo This method does not belong here - refactor to PlantUML specific - * code. - * - * @param full_name Full name of the diagram element. - * @return PlantUML alias. - */ - std::string to_alias(const std::string &full_name) const; - /** * @brief Get list of references to files in the diagram model. * diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8a106be2..88a911e9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -94,7 +94,8 @@ set(TEST_NAMES test_cli_handler test_filters test_thread_pool_executor - test_query_driver_output_extractor) + test_query_driver_output_extractor + test_progress_indicator) foreach(TEST_NAME ${TEST_NAMES}) add_executable(${TEST_NAME}) diff --git a/tests/t00020/.clang-uml b/tests/t00020/.clang-uml index a0f03ac9..3e261e9d 100644 --- a/tests/t00020/.clang-uml +++ b/tests/t00020/.clang-uml @@ -9,7 +9,13 @@ diagrams: - clanguml::t00020 plantuml: after: - - '@A(ProductA1) <.. @A(Factory1)' - - '@A(ProductB1) <.. @A(Factory1)' - - '{{ alias("ProductA2") }} <.. {{ alias("Factory2") }}' - - '{{ alias("ProductB2") }} <.. {{ alias("Factory2") }}' + - '@A(Factory1) ..> @A(ProductA1)' + - '@A(Factory1) ..> @A(ProductB1)' + - '{{ alias("Factory2") }} ..> {{ alias("ProductA2") }}' + - '{{ alias("Factory2") }} ..> {{ alias("ProductB2") }}' + mermaid: + after: + - '@A(Factory1) ..> @A(ProductA1)' + - '@A(Factory1) ..> @A(ProductB1)' + - '{{ alias("Factory2") }} ..> {{ alias("ProductA2") }}' + - '{{ alias("Factory2") }} ..> {{ alias("ProductB2") }}' \ No newline at end of file diff --git a/tests/t00020/test_case.h b/tests/t00020/test_case.h index bef7d8c8..e4a58272 100644 --- a/tests/t00020/test_case.h +++ b/tests/t00020/test_case.h @@ -23,15 +23,29 @@ TEST_CASE("t00020") auto [config, db, diagram, model] = CHECK_CLASS_MODEL("t00020", "t00020_class"); - CHECK_CLASS_DIAGRAM(config, diagram, *model, [](const auto &src) { - REQUIRE(IsAbstractClass(src, "AbstractFactory")); - REQUIRE(IsAbstractClass(src, "ProductA")); - REQUIRE(IsAbstractClass(src, "ProductB")); - REQUIRE(IsClass(src, "ProductA1")); - REQUIRE(IsClass(src, "ProductA2")); - REQUIRE(IsClass(src, "ProductB1")); - REQUIRE(IsClass(src, "ProductB2")); - REQUIRE(IsClass(src, "Factory1")); - REQUIRE(IsClass(src, "Factory2")); - }); + CHECK_CLASS_DIAGRAM( + config, diagram, *model, + [](const auto &src) { + REQUIRE(IsAbstractClass(src, "AbstractFactory")); + REQUIRE(IsAbstractClass(src, "ProductA")); + REQUIRE(IsAbstractClass(src, "ProductB")); + REQUIRE(IsClass(src, "ProductA1")); + REQUIRE(IsClass(src, "ProductA2")); + REQUIRE(IsClass(src, "ProductB1")); + REQUIRE(IsClass(src, "ProductB2")); + REQUIRE(IsClass(src, "Factory1")); + REQUIRE(IsClass(src, "Factory2")); + }, + [](const plantuml_t &src) { + REQUIRE(IsDependency(src, "Factory1", "ProductA1")); + REQUIRE(IsDependency(src, "Factory1", "ProductB1")); + REQUIRE(IsDependency(src, "Factory2", "ProductA2")); + REQUIRE(IsDependency(src, "Factory2", "ProductB2")); + }, + [](const mermaid_t &src) { + REQUIRE(IsDependency(src, "Factory1", "ProductA1")); + REQUIRE(IsDependency(src, "Factory1", "ProductB1")); + REQUIRE(IsDependency(src, "Factory2", "ProductA2")); + REQUIRE(IsDependency(src, "Factory2", "ProductB2")); + }); } \ No newline at end of file diff --git a/tests/t00043/.clang-uml b/tests/t00043/.clang-uml index ec5c5b5d..4735ae82 100644 --- a/tests/t00043/.clang-uml +++ b/tests/t00043/.clang-uml @@ -11,4 +11,9 @@ diagrams: dependencies: - clanguml::t00043::dependencies::J relationships: - - dependency \ No newline at end of file + - dependency + exclude: + dependants: + - clanguml::t00043::dependants::EE + dependencies: + - clanguml::t00043::dependencies::II \ No newline at end of file diff --git a/tests/t00043/t00043.cc b/tests/t00043/t00043.cc index d3a8a7c1..1b9fa75a 100644 --- a/tests/t00043/t00043.cc +++ b/tests/t00043/t00043.cc @@ -24,6 +24,14 @@ struct E { void e(D *d) { } }; +struct EE { + void ee(E *e) { } +}; + +struct EEE { + void eee(EE *e) { } +}; + struct F { }; } // namespace dependants @@ -46,8 +54,18 @@ struct I { void i(H *h) { } }; +struct II; +struct III { + void iii(II *i) { } +}; + +struct II { + void ii() { } +}; + struct J { void i(I *i) { } + void ii(II *ii) { } }; } // namespace dependencies diff --git a/tests/t00043/test_case.h b/tests/t00043/test_case.h index 1f81bb68..23c4c85f 100644 --- a/tests/t00043/test_case.h +++ b/tests/t00043/test_case.h @@ -30,6 +30,9 @@ TEST_CASE("t00043") REQUIRE(IsClass(src, {"dependants", "D"})); REQUIRE(IsClass(src, {"dependants", "BB"})); REQUIRE(IsClass(src, {"dependants", "E"})); + REQUIRE(!IsClass(src, {"dependants", "EE"})); + REQUIRE(!IsClass(src, {"dependants", "EEE"})); + REQUIRE(IsDependency(src, {"dependants", "B"}, {"dependants", "A"})); REQUIRE(IsDependency(src, {"dependants", "BB"}, {"dependants", "A"})); REQUIRE(IsDependency(src, {"dependants", "C"}, {"dependants", "B"})); @@ -40,6 +43,8 @@ TEST_CASE("t00043") REQUIRE(IsClass(src, {"dependencies", "GG"})); REQUIRE(IsClass(src, {"dependencies", "H"})); REQUIRE(!IsClass(src, {"dependencies", "HH"})); + REQUIRE(!IsClass(src, {"dependencies", "II"})); + REQUIRE(!IsClass(src, {"dependencies", "III"})); REQUIRE( IsDependency(src, {"dependencies", "J"}, {"dependencies", "I"})); diff --git a/tests/t00050/t00050.cc b/tests/t00050/t00050.cc index 488b659a..233efcc7 100644 --- a/tests/t00050/t00050.cc +++ b/tests/t00050/t00050.cc @@ -81,6 +81,11 @@ enum class E { E1, E2, E3 }; template class F { T t[N]; V v; + + /// \brief Set value of v + /// + /// \param v_ New value for v + V set_value(V v_) const { return v = v_; } }; /// This is a short description of class G. diff --git a/tests/test_compilation_database.cc b/tests/test_compilation_database.cc index f0c6b26f..77068a5c 100644 --- a/tests/test_compilation_database.cc +++ b/tests/test_compilation_database.cc @@ -68,12 +68,21 @@ TEST_CASE("Test compilation_database should work") .make_preferred() .string())); + REQUIRE_EQ(db->guess_language_from_filename("file.cpp"), "c++"); + REQUIRE_EQ(db->guess_language_from_filename("file.cc"), "c++"); + + REQUIRE_EQ(db->guess_language_from_filename("file.c"), "c"); + auto ccs = db->getAllCompileCommands(); REQUIRE(contains(ccs.at(0).CommandLine, "-Wno-error")); REQUIRE(contains(ccs.at(0).CommandLine, "-Wno-unknown-warning-option")); REQUIRE( !contains(ccs.at(0).CommandLine, "-Wno-deprecated-declarations")); + + REQUIRE_EQ( + db->count_matching_commands({"./src/class_diagram/model/class.cc"}), + 1); } catch (clanguml::error::compilation_database_error &e) { REQUIRE(false); diff --git a/tests/test_model.cc b/tests/test_model.cc index 074c64e7..1a05b970 100644 --- a/tests/test_model.cc +++ b/tests/test_model.cc @@ -22,6 +22,7 @@ #include "class_diagram/model/class.h" #include "common/model/namespace.h" #include "common/model/package.h" +#include "common/model/path.h" #include "common/model/template_parameter.h" TEST_CASE("Test namespace_") @@ -468,4 +469,19 @@ TEST_CASE("Test common::model::package full_name") CHECK(pkg.full_name(false) == "A.B.C:D"); CHECK(pkg.full_name(true) == ":D"); } +} + +TEST_CASE("Test path_type") +{ + using namespace clanguml::common::model; + + REQUIRE_EQ(to_string(path_type::kModule), "module"); + REQUIRE_EQ(to_string(path_type::kFilesystem), "directory"); + REQUIRE_EQ(to_string(path_type::kNamespace), "namespace"); + + // Check that assiging a namespace path to a filesystem path throws + auto p1 = path{"A::B::C", path_type::kNamespace}; + auto p2 = path{"A/B/C/D", path_type::kFilesystem}; + + REQUIRE_THROWS_AS(p1 = p2, std::runtime_error); } \ No newline at end of file diff --git a/tests/test_progress_indicator.cc b/tests/test_progress_indicator.cc new file mode 100644 index 00000000..f2078dcb --- /dev/null +++ b/tests/test_progress_indicator.cc @@ -0,0 +1,105 @@ +/** + * @file tests/test_progress_indicator.cc + * + * Copyright (c) 2021-2024 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN + +#include "doctest/doctest.h" + +#include "common/generators/progress_indicator.h" +#include "util/util.h" + +TEST_CASE("Test progress indicator") +{ + using namespace clanguml::common::generators; + using namespace std::string_literals; + + std::stringstream sstr; + + progress_indicator pi{sstr}; + + pi.add_progress_bar("One", 100, indicators::Color::green); + + // Check if progress indicator has been displayed on the terminal + pi.increment("One"); + + pi.complete("One"); + + pi.stop(); + + std::vector output_lines; + std::string line; + while (std::getline(sstr, line, '\r')) + output_lines.emplace_back(std::move(line)); + + REQUIRE_EQ(output_lines[0], ""); + + REQUIRE_EQ(output_lines[1], + "One [█----------------------------------] " + "[00:00s] 0/100"); + + REQUIRE_EQ(output_lines[2], + "One [█----------------------------------] " + "[00m:00s] 1/100"); + + REQUIRE_EQ(output_lines[3], + "One [███████████████████████████████████] " + "[00m:00s] 100/100 ✔"); +} + +TEST_CASE("Test progress indicator fail") +{ + using namespace clanguml::common::generators; + using namespace std::string_literals; + + std::stringstream sstr; + + progress_indicator pi{sstr}; + + pi.add_progress_bar("One", 100, indicators::Color::green); + + // Check if progress indicator has been displayed on the terminal + pi.increment("One"); + + pi.increment("Two"); // This shouldn't lock the progress bar or change it + + pi.complete("Two"); // This shouldn't lock the progress bar or change it + + pi.fail("Two"); // This shouldn't lock the progress bar or change it + + pi.fail("One"); + + pi.stop(); + + std::vector output_lines; + std::string line; + while (std::getline(sstr, line, '\r')) + output_lines.emplace_back(std::move(line)); + + REQUIRE_EQ(output_lines[0], ""); + + REQUIRE_EQ(output_lines[1], + "One [█----------------------------------] " + "[00:00s] 0/100"); + + REQUIRE_EQ(output_lines[2], + "One [█----------------------------------] " + "[00m:00s] 1/100"); + + REQUIRE_EQ(output_lines[3], + "One [█----------------------------------] " + "[00m:00s] 1/100 ✗"); +} \ No newline at end of file diff --git a/tests/test_types.cc b/tests/test_types.cc index 26d4c9e9..bb61c883 100644 --- a/tests/test_types.cc +++ b/tests/test_types.cc @@ -40,4 +40,26 @@ TEST_CASE("Test eid_t") REQUIRE(global_id.is_global()); REQUIRE(local_id != global_id); + + REQUIRE(local_id != 101); +} + +TEST_CASE("Test to_string") +{ + using namespace clanguml::common; + + std::string t1{"abcd"}; + REQUIRE_EQ(t1, to_string(t1)); + + string_or_regex t2{"abcdef"}; + REQUIRE_EQ(to_string(t2), "abcdef"); + REQUIRE_EQ(to_string(t2), t2.to_string()); + + string_or_regex t3{std::regex{"ab.*"}, "ab.*"}; + REQUIRE_EQ(to_string(t3), "ab.*"); + REQUIRE_EQ(to_string(t3), t3.to_string()); + + REQUIRE_EQ(to_string(generator_type_t::plantuml), "plantuml"); + REQUIRE_EQ(to_string(generator_type_t::mermaid), "mermaid"); + REQUIRE_EQ(to_string(generator_type_t::json), "json"); } \ No newline at end of file