From cb7a4b9038939c6461fcdcbaaee61e65219eb7f0 Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Sun, 23 Jan 2022 22:20:23 +0100 Subject: [PATCH] Added test case for package dependency generation --- .../plantuml/class_diagram_generator.cc | 2 +- src/common/model/element.cc | 3 + .../plantuml/package_diagram_generator.cc | 40 +++- .../plantuml/package_diagram_generator.h | 2 + src/package_diagram/model/package.h | 1 + .../visitor/translation_unit_context.cc | 12 ++ .../visitor/translation_unit_context.h | 6 + .../visitor/translation_unit_visitor.cc | 185 ++++-------------- .../visitor/translation_unit_visitor.h | 4 + tests/t30001/test_case.h | 28 --- tests/t30002/.clang-uml | 18 ++ tests/t30002/t30002.cc | 15 ++ tests/t30002/test_case.h | 61 ++++++ tests/test_cases.cc | 1 + 14 files changed, 203 insertions(+), 175 deletions(-) create mode 100644 tests/t30002/.clang-uml create mode 100644 tests/t30002/t30002.cc create mode 100644 tests/t30002/test_case.h diff --git a/src/class_diagram/generators/plantuml/class_diagram_generator.cc b/src/class_diagram/generators/plantuml/class_diagram_generator.cc index 94b59f25..100aba3e 100644 --- a/src/class_diagram/generators/plantuml/class_diagram_generator.cc +++ b/src/class_diagram/generators/plantuml/class_diagram_generator.cc @@ -119,7 +119,7 @@ void generator::generate_alias(const enum_ &e, std::ostream &ostr) const void generator::generate(const class_ &c, std::ostream &ostr) const { - const auto uns = m_config.using_namespace; + const auto& uns = m_config.using_namespace; std::string class_type{"class"}; if (c.is_abstract()) diff --git a/src/common/model/element.cc b/src/common/model/element.cc index 584e0db7..5a83a2d2 100644 --- a/src/common/model/element.cc +++ b/src/common/model/element.cc @@ -41,6 +41,9 @@ void element::add_relationship(relationship &&cr) return; } + LOG_DBG("Adding relationship: '{}' - {} - '{}'", cr.destination(), + to_string(cr.type()), full_name(true)); + auto it = std::find(relationships_.begin(), relationships_.end(), cr); if (it == relationships_.end()) relationships_.emplace_back(std::move(cr)); diff --git a/src/package_diagram/generators/plantuml/package_diagram_generator.cc b/src/package_diagram/generators/plantuml/package_diagram_generator.cc index 8f55f2f6..c5adbcb9 100644 --- a/src/package_diagram/generators/plantuml/package_diagram_generator.cc +++ b/src/package_diagram/generators/plantuml/package_diagram_generator.cc @@ -79,6 +79,37 @@ std::string generator::name(relationship_t r) const } } +void generator::generate_relationships( + const package &p, std::ostream &ostr) const +{ + const auto &uns = m_config.using_namespace; + + // Generate this packages relationship + if (m_config.should_include_relationship("dependency")) { + LOG_DBG("LOOKING FOR RELATIONSHIPS IN PACKAGE: {}", p.full_name(false)); + for (const auto &r : p.relationships()) { + std::stringstream relstr; + try { + relstr << m_model.to_alias(ns_relative(uns, r.destination())) + << " <.. " + << m_model.to_alias(ns_relative(uns, p.full_name(false))) + << '\n'; + ostr << relstr.str(); + } + catch (error::uml_alias_missing &e) { + LOG_ERROR("=== Skipping dependency relation from {} to {} due " + "to: {}", + p.full_name(false), r.destination(), e.what()); + } + } + } + + // Process it's subpackages relationships + for (auto subpackage = p.cbegin(); subpackage != p.cend(); subpackage++) { + generate_relationships(**subpackage, ostr); + } +} + void generator::generate(const package &p, std::ostream &ostr) const { const auto uns = m_config.using_namespace; @@ -109,9 +140,6 @@ void generator::generate(const package &p, std::ostream &ostr) const << "end note\n"; } } - // - // // Print relationships - // ostr << all_relations_str.str(); } void generator::generate(std::ostream &ostr) const @@ -138,6 +166,12 @@ void generator::generate(std::ostream &ostr) const } } + // Process package relationships + for (const auto &p : m_model) { + generate_relationships(*p, ostr); + ostr << '\n'; + } + // Process aliases in any of the puml directives for (const auto &b : m_config.puml.after) { std::string note{b}; diff --git a/src/package_diagram/generators/plantuml/package_diagram_generator.h b/src/package_diagram/generators/plantuml/package_diagram_generator.h index bf1ab96f..ed5b8fcf 100644 --- a/src/package_diagram/generators/plantuml/package_diagram_generator.h +++ b/src/package_diagram/generators/plantuml/package_diagram_generator.h @@ -58,6 +58,8 @@ public: void generate_alias(const package &c, std::ostream &ostr) const; + void generate_relationships(const package &p, std::ostream &ostr) const; + void generate(const package &e, std::ostream &ostr) const; void generate(std::ostream &ostr) const; diff --git a/src/package_diagram/model/package.h b/src/package_diagram/model/package.h index 409b90c0..51acf379 100644 --- a/src/package_diagram/model/package.h +++ b/src/package_diagram/model/package.h @@ -24,6 +24,7 @@ #include #include +#include #include #include diff --git a/src/package_diagram/visitor/translation_unit_context.cc b/src/package_diagram/visitor/translation_unit_context.cc index 37eb53c2..519e8a58 100644 --- a/src/package_diagram/visitor/translation_unit_context.cc +++ b/src/package_diagram/visitor/translation_unit_context.cc @@ -133,4 +133,16 @@ clanguml::package_diagram::model::diagram &translation_unit_context::diagram() return diagram_; } +void translation_unit_context::set_current_package( + type_safe::optional_ref p) +{ + current_package_ = p; +} + +type_safe::optional_ref +translation_unit_context::get_current_package() const +{ + return current_package_; +} + } diff --git a/src/package_diagram/visitor/translation_unit_context.h b/src/package_diagram/visitor/translation_unit_context.h index 2478f2b2..be1a80d4 100644 --- a/src/package_diagram/visitor/translation_unit_context.h +++ b/src/package_diagram/visitor/translation_unit_context.h @@ -63,6 +63,10 @@ public: clanguml::package_diagram::model::diagram &diagram(); + void set_current_package(type_safe::optional_ref p); + + type_safe::optional_ref get_current_package() const; + private: // Current visitor namespace std::vector namespace_; @@ -83,6 +87,8 @@ private: // Map of discovered template aliases (declared with 'using' keyword) std::map> alias_template_index_; + + type_safe::optional_ref current_package_; }; } diff --git a/src/package_diagram/visitor/translation_unit_visitor.cc b/src/package_diagram/visitor/translation_unit_visitor.cc index 6a8c428b..18d43cd5 100644 --- a/src/package_diagram/visitor/translation_unit_visitor.cc +++ b/src/package_diagram/visitor/translation_unit_visitor.cc @@ -90,7 +90,9 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file) if (!ns_declaration.is_anonymous() && !ns_declaration.is_inline()) { - auto package_path = ctx.get_namespace(); + std::vector package_parent = + ctx.get_namespace(); + auto package_path = package_parent; package_path.push_back(e.name()); auto usn = @@ -100,12 +102,15 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file) auto p = std::make_unique( ctx.config().using_namespace); remove_prefix(package_path, usn); - package_path.pop_back(); + remove_prefix(package_parent, usn); p->set_name(e.name()); - p->set_namespace(package_path); + p->set_namespace(package_parent); + ctx.diagram().add_package( - package_path, std::move(p)); + package_parent, std::move(p)); + ctx.set_current_package( + ctx.diagram().get_package(package_path)); } ctx.push_namespace(e.name()); @@ -155,20 +160,6 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file) cx::util::fully_prefixed(ctx.get_namespace(), cls))) process_class_declaration(cls); } - // else if (e.kind() == cppast::cpp_entity_kind::enum_t) - // { - // LOG_DBG("========== Visiting '{}' - {}", - // cx::util::full_name(ctx.get_namespace(), e), - // cppast::to_string(e.kind())); - // - // auto &enm = static_cast(e); - // - // if (ctx.config().should_include( - // cx::util::fully_prefixed(ctx.get_namespace(), - // enm))) - // process_enum_declaration(enm); - // } else if (e.kind() == cppast::cpp_entity_kind::type_alias_t) { LOG_DBG("========== Visiting '{}' - {}", cx::util::full_name(ctx.get_namespace(), e), @@ -191,27 +182,6 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file) cppast::to_string(e.kind())); auto &at = static_cast(e); - - // if (at.type_alias().underlying_type().kind() - // == - // cppast::cpp_type_kind::unexposed_t) { - // LOG_WARN("Template alias has unexposed - // underlying type: {}", - // static_cast( - // at.type_alias().underlying_type()) - // .name()); - // } - // else { - // class_ tinst = - // build_template_instantiation(static_cast< - // const - // cppast::cpp_template_instantiation_type - // &>( - // at.type_alias().underlying_type())); - // - // ctx.diagram().add_class(std::move(tinst)); - // } } }); } @@ -220,39 +190,14 @@ void translation_unit_visitor::process_class_declaration( const cppast::cpp_class &cls, type_safe::optional_ref tspec) { + auto current_package = ctx.get_current_package(); - return; - /* - class_ c{ctx.config().using_namespace}; - c.is_struct(cls.class_kind() == cppast::cpp_class_kind::struct_t); - c.set_name(cx::util::full_name(ctx.get_namespace(), cls)); - - if (cls.comment().has_value()) - c.add_decorators(decorators::parse(cls.comment().value())); + if (!current_package) + return; cppast::cpp_access_specifier_kind last_access_specifier = cppast::cpp_access_specifier_kind::cpp_private; - // Process class documentation comment - if (cppast::is_templated(cls)) { - if (cls.parent().value().comment().has_value()) - c.add_decorators( - decorators::parse(cls.parent().value().comment().value())); - } - else { - if (cls.comment().has_value()) - c.add_decorators(decorators::parse(cls.comment().value())); - } - - if (c.skip()) - return; - - c.set_style(c.style_spec()); - - // Process class child entities - if (c.is_struct()) - last_access_specifier = cppast::cpp_access_specifier_kind::cpp_public; - for (auto &child : cls) { if (child.kind() == cppast::cpp_entity_kind::access_specifier_t) { auto &as = static_cast(child); @@ -260,92 +205,46 @@ void translation_unit_visitor::process_class_declaration( } else if (child.kind() == cppast::cpp_entity_kind::member_variable_t) { auto &mv = static_cast(child); - process_field(mv, c, last_access_specifier); - } - else if (child.kind() == cppast::cpp_entity_kind::variable_t) { - auto &mv = static_cast(child); - process_static_field(mv, c, last_access_specifier); - } - else if (child.kind() == cppast::cpp_entity_kind::member_function_t) { - auto &mf = static_cast(child); - process_method(mf, c, last_access_specifier); - } - else if (child.kind() == cppast::cpp_entity_kind::function_t) { - auto &mf = static_cast(child); - process_static_method(mf, c, last_access_specifier); - } - else if (child.kind() == cppast::cpp_entity_kind::function_template_t) { - auto &tm = - static_cast(child); - process_template_method(tm, c, last_access_specifier); - } - else if (child.kind() == cppast::cpp_entity_kind::constructor_t) { - auto &mc = static_cast(child); - process_constructor(mc, c, last_access_specifier); - } - else if (child.kind() == cppast::cpp_entity_kind::destructor_t) { - auto &mc = static_cast(child); - process_destructor(mc, c, last_access_specifier); - } - else if (child.kind() == cppast::cpp_entity_kind::enum_t) { - auto &en = static_cast(child); - if (en.name().empty()) { - // Here we only want to handle anonymous enums, regular nested - // enums are handled in the file-level visitor - process_anonymous_enum(en, c, last_access_specifier); - } - } - else if (child.kind() == cppast::cpp_entity_kind::friend_t) { - auto &fr = static_cast(child); - - LOG_DBG("Found friend declaration: {}, {}", child.name(), - child.scope_name() ? child.scope_name().value().name() - : ""); - - process_friend(fr, c); - } - else if (cppast::is_friended(child)) { - auto &fr = - static_cast(child.parent().value()); - - LOG_DBG("Found friend template: {}", child.name()); - - process_friend(fr, c); + process_field(mv, current_package, last_access_specifier); } else { LOG_DBG("Found some other class child: {} ({})", child.name(), cppast::to_string(child.kind())); } } +} - // Process class bases - for (auto &base : cls.bases()) { - class_parent cp; - cp.set_name( - clanguml::cx::util::fully_prefixed(ctx.get_namespace(), base)); - cp.is_virtual(base.is_virtual()); +void translation_unit_visitor::process_field( + const cppast::cpp_member_variable &mv, + type_safe::optional_ref p, + cppast::cpp_access_specifier_kind as) +{ + auto &type = cx::util::unreferenced(cppast::remove_cv(mv.type())); + auto type_ns = + util::split(cx::util::full_name(type, ctx.entity_index(), false), "::"); + type_ns.pop_back(); - switch (base.access_specifier()) { - case cppast::cpp_access_specifier_kind::cpp_private: - cp.set_access(access_t::kPrivate); - break; - case cppast::cpp_access_specifier_kind::cpp_public: - cp.set_access(access_t::kPublic); - break; - case cppast::cpp_access_specifier_kind::cpp_protected: - cp.set_access(access_t::kProtected); - break; - default: - cp.set_access(access_t::kPublic); + if (type.kind() == cppast::cpp_type_kind::user_defined_t) { + LOG_DBG("Processing user defined type field {} {}", + cppast::to_string(type), mv.name()); + + if (!starts_with(ctx.get_namespace(), type_ns) && + !starts_with(type_ns, ctx.get_namespace())) { + relationship r{relationship_t::kDependency, + fmt::format("{}", fmt::join(type_ns, "::"))}; + p.value().add_relationship(std::move(r)); } - - LOG_DBG("Found base class {} for class {}", cp.name(), c.name()); - - c.add_parent(std::move(cp)); } - - ctx.diagram().add_class(std::move(c)); - */ + else if (type.kind() == cppast::cpp_type_kind::template_instantiation_t) { + // template_instantiation_added_as_aggregation = + // process_field_with_template_instantiation( + // mv, resolve_alias(type), c, m, as); + LOG_DBG("Processing template instantiation type {} {}", + cppast::to_string(type), mv.name()); + } + else { + LOG_DBG("Skipping field type: {}", cppast::to_string(type)); + } } const cppast::cpp_type &translation_unit_visitor::resolve_alias( diff --git a/src/package_diagram/visitor/translation_unit_visitor.h b/src/package_diagram/visitor/translation_unit_visitor.h index 3457a2be..eda2c823 100644 --- a/src/package_diagram/visitor/translation_unit_visitor.h +++ b/src/package_diagram/visitor/translation_unit_visitor.h @@ -37,6 +37,7 @@ #include #include #include +#include #include namespace clanguml::package_diagram::visitor { @@ -53,6 +54,9 @@ public: type_safe::optional_ref tspec = nullptr); + void process_field(const cppast::cpp_member_variable &mv, + type_safe::optional_ref p, + cppast::cpp_access_specifier_kind as); private: /** * Try to resolve a type instance into a type referenced through an alias. diff --git a/tests/t30001/test_case.h b/tests/t30001/test_case.h index bdd7dfca..7c5af2af 100644 --- a/tests/t30001/test_case.h +++ b/tests/t30001/test_case.h @@ -50,34 +50,6 @@ TEST_CASE("t30001", "[test-case][package]") REQUIRE_THAT(puml, Contains("component [AA]")); REQUIRE_THAT(puml, Contains("component [AAA]")); - REQUIRE_THAT(puml, Equals(R"(@startuml -' t30001 test package diagram -component [A] as C_0000000561 { -component [AA] as C_0000000562 { -component [AAA] as C_0000000563 { -} -component [BBB] as C_0000000564 { -} -} -component [BB] as C_0000000565 { -} -} - -component [B] as C_0000000566 { -component [AA] as C_0000000567 { -component [AAA] as C_0000000568 { -} -component [BBB] as C_0000000569 { -} -} -component [BB] as C_0000000570 { -} -} - -note right of C_0000000563: A AAA note... -@enduml -)")); - save_puml( "./" + config.output_directory + "/" + diagram->name + ".puml", puml); } diff --git a/tests/t30002/.clang-uml b/tests/t30002/.clang-uml new file mode 100644 index 00000000..dbbd0717 --- /dev/null +++ b/tests/t30002/.clang-uml @@ -0,0 +1,18 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t30002_package: + type: package + glob: + - ../../tests/t30002/t30002.cc + include: + namespaces: + - clanguml::t30002 + exclude: + namespaces: + - clanguml::t30002::detail + using_namespace: + - clanguml::t30002 + plantuml: + before: + - "' t30002 test package diagram" \ No newline at end of file diff --git a/tests/t30002/t30002.cc b/tests/t30002/t30002.cc new file mode 100644 index 00000000..8069ff0e --- /dev/null +++ b/tests/t30002/t30002.cc @@ -0,0 +1,15 @@ +#include + +namespace clanguml { +namespace t30002 { +namespace A::AA::AAA { +struct CA { +}; +} +namespace B::BB::BBB { +struct CBA { + A::AA::AAA::CA *ca_; +}; +} +} // namespace t30002 +} // namespace clanguml diff --git a/tests/t30002/test_case.h b/tests/t30002/test_case.h new file mode 100644 index 00000000..4d90efa0 --- /dev/null +++ b/tests/t30002/test_case.h @@ -0,0 +1,61 @@ +/** + * tests/t30002/test_case.cc + * + * Copyright (c) 2021-2022 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. + */ + +TEST_CASE("t30002", "[test-case][package]") +{ + auto [config, db] = load_config("t30002"); + + auto diagram = config.diagrams["t30002_package"]; + + REQUIRE(diagram->include.namespaces.size() == 1); + REQUIRE_THAT(diagram->include.namespaces, + VectorContains(std::string{"clanguml::t30002"})); + + REQUIRE(diagram->exclude.namespaces.size() == 1); + REQUIRE_THAT(diagram->exclude.namespaces, + VectorContains(std::string{"clanguml::t30002::detail"})); + + REQUIRE(diagram->should_include("clanguml::t30002::A")); + REQUIRE(!diagram->should_include("clanguml::t30002::detail::C")); + REQUIRE(!diagram->should_include("std::vector")); + + REQUIRE(diagram->name == "t30002_package"); + + auto model = generate_package_diagram(db, diagram); + + REQUIRE(model.name() == "t30002_package"); + + auto puml = generate_package_puml(diagram, model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + REQUIRE_THAT(puml, Contains("component [A]")); + REQUIRE_THAT(puml, Contains("component [AA]")); + REQUIRE_THAT(puml, Contains("component [AAA]")); + + REQUIRE_THAT(puml, Contains("component [B]")); + REQUIRE_THAT(puml, Contains("component [BB]")); + REQUIRE_THAT(puml, Contains("component [BBB]")); + + //REQUIRE_THAT(puml, IsDependency(_A("BBB"), _A("AAA"))); + + save_puml( + "./" + config.output_directory + "/" + diagram->name + ".puml", puml); +} diff --git a/tests/test_cases.cc b/tests/test_cases.cc index a2cc142b..f5b811d7 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -177,6 +177,7 @@ using namespace clanguml::test::matchers; // Package diagram tests // #include "t30001/test_case.h" +#include "t30002/test_case.h" // // Other tests (e.g. configuration file)