From 39d3e1f0b05c9a98a03efaea61866533da2aedbb Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Fri, 10 Nov 2023 22:30:32 +0100 Subject: [PATCH] Added test case for context filter with radius option --- src/class_diagram/model/diagram.h | 15 ++ src/common/model/diagram_filter.cc | 139 ++++++++++-------- src/common/model/diagram_filter.h | 47 ++++++- src/config/yaml_decoders.cc | 2 +- tests/t00068/.clang-uml | 45 ++++++ tests/t00068/t00068.cc | 32 +++++ tests/t00068/test_case.h | 195 ++++++++++++++++++++++++++ tests/test_cases.cc | 1 + tests/test_cases.yaml | 3 + util/templates/test_cases/test_case.h | 18 ++- 10 files changed, 429 insertions(+), 68 deletions(-) create mode 100644 tests/t00068/.clang-uml create mode 100644 tests/t00068/t00068.cc create mode 100644 tests/t00068/test_case.h diff --git a/src/class_diagram/model/diagram.h b/src/class_diagram/model/diagram.h index 86bf86d0..546bb717 100644 --- a/src/class_diagram/model/diagram.h +++ b/src/class_diagram/model/diagram.h @@ -175,6 +175,15 @@ public: template opt_ref find(diagram_element::id_t id) const; + /** + * @brief Get reference to vector of elements of specific type + * + * @tparam ElementT Type of elements view + * @return Reference to elements vector + */ + template + const common::reference_vector &elements() const; + /** * @brief Add element to the diagram at a specified nested path. * @@ -383,6 +392,12 @@ opt_ref diagram::find(diagram_element::id_t id) const return {}; } +template +const common::reference_vector &diagram::elements() const +{ + return element_view::view(); +} + // // Template method specialization pre-declarations... // diff --git a/src/common/model/diagram_filter.cc b/src/common/model/diagram_filter.cc index 03563e88..179b3291 100644 --- a/src/common/model/diagram_filter.cc +++ b/src/common/model/diagram_filter.cc @@ -535,7 +535,7 @@ tvl::value_t access_filter::match( } context_filter::context_filter( - filter_t type, std::vector context, unsigned radius) + filter_t type, std::vector context) : filter_visitor{type} , context_{std::move(context)} { @@ -548,10 +548,10 @@ void context_filter::initialize_effective_context( auto &effective_context = effective_contexts_[idx]; - // First add to effective context all elements matching context_ + // First add to effective context all elements matching context_ patterns const auto &context = context_.at(idx); const auto &context_matches = - static_cast(d) + dynamic_cast(d) .find(context.pattern); for (const auto &maybe_match : context_matches) { @@ -572,65 +572,19 @@ void context_filter::initialize_effective_context( current_iteration_context.clear(); // For each class in the model - for (const auto &c : - static_cast(d).classes()) { - // Return a positive match if the element e is in a direct - // relationship with any of the effective context elements ... - for (const relationship &rel : c.get().relationships()) { - for (const auto &ec : effective_context) { - if (d.should_include(rel.type()) && rel.destination() == ec) - current_iteration_context.emplace(c.get().id()); - } - } - // ... or vice-versa - for (const auto &ec : effective_context) { - const auto &maybe_class = - static_cast(d) - .find(ec); + find_elements_in_direct_relationship( + d, effective_context, current_iteration_context); - if (!maybe_class) - continue; + find_elements_inheritance_relationship( + d, effective_context, current_iteration_context); - for (const relationship &rel : - maybe_class.value().relationships()) { + // For each enum in the model + find_elements_in_relationship_with_enum( + d, effective_context, current_iteration_context); - if (d.should_include(rel.type()) && - rel.destination() == c.get().id()) - current_iteration_context.emplace(c.get().id()); - } - } - - // Check if any of the elements parents are already in the - // effective context... - for (const class_diagram::model::class_parent &p : - c.get().parents()) { - for (const auto &ec : effective_context) { - const auto &maybe_parent = - static_cast(d) - .find(ec); - if (!maybe_parent) - continue; - - if (d.should_include(relationship_t::kExtension) && - maybe_parent.value().full_name(false) == p.name()) - current_iteration_context.emplace(c.get().id()); - } - } - - // .. or vice-versa - for (const auto &ec : effective_context) { - const auto &maybe_child = - static_cast(d) - .find(ec); - - for (const class_diagram::model::class_parent &p : - maybe_child.value().parents()) { - if (p.name() == c.get().full_name(false)) { - current_iteration_context.emplace(c.get().id()); - } - } - } - } + // For each concept in the model + find_elements_in_direct_relationship( + d, effective_context, current_iteration_context); for (auto id : current_iteration_context) { if (effective_context.count(id) == 0) { @@ -642,6 +596,71 @@ void context_filter::initialize_effective_context( } } +void context_filter::find_elements_in_relationship_with_enum(const diagram &d, + std::set &effective_context, + std::set ¤t_iteration_context) const +{ + + const auto &cd = dynamic_cast(d); + for (const auto &enm : cd.enums()) { + + for (const auto &ec : effective_context) { + const auto &maybe_class = cd.find(ec); + + if (!maybe_class) + continue; + + for (const relationship &rel : + maybe_class.value().relationships()) { + + if (d.should_include(rel.type()) && + rel.destination() == enm.get().id()) + current_iteration_context.emplace(enm.get().id()); + } + } + } +} + +void context_filter::find_elements_inheritance_relationship(const diagram &d, + std::set &effective_context, + std::set ¤t_iteration_context) const +{ + const auto &cd = dynamic_cast(d); + + for (const auto &c : cd.classes()) { + // Check if any of the elements parents are already in the + // effective context... + for (const class_diagram::model::class_parent &p : c.get().parents()) { + for (const auto &ec : effective_context) { + const auto &maybe_parent = + cd.find(ec); + if (!maybe_parent) + continue; + + if (d.should_include(relationship_t::kExtension) && + maybe_parent.value().full_name(false) == p.name()) + current_iteration_context.emplace(c.get().id()); + } + } + + // .. or vice-versa + for (const auto &ec : effective_context) { + const auto &maybe_child = cd.find(ec); + + // The element might not exist because it might have been + // something other than a class + if (!maybe_child) + continue; + + for (const auto &p : maybe_child.value().parents()) { + if (p.name() == c.get().full_name(false)) { + current_iteration_context.emplace(c.get().id()); + } + } + } + } +} + void context_filter::initialize(const diagram &d) const { if (initialized_) @@ -651,7 +670,7 @@ void context_filter::initialize(const diagram &d) const // Prepare effective_contexts_ for (auto i = 0U; i < context_.size(); i++) { - effective_contexts_.push_back({}); + effective_contexts_.push_back({}); // NOLINT initialize_effective_context(d, i); } } diff --git a/src/common/model/diagram_filter.h b/src/common/model/diagram_filter.h index 589fddf8..ea51d01b 100644 --- a/src/common/model/diagram_filter.h +++ b/src/common/model/diagram_filter.h @@ -447,8 +447,7 @@ private: * to any of the elements specified in context. */ struct context_filter : public filter_visitor { - context_filter(filter_t type, std::vector context, - unsigned radius = 1); + context_filter(filter_t type, std::vector context); ~context_filter() override = default; @@ -459,6 +458,50 @@ private: void initialize_effective_context(const diagram &d, unsigned idx) const; + template + void find_elements_in_direct_relationship(const diagram &d, + std::set &effective_context, + std::set ¤t_iteration_context) const + { + static_assert(std::is_same_v || + std::is_same_v, + "ElementT must be either class_ or concept_"); + + const auto &cd = dynamic_cast(d); + + for (const auto &el : cd.elements()) { + for (const relationship &rel : el.get().relationships()) { + for (const auto &ec : effective_context) { + if (d.should_include(rel.type()) && rel.destination() == ec) + current_iteration_context.emplace(el.get().id()); + } + } + + for (const auto &ec : effective_context) { + const auto &maybe_concept = cd.find(ec); + + if (!maybe_concept) + continue; + + for (const relationship &rel : + maybe_concept.value().relationships()) { + + if (d.should_include(rel.type()) && + rel.destination() == el.get().id()) + current_iteration_context.emplace(el.get().id()); + } + } + } + } + + void find_elements_inheritance_relationship(const diagram &d, + std::set &effective_context, + std::set ¤t_iteration_context) const; + + void find_elements_in_relationship_with_enum(const diagram &d, + std::set &effective_context, + std::set ¤t_iteration_context) const; + std::vector context_; /*! diff --git a/src/config/yaml_decoders.cc b/src/config/yaml_decoders.cc index 50227526..35a78fe7 100644 --- a/src/config/yaml_decoders.cc +++ b/src/config/yaml_decoders.cc @@ -426,7 +426,7 @@ template <> struct convert { using namespace std::string_literals; if (node.IsMap() && has_key(node, "match")) { rhs.radius = node["match"]["radius"].as(); - rhs.pattern = node["match"]["radius"].as(); + rhs.pattern = node["match"]["pattern"].as(); } else { rhs.radius = 1; diff --git a/tests/t00068/.clang-uml b/tests/t00068/.clang-uml new file mode 100644 index 00000000..65bf9354 --- /dev/null +++ b/tests/t00068/.clang-uml @@ -0,0 +1,45 @@ +compilation_database_dir: .. +output_directory: diagrams +diagrams: + t00068_r0_class: + type: class + title: AAA context of radius 0 + glob: + - ../../tests/t00068/t00068.cc + include: + namespaces: + - clanguml::t00068 + context: + - match: + radius: 0 + pattern: clanguml::t00068::AAA + using_namespace: + - clanguml::t00068 + t00068_r1_class: + type: class + title: AAA context of radius 1 + glob: + - ../../tests/t00068/t00068.cc + include: + namespaces: + - clanguml::t00068 + context: + - match: + radius: 1 + pattern: clanguml::t00068::AAA + using_namespace: + - clanguml::t00068 + t00068_r2_class: + type: class + title: AAA context of radius 2 + glob: + - ../../tests/t00068/t00068.cc + include: + namespaces: + - clanguml::t00068 + context: + - match: + radius: 2 + pattern: clanguml::t00068::AAA + using_namespace: + - clanguml::t00068 \ No newline at end of file diff --git a/tests/t00068/t00068.cc b/tests/t00068/t00068.cc new file mode 100644 index 00000000..734a2959 --- /dev/null +++ b/tests/t00068/t00068.cc @@ -0,0 +1,32 @@ +#include +#include + +namespace clanguml { +namespace t00068 { + +struct B { }; + +struct BB { + std::vector b; +}; + +enum class AKind { OneA, TwoA, ThreeA }; + +struct A { }; + +struct AA : public A { }; + +struct AAA : public AA { + BB *bb; + AKind akind; +}; + +struct R { + AAA *aaa; +}; + +struct RR { + std::shared_ptr r; +}; +} +} \ No newline at end of file diff --git a/tests/t00068/test_case.h b/tests/t00068/test_case.h new file mode 100644 index 00000000..543f17c5 --- /dev/null +++ b/tests/t00068/test_case.h @@ -0,0 +1,195 @@ +/** + * tests/t00068/test_case.h + * + * Copyright (c) 2021-2023 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("t00068_r0", "[test-case][class][t00068]") +{ + auto [config, db] = load_config("t00068"); + + auto diagram = config.diagrams["t00068_r0_class"]; + + auto model = generate_class_diagram(*db, diagram); + + REQUIRE(model->name() == "t00068_r0_class"); + + { + auto src = generate_class_puml(diagram, *model); + AliasMatcher _A(src); + + REQUIRE_THAT(src, StartsWith("@startuml")); + REQUIRE_THAT(src, EndsWith("@enduml\n")); + + REQUIRE_THAT(src, !IsClass(_A("A"))); + REQUIRE_THAT(src, !IsClass(_A("AA"))); + REQUIRE_THAT(src, IsClass(_A("AAA"))); + + REQUIRE_THAT(src, !IsClass(_A("B"))); + REQUIRE_THAT(src, !IsClass(_A("BB"))); + + REQUIRE_THAT(src, !IsClass(_A("R"))); + REQUIRE_THAT(src, !IsClass(_A("RR"))); + + REQUIRE_THAT(src, !IsEnum(_A("AKind"))); + + save_puml(config.output_directory(), diagram->name + ".puml", src); + } + + { + auto j = generate_class_json(diagram, *model); + + using namespace json; + + save_json(config.output_directory(), diagram->name + ".json", j); + } + + { + auto src = generate_class_mermaid(diagram, *model); + + mermaid::AliasMatcher _A(src); + using mermaid::IsClass; + using mermaid::IsEnum; + + REQUIRE_THAT(src, !IsClass(_A("A"))); + REQUIRE_THAT(src, !IsClass(_A("AA"))); + REQUIRE_THAT(src, IsClass(_A("AAA"))); + + REQUIRE_THAT(src, !IsClass(_A("B"))); + REQUIRE_THAT(src, !IsClass(_A("BB"))); + + REQUIRE_THAT(src, !IsClass(_A("R"))); + REQUIRE_THAT(src, !IsClass(_A("RR"))); + + REQUIRE_THAT(src, !IsEnum(_A("AKind"))); + + save_mermaid(config.output_directory(), diagram->name + ".mmd", src); + } +} + +TEST_CASE("t00068_r1", "[test-case][class][t00068]") +{ + auto [config, db] = load_config("t00068"); + + auto diagram = config.diagrams["t00068_r1_class"]; + + auto model = generate_class_diagram(*db, diagram); + + REQUIRE(model->name() == "t00068_r1_class"); + + { + auto src = generate_class_puml(diagram, *model); + AliasMatcher _A(src); + + REQUIRE_THAT(src, StartsWith("@startuml")); + REQUIRE_THAT(src, EndsWith("@enduml\n")); + + REQUIRE_THAT(src, !IsClass(_A("A"))); + REQUIRE_THAT(src, IsClass(_A("AA"))); + REQUIRE_THAT(src, IsClass(_A("AAA"))); + + REQUIRE_THAT(src, !IsClass(_A("B"))); + REQUIRE_THAT(src, IsClass(_A("BB"))); + + REQUIRE_THAT(src, IsClass(_A("R"))); + REQUIRE_THAT(src, !IsClass(_A("RR"))); + + REQUIRE_THAT(src, IsEnum(_A("AKind"))); + + save_puml(config.output_directory(), diagram->name + ".puml", src); + } + + { + auto j = generate_class_json(diagram, *model); + + using namespace json; + + save_json(config.output_directory(), diagram->name + ".json", j); + } + + { + auto src = generate_class_mermaid(diagram, *model); + + mermaid::AliasMatcher _A(src); + using mermaid::IsClass; + + REQUIRE_THAT(src, !IsClass(_A("A"))); + REQUIRE_THAT(src, IsClass(_A("AA"))); + REQUIRE_THAT(src, IsClass(_A("AAA"))); + + save_mermaid(config.output_directory(), diagram->name + ".mmd", src); + } +} + +TEST_CASE("t00068_r2", "[test-case][class][t00068]") +{ + auto [config, db] = load_config("t00068"); + + auto diagram = config.diagrams["t00068_r2_class"]; + + auto model = generate_class_diagram(*db, diagram); + + REQUIRE(model->name() == "t00068_r2_class"); + + { + auto src = generate_class_puml(diagram, *model); + AliasMatcher _A(src); + + REQUIRE_THAT(src, StartsWith("@startuml")); + REQUIRE_THAT(src, EndsWith("@enduml\n")); + + REQUIRE_THAT(src, IsClass(_A("A"))); + REQUIRE_THAT(src, IsClass(_A("AA"))); + REQUIRE_THAT(src, IsClass(_A("AAA"))); + + REQUIRE_THAT(src, IsClass(_A("B"))); + REQUIRE_THAT(src, IsClass(_A("BB"))); + + REQUIRE_THAT(src, IsClass(_A("R"))); + REQUIRE_THAT(src, IsClass(_A("RR"))); + + REQUIRE_THAT(src, IsEnum(_A("AKind"))); + + save_puml(config.output_directory(), diagram->name + ".puml", src); + } + + { + auto j = generate_class_json(diagram, *model); + + using namespace json; + + save_json(config.output_directory(), diagram->name + ".json", j); + } + + { + auto src = generate_class_mermaid(diagram, *model); + + mermaid::AliasMatcher _A(src); + using mermaid::IsClass; + using mermaid::IsEnum; + + REQUIRE_THAT(src, IsClass(_A("A"))); + REQUIRE_THAT(src, IsClass(_A("AA"))); + REQUIRE_THAT(src, IsClass(_A("AAA"))); + + REQUIRE_THAT(src, IsClass(_A("B"))); + REQUIRE_THAT(src, IsClass(_A("BB"))); + + REQUIRE_THAT(src, IsClass(_A("R"))); + REQUIRE_THAT(src, IsClass(_A("RR"))); + + save_mermaid(config.output_directory(), diagram->name + ".mmd", src); + } +} \ No newline at end of file diff --git a/tests/test_cases.cc b/tests/test_cases.cc index a50b20df..d156699a 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -384,6 +384,7 @@ using namespace clanguml::test::matchers; #endif #include "t00066/test_case.h" #include "t00067/test_case.h" +#include "t00068/test_case.h" /// /// Sequence diagram tests diff --git a/tests/test_cases.yaml b/tests/test_cases.yaml index da533aa1..7b7c584e 100644 --- a/tests/test_cases.yaml +++ b/tests/test_cases.yaml @@ -198,6 +198,9 @@ test_cases: - name: t00067 title: Class method type filter test case description: + - name: t00068 + title: Context filter radius parameter test case + description: Sequence diagrams: - name: t20001 title: Basic sequence diagram test case diff --git a/util/templates/test_cases/test_case.h b/util/templates/test_cases/test_case.h index 5aba84d9..17d2e3d2 100644 --- a/util/templates/test_cases/test_case.h +++ b/util/templates/test_cases/test_case.h @@ -29,16 +29,16 @@ TEST_CASE("{{ name }}", "[test-case][{{ type }}]") REQUIRE(model->name() == "{{ name }}_{{ type }}"); { - auto puml = generate_{{ type }}_puml(diagram, *model); - AliasMatcher _A(puml); + auto src = generate_{{ type }}_puml(diagram, *model); + AliasMatcher _A(src); - REQUIRE_THAT(puml, StartsWith("@startuml")); - REQUIRE_THAT(puml, EndsWith("@enduml\n")); + REQUIRE_THAT(src, StartsWith("@startuml")); + REQUIRE_THAT(src, EndsWith("@enduml\n")); {{ examples }} save_puml( - config.output_directory(), diagram->name + ".puml", puml); + config.output_directory(), diagram->name + ".puml", src); } { @@ -49,4 +49,12 @@ TEST_CASE("{{ name }}", "[test-case][{{ type }}]") save_json(config.output_directory(), diagram->name + ".json", j); } + { + auto src = generate_class_mermaid(diagram, *model); + + mermaid::AliasMatcher _A(src); + using mermaid::IsClass; + + save_mermaid(config.output_directory(), diagram->name + ".mmd", src); + } }