diff --git a/docs/sequence_diagrams.md b/docs/sequence_diagrams.md index a75a4619..d07574e0 100644 --- a/docs/sequence_diagrams.md +++ b/docs/sequence_diagrams.md @@ -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 diff --git a/src/cli/cli_handler.cc b/src/cli/cli_handler.cc index 36d343ca..f536fcc4 100644 --- a/src/cli/cli_handler.cc +++ b/src/cli/cli_handler.cc @@ -561,6 +561,9 @@ cli_flow_t cli_handler::add_config_diagram( doc["diagrams"][name]["glob"] = std::vector{{"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{{"myproject"}}; doc["diagrams"][name]["include"]["paths"] = diff --git a/src/common/model/diagram.h b/src/common/model/diagram.h index 89161d6d..0c1b497d 100644 --- a/src/common/model/diagram.h +++ b/src/common/model/diagram.h @@ -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 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. * diff --git a/src/config/config.cc b/src/config/config.cc index 627b8ce3..603cfb8b 100644 --- a/src/config/config.cc +++ b/src/config/config.cc @@ -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); diff --git a/src/config/config.h b/src/config/config.h index ee69c0a1..82dd454b 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -570,6 +570,7 @@ struct inheritable_diagram_options { "comment_parser", comment_parser_t::plain}; option combine_free_functions_into_file_participants{ "combine_free_functions_into_file_participants", false}; + option inline_lambda_messages{"inline_lambda_messages", false}; option generate_return_types{"generate_return_types", false}; option generate_condition_statements{ "generate_condition_statements", false}; diff --git a/src/config/schema.h b/src/config/schema.h index 1768774c..c1fcd8f5 100644 --- a/src/config/schema.h +++ b/src/config/schema.h @@ -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 diff --git a/src/config/yaml_decoders.cc b/src/config/yaml_decoders.cc index 6f4e4c1b..b73eed32 100644 --- a/src/config/yaml_decoders.cc +++ b/src/config/yaml_decoders.cc @@ -665,6 +665,7 @@ template <> struct convert { 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 { 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); diff --git a/src/config/yaml_emitters.cc b/src/config/yaml_emitters.cc index a28ea4f3..bb6e77af 100644 --- a/src/config/yaml_emitters.cc +++ b/src/config/yaml_emitters.cc @@ -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; diff --git a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc index 7134e891..b46bc00b 100644 --- a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc +++ b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc @@ -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 << "[->" diff --git a/src/sequence_diagram/model/diagram.cc b/src/sequence_diagram/model/diagram.cc index 03253968..11bb1e04 100644 --- a/src/sequence_diagram/model/diagram.cc +++ b/src/sequence_diagram/model/diagram.cc @@ -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 ¤t_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 ¤t_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 &diagram::sequences() { return sequences_; } +std::map &diagram::sequences() { return activities_; } const std::map &diagram::sequences() const { - return sequences_; + return activities_; } std::map> &diagram::participants() @@ -191,7 +191,7 @@ std::vector diagram::list_from_values() const { std::vector 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 diagram::list_to_values() const { std::vector 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 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> block_message_stack; // Add first stack level - this level will contain the filtered diff --git a/src/sequence_diagram/model/diagram.h b/src/sequence_diagram/model/diagram.h index f8aa3740..30e9f714 100644 --- a/src/sequence_diagram/model/diagram.h +++ b/src/sequence_diagram/model/diagram.h @@ -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 sequences_; + std::map activities_; std::map> participants_; diff --git a/src/sequence_diagram/visitor/translation_unit_visitor.cc b/src/sequence_diagram/visitor/translation_unit_visitor.cc index af8454be..1bc9a7b8 100644 --- a/src/sequence_diagram/visitor/translation_unit_visitor.cc +++ b/src/sequence_diagram/visitor/translation_unit_visitor.cc @@ -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(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 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(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(id); + + if (maybe_lambda_activity) { + const auto parent_class_id = + maybe_lambda_activity.value().class_id(); + auto maybe_parent_class = + diagram().get_participant(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(p.get()); + maybe_class && maybe_class->is_lambda()) { + continue; + } + + // Skip participants which are lambda operator methods + if (const auto *maybe_method = + dynamic_cast(p.get()); + maybe_method) { + auto maybe_class = diagram().get_participant( + 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(m.to()); + + if (maybe_lambda_operator) { + const auto parent_class_id = maybe_lambda_operator.value().class_id(); + auto maybe_parent_class = + diagram().get_participant(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 diff --git a/src/sequence_diagram/visitor/translation_unit_visitor.h b/src/sequence_diagram/visitor/translation_unit_visitor.h index 7678fb98..f404816a 100644 --- a/src/sequence_diagram/visitor/translation_unit_visitor.h +++ b/src/sequence_diagram/visitor/translation_unit_visitor.h @@ -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 diff --git a/tests/t20052/.clang-uml b/tests/t20052/.clang-uml new file mode 100644 index 00000000..3453ae98 --- /dev/null +++ b/tests/t20052/.clang-uml @@ -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()" \ No newline at end of file diff --git a/tests/t20052/t20052.cc b/tests/t20052/t20052.cc new file mode 100644 index 00000000..e7757e63 --- /dev/null +++ b/tests/t20052/t20052.cc @@ -0,0 +1,105 @@ +#include +#include +#include +#include +#include +#include + +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> maybe_b; + std::shared_ptr a; + +public: + template void setup(F &&f) { f(maybe_b); } +}; + +template 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 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(); + // }); +} +} +} \ No newline at end of file diff --git a/tests/t20052/test_case.h b/tests/t20052/test_case.h new file mode 100644 index 00000000..60c38e60 --- /dev/null +++ b/tests/t20052/test_case.h @@ -0,0 +1,176 @@ +/** + * tests/t20052/test_case.h + * + * Copyright (c) 2021-2024 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("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 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); + } +} \ No newline at end of file diff --git a/tests/test_cases.cc b/tests/test_cases.cc index 5797bf67..80ea3778 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -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 diff --git a/tests/test_cases.yaml b/tests/test_cases.yaml index 8e174172..9e61d031 100644 --- a/tests/test_cases.yaml +++ b/tests/test_cases.yaml @@ -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