diff --git a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc index c5929be2..2c3a6b79 100644 --- a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc +++ b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc @@ -77,22 +77,29 @@ void generator::generate_return(const message &m, std::ostream &ostr) const } } -void generator::generate_activity(const activity &a, std::ostream &ostr) const +void generator::generate_activity(const activity &a, std::ostream &ostr, + std::set &visited) const { for (const auto &m : a.messages) { + visited.emplace(m.from); + const auto &to = m_model.get_participant(m.to); if (!to) continue; LOG_DBG("Generating message {} --> {}", m.from, m.to); + generate_call(m, ostr); ostr << "activate " << to.value().alias() << std::endl; if (m_model.sequences.find(m.to) != m_model.sequences.end()) { - LOG_DBG("Creating activity {} --> {} - missing sequence {}", m.from, - m.to, m.to); - generate_activity(m_model.sequences[m.to], ostr); + if (visited.find(m.to) == + visited.end()) { // break infinite recursion on recursive calls + LOG_DBG("Creating activity {} --> {} - missing sequence {}", + m.from, m.to, m.to); + generate_activity(m_model.sequences[m.to], ostr, visited); + } } else LOG_DBG("Skipping activity {} --> {} - missing sequence {}", m.from, @@ -165,7 +172,7 @@ void generator::generate(std::ostream &ostr) const for (const auto &sf : m_config.start_from()) { if (sf.location_type == source_location::location_t::function) { - std::int64_t start_from; + common::model::diagram_element::id_t start_from; for (const auto &[k, v] : m_model.sequences) { const auto &caller = *m_model.participants.at(v.from); std::string vfrom = caller.full_name(false); @@ -175,7 +182,9 @@ void generator::generate(std::ostream &ostr) const break; } } - generate_activity(m_model.sequences[start_from], ostr); + std::set visited_participants; + generate_activity( + m_model.sequences[start_from], ostr, visited_participants); } else { // TODO: Add support for other sequence start location types diff --git a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.h b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.h index 4621682c..fc3c82b9 100644 --- a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.h +++ b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.h @@ -55,7 +55,8 @@ public: void generate_participant(std::ostream &ostr, common::id_t id) const; void generate_activity(const clanguml::sequence_diagram::model::activity &a, - std::ostream &ostr) const; + std::ostream &ostr, + std::set &visited) const; void generate(std::ostream &ostr) const; diff --git a/tests/t20011/.clang-uml b/tests/t20011/.clang-uml new file mode 100644 index 00000000..9a2a29be --- /dev/null +++ b/tests/t20011/.clang-uml @@ -0,0 +1,14 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t20011_sequence: + type: sequence + glob: + - ../../tests/t20011/t20011.cc + include: + namespaces: + - clanguml::t20011 + using_namespace: + - clanguml::t20011 + start_from: + - function: "clanguml::t20011::tmain()" \ No newline at end of file diff --git a/tests/t20011/t20011.cc b/tests/t20011/t20011.cc new file mode 100644 index 00000000..e4ba19c7 --- /dev/null +++ b/tests/t20011/t20011.cc @@ -0,0 +1,31 @@ +namespace clanguml { +namespace t20011 { + +struct A { + void a(int i = 10) + { + if (i > 0) + a(i - 1); + } + + void b(int i = 10) { c(i); } + void c(int i) { d(i); } + void d(int i) + { + if (i > 0) + b(i - 1); + else + a(); + } +}; + +void tmain() +{ + A a; + + a.a(); + + a.b(); +} +} +} \ No newline at end of file diff --git a/tests/t20011/test_case.h b/tests/t20011/test_case.h new file mode 100644 index 00000000..55638487 --- /dev/null +++ b/tests/t20011/test_case.h @@ -0,0 +1,48 @@ +/** + * tests/t20011/test_case.h + * + * Copyright (c) 2021-2022 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("t20011", "[test-case][sequence]") +{ + auto [config, db] = load_config("t20011"); + + auto diagram = config.diagrams["t20011_sequence"]; + + REQUIRE(diagram->name == "t20011_sequence"); + + auto model = generate_sequence_diagram(*db, diagram); + + REQUIRE(model->name() == "t20011_sequence"); + + auto puml = generate_sequence_puml(diagram, *model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + // Check if all calls exist + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("A"), "a")); + REQUIRE_THAT(puml, HasCall(_A("A"), _A("A"), "a")); + + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("A"), "b")); + REQUIRE_THAT(puml, HasCall(_A("A"), _A("A"), "c")); + REQUIRE_THAT(puml, HasCall(_A("A"), _A("A"), "d")); + REQUIRE_THAT(puml, HasCall(_A("A"), _A("A"), "b")); + + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); +} \ No newline at end of file diff --git a/tests/test_cases.cc b/tests/test_cases.cc index 96a4d2f6..287a9539 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -257,6 +257,7 @@ using namespace clanguml::test::matchers; #include "t20008/test_case.h" #include "t20009/test_case.h" #include "t20010/test_case.h" +#include "t20011/test_case.h" /// /// Package diagram tests diff --git a/tests/test_cases.yaml b/tests/test_cases.yaml index 7b1c8a53..5a46f621 100644 --- a/tests/test_cases.yaml +++ b/tests/test_cases.yaml @@ -178,6 +178,9 @@ test_cases: - name: t20010 title: Container sequence diagram test case description: + - name: t20011 + title: Recursive calls sequence diagram test case + description: Package diagrams: - name: t30001 title: Basic package diagram test case