Added test case for context filter with radius option

This commit is contained in:
Bartek Kryza
2023-11-10 22:30:32 +01:00
parent 055897f11b
commit 39d3e1f0b0
10 changed files with 429 additions and 68 deletions

View File

@@ -175,6 +175,15 @@ public:
template <typename ElementT>
opt_ref<ElementT> 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 <typename ElementT>
const common::reference_vector<ElementT> &elements() const;
/**
* @brief Add element to the diagram at a specified nested path.
*
@@ -383,6 +392,12 @@ opt_ref<ElementT> diagram::find(diagram_element::id_t id) const
return {};
}
template <typename ElementT>
const common::reference_vector<ElementT> &diagram::elements() const
{
return element_view<ElementT>::view();
}
//
// Template method specialization pre-declarations...
//

View File

@@ -535,7 +535,7 @@ tvl::value_t access_filter::match(
}
context_filter::context_filter(
filter_t type, std::vector<config::context_config> context, unsigned radius)
filter_t type, std::vector<config::context_config> 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<const class_diagram::model::diagram &>(d)
dynamic_cast<const class_diagram::model::diagram &>(d)
.find<class_diagram::model::class_>(context.pattern);
for (const auto &maybe_match : context_matches) {
@@ -572,21 +572,40 @@ void context_filter::initialize_effective_context(
current_iteration_context.clear();
// For each class in the model
for (const auto &c :
static_cast<const class_diagram::model::diagram &>(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());
find_elements_in_direct_relationship<class_diagram::model::class_>(
d, effective_context, current_iteration_context);
find_elements_inheritance_relationship(
d, effective_context, current_iteration_context);
// For each enum in the model
find_elements_in_relationship_with_enum(
d, effective_context, current_iteration_context);
// For each concept in the model
find_elements_in_direct_relationship<class_diagram::model::concept_>(
d, effective_context, current_iteration_context);
for (auto id : current_iteration_context) {
if (effective_context.count(id) == 0) {
// Found new element to add to context
effective_context.emplace(id);
effective_context_extended = true;
}
}
// ... or vice-versa
}
}
void context_filter::find_elements_in_relationship_with_enum(const diagram &d,
std::set<id_t> &effective_context,
std::set<clanguml::common::id_t> &current_iteration_context) const
{
const auto &cd = dynamic_cast<const class_diagram::model::diagram &>(d);
for (const auto &enm : cd.enums()) {
for (const auto &ec : effective_context) {
const auto &maybe_class =
static_cast<const class_diagram::model::diagram &>(d)
.find<class_diagram::model::class_>(ec);
const auto &maybe_class = cd.find<class_diagram::model::class_>(ec);
if (!maybe_class)
continue;
@@ -595,19 +614,26 @@ void context_filter::initialize_effective_context(
maybe_class.value().relationships()) {
if (d.should_include(rel.type()) &&
rel.destination() == c.get().id())
current_iteration_context.emplace(c.get().id());
rel.destination() == enm.get().id())
current_iteration_context.emplace(enm.get().id());
}
}
}
}
void context_filter::find_elements_inheritance_relationship(const diagram &d,
std::set<id_t> &effective_context,
std::set<clanguml::common::id_t> &current_iteration_context) const
{
const auto &cd = dynamic_cast<const class_diagram::model::diagram &>(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 class_diagram::model::class_parent &p : c.get().parents()) {
for (const auto &ec : effective_context) {
const auto &maybe_parent =
static_cast<const class_diagram::model::diagram &>(d)
.find<class_diagram::model::class_>(ec);
cd.find<class_diagram::model::class_>(ec);
if (!maybe_parent)
continue;
@@ -619,27 +645,20 @@ void context_filter::initialize_effective_context(
// .. or vice-versa
for (const auto &ec : effective_context) {
const auto &maybe_child =
static_cast<const class_diagram::model::diagram &>(d)
.find<class_diagram::model::class_>(ec);
const auto &maybe_child = cd.find<class_diagram::model::class_>(ec);
for (const class_diagram::model::class_parent &p :
maybe_child.value().parents()) {
// 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());
}
}
}
}
for (auto id : current_iteration_context) {
if (effective_context.count(id) == 0) {
// Found new element to add to context
effective_context.emplace(id);
effective_context_extended = true;
}
}
}
}
void context_filter::initialize(const diagram &d) const
@@ -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);
}
}

View File

@@ -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<config::context_config> context,
unsigned radius = 1);
context_filter(filter_t type, std::vector<config::context_config> context);
~context_filter() override = default;
@@ -459,6 +458,50 @@ private:
void initialize_effective_context(const diagram &d, unsigned idx) const;
template <typename ElementT>
void find_elements_in_direct_relationship(const diagram &d,
std::set<id_t> &effective_context,
std::set<clanguml::common::id_t> &current_iteration_context) const
{
static_assert(std::is_same_v<ElementT, class_diagram::model::class_> ||
std::is_same_v<ElementT, class_diagram::model::concept_>,
"ElementT must be either class_ or concept_");
const auto &cd = dynamic_cast<const class_diagram::model::diagram &>(d);
for (const auto &el : cd.elements<ElementT>()) {
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<ElementT>(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<id_t> &effective_context,
std::set<clanguml::common::id_t> &current_iteration_context) const;
void find_elements_in_relationship_with_enum(const diagram &d,
std::set<id_t> &effective_context,
std::set<clanguml::common::id_t> &current_iteration_context) const;
std::vector<config::context_config> context_;
/*!

View File

@@ -426,7 +426,7 @@ template <> struct convert<context_config> {
using namespace std::string_literals;
if (node.IsMap() && has_key(node, "match")) {
rhs.radius = node["match"]["radius"].as<unsigned>();
rhs.pattern = node["match"]["radius"].as<string_or_regex>();
rhs.pattern = node["match"]["pattern"].as<string_or_regex>();
}
else {
rhs.radius = 1;

45
tests/t00068/.clang-uml Normal file
View File

@@ -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

32
tests/t00068/t00068.cc Normal file
View File

@@ -0,0 +1,32 @@
#include <memory>
#include <vector>
namespace clanguml {
namespace t00068 {
struct B { };
struct BB {
std::vector<B> 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> r;
};
}
}

195
tests/t00068/test_case.h Normal file
View File

@@ -0,0 +1,195 @@
/**
* tests/t00068/test_case.h
*
* Copyright (c) 2021-2023 Bartek Kryza <bkryza@gmail.com>
*
* 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);
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}
}