Added test case for package dependency generation

This commit is contained in:
Bartek Kryza
2022-01-23 22:20:23 +01:00
parent c0ef93cb63
commit cb7a4b9038
14 changed files with 203 additions and 175 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -24,6 +24,7 @@
#include <spdlog/spdlog.h>
#include <type_safe/optional_ref.hpp>
#include <set>
#include <string>
#include <vector>

View File

@@ -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<model::package> p)
{
current_package_ = p;
}
type_safe::optional_ref<model::package>
translation_unit_context::get_current_package() const
{
return current_package_;
}
}

View File

@@ -63,6 +63,10 @@ public:
clanguml::package_diagram::model::diagram &diagram();
void set_current_package(type_safe::optional_ref<model::package> p);
type_safe::optional_ref<model::package> get_current_package() const;
private:
// Current visitor namespace
std::vector<std::string> namespace_;
@@ -83,6 +87,8 @@ private:
// Map of discovered template aliases (declared with 'using' keyword)
std::map<std::string, type_safe::object_ref<const cppast::cpp_type>>
alias_template_index_;
type_safe::optional_ref<model::package> current_package_;
};
}

View File

@@ -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<std::string> 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<package>(
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<const cppast::cpp_enum
// &>(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<const cppast::cpp_alias_template &>(e);
// if (at.type_alias().underlying_type().kind()
// ==
// cppast::cpp_type_kind::unexposed_t) {
// LOG_WARN("Template alias has unexposed
// underlying type: {}",
// static_cast<const
// cppast::cpp_unexposed_type &>(
// 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<const cppast::cpp_template_specialization> 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<const cppast::cpp_access_specifier &>(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<const cppast::cpp_member_variable &>(child);
process_field(mv, c, last_access_specifier);
}
else if (child.kind() == cppast::cpp_entity_kind::variable_t) {
auto &mv = static_cast<const cppast::cpp_variable &>(child);
process_static_field(mv, c, last_access_specifier);
}
else if (child.kind() == cppast::cpp_entity_kind::member_function_t) {
auto &mf = static_cast<const cppast::cpp_member_function &>(child);
process_method(mf, c, last_access_specifier);
}
else if (child.kind() == cppast::cpp_entity_kind::function_t) {
auto &mf = static_cast<const cppast::cpp_function &>(child);
process_static_method(mf, c, last_access_specifier);
}
else if (child.kind() == cppast::cpp_entity_kind::function_template_t) {
auto &tm =
static_cast<const cppast::cpp_function_template &>(child);
process_template_method(tm, c, last_access_specifier);
}
else if (child.kind() == cppast::cpp_entity_kind::constructor_t) {
auto &mc = static_cast<const cppast::cpp_constructor &>(child);
process_constructor(mc, c, last_access_specifier);
}
else if (child.kind() == cppast::cpp_entity_kind::destructor_t) {
auto &mc = static_cast<const cppast::cpp_destructor &>(child);
process_destructor(mc, c, last_access_specifier);
}
else if (child.kind() == cppast::cpp_entity_kind::enum_t) {
auto &en = static_cast<const cppast::cpp_enum &>(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<const cppast::cpp_friend &>(child);
LOG_DBG("Found friend declaration: {}, {}", child.name(),
child.scope_name() ? child.scope_name().value().name()
: "<no-scope>");
process_friend(fr, c);
}
else if (cppast::is_friended(child)) {
auto &fr =
static_cast<const cppast::cpp_friend &>(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<model::package> 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(

View File

@@ -37,6 +37,7 @@
#include <functional>
#include <map>
#include <memory>
#include <package_diagram/model/package.h>
#include <string>
namespace clanguml::package_diagram::visitor {
@@ -53,6 +54,9 @@ public:
type_safe::optional_ref<const cppast::cpp_template_specialization>
tspec = nullptr);
void process_field(const cppast::cpp_member_variable &mv,
type_safe::optional_ref<model::package> p,
cppast::cpp_access_specifier_kind as);
private:
/**
* Try to resolve a type instance into a type referenced through an alias.

View File

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

18
tests/t30002/.clang-uml Normal file
View File

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

15
tests/t30002/t30002.cc Normal file
View File

@@ -0,0 +1,15 @@
#include <memory>
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

61
tests/t30002/test_case.h Normal file
View File

@@ -0,0 +1,61 @@
/**
* tests/t30002/test_case.cc
*
* Copyright (c) 2021-2022 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("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);
}

View File

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