Added test case for recursive variadic template specialization

This commit is contained in:
Bartek Kryza
2022-08-07 23:08:37 +02:00
parent 1844b992aa
commit ae7ef11e43
20 changed files with 416 additions and 130 deletions

View File

@@ -10,7 +10,7 @@ project(clang-uml)
#
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_VERBOSE_MAKEFILE ON)
set(CMAKE_VERBOSE_MAKEFILE OFF)
#
# clang-uml custom defines

View File

@@ -31,15 +31,37 @@ template_parameter::template_parameter(const std::string &type,
set_type(type);
}
void template_parameter::set_type(const std::string &type) { type_ = type; }
void template_parameter::set_type(const std::string &type)
{
if (util::ends_with(type, std::string{"..."})) {
type_ = type.substr(0, type.size() - 3);
is_variadic_ = true;
}
else
type_ = type;
}
std::string template_parameter::type() const { return type_; }
std::string template_parameter::type() const
{
if (is_variadic_ && !type_.empty())
return type_ + "...";
void template_parameter::set_name(const std::string &name) { name_ = name; }
return type_;
}
void template_parameter::set_name(const std::string &name)
{
if (util::ends_with(name, std::string{"..."})) {
name_ = name.substr(0, name.size() - 3);
is_variadic_ = true;
}
else
name_ = name;
}
std::string template_parameter::name() const
{
if (is_variadic_)
if (is_variadic_ && type_.empty())
return name_ + "...";
return name_;

View File

@@ -54,6 +54,9 @@ public:
void is_variadic(bool is_variadic) noexcept;
bool is_variadic() const noexcept;
void is_pack(bool is_pack) noexcept;
bool is_pack() const noexcept;
bool is_specialization_of(const template_parameter &ct) const;
friend bool operator==(
@@ -118,6 +121,9 @@ private:
/// Whether the template parameter is variadic
bool is_variadic_{false};
/// Whether the argument specializes argument pack from parent template
bool is_pack_{false};
// Nested template parameters
std::vector<template_parameter> template_params_;

View File

@@ -321,8 +321,8 @@ bool translation_unit_visitor::VisitClassTemplateDecl(
return true;
// Skip forward declarations
if (!cls->getTemplatedDecl()->isCompleteDefinition())
return true;
// if (!cls->getTemplatedDecl()->isCompleteDefinition())
// return true;
auto c_ptr = create_class_declaration(cls->getTemplatedDecl());
@@ -337,14 +337,14 @@ bool translation_unit_visitor::VisitClassTemplateDecl(
process_template_parameters(*cls, *c_ptr);
process_class_declaration(*cls->getTemplatedDecl(), *c_ptr);
if (!cls->getTemplatedDecl()->isCompleteDefinition()) {
forward_declarations_.emplace(id, std::move(c_ptr));
return true;
}
else
else {
process_class_declaration(*cls->getTemplatedDecl(), *c_ptr);
forward_declarations_.erase(id);
}
if (diagram_.should_include(*c_ptr)) {
LOG_DBG("Adding class template {} with id {}", c_ptr->full_name(),
@@ -362,6 +362,10 @@ bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls)
if (source_manager_.isInSystemHeader(cls->getSourceRange().getBegin()))
return true;
// Skip forward declarations
if (!cls->isCompleteDefinition())
return true;
// Templated records are handled by VisitClassTemplateDecl()
if (cls->isTemplated() || cls->isTemplateDecl() ||
(clang::dyn_cast_or_null<clang::ClassTemplateSpecializationDecl>(cls) !=
@@ -414,6 +418,9 @@ std::unique_ptr<class_> translation_unit_visitor::create_class_declaration(
// TODO: refactor to method get_qualified_name()
auto qualified_name = common::get_qualified_name(*cls);
if (!diagram().should_include(qualified_name))
return {};
namespace_ ns{qualified_name};
ns.pop_back();
c.set_name(cls->getNameAsString());
@@ -1070,98 +1077,174 @@ translation_unit_visitor::process_template_specialization(
const auto template_args_count = cls->getTemplateArgs().size();
for (auto arg_it = 0U; arg_it < template_args_count; arg_it++) {
const auto arg = cls->getTemplateArgs().get(arg_it);
const auto argument_kind = arg.getKind();
if (argument_kind == clang::TemplateArgument::ArgKind::Type) {
template_parameter argument;
argument.is_template_parameter(false);
// If this is a nested template type - add nested templates as
// template arguments
if (arg.getAsType()->getAs<clang::TemplateSpecializationType>()) {
const auto *nested_template_type =
arg.getAsType()->getAs<clang::TemplateSpecializationType>();
const auto nested_template_name =
nested_template_type->getTemplateName()
.getAsTemplateDecl()
->getQualifiedNameAsString();
argument.set_name(nested_template_name);
auto nested_template_instantiation =
build_template_instantiation(
*arg.getAsType()
->getAs<clang::TemplateSpecializationType>(),
{&template_instantiation});
argument.set_id(nested_template_instantiation->id());
for (const auto &t : nested_template_instantiation->templates())
argument.add_template_param(t);
// Check if this template should be simplified (e.g. system
// template aliases such as 'std:basic_string<char>' should be
// simply 'std::string')
simplify_system_template(argument,
argument.to_string(config().using_namespace(), false));
}
else {
auto type_name =
to_string(arg.getAsType(), cls->getASTContext());
if (type_name.find('<') != std::string::npos) {
// Sometimes template instantiation is reported as
// RecordType in the AST and getAs to
// TemplateSpecializationType returns null pointer so we
// have to at least make sure it's properly formatted
// (e.g. std:integral_constant, or any template
// specialization which contains it - see t00038)
process_unexposed_template_specialization_parameters(
type_name.substr(type_name.find('<') + 1,
type_name.size() - (type_name.find('<') + 2)),
argument, template_instantiation);
argument.set_name(type_name.substr(0, type_name.find('<')));
}
else
// Otherwise just set the name for the template argument to
// whatever clang says
argument.set_name(type_name);
}
LOG_DBG("Adding template instantiation argument {}",
argument.to_string(config().using_namespace(), false));
template_instantiation.add_template(std::move(argument));
}
else if (argument_kind == clang::TemplateArgument::ArgKind::Integral) {
template_parameter argument;
argument.is_template_parameter(false);
argument.set_type(
std::to_string(arg.getAsIntegral().getExtValue()));
template_instantiation.add_template(std::move(argument));
}
else if (argument_kind ==
clang::TemplateArgument::ArgKind::Expression) {
template_parameter argument;
argument.is_template_parameter(false);
argument.set_type(get_source_text(
arg.getAsExpr()->getSourceRange(), source_manager_));
template_instantiation.add_template(std::move(argument));
}
else {
LOG_ERROR("UNSUPPORTED ARGUMENT KIND FOR ARG {}", arg.getKind());
}
process_template_specialization_argument(
cls, template_instantiation, arg, arg_it);
}
return c_ptr;
}
void translation_unit_visitor::process_template_specialization_argument(
const clang::ClassTemplateSpecializationDecl *cls,
class_ &template_instantiation, const clang::TemplateArgument &arg,
size_t argument_index, bool in_parameter_pack)
{
const auto argument_kind = arg.getKind();
if (argument_kind == clang::TemplateArgument::Type) {
template_parameter argument;
argument.is_template_parameter(false);
// If this is a nested template type - add nested templates as
// template arguments
if (arg.getAsType()->getAs<clang::TemplateSpecializationType>()) {
const auto *nested_template_type =
arg.getAsType()->getAs<clang::TemplateSpecializationType>();
const auto nested_template_name =
nested_template_type->getTemplateName()
.getAsTemplateDecl()
->getQualifiedNameAsString();
argument.set_name(nested_template_name);
auto nested_template_instantiation = build_template_instantiation(
*arg.getAsType()->getAs<clang::TemplateSpecializationType>(),
{&template_instantiation});
argument.set_id(nested_template_instantiation->id());
for (const auto &t : nested_template_instantiation->templates())
argument.add_template_param(t);
// Check if this template should be simplified (e.g. system
// template aliases such as 'std:basic_string<char>' should be
// simply 'std::string')
simplify_system_template(argument,
argument.to_string(config().using_namespace(), false));
}
else if (arg.getAsType()->getAs<clang::TemplateTypeParmType>()) {
auto type_name = to_string(arg.getAsType(), cls->getASTContext());
// clang does not provide declared template parameter/argument names
// in template specializations - so we have to extract them from
// raw source code...
if (type_name.find("type-parameter-") == 0) {
auto declaration_text =
get_source_text_raw(cls->getSourceRange(), source_manager_);
declaration_text = declaration_text.substr(
declaration_text.find(cls->getNameAsString()) +
cls->getNameAsString().size() + 1);
auto template_params =
cx::util::parse_unexposed_template_params(
declaration_text, [](const auto &t) { return t; });
if (template_params.size() > argument_index)
type_name = template_params[argument_index].to_string(
config().using_namespace(), false);
else {
LOG_DBG("Failed to find type specialization for argument "
"{} at index {} in declaration \n===\n{}\n===\n",
type_name, argument_index, declaration_text);
}
}
argument.set_name(type_name);
}
else {
auto type_name = to_string(arg.getAsType(), cls->getASTContext());
if (type_name.find('<') != std::string::npos) {
// Sometimes template instantiation is reported as
// RecordType in the AST and getAs to
// TemplateSpecializationType returns null pointer so we
// have to at least make sure it's properly formatted
// (e.g. std:integral_constant, or any template
// specialization which contains it - see t00038)
process_unexposed_template_specialization_parameters(
type_name.substr(type_name.find('<') + 1,
type_name.size() - (type_name.find('<') + 2)),
argument, template_instantiation);
argument.set_name(type_name.substr(0, type_name.find('<')));
}
else if (type_name.find("type-parameter-") == 0) {
auto declaration_text =
get_source_text_raw(cls->getSourceRange(), source_manager_);
declaration_text = declaration_text.substr(
declaration_text.find(cls->getNameAsString()) +
cls->getNameAsString().size() + 1);
auto template_params =
cx::util::parse_unexposed_template_params(
declaration_text, [](const auto &t) { return t; });
if (template_params.size() > argument_index)
type_name = template_params[argument_index].to_string(
config().using_namespace(), false);
else {
LOG_DBG("Failed to find type specialization for argument "
"{} at index {} in declaration \n===\n{}\n===\n",
type_name, argument_index, declaration_text);
}
// Otherwise just set the name for the template argument to
// whatever clang says
argument.set_name(type_name);
}
else
argument.set_name(type_name);
}
LOG_DBG("Adding template instantiation argument {}",
argument.to_string(config().using_namespace(), false));
simplify_system_template(
argument, argument.to_string(config().using_namespace(), false));
template_instantiation.add_template(std::move(argument));
}
else if (argument_kind == clang::TemplateArgument::Integral) {
template_parameter argument;
argument.is_template_parameter(false);
argument.set_type(std::to_string(arg.getAsIntegral().getExtValue()));
template_instantiation.add_template(std::move(argument));
}
else if (argument_kind == clang::TemplateArgument::Expression) {
template_parameter argument;
argument.is_template_parameter(false);
argument.set_type(get_source_text(
arg.getAsExpr()->getSourceRange(), source_manager_));
template_instantiation.add_template(std::move(argument));
}
else if (argument_kind == clang::TemplateArgument::TemplateExpansion) {
template_parameter argument;
argument.is_template_parameter(true);
cls->getLocation().dump(source_manager_);
}
else if (argument_kind == clang::TemplateArgument::Pack) {
// This will only work for now if pack is at the end
size_t argument_pack_index{argument_index};
for (const auto &template_argument : arg.getPackAsArray()) {
process_template_specialization_argument(cls,
template_instantiation, template_argument,
argument_pack_index++, true);
}
}
else {
LOG_ERROR("Unsupported template argument kind {}", arg.getKind());
}
}
void translation_unit_visitor::
process_unexposed_template_specialization_parameters(
const std::string &type_name, template_parameter &tp, class_ &c)
{
auto template_params = cx::util::parse_unexposed_template_params(
type_name, [this](const std::string &t) { return t; });
type_name, [](const std::string &t) { return t; });
found_relationships_t relationships;
for (auto &param : template_params) {
@@ -1220,7 +1303,9 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
template_base_params{};
auto *template_type_ptr = &template_type_decl;
if (template_type_decl.isTypeAlias())
if (template_type_decl.isTypeAlias() &&
template_type_decl.getAliasedType()
->getAs<clang::TemplateSpecializationType>())
template_type_ptr = template_type_decl.getAliasedType()
->getAs<clang::TemplateSpecializationType>();
@@ -1402,8 +1487,8 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
argument.add_template_param(t);
// Check if this template should be simplified (e.g. system
// template aliases such as 'std:basic_string<char>' should be
// simply 'std::string')
// template aliases such as 'std:basic_string<char>' should
// be simply 'std::string')
simplify_system_template(argument,
argument.to_string(config().using_namespace(), false));
@@ -1455,7 +1540,8 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
if (diagram().should_include(
full_template_specialization_name)) {
// Add dependency relationship to the parent template
// Add dependency relationship to the parent
// template
template_instantiation.add_relationship(
{relationship_t::kDependency,
arg.getAsType()
@@ -1546,8 +1632,8 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
template_instantiation.add_relationship(
{relationship_t::kInstantiation, best_match_id});
}
// If we can't find optimal match for parent template specialization, just
// use whatever clang suggests
// If we can't find optimal match for parent template specialization,
// just use whatever clang suggests
else if (diagram().has_element(template_type.getTemplateName()
.getAsTemplateDecl()
->getID())) {
@@ -1609,8 +1695,8 @@ void translation_unit_visitor::process_field(
auto type_name = to_string(field_type, field_declaration.getASTContext());
// The field name
const auto field_name = field_declaration.getNameAsString();
// If for any reason clang reports the type as empty string, make sure it
// has some default name
// If for any reason clang reports the type as empty string, make sure
// it has some default name
if (type_name.empty())
type_name = "<<anonymous>>";
@@ -1689,8 +1775,8 @@ void translation_unit_visitor::process_field(
// Check if this template instantiation should be added to the
// current diagram. Even if the top level template type for
// this instantiation should not be part of the diagram, e.g. it's
// a std::vector<>, it's nested types might be added
// this instantiation should not be part of the diagram, e.g.
// it's a std::vector<>, it's nested types might be added
bool add_template_instantiation_to_diargam{false};
if (diagram().should_include(
template_specialization.full_name(false))) {
@@ -1731,8 +1817,8 @@ void translation_unit_visitor::process_field(
});
}
// Add any relationships to the class 'c' to the diagram, unless
// the top level type has been added as aggregation
// Add any relationships to the class 'c' to the diagram,
// unless the top level type has been added as aggregation
add_relationships(c, field, nested_relationships,
/* break on first aggregation */ false);
}

View File

@@ -71,16 +71,16 @@ private:
void process_class_declaration(const clang::CXXRecordDecl &cls,
clanguml::class_diagram::model::class_ &c);
std::unique_ptr<clanguml::class_diagram::model::class_>
process_template_specialization(
clang::ClassTemplateSpecializationDecl *cls);
void process_class_bases(const clang::CXXRecordDecl *cls,
clanguml::class_diagram::model::class_ &c) const;
void process_class_children(const clang::CXXRecordDecl *cls,
clanguml::class_diagram::model::class_ &c);
std::unique_ptr<clanguml::class_diagram::model::class_>
process_template_specialization(
clang::ClassTemplateSpecializationDecl *cls);
void process_template_specialization_children(
const clang::ClassTemplateSpecializationDecl *cls,
clanguml::class_diagram::model::class_ &c);
@@ -89,6 +89,12 @@ private:
const clang::ClassTemplateDecl &template_declaration,
clanguml::class_diagram::model::class_ &c);
void process_template_specialization_argument(
const clang::ClassTemplateSpecializationDecl *cls,
model::class_ &template_instantiation,
const clang::TemplateArgument &arg, size_t argument_index,
bool in_parameter_pack = false);
void process_record_containment(const clang::TagDecl &record,
clanguml::common::model::element &c) const;

View File

@@ -336,6 +336,7 @@ std::unique_ptr<DiagramModel> generate(
DiagramConfig &config, bool verbose = false)
{
LOG_INFO("Generating diagram {}.puml", name);
auto diagram = std::make_unique<DiagramModel>();
diagram->set_name(name);
diagram->set_filter(
@@ -346,9 +347,13 @@ std::unique_ptr<DiagramModel> generate(
std::vector<std::string> translation_units{};
for (const auto &g : config.glob()) {
LOG_DBG("Processing glob: {}", g);
const auto matches = glob::rglob(g);
std::copy(matches.begin(), matches.end(),
std::back_inserter(translation_units));
LOG_DBG(
"Found translation units: {}", fmt::join(translation_units, ", "));
}
clang::tooling::ClangTool clang_tool(db, translation_units);

View File

@@ -166,6 +166,14 @@ void class_diagram::initialize_template_aliases()
template_aliases().insert(
{"std::basic_string<char32_t>", "std::u32string"});
}
if (!template_aliases().count("std::integral_constant<bool,true>")) {
template_aliases().insert(
{"std::integral_constant<bool,true>", "std::true_type"});
}
if (!template_aliases().count("std::integral_constant<bool,false>")) {
template_aliases().insert(
{"std::integral_constant<bool,false>", "std::false_type"});
}
}
template <> void append_value<plantuml>(plantuml &l, const plantuml &r)

View File

@@ -41,17 +41,19 @@ std::pair<common::model::namespace_, std::string> split_ns(
std::vector<class_diagram::model::template_parameter>
parse_unexposed_template_params(const std::string &params,
std::function<std::string(const std::string &)> ns_resolve)
std::function<std::string(const std::string &)> ns_resolve, int depth)
{
using class_diagram::model::template_parameter;
std::vector<template_parameter> res;
auto it = params.begin();
while (std::isspace(*it))
++it;
std::string type{};
std::vector<template_parameter> nested_params;
bool complete_class_template{false};
bool complete_class_template_argument{false};
while (it != params.end()) {
if (*it == '<') {
@@ -72,25 +74,32 @@ parse_unexposed_template_params(const std::string &params,
}
bracket_match_end++;
}
std::string nested_params_str(
bracket_match_begin, bracket_match_end);
nested_params =
parse_unexposed_template_params(nested_params_str, ns_resolve);
nested_params = parse_unexposed_template_params(
nested_params_str, ns_resolve, depth + 1);
if (nested_params.empty())
nested_params.emplace_back(
template_parameter{nested_params_str});
it = bracket_match_end - 1;
}
else if (*it == '>') {
complete_class_template = true;
complete_class_template_argument = true;
if (depth == 0) {
break;
}
}
else if (*it == ',') {
complete_class_template = true;
complete_class_template_argument = true;
}
else {
type += *it;
}
if (complete_class_template) {
if (complete_class_template_argument) {
template_parameter t;
t.set_type(ns_resolve(clanguml::util::trim(type)));
type = "";
@@ -98,7 +107,7 @@ parse_unexposed_template_params(const std::string &params,
t.add_template_param(std::move(param));
res.emplace_back(std::move(t));
complete_class_template = false;
complete_class_template_argument = false;
}
it++;
}
@@ -111,7 +120,6 @@ parse_unexposed_template_params(const std::string &params,
t.add_template_param(std::move(param));
res.emplace_back(std::move(t));
complete_class_template = false;
}
return res;

View File

@@ -32,6 +32,6 @@ std::pair<common::model::namespace_, std::string> split_ns(
std::vector<class_diagram::model::template_parameter>
parse_unexposed_template_params(const std::string &params,
std::function<std::string(const std::string &)> ns_resolve);
std::function<std::string(const std::string &)> ns_resolve, int depth = 0);
} // namespace clanguml::cx::util

View File

@@ -41,16 +41,21 @@ void generator::generate_call(const message &m, std::ostream &ostr) const
const auto from = m_config.using_namespace().relative(m.from);
const auto to = m_config.using_namespace().relative(m.to);
if (from.empty() || to.empty()) {
LOG_DBG("Skipping empty call from '{}' to '{}'", from, to);
return;
}
auto message = m.message;
if (!message.empty())
message += "()";
ostr << '"' << from << "\" "
<< common::generators::plantuml::to_plantuml(message_t::kCall) << " \""
<< to << "\" : " << m.message << "()" << std::endl;
<< to << "\" : " << message << std::endl;
if (m.message == "add" && to == "A" && from == "A")
LOG_DBG("Generating call '{}' from {} [{}] to {} [{}]", m.message, from,
m.from_usr, to, m.to_usr);
else
LOG_DBG("Generating call '{}' from {} [{}] to {} [{}]", m.message, from,
m.from_usr, to, m.to_usr);
LOG_DBG("Generated call '{}' from {} [{}] to {} [{}]", message, from,
m.from_usr, to, m.to_usr);
}
void generator::generate_return(const message &m, std::ostream &ostr) const
@@ -71,11 +76,19 @@ void generator::generate_activity(const activity &a, std::ostream &ostr) const
{
for (const auto &m : a.messages) {
const auto to = m_config.using_namespace().relative(m.to);
if (to.empty())
continue;
generate_call(m, ostr);
ostr << "activate " << '"' << to << '"' << std::endl;
if (m_model.sequences.find(m.to_usr) != m_model.sequences.end())
generate_activity(m_model.sequences[m.to_usr], ostr);
generate_return(m, ostr);
ostr << "deactivate " << '"' << to << '"' << std::endl;
}
}

View File

@@ -263,5 +263,12 @@ template <> bool starts_with(const std::string &s, const std::string &prefix)
return s.rfind(prefix, 0) == 0;
}
template <> bool ends_with(const std::string &value, const std::string &suffix)
{
if (suffix.size() > value.size())
return false;
return std::equal(suffix.rbegin(), suffix.rend(), value.rbegin());
}
}
}

View File

@@ -170,6 +170,10 @@ bool starts_with(
template <> bool starts_with(const std::string &s, const std::string &prefix);
template <typename T> bool ends_with(const T &value, const T &suffix);
template <> bool ends_with(const std::string &value, const std::string &suffix);
template <typename T>
bool ends_with(const std::vector<T> &col, const std::vector<T> &suffix)
{

View File

@@ -34,12 +34,12 @@ TEST_CASE("t00012", "[test-case][class]")
REQUIRE_THAT(puml, StartsWith("@startuml"));
REQUIRE_THAT(puml, EndsWith("@enduml\n"));
REQUIRE_THAT(puml, IsClassTemplate("A", "T,Ts..."));
REQUIRE_THAT(puml, IsClassTemplate("B", "int Is..."));
REQUIRE_THAT(puml, IsClassTemplate("B", "int... Is"));
REQUIRE_THAT(puml, IsInstantiation(_A("B<int Is...>"), _A("B<3,2,1>")));
REQUIRE_THAT(puml, IsInstantiation(_A("B<int Is...>"), _A("B<1,1,1,1>")));
REQUIRE_THAT(puml, IsInstantiation(_A("B<int... Is>"), _A("B<3,2,1>")));
REQUIRE_THAT(puml, IsInstantiation(_A("B<int... Is>"), _A("B<1,1,1,1>")));
REQUIRE_THAT(puml,
IsInstantiation(_A("C<T,int Is...>"),
IsInstantiation(_A("C<T,int... Is>"),
_A("C<std::map<int,"
"std::vector<std::vector<std::vector<std::string>>>>,3,3,3>")));

11
tests/t00047/.clang-uml Normal file
View File

@@ -0,0 +1,11 @@
compilation_database_dir: ..
output_directory: puml
diagrams:
t00047_class:
type: class
glob:
- ../../tests/t00047/t00047.cc
using_namespace: clanguml::t00047
include:
namespaces:
- clanguml::t00047

26
tests/t00047/t00047.cc Normal file
View File

@@ -0,0 +1,26 @@
#include <type_traits>
namespace clanguml {
namespace t00047 {
template <typename... Ts> struct conditional_t;
template <typename Else> struct conditional_t<Else> {
using type = Else;
};
template <typename Result, typename... Tail>
struct conditional_t<std::true_type, Result, Tail...> {
using type = Result;
};
template <typename Result, typename... Tail>
struct conditional_t<std::false_type, Result, Tail...> {
using type = typename conditional_t<Tail...>::type;
};
template <typename... Ts>
using conditional = typename conditional_t<Ts...>::type;
}
}

47
tests/t00047/test_case.h Normal file
View File

@@ -0,0 +1,47 @@
/**
* tests/t00047/test_case.h
*
* 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("t00047", "[test-case][class]")
{
auto [config, db] = load_config("t00047");
auto diagram = config.diagrams["t00047_class"];
REQUIRE(diagram->name == "t00047_class");
auto model = generate_class_diagram(*db, diagram);
REQUIRE(model->name() == "t00047_class");
auto puml = generate_class_puml(diagram, *model);
AliasMatcher _A(puml);
REQUIRE_THAT(puml, StartsWith("@startuml"));
REQUIRE_THAT(puml, EndsWith("@enduml\n"));
// Check if class templates exist
REQUIRE_THAT(puml, IsClassTemplate("conditional_t", "Ts..."));
REQUIRE_THAT(puml, IsClassTemplate("conditional_t", "Else"));
REQUIRE_THAT(puml,
IsClassTemplate("conditional_t", "std::true_type,Result,Tail..."));
REQUIRE_THAT(puml,
IsClassTemplate("conditional_t", "std::false_type,Result,Tail..."));
save_puml(
"./" + config.output_directory() + "/" + diagram->name + ".puml", puml);
}

View File

@@ -235,6 +235,7 @@ using namespace clanguml::test::matchers;
#include "t00044/test_case.h"
#include "t00045/test_case.h"
#include "t00046/test_case.h"
#include "t00047/test_case.h"
////
//// Sequence diagram tests

View File

@@ -135,6 +135,9 @@ test_cases:
- name: t00046
title: Test case for root namespace handling with packages
description:
- name: t00047
title: Test case for recursive variadic template
description:
Sequence diagrams:
- name: t20001
title: Basic sequence diagram test case

View File

@@ -135,4 +135,37 @@ TEST_CASE("Test parse_unexposed_template_params", "[unit-test]")
CHECK(class2.template_params()[1].type() == "std::vector");
CHECK(class2.template_params()[1].template_params()[0].type() ==
"std::string");
const std::string empty_string = R"(
> {
using type = Result;
};)";
auto empty_template = parse_unexposed_template_params(
empty_string, [](const auto &n) { return n; });
CHECK(empty_template.size() == 0);
const std::string single_template_string = R"(Else> {
using type = Else;)";
auto single_template = parse_unexposed_template_params(
single_template_string, [](const auto &n) { return n; });
CHECK(single_template.size() == 1);
CHECK(single_template[0].type() == "Else");
const std::string declaration_string = R"(
std::true_type, Result, Tail> {
using type = Result;
};)";
auto declaration_template = parse_unexposed_template_params(
declaration_string, [](const auto &n) { return n; });
CHECK(declaration_template.size() == 3);
CHECK(declaration_template[0].type() == "std::true_type");
CHECK(declaration_template[1].type() == "Result");
CHECK(declaration_template[2].type() == "Tail");
}

View File

@@ -24,7 +24,7 @@ TEST_CASE("{{ name }}", "[test-case][{{ type }}]")
REQUIRE(diagram->name == "{{ name }}_{{ type }}");
auto model = generate_{{ type }}_diagram(db, diagram);
auto model = generate_{{ type }}_diagram(*db, diagram);
REQUIRE(model->name() == "{{ name }}_{{ type }}");