Added option inline_lambda_messages to omit lambda expressions from sequence diagrams (#261)

This commit is contained in:
Bartek Kryza
2024-05-03 14:42:15 +02:00
parent 052d9b7ff3
commit 14a13b45aa
18 changed files with 498 additions and 42 deletions

View File

@@ -252,6 +252,17 @@ results in the following diagram:
![extension](test_cases/t20012_sequence.svg)
In case lambda expressions are redundant and we are only interested in the calls
generate from the lambda expressions, it is possible to inline lambda
expressions in the generated diagrams by specifying the following option:
```yaml
inline_lambda_messages: true
```
For example compare the test cases [t20012](test_cases/t20012.md) and
[t20052](test_cases/t20052.md).
## Customizing participants order
The default participant order in the sequence diagram can be suboptimal in the
sense that consecutive calls can go right, then left, then right again

View File

@@ -561,6 +561,9 @@ cli_flow_t cli_handler::add_config_diagram(
doc["diagrams"][name]["glob"] = std::vector<std::string>{{"src/*.cpp"}};
doc["diagrams"][name]["combine_free_functions_into_file_participants"] =
true;
doc["diagrams"][name]["inline_lambda_messages"] = false;
doc["diagrams"][name]["generate_message_comments"] = false;
doc["diagrams"][name]["generate_condition_statements"] = false;
doc["diagrams"][name]["using_namespace"] =
std::vector<std::string>{{"myproject"}};
doc["diagrams"][name]["include"]["paths"] =

View File

@@ -40,6 +40,11 @@ class diagram {
public:
diagram();
diagram(const diagram &) = delete;
diagram(diagram && /*unused*/) noexcept;
diagram &operator=(const diagram &) = delete;
diagram &operator=(diagram && /*unused*/) noexcept;
virtual ~diagram();
/**
@@ -77,11 +82,6 @@ public:
virtual common::optional_ref<clanguml::common::model::diagram_element>
get_with_namespace(const std::string &name, const namespace_ &ns) const;
diagram(const diagram &) = delete;
diagram(diagram && /*unused*/) noexcept;
diagram &operator=(const diagram &) = delete;
diagram &operator=(diagram && /*unused*/) noexcept;
/**
* Set diagram name.
*

View File

@@ -236,6 +236,7 @@ void inheritable_diagram_options::inherit(
comment_parser.override(parent.comment_parser);
combine_free_functions_into_file_participants.override(
parent.combine_free_functions_into_file_participants);
inline_lambda_messages.override(parent.inline_lambda_messages);
generate_return_types.override(parent.generate_return_types);
generate_condition_statements.override(
parent.generate_condition_statements);

View File

@@ -570,6 +570,7 @@ struct inheritable_diagram_options {
"comment_parser", comment_parser_t::plain};
option<bool> combine_free_functions_into_file_participants{
"combine_free_functions_into_file_participants", false};
option<bool> inline_lambda_messages{"inline_lambda_messages", false};
option<bool> generate_return_types{"generate_return_types", false};
option<bool> generate_condition_statements{
"generate_condition_statements", false};

View File

@@ -218,6 +218,7 @@ types:
#
generate_method_arguments: !optional generate_method_arguments_t
combine_free_functions_into_file_participants: !optional bool
inline_lambda_messages: !optional bool
generate_return_types: !optional bool
generate_condition_statements: !optional bool
generate_message_comments: !optional bool
@@ -340,6 +341,7 @@ root:
include_relations_also_as_members: !optional bool
generate_method_arguments: !optional generate_method_arguments_t
combine_free_functions_into_file_participants: !optional bool
inline_lambda_messages: !optional bool
generate_concept_requirements: !optional bool
generate_return_types: !optional bool
generate_condition_statements: !optional bool

View File

@@ -665,6 +665,7 @@ template <> struct convert<sequence_diagram> {
get_option(node, rhs.from_to);
get_option(node, rhs.to);
get_option(node, rhs.combine_free_functions_into_file_participants);
get_option(node, rhs.inline_lambda_messages);
get_option(node, rhs.generate_return_types);
get_option(node, rhs.generate_condition_statements);
get_option(node, rhs.participants_order);
@@ -844,6 +845,7 @@ template <> struct convert<config> {
get_option(node, rhs.debug_mode);
get_option(node, rhs.generate_metadata);
get_option(node, rhs.combine_free_functions_into_file_participants);
get_option(node, rhs.inline_lambda_messages);
get_option(node, rhs.generate_return_types);
get_option(node, rhs.generate_condition_statements);
get_option(node, rhs.generate_message_comments);

View File

@@ -347,6 +347,7 @@ YAML::Emitter &operator<<(
sd != nullptr) {
out << sd->title;
out << c.combine_free_functions_into_file_participants;
out << c.inline_lambda_messages;
out << c.generate_condition_statements;
out << c.generate_method_arguments;
out << c.generate_return_types;

View File

@@ -125,8 +125,8 @@ void generator::generate_call(const message &m, std::ostream &ostr) const
void generator::generate_return(const message &m, std::ostream &ostr) const
{
// Add return activity only for messages between different actors and
// only if the return type is different than void
// Add return activity only for messages between different actors
// and only if the return type is different than void
if (m.from() == m.to())
return;
@@ -338,7 +338,8 @@ void generator::generate_participant(
auto p = model().get(name);
if (!p.has_value()) {
LOG_WARN("Cannot find participant {} from `participants_order` option",
LOG_WARN("Cannot find participant {} from `participants_order` "
"option",
name);
return;
}
@@ -629,10 +630,10 @@ void generator::generate_diagram(std::ostream &ostr) const
model::function::message_render_mode render_mode =
select_method_arguments_render_mode();
// For methods or functions in diagrams where they are combined into
// file participants, we need to add an 'entry' point call to know
// which method relates to the first activity for this 'start_from'
// condition
// For methods or functions in diagrams where they are
// combined into file participants, we need to add an
// 'entry' point call to know which method relates to the
// first activity for this 'start_from' condition
if (from.value().type_name() == "method" ||
config().combine_free_functions_into_file_participants()) {
ostr << "[->"

View File

@@ -95,22 +95,22 @@ void diagram::add_active_participant(common::id_t id)
const activity &diagram::get_activity(common::id_t id) const
{
return sequences_.at(id);
return activities_.at(id);
}
bool diagram::has_activity(common::id_t id) const
{
return sequences_.count(id) > 0;
return activities_.count(id) > 0;
}
activity &diagram::get_activity(common::id_t id) { return sequences_.at(id); }
activity &diagram::get_activity(common::id_t id) { return activities_.at(id); }
void diagram::add_message(model::message &&message)
{
const auto caller_id = message.from();
if (sequences_.find(caller_id) == sequences_.end()) {
if (activities_.find(caller_id) == activities_.end()) {
activity a{caller_id};
sequences_.insert({caller_id, std::move(a)});
activities_.insert({caller_id, std::move(a)});
}
get_activity(caller_id).add_message(std::move(message));
@@ -126,7 +126,7 @@ void diagram::end_block_message(
{
const auto caller_id = message.from();
if (sequences_.find(caller_id) != sequences_.end()) {
if (activities_.find(caller_id) != activities_.end()) {
auto &current_messages = get_activity(caller_id).messages();
fold_or_end_block_statement(
@@ -139,7 +139,7 @@ void diagram::add_case_stmt_message(model::message &&m)
using clanguml::common::model::message_t;
const auto caller_id = m.from();
if (sequences_.find(caller_id) != sequences_.end()) {
if (activities_.find(caller_id) != activities_.end()) {
auto &current_messages = get_activity(caller_id).messages();
if (current_messages.back().type() == message_t::kCase) {
@@ -151,11 +151,11 @@ void diagram::add_case_stmt_message(model::message &&m)
}
}
std::map<common::id_t, activity> &diagram::sequences() { return sequences_; }
std::map<common::id_t, activity> &diagram::sequences() { return activities_; }
const std::map<common::id_t, activity> &diagram::sequences() const
{
return sequences_;
return activities_;
}
std::map<common::id_t, std::unique_ptr<participant>> &diagram::participants()
@@ -191,7 +191,7 @@ std::vector<std::string> diagram::list_from_values() const
{
std::vector<std::string> result;
for (const auto &[from_id, act] : sequences_) {
for (const auto &[from_id, act] : activities_) {
const auto &from_activity = *(participants_.at(from_id));
const auto &full_name = from_activity.full_name(false);
@@ -209,7 +209,7 @@ std::vector<std::string> diagram::list_to_values() const
{
std::vector<std::string> result;
for (const auto &[from_id, act] : sequences_) {
for (const auto &[from_id, act] : activities_) {
for (const auto &m : act.messages()) {
if (participants_.count(m.to()) > 0) {
const auto &to_activity = *(participants_.at(m.to()));
@@ -406,7 +406,7 @@ std::vector<message_chain_t> diagram::get_all_from_to_message_chains(
bool diagram::is_empty() const
{
return sequences_.empty() || participants_.empty();
return activities_.empty() || participants_.empty();
}
void diagram::print() const
@@ -417,7 +417,7 @@ void diagram::print() const
}
LOG_TRACE(" --- Activities ---");
for (const auto &[from_id, act] : sequences_) {
for (const auto &[from_id, act] : activities_) {
if (participants_.count(from_id) == 0)
continue;
@@ -487,7 +487,7 @@ void diagram::finalize()
// First in each sequence (activity) filter out any remaining
// uninteresting calls
for (auto &[id, act] : sequences_) {
for (auto &[id, act] : activities_) {
util::erase_if(act.messages(), [this](auto &m) {
if (m.type() != message_t::kCall)
return false;
@@ -507,7 +507,7 @@ void diagram::finalize()
}
// Now remove any empty block statements, e.g. if/endif
for (auto &[id, act] : sequences_) {
for (auto &[id, act] : activities_) {
int64_t block_nest_level{0};
std::vector<std::vector<message>> block_message_stack;
// Add first stack level - this level will contain the filtered

View File

@@ -295,6 +295,13 @@ public:
*/
bool is_empty() const override;
void update_sequences_from_diagram(diagram &other)
{
activities_ = std::move(other.activities_);
participants_ = std::move(other.participants_);
active_participants_ = std::move(other.active_participants_);
}
private:
/**
* This method checks the last messages in sequence (current_messages),
@@ -337,7 +344,7 @@ private:
return block_end_types.count(mt) > 0;
};
std::map<common::id_t, activity> sequences_;
std::map<common::id_t, activity> activities_;
std::map<common::id_t, std::unique_ptr<participant>> participants_;

View File

@@ -630,7 +630,7 @@ bool translation_unit_visitor::TraverseCXXConstructExpr(
translation_unit_visitor::VisitCXXConstructExpr(expr);
LOG_TRACE("Leaving member call expression at {}",
LOG_TRACE("Leaving cxx construct call expression at {}",
expr->getBeginLoc().printToString(source_manager()));
context().leave_callexpr();
@@ -2018,6 +2018,38 @@ void translation_unit_visitor::pop_message_to_diagram(
}
void translation_unit_visitor::finalize()
{
resolve_ids_to_global();
// Change all messages with target set to an id of a lambda expression to
// to the ID of their operator() - this is necessary, as some calls to
// lambda expressions are visited before the actual lambda expressions
// are visited...
ensure_lambda_messages_have_operator_as_target();
if (config().inline_lambda_messages())
inline_lambda_operator_calls();
}
void translation_unit_visitor::ensure_lambda_messages_have_operator_as_target()
{
for (auto &[id, activity] : diagram().sequences()) {
for (auto &m : activity.messages()) {
auto participant = diagram().get_participant<model::class_>(m.to());
if (participant && participant.value().is_lambda() &&
participant.value().lambda_operator_id() != 0) {
LOG_DBG("Changing lambda expression target id from {} to {}",
m.to(), participant.value().lambda_operator_id());
m.set_to(participant.value().lambda_operator_id());
m.set_message_name("operator()");
}
}
}
}
void translation_unit_visitor::resolve_ids_to_global()
{
std::set<common::id_t> active_participants_unique;
@@ -2041,25 +2073,116 @@ void translation_unit_visitor::finalize()
}
}
}
}
// Change all messages with target set to an id of a lambda expression to
// to the ID of their operator() - this is necessary, as some calls to
// lambda expressions are visited before the actual lambda expressions
// are visited...
for (auto &[id, activity] : diagram().sequences()) {
for (auto &m : activity.messages()) {
auto participant = diagram().get_participant<model::class_>(m.to());
void translation_unit_visitor::inline_lambda_operator_calls()
{ // If option to inline lambda calls is enabled, we need to modify the
// sequences to skip the lambda calls. In case lambda call does not lead
// to a non-lambda call, omit it entirely
if (participant && participant.value().is_lambda() &&
participant.value().lambda_operator_id() != 0) {
LOG_DBG("Changing lambda expression target id from {} to {}",
m.to(), participant.value().lambda_operator_id());
model::diagram lambdaless_diagram;
m.set_to(participant.value().lambda_operator_id());
m.set_message_name("operator()");
for (auto &[id, act] : diagram().sequences()) {
model::activity new_activity{id};
// If activity is a lambda operator() - skip it
auto maybe_lambda_activity =
diagram().get_participant<model::method>(id);
if (maybe_lambda_activity) {
const auto parent_class_id =
maybe_lambda_activity.value().class_id();
auto maybe_parent_class =
diagram().get_participant<model::class_>(parent_class_id);
if (maybe_parent_class && maybe_parent_class.value().is_lambda()) {
continue;
}
}
// For other activities, check each message - if it calls lambda
// operator() - reattach the message to the next activity in the chain
// (assuming it's not lambda)
for (auto &m : act.messages()) {
auto message_call_to_lambda{false};
message_call_to_lambda =
inline_lambda_operator_call(id, new_activity, m);
if (!message_call_to_lambda)
new_activity.add_message(m);
}
// Add activity
lambdaless_diagram.sequences().insert({id, std::move(new_activity)});
}
for (auto &&[id, p] : diagram().participants()) {
// Skip participants which are lambda classes
if (const auto *maybe_class =
dynamic_cast<const model::class_ *>(p.get());
maybe_class && maybe_class->is_lambda()) {
continue;
}
// Skip participants which are lambda operator methods
if (const auto *maybe_method =
dynamic_cast<const model::method *>(p.get());
maybe_method) {
auto maybe_class = diagram().get_participant<model::class_>(
maybe_method->class_id());
if (maybe_class && maybe_class.value().is_lambda())
continue;
}
// Otherwise move the participant to the new diagram model
lambdaless_diagram.add_participant(std::move(p));
}
// Skip active participants which are not in lambdaless_diagram participants
for (auto id : diagram().active_participants()) {
if (lambdaless_diagram.participants().count(id)) {
lambdaless_diagram.add_active_participant(id);
}
}
diagram().update_sequences_from_diagram(lambdaless_diagram);
}
bool translation_unit_visitor::inline_lambda_operator_call(
const long id, model::activity &new_activity, const model::message &m)
{
bool message_call_to_lambda{false};
auto maybe_lambda_operator =
diagram().get_participant<model::method>(m.to());
if (maybe_lambda_operator) {
const auto parent_class_id = maybe_lambda_operator.value().class_id();
auto maybe_parent_class =
diagram().get_participant<model::class_>(parent_class_id);
if (maybe_parent_class && maybe_parent_class.value().is_lambda()) {
// auto new_message{m};
// new_message.set_
auto lambda_operator_activity = diagram().get_activity(m.to());
// For each call in that lambda activity - reattach this
// call to the current activity
for (auto &mm : lambda_operator_activity.messages()) {
if (!inline_lambda_operator_call(id, new_activity, mm)) {
auto new_message{mm};
new_message.set_from(id);
new_activity.add_message(new_message);
}
}
message_call_to_lambda = true;
}
}
return message_call_to_lambda;
}
std::unique_ptr<clanguml::sequence_diagram::model::method>

View File

@@ -487,6 +487,11 @@ private:
*/
template_builder_t &tbuilder() { return template_builder_; }
void inline_lambda_operator_calls();
bool inline_lambda_operator_call(
const long id, model::activity &new_activity, const model::message &m);
call_expression_context call_expression_context_;
/**
@@ -522,5 +527,7 @@ private:
processed_comments_by_caller_id_;
template_builder_t template_builder_;
void resolve_ids_to_global();
void ensure_lambda_messages_have_operator_as_target();
};
} // namespace clanguml::sequence_diagram::visitor

12
tests/t20052/.clang-uml Normal file
View File

@@ -0,0 +1,12 @@
diagrams:
t20052_sequence:
type: sequence
glob:
- t20052.cc
include:
namespaces:
- clanguml::t20052
using_namespace: clanguml::t20052
inline_lambda_messages: true
from:
- function: "clanguml::t20052::tmain()"

105
tests/t20052/t20052.cc Normal file
View File

@@ -0,0 +1,105 @@
#include <algorithm>
#include <functional>
#include <memory>
#include <optional>
#include <utility>
#include <vector>
namespace clanguml {
namespace t20052 {
struct A {
void a() { aa(); }
void aa() { aaa(); }
void aaa() { }
};
struct B {
void b() { bb(); }
void bb() { bbb(); }
void bbb() { }
void eb() { }
};
struct C {
void c() { cc(); }
void cc() { ccc(); }
void ccc() { }
};
struct D {
int add5(int arg) const { return arg + 5; }
};
class E {
std::optional<std::shared_ptr<B>> maybe_b;
std::shared_ptr<A> a;
public:
template <typename F> void setup(F &&f) { f(maybe_b); }
};
template <typename F> struct R {
R(F &&f)
: f_{std::move(f)}
{
}
void r() { f_(); }
F f_;
};
void tmain()
{
A a;
B b;
C c;
// The activity shouldn't be marked at the lambda definition, but
// wherever it is actually called...
auto alambda = [&a, &b]() {
a.a();
b.b();
};
// ...like here
alambda();
// There should be no call to B in the sequence diagram as the blambda
// is never called
[[maybe_unused]] auto blambda = [&b]() { b.b(); };
// Nested lambdas should also work
auto clambda = [alambda, &c]() {
c.c();
alambda();
};
clambda();
R r{[&c]() { c.c(); }};
r.r();
D d;
std::vector<int> ints{0, 1, 2, 3, 4};
std::transform(ints.begin(), ints.end(), ints.begin(),
[&d](auto i) { return d.add5(i); });
// TODO: Fix naming function call arguments which are lambdas
// E e;
//
// e.setup([](auto &&arg) mutable {
// // We cannot know here what 'arg' might be
// arg.value()->eb();
// });
}
}
}

176
tests/t20052/test_case.h Normal file
View File

@@ -0,0 +1,176 @@
/**
* tests/t20052/test_case.h
*
* Copyright (c) 2021-2024 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("t20052", "[test-case][sequence]")
{
auto [config, db] = load_config("t20052");
auto diagram = config.diagrams["t20052_sequence"];
REQUIRE(diagram->name == "t20052_sequence");
auto model = generate_sequence_diagram(*db, diagram);
REQUIRE(model->name() == "t20052_sequence");
{
auto src = generate_sequence_puml(diagram, *model);
AliasMatcher _A(src);
REQUIRE_THAT(src, StartsWith("@startuml"));
REQUIRE_THAT(src, EndsWith("@enduml\n"));
// Check if all calls exist
REQUIRE_THAT(src,
!HasCall(_A("tmain()"), _A("tmain()::(lambda t20052.cc:67:20)"),
"operator()() const"));
REQUIRE_THAT(src,
!HasCall(_A("tmain()::(lambda t20052.cc:67:20)"), _A("A"), "a()"));
REQUIRE_THAT(src, HasCall(_A("tmain()"), _A("A"), "a()"));
REQUIRE_THAT(src, HasCall(_A("A"), _A("A"), "aa()"));
REQUIRE_THAT(src, HasCall(_A("A"), _A("A"), "aaa()"));
REQUIRE_THAT(src,
!HasCall(_A("tmain()::(lambda t20052.cc:67:20)"), _A("B"), "b()"));
REQUIRE_THAT(src, HasCall(_A("tmain()"), _A("B"), "b()"));
REQUIRE_THAT(src, HasCall(_A("B"), _A("B"), "bb()"));
REQUIRE_THAT(src, HasCall(_A("B"), _A("B"), "bbb()"));
REQUIRE_THAT(src,
!HasCall(_A("tmain()::(lambda t20052.cc:80:20)"), _A("C"), "c()"));
REQUIRE_THAT(src, HasCall(_A("tmain()"), _A("C"), "c()"));
REQUIRE_THAT(src, HasCall(_A("C"), _A("C"), "cc()"));
REQUIRE_THAT(src, HasCall(_A("C"), _A("C"), "ccc()"));
REQUIRE_THAT(src,
!HasCall(_A("tmain()::(lambda t20052.cc:80:20)"),
_A("tmain()::(lambda t20052.cc:67:20)"), "operator()() const"));
REQUIRE_THAT(src, HasCall(_A("C"), _A("C"), "ccc()"));
REQUIRE_THAT(src,
HasCall(_A("tmain()"), _A("R<(lambda at t20052.cc:86:9)>"),
"R((lambda at t20052.cc:86:9) &&)"));
REQUIRE_THAT(src,
HasCall(_A("tmain()"), _A("R<(lambda at t20052.cc:86:9)>"), "r()"));
REQUIRE_THAT(src,
!HasCall(_A("R<(lambda at t20052.cc:86:9)>"),
_A("tmain()::(lambda t20052.cc:86:9)"), "operator()() const"));
REQUIRE_THAT(
src, HasCall(_A("R<(lambda at t20052.cc:86:9)>"), _A("C"), "c()"));
REQUIRE_THAT(src,
!HasCall(_A("tmain()"), _A("tmain()::(lambda t20052.cc:94:9)"),
"operator()(auto) const"));
REQUIRE_THAT(src,
!HasCall(_A("tmain()::(lambda t20052.cc:94:9)"), _A("D"),
"add5(int) const"));
REQUIRE_THAT(src, HasCall(_A("tmain()"), _A("D"), "add5(int) const"));
save_puml(config.output_directory(), diagram->name + ".puml", src);
}
{
auto j = generate_sequence_json(diagram, *model);
using namespace json;
std::vector<int> messages = {
FindMessage(j, "tmain()", "A", "a()"),
FindMessage(j, "A", "A", "aa()"),
FindMessage(j, "A", "A", "aaa()"),
FindMessage(j, "tmain()", "B", "b()"),
FindMessage(j, "B", "B", "bb()"),
FindMessage(j, "B", "B", "bbb()"),
FindMessage(j, "tmain()", "C", "c()"),
FindMessage(j, "C", "C", "cc()"),
FindMessage(j, "C", "C", "ccc()"),
FindMessage(j, "tmain()", "R<(lambda at t20052.cc:86:9)>", "r()"),
FindMessage(j, "R<(lambda at t20052.cc:86:9)>", "C", "c()"),
};
REQUIRE(std::is_sorted(messages.begin(), messages.end()));
save_json(config.output_directory(), diagram->name + ".json", j);
}
{
auto src = generate_sequence_mermaid(diagram, *model);
mermaid::SequenceDiagramAliasMatcher _A(src);
using mermaid::HasCall;
REQUIRE_THAT(src,
!HasCall(_A("tmain()"), _A("tmain()::(lambda t20052.cc:67:20)"),
"operator()() const"));
REQUIRE_THAT(src,
!HasCall(_A("tmain()::(lambda t20052.cc:67:20)"), _A("A"), "a()"));
REQUIRE_THAT(src, HasCall(_A("tmain()"), _A("A"), "a()"));
REQUIRE_THAT(src, HasCall(_A("A"), _A("A"), "aa()"));
REQUIRE_THAT(src, HasCall(_A("A"), _A("A"), "aaa()"));
REQUIRE_THAT(src,
!HasCall(_A("tmain()::(lambda t20052.cc:67:20)"), _A("B"), "b()"));
REQUIRE_THAT(src, HasCall(_A("tmain()"), _A("B"), "b()"));
REQUIRE_THAT(src, HasCall(_A("B"), _A("B"), "bb()"));
REQUIRE_THAT(src, HasCall(_A("B"), _A("B"), "bbb()"));
REQUIRE_THAT(src,
!HasCall(_A("tmain()::(lambda t20052.cc:80:20)"), _A("C"), "c()"));
REQUIRE_THAT(src, HasCall(_A("tmain()"), _A("C"), "c()"));
REQUIRE_THAT(src, HasCall(_A("C"), _A("C"), "cc()"));
REQUIRE_THAT(src, HasCall(_A("C"), _A("C"), "ccc()"));
REQUIRE_THAT(src,
!HasCall(_A("tmain()::(lambda t20052.cc:80:20)"),
_A("tmain()::(lambda t20052.cc:67:20)"), "operator()() const"));
REQUIRE_THAT(src, HasCall(_A("C"), _A("C"), "ccc()"));
REQUIRE_THAT(src,
HasCall(_A("tmain()"), _A("R<(lambda at t20052.cc:86:9)>"),
"R((lambda at t20052.cc:86:9) &&)"));
REQUIRE_THAT(src,
HasCall(_A("tmain()"), _A("R<(lambda at t20052.cc:86:9)>"), "r()"));
REQUIRE_THAT(src,
!HasCall(_A("R<(lambda at t20052.cc:86:9)>"),
_A("tmain()::(lambda t20052.cc:86:9)"), "operator()() const"));
REQUIRE_THAT(
src, HasCall(_A("R<(lambda at t20052.cc:86:9)>"), _A("C"), "c()"));
REQUIRE_THAT(src,
!HasCall(_A("tmain()"), _A("tmain()::(lambda t20052.cc:94:9)"),
"operator()(auto) const"));
REQUIRE_THAT(src,
!HasCall(_A("tmain()::(lambda t20052.cc:94:9)"), _A("D"),
"add5(int) const"));
REQUIRE_THAT(src, HasCall(_A("tmain()"), _A("D"), "add5(int) const"));
save_mermaid(config.output_directory(), diagram->name + ".mmd", src);
}
}

View File

@@ -480,6 +480,7 @@ using namespace clanguml::test::matchers;
#include "t20050/test_case.h"
#include "t20051/test_case.h"
#endif
#include "t20052/test_case.h"
///
/// Package diagram tests

View File

@@ -376,6 +376,9 @@ test_cases:
- name: t20051
title: Test case for CUDA calls callee_type filter
description:
- name: t20052
title: Test case for CUDA calls callee_type filter
description:
Package diagrams:
- name: t30001
title: Basic package diagram test case